From d58290ea1213725ddf0f76be129fc5f680d9ccd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 28 Jun 2022 20:57:48 +0200 Subject: [PATCH 01/81] =?UTF-8?q?input:=20don=E2=80=99t=20ignore=20keyboar?= =?UTF-8?q?d=20enter/leave=20events=20when=20there=E2=80=99s=20no=20keymap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compositor _usually_ sends the keymap event *before* the enter event. But not always. Not (yet) having a keymap is not a reason to ignore the enter event (now, on the other hand, getting a key press/release event without a keymap is a compositor bug). Closes #1097 --- CHANGELOG.md | 3 +++ input.c | 6 ------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dec74bc1..a052d744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,9 +80,12 @@ * Workaround for buggy compositors (e.g. some versions of GNOME) allowing drag-and-drops even though foot has reported it does not support the offered mime-types ([#1092][1092]). +* Keyboard enter/leave events being ignored if there is no keymap + ([#1097][1097]). [1055]: https://codeberg.org/dnkl/foot/issues/1055 [1092]: https://codeberg.org/dnkl/foot/issues/1092 +[1097]: https://codeberg.org/dnkl/foot/issues/1097 ### Security diff --git a/input.c b/input.c index fca46050..d4598cf6 100644 --- a/input.c +++ b/input.c @@ -591,9 +591,6 @@ keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, LOG_DBG("%s: keyboard_enter: keyboard=%p, serial=%u, surface=%p", seat->name, (void *)wl_keyboard, serial, (void *)surface); - if (seat->kbd.xkb == NULL) - return; - term_kbd_focus_in(term); seat->kbd_focus = term; seat->kbd.serial = serial; @@ -653,9 +650,6 @@ keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, LOG_DBG("keyboard_leave: keyboard=%p, serial=%u, surface=%p", (void *)wl_keyboard, serial, (void *)surface); - if (seat->kbd.xkb == NULL) - return; - xassert( seat->kbd_focus == NULL || surface == NULL || /* Seen on Sway 1.2 */ From 37f094280b06cf207b5720fa1a36ff7199225e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jun 2022 19:37:01 +0200 Subject: [PATCH 02/81] =?UTF-8?q?Revert=20"grid:=20invert=20the=20default?= =?UTF-8?q?=20value=20of=20=E2=80=98linebreak=E2=80=99,=20from=20false=20t?= =?UTF-8?q?o=20true"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit cdd46cdf85b1087ac7465c646bb05078f1bbe85b. --- grid.c | 14 +++----------- terminal.c | 4 +++- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/grid.c b/grid.c index a21f24a2..18ca1457 100644 --- a/grid.c +++ b/grid.c @@ -318,7 +318,7 @@ grid_row_alloc(int cols, bool initialize) { struct row *row = xmalloc(sizeof(*row)); row->dirty = false; - row->linebreak = true; + row->linebreak = false; row->extra = NULL; row->prompt_marker = false; @@ -538,7 +538,7 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, } else { /* Scrollback is full, need to re-use a row */ grid_row_reset_extra(new_row); - new_row->linebreak = true; + new_row->linebreak = false; new_row->prompt_marker = false; tll_foreach(old_grid->sixel_images, it) { @@ -878,14 +878,6 @@ grid_resize_and_reflow( &new_row->cells[new_col_idx], &old_row->cells[from], amount * sizeof(struct cell)); - /* - * We’ve “printed” to this line - reset linebreak. - * - * If the old line ends with a hard linebreak, we’ll - * set linebreak=true on the last new row we print to. - */ - new_row->linebreak = false; - count -= amount; from += amount; new_col_idx += amount; @@ -944,7 +936,7 @@ grid_resize_and_reflow( } - if (old_row->linebreak && col_count > 0) { + if (old_row->linebreak) { /* Erase the remaining cells */ memset(&new_row->cells[new_col_idx], 0, (new_cols - new_col_idx) * sizeof(new_row->cells[0])); diff --git a/terminal.c b/terminal.c index c449aa0e..dd84b8cf 100644 --- a/terminal.c +++ b/terminal.c @@ -1811,7 +1811,7 @@ static inline void erase_line(struct terminal *term, struct row *row) { erase_cell_range(term, row, 0, term->cols - 1); - row->linebreak = true; + row->linebreak = false; row->prompt_marker = false; } @@ -3298,6 +3298,7 @@ term_print(struct terminal *term, char32_t wc, int width) /* *Must* get current cell *after* linewrap+insert */ struct row *row = grid->cur_row; row->dirty = true; + row->linebreak = true; struct cell *cell = &row->cells[col]; cell->wc = term->vt.last_printed = wc; @@ -3357,6 +3358,7 @@ ascii_printer_fast(struct terminal *term, char32_t wc) struct row *row = grid->cur_row; row->dirty = true; + row->linebreak = true; struct cell *cell = &row->cells[col]; cell->wc = term->vt.last_printed = wc; From 0c60bb3f295936dff04f56d28ab5dcb4db898a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jun 2022 19:47:18 +0200 Subject: [PATCH 03/81] grid: reflow: require col count > 0 when skipping line truncation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When reflowing the grid, we truncate lines with a hard linebreak after the last non-empty cell. This way we don’t reflow trailing empty cells to a new line when resizing the window to a smaller size. However, “logical” lines (i.e. those without a hard linebreak) are *not* truncated. This is to ensure we don’t trim empty cells in the middle of a logical line spanning multiple physical lines. Since newly allocated rows are initialized with linebreak=false, we need to ensure _those_ are still truncated - otherwise all that empty space under the current prompt will be reflowed. Note that this is a temporary workaround. The correct solution, I think, is to track whether a line has been printed to or not, and simply ignore (not reflow) lines that haven’t yet been touched. --- grid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grid.c b/grid.c index 18ca1457..2790d16b 100644 --- a/grid.c +++ b/grid.c @@ -740,7 +740,7 @@ grid_resize_and_reflow( } } - if (!old_row->linebreak /*&& col_count > 0*/) { + if (!old_row->linebreak && col_count > 0) { /* Don’t truncate logical lines */ col_count = old_cols; } From 87e4004960265494fcab21c71fff48b4f8b0af33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 22 Jul 2022 10:44:33 +0200 Subject: [PATCH 04/81] =?UTF-8?q?csi:=20clamp=20color=20index=20for=20?= =?UTF-8?q?=E2=80=98CSI=2038/48=20;=205=20;=20idx=20m=E2=80=99=20sequences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Indexed color values are stored in the cell attributes as color indices (into the 256-color table). However, the index from the CSI was not validated in any way, meaning you can do something like this: echo -e ‘\e[38:5:1024m CRASH \e[m’ and foot will crash on an out-of-bounds access. Fix by clamping the color index. Closes #1111 --- CHANGELOG.md | 4 ++++ csi.c | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a052d744..7e7aa4a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,10 +82,14 @@ support the offered mime-types ([#1092][1092]). * Keyboard enter/leave events being ignored if there is no keymap ([#1097][1097]). +* Crash when application emitted an invalid `CSI 38;5;m`, `CSI + 38:5:m`, `CSI 48;5;m` or `CSI 48:5:m` sequence + ([#1111][1111]). [1055]: https://codeberg.org/dnkl/foot/issues/1055 [1092]: https://codeberg.org/dnkl/foot/issues/1092 [1097]: https://codeberg.org/dnkl/foot/issues/1097 +[1111]: https://codeberg.org/dnkl/foot/issues/1111 ### Security diff --git a/csi.c b/csi.c index 57cae6b3..659839f0 100644 --- a/csi.c +++ b/csi.c @@ -128,7 +128,8 @@ csi_sgr(struct terminal *term) term->vt.params.v[i + 1].value == 5) { src = COLOR_BASE256; - color = term->vt.params.v[i + 2].value; + color = min(term->vt.params.v[i + 2].value, + ALEN(term->colors.table) - 1); i += 2; } @@ -149,7 +150,8 @@ csi_sgr(struct terminal *term) term->vt.params.v[i].sub.value[0] == 5) { src = COLOR_BASE256; - color = term->vt.params.v[i].sub.value[1]; + color = min(term->vt.params.v[i].sub.value[1], + ALEN(term->colors.table) - 1); } /* From 24c2d568042c6fb0533071aaf72b64405ca2c324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 28 Jul 2022 18:55:34 +0200 Subject: [PATCH 05/81] =?UTF-8?q?render:=20it=E2=80=99s=20unlikely()=20the?= =?UTF-8?q?=20current=20cell=20is=20where=20the=20cursor=20is?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 3b281a89..0dd251ae 100644 --- a/render.c +++ b/render.c @@ -699,7 +699,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, mtx_unlock(&term->render.workers.lock); } - if (has_cursor && term->cursor_style == CURSOR_BLOCK && term->kbd_focus) + if (unlikely(has_cursor && term->cursor_style == CURSOR_BLOCK && term->kbd_focus)) draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols); if (cell->wc == 0 || cell->wc >= CELL_SPACER || cell->wc == U'\t' || From 4abf46955f259ed392c5c7fbfbb931cbc7cc2b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Jul 2022 18:19:34 +0200 Subject: [PATCH 06/81] keymap: change alt+escape to emit \E\E instead of a CSI 27 sequence Closes #1105 --- CHANGELOG.md | 3 +++ keymap.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e7aa4a4..ea0f5803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,8 +64,11 @@ mode ([#1084][1084]). * NUL is now stripped when pasting in non-bracketed mode ([#1084][1084]). +* `alt`+`escape` now emits `\E\E` instead of a `CSI 27` sequence + ([#1105][1105]). [1084]: https://codeberg.org/dnkl/foot/issues/1084 +[1105]: https://codeberg.org/dnkl/foot/issues/1105 ### Deprecated diff --git a/keymap.h b/keymap.h index 9793d882..79d4b8b3 100644 --- a/keymap.h +++ b/keymap.h @@ -24,7 +24,7 @@ struct key_data { static const struct key_data key_escape[] = { {MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;2;27~"}, - {MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;3;27~"}, + {MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\033"}, {MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;4;27~"}, {MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;5;27~"}, {MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;6;27~"}, From 801970aa332da5786f91d2f9410d4ed9f440c6ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Jul 2022 18:44:29 +0200 Subject: [PATCH 07/81] =?UTF-8?q?input:=20kitty:=20always=20treat=20compos?= =?UTF-8?q?ed=20characters=20as=20=E2=80=98printable=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Certain dead key combinations results different escape sequences in foot, compared to kitty, when the kitty keyboard protocol is used. if (composed && is_text) key = utf32; else { key = xkb_keysym_to_utf32(sym_to_use); if (key == 0) return false; /* The *shifted* key. May be the same as the unshifted * key - if so, this is filtered out below, when * emitting the CSI */ alternate = xkb_keysym_to_utf32(sym); } If is_text=false, we’ll fall through to the non-composed logic. is_text is set to true if the character is printable *and* there aren’t any non-consumed modifiers enabled. shift+space is one example where shift is *not* consumed (typically - may be layouts where it is). As a result, pressing ", followed by shift+space with the international english keyboard layout (where " is a dead key) results in different sequences in foot and kitty. This patch fixes this by always treating composed characters as printable. Closes #1120 --- CHANGELOG.md | 4 ++++ input.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0f5803..29eaf084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,11 +88,15 @@ * Crash when application emitted an invalid `CSI 38;5;m`, `CSI 38:5:m`, `CSI 48;5;m` or `CSI 48:5:m` sequence ([#1111][1111]). +* Certain dead-key combinations resulting in different escape + sequences compared to kitty, when the kitty keyboard protocol is + used ([#1120][1120]). [1055]: https://codeberg.org/dnkl/foot/issues/1055 [1092]: https://codeberg.org/dnkl/foot/issues/1092 [1097]: https://codeberg.org/dnkl/foot/issues/1097 [1111]: https://codeberg.org/dnkl/foot/issues/1111 +[1120]: https://codeberg.org/dnkl/foot/issues/1120 ### Security diff --git a/input.c b/input.c index d4598cf6..e0f21c6e 100644 --- a/input.c +++ b/input.c @@ -1239,7 +1239,7 @@ emit_escapes: ? ctx->level0_syms.syms[0] : sym; - if (composed && is_text) + if (composed) key = utf32; else { key = xkb_keysym_to_utf32(sym_to_use); From 4873004c379f9f06babd65ffc65ba4f99fa62906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 27 Jul 2022 19:13:39 +0200 Subject: [PATCH 08/81] test: config: test colors.{jump-labels,scrollback-indicator} --- tests/test-config.c | 54 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/tests/test-config.c b/tests/test-config.c index 874f5b05..5a7cda63 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -401,6 +401,52 @@ test_color(struct context *ctx, bool (*parse_fun)(struct context *ctx), } } +static void +test_two_colors(struct context *ctx, bool (*parse_fun)(struct context *ctx), + const char *key, bool alpha_allowed, + uint32_t *ptr1, uint32_t *ptr2) +{ + ctx->key = key; + + const struct { + const char *option_string; + uint32_t color1; + uint32_t color2; + bool invalid; + } input[] = { + {"000000 000000", 0, 0}, + + /* No alpha */ + {"999999 888888", 0x999999, 0x888888}, + {"ffffff aaaaaa", 0xffffff, 0xaaaaaa}, + + /* Both colors have alpha component */ + {"ffffffff 00000000", 0xffffffff, 0x00000000, !alpha_allowed}, + {"aabbccdd, ee112233", 0xaabbccdd, 0xee112233, !alpha_allowed}, + + /* Only one color has alpha component */ + {"ffffffff 112233", 0xffffffff, 0x112233, !alpha_allowed}, + {"ffffff ff112233", 0x00ffffff, 0xff112233, !alpha_allowed}, + + {"unittest-invalid-color", 0, 0, true}, + }; + + for (size_t i = 0; i < ALEN(input); i++) { + ctx->value = input[i].option_string; + if (input[i].invalid) { + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: did not fail to parse as expected", + ctx->section, ctx->key, ctx->value); + } + } else { + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s: failed to parse", + ctx->section, ctx->key, ctx->value); + } + } + } +} + static void test_section_main(void) { @@ -616,6 +662,12 @@ test_section_colors(void) test_color(&ctx, &parse_section_colors, "selection-foreground", false, &conf.colors.selection_fg); test_color(&ctx, &parse_section_colors, "selection-background", false, &conf.colors.selection_bg); test_color(&ctx, &parse_section_colors, "urls", false, &conf.colors.url); + test_two_colors(&ctx, &parse_section_colors, "jump-labels", false, + &conf.colors.jump_label.fg, + &conf.colors.jump_label.bg); + test_two_colors(&ctx, &parse_section_colors, "scrollback-indicator", false, + &conf.colors.scrollback_indicator.fg, + &conf.colors.scrollback_indicator.bg); for (size_t i = 0; i < 255; i++) { char key_name[4]; @@ -627,8 +679,6 @@ test_section_colors(void) test_invalid_key(&ctx, &parse_section_colors, "256"); /* TODO: alpha (float in range 0-1, converted to uint16_t) */ - /* TODO: jump-labels (two colors) */ - /* TODO: scrollback-indicator (two colors) */ config_free(&conf); } From d79a3b9350ba864464229a1c188e2f696fac5a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 27 Jul 2022 19:14:27 +0200 Subject: [PATCH 09/81] config: add colors.search-box-{no-,}match Closes #1112 --- CHANGELOG.md | 3 +++ config.c | 28 ++++++++++++++++++++++++++++ config.h | 14 ++++++++++++++ doc/foot.ini.5.scd | 10 ++++++++++ foot.ini | 6 ++++-- render.c | 25 ++++++++++++++++++++----- tests/test-config.c | 6 ++++++ 7 files changed, 85 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29eaf084..55961c88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,10 +50,13 @@ * Support for jumping to previous/next prompt (requires shell integration). By default bound to `ctrl`+`shift`+`z` and `ctrl`+`shift`+`x` respectively ([#30][30]). +* `colors.search-box-no-match` and `colors.search-box-match` options + to `foot.ini` ([#1112][1112]). [1058]: https://codeberg.org/dnkl/foot/issues/1058 [1070]: https://codeberg.org/dnkl/foot/issues/1070 [30]: https://codeberg.org/dnkl/foot/issues/30 +[1112]: https://codeberg.org/dnkl/foot/issues/1112 ### Changed diff --git a/config.c b/config.c index ce0e27b8..0e509995 100644 --- a/config.c +++ b/config.c @@ -1171,6 +1171,34 @@ parse_section_colors(struct context *ctx) return true; } + else if (strcmp(key, "search-box-no-match") == 0) { + if (!value_to_two_colors( + ctx, + &conf->colors.search_box.no_match.fg, + &conf->colors.search_box.no_match.bg, + false)) + { + return false; + } + + conf->colors.use_custom.search_box_no_match = true; + return true; + } + + else if (strcmp(key, "search-box-match") == 0) { + if (!value_to_two_colors( + ctx, + &conf->colors.search_box.match.fg, + &conf->colors.search_box.match.bg, + false)) + { + return false; + } + + conf->colors.use_custom.search_box_match = true; + return true; + } + else if (strcmp(key, "urls") == 0) { if (!value_to_color(ctx, &conf->colors.url, false)) return false; diff --git a/config.h b/config.h index de5d8a7b..70e182e3 100644 --- a/config.h +++ b/config.h @@ -217,11 +217,25 @@ struct config { uint32_t bg; } scrollback_indicator; + struct { + struct { + uint32_t fg; + uint32_t bg; + } no_match; + + struct { + uint32_t fg; + uint32_t bg; + } match; + } search_box; + struct { bool selection:1; bool jump_label:1; bool scrollback_indicator:1; bool url:1; + bool search_box_no_match:1; + bool search_box_match:1; uint8_t dim; } use_custom; } colors; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index bd551442..2e62a41f 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -566,6 +566,16 @@ can configure the background transparency with the _alpha_ option. (indicator itself) colors for the scrollback indicator. Default: _regular0 bright4_. +*search-box-no-match* + Two color values specifying the foreground (text) and background + colors for the scrollback search box, when there are no + matches. Default: _regular0 regular1_. + +*search-box-match* + Two color values specifying the foreground (text) and background + colors for the scrollback search box, when the search box is + either empty, or there are matches. Default: _regular0 regular3_. + *urls* Color to use for the underline used to highlight URLs in URL mode. Default: _regular3_. diff --git a/foot.ini b/foot.ini index 10eb56e8..e8ff1870 100644 --- a/foot.ini +++ b/foot.ini @@ -104,9 +104,11 @@ ## Misc colors # selection-foreground= # selection-background= -# jump-labels= +# jump-labels= # black-on-yellow +# scrollback-indicator= # black-on-bright-blue +# search-box-no-match= # black-on-red +# search-box-match= # black-on-yellow # urls= -# scrollback-indicator= [csd] # preferred=server diff --git a/render.c b/render.c index 0dd251ae..9333deb7 100644 --- a/render.c +++ b/render.c @@ -3046,10 +3046,20 @@ render_search_box(struct terminal *term) #define WINDOW_X(x) (margin + x) #define WINDOW_Y(y) (term->height - margin - height + y) - /* Background - yellow on empty/match, red on mismatch */ - pixman_color_t color = color_hex_to_pixman( - term->search.match_len == text_len - ? term->colors.table[3] : term->colors.table[1]); + const bool is_match = term->search.match_len == text_len; + const bool custom_colors = is_match + ? term->conf->colors.use_custom.search_box_match + : term->conf->colors.use_custom.search_box_no_match; + + /* Background - yellow on empty/match, red on mismatch (default) */ + const pixman_color_t color = color_hex_to_pixman( + is_match + ? (custom_colors + ? term->conf->colors.search_box.match.bg + : term->colors.table[3]) + : (custom_colors + ? term->conf->colors.search_box.no_match.bg + : term->colors.table[1])); pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix[0], &color, @@ -3065,7 +3075,12 @@ render_search_box(struct terminal *term) const int x_ofs = term->font_x_ofs; int x = x_left; int y = margin; - pixman_color_t fg = color_hex_to_pixman(term->colors.table[0]); + pixman_color_t fg = color_hex_to_pixman( + custom_colors + ? (is_match + ? term->conf->colors.search_box.match.fg + : term->conf->colors.search_box.no_match.fg) + : term->colors.table[0]); /* Move offset we start rendering at, to ensure the cursor is visible */ for (size_t i = 0, cell_idx = 0; i <= term->search.cursor; cell_idx += widths[i], i++) { diff --git a/tests/test-config.c b/tests/test-config.c index 5a7cda63..930be6bf 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -668,6 +668,12 @@ test_section_colors(void) test_two_colors(&ctx, &parse_section_colors, "scrollback-indicator", false, &conf.colors.scrollback_indicator.fg, &conf.colors.scrollback_indicator.bg); + test_two_colors(&ctx, &parse_section_colors, "search-box-no-match", false, + &conf.colors.search_box.no_match.fg, + &conf.colors.search_box.no_match.bg); + test_two_colors(&ctx, &parse_section_colors, "search-box-match", false, + &conf.colors.search_box.match.fg, + &conf.colors.search_box.match.bg); for (size_t i = 0; i < 255; i++) { char key_name[4]; From 632c4839cdb1c673bfb3903e1d43802bfdcbf03f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 28 Jul 2022 18:56:28 +0200 Subject: [PATCH 10/81] search: find_next(): handle trailing SPACER cells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure to increment match_end_col to account for trailing SPACER cells after a match. This fixes an issue where search matches weren’t highlighted correctly when the match *ends* with a double-width character. --- CHANGELOG.md | 2 ++ search.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55961c88..75950976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,8 @@ * Certain dead-key combinations resulting in different escape sequences compared to kitty, when the kitty keyboard protocol is used ([#1120][1120]). +* Search matches ending with a double-width character not being + highlighted correctly. [1055]: https://codeberg.org/dnkl/foot/issues/1055 [1092]: https://codeberg.org/dnkl/foot/issues/1092 diff --git a/search.c b/search.c index f6d377ea..88bc88aa 100644 --- a/search.c +++ b/search.c @@ -371,6 +371,9 @@ find_next(struct terminal *term, enum search_direction direction, i += additional_chars; match_len += additional_chars; match_end_col++; + + while (match_row->cells[match_end_col].wc > CELL_SPACER) + match_end_col++; } if (match_len != term->search.len) { From a05eaf28bdeea793829bddb6cc99220b1adb1d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 28 Jul 2022 18:27:13 +0200 Subject: [PATCH 11/81] selection: selection_on_rows(): use scrollback relative coords MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When checking if the current selection intersects with the region (passed as parameter to the function), use scrollback relative coordinates. This fixes an issue where selections crossing the scrollback wrap-around being misdetected, resulting in either the selection being canceled while scrolling, even though it wasn’t scrolled out, or the selection _not_ being canceled, when it _was_ scrolled out. --- CHANGELOG.md | 1 + selection.c | 74 +++++++++++++++++++++++++++++++++++++++++----------- selection.h | 2 ++ terminal.c | 12 ++++----- 4 files changed, 68 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75950976..16b2da76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ used ([#1120][1120]). * Search matches ending with a double-width character not being highlighted correctly. +* Selection not being cancelled correctly when scrolled out. [1055]: https://codeberg.org/dnkl/foot/issues/1055 [1092]: https://codeberg.org/dnkl/foot/issues/1092 diff --git a/selection.c b/selection.c index d4887382..36f4078f 100644 --- a/selection.c +++ b/selection.c @@ -64,41 +64,85 @@ selection_get_end(const struct terminal *term) bool selection_on_rows(const struct terminal *term, int row_start, int row_end) { + xassert(term->selection.coords.end.row >= 0); + LOG_DBG("on rows: %d-%d, range: %d-%d (offset=%d)", term->selection.coords.start.row, term->selection.coords.end.row, row_start, row_end, term->grid->offset); - if (term->selection.coords.end.row < 0) - return false; - - xassert(term->selection.coords.start.row != -1); - row_start += term->grid->offset; row_end += term->grid->offset; + xassert(row_end >= row_start); const struct coord *start = &term->selection.coords.start; const struct coord *end = &term->selection.coords.end; - if ((row_start <= start->row && row_end >= start->row) || - (row_start <= end->row && row_end >= end->row)) + const struct grid *grid = term->grid; + const int sb_start = grid->offset + term->rows; + + /* Use scrollback relative coords when checking for overlap */ + const int rel_row_start = + grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, row_start); + const int rel_row_end = + grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, row_start); + int rel_sel_start = + grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, start->row); + int rel_sel_end = + grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, end->row); + + if (rel_sel_start > rel_sel_end) { + int tmp = rel_sel_start; + rel_sel_start = rel_sel_end; + rel_sel_end = tmp; + } + + if ((rel_row_start <= rel_sel_start && rel_row_end >= rel_sel_start) || + (rel_row_start <= rel_sel_end && rel_row_end >= rel_sel_end)) { /* The range crosses one of the selection boundaries */ return true; } - /* For the last check we must ensure start <= end */ - if (start->row > end->row) { - const struct coord *tmp = start; - start = end; - end = tmp; - } - - if (row_start >= start->row && row_end <= end->row) + if (rel_row_start >= rel_sel_start && rel_row_end <= rel_sel_end) return true; return false; } +void +selection_scroll_up(struct terminal *term, int rows) +{ + xassert(term->selection.coords.end.row >= 0); + + const int rel_row_start = + grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.start.row); + const int rel_row_end = + grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.end.row); + const int actual_start = min(rel_row_start, rel_row_end); + + if (actual_start - rows < 0) { + /* Part of the selection will be scrolled out, cancel it */ + selection_cancel(term); + } +} + +void +selection_scroll_down(struct terminal *term, int rows) +{ + xassert(term->selection.coords.end.row >= 0); + + const int rel_row_start = + grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.start.row); + const int rel_row_end = + grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.end.row); + const int actual_end = max(rel_row_start, rel_row_end); + + if (actual_end + rows <= term->grid->num_rows) { + /* Part of the selection will be scrolled out, cancel it */ + selection_cancel(term); + } +} + void selection_view_up(struct terminal *term, int new_view) { diff --git a/selection.h b/selection.h index 3d0c224e..c6d7f968 100644 --- a/selection.h +++ b/selection.h @@ -22,6 +22,8 @@ void selection_extend( bool selection_on_rows(const struct terminal *term, int start, int end); +void selection_scroll_up(struct terminal *term, int rows); +void selection_scroll_down(struct terminal *term, int rows); void selection_view_up(struct terminal *term, int new_view); void selection_view_down(struct terminal *term, int new_view); diff --git a/terminal.c b/terminal.c index dd84b8cf..0763fb52 100644 --- a/terminal.c +++ b/terminal.c @@ -2537,11 +2537,11 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows * scrolled in (i.e. re-used lines). */ if (selection_on_top_region(term, region) || - selection_on_bottom_region(term, region) || - selection_on_rows(term, region.end - rows, region.end - 1)) + selection_on_bottom_region(term, region)) { selection_cancel(term); - } + } else + selection_scroll_up(term, rows); } sixel_scroll_up(term, rows); @@ -2611,11 +2611,11 @@ term_scroll_reverse_partial(struct terminal *term, * scrolled in (i.e. re-used lines). */ if (selection_on_top_region(term, region) || - selection_on_bottom_region(term, region) || - selection_on_rows(term, region.start, region.start + rows - 1)) + selection_on_bottom_region(term, region)) { selection_cancel(term); - } + } else + selection_scroll_down(term, rows); } sixel_scroll_down(term, rows); From 6ebf55572e765394e90c96b5fec0e2c55b3b434e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 28 Jul 2022 18:31:11 +0200 Subject: [PATCH 12/81] selection: foreach_selected_*(): refactor: use grid_row_abs_to_sb() --- selection.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/selection.c b/selection.c index 36f4078f..fd821c06 100644 --- a/selection.c +++ b/selection.c @@ -181,14 +181,14 @@ foreach_selected_normal( const struct coord *start = &_start; const struct coord *end = &_end; - const int scrollback_start = term->grid->offset + term->rows; const int grid_rows = term->grid->num_rows; + /* Start/end rows, relative to the scrollback start */ /* Start/end rows, relative to the scrollback start */ const int rel_start_row = - (start->row - scrollback_start + grid_rows) & (grid_rows - 1); + grid_row_abs_to_sb(term->grid, term->rows, start->row); const int rel_end_row = - (end->row - scrollback_start + grid_rows) & (grid_rows - 1); + grid_row_abs_to_sb(term->grid, term->rows, end->row); int start_row, end_row; int start_col, end_col; @@ -244,14 +244,13 @@ foreach_selected_block( const struct coord *start = &_start; const struct coord *end = &_end; - const int scrollback_start = term->grid->offset + term->rows; const int grid_rows = term->grid->num_rows; /* Start/end rows, relative to the scrollback start */ const int rel_start_row = - (start->row - scrollback_start + grid_rows) & (grid_rows - 1); + grid_row_abs_to_sb(term->grid, term->rows, start->row); const int rel_end_row = - (end->row - scrollback_start + grid_rows) & (grid_rows - 1); + grid_row_abs_to_sb(term->grid, term->rows, end->row); struct coord top_left = { .row = (rel_start_row < rel_end_row From b8506bbea049548310552e210e9cb363195fe74b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 28 Jul 2022 18:32:17 +0200 Subject: [PATCH 13/81] selection: extend: use scrollback relative coordinates When extending a selection, we determine *how* to extend it (which endpoint to move, and whether to grow or shrink the selection) by comparing the extension point with the old start and end coordinates. For this to work correctly, we need to use scrollback relative coordinates. This fixes an issue where extending a very large selection (covering many pages) sometimes shrunk the selection instead of growing it, or just misbehaving in general. --- CHANGELOG.md | 1 + selection.c | 48 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16b2da76..fc84a398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,7 @@ * Search matches ending with a double-width character not being highlighted correctly. * Selection not being cancelled correctly when scrolled out. +* Extending a multi-page selection behaving inconsistently. [1055]: https://codeberg.org/dnkl/foot/issues/1055 [1092]: https://codeberg.org/dnkl/foot/issues/1092 diff --git a/selection.c b/selection.c index fd821c06..54faf49b 100644 --- a/selection.c +++ b/selection.c @@ -1002,27 +1002,37 @@ selection_extend_normal(struct terminal *term, int col, int row, const struct coord *start = &term->selection.coords.start; const struct coord *end = &term->selection.coords.end; - if (start->row > end->row || - (start->row == end->row && start->col > end->col)) + const int rel_row = grid_row_abs_to_sb(term->grid, term->rows, row); + int rel_start_row = grid_row_abs_to_sb(term->grid, term->rows, start->row); + int rel_end_row = grid_row_abs_to_sb(term->grid, term->rows, end->row); + + if (rel_start_row > rel_end_row || + (rel_start_row == rel_end_row && start->col > end->col)) { const struct coord *tmp = start; start = end; end = tmp; - } - xassert(start->row < end->row || start->col < end->col); + int tmp_row = rel_start_row; + rel_start_row = rel_end_row; + rel_end_row = tmp_row; + } struct coord new_start, new_end; enum selection_direction direction; - if (row < start->row || (row == start->row && col < start->col)) { + if (rel_row < rel_start_row || + (rel_row == rel_start_row && col < start->col)) + { /* Extend selection to start *before* current start */ new_start = *end; new_end = (struct coord){col, row}; direction = SELECTION_LEFT; } - else if (row > end->row || (row == end->row && col > end->col)) { + else if (rel_row > rel_end_row || + (rel_row == rel_end_row && col > end->col)) + { /* Extend selection to end *after* current end */ new_start = *start; new_end = (struct coord){col, row}; @@ -1032,10 +1042,10 @@ selection_extend_normal(struct terminal *term, int col, int row, else { /* Shrink selection from start or end, depending on which one is closest */ - const int linear = row * term->cols + col; + const int linear = rel_row * term->cols + col; - if (abs(linear - (start->row * term->cols + start->col)) < - abs(linear - (end->row * term->cols + end->col))) + if (abs(linear - (rel_start_row * term->cols + start->col)) < + abs(linear - (rel_end_row * term->cols + end->col))) { /* Move start point */ new_start = *end; @@ -1110,33 +1120,41 @@ selection_extend_block(struct terminal *term, int col, int row) const struct coord *start = &term->selection.coords.start; const struct coord *end = &term->selection.coords.end; + const int rel_start_row = + grid_row_abs_to_sb(term->grid, term->rows, start->row); + const int rel_end_row = + grid_row_abs_to_sb(term->grid, term->rows, end->row); + struct coord top_left = { - .row = min(start->row, end->row), + .row = rel_start_row < rel_end_row ? start->row : end->row, .col = min(start->col, end->col), }; struct coord top_right = { - .row = min(start->row, end->row), + .row = top_left.row, .col = max(start->col, end->col), }; struct coord bottom_left = { - .row = max(start->row, end->row), + .row = rel_start_row > rel_end_row ? start->row : end->row, .col = min(start->col, end->col), }; struct coord bottom_right = { - .row = max(start->row, end->row), + .row = bottom_left.row, .col = max(start->col, end->col), }; + const int rel_row = grid_row_abs_to_sb(term->grid, term->rows, row); + const int rel_top_row = grid_row_abs_to_sb(term->grid, term->rows, top_left.row); + const int rel_bottom_row = grid_row_abs_to_sb(term->grid, term->rows, bottom_left.row); struct coord new_start; struct coord new_end; enum selection_direction direction = SELECTION_UNDIR; - if (row <= top_left.row || - abs(row - top_left.row) < abs(row - bottom_left.row)) + if (rel_row <= rel_top_row || + abs(rel_row - rel_top_row) < abs(rel_row - rel_bottom_row)) { /* Move one of the top corners */ From fa2d9f86996467ba33cc381f810ea966a4323381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 28 Jul 2022 18:45:25 +0200 Subject: [PATCH 14/81] selection: rework how we update a selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this patch, each selection update would result in grid covered by the selection being walked *three* times. First to “premark” the area that *will* be selected after the update, then again to unmark the previous selection (excluding the cells that were premarked - but the cells are still iterated), and then one more time to finalize the selection state in the grid. Furthermore, each time a frame is rendered, the entire selection were iterated again, to ensure all the cells have their ‘selected’ bit set. This quickly gets *very* slow. This patch takes a completely different approach. Instead of looking at the selection as a range of cells to iterate, we view it as an area, or region. Thus, on each update, we have to regions: the region representing the previous selection, and the region representing the to-be selection. By diffing these two regions, we get two new regions: one that represents the cells that were selected, but aren’t any more, and one that represents the cells that previously were not selected, but now will be. We implement the regions using pixman regions. By subtracting the current selection from the previous selection, we get the region representing the cells that are no longer selected, and that should be unmarked. By subtracting the previous selection from the current, we get the region representing the cells that was added to the selection in this update, and that should be marked. selection_dirty_cells() is rewritten in a similar manner. We create pixman regions for the selection, and the current scrollback view. The intersection represents the (selected) cells that are visible. These need to iterated and marked as being selected. Closes #1114 --- CHANGELOG.md | 2 + selection.c | 304 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 196 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc84a398..6e0b3052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,12 +98,14 @@ highlighted correctly. * Selection not being cancelled correctly when scrolled out. * Extending a multi-page selection behaving inconsistently. +* Poor performance when making very large selections ([#1114][1114]). [1055]: https://codeberg.org/dnkl/foot/issues/1055 [1092]: https://codeberg.org/dnkl/foot/issues/1092 [1097]: https://codeberg.org/dnkl/foot/issues/1097 [1111]: https://codeberg.org/dnkl/foot/issues/1111 [1120]: https://codeberg.org/dnkl/foot/issues/1120 +[1114]: https://codeberg.org/dnkl/foot/issues/1114 ### Security diff --git a/selection.c b/selection.c index 54faf49b..8c3a8357 100644 --- a/selection.c +++ b/selection.c @@ -9,6 +9,8 @@ #include #include +#include + #define LOG_MODULE "selection" #define LOG_ENABLE_DBG 0 #include "log.h" @@ -609,111 +611,150 @@ selection_start(struct terminal *term, int col, int row, } -/* Context used while (un)marking selected cells, to be able to - * exclude empty cells */ -struct mark_context { - const struct row *last_row; - int empty_count; - uint8_t **keep_selection; -}; - -static bool -unmark_selected(struct terminal *term, struct row *row, struct cell *cell, - int row_no, int col, void *data) +static pixman_region32_t +pixman_region_for_coords_normal(const struct terminal *term, + const struct coord *start, + const struct coord *end) { - if (!cell->attrs.selected) - return true; + pixman_region32_t region; + pixman_region32_init(®ion); - struct mark_context *ctx = data; - const uint8_t *keep_selection = - ctx->keep_selection != NULL ? ctx->keep_selection[row_no] : NULL; + const int rel_start_row = + grid_row_abs_to_sb(term->grid, term->rows, start->row); + const int rel_end_row = + grid_row_abs_to_sb(term->grid, term->rows, end->row); - if (keep_selection != NULL) { - unsigned idx = (unsigned)col / 8; - unsigned ofs = (unsigned)col % 8; + if (rel_start_row < rel_end_row) { + /* First partial row (start ->)*/ + pixman_region32_union_rect( + ®ion, ®ion, + start->col, rel_start_row, + term->cols - start->col, 1); - if (keep_selection[idx] & (1 << ofs)) { - /* We’re updating the selection, and this cell is still - * going to be selected */ - return true; + /* Full rows between start and end */ + if (rel_start_row + 1 < rel_end_row) { + pixman_region32_union_rect( + ®ion, ®ion, + 0, rel_start_row + 1, + term->cols, rel_end_row - rel_start_row - 1); } + + /* Last partial row (-> end) */ + pixman_region32_union_rect( + ®ion, ®ion, + 0, rel_end_row, + end->col + 1, 1); + + } else if (rel_start_row > rel_end_row) { + /* First partial row (end ->) */ + pixman_region32_union_rect( + ®ion, ®ion, + end->col, rel_end_row, + term->cols - end->col, 1); + + /* Full rows between end and start */ + if (rel_end_row + 1 < rel_start_row) { + pixman_region32_union_rect( + ®ion, ®ion, + 0, rel_end_row + 1, + term->cols, rel_start_row - rel_end_row - 1); + } + + /* Last partial row (-> start) */ + pixman_region32_union_rect( + ®ion, ®ion, + 0, rel_start_row, + start->col + 1, 1); + } else { + const int start_col = min(start->col, end->col); + const int end_col = max(start->col, end->col); + + pixman_region32_union_rect( + ®ion, ®ion, + start_col, rel_start_row, + end_col + 1 - start_col, 1); } - row->dirty = true; - cell->attrs.selected = false; - cell->attrs.clean = false; - return true; + return region; } -static bool -premark_selected(struct terminal *term, struct row *row, struct cell *cell, - int row_no, int col, void *data) +static pixman_region32_t +pixman_region_for_coords_block(const struct terminal *term, + const struct coord *start, const struct coord *end) { - struct mark_context *ctx = data; - xassert(ctx != NULL); + pixman_region32_t region; + pixman_region32_init(®ion); - if (ctx->last_row != row) { - ctx->last_row = row; - ctx->empty_count = 0; - } + const int rel_start_row = + grid_row_abs_to_sb(term->grid, term->rows, start->row); + const int rel_end_row = + grid_row_abs_to_sb(term->grid, term->rows, end->row); - if (cell->wc == 0 && term->selection.kind != SELECTION_BLOCK) { - ctx->empty_count++; - return true; - } + pixman_region32_union_rect( + ®ion, ®ion, + min(start->col, end->col), min(rel_start_row, rel_end_row), + abs(start->col - end->col) + 1, abs(rel_start_row - rel_end_row) + 1); - uint8_t *keep_selection = ctx->keep_selection[row_no]; - if (keep_selection == NULL) { - keep_selection = xcalloc((term->grid->num_cols + 7) / 8, sizeof(keep_selection[0])); - ctx->keep_selection[row_no] = keep_selection; - } - - /* Tell unmark to leave this be */ - for (int i = 0; i < ctx->empty_count + 1; i++) { - unsigned idx = (unsigned)(col - i) / 8; - unsigned ofs = (unsigned)(col - i) % 8; - keep_selection[idx] |= 1 << ofs; - } - - ctx->empty_count = 0; - return true; + return region; } -static bool -mark_selected(struct terminal *term, struct row *row, struct cell *cell, - int row_no, int col, void *data) +/* Returns a pixman region representing the selection between ‘start’ + * and ‘end’ (given the current selection kind), in *scrollback + * relative coordinates* */ +static pixman_region32_t +pixman_region_for_coords(const struct terminal *term, + const struct coord *start, const struct coord *end) { - struct mark_context *ctx = data; - xassert(ctx != NULL); - - if (ctx->last_row != row) { - ctx->last_row = row; - ctx->empty_count = 0; + switch (term->selection.kind) { + default: return pixman_region_for_coords_normal(term, start, end); + case SELECTION_BLOCK: return pixman_region_for_coords_block(term, start, end); } - - if (cell->wc == 0 && term->selection.kind != SELECTION_BLOCK) { - ctx->empty_count++; - return true; - } - - for (int i = 0; i < ctx->empty_count + 1; i++) { - struct cell *c = &row->cells[col - i]; - if (!c->attrs.selected) { - row->dirty = true; - c->attrs.selected = true; - c->attrs.clean = false; - } - } - - ctx->empty_count = 0; - return true; } static void -reset_modify_context(struct mark_context *ctx) +mark_selected_region(struct terminal *term, pixman_box32_t *boxes, + size_t count, bool selected, bool dirty_cells) { - ctx->last_row = NULL; - ctx->empty_count = 0; + for (size_t i = 0; i < count; i++) { + const pixman_box32_t *box = &boxes[i]; + + LOG_DBG("%s selection in region: %dx%d - %dx%d", + selected ? "marking" : "unmarking", + box->x1, box->y1, + box->x2, box->y2); + + int abs_row_start = grid_row_sb_to_abs( + term->grid, term->rows, box->y1); + + for (int r = abs_row_start, rel_r = box->y1; + rel_r < box->y2; + r = (r + 1) & (term->grid->num_rows - 1), rel_r++) + { + struct row *row = term->grid->rows[r]; + xassert(row != NULL); + + if (dirty_cells) + row->dirty = true; + + for (int c = box->x1, empty_count = 0; c < box->x2; c++) { + if (selected && row->cells[c].wc == 0) { + empty_count++; + continue; + } + + for (int j = 0; j < empty_count + 1; j++) { + xassert(c - j >= 0); + struct cell *cell = &row->cells[c - j]; + + if (dirty_cells) + cell->attrs.clean = false; + cell->attrs.selected = selected; + } + + empty_count = 0; + } + } + } } static void @@ -723,33 +764,46 @@ selection_modify(struct terminal *term, struct coord start, struct coord end) xassert(start.row != -1 && start.col != -1); xassert(end.row != -1 && end.col != -1); - uint8_t **keep_selection = - xcalloc(term->grid->num_rows, sizeof(keep_selection[0])); - - struct mark_context ctx = {.keep_selection = keep_selection}; - - /* Premark all cells that *will* be selected */ - foreach_selected(term, start, end, &premark_selected, &ctx); - reset_modify_context(&ctx); - + pixman_region32_t previous_selection; if (term->selection.coords.end.row >= 0) { - /* Unmark previous selection, ignoring cells that are part of - * the new selection */ - foreach_selected(term, term->selection.coords.start, term->selection.coords.end, - &unmark_selected, &ctx); - reset_modify_context(&ctx); - } + previous_selection = pixman_region_for_coords( + term, + &term->selection.coords.start, + &term->selection.coords.end); + } else + pixman_region32_init(&previous_selection); + + pixman_region32_t current_selection = pixman_region_for_coords( + term, &start, &end); + + pixman_region32_t no_longer_selected; + pixman_region32_init(&no_longer_selected); + pixman_region32_subtract( + &no_longer_selected, &previous_selection, ¤t_selection); + + pixman_region32_t newly_selected; + pixman_region32_init(&newly_selected); + pixman_region32_subtract( + &newly_selected, ¤t_selection, &previous_selection); + + /* Clear selection in cells no longer selected */ + int n_rects = -1; + pixman_box32_t *boxes = NULL; + + boxes = pixman_region32_rectangles(&no_longer_selected, &n_rects); + mark_selected_region(term, boxes, n_rects, false, true); + + boxes = pixman_region32_rectangles(&newly_selected, &n_rects); + mark_selected_region(term, boxes, n_rects, true, true); + + pixman_region32_fini(&newly_selected); + pixman_region32_fini(&no_longer_selected); + pixman_region32_fini(¤t_selection); + pixman_region32_fini(&previous_selection); term->selection.coords.start = start; term->selection.coords.end = end; - - /* Mark new selection */ - foreach_selected(term, start, end, &mark_selected, &ctx); render_refresh(term); - - for (size_t i = 0; i < term->grid->num_rows; i++) - free(keep_selection[i]); - free(keep_selection); } static void @@ -990,9 +1044,26 @@ selection_dirty_cells(struct terminal *term) if (term->selection.coords.start.row < 0 || term->selection.coords.end.row < 0) return; - foreach_selected( - term, term->selection.coords.start, term->selection.coords.end, &mark_selected, - &(struct mark_context){0}); + pixman_region32_t selection = pixman_region_for_coords( + term, &term->selection.coords.start, &term->selection.coords.end); + + pixman_region32_t view = pixman_region_for_coords( + term, + &(struct coord){0, term->grid->view}, + &(struct coord){term->cols - 1, term->grid->view + term->rows - 1}); + + pixman_region32_t visible_and_selected; + pixman_region32_init(&visible_and_selected); + pixman_region32_intersect(&visible_and_selected, &selection, &view); + + int n_rects = -1; + pixman_box32_t *boxes = + pixman_region32_rectangles(&visible_and_selected, &n_rects); + mark_selected_region(term, boxes, n_rects, true, false); + + pixman_region32_fini(&visible_and_selected); + pixman_region32_fini(&view); + pixman_region32_fini(&selection); } static void @@ -1270,6 +1341,19 @@ selection_finalize(struct seat *seat, struct terminal *term, uint32_t serial) } } +static bool +unmark_selected(struct terminal *term, struct row *row, struct cell *cell, + int row_no, int col, void *data) +{ + if (!cell->attrs.selected) + return true; + + row->dirty = true; + cell->attrs.selected = false; + cell->attrs.clean = false; + return true; +} + void selection_cancel(struct terminal *term) { @@ -1282,7 +1366,7 @@ selection_cancel(struct terminal *term) if (term->selection.coords.start.row >= 0 && term->selection.coords.end.row >= 0) { foreach_selected( term, term->selection.coords.start, term->selection.coords.end, - &unmark_selected, &(struct mark_context){0}); + &unmark_selected, NULL); render_refresh(term); } From 8967dd9cfe51276d63ef8ff64e8fc76f80a6fa3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 28 Jul 2022 18:09:16 +0200 Subject: [PATCH 15/81] input: add new Unicode input mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This mode is activated through the new key-bindings.unicode-input and search-bindings.unicode-input key bindings. When active, the user can “build” a Unicode codepoint by typing its hexadecimal value. Note that there’s no visual feedback in this mode. This is intentional. This mode is intended to be a fallback for users that don’t use an IME. Closes #1116 --- CHANGELOG.md | 5 ++++ config.c | 2 ++ doc/foot.ini.5.scd | 30 ++++++++++++++++++++ foot.ini | 2 ++ input.c | 69 +++++++++++++++++++++++++++++++++++++++++++++- key-binding.h | 4 ++- meson.build | 1 + search.c | 5 ++++ unicode-mode.c | 38 +++++++++++++++++++++++++ unicode-mode.h | 7 +++++ wayland.h | 6 ++++ 11 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 unicode-mode.c create mode 100644 unicode-mode.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e0b3052..8e3cd789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,11 +52,16 @@ `ctrl`+`shift`+`x` respectively ([#30][30]). * `colors.search-box-no-match` and `colors.search-box-match` options to `foot.ini` ([#1112][1112]). +* Very basic Unicode input mode via the new + `key-bindings.unicode-input` and `search-bindings.unicode-input` key + bindings. Note that there is no visual feedback, as the preferred + way of entering Unicode characters is with an IME ([#1116][1116]). [1058]: https://codeberg.org/dnkl/foot/issues/1058 [1070]: https://codeberg.org/dnkl/foot/issues/1070 [30]: https://codeberg.org/dnkl/foot/issues/30 [1112]: https://codeberg.org/dnkl/foot/issues/1112 +[1116]: https://codeberg.org/dnkl/foot/issues/1116 ### Changed diff --git a/config.c b/config.c index 0e509995..7a746aea 100644 --- a/config.c +++ b/config.c @@ -117,6 +117,7 @@ static const char *const binding_action_map[] = { [BIND_ACTION_TEXT_BINDING] = "text-binding", [BIND_ACTION_PROMPT_PREV] = "prompt-prev", [BIND_ACTION_PROMPT_NEXT] = "prompt-next", + [BIND_ACTION_UNICODE_INPUT] = "unicode-input", /* Mouse-specific actions */ [BIND_ACTION_SELECT_BEGIN] = "select-begin", @@ -148,6 +149,7 @@ static const char *const search_binding_action_map[] = { [BIND_ACTION_SEARCH_EXTEND_WORD_WS] = "extend-to-next-whitespace", [BIND_ACTION_SEARCH_CLIPBOARD_PASTE] = "clipboard-paste", [BIND_ACTION_SEARCH_PRIMARY_PASTE] = "primary-paste", + [BIND_ACTION_SEARCH_UNICODE_INPUT] = "unicode-input", }; static const char *const url_binding_action_map[] = { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 2e62a41f..d3881274 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -791,6 +791,32 @@ e.g. *search-start=none*. Jump the next prompt (requires shell integration, see *foot*(1)). Default: _Control+Shift+x_. +*unicode-input* + Input a Unicode character by typing its codepoint in hexadecimal, + followed by *Enter* or *Space*. + + For example, to input the character _ö_ (LATIN SMALL LETTER O WITH + DIAERESIS, Unicode codepoint 0xf6), you would first activate this + key binding, then type: *f*, *6*, *Enter*. + + Another example: to input 😍 (SMILING FACE WITH HEART-SHAPED EYES, + Unicode codepoint 0x1f60d), activate this key binding, then type: + *1*, *f*, *6*, *0*, *d*, *Enter*. + + Recognized key bindings in Unicode input mode: + + - Enter, Space: commit the Unicode character, then exit this mode. + - Escape, Ctrl+c, Ctrl+d, Ctrl+g: abort input, then exit this mode. + - 0-9, a-f: append next digit to the Unicode's codepoint. + - Backspace: undo the last digit. + + Note that there is no visual feedback while in this mode. This is + by design; foot's Unicode input mode is considered to be a + fallback. The preferred way of entering Unicode characters, emojis + etc is by using an IME. + + Default: _none_. + # SECTION: search-bindings This section lets you override the default key bindings used in @@ -869,6 +895,10 @@ scrollback search mode. The syntax is exactly the same as the regular Paste from the _primary selection_ into the search buffer. Default: _Shift+Insert_. +*unicode-input* + Unicode input mode. See _key-bindings.unicode-input_ for + details. Default: _none_. + # SECTION: url-bindings This section lets you override the default key bindings used in URL diff --git a/foot.ini b/foot.ini index e8ff1870..4c857296 100644 --- a/foot.ini +++ b/foot.ini @@ -150,6 +150,7 @@ # show-urls-persistent=none # prompt-prev=Control+Shift+z # prompt-next=Control+Shift+x +# unicode-input=none # noop=none [search-bindings] @@ -171,6 +172,7 @@ # extend-to-next-whitespace=Control+Shift+w # clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste # primary-paste=Shift+Insert +# unicode-input=none [url-bindings] # cancel=Control+g Control+c Control+d Escape diff --git a/input.c b/input.c index e0f21c6e..8b47b983 100644 --- a/input.c +++ b/input.c @@ -36,6 +36,7 @@ #include "spawn.h" #include "terminal.h" #include "tokenize.h" +#include "unicode-mode.h" #include "url-mode.h" #include "util.h" #include "vt.h" @@ -416,6 +417,10 @@ execute_binding(struct seat *seat, struct terminal *term, return true; } + case BIND_ACTION_UNICODE_INPUT: + unicode_mode_activate(seat); + return true; + case BIND_ACTION_SELECT_BEGIN: selection_start( term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false); @@ -1405,7 +1410,69 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, xassert(bindings != NULL); if (pressed) { - if (term->is_searching) { + if (seat->unicode_mode.active) { + if (sym == XKB_KEY_Return || + sym == XKB_KEY_space || + sym == XKB_KEY_KP_Enter || + sym == XKB_KEY_KP_Space) + { + char utf8[MB_CUR_MAX]; + size_t chars = c32rtomb( + utf8, seat->unicode_mode.character, &(mbstate_t){0}); + + LOG_DBG("Unicode input: 0x%06x -> %.*s", + seat->unicode_mode.character, (int)chars, utf8); + + if (chars != (size_t)-1) { + if (term->is_searching) + search_add_chars(term, utf8, chars); + else + term_to_slave(term, utf8, chars); + } + + unicode_mode_deactivate(seat); + } + + else if (sym == XKB_KEY_Escape || + (seat->kbd.ctrl && (sym == XKB_KEY_c || + sym == XKB_KEY_d || + sym == XKB_KEY_g))) + { + unicode_mode_deactivate(seat); + } + + else if (sym == XKB_KEY_BackSpace) { + if (seat->unicode_mode.count > 0) { + seat->unicode_mode.character >>= 4; + seat->unicode_mode.count--; + unicode_mode_updated(seat); + } + } + + else if (seat->unicode_mode.count < 6) { + int digit = -1; + + /* 0-9, a-f, A-F */ + if (sym >= XKB_KEY_0 && sym <= XKB_KEY_9) + digit = sym - XKB_KEY_0; + else if (sym >= XKB_KEY_a && sym <= XKB_KEY_f) + digit = 0xa + (sym - XKB_KEY_a); + else if (sym >= XKB_KEY_A && sym <= XKB_KEY_F) + digit = 0xa + (sym - XKB_KEY_A); + + if (digit >= 0) { + xassert(digit >= 0 && digit <= 0xf); + seat->unicode_mode.character <<= 4; + seat->unicode_mode.character |= digit; + seat->unicode_mode.count++; + unicode_mode_updated(seat); + } + } + + return; + } + + else if (term->is_searching) { if (should_repeat) start_repeater(seat, key); diff --git a/key-binding.h b/key-binding.h index 1c0e2a99..448500c1 100644 --- a/key-binding.h +++ b/key-binding.h @@ -38,6 +38,7 @@ enum bind_action_normal { BIND_ACTION_TEXT_BINDING, BIND_ACTION_PROMPT_PREV, BIND_ACTION_PROMPT_NEXT, + BIND_ACTION_UNICODE_INPUT, /* Mouse specific actions - i.e. they require a mouse coordinate */ BIND_ACTION_SELECT_BEGIN, @@ -48,7 +49,7 @@ enum bind_action_normal { BIND_ACTION_SELECT_WORD_WS, BIND_ACTION_SELECT_ROW, - BIND_ACTION_KEY_COUNT = BIND_ACTION_PROMPT_NEXT + 1, + BIND_ACTION_KEY_COUNT = BIND_ACTION_UNICODE_INPUT + 1, BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1, }; @@ -72,6 +73,7 @@ enum bind_action_search { BIND_ACTION_SEARCH_EXTEND_WORD_WS, BIND_ACTION_SEARCH_CLIPBOARD_PASTE, BIND_ACTION_SEARCH_PRIMARY_PASTE, + BIND_ACTION_SEARCH_UNICODE_INPUT, BIND_ACTION_SEARCH_COUNT, }; diff --git a/meson.build b/meson.build index 6dc4d098..676d550a 100644 --- a/meson.build +++ b/meson.build @@ -222,6 +222,7 @@ executable( 'slave.c', 'slave.h', 'spawn.c', 'spawn.h', 'tokenize.c', 'tokenize.h', + 'unicode-mode.c', 'unicode-mode.h', 'url-mode.c', 'url-mode.h', 'user-notification.c', 'user-notification.h', 'wayland.c', 'wayland.h', 'shm-formats.h', diff --git a/search.c b/search.c index 88bc88aa..59765c2e 100644 --- a/search.c +++ b/search.c @@ -18,6 +18,7 @@ #include "render.h" #include "selection.h" #include "shm.h" +#include "unicode-mode.h" #include "util.h" #include "xmalloc.h" @@ -993,6 +994,10 @@ execute_binding(struct seat *seat, struct terminal *term, *update_search_result = *redraw = true; return true; + case BIND_ACTION_SEARCH_UNICODE_INPUT: + unicode_mode_activate(seat); + return true; + case BIND_ACTION_SEARCH_COUNT: BUG("Invalid action type"); return true; diff --git a/unicode-mode.c b/unicode-mode.c new file mode 100644 index 00000000..0da86add --- /dev/null +++ b/unicode-mode.c @@ -0,0 +1,38 @@ +#include "unicode-mode.h" + +#include "render.h" + +void +unicode_mode_activate(struct seat *seat) +{ + if (seat->unicode_mode.active) + return; + + seat->unicode_mode.active = true; + seat->unicode_mode.character = u'\0'; + seat->unicode_mode.count = 0; + unicode_mode_updated(seat); +} + +void +unicode_mode_deactivate(struct seat *seat) +{ + if (!seat->unicode_mode.active) + return; + + seat->unicode_mode.active = false; + unicode_mode_updated(seat); +} + +void +unicode_mode_updated(struct seat *seat) +{ + struct terminal *term = seat->kbd_focus; + if (term == NULL) + return; + + if (term->is_searching) + render_refresh_search(term); + else + render_refresh(term); +} diff --git a/unicode-mode.h b/unicode-mode.h new file mode 100644 index 00000000..eadbe06a --- /dev/null +++ b/unicode-mode.h @@ -0,0 +1,7 @@ +#pragma once + +#include "wayland.h" + +void unicode_mode_activate(struct seat *seat); +void unicode_mode_deactivate(struct seat *seat); +void unicode_mode_updated(struct seat *seat); diff --git a/wayland.h b/wayland.h index 729a225b..803fe390 100644 --- a/wayland.h +++ b/wayland.h @@ -206,6 +206,12 @@ struct seat { uint32_t serial; } ime; #endif + + struct { + bool active; + int count; + char32_t character; + } unicode_mode; }; enum csd_surface { From 001f96c4e3ae26a9b2807b549c782a094a36ff3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 28 Jul 2022 19:34:13 +0200 Subject: [PATCH 16/81] unicode-input: move input (key press) handling to unicode_mode_input() --- input.c | 59 +------------------------------------------- unicode-mode.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ unicode-mode.h | 4 +++ 3 files changed, 72 insertions(+), 58 deletions(-) diff --git a/input.c b/input.c index 8b47b983..847ff6af 100644 --- a/input.c +++ b/input.c @@ -1411,64 +1411,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, if (pressed) { if (seat->unicode_mode.active) { - if (sym == XKB_KEY_Return || - sym == XKB_KEY_space || - sym == XKB_KEY_KP_Enter || - sym == XKB_KEY_KP_Space) - { - char utf8[MB_CUR_MAX]; - size_t chars = c32rtomb( - utf8, seat->unicode_mode.character, &(mbstate_t){0}); - - LOG_DBG("Unicode input: 0x%06x -> %.*s", - seat->unicode_mode.character, (int)chars, utf8); - - if (chars != (size_t)-1) { - if (term->is_searching) - search_add_chars(term, utf8, chars); - else - term_to_slave(term, utf8, chars); - } - - unicode_mode_deactivate(seat); - } - - else if (sym == XKB_KEY_Escape || - (seat->kbd.ctrl && (sym == XKB_KEY_c || - sym == XKB_KEY_d || - sym == XKB_KEY_g))) - { - unicode_mode_deactivate(seat); - } - - else if (sym == XKB_KEY_BackSpace) { - if (seat->unicode_mode.count > 0) { - seat->unicode_mode.character >>= 4; - seat->unicode_mode.count--; - unicode_mode_updated(seat); - } - } - - else if (seat->unicode_mode.count < 6) { - int digit = -1; - - /* 0-9, a-f, A-F */ - if (sym >= XKB_KEY_0 && sym <= XKB_KEY_9) - digit = sym - XKB_KEY_0; - else if (sym >= XKB_KEY_a && sym <= XKB_KEY_f) - digit = 0xa + (sym - XKB_KEY_a); - else if (sym >= XKB_KEY_A && sym <= XKB_KEY_F) - digit = 0xa + (sym - XKB_KEY_A); - - if (digit >= 0) { - xassert(digit >= 0 && digit <= 0xf); - seat->unicode_mode.character <<= 4; - seat->unicode_mode.character |= digit; - seat->unicode_mode.count++; - unicode_mode_updated(seat); - } - } - + unicode_mode_input(seat, term, sym); return; } diff --git a/unicode-mode.c b/unicode-mode.c index 0da86add..6b4b6050 100644 --- a/unicode-mode.c +++ b/unicode-mode.c @@ -1,6 +1,10 @@ #include "unicode-mode.h" +#define LOG_MODULE "unicode-input" +#define LOG_ENABLE_DBG 0 +#include "log.h" #include "render.h" +#include "search.h" void unicode_mode_activate(struct seat *seat) @@ -36,3 +40,66 @@ unicode_mode_updated(struct seat *seat) else render_refresh(term); } + +void +unicode_mode_input(struct seat *seat, struct terminal *term, + xkb_keysym_t sym) +{ + if (sym == XKB_KEY_Return || + sym == XKB_KEY_space || + sym == XKB_KEY_KP_Enter || + sym == XKB_KEY_KP_Space) + { + char utf8[MB_CUR_MAX]; + size_t chars = c32rtomb( + utf8, seat->unicode_mode.character, &(mbstate_t){0}); + + LOG_DBG("Unicode input: 0x%06x -> %.*s", + seat->unicode_mode.character, (int)chars, utf8); + + if (chars != (size_t)-1) { + if (term->is_searching) + search_add_chars(term, utf8, chars); + else + term_to_slave(term, utf8, chars); + } + + unicode_mode_deactivate(seat); + } + + else if (sym == XKB_KEY_Escape || + (seat->kbd.ctrl && (sym == XKB_KEY_c || + sym == XKB_KEY_d || + sym == XKB_KEY_g))) + { + unicode_mode_deactivate(seat); + } + + else if (sym == XKB_KEY_BackSpace) { + if (seat->unicode_mode.count > 0) { + seat->unicode_mode.character >>= 4; + seat->unicode_mode.count--; + unicode_mode_updated(seat); + } + } + + else if (seat->unicode_mode.count < 6) { + int digit = -1; + + /* 0-9, a-f, A-F */ + if (sym >= XKB_KEY_0 && sym <= XKB_KEY_9) + digit = sym - XKB_KEY_0; + else if (sym >= XKB_KEY_a && sym <= XKB_KEY_f) + digit = 0xa + (sym - XKB_KEY_a); + else if (sym >= XKB_KEY_A && sym <= XKB_KEY_F) + digit = 0xa + (sym - XKB_KEY_A); + + if (digit >= 0) { + xassert(digit >= 0 && digit <= 0xf); + seat->unicode_mode.character <<= 4; + seat->unicode_mode.character |= digit; + seat->unicode_mode.count++; + unicode_mode_updated(seat); + } + } +} diff --git a/unicode-mode.h b/unicode-mode.h index eadbe06a..e7c75b9b 100644 --- a/unicode-mode.h +++ b/unicode-mode.h @@ -1,7 +1,11 @@ #pragma once +#include + #include "wayland.h" void unicode_mode_activate(struct seat *seat); void unicode_mode_deactivate(struct seat *seat); void unicode_mode_updated(struct seat *seat); +void unicode_mode_input(struct seat *seat, struct terminal *term, + xkb_keysym_t sym); From 0cbd99710b9d9ff5116c39e1d989556a6d7c27a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 29 Jul 2022 11:56:41 +0200 Subject: [PATCH 17/81] =?UTF-8?q?unicode-mode:=20=E2=80=98q=E2=80=99=20abo?= =?UTF-8?q?rts=20Unicode=20input=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/foot.ini.5.scd | 2 +- unicode-mode.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index d3881274..995a7e33 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -806,7 +806,7 @@ e.g. *search-start=none*. Recognized key bindings in Unicode input mode: - Enter, Space: commit the Unicode character, then exit this mode. - - Escape, Ctrl+c, Ctrl+d, Ctrl+g: abort input, then exit this mode. + - Escape, q, Ctrl+c, Ctrl+d, Ctrl+g: abort input, then exit this mode. - 0-9, a-f: append next digit to the Unicode's codepoint. - Backspace: undo the last digit. diff --git a/unicode-mode.c b/unicode-mode.c index 6b4b6050..a69601ec 100644 --- a/unicode-mode.c +++ b/unicode-mode.c @@ -68,6 +68,7 @@ unicode_mode_input(struct seat *seat, struct terminal *term, } else if (sym == XKB_KEY_Escape || + sym == XKB_KEY_q || (seat->kbd.ctrl && (sym == XKB_KEY_c || sym == XKB_KEY_d || sym == XKB_KEY_g))) From c0a7c7bf0d4412fd9436962704cb26f9596e529f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 29 Jul 2022 21:27:27 +0200 Subject: [PATCH 18/81] config: reset errno before calling getline() again Related to #1107 --- config.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.c b/config.c index 7a746aea..fbeab3e6 100644 --- a/config.c +++ b/config.c @@ -2639,6 +2639,9 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar if (!section_parser(ctx)) error_or_continue(); + + /* For next iteration of getline() */ + errno = 0; } if (errno != 0) { From ffdac61e2a95aa31a9c69eb5152a7dd79346ddf0 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Fri, 29 Jul 2022 20:12:47 +0200 Subject: [PATCH 19/81] server: Use "normal" socket activation, not inetd Systemd, when doing socket activation, pass file descriptors in a non-stable order when there is multiples ones. But we only use one, so we don't need to identify it, and the file descriptors always start at 3. So use 3 for the systemd service. Source : sd_listen_fds (systemd man pages) We also need to unset variables systemd pass to socket activated process, since we don't need them and sub-process (footclient and theirs forks) could be confused by those. Closes #1107 --- CHANGELOG.md | 3 +++ foot-server@.service.in | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e3cd789..6823e16b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,8 @@ * Selection not being cancelled correctly when scrolled out. * Extending a multi-page selection behaving inconsistently. * Poor performance when making very large selections ([#1114][1114]). +* Bogus error message when using systemd socket activation for server + mode ([#1107][1107]) [1055]: https://codeberg.org/dnkl/foot/issues/1055 [1092]: https://codeberg.org/dnkl/foot/issues/1092 @@ -111,6 +113,7 @@ [1111]: https://codeberg.org/dnkl/foot/issues/1111 [1120]: https://codeberg.org/dnkl/foot/issues/1120 [1114]: https://codeberg.org/dnkl/foot/issues/1114 +[1107]: https://codeberg.org/dnkl/foot/issues/1107 ### Security diff --git a/foot-server@.service.in b/foot-server@.service.in index 81c13bb4..c40bb454 100644 --- a/foot-server@.service.in +++ b/foot-server@.service.in @@ -1,8 +1,8 @@ [Service] -ExecStart=@bindir@/foot --server=0 +ExecStart=@bindir@/foot --server=3 Environment=WAYLAND_DISPLAY=%i +UnsetEnvironment=LISTEN_PID LISTEN_FDS LISTEN_FDNAMES NonBlocking=true -StandardInput=socket [Unit] Requires=%N.socket From 2f68b421bfc444c2a713d7418850b67a7e34810f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 6 May 2022 20:03:30 +0200 Subject: [PATCH 20/81] Add no-op xdg_toplevel.configure_bounds handler Next commit uses v5. --- wayland.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/wayland.c b/wayland.c index 850b657b..2d7c303f 100644 --- a/wayland.c +++ b/wayland.c @@ -706,9 +706,18 @@ xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) term_shutdown(term); } +static void +xdg_toplevel_configure_bounds(void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height) +{ + /* TODO: ensure we don't pick a bigger size */ +} + static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = &xdg_toplevel_configure, /*.close = */&xdg_toplevel_close, /* epoll-shim defines a macro ‘close’... */ + .configure_bounds = &xdg_toplevel_configure_bounds, }; static void @@ -902,7 +911,7 @@ handle_global(void *data, struct wl_registry *registry, */ wayl->shell = wl_registry_bind( - wayl->registry, name, &xdg_wm_base_interface, min(version, 2)); + wayl->registry, name, &xdg_wm_base_interface, min(version, 4)); xdg_wm_base_add_listener(wayl->shell, &xdg_wm_base_listener, wayl); } From 129e1a9b8e23007026d19c970941ccbc8ee2f0ca Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 6 May 2022 20:05:04 +0200 Subject: [PATCH 21/81] Add support for xdg_toplevel.wm_capabilities See https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/122 --- CHANGELOG.md | 3 +++ render.c | 6 ++++-- wayland.c | 46 +++++++++++++++++++++++++++++++++++++++++++--- wayland.h | 5 +++++ 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6823e16b..b82cfa00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,12 +56,15 @@ `key-bindings.unicode-input` and `search-bindings.unicode-input` key bindings. Note that there is no visual feedback, as the preferred way of entering Unicode characters is with an IME ([#1116][1116]). +* Support for `xdg_toplevel.wm_capabilities`, to adapt the client-side + decoration buttons to the compositor capabilities ([#1061][1061]). [1058]: https://codeberg.org/dnkl/foot/issues/1058 [1070]: https://codeberg.org/dnkl/foot/issues/1070 [30]: https://codeberg.org/dnkl/foot/issues/30 [1112]: https://codeberg.org/dnkl/foot/issues/1112 [1116]: https://codeberg.org/dnkl/foot/issues/1116 +[1061]: https://codeberg.org/dnkl/foot/pulls/1061 ### Changed diff --git a/render.c b/render.c index 9333deb7..ff82802e 100644 --- a/render.c +++ b/render.c @@ -1726,10 +1726,12 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx) const int button_close_width = term->width >= 1 * button_width ? button_width : 0; - const int button_maximize_width = term->width >= 2 * button_width + const int button_maximize_width = + term->width >= 2 * button_width && term->window->wm_capabilities.maximize ? button_width : 0; - const int button_minimize_width = term->width >= 3 * button_width + const int button_minimize_width = + term->width >= 3 * button_width && term->window->wm_capabilities.minimize ? button_width : 0; switch (surf_idx) { diff --git a/wayland.c b/wayland.c index 2d7c303f..fb41cf7d 100644 --- a/wayland.c +++ b/wayland.c @@ -714,10 +714,38 @@ xdg_toplevel_configure_bounds(void *data, /* TODO: ensure we don't pick a bigger size */ } +#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) +static void +xdg_toplevel_wm_capabilities(void *data, + struct xdg_toplevel *xdg_toplevel, + struct wl_array *caps) +{ + struct wl_window *win = data; + + win->wm_capabilities.maximize = false; + win->wm_capabilities.minimize = false; + + uint32_t *cap_ptr; + wl_array_for_each(cap_ptr, caps) { + switch (*cap_ptr) { + case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: + win->wm_capabilities.maximize = true; + break; + case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: + win->wm_capabilities.minimize = true; + break; + } + } +} +#endif + static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = &xdg_toplevel_configure, /*.close = */&xdg_toplevel_close, /* epoll-shim defines a macro ‘close’... */ .configure_bounds = &xdg_toplevel_configure_bounds, +#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + .wm_capabilities = xdg_toplevel_wm_capabilities, +#endif }; static void @@ -905,13 +933,22 @@ handle_global(void *data, struct wl_registry *registry, return; /* - * We *require* version 1, but _can_ use version 2. Version 2 + * We *require* version 1, but _can_ use version 5. Version 2 * adds 'tiled' window states. We use that information to - * restore the window size when window is un-tiled. + * restore the window size when window is un-tiled. Version 5 + * adds 'wm_capabilities'. We use that information to draw + * window decorations. */ +#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + const uint32_t preferred = XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION; +#elif defined(XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION) + const uint32_t preferred = XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION; +#else + const uint32_t preferred = required; +#endif wayl->shell = wl_registry_bind( - wayl->registry, name, &xdg_wm_base_interface, min(version, 4)); + wayl->registry, name, &xdg_wm_base_interface, min(version, preferred)); xdg_wm_base_add_listener(wayl->shell, &xdg_wm_base_listener, wayl); } @@ -1427,6 +1464,9 @@ wayl_win_init(struct terminal *term, const char *token) win->csd.move_timeout_fd = -1; win->resize_timeout_fd = -1; + win->wm_capabilities.maximize = true; + win->wm_capabilities.minimize = true; + win->surface = wl_compositor_create_surface(wayl->compositor); if (win->surface == NULL) { LOG_ERR("failed to create wayland surface"); diff --git a/wayland.h b/wayland.h index 803fe390..e86c6a3d 100644 --- a/wayland.h +++ b/wayland.h @@ -339,6 +339,11 @@ struct wl_window { uint32_t serial; } csd; + struct { + bool maximize:1; + bool minimize:1; + } wm_capabilities; + struct wl_surf_subsurf search; struct wl_surf_subsurf scrollback_indicator; struct wl_surf_subsurf render_timer; From aaf5894ad9272698cfafeb8e299b925fb9554417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 5 Aug 2022 18:26:37 +0200 Subject: [PATCH 22/81] grid: get rid of empty row at the bottom after reflowing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the window is resized and we reflow the text, we ended up inserting an empty row at the bottom. This happens whenever the actual last row has a hard linebreak (which almost always is the case); we then end the reflow with a line break, causing an extra, empty, row to be allocated and inserted. This patch fixes this by detecting when: 1) the last row is empty 2) the next to last row has a hard line break In this case, we roll back the last line break, by adjusting the new offset we just calculated, and free:ing the empty row. TODO: it would be nice if we could detect this in the reflow loop instead, and avoid doing the last line break all together. I haven’t yet been able to find a way to do this correctly. Closes #1108 --- CHANGELOG.md | 2 ++ grid.c | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b82cfa00..1dddd2d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ * Poor performance when making very large selections ([#1114][1114]). * Bogus error message when using systemd socket activation for server mode ([#1107][1107]) +* Empty line at the bottom after a window resize ([#1108][1108]). [1055]: https://codeberg.org/dnkl/foot/issues/1055 [1092]: https://codeberg.org/dnkl/foot/issues/1092 @@ -117,6 +118,7 @@ [1120]: https://codeberg.org/dnkl/foot/issues/1120 [1114]: https://codeberg.org/dnkl/foot/issues/1114 [1107]: https://codeberg.org/dnkl/foot/issues/1107 +[1108]: https://codeberg.org/dnkl/foot/issues/1108 ### Security diff --git a/grid.c b/grid.c index 2790d16b..f40803e1 100644 --- a/grid.c +++ b/grid.c @@ -984,6 +984,26 @@ grid_resize_and_reflow( /* Set offset such that the last reflowed row is at the bottom */ grid->offset = new_row_idx - new_screen_rows + 1; + + if (new_col_idx == 0) { + int next_to_last_new_row_idx = new_row_idx - 1; + next_to_last_new_row_idx += new_rows; + next_to_last_new_row_idx &= new_rows - 1; + + const struct row *next_to_last_row = new_grid[next_to_last_new_row_idx]; + if (next_to_last_row != NULL && next_to_last_row->linebreak) { + /* + * The next to last row is actually the *last* row. But we + * ended the reflow with a line-break, causing an empty + * row to be inserted at the bottom. Undo this. + */ + /* TODO: can we detect this in the reflow loop above instead? */ + grid->offset--; + grid_row_free(new_grid[new_row_idx]); + new_grid[new_row_idx] = NULL; + } + } + while (grid->offset < 0) grid->offset += new_rows; while (new_grid[grid->offset] == NULL) From e3eefdacde0182aa2c5e56cf450e3abac094e852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Aug 2022 09:31:56 +0200 Subject: [PATCH 23/81] changelog: prepare for 1.13.0 --- CHANGELOG.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dddd2d3..ca337533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.13.0](#1-13-0) * [1.12.1](#1-12-1) * [1.12.0](#1-12-0) * [1.11.0](#1-11-0) @@ -39,7 +39,7 @@ * [1.2.0](#1-2-0) -## Unreleased +## 1.13.0 ### Added @@ -82,8 +82,6 @@ [1105]: https://codeberg.org/dnkl/foot/issues/1105 -### Deprecated -### Removed ### Fixed * Graphical corruption when viewport is at the top of the scrollback, @@ -121,9 +119,14 @@ [1108]: https://codeberg.org/dnkl/foot/issues/1108 -### Security ### Contributors +* Craig Barnes +* Lorenz +* Max Gautier +* Simon Ser +* Stefan Prosiegel + ## 1.12.1 From e0465d3a7a0b8d6962462a505f2f8b32c2ee74bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Aug 2022 09:32:02 +0200 Subject: [PATCH 24/81] meson: bump version to 1.13.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 676d550a..c5597113 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.12.1', + version: '1.13.0', license: 'MIT', meson_version: '>=0.58.0', default_options: [ From a36848a4ad478ebbff3ce4fa27f111f810b9bb2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Aug 2022 09:38:25 +0200 Subject: [PATCH 25/81] =?UTF-8?q?changelog:=20add=20new=20=E2=80=98unrelea?= =?UTF-8?q?sed=E2=80=99=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca337533..da7f779f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.13.0](#1-13-0) * [1.12.1](#1-12-1) * [1.12.0](#1-12-0) @@ -39,6 +40,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.13.0 ### Added From e249b52abd59024e0a933f93b458d7bf4e780222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 8 Aug 2022 16:31:28 +0200 Subject: [PATCH 26/81] render: apply a dimmed overlay while in Unicode input mode --- CHANGELOG.md | 4 ++++ render.c | 33 ++++++++++++++++++++++++++++----- terminal.h | 7 ++++--- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da7f779f..e914e4d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,10 @@ ## Unreleased ### Added ### Changed + +* Window is now dimmed while in Unicode input mode. + + ### Deprecated ### Removed ### Fixed diff --git a/render.c b/render.c index ff82802e..82c9bd13 100644 --- a/render.c +++ b/render.c @@ -1466,10 +1466,21 @@ static void render_overlay(struct terminal *term) { struct wl_surf_subsurf *overlay = &term->window->overlay; + bool unicode_mode_active = false; + + /* Check if unicode mode is active on at least one seat focusing + * this terminal instance */ + tll_foreach(term->wl->seats, it) { + if (it->item.unicode_mode.active) { + unicode_mode_active = true; + break; + } + } const enum overlay_style style = term->is_searching ? OVERLAY_SEARCH : term->flash.active ? OVERLAY_FLASH : + unicode_mode_active ? OVERLAY_UNICODE_MODE : OVERLAY_NONE; if (likely(style == OVERLAY_NONE)) { @@ -1488,9 +1499,21 @@ render_overlay(struct terminal *term) pixman_image_set_clip_region32(buf->pix[0], NULL); - pixman_color_t color = style == OVERLAY_SEARCH - ? (pixman_color_t){0, 0, 0, 0x7fff} - : (pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff}; + pixman_color_t color; + + switch (style) { + case OVERLAY_NONE: + break; + + case OVERLAY_SEARCH: + case OVERLAY_UNICODE_MODE: + color = (pixman_color_t){0, 0, 0, 0x7fff}; + break; + + case OVERLAY_FLASH: + color = (pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff}; + break; + } /* Bounding rectangle of damaged areas - for wl_surface_damage_buffer() */ pixman_box32_t damage_bounds; @@ -1517,7 +1540,7 @@ render_overlay(struct terminal *term) * region that needs to be *cleared* in this frame. * * Finally, the union of the two “diff” regions above, gives - * us the total region affecte by a change, in either way. We + * us the total region affected by a change, in either way. We * use this as the bounding box for the * wl_surface_damage_buffer() call. */ @@ -1605,7 +1628,7 @@ render_overlay(struct terminal *term) else if (buf == term->render.last_overlay_buf && style == term->render.last_overlay_style) { - xassert(style == OVERLAY_FLASH); + xassert(style == OVERLAY_FLASH || style == OVERLAY_UNICODE_MODE); shm_did_not_use_buf(buf); return; } else { diff --git a/terminal.h b/terminal.h index bf6e74fe..0dde6330 100644 --- a/terminal.h +++ b/terminal.h @@ -289,9 +289,10 @@ enum term_surface { }; enum overlay_style { - OVERLAY_NONE = 0, - OVERLAY_SEARCH = 1, - OVERLAY_FLASH = 2, + OVERLAY_NONE, + OVERLAY_SEARCH, + OVERLAY_FLASH, + OVERLAY_UNICODE_MODE, }; typedef tll(struct ptmx_buffer) ptmx_buffer_list_t; From eafff70439c650dba288d0ac05cd009510bf6205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Aug 2022 16:12:36 +0200 Subject: [PATCH 27/81] wayland: #ifdef on XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION This enables us to compile against wayland-protocols < 1.25 --- CHANGELOG.md | 4 ++++ wayland.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e914e4d3..4c10be50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,10 @@ ### Deprecated ### Removed ### Fixed + +* Compiling against wayland-protocols < 1.25 + + ### Security ### Contributors diff --git a/wayland.c b/wayland.c index fb41cf7d..05de79aa 100644 --- a/wayland.c +++ b/wayland.c @@ -706,6 +706,7 @@ xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) term_shutdown(term); } +#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION) static void xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, @@ -713,6 +714,7 @@ xdg_toplevel_configure_bounds(void *data, { /* TODO: ensure we don't pick a bigger size */ } +#endif #if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) static void @@ -742,7 +744,9 @@ xdg_toplevel_wm_capabilities(void *data, static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = &xdg_toplevel_configure, /*.close = */&xdg_toplevel_close, /* epoll-shim defines a macro ‘close’... */ +#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION) .configure_bounds = &xdg_toplevel_configure_bounds, +#endif #if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) .wm_capabilities = xdg_toplevel_wm_capabilities, #endif From 45803791cfd466a68968e22d38515474585301ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Aug 2022 16:13:25 +0200 Subject: [PATCH 28/81] input: ignore pointer motion events on unknown surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In some cases, the compositor sends a pointer enter event with a NULL surface. It’s unclear if this is a compositor bug, or a race (where the compositor sends an enter event on a CSD surface at the same time foot unmaps the CSDs). Regardless, this causes seat->mouse_focus to be unset, which triggers a crash in foot on the next pointer motion event. This patch does two things: a) log a warning when we receive a pointer event with a NULL surface b) ignore motion events where seat->mouse_focus is NULL --- CHANGELOG.md | 3 +++ input.c | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c10be50..5fa35a63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,9 @@ ### Fixed * Compiling against wayland-protocols < 1.25 +* Crash on buggy compositors (GNOME) that sometimes send pointer-enter + events with a NULL surface. Foot now ignores these events, and the + subsequent motion and leave events. ### Security diff --git a/input.c b/input.c index 847ff6af..50336679 100644 --- a/input.c +++ b/input.c @@ -1709,11 +1709,9 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { - xassert(surface != NULL); - xassert(serial != 0); - - if (surface == NULL) { + if (unlikely(surface == NULL)) { /* Seen on mutter-3.38 */ + LOG_WARN("compositor sent pointer_enter event with a NULL surface"); return; } @@ -1866,6 +1864,16 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, struct seat *seat = data; struct wayland *wayl = seat->wayl; struct terminal *term = seat->mouse_focus; + + if (unlikely(term == NULL)) { + /* Typically happens when the compositor sent a pointer enter + * event with a NULL surface - see wl_pointer_enter(). + * + * In this case, we never set seat->mouse_focus (since we + * can’t map the enter event to a specific window). */ + return; + } + struct wl_window *win = term->window; LOG_DBG("pointer_motion: pointer=%p, x=%d, y=%d", (void *)wl_pointer, From 157b64098a0832d66b5f8d206a14aa2c1342334b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Aug 2022 09:38:25 +0200 Subject: [PATCH 29/81] =?UTF-8?q?changelog:=20add=20new=20=E2=80=98unrelea?= =?UTF-8?q?sed=E2=80=99=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca337533..da7f779f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.13.0](#1-13-0) * [1.12.1](#1-12-1) * [1.12.0](#1-12-0) @@ -39,6 +40,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.13.0 ### Added From 1cf22846a0e333c61d5db50c260fd897143c6a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 8 Aug 2022 16:31:28 +0200 Subject: [PATCH 30/81] render: apply a dimmed overlay while in Unicode input mode --- CHANGELOG.md | 4 ++++ render.c | 33 ++++++++++++++++++++++++++++----- terminal.h | 7 ++++--- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da7f779f..e914e4d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,10 @@ ## Unreleased ### Added ### Changed + +* Window is now dimmed while in Unicode input mode. + + ### Deprecated ### Removed ### Fixed diff --git a/render.c b/render.c index ff82802e..82c9bd13 100644 --- a/render.c +++ b/render.c @@ -1466,10 +1466,21 @@ static void render_overlay(struct terminal *term) { struct wl_surf_subsurf *overlay = &term->window->overlay; + bool unicode_mode_active = false; + + /* Check if unicode mode is active on at least one seat focusing + * this terminal instance */ + tll_foreach(term->wl->seats, it) { + if (it->item.unicode_mode.active) { + unicode_mode_active = true; + break; + } + } const enum overlay_style style = term->is_searching ? OVERLAY_SEARCH : term->flash.active ? OVERLAY_FLASH : + unicode_mode_active ? OVERLAY_UNICODE_MODE : OVERLAY_NONE; if (likely(style == OVERLAY_NONE)) { @@ -1488,9 +1499,21 @@ render_overlay(struct terminal *term) pixman_image_set_clip_region32(buf->pix[0], NULL); - pixman_color_t color = style == OVERLAY_SEARCH - ? (pixman_color_t){0, 0, 0, 0x7fff} - : (pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff}; + pixman_color_t color; + + switch (style) { + case OVERLAY_NONE: + break; + + case OVERLAY_SEARCH: + case OVERLAY_UNICODE_MODE: + color = (pixman_color_t){0, 0, 0, 0x7fff}; + break; + + case OVERLAY_FLASH: + color = (pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff}; + break; + } /* Bounding rectangle of damaged areas - for wl_surface_damage_buffer() */ pixman_box32_t damage_bounds; @@ -1517,7 +1540,7 @@ render_overlay(struct terminal *term) * region that needs to be *cleared* in this frame. * * Finally, the union of the two “diff” regions above, gives - * us the total region affecte by a change, in either way. We + * us the total region affected by a change, in either way. We * use this as the bounding box for the * wl_surface_damage_buffer() call. */ @@ -1605,7 +1628,7 @@ render_overlay(struct terminal *term) else if (buf == term->render.last_overlay_buf && style == term->render.last_overlay_style) { - xassert(style == OVERLAY_FLASH); + xassert(style == OVERLAY_FLASH || style == OVERLAY_UNICODE_MODE); shm_did_not_use_buf(buf); return; } else { diff --git a/terminal.h b/terminal.h index bf6e74fe..0dde6330 100644 --- a/terminal.h +++ b/terminal.h @@ -289,9 +289,10 @@ enum term_surface { }; enum overlay_style { - OVERLAY_NONE = 0, - OVERLAY_SEARCH = 1, - OVERLAY_FLASH = 2, + OVERLAY_NONE, + OVERLAY_SEARCH, + OVERLAY_FLASH, + OVERLAY_UNICODE_MODE, }; typedef tll(struct ptmx_buffer) ptmx_buffer_list_t; From 5697348b461f4dec4daf67c62336bb357149baf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Aug 2022 16:12:36 +0200 Subject: [PATCH 31/81] wayland: #ifdef on XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION This enables us to compile against wayland-protocols < 1.25 --- CHANGELOG.md | 4 ++++ wayland.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e914e4d3..4c10be50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,10 @@ ### Deprecated ### Removed ### Fixed + +* Compiling against wayland-protocols < 1.25 + + ### Security ### Contributors diff --git a/wayland.c b/wayland.c index fb41cf7d..05de79aa 100644 --- a/wayland.c +++ b/wayland.c @@ -706,6 +706,7 @@ xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) term_shutdown(term); } +#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION) static void xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, @@ -713,6 +714,7 @@ xdg_toplevel_configure_bounds(void *data, { /* TODO: ensure we don't pick a bigger size */ } +#endif #if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) static void @@ -742,7 +744,9 @@ xdg_toplevel_wm_capabilities(void *data, static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = &xdg_toplevel_configure, /*.close = */&xdg_toplevel_close, /* epoll-shim defines a macro ‘close’... */ +#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION) .configure_bounds = &xdg_toplevel_configure_bounds, +#endif #if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) .wm_capabilities = xdg_toplevel_wm_capabilities, #endif From 20b8ca1601a8a80770110860ae70d4487c95081b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Aug 2022 16:13:25 +0200 Subject: [PATCH 32/81] input: ignore pointer motion events on unknown surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In some cases, the compositor sends a pointer enter event with a NULL surface. It’s unclear if this is a compositor bug, or a race (where the compositor sends an enter event on a CSD surface at the same time foot unmaps the CSDs). Regardless, this causes seat->mouse_focus to be unset, which triggers a crash in foot on the next pointer motion event. This patch does two things: a) log a warning when we receive a pointer event with a NULL surface b) ignore motion events where seat->mouse_focus is NULL --- CHANGELOG.md | 3 +++ input.c | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c10be50..5fa35a63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,9 @@ ### Fixed * Compiling against wayland-protocols < 1.25 +* Crash on buggy compositors (GNOME) that sometimes send pointer-enter + events with a NULL surface. Foot now ignores these events, and the + subsequent motion and leave events. ### Security diff --git a/input.c b/input.c index 847ff6af..50336679 100644 --- a/input.c +++ b/input.c @@ -1709,11 +1709,9 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { - xassert(surface != NULL); - xassert(serial != 0); - - if (surface == NULL) { + if (unlikely(surface == NULL)) { /* Seen on mutter-3.38 */ + LOG_WARN("compositor sent pointer_enter event with a NULL surface"); return; } @@ -1866,6 +1864,16 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, struct seat *seat = data; struct wayland *wayl = seat->wayl; struct terminal *term = seat->mouse_focus; + + if (unlikely(term == NULL)) { + /* Typically happens when the compositor sent a pointer enter + * event with a NULL surface - see wl_pointer_enter(). + * + * In this case, we never set seat->mouse_focus (since we + * can’t map the enter event to a specific window). */ + return; + } + struct wl_window *win = term->window; LOG_DBG("pointer_motion: pointer=%p, x=%d, y=%d", (void *)wl_pointer, From 65ecb7773739dd5dbfcabf905185ba9568f9c346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 20 Aug 2022 18:25:05 +0200 Subject: [PATCH 33/81] =?UTF-8?q?ci:=20codespell:=20ignore=20=E2=80=98zar?= =?UTF-8?q?=E2=80=99=20(user=20who=20contributed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .builds/alpine-x64.yml | 2 +- .gitlab-ci.yml | 2 +- .woodpecker.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.builds/alpine-x64.yml b/.builds/alpine-x64.yml index 8f341f3d..933c7121 100644 --- a/.builds/alpine-x64.yml +++ b/.builds/alpine-x64.yml @@ -49,4 +49,4 @@ tasks: - codespell: | pip install codespell cd foot - ~/.local/bin/codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd + ~/.local/bin/codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b2a459dc..28df1ccb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -109,4 +109,4 @@ codespell: - apk add python3 - apk add py3-pip - pip install codespell - - codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd + - codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd diff --git a/.woodpecker.yml b/.woodpecker.yml index 8493aa47..284da761 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -9,7 +9,7 @@ pipeline: - apk add python3 - apk add py3-pip - pip install codespell - - codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd + - codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd subprojects: when: From a0942f950df3b8826db4b1a3f149b7d0ed45adcf Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 19 Aug 2022 02:54:49 +0200 Subject: [PATCH 34/81] config: add setting for underline thickness This adds an "underline-thickness" setting to the "main" section, similar to the existing "underline-offset" setting. This setting is used to specify a custom height for regular (= non-cursor) underlines. Fixes #1136 --- CHANGELOG.md | 6 ++++++ config.c | 4 ++++ config.h | 1 + doc/foot.ini.5.scd | 12 ++++++++++++ foot.ini | 1 + render.c | 5 ++++- tests/test-config.c | 1 + 7 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa35a63..f0bf2624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,12 @@ ## Unreleased ### Added + +* Support for adjusting the thickness of regular underlines ([#1136][1136]). + +[1136]: https://codeberg.org/dnkl/foot/issues/1136 + + ### Changed * Window is now dimmed while in Unicode input mode. diff --git a/config.c b/config.c index fbeab3e6..0ee01373 100644 --- a/config.c +++ b/config.c @@ -904,6 +904,9 @@ parse_section_main(struct context *ctx) return true; } + else if (strcmp(key, "underline-thickness") == 0) + return value_to_pt_or_px(ctx, &conf->underline_thickness); + else if (strcmp(key, "dpi-aware") == 0) { if (strcmp(value, "auto") == 0) conf->dpi_aware = DPI_AWARE_AUTO; @@ -2833,6 +2836,7 @@ config_load(struct config *conf, const char *conf_path, .vertical_letter_offset = {.pt = 0, .px = 0}, .use_custom_underline_offset = false, .box_drawings_uses_font_glyphs = false, + .underline_thickness = {.pt = 0., .px = -1}, .dpi_aware = DPI_AWARE_AUTO, /* DPI-aware when scaling-factor == 1 */ .bell = { .urgent = false, diff --git a/config.h b/config.h index 70e182e3..f98e4d35 100644 --- a/config.h +++ b/config.h @@ -150,6 +150,7 @@ struct config { bool use_custom_underline_offset; struct pt_or_px underline_offset; + struct pt_or_px underline_thickness; bool box_drawings_uses_font_glyphs; bool can_shape_grapheme; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 995a7e33..0bc21786 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -132,6 +132,18 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*. Default: _unset_. +*underline-thickness* + Use a custom thickness (height) for underlines. The thickness is, by + default, in _points_. + + To specify a thickness in _pixels_, append *px*: + *underline-thickness=1px*. + + If left unset (the default), the thickness specified in the font is + used. + + Default: _unset_ + *box-drawings-uses-font-glyphs* Boolean. When disabled, foot generates box/line drawing characters itself. The are several advantages to doing this instead of using font glyphs: diff --git a/foot.ini b/foot.ini index 4c857296..0c19951e 100644 --- a/foot.ini +++ b/foot.ini @@ -17,6 +17,7 @@ # horizontal-letter-offset=0 # vertical-letter-offset=0 # underline-offset= +# underline-thickness= # box-drawings-uses-font-glyphs=no # dpi-aware=auto diff --git a/render.c b/render.c index 82c9bd13..ef698911 100644 --- a/render.c +++ b/render.c @@ -372,7 +372,10 @@ draw_underline(const struct terminal *term, pixman_image_t *pix, const struct fcft_font *font, const pixman_color_t *color, int x, int y, int cols) { - const int thickness = font->underline.thickness; + const int thickness = term->conf->underline_thickness.px >= 0 + ? term_pt_or_px_as_pixels( + term, &term->conf->underline_thickness) + : font->underline.thickness; /* Make sure the line isn't positioned below the cell */ const int y_ofs = min(underline_offset(term, font), diff --git a/tests/test-config.c b/tests/test-config.c index 930be6bf..837c5106 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -470,6 +470,7 @@ test_section_main(void) test_pt_or_px(&ctx, &parse_section_main, "letter-spacing", &conf.letter_spacing); test_pt_or_px(&ctx, &parse_section_main, "horizontal-letter-offset", &conf.horizontal_letter_offset); test_pt_or_px(&ctx, &parse_section_main, "vertical-letter-offset", &conf.vertical_letter_offset); + test_pt_or_px(&ctx, &parse_section_main, "underline-thickness", &conf.underline_thickness); test_uint16(&ctx, &parse_section_main, "resize-delay-ms", &conf.resize_delay_ms); test_uint16(&ctx, &parse_section_main, "workers", &conf.render_worker_count); From 86663522d5915c0fdb2b8b132bacc247fcfd1fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 17 Aug 2022 17:36:10 +0200 Subject: [PATCH 35/81] selection: never highlight selected, empty cells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a regression, where empty cells "between" non-empty cells (i.e. non-trailing empty cells) sometimes were incorrectly highlighted. The idea has always been to highlight exactly those cells that will get extracted when they’re copied. This means we’ve not highlighted trailing empty cells, but we _have_ highlighted other empty cells, since they are converted to spaces when copied (whereas trailing empty cells are skipped). fa2d9f86996467ba33cc381f810ea966a4323381 changed how a selection is updated. That is, which cells gets marked as selected, and which ones gets unmarked. Since we no longer walk all the cells, but instead work with pixman regions representing selection diffs, we can no longer determine (with certainty) which empty cells should be selected and which shouldn’t. Before this patch (but after fa2d9f86996467ba33cc381f810ea966a4323381), we sometimes incorrectly highlighted empty cells that should not have been highlighted. This happened when we’ve first (correctly) highlighted a region of empty cells, but then shrink the selection such that all those empty cells should be de-selected. This patch changes the selection behavior to *never* highlight empty cells. This fixes the regression, but also means slightly different behavior, compared to pre-fa2d9f86996467ba33cc381f810ea966a4323381. The other alternative is to always highlight all empty cells. But, since I personally like the fact that we’re skipping trailing empty cells, I prefer the approach taken by this patch. --- CHANGELOG.md | 4 ++++ selection.c | 23 +++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0bf2624..8e95c9df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,8 @@ ### Changed * Window is now dimmed while in Unicode input mode. +* Selected empty cells are **never** highlighted as being + selected. They used to be, when followed by non-empty cells. ### Deprecated @@ -61,6 +63,8 @@ * Crash on buggy compositors (GNOME) that sometimes send pointer-enter events with a NULL surface. Foot now ignores these events, and the subsequent motion and leave events. +* Regression: “random” selected empty cells being highlighted as + selected when they should not. ### Security diff --git a/selection.c b/selection.c index 8c3a8357..2fd62f8a 100644 --- a/selection.c +++ b/selection.c @@ -737,8 +737,27 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes, row->dirty = true; for (int c = box->x1, empty_count = 0; c < box->x2; c++) { - if (selected && row->cells[c].wc == 0) { - empty_count++; + if (row->cells[c].wc == 0) { + /* + * We used to highlight empty cells *if* they were + * followed by non-empty cell(s), since this + * corresponds to what gets extracted when the + * selection is copied (that is, empty cells + * “between” non-empty cells are converted to + * spaces). + * + * However, they way we handle selection updates + * (diffing the “old” selection area against the + * “new” one, using pixman regions), means we + * can’t correctly update the state of empty + * cells. The result is “random” empty cells being + * rendered as selected when they shouldn’t. + * + * “Fix” by *never* highlighting selected empty + * cells (they still get converted to spaces when + * copied, if followed by non-empty cells). + */ + /* empty_count++; */ continue; } From 3cf11bfea9e4787998c538bd312c456fd8287fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 17 Aug 2022 18:12:51 +0200 Subject: [PATCH 36/81] theme: change default color theme to solarized-dark-normal-brights This is my variant of the solarized theme, were only the first eight colors (i.e. the "normal") colors are from the solarized theme. The remaining eight (the "bright" colors) are brightened versions of the "normal" colors. This results in a theme that is usually in all applications, not just those that are "aware" that the terminal color theme is "solarized". --- CHANGELOG.md | 2 ++ config.c | 34 +++++++++++++++++----------------- doc/foot.ini.5.scd | 14 ++++++++------ foot.ini | 34 +++++++++++++++++----------------- 4 files changed, 44 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e95c9df..656c5a46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,8 @@ * Window is now dimmed while in Unicode input mode. * Selected empty cells are **never** highlighted as being selected. They used to be, when followed by non-empty cells. +* Default color theme from a variant of the Zenburn theme, to a + variant of the Solarized dark theme. ### Deprecated diff --git a/config.c b/config.c index 0ee01373..3ecb3db5 100644 --- a/config.c +++ b/config.c @@ -30,8 +30,8 @@ #include "xmalloc.h" #include "xsnprintf.h" -static const uint32_t default_foreground = 0xdcdccc; -static const uint32_t default_background = 0x111111; +static const uint32_t default_foreground = 0x839496; +static const uint32_t default_background = 0x002b36; static const size_t min_csd_border_width = 5; @@ -48,23 +48,23 @@ static const size_t min_csd_border_width = 5; static const uint32_t default_color_table[256] = { // Regular - 0x222222, - 0xcc9393, - 0x7f9f7f, - 0xd0bf8f, - 0x6ca0a3, - 0xdc8cc3, - 0x93e0e3, - 0xdcdccc, + 0x073642, + 0xdc322f, + 0x859900, + 0xb58900, + 0x268bd2, + 0xd33682, + 0x2aa198, + 0xeee8d5, // Bright - 0x666666, - 0xdca3a3, - 0xbfebbf, - 0xf0dfaf, - 0x8cd0d3, - 0xfcace3, - 0xb3ffff, + 0x08404f, + 0xe35f5c, + 0x9fb700, + 0xd9a400, + 0x4ba1de, + 0xdc619d, + 0x32c1b6, 0xffffff, // 6x6x6 RGB cube diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 0bc21786..a0cf69f5 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -507,21 +507,23 @@ can configure the background transparency with the _alpha_ option. *foreground* Default foreground color. This is the color used when no ANSI - color is being used. Default: _dcdccc_. + color is being used. Default: _839496_. *background* Default background color. This is the color used when no ANSI - color is being used. Default: _111111_. + color is being used. Default: _002b36_. *regular0*, *regular1* *..* *regular7* The eight basic ANSI colors (Black, Red, Green, Yellow, Blue, - Magenta, Cyan, White). Default: _222222_, _cc9393_, _7f9f7f_, _d0bf8f_, - _6ca0a3_, _dc8cc3_, _93e0e3_ and _dcdccc_ (a variant of the _zenburn_ theme). + Magenta, Cyan, White). Default: _073642_, _dc322f_, _859900_, + _b58900_, _268bd2_, _d33682_, _2aa198_ and _eee8d5_ (a variant of + the _solarized dark_ theme). *bright0*, *bright1* *..* *bright7* The eight bright ANSI colors (Black, Red, Green, Yellow, Blue, - Magenta, Cyan, White). Default: _666666_, _dca3a3_, _bfebbf_, _f0dfaf_, - _8cd0d3_, _fcace3_, _b3ffff_ and _ffffff_ (a variant of the _zenburn_ theme). + Magenta, Cyan, White). Default: _08404f_, _e35f5c_, _9fb700_, + _d9a400_, _4ba1de_, _dc619d_, _32c1b6_ and _ffffff_ (a variant of + the _solarized dark_ theme). *dim0*, *dim1* *..* *dim7* Custom colors to use with dimmed colors. Dimmed colors do not have diff --git a/foot.ini b/foot.ini index 0c19951e..926ed499 100644 --- a/foot.ini +++ b/foot.ini @@ -69,27 +69,27 @@ [colors] # alpha=1.0 -# foreground=dcdccc -# background=111111 +# background=002b36 +# foreground=839496 ## Normal/regular colors (color palette 0-7) -# regular0=222222 # black -# regular1=cc9393 # red -# regular2=7f9f7f # green -# regular3=d0bf8f # yellow -# regular4=6ca0a3 # blue -# regular5=dc8cc3 # magenta -# regular6=93e0e3 # cyan -# regular7=dcdccc # white +# regular0=073642 # black +# regular1=dc322f # red +# regular2=859900 # green +# regular3=b58900 # yellow +# regular4=268bd2 # blue +# regular5=d33682 # magenta +# regular6=2aa198 # cyan +# regular7=eee8d5 # white ## Bright colors (color palette 8-15) -# bright0=666666 # bright black -# bright1=dca3a3 # bright red -# bright2=bfebbf # bright green -# bright3=f0dfaf # bright yellow -# bright4=8cd0d3 # bright blue -# bright5=fcace3 # bright magenta -# bright6=b3ffff # bright cyan +# bright0=08404f # bright black +# bright1=e35f5c # bright red +# bright2=9fb700 # bright green +# bright3=d9a400 # bright yellow +# bright4=4ba1de # bright blue +# bright5=dc619d # bright magenta +# bright6=32c1b6 # bright cyan # bright7=ffffff # bright white ## dimmed colors (see foot.ini(5) man page) From 21ab16239d87fe8fb4d7651b4bccbe1f611df620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 22 Aug 2022 20:14:06 +0200 Subject: [PATCH 37/81] selection: once again highlight non-trailing empty cells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foot 1.13.0 introduced a regression where non-trailing empty cells were highlighted inconsistently (cells that shouldn’t be highlighted, were, seemingly at random). 86663522d5915c0fdb2b8b132bacc247fcfd1fb8 “fixed” this by never highlighting *any* empty cells. This meant the behavior, compared to foot 1.12 and earlier, changed. In foot 1.12 and older versions, non-trailing empty cells were highlighted, as long as the selection covered at least one of the trailing non-empty cells. This patch restores that behavior. To understand how this works, lets first take a look at how selection works: When a selection is made, and updated (i.e. the mouse is dragged, or the selection is extended through RMB etc), we need to (un)tag and dirty the cells that are a) newly selected, or b) newly deselected. That is, we look at the diff between the “old” and the “new” selection, and only update those cells. This is for performance reasons: iterating the entire selection is not feasible with large selections. However, it also means we cannot reason about empty cells; we simply don’t know if an empty cells is a trailing empty cell, or a non-trailing one. Then, when we render a frame, we iterate all the *visible* and *selected* cells, once again tagging them as selected (this is needed since a selected cell might have lost its selected tag if the cell was written to, by the client application, after the selection was made). At this point, we *can* reason about empty cells. So, to restore the highlighting behavior to that of foot 1.12, we do this: When working with the selection diffs when a selection is updated, we don’t special case empty cells at all. Thus, all empty cells covered by the selection is highlighted, and dirtied. But, when rendering the frame, we _do_ special case them. The only difference (compared to foot 1.12) is that we *must* explicitly *clear* the selection tag, and dirty the empty cells. This is to ensure the empty cells that were incorrectly highlighted by the selection update algorithm, isn’t rendered as that. This does have a slight performance impact, as empty cells are now always re-rendered. The impact should however be small. --- CHANGELOG.md | 2 -- selection.c | 44 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 656c5a46..2eddc3bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,8 +51,6 @@ ### Changed * Window is now dimmed while in Unicode input mode. -* Selected empty cells are **never** highlighted as being - selected. They used to be, when followed by non-empty cells. * Default color theme from a variant of the Zenburn theme, to a variant of the Solarized dark theme. diff --git a/selection.c b/selection.c index 2fd62f8a..5b65310c 100644 --- a/selection.c +++ b/selection.c @@ -713,7 +713,8 @@ pixman_region_for_coords(const struct terminal *term, static void mark_selected_region(struct terminal *term, pixman_box32_t *boxes, - size_t count, bool selected, bool dirty_cells) + size_t count, bool selected, bool dirty_cells, + bool highlight_empty) { for (size_t i = 0; i < count; i++) { const pixman_box32_t *box = &boxes[i]; @@ -737,7 +738,9 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes, row->dirty = true; for (int c = box->x1, empty_count = 0; c < box->x2; c++) { - if (row->cells[c].wc == 0) { + struct cell *cell = &row->cells[c]; + + if (cell->wc == 0 && !highlight_empty) { /* * We used to highlight empty cells *if* they were * followed by non-empty cell(s), since this @@ -757,7 +760,36 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes, * cells (they still get converted to spaces when * copied, if followed by non-empty cells). */ - /* empty_count++; */ + empty_count++; + + /* + * When the selection is *modified*, empty cells + * are treated just like non-empty cells; they are + * marked as selected, and dirtied. + * + * This is due to how the algorithm for updating + * the selection works; it uses regions to + * calculate the difference between the “old” and + * the “new” selection. This makes it impossible + * to tell if an empty cell is a *trailing* empty + * cell (that should not be highlighted), or an + * empty cells between non-empty cells (that + * *should* be highlighted). + * + * Then, when a frame is rendered, we loop the + * *visibible* cells that belong to the + * selection. At this point, we *can* tell if an + * empty cell is trailing or not. + * + * So, what we need to do is check if a + * ‘selected’, and empty cell has been marked as + * selected, temporarily unmark (forcing it dirty, + * to ensure it gets re-rendered). If it is *not* + * a trailing empty cell, it will get re-tagged as + * selected in the for-loop below. + */ + cell->attrs.clean = false; + cell->attrs.selected = false; continue; } @@ -810,10 +842,10 @@ selection_modify(struct terminal *term, struct coord start, struct coord end) pixman_box32_t *boxes = NULL; boxes = pixman_region32_rectangles(&no_longer_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, false, true); + mark_selected_region(term, boxes, n_rects, false, true, true); boxes = pixman_region32_rectangles(&newly_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, true, true); + mark_selected_region(term, boxes, n_rects, true, true, true); pixman_region32_fini(&newly_selected); pixman_region32_fini(&no_longer_selected); @@ -1078,7 +1110,7 @@ selection_dirty_cells(struct terminal *term) int n_rects = -1; pixman_box32_t *boxes = pixman_region32_rectangles(&visible_and_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, true, false); + mark_selected_region(term, boxes, n_rects, true, false, false); pixman_region32_fini(&visible_and_selected); pixman_region32_fini(&view); From 4f06d413e246747bbe2ce9a84743963e2690b8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 23 Aug 2022 16:38:39 +0200 Subject: [PATCH 38/81] ci (sr.ht): pull directly from git.sr.ht --- .builds/alpine-x64.yml | 2 +- .builds/alpine-x86.yml.disabled | 2 +- .builds/freebsd-x64.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.builds/alpine-x64.yml b/.builds/alpine-x64.yml index 933c7121..2e2ec2c4 100644 --- a/.builds/alpine-x64.yml +++ b/.builds/alpine-x64.yml @@ -24,7 +24,7 @@ packages: - font-noto-emoji sources: - - https://codeberg.org/dnkl/foot + - https://git.sr.ht/~dnkl/foot # triggers: # - action: email diff --git a/.builds/alpine-x86.yml.disabled b/.builds/alpine-x86.yml.disabled index 22a9e637..6d790227 100644 --- a/.builds/alpine-x86.yml.disabled +++ b/.builds/alpine-x86.yml.disabled @@ -23,7 +23,7 @@ packages: - font-noto-emoji sources: - - https://codeberg.org/dnkl/foot + - https://git.sr.ht/~dnkl/foot # triggers: # - action: email diff --git a/.builds/freebsd-x64.yml b/.builds/freebsd-x64.yml index 89803a6e..9642f96d 100644 --- a/.builds/freebsd-x64.yml +++ b/.builds/freebsd-x64.yml @@ -19,7 +19,7 @@ packages: - noto-emoji sources: - - https://codeberg.org/dnkl/foot + - https://git.sr.ht/~dnkl/foot # triggers: # - action: email From db2737b96a2cf82b3c1ced2549e4e795ffe13b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Aug 2022 17:48:00 +0200 Subject: [PATCH 39/81] selection: restore <= 1.12 behavior in block selection wrt empty cells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That is, highlight empty cells, regardless of whether they’re trailing or not. --- selection.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selection.c b/selection.c index 5b65310c..5bf72e62 100644 --- a/selection.c +++ b/selection.c @@ -1110,7 +1110,9 @@ selection_dirty_cells(struct terminal *term) int n_rects = -1; pixman_box32_t *boxes = pixman_region32_rectangles(&visible_and_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, true, false, false); + + const bool highlight_empty = term->selection.kind == SELECTION_BLOCK; + mark_selected_region(term, boxes, n_rects, true, false, highlight_empty); pixman_region32_fini(&visible_and_selected); pixman_region32_fini(&view); From d5df86f7853a47aa2de4aea7f4b5a84e918ea433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Aug 2022 21:07:20 +0200 Subject: [PATCH 40/81] selection: mark_selected_region(): use an enum to encode how the cells are to be updated --- selection.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/selection.c b/selection.c index 5bf72e62..c94686e1 100644 --- a/selection.c +++ b/selection.c @@ -711,11 +711,26 @@ pixman_region_for_coords(const struct terminal *term, } } +enum mark_selection_variant { + MARK_SELECTION_MARK_AND_DIRTY, + MARK_SELECTION_UNMARK_AND_DIRTY, + MARK_SELECTION_MARK_FOR_RENDER, +}; + static void mark_selected_region(struct terminal *term, pixman_box32_t *boxes, - size_t count, bool selected, bool dirty_cells, - bool highlight_empty) + size_t count, enum mark_selection_variant mark_variant) { + const bool selected = + mark_variant == MARK_SELECTION_MARK_AND_DIRTY || + mark_variant == MARK_SELECTION_MARK_FOR_RENDER; + const bool dirty_cells = + mark_variant == MARK_SELECTION_MARK_AND_DIRTY || + mark_variant == MARK_SELECTION_UNMARK_AND_DIRTY; + const bool highlight_empty = + mark_variant != MARK_SELECTION_MARK_FOR_RENDER || + term->selection.kind == SELECTION_BLOCK; + for (size_t i = 0; i < count; i++) { const pixman_box32_t *box = &boxes[i]; @@ -842,10 +857,10 @@ selection_modify(struct terminal *term, struct coord start, struct coord end) pixman_box32_t *boxes = NULL; boxes = pixman_region32_rectangles(&no_longer_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, false, true, true); + mark_selected_region(term, boxes, n_rects, MARK_SELECTION_UNMARK_AND_DIRTY); boxes = pixman_region32_rectangles(&newly_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, true, true, true); + mark_selected_region(term, boxes, n_rects, MARK_SELECTION_MARK_AND_DIRTY); pixman_region32_fini(&newly_selected); pixman_region32_fini(&no_longer_selected); @@ -1110,9 +1125,7 @@ selection_dirty_cells(struct terminal *term) int n_rects = -1; pixman_box32_t *boxes = pixman_region32_rectangles(&visible_and_selected, &n_rects); - - const bool highlight_empty = term->selection.kind == SELECTION_BLOCK; - mark_selected_region(term, boxes, n_rects, true, false, highlight_empty); + mark_selected_region(term, boxes, n_rects, MARK_SELECTION_MARK_FOR_RENDER); pixman_region32_fini(&visible_and_selected); pixman_region32_fini(&view); From 13281f327bcbe4a4799bea2122b77829f0004774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 29 Aug 2022 20:46:19 +0200 Subject: [PATCH 41/81] =?UTF-8?q?grid:=20when=20setting=20the=20new=20view?= =?UTF-8?q?port,=20ensure=20it=E2=80=99s=20correctly=20bounded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do this by using scrollback relative coordinates, and ensure the new viewport is not larger than (grid_rows - screen_rows), as that would mean the viewport crosses the scrollback wrap-around. --- grid.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/grid.c b/grid.c index f40803e1..f229effa 100644 --- a/grid.c +++ b/grid.c @@ -1016,23 +1016,6 @@ grid_resize_and_reflow( new_grid[idx] = grid_row_alloc(new_cols, true); } - grid->view = view_follows ? grid->offset : viewport.row; - - /* If enlarging the window, the old viewport may be too far down, - * with unallocated rows. Make sure this cannot happen */ - while (true) { - int idx = (grid->view + new_screen_rows - 1) & (new_rows - 1); - if (new_grid[idx] != NULL) - break; - grid->view--; - if (grid->view < 0) - grid->view += new_rows; - } - for (size_t r = 0; r < new_screen_rows; r++) { - int UNUSED idx = (grid->view + r) & (new_rows - 1); - xassert(new_grid[idx] != NULL); - } - /* Free old grid (rows already free:d) */ free(grid->rows); @@ -1040,6 +1023,17 @@ grid_resize_and_reflow( grid->num_rows = new_rows; grid->num_cols = new_cols; + /* + * Set new viewport, making sure it’s not too far down. + * + * This is done by using scrollback-start relative cooardinates, + * and bounding the new viewport to (grid_rows - screen_rows). + */ + int sb_view = grid_row_abs_to_sb( + grid, new_screen_rows, view_follows ? grid->offset : viewport.row); + grid->view = grid_row_sb_to_abs( + grid, new_screen_rows, min(sb_view, new_rows - new_screen_rows)); + /* Convert absolute coordinates to screen relative */ cursor.row -= grid->offset; while (cursor.row < 0) From 5c86358cd1f830c01333326423f862879f9cd6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 29 Aug 2022 20:47:33 +0200 Subject: [PATCH 42/81] =?UTF-8?q?grid:=20reflow:=20don=E2=80=99t=20line-wr?= =?UTF-8?q?ap=20the=20last=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this patch, we would line-wrap the last row, just like any other row, and then afterwards try to reverse this, by adjusting the offset and free:ing and NULL:ing the "last row". The problem with this is if the scrollback is full. In this case, the row we’re freeing is the first row in the scrollback history. This means we’ll crash as soon as the viewport is moved to the top of the scrollback. The fix is fairly, simple. Skip the post-processing logic, and instead detect when we’re line-wrapping the last row, and skip the call to line_wrap(). This way, the last row in the new grid corresponds to the last row in the old grid. --- grid.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/grid.c b/grid.c index f229effa..bbac3395 100644 --- a/grid.c +++ b/grid.c @@ -935,13 +935,14 @@ grid_resize_and_reflow( start += cols; } - if (old_row->linebreak) { /* Erase the remaining cells */ memset(&new_row->cells[new_col_idx], 0, (new_cols - new_col_idx) * sizeof(new_row->cells[0])); new_row->linebreak = true; - line_wrap(); + + if (r + 1 < old_rows) + line_wrap(); } grid_row_free(old_grid[old_row_idx]); @@ -985,25 +986,6 @@ grid_resize_and_reflow( /* Set offset such that the last reflowed row is at the bottom */ grid->offset = new_row_idx - new_screen_rows + 1; - if (new_col_idx == 0) { - int next_to_last_new_row_idx = new_row_idx - 1; - next_to_last_new_row_idx += new_rows; - next_to_last_new_row_idx &= new_rows - 1; - - const struct row *next_to_last_row = new_grid[next_to_last_new_row_idx]; - if (next_to_last_row != NULL && next_to_last_row->linebreak) { - /* - * The next to last row is actually the *last* row. But we - * ended the reflow with a line-break, causing an empty - * row to be inserted at the bottom. Undo this. - */ - /* TODO: can we detect this in the reflow loop above instead? */ - grid->offset--; - grid_row_free(new_grid[new_row_idx]); - new_grid[new_row_idx] = NULL; - } - } - while (grid->offset < 0) grid->offset += new_rows; while (new_grid[grid->offset] == NULL) From 454c82f0f59fea5307fbb570954db26f05ddbd09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 29 Aug 2022 21:03:21 +0200 Subject: [PATCH 43/81] =?UTF-8?q?grid:=20reflow:=20assert=20there=20aren?= =?UTF-8?q?=E2=80=99t=20any=20open=20URIs=20on=20the=20last=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grid.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/grid.c b/grid.c index bbac3395..7bfef5cb 100644 --- a/grid.c +++ b/grid.c @@ -943,6 +943,21 @@ grid_resize_and_reflow( if (r + 1 < old_rows) line_wrap(); + else if (new_row->extra != NULL && + new_row->extra->uri_ranges.count > 0) + { + /* + * line_wrap() "closes" still-open URIs. Since this is + * the *last* row, and since we’re line-breaking due + * to a hard line-break (rather than running out of + * cells in the "new_row"), there shouldn’t be an open + * URI (it would have been closed when we reached the + * end of the URI while reflowing the last "old" + * row). + */ + uint32_t last_idx = new_row->extra->uri_ranges.count - 1; + xassert(new_row->extra->uri_ranges.v[last_idx].end >= 0); + } } grid_row_free(old_grid[old_row_idx]); From 524474728a5c9a1228500ab39b4974469b156c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 29 Aug 2022 21:04:56 +0200 Subject: [PATCH 44/81] changelog: crash when resizing window, or scrolling --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eddc3bf..24403770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,10 @@ subsequent motion and leave events. * Regression: “random” selected empty cells being highlighted as selected when they should not. +* Crash when either resizing the terminal window, or scrolling in the + scrollback history ([#1074][1074]) + +[1074]: https://codeberg.org/dnkl/foot/pulls/1074 ### Security From c753cf8f45e466165d64a173497ca1f6a3efd4a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Aug 2022 17:48:04 +0200 Subject: [PATCH 45/81] url-mode: connect osc-8 links only when both ID and URI matches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this, two OSC-8 links with a matching ID would be connected even if their URIs weren’t the same. This is against the spec: The same id is only used for connecting character cells whose URIs is also the same. Character cells pointing to different URIs should never be underlined together when hovering over. https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#hover-underlining-and-the-id-parameter --- CHANGELOG.md | 2 ++ url-mode.c | 16 ++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24403770..bcaac033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,8 @@ selected when they should not. * Crash when either resizing the terminal window, or scrolling in the scrollback history ([#1074][1074]) +* OSC-8 URLs with matching IDs, but mismatching URIs being incorrectly + connected. [1074]: https://codeberg.org/dnkl/foot/pulls/1074 diff --git a/url-mode.c b/url-mode.c index 538b60f0..6fa16623 100644 --- a/url-mode.c +++ b/url-mode.c @@ -677,18 +677,23 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls) if (count == 0) return; - uint64_t seen_ids[count]; char32_t *combos[count]; generate_key_combos(conf, count, combos); size_t combo_idx = 0; - size_t id_idx = 0; tll_foreach(*urls, it) { bool id_already_seen = false; - for (size_t i = 0; i < id_idx; i++) { - if (it->item.id == seen_ids[i]) { + /* Look for already processed URLs where both the URI and the + * ID matches */ + tll_foreach(*urls, it2) { + if (&it->item == &it2->item) + break; + + if (it->item.id == it2->item.id && + strcmp(it->item.url, it2->item.url) == 0) + { id_already_seen = true; break; } @@ -696,7 +701,6 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls) if (id_already_seen) continue; - seen_ids[id_idx++] = it->item.id; /* * Scan previous URLs, and check if *this* URL matches any of @@ -730,7 +734,7 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls) char *key = ac32tombs(it->item.key); xassert(key != NULL); - LOG_DBG("URL: %s (%s)", it->item.url, key); + LOG_DBG("URL: %s (key=%s, id=%"PRIu64")", it->item.url, key, it->item.id); free(key); } #endif From ccef435736a5364c63cfe6c1d64e7c4f5a66a0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 20 Aug 2022 18:25:05 +0200 Subject: [PATCH 46/81] =?UTF-8?q?ci:=20codespell:=20ignore=20=E2=80=98zar?= =?UTF-8?q?=E2=80=99=20(user=20who=20contributed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .builds/alpine-x64.yml | 2 +- .gitlab-ci.yml | 2 +- .woodpecker.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.builds/alpine-x64.yml b/.builds/alpine-x64.yml index 8f341f3d..933c7121 100644 --- a/.builds/alpine-x64.yml +++ b/.builds/alpine-x64.yml @@ -49,4 +49,4 @@ tasks: - codespell: | pip install codespell cd foot - ~/.local/bin/codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd + ~/.local/bin/codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b2a459dc..28df1ccb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -109,4 +109,4 @@ codespell: - apk add python3 - apk add py3-pip - pip install codespell - - codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd + - codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd diff --git a/.woodpecker.yml b/.woodpecker.yml index 8493aa47..284da761 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -9,7 +9,7 @@ pipeline: - apk add python3 - apk add py3-pip - pip install codespell - - codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd + - codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd subprojects: when: From 736babcdbc10a20e9d620638adc2fec230a81e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 17 Aug 2022 17:36:10 +0200 Subject: [PATCH 47/81] selection: never highlight selected, empty cells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a regression, where empty cells "between" non-empty cells (i.e. non-trailing empty cells) sometimes were incorrectly highlighted. The idea has always been to highlight exactly those cells that will get extracted when they’re copied. This means we’ve not highlighted trailing empty cells, but we _have_ highlighted other empty cells, since they are converted to spaces when copied (whereas trailing empty cells are skipped). fa2d9f86996467ba33cc381f810ea966a4323381 changed how a selection is updated. That is, which cells gets marked as selected, and which ones gets unmarked. Since we no longer walk all the cells, but instead work with pixman regions representing selection diffs, we can no longer determine (with certainty) which empty cells should be selected and which shouldn’t. Before this patch (but after fa2d9f86996467ba33cc381f810ea966a4323381), we sometimes incorrectly highlighted empty cells that should not have been highlighted. This happened when we’ve first (correctly) highlighted a region of empty cells, but then shrink the selection such that all those empty cells should be de-selected. This patch changes the selection behavior to *never* highlight empty cells. This fixes the regression, but also means slightly different behavior, compared to pre-fa2d9f86996467ba33cc381f810ea966a4323381. The other alternative is to always highlight all empty cells. But, since I personally like the fact that we’re skipping trailing empty cells, I prefer the approach taken by this patch. --- CHANGELOG.md | 4 ++++ selection.c | 23 +++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa35a63..be617671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,8 @@ ### Changed * Window is now dimmed while in Unicode input mode. +* Selected empty cells are **never** highlighted as being + selected. They used to be, when followed by non-empty cells. ### Deprecated @@ -55,6 +57,8 @@ * Crash on buggy compositors (GNOME) that sometimes send pointer-enter events with a NULL surface. Foot now ignores these events, and the subsequent motion and leave events. +* Regression: “random” selected empty cells being highlighted as + selected when they should not. ### Security diff --git a/selection.c b/selection.c index 8c3a8357..2fd62f8a 100644 --- a/selection.c +++ b/selection.c @@ -737,8 +737,27 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes, row->dirty = true; for (int c = box->x1, empty_count = 0; c < box->x2; c++) { - if (selected && row->cells[c].wc == 0) { - empty_count++; + if (row->cells[c].wc == 0) { + /* + * We used to highlight empty cells *if* they were + * followed by non-empty cell(s), since this + * corresponds to what gets extracted when the + * selection is copied (that is, empty cells + * “between” non-empty cells are converted to + * spaces). + * + * However, they way we handle selection updates + * (diffing the “old” selection area against the + * “new” one, using pixman regions), means we + * can’t correctly update the state of empty + * cells. The result is “random” empty cells being + * rendered as selected when they shouldn’t. + * + * “Fix” by *never* highlighting selected empty + * cells (they still get converted to spaces when + * copied, if followed by non-empty cells). + */ + /* empty_count++; */ continue; } From 6f1cf6fc56b906fb050009f587a5440188a69b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 22 Aug 2022 20:14:06 +0200 Subject: [PATCH 48/81] selection: once again highlight non-trailing empty cells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foot 1.13.0 introduced a regression where non-trailing empty cells were highlighted inconsistently (cells that shouldn’t be highlighted, were, seemingly at random). 86663522d5915c0fdb2b8b132bacc247fcfd1fb8 “fixed” this by never highlighting *any* empty cells. This meant the behavior, compared to foot 1.12 and earlier, changed. In foot 1.12 and older versions, non-trailing empty cells were highlighted, as long as the selection covered at least one of the trailing non-empty cells. This patch restores that behavior. To understand how this works, lets first take a look at how selection works: When a selection is made, and updated (i.e. the mouse is dragged, or the selection is extended through RMB etc), we need to (un)tag and dirty the cells that are a) newly selected, or b) newly deselected. That is, we look at the diff between the “old” and the “new” selection, and only update those cells. This is for performance reasons: iterating the entire selection is not feasible with large selections. However, it also means we cannot reason about empty cells; we simply don’t know if an empty cells is a trailing empty cell, or a non-trailing one. Then, when we render a frame, we iterate all the *visible* and *selected* cells, once again tagging them as selected (this is needed since a selected cell might have lost its selected tag if the cell was written to, by the client application, after the selection was made). At this point, we *can* reason about empty cells. So, to restore the highlighting behavior to that of foot 1.12, we do this: When working with the selection diffs when a selection is updated, we don’t special case empty cells at all. Thus, all empty cells covered by the selection is highlighted, and dirtied. But, when rendering the frame, we _do_ special case them. The only difference (compared to foot 1.12) is that we *must* explicitly *clear* the selection tag, and dirty the empty cells. This is to ensure the empty cells that were incorrectly highlighted by the selection update algorithm, isn’t rendered as that. This does have a slight performance impact, as empty cells are now always re-rendered. The impact should however be small. --- CHANGELOG.md | 2 -- selection.c | 44 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be617671..0ba3aa6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,8 +45,6 @@ ### Changed * Window is now dimmed while in Unicode input mode. -* Selected empty cells are **never** highlighted as being - selected. They used to be, when followed by non-empty cells. ### Deprecated diff --git a/selection.c b/selection.c index 2fd62f8a..5b65310c 100644 --- a/selection.c +++ b/selection.c @@ -713,7 +713,8 @@ pixman_region_for_coords(const struct terminal *term, static void mark_selected_region(struct terminal *term, pixman_box32_t *boxes, - size_t count, bool selected, bool dirty_cells) + size_t count, bool selected, bool dirty_cells, + bool highlight_empty) { for (size_t i = 0; i < count; i++) { const pixman_box32_t *box = &boxes[i]; @@ -737,7 +738,9 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes, row->dirty = true; for (int c = box->x1, empty_count = 0; c < box->x2; c++) { - if (row->cells[c].wc == 0) { + struct cell *cell = &row->cells[c]; + + if (cell->wc == 0 && !highlight_empty) { /* * We used to highlight empty cells *if* they were * followed by non-empty cell(s), since this @@ -757,7 +760,36 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes, * cells (they still get converted to spaces when * copied, if followed by non-empty cells). */ - /* empty_count++; */ + empty_count++; + + /* + * When the selection is *modified*, empty cells + * are treated just like non-empty cells; they are + * marked as selected, and dirtied. + * + * This is due to how the algorithm for updating + * the selection works; it uses regions to + * calculate the difference between the “old” and + * the “new” selection. This makes it impossible + * to tell if an empty cell is a *trailing* empty + * cell (that should not be highlighted), or an + * empty cells between non-empty cells (that + * *should* be highlighted). + * + * Then, when a frame is rendered, we loop the + * *visibible* cells that belong to the + * selection. At this point, we *can* tell if an + * empty cell is trailing or not. + * + * So, what we need to do is check if a + * ‘selected’, and empty cell has been marked as + * selected, temporarily unmark (forcing it dirty, + * to ensure it gets re-rendered). If it is *not* + * a trailing empty cell, it will get re-tagged as + * selected in the for-loop below. + */ + cell->attrs.clean = false; + cell->attrs.selected = false; continue; } @@ -810,10 +842,10 @@ selection_modify(struct terminal *term, struct coord start, struct coord end) pixman_box32_t *boxes = NULL; boxes = pixman_region32_rectangles(&no_longer_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, false, true); + mark_selected_region(term, boxes, n_rects, false, true, true); boxes = pixman_region32_rectangles(&newly_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, true, true); + mark_selected_region(term, boxes, n_rects, true, true, true); pixman_region32_fini(&newly_selected); pixman_region32_fini(&no_longer_selected); @@ -1078,7 +1110,7 @@ selection_dirty_cells(struct terminal *term) int n_rects = -1; pixman_box32_t *boxes = pixman_region32_rectangles(&visible_and_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, true, false); + mark_selected_region(term, boxes, n_rects, true, false, false); pixman_region32_fini(&visible_and_selected); pixman_region32_fini(&view); From 03d3887e6bb964e9599ce84f5e1548df224371fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 23 Aug 2022 16:38:39 +0200 Subject: [PATCH 49/81] ci (sr.ht): pull directly from git.sr.ht --- .builds/alpine-x64.yml | 2 +- .builds/alpine-x86.yml.disabled | 2 +- .builds/freebsd-x64.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.builds/alpine-x64.yml b/.builds/alpine-x64.yml index 933c7121..2e2ec2c4 100644 --- a/.builds/alpine-x64.yml +++ b/.builds/alpine-x64.yml @@ -24,7 +24,7 @@ packages: - font-noto-emoji sources: - - https://codeberg.org/dnkl/foot + - https://git.sr.ht/~dnkl/foot # triggers: # - action: email diff --git a/.builds/alpine-x86.yml.disabled b/.builds/alpine-x86.yml.disabled index 22a9e637..6d790227 100644 --- a/.builds/alpine-x86.yml.disabled +++ b/.builds/alpine-x86.yml.disabled @@ -23,7 +23,7 @@ packages: - font-noto-emoji sources: - - https://codeberg.org/dnkl/foot + - https://git.sr.ht/~dnkl/foot # triggers: # - action: email diff --git a/.builds/freebsd-x64.yml b/.builds/freebsd-x64.yml index 89803a6e..9642f96d 100644 --- a/.builds/freebsd-x64.yml +++ b/.builds/freebsd-x64.yml @@ -19,7 +19,7 @@ packages: - noto-emoji sources: - - https://codeberg.org/dnkl/foot + - https://git.sr.ht/~dnkl/foot # triggers: # - action: email From 2354298ecac8e08ecce8ff8b208cda0f0c6f5257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Aug 2022 17:48:00 +0200 Subject: [PATCH 50/81] selection: restore <= 1.12 behavior in block selection wrt empty cells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That is, highlight empty cells, regardless of whether they’re trailing or not. --- selection.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selection.c b/selection.c index 5b65310c..5bf72e62 100644 --- a/selection.c +++ b/selection.c @@ -1110,7 +1110,9 @@ selection_dirty_cells(struct terminal *term) int n_rects = -1; pixman_box32_t *boxes = pixman_region32_rectangles(&visible_and_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, true, false, false); + + const bool highlight_empty = term->selection.kind == SELECTION_BLOCK; + mark_selected_region(term, boxes, n_rects, true, false, highlight_empty); pixman_region32_fini(&visible_and_selected); pixman_region32_fini(&view); From d810e4fc8eb3123e427f1453b269ced9608222c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Aug 2022 21:07:20 +0200 Subject: [PATCH 51/81] selection: mark_selected_region(): use an enum to encode how the cells are to be updated --- selection.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/selection.c b/selection.c index 5bf72e62..c94686e1 100644 --- a/selection.c +++ b/selection.c @@ -711,11 +711,26 @@ pixman_region_for_coords(const struct terminal *term, } } +enum mark_selection_variant { + MARK_SELECTION_MARK_AND_DIRTY, + MARK_SELECTION_UNMARK_AND_DIRTY, + MARK_SELECTION_MARK_FOR_RENDER, +}; + static void mark_selected_region(struct terminal *term, pixman_box32_t *boxes, - size_t count, bool selected, bool dirty_cells, - bool highlight_empty) + size_t count, enum mark_selection_variant mark_variant) { + const bool selected = + mark_variant == MARK_SELECTION_MARK_AND_DIRTY || + mark_variant == MARK_SELECTION_MARK_FOR_RENDER; + const bool dirty_cells = + mark_variant == MARK_SELECTION_MARK_AND_DIRTY || + mark_variant == MARK_SELECTION_UNMARK_AND_DIRTY; + const bool highlight_empty = + mark_variant != MARK_SELECTION_MARK_FOR_RENDER || + term->selection.kind == SELECTION_BLOCK; + for (size_t i = 0; i < count; i++) { const pixman_box32_t *box = &boxes[i]; @@ -842,10 +857,10 @@ selection_modify(struct terminal *term, struct coord start, struct coord end) pixman_box32_t *boxes = NULL; boxes = pixman_region32_rectangles(&no_longer_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, false, true, true); + mark_selected_region(term, boxes, n_rects, MARK_SELECTION_UNMARK_AND_DIRTY); boxes = pixman_region32_rectangles(&newly_selected, &n_rects); - mark_selected_region(term, boxes, n_rects, true, true, true); + mark_selected_region(term, boxes, n_rects, MARK_SELECTION_MARK_AND_DIRTY); pixman_region32_fini(&newly_selected); pixman_region32_fini(&no_longer_selected); @@ -1110,9 +1125,7 @@ selection_dirty_cells(struct terminal *term) int n_rects = -1; pixman_box32_t *boxes = pixman_region32_rectangles(&visible_and_selected, &n_rects); - - const bool highlight_empty = term->selection.kind == SELECTION_BLOCK; - mark_selected_region(term, boxes, n_rects, true, false, highlight_empty); + mark_selected_region(term, boxes, n_rects, MARK_SELECTION_MARK_FOR_RENDER); pixman_region32_fini(&visible_and_selected); pixman_region32_fini(&view); From 649d56a4e5142c9ab8ea24f4d1acbd4a49d534f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 29 Aug 2022 20:46:19 +0200 Subject: [PATCH 52/81] =?UTF-8?q?grid:=20when=20setting=20the=20new=20view?= =?UTF-8?q?port,=20ensure=20it=E2=80=99s=20correctly=20bounded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do this by using scrollback relative coordinates, and ensure the new viewport is not larger than (grid_rows - screen_rows), as that would mean the viewport crosses the scrollback wrap-around. --- grid.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/grid.c b/grid.c index f40803e1..f229effa 100644 --- a/grid.c +++ b/grid.c @@ -1016,23 +1016,6 @@ grid_resize_and_reflow( new_grid[idx] = grid_row_alloc(new_cols, true); } - grid->view = view_follows ? grid->offset : viewport.row; - - /* If enlarging the window, the old viewport may be too far down, - * with unallocated rows. Make sure this cannot happen */ - while (true) { - int idx = (grid->view + new_screen_rows - 1) & (new_rows - 1); - if (new_grid[idx] != NULL) - break; - grid->view--; - if (grid->view < 0) - grid->view += new_rows; - } - for (size_t r = 0; r < new_screen_rows; r++) { - int UNUSED idx = (grid->view + r) & (new_rows - 1); - xassert(new_grid[idx] != NULL); - } - /* Free old grid (rows already free:d) */ free(grid->rows); @@ -1040,6 +1023,17 @@ grid_resize_and_reflow( grid->num_rows = new_rows; grid->num_cols = new_cols; + /* + * Set new viewport, making sure it’s not too far down. + * + * This is done by using scrollback-start relative cooardinates, + * and bounding the new viewport to (grid_rows - screen_rows). + */ + int sb_view = grid_row_abs_to_sb( + grid, new_screen_rows, view_follows ? grid->offset : viewport.row); + grid->view = grid_row_sb_to_abs( + grid, new_screen_rows, min(sb_view, new_rows - new_screen_rows)); + /* Convert absolute coordinates to screen relative */ cursor.row -= grid->offset; while (cursor.row < 0) From 22989ef9b3c604f5ccc30a6ba3cb11263f7e1a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 29 Aug 2022 20:47:33 +0200 Subject: [PATCH 53/81] =?UTF-8?q?grid:=20reflow:=20don=E2=80=99t=20line-wr?= =?UTF-8?q?ap=20the=20last=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this patch, we would line-wrap the last row, just like any other row, and then afterwards try to reverse this, by adjusting the offset and free:ing and NULL:ing the "last row". The problem with this is if the scrollback is full. In this case, the row we’re freeing is the first row in the scrollback history. This means we’ll crash as soon as the viewport is moved to the top of the scrollback. The fix is fairly, simple. Skip the post-processing logic, and instead detect when we’re line-wrapping the last row, and skip the call to line_wrap(). This way, the last row in the new grid corresponds to the last row in the old grid. --- grid.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/grid.c b/grid.c index f229effa..bbac3395 100644 --- a/grid.c +++ b/grid.c @@ -935,13 +935,14 @@ grid_resize_and_reflow( start += cols; } - if (old_row->linebreak) { /* Erase the remaining cells */ memset(&new_row->cells[new_col_idx], 0, (new_cols - new_col_idx) * sizeof(new_row->cells[0])); new_row->linebreak = true; - line_wrap(); + + if (r + 1 < old_rows) + line_wrap(); } grid_row_free(old_grid[old_row_idx]); @@ -985,25 +986,6 @@ grid_resize_and_reflow( /* Set offset such that the last reflowed row is at the bottom */ grid->offset = new_row_idx - new_screen_rows + 1; - if (new_col_idx == 0) { - int next_to_last_new_row_idx = new_row_idx - 1; - next_to_last_new_row_idx += new_rows; - next_to_last_new_row_idx &= new_rows - 1; - - const struct row *next_to_last_row = new_grid[next_to_last_new_row_idx]; - if (next_to_last_row != NULL && next_to_last_row->linebreak) { - /* - * The next to last row is actually the *last* row. But we - * ended the reflow with a line-break, causing an empty - * row to be inserted at the bottom. Undo this. - */ - /* TODO: can we detect this in the reflow loop above instead? */ - grid->offset--; - grid_row_free(new_grid[new_row_idx]); - new_grid[new_row_idx] = NULL; - } - } - while (grid->offset < 0) grid->offset += new_rows; while (new_grid[grid->offset] == NULL) From 23871d4db58d15e33e0693010373c426fc50688e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 29 Aug 2022 21:03:21 +0200 Subject: [PATCH 54/81] =?UTF-8?q?grid:=20reflow:=20assert=20there=20aren?= =?UTF-8?q?=E2=80=99t=20any=20open=20URIs=20on=20the=20last=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grid.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/grid.c b/grid.c index bbac3395..7bfef5cb 100644 --- a/grid.c +++ b/grid.c @@ -943,6 +943,21 @@ grid_resize_and_reflow( if (r + 1 < old_rows) line_wrap(); + else if (new_row->extra != NULL && + new_row->extra->uri_ranges.count > 0) + { + /* + * line_wrap() "closes" still-open URIs. Since this is + * the *last* row, and since we’re line-breaking due + * to a hard line-break (rather than running out of + * cells in the "new_row"), there shouldn’t be an open + * URI (it would have been closed when we reached the + * end of the URI while reflowing the last "old" + * row). + */ + uint32_t last_idx = new_row->extra->uri_ranges.count - 1; + xassert(new_row->extra->uri_ranges.v[last_idx].end >= 0); + } } grid_row_free(old_grid[old_row_idx]); From 688bf1bd59df3c692b0891aaf46b6844ea415e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 29 Aug 2022 21:04:56 +0200 Subject: [PATCH 55/81] changelog: crash when resizing window, or scrolling --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba3aa6b..22267556 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,10 @@ subsequent motion and leave events. * Regression: “random” selected empty cells being highlighted as selected when they should not. +* Crash when either resizing the terminal window, or scrolling in the + scrollback history ([#1074][1074]) + +[1074]: https://codeberg.org/dnkl/foot/pulls/1074 ### Security From 5706141d0a5250af761f29989dad8161ed1cf2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Aug 2022 17:48:04 +0200 Subject: [PATCH 56/81] url-mode: connect osc-8 links only when both ID and URI matches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this, two OSC-8 links with a matching ID would be connected even if their URIs weren’t the same. This is against the spec: The same id is only used for connecting character cells whose URIs is also the same. Character cells pointing to different URIs should never be underlined together when hovering over. https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#hover-underlining-and-the-id-parameter --- CHANGELOG.md | 2 ++ url-mode.c | 16 ++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22267556..fe6e7fcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,8 @@ selected when they should not. * Crash when either resizing the terminal window, or scrolling in the scrollback history ([#1074][1074]) +* OSC-8 URLs with matching IDs, but mismatching URIs being incorrectly + connected. [1074]: https://codeberg.org/dnkl/foot/pulls/1074 diff --git a/url-mode.c b/url-mode.c index 538b60f0..6fa16623 100644 --- a/url-mode.c +++ b/url-mode.c @@ -677,18 +677,23 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls) if (count == 0) return; - uint64_t seen_ids[count]; char32_t *combos[count]; generate_key_combos(conf, count, combos); size_t combo_idx = 0; - size_t id_idx = 0; tll_foreach(*urls, it) { bool id_already_seen = false; - for (size_t i = 0; i < id_idx; i++) { - if (it->item.id == seen_ids[i]) { + /* Look for already processed URLs where both the URI and the + * ID matches */ + tll_foreach(*urls, it2) { + if (&it->item == &it2->item) + break; + + if (it->item.id == it2->item.id && + strcmp(it->item.url, it2->item.url) == 0) + { id_already_seen = true; break; } @@ -696,7 +701,6 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls) if (id_already_seen) continue; - seen_ids[id_idx++] = it->item.id; /* * Scan previous URLs, and check if *this* URL matches any of @@ -730,7 +734,7 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls) char *key = ac32tombs(it->item.key); xassert(key != NULL); - LOG_DBG("URL: %s (%s)", it->item.url, key); + LOG_DBG("URL: %s (key=%s, id=%"PRIu64")", it->item.url, key, it->item.id); free(key); } #endif From 864de72b171ea6e6e002ec119f017ed57fecc0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 31 Aug 2022 19:17:38 +0200 Subject: [PATCH 57/81] changelog: prepare for 1.13.1 --- CHANGELOG.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe6e7fcb..ff203e04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.13.1](#1-13-1) * [1.13.0](#1-13-0) * [1.12.1](#1-12-1) * [1.12.0](#1-12-0) @@ -40,15 +40,13 @@ * [1.2.0](#1-2-0) -## Unreleased -### Added +## 1.13.1 + ### Changed * Window is now dimmed while in Unicode input mode. -### Deprecated -### Removed ### Fixed * Compiling against wayland-protocols < 1.25 @@ -65,10 +63,6 @@ [1074]: https://codeberg.org/dnkl/foot/pulls/1074 -### Security -### Contributors - - ## 1.13.0 ### Added From cd1933baf12eeef82e04a926f9150ca815d54768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 31 Aug 2022 19:19:15 +0200 Subject: [PATCH 58/81] meson: bump version to 1.13.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index c5597113..360db8fa 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.13.0', + version: '1.13.1', license: 'MIT', meson_version: '>=0.58.0', default_options: [ From 2d1ded183ac1c79b93f2674fed3ec7c0def65075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 3 Sep 2022 12:16:41 +0200 Subject: [PATCH 59/81] =?UTF-8?q?config:=20change=20default=20=E2=80=98pad?= =?UTF-8?q?=E2=80=99=20to=200x0=20(i.e.=20no=20padding)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + config.c | 4 ++-- doc/foot.ini.5.scd | 2 +- foot.ini | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cca3d366..15b0e55c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ * Default color theme from a variant of the Zenburn theme, to a variant of the Solarized dark theme. +* Default `pad` from 2x2 to 0x0 (i.e. no padding at all). ### Deprecated diff --git a/config.c b/config.c index 3ecb3db5..87452cd6 100644 --- a/config.c +++ b/config.c @@ -2821,8 +2821,8 @@ config_load(struct config *conf, const char *conf_path, .width = 700, .height = 500, }, - .pad_x = 2, - .pad_y = 2, + .pad_x = 0, + .pad_y = 0, .resize_delay_ms = 100, .bold_in_bright = { .enabled = false, diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index a0cf69f5..7382a1d2 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -210,7 +210,7 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*. To instead center the grid content, append *center* (e.g. *pad=5x5 center*). - Default: _2x2_. + Default: _0x0_. *resize-delay-ms* Time, in milliseconds, of "idle time" before foot sends the new diff --git a/foot.ini b/foot.ini index 926ed499..3b8b6f16 100644 --- a/foot.ini +++ b/foot.ini @@ -24,7 +24,7 @@ # initial-window-size-pixels=700x500 # Or, # initial-window-size-chars= # initial-window-mode=windowed -# pad=2x2 # optionally append 'center' +# pad=0x0 # optionally append 'center' # resize-delay-ms=100 # notify=notify-send -a ${app-id} -i ${app-id} ${title} ${body} From c311229b9e4374d81d72e7c20ba51075267e2443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 6 Sep 2022 17:37:19 +0200 Subject: [PATCH 60/81] csi: damage *viewport* when exiting the alt screen This fixes a redraw issue when the normal screen were somewhere in the scrollback history when exiting the alt screen. --- csi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csi.c b/csi.c index 659839f0..4a023554 100644 --- a/csi.c +++ b/csi.c @@ -486,7 +486,7 @@ decset_decrst(struct terminal *term, unsigned param, bool enable) } tll_free(term->alt.scroll_damage); - term_damage_all(term); + term_damage_view(term); } term_update_ascii_printer(term); break; From d2e67689ea0d5ec986b66936188e0557c1f04858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 5 Sep 2022 19:23:40 +0200 Subject: [PATCH 61/81] =?UTF-8?q?terminal:=20don=E2=80=99t=20unref=20a=20n?= =?UTF-8?q?ot-yet-referenced=20key-binding=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Key-binding sets are bound to a seat/configuration pair. The conf reference is done when a new terminal instance is created. When that same terminal instance is destroyed, the key binding set is unref:ed. If the terminal instance is destroyed *before* the key binding set has been referenced, we’ll still unref it. This creates an imbalance. In particular, when the there is exactly one other terminal instance referencing that same key binding set, that terminal instance will trigger a foot server crash as soon as it receives a key press/release event. This happens because the next-to-last terminal instance brought the reference count of the binding set down to 0, causing it to be free:d. Thus, we *must* reference the binding set *before* we can error out (when instantiating a new terminal instance). At this point, we don’t yet have a valid terminal instance. But, that’s ok, because all the key_binding_new_for_term() did with the terminal instance was get the "struct wayland" and "struct config" pointers. So, rename the function and simply pass these pointers explicitly. Similarly, change key_binding_for() to take a "struct config" pointer, rather than a "struct terminal" pointer. Also rename key_binding_unref_term() -> key_binding_unref(). --- CHANGELOG.md | 6 ++++++ input.c | 4 ++-- key-binding.c | 22 +++++++--------------- key-binding.h | 14 ++++++++------ pgo/pgo.c | 9 +++++---- terminal.c | 11 +++++++---- 6 files changed, 35 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b0e55c..b54016de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,12 @@ ### Deprecated ### Removed ### Fixed + +* Crash in `foot --server` on key press, after another `footclient` + has terminated very early (for example, by trying to launch a + non-existing shell/client). + + ### Security ### Contributors diff --git a/input.c b/input.c index 50336679..3aa8b004 100644 --- a/input.c +++ b/input.c @@ -1406,7 +1406,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, seat->kbd.xkb_keymap, key, layout_idx, 0, &raw_syms); const struct key_binding_set *bindings = key_binding_for( - seat->wayl->key_binding_manager, term, seat); + seat->wayl->key_binding_manager, term->conf, seat); xassert(bindings != NULL); if (pressed) { @@ -2335,7 +2335,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, /* Seat has keyboard - use mouse bindings *with* modifiers */ const struct key_binding_set *bindings = key_binding_for( - wayl->key_binding_manager, term, seat); + wayl->key_binding_manager, term->conf, seat); xassert(bindings != NULL); xkb_mod_mask_t mods; diff --git a/key-binding.c b/key-binding.c index 2135abbc..1876a885 100644 --- a/key-binding.c +++ b/key-binding.c @@ -80,17 +80,14 @@ key_binding_new_for_seat(struct key_binding_manager *mgr, } void -key_binding_new_for_term(struct key_binding_manager *mgr, - const struct terminal *term) +key_binding_new_for_conf(struct key_binding_manager *mgr, + const struct wayland *wayl, const struct config *conf) { - const struct config *conf = term->conf; - const struct wayland *wayl = term->wl; - tll_foreach(wayl->seats, it) { struct seat *seat = &it->item; struct key_set *existing = - (struct key_set *)key_binding_for(mgr, term, seat); + (struct key_set *)key_binding_for(mgr, conf, seat); if (existing != NULL) { existing->conf_ref_count++; @@ -116,21 +113,19 @@ key_binding_new_for_term(struct key_binding_manager *mgr, /* Chances are high this set will be requested next */ mgr->last_used_set = &tll_back(mgr->binding_sets); - LOG_DBG("new (term): set=%p, seat=%p, conf=%p, ref-count=1", + LOG_DBG("new (conf): set=%p, seat=%p, conf=%p, ref-count=1", (void *)&tll_back(mgr->binding_sets), (void *)set.seat, (void *)set.conf); } - LOG_DBG("new (term): total number of sets: %zu", + LOG_DBG("new (conf): total number of sets: %zu", tll_length(mgr->binding_sets)); } struct key_binding_set * NOINLINE -key_binding_for(struct key_binding_manager *mgr, const struct terminal *term, +key_binding_for(struct key_binding_manager *mgr, const struct config *conf, const struct seat *seat) { - const struct config *conf = term->conf; - struct key_set *last_used = mgr->last_used_set; if (last_used != NULL && last_used->conf == conf && @@ -192,11 +187,8 @@ key_binding_remove_seat(struct key_binding_manager *mgr, } void -key_binding_unref_term(struct key_binding_manager *mgr, - const struct terminal *term) +key_binding_unref(struct key_binding_manager *mgr, const struct config *conf) { - const struct config *conf = term->conf; - tll_foreach(mgr->binding_sets, it) { struct key_set *set = &it->item; diff --git a/key-binding.h b/key-binding.h index 448500c1..f607644f 100644 --- a/key-binding.h +++ b/key-binding.h @@ -110,6 +110,7 @@ typedef tll(struct key_binding) key_binding_list_t; struct terminal; struct seat; +struct wayland; struct key_binding_set { key_binding_list_t key; @@ -127,20 +128,21 @@ void key_binding_manager_destroy(struct key_binding_manager *mgr); void key_binding_new_for_seat( struct key_binding_manager *mgr, const struct seat *seat); -void key_binding_new_for_term( - struct key_binding_manager *mgr, const struct terminal *term); +void key_binding_new_for_conf( + struct key_binding_manager *mgr, const struct wayland *wayl, + const struct config *conf); -/* Returns the set of key bindings associated with this seat/term pair */ +/* Returns the set of key bindings associated with this seat/conf pair */ struct key_binding_set *key_binding_for( - struct key_binding_manager *mgr, const struct terminal *term, + struct key_binding_manager *mgr, const struct config *conf, const struct seat *seat); /* Remove all key bindings tied to the specified seat */ void key_binding_remove_seat( struct key_binding_manager *mgr, const struct seat *seat); -void key_binding_unref_term( - struct key_binding_manager *mgr, const struct terminal *term); +void key_binding_unref( + struct key_binding_manager *mgr, const struct config *conf); void key_binding_load_keymap( struct key_binding_manager *mgr, const struct seat *seat); diff --git a/pgo/pgo.c b/pgo/pgo.c index 3073c27c..7f6f758b 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -178,15 +178,16 @@ static bool kbd_initialized = false; struct key_binding_set * key_binding_for( - struct key_binding_manager *mgr, const struct terminal *term, + struct key_binding_manager *mgr, const struct config *conf, const struct seat *seat) { return &kbd; } void -key_binding_new_for_term( - struct key_binding_manager *mgr, const struct terminal *term) +key_binding_new_for_conf( + struct key_binding_manager *mgr, const struct wayland *wayl, + const struct config *conf) { if (!kbd_initialized) { kbd_initialized = true; @@ -201,7 +202,7 @@ key_binding_new_for_term( } void -key_binding_unref_term(struct key_binding_manager *mgr, const struct terminal *term) +key_binding_unref(struct key_binding_manager *mgr, const struct config *conf) { } diff --git a/terminal.c b/terminal.c index 0763fb52..3445b89f 100644 --- a/terminal.c +++ b/terminal.c @@ -1089,6 +1089,11 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, goto close_fds; } + /* Need to register *very* early (before the first “goto err”), to + * ensure term_destroy() doesn’t unref a key-binding we haven’t + * yet ref:d */ + key_binding_new_for_conf(wayl->key_binding_manager, wayl, conf); + int ptmx_flags; if ((ptmx_flags = fcntl(ptmx, F_GETFL)) < 0 || fcntl(ptmx, F_SETFL, ptmx_flags | O_NONBLOCK) < 0) @@ -1266,8 +1271,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table)); - key_binding_new_for_term(wayl->key_binding_manager, term); - /* Initialize the Wayland window backend */ if ((term->window = wayl_win_init(term, token)) == NULL) goto err; @@ -1583,7 +1586,7 @@ term_destroy(struct terminal *term) if (term == NULL) return 0; - key_binding_unref_term(term->wl->key_binding_manager, term); + key_binding_unref(term->wl->key_binding_manager, term->conf); tll_foreach(term->wl->terms, it) { if (it->item == term) { @@ -2885,7 +2888,7 @@ term_mouse_grabbed(const struct terminal *term, const struct seat *seat) get_current_modifiers(seat, &mods, NULL, 0); const struct key_binding_set *bindings = - key_binding_for(term->wl->key_binding_manager, term, seat); + key_binding_for(term->wl->key_binding_manager, term->conf, seat); const xkb_mod_mask_t override_modmask = bindings->selection_overrides; bool override_mods_pressed = (mods & override_modmask) == override_modmask; From 1c072856ebf12419378c5098ad543c497197c6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 10 Sep 2022 12:04:39 +0200 Subject: [PATCH 62/81] input: pipe-*: set current CWD when spawning the sub-process Closes #1166 --- CHANGELOG.md | 4 ++++ input.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b54016de..793f6376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,10 @@ * Default color theme from a variant of the Zenburn theme, to a variant of the Solarized dark theme. * Default `pad` from 2x2 to 0x0 (i.e. no padding at all). +* Current working directory (as set by OSC-7) is now passed to the + program executed by the `pipe-*` key bindings ([#1166][166]). + +[1166]: https://codeberg.org/dnkl/foot/issues/1166 ### Deprecated diff --git a/input.c b/input.c index 3aa8b004..0a3773bc 100644 --- a/input.c +++ b/input.c @@ -283,7 +283,7 @@ execute_binding(struct seat *seat, struct terminal *term, } } - if (!spawn(term->reaper, NULL, binding->aux->pipe.args, + if (!spawn(term->reaper, term->cwd, binding->aux->pipe.args, pipe_fd[0], stdout_fd, stderr_fd, NULL)) goto pipe_err; From 8dcfa259a2a4596311b9c271043456b49275d0f8 Mon Sep 17 00:00:00 2001 From: Craig Barnes Date: Sat, 17 Sep 2022 06:34:25 +0100 Subject: [PATCH 63/81] config: fix "maybe-uninitialized" error when compiling with CFLAGS=-Og MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using GCC 12.2.0 with the following build steps: CFLAGS=-Og meson bld ninja -C bld ...produces the compiler error: ../config.c:2000:18: error: ‘sym_equal’ may be used uninitialized [-Werror=maybe-uninitialized] This commit fixes that by using BUG() to assert that all possible values are accounted for in the offending switch statement. --- config.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.c b/config.c index 87452cd6..0ba237a0 100644 --- a/config.c +++ b/config.c @@ -2008,6 +2008,9 @@ resolve_key_binding_collisions(struct config *conf, const char *section_name, sym_equal = (binding1->m.button == binding2->m.button && binding1->m.count == binding2->m.count); break; + + default: + BUG("unhandled key binding type"); } if (!mods_equal || !sym_equal) From 46f6bad728efbeee04cfa98d4a31c4201db07ff8 Mon Sep 17 00:00:00 2001 From: Craig Barnes Date: Sat, 17 Sep 2022 07:17:12 +0100 Subject: [PATCH 64/81] csi: reply with 4 instead of 2 for DECRQM queries of unsupported modes The status value 4 means "permanently reset", as opposed to 2, which means "reset". The former implies that there's no way to enable the mode because it's unsupported (but recognized). Note: this commit also fixes an unrelated typo in CHANGELOG.md. --- CHANGELOG.md | 7 +++- csi.c | 98 +++++++++++++++++++++++++++++----------------------- 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 793f6376..ffbdd08b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,7 +56,10 @@ variant of the Solarized dark theme. * Default `pad` from 2x2 to 0x0 (i.e. no padding at all). * Current working directory (as set by OSC-7) is now passed to the - program executed by the `pipe-*` key bindings ([#1166][166]). + program executed by the `pipe-*` key bindings ([#1166][1166]). +* `DECRPM` replies (to `DECRQM` queries) now report a value of `4` + ("permanently reset") instead of `2` ("reset") for DEC private + modes that are known but unsupported. [1166]: https://codeberg.org/dnkl/foot/issues/1166 @@ -73,6 +76,8 @@ ### Security ### Contributors +* Craig Barnes + ## 1.13.1 diff --git a/csi.c b/csi.c index 4a023554..2eacaaa9 100644 --- a/csi.c +++ b/csi.c @@ -535,47 +535,65 @@ decrst(struct terminal *term, unsigned param) decset_decrst(term, param, false); } -static bool -decrqm(const struct terminal *term, unsigned param, bool *enabled) +/* + * These values represent the current state of a DEC private mode, + * as returned in the DECRPM reply to a DECRQM query. + */ +enum decrpm_status { + DECRPM_NOT_RECOGNIZED = 0, + DECRPM_SET = 1, + DECRPM_RESET = 2, + DECRPM_PERMANENTLY_SET = 3, + DECRPM_PERMANENTLY_RESET = 4, +}; + +static enum decrpm_status +decrpm(bool enabled) +{ + return enabled ? DECRPM_SET : DECRPM_RESET; +} + +static enum decrpm_status +decrqm(const struct terminal *term, unsigned param) { switch (param) { - case 1: *enabled = term->cursor_keys_mode == CURSOR_KEYS_APPLICATION; return true; - case 3: *enabled = false; return true; - case 4: *enabled = false; return true; - case 5: *enabled = term->reverse; return true; - case 6: *enabled = term->origin; return true; - case 7: *enabled = term->auto_margin; return true; - case 9: *enabled = false; /* term->mouse_tracking == MOUSE_X10; */ return true; - case 12: *enabled = term->cursor_blink.decset; return true; - case 25: *enabled = !term->hide_cursor; return true; - case 45: *enabled = term->reverse_wrap; return true; - case 66: *enabled = term->keypad_keys_mode == KEYPAD_APPLICATION; return true; - case 80: *enabled = !term->sixel.scrolling; return true; - case 1000: *enabled = term->mouse_tracking == MOUSE_CLICK; return true; - case 1001: *enabled = false; return true; - case 1002: *enabled = term->mouse_tracking == MOUSE_DRAG; return true; - case 1003: *enabled = term->mouse_tracking == MOUSE_MOTION; return true; - case 1004: *enabled = term->focus_events; return true; - case 1005: *enabled = false; /* term->mouse_reporting == MOUSE_UTF8; */ return true; - case 1006: *enabled = term->mouse_reporting == MOUSE_SGR; return true; - case 1007: *enabled = term->alt_scrolling; return true; - case 1015: *enabled = term->mouse_reporting == MOUSE_URXVT; return true; - case 1016: *enabled = term->mouse_reporting == MOUSE_SGR_PIXELS; return true; - case 1034: *enabled = term->meta.eight_bit; return true; - case 1035: *enabled = term->num_lock_modifier; return true; - case 1036: *enabled = term->meta.esc_prefix; return true; - case 1042: *enabled = term->bell_action_enabled; return true; + case 1: return decrpm(term->cursor_keys_mode == CURSOR_KEYS_APPLICATION); + case 3: return DECRPM_PERMANENTLY_RESET; + case 4: return DECRPM_PERMANENTLY_RESET; + case 5: return decrpm(term->reverse); + case 6: return decrpm(term->origin); + case 7: return decrpm(term->auto_margin); + case 9: return DECRPM_PERMANENTLY_RESET; /* term->mouse_tracking == MOUSE_X10; */ + case 12: return decrpm(term->cursor_blink.decset); + case 25: return decrpm(!term->hide_cursor); + case 45: return decrpm(term->reverse_wrap); + case 66: return decrpm(term->keypad_keys_mode == KEYPAD_APPLICATION); + case 80: return decrpm(!term->sixel.scrolling); + case 1000: return decrpm(term->mouse_tracking == MOUSE_CLICK); + case 1001: return DECRPM_PERMANENTLY_RESET; + case 1002: return decrpm(term->mouse_tracking == MOUSE_DRAG); + case 1003: return decrpm(term->mouse_tracking == MOUSE_MOTION); + case 1004: return decrpm(term->focus_events); + case 1005: return DECRPM_PERMANENTLY_RESET; /* term->mouse_reporting == MOUSE_UTF8; */ + case 1006: return decrpm(term->mouse_reporting == MOUSE_SGR); + case 1007: return decrpm(term->alt_scrolling); + case 1015: return decrpm(term->mouse_reporting == MOUSE_URXVT); + case 1016: return decrpm(term->mouse_reporting == MOUSE_SGR_PIXELS); + case 1034: return decrpm(term->meta.eight_bit); + case 1035: return decrpm(term->num_lock_modifier); + case 1036: return decrpm(term->meta.esc_prefix); + case 1042: return decrpm(term->bell_action_enabled); case 47: /* FALLTHROUGH */ case 1047: /* FALLTHROUGH */ - case 1049: *enabled = term->grid == &term->alt; return true; - case 1070: *enabled = term->sixel.use_private_palette; return true; - case 2004: *enabled = term->bracketed_paste; return true; - case 2026: *enabled = term->render.app_sync_updates.enabled; return true; - case 8452: *enabled = term->sixel.cursor_right_of_graphics; return true; - case 737769: *enabled = term_ime_is_enabled(term); return true; + case 1049: return decrpm(term->grid == &term->alt); + case 1070: return decrpm(term->sixel.use_private_palette); + case 2004: return decrpm(term->bracketed_paste); + case 2026: return decrpm(term->render.app_sync_updates.enabled); + case 8452: return decrpm(term->sixel.cursor_right_of_graphics); + case 737769: return decrpm(term_ime_is_enabled(term)); } - return false; + return DECRPM_NOT_RECOGNIZED; } static void @@ -1721,15 +1739,9 @@ csi_dispatch(struct terminal *term, uint8_t final) * 3 - permanently set * 4 - permantently reset */ - bool enabled; - unsigned value; - if (decrqm(term, param, &enabled)) - value = enabled ? 1 : 2; - else - value = 0; - + unsigned status = decrqm(term, param); char reply[32]; - size_t n = xsnprintf(reply, sizeof(reply), "\033[?%u;%u$y", param, value); + size_t n = xsnprintf(reply, sizeof(reply), "\033[?%u;%u$y", param, status); term_to_slave(term, reply, n); break; From 335612cfa4ea090874354c7080f492b2b28a136b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Magyar?= Date: Sat, 20 Aug 2022 12:57:38 +0200 Subject: [PATCH 65/81] chore: add appstream file Appstream file is usefull for many package management tools to show additional metadata and screenshots. Also it would solve the packaging for flathub. References: https://codeberg.org/dnkl/foot/issues/1129#issuecomment-602089 --- org.codeberg.dnkl.foot.metainfo.xml | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 org.codeberg.dnkl.foot.metainfo.xml diff --git a/org.codeberg.dnkl.foot.metainfo.xml b/org.codeberg.dnkl.foot.metainfo.xml new file mode 100644 index 00000000..22512ce8 --- /dev/null +++ b/org.codeberg.dnkl.foot.metainfo.xml @@ -0,0 +1,45 @@ + + + org.codeberg.dnkl.foot + CC0-1.0 + MIT + dnkl + foot + The fast, lightweight and minimalistic Wayland terminal emulator. + +
    +
  • Fast
  • +
  • Lightweight, in dependencies, on-disk and in-memory
  • +
  • Wayland native
  • +
  • DE agnostic
  • +
  • Server/daemon mode
  • +
  • User configurable font fallback
  • +
  • On-the-fly font resize
  • +
  • On-the-fly DPI font size adjustment
  • +
  • Scrollback search
  • +
  • Keyboard driven URL detection
  • +
  • Color emoji support
  • +
  • IME (via text-input-v3)
  • +
  • Multi-seat
  • +
  • True Color (24bpp)
  • +
  • Synchronized Updates support
  • +
  • Sixel image support
  • +
+
+ + + Foot with sixel graphics + https://codeberg.org/dnkl/foot/media/branch/master/doc/sixel-wow.png + + + + + + + + + org.codeberg.dnkl.foot.desktop + https://codeberg.org/dnkl/foot + https://codeberg.org/dnkl/foot/issues + +
From debf1b8453ada57e69ec86fcb3fcb9ebf140d218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Magyar?= Date: Thu, 1 Sep 2022 07:24:09 +0200 Subject: [PATCH 66/81] chore: rename desktop files --- meson.build | 2 +- foot-server.desktop => org.codeberg.dnkl.foot-server.desktop | 0 foot.desktop => org.codeberg.dnkl.foot.desktop | 0 footclient.desktop => org.codeberg.dnkl.footclient.desktop | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename foot-server.desktop => org.codeberg.dnkl.foot-server.desktop (100%) rename foot.desktop => org.codeberg.dnkl.foot.desktop (100%) rename footclient.desktop => org.codeberg.dnkl.footclient.desktop (100%) diff --git a/meson.build b/meson.build index 360db8fa..61837459 100644 --- a/meson.build +++ b/meson.build @@ -244,7 +244,7 @@ executable( install: true) install_data( - 'foot.desktop', 'foot-server.desktop', 'footclient.desktop', + 'org.codeberg.dnkl.foot.desktop', 'org.codeberg.dnkl.foot-server.desktop', 'org.codeberg.dnkl.footclient.desktop', install_dir: join_paths(get_option('datadir'), 'applications')) systemd = dependency('systemd', required: false) diff --git a/foot-server.desktop b/org.codeberg.dnkl.foot-server.desktop similarity index 100% rename from foot-server.desktop rename to org.codeberg.dnkl.foot-server.desktop diff --git a/foot.desktop b/org.codeberg.dnkl.foot.desktop similarity index 100% rename from foot.desktop rename to org.codeberg.dnkl.foot.desktop diff --git a/footclient.desktop b/org.codeberg.dnkl.footclient.desktop similarity index 100% rename from footclient.desktop rename to org.codeberg.dnkl.footclient.desktop From 05aedd52da1e89fdb2b25d3cc50f9934f1a1ae3b Mon Sep 17 00:00:00 2001 From: Torsten Trautwein Date: Thu, 22 Sep 2022 08:44:04 +0200 Subject: [PATCH 67/81] Add the nightfly theme --- themes/nightfly | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 themes/nightfly diff --git a/themes/nightfly b/themes/nightfly new file mode 100644 index 00000000..3815b42e --- /dev/null +++ b/themes/nightfly @@ -0,0 +1,29 @@ +# nightfly +# Based on https://github.com/bluz71/vim-nightfly-guicolors + +[cursor] +color = 080808 9ca1aa + +[colors] +foreground = acb4c2 +background = 011627 +selection-foreground = 080808 +selection-background = b2ceee + +regular0 = 1d3b53 +regular1 = fc514e +regular2 = a1cd5e +regular3 = e3d18a +regular4 = 82aaff +regular5 = c792ea +regular6 = 7fdbca +regular7 = a1aab8 + +bright0 = 7c8f8f +bright1 = ff5874 +bright2 = 21c7a8 +bright3 = ecc48d +bright4 = 82aaff +bright5 = ae81ff +bright6 = ae81ff +bright7 = d6deeb From cee59bfb3f355fec6488aa98626ee1017272f75e Mon Sep 17 00:00:00 2001 From: Torsten Trautwein Date: Thu, 22 Sep 2022 08:44:21 +0200 Subject: [PATCH 68/81] Add the moonfly theme --- themes/moonfly | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 themes/moonfly diff --git a/themes/moonfly b/themes/moonfly new file mode 100644 index 00000000..54d9203b --- /dev/null +++ b/themes/moonfly @@ -0,0 +1,29 @@ +# moonfly +# Based on https://github.com/bluz71/vim-moonfly-colors + +[cursor] +color = 080808 9e9e9e + +[colors] +foreground = b2b2b2 +background = 080808 +selection-foreground = 080808 +selection-background = b2ceee + +regular0 = 323437 +regular1 = ff5454 +regular2 = 8cc85f +regular3 = e3c78a +regular4 = 80a0ff +regular5 = d183e8 +regular6 = 79dac8 +regular7 = c6c6c6 + +bright0 = 949494 +bright1 = ff5189 +bright2 = 36c692 +bright3 = c2c292 +bright4 = 74b2ff +bright5 = ae81ff +bright6 = 85dc85 +bright7 = e4e4e4 From 020148d67ca2f1598dcf4fe3bdb212f3d6f7e521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 23 Sep 2022 20:20:56 +0200 Subject: [PATCH 69/81] =?UTF-8?q?install.md:=20add=20=E2=80=98utf8proc?= =?UTF-8?q?=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- INSTALL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/INSTALL.md b/INSTALL.md index ae0598d8..18975503 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -44,6 +44,7 @@ subprojects. * pixman * wayland (_client_ and _cursor_ libraries) * xkbcommon +* utf8proc (_optional_, needed for grapheme clustering) * [fcft](https://codeberg.org/dnkl/fcft) [^1] [^1]: can also be built as subprojects, in which case they are From 4db1dde25c4def9933235093e4720c898559f871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Sep 2022 18:32:28 +0200 Subject: [PATCH 70/81] misc: add timespec_add() --- misc.c | 19 ++++++++++++++++++- misc.h | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/misc.c b/misc.c index 7e33e65a..a81aa9e4 100644 --- a/misc.c +++ b/misc.c @@ -13,15 +13,32 @@ isword(char32_t wc, bool spaces_only, const char32_t *delimiters) return isc32graph(wc); } +void +timespec_add(const struct timespec *a, const struct timespec *b, + struct timespec *res) +{ + const long one_sec_in_ns = 1000000000; + + res->tv_sec = a->tv_sec + b->tv_sec; + res->tv_nsec = a->tv_nsec + b->tv_nsec; + /* tv_nsec may be negative */ + if (res->tv_nsec >= one_sec_in_ns) { + res->tv_sec++; + res->tv_nsec -= one_sec_in_ns; + } +} + void timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *res) { + const long one_sec_in_ns = 1000000000; + res->tv_sec = a->tv_sec - b->tv_sec; res->tv_nsec = a->tv_nsec - b->tv_nsec; /* tv_nsec may be negative */ if (res->tv_nsec < 0) { res->tv_sec--; - res->tv_nsec += 1000 * 1000 * 1000; + res->tv_nsec += one_sec_in_ns; } } diff --git a/misc.h b/misc.h index ba8b2ce7..648bb65f 100644 --- a/misc.h +++ b/misc.h @@ -6,4 +6,5 @@ bool isword(char32_t wc, bool spaces_only, const char32_t *delimiters); +void timespec_add(const struct timespec *a, const struct timespec *b, struct timespec *res); void timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *res); From b7ba842237a52c3b6b455fc0e8725bed91f45ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Sep 2022 18:32:54 +0200 Subject: [PATCH 71/81] render: timer: print/log *total* rendering time --- render.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/render.c b/render.c index ef698911..8775171e 100644 --- a/render.c +++ b/render.c @@ -2913,15 +2913,21 @@ grid_render(struct terminal *term) struct timespec double_buffering_time; timespec_sub(&stop_double_buffering, &start_double_buffering, &double_buffering_time); + struct timespec total_render_time; + timespec_add(&render_time, &double_buffering_time, &total_render_time); + switch (term->conf->tweak.render_timer) { case RENDER_TIMER_LOG: case RENDER_TIMER_BOTH: - LOG_INFO("frame rendered in %lds %ldns " - "(%lds %ldns double buffering)", - (long)render_time.tv_sec, - render_time.tv_nsec, - (long)double_buffering_time.tv_sec, - double_buffering_time.tv_nsec); + LOG_INFO( + "frame rendered in %lds %9ldns " + "(%lds %9ldns rendering, %lds %9ldns double buffering)", + (long)total_render_time.tv_sec, + total_render_time.tv_nsec, + (long)render_time.tv_sec, + render_time.tv_nsec, + (long)double_buffering_time.tv_sec, + double_buffering_time.tv_nsec); break; case RENDER_TIMER_OSD: @@ -2932,7 +2938,7 @@ grid_render(struct terminal *term) switch (term->conf->tweak.render_timer) { case RENDER_TIMER_OSD: case RENDER_TIMER_BOTH: - render_render_timer(term, render_time); + render_render_timer(term, total_render_time); break; case RENDER_TIMER_LOG: From 50ae277d90c1eea6bb0086c31e004042088f794f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Sep 2022 18:33:24 +0200 Subject: [PATCH 72/81] =?UTF-8?q?wayland:=20don=E2=80=99t=20crash=20with?= =?UTF-8?q?=20a=20division-by-zero=20when=20monitor=20dimensions=20are=20n?= =?UTF-8?q?egative?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some compositors set the physical dimensions to -1... --- wayland.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wayland.c b/wayland.c index 05de79aa..cd052532 100644 --- a/wayland.c +++ b/wayland.c @@ -367,7 +367,7 @@ update_terms_on_monitor(struct monitor *mon) static void output_update_ppi(struct monitor *mon) { - if (mon->dim.mm.width == 0 || mon->dim.mm.height == 0) + if (mon->dim.mm.width <= 0 || mon->dim.mm.height <= 0) return; int x_inches = mon->dim.mm.width * 0.03937008; From 4340f8a3b4c5149825d8f1786cfeb7a3af9c0e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Sep 2022 18:34:41 +0200 Subject: [PATCH 73/81] render: fix application of old scroll damage when double buffering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On compositors that forces us to double buffer, we need to re-apply the last frame’s damage to the current frame (which uses the buffer from the next-to-last frame). General cell updates are handled by simply copying from the last frame’s pixman buffer to the current frame’s. In an attempt to improve performance, scroll damage were up until now handled by re-playing the last frame’s scroll damage (on the current frame’s buffer). This does not work, and resulted in glitches when scrolling in the scrollback. This patch does the following: * grid_render_scroll{,_reverse}() now update the buffer’s "dirty" region. This means the generic copy-old-frames-buffer handles the scroll damage (albeit in, potentially, a less efficient way). * Tracking of, and re-applying old scroll damage is completely removed. Closes #1173 --- CHANGELOG.md | 5 ++ render.c | 132 +++++++++++++++++++++++++++------------------------ shm.c | 3 -- shm.h | 2 - 4 files changed, 74 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffbdd08b..17430354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,11 @@ * Crash in `foot --server` on key press, after another `footclient` has terminated very early (for example, by trying to launch a non-existing shell/client). +* Glitchy rendering when scrolling in the scrollback, on compositors + that does not allow Wayland buffer re-use (e.g. KDE/plasma) + ([#1173][1173]) + +[1173]: https://codeberg.org/dnkl/foot/issues/1173 ### Security diff --git a/render.c b/render.c index 8775171e..7f883220 100644 --- a/render.c +++ b/render.c @@ -1014,6 +1014,13 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, wl_surface_damage_buffer( term->window->surface, term->margins.left, dst_y, term->width - term->margins.left - term->margins.right, height); + + /* + * TODO: remove this if re-enabling scroll damage when re-applying + * last frame’s damage (see reapply_old_damage() + */ + pixman_region32_union_rect( + &buf->dirty, &buf->dirty, 0, dst_y, buf->width, height); } static void @@ -1079,6 +1086,13 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf, wl_surface_damage_buffer( term->window->surface, term->margins.left, dst_y, term->width - term->margins.left - term->margins.right, height); + + /* + * TODO: remove this if re-enabling scroll damage when re-applying + * last frame’s damage (see reapply_old_damage() + */ + pixman_region32_union_rect( + &buf->dirty, &buf->dirty, 0, dst_y, buf->width, height); } static void @@ -2538,22 +2552,27 @@ reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old return; } - /* - * TODO: remove this frame’s damage from the region we copy from - * the old frame. - * - * - this frame’s dirty region is only valid *after* we’ve applied - * its scroll damage. - * - last frame’s dirty region is only valid *before* we’ve - * applied this frame’s scroll damage. - * - * Can we transform one of the regions? It’s not trivial, since - * scroll damage isn’t just about counting lines; there may be - * multiple damage records, each with different scrolling regions. - */ pixman_region32_t dirty; pixman_region32_init(&dirty); + /* + * Figure out current frame’s damage region + * + * If current frame doesn’t have any scroll damage, we can simply + * subtract this frame’s damage from the last frame’s damage. That + * way, we don’t have to copy areas from the old frame that’ll + * just get overwritten by current frame. + * + * Note that this is row based. A “half damaged” row is not + * excluded. I.e. the entire row will be copied from the old frame + * to the new, and then when actually rendering the new frame, the + * updated cells will overwrite parts of the copied row. + * + * Since we’re scanning the entire viewport anyway, we also track + * whether *all* cells are to be updated. In this case, just force + * a full re-rendering, and don’t copy anything from the old + * frame. + */ bool full_repaint_needed = true; for (int r = 0; r < term->rows; r++) { @@ -2583,35 +2602,31 @@ reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old return; } - for (size_t i = 0; i < old->scroll_damage_count; i++) { - const struct damage *dmg = &old->scroll_damage[i]; - - switch (dmg->type) { - case DAMAGE_SCROLL: - if (term->grid->view == term->grid->offset) - grid_render_scroll(term, new, dmg); - break; - - case DAMAGE_SCROLL_REVERSE: - if (term->grid->view == term->grid->offset) - grid_render_scroll_reverse(term, new, dmg); - break; - - case DAMAGE_SCROLL_IN_VIEW: - grid_render_scroll(term, new, dmg); - break; - - case DAMAGE_SCROLL_REVERSE_IN_VIEW: - grid_render_scroll_reverse(term, new, dmg); - break; - } - } + /* + * TODO: re-apply last frame’s scroll damage + * + * We used to do this, but it turned out to be buggy. If we decide + * to re-add it, this is where to do it. Note that we’d also have + * to remove the updates to buf->dirty from grid_render_scroll() + * and grid_render_scroll_reverse(). + */ if (tll_length(term->grid->scroll_damage) == 0) { + /* + * We can only subtract current frame’s damage from the old + * frame’s if we don’t have any scroll damage. + * + * If we do have scroll damage, the damage region we + * calculated above is not (yet) valid - we need to apply the + * current frame’s scroll damage *first*. This is done later, + * when rendering the frame. + */ pixman_region32_subtract(&dirty, &old->dirty, &dirty); pixman_image_set_clip_region32(new->pix[0], &dirty); - } else + } else { + /* Copy *all* of last frame’s damaged areas */ pixman_image_set_clip_region32(new->pix[0], &old->dirty); + } pixman_image_composite32( PIXMAN_OP_SRC, old->pix[0], NULL, new->pix[0], @@ -2702,38 +2717,29 @@ grid_render(struct terminal *term) shm_addref(buf); buf->age = 0; - free(term->render.last_buf->scroll_damage); - buf->scroll_damage_count = tll_length(term->grid->scroll_damage); - buf->scroll_damage = xmalloc( - buf->scroll_damage_count * sizeof(buf->scroll_damage[0])); - { - size_t i = 0; - tll_foreach(term->grid->scroll_damage, it) { - buf->scroll_damage[i++] = it->item; - - switch (it->item.type) { - case DAMAGE_SCROLL: - if (term->grid->view == term->grid->offset) - grid_render_scroll(term, buf, &it->item); - break; - - case DAMAGE_SCROLL_REVERSE: - if (term->grid->view == term->grid->offset) - grid_render_scroll_reverse(term, buf, &it->item); - break; - - case DAMAGE_SCROLL_IN_VIEW: + tll_foreach(term->grid->scroll_damage, it) { + switch (it->item.type) { + case DAMAGE_SCROLL: + if (term->grid->view == term->grid->offset) grid_render_scroll(term, buf, &it->item); - break; + break; - case DAMAGE_SCROLL_REVERSE_IN_VIEW: + case DAMAGE_SCROLL_REVERSE: + if (term->grid->view == term->grid->offset) grid_render_scroll_reverse(term, buf, &it->item); - break; - } + break; - tll_remove(term->grid->scroll_damage, it); + case DAMAGE_SCROLL_IN_VIEW: + grid_render_scroll(term, buf, &it->item); + break; + + case DAMAGE_SCROLL_REVERSE_IN_VIEW: + grid_render_scroll_reverse(term, buf, &it->item); + break; } + + tll_remove(term->grid->scroll_damage, it); } /* diff --git a/shm.c b/shm.c index 4c342ddf..4394dbe9 100644 --- a/shm.c +++ b/shm.c @@ -151,7 +151,6 @@ buffer_destroy(struct buffer_private *buf) pool_unref(buf->pool); buf->pool = NULL; - free(buf->public.scroll_damage); pixman_region32_fini(&buf->public.dirty); free(buf); } @@ -581,8 +580,6 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height) LOG_DBG("re-using buffer %p from cache", (void *)cached); cached->busy = true; pixman_region32_clear(&cached->public.dirty); - free(cached->public.scroll_damage); - cached->public.scroll_damage = NULL; xassert(cached->public.pix_instances == chain->pix_instances); return &cached->public; } diff --git a/shm.h b/shm.h index bed4285c..440cfa1d 100644 --- a/shm.h +++ b/shm.h @@ -24,8 +24,6 @@ struct buffer { unsigned age; - struct damage *scroll_damage; - size_t scroll_damage_count; pixman_region32_t dirty; }; From 3be44fb316b166c0b5d5536fdb5bb6c7abdd3a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Sep 2022 18:39:00 +0200 Subject: [PATCH 74/81] render: overlay: fix visual glitches when double buffering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When rendering the overlay for scrollback search, the logic assumed buffer re-use. On some compositors this isn’t happening (on e.g. KDE/plasma we’re forced to double buffer). This resulted in matches not being highlighted correctly. The problem is in how we calculated the region for which areas to clear ("un-dim"). It uses the "previous frame’s see-through area" minus the current frame’s see-through area. However, when we’ve detected that the current buffer isn’t the same as the last one, we set the last frame’s see-through region to "the entire buffer". Thus, when calculating the diff, we end up with an empty region, and nothing is highlighted. Fix by simply using the current frame’s see-through region as-is when we’ve detected we’re not re-using the last frame’s buffer. --- CHANGELOG.md | 3 +++ render.c | 29 +++++++++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17430354..dd0ac80b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,9 @@ * Glitchy rendering when scrolling in the scrollback, on compositors that does not allow Wayland buffer re-use (e.g. KDE/plasma) ([#1173][1173]) +* Scrollback search matches not being highlighted correctly, on + compositors that does now allow Wayland buffer re-use + (e.g. KDE/plasma). [1173]: https://codeberg.org/dnkl/foot/issues/1173 diff --git a/render.c b/render.c index 7f883220..b285b120 100644 --- a/render.c +++ b/render.c @@ -1563,11 +1563,12 @@ render_overlay(struct terminal *term) */ pixman_region32_t *see_through = &term->render.last_overlay_clip; pixman_region32_t old_see_through; + const bool buffer_reuse = + buf == term->render.last_overlay_buf && + style == term->render.last_overlay_style && + buf->age == 0; - if (!(buf == term->render.last_overlay_buf && - style == term->render.last_overlay_style && - buf->age == 0)) - { + if (!buffer_reuse) { /* Can’t re-use last frame’s damage - set to full window, * to ensure *everything* is updated */ pixman_region32_init_rect( @@ -1580,8 +1581,8 @@ render_overlay(struct terminal *term) pixman_region32_clear(see_through); + /* Build region consisting of all current search matches */ struct search_match_iterator iter = search_matches_new_iter(term); - for (struct range match = search_matches_next(&iter); match.start.row >= 0; match = search_matches_next(&iter)) @@ -1609,20 +1610,28 @@ render_overlay(struct terminal *term) } } - /* Current see-through, minus old see-through - aka cells that - * need to be cleared */ + /* Areas that need to be cleared: cells that were dimmed in + * the last frame but is now see-through */ pixman_region32_t new_see_through; pixman_region32_init(&new_see_through); - pixman_region32_subtract(&new_see_through, see_through, &old_see_through); + + if (buffer_reuse) + pixman_region32_subtract(&new_see_through, see_through, &old_see_through); + else { + /* Buffer content is unknown - explicitly clear *all* + * current see-through areas */ + pixman_region32_copy(&new_see_through, see_through); + } pixman_image_set_clip_region32(buf->pix[0], &new_see_through); - /* Old see-through, minus new see-through - aka cells that - * needs to be dimmed */ + /* Areas that need to be dimmed: cells that were cleared in + * the last frame but is not anymore */ pixman_region32_t new_dimmed; pixman_region32_init(&new_dimmed); pixman_region32_subtract(&new_dimmed, &old_see_through, see_through); pixman_region32_fini(&old_see_through); + /* Total affected area */ pixman_region32_t damage; pixman_region32_init(&damage); pixman_region32_union(&damage, &new_see_through, &new_dimmed); From aa10b1d2da3ecf9672ae915a49073bd30ee0a785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 23 Sep 2022 20:24:04 +0200 Subject: [PATCH 75/81] Add support for creating utmp records MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds support for creating utmp records using the ‘utempter’ helper binary from the ‘libutempter’ package. * New config option ‘main.utempter’ * New meson command line option, -Ddefault-utempter-path. Defaults to auto-detecting the path. The default value of the new ‘main.utempter’ config option depends on the meson command line option ‘-Ddefault-utempter-path’. If ‘main.utempter’ is *not* set to ‘none’, foot will try to execute the utempter helper binary to create utmp records when a new terminal is instantiated. The record is removed when the terminal instance is destroyed. --- CHANGELOG.md | 2 ++ INSTALL.md | 22 ++++++++++++---------- config.c | 19 +++++++++++++++++++ config.h | 2 ++ doc/foot.ini.5.scd | 4 ++++ doc/meson.build | 7 +++++++ foot.ini | 1 + meson.build | 24 +++++++++++++++++++++++- meson_options.txt | 3 +++ terminal.c | 31 +++++++++++++++++++++++++++++++ tests/test-config.c | 1 + 11 files changed, 105 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd0ac80b..1bc41eef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,8 @@ ### Added * Support for adjusting the thickness of regular underlines ([#1136][1136]). +* Support (optional) for utmp logging with libutempter. + [1136]: https://codeberg.org/dnkl/foot/issues/1136 diff --git a/INSTALL.md b/INSTALL.md index 18975503..da3a667e 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -45,6 +45,7 @@ subprojects. * wayland (_client_ and _cursor_ libraries) * xkbcommon * utf8proc (_optional_, needed for grapheme clustering) +* libutempter (_optional_, needed for utmp logging) * [fcft](https://codeberg.org/dnkl/fcft) [^1] [^1]: can also be built as subprojects, in which case they are @@ -141,16 +142,17 @@ mkdir -p bld/release && cd bld/release Available compile-time options: -| Option | Type | Default | Description | Extra dependencies | -|--------------------------------------|---------|-------------------------|-------------------------------------------------------|--------------------| -| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc | -| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | none | -| `-Dime` | bool | `true` | Enables IME support | None | -| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc | -| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) | -| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | none | -| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None | -| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None | +| Option | Type | Default | Description | Extra dependencies | +|--------------------------------------|---------|-------------------------|-----------------------------------------------------------|--------------------| +| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc | +| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | none | +| `-Dime` | bool | `true` | Enables IME support | None | +| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc | +| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) | +| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | none | +| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None | +| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None | +| `-Ddefault-utempter-path` | feature | `auto` | Default path to utempter binary (‘none’ disables default) | libutempter | Documentation includes the man pages, readme, changelog and license files. diff --git a/config.c b/config.c index 0ba237a0..e2c007d2 100644 --- a/config.c +++ b/config.c @@ -944,6 +944,18 @@ parse_section_main(struct context *ctx) else if (strcmp(key, "box-drawings-uses-font-glyphs") == 0) return value_to_bool(ctx, &conf->box_drawings_uses_font_glyphs); + else if (strcmp(key, "utempter") == 0) { + if (!value_to_str(ctx, &conf->utempter_path)) + return false; + + if (strcmp(conf->utempter_path, "none") == 0) { + free(conf->utempter_path); + conf->utempter_path = NULL; + } + + return true; + } + else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -2937,6 +2949,9 @@ config_load(struct config *conf, const char *conf_path, }, .env_vars = tll_init(), + .utempter_path = (strlen(FOOT_DEFAULT_UTEMPTER_PATH) > 0 + ? xstrdup(FOOT_DEFAULT_UTEMPTER_PATH) + : NULL), .notifications = tll_init(), }; @@ -3225,6 +3240,9 @@ config_clone(const struct config *old) tll_push_back(conf->env_vars, copy); } + conf->utempter_path = + old->utempter_path != NULL ? xstrdup(old->utempter_path) : NULL; + conf->notifications.length = 0; conf->notifications.head = conf->notifications.tail = 0; tll_foreach(old->notifications, it) { @@ -3291,6 +3309,7 @@ config_free(struct config *conf) tll_remove(conf->env_vars, it); } + free(conf->utempter_path); user_notifications_free(&conf->notifications); } diff --git a/config.h b/config.h index f98e4d35..d35abbb2 100644 --- a/config.h +++ b/config.h @@ -319,6 +319,8 @@ struct config { env_var_list_t env_vars; + char *utempter_path; + struct { enum fcft_scaling_filter fcft_filter; bool overflowing_glyphs; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 7382a1d2..46a6d33e 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -317,6 +317,10 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*. (including SMT). Note that this is not always the best value. In some cases, the number of physical _cores_ is better. +*utempter* + Path to utempter helper binary. Set to *none* to disable utmp + records. Default: _@utempter@_. + # SECTION: environment This section is used to define environment variables that will be set diff --git a/doc/meson.build b/doc/meson.build index 75c3be95..86e75952 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -2,9 +2,16 @@ sh = find_program('sh', native: true) scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true) +if utempter_path == '' + default_utempter_value = 'not set' +else + default_utempter_value = utempter_path +endif + conf_data = configuration_data( { 'default_terminfo': get_option('default-terminfo'), + 'utempter': default_utempter_value, } ) diff --git a/foot.ini b/foot.ini index 3b8b6f16..62f853fa 100644 --- a/foot.ini +++ b/foot.ini @@ -33,6 +33,7 @@ # word-delimiters=,│`|:"'()[]{}<> # selection-target=primary # workers= +# utempter=/usr/lib/utempter/utempter [environment] # name=value diff --git a/meson.build b/meson.build index 61837459..4d3b6213 100644 --- a/meson.build +++ b/meson.build @@ -16,9 +16,30 @@ if cc.has_function('memfd_create') add_project_arguments('-DMEMFD_CREATE', language: 'c') endif +utempter_path = get_option('default-utempter-path') +if utempter_path == '' + utempter = find_program( + 'utempter', + required: false, + dirs: [join_paths(get_option('prefix'), get_option('libdir'), 'utempter'), + join_paths(get_option('prefix'), get_option('libexecdir'), 'utempter'), + '/usr/lib/utempter', + '/usr/libexec/utempter', + '/lib/utempter'] + ) + if utempter.found() + utempter_path = utempter.full_path() + else + utempter_path = '' + endif +elif utempter_path == 'none' + utempter_path = '' +endif + add_project_arguments( ['-D_GNU_SOURCE=200809L', - '-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo'))] + + '-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo')), + '-DFOOT_DEFAULT_UTEMPTER_PATH="@0@"'.format(utempter_path)] + (is_debug_build ? ['-D_DEBUG'] : [cc.get_supported_arguments('-fno-asynchronous-unwind-tables')]) + @@ -321,6 +342,7 @@ summary( 'Themes': get_option('themes'), 'IME': get_option('ime'), 'Grapheme clustering': utf8proc.found(), + 'Utempter path': utempter_path, 'Build terminfo': tic.found(), 'Terminfo install location': terminfo_install_location, 'Default TERM': get_option('default-terminfo'), diff --git a/meson_options.txt b/meson_options.txt index 0c660a75..c38a8ca8 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -21,3 +21,6 @@ option('custom-terminfo-install-location', type: 'string', value: '', option('systemd-units-dir', type: 'string', value: '', description: 'Where to install the systemd service files (absolute path). Default: ${systemduserunitdir}') + +option('default-utempter-path', type: 'string', value: '', + description: 'Default path to utempter helper binary. Default: auto-detect') diff --git a/terminal.c b/terminal.c index 3445b89f..4c9bf3db 100644 --- a/terminal.c +++ b/terminal.c @@ -203,6 +203,30 @@ fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data) return true; } +static bool +add_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx) +{ + if (ptmx < 0) + return true; + if (conf->utempter_path == NULL) + return true; + + char *const argv[] = {conf->utempter_path, "add", NULL}; + return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL); +} + +static bool +del_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx) +{ + if (ptmx < 0) + return true; + if (conf->utempter_path == NULL) + return true; + + char *const argv[] = {conf->utempter_path, "del", NULL}; + return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL); +} + #if PTMX_TIMING static struct timespec last = {0}; #endif @@ -326,6 +350,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) } if (hup) { + del_utmp_record(term->conf, term->reaper, term->ptmx); fdm_del(fdm, fd); term->ptmx = -1; } @@ -1251,6 +1276,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, } term->font_line_height = conf->line_height; + add_utmp_record(conf, reaper, ptmx); + /* Start the slave/client */ if ((term->slave = slave_spawn( term->ptmx, argc, term->cwd, argv, envp, &conf->env_vars, @@ -1514,6 +1541,8 @@ term_shutdown(struct terminal *term) fdm_del(term->fdm, term->blink.fd); fdm_del(term->fdm, term->flash.fd); + del_utmp_record(term->conf, term->reaper, term->ptmx); + if (term->window != NULL && term->window->is_configured) fdm_del(term->fdm, term->ptmx); else @@ -1595,6 +1624,8 @@ term_destroy(struct terminal *term) } } + del_utmp_record(term->conf, term->reaper, term->ptmx); + fdm_del(term->fdm, term->selection.auto_scroll.fd); fdm_del(term->fdm, term->render.app_sync_updates.timer_fd); fdm_del(term->fdm, term->render.title.timer_fd); diff --git a/tests/test-config.c b/tests/test-config.c index 837c5106..ae7969dc 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -458,6 +458,7 @@ test_section_main(void) test_string(&ctx, &parse_section_main, "shell", &conf.shell); test_string(&ctx, &parse_section_main, "term", &conf.term); test_string(&ctx, &parse_section_main, "app-id", &conf.app_id); + test_string(&ctx, &parse_section_main, "utempter", &conf.utempter_path); test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters); From c93eb45b42691e0f13b21668298d2cf4a94e67e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 23 Sep 2022 23:04:10 +0200 Subject: [PATCH 76/81] =?UTF-8?q?term:=20utmp:=20set=20=E2=80=98host?= =?UTF-8?q?=E2=80=99=20to=20WAYLAND=5FDISPLAY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is similar to what XTerm does (setting it to DISPLAY) --- terminal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminal.c b/terminal.c index 4c9bf3db..df17201b 100644 --- a/terminal.c +++ b/terminal.c @@ -211,7 +211,7 @@ add_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx) if (conf->utempter_path == NULL) return true; - char *const argv[] = {conf->utempter_path, "add", NULL}; + char *const argv[] = {conf->utempter_path, "add", getenv("WAYLAND_DISPLAY"), NULL}; return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL); } @@ -223,7 +223,7 @@ del_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx) if (conf->utempter_path == NULL) return true; - char *const argv[] = {conf->utempter_path, "del", NULL}; + char *const argv[] = {conf->utempter_path, "del", getenv("WAYLAND_DISPLAY"), NULL}; return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL); } From bb02b319d02f986c8f8f2562a77b5cfbd7d51e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 24 Sep 2022 12:32:17 +0200 Subject: [PATCH 77/81] terminfo: add kxIN + kxOUT (focus in/out events) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These capabilities are not included in the standard ‘xterm’ or ‘xterm-256color’ terminfos. They’re used in ‘xterm+focus’ -> ‘xterm+sm+1002’ -> ‘xterm-1002|xterm+sm+1003’ -> ‘xterm-1003’ (https://invisible-island.net/ncurses/terminfo.ti.html#tic-xterm_focus) However, as far as I can tell, ncurses doesn’t use these capabilities at all. --- CHANGELOG.md | 2 +- foot.info | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc41eef..dc56c989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ * Support for adjusting the thickness of regular underlines ([#1136][1136]). * Support (optional) for utmp logging with libutempter. - +* `kxIN` and `kxOUT` (focus in/out events) to terminfo. [1136]: https://codeberg.org/dnkl/foot/issues/1136 diff --git a/foot.info b/foot.info index 2dae3eca..f4030b22 100644 --- a/foot.info +++ b/foot.info @@ -218,6 +218,8 @@ knp=\E[6~, kpp=\E[5~, kri=\E[1;2A, + kxIN=\E[I, + kxOUT=\E[O, oc=\E]104\E\\, op=\E[39;49m, rc=\E8, From 88c3128515832e64fa78a814402e30c1470f6d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Sep 2022 21:09:35 +0200 Subject: [PATCH 78/81] =?UTF-8?q?scripts:=20generate-builtin-terminfo:=20a?= =?UTF-8?q?dd=20synthetic=20=E2=80=98name=E2=80=99=20capability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same as ‘TN’; reports the terminfo name. --- CHANGELOG.md | 1 + scripts/generate-builtin-terminfo.py | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc56c989..ea3fac1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ * Support for adjusting the thickness of regular underlines ([#1136][1136]). * Support (optional) for utmp logging with libutempter. * `kxIN` and `kxOUT` (focus in/out events) to terminfo. +* `name` capability to `XTGETTCAP`. [1136]: https://codeberg.org/dnkl/foot/issues/1136 diff --git a/scripts/generate-builtin-terminfo.py b/scripts/generate-builtin-terminfo.py index c8d3be4b..906e2be0 100755 --- a/scripts/generate-builtin-terminfo.py +++ b/scripts/generate-builtin-terminfo.py @@ -166,6 +166,7 @@ def main(): entry.add_capability(IntCapability('Co', 256)) entry.add_capability(StringCapability('TN', target_entry_name)) + entry.add_capability(StringCapability('name', target_entry_name)) entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel terminfo_parts = [] From 9e5866109374a3181da06ad411b15bab4a728d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Sep 2022 19:00:27 +0200 Subject: [PATCH 79/81] slave: spawn: set PWD environment variable This improves handling of symlinks (in CWD) when launching a new terminal instance, either through ctrl+shift+n, or using the --working-directory command line option. Closes #1179 --- CHANGELOG.md | 2 ++ slave.c | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea3fac1a..7a6efd08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,8 +63,10 @@ * `DECRPM` replies (to `DECRQM` queries) now report a value of `4` ("permanently reset") instead of `2` ("reset") for DEC private modes that are known but unsupported. +* Set `PWD` environment variable in the slave process ([#1179][1179]). [1166]: https://codeberg.org/dnkl/foot/issues/1166 +[1179]: https://codeberg.org/dnkl/foot/issues/1179 ### Deprecated diff --git a/slave.c b/slave.c index 6473ac7c..4dd80e6f 100644 --- a/slave.c +++ b/slave.c @@ -352,6 +352,7 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv, setenv("TERM", term_env, 1); setenv("COLORTERM", "truecolor", 1); + setenv("PWD", cwd, 1); #if defined(FOOT_TERMINFO_PATH) setenv("TERMINFO", FOOT_TERMINFO_PATH, 1); From 90ce4f3008a7c7f01dcdee4504b9c512c96a41d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Sep 2022 19:09:33 +0200 Subject: [PATCH 80/81] main/client: use $PWD for cwd, when $PWD is valid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If $PWD is set, and its resolved path matches the *actual* working directory, use $PWD for cwd when instantiating the terminal. This makes a difference when $PWD refers to a symlink; before this patch, we’d instantiate the terminal in the *resolved* path. Now it’ll use the symlink instead. --- client.c | 22 ++++++++++++++++++++++ main.c | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/client.c b/client.c index 7624e7db..2a802d16 100644 --- a/client.c +++ b/client.c @@ -408,6 +408,28 @@ main(int argc, char *const *argv) cwd = _cwd; } + const char *pwd = getenv("PWD"); + if (pwd != NULL) { + char *resolved_path_cwd = realpath(cwd, NULL); + char *resolved_path_pwd = realpath(pwd, NULL); + + if (resolved_path_cwd != NULL && + resolved_path_pwd != NULL && + strcmp(resolved_path_cwd, resolved_path_pwd) == 0) + { + /* + * The resolved path of $PWD matches the resolved path of + * the *actual* working directory - use $PWD. + * + * This makes a difference when $PWD refers to a symlink. + */ + cwd = pwd; + } + + free(resolved_path_cwd); + free(resolved_path_pwd); + } + if (client_environment) { for (char **e = environ; *e != NULL; e++) { if (!push_string(&envp, *e, &total_len)) diff --git a/main.c b/main.c index 4617a3c7..a3ae579d 100644 --- a/main.c +++ b/main.c @@ -594,6 +594,28 @@ main(int argc, char *const *argv) cwd = _cwd; } + const char *pwd = getenv("PWD"); + if (pwd != NULL) { + char *resolved_path_cwd = realpath(cwd, NULL); + char *resolved_path_pwd = realpath(pwd, NULL); + + if (resolved_path_cwd != NULL && + resolved_path_pwd != NULL && + strcmp(resolved_path_cwd, resolved_path_pwd) == 0) + { + /* + * The resolved path of $PWD matches the resolved path of + * the *actual* working directory - use $PWD. + * + * This makes a difference when $PWD refers to a symlink. + */ + cwd = pwd; + } + + free(resolved_path_cwd); + free(resolved_path_pwd); + } + shm_set_max_pool_size(conf.tweak.max_shm_pool_size); if ((fdm = fdm_init()) == NULL) From 332cb90134bf7ba4f5248147aeee9f8f6a99a579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Sep 2022 19:16:40 +0200 Subject: [PATCH 81/81] spawn: set $PWD, in addition to calling chdir(cwd) --- spawn.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spawn.c b/spawn.c index 7c6641da..90b892f3 100644 --- a/spawn.c +++ b/spawn.c @@ -54,9 +54,12 @@ spawn(struct reaper *reaper, const char *cwd, char *const argv[], goto child_err; } - if (cwd != NULL && chdir(cwd) < 0) { - LOG_WARN("failed to change working directory to %s: %s", - cwd, strerror(errno)); + if (cwd != NULL) { + setenv("PWD", cwd, 1); + if (chdir(cwd) < 0) { + LOG_WARN("failed to change working directory to %s: %s", + cwd, strerror(errno)); + } } if (xdg_activation_token != NULL) {