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 diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f17114..026e745c 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,42 @@ * [1.2.0](#1-2-0) +## Unreleased + +### 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) +* “Window menu” (compositor provided) on right clicks on the CSD title + bar. + + +### Changed +### 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). +* 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. +* 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. +* Color palette changes (via OSC 4/104) no longer affect RGB colors + (https://codeberg.org/dnkl/foot/issues/678). + + +### Security +### Contributors + + ## 1.10.1 ### Added @@ -51,7 +88,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 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/csi.c b/csi.c index 0224dd80..f27f805f 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,25 @@ csi_dispatch(struct terminal *term, uint8_t final) } break; /* final == 'm' */ + case 'u': { + int flags = vt_param_get(term, 0, 0) & KITTY_KBD_SUPPORTED; + + 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; + + LOG_DBG("kitty kbd: pushed new flags: 0x%03x", flags); + break; + } + case 'q': { /* XTVERSION */ if (vt_param_get(term, 0, 0) != 0) { @@ -1556,6 +1585,36 @@ 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; + + LOG_DBG("kitty kbd: flags after pop: 0x%03x", + term->grid->kitty_kbd.flags[idx]); + break; + } + } + break; /* private[0] == ‘<’ */ + } + case ' ': { switch (final) { case 'q': { @@ -1633,6 +1692,39 @@ 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_SUPPORTED; + 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; + } + + LOG_DBG("kitty kbd: flags after update: 0x%03x", + grid->kitty_kbd.flags[idx]); + break; + } + default: UNHANDLED(); break; 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/grid.c b/grid.c index 459fa369..d6ddafad 100644 --- a/grid.c +++ b/grid.c @@ -15,6 +15,127 @@ #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 + count_to_add; + 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, int start, int end, + uint64_t id, const char *uri) +{ + 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.count++; + extra->uri_ranges.v[idx] = (struct row_uri_range){ + .start = start, + .end = end, + .id = id, + .uri = xstrdup(uri), + }; +} + +static void +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++] = (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 +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) { @@ -47,22 +168,20 @@ 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 = xcalloc(1, sizeof(*new_extra)); + const struct row_data *extra = row->extra; - 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), - }; + if (extra != NULL) { + struct row_data *clone_extra = xcalloc(1, sizeof(*clone_extra)); + clone_row->extra = clone_extra; - tll_push_back(new_extra->uri_ranges, range); + 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, + range->start, range->end, range->id, range->uri); } - - clone_row->extra = new_extra; } else clone_row->extra = NULL; } @@ -221,22 +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; - tll_foreach(old_row->extra->uri_ranges, it) { - if (it->item.start >= new_rows) { + ensure_row_has_extra_data(new_row); + struct row_data *new_extra = new_row->extra; + + 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; } - 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), - }; - grid_row_uri_range_add(new_row, range); + 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); } } @@ -249,6 +372,20 @@ 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]; + + if (row == NULL) + continue; + if (row->extra == NULL) + continue; + + 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++) grid_row_free(old_grid[r]); @@ -296,22 +433,21 @@ 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, - }; + ensure_row_has_extra_data(new_row); + uri_range_append_no_strdup + (new_row->extra, new_col_idx, -1, range->id, range->uri); range->uri = NULL; - grid_row_uri_range_add(new_row, new_range); } 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 +480,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,21 +489,18 @@ _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 */ 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), - }; - grid_row_uri_range_add(new_row, new_range); + ensure_row_has_extra_data(new_row); + uri_range_append(new_row->extra, 0, -1, range->id, range->uri); } } @@ -549,16 +683,20 @@ grid_resize_and_reflow( tp = NULL; /* 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_uri_range *range, *range_terminator; + struct row_data *extra = old_row->extra; - /* Make sure the *last* URI range's end point is included in the copy */ + if (extra != NULL && extra->uri_ranges.count > 0) { + 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 */ 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; + range = range_terminator = NULL; for (int start = 0, left = col_count; left > 0;) { int end; @@ -572,7 +710,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) { @@ -697,19 +835,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(&tll_front(old_row->extra->uri_ranges) == 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) - : NULL; + range++; } } @@ -752,8 +886,11 @@ 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_no_overlapping_uris(row->extra); + verify_uris_are_sorted(row->extra); } /* Verify all old rows have been free:d */ @@ -837,41 +974,164 @@ 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)); -} - void -grid_row_uri_range_add(struct row *row, struct row_uri_range range) +grid_row_uri_range_put(struct row *row, int col, const char *uri, uint64_t id) { 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); + + size_t insert_idx = 0; + bool replace = false; + bool run_merge_pass = false; + + 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; + } + + else if (r->start > col) + continue; + + else { + xassert(r->start <= col); + xassert(r->end >= col); + + if (matching_id) + goto out; + + 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++; + insert_idx = i; + } else if (r->end == col) { + run_merge_pass = true; + r->end--; + insert_idx = i + 1; + } else { + xassert(r->start < col); + xassert(r->end > col); + + uri_range_insert(extra, i + 1, col + 1, r->end, r->id, r->uri); + + r->end = col - 1; + xassert(r->start <= r->end); + + insert_idx = i + 1; + } + + break; + } } - tll_push_front(row->extra->uri_ranges, range); + xassert(insert_idx <= extra->uri_ranges.count); -out: - ; -#if defined(_DEBUG) - tll_foreach(row->extra->uri_ranges, it1) { - tll_foreach(row->extra->uri_ranges, it2) { - if (&it1->item == &it2->item) - continue; + if (replace) { + grid_row_uri_range_destroy(&extra->uri_ranges.v[insert_idx]); + 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, col, col, id, uri); - 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); + 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--; } } -#endif + } + +out: + verify_no_overlapping_uris(extra); + verify_uris_are_sorted(extra); +} + +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 @@ -880,31 +1140,27 @@ 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 = (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 */ - grid_row_uri_range_destroy(old); - tll_remove(row->extra->uri_ranges, it); + uri_range_delete(extra, i); } 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, - }; - tll_insert_after(row->extra->uri_ranges, it, 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 */ } @@ -913,80 +1169,69 @@ 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 */ } } } UNITTEST { - struct row_data row_data = {.uri_ranges = tll_init()}; + struct row_data row_data = {.uri_ranges = {0}}; 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) + /* Try erasing a row without any URIs */ + grid_row_uri_range_erase(&row, 0, 200); + xassert(row_data.uri_ranges.count == 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); - - 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); + 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); + verify_no_overlapping_uris(&row_data); + verify_uris_are_sorted(&row_data); /* 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); + xassert(row_data.uri_ranges.count == 0); + 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, 1, 10, 0, "dummy"); + uri_range_append(&row_data, 11, 20, 0, "dummy"); 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); - row_has_no_overlapping_uris(&row); + 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_data); + verify_uris_are_sorted(&row_data); - tll_pop_back(row_data.uri_ranges); - tll_pop_back(row_data.uri_ranges); - xassert(tll_length(row_data.uri_ranges) == 0); + 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 */ - grid_row_uri_range_add(&row, (struct row_uri_range){1, 10}); + uri_range_append(&row_data, 1, 10, 0, "dummy"); 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); - row_has_no_overlapping_uris(&row); + 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_data); + verify_uris_are_sorted(&row_data); -#undef row_has_no_overlapping_uris - - tll_free(row_data.uri_ranges); + 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); } diff --git a/grid.h b/grid.h index e2cd3a63..7819db4d 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); @@ -86,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/input.c b/input.c index 0aef43cc..055c0fc8 100644 --- a/input.c +++ b/input.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -361,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_super != XKB_MOD_INVALID) + mods |= modifiers->meta << seat->kbd.mod_super; return mods; } @@ -672,7 +677,25 @@ 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); + + 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_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) + 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"); @@ -778,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); @@ -965,24 +988,424 @@ 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 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; - if (effective != NULL) { *effective = xkb_state_serialize_mods( seat->kbd.xkb_state, XKB_STATE_MODS_EFFECTIVE); - *effective &= significant; } if (consumed != NULL) { - *consumed = xkb_state_key_get_consumed_mods(seat->kbd.xkb_state, key); - *consumed &= significant; + *consumed = xkb_state_key_get_consumed_mods2( + seat->kbd.xkb_state, key, XKB_CONSUMED_MODE_XKB); } } +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 bool +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; + keymap_mods |= seat->kbd.alt ? MOD_ALT : MOD_NONE; + keymap_mods |= seat->kbd.ctrl ? MOD_CTRL : 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; + 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~"}; + keymap = &esc; + } else + keymap = keymap_lookup(term, sym, keymap_mods); + + if (keymap != NULL) { + term_to_slave(term, keymap->seq, strlen(keymap->seq)); + return true; + } + + if (count == 0) + return false; + +#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=%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; + 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 < ALEN(mod_param_map)); + int modify_param = mod_param_map[keymap_mods]; + xassert(modify_param != 0); + + char reply[32]; + size_t n = xsnprintf(reply, sizeof(reply), "\x1b[27;%d;%d~", modify_param, sym); + term_to_slave(term, reply, n); + } + + else if (keymap_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 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. + */ + 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); + + return true; +} + +static bool +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 = 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) | + (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; + const size_t count = ctx->utf8.count; + + if (ctx->compose_status == XKB_COMPOSE_COMPOSED) { + term_to_slave(term, utf8, count); + return true; + } + + if (effective == 0) { + switch (sym) { + 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; + } + } + + /* + * 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 & ~caps_num) == 0) { + term_to_slave(term, utf8, count); + return true; + } + + unsigned int encoded_mods = 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_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) + encoded_mods |= mods & (1 << seat->kbd.mod_num) ? (1 << 7) : 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_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; + 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_Scroll_Lock: key = 57359; 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; + + 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; + +#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_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) { + if (effective == 0) { + term_to_slave(term, utf8, count); + return true; + } + + /* + * 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)); + + /* 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] & ~seat->kbd.kitty_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; + } + + xassert(encoded_mods >= 1); + + char buf[16]; + int bytes; + + if (key < 0) + return false; + + 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); + return true; +} + static void key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, uint32_t key, uint32_t state) @@ -995,7 +1418,6 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, return; } - if (state == XKB_KEY_UP) { stop_repeater(seat, key); return; @@ -1026,14 +1448,15 @@ 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); + 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); @@ -1045,13 +1468,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; } @@ -1081,14 +1504,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 */ @@ -1114,29 +1537,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; - - 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,116 +1545,61 @@ 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]; 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); + } + + 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, + }; + + 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); -#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); - } - if (utf8 != buf) free(utf8); - term_reset_view(term); - selection_cancel(term); + if (handled) { + term_reset_view(term); + selection_cancel(term); + } maybe_repeat: clock_gettime( @@ -1287,14 +1632,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.super = seat->kbd.mod_super != XKB_MOD_INVALID + ? xkb_state_mod_index_is_active( + seat->kbd.xkb_state, seat->kbd.mod_super, XKB_STATE_MODS_EFFECTIVE) + : false; } if (seat->kbd_focus && seat->kbd_focus->active_surface == TERM_SURF_GRID) @@ -1372,7 +1725,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; @@ -1405,23 +1758,28 @@ 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; - seat->pointer.serial = serial; - seat->pointer.hidden = false; - - LOG_DBG("pointer-enter: pointer=%p, serial=%u, surface = %p, new-moused = %p", - (void *)wl_pointer, serial, (void *)surface, (void *)term); - - xassert(tll_length(seat->mouse.buttons) == 0); - - /* Scale may have changed */ - wayl_reload_xcursor_theme(seat, term->scale); - - 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))) { + 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, " + "x=%d, y=%d", + (void *)wl_pointer, serial, (void *)surface, (void *)term, + x, y); + + xassert(tll_length(seat->mouse.buttons) == 0); + + seat->mouse_focus = term; + 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 @@ -1439,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; } @@ -1448,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; @@ -1490,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; @@ -1519,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: @@ -1531,8 +1884,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: @@ -1569,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; @@ -1606,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: { @@ -1657,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; @@ -1920,6 +2275,15 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, close(fd); } } + + if (button == BTN_RIGHT && tll_length(seat->mouse.buttons) == 1) { + const struct csd_data 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) { @@ -2006,11 +2370,13 @@ 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 * 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; diff --git a/input.h b/input.h index 68b8719d..da887ef2 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/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]; } } 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) { diff --git a/render.c b/render.c index 2710c0a2..34a4da3a 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); @@ -1763,14 +1756,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 +1783,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}); 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); diff --git a/terminal.c b/terminal.c index 4cc9ed09..25310d36 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, @@ -1818,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; @@ -1826,7 +1827,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) { @@ -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 @@ -2962,14 +2962,42 @@ 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 = NULL; + + 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 + : 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: + return; + } + + if (xcursor == NULL) + BUG("xcursor not set"); render_xcursor_set(seat, term, xcursor); } @@ -3151,7 +3179,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 +3239,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 +3267,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 +3298,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 +3324,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 +3332,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 +3529,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..62ccfb2a 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 { @@ -123,6 +127,15 @@ struct sixel { bool opaque; }; +enum kitty_kbd_flags { + KITTY_KBD_DISAMBIGUATE = 0x01, + KITTY_KBD_REPORT_EVENT = 0x02, + KITTY_KBD_REPORT_ALTERNATE = 0x04, + KITTY_KBD_REPORT_ALL = 0x08, + KITTY_KBD_REPORT_ASSOCIATED = 0x10, + KITTY_KBD_SUPPORTED = KITTY_KBD_DISAMBIGUATE, +}; + struct grid { int num_rows; int num_cols; @@ -145,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 { @@ -185,7 +204,6 @@ struct vt { struct { uint64_t id; char *uri; - struct coord begin; } osc8; struct { 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, 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; diff --git a/wayland.h b/wayland.h index 0fffc0a7..d415588b 100644 --- a/wayland.h +++ b/wayland.h @@ -194,7 +194,12 @@ 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; + + xkb_mod_mask_t bind_significant; + xkb_mod_mask_t kitty_significant; xkb_keycode_t key_arrow_up; xkb_keycode_t key_arrow_down; @@ -203,7 +208,7 @@ struct seat { bool shift; bool alt; bool ctrl; - bool meta; + bool super; struct { key_binding_list_t key;