From 8b979e9dba808dd9942e8cbe0a57195f674a227d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 22 Nov 2021 22:23:28 +0100 Subject: [PATCH 01/59] =?UTF-8?q?changelog:=20add=20#801=20to=20=E2=80=9CO?= =?UTF-8?q?SC-8=20data=20not=20being=20cleared...=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f17114..63a1c1ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,7 +51,8 @@ resulting in invalid error messages (https://codeberg.org/dnkl/foot/issues/809). * OSC-8 data not being cleared when cell is overwritten - (https://codeberg.org/dnkl/foot/issues/804). + (https://codeberg.org/dnkl/foot/issues/804, + https://codeberg.org/dnkl/foot/issues/801). ### Contributors From caec64cbdae2fc59da2a40cf3bf142a3df9b15e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 22 Nov 2021 22:24:13 +0100 Subject: [PATCH 02/59] =?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 63a1c1ce..cffc2c25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#Unreleased) * [1.10.1](#1-10-1) * [1.10.0](#1-10-0) * [1.9.2](#1-9-2) @@ -33,6 +34,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.10.1 ### Added From c1c0f11821e36e96add5060916d83533308b4126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 22 Nov 2021 23:02:25 +0100 Subject: [PATCH 03/59] config: add tweak.grapheme-width-method=max MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ‘max’ is a new value for ‘tweak.grapheme-width-method’. When enabled, the width of a grapheme cluster is that of the cluster’s widest codepoint. --- CHANGELOG.md | 5 +++++ config.c | 2 +- config.h | 6 +++++- doc/foot.ini.5.scd | 4 +++- vt.c | 4 ++++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cffc2c25..a2255c08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,12 @@ ## Unreleased + ### Added + +* New value, `max`, for `[tweak].grapheme-width-method`. + + ### Changed ### Deprecated ### Removed diff --git a/config.c b/config.c index 68061cc3..0616e87a 100644 --- a/config.c +++ b/config.c @@ -2248,7 +2248,7 @@ parse_section_tweak(struct context *ctx) return value_to_enum( ctx, - (const char *[]){"wcswidth", "double-width", NULL}, + (const char *[]){"wcswidth", "double-width", "max", NULL}, (int *)&conf->tweak.grapheme_width_method); } diff --git a/config.h b/config.h index 652f8422..afe9cf74 100644 --- a/config.h +++ b/config.h @@ -256,7 +256,11 @@ struct config { enum fcft_scaling_filter fcft_filter; bool overflowing_glyphs; bool grapheme_shaping; - enum {GRAPHEME_WIDTH_WCSWIDTH, GRAPHEME_WIDTH_DOUBLE} grapheme_width_method; + enum { + GRAPHEME_WIDTH_WCSWIDTH, + GRAPHEME_WIDTH_DOUBLE, + GRAPHEME_WIDTH_MAX, + } grapheme_width_method; bool render_timer_osd; bool render_timer_log; bool damage_whole_window; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 9f5c429b..1b6c4d8c 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1093,7 +1093,7 @@ any of these options. *grapheme-width-method* Selects which method to use when calculating the width (i.e. number of columns) of a grapheme cluster. One of - *double-width* and *wcswidth*. + *wcswidth*, *double-width* and *max*. *wcswidth* simply adds together the individual width of all codepoints making up the cluster. @@ -1104,6 +1104,8 @@ any of these options. internally to calculate the width. This results in cursor de-synchronization issues. + *max* uses the width of the largest codepoint in the cluster. + Default: _wcswidth_ *font-monospace-warn* diff --git a/vt.c b/vt.c index 687be912..736a2cfd 100644 --- a/vt.c +++ b/vt.c @@ -792,6 +792,10 @@ action_utf8_print(struct terminal *term, wchar_t wc) composed != NULL ? composed->width : base_width; switch (term->conf->tweak.grapheme_width_method) { + case GRAPHEME_WIDTH_MAX: + new_cc->width = max(grapheme_width, width); + break; + case GRAPHEME_WIDTH_DOUBLE: if (unlikely(wc == 0xfe0f)) width = 2; From 325ad6dd4e950546514448a838ce03027103d0bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 25 Nov 2021 15:21:53 +0100 Subject: [PATCH 04/59] input: finalize mouse selection on a pointer-leave event on the GRID surface If a mouse selection was ongoing, and the user switched workspace (probably using the keyboard...), and then back, the selection was still treated as ongoing, while all other mouse state has been reset. This meant the user had to tap at least once to stop the selection. --- CHANGELOG.md | 5 +++++ input.c | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2255c08..b506aafa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,11 @@ ### Deprecated ### Removed ### Fixed + +* An ongoing mouse selection is now finalized on a pointer leave event + (for example by switching workspace while doing a mouse selection). + + ### Security ### Contributors diff --git a/input.c b/input.c index 0aef43cc..02035909 100644 --- a/input.c +++ b/input.c @@ -1531,8 +1531,11 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, render_refresh_csd(old_moused); break; - case TERM_SURF_NONE: case TERM_SURF_GRID: + selection_finalize(seat, old_moused, seat->pointer.serial); + break; + + case TERM_SURF_NONE: case TERM_SURF_SEARCH: case TERM_SURF_SCROLLBACK_INDICATOR: case TERM_SURF_RENDER_TIMER: From 8c50a7afd401686f645fb26676fbcaedb8d4ab62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 25 Nov 2021 19:22:52 +0100 Subject: [PATCH 05/59] osc8: update URI ranges as we print data, *not* when the URI is closed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At first, an OSC-8 URI range was added when we received the closing OSC-8 escape (i.e. with an empty URI). But, this meant that cursor movements while the OSC-8 escape was in effect wasn’t handled correctly, since we’d add a range that spanned the cursor movements. Attempts were made to handle this in the cursor movement functions, by closing and re-opening the URI. However, there are too many corner cases to make this a viable approach. Scrolling is one such example, line-wrapping another. This patch takes a different approach; emit, or update the URI range when we print to the grid. This models the intended behavior much more closely, where an active OSC-8 URI act like any other SGR attribute - it is applied to all cells printed to, but otherwise have no effect. To avoid killing performance, this is only done in the “generic” printer. This means OSC-8 open/close calls must now “switch” the ASCII printer. Note that the “fast” printer still needs to *erase* pre-existing OSC-8 URIs. Closes #816 --- CHANGELOG.md | 3 + grid.c | 152 +++++++++++++++++++++++++++++++++++++++------------ grid.h | 2 + terminal.c | 101 +++++++--------------------------- terminal.h | 1 - 5 files changed, 143 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b506aafa..c185dd72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,9 @@ * An ongoing mouse selection is now finalized on a pointer leave event (for example by switching workspace while doing a mouse selection). +* OSC-8 URIs in the last column +* OSC-8 URIs sometimes being applied to too many, and seemingly + unrelated cells (https://codeberg.org/dnkl/foot/issues/816). ### Security diff --git a/grid.c b/grid.c index 459fa369..2dcbaa55 100644 --- a/grid.c +++ b/grid.c @@ -844,6 +844,117 @@ ensure_row_has_extra_data(struct row *row) row->extra = xcalloc(1, sizeof(*row->extra)); } +static void +verify_no_overlapping_uris(const struct row *row) +{ +#if defined(_DEBUG) + const struct row_data *extra = row->extra; + tll_foreach(extra->uri_ranges, it1) { + const struct row_uri_range *r1 = &it1->item; + + tll_foreach(extra->uri_ranges, it2) { + const struct row_uri_range *r2 = &it2->item; + + if (r1 == r2) + continue; + + if ((r1->start <= r2->start && r1->end >= r2->start) || + (r1->start <= r2->end && r1->end >= r2->end)) + { + BUG("OSC-8 URI overlap: %s: %d-%d: %s: %d-%d", + r1->uri, r1->start, r1->end, + r2->uri, r2->start, r2->end); + } + } + } +#endif +} + +void +grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) +{ + ensure_row_has_extra_data(row); + + struct row_data *extra = row->extra; + tll_rforeach(extra->uri_ranges, it) { + struct row_uri_range *r = &it->item; + + if (r->end + 1 < col) { + /* Ranges are sorted, and this means all the remaining + * URIs *end* before ‘col’ */ + break; + } + + if (r->start <= col && r->end >= col) { + /* + * ‘col’ is *inside* an existing range + */ + + if (r->id == id) { + /* ‘col’ is already inside a matching URI range */ + goto out; + } + + /* Splice old range */ + if (r->start == r->end) + tll_remove(extra->uri_ranges, it); + else if (r->start == col) + r->start++; + else if (r->end == col) + r->end--; + else { + xassert(r->start < col); + xassert(r->end > col); + + struct row_uri_range new_tail = { + .start = col + 1, + .end = r->end, + .id = r->id, + .uri = xstrdup(r->uri), + }; + + r->end = col - 1; + + xassert(r->start <= r->end); + xassert(new_tail.start <= new_tail.end); + + tll_insert_after(extra->uri_ranges, it, new_tail); + } + + break; /* Break out add and a new range for ‘col’ */ + } + + else if (r->id != id) + continue; + +#if 0 /* Trust the URI ID, for now... */ + if (strcmp(r->uri, uri) != 0) + continue; +#endif + + else if (likely(r->end + 1 == col)) { + r->end = col; + goto out; + } + + else if (col + 1 == r->start) { + r->start = col; + goto out; + } + } + + struct row_uri_range new_range = { + .start = col, + .end = col, + .id = id, + .uri = xstrdup(uri), + }; + grid_row_uri_range_add(row, new_range); + +out: + verify_no_overlapping_uris(row); +} + void grid_row_uri_range_add(struct row *row, struct row_uri_range range) { @@ -858,20 +969,7 @@ grid_row_uri_range_add(struct row *row, struct row_uri_range range) tll_push_front(row->extra->uri_ranges, range); out: - ; -#if defined(_DEBUG) - tll_foreach(row->extra->uri_ranges, it1) { - tll_foreach(row->extra->uri_ranges, it2) { - if (&it1->item == &it2->item) - continue; - - xassert(it1->item.start != it2->item.start); - xassert(it1->item.start != it2->item.end); - xassert(it1->item.end != it2->item.start); - xassert(it1->item.end != it2->item.end); - } - } -#endif + verify_no_overlapping_uris(row); } void @@ -929,36 +1027,22 @@ UNITTEST struct row_data row_data = {.uri_ranges = tll_init()}; struct row row = {.extra = &row_data}; -#define row_has_no_overlapping_uris(row) \ - do { \ - tll_foreach((row)->extra->uri_ranges, it1) { \ - tll_foreach((row)->extra->uri_ranges, it2) { \ - if (&it1->item == &it2->item) \ - continue; \ - xassert(it1->item.start != it2->item.start); \ - xassert(it1->item.start != it2->item.end); \ - xassert(it1->item.end != it2->item.start); \ - xassert(it1->item.end != it2->item.end); \ - } \ - } \ - } while (0) - grid_row_uri_range_add(&row, (struct row_uri_range){1, 10}); xassert(tll_length(row_data.uri_ranges) == 1); xassert(tll_front(row_data.uri_ranges).start == 1); xassert(tll_front(row_data.uri_ranges).end == 10); - row_has_no_overlapping_uris(&row); + verify_no_overlapping_uris(&row); grid_row_uri_range_add(&row, (struct row_uri_range){11, 20}); xassert(tll_length(row_data.uri_ranges) == 2); xassert(tll_back(row_data.uri_ranges).start == 11); xassert(tll_back(row_data.uri_ranges).end == 20); - row_has_no_overlapping_uris(&row); + verify_no_overlapping_uris(&row); /* Erase both URis */ grid_row_uri_range_erase(&row, 1, 20); xassert(tll_length(row_data.uri_ranges) == 0); - row_has_no_overlapping_uris(&row); + verify_no_overlapping_uris(&row); /* Two URIs, then erase second half of the first, first half of the second */ @@ -970,7 +1054,7 @@ UNITTEST xassert(tll_front(row_data.uri_ranges).end == 4); xassert(tll_back(row_data.uri_ranges).start == 16); xassert(tll_back(row_data.uri_ranges).end == 20); - row_has_no_overlapping_uris(&row); + verify_no_overlapping_uris(&row); tll_pop_back(row_data.uri_ranges); tll_pop_back(row_data.uri_ranges); @@ -984,9 +1068,7 @@ UNITTEST xassert(tll_front(row_data.uri_ranges).end == 4); xassert(tll_back(row_data.uri_ranges).start == 7); xassert(tll_back(row_data.uri_ranges).end == 10); - row_has_no_overlapping_uris(&row); - -#undef row_has_no_overlapping_uris + verify_no_overlapping_uris(&row); tll_free(row_data.uri_ranges); } diff --git a/grid.h b/grid.h index e2cd3a63..8ede4773 100644 --- a/grid.h +++ b/grid.h @@ -74,6 +74,8 @@ grid_row_in_view(struct grid *grid, int row_no) return row; } +void grid_row_uri_range_put( + struct row *row, int col, const char *uri, uint64_t id); void grid_row_uri_range_add(struct row *row, struct row_uri_range range); void grid_row_uri_range_erase(struct row *row, int start, int end); diff --git a/terminal.c b/terminal.c index 4cc9ed09..03ff1898 100644 --- a/terminal.c +++ b/terminal.c @@ -1134,9 +1134,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .blink = {.fd = -1}, .vt = { .state = 0, /* STATE_GROUND */ - .osc8 = { - .begin = {-1, -1}, - }, }, .colors = { .fg = conf->colors.fg, @@ -1826,7 +1823,6 @@ term_reset(struct terminal *term, bool hard) term->vt = (struct vt){ .state = 0, /* STATE_GROUND */ - .osc8 = {.begin = (struct coord){-1, -1}}, }; if (term->grid == &term->alt) { @@ -3151,7 +3147,8 @@ print_insert(struct terminal *term, int width) static void print_spacer(struct terminal *term, int col, int remaining) { - struct row *row = term->grid->cur_row; + struct grid *grid = term->grid; + struct row *row = grid->cur_row; struct cell *cell = &row->cells[col]; cell->wc = CELL_SPACER + remaining; @@ -3210,7 +3207,19 @@ term_print(struct terminal *term, wchar_t wc, int width) cell->wc = term->vt.last_printed = wc; cell->attrs = term->vt.attrs; - const int uri_start = col; + if (term->vt.osc8.uri != NULL) { + grid_row_uri_range_put( + row, col, term->vt.osc8.uri, term->vt.osc8.id); + + switch (term->conf->url.osc8_underline) { + case OSC8_UNDERLINE_ALWAYS: + cell->attrs.url = true; + break; + + case OSC8_UNDERLINE_URL_MODE: + break; + } + } /* Advance cursor the 'additional' columns while dirty:ing the cells */ for (int i = 1; i < width && col < term->cols - 1; i++) { @@ -3226,9 +3235,6 @@ term_print(struct terminal *term, wchar_t wc, int width) xassert(!grid->cursor.lcf); grid->cursor.point.col = col; - - if (unlikely(row->extra != NULL)) - grid_row_uri_range_erase(row, uri_start, uri_start + width - 1); } static void @@ -3260,7 +3266,6 @@ ascii_printer_fast(struct terminal *term, wchar_t wc) cell->wc = term->vt.last_printed = wc; cell->attrs = term->vt.attrs; - /* Advance cursor */ if (unlikely(++col >= term->cols)) { grid->cursor.lcf = true; @@ -3287,6 +3292,7 @@ term_update_ascii_printer(struct terminal *term) { void (*new_printer)(struct terminal *term, wchar_t wc) = unlikely(tll_length(term->grid->sixel_images) > 0 || + term->vt.osc8.uri != NULL || term->charsets.set[term->charsets.selected] == CHARSET_GRAPHIC || term->insert_mode) ? &ascii_printer_generic @@ -3294,7 +3300,7 @@ term_update_ascii_printer(struct terminal *term) #if defined(_DEBUG) && LOG_ENABLE_DBG if (term->ascii_printer != new_printer) { - LOG_DBG("switching ASCII printer %s -> %s", + LOG_DBG("§switching ASCII printer %s -> %s", term->ascii_printer == &ascii_printer_fast ? "fast" : "generic", new_printer == &ascii_printer_fast ? "fast" : "generic"); } @@ -3491,84 +3497,19 @@ term_ime_set_cursor_rect(struct terminal *term, int x, int y, int width, void term_osc8_open(struct terminal *term, uint64_t id, const char *uri) { - if (unlikely(term->vt.osc8.begin.row >= 0)) { - /* It’s valid to switch from one URI to another without - * closing the first one */ - term_osc8_close(term); - } - + term_osc8_close(term); xassert(term->vt.osc8.uri == NULL); - term->vt.osc8.begin = (struct coord){ - .col = term->grid->cursor.point.col, - .row = grid_row_absolute(term->grid, term->grid->cursor.point.row), - }; term->vt.osc8.id = id; term->vt.osc8.uri = xstrdup(uri); + term_update_ascii_printer(term); } void term_osc8_close(struct terminal *term) { - if (term->vt.osc8.begin.row < 0) - return; - - if (term->vt.osc8.uri[0] == '\0') - goto done; - - struct coord start = term->vt.osc8.begin; - struct coord end = (struct coord){ - .col = term->grid->cursor.point.col, - .row = grid_row_absolute(term->grid, term->grid->cursor.point.row), - }; - - if (start.row == end.row && start.col == end.col) { - /* Zero-length URL, e.g: \E]8;;http://foo\E\\\E]8;;\E\\ */ - goto done; - } - - /* end is *inclusive */ - if (--end.col < 0) { - end.row--; - end.col = term->cols - 1; - } - - int r = start.row; - int start_col = start.col; - while (true) { - int end_col = r == end.row ? end.col : term->cols - 1; - - struct row *row = term->grid->rows[r]; - - switch (term->conf->url.osc8_underline) { - case OSC8_UNDERLINE_ALWAYS: - for (int c = start_col; c <= end_col; c++) - row->cells[c].attrs.url = true; - break; - - case OSC8_UNDERLINE_URL_MODE: - break; - } - - struct row_uri_range range = { - .start = start_col, - .end = end_col, - .id = term->vt.osc8.id, - .uri = xstrdup(term->vt.osc8.uri), - }; - grid_row_uri_range_add(row, range); - start_col = 0; - - if (r == end.row) - break; - - r++; - r &= term->grid->num_rows - 1; - } - -done: free(term->vt.osc8.uri); - term->vt.osc8.id = 0; term->vt.osc8.uri = NULL; - term->vt.osc8.begin = (struct coord){-1, -1}; + term->vt.osc8.id = 0; + term_update_ascii_printer(term); } diff --git a/terminal.h b/terminal.h index b0526803..1f3913ae 100644 --- a/terminal.h +++ b/terminal.h @@ -185,7 +185,6 @@ struct vt { struct { uint64_t id; char *uri; - struct coord begin; } osc8; struct { From b1043a72f84cbb25892b4124edebefcdf938f147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Nov 2021 20:08:45 +0100 Subject: [PATCH 06/59] ci: fcft:text-shaping -> fcft:grapheme-shaping + fcft:run-shaping --- .gitlab-ci.yml | 2 +- .woodpecker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 99cb2728..296f6f78 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,7 +33,7 @@ debug-x64-no-grapheme-clustering: - apk del harfbuzz harfbuzz-dev utf8proc utf8proc-dev - mkdir -p bld/debug - cd bld/debug - - meson --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:text-shaping=disabled -Dfcft:test-text-shaping=false ../../ + - meson --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../../ - ninja -v -k0 - ninja -v test artifacts: diff --git a/.woodpecker.yml b/.woodpecker.yml index 0d97608a..b109aa0c 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -64,7 +64,7 @@ pipeline: - apk del harfbuzz harfbuzz-dev utf8proc utf8proc-dev - mkdir -p bld/debug - cd bld/debug - - meson --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:text-shaping=disabled -Dfcft:test-text-shaping=false ../.. + - meson --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../.. - ninja -v -k0 - ninja -v test - ./foot --version From ccee08a393f4c55994e921b43a16f5942816d764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Nov 2021 19:55:27 +0100 Subject: [PATCH 07/59] osc8: uri ranges: use a dynamically re-sizable array instead of a tllist --- grid.c | 408 ++++++++++++++++++++++++++++++++++++++--------------- grid.h | 13 +- terminal.h | 6 +- url-mode.c | 15 +- 4 files changed, 319 insertions(+), 123 deletions(-) diff --git a/grid.c b/grid.c index 2dcbaa55..e1b7ce38 100644 --- a/grid.c +++ b/grid.c @@ -49,19 +49,26 @@ grid_snapshot(const struct grid *grid) if (row->extra != NULL) { const struct row_data *extra = row->extra; - struct row_data *new_extra = xcalloc(1, sizeof(*new_extra)); + struct row_data *new_extra = xmalloc(sizeof(*new_extra)); + struct row_uri_range *ranges = + xmalloc(extra->uri_ranges.count * sizeof(ranges[0])); - tll_foreach(extra->uri_ranges, it) { - struct row_uri_range range = { - .start = it->item.start, - .end = it->item.end, - .id = it->item.id, - .uri = xstrdup(it->item.uri), + for (size_t i = 0; i < extra->uri_ranges.count; i++) { + const struct row_uri_range *range = &extra->uri_ranges.v[i]; + + ranges[i] = (struct row_uri_range){ + .start = range->start, + .end = range->end, + .id = range->id, + .uri = xstrdup(range->uri), }; - tll_push_back(new_extra->uri_ranges, range); } + new_extra->uri_ranges.v = ranges; + new_extra->uri_ranges.size = extra->uri_ranges.count; + new_extra->uri_ranges.count = extra->uri_ranges.count; + clone_row->extra = new_extra; } else clone_row->extra = NULL; @@ -224,17 +231,20 @@ grid_resize_without_reflow( if (old_row->extra == NULL) continue; - tll_foreach(old_row->extra->uri_ranges, it) { - if (it->item.start >= new_rows) { + for (size_t i = 0; i < old_row->extra->uri_ranges.count; i++) { + const struct row_uri_range *old_range = + &old_row->extra->uri_ranges.v[i]; + + if (old_range->start >= new_rows) { /* The whole range is truncated */ continue; } struct row_uri_range range = { - .start = it->item.start, - .end = min(it->item.end, new_cols - 1), - .id = it->item.id, - .uri = xstrdup(it->item.uri), + .start = old_range->start, + .end = min(old_range->end, new_cols - 1), + .id = old_range->id, + .uri = xstrdup(old_range->uri), }; grid_row_uri_range_add(new_row, range); } @@ -310,8 +320,11 @@ static void reflow_uri_range_end(struct row_uri_range *range, struct row *new_row, int new_col_idx) { - xassert(tll_length(new_row->extra->uri_ranges) > 0); - struct row_uri_range *new_range = &tll_back(new_row->extra->uri_ranges); + struct row_data *extra = new_row->extra; + xassert(extra->uri_ranges.count > 0); + + struct row_uri_range *new_range = + &extra->uri_ranges.v[extra->uri_ranges.count - 1]; xassert(new_range->id == range->id); xassert(new_range->end < 0); @@ -344,7 +357,8 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, } } - if (row->extra == NULL) + struct row_data *extra = row->extra; + if (extra == NULL) return new_row; /* @@ -352,8 +366,10 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, * ranges on the previous row, and re-open them on the * next/current row. */ - if (tll_length(row->extra->uri_ranges) > 0) { - struct row_uri_range *range = &tll_back(row->extra->uri_ranges); + if (extra->uri_ranges.count > 0) { + struct row_uri_range *range = + &extra->uri_ranges.v[extra->uri_ranges.count - 1]; + if (range->end < 0) { /* Terminate URI range on the previous row */ @@ -550,12 +566,15 @@ grid_resize_and_reflow( /* Does this row have any URIs? */ struct row_uri_range *range; - if (old_row->extra != NULL && tll_length(old_row->extra->uri_ranges) > 0) { - range = &tll_front(old_row->extra->uri_ranges); + struct row_data *extra = old_row->extra; + size_t uri_range_idx = 0; + + if (extra != NULL && extra->uri_ranges.count > 0) { + range = &extra->uri_ranges.v[uri_range_idx]; /* Make sure the *last* URI range's end point is included in the copy */ const struct row_uri_range *last_on_row = - &tll_back(old_row->extra->uri_ranges); + &extra->uri_ranges.v[extra->uri_ranges.count - 1]; col_count = max(col_count, last_on_row->end + 1); } else range = NULL; @@ -703,12 +722,13 @@ grid_resize_and_reflow( if (range->end == end - 1) { reflow_uri_range_end(range, new_row, new_col_idx - 1); - xassert(&tll_front(old_row->extra->uri_ranges) == range); + xassert(&extra->uri_ranges.v[uri_range_idx] == range); grid_row_uri_range_destroy(range); - tll_pop_front(old_row->extra->uri_ranges); - range = tll_length(old_row->extra->uri_ranges) > 0 - ? &tll_front(old_row->extra->uri_ranges) + uri_range_idx++; + + range = uri_range_idx < extra->uri_ranges.count + ? &old_row->extra->uri_ranges.v[uri_range_idx] : NULL; } } @@ -752,8 +772,8 @@ grid_resize_and_reflow( if (row->extra == NULL) continue; - tll_foreach(row->extra->uri_ranges, it) - xassert(it->item.end >= 0); + for (size_t i = 0; i < row->extra->uri_ranges.count; i++) + xassert(row->extra->uri_ranges.v[i].end >= 0); } /* Verify all old rows have been free:d */ @@ -849,14 +869,12 @@ verify_no_overlapping_uris(const struct row *row) { #if defined(_DEBUG) const struct row_data *extra = row->extra; - tll_foreach(extra->uri_ranges, it1) { - const struct row_uri_range *r1 = &it1->item; + for (size_t i = 0; i < extra->uri_ranges.count; i++) { + const struct row_uri_range *r1 = &extra->uri_ranges.v[i]; - tll_foreach(extra->uri_ranges, it2) { - const struct row_uri_range *r2 = &it2->item; - - if (r1 == r2) - continue; + for (size_t j = i + 1; j < extra->uri_ranges.count; j++) { + const struct row_uri_range *r2 = &extra->uri_ranges.v[j]; + xassert(r1 != r2); if ((r1->start <= r2->start && r1->end >= r2->start) || (r1->start <= r2->end && r1->end >= r2->end)) @@ -870,39 +888,129 @@ verify_no_overlapping_uris(const struct row *row) #endif } +static void +verify_uris_are_sorted(const struct row *row) +{ +#if defined(_DEBUG) + const struct row_data *extra = row->extra; + const struct row_uri_range *last = NULL; + + for (size_t i = 0; i < extra->uri_ranges.count; i++) { + const struct row_uri_range *r = &extra->uri_ranges.v[i]; + + if (last != NULL) { + if (last->start >= r->start || last->end >= r->end) { + BUG("OSC-8 URI not sorted correctly: " + "%s: %d-%d came before %s: %d-%d", + last->uri, last->start, last->end, + r->uri, r->start, r->end); + } + } + + last = r; + } +#endif +} + +static void +uri_range_ensure_size(struct row_data *extra, size_t count_to_add) +{ + if (extra->uri_ranges.count + count_to_add > extra->uri_ranges.size) { + extra->uri_ranges.size += count_to_add + 4; + extra->uri_ranges.v = xrealloc( + extra->uri_ranges.v, + extra->uri_ranges.size * sizeof(extra->uri_ranges.v[0])); + } + + xassert(extra->uri_ranges.count + count_to_add <= extra->uri_ranges.size); +} + +static void +uri_range_insert(struct row_data *extra, size_t idx, struct row_uri_range range) +{ + uri_range_ensure_size(extra, 1); + + xassert(idx <= extra->uri_ranges.count); + + const size_t move_count = extra->uri_ranges.count - idx; + memmove(&extra->uri_ranges.v[idx + 1], + &extra->uri_ranges.v[idx], + move_count * sizeof(extra->uri_ranges.v[0])); + + extra->uri_ranges.v[idx] = range; + extra->uri_ranges.count++; +} + +#if 0 +static void +uri_range_append(struct row_data *extra, struct row_uri_range range) +{ + uri_range_ensure_size(extra, 1); + extra->uri_ranges.v[extra->uri_ranges.count++] = range; +} +#endif +static void +uri_range_delete(struct row_data *extra, size_t idx) +{ + xassert(idx < extra->uri_ranges.count); + grid_row_uri_range_destroy(&extra->uri_ranges.v[idx]); + + const size_t move_count = extra->uri_ranges.count - idx - 1; + memmove(&extra->uri_ranges.v[idx], + &extra->uri_ranges.v[idx + 1], + move_count * sizeof(extra->uri_ranges.v[0])); + extra->uri_ranges.count--; +} + void grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) { ensure_row_has_extra_data(row); - struct row_data *extra = row->extra; - tll_rforeach(extra->uri_ranges, it) { - struct row_uri_range *r = &it->item; + size_t insert_idx = 0; + bool replace = false; + bool run_merge_pass = false; - if (r->end + 1 < col) { - /* Ranges are sorted, and this means all the remaining - * URIs *end* before ‘col’ */ + struct row_data *extra = row->extra; + for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) { + struct row_uri_range *r = &extra->uri_ranges.v[i]; + + const bool matching_id = r->id == id; + + if (matching_id && r->end + 1 == col) { + /* Extend existing URI’s tail */ + r->end++; + goto out; + } + + else if (r->end < col) { + insert_idx = i + 1; break; } - if (r->start <= col && r->end >= col) { - /* - * ‘col’ is *inside* an existing range - */ + else if (r->start > col) + continue; - if (r->id == id) { - /* ‘col’ is already inside a matching URI range */ + else { + xassert(r->start <= col); + xassert(r->end >= col); + + if (matching_id) goto out; - } - /* Splice old range */ - if (r->start == r->end) - tll_remove(extra->uri_ranges, it); - else if (r->start == col) + if (r->start == r->end) { + replace = true; + run_merge_pass = true; + insert_idx = i; + } else if (r->start == col) { + run_merge_pass = true; r->start++; - else if (r->end == col) + insert_idx = i; + } else if (r->end == col) { + run_merge_pass = true; r->end--; - else { + insert_idx = i + 1; + } else { xassert(r->start < col); xassert(r->end > col); @@ -912,34 +1020,17 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) .id = r->id, .uri = xstrdup(r->uri), }; - - r->end = col - 1; - - xassert(r->start <= r->end); xassert(new_tail.start <= new_tail.end); - tll_insert_after(extra->uri_ranges, it, new_tail); + uri_range_insert(extra, i + 1, new_tail); + + r->end = col - 1; + xassert(r->start <= r->end); + + insert_idx = i + 1; } - break; /* Break out add and a new range for ‘col’ */ - } - - else if (r->id != id) - continue; - -#if 0 /* Trust the URI ID, for now... */ - if (strcmp(r->uri, uri) != 0) - continue; -#endif - - else if (likely(r->end + 1 == col)) { - r->end = col; - goto out; - } - - else if (col + 1 == r->start) { - r->start = col; - goto out; + break; } } @@ -949,27 +1040,119 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) .id = id, .uri = xstrdup(uri), }; - grid_row_uri_range_add(row, new_range); + + xassert(insert_idx >= 0); + xassert(insert_idx <= extra->uri_ranges.count); + + if (replace) { + grid_row_uri_range_destroy(&extra->uri_ranges.v[insert_idx]); + extra->uri_ranges.v[insert_idx] = new_range; + } else + uri_range_insert(extra, insert_idx, new_range); + + if (run_merge_pass) { + for (size_t i = 1; i < extra->uri_ranges.count; i++) { + struct row_uri_range *r1 = &extra->uri_ranges.v[i - 1]; + struct row_uri_range *r2 = &extra->uri_ranges.v[i]; + + if (r1->id == r2->id && r1->end + 1 == r2->start) { + r1->end = r2->end; + uri_range_delete(extra, i); + i--; + } + } + } out: verify_no_overlapping_uris(row); + verify_uris_are_sorted(row); +} + +UNITTEST +{ + struct row_data row_data = {.uri_ranges = {0}}; + struct row row = {.extra = &row_data}; + +#define verify_range(idx, _start, _end, _id) \ + do { \ + xassert(idx < row_data.uri_ranges.count); \ + xassert(row_data.uri_ranges.v[idx].start == _start); \ + xassert(row_data.uri_ranges.v[idx].end == _end); \ + xassert(row_data.uri_ranges.v[idx].id == _id); \ + } while (0) + + grid_row_uri_range_put(&row, 0, "http://foo.bar", 123); + grid_row_uri_range_put(&row, 1, "http://foo.bar", 123); + grid_row_uri_range_put(&row, 2, "http://foo.bar", 123); + grid_row_uri_range_put(&row, 3, "http://foo.bar", 123); + xassert(row_data.uri_ranges.count == 1); + verify_range(0, 0, 3, 123); + + /* No-op */ + grid_row_uri_range_put(&row, 0, "http://foo.bar", 123); + xassert(row_data.uri_ranges.count == 1); + verify_range(0, 0, 3, 123); + + /* Replace head */ + grid_row_uri_range_put(&row, 0, "http://head", 456); + xassert(row_data.uri_ranges.count == 2); + verify_range(0, 0, 0, 456); + verify_range(1, 1, 3, 123); + + /* Replace tail */ + grid_row_uri_range_put(&row, 3, "http://tail", 789); + xassert(row_data.uri_ranges.count == 3); + verify_range(1, 1, 2, 123); + verify_range(2, 3, 3, 789); + + /* Replace tail + extend head */ + grid_row_uri_range_put(&row, 2, "http://tail", 789); + xassert(row_data.uri_ranges.count == 3); + verify_range(1, 1, 1, 123); + verify_range(2, 2, 3, 789); + + /* Replace + extend tail */ + grid_row_uri_range_put(&row, 1, "http://head", 456); + xassert(row_data.uri_ranges.count == 2); + verify_range(0, 0, 1, 456); + verify_range(1, 2, 3, 789); + + /* Replace + extend, then splice */ + grid_row_uri_range_put(&row, 1, "http://tail", 789); + grid_row_uri_range_put(&row, 2, "http://splice", 000); + xassert(row_data.uri_ranges.count == 4); + verify_range(0, 0, 0, 456); + verify_range(1, 1, 1, 789); + verify_range(2, 2, 2, 000); + verify_range(3, 3, 3, 789); + + for (size_t i = 0; i < row_data.uri_ranges.count; i++) + grid_row_uri_range_destroy(&row_data.uri_ranges.v[i]); + free(row_data.uri_ranges.v); + +#undef verify_range } void grid_row_uri_range_add(struct row *row, struct row_uri_range range) { ensure_row_has_extra_data(row); - tll_rforeach(row->extra->uri_ranges, it) { - if (it->item.end < range.start) { - tll_insert_after(row->extra->uri_ranges, it, range); + struct row_data *extra = row->extra; + + for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) { + const struct row_uri_range *r = &extra->uri_ranges.v[i]; + + if (r->end < range.start) { + uri_range_insert(extra, i + 1, range); goto out; } } - tll_push_front(row->extra->uri_ranges, range); + uri_range_insert(extra, 0, range); out: verify_no_overlapping_uris(row); + verify_uris_are_sorted(row); } void @@ -978,9 +1161,11 @@ grid_row_uri_range_erase(struct row *row, int start, int end) xassert(row->extra != NULL); xassert(start <= end); + struct row_data *extra = row->extra; + /* Split up, or remove, URI ranges affected by the erase */ - tll_foreach(row->extra->uri_ranges, it) { - struct row_uri_range *old = &it->item; + for (ssize_t i = 0; i < extra->uri_ranges.count; i++) { + struct row_uri_range *old = &extra->uri_ranges.v[i]; if (old->end < start) continue; @@ -990,8 +1175,8 @@ grid_row_uri_range_erase(struct row *row, int start, int end) if (start <= old->start && end >= old->end) { /* Erase range covers URI completely - remove it */ - grid_row_uri_range_destroy(old); - tll_remove(row->extra->uri_ranges, it); + uri_range_delete(extra, i); + i--; } else if (start > old->start && end < old->end) { @@ -1002,7 +1187,7 @@ grid_row_uri_range_erase(struct row *row, int start, int end) .id = old->id, .uri = old->uri != NULL ? xstrdup(old->uri) : NULL, }; - tll_insert_after(row->extra->uri_ranges, it, old_tail); + uri_range_insert(extra, i + 1, old_tail); old->end = start - 1; return; /* There can be no more URIs affected by the erase range */ } @@ -1024,51 +1209,54 @@ grid_row_uri_range_erase(struct row *row, int start, int end) UNITTEST { - struct row_data row_data = {.uri_ranges = tll_init()}; + struct row_data row_data = {.uri_ranges = {0}}; struct row row = {.extra = &row_data}; grid_row_uri_range_add(&row, (struct row_uri_range){1, 10}); - xassert(tll_length(row_data.uri_ranges) == 1); - xassert(tll_front(row_data.uri_ranges).start == 1); - xassert(tll_front(row_data.uri_ranges).end == 10); + xassert(row_data.uri_ranges.count == 1); + xassert(row_data.uri_ranges.v[0].start == 1); + xassert(row_data.uri_ranges.v[0].end == 10); verify_no_overlapping_uris(&row); + verify_uris_are_sorted(&row); grid_row_uri_range_add(&row, (struct row_uri_range){11, 20}); - xassert(tll_length(row_data.uri_ranges) == 2); - xassert(tll_back(row_data.uri_ranges).start == 11); - xassert(tll_back(row_data.uri_ranges).end == 20); + xassert(row_data.uri_ranges.count == 2); + xassert(row_data.uri_ranges.v[1].start == 11); + xassert(row_data.uri_ranges.v[1].end == 20); verify_no_overlapping_uris(&row); + verify_uris_are_sorted(&row); /* Erase both URis */ grid_row_uri_range_erase(&row, 1, 20); - xassert(tll_length(row_data.uri_ranges) == 0); + xassert(row_data.uri_ranges.count == 0); verify_no_overlapping_uris(&row); + verify_uris_are_sorted(&row); /* Two URIs, then erase second half of the first, first half of the second */ grid_row_uri_range_add(&row, (struct row_uri_range){1, 10}); grid_row_uri_range_add(&row, (struct row_uri_range){11, 20}); grid_row_uri_range_erase(&row, 5, 15); - xassert(tll_length(row_data.uri_ranges) == 2); - xassert(tll_front(row_data.uri_ranges).start == 1); - xassert(tll_front(row_data.uri_ranges).end == 4); - xassert(tll_back(row_data.uri_ranges).start == 16); - xassert(tll_back(row_data.uri_ranges).end == 20); + xassert(row_data.uri_ranges.count == 2); + xassert(row_data.uri_ranges.v[0].start == 1); + xassert(row_data.uri_ranges.v[0].end == 4); + xassert(row_data.uri_ranges.v[1].start == 16); + xassert(row_data.uri_ranges.v[1].end == 20); verify_no_overlapping_uris(&row); + verify_uris_are_sorted(&row); - tll_pop_back(row_data.uri_ranges); - tll_pop_back(row_data.uri_ranges); - xassert(tll_length(row_data.uri_ranges) == 0); + row_data.uri_ranges.count = 0; /* One URI, erase middle part of it */ grid_row_uri_range_add(&row, (struct row_uri_range){1, 10}); grid_row_uri_range_erase(&row, 5, 6); - xassert(tll_length(row_data.uri_ranges) == 2); - xassert(tll_front(row_data.uri_ranges).start == 1); - xassert(tll_front(row_data.uri_ranges).end == 4); - xassert(tll_back(row_data.uri_ranges).start == 7); - xassert(tll_back(row_data.uri_ranges).end == 10); + xassert(row_data.uri_ranges.count == 2); + xassert(row_data.uri_ranges.v[0].start == 1); + xassert(row_data.uri_ranges.v[0].end == 4); + xassert(row_data.uri_ranges.v[1].start == 7); + xassert(row_data.uri_ranges.v[1].end == 10); verify_no_overlapping_uris(&row); + verify_uris_are_sorted(&row); - tll_free(row_data.uri_ranges); + free(row_data.uri_ranges.v); } diff --git a/grid.h b/grid.h index 8ede4773..7819db4d 100644 --- a/grid.h +++ b/grid.h @@ -88,14 +88,15 @@ grid_row_uri_range_destroy(struct row_uri_range *range) static inline void grid_row_reset_extra(struct row *row) { - if (likely(row->extra == NULL)) + struct row_data *extra = row->extra; + + if (likely(extra == NULL)) return; - tll_foreach(row->extra->uri_ranges, it) { - grid_row_uri_range_destroy(&it->item); - tll_remove(row->extra->uri_ranges, it); - } + for (size_t i = 0; i < extra->uri_ranges.count; i++) + grid_row_uri_range_destroy(&extra->uri_ranges.v[i]); + free(extra->uri_ranges.v); - free(row->extra); + free(extra); row->extra = NULL; } diff --git a/terminal.h b/terminal.h index 1f3913ae..3096186f 100644 --- a/terminal.h +++ b/terminal.h @@ -102,7 +102,11 @@ struct row_uri_range { }; struct row_data { - tll(struct row_uri_range) uri_ranges; + struct { + struct row_uri_range *v; + uint32_t size; + uint32_t count; + } uri_ranges; }; struct row { diff --git a/url-mode.c b/url-mode.c index 8fb8a8a5..b565a87d 100644 --- a/url-mode.c +++ b/url-mode.c @@ -439,24 +439,27 @@ osc8_uris(const struct terminal *term, enum url_action action, url_list_t *urls) for (int r = 0; r < term->rows; r++) { const struct row *row = grid_row_in_view(term->grid, r); + const struct row_data *extra = row->extra; - if (row->extra == NULL) + if (extra == NULL) continue; - tll_foreach(row->extra->uri_ranges, it) { + for (size_t i = 0; i < extra->uri_ranges.count; i++) { + const struct row_uri_range *range = &extra->uri_ranges.v[i]; + struct coord start = { - .col = it->item.start, + .col = range->start, .row = r + term->grid->view, }; struct coord end = { - .col = it->item.end, + .col = range->end, .row = r + term->grid->view, }; tll_push_back( *urls, ((struct url){ - .id = it->item.id, - .url = xstrdup(it->item.uri), + .id = range->id, + .url = xstrdup(range->uri), .start = start, .end = end, .action = action, From 4eb0aa0f185ea9da604582bf8f091ab428b27997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Nov 2021 20:25:07 +0100 Subject: [PATCH 08/59] osc8: replace grid_row_uri_range_add() with internal uri_range_append() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grid_row_uri_range_add() was only used while reflowing. In this case, we know the new URIs being added are always going at the end of the URI list (since we’re going top-to-bottom, left-to-right). Thus, we don’t need the insertion logic, and can simply append instead. --- grid.c | 289 +++++++++++++++++++++++++++------------------------------ 1 file changed, 138 insertions(+), 151 deletions(-) diff --git a/grid.c b/grid.c index e1b7ce38..a68f1ee4 100644 --- a/grid.c +++ b/grid.c @@ -15,6 +15,108 @@ #define TIME_REFLOW 0 +static void +ensure_row_has_extra_data(struct row *row) +{ + if (row->extra == NULL) + row->extra = xcalloc(1, sizeof(*row->extra)); +} + +static void +verify_no_overlapping_uris(const struct row_data *extra) +{ +#if defined(_DEBUG) + for (size_t i = 0; i < extra->uri_ranges.count; i++) { + const struct row_uri_range *r1 = &extra->uri_ranges.v[i]; + + for (size_t j = i + 1; j < extra->uri_ranges.count; j++) { + const struct row_uri_range *r2 = &extra->uri_ranges.v[j]; + xassert(r1 != r2); + + if ((r1->start <= r2->start && r1->end >= r2->start) || + (r1->start <= r2->end && r1->end >= r2->end)) + { + BUG("OSC-8 URI overlap: %s: %d-%d: %s: %d-%d", + r1->uri, r1->start, r1->end, + r2->uri, r2->start, r2->end); + } + } + } +#endif +} + +static void +verify_uris_are_sorted(const struct row_data *extra) +{ +#if defined(_DEBUG) + const struct row_uri_range *last = NULL; + + for (size_t i = 0; i < extra->uri_ranges.count; i++) { + const struct row_uri_range *r = &extra->uri_ranges.v[i]; + + if (last != NULL) { + if (last->start >= r->start || last->end >= r->end) { + BUG("OSC-8 URI not sorted correctly: " + "%s: %d-%d came before %s: %d-%d", + last->uri, last->start, last->end, + r->uri, r->start, r->end); + } + } + + last = r; + } +#endif +} + +static void +uri_range_ensure_size(struct row_data *extra, size_t count_to_add) +{ + if (extra->uri_ranges.count + count_to_add > extra->uri_ranges.size) { + extra->uri_ranges.size += count_to_add + 4; + extra->uri_ranges.v = xrealloc( + extra->uri_ranges.v, + extra->uri_ranges.size * sizeof(extra->uri_ranges.v[0])); + } + + xassert(extra->uri_ranges.count + count_to_add <= extra->uri_ranges.size); +} + +static void +uri_range_insert(struct row_data *extra, size_t idx, struct row_uri_range range) +{ + uri_range_ensure_size(extra, 1); + + xassert(idx <= extra->uri_ranges.count); + + const size_t move_count = extra->uri_ranges.count - idx; + memmove(&extra->uri_ranges.v[idx + 1], + &extra->uri_ranges.v[idx], + move_count * sizeof(extra->uri_ranges.v[0])); + + extra->uri_ranges.v[idx] = range; + extra->uri_ranges.count++; +} + +static void +uri_range_append(struct row_data *extra, struct row_uri_range range) +{ + uri_range_ensure_size(extra, 1); + extra->uri_ranges.v[extra->uri_ranges.count++] = range; +} + +static void +uri_range_delete(struct row_data *extra, size_t idx) +{ + xassert(idx < extra->uri_ranges.count); + grid_row_uri_range_destroy(&extra->uri_ranges.v[idx]); + + const size_t move_count = extra->uri_ranges.count - idx - 1; + memmove(&extra->uri_ranges.v[idx], + &extra->uri_ranges.v[idx + 1], + move_count * sizeof(extra->uri_ranges.v[0])); + extra->uri_ranges.count--; +} + struct grid * grid_snapshot(const struct grid *grid) { @@ -246,7 +348,8 @@ grid_resize_without_reflow( .id = old_range->id, .uri = xstrdup(old_range->uri), }; - grid_row_uri_range_add(new_row, range); + ensure_row_has_extra_data(new_row); + uri_range_append(new_row->extra, range); } } @@ -259,6 +362,18 @@ grid_resize_without_reflow( new_row->dirty = true; } + for (size_t r = 0; r < new_rows; r++) { + const struct row *row = new_grid[r]; + + if (row == NULL) + continue; + if (row->extra == NULL) + continue; + + verify_no_overlapping_uris(row->extra); + verify_uris_are_sorted(row->extra); + } + /* Free old grid */ for (int r = 0; r < grid->num_rows; r++) grid_row_free(old_grid[r]); @@ -313,7 +428,8 @@ reflow_uri_range_start(struct row_uri_range *range, struct row *new_row, .uri = range->uri, }; range->uri = NULL; - grid_row_uri_range_add(new_row, new_range); + ensure_row_has_extra_data(new_row); + uri_range_append(new_row->extra, new_range); } static void @@ -382,7 +498,8 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, .id = range->id, .uri = xstrdup(range->uri), }; - grid_row_uri_range_add(new_row, new_range); + ensure_row_has_extra_data(new_row); + uri_range_append(new_row->extra, new_range); } } @@ -774,6 +891,9 @@ grid_resize_and_reflow( for (size_t i = 0; i < row->extra->uri_ranges.count; i++) xassert(row->extra->uri_ranges.v[i].end >= 0); + + verify_no_overlapping_uris(row->extra); + verify_uris_are_sorted(row->extra); } /* Verify all old rows have been free:d */ @@ -857,111 +977,6 @@ grid_resize_and_reflow( #endif } -static void -ensure_row_has_extra_data(struct row *row) -{ - if (row->extra == NULL) - row->extra = xcalloc(1, sizeof(*row->extra)); -} - -static void -verify_no_overlapping_uris(const struct row *row) -{ -#if defined(_DEBUG) - const struct row_data *extra = row->extra; - for (size_t i = 0; i < extra->uri_ranges.count; i++) { - const struct row_uri_range *r1 = &extra->uri_ranges.v[i]; - - for (size_t j = i + 1; j < extra->uri_ranges.count; j++) { - const struct row_uri_range *r2 = &extra->uri_ranges.v[j]; - xassert(r1 != r2); - - if ((r1->start <= r2->start && r1->end >= r2->start) || - (r1->start <= r2->end && r1->end >= r2->end)) - { - BUG("OSC-8 URI overlap: %s: %d-%d: %s: %d-%d", - r1->uri, r1->start, r1->end, - r2->uri, r2->start, r2->end); - } - } - } -#endif -} - -static void -verify_uris_are_sorted(const struct row *row) -{ -#if defined(_DEBUG) - const struct row_data *extra = row->extra; - const struct row_uri_range *last = NULL; - - for (size_t i = 0; i < extra->uri_ranges.count; i++) { - const struct row_uri_range *r = &extra->uri_ranges.v[i]; - - if (last != NULL) { - if (last->start >= r->start || last->end >= r->end) { - BUG("OSC-8 URI not sorted correctly: " - "%s: %d-%d came before %s: %d-%d", - last->uri, last->start, last->end, - r->uri, r->start, r->end); - } - } - - last = r; - } -#endif -} - -static void -uri_range_ensure_size(struct row_data *extra, size_t count_to_add) -{ - if (extra->uri_ranges.count + count_to_add > extra->uri_ranges.size) { - extra->uri_ranges.size += count_to_add + 4; - extra->uri_ranges.v = xrealloc( - extra->uri_ranges.v, - extra->uri_ranges.size * sizeof(extra->uri_ranges.v[0])); - } - - xassert(extra->uri_ranges.count + count_to_add <= extra->uri_ranges.size); -} - -static void -uri_range_insert(struct row_data *extra, size_t idx, struct row_uri_range range) -{ - uri_range_ensure_size(extra, 1); - - xassert(idx <= extra->uri_ranges.count); - - const size_t move_count = extra->uri_ranges.count - idx; - memmove(&extra->uri_ranges.v[idx + 1], - &extra->uri_ranges.v[idx], - move_count * sizeof(extra->uri_ranges.v[0])); - - extra->uri_ranges.v[idx] = range; - extra->uri_ranges.count++; -} - -#if 0 -static void -uri_range_append(struct row_data *extra, struct row_uri_range range) -{ - uri_range_ensure_size(extra, 1); - extra->uri_ranges.v[extra->uri_ranges.count++] = range; -} -#endif -static void -uri_range_delete(struct row_data *extra, size_t idx) -{ - xassert(idx < extra->uri_ranges.count); - grid_row_uri_range_destroy(&extra->uri_ranges.v[idx]); - - const size_t move_count = extra->uri_ranges.count - idx - 1; - memmove(&extra->uri_ranges.v[idx], - &extra->uri_ranges.v[idx + 1], - move_count * sizeof(extra->uri_ranges.v[0])); - extra->uri_ranges.count--; -} - void grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) { @@ -1064,8 +1079,8 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) } out: - verify_no_overlapping_uris(row); - verify_uris_are_sorted(row); + verify_no_overlapping_uris(extra); + verify_uris_are_sorted(extra); } UNITTEST @@ -1133,28 +1148,6 @@ UNITTEST #undef verify_range } -void -grid_row_uri_range_add(struct row *row, struct row_uri_range range) -{ - ensure_row_has_extra_data(row); - struct row_data *extra = row->extra; - - for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) { - const struct row_uri_range *r = &extra->uri_ranges.v[i]; - - if (r->end < range.start) { - uri_range_insert(extra, i + 1, range); - goto out; - } - } - - uri_range_insert(extra, 0, range); - -out: - verify_no_overlapping_uris(row); - verify_uris_are_sorted(row); -} - void grid_row_uri_range_erase(struct row *row, int start, int end) { @@ -1212,51 +1205,45 @@ UNITTEST struct row_data row_data = {.uri_ranges = {0}}; struct row row = {.extra = &row_data}; - grid_row_uri_range_add(&row, (struct row_uri_range){1, 10}); - xassert(row_data.uri_ranges.count == 1); - xassert(row_data.uri_ranges.v[0].start == 1); - xassert(row_data.uri_ranges.v[0].end == 10); - verify_no_overlapping_uris(&row); - verify_uris_are_sorted(&row); - - grid_row_uri_range_add(&row, (struct row_uri_range){11, 20}); + uri_range_append(&row_data, (struct row_uri_range){1, 10}); + uri_range_append(&row_data, (struct row_uri_range){11, 20}); xassert(row_data.uri_ranges.count == 2); xassert(row_data.uri_ranges.v[1].start == 11); xassert(row_data.uri_ranges.v[1].end == 20); - verify_no_overlapping_uris(&row); - verify_uris_are_sorted(&row); + verify_no_overlapping_uris(&row_data); + verify_uris_are_sorted(&row_data); /* Erase both URis */ grid_row_uri_range_erase(&row, 1, 20); xassert(row_data.uri_ranges.count == 0); - verify_no_overlapping_uris(&row); - verify_uris_are_sorted(&row); + verify_no_overlapping_uris(&row_data); + verify_uris_are_sorted(&row_data); /* Two URIs, then erase second half of the first, first half of the second */ - grid_row_uri_range_add(&row, (struct row_uri_range){1, 10}); - grid_row_uri_range_add(&row, (struct row_uri_range){11, 20}); + uri_range_append(&row_data, (struct row_uri_range){1, 10}); + uri_range_append(&row_data, (struct row_uri_range){11, 20}); grid_row_uri_range_erase(&row, 5, 15); xassert(row_data.uri_ranges.count == 2); xassert(row_data.uri_ranges.v[0].start == 1); xassert(row_data.uri_ranges.v[0].end == 4); xassert(row_data.uri_ranges.v[1].start == 16); xassert(row_data.uri_ranges.v[1].end == 20); - verify_no_overlapping_uris(&row); - verify_uris_are_sorted(&row); + verify_no_overlapping_uris(&row_data); + verify_uris_are_sorted(&row_data); row_data.uri_ranges.count = 0; /* One URI, erase middle part of it */ - grid_row_uri_range_add(&row, (struct row_uri_range){1, 10}); + uri_range_append(&row_data, (struct row_uri_range){1, 10}); grid_row_uri_range_erase(&row, 5, 6); xassert(row_data.uri_ranges.count == 2); xassert(row_data.uri_ranges.v[0].start == 1); xassert(row_data.uri_ranges.v[0].end == 4); xassert(row_data.uri_ranges.v[1].start == 7); xassert(row_data.uri_ranges.v[1].end == 10); - verify_no_overlapping_uris(&row); - verify_uris_are_sorted(&row); + verify_no_overlapping_uris(&row_data); + verify_uris_are_sorted(&row_data); free(row_data.uri_ranges.v); } From be203aeae1280ad7cac3ec9e287d1dd97528f390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Nov 2021 20:27:55 +0100 Subject: [PATCH 09/59] grid: bug: OSC-8 URIs were incorrectly skipped while resizing the alt screen --- CHANGELOG.md | 2 ++ grid.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c185dd72..9b928d68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,8 @@ * OSC-8 URIs in the last column * OSC-8 URIs sometimes being applied to too many, and seemingly unrelated cells (https://codeberg.org/dnkl/foot/issues/816). +* OSC-8 URIs incorrectly being dropped when resizing the terminal + window with the alternate screen active. ### Security diff --git a/grid.c b/grid.c index a68f1ee4..92675145 100644 --- a/grid.c +++ b/grid.c @@ -337,7 +337,7 @@ grid_resize_without_reflow( const struct row_uri_range *old_range = &old_row->extra->uri_ranges.v[i]; - if (old_range->start >= new_rows) { + if (old_range->start >= new_cols) { /* The whole range is truncated */ continue; } From 41565a0d0ef4486b8b0a223fc8cc28180022cb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Nov 2021 20:36:59 +0100 Subject: [PATCH 10/59] grid: resize without reflow: only verify URI ranges in debug builds --- grid.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grid.c b/grid.c index 92675145..dd6011c1 100644 --- a/grid.c +++ b/grid.c @@ -362,6 +362,7 @@ grid_resize_without_reflow( new_row->dirty = true; } +#if defined(_DEBUG) for (size_t r = 0; r < new_rows; r++) { const struct row *row = new_grid[r]; @@ -373,6 +374,7 @@ grid_resize_without_reflow( verify_no_overlapping_uris(row->extra); verify_uris_are_sorted(row->extra); } +#endif /* Free old grid */ for (int r = 0; r < grid->num_rows; r++) From 76992713161b589407060c2fc7a42e7ffb192844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Nov 2021 20:57:08 +0100 Subject: [PATCH 11/59] grid: reflow: no need to keep a uri-range-idx variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The URI ranges are now an array, which means we can get the next URI range by incrementing the pointer. Instead of checking if range is not NULL, check that it isn’t the range terminator (which is NULL when we don’t have any ranges, and the first address *after* the last range otherwise). --- grid.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/grid.c b/grid.c index dd6011c1..e168353f 100644 --- a/grid.c +++ b/grid.c @@ -684,19 +684,20 @@ grid_resize_and_reflow( tp = NULL; /* Does this row have any URIs? */ - struct row_uri_range *range; + struct row_uri_range *range, *range_terminator; struct row_data *extra = old_row->extra; - size_t uri_range_idx = 0; if (extra != NULL && extra->uri_ranges.count > 0) { - range = &extra->uri_ranges.v[uri_range_idx]; + range = &extra->uri_ranges.v[0]; + range_terminator = &extra->uri_ranges.v[extra->uri_ranges.count]; - /* Make sure the *last* URI range's end point is included in the copy */ + /* Make sure the *last* URI range's end point is included + * in the copy */ const struct row_uri_range *last_on_row = &extra->uri_ranges.v[extra->uri_ranges.count - 1]; col_count = max(col_count, last_on_row->end + 1); } else - range = NULL; + range = range_terminator = NULL; for (int start = 0, left = col_count; left > 0;) { int end; @@ -710,7 +711,7 @@ grid_resize_and_reflow( * If there are no more tracking points, or URI ranges, * the end-coordinate will be at the end of the row, */ - if (range != NULL) { + if (range != range_terminator) { int uri_col = (range->start >= start ? range->start : range->end) + 1; if (tp != NULL) { @@ -835,20 +836,15 @@ grid_resize_and_reflow( } if (uri_break) { + xassert(range != NULL); + if (range->start == end - 1) reflow_uri_range_start(range, new_row, new_col_idx - 1); if (range->end == end - 1) { reflow_uri_range_end(range, new_row, new_col_idx - 1); - - xassert(&extra->uri_ranges.v[uri_range_idx] == range); grid_row_uri_range_destroy(range); - - uri_range_idx++; - - range = uri_range_idx < extra->uri_ranges.count - ? &old_row->extra->uri_ranges.v[uri_range_idx] - : NULL; + range++; } } From 5c7e881cd49d7814c7f791f5e3457aca2b5f72d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 16:03:07 +0100 Subject: [PATCH 12/59] grid: uri_range_ensure_size(): add the requested amount of entries --- grid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grid.c b/grid.c index e168353f..609822e3 100644 --- a/grid.c +++ b/grid.c @@ -72,7 +72,7 @@ static void uri_range_ensure_size(struct row_data *extra, size_t count_to_add) { if (extra->uri_ranges.count + count_to_add > extra->uri_ranges.size) { - extra->uri_ranges.size += count_to_add + 4; + extra->uri_ranges.size += count_to_add + count_to_add; extra->uri_ranges.v = xrealloc( extra->uri_ranges.v, extra->uri_ranges.size * sizeof(extra->uri_ranges.v[0])); From 5c6c2de0517fbe93b748d3315b7b52ca682bac24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 16:07:50 +0100 Subject: [PATCH 13/59] grid: snapshot: use uri range utility functions --- grid.c | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/grid.c b/grid.c index 609822e3..110a1ce4 100644 --- a/grid.c +++ b/grid.c @@ -149,29 +149,24 @@ grid_snapshot(const struct grid *grid) for (int c = 0; c < grid->num_cols; c++) clone_row->cells[c] = row->cells[c]; - if (row->extra != NULL) { - const struct row_data *extra = row->extra; - struct row_data *new_extra = xmalloc(sizeof(*new_extra)); - struct row_uri_range *ranges = - xmalloc(extra->uri_ranges.count * sizeof(ranges[0])); + const struct row_data *extra = row->extra; + + if (extra != NULL) { + ensure_row_has_extra_data(clone_row); + uri_range_ensure_size(clone_row->extra, extra->uri_ranges.count); + + struct row_data *clone_extra = clone_row->extra; for (size_t i = 0; i < extra->uri_ranges.count; i++) { const struct row_uri_range *range = &extra->uri_ranges.v[i]; - - ranges[i] = (struct row_uri_range){ - .start = range->start, - .end = range->end, - .id = range->id, - .uri = xstrdup(range->uri), - }; - + uri_range_append( + clone_extra, + (struct row_uri_range){ + .start = range->start, + .end = range->end, + .id = range->id, + .uri = xstrdup(range->uri)}); } - - new_extra->uri_ranges.v = ranges; - new_extra->uri_ranges.size = extra->uri_ranges.count; - new_extra->uri_ranges.count = extra->uri_ranges.count; - - clone_row->extra = new_extra; } else clone_row->extra = NULL; } From dfeca4e13497f9a4295cb4c23e53e7639d614e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 16:24:26 +0100 Subject: [PATCH 14/59] grid: uri_range_{append,insert}: pass range attributes as separate parameters --- grid.c | 118 ++++++++++++++++++++++++++------------------------------- 1 file changed, 54 insertions(+), 64 deletions(-) diff --git a/grid.c b/grid.c index 110a1ce4..fd1e5b16 100644 --- a/grid.c +++ b/grid.c @@ -82,7 +82,8 @@ uri_range_ensure_size(struct row_data *extra, size_t count_to_add) } static void -uri_range_insert(struct row_data *extra, size_t idx, struct row_uri_range range) +uri_range_insert(struct row_data *extra, size_t idx, int start, int end, + uint64_t id, const char *uri) { uri_range_ensure_size(extra, 1); @@ -93,15 +94,33 @@ uri_range_insert(struct row_data *extra, size_t idx, struct row_uri_range range) &extra->uri_ranges.v[idx], move_count * sizeof(extra->uri_ranges.v[0])); - extra->uri_ranges.v[idx] = range; extra->uri_ranges.count++; + extra->uri_ranges.v[idx] = (struct row_uri_range){ + .start = start, + .end = end, + .id = id, + .uri = xstrdup(uri), + }; } static void -uri_range_append(struct row_data *extra, struct row_uri_range range) +uri_range_append_no_strdup(struct row_data *extra, int start, int end, + uint64_t id, char *uri) { uri_range_ensure_size(extra, 1); - extra->uri_ranges.v[extra->uri_ranges.count++] = range; + extra->uri_ranges.v[extra->uri_ranges.count++] = (struct row_uri_range){ + .start = start, + .end = end, + .id = id, + .uri = uri, + }; +} + +static void +uri_range_append(struct row_data *extra, int start, int end, uint64_t id, + const char *uri) +{ + uri_range_append_no_strdup(extra, start, end, id, xstrdup(uri)); } static void @@ -152,20 +171,16 @@ grid_snapshot(const struct grid *grid) const struct row_data *extra = row->extra; if (extra != NULL) { - ensure_row_has_extra_data(clone_row); - uri_range_ensure_size(clone_row->extra, extra->uri_ranges.count); + struct row_data *clone_extra = xcalloc(1, sizeof(*clone_extra)); + clone_row->extra = clone_extra; - struct row_data *clone_extra = clone_row->extra; + uri_range_ensure_size(clone_extra, extra->uri_ranges.count); for (size_t i = 0; i < extra->uri_ranges.count; i++) { const struct row_uri_range *range = &extra->uri_ranges.v[i]; uri_range_append( clone_extra, - (struct row_uri_range){ - .start = range->start, - .end = range->end, - .id = range->id, - .uri = xstrdup(range->uri)}); + range->start, range->end, range->id, range->uri); } } else clone_row->extra = NULL; @@ -337,14 +352,11 @@ grid_resize_without_reflow( continue; } - struct row_uri_range range = { - .start = old_range->start, - .end = min(old_range->end, new_cols - 1), - .id = old_range->id, - .uri = xstrdup(old_range->uri), - }; ensure_row_has_extra_data(new_row); - uri_range_append(new_row->extra, range); + uri_range_append( + new_row->extra, + old_range->start, min(old_range->end, new_cols - 1), + old_range->id, xstrdup(old_range->uri)); } } @@ -418,15 +430,10 @@ static void reflow_uri_range_start(struct row_uri_range *range, struct row *new_row, int new_col_idx) { - struct row_uri_range new_range = { - .start = new_col_idx, - .end = -1, - .id = range->id, - .uri = range->uri, - }; - range->uri = NULL; ensure_row_has_extra_data(new_row); - uri_range_append(new_row->extra, new_range); + uri_range_append_no_strdup + (new_row->extra, new_col_idx, -1, range->id, range->uri); + range->uri = NULL; } static void @@ -489,14 +496,8 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, range->end = col_count - 1; /* Open a new range on the new/current row */ - struct row_uri_range new_range = { - .start = 0, - .end = -1, - .id = range->id, - .uri = xstrdup(range->uri), - }; ensure_row_has_extra_data(new_row); - uri_range_append(new_row->extra, new_range); + uri_range_append(new_row->extra, 0, -1, range->id, range->uri); } } @@ -1022,15 +1023,7 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) xassert(r->start < col); xassert(r->end > col); - struct row_uri_range new_tail = { - .start = col + 1, - .end = r->end, - .id = r->id, - .uri = xstrdup(r->uri), - }; - xassert(new_tail.start <= new_tail.end); - - uri_range_insert(extra, i + 1, new_tail); + uri_range_insert(extra, i + 1, col + 1, r->end, r->id, r->uri); r->end = col - 1; xassert(r->start <= r->end); @@ -1042,21 +1035,19 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) } } - struct row_uri_range new_range = { - .start = col, - .end = col, - .id = id, - .uri = xstrdup(uri), - }; - xassert(insert_idx >= 0); xassert(insert_idx <= extra->uri_ranges.count); if (replace) { grid_row_uri_range_destroy(&extra->uri_ranges.v[insert_idx]); - extra->uri_ranges.v[insert_idx] = new_range; + extra->uri_ranges.v[insert_idx] = (struct row_uri_range){ + .start = col, + .end = col, + .id = id, + .uri = xstrdup(uri), + }; } else - uri_range_insert(extra, insert_idx, new_range); + uri_range_insert(extra, insert_idx, col, col, id, uri); if (run_merge_pass) { for (size_t i = 1; i < extra->uri_ranges.count; i++) { @@ -1167,13 +1158,8 @@ grid_row_uri_range_erase(struct row *row, int start, int end) else if (start > old->start && end < old->end) { /* Erase range erases a part in the middle of the URI */ - struct row_uri_range old_tail = { - .start = end + 1, - .end = old->end, - .id = old->id, - .uri = old->uri != NULL ? xstrdup(old->uri) : NULL, - }; - uri_range_insert(extra, i + 1, old_tail); + uri_range_insert( + extra, i + 1, end + 1, old->end, old->id, old->uri); old->end = start - 1; return; /* There can be no more URIs affected by the erase range */ } @@ -1198,8 +1184,8 @@ UNITTEST struct row_data row_data = {.uri_ranges = {0}}; struct row row = {.extra = &row_data}; - uri_range_append(&row_data, (struct row_uri_range){1, 10}); - uri_range_append(&row_data, (struct row_uri_range){11, 20}); + uri_range_append(&row_data, 1, 10, 0, "dummy"); + uri_range_append(&row_data, 11, 20, 0, "dummy"); xassert(row_data.uri_ranges.count == 2); xassert(row_data.uri_ranges.v[1].start == 11); xassert(row_data.uri_ranges.v[1].end == 20); @@ -1214,8 +1200,8 @@ UNITTEST /* Two URIs, then erase second half of the first, first half of the second */ - uri_range_append(&row_data, (struct row_uri_range){1, 10}); - uri_range_append(&row_data, (struct row_uri_range){11, 20}); + uri_range_append(&row_data, 1, 10, 0, "dummy"); + uri_range_append(&row_data, 11, 20, 0, "dummy"); grid_row_uri_range_erase(&row, 5, 15); xassert(row_data.uri_ranges.count == 2); xassert(row_data.uri_ranges.v[0].start == 1); @@ -1225,10 +1211,12 @@ UNITTEST verify_no_overlapping_uris(&row_data); verify_uris_are_sorted(&row_data); + grid_row_uri_range_destroy(&row_data.uri_ranges.v[0]); + grid_row_uri_range_destroy(&row_data.uri_ranges.v[1]); row_data.uri_ranges.count = 0; /* One URI, erase middle part of it */ - uri_range_append(&row_data, (struct row_uri_range){1, 10}); + uri_range_append(&row_data, 1, 10, 0, "dummy"); grid_row_uri_range_erase(&row, 5, 6); xassert(row_data.uri_ranges.count == 2); xassert(row_data.uri_ranges.v[0].start == 1); @@ -1238,5 +1226,7 @@ UNITTEST verify_no_overlapping_uris(&row_data); verify_uris_are_sorted(&row_data); + for (size_t i = 0; i < row_data.uri_ranges.count; i++) + grid_row_uri_range_destroy(&row_data.uri_ranges.v[i]); free(row_data.uri_ranges.v); } From 5ce6c89df638bb7bff5552997f671092cd32a858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 16:31:33 +0100 Subject: [PATCH 15/59] grid: resize without reflowing: use URI range utility functions --- grid.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/grid.c b/grid.c index fd1e5b16..de51f90b 100644 --- a/grid.c +++ b/grid.c @@ -340,23 +340,26 @@ grid_resize_without_reflow( } /* Copy URI ranges, truncating them if necessary */ - if (old_row->extra == NULL) + const struct row_data *old_extra = old_row->extra; + if (old_extra == NULL) continue; - for (size_t i = 0; i < old_row->extra->uri_ranges.count; i++) { - const struct row_uri_range *old_range = - &old_row->extra->uri_ranges.v[i]; + ensure_row_has_extra_data(new_row); + struct row_data *new_extra = new_row->extra; - if (old_range->start >= new_cols) { + uri_range_ensure_size(new_extra, old_extra->uri_ranges.count); + + for (size_t i = 0; i < old_extra->uri_ranges.count; i++) { + const struct row_uri_range *range = &old_extra->uri_ranges.v[i]; + + if (range->start >= new_cols) { /* The whole range is truncated */ continue; } - ensure_row_has_extra_data(new_row); - uri_range_append( - new_row->extra, - old_range->start, min(old_range->end, new_cols - 1), - old_range->id, xstrdup(old_range->uri)); + const int start = range->start; + const int end = min(range->end, new_cols - 1); + uri_range_append(new_extra, start, end, range->id, range->uri); } } From 747da83c765d8df4c369d3a9bf82ea87ce9f1284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 18:15:59 +0100 Subject: [PATCH 16/59] grid: remove useless assertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grid.c:1041:24: warning: comparison of unsigned expression in ‘>= 0’ is always true [-Wtype-limits] --- grid.c | 1 - 1 file changed, 1 deletion(-) diff --git a/grid.c b/grid.c index de51f90b..c0e10b23 100644 --- a/grid.c +++ b/grid.c @@ -1038,7 +1038,6 @@ grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) } } - xassert(insert_idx >= 0); xassert(insert_idx <= extra->uri_ranges.count); if (replace) { From 66b8c92c30667327ce7b5de3cbc068be67b3719a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Nov 2021 10:56:32 +0100 Subject: [PATCH 17/59] grid: uri_range_erase: walk the ranges backwards This way, ranges are deleted last-to-first, thus avoiding memmoving multiple ranges over and over again when erasing e.g. a full line. --- grid.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/grid.c b/grid.c index c0e10b23..bfb07d16 100644 --- a/grid.c +++ b/grid.c @@ -1143,19 +1143,18 @@ grid_row_uri_range_erase(struct row *row, int start, int end) struct row_data *extra = row->extra; /* Split up, or remove, URI ranges affected by the erase */ - for (ssize_t i = 0; i < extra->uri_ranges.count; i++) { + for (ssize_t i = (ssize_t)extra->uri_ranges.count - 1; i >= 0; i--) { struct row_uri_range *old = &extra->uri_ranges.v[i]; if (old->end < start) - continue; + return; if (old->start > end) - return; + continue; if (start <= old->start && end >= old->end) { /* Erase range covers URI completely - remove it */ uri_range_delete(extra, i); - i--; } else if (start > old->start && end < old->end) { @@ -1170,13 +1169,13 @@ grid_row_uri_range_erase(struct row *row, int start, int end) /* Erase range erases the head of the URI */ xassert(start <= old->start); old->start = end + 1; - return; /* There can be no more overlapping URIs */ } else if (start <= old->end && end >= old->end) { /* Erase range erases the tail of the URI */ xassert(end >= old->end); old->end = start - 1; + return; /* There can be no more overlapping URIs */ } } } From 2fe2dfa847a2fdc3563d53a1a1a81e1853868c4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Nov 2021 11:05:00 +0100 Subject: [PATCH 18/59] grid: uri_range_erase(): unittest: try erasing a row without any URIs --- grid.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/grid.c b/grid.c index bfb07d16..d6ddafad 100644 --- a/grid.c +++ b/grid.c @@ -1185,6 +1185,10 @@ UNITTEST struct row_data row_data = {.uri_ranges = {0}}; struct row row = {.extra = &row_data}; + /* Try erasing a row without any URIs */ + grid_row_uri_range_erase(&row, 0, 200); + xassert(row_data.uri_ranges.count == 0); + uri_range_append(&row_data, 1, 10, 0, "dummy"); uri_range_append(&row_data, 11, 20, 0, "dummy"); xassert(row_data.uri_ranges.count == 2); From fec42e5941c20f4b69164dd0d78ea7e7e0f17de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 16 Nov 2021 16:32:58 +0100 Subject: [PATCH 19/59] kitty kbd: add flag state, implement push/pop/update/query --- csi.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ terminal.c | 4 +++ terminal.h | 15 +++++++++ 3 files changed, 110 insertions(+) diff --git a/csi.c b/csi.c index 0224dd80..45bf1a16 100644 --- a/csi.c +++ b/csi.c @@ -1444,6 +1444,16 @@ csi_dispatch(struct terminal *term, uint8_t final) break; } + case 'u': { + enum kitty_kbd_flags flags = + term->grid->kitty_kbd.flags[term->grid->kitty_kbd.idx]; + + char reply[8]; + int chars = snprintf(reply, sizeof(reply), "\033[?%uu", flags); + term_to_slave(term, reply, chars); + break; + } + default: UNHANDLED(); break; @@ -1532,6 +1542,26 @@ csi_dispatch(struct terminal *term, uint8_t final) } break; /* final == 'm' */ + case 'u': { + int flags = vt_param_get(term, 0, 0) & KITTY_KBD_MASK; + + struct grid *grid = term->grid; + uint8_t idx = grid->kitty_kbd.idx; + + if (idx + 1 >= ALEN(grid->kitty_kbd.flags)) { + /* Stack full, evict oldest by wrapping around */ + idx = 0; + } else + idx++; + + grid->kitty_kbd.flags[idx] = flags; + grid->kitty_kbd.idx = idx; + + xassert(grid->kitty_kbd.idx < ALEN(grid->kitty_kbd.flags)); + LOG_DBG("kitty kbd: pushed new mode: 0x%03x", flags); + break; + } + case 'q': { /* XTVERSION */ if (vt_param_get(term, 0, 0) != 0) { @@ -1556,6 +1586,37 @@ csi_dispatch(struct terminal *term, uint8_t final) break; /* private[0] == '>' */ } + case '<': { + switch (final) { + case 'u': { + int count = vt_param_get(term, 0, 1); + LOG_DBG("kitty kbd: popping %d levels of flags", count); + + struct grid *grid = term->grid; + uint8_t idx = grid->kitty_kbd.idx; + + for (int i = 0; i < count; i++) { + /* Reset flags. This ensures we get flags=0 when + * over-popping */ + grid->kitty_kbd.flags[idx] = 0; + + if (idx == 0) + idx = ALEN(grid->kitty_kbd.flags) - 1; + else + idx--; + } + + grid->kitty_kbd.idx = idx; + xassert(grid->kitty_kbd.idx < ALEN(grid->kitty_kbd.flags)); + + LOG_DBG("kitty kbd: mode after pop: 0x%03x", + term->grid->kitty_kbd.flags[idx]); + break; + } + } + break; /* private[0] == ‘<’ */ + } + case ' ': { switch (final) { case 'q': { @@ -1633,6 +1694,36 @@ csi_dispatch(struct terminal *term, uint8_t final) term_to_slave(term, "\033P!|464f4f54\033\\", 14); /* FOOT */ break; + case 'u': { + int flag_set = vt_param_get(term, 0, 0) & KITTY_KBD_MASK; + int mode = vt_param_get(term, 1, 1); + + struct grid *grid = term->grid; + uint8_t idx = grid->kitty_kbd.idx; + + switch (mode) { + case 1: + /* set bits are set, unset bits are reset */ + grid->kitty_kbd.flags[idx] = flag_set; + break; + + case 2: + /* set bits are set, unset bits are left unchanged */ + grid->kitty_kbd.flags[idx] |= flag_set; + break; + + case 3: + /* set bits are reset, unset bits are left unchanged */ + grid->kitty_kbd.flags[idx] &= ~flag_set; + break; + + default: + UNHANDLED(); + break; + } + break; + } + default: UNHANDLED(); break; diff --git a/terminal.c b/terminal.c index 03ff1898..a56ead03 100644 --- a/terminal.c +++ b/terminal.c @@ -1815,6 +1815,10 @@ term_reset(struct terminal *term, bool hard) tll_free_and_free(term->window_title_stack, free); term_set_window_title(term, term->conf->title); + memset(term->normal.kitty_kbd.flags, 0, sizeof(term->normal.kitty_kbd.flags)); + memset(term->alt.kitty_kbd.flags, 0, sizeof(term->alt.kitty_kbd.flags)); + term->normal.kitty_kbd.idx = term->alt.kitty_kbd.idx = 0; + term->scroll_region.start = 0; term->scroll_region.end = term->rows; diff --git a/terminal.h b/terminal.h index 3096186f..c20e30fc 100644 --- a/terminal.h +++ b/terminal.h @@ -127,6 +127,15 @@ struct sixel { bool opaque; }; +enum kitty_kbd_flags { + KITTY_KBD_DISAMBIGUATE = 0x01, + KITTY_KBD_REPORT_EVENT = 0x02, + KITTY_BKD_REPORT_ALTERNATE = 0x04, + KITTY_KBD_REPORT_ALL = 0x08, + KITTY_KBD_REPORT_ASSOCIATED = 0x10, + KITTY_KBD_MASK = 0x1f, +}; + struct grid { int num_rows; int num_cols; @@ -149,6 +158,12 @@ struct grid { tll(struct damage) scroll_damage; tll(struct sixel) sixel_images; + + struct { + enum kitty_kbd_flags flags[8]; + uint8_t idx; + } kitty_kbd; + }; struct vt_subparams { From ad2539af4430751c5fceafc635a0b0a45cc438c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 16 Nov 2021 16:37:10 +0100 Subject: [PATCH 20/59] csi: kitty: s/mode/flags in debug logs --- csi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/csi.c b/csi.c index 45bf1a16..a62aaa35 100644 --- a/csi.c +++ b/csi.c @@ -1558,7 +1558,7 @@ csi_dispatch(struct terminal *term, uint8_t final) grid->kitty_kbd.idx = idx; xassert(grid->kitty_kbd.idx < ALEN(grid->kitty_kbd.flags)); - LOG_DBG("kitty kbd: pushed new mode: 0x%03x", flags); + LOG_DBG("kitty kbd: pushed new flags: 0x%03x", flags); break; } @@ -1609,7 +1609,7 @@ csi_dispatch(struct terminal *term, uint8_t final) grid->kitty_kbd.idx = idx; xassert(grid->kitty_kbd.idx < ALEN(grid->kitty_kbd.flags)); - LOG_DBG("kitty kbd: mode after pop: 0x%03x", + LOG_DBG("kitty kbd: flags after pop: 0x%03x", term->grid->kitty_kbd.flags[idx]); break; } From c3fda015544c2ef442656bc5b1b9a1363a247876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 16 Nov 2021 16:37:23 +0100 Subject: [PATCH 21/59] =?UTF-8?q?csi:=20kitty:=20log=20flags=20after=20?= =?UTF-8?q?=E2=80=98update=E2=80=99=20operation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- csi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/csi.c b/csi.c index a62aaa35..183caa08 100644 --- a/csi.c +++ b/csi.c @@ -1721,6 +1721,9 @@ csi_dispatch(struct terminal *term, uint8_t final) UNHANDLED(); break; } + + LOG_DBG("kitty kbd: flags after update: 0x%03x", + grid->kitty_kbd.flags[idx]); break; } From 3c01eb48dc1b54b8939b6afa628361523a9def3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 16 Nov 2021 19:55:23 +0100 Subject: [PATCH 22/59] =?UTF-8?q?term:=20kitty=20kbd:=20fix=20typo:=20?= =?UTF-8?q?=E2=80=98BKD=E2=80=99=20->=20=E2=80=98KBD=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- terminal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal.h b/terminal.h index c20e30fc..12d3fa0d 100644 --- a/terminal.h +++ b/terminal.h @@ -130,7 +130,7 @@ struct sixel { enum kitty_kbd_flags { KITTY_KBD_DISAMBIGUATE = 0x01, KITTY_KBD_REPORT_EVENT = 0x02, - KITTY_BKD_REPORT_ALTERNATE = 0x04, + KITTY_KBD_REPORT_ALTERNATE = 0x04, KITTY_KBD_REPORT_ALL = 0x08, KITTY_KBD_REPORT_ASSOCIATED = 0x10, KITTY_KBD_MASK = 0x1f, From 9f3dba683e0b308de9d41982089342f532634528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 17 Nov 2021 17:51:41 +0100 Subject: [PATCH 23/59] input: refactor: new function: legacy_kbd_protocol() This breaks out all handling of key escapes to-be-sent to the client, to a separate function, legacy_kbd_protocol(). That is, the key press/release handler first handles key generic handling, such as starting and stopping the repeat timer. Then it checks for foot keyboard bindings. If not bindings match, we need to pass the keyboard event to the client. This code has now been separated out into a new function. --- input.c | 212 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 107 insertions(+), 105 deletions(-) diff --git a/input.c b/input.c index 02035909..f5f6d754 100644 --- a/input.c +++ b/input.c @@ -983,6 +983,109 @@ get_current_modifiers(const struct seat *seat, } } +static void +legacy_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, + enum modifier mods, size_t count, + const uint8_t utf8[static count], uint32_t state) +{ + const struct key_data *keymap; + if (sym == XKB_KEY_Escape && mods == MOD_NONE && term->modify_escape_key) { + static const struct key_data esc = {.seq = "\033[27;1;27~"}; + keymap = &esc; + } else + keymap = keymap_lookup(term, sym, mods); + + if (keymap != NULL) { + term_to_slave(term, keymap->seq, strlen(keymap->seq)); + return; + } + + if (count == 0) + return; + +#define is_control_key(x) ((x) >= 0x40 && (x) <= 0x7f) +#define IS_CTRL(x) ((x) < 0x20 || ((x) >= 0x7f && (x) <= 0x9f)) + + LOG_DBG("term->modify_other_keys=%d, count=%d, is_ctrl=%d (utf8=0x%02x), sym=%d", + term->modify_other_keys_2, count, IS_CTRL(utf8[0]), utf8[0], sym); + + bool ctrl_is_in_effect = (mods & MOD_CTRL) != 0; + bool ctrl_seq = is_control_key(sym) || (count == 1 && IS_CTRL(utf8[0])); + + if (mods != MOD_NONE && (term->modify_other_keys_2 || + (ctrl_is_in_effect && !ctrl_seq))) + { + static const int mod_param_map[32] = { + [MOD_SHIFT] = 2, + [MOD_ALT] = 3, + [MOD_SHIFT | MOD_ALT] = 4, + [MOD_CTRL] = 5, + [MOD_SHIFT | MOD_CTRL] = 6, + [MOD_ALT | MOD_CTRL] = 7, + [MOD_SHIFT | MOD_ALT | MOD_CTRL] = 8, + [MOD_META] = 9, + [MOD_META | MOD_SHIFT] = 10, + [MOD_META | MOD_ALT] = 11, + [MOD_META | MOD_SHIFT | MOD_ALT] = 12, + [MOD_META | MOD_CTRL] = 13, + [MOD_META | MOD_SHIFT | MOD_CTRL] = 14, + [MOD_META | MOD_ALT | MOD_CTRL] = 15, + [MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL] = 16, + }; + + xassert(mods < sizeof(mod_param_map) / sizeof(mod_param_map[0])); + int modify_param = mod_param_map[mods]; + xassert(modify_param != 0); + + char reply[1024]; + size_t n = xsnprintf(reply, sizeof(reply), "\x1b[27;%d;%d~", modify_param, sym); + term_to_slave(term, reply, n); + } + + else if (mods & MOD_ALT) { + /* + * When the alt modifier is pressed, we do one out of three things: + * + * 1. we prefix the output bytes with ESC + * 2. we set the 8:th bit in the output byte + * 3. we ignore the alt modifier + * + * #1 is configured with \E[?1036, and is on by default + * + * If #1 has been disabled, we use #2, *if* it's a single + * byte we're emitting. Since this is an UTF-8 terminal, + * we then UTF8-encode the 8-bit character. #2 is + * configured with \E[?1034, and is on by default. + * + * Lastly, if both #1 and #2 have been disabled, the alt + * modifier is ignored. + */ + if (term->meta.esc_prefix) { + term_to_slave(term, "\x1b", 1); + term_to_slave(term, utf8, count); + } + + else if (term->meta.eight_bit && count == 1) { + const wchar_t wc = 0x80 | utf8[0]; + + char wc_as_utf8[8]; + mbstate_t ps = {0}; + size_t chars = wcrtomb(wc_as_utf8, wc, &ps); + + if (chars != (size_t)-1) + term_to_slave(term, wc_as_utf8, chars); + else + term_to_slave(term, wc_as_utf8, count); + } + + else { + /* Alt ignored */ + term_to_slave(term, utf8, count); + } + } else + term_to_slave(term, utf8, count); +} + static void key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, uint32_t key, uint32_t state) @@ -995,7 +1098,6 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, return; } - if (state == XKB_KEY_UP) { stop_repeater(seat, key); return; @@ -1120,24 +1222,6 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, keymap_mods |= seat->kbd.ctrl ? MOD_CTRL : MOD_NONE; keymap_mods |= seat->kbd.meta ? MOD_META : MOD_NONE; - const struct key_data *keymap; - if (sym == XKB_KEY_Escape && keymap_mods == MOD_NONE && term->modify_escape_key) { - static const struct key_data esc = {.seq = "\033[27;1;27~"}; - keymap = &esc; - } else - keymap = keymap_lookup(term, sym, keymap_mods); - - if (keymap != NULL) { - term_to_slave(term, keymap->seq, strlen(keymap->seq)); - - term_reset_view(term); - selection_cancel(term); - goto maybe_repeat; - } - - if (compose_status == XKB_COMPOSE_CANCELLED) - goto maybe_repeat; - /* * Compose, and maybe emit "normal" character */ @@ -1145,13 +1229,13 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, xassert(seat->kbd.xkb_compose_state != NULL || compose_status != XKB_COMPOSE_COMPOSED); + if (compose_status == XKB_COMPOSE_CANCELLED) + goto maybe_repeat; + int count = compose_status == XKB_COMPOSE_COMPOSED ? xkb_compose_state_get_utf8(seat->kbd.xkb_compose_state, NULL, 0) : xkb_state_key_get_utf8(seat->kbd.xkb_state, key, NULL, 0); - if (count <= 0) - goto maybe_repeat; - /* Buffer for translated key. Use a static buffer in most cases, * and use a malloc:ed buffer when necessary */ uint8_t buf[32]; @@ -1166,89 +1250,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, if (seat->kbd.xkb_compose_state != NULL) xkb_compose_state_reset(seat->kbd.xkb_compose_state); -#define is_control_key(x) ((x) >= 0x40 && (x) <= 0x7f) -#define IS_CTRL(x) ((x) < 0x20 || ((x) >= 0x7f && (x) <= 0x9f)) - - LOG_DBG("term->modify_other_keys=%d, count=%d, is_ctrl=%d (utf8=0x%02x), sym=%d", - term->modify_other_keys_2, count, IS_CTRL(utf8[0]), utf8[0], sym); - - bool ctrl_is_in_effect = (keymap_mods & MOD_CTRL) != 0; - bool ctrl_seq = is_control_key(sym) || (count == 1 && IS_CTRL(utf8[0])); - - if (keymap_mods != MOD_NONE && (term->modify_other_keys_2 || - (ctrl_is_in_effect && !ctrl_seq))) - { - static const int mod_param_map[32] = { - [MOD_SHIFT] = 2, - [MOD_ALT] = 3, - [MOD_SHIFT | MOD_ALT] = 4, - [MOD_CTRL] = 5, - [MOD_SHIFT | MOD_CTRL] = 6, - [MOD_ALT | MOD_CTRL] = 7, - [MOD_SHIFT | MOD_ALT | MOD_CTRL] = 8, - [MOD_META] = 9, - [MOD_META | MOD_SHIFT] = 10, - [MOD_META | MOD_ALT] = 11, - [MOD_META | MOD_SHIFT | MOD_ALT] = 12, - [MOD_META | MOD_CTRL] = 13, - [MOD_META | MOD_SHIFT | MOD_CTRL] = 14, - [MOD_META | MOD_ALT | MOD_CTRL] = 15, - [MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL] = 16, - }; - - xassert(keymap_mods < sizeof(mod_param_map) / sizeof(mod_param_map[0])); - int modify_param = mod_param_map[keymap_mods]; - xassert(modify_param != 0); - - char reply[1024]; - size_t n = xsnprintf(reply, sizeof(reply), "\x1b[27;%d;%d~", modify_param, sym); - term_to_slave(term, reply, n); - } - - else { - if (mods & (1 << seat->kbd.mod_alt)) { - /* - * When the alt modifier is pressed, we do one out of three things: - * - * 1. we prefix the output bytes with ESC - * 2. we set the 8:th bit in the output byte - * 3. we ignore the alt modifier - * - * #1 is configured with \E[?1036, and is on by default - * - * If #1 has been disabled, we use #2, *if* it's a single - * byte we're emitting. Since this is an UTF-8 terminal, - * we then UTF8-encode the 8-bit character. #2 is - * configured with \E[?1034, and is on by default. - * - * Lastly, if both #1 and #2 have been disabled, the alt - * modifier is ignored. - */ - if (term->meta.esc_prefix) { - term_to_slave(term, "\x1b", 1); - term_to_slave(term, utf8, count); - } - - else if (term->meta.eight_bit && count == 1) { - const wchar_t wc = 0x80 | utf8[0]; - - char utf8[8]; - mbstate_t ps = {0}; - size_t chars = wcrtomb(utf8, wc, &ps); - - if (chars != (size_t)-1) - term_to_slave(term, utf8, chars); - else - term_to_slave(term, utf8, count); - } - - else { - /* Alt ignored */ - term_to_slave(term, utf8, count); - } - } else - term_to_slave(term, utf8, count); - } + legacy_kbd_protocol(seat, term, sym, keymap_mods, count, utf8, state); if (utf8 != buf) free(utf8); From 2d85dbec6bbc0bfcb4ebfe219a024cd3f929a0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 20 Nov 2021 19:23:39 +0100 Subject: [PATCH 24/59] input: enable repeat while COMPOSING --- input.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/input.c b/input.c index f5f6d754..e9d3e22b 100644 --- a/input.c +++ b/input.c @@ -1128,10 +1128,8 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, seat->kbd.xkb_compose_state); } - if (compose_status == XKB_COMPOSE_COMPOSING) { - /* TODO: goto maybe_repeat? */ - return; - } + if (compose_status == XKB_COMPOSE_COMPOSING) + goto maybe_repeat; xkb_mod_mask_t mods, consumed; get_current_modifiers(seat, &mods, &consumed, key); From ab5dfa3f3b5eab1a0763856c8d24792c0f07c12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 20 Nov 2021 19:24:00 +0100 Subject: [PATCH 25/59] =?UTF-8?q?input:=20wip:=20add=20support=20for=20kit?= =?UTF-8?q?ty=20kbd=20protocol=20=E2=80=9CDisambiguate=20escape=20codes?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most things appear to work as in kitty. There’s one known difference: tri-state keys don’t generate the same unshifted symbol while holding Shift (but e.g. Ctrl+a and Ctrl+Shift+a *does* generate the same base symbol). For example, the Swedish keyboard layout has double quote, ‘2’ and ‘@’ on the same key. ‘2’ is the base (unshifted) symbol. Double quote is Shift+2. ‘@’ is AltGr+2. Kitty generates the same base symbol for ‘2’ and double quote (the base symbol is, as expected, ‘2’). But, for ‘@’ kitty generates the base symbol ‘@’. Currently, foot generates the base symbol by calling xkb_keysym_to_lower(). I _think_ what we need to do is “consume” the shift modifier, and then re-retrieve the symbol (given the current xkb state and key pressed). --- input.c | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 193 insertions(+), 21 deletions(-) diff --git a/input.c b/input.c index e9d3e22b..9db2318d 100644 --- a/input.c +++ b/input.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -985,15 +986,22 @@ get_current_modifiers(const struct seat *seat, static void legacy_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, - enum modifier mods, size_t count, - const uint8_t utf8[static count], uint32_t state) + xkb_mod_mask_t mods, xkb_mod_mask_t consumed, + size_t count, const uint8_t utf8[static count], + uint32_t state) { + enum modifier keymap_mods = MOD_NONE; + keymap_mods |= seat->kbd.shift ? MOD_SHIFT : MOD_NONE; + keymap_mods |= seat->kbd.alt ? MOD_ALT : MOD_NONE; + keymap_mods |= seat->kbd.ctrl ? MOD_CTRL : MOD_NONE; + keymap_mods |= seat->kbd.meta ? MOD_META : MOD_NONE; + const struct key_data *keymap; - if (sym == XKB_KEY_Escape && mods == MOD_NONE && term->modify_escape_key) { + if (sym == XKB_KEY_Escape && keymap_mods == MOD_NONE && term->modify_escape_key) { static const struct key_data esc = {.seq = "\033[27;1;27~"}; keymap = &esc; } else - keymap = keymap_lookup(term, sym, mods); + keymap = keymap_lookup(term, sym, keymap_mods); if (keymap != NULL) { term_to_slave(term, keymap->seq, strlen(keymap->seq)); @@ -1009,11 +1017,11 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, LOG_DBG("term->modify_other_keys=%d, count=%d, is_ctrl=%d (utf8=0x%02x), sym=%d", term->modify_other_keys_2, count, IS_CTRL(utf8[0]), utf8[0], sym); - bool ctrl_is_in_effect = (mods & MOD_CTRL) != 0; + bool ctrl_is_in_effect = (keymap_mods & MOD_CTRL) != 0; bool ctrl_seq = is_control_key(sym) || (count == 1 && IS_CTRL(utf8[0])); - if (mods != MOD_NONE && (term->modify_other_keys_2 || - (ctrl_is_in_effect && !ctrl_seq))) + if (keymap_mods != MOD_NONE && (term->modify_other_keys_2 || + (ctrl_is_in_effect && !ctrl_seq))) { static const int mod_param_map[32] = { [MOD_SHIFT] = 2, @@ -1033,8 +1041,8 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, [MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL] = 16, }; - xassert(mods < sizeof(mod_param_map) / sizeof(mod_param_map[0])); - int modify_param = mod_param_map[mods]; + xassert(keymap_mods < sizeof(mod_param_map) / sizeof(mod_param_map[0])); + int modify_param = mod_param_map[keymap_mods]; xassert(modify_param != 0); char reply[1024]; @@ -1042,7 +1050,7 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, term_to_slave(term, reply, n); } - else if (mods & MOD_ALT) { + else if (keymap_mods & MOD_ALT) { /* * When the alt modifier is pressed, we do one out of three things: * @@ -1086,6 +1094,168 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, term_to_slave(term, utf8, count); } +static void +kitty_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, + xkb_mod_mask_t mods, xkb_mod_mask_t consumed, + size_t count, const uint8_t utf8[static count], + uint32_t utf32, enum xkb_compose_status compose_status, + uint32_t state) +{ + /* TODO: shift seems to already have been excluded from ‘mods’*/ + xkb_mod_mask_t effective = mods & ~consumed; + + if (compose_status == XKB_COMPOSE_COMPOSED) { + term_to_slave(term, utf8, count); + return; + } + + if (effective == 0) { + switch (sym) { + case XKB_KEY_Return: term_to_slave(term, "\r", 1); return; + case XKB_KEY_BackSpace: term_to_slave(term, "\x7f", 1); return; + case XKB_KEY_Tab: term_to_slave(term, "\t", 1); return; + } + + if (iswprint(utf32)) { + term_to_slave(term, utf8, count); + return; + } + } + + unsigned int encoded_mods = 0; + encoded_mods |= mods & (1 << seat->kbd.mod_shift) ? (1 << 0) : 0; + encoded_mods |= mods & (1 << seat->kbd.mod_alt) ? (1 << 1) : 0; + encoded_mods |= mods & (1 << seat->kbd.mod_ctrl) ? (1 << 2) : 0; + encoded_mods |= mods & (1 << seat->kbd.mod_meta) ? (1 << 3) : 0; + encoded_mods++; + + int key = -1; + char final; + + switch (sym) { + case XKB_KEY_Escape: key = 27; final = 'u'; break; + case XKB_KEY_Return: key = 13; final = 'u'; break; + case XKB_KEY_Tab: key = 9; final = 'u'; break; + case XKB_KEY_BackSpace: key = 127; final = 'u'; break; + case XKB_KEY_Insert: key = 2; final = '~'; break; + case XKB_KEY_Delete: key = 3; final = '~'; break; + case XKB_KEY_Left: key = 1; final = 'D'; break; + case XKB_KEY_Right: key = 1; final = 'C'; break; + case XKB_KEY_Up: key = 1; final = 'A'; break; + case XKB_KEY_Down: key = 1; final = 'B'; break; + case XKB_KEY_Page_Up: key = 5; final = '~'; break; + case XKB_KEY_Page_Down: key = 6; final = '~'; break; + case XKB_KEY_Home: key = 1; final = 'H'; break; + case XKB_KEY_End: key = 1; final = 'F'; break; + case XKB_KEY_Caps_Lock: key = 57358; final = 'u'; break; + case XKB_KEY_Scroll_Lock: key = 57359; final = 'u'; break; + case XKB_KEY_Num_Lock: key = 57360; final = 'u'; break; + case XKB_KEY_Print: key = 57361; final = 'u'; break; + case XKB_KEY_Pause: key = 57362; final = 'u'; break; + case XKB_KEY_Menu: key = 57363; final = 'u'; break; + case XKB_KEY_F1: key = 1; final = 'P'; break; + case XKB_KEY_F2: key = 1; final = 'Q'; break; + case XKB_KEY_F3: key = 1; final = 'R'; break; + case XKB_KEY_F4: key = 1; final = 'S'; break; + case XKB_KEY_F5: key = 15; final = '~'; break; + case XKB_KEY_F6: key = 17; final = '~'; break; + case XKB_KEY_F7: key = 18; final = '~'; break; + case XKB_KEY_F8: key = 19; final = '~'; break; + case XKB_KEY_F9: key = 20; final = '~'; break; + case XKB_KEY_F10: key = 21; final = '~'; break; + case XKB_KEY_F11: key = 23; final = '~'; break; + case XKB_KEY_F12: key = 24; final = '~'; break; + case XKB_KEY_F13: key = 57376; final = 'u'; break; + case XKB_KEY_F14: key = 57377; final = 'u'; break; + case XKB_KEY_F15: key = 57378; final = 'u'; break; + case XKB_KEY_F16: key = 57379; final = 'u'; break; + case XKB_KEY_F17: key = 57380; final = 'u'; break; + case XKB_KEY_F18: key = 57381; final = 'u'; break; + case XKB_KEY_F19: key = 57382; final = 'u'; break; + case XKB_KEY_F20: key = 57383; final = 'u'; break; + case XKB_KEY_F21: key = 57384; final = 'u'; break; + case XKB_KEY_F22: key = 57385; final = 'u'; break; + case XKB_KEY_F23: key = 57386; final = 'u'; break; + case XKB_KEY_F24: key = 57387; final = 'u'; break; + case XKB_KEY_F25: key = 57388; final = 'u'; break; + case XKB_KEY_F26: key = 57389; final = 'u'; break; + case XKB_KEY_F27: key = 57390; final = 'u'; break; + case XKB_KEY_F28: key = 57391; final = 'u'; break; + case XKB_KEY_F29: key = 57392; final = 'u'; break; + case XKB_KEY_F30: key = 57393; final = 'u'; break; + case XKB_KEY_F31: key = 57394; final = 'u'; break; + case XKB_KEY_F32: key = 57395; final = 'u'; break; + case XKB_KEY_F33: key = 57396; final = 'u'; break; + case XKB_KEY_F34: key = 57397; final = 'u'; break; + case XKB_KEY_F35: key = 57398; final = 'u'; break; + case XKB_KEY_KP_0: key = 57399; final = 'u'; break; + case XKB_KEY_KP_1: key = 57400; final = 'u'; break; + case XKB_KEY_KP_2: key = 57401; final = 'u'; break; + case XKB_KEY_KP_3: key = 57402; final = 'u'; break; + case XKB_KEY_KP_4: key = 57403; final = 'u'; break; + case XKB_KEY_KP_5: key = 57404; final = 'u'; break; + case XKB_KEY_KP_6: key = 57405; final = 'u'; break; + case XKB_KEY_KP_7: key = 57406; final = 'u'; break; + case XKB_KEY_KP_8: key = 57407; final = 'u'; break; + case XKB_KEY_KP_9: key = 57408; final = 'u'; break; + case XKB_KEY_KP_Decimal: key = 57409; final = 'u'; break; + case XKB_KEY_KP_Divide: key = 57410; final = 'u'; break; + case XKB_KEY_KP_Multiply: key = 57411; final = 'u'; break; + case XKB_KEY_KP_Subtract: key = 57412; final = 'u'; break; + case XKB_KEY_KP_Add: key = 57413; final = 'u'; break; + case XKB_KEY_KP_Enter: key = 57414; final = 'u'; break; + case XKB_KEY_KP_Equal: key = 57415; final = 'u'; break; + case XKB_KEY_KP_Separator: key = 57416; final = 'u'; break; + case XKB_KEY_KP_Left: key = 57417; final = 'u'; break; + case XKB_KEY_KP_Right: key = 57418; final = 'u'; break; + case XKB_KEY_KP_Up: key = 57419; final = 'u'; break; + case XKB_KEY_KP_Down: key = 57420; final = 'u'; break; + case XKB_KEY_KP_Page_Up: key = 57421; final = 'u'; break; + case XKB_KEY_KP_Page_Down: key = 57422; final = 'u'; break; + case XKB_KEY_KP_Home: key = 57423; final = 'u'; break; + case XKB_KEY_KP_End: key = 57424; final = 'u'; break; + case XKB_KEY_KP_Insert: key = 57425; final = 'u'; break; + case XKB_KEY_KP_Delete: key = 57426; final = 'u'; break; + case XKB_KEY_KP_Begin: key = 1; final = 'E'; break; + + default: + if (count > 0) { + if (effective == 0) { + term_to_slave(term, utf8, count); + return; + } + + /* TODO: this isn’t correct */ + key = xkb_keysym_to_lower(sym); + final = 'u'; + } + break; + } + + xassert(encoded_mods >= 1); + + char buf[16]; + int bytes; + + if (key < 0) + return; + + if (final == 'u' || final == '~') { + if (encoded_mods > 1) + bytes = snprintf(buf, sizeof(buf), "\x1b[%u;%u%c", + key, encoded_mods, final); + else + bytes = snprintf(buf, sizeof(buf), "\x1b[%u%c", key, final); + } else { + if (encoded_mods > 1) + bytes = snprintf(buf, sizeof(buf), "\x1b[1;%u%c", encoded_mods, final); + else + bytes = snprintf(buf, sizeof(buf), "\x1b[%c", final); + } + + term_to_slave(term, buf, bytes); +} + static void key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, uint32_t key, uint32_t state) @@ -1214,11 +1384,6 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, * Keys generating escape sequences */ - enum modifier keymap_mods = MOD_NONE; - keymap_mods |= seat->kbd.shift ? MOD_SHIFT : MOD_NONE; - keymap_mods |= seat->kbd.alt ? MOD_ALT : MOD_NONE; - keymap_mods |= seat->kbd.ctrl ? MOD_CTRL : MOD_NONE; - keymap_mods |= seat->kbd.meta ? MOD_META : MOD_NONE; /* * Compose, and maybe emit "normal" character @@ -1238,18 +1403,25 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, * and use a malloc:ed buffer when necessary */ uint8_t buf[32]; uint8_t *utf8 = count < sizeof(buf) ? buf : xmalloc(count + 1); + uint32_t utf32 = (uint32_t)-1; - compose_status == XKB_COMPOSE_COMPOSED - ? xkb_compose_state_get_utf8( - seat->kbd.xkb_compose_state, (char *)utf8, count + 1) - : xkb_state_key_get_utf8( + if (compose_status == XKB_COMPOSE_COMPOSED) { + xkb_compose_state_get_utf8( + seat->kbd.xkb_compose_state, (char *)utf8, count + 1); + } else { + xkb_state_key_get_utf8( seat->kbd.xkb_state, key, (char *)utf8, count + 1); + utf32 = xkb_state_key_get_utf32(seat->kbd.xkb_state, key); + } + + if (term->grid->kitty_kbd.flags[term->grid->kitty_kbd.idx] != 0) + kitty_kbd_protocol(seat, term, sym, mods, consumed, count, utf8, utf32, compose_status, state); + else + legacy_kbd_protocol(seat, term, sym, mods, consumed, count, utf8, state); if (seat->kbd.xkb_compose_state != NULL) xkb_compose_state_reset(seat->kbd.xkb_compose_state); - legacy_kbd_protocol(seat, term, sym, keymap_mods, count, utf8, state); - if (utf8 != buf) free(utf8); From b9d03c16a6f01aa1d1154423761e447bc171ee11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 20 Nov 2021 20:31:59 +0100 Subject: [PATCH 26/59] input: kitty: use base symbol instead of lowering the symbol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When emitting an escape sequence for a printable character, with modifiers (e.g. ctrl+a), use the key’s base symbol instead of “lowering” it. This means we now handle e.g. ctrl+2 and ctrl+shift+2, with Swedish layout. There’s a twist however. We *only* use the base symbol if the modifiers that is used to “generate” the symbol are “significant”. Significant modifiers are, in this context, modifiers we can encode in the kitty escape sequences. In the Swedish layout, pressing AltGr+2 results in ‘@’. AltGr cannot be encoded in the kitty protocol. If we were to use the base symbol, AltGr+Alt+2 would result in exactly the same escape sequence as Alt+2. --- input.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++------- wayland.h | 2 + 2 files changed, 141 insertions(+), 21 deletions(-) diff --git a/input.c b/input.c index 9db2318d..f2dad7bc 100644 --- a/input.c +++ b/input.c @@ -674,6 +674,8 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, seat->kbd.mod_alt = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_ALT) ; seat->kbd.mod_ctrl = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_CTRL); seat->kbd.mod_meta = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_LOGO); + seat->kbd.mod_caps = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_CAPS); + seat->kbd.mod_num = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_NUM); seat->kbd.key_arrow_up = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "UP"); seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN"); @@ -961,16 +963,30 @@ UNITTEST xassert(strcmp(info->seq, "\033[27;3;13~") == 0); } -static void -get_current_modifiers(const struct seat *seat, - xkb_mod_mask_t *effective, - xkb_mod_mask_t *consumed, uint32_t key) +static xkb_mod_mask_t +get_insignificant_modifiers(const struct seat *seat) +{ + const xkb_mod_mask_t caps = 1 << seat->kbd.mod_caps; + const xkb_mod_mask_t num = 1 << seat->kbd.mod_num; + return caps | num; +} + +static xkb_mod_mask_t +get_significant_modifiers(const struct seat *seat) { const xkb_mod_mask_t ctrl = 1 << seat->kbd.mod_ctrl; const xkb_mod_mask_t alt = 1 << seat->kbd.mod_alt; const xkb_mod_mask_t shift = 1 << seat->kbd.mod_shift; const xkb_mod_mask_t meta = 1 << seat->kbd.mod_meta; - const xkb_mod_mask_t significant = ctrl | alt | shift | meta; + return ctrl | alt | shift | meta; +} + +static void +get_current_modifiers(const struct seat *seat, + xkb_mod_mask_t *effective, + xkb_mod_mask_t *consumed, uint32_t key) +{ + const xkb_mod_mask_t significant = get_significant_modifiers(seat); if (effective != NULL) { *effective = xkb_state_serialize_mods( @@ -984,11 +1000,32 @@ get_current_modifiers(const struct seat *seat, } } +struct kbd_ctx { + xkb_layout_index_t layout; + xkb_keycode_t key; + xkb_keysym_t sym; + + struct { + const xkb_keysym_t *syms; + size_t count; + } level0_syms; + + xkb_mod_mask_t mods; + xkb_mod_mask_t consumed; + + struct { + const uint8_t *buf; + size_t count; + } utf8; + uint32_t utf32; + + enum xkb_compose_status compose_status; + enum wl_keyboard_key_state key_state; +}; + static void -legacy_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, - xkb_mod_mask_t mods, xkb_mod_mask_t consumed, - size_t count, const uint8_t utf8[static count], - uint32_t state) +legacy_kbd_protocol(struct seat *seat, struct terminal *term, + const struct kbd_ctx *ctx) { enum modifier keymap_mods = MOD_NONE; keymap_mods |= seat->kbd.shift ? MOD_SHIFT : MOD_NONE; @@ -996,6 +1033,10 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, keymap_mods |= seat->kbd.ctrl ? MOD_CTRL : MOD_NONE; keymap_mods |= seat->kbd.meta ? MOD_META : MOD_NONE; + const xkb_keysym_t sym = ctx->sym; + const size_t count = ctx->utf8.count; + const uint8_t *const utf8 = ctx->utf8.buf; + const struct key_data *keymap; if (sym == XKB_KEY_Escape && keymap_mods == MOD_NONE && term->modify_escape_key) { static const struct key_data esc = {.seq = "\033[27;1;27~"}; @@ -1095,16 +1136,18 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, } static void -kitty_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, - xkb_mod_mask_t mods, xkb_mod_mask_t consumed, - size_t count, const uint8_t utf8[static count], - uint32_t utf32, enum xkb_compose_status compose_status, - uint32_t state) +kitty_kbd_protocol(struct seat *seat, struct terminal *term, + const struct kbd_ctx *ctx) { - /* TODO: shift seems to already have been excluded from ‘mods’*/ - xkb_mod_mask_t effective = mods & ~consumed; + const xkb_mod_mask_t mods = ctx->mods; + const xkb_mod_mask_t consumed = ctx->consumed; + const xkb_mod_mask_t effective = mods & ~consumed; + const xkb_keysym_t sym = ctx->sym; + const uint32_t utf32 = ctx->utf32; + const uint8_t *const utf8 = ctx->utf8.buf; + const size_t count = ctx->utf8.count; - if (compose_status == XKB_COMPOSE_COMPOSED) { + if (ctx->compose_status == XKB_COMPOSE_COMPOSED) { term_to_slave(term, utf8, count); return; } @@ -1225,8 +1268,64 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, xkb_keysym_t sym, return; } - /* TODO: this isn’t correct */ - key = xkb_keysym_to_lower(sym); + /* + * Use keysym (typically its Unicode codepoint value). + * + * If the keysym is shifted, use its unshifted codepoint + * instead. In other words, ctrl+a and ctrl+shift+a should + * both use the same value for ‘key’ (97 - i.a. ‘a’). + * + * However, if a non-significant modifier was used to + * generate the symbol. This is needed since we cannot + * encode non-significant modifiers, and thus the “extra” + * modifier(s) would get lost. + * + * Example: + * + * the Swedish layout has ‘2’, QUOTATION MARK (“double + * quote”), ‘@’, and ‘²’ on the same key. ‘2’ is the base + * symbol. + * + * Shift+2 results in QUOTATION MARK + * AltGr+2 results in ‘@’ + * AltGr+Shift+2 results in ‘²’ + * + * The kitty kbd protocol can’t encode AltGr. So, if we + * always used the base symbol (‘2’), Alt+Shift+2 would + * result in the same escape sequence as + * AltGr+Alt+Shift+2. + * + * (yes, this matches what kitty does, as of 0.23.1) + */ + + /* Get the key’s shift level */ + xkb_level_index_t lvl = xkb_state_key_get_level( + seat->kbd.xkb_state, ctx->key, ctx->layout); + + /* And get all modifier combinations that, combined with + * the pressed key, results in the current shift level */ + xkb_mod_mask_t masks[32]; + size_t mask_count = xkb_keymap_key_get_mods_for_level( + seat->kbd.xkb_keymap, ctx->key, ctx->layout, lvl, + masks, ALEN(masks)); + + xkb_mod_mask_t significant = get_significant_modifiers(seat); + xkb_mod_mask_t insignificant = get_insignificant_modifiers(seat); + + /* Check modifier combinations - if a combination has + * modifiers not in our set of ‘significant’ modifiers, + * use key sym as-is */ + bool use_level0_sym = true; + for (size_t i = 0; i < mask_count; i++) { + if ((masks[i] & ~insignificant & ~significant) > 0) { + use_level0_sym = false; + break; + } + } + + key = use_level0_sym && ctx->level0_syms.count > 0 + ? ctx->level0_syms.syms[0] + : sym; final = 'u'; } break; @@ -1414,10 +1513,29 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, utf32 = xkb_state_key_get_utf32(seat->kbd.xkb_state, key); } + struct kbd_ctx ctx = { + .layout = layout_idx, + .key = key, + .sym = sym, + .level0_syms = { + .syms = raw_syms, + .count = raw_count, + }, + .mods = mods, + .consumed = consumed, + .utf8 = { + .buf = utf8, + .count = count, + }, + .utf32 = utf32, + .compose_status = compose_status, + .key_state = state, + }; + if (term->grid->kitty_kbd.flags[term->grid->kitty_kbd.idx] != 0) - kitty_kbd_protocol(seat, term, sym, mods, consumed, count, utf8, utf32, compose_status, state); + kitty_kbd_protocol(seat, term, &ctx); else - legacy_kbd_protocol(seat, term, sym, mods, consumed, count, utf8, state); + legacy_kbd_protocol(seat, term, &ctx); if (seat->kbd.xkb_compose_state != NULL) xkb_compose_state_reset(seat->kbd.xkb_compose_state); diff --git a/wayland.h b/wayland.h index 0fffc0a7..c36dff52 100644 --- a/wayland.h +++ b/wayland.h @@ -195,6 +195,8 @@ struct seat { xkb_mod_index_t mod_alt; xkb_mod_index_t mod_ctrl; xkb_mod_index_t mod_meta; + xkb_mod_index_t mod_caps; + xkb_mod_index_t mod_num; xkb_keycode_t key_arrow_up; xkb_keycode_t key_arrow_down; From db746d72edb2fdacf3cc9dbdf1acfc6b70ec3b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 11:27:31 +0100 Subject: [PATCH 27/59] input: get_current_modifiers() no longer strips insignificant mods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Our internal binding handling cares about a different set of modifiers, compared to the kitty keyboard protocol. To handle this, get_current_modifiers() has been modified, to no longer strip the “unsignificant” modifiers. This is now up to the caller to do. To help, we keep two masks (for significant modifiers) in the seat struct; one for our internal binding handling (and the legacy keyboard protocol), and one for the kitty keyboard protocol. These two masks are updated when the seat’s keymap is updated/changed. --- input.c | 58 +++++++++++++++++++++++-------------------------------- wayland.h | 3 +++ 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/input.c b/input.c index f2dad7bc..c55bab3d 100644 --- a/input.c +++ b/input.c @@ -677,6 +677,16 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, seat->kbd.mod_caps = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_CAPS); seat->kbd.mod_num = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_NUM); + seat->kbd.bind_significant = + 1 << seat->kbd.mod_shift | + 1 << seat->kbd.mod_alt | + 1 << seat->kbd.mod_ctrl | + 1 << seat->kbd.mod_meta; + + seat->kbd.kitty_significant = seat->kbd.bind_significant | + 1 << seat->kbd.mod_caps | + 1 << seat->kbd.mod_num; + seat->kbd.key_arrow_up = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "UP"); seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN"); } @@ -963,41 +973,18 @@ UNITTEST xassert(strcmp(info->seq, "\033[27;3;13~") == 0); } -static xkb_mod_mask_t -get_insignificant_modifiers(const struct seat *seat) -{ - const xkb_mod_mask_t caps = 1 << seat->kbd.mod_caps; - const xkb_mod_mask_t num = 1 << seat->kbd.mod_num; - return caps | num; -} - -static xkb_mod_mask_t -get_significant_modifiers(const struct seat *seat) -{ - const xkb_mod_mask_t ctrl = 1 << seat->kbd.mod_ctrl; - const xkb_mod_mask_t alt = 1 << seat->kbd.mod_alt; - const xkb_mod_mask_t shift = 1 << seat->kbd.mod_shift; - const xkb_mod_mask_t meta = 1 << seat->kbd.mod_meta; - return ctrl | alt | shift | meta; -} - static void get_current_modifiers(const struct seat *seat, xkb_mod_mask_t *effective, xkb_mod_mask_t *consumed, uint32_t key) { - const xkb_mod_mask_t significant = get_significant_modifiers(seat); - if (effective != NULL) { *effective = xkb_state_serialize_mods( seat->kbd.xkb_state, XKB_STATE_MODS_EFFECTIVE); - *effective &= significant; } - if (consumed != NULL) { + if (consumed != NULL) *consumed = xkb_state_key_get_consumed_mods(seat->kbd.xkb_state, key); - *consumed &= significant; - } } struct kbd_ctx { @@ -1139,8 +1126,8 @@ static void kitty_kbd_protocol(struct seat *seat, struct terminal *term, const struct kbd_ctx *ctx) { - const xkb_mod_mask_t mods = ctx->mods; - const xkb_mod_mask_t consumed = ctx->consumed; + const xkb_mod_mask_t mods = ctx->mods & seat->kbd.kitty_significant; + const xkb_mod_mask_t consumed = ctx->consumed & seat->kbd.kitty_significant; const xkb_mod_mask_t effective = mods & ~consumed; const xkb_keysym_t sym = ctx->sym; const uint32_t utf32 = ctx->utf32; @@ -1170,6 +1157,8 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, encoded_mods |= mods & (1 << seat->kbd.mod_alt) ? (1 << 1) : 0; encoded_mods |= mods & (1 << seat->kbd.mod_ctrl) ? (1 << 2) : 0; encoded_mods |= mods & (1 << seat->kbd.mod_meta) ? (1 << 3) : 0; + encoded_mods |= mods & (1 << seat->kbd.mod_caps) ? (1 << 6) : 0; + encoded_mods |= mods & (1 << seat->kbd.mod_num) ? (1 << 7) : 0; encoded_mods++; int key = -1; @@ -1309,15 +1298,12 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, seat->kbd.xkb_keymap, ctx->key, ctx->layout, lvl, masks, ALEN(masks)); - xkb_mod_mask_t significant = get_significant_modifiers(seat); - xkb_mod_mask_t insignificant = get_insignificant_modifiers(seat); - /* Check modifier combinations - if a combination has * modifiers not in our set of ‘significant’ modifiers, * use key sym as-is */ bool use_level0_sym = true; for (size_t i = 0; i < mask_count; i++) { - if ((masks[i] & ~insignificant & ~significant) > 0) { + if ((masks[i] & ~seat->kbd.kitty_significant) > 0) { use_level0_sym = false; break; } @@ -1403,6 +1389,9 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, xkb_mod_mask_t mods, consumed; get_current_modifiers(seat, &mods, &consumed, key); + xkb_mod_mask_t bind_mods = mods & seat->kbd.bind_significant; + xkb_mod_mask_t bind_consumed = consumed & seat->kbd.bind_significant; + xkb_layout_index_t layout_idx = xkb_state_key_get_layout(seat->kbd.xkb_state, key); @@ -1414,13 +1403,13 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, if (should_repeat) start_repeater(seat, key); search_input( - seat, term, key, sym, mods, consumed, raw_syms, raw_count, serial); + seat, term, key, sym, bind_mods, bind_consumed, raw_syms, raw_count, serial); return; } else if (urls_mode_is_active(term)) { if (should_repeat) start_repeater(seat, key); urls_input( - seat, term, key, sym, mods, consumed, raw_syms, raw_count, serial); + seat, term, key, sym, bind_mods, bind_consumed, raw_syms, raw_count, serial); return; } @@ -1450,14 +1439,14 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, /* Match translated symbol */ if (bind->sym == sym && - bind->mods == (mods & ~consumed) && + bind->mods == (bind_mods & ~bind_consumed) && execute_binding( seat, term, bind->action, bind->pipe_argv, serial)) { goto maybe_repeat; } - if (bind->mods != mods) + if (bind->mods != bind_mods) continue; /* Match untranslated symbols */ @@ -2299,6 +2288,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, xkb_mod_mask_t mods; get_current_modifiers(seat, &mods, NULL, 0); + mods &= seat->kbd.bind_significant; /* Ignore Shift when matching modifiers, since it is * used to enable selection in mouse grabbing client diff --git a/wayland.h b/wayland.h index c36dff52..a2e1d17e 100644 --- a/wayland.h +++ b/wayland.h @@ -198,6 +198,9 @@ struct seat { xkb_mod_index_t mod_caps; xkb_mod_index_t mod_num; + xkb_mod_mask_t bind_significant; + xkb_mod_mask_t kitty_significant; + xkb_keycode_t key_arrow_up; xkb_keycode_t key_arrow_down; From e744cee7604eb0a98c4468d9f2160f440b851c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 11:38:36 +0100 Subject: [PATCH 28/59] input: kitty: printables are emitted as text, even if Caps- or Num-Lock is in effect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not sure if this is the best/correct way to do it. But kitty seems to ignore at least Num-Lock for printables, while it _does_ affect other keys. For example, Return, which usually emits ‘\r’, are affected by Num-Lock and emit ‘CSI 13;129u’. Note that as soon as some other modifier is in effect, the Num-Lock modifier *is* encoded in the CSI, also for printables. --- input.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/input.c b/input.c index c55bab3d..6a1c29bc 100644 --- a/input.c +++ b/input.c @@ -1145,11 +1145,22 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, case XKB_KEY_BackSpace: term_to_slave(term, "\x7f", 1); return; case XKB_KEY_Tab: term_to_slave(term, "\t", 1); return; } + } - if (iswprint(utf32)) { - term_to_slave(term, utf8, count); - return; - } + /* + * Printables without any modifiers are printed as is. + * + * TODO: plain text keys (a-z, 0-9 etc) are still printed as text, + * even when NumLock is active, despite NumLock being a + * significant modifier, *and* despite NumLock affecting other + * keys, like Return and Backspace; figure out if there’s some + * better magic than filtering out Caps- and Num-Lock here.. + */ + if (iswprint(utf32) && (effective & ~(1 << seat->kbd.mod_caps | + 1 << seat->kbd.mod_num)) == 0) + { + term_to_slave(term, utf8, count); + return; } unsigned int encoded_mods = 0; From ebad4bba2898cb285f69d42cfeb02b19bb142e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 11:44:53 +0100 Subject: [PATCH 29/59] input: kitty: disable CSI for Caps- and Num-Lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not sure why these keys have CSIs in the kitty spec; they don’t emit anything. Could it be that they are used if the keys are *not* modifiers in the current layout? --- input.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/input.c b/input.c index 6a1c29bc..b4a46ec7 100644 --- a/input.c +++ b/input.c @@ -1190,9 +1190,9 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, case XKB_KEY_Page_Down: key = 6; final = '~'; break; case XKB_KEY_Home: key = 1; final = 'H'; break; case XKB_KEY_End: key = 1; final = 'F'; break; - case XKB_KEY_Caps_Lock: key = 57358; final = 'u'; break; + //case XKB_KEY_Caps_Lock: key = 57358; final = 'u'; break; case XKB_KEY_Scroll_Lock: key = 57359; final = 'u'; break; - case XKB_KEY_Num_Lock: key = 57360; final = 'u'; break; + //case XKB_KEY_Num_Lock: key = 57360; final = 'u'; break; case XKB_KEY_Print: key = 57361; final = 'u'; break; case XKB_KEY_Pause: key = 57362; final = 'u'; break; case XKB_KEY_Menu: key = 57363; final = 'u'; break; From 8fb641a7ed66bf57ca77e0a6d574c3be43d4ce0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 11:53:22 +0100 Subject: [PATCH 30/59] =?UTF-8?q?input:=20handle=20=E2=80=9Cinvalid?= =?UTF-8?q?=E2=80=9D=20XKB=20modifiers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A modifier may not exist in a specific layout. This is indicated by XKB returning XKB_MOD_INVALID from xkb_keymap_mod_get_index(). --- input.c | 86 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/input.c b/input.c index b4a46ec7..8be6639d 100644 --- a/input.c +++ b/input.c @@ -362,10 +362,14 @@ conf_modifiers_to_mask(const struct seat *seat, const struct config_key_modifiers *modifiers) { xkb_mod_mask_t mods = 0; - mods |= modifiers->shift << seat->kbd.mod_shift; - mods |= modifiers->ctrl << seat->kbd.mod_ctrl; - mods |= modifiers->alt << seat->kbd.mod_alt; - mods |= modifiers->meta << seat->kbd.mod_meta; + if (seat->kbd.mod_shift != XKB_MOD_INVALID) + mods |= modifiers->shift << seat->kbd.mod_shift; + if (seat->kbd.mod_ctrl != XKB_MOD_INVALID) + mods |= modifiers->ctrl << seat->kbd.mod_ctrl; + if (seat->kbd.mod_alt != XKB_MOD_INVALID) + mods |= modifiers->alt << seat->kbd.mod_alt; + if (seat->kbd.mod_meta != XKB_MOD_INVALID) + mods |= modifiers->meta << seat->kbd.mod_meta; return mods; } @@ -677,15 +681,21 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, seat->kbd.mod_caps = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_CAPS); seat->kbd.mod_num = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_NUM); - seat->kbd.bind_significant = - 1 << seat->kbd.mod_shift | - 1 << seat->kbd.mod_alt | - 1 << seat->kbd.mod_ctrl | - 1 << seat->kbd.mod_meta; + seat->kbd.bind_significant = 0; + if (seat->kbd.mod_shift != XKB_MOD_INVALID) + seat->kbd.bind_significant |= 1 << seat->kbd.mod_shift; + if (seat->kbd.mod_alt != XKB_MOD_INVALID) + seat->kbd.bind_significant |= 1 << seat->kbd.mod_alt; + if (seat->kbd.mod_ctrl != XKB_MOD_INVALID) + seat->kbd.bind_significant |= 1 << seat->kbd.mod_ctrl; + if (seat->kbd.mod_meta != XKB_MOD_INVALID) + seat->kbd.bind_significant |= 1 << seat->kbd.mod_meta; - seat->kbd.kitty_significant = seat->kbd.bind_significant | - 1 << seat->kbd.mod_caps | - 1 << seat->kbd.mod_num; + seat->kbd.kitty_significant = seat->kbd.bind_significant; + if (seat->kbd.mod_caps != XKB_MOD_INVALID) + seat->kbd.kitty_significant |= 1 << seat->kbd.mod_caps; + if (seat->kbd.mod_num != XKB_MOD_INVALID) + seat->kbd.kitty_significant |= 1 << seat->kbd.mod_num; seat->kbd.key_arrow_up = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "UP"); seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN"); @@ -1129,6 +1139,9 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, const xkb_mod_mask_t mods = ctx->mods & seat->kbd.kitty_significant; const xkb_mod_mask_t consumed = ctx->consumed & seat->kbd.kitty_significant; const xkb_mod_mask_t effective = mods & ~consumed; + const xkb_mod_mask_t caps_num = + (seat->kbd.mod_caps != XKB_MOD_INVALID ? 1 << seat->kbd.mod_caps : 0) | + (seat->kbd.mod_num != XKB_MOD_INVALID ? 1 << seat->kbd.mod_num : 0); const xkb_keysym_t sym = ctx->sym; const uint32_t utf32 = ctx->utf32; const uint8_t *const utf8 = ctx->utf8.buf; @@ -1156,20 +1169,24 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, * keys, like Return and Backspace; figure out if there’s some * better magic than filtering out Caps- and Num-Lock here.. */ - if (iswprint(utf32) && (effective & ~(1 << seat->kbd.mod_caps | - 1 << seat->kbd.mod_num)) == 0) - { + if (iswprint(utf32) && (effective & ~caps_num) == 0) { term_to_slave(term, utf8, count); return; } unsigned int encoded_mods = 0; - encoded_mods |= mods & (1 << seat->kbd.mod_shift) ? (1 << 0) : 0; - encoded_mods |= mods & (1 << seat->kbd.mod_alt) ? (1 << 1) : 0; - encoded_mods |= mods & (1 << seat->kbd.mod_ctrl) ? (1 << 2) : 0; - encoded_mods |= mods & (1 << seat->kbd.mod_meta) ? (1 << 3) : 0; - encoded_mods |= mods & (1 << seat->kbd.mod_caps) ? (1 << 6) : 0; - encoded_mods |= mods & (1 << seat->kbd.mod_num) ? (1 << 7) : 0; + if (seat->kbd.mod_shift != XKB_MOD_INVALID) + encoded_mods |= mods & (1 << seat->kbd.mod_shift) ? (1 << 0) : 0; + if (seat->kbd.mod_alt != XKB_MOD_INVALID) + encoded_mods |= mods & (1 << seat->kbd.mod_alt) ? (1 << 1) : 0; + if (seat->kbd.mod_ctrl != XKB_MOD_INVALID) + encoded_mods |= mods & (1 << seat->kbd.mod_ctrl) ? (1 << 2) : 0; + if (seat->kbd.mod_meta != XKB_MOD_INVALID) + encoded_mods |= mods & (1 << seat->kbd.mod_meta) ? (1 << 3) : 0; + if (seat->kbd.mod_caps != XKB_MOD_INVALID) + encoded_mods |= mods & (1 << seat->kbd.mod_caps) ? (1 << 6) : 0; + if (seat->kbd.mod_num != XKB_MOD_INVALID) + encoded_mods |= mods & (1 << seat->kbd.mod_num) ? (1 << 7) : 0; encoded_mods++; int key = -1; @@ -1577,14 +1594,22 @@ keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, seat->kbd.xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); /* Update state of modifiers we're interested in for e.g mouse events */ - seat->kbd.shift = xkb_state_mod_index_is_active( - seat->kbd.xkb_state, seat->kbd.mod_shift, XKB_STATE_MODS_EFFECTIVE); - seat->kbd.alt = xkb_state_mod_index_is_active( - seat->kbd.xkb_state, seat->kbd.mod_alt, XKB_STATE_MODS_EFFECTIVE); - seat->kbd.ctrl = xkb_state_mod_index_is_active( - seat->kbd.xkb_state, seat->kbd.mod_ctrl, XKB_STATE_MODS_EFFECTIVE); - seat->kbd.meta = xkb_state_mod_index_is_active( - seat->kbd.xkb_state, seat->kbd.mod_meta, XKB_STATE_MODS_EFFECTIVE); + seat->kbd.shift = seat->kbd.mod_shift != XKB_MOD_INVALID + ? xkb_state_mod_index_is_active( + seat->kbd.xkb_state, seat->kbd.mod_shift, XKB_STATE_MODS_EFFECTIVE) + : false; + seat->kbd.alt = seat->kbd.mod_alt != XKB_MOD_INVALID + ? xkb_state_mod_index_is_active( + seat->kbd.xkb_state, seat->kbd.mod_alt, XKB_STATE_MODS_EFFECTIVE) + : false; + seat->kbd.ctrl = seat->kbd.mod_ctrl != XKB_MOD_INVALID + ? xkb_state_mod_index_is_active( + seat->kbd.xkb_state, seat->kbd.mod_ctrl, XKB_STATE_MODS_EFFECTIVE) + : false; + seat->kbd.meta = seat->kbd.mod_meta != XKB_MOD_INVALID + ? xkb_state_mod_index_is_active( + seat->kbd.xkb_state, seat->kbd.mod_meta, XKB_STATE_MODS_EFFECTIVE) + : false; } if (seat->kbd_focus && seat->kbd_focus->active_surface == TERM_SURF_GRID) @@ -2304,7 +2329,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, /* Ignore Shift when matching modifiers, since it is * used to enable selection in mouse grabbing client * applications */ - mods &= ~(1 << seat->kbd.mod_shift); + if (seat->kbd.mod_shift != XKB_MOD_INVALID) + mods &= ~(1 << seat->kbd.mod_shift); const struct mouse_binding *match = NULL; From a08494a766973cb580dc09ff4d5b3efb40f6ecb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 11:59:28 +0100 Subject: [PATCH 31/59] =?UTF-8?q?input:=20kitty:=20only=20emit=20CSIs=20fo?= =?UTF-8?q?r=20Caps-=20and=20Num-Lock=20when=20they=20aren=E2=80=99t=20mod?= =?UTF-8?q?ifiers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/input.c b/input.c index 8be6639d..06f5b731 100644 --- a/input.c +++ b/input.c @@ -1207,9 +1207,7 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, case XKB_KEY_Page_Down: key = 6; final = '~'; break; case XKB_KEY_Home: key = 1; final = 'H'; break; case XKB_KEY_End: key = 1; final = 'F'; break; - //case XKB_KEY_Caps_Lock: key = 57358; final = 'u'; break; case XKB_KEY_Scroll_Lock: key = 57359; final = 'u'; break; - //case XKB_KEY_Num_Lock: key = 57360; final = 'u'; break; case XKB_KEY_Print: key = 57361; final = 'u'; break; case XKB_KEY_Pause: key = 57362; final = 'u'; break; case XKB_KEY_Menu: key = 57363; final = 'u'; break; @@ -1278,6 +1276,20 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, case XKB_KEY_KP_Delete: key = 57426; final = 'u'; break; case XKB_KEY_KP_Begin: key = 1; final = 'E'; break; + case XKB_KEY_Caps_Lock: + if (seat->kbd.mod_caps == XKB_MOD_INVALID) { + key = 57358; + final = 'u'; + } + break; + + case XKB_KEY_Num_Lock: + if (seat->kbd.mod_num == XKB_MOD_INVALID) { + key = 57360; + final = 'u'; + } + break; + default: if (count > 0) { if (effective == 0) { From 66171f104588a280e598ceb38dc90d9acdce060f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 12:01:16 +0100 Subject: [PATCH 32/59] =?UTF-8?q?input:=20rename=20=E2=80=98meta=E2=80=99?= =?UTF-8?q?=20to=20=E2=80=98super=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input.c | 22 +++++++++++----------- terminal.c | 2 +- wayland.h | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/input.c b/input.c index 06f5b731..fed52a49 100644 --- a/input.c +++ b/input.c @@ -368,8 +368,8 @@ conf_modifiers_to_mask(const struct seat *seat, mods |= modifiers->ctrl << seat->kbd.mod_ctrl; if (seat->kbd.mod_alt != XKB_MOD_INVALID) mods |= modifiers->alt << seat->kbd.mod_alt; - if (seat->kbd.mod_meta != XKB_MOD_INVALID) - mods |= modifiers->meta << seat->kbd.mod_meta; + if (seat->kbd.mod_super != XKB_MOD_INVALID) + mods |= modifiers->meta << seat->kbd.mod_super; return mods; } @@ -677,7 +677,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, seat->kbd.mod_shift = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_SHIFT); seat->kbd.mod_alt = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_ALT) ; seat->kbd.mod_ctrl = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_CTRL); - seat->kbd.mod_meta = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_LOGO); + seat->kbd.mod_super = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_LOGO); seat->kbd.mod_caps = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_CAPS); seat->kbd.mod_num = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_MOD_NAME_NUM); @@ -688,8 +688,8 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, seat->kbd.bind_significant |= 1 << seat->kbd.mod_alt; if (seat->kbd.mod_ctrl != XKB_MOD_INVALID) seat->kbd.bind_significant |= 1 << seat->kbd.mod_ctrl; - if (seat->kbd.mod_meta != XKB_MOD_INVALID) - seat->kbd.bind_significant |= 1 << seat->kbd.mod_meta; + if (seat->kbd.mod_super != XKB_MOD_INVALID) + seat->kbd.bind_significant |= 1 << seat->kbd.mod_super; seat->kbd.kitty_significant = seat->kbd.bind_significant; if (seat->kbd.mod_caps != XKB_MOD_INVALID) @@ -801,7 +801,7 @@ keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, seat->kbd.shift = false; seat->kbd.alt = false; seat->kbd.ctrl = false; - seat->kbd.meta = false; + seat->kbd.super = false; if (seat->kbd.xkb_compose_state != NULL) xkb_compose_state_reset(seat->kbd.xkb_compose_state); @@ -1028,7 +1028,7 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, keymap_mods |= seat->kbd.shift ? MOD_SHIFT : MOD_NONE; keymap_mods |= seat->kbd.alt ? MOD_ALT : MOD_NONE; keymap_mods |= seat->kbd.ctrl ? MOD_CTRL : MOD_NONE; - keymap_mods |= seat->kbd.meta ? MOD_META : MOD_NONE; + keymap_mods |= seat->kbd.super ? MOD_META : MOD_NONE; const xkb_keysym_t sym = ctx->sym; const size_t count = ctx->utf8.count; @@ -1181,8 +1181,8 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, encoded_mods |= mods & (1 << seat->kbd.mod_alt) ? (1 << 1) : 0; if (seat->kbd.mod_ctrl != XKB_MOD_INVALID) encoded_mods |= mods & (1 << seat->kbd.mod_ctrl) ? (1 << 2) : 0; - if (seat->kbd.mod_meta != XKB_MOD_INVALID) - encoded_mods |= mods & (1 << seat->kbd.mod_meta) ? (1 << 3) : 0; + if (seat->kbd.mod_super != XKB_MOD_INVALID) + encoded_mods |= mods & (1 << seat->kbd.mod_super) ? (1 << 3) : 0; if (seat->kbd.mod_caps != XKB_MOD_INVALID) encoded_mods |= mods & (1 << seat->kbd.mod_caps) ? (1 << 6) : 0; if (seat->kbd.mod_num != XKB_MOD_INVALID) @@ -1618,9 +1618,9 @@ keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, ? xkb_state_mod_index_is_active( seat->kbd.xkb_state, seat->kbd.mod_ctrl, XKB_STATE_MODS_EFFECTIVE) : false; - seat->kbd.meta = seat->kbd.mod_meta != XKB_MOD_INVALID + seat->kbd.super = seat->kbd.mod_super != XKB_MOD_INVALID ? xkb_state_mod_index_is_active( - seat->kbd.xkb_state, seat->kbd.mod_meta, XKB_STATE_MODS_EFFECTIVE) + seat->kbd.xkb_state, seat->kbd.mod_super, XKB_STATE_MODS_EFFECTIVE) : false; } diff --git a/terminal.c b/terminal.c index a56ead03..e5c3ac65 100644 --- a/terminal.c +++ b/terminal.c @@ -2829,7 +2829,7 @@ term_mouse_grabbed(const struct terminal *term, struct seat *seat) return term->mouse_tracking == MOUSE_NONE || (seat->kbd_focus == term && seat->kbd.shift && - !seat->kbd.alt && /*!seat->kbd.ctrl &&*/ !seat->kbd.meta); + !seat->kbd.alt && /*!seat->kbd.ctrl &&*/ !seat->kbd.super); } void diff --git a/wayland.h b/wayland.h index a2e1d17e..d415588b 100644 --- a/wayland.h +++ b/wayland.h @@ -194,7 +194,7 @@ struct seat { xkb_mod_index_t mod_shift; xkb_mod_index_t mod_alt; xkb_mod_index_t mod_ctrl; - xkb_mod_index_t mod_meta; + xkb_mod_index_t mod_super; xkb_mod_index_t mod_caps; xkb_mod_index_t mod_num; @@ -208,7 +208,7 @@ struct seat { bool shift; bool alt; bool ctrl; - bool meta; + bool super; struct { key_binding_list_t key; From 1ec218c3ac0bf101a6b3e94945fc8fe0ed337c5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 12:20:26 +0100 Subject: [PATCH 33/59] input: kitty: map ISO_Left_Tab to Tab --- input.c | 1 + 1 file changed, 1 insertion(+) diff --git a/input.c b/input.c index fed52a49..09595956 100644 --- a/input.c +++ b/input.c @@ -1196,6 +1196,7 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, case XKB_KEY_Escape: key = 27; final = 'u'; break; case XKB_KEY_Return: key = 13; final = 'u'; break; case XKB_KEY_Tab: key = 9; final = 'u'; break; + case XKB_KEY_ISO_Left_Tab: key = 9; final = 'u'; break; case XKB_KEY_BackSpace: key = 127; final = 'u'; break; case XKB_KEY_Insert: key = 2; final = '~'; break; case XKB_KEY_Delete: key = 3; final = '~'; break; From 9933284ab1d1dd4baf0a3bd5c056b7c9dc5c70f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 12:39:40 +0100 Subject: [PATCH 34/59] =?UTF-8?q?input:=20kitty:=20add=20=E2=80=98media?= =?UTF-8?q?=E2=80=99=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/input.c b/input.c index 09595956..e1f4959e 100644 --- a/input.c +++ b/input.c @@ -1277,6 +1277,20 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, case XKB_KEY_KP_Delete: key = 57426; final = 'u'; break; case XKB_KEY_KP_Begin: key = 1; final = 'E'; break; + case XKB_KEY_XF86AudioPlay: key = 57428; final = 'u'; break; + case XKB_KEY_XF86AudioPause: key = 57429; final = 'u'; break; + //case XKB_KEY_XF86AudioPlayPause: key = 57430; final = 'u'; break; + //case XKB_KEY_XF86AudioReverse: key = 57431; final = 'u'; break; + case XKB_KEY_XF86AudioStop: key = 57432; final = 'u'; break; + case XKB_KEY_XF86AudioForward: key = 57433; final = 'u'; break; + case XKB_KEY_XF86AudioRewind: key = 57434; final = 'u'; break; + case XKB_KEY_XF86AudioNext: key = 57435; final = 'u'; break; + case XKB_KEY_XF86AudioPrev: key = 57436; final = 'u'; break; + case XKB_KEY_XF86AudioRecord: key = 57437; final = 'u'; break; + case XKB_KEY_XF86AudioLowerVolume: key = 57438; final = 'u'; break; + case XKB_KEY_XF86AudioRaiseVolume: key = 57439; final = 'u'; break; + case XKB_KEY_XF86AudioMute: key = 57440; final = 'u'; break; + case XKB_KEY_Caps_Lock: if (seat->kbd.mod_caps == XKB_MOD_INVALID) { key = 57358; From 6930abe9452487d1b0e5842081d55437766ee469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 12:40:04 +0100 Subject: [PATCH 35/59] input: kitty: add shift/alt/ctrl/super/hyper/meta keys --- input.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/input.c b/input.c index e1f4959e..d8fee4e4 100644 --- a/input.c +++ b/input.c @@ -1305,6 +1305,54 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, } break; + case XKB_KEY_Shift_L: + case XKB_KEY_Shift_R: + if (seat->kbd.mod_shift == XKB_MOD_INVALID) { + key = sym == XKB_KEY_Shift_L ? 57441 : 57447; + final = 'u'; + } + break; + + case XKB_KEY_Control_L: + case XKB_KEY_Control_R: + if (seat->kbd.mod_ctrl == XKB_MOD_INVALID) { + key = sym == XKB_KEY_Control_L ? 57442 : 57448; + final = 'u'; + } + break; + + case XKB_KEY_Alt_L: + case XKB_KEY_Alt_R: + if (seat->kbd.mod_alt == XKB_MOD_INVALID) { + key = sym == XKB_KEY_Alt_L ? 57443 : 57449; + final = 'u'; + } + break; + + case XKB_KEY_Super_L: + case XKB_KEY_Super_R: + if (seat->kbd.mod_super == XKB_MOD_INVALID) { + key = sym == XKB_KEY_Super_L ? 57444 : 57450; + final = 'u'; + } + break; + + case XKB_KEY_Hyper_L: + case XKB_KEY_Hyper_R: + //if (seat->kbd.mod_hyper == XKB_MOD_INVALID) { + key = sym == XKB_KEY_Hyper_L ? 57445 : 57451; + final = 'u'; + //} + break; + + case XKB_KEY_Meta_L: + case XKB_KEY_Meta_R: + //if (seat->kbd.mod_meta == XKB_MOD_INVALID) { + key = sym == XKB_KEY_Meta_L ? 57446 : 57452; + final = 'u'; + //} + break; + default: if (count > 0) { if (effective == 0) { From 07068165ece205666cbb82196c3746e6b2b30776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 13:53:52 +0100 Subject: [PATCH 36/59] =?UTF-8?q?input:=20only=20report=20modifiers=20when?= =?UTF-8?q?=20=E2=80=9CReport=20all=20keys=20as=20escape=20codes=E2=80=9D?= =?UTF-8?q?=20is=20enabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input.c | 76 ++++++++++++--------------------------------------------- 1 file changed, 16 insertions(+), 60 deletions(-) diff --git a/input.c b/input.c index d8fee4e4..773c6c0c 100644 --- a/input.c +++ b/input.c @@ -1291,67 +1291,23 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, case XKB_KEY_XF86AudioRaiseVolume: key = 57439; final = 'u'; break; case XKB_KEY_XF86AudioMute: key = 57440; final = 'u'; break; - case XKB_KEY_Caps_Lock: - if (seat->kbd.mod_caps == XKB_MOD_INVALID) { - key = 57358; - final = 'u'; - } - break; +#if 0 /* TODO: enable when “Report all keys as escape codes” is enabled */ + case XKB_KEY_Caps_Lock: key = 57358; final = 'u'; break; + case XKB_KEY_Num_Lock: key = 57360; final = 'u'; break; - case XKB_KEY_Num_Lock: - if (seat->kbd.mod_num == XKB_MOD_INVALID) { - key = 57360; - final = 'u'; - } - break; - - case XKB_KEY_Shift_L: - case XKB_KEY_Shift_R: - if (seat->kbd.mod_shift == XKB_MOD_INVALID) { - key = sym == XKB_KEY_Shift_L ? 57441 : 57447; - final = 'u'; - } - break; - - case XKB_KEY_Control_L: - case XKB_KEY_Control_R: - if (seat->kbd.mod_ctrl == XKB_MOD_INVALID) { - key = sym == XKB_KEY_Control_L ? 57442 : 57448; - final = 'u'; - } - break; - - case XKB_KEY_Alt_L: - case XKB_KEY_Alt_R: - if (seat->kbd.mod_alt == XKB_MOD_INVALID) { - key = sym == XKB_KEY_Alt_L ? 57443 : 57449; - final = 'u'; - } - break; - - case XKB_KEY_Super_L: - case XKB_KEY_Super_R: - if (seat->kbd.mod_super == XKB_MOD_INVALID) { - key = sym == XKB_KEY_Super_L ? 57444 : 57450; - final = 'u'; - } - break; - - case XKB_KEY_Hyper_L: - case XKB_KEY_Hyper_R: - //if (seat->kbd.mod_hyper == XKB_MOD_INVALID) { - key = sym == XKB_KEY_Hyper_L ? 57445 : 57451; - final = 'u'; - //} - break; - - case XKB_KEY_Meta_L: - case XKB_KEY_Meta_R: - //if (seat->kbd.mod_meta == XKB_MOD_INVALID) { - key = sym == XKB_KEY_Meta_L ? 57446 : 57452; - final = 'u'; - //} - break; + case XKB_KEY_Shift_L: key = 57441; final = 'u'; break; + case XKB_KEY_Control_L: key = 57442; final = 'u'; break; + case XKB_KEY_Alt_L: key = 57443; final = 'u'; break; + case XKB_KEY_Super_L: key = 57444; final = 'u'; break; + case XKB_KEY_Hyper_L: key = 57445; final = 'u'; break; + case XKB_KEY_Meta_L: key = 57446; final = 'u'; break; + case XKB_KEY_Shift_R: key = 57447; final = 'u'; break; + case XKB_KEY_Control_R: key = 57448; final = 'u'; break; + case XKB_KEY_Alt_R: key = 57449; final = 'u'; break; + case XKB_KEY_Super_R: key = 57450; final = 'u'; break; + case XKB_KEY_Hyper_R: key = 57451; final = 'u'; break; + case XKB_KEY_Meta_R: key = 57452; final = 'u'; break; +#endif default: if (count > 0) { From 28ad0ca60295fadc42ac75f6a1237d12fff371ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 15:05:00 +0100 Subject: [PATCH 37/59] csi: kitty: remove unneeded xasserts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ‘idx’ is calculated just above, and the logic is simple enough that we don’t really need to assert it. --- csi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/csi.c b/csi.c index 183caa08..9f29bb3d 100644 --- a/csi.c +++ b/csi.c @@ -1557,7 +1557,6 @@ csi_dispatch(struct terminal *term, uint8_t final) grid->kitty_kbd.flags[idx] = flags; grid->kitty_kbd.idx = idx; - xassert(grid->kitty_kbd.idx < ALEN(grid->kitty_kbd.flags)); LOG_DBG("kitty kbd: pushed new flags: 0x%03x", flags); break; } @@ -1607,7 +1606,6 @@ csi_dispatch(struct terminal *term, uint8_t final) } grid->kitty_kbd.idx = idx; - xassert(grid->kitty_kbd.idx < ALEN(grid->kitty_kbd.flags)); LOG_DBG("kitty kbd: flags after pop: 0x%03x", term->grid->kitty_kbd.flags[idx]); From ce8ea2db66db0f81312df8fc993aa47d200a1305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 15:05:38 +0100 Subject: [PATCH 38/59] input: legacy: reduce size of reply buffer Its maximum size is known; the only two variables are two integers. We know the maximum length of an integer converted to a string. --- input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input.c b/input.c index 773c6c0c..9d4efe34 100644 --- a/input.c +++ b/input.c @@ -1083,7 +1083,7 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, int modify_param = mod_param_map[keymap_mods]; xassert(modify_param != 0); - char reply[1024]; + char reply[32]; size_t n = xsnprintf(reply, sizeof(reply), "\x1b[27;%d;%d~", modify_param, sym); term_to_slave(term, reply, n); } From 42d1fcb48443091672ac11a76985297f994f08c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 15:06:41 +0100 Subject: [PATCH 39/59] =?UTF-8?q?input:=20grammar:=20=E2=80=9Can=20UTF-8?= =?UTF-8?q?=E2=80=9D=20->=20=E2=80=9Ca=20UTF-8=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/input.c b/input.c index 9d4efe34..211d3dc8 100644 --- a/input.c +++ b/input.c @@ -1098,10 +1098,10 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, * * #1 is configured with \E[?1036, and is on by default * - * If #1 has been disabled, we use #2, *if* it's a single - * byte we're emitting. Since this is an UTF-8 terminal, - * we then UTF8-encode the 8-bit character. #2 is - * configured with \E[?1034, and is on by default. + * If #1 has been disabled, we use #2, *if* it's a single byte + * we're emitting. Since this is a UTF-8 terminal, we then + * UTF8-encode the 8-bit character. #2 is configured with + * \E[?1034, and is on by default. * * Lastly, if both #1 and #2 have been disabled, the alt * modifier is ignored. From 546bcd66b7a96ab9dfc3e3e598efd604a6e95858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 21 Nov 2021 15:07:38 +0100 Subject: [PATCH 40/59] input: legacy: use ALEN(mod_param_map) --- input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input.c b/input.c index 211d3dc8..2c2cfa96 100644 --- a/input.c +++ b/input.c @@ -1079,7 +1079,7 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, [MOD_META | MOD_SHIFT | MOD_ALT | MOD_CTRL] = 16, }; - xassert(keymap_mods < sizeof(mod_param_map) / sizeof(mod_param_map[0])); + xassert(keymap_mods < ALEN(mod_param_map)); int modify_param = mod_param_map[keymap_mods]; xassert(modify_param != 0); From 8f38cd486f73db1bb2239acf0ab8382786a21752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 22 Nov 2021 22:42:49 +0100 Subject: [PATCH 41/59] term: rename: KITTY_KBD_MASK -> KITTY_KBD_SUPPORTED --- csi.c | 4 ++-- terminal.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/csi.c b/csi.c index 9f29bb3d..f27f805f 100644 --- a/csi.c +++ b/csi.c @@ -1543,7 +1543,7 @@ csi_dispatch(struct terminal *term, uint8_t final) break; /* final == 'm' */ case 'u': { - int flags = vt_param_get(term, 0, 0) & KITTY_KBD_MASK; + int flags = vt_param_get(term, 0, 0) & KITTY_KBD_SUPPORTED; struct grid *grid = term->grid; uint8_t idx = grid->kitty_kbd.idx; @@ -1693,7 +1693,7 @@ csi_dispatch(struct terminal *term, uint8_t final) break; case 'u': { - int flag_set = vt_param_get(term, 0, 0) & KITTY_KBD_MASK; + int flag_set = vt_param_get(term, 0, 0) & KITTY_KBD_SUPPORTED; int mode = vt_param_get(term, 1, 1); struct grid *grid = term->grid; diff --git a/terminal.h b/terminal.h index 12d3fa0d..62ccfb2a 100644 --- a/terminal.h +++ b/terminal.h @@ -133,7 +133,7 @@ enum kitty_kbd_flags { KITTY_KBD_REPORT_ALTERNATE = 0x04, KITTY_KBD_REPORT_ALL = 0x08, KITTY_KBD_REPORT_ASSOCIATED = 0x10, - KITTY_KBD_MASK = 0x1f, + KITTY_KBD_SUPPORTED = KITTY_KBD_DISAMBIGUATE, }; struct grid { From 913dd8b4a600538a0d90c2847395d0ad92ae5ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 18:44:29 +0100 Subject: [PATCH 42/59] input: get_current_modifiers(): use xkb_state_key_get_consumed_mods2() Explicitly request consumed modifiers using the `XKB` mode. --- input.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/input.c b/input.c index 2c2cfa96..b37a02f7 100644 --- a/input.c +++ b/input.c @@ -993,8 +993,10 @@ get_current_modifiers(const struct seat *seat, seat->kbd.xkb_state, XKB_STATE_MODS_EFFECTIVE); } - if (consumed != NULL) - *consumed = xkb_state_key_get_consumed_mods(seat->kbd.xkb_state, key); + if (consumed != NULL) { + *consumed = xkb_state_key_get_consumed_mods2( + seat->kbd.xkb_state, key, XKB_CONSUMED_MODE_XKB); + } } struct kbd_ctx { From 8f41a8dc941c4bddbe7134498a25c41546c5188f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 18:45:23 +0100 Subject: [PATCH 43/59] input: kitty: use `XKB_CONSUMED_MODE_GTK` when retrieving consumed mods --- input.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/input.c b/input.c index b37a02f7..f97deb93 100644 --- a/input.c +++ b/input.c @@ -1139,7 +1139,8 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, const struct kbd_ctx *ctx) { const xkb_mod_mask_t mods = ctx->mods & seat->kbd.kitty_significant; - const xkb_mod_mask_t consumed = ctx->consumed & seat->kbd.kitty_significant; + const xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2( + seat->kbd.xkb_state, ctx->key, XKB_CONSUMED_MODE_GTK) & seat->kbd.kitty_significant; const xkb_mod_mask_t effective = mods & ~consumed; const xkb_mod_mask_t caps_num = (seat->kbd.mod_caps != XKB_MOD_INVALID ? 1 << seat->kbd.mod_caps : 0) | From 1f19dd6694b1268b40620ca5743ac458948ad0c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 27 Nov 2021 18:47:20 +0100 Subject: [PATCH 44/59] changelog: kitty keyboard protocol - disambiguate escape codes --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b928d68..4c3ad3e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,9 @@ ### Added * New value, `max`, for `[tweak].grapheme-width-method`. +* Initial support for the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/). + Modes supported: + - [Disambiguate escape codes](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#disambiguate) ### Changed From a55a3daae7280350f413bcb7570104480fcbebcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Nov 2021 16:48:30 +0100 Subject: [PATCH 45/59] =?UTF-8?q?input:=20regression:=20special=20keys=20d?= =?UTF-8?q?on=E2=80=99t=20reset=20view=20and=20cancel=20selection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes an issue where e.g. holding down ctrl would cancel the selection, thus making it impossible to copy text to the clipboard. --- input.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/input.c b/input.c index f97deb93..0c981376 100644 --- a/input.c +++ b/input.c @@ -1594,8 +1594,10 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, if (utf8 != buf) free(utf8); - term_reset_view(term); - selection_cancel(term); + if (count > 0) { + term_reset_view(term); + selection_cancel(term); + } maybe_repeat: clock_gettime( From 5c2557b42116dd6d0f86c90b2cf04cd43f80a36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Fri, 19 Nov 2021 15:02:48 +0100 Subject: [PATCH 46/59] terminal: Make seat xcursor update focus aware When term_xcursor_update_for_seat() was called on e.g. keyboard focus loss, it'd update the curret xcursor to 'text' even if it was e.g. on top of the window title, or resize areas. This makes the function a bit more focus aware, and will not be so eager to set the text xcursor. --- input.c | 2 +- input.h | 2 ++ terminal.c | 43 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/input.c b/input.c index 0aef43cc..5f864a8d 100644 --- a/input.c +++ b/input.c @@ -1372,7 +1372,7 @@ is_bottom_right(const struct terminal *term, int x, int y) (term->active_surface == TERM_SURF_BORDER_BOTTOM && x > term->width + 1 * csd_border_size * term->scale - 10 * term->scale))); } -static const char * +const char * xcursor_for_csd_border(struct terminal *term, int x, int y) { if (is_top_left(term, x, y)) return XCURSOR_TOP_LEFT_CORNER; diff --git a/input.h b/input.h index 68b8719d..5a2c7a67 100644 --- a/input.h +++ b/input.h @@ -26,3 +26,5 @@ extern const struct wl_keyboard_listener keyboard_listener; extern const struct wl_pointer_listener pointer_listener; void input_repeat(struct seat *seat, uint32_t key); + +const char * xcursor_for_csd_border(struct terminal *term, int x, int y); diff --git a/terminal.c b/terminal.c index df8128fc..83204675 100644 --- a/terminal.c +++ b/terminal.c @@ -3015,14 +3015,41 @@ term_mouse_motion(struct terminal *term, int button, int row, int col, void term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) { - const char *xcursor - = seat->pointer.hidden ? XCURSOR_HIDDEN - : term->is_searching ? XCURSOR_LEFT_PTR - : (seat->mouse.col >= 0 && - seat->mouse.row >= 0 && - term_mouse_grabbed(term, seat)) ? XCURSOR_TEXT - : term->is_searching ? XCURSOR_TEXT - : XCURSOR_LEFT_PTR; + const char *xcursor; + + switch (term->active_surface) { + case TERM_SURF_GRID: { + xcursor = seat->pointer.hidden ? XCURSOR_HIDDEN + : term->is_searching ? XCURSOR_LEFT_PTR + : (seat->mouse.col >= 0 && + seat->mouse.row >= 0 && + term_mouse_grabbed(term, seat)) ? XCURSOR_TEXT + : term->is_searching ? XCURSOR_TEXT + : XCURSOR_LEFT_PTR; + break; + } + case TERM_SURF_SEARCH: + case TERM_SURF_SCROLLBACK_INDICATOR: + case TERM_SURF_RENDER_TIMER: + case TERM_SURF_JUMP_LABEL: + case TERM_SURF_TITLE: + case TERM_SURF_BUTTON_MINIMIZE: + case TERM_SURF_BUTTON_MAXIMIZE: + case TERM_SURF_BUTTON_CLOSE: + xcursor = XCURSOR_LEFT_PTR; + break; + + case TERM_SURF_BORDER_LEFT: + case TERM_SURF_BORDER_RIGHT: + case TERM_SURF_BORDER_TOP: + case TERM_SURF_BORDER_BOTTOM: + xcursor = xcursor_for_csd_border(term, seat->mouse.x, seat->mouse.y); + break; + + case TERM_SURF_NONE: + default: + return; + } render_xcursor_set(seat, term, xcursor); } From c0ce131f1a4195585cc89f58ab65cfd7658ca0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Fri, 19 Nov 2021 15:04:51 +0100 Subject: [PATCH 47/59] input: Update mouse x/y coordinates on wl_pointer_enter Otherwise if you don't receive motion event before e.g. button pressed, the coordinates will be incorrect. This happens when e.g. you get alt-tabbed so that the mouse cursor ends up on top of the terminal window, but the mouse never actually moved. --- input.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/input.c b/input.c index 5f864a8d..35baf3d4 100644 --- a/input.c +++ b/input.c @@ -1405,11 +1405,18 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, struct wl_window *win = wl_surface_get_user_data(surface); struct terminal *term = win->term; + int x = wl_fixed_to_int(surface_x) * term->scale; + int y = wl_fixed_to_int(surface_y) * term->scale; + seat->pointer.serial = serial; seat->pointer.hidden = false; + seat->mouse.x = x; + seat->mouse.y = y; - LOG_DBG("pointer-enter: pointer=%p, serial=%u, surface = %p, new-moused = %p", - (void *)wl_pointer, serial, (void *)surface, (void *)term); + LOG_DBG("pointer-enter: pointer=%p, serial=%u, surface = %p, new-moused = %p, " + "x=%d, y=%d", + (void *)wl_pointer, serial, (void *)surface, (void *)term, + x, y); xassert(tll_length(seat->mouse.buttons) == 0); @@ -1418,9 +1425,6 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, seat->mouse_focus = term; - int x = wl_fixed_to_int(surface_x) * term->scale; - int y = wl_fixed_to_int(surface_y) * term->scale; - switch ((term->active_surface = term_surface_kind(term, surface))) { case TERM_SURF_GRID: { /* From 38741baf9afc2697c7e508cb87c3b2983de9f0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Fri, 19 Nov 2021 15:08:46 +0100 Subject: [PATCH 48/59] input: Add support for xdg_toplevl.show_window_menu() This makes, if the compositor supports it, the window menu appear when right clicking on the title bar. --- input.c | 10 ++++++++++ render.c | 9 +-------- render.h | 9 +++++++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/input.c b/input.c index 35baf3d4..7e0cde75 100644 --- a/input.c +++ b/input.c @@ -1924,6 +1924,16 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, close(fd); } } + + if (button == BTN_RIGHT && tll_length(seat->mouse.buttons) == 1) { + struct csd_data info; + info = get_csd_data(term, CSD_SURF_TITLE); + xdg_toplevel_show_window_menu( + win->xdg_toplevel, + seat->wl_seat, + seat->pointer.serial, + seat->mouse.x + info.x, seat->mouse.y + info.y); + } } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { diff --git a/render.c b/render.c index 46cc0f75..76fe7c95 100644 --- a/render.c +++ b/render.c @@ -1521,14 +1521,7 @@ render_worker_thread(void *_ctx) return -1; } -struct csd_data { - int x; - int y; - int width; - int height; -}; - -static struct csd_data +struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx) { xassert(term->window->csd_mode == CSD_YES); diff --git a/render.h b/render.h index 1179a5dc..145aaba8 100644 --- a/render.h +++ b/render.h @@ -24,3 +24,12 @@ struct render_worker_context { struct terminal *term; }; int render_worker_thread(void *_ctx); + +struct csd_data { + int x; + int y; + int width; + int height; +}; + +struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx); From 73a048f9d37af86264c5c48b7889e20f3e57f693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 29 Nov 2021 19:31:48 +0100 Subject: [PATCH 49/59] =?UTF-8?q?input:=20regression:=20reset=20view=20(an?= =?UTF-8?q?d=20cancel=20selection)=20on=20=E2=80=9Chandled=E2=80=9D=20inpu?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a regression, where the view (and selection) was only reset if the keyboard input resulted in plain text. That is, key presses like enter, arrows etc did not. --- input.c | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/input.c b/input.c index 0c981376..bafd413b 100644 --- a/input.c +++ b/input.c @@ -1022,7 +1022,7 @@ struct kbd_ctx { enum wl_keyboard_key_state key_state; }; -static void +static bool legacy_kbd_protocol(struct seat *seat, struct terminal *term, const struct kbd_ctx *ctx) { @@ -1045,11 +1045,11 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, if (keymap != NULL) { term_to_slave(term, keymap->seq, strlen(keymap->seq)); - return; + return true; } if (count == 0) - return; + return false; #define is_control_key(x) ((x) >= 0x40 && (x) <= 0x7f) #define IS_CTRL(x) ((x) < 0x20 || ((x) >= 0x7f && (x) <= 0x9f)) @@ -1132,9 +1132,11 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, } } else term_to_slave(term, utf8, count); + + return true; } -static void +static bool kitty_kbd_protocol(struct seat *seat, struct terminal *term, const struct kbd_ctx *ctx) { @@ -1152,14 +1154,14 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, if (ctx->compose_status == XKB_COMPOSE_COMPOSED) { term_to_slave(term, utf8, count); - return; + return true; } if (effective == 0) { switch (sym) { - case XKB_KEY_Return: term_to_slave(term, "\r", 1); return; - case XKB_KEY_BackSpace: term_to_slave(term, "\x7f", 1); return; - case XKB_KEY_Tab: term_to_slave(term, "\t", 1); return; + case XKB_KEY_Return: term_to_slave(term, "\r", 1); return true; + case XKB_KEY_BackSpace: term_to_slave(term, "\x7f", 1); return true; + case XKB_KEY_Tab: term_to_slave(term, "\t", 1); return true; } } @@ -1174,7 +1176,7 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, */ if (iswprint(utf32) && (effective & ~caps_num) == 0) { term_to_slave(term, utf8, count); - return; + return true; } unsigned int encoded_mods = 0; @@ -1316,7 +1318,7 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, if (count > 0) { if (effective == 0) { term_to_slave(term, utf8, count); - return; + return true; } /* @@ -1385,7 +1387,7 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, int bytes; if (key < 0) - return; + return false; if (final == 'u' || final == '~') { if (encoded_mods > 1) @@ -1401,6 +1403,7 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, } term_to_slave(term, buf, bytes); + return true; } static void @@ -1583,10 +1586,9 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, .key_state = state, }; - if (term->grid->kitty_kbd.flags[term->grid->kitty_kbd.idx] != 0) - kitty_kbd_protocol(seat, term, &ctx); - else - legacy_kbd_protocol(seat, term, &ctx); + bool handled = term->grid->kitty_kbd.flags[term->grid->kitty_kbd.idx] != 0 + ? kitty_kbd_protocol(seat, term, &ctx) + : legacy_kbd_protocol(seat, term, &ctx); if (seat->kbd.xkb_compose_state != NULL) xkb_compose_state_reset(seat->kbd.xkb_compose_state); @@ -1594,7 +1596,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, if (utf8 != buf) free(utf8); - if (count > 0) { + if (handled) { term_reset_view(term); selection_cancel(term); } From 3afe317e4637d511bb4f795d20d8e0467c0e42bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 29 Nov 2021 19:23:58 +0100 Subject: [PATCH 50/59] render: fix csd border rendering glitch when width > 5px CSD borders are always *at least* 5px. If url.border-width=0, those 5px are all fully transparent (and act as interactive resize handles). As csd.border-width increases, the number of transparent pixels decrease. Once csd.border-width >= 5, the border is fully opaque. When csd.border-width > 5, then width of the border is (obviously) more than 5px. But, when rendering the opaque part of the border, we still used 5px for the invisible part, which caused some pixman rectangles to have negative x/y coordinates. This resulted in rendering glitches due to overflows in pixman when rendering the borders. The fix is to ensure the total border size is always at least, but not *always* 5px. That is, set it to max(5, csd.border-width). This patch also fixes an issue where the CSD borders were not dimmed (like the titlebar) when the window looses input focus. Closes #823 --- CHANGELOG.md | 3 +++ render.c | 26 ++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c3ad3e4..19f5c7ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,9 @@ unrelated cells (https://codeberg.org/dnkl/foot/issues/816). * OSC-8 URIs incorrectly being dropped when resizing the terminal window with the alternate screen active. +* CSD border not being dimmed when window is not focused. +* Visual corruption with large CSD borders + (https://codeberg.org/dnkl/foot/issues/823). ### Security diff --git a/render.c b/render.c index 2710c0a2..cd38d712 100644 --- a/render.c +++ b/render.c @@ -1763,14 +1763,16 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, * The “visible” border. */ - int bwidth = term->conf->csd.border_width; /* Full border size */ - int vwidth = term->conf->csd.border_width_visible; /* Visibls size */ + int bwidth = max(term->conf->csd.border_width, + term->conf->csd.border_width_visible); /* Full border size */ + int vwidth = term->conf->csd.border_width_visible; /* Visibls size */ if (vwidth > 0) { const struct config *conf = term->conf; int x = 0, y = 0, w = 0, h = 0; + switch (surf_idx) { case CSD_SURF_TOP: case CSD_SURF_BOTTOM: @@ -1788,17 +1790,33 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, h = info->height; break; - default: - break; + case CSD_SURF_TITLE: + case CSD_SURF_MINIMIZE: + case CSD_SURF_MAXIMIZE: + case CSD_SURF_CLOSE: + case CSD_SURF_COUNT: + BUG("unexpected CSD surface type"); } + xassert(x >= 0); + xassert(y >= 0); + xassert(w >= 0); + xassert(h >= 0); + + xassert(x + w <= info->width); + xassert(y + h <= info->height); + uint32_t _color = conf->csd.color.border_set ? conf->csd.color.border : conf->csd.color.title_set ? conf->csd.color.title : 0xffu << 24 | term->conf->colors.fg; + if (!term->visual_focus) + _color = color_dim(term, _color); + uint16_t alpha = _color >> 24 | (_color >> 24 << 8); pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha); + pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix[0], &color, 1, &(pixman_rectangle16_t){x, y, w, h}); From fd82fb81fa8bef795eb4d9ac2fdccdf787c35759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Nov 2021 22:12:17 +0100 Subject: [PATCH 51/59] changelog: window-menu --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f5c7ad..97f20302 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ * Initial support for the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/). Modes supported: - [Disambiguate escape codes](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#disambiguate) +* “Window menu” (compositor provided) on right clicks on the CSD title + bar. ### Changed From bfbb4b08308f6deb872a19c19e8481a907347be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Nov 2021 22:12:56 +0100 Subject: [PATCH 52/59] term: xcursor_update_for_seat(): remove switch default case Let the compiler catch missing enum values --- terminal.c | 1 - 1 file changed, 1 deletion(-) diff --git a/terminal.c b/terminal.c index 10211ca6..09c3ec73 100644 --- a/terminal.c +++ b/terminal.c @@ -2994,7 +2994,6 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) break; case TERM_SURF_NONE: - default: return; } From 59e6285037cba04d0ffdcd77cd27bf8aaa59fe8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Nov 2021 22:14:05 +0100 Subject: [PATCH 53/59] term: xcursor_update_for_seat(): remove duplicate check for term->is_searching --- terminal.c | 1 - 1 file changed, 1 deletion(-) diff --git a/terminal.c b/terminal.c index 09c3ec73..d65c0bd0 100644 --- a/terminal.c +++ b/terminal.c @@ -2971,7 +2971,6 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) : (seat->mouse.col >= 0 && seat->mouse.row >= 0 && term_mouse_grabbed(term, seat)) ? XCURSOR_TEXT - : term->is_searching ? XCURSOR_TEXT : XCURSOR_LEFT_PTR; break; } From fce13c4106afa3509536802d2c7d74e4d8b8e50d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Nov 2021 22:15:13 +0100 Subject: [PATCH 54/59] =?UTF-8?q?input:=20make=20csd=5Fdata=20variable=20?= =?UTF-8?q?=E2=80=98const=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/input.c b/input.c index b8b27d4c..e3b14297 100644 --- a/input.c +++ b/input.c @@ -2282,8 +2282,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, } if (button == BTN_RIGHT && tll_length(seat->mouse.buttons) == 1) { - struct csd_data info; - info = get_csd_data(term, CSD_SURF_TITLE); + const struct csd_data info = get_csd_data(term, CSD_SURF_TITLE); xdg_toplevel_show_window_menu( win->xdg_toplevel, seat->wl_seat, From b3029234af2a0f4ff92947ec6d83581c26e6d3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Nov 2021 22:25:32 +0100 Subject: [PATCH 55/59] term: xcursor_update_for_seat(): BUG on xcursor == NULL --- terminal.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/terminal.c b/terminal.c index d65c0bd0..25310d36 100644 --- a/terminal.c +++ b/terminal.c @@ -2962,7 +2962,7 @@ term_mouse_motion(struct terminal *term, int button, int row, int col, void term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) { - const char *xcursor; + const char *xcursor = NULL; switch (term->active_surface) { case TERM_SURF_GRID: { @@ -2996,6 +2996,9 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) return; } + if (xcursor == NULL) + BUG("xcursor not set"); + render_xcursor_set(seat, term, xcursor); } From 1752745fcfcce6ad0ad540e006a39640ef6fc432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Nov 2021 22:43:41 +0100 Subject: [PATCH 56/59] pgo: add stub for xcursor_for_csd_border() --- input.h | 2 +- pgo/pgo.c | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/input.h b/input.h index 5a2c7a67..da887ef2 100644 --- a/input.h +++ b/input.h @@ -27,4 +27,4 @@ extern const struct wl_pointer_listener pointer_listener; void input_repeat(struct seat *seat, uint32_t key); -const char * xcursor_for_csd_border(struct terminal *term, int x, int y); +const char *xcursor_for_csd_border(struct terminal *term, int x, int y); diff --git a/pgo/pgo.c b/pgo/pgo.c index 0be2bc62..aec1a31f 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -74,6 +74,12 @@ render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor return true; } +const char * +xcursor_for_csd_border(struct terminal *term, int x, int y) +{ + return XCURSOR_LEFT_PTR; +} + struct wl_window * wayl_win_init(struct terminal *term, const char *token) { From 63e8b1b2924aef94316e621513fd938eb0acc4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 1 Dec 2021 20:03:18 +0100 Subject: [PATCH 57/59] =?UTF-8?q?input:=20fix=20debug=20log=20format=20spe?= =?UTF-8?q?cifier;=20=E2=80=98count=E2=80=99=20is=20a=20size=5Ft?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input.c b/input.c index e3b14297..ef81b637 100644 --- a/input.c +++ b/input.c @@ -1054,7 +1054,7 @@ legacy_kbd_protocol(struct seat *seat, struct terminal *term, #define is_control_key(x) ((x) >= 0x40 && (x) <= 0x7f) #define IS_CTRL(x) ((x) < 0x20 || ((x) >= 0x7f && (x) <= 0x9f)) - LOG_DBG("term->modify_other_keys=%d, count=%d, is_ctrl=%d (utf8=0x%02x), sym=%d", + LOG_DBG("term->modify_other_keys=%d, count=%zu, is_ctrl=%d (utf8=0x%02x), sym=%d", term->modify_other_keys_2, count, IS_CTRL(utf8[0]), utf8[0], sym); bool ctrl_is_in_effect = (keymap_mods & MOD_CTRL) != 0; From 1619e83c13875f71f79e5ca0e4504dd59dd81d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 1 Dec 2021 20:04:01 +0100 Subject: [PATCH 58/59] input: always update the xcursor shape in pointer enter/motion events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that term_xcursor_update_for_seat() takes the current surface into account (i.e. doesn’t assume the cursor is over the main grid), there’s no longer any need to call render_xcursor_set() directly. Thus, we can simply call term_xcursor_update_for_seat() on **all** pointer enter and motion events. As long as we take care to update the internal state to reflect the, possibly new, current surface before doing so. Also make sure to **always** reset the seat’s “current” xcursor pointer on pointer leave events. This is done without actually sending anything to the compositor, but is necessary to ensure that we *do* send a request to update the xcursor on the next pointer enter event. --- CHANGELOG.md | 1 + input.c | 25 ++++++++++--------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f20302..1a725822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ * CSD border not being dimmed when window is not focused. * Visual corruption with large CSD borders (https://codeberg.org/dnkl/foot/issues/823). +* Mouse cursor shape sometimes not being updated correctly. ### Security diff --git a/input.c b/input.c index ef81b637..055c0fc8 100644 --- a/input.c +++ b/input.c @@ -1773,12 +1773,13 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, xassert(tll_length(seat->mouse.buttons) == 0); - /* Scale may have changed */ - wayl_reload_xcursor_theme(seat, term->scale); - seat->mouse_focus = term; + term->active_surface = term_surface_kind(term, surface); - switch ((term->active_surface = term_surface_kind(term, surface))) { + wayl_reload_xcursor_theme(seat, term->scale); /* Scale may have changed */ + term_xcursor_update_for_seat(term, seat); + + switch (term->active_surface) { case TERM_SURF_GRID: { /* * Translate x,y pixel coordinate to a cell coordinate, or -1 @@ -1796,7 +1797,6 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, else seat->mouse.row = (y - term->margins.top) / term->cell_height; - term_xcursor_update_for_seat(term, seat); break; } @@ -1805,20 +1805,15 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, case TERM_SURF_RENDER_TIMER: case TERM_SURF_JUMP_LABEL: case TERM_SURF_TITLE: - render_xcursor_set(seat, term, XCURSOR_LEFT_PTR); - break; - case TERM_SURF_BORDER_LEFT: case TERM_SURF_BORDER_RIGHT: case TERM_SURF_BORDER_TOP: case TERM_SURF_BORDER_BOTTOM: - render_xcursor_set(seat, term, xcursor_for_csd_border(term, x, y)); break; case TERM_SURF_BUTTON_MINIMIZE: case TERM_SURF_BUTTON_MAXIMIZE: case TERM_SURF_BUTTON_CLOSE: - render_xcursor_set(seat, term, XCURSOR_LEFT_PTR); render_refresh_csd(term); break; @@ -1847,9 +1842,11 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, wl_callback_destroy(seat->pointer.xcursor_callback); seat->pointer.xcursor_callback = NULL; seat->pointer.xcursor_pending = false; - seat->pointer.xcursor = NULL; } + /* Reset last-set-xcursor, to ensure we update it on a pointer-enter event */ + seat->pointer.xcursor = NULL; + /* Reset mouse state */ seat->mouse.x = seat->mouse.y = 0; seat->mouse.col = seat->mouse.row = 0; @@ -1876,7 +1873,6 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, enum term_surface active_surface = old_moused->active_surface; old_moused->active_surface = TERM_SURF_NONE; - term_xcursor_update_for_seat(old_moused, seat); switch (active_surface) { case TERM_SURF_BUTTON_MINIMIZE: @@ -1929,6 +1925,8 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, seat->mouse.x = x; seat->mouse.y = y; + term_xcursor_update_for_seat(term, seat); + enum term_surface surf_kind = term->active_surface; int button = 0; bool send_to_client = false; @@ -1966,7 +1964,6 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, case TERM_SURF_BORDER_RIGHT: case TERM_SURF_BORDER_TOP: case TERM_SURF_BORDER_BOTTOM: - render_xcursor_set(seat, term, xcursor_for_csd_border(term, x, y)); break; case TERM_SURF_GRID: { @@ -2017,8 +2014,6 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, xassert(seat->mouse.col == -1 || (seat->mouse.col >= 0 && seat->mouse.col < term->cols)); xassert(seat->mouse.row == -1 || (seat->mouse.row >= 0 && seat->mouse.row < term->rows)); - term_xcursor_update_for_seat(term, seat); - /* Cursor has moved to a different cell since last time */ bool cursor_is_on_new_cell = old_col != seat->mouse.col || old_row != seat->mouse.row; From a3016a6cc909d9c383359a4eaafd30bcd52bd272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Nov 2021 19:36:28 +0100 Subject: [PATCH 59/59] =?UTF-8?q?osc-4:=20don=E2=80=99t=20update=20the=20c?= =?UTF-8?q?olor=20of=20cells=20with=20RGB=20fg/bg=20colors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OSC 4/104 changes the 256-color palette. We also run a pass over the visible cells, and update their colors. This was previously done by comparing the actual color of the cell, with the “old” color in the palette. If they matched, the cell was updated. This meant that cells with an RGB color (i.e. not a palette based color) was also updated, _if_ its color matched the palette color. Now that each cell tracks its color *source*, we can ignore all non-palette based cells. Note that this still isn’t perfect: if the palette contains multiple entries with the same color, we’ll end up updating the “wrong” cells. Closes #678 --- CHANGELOG.md | 2 ++ osc.c | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a725822..026e745c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,8 @@ * Visual corruption with large CSD borders (https://codeberg.org/dnkl/foot/issues/823). * Mouse cursor shape sometimes not being updated correctly. +* Color palette changes (via OSC 4/104) no longer affect RGB colors + (https://codeberg.org/dnkl/foot/issues/678). ### Security diff --git a/osc.c b/osc.c index 910c1e02..357139b6 100644 --- a/osc.c +++ b/osc.c @@ -528,8 +528,7 @@ osc_notify(struct terminal *term, char *string) } static void -update_color_in_grids(struct terminal *term, uint32_t old_color, - uint32_t new_color) +update_color_in_grids(struct terminal *term, int palette_idx, uint32_t new_color) { /* * Update color of already rendered cells. @@ -560,16 +559,19 @@ update_color_in_grids(struct terminal *term, uint32_t old_color, for (size_t c = 0; c < term->grid->num_cols; c++) { struct cell *cell = &row->cells[c]; - if (cell->attrs.fg_src != COLOR_DEFAULT && - cell->attrs.fg == old_color) + enum color_source fg_src = cell->attrs.fg_src; + enum color_source bg_src = cell->attrs.bg_src; + + if ((fg_src == COLOR_BASE16 || fg_src == COLOR_BASE256) && + cell->attrs.fg == term->colors.table[palette_idx]) { cell->attrs.fg = new_color; cell->attrs.clean = 0; row->dirty = true; } - if (cell->attrs.bg_src != COLOR_DEFAULT && - cell->attrs.bg == old_color) + if ((bg_src == COLOR_BASE16 || bg_src == COLOR_BASE256) && + cell->attrs.bg == term->colors.table[palette_idx]) { cell->attrs.bg = new_color; cell->attrs.clean = 0; @@ -666,7 +668,7 @@ osc_dispatch(struct terminal *term) LOG_DBG("change color definition for #%u from %06x to %06x", idx, term->colors.table[idx], color); - update_color_in_grids(term, term->colors.table[idx], color); + update_color_in_grids(term, idx, color); term->colors.table[idx] = color; } } @@ -810,8 +812,7 @@ osc_dispatch(struct terminal *term) if (strlen(string) == 0) { LOG_DBG("resetting all colors"); for (size_t i = 0; i < ALEN(term->colors.table); i++) { - update_color_in_grids( - term, term->colors.table[i], term->conf->colors.table[i]); + update_color_in_grids(term, i, term->conf->colors.table[i]); term->colors.table[i] = term->conf->colors.table[i]; } } @@ -834,8 +835,7 @@ osc_dispatch(struct terminal *term) } LOG_DBG("resetting color #%u", idx); - update_color_in_grids( - term, term->colors.table[idx], term->conf->colors.table[idx]); + update_color_in_grids(term, idx, term->conf->colors.table[idx]); term->colors.table[idx] = term->conf->colors.table[idx]; } }