Compare commits

..

No commits in common. "master" and "1.27.0" have entirely different histories.

12 changed files with 50 additions and 373 deletions

View file

@ -1,5 +0,0 @@
# -*- yaml -*-
CompileFlags:
Add:
- -Wno-c2y-extensions

View file

@ -1,6 +1,5 @@
# Changelog
* [Unreleased](#unreleased)
* [1.27.0](#1-27-0)
* [1.26.1](#1-26-1)
* [1.26.0](#1-26-0)
@ -70,56 +69,6 @@
* [1.2.0](#1-2-0)
## Unreleased
### Added
### Changed
* Do not allow codepoints to be merged into grapheme clusters directly
after a cursor move ([#2383][2383]).
* Dracula theme updated to latest official, and light theme alucard
added.
* Sixels: pan/pad clamped to 5 ([#2371][2371]).
[2383]: https://codeberg.org/dnkl/foot/issues/2383
[2371]: https://codeberg.org/dnkl/foot/issues/2371
### Deprecated
### Removed
### Fixed
* Out-of-bounds read when parsing URIs with malformed %-encoded
content ([#2353][2353]).
* DECCRA not clamping or verifying the destination rectangle
([#2352][2352]).
* Empty selection clearing the clipboard ([#2327][2327]).
* Require xkbcommon >= 1.6.0. This has been the case for a while, due
to our use of `XKB_KEYSYM_MAX`. Now it is formalized in
`meson.build` ([#2379][2379]).
* Block selection area incorrectly updated when selecting back
across the starting column.
* Passing a very large value as CHT/CBT argument hangs the terminal
([#2360][2360]).
* Sixel: crash when using a shared palette and gamma-correct blending
has been enabled, or foot is using 10-bit surface ([#2370][2370]).
* Kitty text-size protocol: fix crash when text is zero-length
([#2364][2364]).
* Escape quotes in file names being DnD:ed on the command line
([#2363][2363]).
[2353]: https://codeberg.org/dnkl/foot/issues/2353
[2352]: https://codeberg.org/dnkl/foot/issues/2352
[2327]: https://codeberg.org/dnkl/foot/issues/2327
[2379]: https://codeberg.org/dnkl/foot/issues/2379
[2360]: https://codeberg.org/dnkl/foot/issues/2360
[2370]: https://codeberg.org/dnkl/foot/issues/2370
[2364]: https://codeberg.org/dnkl/foot/issues/2364
### Security
### Contributors
## 1.27.0
### Added

56
csi.c
View file

@ -774,7 +774,7 @@ params_to_rectangular_area(const struct terminal *term, int first_idx,
int rel_bottom = vt_param_get(term, first_idx + 2, term->rows) - 1;
*right = min(vt_param_get(term, first_idx + 3, term->cols) - 1, term->cols - 1);
if (unlikely(rel_top > rel_bottom || *left > *right))
if (rel_top > rel_bottom || *left > *right)
return false;
*top = term_row_rel_to_abs(term, rel_top);
@ -1171,40 +1171,37 @@ csi_dispatch(struct terminal *term, uint8_t final)
case 'I': {
/* CHT - Tab Forward (param is number of tab stops to move through) */
int count = vt_param_get(term, 0, 1);
int new_col = term->grid->cursor.point.col;
tll_foreach(term->tab_stops, it) {
if (it->item > new_col) {
if (--count < 0) {
for (int i = 0; i < vt_param_get(term, 0, 1); i++) {
int new_col = term->cols - 1;
tll_foreach(term->tab_stops, it) {
if (it->item > term->grid->cursor.point.col) {
new_col = it->item;
break;
}
new_col = it->item;
}
}
xassert(new_col >= term->grid->cursor.point.col);
xassert(new_col >= term->grid->cursor.point.col);
bool lcf = term->grid->cursor.lcf;
term_cursor_right(term, new_col - term->grid->cursor.point.col);
term->grid->cursor.lcf = lcf;
bool lcf = term->grid->cursor.lcf;
term_cursor_right(term, new_col - term->grid->cursor.point.col);
term->grid->cursor.lcf = lcf;
}
break;
}
case 'Z': {
case 'Z':
/* CBT - Back tab (param is number of tab stops to move back through) */
int count = vt_param_get(term, 0, 1);
int new_col = term->grid->cursor.point.col;
tll_rforeach(term->tab_stops, it) {
if (it->item < new_col) {
if (--count < 0) {
for (int i = 0; i < vt_param_get(term, 0, 1); i++) {
int new_col = 0;
tll_rforeach(term->tab_stops, it) {
if (it->item < term->grid->cursor.point.col) {
new_col = it->item;
break;
}
new_col = it->item;
}
xassert(term->grid->cursor.point.col >= new_col);
term_cursor_left(term, term->grid->cursor.point.col - new_col);
}
xassert(term->grid->cursor.point.col >= new_col);
term_cursor_left(term, term->grid->cursor.point.col - new_col);
break;
}
case 'h':
case 'l': {
@ -2008,8 +2005,9 @@ csi_dispatch(struct terminal *term, uint8_t final)
}
int src_page = vt_param_get(term, 4, 1);
int dst_rel_top = vt_param_get(term, 5, 1) - 1;
int dst_left = min(vt_param_get(term, 6, 1) - 1, term->cols - 1);
int dst_left = vt_param_get(term, 6, 1) - 1;
int dst_page = vt_param_get(term, 7, 1);
if (unlikely(src_page != 1 || dst_page != 1)) {
@ -2023,18 +2021,6 @@ csi_dispatch(struct terminal *term, uint8_t final)
int dst_top = term_row_rel_to_abs(term, dst_rel_top);
int dst_bottom = term_row_rel_to_abs(term, dst_rel_bottom);
if (unlikely(dst_left > dst_right || dst_top > dst_bottom))
break;
/*
* src validated by params_to_rectangular_area()
* dst validated above
*/
xassert(src_bottom - src_top >= 0);
xassert(dst_bottom - dst_top >= 0);
xassert(src_right - src_left >= 0);
xassert(dst_right - dst_left >= 0);
/* Target area outside the screen is clipped */
const size_t row_count = min(src_bottom - src_top,
dst_bottom - dst_top) + 1;

View file

@ -151,7 +151,7 @@ wayland_protocols = dependency('wayland-protocols', version: '>=1.41',
default_options: ['tests=false'])
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
xkb = dependency('xkbcommon', version: '>=1.6.0')
xkb = dependency('xkbcommon', version: '>=1.0.0')
fontconfig = dependency('fontconfig')
utf8proc = dependency('libutf8proc', required: get_option('grapheme-clustering'))

3
osc.c
View file

@ -1149,9 +1149,6 @@ kitty_text_size(struct terminal *term, char *string)
*text = '\0';
text++;
if (text[0] == '\0')
return;
char32_t *wchars = ambstoc32(text);
if (wchars == NULL)
return;

View file

@ -1145,7 +1145,7 @@ selection_update(struct terminal *term, int col, int row)
struct coord *pivot_end = &term->selection.pivot.end;
if (term->selection.kind == SELECTION_BLOCK) {
if (new_end.col >= pivot_start->col)
if (new_end.col > pivot_start->col)
new_direction = SELECTION_RIGHT;
else
new_direction = SELECTION_LEFT;
@ -1948,9 +1948,6 @@ static const struct zwp_primary_selection_source_v1_listener primary_selection_s
bool
text_to_clipboard(struct seat *seat, struct terminal *term, char *text, uint32_t serial)
{
if (text == NULL || text[0] == '\0')
return false;
xassert(serial != 0);
struct wl_clipboard *clipboard = &seat->clipboard;
@ -2098,16 +2095,7 @@ decode_one_uri(struct clipboard_receive *ctx, char *uri, size_t len)
if (ctx->quote_paths)
ctx->cb("'", 1, ctx->user);
char *path_remaining = path;
for (char *next_quote = strchr(path_remaining, '\'');
next_quote != NULL;
path_remaining = next_quote + 1,
next_quote = strchr(path_remaining, '\''))
{
ctx->cb(path_remaining, next_quote - path_remaining, ctx->user);
ctx->cb("\\'", 2, ctx->user);
}
ctx->cb(path_remaining, strlen(path_remaining), ctx->user);
ctx->cb(path, strlen(path), ctx->user);
if (ctx->quote_paths)
ctx->cb("'", 1, ctx->user);
@ -2430,9 +2418,6 @@ selection_from_clipboard(struct seat *seat, struct terminal *term, uint32_t seri
bool
text_to_primary(struct seat *seat, struct terminal *term, char *text, uint32_t serial)
{
if (text == NULL || text[0] == '\0')
return false;
if (term->wl->primary_selection_device_manager == NULL)
return false;

12
sixel.c
View file

@ -169,10 +169,10 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
if (term->sixel.linear_blending || term->sixel.use_10bit) {
for (size_t i = 0; i < active_palette_entries; i++) {
uint8_t r = (term->sixel.shared_palette[i] >> 16) & 0xff;
uint8_t g = (term->sixel.shared_palette[i] >> 8) & 0xff;
uint8_t b = (term->sixel.shared_palette[i] >> 0) & 0xff;
term->sixel.shared_palette[i] = color_decode_srgb(term, r, g, b);
uint8_t r = (term->sixel.private_palette[i] >> 16) & 0xff;
uint8_t g = (term->sixel.private_palette[i] >> 8) & 0xff;
uint8_t b = (term->sixel.private_palette[i] >> 0) & 0xff;
term->sixel.private_palette[i] = color_decode_srgb(term, r, g, b);
}
}
} else {
@ -1888,8 +1888,8 @@ decgra(struct terminal *term, uint8_t c)
unsigned ph = nparams > 2 ? term->sixel.params[2] : 0;
unsigned pv = nparams > 3 ? term->sixel.params[3] : 0;
pan = pan > 0 ? min(pan, 5) : 1;
pad = pad > 0 ? min(pad, 5) : 1;
pan = pan > 0 ? pan : 1;
pad = pad > 0 ? pad : 1;
if (likely(term->sixel.image.width == 0 &&
term->sixel.image.height == 0))

View file

@ -4086,10 +4086,6 @@ term_print(struct terminal *term, char32_t wc, int width, bool insert_mode_disab
xassert(!grid->cursor.lcf);
grid->cursor.point.col = col;
#if defined(FOOT_GRAPHEME_CLUSTERING)
term->vt.codepoint_merging_ok = true;
#endif
}
static void
@ -4130,9 +4126,6 @@ ascii_printer_fast(struct terminal *term, char32_t wc)
xassert(!grid->cursor.lcf);
grid->cursor.point.col = col;
#if defined(FOOT_GRAPHEME_CLUSTERING)
term->vt.codepoint_merging_ok = true;
#endif
if (unlikely(row->extra != NULL)) {
grid_row_uri_range_erase(row, uri_start, uri_start);
@ -4217,11 +4210,7 @@ term_process_and_print_non_ascii(struct terminal *term, char32_t wc)
{
int width = c32width(wc);
bool insert_mode_disable = false;
const bool grapheme_clustering = term->grapheme_shaping
#if defined(FOOT_GRAPHEME_CLUSTERING)
&& term->vt.codepoint_merging_ok
#endif
;
const bool grapheme_clustering = term->grapheme_shaping;
#if !defined(FOOT_GRAPHEME_CLUSTERING)
xassert(!grapheme_clustering);

View file

@ -264,7 +264,6 @@ struct vt {
char32_t last_printed;
#if defined(FOOT_GRAPHEME_CLUSTERING)
utf8proc_int32_t grapheme_state;
bool codepoint_merging_ok;
#endif
char32_t utf8;
struct {
@ -995,6 +994,5 @@ static inline void term_reset_grapheme_state(struct terminal *term)
{
#if defined(FOOT_GRAPHEME_CLUSTERING)
term->vt.grapheme_state = 0;
term->vt.codepoint_merging_ok = false;
#endif
}

View file

@ -1,26 +0,0 @@
# -*- conf -*-
# theme: Ayu Dark
# upstream: https://github.com/ayu-theme/ayu-colors/blob/master/themes/dark.yaml
[colors-dark]
background=0d1017
foreground=bfbdb6
cursor=0d1017 ffb454
regular0=0a0000 # black
regular1=d75b63 # red
regular2=94c230 # green
regular3=e79e3a # yellow
regular4=40abe7 # blue
regular5=bc90e7 # magenta
regular6=7ecfb5 # cyan
regular7=ffffff # white
bright0=0a0000 # bright black
bright1=f07178 # bright red
bright2=aad94c # bright green
bright3=ffb454 # bright yellow
bright4=59c2ff # bright blue
bright5=d2a6ff # bright magenta
bright6=95e6cb # bright cyan
bright7=ffffff # bright white

View file

@ -1,43 +1,23 @@
# -*- conf -*-
# Dracula / Alucard
# Source: https://github.com/dracula/foot
# Dracula
[colors-dark]
cursor=282a36 f8f8f2
foreground=f8f8f2
background=282a36
regular0=21222c # black
regular1=ff5555 # red
regular2=50fa7b # green
regular3=f1fa8c # yellow
regular4=bd93f9 # blue
regular5=ff79c6 # magenta
regular6=8be9fd # cyan
regular7=f8f8f2 # white
bright0=6272a4 # bright black
bright1=ff6e6e # bright red
bright2=69ff94 # bright green
bright3=ffffa5 # bright yellow
bright4=d6acff # bright blue
bright5=ff92df # bright magenta
bright6=a4ffff # bright cyan
bright7=ffffff # bright white
[colors-light]
foreground=1f1f1f
background=fffbeb
regular0=fffbeb # white
regular1=cb3a2a # red
regular2=14710a # green
regular3=846e15 # yellow
regular4=644ac9 # blue
regular5=a3144d # magenta
regular6=036a96 # cyan
regular7=1f1f1f # white
bright0=6c664b # bright black
bright1=d74c3d # bright red
bright2=198d0c # bright green
bright3=9e841a # bright yellow
bright4=7862d0 # bright blue
bright5=bf185a # bright magenta
bright6=047fb4 # bright cyan
bright7=2c2b31 # bright white
regular0=000000 # black
regular1=ff5555 # red
regular2=50fa7b # green
regular3=f1fa8c # yellow
regular4=bd93f9 # blue
regular5=ff79c6 # magenta
regular6=8be9fd # cyan
regular7=bfbfbf # white
bright0=4d4d4d # bright black
bright1=ff6e67 # bright red
bright2=5af78e # bright green
bright3=f4f99d # bright yellow
bright4=caa9fa # bright blue
bright5=ff92d0 # bright magenta
bright6=9aedfe # bright cyan
bright7=e6e6e6 # bright white

178
uri.c
View file

@ -195,9 +195,7 @@ uri_parse(const char *uri, size_t len,
encoded_len -= prefix_len;
decoded_len += prefix_len;
if (encoded_len >= 3 &&
hex2nibble(next[1]) <= 15 && hex2nibble(next[2]) <= 15)
{
if (hex2nibble(next[1]) <= 15 && hex2nibble(next[2]) <= 15) {
*p++ = hex2nibble(next[1]) << 4 | hex2nibble(next[2]);
decoded_len++;
encoded_len -= 3;
@ -271,177 +269,3 @@ hostname_is_localhost(const char *hostname)
streq(hostname, "localhost") ||
streq(hostname, this_host)));
}
UNITTEST
{
/* Valid URI, all components */
const char uri[] = "http://john:wick@www.foobar.com:80/this/is/the%20path?query1=abc&query2=def#fragment";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(streq(user, "john")); free(user);
xassert(streq(password, "wick")); free(password);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path")); free(path);
xassert(streq(query, "query1=abc&query2=def")); free(query);
xassert(streq(fragment, "fragment")); free(fragment);
}
UNITTEST
{
/* Valid URI, all components. Using file scheme, so query+fragment is treated as part of the path */
const char uri[] = "file://john:wick@www.foobar.com:80/this/is/the%20path?query1=abc&query2=def#fragment";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "file")); free(scheme);
xassert(streq(user, "john")); free(user);
xassert(streq(password, "wick")); free(password);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path?query1=abc&query2=def#fragment")); free(path);
xassert(query == NULL);
xassert(fragment == NULL);
}
UNITTEST
{
/* Test a valid URI containing all components, except password */
const char uri[] = "http://john@www.foobar.com:80/this/is/the%20path?query1=abc&query2=def#fragment";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(streq(user, "john")); free(user);
xassert(password == NULL);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path")); free(path);
xassert(streq(query, "query1=abc&query2=def")); free(query);
xassert(streq(fragment, "fragment")); free(fragment);
}
UNITTEST
{
/* Valid URI, all components, except user+password */
const char uri[] = "http://www.foobar.com:80/this/is/the%20path?query1=abc&query2=def#fragment";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(user == NULL);
xassert(password == NULL);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path")); free(path);
xassert(streq(query, "query1=abc&query2=def")); free(query);
xassert(streq(fragment, "fragment")); free(fragment);
}
UNITTEST
{
/* Valid URI, fragment, no query */
const char uri[] = "http://www.foobar.com:80/this/is/the%20path#fragment";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(user == NULL);
xassert(password == NULL);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path")); free(path);
xassert(query == NULL);
xassert(streq(fragment, "fragment")); free(fragment);
}
UNITTEST
{
/* Valid URI, query, no fragment */
const char uri[] = "http://www.foobar.com:80/this/is/the%20path?query1=abc&query2=def";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(user == NULL);
xassert(password == NULL);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path")); free(path);
xassert(streq(query, "query1=abc&query2=def")); free(query);
xassert(fragment == NULL);
}
UNITTEST
{
/* Malformed URI, fragment before query (treated as part of path instead) */
const char uri[] = "http://www.foobar.com:80/this/is/the%20path#fragment?query1=abc&query2=def";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(user == NULL);
xassert(password == NULL);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path#fragment?query1=abc&query2=def")); free(path);
xassert(query == NULL);
xassert(fragment == NULL);
}
UNITTEST
{
/* Malformed URI, trailing '%' */
const char uri[] = "file:///%ABNOT-PART-OF-INPUT";
char *path;
uri_parse(uri, 9, NULL, NULL, NULL, NULL, NULL, &path, NULL, NULL);
xassert(streq(path, "/%")); free(path);
}
UNITTEST
{
/* Malformed URI, trailing '%2' */
const char uri[] = "file:///%2ANOT-PART-OF-INPUT";
char *path;
uri_parse(uri, 10, NULL, NULL, NULL, NULL, NULL, &path, NULL, NULL);
xassert(streq(path, "/%2")); free(path);
}
UNITTEST
{
/* Malformed URI, trailing '%ag' */
const char uri[] = "file:///%ag";
char *path;
uri_parse(uri, sizeof(uri) - 1, NULL, NULL, NULL, NULL, NULL, &path, NULL, NULL);
xassert(streq(path, "/%ag")); free(path);
}