Merge branch 'master' into releases/1.10

This commit is contained in:
Daniel Eklöf 2021-12-02 20:25:19 +01:00
commit f9da959aa9
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
20 changed files with 1235 additions and 454 deletions

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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;

92
csi.c
View file

@ -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;

View file

@ -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*

533
grid.c
View file

@ -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 URIs 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);
}

15
grid.h
View file

@ -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;
}

718
input.c
View file

@ -6,6 +6,7 @@
#include <threads.h>
#include <locale.h>
#include <errno.h>
#include <wctype.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/timerfd.h>
@ -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 theres 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 cant 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 keys 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;

View file

@ -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);

22
osc.c
View file

@ -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];
}
}

View file

@ -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)
{

View file

@ -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});

View file

@ -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);

View file

@ -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)) {
/* Its 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);
}

View file

@ -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 {

View file

@ -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,

4
vt.c
View file

@ -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;

View file

@ -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;