From 7eea69df8928ff0f0ea481ff62d8a9166151509f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 May 2023 09:42:35 +0200 Subject: [PATCH 001/135] term: reset: switch modifyOtherKeys back to level 1 --- CHANGELOG.md | 1 + terminal.c | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d64392a2..5b2625fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ * Incorrect icon in dock and window switcher on Gnome ([#1317][1317]) * Crash when scrolling after resizing the window with non-zero scrolling regions. +* `XTMODKEYS` state not being reset on a terminal reset. [1317]: https://codeberg.org/dnkl/foot/issues/1317 diff --git a/terminal.c b/terminal.c index 2e62fbb7..39d9b406 100644 --- a/terminal.c +++ b/terminal.c @@ -1933,6 +1933,7 @@ term_reset(struct terminal *term, bool hard) term_set_user_mouse_cursor(term, NULL); + term->modify_other_keys_2 = false; 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; From 3b41379be43a21a00a776d0d136c5d1b2fe4007e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 25 Apr 2023 21:33:45 +0200 Subject: [PATCH 002/135] quirks: sway does not damage surface beneath sub-surface, when unmapped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When unmapping a sub-surface, Sway <= 1.8 does not damage the surface beneath the sub-surface. https://github.com/swaywm/sway/issues/6960 The workaround is to manually damage the main surface. Previously, this was done when exiting scrollback search, and after the ‘flash’ OSC. But other sub-surfaces, that may also be unmapped, did not. This patch adds a quirk handler that does this, and calls it when: * Exiting scrollback search * Ending the ‘flash’ OSC * Exiting unicode input mode * Clearing URL labels * Removing the scrollback position indicator Closes #1335 --- quirks.c | 25 +++++++++++++++++++++++++ quirks.h | 2 ++ render.c | 11 ++++++++++- search.c | 6 +----- terminal.c | 5 ----- url-mode.c | 5 +++++ 6 files changed, 43 insertions(+), 11 deletions(-) diff --git a/quirks.c b/quirks.c index e4fe4a1f..bf9bc7fb 100644 --- a/quirks.c +++ b/quirks.c @@ -66,3 +66,28 @@ quirk_weston_csd_off(struct terminal *term) for (int i = 0; i < ALEN(term->window->csd.surface); i++) quirk_weston_subsurface_desync_off(term->window->csd.surface[i].sub); } + +static bool +is_sway(void) +{ + static bool is_sway = false; + static bool initialized = false; + + if (!initialized) { + initialized = true; + is_sway = getenv("SWAYSOCK") != NULL; + if (is_sway) + LOG_WARN("applying wl_surface_damage_buffer() workaround for Sway"); + } + + return is_sway; +} + +void +quirk_sway_subsurface_unmap(struct terminal *term) +{ + if (!is_sway()) + return; + + wl_surface_damage_buffer(term->window->surface, 0, 0, INT32_MAX, INT32_MAX); +} diff --git a/quirks.h b/quirks.h index e762bb3e..0e840667 100644 --- a/quirks.h +++ b/quirks.h @@ -21,3 +21,5 @@ void quirk_weston_subsurface_desync_off(struct wl_subsurface *sub); /* Shortcuts to call desync_{on,off} on all CSD subsurfaces */ void quirk_weston_csd_on(struct terminal *term); void quirk_weston_csd_off(struct terminal *term); + +void quirk_sway_subsurface_unmap(struct terminal *term); diff --git a/render.c b/render.c index 521c8b7f..f1e80392 100644 --- a/render.c +++ b/render.c @@ -1527,6 +1527,10 @@ render_overlay(struct terminal *term) wl_surface_commit(overlay->surf); term->render.last_overlay_style = OVERLAY_NONE; term->render.last_overlay_buf = NULL; + + /* Work around Sway bug - unmapping a sub-surface does not + * damage the underlying surface */ + quirk_sway_subsurface_unmap(term); } return; } @@ -2374,8 +2378,13 @@ render_scrollback_position(struct terminal *term) struct wl_window *win = term->window; if (term->grid->view == term->grid->offset) { - if (win->scrollback_indicator.surf != NULL) + if (win->scrollback_indicator.surf != NULL) { wayl_win_subsurface_destroy(&win->scrollback_indicator); + + /* Work around Sway bug - unmapping a sub-surface does not damage + * the underlying surface */ + quirk_sway_subsurface_unmap(term); + } return; } diff --git a/search.c b/search.c index 59765c2e..b4bec057 100644 --- a/search.c +++ b/search.c @@ -15,6 +15,7 @@ #include "input.h" #include "key-binding.h" #include "misc.h" +#include "quirks.h" #include "render.h" #include "selection.h" #include "shm.h" @@ -117,11 +118,6 @@ search_cancel_keep_selection(struct terminal *term) term_xcursor_update(term); render_refresh(term); - - /* Work around Sway bug - unmapping a sub-surface does not damage - * the underlying surface */ - term_damage_margins(term); - term_damage_view(term); } void diff --git a/terminal.c b/terminal.c index 39d9b406..ddc24cb3 100644 --- a/terminal.c +++ b/terminal.c @@ -406,11 +406,6 @@ fdm_flash(struct fdm *fdm, int fd, int events, void *data) term->flash.active = false; render_refresh(term); - - /* Work around Sway bug - unmapping a sub-surface does not damage - * the underlying surface */ - term_damage_margins(term); - term_damage_view(term); return true; } diff --git a/url-mode.c b/url-mode.c index 7d7ffd81..bd9b5157 100644 --- a/url-mode.c +++ b/url-mode.c @@ -14,6 +14,7 @@ #include "char32.h" #include "grid.h" #include "key-binding.h" +#include "quirks.h" #include "render.h" #include "selection.h" #include "spawn.h" @@ -859,6 +860,10 @@ urls_reset(struct terminal *term) tll_foreach(term->window->urls, it) { wayl_win_subsurface_destroy(&it->item.surf); tll_remove(term->window->urls, it); + + /* Work around Sway bug - unmapping a sub-surface does not + * damage the underlying surface */ + quirk_sway_subsurface_unmap(term); } } From 738deb236853ebf1e19843e536b9bda053b69bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 15 May 2023 20:34:58 +0200 Subject: [PATCH 003/135] search: regression: refresh current view when canceling a scrollback search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3b41379be43a21a00a776d0d136c5d1b2fe4007e introduced a regression, where canceling a scrollback search didn’t refresh the viewport correctly; the viewport was changed, but the screen content was not refreshed. This worked before, because the workaround for https://github.com/swaywm/sway/issues/6960 always called term_damage_view() when exiting scrollback search mode. 3b41379be43a21a00a776d0d136c5d1b2fe4007e removed that call since it’s no longer required. *Except* when executing the BIND_ACTION_SEARCH_CANCEL binding, since then the viewport may be moved. Note that this regression affected *all* compositors, not just Sway. Closes #1354 --- search.c | 1 + 1 file changed, 1 insertion(+) diff --git a/search.c b/search.c index b4bec057..6c2a2a7e 100644 --- a/search.c +++ b/search.c @@ -829,6 +829,7 @@ execute_binding(struct seat *seat, struct terminal *term, grid->view = ensure_view_is_allocated( term, term->search.original_view); } + term_damage_view(term); search_cancel(term); return true; From d2f81443f167408318eeaf4e1c1eae6cb74e672e Mon Sep 17 00:00:00 2001 From: locture Date: Tue, 25 Apr 2023 03:43:36 +0000 Subject: [PATCH 004/135] customized gnome-like csd buttons --- render.c | 207 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 121 insertions(+), 86 deletions(-) diff --git a/render.c b/render.c index f1e80392..340e0378 100644 --- a/render.c +++ b/render.c @@ -2096,41 +2096,24 @@ render_csd_button_minimize(struct terminal *term, struct buffer *buf) pixman_color_t color = get_csd_button_fg_color(term->conf); pixman_image_t *src = pixman_image_create_solid_fill(&color); - const int max_height = buf->height / 2; - const int max_width = buf->width / 2; + const int max_height = buf->height / 3; + const int max_width = buf->width / 3; - int width = max_width; - int height = max_width / 2; + int width = min(max_height, max_width); + int thick = min(width / 2, 1 * term->scale); - if (height > max_height) { - height = max_height; - width = height * 2; - } + const int x_margin = (buf->width - width) / 2; + const int y_margin = (buf->height - width) / 2; - xassert(width <= max_width); - xassert(height <= max_height); + xassert(x_margin + width - thick >= 0); + xassert(width - 2 * thick >= 0); + xassert(y_margin + width - thick >= 0); + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &color, 1, + (pixman_rectangle16_t[]) { + {x_margin, y_margin + width - thick, width, thick} + }); - int x_margin = (buf->width - width) / 2.; - int y_margin = (buf->height - height) / 2.; - - pixman_triangle_t tri = { - .p1 = { - .x = pixman_int_to_fixed(x_margin), - .y = pixman_int_to_fixed(y_margin), - }, - .p2 = { - .x = pixman_int_to_fixed(x_margin + width), - .y = pixman_int_to_fixed(y_margin), - }, - .p3 = { - .x = pixman_int_to_fixed(buf->width / 2), - .y = pixman_int_to_fixed(y_margin + height), - }, - }; - - pixman_composite_triangles( - PIXMAN_OP_OVER, src, buf->pix[0], PIXMAN_a1, - 0, 0, 0, 0, 1, &tri); pixman_image_unref(src); } @@ -2147,6 +2130,38 @@ render_csd_button_maximize_maximized( int width = min(max_height, max_width); int thick = min(width / 2, 1 * term->scale); + const int x_margin = (buf->width - width) / 2; + const int y_margin = (buf->height - width) / 2; + const int shrink = 1; + xassert(x_margin + width - thick >= 0); + xassert(width - 2 * thick >= 0); + xassert(y_margin + width - thick >= 0); + + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &color, 4, + (pixman_rectangle16_t[]){ + {x_margin + shrink, y_margin + shrink, width - 2 * shrink, thick}, + { x_margin + shrink, y_margin + thick, thick, width - 2 * thick - shrink }, + { x_margin + width - thick - shrink, y_margin + thick, thick, width - 2 * thick - shrink }, + { x_margin + shrink, y_margin + width - thick - shrink, width - 2 * shrink, thick }}); + + pixman_image_unref(src); + +} + +static void +render_csd_button_maximize_window( + struct terminal *term, struct buffer *buf) +{ + pixman_color_t color = get_csd_button_fg_color(term->conf); + pixman_image_t *src = pixman_image_create_solid_fill(&color); + + const int max_height = buf->height / 3; + const int max_width = buf->width / 3; + + int width = min(max_height, max_width); + int thick = min(width / 2, 1 * term->scale); + const int x_margin = (buf->width - width) / 2; const int y_margin = (buf->height - width) / 2; @@ -2156,58 +2171,12 @@ render_csd_button_maximize_maximized( pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix[0], &color, 4, - (pixman_rectangle16_t[]){ + (pixman_rectangle16_t[]) { {x_margin, y_margin, width, thick}, - {x_margin, y_margin + thick, thick, width - 2 * thick}, - {x_margin + width - thick, y_margin + thick, thick, width - 2 * thick}, - {x_margin, y_margin + width - thick, width, thick}}); - - pixman_image_unref(src); - -} - -static void -render_csd_button_maximize_window( - struct terminal *term, struct buffer *buf) -{ - pixman_color_t color = get_csd_button_fg_color(term->conf); - pixman_image_t *src = pixman_image_create_solid_fill(&color); - - const int max_height = buf->height / 2; - const int max_width = buf->width / 2; - - int width = max_width; - int height = max_width / 2; - - if (height > max_height) { - height = max_height; - width = height * 2; - } - - xassert(width <= max_width); - xassert(height <= max_height); - - int x_margin = (buf->width - width) / 2.; - int y_margin = (buf->height - height) / 2.; - - pixman_triangle_t tri = { - .p1 = { - .x = pixman_int_to_fixed(buf->width / 2), - .y = pixman_int_to_fixed(y_margin), - }, - .p2 = { - .x = pixman_int_to_fixed(x_margin), - .y = pixman_int_to_fixed(y_margin + height), - }, - .p3 = { - .x = pixman_int_to_fixed(x_margin + width), - .y = pixman_int_to_fixed(y_margin + height), - }, - }; - - pixman_composite_triangles( - PIXMAN_OP_OVER, src, buf->pix[0], PIXMAN_a1, - 0, 0, 0, 0, 1, &tri); + { x_margin, y_margin + thick, thick, width - 2 * thick }, + { x_margin + width - thick, y_margin + thick, thick, width - 2 * thick }, + { x_margin, y_margin + width - thick, width, thick } + }); pixman_image_unref(src); } @@ -2231,13 +2200,79 @@ render_csd_button_close(struct terminal *term, struct buffer *buf) const int max_width = buf->width / 3; int width = min(max_height, max_width); - + int thick = min(width / 2, 1 * term->scale); const int x_margin = (buf->width - width) / 2; const int y_margin = (buf->height - width) / 2; - pixman_image_fill_rectangles( - PIXMAN_OP_SRC, buf->pix[0], &color, 1, - &(pixman_rectangle16_t){x_margin, y_margin, width, width}); + xassert(x_margin + width - thick >= 0); + xassert(width - 2 * thick >= 0); + xassert(y_margin + width - thick >= 0); + + pixman_triangle_t tri[4] = { + { + .p1 = { + .x = pixman_int_to_fixed(x_margin), + .y = pixman_int_to_fixed(y_margin + thick), + }, + .p2 = { + .x = pixman_int_to_fixed(x_margin + width - thick), + .y = pixman_int_to_fixed(y_margin + width), + }, + .p3 = { + .x = pixman_int_to_fixed(x_margin + thick), + .y = pixman_int_to_fixed(y_margin), + }, + }, + + { + .p1 = { + .x = pixman_int_to_fixed(x_margin + width), + .y = pixman_int_to_fixed(y_margin + width - thick), + }, + .p2 = { + .x = pixman_int_to_fixed(x_margin + thick), + .y = pixman_int_to_fixed(y_margin), + }, + .p3 = { + .x = pixman_int_to_fixed(x_margin + width - thick), + .y = pixman_int_to_fixed(y_margin + width), + }, + }, + + { + .p1 = { + .x = pixman_int_to_fixed(x_margin), + .y = pixman_int_to_fixed(y_margin + width - thick), + }, + .p2 = { + .x = pixman_int_to_fixed(x_margin + width), + .y = pixman_int_to_fixed(y_margin + thick), + }, + .p3 = { + .x = pixman_int_to_fixed(x_margin + thick), + .y = pixman_int_to_fixed(y_margin + width), + }, + }, + + { + .p1 = { + .x = pixman_int_to_fixed(x_margin + width), + .y = pixman_int_to_fixed(y_margin + thick), + }, + .p2 = { + .x = pixman_int_to_fixed(x_margin), + .y = pixman_int_to_fixed(y_margin + width - thick), + }, + .p3 = { + .x = pixman_int_to_fixed(x_margin + width - thick), + .y = pixman_int_to_fixed(y_margin), + }, + }, + }; + + pixman_composite_triangles( + PIXMAN_OP_OVER, src, buf->pix[0], PIXMAN_a1, + 0, 0, 0, 0, 4, tri); pixman_image_unref(src); } From a2f765b72a6612758a034a5b0c24ec08625db1a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 May 2023 14:55:55 +0200 Subject: [PATCH 005/135] slave: unset TERM_PROGRAM{,_VERSION} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foot’s policy is to not set environment variables that identifies it (except the well-known and established `TERM` variable). We encourage applications to use terminfo to determine capabilities, or terminal queries, when available. Or, at least use terminal queries to detect the terminal and its version. Setting environment variables is a bad idea since they are inherited by all applications started by the terminal (which is the whole point). But, this includes other terminal emulators, making it very possible a terminal emulator gets mis-detected just because it was started from another terminal. Since there are a couple of terminal emulators that _do_ set TERM_PROGRAM and TERM_PROGRAM_VERSION, unset these environment variables to avoid being misdetected. Closes #1349 --- CHANGELOG.md | 2 +- doc/foot.1.scd | 11 ----------- doc/footclient.1.scd | 11 ----------- generate-version.sh | 1 - slave.c | 6 +++--- 5 files changed, 4 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b2625fb..e9912729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,7 +93,7 @@ * “Report DA2” terminfo entries (`RV`/`rv`). * `XF` terminfo capability (focus in/out events available). * `$TERM_PROGRAM` and `$TERM_PROGRAM_VERSION` environment variables - set in the slave process. + unset in the slave process. [1136]: https://codeberg.org/dnkl/foot/issues/1136 [1225]: https://codeberg.org/dnkl/foot/issues/1225 diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 51c53130..6f63d4c8 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -546,17 +546,6 @@ In all other cases, the exit code is that of the client application This variable is set to *truecolor*, to indicate to client applications that 24-bit RGB colors are supported. -*TERM_PROGRAM* - Always set to *foot*. This can be used by client applications to - check which terminal is in use, but with the caveat that it may - have been inherited from a parent process in other terminals that - aren't known to set the variable. - -*TERM_PROGRAM_VERSION* - Set to the foot version string, in the format _major_*.*_minor_*.*_patch_ - or _major_*.*_minor_*.*_patch_*-*_revision_*-\g*_commit_ for inter-release - builds. The same caveat as for *TERM_PROGRAM* applies. - In addition to the variables listed above, custom environment variables may be defined in *foot.ini*(5). diff --git a/doc/footclient.1.scd b/doc/footclient.1.scd index 189d9e3c..63235134 100644 --- a/doc/footclient.1.scd +++ b/doc/footclient.1.scd @@ -158,17 +158,6 @@ terminfo entries manually, by copying *foot* and *foot-direct* to This variable is set to *truecolor*, to indicate to client applications that 24-bit RGB colors are supported. -*TERM_PROGRAM* - Always set to *foot*. This can be used by client applications to - check which terminal is in use, but with the caveat that it may - have been inherited from a parent process in other terminals that - aren't known to set the variable. - -*TERM_PROGRAM_VERSION* - Set to the foot version string, in the format _major_*.*_minor_*.*_patch_ - or _major_*.*_minor_*.*_patch_*-*_revision_*-\g*_commit_ for inter-release - builds. The same caveat as for *TERM_PROGRAM* applies. - In addition to the variables listed above, custom environment variables may be defined in *foot.ini*(5). diff --git a/generate-version.sh b/generate-version.sh index 3772008b..a030d512 100755 --- a/generate-version.sh +++ b/generate-version.sh @@ -41,7 +41,6 @@ patch=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\3/') extra=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+)(-([0-9]+-g[a-z0-9]+) .*)?.*/\5/') new_version="#define FOOT_VERSION \"${new_version}\" -#define FOOT_VERSION_SHORT \"${git_version:-${default_version}}\" #define FOOT_MAJOR ${major} #define FOOT_MINOR ${minor} #define FOOT_PATCH ${patch} diff --git a/slave.c b/slave.c index 2f23e996..ecfce7e6 100644 --- a/slave.c +++ b/slave.c @@ -21,7 +21,6 @@ #include "macros.h" #include "terminal.h" #include "tokenize.h" -#include "version.h" #include "xmalloc.h" extern char **environ; @@ -352,11 +351,12 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv, } setenv("TERM", term_env, 1); - setenv("TERM_PROGRAM", "foot", 1); - setenv("TERM_PROGRAM_VERSION", FOOT_VERSION_SHORT, 1); setenv("COLORTERM", "truecolor", 1); setenv("PWD", cwd, 1); + unsetenv("TERM_PROGRAM"); + unsetenv("TERM_PROGRAM_VERSION"); + #if defined(FOOT_TERMINFO_PATH) setenv("TERMINFO", FOOT_TERMINFO_PATH, 1); #endif From e78319fccd2b8c057deff4b1937b325be1d35140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 17 May 2023 20:51:40 +0200 Subject: [PATCH 006/135] utmp: rewrite utmp logging This patch generalizes the utmp support, to not only support libutempter, but also ulog (and in the future, even more interfaces). * Rename config option main.utempter to main.utmp-helper * Add meson option -Dutmp-backend=none|libutempter|ulog|auto * Rename meson option -Ddefault-utempter-path to -Dutmp-default-helper-path * utmp is no longer detected at compile time, but at runtime instead. Meson will configure the following pre-processor macros, based on the selected utmp backend: * UTMP_ADD - argument to pass to utmp helper when adding a record (starting foot) * UTMP_DEL - argument to pass to utmp helper when removing a record (exiting foot) * UTMP_DEL_HAVE_ARGUMENT - if defined, UTMP_DEL expects an extra argument ($WAYLAND_DISPLAY) * UTMP_DEFAULT_HELPER_PATH - path to the default utmp helper binary The documentation has been updated to mention which arguments are passed to the helper binary. Closes #1314 --- CHANGELOG.md | 11 ++++++++ INSTALL.md | 26 ++++++++++--------- config.c | 41 +++++++++++++++++++++-------- config.h | 2 +- doc/foot.ini.5.scd | 21 ++++++++++++--- doc/meson.build | 18 ++++++++++--- foot.ini | 3 ++- meson.build | 63 +++++++++++++++++++++++++++++++-------------- meson_options.txt | 6 +++-- terminal.c | 24 ++++++++++++++--- tests/test-config.c | 2 +- 11 files changed, 159 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9912729..d3b4df58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,9 @@ ### Added * VT: implemented `XTQMODKEYS` query (`CSI ? Pp m`). +* Meson option `utmp-backend=none|libutempter|ulog|auto`. The default + is `auto`, which will select `libutempter` on Linux, `ulog` on + FreeBSD, and `none` for all others. ### Changed @@ -54,9 +57,17 @@ `CSI R`. The kitty keyboard protocol originally allowed F3 to be encoded as `CSI R`, but this was removed from the specification since `CSI R` conflicts with the _”Cursor Position Report”_. +* `[main].utempter` renamed to `[main].utmp-helper`. The old option + name is still recognized, but will log a deprecation warning. +* Meson option `default-utempter-path` renamed to + `utmp-default-helper-path`. ### Deprecated + +* `[main].utempter` option. + + ### Removed ### Fixed diff --git a/INSTALL.md b/INSTALL.md index 6cc51750..9e2da8ec 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -45,7 +45,8 @@ subprojects. * wayland (_client_ and _cursor_ libraries) * xkbcommon * utf8proc (_optional_, needed for grapheme clustering) -* libutempter (_optional_, needed for utmp logging) +* libutempter (_optional_, needed for utmp logging on Linux) +* ulog (_optional_, needed for utmp logging on FreeBSD) * [fcft](https://codeberg.org/dnkl/fcft) [^1] [^1]: can also be built as subprojects, in which case they are @@ -142,17 +143,18 @@ mkdir -p bld/release && cd bld/release Available compile-time options: -| Option | Type | Default | Description | Extra dependencies | -|--------------------------------------|---------|-------------------------|-----------------------------------------------------------|--------------------| -| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc | -| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | none | -| `-Dime` | bool | `true` | Enables IME support | None | -| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc | -| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) | -| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | none | -| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None | -| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None | -| `-Ddefault-utempter-path` | feature | `auto` | Default path to utempter binary (‘none’ disables default) | libutempter | +| Option | Type | Default | Description | Extra dependencies | +|--------------------------------------|---------|-------------------------|---------------------------------------------------------------------------------|---------------------| +| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc | +| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | None | +| `-Dime` | bool | `true` | Enables IME support | None | +| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc | +| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) | +| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | None | +| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None | +| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None | +| `-Dutmp-backend` | combo | `auto` | Which utmp backend to use (`none`, `libutempter`, `ulog` or `auto`) | libutempter or ulog | +| `-Dutmp-default-helper-path` | string | `auto` | Default path to utmp helper binary. `auto` selects path based on `utmp-backend` | None | Documentation includes the man pages, readme, changelog and license files. diff --git a/config.c b/config.c index 2ede1aa5..c3ab61e2 100644 --- a/config.c +++ b/config.c @@ -1009,13 +1009,29 @@ parse_section_main(struct context *ctx) else if (strcmp(key, "box-drawings-uses-font-glyphs") == 0) return value_to_bool(ctx, &conf->box_drawings_uses_font_glyphs); - else if (strcmp(key, "utempter") == 0) { - if (!value_to_str(ctx, &conf->utempter_path)) + else if (strcmp(key, "utmp-helper") == 0 || strcmp(key, "utempter") == 0) { + if (strcmp(key, "utempter") == 0) { + struct user_notification deprecation = { + .kind = USER_NOTIFICATION_DEPRECATED, + .text = xasprintf( + "%s:%d: \033[1m[main].utempter\033[22m, " + "use \033[1m[main].utmp-helper\033[22m instead", + ctx->path, ctx->lineno), + }; + tll_push_back(conf->notifications, deprecation); + + LOG_WARN( + "%s:%d: [main].utempter is deprecated, " + "use [main].utmp-helper instead", + ctx->path, ctx->lineno); + } + + if (!value_to_str(ctx, &conf->utmp_helper_path)) return false; - if (strcmp(conf->utempter_path, "none") == 0) { - free(conf->utempter_path); - conf->utempter_path = NULL; + if (strcmp(conf->utmp_helper_path, "none") == 0) { + free(conf->utmp_helper_path); + conf->utmp_helper_path = NULL; } return true; @@ -3019,9 +3035,12 @@ config_load(struct config *conf, const char *conf_path, }, .env_vars = tll_init(), - .utempter_path = (strlen(FOOT_DEFAULT_UTEMPTER_PATH) > 0 - ? xstrdup(FOOT_DEFAULT_UTEMPTER_PATH) - : NULL), +#if defined(UTMP_DEFAULT_HELPER_PATH) + .utmp_helper_path = ((strlen(UTMP_DEFAULT_HELPER_PATH) > 0 && + access(UTMP_DEFAULT_HELPER_PATH, X_OK) == 0) + ? xstrdup(UTMP_DEFAULT_HELPER_PATH) + : NULL), +#endif .notifications = tll_init(), }; @@ -3310,8 +3329,8 @@ config_clone(const struct config *old) tll_push_back(conf->env_vars, copy); } - conf->utempter_path = - old->utempter_path != NULL ? xstrdup(old->utempter_path) : NULL; + conf->utmp_helper_path = + old->utmp_helper_path != NULL ? xstrdup(old->utmp_helper_path) : NULL; conf->notifications.length = 0; conf->notifications.head = conf->notifications.tail = 0; @@ -3379,7 +3398,7 @@ config_free(struct config *conf) tll_remove(conf->env_vars, it); } - free(conf->utempter_path); + free(conf->utmp_helper_path); user_notifications_free(&conf->notifications); } diff --git a/config.h b/config.h index 31dddc64..34517019 100644 --- a/config.h +++ b/config.h @@ -320,7 +320,7 @@ struct config { env_var_list_t env_vars; - char *utempter_path; + char *utmp_helper_path; struct { enum fcft_scaling_filter fcft_filter; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 5ef62045..6a820892 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -343,9 +343,24 @@ empty string to be set, but it must be quoted: *KEY=""*) (including SMT). Note that this is not always the best value. In some cases, the number of physical _cores_ is better. -*utempter* - Path to utempter helper binary. Set to *none* to disable utmp - records. Default: _@utempter@_. +*utmp-helper* + Path to utmp logging helper binary. + + When starting foot, an utmp record is created by launching the + helper binary with the following arguments: + + ``` + @utmp_add_args@ + ``` + + When foot is closed, the utmp record is removed by launching the + helper binary with the following arguments: + + ``` + @utmp_del_args@ + ``` + + Set to *none* to disable utmp records. Default: _@utmp_helper_path@_. # SECTION: environment diff --git a/doc/meson.build b/doc/meson.build index 86e75952..17f09f39 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -2,16 +2,26 @@ sh = find_program('sh', native: true) scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true) -if utempter_path == '' - default_utempter_value = 'not set' +if utmp_backend != 'none' + utmp_add_args = '@0@ $WAYLAND_DISPLAY'.format(utmp_add) + utmp_del_args = (utmp_del_have_argument + ? '@0@ $WAYLAND_DISPLAY'.format(utmp_del) + : '@0@'.format(utmp_del)) + utmp_path = utmp_default_helper_path else - default_utempter_value = utempter_path + utmp_add_args = '' + utmp_del_args = '' + utmp_path = 'none' endif + conf_data = configuration_data( { 'default_terminfo': get_option('default-terminfo'), - 'utempter': default_utempter_value, + 'utmp_backend': utmp_backend, + 'utmp_add_args': utmp_add_args, + 'utmp_del_args': utmp_del_args, + 'utmp_helper_path': utmp_path, } ) diff --git a/foot.ini b/foot.ini index 8266b01b..4b2218a4 100644 --- a/foot.ini +++ b/foot.ini @@ -34,7 +34,8 @@ # word-delimiters=,│`|:"'()[]{}<> # selection-target=primary # workers= -# utempter=/usr/lib/utempter/utempter +# utmp-helper=/usr/lib/utempter/utempter # When utmp backend is ‘libutempter’ (Linux) +# utmp-helper=/usr/libexec/ulog-helper # When utmp backend is ‘ulog’ (FreeBSD) [environment] # name=value diff --git a/meson.build b/meson.build index a6892cc4..c5ef1928 100644 --- a/meson.build +++ b/meson.build @@ -16,30 +16,54 @@ if cc.has_function('memfd_create') add_project_arguments('-DMEMFD_CREATE', language: 'c') endif -utempter_path = get_option('default-utempter-path') -if utempter_path == '' - utempter = find_program( - 'utempter', - required: false, - dirs: [join_paths(get_option('prefix'), get_option('libdir'), 'utempter'), - join_paths(get_option('prefix'), get_option('libexecdir'), 'utempter'), - '/usr/lib/utempter', - '/usr/libexec/utempter', - '/lib/utempter'] - ) - if utempter.found() - utempter_path = utempter.full_path() +utmp_backend = get_option('utmp-backend') +if utmp_backend == 'auto' + host_os = host_machine.system() + if host_os == 'linux' + utmp_backend = 'libutempter' + elif host_os == 'freebsd' + utmp_backend = 'ulog' else - utempter_path = '' + utmp_backend = 'none' endif -elif utempter_path == 'none' - utempter_path = '' +endif + +utmp_default_helper_path = get_option('utmp-default-helper-path') + +if utmp_backend == 'none' + utmp_add = '' + utmp_del = '' + utmp_del_have_argument = false + utmp_default_helper_path = '' +elif utmp_backend == 'libutempter' + utmp_add = 'add' + utmp_del = 'del' + utmp_del_have_argument = true + if utmp_default_helper_path == 'auto' + utmp_default_helper_path = join_paths('/usr', get_option('libdir'), 'utempter', 'utempter') + endif +elif utmp_backend == 'ulog' + utmp_add = 'login' + utmp_del = 'logout' + utmp_del_have_argument = false + if utmp_default_helper_path == 'auto' + utmp_default_helper_path = join_paths('/usr', get_option('libexecdir'), 'ulog-helper') + endif +else + error('invalid utmp backend') endif add_project_arguments( ['-D_GNU_SOURCE=200809L', - '-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo')), - '-DFOOT_DEFAULT_UTEMPTER_PATH="@0@"'.format(utempter_path)] + + '-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo'))] + + (utmp_backend != 'none' + ? ['-DUTMP_ADD="@0@"'.format(utmp_add), + '-DUTMP_DEL="@0@"'.format(utmp_del), + '-DUTMP_DEFAULT_HELPER_PATH="@0@"'.format(utmp_default_helper_path)] + : []) + + (utmp_del_have_argument + ? ['-DUTMP_DEL_HAVE_ARGUMENT=1'] + : []) + (is_debug_build ? ['-D_DEBUG'] : [cc.get_supported_arguments('-fno-asynchronous-unwind-tables')]) + @@ -343,7 +367,8 @@ summary( 'Themes': get_option('themes'), 'IME': get_option('ime'), 'Grapheme clustering': utf8proc.found(), - 'Utempter path': utempter_path, + 'utmp backend': utmp_backend, + 'utmp helper default path': utmp_default_helper_path, 'Build terminfo': tic.found(), 'Terminfo install location': terminfo_install_location, 'Default TERM': get_option('default-terminfo'), diff --git a/meson_options.txt b/meson_options.txt index c38a8ca8..76121e60 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -22,5 +22,7 @@ option('custom-terminfo-install-location', type: 'string', value: '', option('systemd-units-dir', type: 'string', value: '', description: 'Where to install the systemd service files (absolute path). Default: ${systemduserunitdir}') -option('default-utempter-path', type: 'string', value: '', - description: 'Default path to utempter helper binary. Default: auto-detect') +option('utmp-backend', type: 'combo', value: 'auto', choices: ['none', 'libutempter', 'ulog', 'auto'], + description: 'Which utmp logging backend to use. This affects how (with what arguments) the utmp helper binary (see \utmp-default-helper-path\')is called. Default: auto (linux=libutempter, freebsd=ulog, others=none)') +option('utmp-default-helper-path', type: 'string', value: 'auto', + description: 'Default path to the utmp helper binary. Default: auto-detect') diff --git a/terminal.c b/terminal.c index ddc24cb3..dd4c627c 100644 --- a/terminal.c +++ b/terminal.c @@ -207,25 +207,41 @@ fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data) static bool add_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx) { +#if defined(UTMP_ADD) if (ptmx < 0) return true; - if (conf->utempter_path == NULL) + if (conf->utmp_helper_path == NULL) return true; - char *const argv[] = {conf->utempter_path, "add", getenv("WAYLAND_DISPLAY"), NULL}; + char *const argv[] = {conf->utmp_helper_path, UTMP_ADD, getenv("WAYLAND_DISPLAY"), NULL}; return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL); +#else + return true; +#endif } static bool del_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx) { +#if defined(UTMP_DEL) if (ptmx < 0) return true; - if (conf->utempter_path == NULL) + if (conf->utmp_helper_path == NULL) return true; - char *const argv[] = {conf->utempter_path, "del", getenv("WAYLAND_DISPLAY"), NULL}; + char *del_argument = +#if defined(UTMP_DEL_HAVE_ARGUMENT) + getenv("WAYLAND_DISPLAY") +#else + NULL +#endif + ; + + char *const argv[] = {conf->utmp_helper_path, UTMP_DEL, del_argument, NULL}; return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL); +#else + return true; +#endif } #if PTMX_TIMING diff --git a/tests/test-config.c b/tests/test-config.c index 4736a46b..4b7de298 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -458,7 +458,7 @@ test_section_main(void) test_string(&ctx, &parse_section_main, "shell", &conf.shell); test_string(&ctx, &parse_section_main, "term", &conf.term); test_string(&ctx, &parse_section_main, "app-id", &conf.app_id); - test_string(&ctx, &parse_section_main, "utempter", &conf.utempter_path); + test_string(&ctx, &parse_section_main, "utempter", &conf.utmp_helper_path); test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters); From f4b8e4f4d675ff30f1be43211ab71b35da7c258f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 17 May 2023 21:05:43 +0200 Subject: [PATCH 007/135] test: config: add test for main.utmp-helper option --- tests/test-config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test-config.c b/tests/test-config.c index 4b7de298..da41b2b8 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -459,6 +459,7 @@ test_section_main(void) test_string(&ctx, &parse_section_main, "term", &conf.term); test_string(&ctx, &parse_section_main, "app-id", &conf.app_id); test_string(&ctx, &parse_section_main, "utempter", &conf.utmp_helper_path); + test_string(&ctx, &parse_section_main, "utmp-helper", &conf.utmp_helper_path); test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters); From 134b54dfe0e4d142ac4b6b7fe25e84485ba63387 Mon Sep 17 00:00:00 2001 From: jdevdevdev Date: Tue, 2 May 2023 01:53:01 +1000 Subject: [PATCH 008/135] .desktop: remove StartupWMClass from server, use distinct StartupWMClass for foot and footclient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For this to work, the default app-id of footclient has been changed from ‘foot’ to ‘footclient’. By using distinct StartupWMClasses, the compositor can connect a running foot/footclient instance to the correct .desktop-file. This ensures the correct icon is being used in e.g. docks, and that actions like “open another window” works correctly. Note that the user can override the app-id, either by setting app-id in foot.ini, or with the -a,--app-id command line option. Closes #1355 --- CHANGELOG.md | 5 ++++- config.c | 7 ++++--- config.h | 3 ++- doc/foot.1.scd | 2 +- doc/foot.ini.5.scd | 6 ++++-- doc/footclient.1.scd | 2 +- foot.ini | 2 +- main.c | 2 +- org.codeberg.dnkl.foot-server.desktop | 1 - org.codeberg.dnkl.footclient.desktop | 2 +- 10 files changed, 19 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3b4df58..4c8245b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,7 +62,6 @@ * Meson option `default-utempter-path` renamed to `utmp-default-helper-path`. - ### Deprecated * `[main].utempter` option. @@ -75,8 +74,12 @@ * Crash when scrolling after resizing the window with non-zero scrolling regions. * `XTMODKEYS` state not being reset on a terminal reset. +* In Gnome dock foot always groups under "foot client". Change + instances of footclient and foot to appear as "foot client" and + "foot" respectively. ([#1355][1355]) [1317]: https://codeberg.org/dnkl/foot/issues/1317 +[1355]: https://codeberg.org/dnkl/foot/issues/1355 ### Security diff --git a/config.c b/config.c index c3ab61e2..1d9f200b 100644 --- a/config.c +++ b/config.c @@ -2905,7 +2905,8 @@ config_font_list_clone(struct config_font_list *dst, bool config_load(struct config *conf, const char *conf_path, user_notifications_t *initial_user_notifications, - config_override_t *overrides, bool errors_are_fatal) + config_override_t *overrides, bool errors_are_fatal, + bool as_server) { bool ret = false; enum fcft_capabilities fcft_caps = fcft_capabilities(); @@ -2914,7 +2915,7 @@ config_load(struct config *conf, const char *conf_path, .term = xstrdup(FOOT_DEFAULT_TERM), .shell = get_shell(), .title = xstrdup("foot"), - .app_id = xstrdup("foot"), + .app_id = (as_server ? xstrdup("footclient") : xstrdup("foot")), .word_delimiters = xc32dup(U",│`|:\"'()[]{}<>"), .size = { .type = CONF_SIZE_PX, @@ -3348,7 +3349,7 @@ UNITTEST user_notifications_t nots = tll_init(); config_override_t overrides = tll_init(); - bool ret = config_load(&original, "/dev/null", ¬s, &overrides, false); + bool ret = config_load(&original, "/dev/null", ¬s, &overrides, false, false); xassert(ret); struct config *clone = config_clone(&original); diff --git a/config.h b/config.h index 34517019..ce1ee536 100644 --- a/config.h +++ b/config.h @@ -355,7 +355,8 @@ bool config_override_apply(struct config *conf, config_override_t *overrides, bool config_load( struct config *conf, const char *path, user_notifications_t *initial_user_notifications, - config_override_t *overrides, bool errors_are_fatal); + config_override_t *overrides, bool errors_are_fatal, + bool as_server); void config_free(struct config *conf); struct config *config_clone(const struct config *old); diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 6f63d4c8..60420bef 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -65,7 +65,7 @@ the foot command line *-a*,*--app-id*=_ID_ Value to set the *app-id* property on the Wayland window - to. Default: _foot_. + to. Default: _foot_ (normal mode), or _footclient_ (server mode). *-m*,*--maximized* Start in maximized mode. If both *--maximized* and *--fullscreen* diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 6a820892..32c493be 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -289,7 +289,8 @@ empty string to be set, but it must be quoted: *KEY=""*) *app-id* Value to set the *app-id* property on the Wayland window to. The compositor can use this value to e.g. group multiple windows, or - apply window management rules. Default: _foot_. + apply window management rules. Default: _foot_ (normal mode), or + _footclient_ (server mode). *bold-text-in-bright* Semi-boolean. When enabled, bold text is rendered in a brighter @@ -314,7 +315,8 @@ empty string to be set, but it must be quoted: *KEY=""*) and _body_ (message content). _${app-id}_ is replaced with the value of the command line option - _--app-id_, and defaults to *foot*. + _--app-id_, and defaults to *foot* (normal mode), or + *footclient* (server mode). _${window-title}_ is replaced with the current window title. diff --git a/doc/footclient.1.scd b/doc/footclient.1.scd index 63235134..1464700c 100644 --- a/doc/footclient.1.scd +++ b/doc/footclient.1.scd @@ -31,7 +31,7 @@ terminal has terminated. *-a*,*--app-id*=_ID_ Value to set the *app-id* property on the Wayland window - to. Default: _foot_. + to. Default: _foot_ (normal mode), or _footclient_ (server mode). *-w*,*--window-size-pixels*=_WIDTHxHEIGHT_ Set initial window width and height, in pixels. Default: _700x500_. diff --git a/foot.ini b/foot.ini index 4b2218a4..fcaef4a9 100644 --- a/foot.ini +++ b/foot.ini @@ -4,7 +4,7 @@ # term=foot (or xterm-256color if built with -Dterminfo=disabled) # login-shell=no -# app-id=foot +# app-id=foot # globally set wayland app-id. Default values are "foot" and "footclient" for desktop and server mode # title=foot # locked-title=no diff --git a/main.c b/main.c index 4af200fd..3f9846f3 100644 --- a/main.c +++ b/main.c @@ -487,7 +487,7 @@ main(int argc, char *const *argv) struct config conf = {NULL}; bool conf_successful = config_load( - &conf, conf_path, &user_notifications, &overrides, check_config); + &conf, conf_path, &user_notifications, &overrides, check_config, as_server); tll_free(overrides); if (!conf_successful) { diff --git a/org.codeberg.dnkl.foot-server.desktop b/org.codeberg.dnkl.foot-server.desktop index a40117c7..6e8891c0 100644 --- a/org.codeberg.dnkl.foot-server.desktop +++ b/org.codeberg.dnkl.foot-server.desktop @@ -9,4 +9,3 @@ Keywords=shell;prompt;command;commandline; Name=Foot Server GenericName=Terminal Comment=A wayland native terminal emulator (server) -StartupWMClass=foot diff --git a/org.codeberg.dnkl.footclient.desktop b/org.codeberg.dnkl.footclient.desktop index dc8bc5dc..b65790b4 100644 --- a/org.codeberg.dnkl.footclient.desktop +++ b/org.codeberg.dnkl.footclient.desktop @@ -9,4 +9,4 @@ Keywords=shell;prompt;command;commandline; Name=Foot Client GenericName=Terminal Comment=A wayland native terminal emulator (client) -StartupWMClass=foot +StartupWMClass=footclient From c51050a9bc16b231de964156cd3ffd20ee12569e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 25 May 2023 18:39:32 +0200 Subject: [PATCH 009/135] osc: update font subpixel mode, and window opaque compositor hint, on alpha changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When background alpha is changed at runtime (using OSC-11), we (may) have to update the opaque hint we send to the compositor. We must also update the subpixel mode used when rendering font glyphs. Why? When the window is fully opaque, we use wl_surface_set_opaque_region() on the entire surface, to hint to the compositor that it doesn’t have to blend the window content with whatever is behind the window. Obviously, if alpha is changed from opaque, to transparent (or semi-transparent), that hint must be removed. Sub-pixel mode is harder to explain, but in short, we can’t do subpixel hinting with a (semi-)transparent background. Thus, similar to the opaque hint, subpixel antialiasing must be enabled/disabled when background alpha is changed. --- CHANGELOG.md | 4 +++- osc.c | 9 ++++++++- pgo/pgo.c | 1 + wayland.c | 29 +++++++++++++++++++---------- wayland.h | 1 + 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c8245b2..0c44dd13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,7 +76,9 @@ * `XTMODKEYS` state not being reset on a terminal reset. * In Gnome dock foot always groups under "foot client". Change instances of footclient and foot to appear as "foot client" and - "foot" respectively. ([#1355][1355]) + "foot" respectively. ([#1355][1355]). +* Glitchy rendering when alpha (transparency) is changed between + opaque and non-opaque at runtime (using OSC-11). [1317]: https://codeberg.org/dnkl/foot/issues/1317 [1355]: https://codeberg.org/dnkl/foot/issues/1355 diff --git a/osc.c b/osc.c index 55cfcf84..45d114de 100644 --- a/osc.c +++ b/osc.c @@ -729,8 +729,15 @@ osc_dispatch(struct terminal *term) case 11: term->colors.bg = color; - if (have_alpha) + if (have_alpha) { + const bool changed = term->colors.alpha != alpha; term->colors.alpha = alpha; + + if (changed) { + wayl_win_alpha_changed(term->window); + term_font_subpixel_changed(term); + } + } break; case 17: diff --git a/pgo/pgo.c b/pgo/pgo.c index b41b5850..d9ee5855 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -94,6 +94,7 @@ wayl_win_init(struct terminal *term, const char *token) } void wayl_win_destroy(struct wl_window *win) {} +void wayl_win_alpha_changed(struct wl_window *win) {} bool wayl_win_set_urgent(struct wl_window *win) { return true; } bool diff --git a/wayland.c b/wayland.c index 68a7a4f1..51161cf0 100644 --- a/wayland.c +++ b/wayland.c @@ -1495,16 +1495,7 @@ wayl_win_init(struct terminal *term, const char *token) goto out; } - if (term->colors.alpha == 0xffff) { - struct wl_region *region = wl_compositor_create_region( - term->wl->compositor); - - if (region != NULL) { - wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); - wl_surface_set_opaque_region(win->surface, region); - wl_region_destroy(region); - } - } + wayl_win_alpha_changed(win); wl_surface_add_listener(win->surface, &surface_listener, win); @@ -1798,6 +1789,24 @@ wayl_roundtrip(struct wayland *wayl) wayl_flush(wayl); } +void +wayl_win_alpha_changed(struct wl_window *win) +{ + struct terminal *term = win->term; + + if (term->colors.alpha == 0xffff) { + struct wl_region *region = wl_compositor_create_region( + term->wl->compositor); + + if (region != NULL) { + wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_set_opaque_region(win->surface, region); + wl_region_destroy(region); + } + } else + wl_surface_set_opaque_region(win->surface, NULL); +} + #if defined(HAVE_XDG_ACTIVATION) static void activation_token_for_urgency_done(const char *token, void *data) diff --git a/wayland.h b/wayland.h index 4b6939ab..0d627052 100644 --- a/wayland.h +++ b/wayland.h @@ -434,6 +434,7 @@ void wayl_roundtrip(struct wayland *wayl); struct wl_window *wayl_win_init(struct terminal *term, const char *token); void wayl_win_destroy(struct wl_window *win); +void wayl_win_alpha_changed(struct wl_window *win); bool wayl_win_set_urgent(struct wl_window *win); bool wayl_win_csd_titlebar_visible(const struct wl_window *win); From b4e418f2518051326ea4805f97740c949140bc83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 May 2023 10:20:05 +0200 Subject: [PATCH 010/135] ci: try alpine edge instead of latest --- .builds/alpine-x64.yml | 2 +- .woodpecker.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.builds/alpine-x64.yml b/.builds/alpine-x64.yml index 2e2ec2c4..6ec489fc 100644 --- a/.builds/alpine-x64.yml +++ b/.builds/alpine-x64.yml @@ -1,4 +1,4 @@ -image: alpine/latest +image: alpine/edge packages: - musl-dev - eudev-libs diff --git a/.woodpecker.yml b/.woodpecker.yml index 06631f89..acd30fc7 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -4,7 +4,7 @@ pipeline: branch: - master - releases/* - image: alpine:latest + image: alpine:edge commands: - apk add python3 - apk add py3-pip @@ -16,7 +16,7 @@ pipeline: branch: - master - releases/* - image: alpine:latest + image: alpine:edge commands: - apk add git - mkdir -p subprojects && cd subprojects @@ -30,7 +30,7 @@ pipeline: - master - releases/* group: build - image: alpine:latest + image: alpine:edge commands: - apk update - apk add musl-dev linux-headers meson ninja gcc clang scdoc ncurses @@ -87,7 +87,7 @@ pipeline: - master - releases/* group: build - image: i386/alpine:latest + image: i386/alpine:edge commands: - apk update - apk add musl-dev linux-headers meson ninja gcc clang scdoc ncurses From 1433a81c08d696cad8c5460ca739bc0cd97a9a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 31 May 2023 16:27:48 +0200 Subject: [PATCH 011/135] sixel: apply background alpha when P2=0 or P2=2, and current bg color is the default bg color Closes #1360 --- CHANGELOG.md | 6 ++++++ sixel.c | 22 ++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c44dd13..8a26dad6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,12 @@ name is still recognized, but will log a deprecation warning. * Meson option `default-utempter-path` renamed to `utmp-default-helper-path`. +* Opaque sixels now retain the background opacity (when current + background color is the **default** background color) + ([#1360][1360]). + +[1360]: https://codeberg.org/dnkl/foot/issues/1360 + ### Deprecated diff --git a/sixel.c b/sixel.c index 592f48f8..70ec2ee5 100644 --- a/sixel.c +++ b/sixel.c @@ -73,22 +73,36 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) switch (term->vt.attrs.bg_src) { case COLOR_RGB: - bg = term->vt.attrs.bg; + bg = 0xffu << 24 | term->vt.attrs.bg; break; case COLOR_BASE16: case COLOR_BASE256: - bg = term->colors.table[term->vt.attrs.bg]; + bg = 0xffu << 24 | term->colors.table[term->vt.attrs.bg]; break; case COLOR_DEFAULT: - bg = term->colors.bg; + if (term->colors.alpha == 0xffff) + bg = 0xffu << 24 | term->colors.bg; + else { + /* Alpha needs to be pre-multiplied */ + uint32_t r = (term->colors.bg >> 16) & 0xff; + uint32_t g = (term->colors.bg >> 8) & 0xff; + uint32_t b = (term->colors.bg >> 0) & 0xff; + + uint32_t alpha = term->colors.alpha; + r *= alpha; r /= 0xffff; + g *= alpha; g /= 0xffff; + b *= alpha; b /= 0xffff; + + bg = (alpha >> 8) << 24 | (r & 0xff) << 16 | (g & 0xff) << 8 | (b & 0xff); + } break; } term->sixel.default_bg = term->sixel.transparent_bg ? 0x00000000u - : 0xffu << 24 | bg; + : bg; for (size_t i = 0; i < 1 * 6; i++) term->sixel.image.data[i] = term->sixel.default_bg; From 8859e134efa422d50e53c0bbb0e83d07ddf66091 Mon Sep 17 00:00:00 2001 From: Phillip Susi Date: Tue, 30 May 2023 15:49:01 -0400 Subject: [PATCH 012/135] Fix non UTF-8 locale complaint If the locale isn't UTF-8, foot tries to fall back to C.UTF-8 and prints a warning. The warning was garbled because the name of the original locale is no longer valid after calling setlocale() a second time. Use strdup to stash the original string. Closes #1362 --- main.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/main.c b/main.c index 3f9846f3..f58e170f 100644 --- a/main.c +++ b/main.c @@ -450,6 +450,7 @@ main(int argc, char *const *argv) "C.UTF-8", "en_US.UTF-8", }; + char *saved_locale = xstrdup(locale); /* * Try to force an UTF-8 locale. If we succeed, launch the @@ -461,12 +462,12 @@ main(int argc, char *const *argv) if (setlocale(LC_CTYPE, fallback_locale) != NULL) { LOG_WARN("'%s' is not a UTF-8 locale, using '%s' instead", - locale, fallback_locale); + saved_locale, fallback_locale); user_notification_add_fmt( &user_notifications, USER_NOTIFICATION_WARNING, "'%s' is not a UTF-8 locale, using '%s' instead", - locale, fallback_locale); + saved_locale, fallback_locale); bad_locale = false; break; @@ -476,13 +477,14 @@ main(int argc, char *const *argv) if (bad_locale) { LOG_ERR( "'%s' is not a UTF-8 locale, and failed to find a fallback", - locale); + saved_locale); user_notification_add_fmt( &user_notifications, USER_NOTIFICATION_ERROR, "'%s' is not a UTF-8 locale, and failed to find a fallback", - locale); + saved_locale); } + free(saved_locale); } struct config conf = {NULL}; From 16872ecc4137a5a58f9da8e62990d5587afbec21 Mon Sep 17 00:00:00 2001 From: sewn Date: Wed, 14 Jun 2023 12:26:19 +0000 Subject: [PATCH 013/135] meson: use meson feed feature for scdoc input Removes the need for a shell dependency. --- CHANGELOG.md | 2 ++ doc/meson.build | 5 ++--- meson.build | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a26dad6..692784ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ ### Changed +* Minimum required meson version is now 0.59 ([#1371][1371]). * Kitty keyboard protocol: F3 is now encoded as `CSI 13~` instead of `CSI R`. The kitty keyboard protocol originally allowed F3 to be encoded as `CSI R`, but this was removed from the specification @@ -65,6 +66,7 @@ background color is the **default** background color) ([#1360][1360]). +[1371]: https://codeberg.org/dnkl/foot/pulls/1371 [1360]: https://codeberg.org/dnkl/foot/issues/1360 diff --git a/doc/meson.build b/doc/meson.build index 17f09f39..37972652 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -1,5 +1,3 @@ -sh = find_program('sh', native: true) - scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true) if utmp_backend != 'none' @@ -43,8 +41,9 @@ foreach man_src : [{'name': 'foot', 'section' : 1}, out, output: out, input: preprocessed, - command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())], + command: scdoc_prog.full_path(), capture: true, + feed: true, install: true, install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section))) endforeach diff --git a/meson.build b/meson.build index c5ef1928..29951541 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('foot', 'c', version: '1.14.0', license: 'MIT', - meson_version: '>=0.58.0', + meson_version: '>=0.59.0', default_options: [ 'c_std=c11', 'warning_level=1', From 93b6883896f2bd75af15784c1be4b43dfd62b3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 5 Jun 2023 17:31:35 +0200 Subject: [PATCH 014/135] terminfo: XM: add private mode 1004 This was added to ncurses (to the xterm+sm+1006 fragment) in 2023-05-08. --- foot.info | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foot.info b/foot.info index cf81d721..ef8314e9 100644 --- a/foot.info +++ b/foot.info @@ -41,7 +41,7 @@ Se=\E[ q, Ss=\E[%p1%d q, Sync=\E[?2026%?%p1%{1}%-%tl%eh, - XM=\E[?1006;1000%?%p1%{1}%=%th%el%;, + XM=\E[?1006;1004;1000%?%p1%{1}%=%th%el%;, XR=\E[>0q, acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, bel=^G, From b91bde8a651bdf25a510a9b4a6c1f1f5fdf23d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 5 Jun 2023 17:32:28 +0200 Subject: [PATCH 015/135] terminfo: add TS capability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a new ‘extended’ capability. ‘TS’ has been around for a while, but was originally not part of foot’s terminfo. Not sure when it was added to ncurses’ foot terminfo. In any case, ncurses has this to say about TS: These building-blocks allow access to the X titlebar and icon name as a status line. There are a few problems in using them in entries: a) tsl should have a parameter to denote the column on which to transfer to the status line. ... But that issue regarding the parameter for tsl means that applications may not rely on it. The SVr4 documentation says tsl will "move to status line, column #1". At the point in time when ESR added DJM's "pseudo-color" entry with the split-up escape sequence for tsl/fsl, there were 65 entries using tsl: 32 used a parameter, matching the documentation (including x10term). 21 used a parameterless control, exiting from the status line on ^M. 6 used parameterless controls for tsl and fsl 6 used a split-up escape sequence, e.g., the same approach. The extension "TS" is preferable, because it does not accept a parameter. However, if you are using a non-extended terminfo, "TS" is not visible. --- foot.info | 1 + 1 file changed, 1 insertion(+) diff --git a/foot.info b/foot.info index ef8314e9..4f95bf7b 100644 --- a/foot.info +++ b/foot.info @@ -41,6 +41,7 @@ Se=\E[ q, Ss=\E[%p1%d q, Sync=\E[?2026%?%p1%{1}%-%tl%eh, + TS=\E]2;, XM=\E[?1006;1004;1000%?%p1%{1}%=%th%el%;, XR=\E[>0q, acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, From 690d78edfa3f468b5c3be56f37fd622e4161dd9b Mon Sep 17 00:00:00 2001 From: Dan Bungert Date: Sat, 10 Jun 2023 20:40:01 -0600 Subject: [PATCH 016/135] test: config: add test for url.protocols option --- tests/test-config.c | 46 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/tests/test-config.c b/tests/test-config.c index da41b2b8..b14f28b1 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -106,6 +106,50 @@ test_c32string(struct context *ctx, bool (*parse_fun)(struct context *ctx), } } +static void +test_protocols(struct context *ctx, bool (*parse_fun)(struct context *ctx), + const char *key, char32_t **const *ptr) +{ + ctx->key = key; + + static const struct { + const char *option_string; + int count; + const char32_t *value[2]; + bool invalid; + } input[] = { + {""}, + {"http", 1, {U"http://"}}, + {" http", 1, {U"http://"}}, + {"http, https", 2, {U"http://", U"https://"}}, + {"longprotocolislong", 1, {U"longprotocolislong://"}}, + }; + + for (size_t i = 0; i < ALEN(input); i++) { + ctx->value = input[i].option_string; + + if (input[i].invalid) { + if (parse_fun(ctx)) { + BUG("[%s].%s=%s: did not fail to parse as expected", + ctx->section, ctx->key, &ctx->value[0]); + } + } else { + if (!parse_fun(ctx)) { + BUG("[%s].%s=%s: failed to parse", + ctx->section, ctx->key, &ctx->value[0]); + } + for (int c = 0; c < input[i].count; c++) { + if (c32cmp((*ptr)[c], input[i].value[c]) != 0) { + BUG("[%s].%s=%s: set value[%d] (%ls) not the expected one (%ls)", + ctx->section, ctx->key, &ctx->value[c], c, + (const wchar_t *)(*ptr)[c], + (const wchar_t *)input[i].value[c]); + } + } + } + } +} + static void test_boolean(struct context *ctx, bool (*parse_fun)(struct context *ctx), const char *key, const bool *ptr) @@ -578,8 +622,8 @@ test_section_url(void) (int []){OSC8_UNDERLINE_URL_MODE, OSC8_UNDERLINE_ALWAYS}, (int *)&conf.url.osc8_underline); test_c32string(&ctx, &parse_section_url, "label-letters", &conf.url.label_letters); + test_protocols(&ctx, &parse_section_url, "protocols", &conf.url.protocols); - /* TODO: protocols (list of wchars) */ /* TODO: uri-characters (wchar string, but sorted) */ config_free(&conf); From d88bea5e22330c36c241ae0d441279799ac5897f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 16 Jun 2023 16:26:13 +0200 Subject: [PATCH 017/135] vt: split up action_param() to three separate functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re already switching on the next VT input byte in the state machine; no need to if...else if in action_param() too. That is, split up action_param() into three: * action_param_new() * action_param_new_subparam() * action_param() This makes the code cleaner, and hopefully slightly faster. Next, to improve performance further, only check for (sub)parameter overflow in action_param_new() and action_param_subparam(). Add pointers to the VT struct that points to the currently active parameter and sub-parameter. When the number of parameters (or sub-parameters) overflow, warn, and then point the parameter pointer to a "dummy" value in the VT struct. This way, we don’t have to check anything in action_param(). --- terminal.h | 8 +++- vt.c | 135 +++++++++++++++++++++++++++-------------------------- 2 files changed, 75 insertions(+), 68 deletions(-) diff --git a/terminal.h b/terminal.h index d2762a5a..abedf44d 100644 --- a/terminal.h +++ b/terminal.h @@ -180,8 +180,10 @@ struct grid { }; struct vt_subparams { - unsigned value[16]; uint8_t idx; + unsigned *cur; + unsigned value[16]; + unsigned dummy; }; struct vt_param { @@ -197,8 +199,10 @@ struct vt { #endif char32_t utf8; struct { - struct vt_param v[16]; uint8_t idx; + struct vt_param *cur; + struct vt_param v[16]; + struct vt_param dummy; } params; uint32_t private; /* LSB=priv0, MSB=priv3 */ diff --git a/vt.c b/vt.c index 91f00e6f..2ee2dbaf 100644 --- a/vt.c +++ b/vt.c @@ -294,74 +294,31 @@ action_print(struct terminal *term, uint8_t c) } static void -action_param(struct terminal *term, uint8_t c) +action_param_lazy_init(struct terminal *term) { if (term->vt.params.idx == 0) { struct vt_param *param = &term->vt.params.v[0]; + + term->vt.params.cur = param; param->value = 0; param->sub.idx = 0; + param->sub.cur = NULL; term->vt.params.idx = 1; } +} - xassert(term->vt.params.idx > 0); +static void +action_param_new(struct terminal *term, uint8_t c) +{ + xassert(c == ';'); + action_param_lazy_init(term); const size_t max_params = sizeof(term->vt.params.v) / sizeof(term->vt.params.v[0]); - const size_t max_sub_params - = sizeof(term->vt.params.v[0].sub.value) / sizeof(term->vt.params.v[0].sub.value[0]); - /* New parameter */ - if (c == ';') { - if (unlikely(term->vt.params.idx >= max_params)) - goto excess_params; + struct vt_param *param; - struct vt_param *param = &term->vt.params.v[term->vt.params.idx++]; - param->value = 0; - param->sub.idx = 0; - } - - /* New sub-parameter */ - else if (c == ':') { - if (unlikely(term->vt.params.idx - 1 >= max_params)) - goto excess_params; - - struct vt_param *param = &term->vt.params.v[term->vt.params.idx - 1]; - if (unlikely(param->sub.idx >= max_sub_params)) - goto excess_sub_params; - - param->sub.value[param->sub.idx++] = 0; - } - - /* New digit for current parameter/sub-parameter */ - else { - if (unlikely(term->vt.params.idx - 1 >= max_params)) - goto excess_params; - - struct vt_param *param = &term->vt.params.v[term->vt.params.idx - 1]; - unsigned *value; - - if (param->sub.idx > 0) { - if (unlikely(param->sub.idx - 1 >= max_sub_params)) - goto excess_sub_params; - value = ¶m->sub.value[param->sub.idx - 1]; - } else - value = ¶m->value; - - *value *= 10; - *value += c - '0'; - } - -#if defined(_DEBUG) - /* The rest of the code assumes 'idx' *never* points outside the array */ - xassert(term->vt.params.idx <= max_params); - for (size_t i = 0; i < term->vt.params.idx; i++) - xassert(term->vt.params.v[i].sub.idx <= max_sub_params); -#endif - - return; - -excess_params: - { + if (unlikely(term->vt.params.idx >= max_params)) { static bool have_warned = false; if (!have_warned) { have_warned = true; @@ -370,11 +327,29 @@ excess_params: "(will not warn again)", sizeof(term->vt.params.v) / sizeof(term->vt.params.v[0])); } - } - return; + param = &term->vt.params.dummy; + } else + param = &term->vt.params.v[term->vt.params.idx++]; -excess_sub_params: - { + term->vt.params.cur = param; + param->value = 0; + param->sub.idx = 0; + param->sub.cur = NULL; +} + +static void +action_param_new_subparam(struct terminal *term, uint8_t c) +{ + xassert(c == ':'); + action_param_lazy_init(term); + + const size_t max_sub_params + = sizeof(term->vt.params.v[0].sub.value) / sizeof(term->vt.params.v[0].sub.value[0]); + + struct vt_param *param = term->vt.params.cur; + unsigned *sub_param_value; + + if (unlikely(param->sub.idx >= max_sub_params)) { static bool have_warned = false; if (!have_warned) { have_warned = true; @@ -383,8 +358,33 @@ excess_sub_params: "(will not warn again)", sizeof(term->vt.params.v[0].sub.value) / sizeof(term->vt.params.v[0].sub.value[0])); } - } - return; + + sub_param_value = ¶m->sub.dummy; + } else + sub_param_value = ¶m->sub.value[param->sub.idx++]; + + param->sub.cur = sub_param_value; + *sub_param_value = 0; +} + +static void +action_param(struct terminal *term, uint8_t c) +{ + action_param_lazy_init(term); + xassert(term->vt.params.cur != NULL); + + struct vt_param *param = term->vt.params.cur; + unsigned *value; + + if (unlikely(param->sub.cur != NULL)) + value = param->sub.cur; + else + value = ¶m->value; + + unsigned v = *value; + v *= 10; + v += c - '0'; + *value = v; } static void @@ -1024,7 +1024,9 @@ state_csi_entry_switch(struct terminal *term, uint8_t data) case 0x20 ... 0x2f: action_collect(term, data); return STATE_CSI_INTERMEDIATE; case 0x30 ... 0x39: action_param(term, data); return STATE_CSI_PARAM; - case 0x3a ... 0x3b: action_param(term, data); return STATE_CSI_PARAM; + case 0x3a: action_param_new_subparam(term, data); return STATE_CSI_PARAM; + case 0x3b: action_param_new(term, data); return STATE_CSI_PARAM; + case 0x3c ... 0x3f: action_collect(term, data); return STATE_CSI_PARAM; case 0x40 ... 0x7e: action_csi_dispatch(term, data); return STATE_GROUND; case 0x7f: action_ignore(term); return STATE_CSI_ENTRY; @@ -1044,8 +1046,9 @@ state_csi_param_switch(struct terminal *term, uint8_t data) case 0x20 ... 0x2f: action_collect(term, data); return STATE_CSI_INTERMEDIATE; - case 0x30 ... 0x39: - case 0x3a ... 0x3b: action_param(term, data); return STATE_CSI_PARAM; + case 0x30 ... 0x39: action_param(term, data); return STATE_CSI_PARAM; + case 0x3a: action_param_new_subparam(term, data); return STATE_CSI_PARAM; + case 0x3b: action_param_new(term, data); return STATE_CSI_PARAM; case 0x3c ... 0x3f: return STATE_CSI_IGNORE; case 0x40 ... 0x7e: action_csi_dispatch(term, data); return STATE_GROUND; @@ -1126,7 +1129,7 @@ state_dcs_entry_switch(struct terminal *term, uint8_t data) case 0x20 ... 0x2f: action_collect(term, data); return STATE_DCS_INTERMEDIATE; case 0x30 ... 0x39: action_param(term, data); return STATE_DCS_PARAM; case 0x3a: return STATE_DCS_IGNORE; - case 0x3b: action_param(term, data); return STATE_DCS_PARAM; + case 0x3b: action_param_new(term, data); return STATE_DCS_PARAM; case 0x3c ... 0x3f: action_collect(term, data); return STATE_DCS_PARAM; case 0x40 ... 0x7e: action_hook(term, data); return STATE_DCS_PASSTHROUGH; case 0x7f: action_ignore(term); return STATE_DCS_ENTRY; @@ -1147,7 +1150,7 @@ state_dcs_param_switch(struct terminal *term, uint8_t data) case 0x20 ... 0x2f: action_collect(term, data); return STATE_DCS_INTERMEDIATE; case 0x30 ... 0x39: action_param(term, data); return STATE_DCS_PARAM; case 0x3a: return STATE_DCS_IGNORE; - case 0x3b: action_param(term, data); return STATE_DCS_PARAM; + case 0x3b: action_param_new(term, data); return STATE_DCS_PARAM; case 0x3c ... 0x3f: return STATE_DCS_IGNORE; case 0x40 ... 0x7e: action_hook(term, data); return STATE_DCS_PASSTHROUGH; case 0x7f: action_ignore(term); return STATE_DCS_PARAM; From 24f12c7b5e753a5152eac44da9024954d43250e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 16 Jun 2023 16:33:15 +0200 Subject: [PATCH 018/135] term: add term_cursor_col() Set cursor column, absolute. term_cursor_to() needs to reload the current row pointer, and is thus not very effective when we only need to modify the column. --- terminal.c | 9 +++++++++ terminal.h | 1 + 2 files changed, 10 insertions(+) diff --git a/terminal.c b/terminal.c index dd4c627c..3beffcf3 100644 --- a/terminal.c +++ b/terminal.c @@ -2557,6 +2557,15 @@ term_cursor_home(struct terminal *term) term_cursor_to(term, term_row_rel_to_abs(term, 0), 0); } +void +term_cursor_col(struct terminal *term, int col) +{ + xassert(col < term->cols); + + term->grid->cursor.lcf = false; + term->grid->cursor.point.col = col; +} + void term_cursor_left(struct terminal *term, int count) { diff --git a/terminal.h b/terminal.h index abedf44d..ec0db44b 100644 --- a/terminal.h +++ b/terminal.h @@ -743,6 +743,7 @@ void term_erase_scrollback(struct terminal *term); int term_row_rel_to_abs(const struct terminal *term, int row); void term_cursor_home(struct terminal *term); void term_cursor_to(struct terminal *term, int row, int col); +void term_cursor_col(struct terminal *term, int col); void term_cursor_left(struct terminal *term, int count); void term_cursor_right(struct terminal *term, int count); void term_cursor_up(struct terminal *term, int count); From 2c0c4ce821b90dea9ebbcdab98c10865de1872fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 16 Jun 2023 16:34:17 +0200 Subject: [PATCH 019/135] csi: CHA+HPA (cursor horizontal absolute): use term_cursor_col() --- csi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csi.c b/csi.c index 7b318d0a..ef1a28f2 100644 --- a/csi.c +++ b/csi.c @@ -815,7 +815,7 @@ csi_dispatch(struct terminal *term, uint8_t final) case 'G': { /* Cursor horizontal absolute */ int col = min(vt_param_get(term, 0, 1), term->cols) - 1; - term_cursor_to(term, term->grid->cursor.point.row, col); + term_cursor_col(term, col); break; } From 67b3663f39146d0f33d93d0f2ac3127e826a9957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Beaupr=C3=A9?= Date: Wed, 14 Jun 2023 14:52:58 -0400 Subject: [PATCH 020/135] add srcery theme Based on https://srcery.sh/ --- themes/srcery | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 themes/srcery diff --git a/themes/srcery b/themes/srcery new file mode 100644 index 00000000..54966707 --- /dev/null +++ b/themes/srcery @@ -0,0 +1,26 @@ +# srcery + +[colors] +background= 1c1b19 +foreground= fce8c3 +regular0= 1c1b19 +regular1= ef2f27 +regular2= 519f50 +regular3= fbb829 +regular4= 2c78bf +regular5= e02c6d +regular6= 0aaeb3 +regular7= baa67f +bright0= 918175 +bright1= f75341 +bright2= 98bc37 +bright3= fed06e +bright4= 68a8e4 +bright5= ff5c8f +bright6= 2be4d0 +bright7= fce8c3 + +## Enable if prefer solarized colors instead of inverterd fg/bg for +## highlighting (mouse selection) +# selection-foreground=93a1a1 +# selection-background=073642 From 3a59cbbaa3906da6f9ab73ad949bc598318be7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 20 Jun 2023 15:59:16 +0200 Subject: [PATCH 021/135] render: resize: fix crash when reflowing the alt screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When doing an interactive resize, and `resize-delay-ms` > 0 (the default), we would crash if the original screen size (i.e. the size before the interactive resize started) was larger than the last window size. For example, if we interactively go from 85 rows to 75, and then non-interactively went from 75 to 80, we’d crash. The resizes had to be made in a single go. One way to trigger this was to start an interactive resize on a floating window, and then *while resizing* toggle the window’s floating mode. Closes #1377 --- CHANGELOG.md | 3 +++ render.c | 10 +++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 692784ee..2eab13eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,9 +87,12 @@ "foot" respectively. ([#1355][1355]). * Glitchy rendering when alpha (transparency) is changed between opaque and non-opaque at runtime (using OSC-11). +* Regression: crash when resizing the window when `resize-delay-ms > + 0` ([#1377][1377]). [1317]: https://codeberg.org/dnkl/foot/issues/1317 [1355]: https://codeberg.org/dnkl/foot/issues/1355 +[1377]: https://codeberg.org/dnkl/foot/issues/1377 ### Security diff --git a/render.c b/render.c index 340e0378..1858467d 100644 --- a/render.c +++ b/render.c @@ -4030,7 +4030,9 @@ maybe_resize(struct terminal *term, int width, int height, bool force) term->interactive_resizing.old_hide_cursor = term->hide_cursor; term->interactive_resizing.grid = xmalloc(sizeof(*term->interactive_resizing.grid)); *term->interactive_resizing.grid = term->normal; - term->interactive_resizing.selection_coords = term->selection.coords; + + if (term->grid == &term->normal) + term->interactive_resizing.selection_coords = term->selection.coords; } else { /* We’ll replace the current temporary grid, with a new * one (again based on the original grid) */ @@ -4118,6 +4120,8 @@ maybe_resize(struct terminal *term, int width, int height, bool force) } else { /* Full text reflow */ + int old_normal_rows = old_rows; + if (term->interactive_resizing.grid != NULL) { /* Throw away the current, truncated, “normal” grid, and * use the original grid instead (from before the resize @@ -4129,7 +4133,7 @@ maybe_resize(struct terminal *term, int width, int height, bool force) term->hide_cursor = term->interactive_resizing.old_hide_cursor; term->selection.coords = term->interactive_resizing.selection_coords; - old_rows = term->interactive_resizing.old_screen_rows; + old_normal_rows = term->interactive_resizing.old_screen_rows; term->interactive_resizing.grid = NULL; term->interactive_resizing.old_screen_rows = 0; @@ -4145,7 +4149,7 @@ maybe_resize(struct terminal *term, int width, int height, bool force) }; grid_resize_and_reflow( - &term->normal, new_normal_grid_rows, new_cols, old_rows, new_rows, + &term->normal, new_normal_grid_rows, new_cols, old_normal_rows, new_rows, term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0, tracking_points); } From 70ffc2632f508d0298ca53160ce69300861aef5b Mon Sep 17 00:00:00 2001 From: wout Date: Fri, 23 Jun 2023 18:10:19 +0000 Subject: [PATCH 022/135] Fixed a type for the pixel fontsize change xp -> px --- doc/foot.ini.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 32c493be..f9174fc6 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -87,7 +87,7 @@ empty string to be set, but it must be quoted: *KEY=""*) Examples: ``` font-size-adjustment=0.5 # Adjust by 0.5 points - font-size-adjustment=10xp # Adjust by 10 pixels + font-size-adjustment=10px # Adjust by 10 pixels font-size-adjustment=7.5% # Adjust by 7.5 percent ``` From 8a3620bafaa4119b9f6d3f74189c2dac78614d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 23 Jun 2023 20:20:01 +0200 Subject: [PATCH 023/135] term: scroll: only record scroll damage when viewport is at the bottom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don’t need to record scroll damage if the viewport isn’t at the bottom, since in this case, the renderer ignores the scroll damage anyway. This fixes a performance corner case, when the viewport is at the top of the scrollback history. When application scrolls the terminal contents, and the scrollback history is full, and the viewport is at top of the history, then the viewport needs to be moved (the scrollback history is a circular buffer, and thus the top of the history “moves” when we’re scrolling in new contents). Moving the viewport typically results in another type of scroll damage (DAMAGE_SCROLL_IN_VIEW, instead of the “normal” DAMAGE_SCROLL). Thus, each application triggered scroll, will result in two scroll damage records: one DAMAGE_SCROLL, and one DAMAGE_SCROLL_IN_VIEW. These two are incompatible, meaning they can’t be merged. What’s worse, it also means the DAMAGE_SCROLL records from two application triggered scrolls cannot be merged (since there’s a DAMAGE_SCROLL_IN_VIEW in between). As a result, the renderer will not see one, or “a few” scroll damage events, but a *ton*. _Each_ one typically a single line, or so. And each one resulting in lots of traffic on the wayland socket, as we create and destroy new buffer pools, when doing “shm scrolling”. This eventually leads to the socket not being able to keep up, and the socket is closed on us, forcing us to exit. The fix is really simple: don’t record “normal” scroll damage when scrolling, _unless_ the viewport is at the bottom (and thus “follows” the application output). As soon as the user scrolls up in the history, we’ll stop emitting normal scroll damage records. This is just fine, since, as mentioned above, the renderer ignores them when the viewport isn’t at the bottom. What if the viewport is moved back down again, before the next frame has been rendered? Wont there be “missing” scroll damage records? No, because moving the viewport results in scroll damage records by itself. Closes #1380 --- CHANGELOG.md | 3 +++ terminal.c | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eab13eb..658e0c1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,10 +89,13 @@ opaque and non-opaque at runtime (using OSC-11). * Regression: crash when resizing the window when `resize-delay-ms > 0` ([#1377][1377]). +* Crash when scrolling up while running something that generates a lot + of output (for example, `yes`) ([#1380][1380]). [1317]: https://codeberg.org/dnkl/foot/issues/1317 [1355]: https://codeberg.org/dnkl/foot/issues/1355 [1377]: https://codeberg.org/dnkl/foot/issues/1377 +[1380]: https://codeberg.org/dnkl/foot/issues/1380 ### Security diff --git a/terminal.c b/terminal.c index 3beffcf3..815dc2d4 100644 --- a/terminal.c +++ b/terminal.c @@ -2714,6 +2714,7 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows term->grid->offset &= term->grid->num_rows - 1; if (likely(view_follows)) { + term_damage_scroll(term, DAMAGE_SCROLL, region, rows); selection_view_down(term, term->grid->offset); term->grid->view = term->grid->offset; } else if (unlikely(rows > view_sb_start_distance)) { @@ -2737,13 +2738,12 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows erase_line(term, row); } + term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); + #if defined(_DEBUG) for (int r = 0; r < term->rows; r++) xassert(grid_row(term->grid, r) != NULL); #endif - - term_damage_scroll(term, DAMAGE_SCROLL, region, rows); - term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); } void @@ -2800,6 +2800,7 @@ term_scroll_reverse_partial(struct terminal *term, xassert(term->grid->offset < term->grid->num_rows); if (view_follows) { + term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows); selection_view_up(term, term->grid->offset); term->grid->view = term->grid->offset; } @@ -2818,7 +2819,6 @@ term_scroll_reverse_partial(struct terminal *term, erase_line(term, row); } - term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows); term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); #if defined(_DEBUG) From 66d9b8da604e4fd3aa6ee96da3609ff6b9896f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 16 Jun 2023 16:20:37 +0200 Subject: [PATCH 024/135] sixel: fix cursor positioning logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adjusts the logic that positions the text cursor after emitting a sixel, when sixel scrolling mode is *enabled*. We’ve always mimicked XTerm’s behavior. However, XTerm recently changed its behavior, to better match that of an VT382. Now, the cursor is placed *on* the last row of the sixel, instead of on a new row after the sixel. This allows applications to print sixels to the bottom row of the terminal, without causing the content to scroll. Finally, there was a bug in the horizontal positioning of the cursor; it was placed on the *first* column of the row, instead of on the first column of the sixel. --- CHANGELOG.md | 9 +++++++++ sixel.c | 37 +++++++++++++++++-------------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 658e0c1d..a2fc9ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,15 @@ * Opaque sixels now retain the background opacity (when current background color is the **default** background color) ([#1360][1360]). +* Text cursor’s vertical position after emitting a sixel, when sixel + scrolling is **enabled** (the default) has been updated to match + XTerm’s, and the VT382’s behavior: the cursor is positioned **on** + the last sixel row, rather than _after_ it. This allows printing + sixels on the last row without scrolling up, but also means + applications may have to explicitly emit a newline to ensure the + sixel is visible. For example, `cat`:ing a sixel in the shell will + typically result in the last row not being visible, unless a newline + is explicitly added. [1371]: https://codeberg.org/dnkl/foot/pulls/1371 [1360]: https://codeberg.org/dnkl/foot/issues/1360 diff --git a/sixel.c b/sixel.c index 70ec2ee5..1af214a9 100644 --- a/sixel.c +++ b/sixel.c @@ -1043,6 +1043,23 @@ sixel_unhook(struct terminal *term) pixel_rows_left -= height; rows_avail -= image.rows; + if (do_scroll) { + /* Yes, truncate last row. This matches XTerm’s, and VT382’s behavior */ + const int linefeed_count = image.height / term->cell_height; + for (size_t i = 0; i < linefeed_count; i++) + term_linefeed(term); + + /* Position text cursor if this is the last image chunk */ + if (rows_avail == 0) { + term_cursor_to( + term, + term->grid->cursor.point.row, + (term->sixel.cursor_right_of_graphics + ? min(image.pos.col + image.cols, term->cols - 1) + : image.pos.col)); + } + } + /* Dirty touched cells, and scroll terminal content if necessary */ for (size_t i = 0; i < image.rows; i++) { struct row *row = term->grid->rows[cur_row + i]; @@ -1055,26 +1072,6 @@ sixel_unhook(struct terminal *term) row->cells[col].attrs.clean = 0; } - if (do_scroll) { - /* - * Linefeed, *unless* we're on the very last row of - * the final image (not just this chunk) and private - * mode 8452 (leave cursor at the right of graphics) - * is enabled. - */ - if (term->sixel.cursor_right_of_graphics && - rows_avail == 0 && - i >= image.rows - 1) - { - term_cursor_to( - term, - term->grid->cursor.point.row, - min(image.pos.col + image.cols, term->cols - 1)); - } else { - term_linefeed(term); - term_carriage_return(term); - } - } } _sixel_overwrite_by_rectangle( From d6d143e2a6916178d1ea74551617cf8a5cddc39e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 19 Jun 2023 19:06:38 +0200 Subject: [PATCH 025/135] sixel: respect sixel aspect ratio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That is, parse P1 when initializing a new sixel, and don’t ignore pad/pad in the raster attributes command. The default aspect ratio is 2:1, but most sixels will override it in the raster attributes command (to 1:1). --- CHANGELOG.md | 3 +++ sixel.c | 54 ++++++++++++++++++++++++++++++++++++++-------------- terminal.h | 9 +++++++++ 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2fc9ec8..663d3983 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ * Meson option `utmp-backend=none|libutempter|ulog|auto`. The default is `auto`, which will select `libutempter` on Linux, `ulog` on FreeBSD, and `none` for all others. +* Sixel aspect ratio. ### Changed @@ -74,6 +75,8 @@ sixel is visible. For example, `cat`:ing a sixel in the shell will typically result in the last row not being visible, unless a newline is explicitly added. +* Default sixel aspect ratio is now 2:1 instead of 1:1. + [1371]: https://codeberg.org/dnkl/foot/pulls/1371 [1360]: https://codeberg.org/dnkl/foot/issues/1360 diff --git a/sixel.c b/sixel.c index 1af214a9..ed165aed 100644 --- a/sixel.c +++ b/sixel.c @@ -38,18 +38,33 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) xassert(term->sixel.image.data == NULL); xassert(term->sixel.palette_size <= SIXEL_MAX_COLORS); + /* Default aspect ratio is 2:1 */ + const int pad = 1; + const int pan = + (p1 == 2) ? 5 : + (p1 == 3 || p1 == 4) ? 3 : + (p1 == 7 || p1 == 8 || p1 == 9) ? 1 : 2; + + LOG_DBG("initializing sixel with " + "p1=%d (pan=%d, pad=%d, AR=%d:%d), " + "p2=%d (transparent=%d), " + "p3=%d (ignored)", + p1, pan, pad, pan, pad, p2, p2 == 1, p3); + term->sixel.state = SIXEL_DECSIXEL; term->sixel.pos = (struct coord){0, 0}; term->sixel.max_non_empty_row_no = -1; term->sixel.row_byte_ofs = 0; term->sixel.color_idx = 0; + term->sixel.pan = pan; + term->sixel.pad = pad; term->sixel.param = 0; term->sixel.param_idx = 0; memset(term->sixel.params, 0, sizeof(term->sixel.params)); term->sixel.transparent_bg = p2 == 1; - term->sixel.image.data = xmalloc(1 * 6 * sizeof(term->sixel.image.data[0])); - term->sixel.image.width = 1; - term->sixel.image.height = 6; + term->sixel.image.data = NULL; + term->sixel.image.width = 0; + term->sixel.image.height = 6 * pan; /* TODO: default palette */ @@ -104,9 +119,6 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) ? 0x00000000u : bg; - for (size_t i = 0; i < 1 * 6; i++) - term->sixel.image.data[i] = term->sixel.default_bg; - count = 0; } @@ -1045,7 +1057,7 @@ sixel_unhook(struct terminal *term) if (do_scroll) { /* Yes, truncate last row. This matches XTerm’s, and VT382’s behavior */ - const int linefeed_count = image.height / term->cell_height; + const int linefeed_count = (image.height - 6 * term->sixel.pan + 1) / term->cell_height; for (size_t i = 0; i < linefeed_count; i++) term_linefeed(term); @@ -1131,7 +1143,8 @@ resize_horizontally(struct terminal *term, int new_width) const int old_width = term->sixel.image.width; const int height = term->sixel.image.height; - int alloc_height = (height + 6 - 1) / 6 * 6; + const int sixel_row_height = 6 * term->sixel.pan; + int alloc_height = (height + sixel_row_height - 1) / sixel_row_height * sixel_row_height; xassert(new_width > 0); xassert(alloc_height > 0); @@ -1176,9 +1189,14 @@ resize_vertically(struct terminal *term, int new_height) int alloc_height = (new_height + 6 - 1) / 6 * 6; - xassert(width > 0); xassert(new_height > 0); + if (unlikely(width == 0)) { + xassert(term->sixel.image.data == NULL); + term->sixel.image.height = new_height; + return true; + } + uint32_t *new_data = realloc( old_data, width * alloc_height * sizeof(uint32_t)); @@ -1283,7 +1301,7 @@ sixel_add(struct terminal *term, int col, int width, uint32_t color, uint8_t six int max_non_empty_row = -1; int row = term->sixel.pos.row; - for (int i = 0; i < 6; i++, sixel >>= 1, data += width) { + for (int i = 0; i < 6 * term->sixel.pan; i++, sixel >>= 1, data += width) { if (sixel & 1) { *data = color; max_non_empty_row = row + i; @@ -1303,6 +1321,8 @@ sixel_add_many(struct terminal *term, uint8_t c, unsigned count) int col = term->sixel.pos.col; int width = term->sixel.image.width; + count *= term->sixel.pad; + if (unlikely(col + count - 1 >= width)) { resize_horizontally(term, col + count); width = term->sixel.image.width; @@ -1352,13 +1372,13 @@ decsixel(struct terminal *term, uint8_t c) break; case '-': - term->sixel.pos.row += 6; + term->sixel.pos.row += 6 * term->sixel.pan; term->sixel.pos.col = 0; - term->sixel.row_byte_ofs += term->sixel.image.width * 6; + term->sixel.row_byte_ofs += term->sixel.image.width * 6 * term->sixel.pan; if (term->sixel.pos.row >= term->sixel.image.height) { - if (!resize_vertically(term, term->sixel.pos.row + 6)) - term->sixel.pos.col = term->sixel.max_width + 1; + if (!resize_vertically(term, term->sixel.pos.row + 6 * term->sixel.pan)) + term->sixel.pos.col = term->sixel.max_width + 1 * term->sixel.pad; } break; @@ -1415,6 +1435,12 @@ decgra(struct terminal *term, uint8_t c) pan = pan > 0 ? pan : 1; pad = pad > 0 ? pad : 1; + pv *= pan; + ph *= pad; + + term->sixel.pan = pan; + term->sixel.pad = pad; + LOG_DBG("pan=%u, pad=%u (aspect ratio = %u), size=%ux%u", pan, pad, pan / pad, ph, pv); diff --git a/terminal.h b/terminal.h index ec0db44b..0df9c5b6 100644 --- a/terminal.h +++ b/terminal.h @@ -634,6 +634,15 @@ struct terminal { int height; /* Image height, in pixels */ } image; + /* + * Pan is the vertical shape of a pixel + * Pad is the horizontal shape of a pixel + * + * pan/pad is the sixel’s aspect ratio + */ + int pan; + int pad; + bool scrolling:1; /* Private mode 80 */ bool use_private_palette:1; /* Private mode 1070 */ bool cursor_right_of_graphics:1; /* Private mode 8452 */ From 774570ec41a3c585f40b03467eaada786a2e97d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 19 Jun 2023 19:09:58 +0200 Subject: [PATCH 026/135] sixel: stop cropping images to the last non-transparent row --- CHANGELOG.md | 1 + sixel.c | 21 --------------------- terminal.h | 1 - 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 663d3983..943dd411 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ typically result in the last row not being visible, unless a newline is explicitly added. * Default sixel aspect ratio is now 2:1 instead of 1:1. +* Sixel images are no longer cropped to the last non-transparent row. [1371]: https://codeberg.org/dnkl/foot/pulls/1371 diff --git a/sixel.c b/sixel.c index ed165aed..71c4f2c0 100644 --- a/sixel.c +++ b/sixel.c @@ -53,7 +53,6 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) term->sixel.state = SIXEL_DECSIXEL; term->sixel.pos = (struct coord){0, 0}; - term->sixel.max_non_empty_row_no = -1; term->sixel.row_byte_ofs = 0; term->sixel.color_idx = 0; term->sixel.pan = pan; @@ -952,13 +951,6 @@ sixel_reflow(struct terminal *term) void sixel_unhook(struct terminal *term) { - if (term->sixel.image.height > term->sixel.max_non_empty_row_no + 1) { - LOG_DBG( - "last row only partially filled, reducing image height: %d -> %d", - term->sixel.image.height, term->sixel.max_non_empty_row_no + 1); - term->sixel.image.height = term->sixel.max_non_empty_row_no + 1; - } - int pixel_row_idx = 0; int pixel_rows_left = term->sixel.image.height; const int stride = term->sixel.image.width * sizeof(uint32_t); @@ -1298,21 +1290,13 @@ sixel_add(struct terminal *term, int col, int width, uint32_t color, uint8_t six size_t ofs = term->sixel.row_byte_ofs + col; uint32_t *data = &term->sixel.image.data[ofs]; - int max_non_empty_row = -1; - int row = term->sixel.pos.row; - for (int i = 0; i < 6 * term->sixel.pan; i++, sixel >>= 1, data += width) { if (sixel & 1) { *data = color; - max_non_empty_row = row + i; } } xassert(sixel == 0); - - term->sixel.max_non_empty_row_no = max( - term->sixel.max_non_empty_row_no, - max_non_empty_row); } static void @@ -1448,11 +1432,6 @@ decgra(struct terminal *term, uint8_t c) ph <= term->sixel.max_height && pv <= term->sixel.max_width) { resize(term, ph, pv); - - /* This ensures the sixel’s final image size is *at least* - * this large */ - term->sixel.max_non_empty_row_no = - min(pv, term->sixel.image.height) - 1; } term->sixel.state = SIXEL_DECSIXEL; diff --git a/terminal.h b/terminal.h index 0df9c5b6..e239e2af 100644 --- a/terminal.h +++ b/terminal.h @@ -620,7 +620,6 @@ struct terminal { } state; struct coord pos; /* Current sixel coordinate */ - int max_non_empty_row_no; size_t row_byte_ofs; /* Byte position into image, for current row */ int color_idx; /* Current palette index */ uint32_t *private_palette; /* Private palette, used when private mode 1070 is enabled */ From 1eb90b2405a010a6e545ef4160cbfdb2fc3e503b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 20 Jun 2023 12:58:35 +0200 Subject: [PATCH 027/135] sixel: minor fixes after implementing support for non-1:1 aspect ratios * Lazy initialize image height. This is necessary to prevent garbage from being rendered for "empty" sixels. * Fix plotting of non-1:1 pixels * Fix calculation of height in resize(), for non-1:1 aspect ratios --- sixel.c | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/sixel.c b/sixel.c index 71c4f2c0..a40a54a4 100644 --- a/sixel.c +++ b/sixel.c @@ -46,10 +46,10 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) (p1 == 7 || p1 == 8 || p1 == 9) ? 1 : 2; LOG_DBG("initializing sixel with " - "p1=%d (pan=%d, pad=%d, AR=%d:%d), " - "p2=%d (transparent=%d), " + "p1=%d (pan=%d, pad=%d, aspect-ratio=%d:%d), " + "p2=%d (transparent=%s), " "p3=%d (ignored)", - p1, pan, pad, pan, pad, p2, p2 == 1, p3); + p1, pan, pad, pan, pad, p2, p2 == 1 ? "yes" : "no", p3); term->sixel.state = SIXEL_DECSIXEL; term->sixel.pos = (struct coord){0, 0}; @@ -63,7 +63,7 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) term->sixel.transparent_bg = p2 == 1; term->sixel.image.data = NULL; term->sixel.image.width = 0; - term->sixel.image.height = 6 * pan; + term->sixel.image.height = 0; /* TODO: default palette */ @@ -1119,10 +1119,6 @@ sixel_unhook(struct terminal *term) static void resize_horizontally(struct terminal *term, int new_width) { - LOG_DBG("resizing image horizontally: %dx(%d) -> %dx(%d)", - term->sixel.image.width, term->sixel.image.height, - new_width, term->sixel.image.height); - if (unlikely(new_width > term->sixel.max_width)) { LOG_WARN("maximum image dimensions exceeded, truncating"); new_width = term->sixel.max_width; @@ -1131,11 +1127,23 @@ resize_horizontally(struct terminal *term, int new_width) if (unlikely(term->sixel.image.width == new_width)) return; + const int sixel_row_height = 6 * term->sixel.pan; + uint32_t *old_data = term->sixel.image.data; const int old_width = term->sixel.image.width; - const int height = term->sixel.image.height; - const int sixel_row_height = 6 * term->sixel.pan; + int height; + if (unlikely(term->sixel.image.height == 0)) { + /* Lazy initialize height on first printed sixel */ + xassert(old_width == 0); + term->sixel.image.height = height = sixel_row_height; + } else + height = term->sixel.image.height; + + LOG_DBG("resizing image horizontally: %dx(%d) -> %dx(%d)", + term->sixel.image.width, term->sixel.image.height, + new_width, height); + int alloc_height = (height + sixel_row_height - 1) / sixel_row_height * sixel_row_height; xassert(new_width > 0); @@ -1231,10 +1239,11 @@ resize(struct terminal *term, int new_width, int new_height) const int old_width = term->sixel.image.width; const int old_height = term->sixel.image.height; + const int sixel_row_height = 6 * term->sixel.pan; int alloc_new_width = new_width; - int alloc_new_height = (new_height + 6 - 1) / 6 * 6; + int alloc_new_height = (new_height + sixel_row_height - 1) / sixel_row_height * sixel_row_height; xassert(alloc_new_height >= new_height); - xassert(alloc_new_height - new_height < 6); + xassert(alloc_new_height - new_height < sixel_row_height); uint32_t *new_data = NULL; uint32_t bg = term->sixel.default_bg; @@ -1287,13 +1296,16 @@ sixel_add(struct terminal *term, int col, int width, uint32_t color, uint8_t six xassert(term->sixel.pos.col < term->sixel.image.width); xassert(term->sixel.pos.row < term->sixel.image.height); + const int pan = term->sixel.pan; size_t ofs = term->sixel.row_byte_ofs + col; uint32_t *data = &term->sixel.image.data[ofs]; - for (int i = 0; i < 6 * term->sixel.pan; i++, sixel >>= 1, data += width) { + for (int i = 0; i < 6; i++, sixel >>= 1) { if (sixel & 1) { - *data = color; - } + for (int r = 0; r < pan; r++, data += width) + *data = color; + } else + data += width * pan; } xassert(sixel == 0); @@ -1425,8 +1437,8 @@ decgra(struct terminal *term, uint8_t c) term->sixel.pan = pan; term->sixel.pad = pad; - LOG_DBG("pan=%u, pad=%u (aspect ratio = %u), size=%ux%u", - pan, pad, pan / pad, ph, pv); + LOG_DBG("pan=%u, pad=%u (aspect ratio = %d:%d), size=%ux%u", + pan, pad, pan, pad, ph, pv); if (ph >= term->sixel.image.height && pv >= term->sixel.image.width && ph <= term->sixel.max_height && pv <= term->sixel.max_width) From 5d576fccbaef3e413bcb5b7a98708d2f1904f5d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 20 Jun 2023 14:52:17 +0200 Subject: [PATCH 028/135] sixel: regression: linefeed count for chunked up sixel image All image chunks but the last *should* scroll the screen content. --- sixel.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sixel.c b/sixel.c index a40a54a4..29585d71 100644 --- a/sixel.c +++ b/sixel.c @@ -1049,7 +1049,12 @@ sixel_unhook(struct terminal *term) if (do_scroll) { /* Yes, truncate last row. This matches XTerm’s, and VT382’s behavior */ - const int linefeed_count = (image.height - 6 * term->sixel.pan + 1) / term->cell_height; + const int linefeed_count = rows_avail == 0 + ? (image.height - 6 * term->sixel.pan + 1) / term->cell_height + : image.height / term->cell_height; + + xassert(rows_avail == 0 || image.height % term->cell_height == 0); + for (size_t i = 0; i < linefeed_count; i++) term_linefeed(term); From 425cf894d4db5401e069d5f1c76652eb822c131c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 21 Jun 2023 11:39:54 +0200 Subject: [PATCH 029/135] sixel: resize(): handle no size change --- sixel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sixel.c b/sixel.c index 29585d71..4a8a4e9c 100644 --- a/sixel.c +++ b/sixel.c @@ -1244,6 +1244,9 @@ resize(struct terminal *term, int new_width, int new_height) const int old_width = term->sixel.image.width; const int old_height = term->sixel.image.height; + if (unlikely(old_width == new_width && old_height == new_height)) + return true; + const int sixel_row_height = 6 * term->sixel.pan; int alloc_new_width = new_width; int alloc_new_height = (new_height + sixel_row_height - 1) / sixel_row_height * sixel_row_height; From c15e75357a3823f264c798e87cf4d7fa802e4c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 22:01:51 +0200 Subject: [PATCH 030/135] sixel: ensure enough rows have been scrolled in, to fit the image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When emitting a sixel, we need to: a) scroll terminal content to ensure the new image fits b) position the text cursor Recent changes in the cursor positioning logic meant we reduced the number of linefeeds, to ensure 1) sixels could be printed to the bottom row without scrolling the terminal contents, and 2) the cursor was positioned on the last sixel row. Except, we’re not actually positioning the cursor on the last sixel row. We’re positioning it on the text row that maps to the *upper* pixel of the last sixel. In most cases, this _is_ the last row of the sixel. But for certain combinations of font and image sizes, it may be higher up. This patch fixes a regression, where the terminal contents weren’t scrolled up enough for certain images, causing a crash when trying to dirty a not-yet scrolled in row. The fix is this: * Always scroll by the number of rows occupied by the image, minus one. This ensures the image "fits". * Adjust the cursor position, if necessary. --- sixel.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/sixel.c b/sixel.c index 4a8a4e9c..3334c085 100644 --- a/sixel.c +++ b/sixel.c @@ -1048,10 +1048,17 @@ sixel_unhook(struct terminal *term) rows_avail -= image.rows; if (do_scroll) { - /* Yes, truncate last row. This matches XTerm’s, and VT382’s behavior */ + /* + * Linefeeds - always one less than the number of rows + * occupied by the image. + * + * Unless this is *not* the last chunk. In that case, + * linefeed past the chunk, so that the next chunk + * "starts" at a "new" row. + */ const int linefeed_count = rows_avail == 0 - ? (image.height - 6 * term->sixel.pan + 1) / term->cell_height - : image.height / term->cell_height; + ? max(0, image.rows - 1) + : image.rows; xassert(rows_avail == 0 || image.height % term->cell_height == 0); @@ -1060,9 +1067,28 @@ sixel_unhook(struct terminal *term) /* Position text cursor if this is the last image chunk */ if (rows_avail == 0) { + int row = term->grid->cursor.point.row; + + /* + * Position the text cursor based on the **upper** + * pixel, of the last sixel. + * + * In most cases, that’ll end up being the very last + * row of the sixel (which we’re already at, thanks to + * the linefeeds). But for some combinations of font + * and image sizes, the final cursor position is + * higher up. + */ + const int sixel_row_height = 6 * term->sixel.pan; + const int sixel_rows = (image.height + sixel_row_height - 1) / sixel_row_height; + const int upper_pixel_last_sixel = (sixel_rows - 1) * sixel_row_height; + const int term_rows = (upper_pixel_last_sixel + term->cell_height - 1) / term->cell_height; + + row -= (image.rows - term_rows); + term_cursor_to( term, - term->grid->cursor.point.row, + max(0, row), (term->sixel.cursor_right_of_graphics ? min(image.pos.col + image.cols, term->cols - 1) : image.pos.col)); From 2388015b105f420f42161b5a625dda8f6b9ebdb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 22:12:02 +0200 Subject: [PATCH 031/135] sixel: assert upper pixel of last sixel maps to last image row, *or lower* --- sixel.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sixel.c b/sixel.c index 3334c085..b0746a9a 100644 --- a/sixel.c +++ b/sixel.c @@ -1084,6 +1084,8 @@ sixel_unhook(struct terminal *term) const int upper_pixel_last_sixel = (sixel_rows - 1) * sixel_row_height; const int term_rows = (upper_pixel_last_sixel + term->cell_height - 1) / term->cell_height; + xassert(term_rows <= image.rows); + row -= (image.rows - term_rows); term_cursor_to( From d63a00a649c83d602b8914164251296c77029e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 20:15:36 +0200 Subject: [PATCH 032/135] config: unittest: explicitly call fcft_init() + fcft_fini() This plugs a memory leak, caused by fontconfig functions being called as part of the unit test implicitly allocating global objects. --- config.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config.c b/config.c index 1d9f200b..aba85db3 100644 --- a/config.c +++ b/config.c @@ -3349,6 +3349,8 @@ UNITTEST user_notifications_t nots = tll_init(); config_override_t overrides = tll_init(); + fcft_init(FCFT_LOG_COLORIZE_NEVER, false, FCFT_LOG_CLASS_NONE); + bool ret = config_load(&original, "/dev/null", ¬s, &overrides, false, false); xassert(ret); @@ -3360,6 +3362,8 @@ UNITTEST config_free(clone); free(clone); + fcft_fini(); + tll_free(overrides); tll_free(nots); } From 1dddb63d9fade867f5493903da67a46f45232ec8 Mon Sep 17 00:00:00 2001 From: Vladimir Bauer Date: Tue, 27 Jun 2023 17:00:31 +0500 Subject: [PATCH 033/135] correct csd section entry: hide-when-maximized --- foot.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foot.ini b/foot.ini index fcaef4a9..a4b91ef7 100644 --- a/foot.ini +++ b/foot.ini @@ -119,7 +119,7 @@ # size=26 # font= # color= -# hide-when-typing=no +# hide-when-maximized=no # border-width=0 # border-color= # button-width=26 From 1e6204e1ac3e153fc4eb95a78cb0710803c93130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 8 Mar 2023 10:43:30 +0100 Subject: [PATCH 034/135] meson: generate bindings for wp-fractional-scale + wp-viewport --- meson.build | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/meson.build b/meson.build index 29951541..3bad7ab2 100644 --- a/meson.build +++ b/meson.build @@ -158,6 +158,11 @@ if wayland_protocols.version().version_compare('>=1.21') add_project_arguments('-DHAVE_XDG_ACTIVATION', language: 'c') wl_proto_xml += [wayland_protocols_datadir + '/staging/xdg-activation/xdg-activation-v1.xml'] endif +if wayland_protocols.version().version_compare('>=1.31') + add_project_arguments('-DHAVE_FRACTIONAL_SCALE', language: 'c') + wl_proto_xml += [wayland_protocols_datadir + '/stable/viewporter/viewporter.xml'] + wl_proto_xml += [wayland_protocols_datadir + '/staging/fractional-scale/fractional-scale-v1.xml'] +endif foreach prot : wl_proto_xml wl_proto_headers += custom_target( From a9ecf1449e731c2461ced75cc9c16247523aca47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 8 Mar 2023 10:44:03 +0100 Subject: [PATCH 035/135] wayland: plumbing for wp-fractional-scale * Bind the wp-viewporter and wp-fractional-scale-manager globals. * Create a viewport and fractional-scale when instantiating a window. * Add fractional-scale listener (that does nothing at the moment). * Destroy everything on teardown. --- wayland.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ wayland.h | 16 +++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/wayland.c b/wayland.c index 51161cf0..7aa48e71 100644 --- a/wayland.c +++ b/wayland.c @@ -1121,6 +1121,27 @@ handle_global(void *data, struct wl_registry *registry, } #endif +#if defined(HAVE_FRACTIONAL_SCALE) + else if (strcmp(interface, wp_viewporter_interface.name) == 0) { + const uint32_t required = 1; + if (!verify_iface_version(interface, version, required)) + return; + + wayl->viewporter = wl_registry_bind( + wayl->registry, name, &wp_viewporter_interface, required); + } + + else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { + const uint32_t required = 1; + if (!verify_iface_version(interface, version, required)) + return; + + wayl->fractional_scale_manager = wl_registry_bind( + wayl->registry, name, + &wp_fractional_scale_manager_v1_interface, required); + } +#endif + #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) { const uint32_t required = 1; @@ -1435,6 +1456,12 @@ wayl_destroy(struct wayland *wayl) zwp_text_input_manager_v3_destroy(wayl->text_input_manager); #endif +#if defined(HAVE_FRACTIONAL_SCALE) + if (wayl->fractional_scale_manager != NULL) + wp_fractional_scale_manager_v1_destroy(wayl->fractional_scale_manager); + if (wayl->viewporter != NULL) + wp_viewporter_destroy(wayl->viewporter); +#endif #if defined(HAVE_XDG_ACTIVATION) if (wayl->xdg_activation != NULL) xdg_activation_v1_destroy(wayl->xdg_activation); @@ -1469,6 +1496,21 @@ wayl_destroy(struct wayland *wayl) free(wayl); } +#if defined(HAVE_FRACTIONAL_SCALE) +static void fractional_scale_preferred_scale( + void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, + uint32_t scale) +{ + struct wl_window *win = data; + win->scale = (float)scale / 120.; + LOG_DBG("fractional scale: %.3f", win->scale); +} + +static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { + .preferred_scale = &fractional_scale_preferred_scale, +}; +#endif + struct wl_window * wayl_win_init(struct terminal *term, const char *token) { @@ -1499,6 +1541,19 @@ wayl_win_init(struct terminal *term, const char *token) wl_surface_add_listener(win->surface, &surface_listener, win); +#if defined(HAVE_FRACTIONAL_SCALE) + if (wayl->fractional_scale_manager != NULL && wayl->viewporter != NULL) { + LOG_ERR("LDKJFLDF"); + win->viewport = wp_viewporter_get_viewport(wayl->viewporter, win->surface); + + win->fractional_scale = + wp_fractional_scale_manager_v1_get_fractional_scale( + wayl->fractional_scale_manager, win->surface); + wp_fractional_scale_v1_add_listener( + win->fractional_scale, &fractional_scale_listener, win); + } +#endif + win->xdg_surface = xdg_wm_base_get_xdg_surface(wayl->shell, win->surface); xdg_surface_add_listener(win->xdg_surface, &xdg_surface_listener, win); @@ -1652,6 +1707,12 @@ wayl_win_destroy(struct wl_window *win) tll_remove(win->xdg_tokens, it); } +#endif +#if defined(HAVE_FRACTIONAL_SCALE) + if (win->fractional_scale != NULL) + wp_fractional_scale_v1_destroy(win->fractional_scale); + if (win->viewport != NULL) + wp_viewport_destroy(win->viewport); #endif if (win->frame_callback != NULL) wl_callback_destroy(win->frame_callback); diff --git a/wayland.h b/wayland.h index 0d627052..20edcb68 100644 --- a/wayland.h +++ b/wayland.h @@ -20,6 +20,11 @@ #include #endif +#if defined(HAVE_FRACTIONAL_SCALE) + #include + #include +#endif + #include #include @@ -326,9 +331,15 @@ struct wl_window { #if defined(HAVE_XDG_ACTIVATION) tll(struct xdg_activation_token_context *) xdg_tokens; bool urgency_token_is_pending; +#endif +#if defined(HAVE_FRACTIONAL_SCALE) + struct wp_viewport *viewport; + struct wp_fractional_scale_v1 *fractional_scale; #endif bool unmapped; + float scale; + struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration; enum csd_mode csd_mode; @@ -414,6 +425,11 @@ struct wayland { struct zwp_text_input_manager_v3 *text_input_manager; #endif +#if defined(HAVE_FRACTIONAL_SCALE) + struct wp_viewporter *viewporter; + struct wp_fractional_scale_manager_v1 *fractional_scale_manager; +#endif + bool have_argb8888; tll(struct monitor) monitors; /* All available outputs */ tll(struct seat) seats; From c1f374cc8dab121388669cee6df3e2d1c483d77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 14:21:51 +0200 Subject: [PATCH 036/135] =?UTF-8?q?term:=20convert=20=E2=80=98scale?= =?UTF-8?q?=E2=80=99=20to=20a=20float?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- csi.c | 20 ++++++++++++-------- render.c | 24 ++++++++++++------------ terminal.h | 2 +- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/csi.c b/csi.c index ef1a28f2..153a1099 100644 --- a/csi.c +++ b/csi.c @@ -1206,8 +1206,10 @@ csi_dispatch(struct terminal *term, uint8_t final) if (width >= 0 && height >= 0) { char reply[64]; - size_t n = xsnprintf(reply, sizeof(reply), "\033[4;%d;%dt", - height / term->scale, width / term->scale); + size_t n = xsnprintf( + reply, sizeof(reply), "\033[4;%d;%dt", + (int)round(height / term->scale), + (int)(width / term->scale)); term_to_slave(term, reply, n); } break; @@ -1229,9 +1231,10 @@ csi_dispatch(struct terminal *term, uint8_t final) case 16: { /* report cell size in pixels */ char reply[64]; - size_t n = xsnprintf(reply, sizeof(reply), "\033[6;%d;%dt", - term->cell_height / term->scale, - term->cell_width / term->scale); + size_t n = xsnprintf( + reply, sizeof(reply), "\033[6;%d;%dt", + (int)round(term->cell_height / term->scale), + (int)round(term->cell_width / term->scale)); term_to_slave(term, reply, n); break; } @@ -1247,9 +1250,10 @@ csi_dispatch(struct terminal *term, uint8_t final) case 19: { /* report screen size in chars */ tll_foreach(term->window->on_outputs, it) { char reply[64]; - size_t n = xsnprintf(reply, sizeof(reply), "\033[9;%d;%dt", - it->item->dim.px_real.height / term->cell_height / term->scale, - it->item->dim.px_real.width / term->cell_width / term->scale); + size_t n = xsnprintf( + reply, sizeof(reply), "\033[9;%d;%dt", + (int)round(it->item->dim.px_real.height / term->cell_height / term->scale), + (int)round(it->item->dim.px_real.width / term->cell_width / term->scale)); term_to_slave(term, reply, n); break; } diff --git a/render.c b/render.c index 1858467d..f5438e42 100644 --- a/render.c +++ b/render.c @@ -1830,8 +1830,8 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx) static void csd_commit(struct terminal *term, struct wl_surface *surf, struct buffer *buf) { - xassert(buf->width % term->scale == 0); - xassert(buf->height % term->scale == 0); + xassert(buf->width % (int)term->scale == 0); + xassert(buf->height % (int)term->scale == 0); wl_surface_attach(surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(surf, 0, 0, buf->width, buf->height); @@ -1926,8 +1926,8 @@ render_osd(struct terminal *term, pixman_image_unref(src); pixman_image_set_clip_region32(buf->pix[0], NULL); - xassert(buf->width % term->scale == 0); - xassert(buf->height % term->scale == 0); + xassert(buf->width % (int)term->scale == 0); + xassert(buf->height % (int)term->scale == 0); quirk_weston_subsurface_desync_on(sub_surf); wl_surface_attach(surf, buf->wl_buf, 0, 0); @@ -1955,8 +1955,8 @@ render_csd_title(struct terminal *term, const struct csd_data *info, if (info->width == 0 || info->height == 0) return; - xassert(info->width % term->scale == 0); - xassert(info->height % term->scale == 0); + xassert(info->width % (int)term->scale == 0); + xassert(info->height % (int)term->scale == 0); uint32_t bg = term->conf->csd.color.title_set ? term->conf->csd.color.title @@ -2000,8 +2000,8 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, if (info->width == 0 || info->height == 0) return; - xassert(info->width % term->scale == 0); - xassert(info->height % term->scale == 0); + xassert(info->width % (int)term->scale == 0); + xassert(info->height % (int)term->scale == 0); { pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0); @@ -2289,8 +2289,8 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, if (info->width == 0 || info->height == 0) return; - xassert(info->width % term->scale == 0); - xassert(info->height % term->scale == 0); + xassert(info->width % (int)term->scale == 0); + xassert(info->height % (int)term->scale == 0); uint32_t _color; uint16_t alpha = 0xffff; @@ -3067,8 +3067,8 @@ grid_render(struct terminal *term) term->window->surface, 0, 0, INT32_MAX, INT32_MAX); } - xassert(buf->width % term->scale == 0); - xassert(buf->height % term->scale == 0); + xassert(buf->width % (int)term->scale == 0); + xassert(buf->height % (int)term->scale == 0); wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0); wl_surface_commit(term->window->surface); diff --git a/terminal.h b/terminal.h index e239e2af..220070e1 100644 --- a/terminal.h +++ b/terminal.h @@ -454,7 +454,7 @@ struct terminal { int fd; } blink; - int scale; + float scale; int width; /* pixels */ int height; /* pixels */ int stashed_width; From 6e2a47287aa5160134a8bb75f562af5c85f3aaf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 14:23:53 +0200 Subject: [PATCH 037/135] wayland: pointer.scale: convert to float --- wayland.c | 4 ++-- wayland.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wayland.c b/wayland.c index 7aa48e71..b5bca9ba 100644 --- a/wayland.c +++ b/wayland.c @@ -1733,7 +1733,7 @@ wayl_win_destroy(struct wl_window *win) } bool -wayl_reload_xcursor_theme(struct seat *seat, int new_scale) +wayl_reload_xcursor_theme(struct seat *seat, float new_scale) { if (seat->pointer.theme != NULL && seat->pointer.scale == new_scale) { /* We already have a theme loaded, and the scale hasn't changed */ @@ -1766,7 +1766,7 @@ wayl_reload_xcursor_theme(struct seat *seat, int new_scale) const char *xcursor_theme = getenv("XCURSOR_THEME"); - LOG_INFO("cursor theme: %s, size: %d, scale: %d", + LOG_INFO("cursor theme: %s, size: %d, scale: %.2f", xcursor_theme ? xcursor_theme : "(null)", xcursor_size, new_scale); diff --git a/wayland.h b/wayland.h index 20edcb68..a5316837 100644 --- a/wayland.h +++ b/wayland.h @@ -135,7 +135,7 @@ struct seat { struct wl_surface *surface; struct wl_cursor_theme *theme; struct wl_cursor *cursor; - int scale; + float scale; bool hidden; const char *xcursor; @@ -442,7 +442,7 @@ struct wayland *wayl_init( bool presentation_timings); void wayl_destroy(struct wayland *wayl); -bool wayl_reload_xcursor_theme(struct seat *seat, int new_scale); +bool wayl_reload_xcursor_theme(struct seat *seat, float new_scale); void wayl_flush(struct wayland *wayl); void wayl_roundtrip(struct wayland *wayl); From 44743b5635aeee2d9cbb2bae33d5dcbc374be8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 14:27:16 +0200 Subject: [PATCH 038/135] render: draw_unfocused_block(): round scale, instead of truncating --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index f5438e42..e62e000d 100644 --- a/render.c +++ b/render.c @@ -311,7 +311,7 @@ static void draw_unfocused_block(const struct terminal *term, pixman_image_t *pix, const pixman_color_t *color, int x, int y, int cell_cols) { - const int scale = term->scale; + const int scale = round(term->scale); const int width = min(min(scale, term->cell_width), term->cell_height); pixman_image_fill_rectangles( From b65612479135da9bf888fcd037c86519940e17b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 14:27:37 +0200 Subject: [PATCH 039/135] render: csd_border: round scaled border width, instead of truncating --- render.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/render.c b/render.c index e62e000d..f87ef093 100644 --- a/render.c +++ b/render.c @@ -2012,9 +2012,9 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, * The “visible” border. */ - int scale = term->scale; - int bwidth = term->conf->csd.border_width * scale; - int vwidth = term->conf->csd.border_width_visible * scale; /* Visible size */ + float scale = term->scale; + int bwidth = round(term->conf->csd.border_width * scale); + int vwidth = round(term->conf->csd.border_width_visible * scale); /* Visible size */ xassert(bwidth >= vwidth); @@ -2067,7 +2067,6 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, uint16_t alpha = _color >> 24 | (_color >> 24 << 8); pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha); - pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix[0], &color, 1, &(pixman_rectangle16_t){x, y, w, h}); From cf280e6655da63dd6d2c2e146ec1ff8e71235779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 14:35:02 +0200 Subject: [PATCH 040/135] render: render_timer(): round scaling factor --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index f87ef093..51f4f1e8 100644 --- a/render.c +++ b/render.c @@ -2568,7 +2568,7 @@ render_render_timer(struct terminal *term, struct timespec render_time) char32_t text[256]; mbstoc32(text, usecs_str, ALEN(text)); - const int scale = term->scale; + const int scale = round(term->scale); const int cell_count = c32len(text); const int margin = 3 * scale; const int width = From 30c8d3e652db1cff7138e5e916cdaf2c9b6026a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 14:35:19 +0200 Subject: [PATCH 041/135] render: search_box(): round scaling factor --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 51f4f1e8..34648d53 100644 --- a/render.c +++ b/render.c @@ -3132,7 +3132,7 @@ render_search_box(struct terminal *term) const size_t wanted_visible_cells = max(20, total_cells); xassert(term->scale >= 1); - const int scale = term->scale; + const int scale = round(term->scale); const size_t margin = 3 * scale; From d8f64d1047415d1446dad893167d1a756ffa9a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 14:35:29 +0200 Subject: [PATCH 042/135] render: urls(): round scaling factor --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 34648d53..80025d5e 100644 --- a/render.c +++ b/render.c @@ -3422,7 +3422,7 @@ render_urls(struct terminal *term) struct wl_window *win = term->window; xassert(tll_length(win->urls) > 0); - const int scale = term->scale; + const int scale = round(term->scale); const int x_margin = 2 * scale; const int y_margin = 1 * scale; From 2bb7b28837aac939dad61b7ca58a0dcef08399e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 14:37:31 +0200 Subject: [PATCH 043/135] =?UTF-8?q?render:=20xcursor=5Fupdate():=20convert?= =?UTF-8?q?=20local=20=E2=80=98scale=E2=80=99=20variable=20to=20float?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 80025d5e..25249218 100644 --- a/render.c +++ b/render.c @@ -4276,7 +4276,7 @@ render_xcursor_update(struct seat *seat) xassert(seat->pointer.cursor != NULL); - const int scale = seat->pointer.scale; + const float scale = seat->pointer.scale; struct wl_cursor_image *image = seat->pointer.cursor->images[0]; wl_surface_attach( From 424d0450840bc3db018457787fcdf93db101b265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 14:39:34 +0200 Subject: [PATCH 044/135] =?UTF-8?q?term:=20reload=5Ffonts():=20=E2=80=98sc?= =?UTF-8?q?ale=E2=80=99=20is=20not=20a=20float?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- terminal.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/terminal.c b/terminal.c index 815dc2d4..43ba157b 100644 --- a/terminal.c +++ b/terminal.c @@ -1000,14 +1000,14 @@ reload_fonts(struct terminal *term) bool use_px_size = term->font_sizes[i][j].px_size > 0; char size[64]; - const int scale = term->font_is_sized_by_dpi ? 1 : term->scale; + const float scale = term->font_is_sized_by_dpi ? 1. : term->scale; if (use_px_size) snprintf(size, sizeof(size), ":pixelsize=%d", - term->font_sizes[i][j].px_size * scale); + (int)round(term->font_sizes[i][j].px_size * scale)); else snprintf(size, sizeof(size), ":size=%.2f", - term->font_sizes[i][j].pt_size * (double)scale); + term->font_sizes[i][j].pt_size * scale); size_t len = strlen(font->pattern) + strlen(size) + 1; names[i][j] = xmalloc(len); @@ -1232,7 +1232,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .reverse_wrap = true, .auto_margin = true, .window_title_stack = tll_init(), - .scale = 1, + .scale = 1., .flash = {.fd = flash_fd}, .blink = {.fd = -1}, .vt = { From 29a14632d369c6a9d2ceea70e75f17dc47ab23ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 14:39:49 +0200 Subject: [PATCH 045/135] =?UTF-8?q?wayland:=20csd=5Freload=5Ffont():=20?= =?UTF-8?q?=E2=80=98scale=E2=80=99=20is=20now=20a=20float?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wayland.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wayland.c b/wayland.c index b5bca9ba..575d2e83 100644 --- a/wayland.c +++ b/wayland.c @@ -32,12 +32,12 @@ #include "xmalloc.h" static void -csd_reload_font(struct wl_window *win, int old_scale) +csd_reload_font(struct wl_window *win, float old_scale) { struct terminal *term = win->term; const struct config *conf = term->conf; - const int scale = term->scale; + const float scale = term->scale; bool enable_csd = win->csd_mode == CSD_YES && !win->is_fullscreen; if (!enable_csd) @@ -52,10 +52,10 @@ csd_reload_font(struct wl_window *win, int old_scale) patterns[i] = conf->csd.font.arr[i].pattern; char pixelsize[32]; - snprintf(pixelsize, sizeof(pixelsize), - "pixelsize=%u", conf->csd.title_height * scale * 1 / 2); + snprintf(pixelsize, sizeof(pixelsize), "pixelsize=%u", + (int)round(conf->csd.title_height * scale * 1 / 2)); - LOG_DBG("loading CSD font \"%s:%s\" (old-scale=%d, scale=%d)", + LOG_DBG("loading CSD font \"%s:%s\" (old-scale=%.2f, scale=%.2f)", patterns[0], pixelsize, old_scale, scale); win->csd.font = fcft_from_name(conf->csd.font.count, patterns, pixelsize); @@ -79,7 +79,7 @@ csd_instantiate(struct wl_window *win) xassert(ret); } - csd_reload_font(win, -1); + csd_reload_font(win, -1.); } static void @@ -334,7 +334,7 @@ update_term_for_output_change(struct terminal *term) if (tll_length(term->window->on_outputs) == 0) return; - int old_scale = term->scale; + float old_scale = term->scale; render_resize(term, term->width / term->scale, term->height / term->scale); term_font_dpi_changed(term, old_scale); From 913ae94cf99cb4a171ea69e6340b804a0e8c6a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 15:01:33 +0200 Subject: [PATCH 046/135] wayland: add wayl_fractional_scaling() Returns true if fractional scaling is available. --- wayland.c | 10 ++++++++++ wayland.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/wayland.c b/wayland.c index 575d2e83..e01a3a50 100644 --- a/wayland.c +++ b/wayland.c @@ -2048,3 +2048,13 @@ wayl_get_activation_token( return true; } #endif + +bool +wayl_fractional_scaling(const struct wayland *wayl) +{ +#if defined(HAVE_FRACTIONAL_SCALE) + return wayl->fractional_scale_manager != NULL; +#else + return false; +#endif +} diff --git a/wayland.h b/wayland.h index a5316837..756da8d2 100644 --- a/wayland.h +++ b/wayland.h @@ -469,3 +469,5 @@ bool wayl_get_activation_token( struct wayland *wayl, struct seat *seat, uint32_t serial, struct wl_window *win, activation_token_cb_t cb, void *cb_data); #endif + +bool wayl_fractional_scaling(const struct wayland *wayl); From 4bd62b10058c626cecb5eb15c9c44aee58bf547a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 22 Jun 2023 15:01:59 +0200 Subject: [PATCH 047/135] =?UTF-8?q?render:=20maybe=5Fresize():=20convert?= =?UTF-8?q?=20local=20variable=20=E2=80=98scale=E2=80=99=20to=20float?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/render.c b/render.c index 25249218..3c2e6f18 100644 --- a/render.c +++ b/render.c @@ -3868,13 +3868,13 @@ maybe_resize(struct terminal *term, int width, int height, bool force) if (term->cell_width == 0 && term->cell_height == 0) return false; - int scale = -1; + float scale = -1; tll_foreach(term->window->on_outputs, it) { if (it->item->scale > scale) scale = it->item->scale; } - if (scale < 0) { + if (scale < 0.) { /* Haven't 'entered' an output yet? */ scale = term->scale; } @@ -3922,13 +3922,18 @@ maybe_resize(struct terminal *term, int width, int height, bool force) * Ensure we can scale to logical size, and back to * pixels without truncating. */ - if (width % scale) - width += scale - width % scale; - if (height % scale) - height += scale - height % scale; + if (wayl_fractional_scaling(term->wl)) { + xassert((int)round(scale) == (int)scale); - xassert(width % scale == 0); - xassert(height % scale == 0); + int iscale = scale; + if (width % iscale) + width += iscale - width % iscale; + if (height % iscale) + height += iscale - height % iscale; + + xassert(width % iscale == 0); + xassert(height % iscale == 0); + } break; } } From 0a5073f5703e89cfa4219d5b532cb134f9def6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 15:51:04 +0200 Subject: [PATCH 048/135] wayland: add wayl_surface_scale(), and wayl_win_scale() These functions scale a surface+buffer. For now, only using the legacy scaling method (wl_surface_set_buffer_scale()). --- render.c | 47 +++++++++++++---------------------------------- wayland.c | 44 ++++++++++++++++++++++++++++++++++---------- wayland.h | 7 +++++-- 3 files changed, 52 insertions(+), 46 deletions(-) diff --git a/render.c b/render.c index 3c2e6f18..5663c45a 100644 --- a/render.c +++ b/render.c @@ -1691,8 +1691,8 @@ render_overlay(struct terminal *term) &(pixman_rectangle16_t){0, 0, term->width, term->height}); quirk_weston_subsurface_desync_on(overlay->sub); + wayl_surface_scale(term->wl, overlay->surf, term->scale); wl_subsurface_set_position(overlay->sub, 0, 0); - wl_surface_set_buffer_scale(overlay->surf, term->scale); wl_surface_attach(overlay->surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer( @@ -1830,12 +1830,9 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx) static void csd_commit(struct terminal *term, struct wl_surface *surf, struct buffer *buf) { - xassert(buf->width % (int)term->scale == 0); - xassert(buf->height % (int)term->scale == 0); - + wayl_surface_scale(term->wl, surf, term->scale); wl_surface_attach(surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(surf, 0, 0, buf->width, buf->height); - wl_surface_set_buffer_scale(surf, term->scale); wl_surface_commit(surf); } @@ -1926,13 +1923,10 @@ render_osd(struct terminal *term, pixman_image_unref(src); pixman_image_set_clip_region32(buf->pix[0], NULL); - xassert(buf->width % (int)term->scale == 0); - xassert(buf->height % (int)term->scale == 0); - quirk_weston_subsurface_desync_on(sub_surf); + wayl_surface_scale(term->wl, surf, term->scale); wl_surface_attach(surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(surf, 0, 0, buf->width, buf->height); - wl_surface_set_buffer_scale(surf, term->scale); struct wl_region *region = wl_compositor_create_region(term->wl->compositor); if (region != NULL) { @@ -1955,9 +1949,6 @@ render_csd_title(struct terminal *term, const struct csd_data *info, if (info->width == 0 || info->height == 0) return; - xassert(info->width % (int)term->scale == 0); - xassert(info->height % (int)term->scale == 0); - uint32_t bg = term->conf->csd.color.title_set ? term->conf->csd.color.title : 0xffu << 24 | term->conf->colors.fg; @@ -2000,9 +1991,6 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, if (info->width == 0 || info->height == 0) return; - xassert(info->width % (int)term->scale == 0); - xassert(info->height % (int)term->scale == 0); - { pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0); render_csd_part(term, surf, buf, info->width, info->height, &color); @@ -2288,9 +2276,6 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, if (info->width == 0 || info->height == 0) return; - xassert(info->width % (int)term->scale == 0); - xassert(info->height % (int)term->scale == 0); - uint32_t _color; uint16_t alpha = 0xffff; bool is_active = false; @@ -3032,7 +3017,7 @@ grid_render(struct terminal *term) term->window->frame_callback = wl_surface_frame(term->window->surface); wl_callback_add_listener(term->window->frame_callback, &frame_listener, term); - wl_surface_set_buffer_scale(term->window->surface, term->scale); + wayl_win_scale(term->window); if (term->wl->presentation != NULL && term->conf->presentation_timings) { struct timespec commit_time; @@ -3066,9 +3051,6 @@ grid_render(struct terminal *term) term->window->surface, 0, 0, INT32_MAX, INT32_MAX); } - xassert(buf->width % (int)term->scale == 0); - xassert(buf->height % (int)term->scale == 0); - wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0); wl_surface_commit(term->window->surface); } @@ -3132,17 +3114,17 @@ render_search_box(struct terminal *term) const size_t wanted_visible_cells = max(20, total_cells); xassert(term->scale >= 1); - const int scale = round(term->scale); + const int rounded_scale = round(term->scale); - const size_t margin = 3 * scale; + const size_t margin = 3 * rounded_scale; const size_t width = term->width - 2 * margin; const size_t visible_width = min( term->width - 2 * margin, - (2 * margin + wanted_visible_cells * term->cell_width + scale - 1) / scale * scale); + (2 * margin + wanted_visible_cells * term->cell_width + rounded_scale - 1) / rounded_scale * rounded_scale); const size_t height = min( term->height - 2 * margin, - (2 * margin + 1 * term->cell_height + scale - 1) / scale * scale); + (2 * margin + 1 * term->cell_height + rounded_scale - 1) / rounded_scale * rounded_scale); const size_t visible_cells = (visible_width - 2 * margin) / term->cell_width; size_t glyph_offset = term->render.search_glyph_offset; @@ -3389,15 +3371,12 @@ render_search_box(struct terminal *term) /* TODO: this is only necessary on a window resize */ wl_subsurface_set_position( term->window->search.sub, - margin / scale, - max(0, (int32_t)term->height - height - margin) / scale); - - xassert(buf->width % scale == 0); - xassert(buf->height % scale == 0); + margin / term->scale, + max(0, (int32_t)term->height - height - margin) / term->scale); + wayl_surface_scale(term->wl, term->window->search.surf, term->scale); wl_surface_attach(term->window->search.surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(term->window->search.surf, 0, 0, width, height); - wl_surface_set_buffer_scale(term->window->search.surf, scale); struct wl_region *region = wl_compositor_create_region(term->wl->compositor); if (region != NULL) { @@ -4284,6 +4263,8 @@ render_xcursor_update(struct seat *seat) const float scale = seat->pointer.scale; struct wl_cursor_image *image = seat->pointer.cursor->images[0]; + wayl_surface_scale(seat->wayl, seat->pointer.surface, scale); + wl_surface_attach( seat->pointer.surface, wl_cursor_image_get_buffer(image), 0, 0); @@ -4295,8 +4276,6 @@ render_xcursor_update(struct seat *seat) wl_surface_damage_buffer( seat->pointer.surface, 0, 0, INT32_MAX, INT32_MAX); - wl_surface_set_buffer_scale(seat->pointer.surface, scale); - xassert(seat->pointer.xcursor_callback == NULL); seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface); wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat); diff --git a/wayland.c b/wayland.c index e01a3a50..5aa10966 100644 --- a/wayland.c +++ b/wayland.c @@ -1850,6 +1850,40 @@ wayl_roundtrip(struct wayland *wayl) wayl_flush(wayl); } + +bool +wayl_fractional_scaling(const struct wayland *wayl) +{ +#if defined(HAVE_FRACTIONAL_SCALE) + return wayl->fractional_scale_manager != NULL; +#else + return false; +#endif +} + +void +wayl_surface_scale(const struct wayland *wayl, struct wl_surface *surf, + float scale) +{ + LOG_WARN("scaling by a factor of %.2f (legacy)", scale); + + if (wayl_fractional_scaling(wayl)) { + BUG("not yet implemented"); + } else { + wl_surface_set_buffer_scale(surf, (int)scale); + } +} + +void +wayl_win_scale(struct wl_window *win) +{ + const struct terminal *term = win->term; + const struct wayland *wayl = term->wl; + const float scale = term->scale; + + wayl_surface_scale(wayl, win->surface, scale); +} + void wayl_win_alpha_changed(struct wl_window *win) { @@ -2048,13 +2082,3 @@ wayl_get_activation_token( return true; } #endif - -bool -wayl_fractional_scaling(const struct wayland *wayl) -{ -#if defined(HAVE_FRACTIONAL_SCALE) - return wayl->fractional_scale_manager != NULL; -#else - return false; -#endif -} diff --git a/wayland.h b/wayland.h index 756da8d2..06150328 100644 --- a/wayland.h +++ b/wayland.h @@ -447,9 +447,14 @@ bool wayl_reload_xcursor_theme(struct seat *seat, float new_scale); void wayl_flush(struct wayland *wayl); void wayl_roundtrip(struct wayland *wayl); +bool wayl_fractional_scaling(const struct wayland *wayl); +void wayl_surface_scale( + const struct wayland *wayl, struct wl_surface *surf, float scale); + struct wl_window *wayl_win_init(struct terminal *term, const char *token); void wayl_win_destroy(struct wl_window *win); +void wayl_win_scale(struct wl_window *win); void wayl_win_alpha_changed(struct wl_window *win); bool wayl_win_set_urgent(struct wl_window *win); @@ -469,5 +474,3 @@ bool wayl_get_activation_token( struct wayland *wayl, struct seat *seat, uint32_t serial, struct wl_window *win, activation_token_cb_t cb, void *cb_data); #endif - -bool wayl_fractional_scaling(const struct wayland *wayl); From c5d533ec71aafe1c842189a7e662f7a7696c56a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 15:55:40 +0200 Subject: [PATCH 049/135] wayland: add viewport object to sub-surface struct --- wayland.c | 27 +++++++++++++++++++++++++-- wayland.h | 3 +++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/wayland.c b/wayland.c index 5aa10966..9a3d11e8 100644 --- a/wayland.c +++ b/wayland.c @@ -1543,7 +1543,6 @@ wayl_win_init(struct terminal *term, const char *token) #if defined(HAVE_FRACTIONAL_SCALE) if (wayl->fractional_scale_manager != NULL && wayl->viewporter != NULL) { - LOG_ERR("LDKJFLDF"); win->viewport = wp_viewporter_get_viewport(wayl->viewporter, win->surface); win->fractional_scale = @@ -1965,17 +1964,33 @@ wayl_win_subsurface_new_with_custom_parent( struct wl_surface *main_surface = wl_compositor_create_surface(wayl->compositor); - if (main_surface == NULL) + if (main_surface == NULL) { + LOG_ERR("failed to instantiate surface for sub-surface"); return false; + } struct wl_subsurface *sub = wl_subcompositor_get_subsurface( wayl->sub_compositor, main_surface, parent); if (sub == NULL) { + LOG_ERR("failed to instantiate sub-surface"); wl_surface_destroy(main_surface); return false; } +#if defined(HAVE_FRACTIONAL_SCALE) + struct wp_viewport *viewport = NULL; + if (wayl->fractional_scale_manager != NULL && wayl->viewporter != NULL) { + viewport = wp_viewporter_get_viewport(wayl->viewporter, main_surface); + if (viewport == NULL) { + LOG_ERR("failed to instantiate viewport for sub-surface"); + wl_subsurface_destroy(sub); + wl_surface_destroy(main_surface); + return false; + } + } +#endif + wl_surface_set_user_data(main_surface, win); wl_subsurface_set_sync(sub); @@ -1989,6 +2004,9 @@ wayl_win_subsurface_new_with_custom_parent( surf->surf = main_surface; surf->sub = sub; +#if defined(HAVE_FRACTIONAL_SCALE) + surf->viewport = viewport; +#endif return true; } @@ -2005,6 +2023,11 @@ wayl_win_subsurface_destroy(struct wl_surf_subsurf *surf) { if (surf == NULL) return; + +#if defined(HAVE_FRACTIONAL_SCALE) + if (surf->viewport != NULL) + wp_viewport_destroy(surf->viewport); +#endif if (surf->sub != NULL) wl_subsurface_destroy(surf->sub); if (surf->surf != NULL) diff --git a/wayland.h b/wayland.h index 06150328..bb9bf77f 100644 --- a/wayland.h +++ b/wayland.h @@ -297,6 +297,9 @@ struct monitor { struct wl_surf_subsurf { struct wl_surface *surf; struct wl_subsurface *sub; +#if defined(HAVE_FRACTIONAL_SCALE) + struct wp_viewport *viewport; +#endif }; struct wl_url { From ba46a039aca734bb284c22ba5d5bddf5407e3848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 16:10:40 +0200 Subject: [PATCH 050/135] wayland: refactor: wrap wl_surface pointers in a wayl_surface struct And add a viewport object to accompany the surface (to be used when scaling the surface). Also rename the wl_surf_subsurf struct to wayl_sub_surface, and add a wayl_surface object to it, rather than a plain wl_surface pointer (to also get the viewport pointer). --- quirks.c | 2 +- render.c | 100 +++++++++++++++++++++--------------------- terminal.c | 18 ++++---- wayland.c | 125 ++++++++++++++++++++++++++++------------------------- wayland.h | 43 +++++++++--------- 5 files changed, 149 insertions(+), 139 deletions(-) diff --git a/quirks.c b/quirks.c index bf9bc7fb..9769f1ff 100644 --- a/quirks.c +++ b/quirks.c @@ -89,5 +89,5 @@ quirk_sway_subsurface_unmap(struct terminal *term) if (!is_sway()) return; - wl_surface_damage_buffer(term->window->surface, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_damage_buffer(term->window->surface.surf, 0, 0, INT32_MAX, INT32_MAX); } diff --git a/render.c b/render.c index 5663c45a..5ebd69eb 100644 --- a/render.c +++ b/render.c @@ -905,21 +905,21 @@ render_margin(struct terminal *term, struct buffer *buf, if (apply_damage) { /* Top */ wl_surface_damage_buffer( - term->window->surface, 0, 0, term->width, term->margins.top); + term->window->surface.surf, 0, 0, term->width, term->margins.top); /* Bottom */ wl_surface_damage_buffer( - term->window->surface, 0, bmargin, term->width, term->margins.bottom); + term->window->surface.surf, 0, bmargin, term->width, term->margins.bottom); /* Left */ wl_surface_damage_buffer( - term->window->surface, + term->window->surface.surf, 0, term->margins.top + start_line * term->cell_height, term->margins.left, line_count * term->cell_height); /* Right */ wl_surface_damage_buffer( - term->window->surface, + term->window->surface.surf, rmargin, term->margins.top + start_line * term->cell_height, term->margins.right, line_count * term->cell_height); } @@ -1027,7 +1027,7 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, #endif wl_surface_damage_buffer( - term->window->surface, term->margins.left, dst_y, + term->window->surface.surf, term->margins.left, dst_y, term->width - term->margins.left - term->margins.right, height); /* @@ -1104,7 +1104,7 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf, #endif wl_surface_damage_buffer( - term->window->surface, term->margins.left, dst_y, + term->window->surface.surf, term->margins.left, dst_y, term->width - term->margins.left - term->margins.right, height); /* @@ -1153,7 +1153,7 @@ render_sixel_chunk(struct terminal *term, pixman_image_t *pix, const struct sixe x, y, width, height); - wl_surface_damage_buffer(term->window->surface, x, y, width, height); + wl_surface_damage_buffer(term->window->surface.surf, x, y, width, height); } static void @@ -1480,7 +1480,7 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, free(real_cells); wl_surface_damage_buffer( - term->window->surface, + term->window->surface.surf, term->margins.left, term->margins.top + row_idx * term->cell_height, term->width - term->margins.left - term->margins.right, @@ -1502,7 +1502,7 @@ render_ime_preedit(struct terminal *term, struct buffer *buf) static void render_overlay(struct terminal *term) { - struct wl_surf_subsurf *overlay = &term->window->overlay; + struct wayl_sub_surface *overlay = &term->window->overlay; bool unicode_mode_active = false; /* Check if unicode mode is active on at least one seat focusing @@ -1523,8 +1523,8 @@ render_overlay(struct terminal *term) if (likely(style == OVERLAY_NONE)) { if (term->render.last_overlay_style != OVERLAY_NONE) { /* Unmap overlay sub-surface */ - wl_surface_attach(overlay->surf, NULL, 0, 0); - wl_surface_commit(overlay->surf); + wl_surface_attach(overlay->surface.surf, NULL, 0, 0); + wl_surface_commit(overlay->surface.surf); term->render.last_overlay_style = OVERLAY_NONE; term->render.last_overlay_buf = NULL; @@ -1691,17 +1691,17 @@ render_overlay(struct terminal *term) &(pixman_rectangle16_t){0, 0, term->width, term->height}); quirk_weston_subsurface_desync_on(overlay->sub); - wayl_surface_scale(term->wl, overlay->surf, term->scale); + wayl_surface_scale(term->wl, overlay->surface.surf, term->scale); wl_subsurface_set_position(overlay->sub, 0, 0); - wl_surface_attach(overlay->surf, buf->wl_buf, 0, 0); + wl_surface_attach(overlay->surface.surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer( - overlay->surf, + overlay->surface.surf, damage_bounds.x1, damage_bounds.y1, damage_bounds.x2 - damage_bounds.x1, damage_bounds.y2 - damage_bounds.y1); - wl_surface_commit(overlay->surf); + wl_surface_commit(overlay->surface.surf); quirk_weston_subsurface_desync_off(overlay->sub); buf->age = 0; @@ -1945,7 +1945,7 @@ render_csd_title(struct terminal *term, const struct csd_data *info, { xassert(term->window->csd_mode == CSD_YES); - struct wl_surf_subsurf *surf = &term->window->csd.surface[CSD_SURF_TITLE]; + struct wayl_sub_surface *surf = &term->window->csd.surface[CSD_SURF_TITLE]; if (info->width == 0 || info->height == 0) return; @@ -1971,11 +1971,11 @@ render_csd_title(struct terminal *term, const struct csd_data *info, const int margin = M != NULL ? M->advance.x : win->csd.font->max_advance.x; - render_osd(term, surf->surf, surf->sub, win->csd.font, + render_osd(term, surf->surface.surf, surf->sub, win->csd.font, buf, title_text, fg, bg, margin, (buf->height - win->csd.font->height) / 2); - csd_commit(term, surf->surf, buf); + csd_commit(term, surf->surface.surf, buf); free(_title_text); } @@ -1986,7 +1986,7 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, xassert(term->window->csd_mode == CSD_YES); xassert(surf_idx >= CSD_SURF_LEFT && surf_idx <= CSD_SURF_BOTTOM); - struct wl_surface *surf = term->window->csd.surface[surf_idx].surf; + struct wl_surface *surf = term->window->csd.surface[surf_idx].surface.surf; if (info->width == 0 || info->height == 0) return; @@ -2271,7 +2271,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, xassert(term->window->csd_mode == CSD_YES); xassert(surf_idx >= CSD_SURF_MINIMIZE && surf_idx <= CSD_SURF_CLOSE); - struct wl_surface *surf = term->window->csd.surface[surf_idx].surf; + struct wl_surface *surf = term->window->csd.surface[surf_idx].surface.surf; if (info->width == 0 || info->height == 0) return; @@ -2358,7 +2358,7 @@ render_csd(struct terminal *term) const int width = infos[i].width; const int height = infos[i].height; - struct wl_surface *surf = term->window->csd.surface[i].surf; + struct wl_surface *surf = term->window->csd.surface[i].surface.surf; struct wl_subsurface *sub = term->window->csd.surface[i].sub; xassert(surf != NULL); @@ -2397,7 +2397,7 @@ render_scrollback_position(struct terminal *term) struct wl_window *win = term->window; if (term->grid->view == term->grid->offset) { - if (win->scrollback_indicator.surf != NULL) { + if (win->scrollback_indicator.surface.surf != NULL) { wayl_win_subsurface_destroy(&win->scrollback_indicator); /* Work around Sway bug - unmapping a sub-surface does not damage @@ -2407,7 +2407,7 @@ render_scrollback_position(struct terminal *term) return; } - if (win->scrollback_indicator.surf == NULL) { + if (win->scrollback_indicator.surface.surf == NULL) { if (!wayl_win_subsurface_new( win, &win->scrollback_indicator, false)) { @@ -2416,7 +2416,7 @@ render_scrollback_position(struct terminal *term) } } - xassert(win->scrollback_indicator.surf != NULL); + xassert(win->scrollback_indicator.surface.surf != NULL); xassert(win->scrollback_indicator.sub != NULL); /* Find absolute row number of the scrollback start */ @@ -2514,8 +2514,8 @@ render_scrollback_position(struct terminal *term) const int y = (term->margins.top + surf_top) / scale * scale; if (y + height > term->height) { - wl_surface_attach(win->scrollback_indicator.surf, NULL, 0, 0); - wl_surface_commit(win->scrollback_indicator.surf); + wl_surface_attach(win->scrollback_indicator.surface.surf, NULL, 0, 0); + wl_surface_commit(win->scrollback_indicator.surface.surf); return; } @@ -2534,7 +2534,7 @@ render_scrollback_position(struct terminal *term) render_osd( term, - win->scrollback_indicator.surf, + win->scrollback_indicator.surface.surf, win->scrollback_indicator.sub, term->fonts[0], buf, text, fg, 0xffu << 24 | bg, @@ -2571,7 +2571,7 @@ render_render_timer(struct terminal *term, struct timespec render_time) render_osd( term, - win->render_timer.surf, + win->render_timer.surface.surf, win->render_timer.sub, term->fonts[0], buf, text, term->colors.table[0], 0xffu << 24 | term->colors.table[8 + 1], @@ -2919,7 +2919,7 @@ grid_render(struct terminal *term) int height = (r - first_dirty_row) * term->cell_height; wl_surface_damage_buffer( - term->window->surface, x, y, width, height); + term->window->surface.surf, x, y, width, height); pixman_region32_union_rect( &buf->dirty, &buf->dirty, 0, y, buf->width, height); } @@ -2947,7 +2947,7 @@ grid_render(struct terminal *term) int width = term->width - term->margins.left - term->margins.right; int height = (term->rows - first_dirty_row) * term->cell_height; - wl_surface_damage_buffer(term->window->surface, x, y, width, height); + wl_surface_damage_buffer(term->window->surface.surf, x, y, width, height); pixman_region32_union_rect(&buf->dirty, &buf->dirty, 0, y, buf->width, height); } @@ -3014,7 +3014,7 @@ grid_render(struct terminal *term) xassert(term->grid->view >= 0 && term->grid->view < term->grid->num_rows); xassert(term->window->frame_callback == NULL); - term->window->frame_callback = wl_surface_frame(term->window->surface); + term->window->frame_callback = wl_surface_frame(term->window->surface.surf); wl_callback_add_listener(term->window->frame_callback, &frame_listener, term); wayl_win_scale(term->window); @@ -3024,7 +3024,7 @@ grid_render(struct terminal *term) clock_gettime(term->wl->presentation_clock_id, &commit_time); struct wp_presentation_feedback *feedback = wp_presentation_feedback( - term->wl->presentation, term->window->surface); + term->wl->presentation, term->window->surface.surf); if (feedback == NULL) { LOG_WARN("failed to create presentation feedback"); @@ -3048,11 +3048,11 @@ grid_render(struct terminal *term) if (term->conf->tweak.damage_whole_window) { wl_surface_damage_buffer( - term->window->surface, 0, 0, INT32_MAX, INT32_MAX); + term->window->surface.surf, 0, 0, INT32_MAX, INT32_MAX); } - wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0); - wl_surface_commit(term->window->surface); + wl_surface_attach(term->window->surface.surf, buf->wl_buf, 0, 0); + wl_surface_commit(term->window->surface.surf); } static void @@ -3374,18 +3374,18 @@ render_search_box(struct terminal *term) margin / term->scale, max(0, (int32_t)term->height - height - margin) / term->scale); - wayl_surface_scale(term->wl, term->window->search.surf, term->scale); - wl_surface_attach(term->window->search.surf, buf->wl_buf, 0, 0); - wl_surface_damage_buffer(term->window->search.surf, 0, 0, width, height); + wayl_surface_scale(term->wl, term->window->search.surface.surf, term->scale); + wl_surface_attach(term->window->search.surface.surf, buf->wl_buf, 0, 0); + wl_surface_damage_buffer(term->window->search.surface.surf, 0, 0, width, height); struct wl_region *region = wl_compositor_create_region(term->wl->compositor); if (region != NULL) { wl_region_add(region, width - visible_width, 0, visible_width, height); - wl_surface_set_opaque_region(term->window->search.surf, region); + wl_surface_set_opaque_region(term->window->search.surface.surf, region); wl_region_destroy(region); } - wl_surface_commit(term->window->search.surf); + wl_surface_commit(term->window->search.surface.surf); quirk_weston_subsurface_desync_off(term->window->search.sub); #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED @@ -3466,7 +3466,7 @@ render_urls(struct terminal *term) continue; } - struct wl_surface *surf = it->item.surf.surf; + struct wl_surface *surf = it->item.surf.surface.surf; struct wl_subsurface *sub_surf = it->item.surf.sub; if (surf == NULL || sub_surf == NULL) @@ -3601,7 +3601,7 @@ render_urls(struct terminal *term) : term->colors.table[3]; for (size_t i = 0; i < render_count; i++) { - struct wl_surface *surf = info[i].url->surf.surf; + struct wl_surface *surf = info[i].url->surf.surface.surf; struct wl_subsurface *sub_surf = info[i].url->surf.sub; const char32_t *label = info[i].text; @@ -4253,8 +4253,8 @@ render_xcursor_update(struct seat *seat) if (seat->pointer.xcursor == XCURSOR_HIDDEN) { /* Hide cursor */ - wl_surface_attach(seat->pointer.surface, NULL, 0, 0); - wl_surface_commit(seat->pointer.surface); + wl_surface_attach(seat->pointer.surface.surf, NULL, 0, 0); + wl_surface_commit(seat->pointer.surface.surf); return; } @@ -4263,24 +4263,24 @@ render_xcursor_update(struct seat *seat) const float scale = seat->pointer.scale; struct wl_cursor_image *image = seat->pointer.cursor->images[0]; - wayl_surface_scale(seat->wayl, seat->pointer.surface, scale); + wayl_surface_scale(seat->wayl, seat->pointer.surface.surf, scale); wl_surface_attach( - seat->pointer.surface, wl_cursor_image_get_buffer(image), 0, 0); + seat->pointer.surface.surf, wl_cursor_image_get_buffer(image), 0, 0); wl_pointer_set_cursor( seat->wl_pointer, seat->pointer.serial, - seat->pointer.surface, + seat->pointer.surface.surf, image->hotspot_x / scale, image->hotspot_y / scale); wl_surface_damage_buffer( - seat->pointer.surface, 0, 0, INT32_MAX, INT32_MAX); + seat->pointer.surface.surf, 0, 0, INT32_MAX, INT32_MAX); xassert(seat->pointer.xcursor_callback == NULL); - seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface); + seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface.surf); wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat); - wl_surface_commit(seat->pointer.surface); + wl_surface_commit(seat->pointer.surface.surf); } static void diff --git a/terminal.c b/terminal.c index 43ba157b..825e1550 100644 --- a/terminal.c +++ b/terminal.c @@ -3589,23 +3589,23 @@ term_single_shift(struct terminal *term, enum charset_designator idx) enum term_surface term_surface_kind(const struct terminal *term, const struct wl_surface *surface) { - if (likely(surface == term->window->surface)) + if (likely(surface == term->window->surface.surf)) return TERM_SURF_GRID; - else if (surface == term->window->csd.surface[CSD_SURF_TITLE].surf) + else if (surface == term->window->csd.surface[CSD_SURF_TITLE].surface.surf) return TERM_SURF_TITLE; - else if (surface == term->window->csd.surface[CSD_SURF_LEFT].surf) + else if (surface == term->window->csd.surface[CSD_SURF_LEFT].surface.surf) return TERM_SURF_BORDER_LEFT; - else if (surface == term->window->csd.surface[CSD_SURF_RIGHT].surf) + else if (surface == term->window->csd.surface[CSD_SURF_RIGHT].surface.surf) return TERM_SURF_BORDER_RIGHT; - else if (surface == term->window->csd.surface[CSD_SURF_TOP].surf) + else if (surface == term->window->csd.surface[CSD_SURF_TOP].surface.surf) return TERM_SURF_BORDER_TOP; - else if (surface == term->window->csd.surface[CSD_SURF_BOTTOM].surf) + else if (surface == term->window->csd.surface[CSD_SURF_BOTTOM].surface.surf) return TERM_SURF_BORDER_BOTTOM; - else if (surface == term->window->csd.surface[CSD_SURF_MINIMIZE].surf) + else if (surface == term->window->csd.surface[CSD_SURF_MINIMIZE].surface.surf) return TERM_SURF_BUTTON_MINIMIZE; - else if (surface == term->window->csd.surface[CSD_SURF_MAXIMIZE].surf) + else if (surface == term->window->csd.surface[CSD_SURF_MAXIMIZE].surface.surf) return TERM_SURF_BUTTON_MAXIMIZE; - else if (surface == term->window->csd.surface[CSD_SURF_CLOSE].surf) + else if (surface == term->window->csd.surface[CSD_SURF_CLOSE].surface.surf) return TERM_SURF_BUTTON_CLOSE; else return TERM_SURF_NONE; diff --git a/wayland.c b/wayland.c index 9a3d11e8..3b6833c5 100644 --- a/wayland.c +++ b/wayland.c @@ -74,7 +74,7 @@ csd_instantiate(struct wl_window *win) for (size_t i = CSD_SURF_MINIMIZE; i < CSD_SURF_COUNT; i++) { bool ret = wayl_win_subsurface_new_with_custom_parent( - win, win->csd.surface[CSD_SURF_TITLE].surf, &win->csd.surface[i], + win, win->csd.surface[CSD_SURF_TITLE].surface.surf, &win->csd.surface[i], true); xassert(ret); } @@ -187,8 +187,12 @@ seat_destroy(struct seat *seat) if (seat->pointer.theme != NULL) wl_cursor_theme_destroy(seat->pointer.theme); - if (seat->pointer.surface != NULL) - wl_surface_destroy(seat->pointer.surface); + if (seat->pointer.surface.surf != NULL) + wl_surface_destroy(seat->pointer.surface.surf); +#if defined(HAVE_FRACTIONAL_SCALE) + if (seat->pointer.surface.viewport != NULL) + wp_viewport_destroy(seat->pointer.surface.viewport); +#endif if (seat->pointer.xcursor_callback != NULL) wl_callback_destroy(seat->pointer.xcursor_callback); @@ -288,10 +292,10 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, if (caps & WL_SEAT_CAPABILITY_POINTER) { if (seat->wl_pointer == NULL) { - xassert(seat->pointer.surface == NULL); - seat->pointer.surface = wl_compositor_create_surface(seat->wayl->compositor); + xassert(seat->pointer.surface.surf == NULL); + seat->pointer.surface.surf = wl_compositor_create_surface(seat->wayl->compositor); - if (seat->pointer.surface == NULL) { + if (seat->pointer.surface.surf == NULL) { LOG_ERR("%s: failed to create pointer surface", seat->name); return; } @@ -302,13 +306,13 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, } else { if (seat->wl_pointer != NULL) { wl_pointer_release(seat->wl_pointer); - wl_surface_destroy(seat->pointer.surface); + wl_surface_destroy(seat->pointer.surface.surf); if (seat->pointer.theme != NULL) wl_cursor_theme_destroy(seat->pointer.theme); seat->wl_pointer = NULL; - seat->pointer.surface = NULL; + seat->pointer.surface.surf = NULL; seat->pointer.theme = NULL; seat->pointer.cursor = NULL; } @@ -848,7 +852,7 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, * anytime soon. Some compositors require a commit in * combination with an ack - make them happy. */ - wl_surface_commit(win->surface); + wl_surface_commit(win->surface.surf); } if (wasnt_configured) @@ -1225,7 +1229,7 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) if (seat->wl_keyboard != NULL) keyboard_listener.leave( - seat, seat->wl_keyboard, -1, seat->kbd_focus->window->surface); + seat, seat->wl_keyboard, -1, seat->kbd_focus->window->surface.surf); } if (seat->mouse_focus != NULL) { @@ -1235,7 +1239,7 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) if (seat->wl_pointer != NULL) pointer_listener.leave( - seat, seat->wl_pointer, -1, seat->mouse_focus->window->surface); + seat, seat->wl_pointer, -1, seat->mouse_focus->window->surface.surf); } seat_destroy(seat); @@ -1531,29 +1535,29 @@ wayl_win_init(struct terminal *term, const char *token) win->wm_capabilities.maximize = true; win->wm_capabilities.minimize = true; - win->surface = wl_compositor_create_surface(wayl->compositor); - if (win->surface == NULL) { + win->surface.surf = wl_compositor_create_surface(wayl->compositor); + if (win->surface.surf == NULL) { LOG_ERR("failed to create wayland surface"); goto out; } wayl_win_alpha_changed(win); - wl_surface_add_listener(win->surface, &surface_listener, win); + wl_surface_add_listener(win->surface.surf, &surface_listener, win); #if defined(HAVE_FRACTIONAL_SCALE) if (wayl->fractional_scale_manager != NULL && wayl->viewporter != NULL) { - win->viewport = wp_viewporter_get_viewport(wayl->viewporter, win->surface); + win->surface.viewport = wp_viewporter_get_viewport(wayl->viewporter, win->surface.surf); win->fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale( - wayl->fractional_scale_manager, win->surface); + wayl->fractional_scale_manager, win->surface.surf); wp_fractional_scale_v1_add_listener( win->fractional_scale, &fractional_scale_listener, win); } #endif - win->xdg_surface = xdg_wm_base_get_xdg_surface(wayl->shell, win->surface); + win->xdg_surface = xdg_wm_base_get_xdg_surface(wayl->shell, win->surface.surf); xdg_surface_add_listener(win->xdg_surface, &xdg_surface_listener, win); win->xdg_toplevel = xdg_surface_get_toplevel(win->xdg_surface); @@ -1586,12 +1590,12 @@ wayl_win_init(struct terminal *term, const char *token) LOG_WARN("no decoration manager available - using CSDs unconditionally"); } - wl_surface_commit(win->surface); + wl_surface_commit(win->surface.surf); #if defined(HAVE_XDG_ACTIVATION) /* Complete XDG startup notification */ if (token) - xdg_activation_v1_activate(wayl->xdg_activation, token, win->surface); + xdg_activation_v1_activate(wayl->xdg_activation, token, win->surface.surf); #endif if (!wayl_win_subsurface_new(win, &win->overlay, false)) { @@ -1641,33 +1645,33 @@ wayl_win_destroy(struct wl_window *win) * nor mouse focus). */ - if (win->render_timer.surf != NULL) { - wl_surface_attach(win->render_timer.surf, NULL, 0, 0); - wl_surface_commit(win->render_timer.surf); + if (win->render_timer.surface.surf != NULL) { + wl_surface_attach(win->render_timer.surface.surf, NULL, 0, 0); + wl_surface_commit(win->render_timer.surface.surf); } - if (win->scrollback_indicator.surf != NULL) { - wl_surface_attach(win->scrollback_indicator.surf, NULL, 0, 0); - wl_surface_commit(win->scrollback_indicator.surf); + if (win->scrollback_indicator.surface.surf != NULL) { + wl_surface_attach(win->scrollback_indicator.surface.surf, NULL, 0, 0); + wl_surface_commit(win->scrollback_indicator.surface.surf); } /* Scrollback search */ - if (win->search.surf != NULL) { - wl_surface_attach(win->search.surf, NULL, 0, 0); - wl_surface_commit(win->search.surf); + if (win->search.surface.surf != NULL) { + wl_surface_attach(win->search.surface.surf, NULL, 0, 0); + wl_surface_commit(win->search.surface.surf); } /* URLs */ tll_foreach(win->urls, it) { - wl_surface_attach(it->item.surf.surf, NULL, 0, 0); - wl_surface_commit(it->item.surf.surf); + wl_surface_attach(it->item.surf.surface.surf, NULL, 0, 0); + wl_surface_commit(it->item.surf.surface.surf); } /* CSD */ for (size_t i = 0; i < ALEN(win->csd.surface); i++) { - if (win->csd.surface[i].surf != NULL) { - wl_surface_attach(win->csd.surface[i].surf, NULL, 0, 0); - wl_surface_commit(win->csd.surface[i].surf); + if (win->csd.surface[i].surface.surf != NULL) { + wl_surface_attach(win->csd.surface[i].surface.surf, NULL, 0, 0); + wl_surface_commit(win->csd.surface[i].surface.surf); } } @@ -1675,8 +1679,8 @@ wayl_win_destroy(struct wl_window *win) /* Main window */ win->unmapped = true; - wl_surface_attach(win->surface, NULL, 0, 0); - wl_surface_commit(win->surface); + wl_surface_attach(win->surface.surf, NULL, 0, 0); + wl_surface_commit(win->surface.surf); wayl_roundtrip(win->term->wl); tll_free(win->on_outputs); @@ -1710,8 +1714,8 @@ wayl_win_destroy(struct wl_window *win) #if defined(HAVE_FRACTIONAL_SCALE) if (win->fractional_scale != NULL) wp_fractional_scale_v1_destroy(win->fractional_scale); - if (win->viewport != NULL) - wp_viewport_destroy(win->viewport); + if (win->surface.viewport != NULL) + wp_viewport_destroy(win->surface.viewport); #endif if (win->frame_callback != NULL) wl_callback_destroy(win->frame_callback); @@ -1721,8 +1725,8 @@ wayl_win_destroy(struct wl_window *win) xdg_toplevel_destroy(win->xdg_toplevel); if (win->xdg_surface != NULL) xdg_surface_destroy(win->xdg_surface); - if (win->surface != NULL) - wl_surface_destroy(win->surface); + if (win->surface.surf != NULL) + wl_surface_destroy(win->surface.surf); wayl_roundtrip(win->term->wl); @@ -1880,7 +1884,7 @@ wayl_win_scale(struct wl_window *win) const struct wayland *wayl = term->wl; const float scale = term->scale; - wayl_surface_scale(wayl, win->surface, scale); + wayl_surface_scale(wayl, win->surface.surf, scale); } void @@ -1894,11 +1898,11 @@ wayl_win_alpha_changed(struct wl_window *win) if (region != NULL) { wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); - wl_surface_set_opaque_region(win->surface, region); + wl_surface_set_opaque_region(win->surface.surf, region); wl_region_destroy(region); } } else - wl_surface_set_opaque_region(win->surface, NULL); + wl_surface_set_opaque_region(win->surface.surf, NULL); } #if defined(HAVE_XDG_ACTIVATION) @@ -1909,7 +1913,7 @@ activation_token_for_urgency_done(const char *token, void *data) struct wayland *wayl = win->term->wl; win->urgency_token_is_pending = false; - xdg_activation_v1_activate(wayl->xdg_activation, token, win->surface); + xdg_activation_v1_activate(wayl->xdg_activation, token, win->surface.surf); } #endif /* HAVE_XDG_ACTIVATION */ @@ -1954,11 +1958,11 @@ wayl_win_csd_borders_visible(const struct wl_window *win) bool wayl_win_subsurface_new_with_custom_parent( struct wl_window *win, struct wl_surface *parent, - struct wl_surf_subsurf *surf, bool allow_pointer_input) + struct wayl_sub_surface *surf, bool allow_pointer_input) { struct wayland *wayl = win->term->wl; - surf->surf = NULL; + surf->surface.surf = NULL; surf->sub = NULL; struct wl_surface *main_surface @@ -2002,39 +2006,42 @@ wayl_win_subsurface_new_with_custom_parent( wl_region_destroy(empty); } - surf->surf = main_surface; + surf->surface.surf = main_surface; surf->sub = sub; #if defined(HAVE_FRACTIONAL_SCALE) - surf->viewport = viewport; + surf->surface.viewport = viewport; #endif return true; } bool -wayl_win_subsurface_new(struct wl_window *win, struct wl_surf_subsurf *surf, +wayl_win_subsurface_new(struct wl_window *win, struct wayl_sub_surface *surf, bool allow_pointer_input) { return wayl_win_subsurface_new_with_custom_parent( - win, win->surface, surf, allow_pointer_input); + win, win->surface.surf, surf, allow_pointer_input); } void -wayl_win_subsurface_destroy(struct wl_surf_subsurf *surf) +wayl_win_subsurface_destroy(struct wayl_sub_surface *surf) { if (surf == NULL) return; #if defined(HAVE_FRACTIONAL_SCALE) - if (surf->viewport != NULL) - wp_viewport_destroy(surf->viewport); + if (surf->surface.viewport != NULL) { + wp_viewport_destroy(surf->surface.viewport); + surf->surface.viewport = NULL; + } #endif - if (surf->sub != NULL) + if (surf->sub != NULL) { wl_subsurface_destroy(surf->sub); - if (surf->surf != NULL) - wl_surface_destroy(surf->surf); - - surf->surf = NULL; - surf->sub = NULL; + surf->sub = NULL; + } + if (surf->surface.surf != NULL) { + wl_surface_destroy(surf->surface.surf); + surf->surface.surf = NULL; + } } #if defined(HAVE_XDG_ACTIVATION) @@ -2099,7 +2106,7 @@ wayl_get_activation_token( if (seat != NULL && serial != 0) xdg_activation_token_v1_set_serial(token, serial, seat->wl_seat); - xdg_activation_token_v1_set_surface(token, win->surface); + xdg_activation_token_v1_set_surface(token, win->surface.surf); xdg_activation_token_v1_add_listener(token, &activation_token_listener, ctx); xdg_activation_token_v1_commit(token); return true; diff --git a/wayland.h b/wayland.h index bb9bf77f..3ad05d33 100644 --- a/wayland.h +++ b/wayland.h @@ -45,6 +45,18 @@ enum data_offer_mime_type { DATA_OFFER_MIME_TEXT_UTF8_STRING, }; +struct wayl_surface { + struct wl_surface *surf; +#if defined(HAVE_FRACTIONAL_SCALE) + struct wp_viewport *viewport; +#endif +}; + +struct wayl_sub_surface { + struct wayl_surface surface; + struct wl_subsurface *sub; +}; + struct wl_window; struct wl_clipboard { struct wl_window *window; /* For DnD */ @@ -132,7 +144,7 @@ struct seat { struct { uint32_t serial; - struct wl_surface *surface; + struct wayl_surface surface; struct wl_cursor_theme *theme; struct wl_cursor *cursor; float scale; @@ -294,17 +306,9 @@ struct monitor { bool use_output_release; }; -struct wl_surf_subsurf { - struct wl_surface *surf; - struct wl_subsurface *sub; -#if defined(HAVE_FRACTIONAL_SCALE) - struct wp_viewport *viewport; -#endif -}; - struct wl_url { const struct url *url; - struct wl_surf_subsurf surf; + struct wayl_sub_surface surf; }; enum csd_mode {CSD_UNKNOWN, CSD_NO, CSD_YES}; @@ -328,7 +332,7 @@ struct xdg_activation_token_context { struct wayland; struct wl_window { struct terminal *term; - struct wl_surface *surface; + struct wayl_surface surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; #if defined(HAVE_XDG_ACTIVATION) @@ -336,7 +340,6 @@ struct wl_window { bool urgency_token_is_pending; #endif #if defined(HAVE_FRACTIONAL_SCALE) - struct wp_viewport *viewport; struct wp_fractional_scale_v1 *fractional_scale; #endif bool unmapped; @@ -348,7 +351,7 @@ struct wl_window { enum csd_mode csd_mode; struct { - struct wl_surf_subsurf surface[CSD_SURF_COUNT]; + struct wayl_sub_surface surface[CSD_SURF_COUNT]; struct fcft_font *font; int move_timeout_fd; uint32_t serial; @@ -359,10 +362,10 @@ struct wl_window { bool minimize:1; } wm_capabilities; - struct wl_surf_subsurf search; - struct wl_surf_subsurf scrollback_indicator; - struct wl_surf_subsurf render_timer; - struct wl_surf_subsurf overlay; + struct wayl_sub_surface search; + struct wayl_sub_surface scrollback_indicator; + struct wayl_sub_surface render_timer; + struct wayl_sub_surface overlay; struct wl_callback *frame_callback; @@ -465,12 +468,12 @@ bool wayl_win_csd_titlebar_visible(const struct wl_window *win); bool wayl_win_csd_borders_visible(const struct wl_window *win); bool wayl_win_subsurface_new( - struct wl_window *win, struct wl_surf_subsurf *surf, + struct wl_window *win, struct wayl_sub_surface *surf, bool allow_pointer_input); bool wayl_win_subsurface_new_with_custom_parent( struct wl_window *win, struct wl_surface *parent, - struct wl_surf_subsurf *surf, bool allow_pointer_input); -void wayl_win_subsurface_destroy(struct wl_surf_subsurf *surf); + struct wayl_sub_surface *surf, bool allow_pointer_input); +void wayl_win_subsurface_destroy(struct wayl_sub_surface *surf); #if defined(HAVE_XDG_ACTIVATION) bool wayl_get_activation_token( From 434fd6aa1f4a21ef6572a772ec891eeb2006a82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 16:53:16 +0200 Subject: [PATCH 051/135] wayland: refactor: wayl_surface_scale(): pass wayl_surface pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of passing a raw wl_surface pointer, pass a wayl_surface pointer. This is needed later, when using fractional scaling to scale the surface (since then we need the surface’s viewport object). --- render.c | 61 +++++++++++++++++++++++++------------------------------ wayland.c | 6 +++--- wayland.h | 2 +- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/render.c b/render.c index 5ebd69eb..e78f1eeb 100644 --- a/render.c +++ b/render.c @@ -1691,7 +1691,7 @@ render_overlay(struct terminal *term) &(pixman_rectangle16_t){0, 0, term->width, term->height}); quirk_weston_subsurface_desync_on(overlay->sub); - wayl_surface_scale(term->wl, overlay->surface.surf, term->scale); + wayl_surface_scale(term->wl, &overlay->surface, term->scale); wl_subsurface_set_position(overlay->sub, 0, 0); wl_surface_attach(overlay->surface.surf, buf->wl_buf, 0, 0); @@ -1828,12 +1828,12 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx) } static void -csd_commit(struct terminal *term, struct wl_surface *surf, struct buffer *buf) +csd_commit(struct terminal *term, struct wayl_surface *surf, struct buffer *buf) { wayl_surface_scale(term->wl, surf, term->scale); - wl_surface_attach(surf, buf->wl_buf, 0, 0); - wl_surface_damage_buffer(surf, 0, 0, buf->width, buf->height); - wl_surface_commit(surf); + wl_surface_attach(surf->surf, buf->wl_buf, 0, 0); + wl_surface_damage_buffer(surf->surf, 0, 0, buf->width, buf->height); + wl_surface_commit(surf->surf); } static void @@ -1849,8 +1849,7 @@ render_csd_part(struct terminal *term, } static void -render_osd(struct terminal *term, - struct wl_surface *surf, struct wl_subsurface *sub_surf, +render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf, struct fcft_font *font, struct buffer *buf, const char32_t *text, uint32_t _fg, uint32_t _bg, unsigned x, unsigned y) @@ -1923,20 +1922,20 @@ render_osd(struct terminal *term, pixman_image_unref(src); pixman_image_set_clip_region32(buf->pix[0], NULL); - quirk_weston_subsurface_desync_on(sub_surf); - wayl_surface_scale(term->wl, surf, term->scale); - wl_surface_attach(surf, buf->wl_buf, 0, 0); - wl_surface_damage_buffer(surf, 0, 0, buf->width, buf->height); + quirk_weston_subsurface_desync_on(sub_surf->sub); + wayl_surface_scale(term->wl, &sub_surf->surface, term->scale); + wl_surface_attach(sub_surf->surface.surf, buf->wl_buf, 0, 0); + wl_surface_damage_buffer(sub_surf->surface.surf, 0, 0, buf->width, buf->height); struct wl_region *region = wl_compositor_create_region(term->wl->compositor); if (region != NULL) { wl_region_add(region, 0, 0, buf->width, buf->height); - wl_surface_set_opaque_region(surf, region); + wl_surface_set_opaque_region(sub_surf->surface.surf, region); wl_region_destroy(region); } - wl_surface_commit(surf); - quirk_weston_subsurface_desync_off(sub_surf); + wl_surface_commit(sub_surf->surface.surf); + quirk_weston_subsurface_desync_off(sub_surf->sub); } static void @@ -1971,11 +1970,10 @@ render_csd_title(struct terminal *term, const struct csd_data *info, const int margin = M != NULL ? M->advance.x : win->csd.font->max_advance.x; - render_osd(term, surf->surface.surf, surf->sub, win->csd.font, - buf, title_text, fg, bg, margin, + render_osd(term, surf, win->csd.font, buf, title_text, fg, bg, margin, (buf->height - win->csd.font->height) / 2); - csd_commit(term, surf->surface.surf, buf); + csd_commit(term, &surf->surface, buf); free(_title_text); } @@ -1986,14 +1984,14 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, xassert(term->window->csd_mode == CSD_YES); xassert(surf_idx >= CSD_SURF_LEFT && surf_idx <= CSD_SURF_BOTTOM); - struct wl_surface *surf = term->window->csd.surface[surf_idx].surface.surf; + struct wayl_surface *surf = &term->window->csd.surface[surf_idx].surface; if (info->width == 0 || info->height == 0) return; { pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0); - render_csd_part(term, surf, buf, info->width, info->height, &color); + render_csd_part(term, surf->surf, buf, info->width, info->height, &color); } /* @@ -2271,7 +2269,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, xassert(term->window->csd_mode == CSD_YES); xassert(surf_idx >= CSD_SURF_MINIMIZE && surf_idx <= CSD_SURF_CLOSE); - struct wl_surface *surf = term->window->csd.surface[surf_idx].surface.surf; + struct wayl_surface *surf = &term->window->csd.surface[surf_idx].surface; if (info->width == 0 || info->height == 0) return; @@ -2323,7 +2321,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, _color = color_dim(term, _color); pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha); - render_csd_part(term, surf, buf, info->width, info->height, &color); + render_csd_part(term, surf->surf, buf, info->width, info->height, &color); switch (surf_idx) { case CSD_SURF_MINIMIZE: render_csd_button_minimize(term, buf); break; @@ -2534,8 +2532,7 @@ render_scrollback_position(struct terminal *term) render_osd( term, - win->scrollback_indicator.surface.surf, - win->scrollback_indicator.sub, + &win->scrollback_indicator, term->fonts[0], buf, text, fg, 0xffu << 24 | bg, width - margin - c32len(text) * term->cell_width, margin); @@ -2571,8 +2568,7 @@ render_render_timer(struct terminal *term, struct timespec render_time) render_osd( term, - win->render_timer.surface.surf, - win->render_timer.sub, + &win->render_timer, term->fonts[0], buf, text, term->colors.table[0], 0xffu << 24 | term->colors.table[8 + 1], margin, margin); @@ -3374,7 +3370,7 @@ render_search_box(struct terminal *term) margin / term->scale, max(0, (int32_t)term->height - height - margin) / term->scale); - wayl_surface_scale(term->wl, term->window->search.surface.surf, term->scale); + wayl_surface_scale(term->wl, &term->window->search.surface, term->scale); wl_surface_attach(term->window->search.surface.surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(term->window->search.surface.surf, 0, 0, width, height); @@ -3601,23 +3597,22 @@ render_urls(struct terminal *term) : term->colors.table[3]; for (size_t i = 0; i < render_count; i++) { - struct wl_surface *surf = info[i].url->surf.surface.surf; - struct wl_subsurface *sub_surf = info[i].url->surf.sub; + const struct wayl_sub_surface *sub_surf = &info[i].url->surf; const char32_t *label = info[i].text; const int x = info[i].x; const int y = info[i].y; - xassert(surf != NULL); - xassert(sub_surf != NULL); + xassert(sub_surf->surface.surf != NULL); + xassert(sub_surf->sub != NULL); wl_subsurface_set_position( - sub_surf, + sub_surf->sub, (term->margins.left + x) / term->scale, (term->margins.top + y) / term->scale); render_osd( - term, surf, sub_surf, term->fonts[0], bufs[i], label, + term, sub_surf, term->fonts[0], bufs[i], label, fg, 0xffu << 24 | bg, x_margin, y_margin); free(info[i].text); @@ -4263,7 +4258,7 @@ render_xcursor_update(struct seat *seat) const float scale = seat->pointer.scale; struct wl_cursor_image *image = seat->pointer.cursor->images[0]; - wayl_surface_scale(seat->wayl, seat->pointer.surface.surf, scale); + wayl_surface_scale(seat->wayl, &seat->pointer.surface, scale); wl_surface_attach( seat->pointer.surface.surf, wl_cursor_image_get_buffer(image), 0, 0); diff --git a/wayland.c b/wayland.c index 3b6833c5..cd1a4c68 100644 --- a/wayland.c +++ b/wayland.c @@ -1865,7 +1865,7 @@ wayl_fractional_scaling(const struct wayland *wayl) } void -wayl_surface_scale(const struct wayland *wayl, struct wl_surface *surf, +wayl_surface_scale(const struct wayland *wayl, const struct wayl_surface *surf, float scale) { LOG_WARN("scaling by a factor of %.2f (legacy)", scale); @@ -1873,7 +1873,7 @@ wayl_surface_scale(const struct wayland *wayl, struct wl_surface *surf, if (wayl_fractional_scaling(wayl)) { BUG("not yet implemented"); } else { - wl_surface_set_buffer_scale(surf, (int)scale); + wl_surface_set_buffer_scale(surf->surf, (int)scale); } } @@ -1884,7 +1884,7 @@ wayl_win_scale(struct wl_window *win) const struct wayland *wayl = term->wl; const float scale = term->scale; - wayl_surface_scale(wayl, win->surface.surf, scale); + wayl_surface_scale(wayl, &win->surface, scale); } void diff --git a/wayland.h b/wayland.h index 3ad05d33..0506f82b 100644 --- a/wayland.h +++ b/wayland.h @@ -455,7 +455,7 @@ void wayl_roundtrip(struct wayland *wayl); bool wayl_fractional_scaling(const struct wayland *wayl); void wayl_surface_scale( - const struct wayland *wayl, struct wl_surface *surf, float scale); + const struct wayland *wayl, const struct wayl_surface *surf, float scale); struct wl_window *wayl_win_init(struct terminal *term, const char *token); void wayl_win_destroy(struct wl_window *win); From 5a60bbc119397c2993fc1895bae75c2c1e69fe1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 17:05:16 +0200 Subject: [PATCH 052/135] wayland: refactor: add a buffer argument to wayl_*_scale() functions This will be needed later, when using fractional scaling + viewporter to scale. --- render.c | 28 +++++++++++++++++----------- wayland.c | 17 +++++++++++++---- wayland.h | 9 +++++++-- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/render.c b/render.c index e78f1eeb..72d87bff 100644 --- a/render.c +++ b/render.c @@ -1691,7 +1691,8 @@ render_overlay(struct terminal *term) &(pixman_rectangle16_t){0, 0, term->width, term->height}); quirk_weston_subsurface_desync_on(overlay->sub); - wayl_surface_scale(term->wl, &overlay->surface, term->scale); + wayl_surface_scale( + term->wl, &overlay->surface, buf, term->scale); wl_subsurface_set_position(overlay->sub, 0, 0); wl_surface_attach(overlay->surface.surf, buf->wl_buf, 0, 0); @@ -1830,7 +1831,7 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx) static void csd_commit(struct terminal *term, struct wayl_surface *surf, struct buffer *buf) { - wayl_surface_scale(term->wl, surf, term->scale); + wayl_surface_scale(term->wl, surf, buf, term->scale); wl_surface_attach(surf->surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(surf->surf, 0, 0, buf->width, buf->height); wl_surface_commit(surf->surf); @@ -1923,7 +1924,7 @@ render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf, pixman_image_set_clip_region32(buf->pix[0], NULL); quirk_weston_subsurface_desync_on(sub_surf->sub); - wayl_surface_scale(term->wl, &sub_surf->surface, term->scale); + wayl_surface_scale(term->wl, &sub_surf->surface, buf, term->scale); wl_surface_attach(sub_surf->surface.surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(sub_surf->surface.surf, 0, 0, buf->width, buf->height); @@ -3013,7 +3014,7 @@ grid_render(struct terminal *term) term->window->frame_callback = wl_surface_frame(term->window->surface.surf); wl_callback_add_listener(term->window->frame_callback, &frame_listener, term); - wayl_win_scale(term->window); + wayl_win_scale(term->window, buf); if (term->wl->presentation != NULL && term->conf->presentation_timings) { struct timespec commit_time; @@ -3370,7 +3371,7 @@ render_search_box(struct terminal *term) margin / term->scale, max(0, (int32_t)term->height - height - margin) / term->scale); - wayl_surface_scale(term->wl, &term->window->search.surface, term->scale); + wayl_surface_scale(term->wl, &term->window->search.surface, buf, term->scale); wl_surface_attach(term->window->search.surface.surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(term->window->search.surface.surf, 0, 0, width, height); @@ -3843,9 +3844,13 @@ maybe_resize(struct terminal *term, int width, int height, bool force) return false; float scale = -1; - tll_foreach(term->window->on_outputs, it) { - if (it->item->scale > scale) - scale = it->item->scale; + if (wayl_fractional_scaling(term->wl)) { + scale = term->window->scale; + } else { + tll_foreach(term->window->on_outputs, it) { + if (it->item->scale > scale) + scale = it->item->scale; + } } if (scale < 0.) { @@ -4257,11 +4262,12 @@ render_xcursor_update(struct seat *seat) const float scale = seat->pointer.scale; struct wl_cursor_image *image = seat->pointer.cursor->images[0]; + struct wl_buffer *buf = wl_cursor_image_get_buffer(image); - wayl_surface_scale(seat->wayl, &seat->pointer.surface, scale); + wayl_surface_scale_explicit_width_height( + seat->wayl, &seat->pointer.surface, image->width, image->height, scale); - wl_surface_attach( - seat->pointer.surface.surf, wl_cursor_image_get_buffer(image), 0, 0); + wl_surface_attach(seat->pointer.surface.surf, buf, 0, 0); wl_pointer_set_cursor( seat->wl_pointer, seat->pointer.serial, diff --git a/wayland.c b/wayland.c index cd1a4c68..43f85066 100644 --- a/wayland.c +++ b/wayland.c @@ -1865,8 +1865,9 @@ wayl_fractional_scaling(const struct wayland *wayl) } void -wayl_surface_scale(const struct wayland *wayl, const struct wayl_surface *surf, - float scale) +wayl_surface_scale_explicit_width_height( + const struct wayland *wayl, const struct wayl_surface *surf, + int width, int height, float scale) { LOG_WARN("scaling by a factor of %.2f (legacy)", scale); @@ -1878,13 +1879,21 @@ wayl_surface_scale(const struct wayland *wayl, const struct wayl_surface *surf, } void -wayl_win_scale(struct wl_window *win) +wayl_surface_scale(const struct wayland *wayl, const struct wayl_surface *surf, + const struct buffer *buf, float scale) +{ + wayl_surface_scale_explicit_width_height( + wayl, surf, buf->width, buf->height, scale); +} + +void +wayl_win_scale(struct wl_window *win, const struct buffer *buf) { const struct terminal *term = win->term; const struct wayland *wayl = term->wl; const float scale = term->scale; - wayl_surface_scale(wayl, &win->surface, scale); + wayl_surface_scale(wayl, &win->surface, buf, scale); } void diff --git a/wayland.h b/wayland.h index 0506f82b..9736ea4d 100644 --- a/wayland.h +++ b/wayland.h @@ -32,6 +32,7 @@ /* Forward declarations */ struct terminal; +struct buffer; /* Mime-types we support when dealing with data offers (e.g. copy-paste, or DnD) */ enum data_offer_mime_type { @@ -455,12 +456,16 @@ void wayl_roundtrip(struct wayland *wayl); bool wayl_fractional_scaling(const struct wayland *wayl); void wayl_surface_scale( - const struct wayland *wayl, const struct wayl_surface *surf, float scale); + const struct wayland *wayl, const struct wayl_surface *surf, + const struct buffer *buf, float scale); +void wayl_surface_scale_explicit_width_height( + const struct wayland *wayl, const struct wayl_surface *surf, + int width, int height, float scale); struct wl_window *wayl_win_init(struct terminal *term, const char *token); void wayl_win_destroy(struct wl_window *win); -void wayl_win_scale(struct wl_window *win); +void wayl_win_scale(struct wl_window *win, const struct buffer *buf); void wayl_win_alpha_changed(struct wl_window *win); bool wayl_win_set_urgent(struct wl_window *win); From e5989d81b931afb31ac0ac3df9269841adef2d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 17:31:14 +0200 Subject: [PATCH 053/135] wayland: instantiate+destroy viewport for pointer surface --- wayland.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/wayland.c b/wayland.c index 43f85066..6dea027d 100644 --- a/wayland.c +++ b/wayland.c @@ -293,13 +293,27 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, if (caps & WL_SEAT_CAPABILITY_POINTER) { if (seat->wl_pointer == NULL) { xassert(seat->pointer.surface.surf == NULL); - seat->pointer.surface.surf = wl_compositor_create_surface(seat->wayl->compositor); + seat->pointer.surface.surf = + wl_compositor_create_surface(seat->wayl->compositor); if (seat->pointer.surface.surf == NULL) { LOG_ERR("%s: failed to create pointer surface", seat->name); return; } +#if defined(HAVE_FRACTIONAL_SCALE) + xassert(seat->pointer.surface.viewport == NULL); + seat->pointer.surface.viewport = wp_viewporter_get_viewport( + seat->wayl->viewporter, seat->pointer.surface.surf); + + if (seat->pointer.surface.viewport == NULL) { + LOG_ERR("%s: failed to create pointer viewport", seat->name); + wl_surface_destroy(seat->pointer.surface.surf); + seat->pointer.surface.surf = NULL; + return; + } +#endif + seat->wl_pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); } @@ -308,6 +322,11 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, wl_pointer_release(seat->wl_pointer); wl_surface_destroy(seat->pointer.surface.surf); +#if defined(HAVE_FRACTIONAL_SCALE) + wp_viewport_destroy(seat->pointer.surface.viewport); + seat->pointer.surface.viewport = NULL; +#endif + if (seat->pointer.theme != NULL) wl_cursor_theme_destroy(seat->pointer.theme); From 36818459e51d99bdb9f48bf0b7a9471c4b8fe67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 17:31:39 +0200 Subject: [PATCH 054/135] wayland: initialize window scale to -1 --- wayland.c | 1 + 1 file changed, 1 insertion(+) diff --git a/wayland.c b/wayland.c index 6dea027d..a45171ff 100644 --- a/wayland.c +++ b/wayland.c @@ -1550,6 +1550,7 @@ wayl_win_init(struct terminal *term, const char *token) win->csd_mode = CSD_UNKNOWN; win->csd.move_timeout_fd = -1; win->resize_timeout_fd = -1; + win->scale = -1.; win->wm_capabilities.maximize = true; win->wm_capabilities.minimize = true; From 8ccabb79745f079417b3a049f52c4779d6efdf07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 17:32:01 +0200 Subject: [PATCH 055/135] wayland: surface_scale(): implement fractional scaling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is done by setting the surface’s viewport destination --- wayland.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/wayland.c b/wayland.c index a45171ff..fce6a67f 100644 --- a/wayland.c +++ b/wayland.c @@ -1889,11 +1889,20 @@ wayl_surface_scale_explicit_width_height( const struct wayland *wayl, const struct wayl_surface *surf, int width, int height, float scale) { - LOG_WARN("scaling by a factor of %.2f (legacy)", scale); if (wayl_fractional_scaling(wayl)) { - BUG("not yet implemented"); +#if defined(HAVE_FRACTIONAL_SCALE) + LOG_DBG("scaling by a factor of %.2f (fractional scaling)", scale); + wp_viewport_set_destination( + surf->viewport, + round((float)width / scale), + round((float)height / scale)); +#else + BUG("wayl_fraction_scaling() returned true, " + "but fractional scaling was not available at compile time"). +#endif } else { + LOG_DBG("scaling by a factor of %.2f (legacy)", scale); wl_surface_set_buffer_scale(surf->surf, (int)scale); } } From 0bdb6580bd5f470ca82e771c70a8f7cd4f5a0240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 17:32:33 +0200 Subject: [PATCH 056/135] wayland: update terminal when preferred scaling factor changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the window’s preferred scaling factor is changed (through the fractional-scaling protocol), update the terminal; resize font, resize sub-surfaces etc. --- wayland.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wayland.c b/wayland.c index fce6a67f..683338c0 100644 --- a/wayland.c +++ b/wayland.c @@ -1526,7 +1526,9 @@ static void fractional_scale_preferred_scale( { struct wl_window *win = data; win->scale = (float)scale / 120.; + LOG_DBG("fractional scale: %.3f", win->scale); + update_term_for_output_change(win->term); } static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { From 32b8c5c9b6628f3dd1b9f25f9b13a6d016de86cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 17:34:20 +0200 Subject: [PATCH 057/135] changelog: mention the newly added support for fractional-scaling-v1 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 943dd411..c3e66216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,9 @@ is `auto`, which will select `libutempter` on Linux, `ulog` on FreeBSD, and `none` for all others. * Sixel aspect ratio. +* Support for the new fractional-scaling-v1 Wayland protocol. This + brings true fractional scaling to Wayland in general, and with this + release, foot. ### Changed From 64b6b5d2a7b81bcad3f0a50a6c1556fd7300c171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 17:55:04 +0200 Subject: [PATCH 058/135] =?UTF-8?q?config:=20dpi-aware:=20remove=20?= =?UTF-8?q?=E2=80=98auto=E2=80=99=20value,=20and=20default=20to=20?= =?UTF-8?q?=E2=80=98no=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now default to scaling fonts using the scaling factor, not monitor DPI. The ‘auto’ value for dpi-aware has been removed. Documentation (man pages and README) have been updated to reflect the new default. --- README.md | 52 +++++++++++++++++++++++++++++++++------------ config.c | 15 +++---------- config.h | 2 +- doc/foot.ini.5.scd | 10 ++------- foot.ini | 2 +- terminal.c | 38 +-------------------------------- tests/test-config.c | 12 +---------- wayland.c | 5 ----- 8 files changed, 48 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 8d037af3..0d6262dc 100644 --- a/README.md +++ b/README.md @@ -411,27 +411,53 @@ This is not how it is meant to be. Fonts are measured in _point sizes_ **for a reason**; a given point size should have the same height on all mediums, be it printers or monitors, regardless of their DPI. -Foot’s default behavior is to use the monitor’s DPI to size fonts when -output scaling has been disabled on **all** monitors. If at least one -monitor has output scaling enabled, fonts will instead by sized using -the scaling factor. +That said, on Wayland, Hi-DPI monitors are typically handled by +configuring a _"scaling factor"_ in the compositor. This is usually +expressed as either a rational value (e.g. _1.5_), or as a percentage +(e.g. _150%_), by which all fonts and window sizes are supposed to be +multiplied. -This can be changed to either **always** use the monitor’s DPI -(regardless of scaling factor), or to **never** use it, with the -`dpi-aware` option in `foot.ini`. See the man page, **foot.ini**(5) -for more information. +For this reason, and because of the new _fractional scaling_ protocol +(see below for details), and because this is how Wayland applications +are expected to behave, foot >= 1.15 will default to scaling fonts +using the compositor’s scaling factor, and **not** the monitor +DPI. -When fonts are sized using the monitor’s DPI, glyphs should always -have the same physical height, regardless of monitor. +This means the (assuming the monitors are at the same viewing +distance) the font size will appear to change when you move the foot +window across different monitors, **unless** you have configured the +monitors’ scaling factors correctly in the compositor. -Furthermore, foot will re-size the fonts on-the-fly when the window is -moved between screens with different DPIs values. If the window covers -multiple screens, with different DPIs, the highest DPI will be used. +This can be changed by setting the `dpi-aware` option to `yes` in +`foot.ini`. When enabled, fonts will **not** be sized using the +scaling factor, but will instead be sized using the monitor’s +DPI. When the foot window is moved across monitors, the font size is +updated for the current monitor’s DPI. + +This means that, assuming the monitors are **at the same viewing +distance**, the font size will appear to be the same, at all times. _Note_: if you configure **pixelsize**, rather than **size**, then DPI changes will **not** change the font size. Pixels are always pixels. +### Fractional scaling on Wayland + +For a long time, there was no **true** support for _fractional +scaling_. That is, values like 1.5 (150%), 1.8 (180%) etc, only +integer values, like 2 (200%). + +Compositors that _did_ support fractional scaling did so using a hack; +all applications were told to scale to 200%, and then the compositor +would down-scale the rendered image to e.g. 150%. This works OK for +everything **except fonts**, which ended up blurry. + +With _wayland-protocols 1.32_, a new protocol was introduced, that +allows compositors to tell applications the _actual_ scaling +factor. Applications can then scale the image using a _viewport_ +object, instead of setting a scale factor on the raw pixel buffer. + + ## Supported OSCs OSC, _Operating System Command_, are escape sequences that interacts diff --git a/config.c b/config.c index aba85db3..3d02355f 100644 --- a/config.c +++ b/config.c @@ -972,17 +972,8 @@ parse_section_main(struct context *ctx) else if (strcmp(key, "underline-thickness") == 0) return value_to_pt_or_px(ctx, &conf->underline_thickness); - else if (strcmp(key, "dpi-aware") == 0) { - if (strcmp(value, "auto") == 0) - conf->dpi_aware = DPI_AWARE_AUTO; - else { - bool value; - if (!value_to_bool(ctx, &value)) - return false; - conf->dpi_aware = value ? DPI_AWARE_YES : DPI_AWARE_NO; - } - return true; - } + else if (strcmp(key, "dpi-aware") == 0) + return value_to_bool(ctx, &conf->dpi_aware); else if (strcmp(key, "workers") == 0) return value_to_uint16(ctx, 10, &conf->render_worker_count); @@ -2939,7 +2930,7 @@ config_load(struct config *conf, const char *conf_path, .use_custom_underline_offset = false, .box_drawings_uses_font_glyphs = false, .underline_thickness = {.pt = 0., .px = -1}, - .dpi_aware = DPI_AWARE_AUTO, /* DPI-aware when scaling-factor == 1 */ + .dpi_aware = false, .bell = { .urgent = false, .notify = false, diff --git a/config.h b/config.h index ce1ee536..2034752f 100644 --- a/config.h +++ b/config.h @@ -137,7 +137,7 @@ struct config { enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode; - enum {DPI_AWARE_AUTO, DPI_AWARE_YES, DPI_AWARE_NO} dpi_aware; + bool dpi_aware; struct config_font_list fonts[4]; struct font_size_adjustment font_size_adjustment; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index f9174fc6..e28cf416 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -185,7 +185,7 @@ empty string to be set, but it must be quoted: *KEY=""*) Default: _no_. *dpi-aware* - *auto*, *yes*, or *no*. + Boolean. When set to *yes*, fonts are sized using the monitor's DPI, making a font of a given size have the same physical size, regardless of @@ -199,12 +199,6 @@ empty string to be set, but it must be quoted: *KEY=""*) instead sized using the monitor's scaling factor; doubling the scaling factor *does* double the font size. - Finally, if set to *auto*, fonts will be sized using the monitor's - DPI if _all_ monitors have a scaling factor of 1. If at least one - monitor as a scaling factor larger than 1 (regardless of whether - the foot window is mapped on that monitor or not), fonts will be - scaled using the scaling factor. - Note that this option typically does not work with bitmap fonts, which only contains a pre-defined set of sizes, and cannot be dynamically scaled. Whichever size (of the available ones) that @@ -217,7 +211,7 @@ empty string to be set, but it must be quoted: *KEY=""*) to size the font (*dpi-aware=no*), the font's pixel size will be multiplied with the scaling factor. - Default: _auto_ + Default: _no_ *pad* Padding between border and glyphs, in pixels (subject to output diff --git a/foot.ini b/foot.ini index a4b91ef7..61e88aec 100644 --- a/foot.ini +++ b/foot.ini @@ -20,7 +20,7 @@ # underline-offset= # underline-thickness= # box-drawings-uses-font-glyphs=no -# dpi-aware=auto +# dpi-aware=no # initial-window-size-pixels=700x500 # Or, # initial-window-size-chars= diff --git a/terminal.c b/terminal.c index 825e1550..090cbb20 100644 --- a/terminal.c +++ b/terminal.c @@ -912,42 +912,6 @@ get_font_subpixel(const struct terminal *term) return FCFT_SUBPIXEL_DEFAULT; } -static bool -term_font_size_by_dpi(const struct terminal *term) -{ - switch (term->conf->dpi_aware) { - case DPI_AWARE_YES: return true; - case DPI_AWARE_NO: return false; - - case DPI_AWARE_AUTO: - /* - * Scale using DPI if all monitors have a scaling factor or 1. - * - * The idea is this: if a user, with multiple monitors, have - * enabled scaling on at least one monitor, then he/she has - * most likely done so to match the size of his/hers other - * monitors. - * - * I.e. if the user has one monitor with a scaling factor of - * one, and another with a scaling factor of two, he/she - * expects things to be twice as large on the second - * monitor. - * - * If we (foot) scale using DPI on the first monitor, and - * using the scaling factor on the second monitor, foot will - * *not* look twice as big on the second monitor. - */ - tll_foreach(term->wl->monitors, it) { - const struct monitor *mon = &it->item; - if (mon->scale > 1) - return false; - } - return true; - } - - BUG("unhandled DPI awareness value"); -} - int term_pt_or_px_as_pixels(const struct terminal *term, const struct pt_or_px *pt_or_px) @@ -2158,7 +2122,7 @@ term_font_dpi_changed(struct terminal *term, int old_scale) xassert(term->scale > 0); bool was_scaled_using_dpi = term->font_is_sized_by_dpi; - bool will_scale_using_dpi = term_font_size_by_dpi(term); + bool will_scale_using_dpi = term->conf->dpi_aware; bool need_font_reload = was_scaled_using_dpi != will_scale_using_dpi || diff --git a/tests/test-config.c b/tests/test-config.c index b14f28b1..c70f7a43 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -511,6 +511,7 @@ test_section_main(void) test_boolean(&ctx, &parse_section_main, "box-drawings-uses-font-glyphs", &conf.box_drawings_uses_font_glyphs); test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title); test_boolean(&ctx, &parse_section_main, "notify-focus-inhibit", &conf.notify_focus_inhibit); + test_boolean(&ctx, &parse_section_main, "dpi-aware", &conf.dpi_aware); test_pt_or_px(&ctx, &parse_section_main, "font-size-adjustment", &conf.font_size_adjustment.pt_or_px); /* TODO: test ‘N%’ values too */ test_pt_or_px(&ctx, &parse_section_main, "line-height", &conf.line_height); @@ -524,17 +525,6 @@ test_section_main(void) test_spawn_template(&ctx, &parse_section_main, "notify", &conf.notify); - test_enum( - &ctx, &parse_section_main, "dpi-aware", - 9, - (const char *[]){"on", "true", "yes", "1", - "off", "false", "no", "0", - "auto"}, - (int []){DPI_AWARE_YES, DPI_AWARE_YES, DPI_AWARE_YES, DPI_AWARE_YES, - DPI_AWARE_NO, DPI_AWARE_NO, DPI_AWARE_NO, DPI_AWARE_NO, - DPI_AWARE_AUTO}, - (int *)&conf.dpi_aware); - test_enum(&ctx, &parse_section_main, "selection-target", 4, (const char *[]){"none", "primary", "clipboard", "both"}, diff --git a/wayland.c b/wayland.c index 683338c0..0f5cad2b 100644 --- a/wayland.c +++ b/wayland.c @@ -373,11 +373,6 @@ update_terms_on_monitor(struct monitor *mon) tll_foreach(wayl->terms, it) { struct terminal *term = it->item; - if (term->conf->dpi_aware == DPI_AWARE_AUTO) { - update_term_for_output_change(term); - continue; - } - tll_foreach(term->window->on_outputs, it2) { if (it2->item == mon) { update_term_for_output_change(term); From 27a92b11588203c90c0ec4c61a1a6c7a741b2935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 17:55:59 +0200 Subject: [PATCH 059/135] =?UTF-8?q?changelog:=20dpi-aware=E2=80=99s=20defa?= =?UTF-8?q?ult=20value=20is=20now=20=E2=80=98no=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e66216..ff63610e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,8 @@ is explicitly added. * Default sixel aspect ratio is now 2:1 instead of 1:1. * Sixel images are no longer cropped to the last non-transparent row. +* `dpi-aware` now defaults to `no`, and the `auto` value has been + removed. [1371]: https://codeberg.org/dnkl/foot/pulls/1371 @@ -92,6 +94,10 @@ ### Removed + +* `auto` value for the `dpi-aware` option. + + ### Fixed * Incorrect icon in dock and window switcher on Gnome ([#1317][1317]) From 9db92bd942706d6d0ca0250d6011c49ec88f4051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 18:00:01 +0200 Subject: [PATCH 060/135] feature: add a feature flag (for --version) for fractional scaling --- client.c | 4 +++- foot-features.h | 9 +++++++++ main.c | 4 +++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/client.c b/client.c index 6954d17e..84bfb2c3 100644 --- a/client.c +++ b/client.c @@ -66,11 +66,13 @@ static const char * version_and_features(void) { static char buf[256]; - snprintf(buf, sizeof(buf), "version: %s %cpgo %cime %cgraphemes %cassertions", + snprintf(buf, sizeof(buf), + "version: %s %cpgo %cime %cgraphemes %cfractional-scaling %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', + feature_fractional_scaling() ? '+' : ':', feature_assertions() ? '+' : '-'); return buf; } diff --git a/foot-features.h b/foot-features.h index ad447767..77923aaf 100644 --- a/foot-features.h +++ b/foot-features.h @@ -37,3 +37,12 @@ static inline bool feature_graphemes(void) return false; #endif } + +static inline bool feature_fractional_scaling(void) +{ +#if defined(HAVE_FRACTIONAL_SCALE) + return true; +#else + return false; +#endif +} diff --git a/main.c b/main.c index f58e170f..6dd9e468 100644 --- a/main.c +++ b/main.c @@ -52,11 +52,13 @@ static const char * version_and_features(void) { static char buf[256]; - snprintf(buf, sizeof(buf), "version: %s %cpgo %cime %cgraphemes %cassertions", + snprintf(buf, sizeof(buf), + "version: %s %cpgo %cime %cgraphemes %cfractional-scaling %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', + feature_fractional_scaling() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } From 8a4efb34276e9f15a65304477c40e71aa281f7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 18:37:49 +0200 Subject: [PATCH 061/135] =?UTF-8?q?wayland:=20warn=20when=20fractional=20s?= =?UTF-8?q?caling=20isn=E2=80=99t=20available?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wayland.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/wayland.c b/wayland.c index 0f5cad2b..28bb2b6f 100644 --- a/wayland.c +++ b/wayland.c @@ -1386,6 +1386,14 @@ wayl_init(struct fdm *fdm, struct key_binding_manager *key_binding_manager, "bell.urgent will fall back to coloring the window margins red"); } +#if defined(HAVE_FRACTIONAL_SCALE) + if (wayl->fractional_scale_manager == NULL || wayl->viewporter == NULL) { +#else + if (true) { +#endif + LOG_WARN("fractional scaling not available"); + } + if (presentation_timings && wayl->presentation == NULL) { LOG_ERR("presentation time interface not implemented by compositor"); goto out; From c309c9f572a331881c515d7715d474ef854ea958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 20:25:16 +0200 Subject: [PATCH 062/135] wayland: surface_scale(): assert surface width/height is a multiple of scale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When doing legacy scaling (non-fractional scaling), assert the surface’s width and height are multiples of the (integer) scale. --- wayland.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wayland.c b/wayland.c index 28bb2b6f..ce0e879f 100644 --- a/wayland.c +++ b/wayland.c @@ -1908,6 +1908,13 @@ wayl_surface_scale_explicit_width_height( #endif } else { LOG_DBG("scaling by a factor of %.2f (legacy)", scale); + + xassert(scale == floor(scale)); + + const int iscale = (int)scale; + xassert(width % iscale == 0); + xassert(height % iscale == 0); + wl_surface_set_buffer_scale(surf->surf, (int)scale); } } From d71e588800fea0f7b7815cc5ce51ded752070b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 21:06:47 +0200 Subject: [PATCH 063/135] wayland: refactor: surface_scale(): pass wl_window pointer, instead of wayland global --- render.c | 11 ++++++----- wayland.c | 11 +++++------ wayland.h | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/render.c b/render.c index 72d87bff..11b5223a 100644 --- a/render.c +++ b/render.c @@ -1692,7 +1692,7 @@ render_overlay(struct terminal *term) quirk_weston_subsurface_desync_on(overlay->sub); wayl_surface_scale( - term->wl, &overlay->surface, buf, term->scale); + term->window, &overlay->surface, buf, term->scale); wl_subsurface_set_position(overlay->sub, 0, 0); wl_surface_attach(overlay->surface.surf, buf->wl_buf, 0, 0); @@ -1831,7 +1831,7 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx) static void csd_commit(struct terminal *term, struct wayl_surface *surf, struct buffer *buf) { - wayl_surface_scale(term->wl, surf, buf, term->scale); + wayl_surface_scale(term->window, surf, buf, term->scale); wl_surface_attach(surf->surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(surf->surf, 0, 0, buf->width, buf->height); wl_surface_commit(surf->surf); @@ -1924,7 +1924,7 @@ render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf, pixman_image_set_clip_region32(buf->pix[0], NULL); quirk_weston_subsurface_desync_on(sub_surf->sub); - wayl_surface_scale(term->wl, &sub_surf->surface, buf, term->scale); + wayl_surface_scale(term->window, &sub_surf->surface, buf, term->scale); wl_surface_attach(sub_surf->surface.surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(sub_surf->surface.surf, 0, 0, buf->width, buf->height); @@ -3371,7 +3371,7 @@ render_search_box(struct terminal *term) margin / term->scale, max(0, (int32_t)term->height - height - margin) / term->scale); - wayl_surface_scale(term->wl, &term->window->search.surface, buf, term->scale); + wayl_surface_scale(term->window, &term->window->search.surface, buf, term->scale); wl_surface_attach(term->window->search.surface.surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(term->window->search.surface.surf, 0, 0, width, height); @@ -4265,7 +4265,8 @@ render_xcursor_update(struct seat *seat) struct wl_buffer *buf = wl_cursor_image_get_buffer(image); wayl_surface_scale_explicit_width_height( - seat->wayl, &seat->pointer.surface, image->width, image->height, scale); + seat->mouse_focus->window, + &seat->pointer.surface, image->width, image->height, scale); wl_surface_attach(seat->pointer.surface.surf, buf, 0, 0); diff --git a/wayland.c b/wayland.c index ce0e879f..aa1b298c 100644 --- a/wayland.c +++ b/wayland.c @@ -1891,7 +1891,7 @@ wayl_fractional_scaling(const struct wayland *wayl) void wayl_surface_scale_explicit_width_height( - const struct wayland *wayl, const struct wayl_surface *surf, + const struct wl_window *win, const struct wayl_surface *surf, int width, int height, float scale) { @@ -1915,26 +1915,25 @@ wayl_surface_scale_explicit_width_height( xassert(width % iscale == 0); xassert(height % iscale == 0); - wl_surface_set_buffer_scale(surf->surf, (int)scale); + wl_surface_set_buffer_scale(surf->surf, iscale); } } void -wayl_surface_scale(const struct wayland *wayl, const struct wayl_surface *surf, +wayl_surface_scale(const struct wl_window *win, const struct wayl_surface *surf, const struct buffer *buf, float scale) { wayl_surface_scale_explicit_width_height( - wayl, surf, buf->width, buf->height, scale); + win, surf, buf->width, buf->height, scale); } void wayl_win_scale(struct wl_window *win, const struct buffer *buf) { const struct terminal *term = win->term; - const struct wayland *wayl = term->wl; const float scale = term->scale; - wayl_surface_scale(wayl, &win->surface, buf, scale); + wayl_surface_scale(win, &win->surface, buf, scale); } void diff --git a/wayland.h b/wayland.h index 9736ea4d..7305ade7 100644 --- a/wayland.h +++ b/wayland.h @@ -456,10 +456,10 @@ void wayl_roundtrip(struct wayland *wayl); bool wayl_fractional_scaling(const struct wayland *wayl); void wayl_surface_scale( - const struct wayland *wayl, const struct wayl_surface *surf, + const struct wl_window *win, const struct wayl_surface *surf, const struct buffer *buf, float scale); void wayl_surface_scale_explicit_width_height( - const struct wayland *wayl, const struct wayl_surface *surf, + const struct wl_window *win, const struct wayl_surface *surf, int width, int height, float scale); struct wl_window *wayl_win_init(struct terminal *term, const char *token); From 8f74b1090a6a5297ac7b6710420528ff56ed03d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 21:07:24 +0200 Subject: [PATCH 064/135] wayland: use legacy scaling until fractional_scale::preferred_scale() has been called This way, the initial frame is more likely to get scaled correctly; foot will guess the initial (integer) scale from the available monitors, and use that. By using legacy scaling, we force the compositor to down-scale the image to the correct scale factor. If we use the new fraction scaling method with an integer scaling factor, the initial frame gets rendered way too big. --- wayland.c | 3 ++- wayland.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/wayland.c b/wayland.c index aa1b298c..bf8f2682 100644 --- a/wayland.c +++ b/wayland.c @@ -1529,6 +1529,7 @@ static void fractional_scale_preferred_scale( { struct wl_window *win = data; win->scale = (float)scale / 120.; + win->have_preferred_scale = true; LOG_DBG("fractional scale: %.3f", win->scale); update_term_for_output_change(win->term); @@ -1895,7 +1896,7 @@ wayl_surface_scale_explicit_width_height( int width, int height, float scale) { - if (wayl_fractional_scaling(wayl)) { + if (wayl_fractional_scaling(win->term->wl) && win->have_preferred_scale) { #if defined(HAVE_FRACTIONAL_SCALE) LOG_DBG("scaling by a factor of %.2f (fractional scaling)", scale); wp_viewport_set_destination( diff --git a/wayland.h b/wayland.h index 7305ade7..e2d22031 100644 --- a/wayland.h +++ b/wayland.h @@ -346,6 +346,7 @@ struct wl_window { bool unmapped; float scale; + bool have_preferred_scale; struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration; From c61247f317b8b0559feeda551e8e7e33b54d3b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 21:09:18 +0200 Subject: [PATCH 065/135] wayland: surface_scale(): improve debug logging --- wayland.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wayland.c b/wayland.c index bf8f2682..cff432bb 100644 --- a/wayland.c +++ b/wayland.c @@ -1898,7 +1898,9 @@ wayl_surface_scale_explicit_width_height( if (wayl_fractional_scaling(win->term->wl) && win->have_preferred_scale) { #if defined(HAVE_FRACTIONAL_SCALE) - LOG_DBG("scaling by a factor of %.2f (fractional scaling)", scale); + LOG_DBG("scaling by a factor of %.2f using fractional scaling " + "(width=%d, height=%d) ", scale, width, height); + wp_viewport_set_destination( surf->viewport, round((float)width / scale), @@ -1908,7 +1910,8 @@ wayl_surface_scale_explicit_width_height( "but fractional scaling was not available at compile time"). #endif } else { - LOG_DBG("scaling by a factor of %.2f (legacy)", scale); + LOG_DBG("scaling by a factor of %.2f using legacy mode " + "(width=%d, height=%d)", scale, width, height); xassert(scale == floor(scale)); From ce31cc518a48d978c84448424d0d2a6bba4a5a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 26 Jun 2023 21:09:30 +0200 Subject: [PATCH 066/135] wayland: surface_scale(): reset buffer scale when using fractional scaling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the first frame uses legacy scaling, the surface may have a buffer scale > 1, which isn’t allowed. --- wayland.c | 1 + 1 file changed, 1 insertion(+) diff --git a/wayland.c b/wayland.c index cff432bb..c9693200 100644 --- a/wayland.c +++ b/wayland.c @@ -1901,6 +1901,7 @@ wayl_surface_scale_explicit_width_height( LOG_DBG("scaling by a factor of %.2f using fractional scaling " "(width=%d, height=%d) ", scale, width, height); + wl_surface_set_buffer_scale(surf->surf, 1); wp_viewport_set_destination( surf->viewport, round((float)width / scale), From 3555e81fee4a069bf4886007082f676b8777f6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 14:25:55 +0200 Subject: [PATCH 067/135] sixel: special case parsing of images with an aspect ratio of 1:1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Images with an aspect ratio of 1:1 are by far the most common (though not the default). It makes a lot of sense, performance wise, to special case them. Specifically, the sixel_add() function benefits greatly from this, as it is the inner most, most heavily executed function when parsing a sixel image. sixel_add_many() also benefits, since allows us to drop a multiplication. Since sixel_add_many() always called first (no other call sites call sixel_add() directly), this has a noticeable effect on performance. Another thing that helps (though not as much), and not specifically with AR 1:1 images, is special casing DECGRI a bit. Up until now, it simply updated the current sixel parameter value. The problem is that the default parameter value is 0. But, a value of 0 should be treated as 1. By adding a special ‘repeat_count’ variable to the sixel struct, we can initialize it to ‘1’ when we see DECGRI, and then simply overwrite it as the parameter value gets updated. This allows us to drop an if..else when emitting the sixel. --- dcs.c | 3 +- sixel.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++------- sixel.h | 5 +- terminal.h | 1 + 4 files changed, 153 insertions(+), 27 deletions(-) diff --git a/dcs.c b/dcs.c index fb4a14b6..7ce1a868 100644 --- a/dcs.c +++ b/dcs.c @@ -427,8 +427,7 @@ dcs_hook(struct terminal *term, uint8_t final) int p2 = vt_param_get(term, 1,0); int p3 = vt_param_get(term, 2, 0); - sixel_init(term, p1, p2, p3); - term->vt.dcs.put_handler = &sixel_put; + term->vt.dcs.put_handler = sixel_init(term, p1, p2, p3); term->vt.dcs.unhook_handler = &sixel_unhook; break; } diff --git a/sixel.c b/sixel.c index b0746a9a..e454032c 100644 --- a/sixel.c +++ b/sixel.c @@ -16,6 +16,9 @@ static size_t count; +static void sixel_put_generic(struct terminal *term, uint8_t c); +static void sixel_put_ar_11(struct terminal *term, uint8_t c); + void sixel_fini(struct terminal *term) { @@ -24,7 +27,7 @@ sixel_fini(struct terminal *term) free(term->sixel.shared_palette); } -void +sixel_put sixel_init(struct terminal *term, int p1, int p2, int p3) { /* @@ -119,6 +122,7 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) : bg; count = 0; + return pan == 1 && pad == 1 ? &sixel_put_ar_11 : &sixel_put_generic; } void @@ -1327,7 +1331,8 @@ resize(struct terminal *term, int new_width, int new_height) } static void -sixel_add(struct terminal *term, int col, int width, uint32_t color, uint8_t sixel) +sixel_add_generic(struct terminal *term, int col, int width, uint32_t color, + uint8_t sixel) { xassert(term->sixel.pos.col < term->sixel.image.width); xassert(term->sixel.pos.row < term->sixel.image.height); @@ -1348,7 +1353,26 @@ sixel_add(struct terminal *term, int col, int width, uint32_t color, uint8_t six } static void -sixel_add_many(struct terminal *term, uint8_t c, unsigned count) +sixel_add_ar_11(struct terminal *term, int col, int width, uint32_t color, + uint8_t sixel) +{ + xassert(term->sixel.pos.col < term->sixel.image.width); + xassert(term->sixel.pos.row < term->sixel.image.height); + xassert(term->sixel.pan == 1); + + size_t ofs = term->sixel.row_byte_ofs + col; + uint32_t *data = &term->sixel.image.data[ofs]; + + for (int i = 0; i < 6; i++, sixel >>= 1, data += width) { + if (sixel & 1) + *data = color; + } + + xassert(sixel == 0); +} + +static void +sixel_add_many_generic(struct terminal *term, uint8_t c, unsigned count) { int col = term->sixel.pos.col; int width = term->sixel.image.width; @@ -1362,14 +1386,38 @@ sixel_add_many(struct terminal *term, uint8_t c, unsigned count) } uint32_t color = term->sixel.color; - for (unsigned i = 0; i < count; i++, col++) - sixel_add(term, col, width, color, c); + for (unsigned i = 0; i < count; i++, col++) { + /* TODO: is it worth dynamically dispatching to either generic or AR-11? */ + sixel_add_generic(term, col, width, color, c); + } term->sixel.pos.col = col; } static void -decsixel(struct terminal *term, uint8_t c) +sixel_add_many_ar_11(struct terminal *term, uint8_t c, unsigned count) +{ + xassert(term->sixel.pan == 1); + xassert(term->sixel.pad == 1); + + int col = term->sixel.pos.col; + int width = term->sixel.image.width; + + if (unlikely(col + count - 1 >= width)) { + resize_horizontally(term, col + count); + width = term->sixel.image.width; + count = min(count, max(width - col, 0)); + } + + uint32_t color = term->sixel.color; + for (unsigned i = 0; i < count; i++, col++) + sixel_add_ar_11(term, col, width, color, c); + + term->sixel.pos.col = col; +} + +static void +decsixel_generic(struct terminal *term, uint8_t c) { switch (c) { case '"': @@ -1382,6 +1430,7 @@ decsixel(struct terminal *term, uint8_t c) term->sixel.state = SIXEL_DECGRI; term->sixel.param = 0; term->sixel.param_idx = 0; + term->sixel.repeat_count = 1; break; case '#': @@ -1424,7 +1473,7 @@ decsixel(struct terminal *term, uint8_t c) case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': - sixel_add_many(term, c - 63, 1); + sixel_add_many_generic(term, c - 63, 1); break; case ' ': @@ -1438,6 +1487,29 @@ decsixel(struct terminal *term, uint8_t c) } } +static void +decsixel_ar_11(struct terminal *term, uint8_t c) +{ + switch (c) { + case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': + case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': + case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': + case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': + case '~': + sixel_add_many_ar_11(term, c - 63, 1); + break; + + default: + decsixel_generic(term, c); + break; + } +} + static void decgra(struct terminal *term, uint8_t c) { @@ -1483,21 +1555,34 @@ decgra(struct terminal *term, uint8_t c) } term->sixel.state = SIXEL_DECSIXEL; - decsixel(term, c); + + /* Update DCS put handler, since pan/pad may have changed */ + term->vt.dcs.put_handler = pan == 1 && pad == 1 + ? &sixel_put_ar_11 + : &sixel_put_generic; + + if (likely(pan == 1 && pad == 1)) + decsixel_ar_11(term, c); + else + decsixel_generic(term, c); + break; } } } static void -decgri(struct terminal *term, uint8_t c) +decgri_generic(struct terminal *term, uint8_t c) { switch (c) { case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - term->sixel.param *= 10; - term->sixel.param += c - '0'; + case '5': case '6': case '7': case '8': case '9': { + unsigned param = term->sixel.param; + param *= 10; + param += c - '0'; + term->sixel.repeat_count = term->sixel.param = param; break; + } case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': @@ -1509,18 +1594,41 @@ decgri(struct terminal *term, uint8_t c) case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': { - unsigned count = term->sixel.param; - if (likely(count > 0)) - sixel_add_many(term, c - 63, count); - else if (unlikely(count == 0)) - sixel_add_many(term, c - 63, 1); + const unsigned count = term->sixel.repeat_count; + sixel_add_many_generic(term, c - 63, count); term->sixel.state = SIXEL_DECSIXEL; break; } default: term->sixel.state = SIXEL_DECSIXEL; - sixel_put(term, c); + term->vt.dcs.put_handler(term, c); + break; + } +} + +static void +decgri_ar_11(struct terminal *term, uint8_t c) +{ + switch (c) { + case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': + case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': + case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': + case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': + case '~': { + const unsigned count = term->sixel.repeat_count; + sixel_add_many_ar_11(term, c - 63, count); + term->sixel.state = SIXEL_DECSIXEL; + break; + } + + default: + decgri_generic(term, c); break; } } @@ -1601,19 +1709,36 @@ decgci(struct terminal *term, uint8_t c) term->sixel.color = term->sixel.palette[term->sixel.color_idx]; term->sixel.state = SIXEL_DECSIXEL; - decsixel(term, c); + + if (likely(term->sixel.pan == 1 && term->sixel.pad == 1)) + decsixel_ar_11(term, c); + else + decsixel_generic(term, c); break; } } } -void -sixel_put(struct terminal *term, uint8_t c) +static void +sixel_put_generic(struct terminal *term, uint8_t c) { switch (term->sixel.state) { - case SIXEL_DECSIXEL: decsixel(term, c); break; + case SIXEL_DECSIXEL: decsixel_generic(term, c); break; case SIXEL_DECGRA: decgra(term, c); break; - case SIXEL_DECGRI: decgri(term, c); break; + case SIXEL_DECGRI: decgri_generic(term, c); break; + case SIXEL_DECGCI: decgci(term, c); break; + } + + count++; +} + +static void +sixel_put_ar_11(struct terminal *term, uint8_t c) +{ + switch (term->sixel.state) { + case SIXEL_DECSIXEL: decsixel_ar_11(term, c); break; + case SIXEL_DECGRA: decgra(term, c); break; + case SIXEL_DECGRI: decgri_ar_11(term, c); break; case SIXEL_DECGCI: decgci(term, c); break; } diff --git a/sixel.h b/sixel.h index f72b4dc4..efd64908 100644 --- a/sixel.h +++ b/sixel.h @@ -6,10 +6,11 @@ #define SIXEL_MAX_WIDTH 10000u #define SIXEL_MAX_HEIGHT 10000u +typedef void (*sixel_put)(struct terminal *term, uint8_t c); + void sixel_fini(struct terminal *term); -void sixel_init(struct terminal *term, int p1, int p2, int p3); -void sixel_put(struct terminal *term, uint8_t c); +sixel_put sixel_init(struct terminal *term, int p1, int p2, int p3); void sixel_unhook(struct terminal *term); void sixel_destroy(struct sixel *sixel); diff --git a/terminal.h b/terminal.h index 220070e1..1cb62686 100644 --- a/terminal.h +++ b/terminal.h @@ -649,6 +649,7 @@ struct terminal { unsigned params[5]; /* Collected parameters, for RASTER, COLOR_SPEC */ unsigned param; /* Currently collecting parameter, for RASTER, COLOR_SPEC and REPEAT */ unsigned param_idx; /* Parameters seen */ + unsigned repeat_count; bool transparent_bg; uint32_t default_bg; From 75f9bed6b6fc7bc97927d70e9e7df75dcea621c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 15:39:11 +0200 Subject: [PATCH 068/135] sixel: refactor: shorten very verbose switch case statements --- sixel.c | 67 ++++++++++++--------------------------------------------- 1 file changed, 14 insertions(+), 53 deletions(-) diff --git a/sixel.c b/sixel.c index e454032c..ec0cbc5a 100644 --- a/sixel.c +++ b/sixel.c @@ -1416,6 +1416,8 @@ sixel_add_many_ar_11(struct terminal *term, uint8_t c, unsigned count) term->sixel.pos.col = col; } +IGNORE_WARNING("-Wpedantic") + static void decsixel_generic(struct terminal *term, uint8_t c) { @@ -1463,16 +1465,7 @@ decsixel_generic(struct terminal *term, uint8_t c) } break; - case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': - case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': - case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': - case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': - case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': - case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': - case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': - case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': - case '~': + case '?' ... '~': sixel_add_many_generic(term, c - 63, 1); break; @@ -1487,27 +1480,15 @@ decsixel_generic(struct terminal *term, uint8_t c) } } +UNIGNORE_WARNINGS + static void decsixel_ar_11(struct terminal *term, uint8_t c) { - switch (c) { - case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': - case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': - case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': - case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': - case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': - case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': - case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': - case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': - case '~': + if (likely(c >= '?' && c <= '~')) sixel_add_many_ar_11(term, c - 63, 1); - break; - - default: + else decsixel_generic(term, c); - break; - } } static void @@ -1571,6 +1552,8 @@ decgra(struct terminal *term, uint8_t c) } } +IGNORE_WARNING("-Wpedantic") + static void decgri_generic(struct terminal *term, uint8_t c) { @@ -1584,16 +1567,7 @@ decgri_generic(struct terminal *term, uint8_t c) break; } - case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': - case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': - case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': - case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': - case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': - case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': - case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': - case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': - case '~': { + case '?' ... '~': { const unsigned count = term->sixel.repeat_count; sixel_add_many_generic(term, c - 63, count); term->sixel.state = SIXEL_DECSIXEL; @@ -1607,30 +1581,17 @@ decgri_generic(struct terminal *term, uint8_t c) } } +UNIGNORE_WARNINGS + static void decgri_ar_11(struct terminal *term, uint8_t c) { - switch (c) { - case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': - case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': - case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': - case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': - case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': - case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': - case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': - case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': - case '~': { + if (likely(c >= '?' && c <= '~')) { const unsigned count = term->sixel.repeat_count; sixel_add_many_ar_11(term, c - 63, count); term->sixel.state = SIXEL_DECSIXEL; - break; - } - - default: + } else decgri_generic(term, c); - break; - } } static void From fc46087ce9c451acded2692bcb9064ba7a716e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 15:49:47 +0200 Subject: [PATCH 069/135] scripts: generate-alt-random: set P2=1 when emitting sixels P2=1 means "empty sixels remain at their current color". This is usually the case with modern sixel encoders. --- scripts/generate-alt-random-writes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 812b0213..789d64e0 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -207,8 +207,8 @@ def main(): six_height, six_width = last_size six_rows = (six_height + 5) // 6 # Round up; each sixel is 6 pixels - # Begin sixel - out.write('\033Pq') + # Begin sixel (with P2=1 - empty sixels are transparent) + out.write('\033P;1q') # Sixel size. Without this, sixels will be # auto-resized on cell-boundaries. From 5e9d68695c05d4be03ac7b83dd0eabc42bb7fefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 16:21:26 +0200 Subject: [PATCH 070/135] sixel: add_ar_11(): manually unroll loop This generates both smaller, and faster code --- sixel.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/sixel.c b/sixel.c index ec0cbc5a..31a55a69 100644 --- a/sixel.c +++ b/sixel.c @@ -1360,15 +1360,26 @@ sixel_add_ar_11(struct terminal *term, int col, int width, uint32_t color, xassert(term->sixel.pos.row < term->sixel.image.height); xassert(term->sixel.pan == 1); - size_t ofs = term->sixel.row_byte_ofs + col; + const size_t ofs = term->sixel.row_byte_ofs + col; uint32_t *data = &term->sixel.image.data[ofs]; - for (int i = 0; i < 6; i++, sixel >>= 1, data += width) { - if (sixel & 1) - *data = color; - } - - xassert(sixel == 0); + if (sixel & 0x01) + *data = color; + data += width; + if (sixel & 0x02) + *data = color; + data += width; + if (sixel & 0x04) + *data = color; + data += width; + if (sixel & 0x08) + *data = color; + data += width; + if (sixel & 0x10) + *data = color; + data += width; + if (sixel & 0x20) + *data = color; } static void From 5e305fa8547bbdeef765813ca4a1c29037248ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Jun 2023 08:24:02 +0200 Subject: [PATCH 071/135] =?UTF-8?q?wayland:=20typo:=20=E2=80=98.=E2=80=99?= =?UTF-8?q?=20->=20=E2=80=98;=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1392 --- wayland.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wayland.c b/wayland.c index c9693200..406aba6d 100644 --- a/wayland.c +++ b/wayland.c @@ -1908,7 +1908,7 @@ wayl_surface_scale_explicit_width_height( round((float)height / scale)); #else BUG("wayl_fraction_scaling() returned true, " - "but fractional scaling was not available at compile time"). + "but fractional scaling was not available at compile time"); #endif } else { LOG_DBG("scaling by a factor of %.2f using legacy mode " From 7a37e6891f898e6a26177d8cdd83863bd2671758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Jun 2023 08:28:20 +0200 Subject: [PATCH 072/135] meson: log availability of optional wayland protocols --- meson.build | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/meson.build b/meson.build index 3bad7ab2..6e219caf 100644 --- a/meson.build +++ b/meson.build @@ -157,11 +157,17 @@ wl_proto_xml = [ if wayland_protocols.version().version_compare('>=1.21') add_project_arguments('-DHAVE_XDG_ACTIVATION', language: 'c') wl_proto_xml += [wayland_protocols_datadir + '/staging/xdg-activation/xdg-activation-v1.xml'] + xdg_activation = true +else + xdg_activation = false endif if wayland_protocols.version().version_compare('>=1.31') add_project_arguments('-DHAVE_FRACTIONAL_SCALE', language: 'c') wl_proto_xml += [wayland_protocols_datadir + '/stable/viewporter/viewporter.xml'] wl_proto_xml += [wayland_protocols_datadir + '/staging/fractional-scale/fractional-scale-v1.xml'] + fractional_scale = true +else + fractional_scale = false endif foreach prot : wl_proto_xml @@ -372,6 +378,8 @@ summary( 'Themes': get_option('themes'), 'IME': get_option('ime'), 'Grapheme clustering': utf8proc.found(), + 'Wayland: xdg-activation-v1': xdg_activation, + 'Wayland: fractional-scale-v1': fractional_scale, 'utmp backend': utmp_backend, 'utmp helper default path': utmp_default_helper_path, 'Build terminfo': tic.found(), From 49fb0cf359bb2486bbdf58df62c66c890d7e31cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Jun 2023 14:49:54 +0200 Subject: [PATCH 073/135] sixel: re-scale images when the cell dimensions change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this patch, when the cell dimensions changed (i.e. when the font size changes), sixel images were either removed (the new cell dimensions are smaller than the old), or simply kept at their original size (new cell dimensions are larger). With this patch, sixels are instead resized. This means a sixel *always* occupies the same number of rows and columns, regardless of how much the font size is changed. This is done by maintaining two sets of image data and pixman images, as well as their dimensions. These two sets are the new ‘original’ and ‘scaled’ members of the sixel struct. The "top-level" pixman image pointer, and the ‘width’ and ‘height’ members either point to the "original", or the "scaled" version. They are invalidated as soon as the cell dimensions change. They, and the ‘scaled’ image is updated on-demand (when we need to render a sixel). Note that the ‘scaled’ image is always NULL when the current cell dimensions matches the ones used when emitting the sixel (to save run-time memory). Closes #1383 --- CHANGELOG.md | 4 +- grid.c | 69 ++++++++++--- render.c | 5 + sixel.c | 266 +++++++++++++++++++++++++++++++++++++-------------- sixel.h | 1 + terminal.c | 26 +---- terminal.h | 36 ++++++- 7 files changed, 295 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff63610e..09b47f09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,12 +80,14 @@ is explicitly added. * Default sixel aspect ratio is now 2:1 instead of 1:1. * Sixel images are no longer cropped to the last non-transparent row. +* Sixel images are now re-scaled when the font size is changed + ([#1383][1383]). * `dpi-aware` now defaults to `no`, and the `auto` value has been removed. - [1371]: https://codeberg.org/dnkl/foot/pulls/1371 [1360]: https://codeberg.org/dnkl/foot/issues/1360 +[1383]: https://codeberg.org/dnkl/foot/issues/1383 ### Deprecated diff --git a/grid.c b/grid.c index e1c4d28b..22d2a89a 100644 --- a/grid.c +++ b/grid.c @@ -255,27 +255,68 @@ grid_snapshot(const struct grid *grid) } tll_foreach(grid->sixel_images, it) { - int width = it->item.width; - int height = it->item.height; - pixman_image_t *pix = it->item.pix; - pixman_format_code_t pix_fmt = pixman_image_get_format(pix); - int stride = stride_for_format_and_width(pix_fmt, width); + int original_width = it->item.original.width; + int original_height = it->item.original.height; + pixman_image_t *original_pix = it->item.original.pix; + pixman_format_code_t original_pix_fmt = pixman_image_get_format(original_pix); + int original_stride = stride_for_format_and_width(original_pix_fmt, original_width); - size_t size = stride * height; - void *new_data = xmalloc(size); - memcpy(new_data, it->item.data, size); + size_t original_size = original_stride * original_height; + void *new_original_data = xmalloc(original_size); + memcpy(new_original_data, it->item.original.data, original_size); - pixman_image_t *new_pix = pixman_image_create_bits_no_clear( - pix_fmt, width, height, new_data, stride); + pixman_image_t *new_original_pix = pixman_image_create_bits_no_clear( + original_pix_fmt, original_width, original_height, + new_original_data, original_stride); + + void *new_scaled_data = NULL; + pixman_image_t *new_scaled_pix = NULL; + int scaled_width = -1; + int scaled_height = -1; + + if (it->item.scaled.data != NULL) { + scaled_width = it->item.scaled.width; + scaled_height = it->item.scaled.height; + + pixman_image_t *scaled_pix = it->item.scaled.pix; + pixman_format_code_t scaled_pix_fmt = pixman_image_get_format(scaled_pix); + int scaled_stride = stride_for_format_and_width(scaled_pix_fmt, scaled_width); + + size_t scaled_size = scaled_stride * scaled_height; + new_scaled_data = xmalloc(scaled_size); + memcpy(new_scaled_data, it->item.scaled.data, scaled_size); + + new_scaled_pix = pixman_image_create_bits_no_clear( + scaled_pix_fmt, scaled_width, scaled_height, new_scaled_data, + scaled_stride); + } struct sixel six = { - .data = new_data, - .pix = new_pix, - .width = width, - .height = height, + .pix = (it->item.pix == it->item.original.pix + ? new_original_pix + : (it->item.pix == it->item.scaled.pix + ? new_scaled_pix + : NULL)), + .width = it->item.width, + .height = it->item.height, .rows = it->item.rows, .cols = it->item.cols, .pos = it->item.pos, + .opaque = it->item.opaque, + .cell_width = it->item.cell_width, + .cell_height = it->item.cell_height, + .original = { + .data = new_original_data, + .pix = new_original_pix, + .width = original_width, + .height = original_height, + }, + .scaled = { + .data = new_scaled_data, + .pix = new_scaled_pix, + .width = scaled_width, + .height = scaled_height, + }, }; tll_push_back(clone->sixel_images, six); diff --git a/render.c b/render.c index 11b5223a..4498290e 100644 --- a/render.c +++ b/render.c @@ -1160,6 +1160,10 @@ static void render_sixel(struct terminal *term, pixman_image_t *pix, const struct coord *cursor, const struct sixel *sixel) { + xassert(sixel->pix != NULL); + xassert(sixel->width >= 0); + xassert(sixel->height >= 0); + const int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1); const bool last_row_needs_erase = sixel->height % term->cell_height != 0; const bool last_col_needs_erase = sixel->width % term->cell_width != 0; @@ -1324,6 +1328,7 @@ render_sixel_images(struct terminal *term, pixman_image_t *pix, break; } + sixel_sync_cache(term, &it->item); render_sixel(term, pix, cursor, &it->item); } } diff --git a/sixel.c b/sixel.c index 31a55a69..c7c04b0d 100644 --- a/sixel.c +++ b/sixel.c @@ -125,15 +125,34 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) return pan == 1 && pad == 1 ? &sixel_put_ar_11 : &sixel_put_generic; } +static void +sixel_invalidate_cache(struct sixel *sixel) +{ + if (sixel->scaled.pix != NULL) + pixman_image_unref(sixel->scaled.pix); + + free(sixel->scaled.data); + sixel->scaled.pix = NULL; + sixel->scaled.data = NULL; + sixel->scaled.width = -1; + sixel->scaled.height = -1; + + sixel->pix = NULL; + sixel->width = -1; + sixel->height = -1; +} + void sixel_destroy(struct sixel *sixel) { - if (sixel->pix != NULL) - pixman_image_unref(sixel->pix); + sixel_invalidate_cache(sixel); - free(sixel->data); - sixel->pix = NULL; - sixel->data = NULL; + if (sixel->original.pix != NULL) + pixman_image_unref(sixel->original.pix); + + free(sixel->original.data); + sixel->original.pix = NULL; + sixel->original.data = NULL; } void @@ -396,10 +415,14 @@ blend_new_image_over_old(const struct terminal *term, xassert(pix != NULL); xassert(opaque != NULL); - const int six_ofs_x = six->pos.col * term->cell_width; - const int six_ofs_y = six->pos.row * term->cell_height; - const int img_ofs_x = col * term->cell_width; - const int img_ofs_y = row * term->cell_height; + /* + * TODO: handle images being emitted with different cell dimensions + */ + + const int six_ofs_x = six->pos.col * six->cell_width; + const int six_ofs_y = six->pos.row * six->cell_height; + const int img_ofs_x = col * six->cell_width; + const int img_ofs_y = row * six->cell_height; const int img_width = pixman_image_get_width(*pix); const int img_height = pixman_image_get_height(*pix); @@ -429,7 +452,7 @@ blend_new_image_over_old(const struct terminal *term, */ pixman_image_composite32( PIXMAN_OP_OVER_REVERSE, - six->pix, NULL, *pix, + six->original.pix, NULL, *pix, box->x1 - six_ofs_x, box->y1 - six_ofs_y, 0, 0, box->x1 - img_ofs_x, box->y1 - img_ofs_y, @@ -446,15 +469,15 @@ blend_new_image_over_old(const struct terminal *term, * old image, or the next cell boundary, whichever comes * first. */ - int bounding_x = six_ofs_x + six->width > img_ofs_x + img_width + int bounding_x = six_ofs_x + six->original.width > img_ofs_x + img_width ? min( - six_ofs_x + six->width, - (box->x2 + term->cell_width - 1) / term->cell_width * term->cell_width) + six_ofs_x + six->original.width, + (box->x2 + six->cell_width - 1) / six->cell_width * six->cell_width) : box->x2; - int bounding_y = six_ofs_y + six->height > img_ofs_y + img_height + int bounding_y = six_ofs_y + six->original.height > img_ofs_y + img_height ? min( - six_ofs_y + six->height, - (box->y2 + term->cell_height - 1) / term->cell_height * term->cell_height) + six_ofs_y + six->original.height, + (box->y2 + six->cell_height - 1) / six->cell_height * six->cell_height) : box->y2; /* The required size of the new image */ @@ -494,7 +517,7 @@ blend_new_image_over_old(const struct terminal *term, /* Copy the bottom tile of the old sixel image into the new pixmap */ pixman_image_composite32( PIXMAN_OP_SRC, - six->pix, NULL, pix2, + six->original.pix, NULL, pix2, box->x1 - six_ofs_x, box->y2 - six_ofs_y, 0, 0, box->x1 - img_ofs_x, box->y2 - img_ofs_y, @@ -503,7 +526,7 @@ blend_new_image_over_old(const struct terminal *term, /* Copy the right tile of the old sixel image into the new pixmap */ pixman_image_composite32( PIXMAN_OP_SRC, - six->pix, NULL, pix2, + six->original.pix, NULL, pix2, box->x2 - six_ofs_x, box->y1 - six_ofs_y, 0, 0, box->x2 - img_ofs_x, box->y1 - img_ofs_y, @@ -577,14 +600,14 @@ sixel_overwrite(struct terminal *term, struct sixel *six, pixman_region32_t six_rect; pixman_region32_init_rect( &six_rect, - six->pos.col * term->cell_width, six->pos.row * term->cell_height, - six->width, six->height); + six->pos.col * six->cell_width, six->pos.row * six->cell_height, + six->original.width, six->original.height); pixman_region32_t overwrite_rect; pixman_region32_init_rect( &overwrite_rect, - col * term->cell_width, row * term->cell_height, - width * term->cell_width, height * term->cell_height); + col * six->cell_width, row * six->cell_height, + width * six->cell_width, height * six->cell_height); #if defined(_DEBUG) pixman_region32_t cell_intersection; @@ -597,7 +620,6 @@ sixel_overwrite(struct terminal *term, struct sixel *six, if (pix != NULL) blend_new_image_over_old(term, six, &six_rect, row, col, pix, opaque); - pixman_region32_t diff; pixman_region32_init(&diff); pixman_region32_subtract(&diff, &six_rect, &overwrite_rect); @@ -612,12 +634,12 @@ sixel_overwrite(struct terminal *term, struct sixel *six, LOG_DBG("box #%d: x1=%d, y1=%d, x2=%d, y2=%d", i, boxes[i].x1, boxes[i].y1, boxes[i].x2, boxes[i].y2); - xassert(boxes[i].x1 % term->cell_width == 0); - xassert(boxes[i].y1 % term->cell_height == 0); + xassert(boxes[i].x1 % six->cell_width == 0); + xassert(boxes[i].y1 % six->cell_height == 0); /* New image's position, in cells */ - const int new_col = boxes[i].x1 / term->cell_width; - const int new_row = boxes[i].y1 / term->cell_height; + const int new_col = boxes[i].x1 / six->cell_width; + const int new_row = boxes[i].y1 / six->cell_height; xassert(new_row < term->grid->num_rows); @@ -626,17 +648,17 @@ sixel_overwrite(struct terminal *term, struct sixel *six, const int new_height = boxes[i].y2 - boxes[i].y1; uint32_t *new_data = xmalloc(new_width * new_height * sizeof(uint32_t)); - const uint32_t *old_data = six->data; + const uint32_t *old_data = six->original.data; /* Pixel offsets into old image backing memory */ - const int x_ofs = boxes[i].x1 - six->pos.col * term->cell_width; - const int y_ofs = boxes[i].y1 - six->pos.row * term->cell_height; + const int x_ofs = boxes[i].x1 - six->pos.col * six->cell_width; + const int y_ofs = boxes[i].y1 - six->pos.row * six->cell_height; /* Copy image data, one row at a time */ for (size_t j = 0; j < new_height; j++) { memcpy( &new_data[(0 + j) * new_width], - &old_data[(y_ofs + j) * six->width + x_ofs], + &old_data[(y_ofs + j) * six->original.width + x_ofs], new_width * sizeof(uint32_t)); } @@ -645,14 +667,27 @@ sixel_overwrite(struct terminal *term, struct sixel *six, new_width, new_height, new_data, new_width * sizeof(uint32_t)); struct sixel new_six = { - .data = new_data, - .pix = new_pix, - .width = new_width, - .height = new_height, + .pix = NULL, + .width = -1, + .height = -1, .pos = {.col = new_col, .row = new_row}, - .cols = (new_width + term->cell_width - 1) / term->cell_width, - .rows = (new_height + term->cell_height - 1) / term->cell_height, + .cols = (new_width + six->cell_width - 1) / six->cell_width, + .rows = (new_height + six->cell_height - 1) / six->cell_height, .opaque = six->opaque, + .cell_width = six->cell_width, + .cell_height = six->cell_height, + .original = { + .data = new_data, + .pix = new_pix, + .width = new_width, + .height = new_height, + }, + .scaled = { + .data = NULL, + .pix = NULL, + .width = -1, + .height = -1, + }, }; #if defined(_DEBUG) @@ -847,23 +882,94 @@ sixel_overwrite_at_cursor(struct terminal *term, int width) void sixel_cell_size_changed(struct terminal *term) { - struct grid *g = term->grid; + tll_foreach(term->normal.sixel_images, it) + sixel_invalidate_cache(&it->item); - term->grid = &term->normal; - tll_foreach(term->normal.sixel_images, it) { - struct sixel *six = &it->item; - six->rows = (six->height + term->cell_height - 1) / term->cell_height; - six->cols = (six->width + term->cell_width - 1) / term->cell_width; + tll_foreach(term->alt.sixel_images, it) + sixel_invalidate_cache(&it->item); +} + +void +sixel_sync_cache(const struct terminal *term, struct sixel *six) +{ + if (six->pix != NULL) { +#if defined(_DEBUG) + if (six->cell_width == term->cell_width && + six->cell_height == term->cell_height) + { + xassert(six->pix == six->original.pix); + xassert(six->width == six->original.width); + xassert(six->height == six->original.height); + + xassert(six->scaled.data == NULL); + xassert(six->scaled.pix == NULL); + xassert(six->scaled.width < 0); + xassert(six->scaled.height < 0); + } else { + xassert(six->pix == six->scaled.pix); + xassert(six->width == six->scaled.width); + xassert(six->height == six->scaled.height); + + xassert(six->scaled.data != NULL); + xassert(six->scaled.pix != NULL); + + /* TODO: check ratio */ + xassert(six->scaled.width >= 0); + xassert(six->scaled.height >= 0); + } +#endif + return; } - term->grid = &term->alt; - tll_foreach(term->alt.sixel_images, it) { - struct sixel *six = &it->item; - six->rows = (six->height + term->cell_height - 1) / term->cell_height; - six->cols = (six->width + term->cell_width - 1) / term->cell_width; - } + /* Cache should be invalid */ + xassert(six->scaled.data == NULL); + xassert(six->scaled.pix == NULL); + xassert(six->scaled.width < 0); + xassert(six->scaled.height < 0); - term->grid = g; + if (six->cell_width == term->cell_width && + six->cell_height == term->cell_height) + { + six->pix = six->original.pix; + six->width = six->original.width; + six->height = six->original.height; + } else { + const double width_ratio = (double)term->cell_width / six->cell_width; + const double height_ratio = (double)term->cell_height / six->cell_height; + + struct pixman_f_transform scale; + pixman_f_transform_init_scale( + &scale, 1. / width_ratio, 1. / height_ratio); + + struct pixman_transform _scale; + pixman_transform_from_pixman_f_transform(&_scale, &scale); + pixman_image_set_transform(six->original.pix, &_scale); + pixman_image_set_filter(six->original.pix, PIXMAN_FILTER_BILINEAR, NULL, 0); + + int scaled_width = (double)six->original.width * width_ratio; + int scaled_height = (double)six->original.height * height_ratio; + int scaled_stride = scaled_width * sizeof(uint32_t); + + LOG_DBG("scaling sixel: %dx%d -> %dx%d", + six->original.width, six->original.height, + scaled_width, scaled_height); + + uint8_t *scaled_data = xmalloc(scaled_height * scaled_stride); + pixman_image_t *scaled_pix = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, scaled_width, scaled_height, + (uint32_t *)scaled_data, scaled_stride); + + pixman_image_composite32( + PIXMAN_OP_SRC, six->original.pix, NULL, scaled_pix, 0, 0, 0, 0, + 0, 0, scaled_width, scaled_height); + + pixman_image_set_transform(six->original.pix, NULL); + + six->scaled.data = scaled_data; + six->scaled.pix = six->pix = scaled_pix; + six->scaled.width = six->width = scaled_width; + six->scaled.height = six->height = scaled_height; + } } void @@ -926,14 +1032,15 @@ sixel_reflow_grid(struct terminal *term, struct grid *grid) * allowed of course */ _sixel_overwrite_by_rectangle( term, six->pos.row, six->pos.col, six->rows, six->cols, - &it->item.pix, &it->item.opaque); + &it->item.original.pix, &it->item.opaque); - if (it->item.data != pixman_image_get_data(it->item.pix)) { - it->item.data = pixman_image_get_data(it->item.pix); - it->item.width = pixman_image_get_width(it->item.pix); - it->item.height = pixman_image_get_height(it->item.pix); - it->item.cols = (it->item.width + term->cell_width - 1) / term->cell_width; - it->item.rows = (it->item.height + term->cell_height - 1) / term->cell_height; + if (it->item.original.data != pixman_image_get_data(it->item.original.pix)) { + it->item.original.data = pixman_image_get_data(it->item.original.pix); + it->item.original.width = pixman_image_get_width(it->item.original.pix); + it->item.original.height = pixman_image_get_height(it->item.original.pix); + it->item.cols = (it->item.original.width + it->item.cell_width - 1) / it->item.cell_width; + it->item.rows = (it->item.original.height + it->item.cell_height - 1) / it->item.cell_height; + sixel_invalidate_cache(&it->item); } sixel_insert(term, it->item); @@ -1027,13 +1134,27 @@ sixel_unhook(struct terminal *term) } struct sixel image = { - .data = img_data, - .width = width, - .height = height, + .pix = NULL, + .width = -1, + .height = -1, .rows = (height + term->cell_height - 1) / term->cell_height, .cols = (width + term->cell_width - 1) / term->cell_width, .pos = (struct coord){start_col, cur_row}, .opaque = !term->sixel.transparent_bg, + .cell_width = term->cell_width, + .cell_height = term->cell_height, + .original = { + .data = img_data, + .pix = NULL, + .width = width, + .height = height, + }, + .scaled = { + .data = NULL, + .pix = NULL, + .width = -1, + .height = -1, + }, }; xassert(image.rows <= term->grid->num_rows); @@ -1044,8 +1165,9 @@ sixel_unhook(struct terminal *term) image.width, image.height, image.pos.row, image.pos.row + image.rows); - image.pix = pixman_image_create_bits_no_clear( - PIXMAN_a8r8g8b8, image.width, image.height, img_data, stride); + image.original.pix = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, image.original.width, image.original.height, + img_data, stride); pixel_row_idx += height; pixel_rows_left -= height; @@ -1064,7 +1186,8 @@ sixel_unhook(struct terminal *term) ? max(0, image.rows - 1) : image.rows; - xassert(rows_avail == 0 || image.height % term->cell_height == 0); + xassert(rows_avail == 0 || + image.original.height % term->cell_height == 0); for (size_t i = 0; i < linefeed_count; i++) term_linefeed(term); @@ -1084,7 +1207,7 @@ sixel_unhook(struct terminal *term) * higher up. */ const int sixel_row_height = 6 * term->sixel.pan; - const int sixel_rows = (image.height + sixel_row_height - 1) / sixel_row_height; + const int sixel_rows = (image.original.height + sixel_row_height - 1) / sixel_row_height; const int upper_pixel_last_sixel = (sixel_rows - 1) * sixel_row_height; const int term_rows = (upper_pixel_last_sixel + term->cell_height - 1) / term->cell_height; @@ -1117,14 +1240,15 @@ sixel_unhook(struct terminal *term) _sixel_overwrite_by_rectangle( term, image.pos.row, image.pos.col, image.rows, image.cols, - &image.pix, &image.opaque); + &image.original.pix, &image.opaque); - if (image.data != pixman_image_get_data(image.pix)) { - image.data = pixman_image_get_data(image.pix); - image.width = pixman_image_get_width(image.pix); - image.height = pixman_image_get_height(image.pix); - image.cols = (image.width + term->cell_width - 1) / term->cell_width; - image.rows = (image.height + term->cell_height - 1) / term->cell_height; + if (image.original.data != pixman_image_get_data(image.original.pix)) { + image.original.data = pixman_image_get_data(image.original.pix); + image.original.width = pixman_image_get_width(image.original.pix); + image.original.height = pixman_image_get_height(image.original.pix); + image.cols = (image.original.width + image.cell_width - 1) / image.cell_width; + image.rows = (image.original.height + image.cell_height - 1) / image.cell_height; + sixel_invalidate_cache(&image); } sixel_insert(term, image); diff --git a/sixel.h b/sixel.h index efd64908..ab8a5050 100644 --- a/sixel.h +++ b/sixel.h @@ -20,6 +20,7 @@ void sixel_scroll_up(struct terminal *term, int rows); void sixel_scroll_down(struct terminal *term, int rows); void sixel_cell_size_changed(struct terminal *term); +void sixel_sync_cache(const struct terminal *term, struct sixel *sixel); void sixel_reflow_grid(struct terminal *term, struct grid *grid); diff --git a/terminal.c b/terminal.c index 090cbb20..df77d2b9 100644 --- a/terminal.c +++ b/terminal.c @@ -763,9 +763,6 @@ term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4]) free_custom_glyphs( &term->custom_glyphs.legacy, GLYPH_LEGACY_COUNT); - const int old_cell_width = term->cell_width; - const int old_cell_height = term->cell_height; - const struct config *conf = term->conf; const struct fcft_glyph *M = fcft_rasterize_char_utf32( @@ -792,28 +789,7 @@ term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4]) LOG_INFO("cell width=%d, height=%d", term->cell_width, term->cell_height); - if (term->cell_width < old_cell_width || - term->cell_height < old_cell_height) - { - /* - * The cell size has decreased. - * - * This means sixels, which we cannot resize, no longer fit - * into their "allocated" grid space. - * - * To be able to fit them, we would have to change the grid - * content. Inserting empty lines _might_ seem acceptable, but - * we'd also need to insert empty columns, which would break - * existing layout completely. - * - * So we delete them. - */ - sixel_destroy_all(term); - } else if (term->cell_width != old_cell_width || - term->cell_height != old_cell_height) - { - sixel_cell_size_changed(term); - } + sixel_cell_size_changed(term); /* Use force, since cell-width/height may have changed */ render_resize_force(term, term->width / term->scale, term->height / term->scale); diff --git a/terminal.h b/terminal.h index 1cb62686..1ccb3219 100644 --- a/terminal.h +++ b/terminal.h @@ -126,14 +126,48 @@ struct row { }; struct sixel { - void *data; + /* + * These three members reflect the "current", maybe scaled version + * of the image. + * + * The values will either be NULL/-1/-1, or match either the + * values in "original", or "scaled". + * + * They are typically reset when we need to invalidate the cached + * version (e.g. when the cell dimensions change). + */ pixman_image_t *pix; int width; int height; + int rows; int cols; struct coord pos; bool opaque; + + /* + * We store the cell dimensions of the time the sixel was emitted. + * + * If the font size is changed, we rescale the image accordingly, + * to ensure it stays within its cell boundaries. ‘scaled’ is a + * cached, rescaled version of ‘data’ + ‘pix’. + */ + int cell_width; + int cell_height; + + struct { + void *data; + pixman_image_t *pix; + int width; + int height; + } original; + + struct { + void *data; + pixman_image_t *pix; + int width; + int height; + } scaled; }; enum kitty_kbd_flags { From 72bc0acfbd4b002ebf26d65368bb65601452353e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Jul 2023 14:36:03 +0200 Subject: [PATCH 074/135] wayland: handle enum value XDG_TOPLEVEL_STATE_SUSPENDED Added in wayland-protocols-1.32 --- wayland.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/wayland.c b/wayland.c index 406aba6d..5160240b 100644 --- a/wayland.c +++ b/wayland.c @@ -651,6 +651,7 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, bool is_tiled_bottom = false; bool is_tiled_left = false; bool is_tiled_right = false; + bool is_suspended UNUSED = false; #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG char state_str[2048]; @@ -665,29 +666,35 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, [XDG_TOPLEVEL_STATE_TILED_RIGHT] = "tiled:right", [XDG_TOPLEVEL_STATE_TILED_TOP] = "tiled:top", [XDG_TOPLEVEL_STATE_TILED_BOTTOM] = "tiled:bottom", +#if defined(XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION) /* wayland-protocols >= 1.32 */ + [XDG_TOPLEVEL_STATE_SUSPENDED] = "suspended", +#endif }; #endif enum xdg_toplevel_state *state; wl_array_for_each(state, states) { switch (*state) { - case XDG_TOPLEVEL_STATE_ACTIVATED: is_activated = true; break; - case XDG_TOPLEVEL_STATE_FULLSCREEN: is_fullscreen = true; break; case XDG_TOPLEVEL_STATE_MAXIMIZED: is_maximized = true; break; + case XDG_TOPLEVEL_STATE_FULLSCREEN: is_fullscreen = true; break; + case XDG_TOPLEVEL_STATE_RESIZING: is_resizing = true; break; + case XDG_TOPLEVEL_STATE_ACTIVATED: is_activated = true; break; case XDG_TOPLEVEL_STATE_TILED_LEFT: is_tiled_left = true; break; case XDG_TOPLEVEL_STATE_TILED_RIGHT: is_tiled_right = true; break; case XDG_TOPLEVEL_STATE_TILED_TOP: is_tiled_top = true; break; case XDG_TOPLEVEL_STATE_TILED_BOTTOM: is_tiled_bottom = true; break; - case XDG_TOPLEVEL_STATE_RESIZING: is_resizing = true; break; - } + +#if defined(XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION) + case XDG_TOPLEVEL_STATE_SUSPENDED: is_suspended = true; break; +#endif + } #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG - if (*state >= XDG_TOPLEVEL_STATE_MAXIMIZED && - *state <= XDG_TOPLEVEL_STATE_TILED_BOTTOM) - { + if (*state >= 0 && *state < ALEN(strings)) { state_chars += snprintf( &state_str[state_chars], sizeof(state_str) - state_chars, - "%s, ", strings[*state]); + "%s, ", + strings[*state] != NULL ? strings[*state] : ""); } #endif } From ee794a121e4603d01fb662ad6d1f3b18b6937d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 16:57:33 +0200 Subject: [PATCH 075/135] refactor: track current xcursor using an enum, instead of a char pointer --- cursor-shape.c | 29 +++++++++++++++++++++++++++++ cursor-shape.h | 21 +++++++++++++++++++++ input.c | 22 +++++++++++----------- input.h | 5 +++-- meson.build | 1 + render.c | 24 +++++++++++++++--------- render.h | 2 +- terminal.c | 34 ++++++++++------------------------ terminal.h | 14 -------------- wayland.h | 3 ++- 10 files changed, 93 insertions(+), 62 deletions(-) create mode 100644 cursor-shape.c create mode 100644 cursor-shape.h diff --git a/cursor-shape.c b/cursor-shape.c new file mode 100644 index 00000000..152f176f --- /dev/null +++ b/cursor-shape.c @@ -0,0 +1,29 @@ +#include + +#include "cursor-shape.h" +#include "debug.h" +#include "util.h" + +const char * +cursor_shape_to_string(enum cursor_shape shape) +{ + static const char *const table[] = { + [CURSOR_SHAPE_NONE] = NULL, + [CURSOR_SHAPE_HIDDEN] = "hidden", + [CURSOR_SHAPE_LEFT_PTR] = "left_ptr", + [CURSOR_SHAPE_TEXT] = "text", + [CURSOR_SHAPE_TEXT_FALLBACK] = "xterm", + [CURSOR_SHAPE_TOP_LEFT_CORNER] = "top_left_corner", + [CURSOR_SHAPE_TOP_RIGHT_CORNER] = "top_right_corner", + [CURSOR_SHAPE_BOTTOM_LEFT_CORNER] = "bottom_left_corner", + [CURSOR_SHAPE_BOTTOM_RIGHT_CORNER] = "bottom_right_corner", + [CURSOR_SHAPE_LEFT_SIDE] = "left_side", + [CURSOR_SHAPE_RIGHT_SIDE] = "right_side", + [CURSOR_SHAPE_TOP_SIDE] = "top_side", + [CURSOR_SHAPE_BOTTOM_SIDE] = "bottom_side", + + }; + + xassert(shape <= ALEN(table)); + return table[shape]; +} diff --git a/cursor-shape.h b/cursor-shape.h new file mode 100644 index 00000000..fb79e45a --- /dev/null +++ b/cursor-shape.h @@ -0,0 +1,21 @@ +#pragma once + +enum cursor_shape { + CURSOR_SHAPE_NONE, + CURSOR_SHAPE_CUSTOM, + + CURSOR_SHAPE_HIDDEN, + CURSOR_SHAPE_LEFT_PTR, + CURSOR_SHAPE_TEXT, + CURSOR_SHAPE_TEXT_FALLBACK, + CURSOR_SHAPE_TOP_LEFT_CORNER, + CURSOR_SHAPE_TOP_RIGHT_CORNER, + CURSOR_SHAPE_BOTTOM_LEFT_CORNER, + CURSOR_SHAPE_BOTTOM_RIGHT_CORNER, + CURSOR_SHAPE_LEFT_SIDE, + CURSOR_SHAPE_RIGHT_SIDE, + CURSOR_SHAPE_TOP_SIDE, + CURSOR_SHAPE_BOTTOM_SIDE, +}; + +const char *cursor_shape_to_string(enum cursor_shape shape); diff --git a/input.c b/input.c index 7e5d204d..0f638cc1 100644 --- a/input.c +++ b/input.c @@ -1704,20 +1704,20 @@ 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))); } -const char * +enum cursor_shape xcursor_for_csd_border(struct terminal *term, int x, int y) { - if (is_top_left(term, x, y)) return XCURSOR_TOP_LEFT_CORNER; - else if (is_top_right(term, x, y)) return XCURSOR_TOP_RIGHT_CORNER; - else if (is_bottom_left(term, x, y)) return XCURSOR_BOTTOM_LEFT_CORNER; - else if (is_bottom_right(term, x, y)) return XCURSOR_BOTTOM_RIGHT_CORNER; - else if (term->active_surface == TERM_SURF_BORDER_LEFT) return XCURSOR_LEFT_SIDE; - else if (term->active_surface == TERM_SURF_BORDER_RIGHT) return XCURSOR_RIGHT_SIDE; - else if (term->active_surface == TERM_SURF_BORDER_TOP) return XCURSOR_TOP_SIDE; - else if (term->active_surface == TERM_SURF_BORDER_BOTTOM) return XCURSOR_BOTTOM_SIDE; + if (is_top_left(term, x, y)) return CURSOR_SHAPE_TOP_LEFT_CORNER; + else if (is_top_right(term, x, y)) return CURSOR_SHAPE_TOP_RIGHT_CORNER; + else if (is_bottom_left(term, x, y)) return CURSOR_SHAPE_BOTTOM_LEFT_CORNER; + else if (is_bottom_right(term, x, y)) return CURSOR_SHAPE_BOTTOM_RIGHT_CORNER; + else if (term->active_surface == TERM_SURF_BORDER_LEFT) return CURSOR_SHAPE_LEFT_SIDE; + else if (term->active_surface == TERM_SURF_BORDER_RIGHT) return CURSOR_SHAPE_RIGHT_SIDE; + else if (term->active_surface == TERM_SURF_BORDER_TOP) return CURSOR_SHAPE_TOP_SIDE; + else if (term->active_surface == TERM_SURF_BORDER_BOTTOM) return CURSOR_SHAPE_BOTTOM_SIDE; else { BUG("Unreachable"); - return NULL; + return CURSOR_SHAPE_NONE; } } @@ -1819,7 +1819,7 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, } /* Reset last-set-xcursor, to ensure we update it on a pointer-enter event */ - seat->pointer.xcursor = NULL; + seat->pointer.shape = CURSOR_SHAPE_NONE; /* Reset mouse state */ seat->mouse.x = seat->mouse.y = 0; diff --git a/input.h b/input.h index ea488a86..825dc3be 100644 --- a/input.h +++ b/input.h @@ -3,8 +3,9 @@ #include #include -#include "wayland.h" +#include "cursor-shape.h" #include "misc.h" +#include "wayland.h" /* * Custom defines for mouse wheel left/right buttons. @@ -33,4 +34,4 @@ void get_current_modifiers(const struct seat *seat, xkb_mod_mask_t *consumed, uint32_t key); -const char *xcursor_for_csd_border(struct terminal *term, int x, int y); +enum cursor_shape xcursor_for_csd_border(struct terminal *term, int x, int y); diff --git a/meson.build b/meson.build index 6e219caf..9560504f 100644 --- a/meson.build +++ b/meson.build @@ -261,6 +261,7 @@ executable( 'box-drawing.c', 'box-drawing.h', 'config.c', 'config.h', 'commands.c', 'commands.h', + 'cursor-shape.c', 'cursor-shape.h', 'extract.c', 'extract.h', 'fdm.c', 'fdm.h', 'foot-features.h', diff --git a/render.c b/render.c index 4498290e..fedc3467 100644 --- a/render.c +++ b/render.c @@ -4254,9 +4254,9 @@ render_xcursor_update(struct seat *seat) if (!seat->mouse_focus) return; - xassert(seat->pointer.xcursor != NULL); + xassert(seat->pointer.shape != CURSOR_SHAPE_NONE); - if (seat->pointer.xcursor == XCURSOR_HIDDEN) { + if (seat->pointer.shape == CURSOR_SHAPE_HIDDEN) { /* Hide cursor */ wl_surface_attach(seat->pointer.surface.surf, NULL, 0, 0); wl_surface_commit(seat->pointer.surface.surf); @@ -4434,13 +4434,13 @@ render_refresh_urls(struct terminal *term) } bool -render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor) +render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape shape) { if (seat->pointer.theme == NULL) return false; if (seat->mouse_focus == NULL) { - seat->pointer.xcursor = NULL; + seat->pointer.shape = CURSOR_SHAPE_NONE; return true; } @@ -4449,18 +4449,24 @@ render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor return true; } - if (seat->pointer.xcursor == xcursor) + if (seat->pointer.shape == shape) return true; - if (xcursor != XCURSOR_HIDDEN) { + if (shape != CURSOR_SHAPE_HIDDEN) { + const char *const xcursor = cursor_shape_to_string(shape); + const char *const fallback = + cursor_shape_to_string(CURSOR_SHAPE_TEXT_FALLBACK); + seat->pointer.cursor = wl_cursor_theme_get_cursor( seat->pointer.theme, xcursor); if (seat->pointer.cursor == NULL) { seat->pointer.cursor = wl_cursor_theme_get_cursor( - seat->pointer.theme, XCURSOR_TEXT_FALLBACK ); + seat->pointer.theme, fallback); + if (seat->pointer.cursor == NULL) { - LOG_ERR("failed to load xcursor pointer '%s', and fallback '%s'", xcursor, XCURSOR_TEXT_FALLBACK); + LOG_ERR("failed to load xcursor pointer " + "'%s', and fallback '%s'", xcursor, fallback); return false; } } @@ -4468,7 +4474,7 @@ render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor seat->pointer.cursor = NULL; /* FDM hook takes care of actual rendering */ - seat->pointer.xcursor = xcursor; + seat->pointer.shape = shape; seat->pointer.xcursor_pending = true; return true; } diff --git a/render.h b/render.h index d2c673ee..f038ffb0 100644 --- a/render.h +++ b/render.h @@ -19,7 +19,7 @@ void render_refresh_search(struct terminal *term); void render_refresh_title(struct terminal *term); void render_refresh_urls(struct terminal *term); bool render_xcursor_set( - struct seat *seat, struct terminal *term, const char *xcursor); + struct seat *seat, struct terminal *term, enum cursor_shape shape); bool render_xcursor_is_valid(const struct seat *seat, const char *cursor); struct render_worker_context { diff --git a/terminal.c b/terminal.c index df77d2b9..3591040b 100644 --- a/terminal.c +++ b/terminal.c @@ -47,20 +47,6 @@ #define PTMX_TIMING 0 -const char *const XCURSOR_HIDDEN = "hidden"; -const char *const XCURSOR_LEFT_PTR = "left_ptr"; -const char *const XCURSOR_TEXT = "text"; -const char *const XCURSOR_TEXT_FALLBACK = "xterm"; -//const char *const XCURSOR_HAND2 = "hand2"; -const char *const XCURSOR_TOP_LEFT_CORNER = "top_left_corner"; -const char *const XCURSOR_TOP_RIGHT_CORNER = "top_right_corner"; -const char *const XCURSOR_BOTTOM_LEFT_CORNER = "bottom_left_corner"; -const char *const XCURSOR_BOTTOM_RIGHT_CORNER = "bottom_right_corner"; -const char *const XCURSOR_LEFT_SIDE = "left_side"; -const char *const XCURSOR_RIGHT_SIDE = "right_side"; -const char *const XCURSOR_TOP_SIDE = "top_side"; -const char *const XCURSOR_BOTTOM_SIDE = "bottom_side"; - static void enqueue_data_for_slave(const void *data, size_t len, size_t offset, ptmx_buffer_list_t *buffer_list) @@ -3137,44 +3123,44 @@ 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 = NULL; + enum cursor_shape shape = CURSOR_SHAPE_NONE; switch (term->active_surface) { case TERM_SURF_GRID: { bool have_custom_cursor = render_xcursor_is_valid(seat, term->mouse_user_cursor); - xcursor = seat->pointer.hidden ? XCURSOR_HIDDEN - : have_custom_cursor ? term->mouse_user_cursor - : term->is_searching ? XCURSOR_LEFT_PTR + shape = seat->pointer.hidden ? CURSOR_SHAPE_HIDDEN + : have_custom_cursor ? CURSOR_SHAPE_CUSTOM //term->mouse_user_cursor + : term->is_searching ? CURSOR_SHAPE_LEFT_PTR : (seat->mouse.col >= 0 && seat->mouse.row >= 0 && - term_mouse_grabbed(term, seat)) ? XCURSOR_TEXT - : XCURSOR_LEFT_PTR; + term_mouse_grabbed(term, seat)) ? CURSOR_SHAPE_TEXT + : CURSOR_SHAPE_LEFT_PTR; break; } case TERM_SURF_TITLE: case TERM_SURF_BUTTON_MINIMIZE: case TERM_SURF_BUTTON_MAXIMIZE: case TERM_SURF_BUTTON_CLOSE: - xcursor = XCURSOR_LEFT_PTR; + shape = CURSOR_SHAPE_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); + shape = xcursor_for_csd_border(term, seat->mouse.x, seat->mouse.y); break; case TERM_SURF_NONE: return; } - if (xcursor == NULL) + if (shape == CURSOR_SHAPE_NONE) BUG("xcursor not set"); - render_xcursor_set(seat, term, xcursor); + render_xcursor_set(seat, term, shape); } void diff --git a/terminal.h b/terminal.h index 1ccb3219..6dace7ac 100644 --- a/terminal.h +++ b/terminal.h @@ -718,20 +718,6 @@ struct terminal { char *cwd; }; -extern const char *const XCURSOR_HIDDEN; -extern const char *const XCURSOR_LEFT_PTR; -extern const char *const XCURSOR_TEXT; -extern const char *const XCURSOR_TEXT_FALLBACK; -//extern const char *const XCURSOR_HAND2; -extern const char *const XCURSOR_TOP_LEFT_CORNER; -extern const char *const XCURSOR_TOP_RIGHT_CORNER; -extern const char *const XCURSOR_BOTTOM_LEFT_CORNER; -extern const char *const XCURSOR_BOTTOM_RIGHT_CORNER; -extern const char *const XCURSOR_LEFT_SIDE; -extern const char *const XCURSOR_RIGHT_SIDE; -extern const char *const XCURSOR_TOP_SIDE; -extern const char *const XCURSOR_BOTTOM_SIDE; - struct config; struct terminal *term_init( const struct config *conf, struct fdm *fdm, struct reaper *reaper, diff --git a/wayland.h b/wayland.h index e2d22031..af1bcb3f 100644 --- a/wayland.h +++ b/wayland.h @@ -28,6 +28,7 @@ #include #include +#include "cursor-shape.h" #include "fdm.h" /* Forward declarations */ @@ -151,7 +152,7 @@ struct seat { float scale; bool hidden; - const char *xcursor; + enum cursor_shape shape; struct wl_callback *xcursor_callback; bool xcursor_pending; } pointer; From c8e13ad3938d68635693d0b0f4bf1054628e8387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 17:25:57 +0200 Subject: [PATCH 076/135] cursor-shape: add support for server side cursor shapes This implements support for the new cursor-shape-v1 protocol. When available, we use it, instead of client-side cursor surfaces, to select the xcursor shape. Note that we still need to keep client side pointers, for: * backward compatibility * to be able to "hide" the cursor Closes #1379 --- CHANGELOG.md | 4 ++ cursor-shape-v1.xml | 147 ++++++++++++++++++++++++++++++++++++++++++++ cursor-shape.c | 25 +++++++- cursor-shape.h | 13 +++- meson.build | 6 ++ render.c | 47 +++++++++----- wayland.c | 46 ++++++++++++++ wayland.h | 12 +++- 8 files changed, 280 insertions(+), 20 deletions(-) create mode 100644 cursor-shape-v1.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b47f09..6625c56d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,10 @@ * Support for the new fractional-scaling-v1 Wayland protocol. This brings true fractional scaling to Wayland in general, and with this release, foot. +* Support for the new `cursor-shape-v1` Wayland protocol, i.e. server + side cursor shapes ([#1379][1379]). + +[1379]: https://codeberg.org/dnkl/foot/issues/1379 ### Changed diff --git a/cursor-shape-v1.xml b/cursor-shape-v1.xml new file mode 100644 index 00000000..56f6a1a6 --- /dev/null +++ b/cursor-shape-v1.xml @@ -0,0 +1,147 @@ + + + + Copyright 2018 The Chromium Authors + Copyright 2023 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This global offers an alternative, optional way to set cursor images. This + new way uses enumerated cursors instead of a wl_surface like + wl_pointer.set_cursor does. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + Destroy the cursor shape manager. + + + + + + Obtain a wp_cursor_shape_device_v1 for a wl_pointer object. + + + + + + + + Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object. + + + + + + + + + This interface advertises the list of supported cursor shapes for a + device, and allows clients to set the cursor shape. + + + + + This enum describes cursor shapes. + + The names are taken from the CSS W3C specification: + https://w3c.github.io/csswg-drafts/css-ui/#cursor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Destroy the cursor shape device. + + The device cursor shape remains unchanged. + + + + + + Sets the device cursor to the specified shape. The compositor will + change the cursor image based on the specified shape. + + The cursor actually changes only if the input device focus is one of + the requesting client's surfaces. If any, the previous cursor image + (surface or shape) is replaced. + + The "shape" argument must be a valid enum entry, otherwise the + invalid_shape protocol error is raised. + + This is similar to the wl_pointer.set_cursor and + zwp_tablet_tool_v2.set_cursor requests, but this request accepts a + shape instead of contents in the form of a surface. Clients can mix + set_cursor and set_shape requests. + + The serial parameter must match the latest wl_pointer.enter or + zwp_tablet_tool_v2.proximity_in serial number sent to the client. + Otherwise the request will be ignored. + + + + + + diff --git a/cursor-shape.c b/cursor-shape.c index 152f176f..cd9ba221 100644 --- a/cursor-shape.c +++ b/cursor-shape.c @@ -7,7 +7,7 @@ const char * cursor_shape_to_string(enum cursor_shape shape) { - static const char *const table[] = { + static const char *const table[CURSOR_SHAPE_COUNT] = { [CURSOR_SHAPE_NONE] = NULL, [CURSOR_SHAPE_HIDDEN] = "hidden", [CURSOR_SHAPE_LEFT_PTR] = "left_ptr", @@ -27,3 +27,26 @@ cursor_shape_to_string(enum cursor_shape shape) xassert(shape <= ALEN(table)); return table[shape]; } + +#if defined(HAVE_CURSOR_SHAPE) +enum wp_cursor_shape_device_v1_shape +cursor_shape_to_server_shape(enum cursor_shape shape) +{ + static const enum wp_cursor_shape_device_v1_shape table[CURSOR_SHAPE_COUNT] = { + [CURSOR_SHAPE_LEFT_PTR] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT, + [CURSOR_SHAPE_TEXT] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT, + [CURSOR_SHAPE_TEXT_FALLBACK] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT, + [CURSOR_SHAPE_TOP_LEFT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE, + [CURSOR_SHAPE_TOP_RIGHT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE, + [CURSOR_SHAPE_BOTTOM_LEFT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE, + [CURSOR_SHAPE_BOTTOM_RIGHT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE, + [CURSOR_SHAPE_LEFT_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE, + [CURSOR_SHAPE_RIGHT_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE, + [CURSOR_SHAPE_TOP_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE, + [CURSOR_SHAPE_BOTTOM_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE, + }; + + xassert(shape <= ALEN(table)); + return table[shape]; +} +#endif diff --git a/cursor-shape.h b/cursor-shape.h index fb79e45a..0cb0b4d8 100644 --- a/cursor-shape.h +++ b/cursor-shape.h @@ -1,10 +1,14 @@ #pragma once +#if defined(HAVE_CURSOR_SHAPE) +#include +#endif + enum cursor_shape { CURSOR_SHAPE_NONE, CURSOR_SHAPE_CUSTOM, - CURSOR_SHAPE_HIDDEN, + CURSOR_SHAPE_LEFT_PTR, CURSOR_SHAPE_TEXT, CURSOR_SHAPE_TEXT_FALLBACK, @@ -16,6 +20,13 @@ enum cursor_shape { CURSOR_SHAPE_RIGHT_SIDE, CURSOR_SHAPE_TOP_SIDE, CURSOR_SHAPE_BOTTOM_SIDE, + + CURSOR_SHAPE_COUNT, }; const char *cursor_shape_to_string(enum cursor_shape shape); + +#if defined(HAVE_CURSOR_SHAPE) +enum wp_cursor_shape_device_v1_shape cursor_shape_to_server_shape( + enum cursor_shape shape); +#endif diff --git a/meson.build b/meson.build index 9560504f..ed0bf4a0 100644 --- a/meson.build +++ b/meson.build @@ -170,6 +170,12 @@ else fractional_scale = false endif +# TODO: check wayland-protocols version +wl_proto_xml += [wayland_protocols_datadir + '/unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1 + 'cursor-shape-v1.xml', # TODO: use wayland-protocols + ] +add_project_arguments('-DHAVE_CURSOR_SHAPE', language: 'c') + foreach prot : wl_proto_xml wl_proto_headers += custom_target( prot.underscorify() + '-client-header', diff --git a/render.c b/render.c index fedc3467..ab1ddc51 100644 --- a/render.c +++ b/render.c @@ -31,6 +31,7 @@ #include "box-drawing.h" #include "char32.h" #include "config.h" +#include "cursor-shape.h" #include "grid.h" #include "hsl.h" #include "ime.h" @@ -4259,35 +4260,46 @@ render_xcursor_update(struct seat *seat) if (seat->pointer.shape == CURSOR_SHAPE_HIDDEN) { /* Hide cursor */ wl_surface_attach(seat->pointer.surface.surf, NULL, 0, 0); + wl_pointer_set_cursor( + seat->wl_pointer, seat->pointer.serial, seat->pointer.surface.surf, + 0, 0); wl_surface_commit(seat->pointer.surface.surf); return; } xassert(seat->pointer.cursor != NULL); - const float scale = seat->pointer.scale; - struct wl_cursor_image *image = seat->pointer.cursor->images[0]; - struct wl_buffer *buf = wl_cursor_image_get_buffer(image); +#if defined(HAVE_CURSOR_SHAPE) + if (seat->pointer.shape_device != NULL) { + wp_cursor_shape_device_v1_set_shape( + seat->pointer.shape_device, + seat->pointer.serial, + cursor_shape_to_server_shape(seat->pointer.shape)); + } else +#endif + { + const int scale = seat->pointer.scale; + struct wl_cursor_image *image = seat->pointer.cursor->images[0]; - wayl_surface_scale_explicit_width_height( - seat->mouse_focus->window, - &seat->pointer.surface, image->width, image->height, scale); + wl_surface_attach( + seat->pointer.surface.surf, wl_cursor_image_get_buffer(image), 0, 0); - wl_surface_attach(seat->pointer.surface.surf, buf, 0, 0); + wl_pointer_set_cursor( + seat->wl_pointer, seat->pointer.serial, + seat->pointer.surface.surf, + image->hotspot_x / scale, image->hotspot_y / scale); - wl_pointer_set_cursor( - seat->wl_pointer, seat->pointer.serial, - seat->pointer.surface.surf, - image->hotspot_x / scale, image->hotspot_y / scale); + wl_surface_damage_buffer( + seat->pointer.surface.surf, 0, 0, INT32_MAX, INT32_MAX); - wl_surface_damage_buffer( - seat->pointer.surface.surf, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_set_buffer_scale(seat->pointer.surface.surf, scale); - xassert(seat->pointer.xcursor_callback == NULL); - seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface.surf); - wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat); + xassert(seat->pointer.xcursor_callback == NULL); + seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface.surf); + wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat); - wl_surface_commit(seat->pointer.surface.surf); + wl_surface_commit(seat->pointer.surface.surf); + } } static void @@ -4452,6 +4464,7 @@ render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape s if (seat->pointer.shape == shape) return true; + /* TODO: skip this when using server-side cursors */ if (shape != CURSOR_SHAPE_HIDDEN) { const char *const xcursor = cursor_shape_to_string(shape); const char *const fallback = diff --git a/wayland.c b/wayland.c index 5160240b..f3a5619d 100644 --- a/wayland.c +++ b/wayland.c @@ -14,6 +14,10 @@ #include #include +#if defined(HAVE_CURSOR_SHAPE) +#include +#endif + #include #define LOG_MODULE "wayland" @@ -209,6 +213,11 @@ seat_destroy(struct seat *seat) if (seat->data_device != NULL) wl_data_device_release(seat->data_device); +#if defined(HAVE_CURSOR_SHAPE) + if (seat->pointer.shape_device != NULL) + wp_cursor_shape_device_v1_destroy(seat->pointer.shape_device); +#endif + if (seat->wl_keyboard != NULL) wl_keyboard_release(seat->wl_keyboard); if (seat->wl_pointer != NULL) @@ -316,9 +325,22 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, seat->wl_pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); + +#if defined(HAVE_CURSOR_SHAPE) + if (seat->wayl->cursor_shape_manager != NULL) { + xassert(seat->pointer.shape_device == NULL); + seat->pointer.shape_device = wp_cursor_shape_manager_v1_get_pointer( + seat->wayl->cursor_shape_manager, seat->wl_pointer); + } +#endif } } else { if (seat->wl_pointer != NULL) { +#if defined(HAVE_CURSOR_SHAPE) + wp_cursor_shape_device_v1_destroy(seat->pointer.shape_device); + seat->pointer.shape_device = NULL; +#endif + wl_pointer_release(seat->wl_pointer); wl_surface_destroy(seat->pointer.surface.surf); @@ -1167,6 +1189,17 @@ handle_global(void *data, struct wl_registry *registry, } #endif +#if defined(HAVE_CURSOR_SHAPE) + else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { + const uint32_t required = 1; + if (!verify_iface_version(interface, version, required)) + return; + + wayl->cursor_shape_manager = wl_registry_bind( + wayl->registry, name, &wp_cursor_shape_manager_v1_interface, required); + } +#endif + #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) { const uint32_t required = 1; @@ -1401,6 +1434,15 @@ wayl_init(struct fdm *fdm, struct key_binding_manager *key_binding_manager, LOG_WARN("fractional scaling not available"); } +#if defined(HAVE_CURSOR_SHAPE) + if (wayl->cursor_shape_manager == NULL) { +#else + if (true) { +#endif + LOG_WARN("no server-side cursors available, " + "falling back to client-side cursors"); + } + if (presentation_timings && wayl->presentation == NULL) { LOG_ERR("presentation time interface not implemented by compositor"); goto out; @@ -1495,6 +1537,10 @@ wayl_destroy(struct wayland *wayl) if (wayl->viewporter != NULL) wp_viewporter_destroy(wayl->viewporter); #endif +#if defined(HAVE_CURSOR_SHAPE) + if (wayl->cursor_shape_manager != NULL) + wp_cursor_shape_manager_v1_destroy(wayl->cursor_shape_manager); +#endif #if defined(HAVE_XDG_ACTIVATION) if (wayl->xdg_activation != NULL) xdg_activation_v1_destroy(wayl->xdg_activation); diff --git a/wayland.h b/wayland.h index af1bcb3f..b30ad307 100644 --- a/wayland.h +++ b/wayland.h @@ -146,12 +146,18 @@ struct seat { struct { uint32_t serial; + /* Client-side cursor */ struct wayl_surface surface; struct wl_cursor_theme *theme; struct wl_cursor *cursor; + + /* Server-side cursor */ +#if defined(HAVE_CURSOR_SHAPE) + struct wp_cursor_shape_device_v1 *shape_device; +#endif + float scale; bool hidden; - enum cursor_shape shape; struct wl_callback *xcursor_callback; bool xcursor_pending; @@ -426,6 +432,10 @@ struct wayland { struct xdg_activation_v1 *xdg_activation; #endif +#if defined(HAVE_CURSOR_SHAPE) + struct wp_cursor_shape_manager_v1 *cursor_shape_manager; +#endif + bool presentation_timings; struct wp_presentation *presentation; uint32_t presentation_clock_id; From 6ed5dce5ab5651c1ee345d0315c1538900282f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 17:42:47 +0200 Subject: [PATCH 077/135] render: debug log which method we use to set the xcursor --- render.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/render.c b/render.c index ab1ddc51..d7cc9dfb 100644 --- a/render.c +++ b/render.c @@ -4259,6 +4259,7 @@ render_xcursor_update(struct seat *seat) if (seat->pointer.shape == CURSOR_SHAPE_HIDDEN) { /* Hide cursor */ + LOG_DBG("hiding cursor using client-side NULL-surface"); wl_surface_attach(seat->pointer.surface.surf, NULL, 0, 0); wl_pointer_set_cursor( seat->wl_pointer, seat->pointer.serial, seat->pointer.surface.surf, @@ -4271,6 +4272,7 @@ render_xcursor_update(struct seat *seat) #if defined(HAVE_CURSOR_SHAPE) if (seat->pointer.shape_device != NULL) { + LOG_DBG("setting cursor shape using cursor-shape-v1"); wp_cursor_shape_device_v1_set_shape( seat->pointer.shape_device, seat->pointer.serial, @@ -4278,6 +4280,7 @@ render_xcursor_update(struct seat *seat) } else #endif { + LOG_DBG("setting cursor shape using a client-side cursor surface"); const int scale = seat->pointer.scale; struct wl_cursor_image *image = seat->pointer.cursor->images[0]; From 803b250652d766cf08721e334215585f4fd72fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 18:16:33 +0200 Subject: [PATCH 078/135] pgo: update xcursor stubs to use enum instead of char pointer --- pgo/pgo.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pgo/pgo.c b/pgo/pgo.c index d9ee5855..6be60363 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -76,15 +76,15 @@ render_xcursor_is_valid(const struct seat *seat, const char *cursor) } bool -render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor) +render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape shape) { return true; } -const char * +enum cursor_shape xcursor_for_csd_border(struct terminal *term, int x, int y) { - return XCURSOR_LEFT_PTR; + return CURSOR_SHAPE_LEFT_PTR; } struct wl_window * From 9155948ac8f2902acae107e9ff765630295ca6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 18:40:25 +0200 Subject: [PATCH 079/135] cursor-shape: assert lookup succeeded --- cursor-shape.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cursor-shape.c b/cursor-shape.c index cd9ba221..48f7419f 100644 --- a/cursor-shape.c +++ b/cursor-shape.c @@ -25,6 +25,7 @@ cursor_shape_to_string(enum cursor_shape shape) }; xassert(shape <= ALEN(table)); + xassert(table[shape] != NULL); return table[shape]; } @@ -47,6 +48,7 @@ cursor_shape_to_server_shape(enum cursor_shape shape) }; xassert(shape <= ALEN(table)); + xassert(table[shape] != 0); return table[shape]; } #endif From ddd6004b275dd9a24f26e03dead90f0fffb0cc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 18:40:44 +0200 Subject: [PATCH 080/135] =?UTF-8?q?render:=20don=E2=80=99t=20(can=E2=80=99?= =?UTF-8?q?t)=20use=20cursor-shape-v1=20when=20user=20has=20set=20a=20cust?= =?UTF-8?q?om=20cursor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Well, we _could_, but we’d have to reverse map the string to a cursor-shape-v1 enum value. Let’s not do that, for now at least. --- render.c | 11 ++++++++--- terminal.c | 27 ++++++++++++++++----------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/render.c b/render.c index d7cc9dfb..b0b1ec32 100644 --- a/render.c +++ b/render.c @@ -4271,7 +4271,9 @@ render_xcursor_update(struct seat *seat) xassert(seat->pointer.cursor != NULL); #if defined(HAVE_CURSOR_SHAPE) - if (seat->pointer.shape_device != NULL) { + if (seat->pointer.shape_device != NULL && + seat->pointer.shape != CURSOR_SHAPE_CUSTOM) + { LOG_DBG("setting cursor shape using cursor-shape-v1"); wp_cursor_shape_device_v1_set_shape( seat->pointer.shape_device, @@ -4449,7 +4451,8 @@ render_refresh_urls(struct terminal *term) } bool -render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape shape) +render_xcursor_set(struct seat *seat, struct terminal *term, + enum cursor_shape shape) { if (seat->pointer.theme == NULL) return false; @@ -4469,7 +4472,9 @@ render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape s /* TODO: skip this when using server-side cursors */ if (shape != CURSOR_SHAPE_HIDDEN) { - const char *const xcursor = cursor_shape_to_string(shape); + const char *const xcursor = shape == CURSOR_SHAPE_CUSTOM + ? term->mouse_user_cursor + : cursor_shape_to_string(shape); const char *const fallback = cursor_shape_to_string(CURSOR_SHAPE_TEXT_FALLBACK); diff --git a/terminal.c b/terminal.c index 3591040b..3f2c9095 100644 --- a/terminal.c +++ b/terminal.c @@ -3126,19 +3126,24 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) enum cursor_shape shape = CURSOR_SHAPE_NONE; switch (term->active_surface) { - case TERM_SURF_GRID: { - bool have_custom_cursor = - render_xcursor_is_valid(seat, term->mouse_user_cursor); + case TERM_SURF_GRID: + if (seat->pointer.hidden) + shape = CURSOR_SHAPE_HIDDEN; - shape = seat->pointer.hidden ? CURSOR_SHAPE_HIDDEN - : have_custom_cursor ? CURSOR_SHAPE_CUSTOM //term->mouse_user_cursor - : term->is_searching ? CURSOR_SHAPE_LEFT_PTR - : (seat->mouse.col >= 0 && - seat->mouse.row >= 0 && - term_mouse_grabbed(term, seat)) ? CURSOR_SHAPE_TEXT - : CURSOR_SHAPE_LEFT_PTR; + else if (render_xcursor_is_valid(seat, term->mouse_user_cursor)) + shape = CURSOR_SHAPE_CUSTOM; + + else if (seat->mouse.col >= 0 && + seat->mouse.row >= 0 && + term_mouse_grabbed(term, seat)) + { + shape = CURSOR_SHAPE_TEXT; + } + + else + shape = CURSOR_SHAPE_LEFT_PTR; break; - } + case TERM_SURF_TITLE: case TERM_SURF_BUTTON_MINIMIZE: case TERM_SURF_BUTTON_MAXIMIZE: From bf83a0b2bdb2299c95b6296fe8fd93bf0b99b743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Jun 2023 08:38:20 +0200 Subject: [PATCH 081/135] meson: cursor-shape: use .xml from wayland-protocols This patch assumes a git snapshot of wayland-protocols are installed. We need to bump the version number as soon as the next version of wayland-protocols have been released. --- cursor-shape-v1.xml | 147 -------------------------------------------- meson.build | 12 ++-- 2 files changed, 7 insertions(+), 152 deletions(-) delete mode 100644 cursor-shape-v1.xml diff --git a/cursor-shape-v1.xml b/cursor-shape-v1.xml deleted file mode 100644 index 56f6a1a6..00000000 --- a/cursor-shape-v1.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - - Copyright 2018 The Chromium Authors - Copyright 2023 Simon Ser - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - - This global offers an alternative, optional way to set cursor images. This - new way uses enumerated cursors instead of a wl_surface like - wl_pointer.set_cursor does. - - Warning! The protocol described in this file is currently in the testing - phase. Backward compatible changes may be added together with the - corresponding interface version bump. Backward incompatible changes can - only be done by creating a new major version of the extension. - - - - - Destroy the cursor shape manager. - - - - - - Obtain a wp_cursor_shape_device_v1 for a wl_pointer object. - - - - - - - - Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object. - - - - - - - - - This interface advertises the list of supported cursor shapes for a - device, and allows clients to set the cursor shape. - - - - - This enum describes cursor shapes. - - The names are taken from the CSS W3C specification: - https://w3c.github.io/csswg-drafts/css-ui/#cursor - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Destroy the cursor shape device. - - The device cursor shape remains unchanged. - - - - - - Sets the device cursor to the specified shape. The compositor will - change the cursor image based on the specified shape. - - The cursor actually changes only if the input device focus is one of - the requesting client's surfaces. If any, the previous cursor image - (surface or shape) is replaced. - - The "shape" argument must be a valid enum entry, otherwise the - invalid_shape protocol error is raised. - - This is similar to the wl_pointer.set_cursor and - zwp_tablet_tool_v2.set_cursor requests, but this request accepts a - shape instead of contents in the form of a surface. Clients can mix - set_cursor and set_shape requests. - - The serial parameter must match the latest wl_pointer.enter or - zwp_tablet_tool_v2.proximity_in serial number sent to the client. - Otherwise the request will be ignored. - - - - - - diff --git a/meson.build b/meson.build index ed0bf4a0..8535a4d1 100644 --- a/meson.build +++ b/meson.build @@ -170,11 +170,13 @@ else fractional_scale = false endif -# TODO: check wayland-protocols version -wl_proto_xml += [wayland_protocols_datadir + '/unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1 - 'cursor-shape-v1.xml', # TODO: use wayland-protocols - ] -add_project_arguments('-DHAVE_CURSOR_SHAPE', language: 'c') +if wayland_protocols.version().version_compare('>=1.31') # TODO: 1.32 + wl_proto_xml += [ + wayland_protocols_datadir + '/unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1 + wayland_protocols_datadir + '/staging/cursor-shape/cursor-shape-v1.xml', + ] + add_project_arguments('-DHAVE_CURSOR_SHAPE', language: 'c') +endif foreach prot : wl_proto_xml wl_proto_headers += custom_target( From c2baaff3c1311335b46e75db27bfd79d4f7b623b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Jun 2023 13:25:08 +0200 Subject: [PATCH 082/135] cursor-shape: use server-side cursors for custom (OSC-22), if possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using a lookup table, try to map the user-provided xcursor string to a cursor-shape-v1 known shape. If we succeed, set the user’s custom cursor using server side cursors (i.e. using cursor-shape-v1). If not, fallback to trying to load the image ourselves (using wl_cursor_theme_get_cursor()), and set it using the legacy wl_pointer_set_cursor(). --- cursor-shape.c | 63 +++++++++++++++++++++++++++++++++- cursor-shape.h | 2 ++ render.c | 92 ++++++++++++++++++++++++++++++++++---------------- terminal.c | 13 +++++-- wayland.c | 1 + wayland.h | 2 ++ 6 files changed, 141 insertions(+), 32 deletions(-) diff --git a/cursor-shape.c b/cursor-shape.c index 48f7419f..aafeae8b 100644 --- a/cursor-shape.c +++ b/cursor-shape.c @@ -1,4 +1,9 @@ #include +#include + +#define LOG_MODULE "cursor-shape" +#define LOG_ENABLE_DBG 0 +#include "log.h" #include "cursor-shape.h" #include "debug.h" @@ -30,6 +35,7 @@ cursor_shape_to_string(enum cursor_shape shape) } #if defined(HAVE_CURSOR_SHAPE) + enum wp_cursor_shape_device_v1_shape cursor_shape_to_server_shape(enum cursor_shape shape) { @@ -51,4 +57,59 @@ cursor_shape_to_server_shape(enum cursor_shape shape) xassert(table[shape] != 0); return table[shape]; } -#endif + +enum wp_cursor_shape_device_v1_shape +cursor_string_to_server_shape(const char *xcursor) +{ + if (xcursor == NULL) + return 0; + + static const char *const table[][2] = { + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT] = {"default", "left_ptr"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU] = {"context-menu"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP] = {"help", "question_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER] = {"pointer", "hand"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS] = {"progress", "left_ptr_watch"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT] = {"wait", "watch"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL] = {"cell"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR] = {"crosshair", "cross"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT] = {"text", "xterm"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT] = {"vertical-text"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS] = {"alias", "dnd-link"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY] = {"copy", "dnd-copy"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE] = {"move"}, /* dnd-move? */ + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP] = {"no-drop", "dnd-no-drop"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED] = {"not-allowed", "crossed_circle"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB] = {"grab", "hand1"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING] = {"grabbing"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE] = {"e-resize", "right_side"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE] = {"n-resize", "top_side"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE] = {"ne-resize", "top_right_corner"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE] = {"nw-resize", "top_left_corner"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE] = {"s-resize", "bottom_side"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE] = {"se-resize", "bottom_right_corner"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE] = {"sw-resize", "bottom_left_corner"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE] = {"w-resize", "left_side"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE] = {"ew-resize", "sb_h_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE] = {"ns-resize", "sb_v_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE] = {"nesw-resize", "fd_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE] = {"nwse-resize", "bd_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE] = {"col-resize", "sb_h_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE] = {"row-resize", "sb_v_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL] = {"all-scroll", "fleur"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN] = {"zoom-in"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT] = {"zoom-out"}, + }; + + for (size_t i = 0; i < ALEN(table); i++) { + for (size_t j = 0; j < ALEN(table[i]); j++) { + if (table[i][j] != NULL && strcmp(xcursor, table[i][j]) == 0) { + return i; + } + } + } + + return 0; +} + +#endif /* HAVE_CURSOR_SHAPE */ diff --git a/cursor-shape.h b/cursor-shape.h index 0cb0b4d8..a9619553 100644 --- a/cursor-shape.h +++ b/cursor-shape.h @@ -29,4 +29,6 @@ const char *cursor_shape_to_string(enum cursor_shape shape); #if defined(HAVE_CURSOR_SHAPE) enum wp_cursor_shape_device_v1_shape cursor_shape_to_server_shape( enum cursor_shape shape); +enum wp_cursor_shape_device_v1_shape cursor_string_to_server_shape( + const char *xcursor); #endif diff --git a/render.c b/render.c index b0b1ec32..df7a43f9 100644 --- a/render.c +++ b/render.c @@ -4270,41 +4270,62 @@ render_xcursor_update(struct seat *seat) xassert(seat->pointer.cursor != NULL); + const enum cursor_shape shape = seat->pointer.shape; + const char *const xcursor = seat->pointer.last_custom_xcursor; + #if defined(HAVE_CURSOR_SHAPE) - if (seat->pointer.shape_device != NULL && - seat->pointer.shape != CURSOR_SHAPE_CUSTOM) - { - LOG_DBG("setting cursor shape using cursor-shape-v1"); - wp_cursor_shape_device_v1_set_shape( - seat->pointer.shape_device, - seat->pointer.serial, - cursor_shape_to_server_shape(seat->pointer.shape)); - } else -#endif - { - LOG_DBG("setting cursor shape using a client-side cursor surface"); - const int scale = seat->pointer.scale; - struct wl_cursor_image *image = seat->pointer.cursor->images[0]; + if (seat->pointer.shape_device != NULL) { + xassert(shape != CURSOR_SHAPE_CUSTOM || xcursor != NULL); - wl_surface_attach( - seat->pointer.surface.surf, wl_cursor_image_get_buffer(image), 0, 0); + const enum wp_cursor_shape_device_v1_shape custom_shape = + (shape == CURSOR_SHAPE_CUSTOM && xcursor != NULL + ? cursor_string_to_server_shape(xcursor) + : 0); - wl_pointer_set_cursor( - seat->wl_pointer, seat->pointer.serial, - seat->pointer.surface.surf, - image->hotspot_x / scale, image->hotspot_y / scale); + if (shape != CURSOR_SHAPE_CUSTOM || custom_shape != 0) { + xassert(custom_shape == 0 || shape == CURSOR_SHAPE_CUSTOM); - wl_surface_damage_buffer( - seat->pointer.surface.surf, 0, 0, INT32_MAX, INT32_MAX); + const enum wp_cursor_shape_device_v1_shape wp_shape = custom_shape != 0 + ? custom_shape + : cursor_shape_to_server_shape(shape); - wl_surface_set_buffer_scale(seat->pointer.surface.surf, scale); + LOG_DBG("setting %scursor shape using cursor-shape-v1", + custom_shape != 0 ? "custom " : ""); - xassert(seat->pointer.xcursor_callback == NULL); - seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface.surf); - wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat); + wp_cursor_shape_device_v1_set_shape( + seat->pointer.shape_device, + seat->pointer.serial, + wp_shape); - wl_surface_commit(seat->pointer.surface.surf); + return; + } } +#endif + + LOG_DBG("setting %scursor shape using a client-side cursor surface", + shape == CURSOR_SHAPE_CUSTOM ? "custom " : ""); + + const int scale = seat->pointer.scale; + struct wl_cursor_image *image = seat->pointer.cursor->images[0]; + + wl_surface_attach( + seat->pointer.surface.surf, wl_cursor_image_get_buffer(image), 0, 0); + + wl_pointer_set_cursor( + seat->wl_pointer, seat->pointer.serial, + seat->pointer.surface.surf, + image->hotspot_x / scale, image->hotspot_y / scale); + + wl_surface_damage_buffer( + seat->pointer.surface.surf, 0, 0, INT32_MAX, INT32_MAX); + + wl_surface_set_buffer_scale(seat->pointer.surface.surf, scale); + + xassert(seat->pointer.xcursor_callback == NULL); + seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface.surf); + wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat); + + wl_surface_commit(seat->pointer.surface.surf); } static void @@ -4467,8 +4488,13 @@ render_xcursor_set(struct seat *seat, struct terminal *term, return true; } - if (seat->pointer.shape == shape) + if (seat->pointer.shape == shape && + !(shape == CURSOR_SHAPE_CUSTOM && + strcmp(seat->pointer.last_custom_xcursor, + term->mouse_user_cursor) != 0)) + { return true; + } /* TODO: skip this when using server-side cursors */ if (shape != CURSOR_SHAPE_HIDDEN) { @@ -4491,8 +4517,16 @@ render_xcursor_set(struct seat *seat, struct terminal *term, return false; } } - } else + + if (shape == CURSOR_SHAPE_CUSTOM) { + free(seat->pointer.last_custom_xcursor); + seat->pointer.last_custom_xcursor = xstrdup(term->mouse_user_cursor); + } + } else { seat->pointer.cursor = NULL; + free(seat->pointer.last_custom_xcursor); + seat->pointer.last_custom_xcursor = NULL; + } /* FDM hook takes care of actual rendering */ seat->pointer.shape = shape; diff --git a/terminal.c b/terminal.c index 3f2c9095..4a0d99cd 100644 --- a/terminal.c +++ b/terminal.c @@ -3130,8 +3130,15 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) if (seat->pointer.hidden) shape = CURSOR_SHAPE_HIDDEN; - else if (render_xcursor_is_valid(seat, term->mouse_user_cursor)) +#if defined(HAVE_CURSOR_SHAPE) + else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 +#elif + else if (true +#endif + || render_xcursor_is_valid(seat, term->mouse_user_cursor)) + { shape = CURSOR_SHAPE_CUSTOM; + } else if (seat->mouse.col >= 0 && seat->mouse.row >= 0 && @@ -3716,6 +3723,8 @@ void term_set_user_mouse_cursor(struct terminal *term, const char *cursor) { free(term->mouse_user_cursor); - term->mouse_user_cursor = cursor != NULL ? xstrdup(cursor) : NULL; + term->mouse_user_cursor = cursor != NULL && strlen(cursor) > 0 + ? xstrdup(cursor) + : NULL; term_xcursor_update(term); } diff --git a/wayland.c b/wayland.c index f3a5619d..e862b5e8 100644 --- a/wayland.c +++ b/wayland.c @@ -234,6 +234,7 @@ seat_destroy(struct seat *seat) ime_reset_pending(seat); free(seat->clipboard.text); free(seat->primary.text); + free(seat->pointer.last_custom_xcursor); free(seat->name); } diff --git a/wayland.h b/wayland.h index b30ad307..d2c1ead1 100644 --- a/wayland.h +++ b/wayland.h @@ -159,6 +159,8 @@ struct seat { float scale; bool hidden; enum cursor_shape shape; + char *last_custom_xcursor; + struct wl_callback *xcursor_callback; bool xcursor_pending; } pointer; From c2e481fb6af7a2b9c7e3237436aee67019a32878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Jun 2023 16:06:01 +0200 Subject: [PATCH 083/135] meson: bump wayland-protocols version required for cursor-shape to 1.32 --- meson.build | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 8535a4d1..10746d9d 100644 --- a/meson.build +++ b/meson.build @@ -169,8 +169,7 @@ if wayland_protocols.version().version_compare('>=1.31') else fractional_scale = false endif - -if wayland_protocols.version().version_compare('>=1.31') # TODO: 1.32 +if wayland_protocols.version().version_compare('>=1.32') wl_proto_xml += [ wayland_protocols_datadir + '/unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1 wayland_protocols_datadir + '/staging/cursor-shape/cursor-shape-v1.xml', From 7bfa700c556c529036646e3224c6969e845840f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Jun 2023 16:06:52 +0200 Subject: [PATCH 084/135] terminal: #elif -> #else --- terminal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal.c b/terminal.c index 4a0d99cd..2d1313c1 100644 --- a/terminal.c +++ b/terminal.c @@ -3132,7 +3132,7 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) #if defined(HAVE_CURSOR_SHAPE) else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 -#elif +#else else if (true #endif || render_xcursor_is_valid(seat, term->mouse_user_cursor)) From 6388954e8f908a8cfd2f170c6a4f2c05370de762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Jun 2023 16:07:56 +0200 Subject: [PATCH 085/135] =?UTF-8?q?render:=20move=20variables=20inside=20#?= =?UTF-8?q?ifdef,=20as=20they=E2=80=99re=20not=20used=20outside=20of=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index df7a43f9..5a757a88 100644 --- a/render.c +++ b/render.c @@ -4270,10 +4270,10 @@ render_xcursor_update(struct seat *seat) xassert(seat->pointer.cursor != NULL); +#if defined(HAVE_CURSOR_SHAPE) const enum cursor_shape shape = seat->pointer.shape; const char *const xcursor = seat->pointer.last_custom_xcursor; -#if defined(HAVE_CURSOR_SHAPE) if (seat->pointer.shape_device != NULL) { xassert(shape != CURSOR_SHAPE_CUSTOM || xcursor != NULL); From a361d7917b311cad2def1ef42e740670cbddd7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Jun 2023 16:12:54 +0200 Subject: [PATCH 086/135] main/client: add a version feature flag for cursor-shape --- client.c | 3 ++- foot-features.h | 9 +++++++++ main.c | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/client.c b/client.c index 84bfb2c3..99c7c1e8 100644 --- a/client.c +++ b/client.c @@ -67,12 +67,13 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %cfractional-scaling %cassertions", + "version: %s %cpgo %cime %cgraphemes %cfractional-scaling %ccursor-shape %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', feature_fractional_scaling() ? '+' : ':', + feature_cursor_shape() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } diff --git a/foot-features.h b/foot-features.h index 77923aaf..f8043c12 100644 --- a/foot-features.h +++ b/foot-features.h @@ -46,3 +46,12 @@ static inline bool feature_fractional_scaling(void) return false; #endif } + +static inline bool feature_cursor_shape(void) +{ +#if defined(HAVE_CURSOR_SHAPE) + return true; +#else + return false; +#endif +} diff --git a/main.c b/main.c index 6dd9e468..fc329574 100644 --- a/main.c +++ b/main.c @@ -53,12 +53,13 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %cfractional-scaling %cassertions", + "version: %s %cpgo %cime %cgraphemes %cfractional-scaling %ccursor-shape %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', feature_fractional_scaling() ? '+' : '-', + feature_cursor_shape() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } From 8fc43ccd2d80488dc3ef1b73e6a1309c4530e7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Jun 2023 08:29:21 +0200 Subject: [PATCH 087/135] meson: log availability of cursor-shape-v1 --- meson.build | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/meson.build b/meson.build index 10746d9d..0d75e1e4 100644 --- a/meson.build +++ b/meson.build @@ -175,6 +175,9 @@ if wayland_protocols.version().version_compare('>=1.32') wayland_protocols_datadir + '/staging/cursor-shape/cursor-shape-v1.xml', ] add_project_arguments('-DHAVE_CURSOR_SHAPE', language: 'c') + cursor_shape = true +else + cursor_shape = false endif foreach prot : wl_proto_xml @@ -388,6 +391,7 @@ summary( 'Grapheme clustering': utf8proc.found(), 'Wayland: xdg-activation-v1': xdg_activation, 'Wayland: fractional-scale-v1': fractional_scale, + 'Wayland: cursor-shape-v1': cursor_shape, 'utmp backend': utmp_backend, 'utmp helper default path': utmp_default_helper_path, 'Build terminfo': tic.found(), From ba09d55aabb8979a305c13afe8d51f98e5118410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Jul 2023 14:26:01 +0200 Subject: [PATCH 088/135] term_xcursor_update_for_seat(): fix missing evaluation of render_xcursor_is_valid() When compiling *without* cursor-shape-v1 support, term_xcursor_update_for_seat() would incorrectly set shape=CURSOR_SHAPE_CUSTOM, even though no custom cursor had been set by the user. This resulted in a crash in render_xcursor_set(), when trying to use a NULL-string as custom cursor. --- terminal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal.c b/terminal.c index 2d1313c1..fff55019 100644 --- a/terminal.c +++ b/terminal.c @@ -3133,7 +3133,7 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) #if defined(HAVE_CURSOR_SHAPE) else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 #else - else if (true + else if (false #endif || render_xcursor_is_valid(seat, term->mouse_user_cursor)) { From 3800b279d668403699e96c3c7974d63bf985d83b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Jul 2023 14:42:22 +0200 Subject: [PATCH 089/135] =?UTF-8?q?meson:=20move=20cursor-shape.{c,h}=20fr?= =?UTF-8?q?om=20=E2=80=98foot=E2=80=99=20binary=20to=20vtlib?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should fix a build issue when doing partial PGO builds, when cursor-shape-v1 is *available*. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 0d75e1e4..fc2491d2 100644 --- a/meson.build +++ b/meson.build @@ -233,6 +233,7 @@ vtlib = static_library( 'vtlib', 'base64.c', 'base64.h', 'composed.c', 'composed.h', + 'cursor-shape.c', 'cursor-shape.h', 'csi.c', 'csi.h', 'dcs.c', 'dcs.h', 'macros.h', @@ -271,7 +272,6 @@ executable( 'box-drawing.c', 'box-drawing.h', 'config.c', 'config.h', 'commands.c', 'commands.h', - 'cursor-shape.c', 'cursor-shape.h', 'extract.c', 'extract.h', 'fdm.c', 'fdm.h', 'foot-features.h', From 247035e9e4f8b54ed48e51971bbdd337866038b5 Mon Sep 17 00:00:00 2001 From: Craig Barnes Date: Mon, 3 Jul 2023 20:11:20 +0100 Subject: [PATCH 090/135] meson: fix typo in meson_options.txt When using muon[1] instead of meson, this was causing the following error: $ muon setup bld .../meson_options.txt:26:124: error unterminated hex escape .../meson_options.txt:26:223: error unterminated string [1]: https://muon.build/ --- meson_options.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson_options.txt b/meson_options.txt index 76121e60..d16e23ae 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -23,6 +23,6 @@ option('systemd-units-dir', type: 'string', value: '', description: 'Where to install the systemd service files (absolute path). Default: ${systemduserunitdir}') option('utmp-backend', type: 'combo', value: 'auto', choices: ['none', 'libutempter', 'ulog', 'auto'], - description: 'Which utmp logging backend to use. This affects how (with what arguments) the utmp helper binary (see \utmp-default-helper-path\')is called. Default: auto (linux=libutempter, freebsd=ulog, others=none)') + description: 'Which utmp logging backend to use. This affects how (with what arguments) the utmp helper binary (see \'utmp-default-helper-path\')is called. Default: auto (linux=libutempter, freebsd=ulog, others=none)') option('utmp-default-helper-path', type: 'string', value: 'auto', description: 'Default path to the utmp helper binary. Default: auto-detect') From 4a7382891112d898d4ba0f8d445439b8ad86477e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 4 Jul 2023 08:38:07 +0200 Subject: [PATCH 091/135] changelog: fractional-scaling-v1 -> fractional-scale-v1 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6625c56d..7cbc5b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,9 +50,9 @@ is `auto`, which will select `libutempter` on Linux, `ulog` on FreeBSD, and `none` for all others. * Sixel aspect ratio. -* Support for the new fractional-scaling-v1 Wayland protocol. This +* Support for the new `fractional-scale-v1` Wayland protocol. This brings true fractional scaling to Wayland in general, and with this - release, foot. + release, to foot. * Support for the new `cursor-shape-v1` Wayland protocol, i.e. server side cursor shapes ([#1379][1379]). From d2fcb5343f57c132abec9a21ad89d1505a32c47c Mon Sep 17 00:00:00 2001 From: CismonX Date: Wed, 5 Jul 2023 00:19:21 +0800 Subject: [PATCH 092/135] input: add basic support for touchscreen input Closes #517 --- CHANGELOG.md | 2 + README.md | 12 ++ config.c | 20 ++++ config.h | 4 + doc/foot.1.scd | 12 ++ doc/foot.ini.5.scd | 8 ++ foot.ini | 3 + input.c | 266 ++++++++++++++++++++++++++++++++++++++++---- input.h | 1 + tests/test-config.c | 16 +++ wayland.c | 23 +++- wayland.h | 19 ++++ 12 files changed, 364 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cbc5b61..87a35bdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,8 +55,10 @@ release, to foot. * Support for the new `cursor-shape-v1` Wayland protocol, i.e. server side cursor shapes ([#1379][1379]). +* Support for touchscreen input ([#517][517]). [1379]: https://codeberg.org/dnkl/foot/issues/1379 +[517]: https://codeberg.org/dnkl/foot/issues/517 ### Changed diff --git a/README.md b/README.md index 0d6262dc..42be5792 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator. 1. [Normal mode](#normal-mode) 1. [Scrollback search](#scrollback-search) 1. [Mouse](#mouse) + 1. [Touchscreen](#touchscreen) 1. [Server (daemon) mode](#server-daemon-mode) 1. [URLs](#urls) 1. [Shell integration](#shell-integration) @@ -246,6 +247,17 @@ These are the default shortcuts. See `man foot.ini` and the example : Scroll up/down in history +### Touchscreen + +tap +: Emulates mouse left button click. + +drag +: Scrolls up/down in history. +: Holding for a while before dragging (time delay can be configured) + emulates mouse dragging with left button held. + + ## Server (daemon) mode When run normally, **foot** is a single-window application; if you diff --git a/config.c b/config.c index 3d02355f..5297bbdc 100644 --- a/config.c +++ b/config.c @@ -2475,6 +2475,20 @@ parse_section_tweak(struct context *ctx) } } +static bool +parse_section_touch(struct context *ctx) { + struct config *conf = ctx->conf; + const char *key = ctx->key; + + if (strcmp(key, "long-press-delay") == 0) + return value_to_uint32(ctx, 10, &conf->touch.long_press_delay); + + else { + LOG_CONTEXTUAL_ERR("not a valid option: %s", key); + return false; + } +} + static bool parse_key_value(char *kv, const char **section, const char **key, const char **value) { @@ -2554,6 +2568,7 @@ enum section { SECTION_TEXT_BINDINGS, SECTION_ENVIRONMENT, SECTION_TWEAK, + SECTION_TOUCH, SECTION_COUNT, }; @@ -2579,6 +2594,7 @@ static const struct { [SECTION_TEXT_BINDINGS] = {&parse_section_text_bindings, "text-bindings"}, [SECTION_ENVIRONMENT] = {&parse_section_environment, "environment"}, [SECTION_TWEAK] = {&parse_section_tweak, "tweak"}, + [SECTION_TOUCH] = {&parse_section_touch, "touch"}, }; static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch"); @@ -3026,6 +3042,10 @@ config_load(struct config *conf, const char *conf_path, .sixel = true, }, + .touch = { + .long_press_delay = 400, + }, + .env_vars = tll_init(), #if defined(UTMP_DEFAULT_HELPER_PATH) .utmp_helper_path = ((strlen(UTMP_DEFAULT_HELPER_PATH) > 0 && diff --git a/config.h b/config.h index 2034752f..20c07f6c 100644 --- a/config.h +++ b/config.h @@ -347,6 +347,10 @@ struct config { bool sixel; } tweak; + struct { + uint32_t long_press_delay; + } touch; + user_notifications_t notifications; }; diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 60420bef..1cdf47e4 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -283,6 +283,18 @@ default) available; see *foot.ini*(5). *wheel* Scroll up/down in history +## TOUCHSCREEN + +*tap* + Emulates mouse left button click. + +*drag* + Scrolls up/down in history. + + Holding for a while before dragging (time delay can be configured) + emulates mouse dragging with left button held. + + # FONT FORMAT The font is specified in FontConfig syntax. That is, a colon-separated diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index e28cf416..ac22ae5a 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -535,6 +535,14 @@ applications can change these at runtime. Default: _yes_. +# SECTION: touch + +*long-press-delay* + Number of milliseconds to distinguish between a short press and + a long press on the touchscreen. + + Default: _400_. + # SECTION: colors This section controls the 16 ANSI colors, the default foreground and diff --git a/foot.ini b/foot.ini index 61e88aec..94d82f6f 100644 --- a/foot.ini +++ b/foot.ini @@ -70,6 +70,9 @@ # hide-when-typing=no # alternate-scroll-mode=yes +[touch] +# long-press-delay=400 + [colors] # alpha=1.0 # background=002b36 diff --git a/input.c b/input.c index 0f638cc1..3bf6535a 100644 --- a/input.c +++ b/input.c @@ -1721,6 +1721,36 @@ xcursor_for_csd_border(struct terminal *term, int x, int y) } } +static void +mouse_button_state_reset(struct seat *seat) +{ + tll_free(seat->mouse.buttons); + seat->mouse.count = 0; + seat->mouse.last_released_button = 0; + memset(&seat->mouse.last_time, 0, sizeof(seat->mouse.last_time)); +} + +static void +mouse_coord_pixel_to_cell(struct seat *seat, const struct terminal *term, + int x, int y) +{ + /* + * Translate x,y pixel coordinate to a cell coordinate, or -1 + * if the cursor is outside the grid. I.e. if it is inside the + * margins. + */ + + if (x < term->margins.left || x >= term->width - term->margins.right) + seat->mouse.col = -1; + else + seat->mouse.col = (x - term->margins.left) / term->cell_width; + + if (y < term->margins.top || y >= term->height - term->margins.bottom) + seat->mouse.row = -1; + else + seat->mouse.row = (y - term->margins.top) / term->cell_height; +} + static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, @@ -1733,6 +1763,24 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, } struct seat *seat = data; + + if (seat->wl_touch != NULL) { + switch (seat->touch.state) { + case TOUCH_STATE_IDLE: + mouse_button_state_reset(seat); + seat->touch.state = TOUCH_STATE_INHIBITED; + break; + + case TOUCH_STATE_INHIBITED: + break; + + case TOUCH_STATE_HELD: + case TOUCH_STATE_DRAGGING: + case TOUCH_STATE_SCROLLING: + return; + } + } + struct wl_window *win = wl_surface_get_user_data(surface); struct terminal *term = win->term; @@ -1759,22 +1807,7 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, switch (term->active_surface) { case TERM_SURF_GRID: { - /* - * Translate x,y pixel coordinate to a cell coordinate, or -1 - * if the cursor is outside the grid. I.e. if it is inside the - * margins. - */ - - if (x < term->margins.left || x >= term->width - term->margins.right) - seat->mouse.col = -1; - else - seat->mouse.col = (x - term->margins.left) / term->cell_width; - - if (y < term->margins.top || y >= term->height - term->margins.bottom) - seat->mouse.row = -1; - else - seat->mouse.row = (y - term->margins.top) / term->cell_height; - + mouse_coord_pixel_to_cell(seat, term, x, y); break; } @@ -1802,6 +1835,14 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { struct seat *seat = data; + + if (seat->wl_touch != NULL) { + if (seat->touch.state != TOUCH_STATE_INHIBITED) { + return; + } + seat->touch.state = TOUCH_STATE_IDLE; + } + struct terminal *old_moused = seat->mouse_focus; LOG_DBG( @@ -1824,10 +1865,7 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, /* Reset mouse state */ seat->mouse.x = seat->mouse.y = 0; seat->mouse.col = seat->mouse.row = 0; - tll_free(seat->mouse.buttons); - seat->mouse.count = 0; - seat->mouse.last_released_button = 0; - memset(&seat->mouse.last_time, 0, sizeof(seat->mouse.last_time)); + mouse_button_state_reset(seat); for (size_t i = 0; i < ALEN(seat->mouse.aggregated); i++) seat->mouse.aggregated[i] = 0.0; seat->mouse.have_discrete = false; @@ -1879,6 +1917,11 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct seat *seat = data; + + /* Touch-emulated pointer events have wl_pointer == NULL. */ + if (wl_pointer != NULL && seat->touch.state != TOUCH_STATE_INHIBITED) + return; + struct wayland *wayl = seat->wayl; struct terminal *term = seat->mouse_focus; @@ -2102,6 +2145,11 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, xassert(serial != 0); struct seat *seat = data; + + /* Touch-emulated pointer events have wl_pointer == NULL. */ + if (wl_pointer != NULL && seat->touch.state != TOUCH_STATE_INHIBITED) + return; + struct wayland *wayl = seat->wayl; struct terminal *term = seat->mouse_focus; @@ -2559,6 +2607,9 @@ wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, { struct seat *seat = data; + if (seat->touch.state != TOUCH_STATE_INHIBITED) + return; + if (seat->mouse.have_discrete) return; @@ -2588,6 +2639,10 @@ wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { struct seat *seat = data; + + if (seat->touch.state != TOUCH_STATE_INHIBITED) + return; + seat->mouse.have_discrete = true; int amount = discrete; @@ -2604,6 +2659,10 @@ static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { struct seat *seat = data; + + if (seat->touch.state != TOUCH_STATE_INHIBITED) + return; + seat->mouse.have_discrete = false; } @@ -2619,6 +2678,9 @@ wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, { struct seat *seat = data; + if (seat->touch.state != TOUCH_STATE_INHIBITED) + return; + xassert(axis < ALEN(seat->mouse.aggregated)); seat->mouse.aggregated[axis] = 0.; } @@ -2634,3 +2696,167 @@ const struct wl_pointer_listener pointer_listener = { .axis_stop = wl_pointer_axis_stop, .axis_discrete = wl_pointer_axis_discrete, }; + +static bool +touch_to_scroll(struct seat *seat, struct terminal *term, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + bool coord_updated = false; + + int y = wl_fixed_to_int(surface_y) * term->scale; + int rows = (y - seat->mouse.y) / term->cell_height; + if (rows != 0) { + mouse_scroll(seat, -rows, WL_POINTER_AXIS_VERTICAL_SCROLL); + seat->mouse.y += rows * term->cell_height; + coord_updated = true; + } + + int x = wl_fixed_to_int(surface_x) * term->scale; + int cols = (x - seat->mouse.x) / term->cell_width; + if (cols != 0) { + mouse_scroll(seat, -cols, WL_POINTER_AXIS_HORIZONTAL_SCROLL); + seat->mouse.x += cols * term->cell_width; + coord_updated = true; + } + + return coord_updated; +} + +static void +wl_touch_down(void *data, struct wl_touch *wl_touch, uint32_t serial, + uint32_t time, struct wl_surface *surface, int32_t id, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + struct seat *seat = data; + + if (seat->touch.state != TOUCH_STATE_IDLE) + return; + + struct wl_window *win = wl_surface_get_user_data(surface); + struct terminal *term = win->term; + + term->active_surface = term_surface_kind(term, surface); + if (term->active_surface != TERM_SURF_GRID) + return; + + LOG_DBG("touch_down: touch=%p, x=%d, y=%d", (void *)wl_touch, + wl_fixed_to_int(surface_x), wl_fixed_to_int(surface_y)); + + int x = wl_fixed_to_int(surface_x) * term->scale; + int y = wl_fixed_to_int(surface_y) * term->scale; + + seat->mouse.x = x; + seat->mouse.y = y; + mouse_coord_pixel_to_cell(seat, term, x, y); + + seat->touch.state = TOUCH_STATE_HELD; + seat->touch.serial = serial; + seat->touch.time = time + term->conf->touch.long_press_delay; + seat->touch.surface = surface; + seat->touch.id = id; +} + +static void +wl_touch_up(void *data, struct wl_touch *wl_touch, uint32_t serial, + uint32_t time, int32_t id) +{ + struct seat *seat = data; + + if (seat->touch.state <= TOUCH_STATE_IDLE || id != seat->touch.id) + return; + + LOG_DBG("touch_up: touch=%p", (void *)wl_touch); + + struct wl_window *win = wl_surface_get_user_data(seat->touch.surface); + struct terminal *term = win->term; + + seat->mouse_focus = term; + + switch (seat->touch.state) { + case TOUCH_STATE_HELD: + wl_pointer_button(seat, NULL, seat->touch.serial, time, BTN_LEFT, + WL_POINTER_BUTTON_STATE_PRESSED); + /* fallthrough */ + case TOUCH_STATE_DRAGGING: + wl_pointer_button(seat, NULL, serial, time, BTN_LEFT, + WL_POINTER_BUTTON_STATE_RELEASED); + /* fallthrough */ + case TOUCH_STATE_SCROLLING: + seat->touch.state = TOUCH_STATE_IDLE; + break; + + case TOUCH_STATE_INHIBITED: + case TOUCH_STATE_IDLE: + BUG("Bad touch state: %d", seat->touch.state); + break; + } + + seat->mouse_focus = NULL; +} + +static void +wl_touch_motion(void *data, struct wl_touch *wl_touch, uint32_t time, + int32_t id, wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + struct seat *seat = data; + if (seat->touch.state <= TOUCH_STATE_IDLE || id != seat->touch.id) + return; + + LOG_DBG("touch_motion: touch=%p, x=%d, y=%d", (void *)wl_touch, + wl_fixed_to_int(surface_x), wl_fixed_to_int(surface_y)); + + struct wl_window *win = wl_surface_get_user_data(seat->touch.surface); + struct terminal *term = win->term; + + seat->mouse_focus = term; + + switch (seat->touch.state) { + case TOUCH_STATE_HELD: + if (time <= seat->touch.time) { + if (touch_to_scroll(seat, term, surface_x, surface_y)) + seat->touch.state = TOUCH_STATE_SCROLLING; + break; + } else { + wl_pointer_button(seat, NULL, seat->touch.serial, time, BTN_LEFT, + WL_POINTER_BUTTON_STATE_PRESSED); + seat->touch.state = TOUCH_STATE_DRAGGING; + /* fallthrough */ + } + case TOUCH_STATE_DRAGGING: + wl_pointer_motion(seat, NULL, time, surface_x, surface_y); + break; + case TOUCH_STATE_SCROLLING: + touch_to_scroll(seat, term, surface_x, surface_y); + break; + + case TOUCH_STATE_INHIBITED: + case TOUCH_STATE_IDLE: + BUG("Bad touch state: %d", seat->touch.state); + break; + } + + seat->mouse_focus = NULL; +} + +static void +wl_touch_frame(void *data, struct wl_touch *wl_touch) +{ +} + +static void +wl_touch_cancel(void *data, struct wl_touch *wl_touch) +{ + struct seat *seat = data; + if (seat->touch.state == TOUCH_STATE_INHIBITED) + return; + + seat->touch.state = TOUCH_STATE_IDLE; +} + +const struct wl_touch_listener touch_listener = { + .down = wl_touch_down, + .up = wl_touch_up, + .motion = wl_touch_motion, + .frame = wl_touch_frame, + .cancel = wl_touch_cancel, +}; diff --git a/input.h b/input.h index 825dc3be..906008d5 100644 --- a/input.h +++ b/input.h @@ -26,6 +26,7 @@ extern const struct wl_keyboard_listener keyboard_listener; extern const struct wl_pointer_listener pointer_listener; +extern const struct wl_touch_listener touch_listener; void input_repeat(struct seat *seat, uint32_t key); diff --git a/tests/test-config.c b/tests/test-config.c index c70f7a43..e59c104e 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -662,6 +662,21 @@ test_section_mouse(void) config_free(&conf); } +static void +test_section_touch(void) +{ + struct config conf = {0}; + struct context ctx = { + .conf = &conf, .section = "touch", .path = "unittest"}; + + test_invalid_key(&ctx, &parse_section_touch, "invalid-key"); + + test_uint32(&ctx, &parse_section_touch, "long-press-delay", + &conf.touch.long_press_delay); + + config_free(&conf); +} + static void test_section_colors(void) { @@ -1347,6 +1362,7 @@ main(int argc, const char *const *argv) test_section_url(); test_section_cursor(); test_section_mouse(); + test_section_touch(); test_section_colors(); test_section_csd(); test_section_key_bindings(); diff --git a/wayland.c b/wayland.c index e862b5e8..a25ec0f6 100644 --- a/wayland.c +++ b/wayland.c @@ -222,6 +222,8 @@ seat_destroy(struct seat *seat) wl_keyboard_release(seat->wl_keyboard); if (seat->wl_pointer != NULL) wl_pointer_release(seat->wl_pointer); + if (seat->wl_touch != NULL) + wl_touch_release(seat->wl_touch); #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED if (seat->wl_text_input != NULL) @@ -284,9 +286,10 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, struct seat *seat = data; xassert(seat->wl_seat == wl_seat); - LOG_DBG("%s: keyboard=%s, pointer=%s", seat->name, + LOG_DBG("%s: keyboard=%s, pointer=%s, touch=%s", seat->name, (caps & WL_SEAT_CAPABILITY_KEYBOARD) ? "yes" : "no", - (caps & WL_SEAT_CAPABILITY_POINTER) ? "yes" : "no"); + (caps & WL_SEAT_CAPABILITY_POINTER) ? "yes" : "no", + (caps & WL_SEAT_CAPABILITY_TOUCH) ? "yes" : "no"); if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { if (seat->wl_keyboard == NULL) { @@ -359,6 +362,22 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, seat->pointer.cursor = NULL; } } + + if (caps & WL_SEAT_CAPABILITY_TOUCH) { + if (seat->wl_touch == NULL) { + seat->wl_touch = wl_seat_get_touch(wl_seat); + wl_touch_add_listener(seat->wl_touch, &touch_listener, seat); + + seat->touch.state = TOUCH_STATE_IDLE; + } + } else { + if (seat->wl_touch != NULL) { + wl_touch_release(seat->wl_touch); + seat->wl_touch = NULL; + } + + seat->touch.state = TOUCH_STATE_INHIBITED; + } } static void diff --git a/wayland.h b/wayland.h index d2c1ead1..6d1cd727 100644 --- a/wayland.h +++ b/wayland.h @@ -47,6 +47,14 @@ enum data_offer_mime_type { DATA_OFFER_MIME_TEXT_UTF8_STRING, }; +enum touch_state { + TOUCH_STATE_INHIBITED = -1, + TOUCH_STATE_IDLE, + TOUCH_STATE_HELD, + TOUCH_STATE_DRAGGING, + TOUCH_STATE_SCROLLING, +}; + struct wayl_surface { struct wl_surface *surf; #if defined(HAVE_FRACTIONAL_SCALE) @@ -165,6 +173,17 @@ struct seat { bool xcursor_pending; } pointer; + /* Touch state */ + struct wl_touch *wl_touch; + struct { + enum touch_state state; + + uint32_t serial; + uint32_t time; + struct wl_surface *surface; + int32_t id; + } touch; + struct { int x; int y; From 080a11eb7374af3335bde3841789bd2b659237c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Beaupr=C3=A9?= Date: Thu, 15 Dec 2022 11:49:51 -0500 Subject: [PATCH 093/135] bind control-shift-u to unicode-input, move urls to o Having a keybinding to invoke arbitrary unicode characters is very useful. It's often used as a method of last resort to communicate with people outside of your main language. For example, if you want to type the last letter of my real name, you can invoke the latin-1 character 0xe9 or unicode 0x00e9. You can also use this to type special characters, for example, unicode U+1F4A9 is of course, the infamous PILE OF POO, which is sure to produce million laughs everywhere you go. In foot, there's no keybinding by default to invoke the very useful unicode-input command. There is no "standard" (as in "ISO") keybinding this either. But there *is* a de-facto standard currently deployed by *both* GTK and Qt (a rare feat) *and* Chrome OS (an even rarer feat) and it's control-shift-u. Alternatives include Control-x 8 (emacs), Control V u (vim), Alt (Windows, LibreOffice), or Option (Mac). I doubt we want to adopt any of those. So let's use control-shift-u for this. Unfortunately, it's currently assigned to show-urls-launch, which is unfortunate, but insurmountable. We can reassign this keybinding elsewhere. I have picked control-shift-o in my configuration, because "o" is a good mnemonic for "open URLs". Others have suggested "m" instead. Closes: #1183 --- CHANGELOG.md | 4 ++++ config.c | 3 ++- doc/foot.1.scd | 2 +- doc/foot.ini.5.scd | 4 ++-- foot.ini | 4 ++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87a35bdd..f02cf039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,9 @@ ### Changed * Minimum required meson version is now 0.59 ([#1371][1371]). +* `Control+Shift+u` now bound to `unicode-input` to follow the + convention established in GTK and Qt, `show-urls-launch` now bound + to `Control+Shift+o` ([#1183][1183]) * Kitty keyboard protocol: F3 is now encoded as `CSI 13~` instead of `CSI R`. The kitty keyboard protocol originally allowed F3 to be encoded as `CSI R`, but this was removed from the specification @@ -92,6 +95,7 @@ removed. [1371]: https://codeberg.org/dnkl/foot/pulls/1371 +[1183]: https://codeberg.org/dnkl/foot/issues/1183 [1360]: https://codeberg.org/dnkl/foot/issues/1360 [1383]: https://codeberg.org/dnkl/foot/issues/1383 diff --git a/config.c b/config.c index 5297bbdc..1cefba3e 100644 --- a/config.c +++ b/config.c @@ -2807,7 +2807,8 @@ add_default_key_bindings(struct config *conf) {BIND_ACTION_FONT_SIZE_RESET, m_ctrl, {{XKB_KEY_0}}}, {BIND_ACTION_FONT_SIZE_RESET, m_ctrl, {{XKB_KEY_KP_0}}}, {BIND_ACTION_SPAWN_TERMINAL, m_ctrl_shift, {{XKB_KEY_n}}}, - {BIND_ACTION_SHOW_URLS_LAUNCH, m_ctrl_shift, {{XKB_KEY_u}}}, + {BIND_ACTION_SHOW_URLS_LAUNCH, m_ctrl_shift, {{XKB_KEY_o}}}, + {BIND_ACTION_UNICODE_INPUT, m_ctrl_shift, {{XKB_KEY_u}}}, {BIND_ACTION_PROMPT_PREV, m_ctrl_shift, {{XKB_KEY_z}}}, {BIND_ACTION_PROMPT_NEXT, m_ctrl_shift, {{XKB_KEY_x}}}, }; diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 1cdf47e4..6143275c 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -310,7 +310,7 @@ Foot supports URL detection. But, unlike many other terminal emulators, where URLs are highlighted when they are hovered and opened by clicking on them, foot uses a keyboard driven approach. -Pressing *ctrl*+*shift*+*u* enters _“URL mode”_, where all currently +Pressing *ctrl*+*shift*+*o* enters _“Open URL mode”_, where all currently visible URLs are underlined, and is associated with a _“jump-label”_. The jump-label indicates the _key sequence_ (e.g. *”AF”*) to use to activate the URL. diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index ac22ae5a..273a74c2 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -834,7 +834,7 @@ e.g. *search-start=none*. *show-urls-launch* Enter URL mode, where all currently visible URLs are tagged with a jump label with a key sequence that will open the URL (and exit - URL mode). Default: _Control+Shift+u_. + URL mode). Default: _Control+Shift+o_. *show-urls-persistent* Similar to *show-urls-launch*, but does not automatically exit URL @@ -877,7 +877,7 @@ e.g. *search-start=none*. fallback. The preferred way of entering Unicode characters, emojis etc is by using an IME. - Default: _none_. + Default: _Control+Shift+u_. # SECTION: search-bindings diff --git a/foot.ini b/foot.ini index 94d82f6f..2735d370 100644 --- a/foot.ini +++ b/foot.ini @@ -152,12 +152,12 @@ # pipe-visible=[sh -c "xurls | fuzzel | xargs -r firefox"] none # pipe-scrollback=[sh -c "xurls | fuzzel | xargs -r firefox"] none # pipe-selected=[xargs -r firefox] none -# show-urls-launch=Control+Shift+u +# show-urls-launch=Control+Shift+o # show-urls-copy=none # show-urls-persistent=none # prompt-prev=Control+Shift+z # prompt-next=Control+Shift+x -# unicode-input=none +# unicode-input=Control+Shift+u # noop=none [search-bindings] From 19e37b17aa9a636a457f952e292612fe4ed315f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Jul 2023 12:36:18 +0200 Subject: [PATCH 094/135] readme: a few more places mentioning the default URL mode shortcut --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 42be5792..5fb7ff4c 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ These are the default shortcuts. See `man foot.ini` and the example sequence](https://codeberg.org/dnkl/foot/wiki#user-content-spawning-new-terminal-instances-in-the-current-working-directory), the new terminal will start in the current working directory. -ctrl+shift+u +ctrl+shift+o : Enter URL mode, where all currently visible URLs are tagged with a jump label with a key sequence that will open the URL. @@ -299,7 +299,7 @@ Foot supports URL detection. But, unlike many other terminal emulators, where URLs are highlighted when they are hovered and opened by clicking on them, foot uses a keyboard driven approach. -Pressing ctrl+shift+u enters _“URL +Pressing ctrl+shift+o enters _“URL mode”_, where all currently visible URLs are underlined, and is associated with a _“jump-label”_. The jump-label indicates the _key sequence_ (e.g. **”AF”**) to use to activate the URL. From 87d45c2a01268bddd42d4a43009413af8a391f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Jul 2023 12:36:41 +0200 Subject: [PATCH 095/135] readme: add default shortcut for unicode input --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5fb7ff4c..b1cfb37d 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,9 @@ These are the default shortcuts. See `man foot.ini` and the example : Enter URL mode, where all currently visible URLs are tagged with a jump label with a key sequence that will open the URL. +ctrl+shift+u +: Enter Unicode input mode. + ctrl+shift+z : Jump to the previous, currently not visible, prompt. Requires [shell integration](https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts). From 5b74808ed0d60241417a040e4b82514cbfebeb2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Jul 2023 12:36:55 +0200 Subject: [PATCH 096/135] doc: foot: update default key binding for URL mode --- doc/foot.1.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 6143275c..62ca0374 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -202,7 +202,7 @@ default) available; see *foot.ini*(5). _OSC 7_ escape sequence, the new terminal will start in the current working directory. -*ctrl*+*shift*+*u* +*ctrl*+*shift*+*o* Activate URL mode, allowing you to "launch" URLs. *ctrl*+*shift*+*z* From 0e1dbbbd06caf9bcf978e9ca2cca3359e77b54df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Jul 2023 12:37:10 +0200 Subject: [PATCH 097/135] doc: foot: add default key binding for unicode input --- doc/foot.1.scd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 62ca0374..770c7f32 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -205,6 +205,9 @@ default) available; see *foot.ini*(5). *ctrl*+*shift*+*o* Activate URL mode, allowing you to "launch" URLs. +*ctrl*+*shift*+*u* + Activate Unicode input. + *ctrl*+*shift*+*z* Jump to the previous, currently not visible, prompt. Requires shell integration. From 58898c06339bd1374e975366515d437f22914bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Jul 2023 12:42:10 +0200 Subject: [PATCH 098/135] changelog: split up key binding changes for show-urls-launch and unicode-input --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f02cf039..0a36f9c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,9 +64,10 @@ ### Changed * Minimum required meson version is now 0.59 ([#1371][1371]). -* `Control+Shift+u` now bound to `unicode-input` to follow the - convention established in GTK and Qt, `show-urls-launch` now bound - to `Control+Shift+o` ([#1183][1183]) +* `Control+Shift+u` is now bound to `unicode-input` instead of + `show-urls-launch`, to follow the convention established in GTK and + Qt ([#1183][1183]). +* `show-urls-launch` now bound to `Control+Shift+o` ([#1183][1183]). * Kitty keyboard protocol: F3 is now encoded as `CSI 13~` instead of `CSI R`. The kitty keyboard protocol originally allowed F3 to be encoded as `CSI R`, but this was removed from the specification From 3609017c383a74bdb142c5e667fdaa35d11e6da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Jul 2023 12:42:36 +0200 Subject: [PATCH 099/135] =?UTF-8?q?changelog:=20mention=20the=20new=20defa?= =?UTF-8?q?ult=20key=20binding=20for=20show-urls-launch=20under=20?= =?UTF-8?q?=E2=80=9Cfixed=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It “fixes” the key binding conflict seen on e.g. GNOME, and increases the exposure of the change to, hopefully, more users. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a36f9c7..968dd1a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,9 @@ 0` ([#1377][1377]). * Crash when scrolling up while running something that generates a lot of output (for example, `yes`) ([#1380][1380]). +* Default key binding for URL mode conflicting with Unicode input on + some DEs; `show-urls-launched` is now mapped to `Control+Shift+o` by + default, instead of `Control+Shift+u` ([#1183][1183]). [1317]: https://codeberg.org/dnkl/foot/issues/1317 [1355]: https://codeberg.org/dnkl/foot/issues/1355 From dbee099eebc46bd6a72f4be1b65c5552f02d471a Mon Sep 17 00:00:00 2001 From: CismonX Date: Tue, 11 Jul 2023 00:51:32 +0800 Subject: [PATCH 100/135] sixel: fix regression for DECGRI with a repeat count of 0 --- sixel.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sixel.c b/sixel.c index c7c04b0d..bd2ebe1d 100644 --- a/sixel.c +++ b/sixel.c @@ -1703,7 +1703,11 @@ decgri_generic(struct terminal *term, uint8_t c) } case '?' ... '~': { - const unsigned count = term->sixel.repeat_count; + unsigned count = term->sixel.repeat_count; + if (unlikely(count == 0)) { + count = 1; + } + sixel_add_many_generic(term, c - 63, count); term->sixel.state = SIXEL_DECSIXEL; break; @@ -1722,7 +1726,11 @@ static void decgri_ar_11(struct terminal *term, uint8_t c) { if (likely(c >= '?' && c <= '~')) { - const unsigned count = term->sixel.repeat_count; + unsigned count = term->sixel.repeat_count; + if (unlikely(count == 0)) { + count = 1; + } + sixel_add_many_ar_11(term, c - 63, count); term->sixel.state = SIXEL_DECSIXEL; } else From efc89a7317fa88d949509cb5542fd6b8c8f0acc0 Mon Sep 17 00:00:00 2001 From: Kyle Gunger Date: Thu, 27 Apr 2023 15:57:57 +0000 Subject: [PATCH 101/135] Aero root theme --- themes/aeroroot | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 themes/aeroroot diff --git a/themes/aeroroot b/themes/aeroroot new file mode 100644 index 00000000..77ce7443 --- /dev/null +++ b/themes/aeroroot @@ -0,0 +1,35 @@ +# Aero root theme + +[cursor] +color=1a1a1a 9fd5f5 + +[colors] +foreground=dedeef +background=1a1a1a + +regular0=1a1a1a +regular1=ff3a3a +regular2=3aef3a +regular3=e6e61a +regular4=1a7eff +regular5=df3adf +regular6=3ff0e0 +regular7=dadada + +bright0=5a5a5a +bright1=ffaaaa +bright2=aaf3aa +bright3=f3f35a +bright4=6abaff +bright5=e5aae5 +bright6=aafff0 +bright7=f3f3f3 + +dim0=000000 +dim1=b71a1a +dim2=1ab71a +dim3=b5b50a +dim4=0A4FAA +dim5=a71aa7 +dim6=1AA59F +dim7=a5a5a5 From 50f47dcba9106a89695ce57b021e39a9c24a9e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 09:10:35 +0200 Subject: [PATCH 102/135] themes: aeroroot: disable cursor colors by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default cursor color is "inversed fg/bg", and themes aren’t supposed to change that. --- themes/aeroroot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/aeroroot b/themes/aeroroot index 77ce7443..204ed46b 100644 --- a/themes/aeroroot +++ b/themes/aeroroot @@ -1,7 +1,7 @@ # Aero root theme [cursor] -color=1a1a1a 9fd5f5 +# color=1a1a1a 9fd5f5 [colors] foreground=dedeef From 98dfeb05abec3513cacf9701750066793dd964f5 Mon Sep 17 00:00:00 2001 From: ShugarSkull Date: Thu, 13 Apr 2023 17:39:45 +0200 Subject: [PATCH 103/135] ayu-mirage theme added --- themes/ayu-mirage | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 themes/ayu-mirage diff --git a/themes/ayu-mirage b/themes/ayu-mirage new file mode 100644 index 00000000..64e85a4e --- /dev/null +++ b/themes/ayu-mirage @@ -0,0 +1,28 @@ +# -*- conf -*- +# theme: Ayu Mirage +# description: a theme based on Ayu Mirage for Sublime Text (original: https://github.com/dempfi/ayu) + +[cursor] +color = ffcc66 665a44 + +[colors] +foreground = cccac2 +background = 242936 + +regular0 = 242936 # black +regular1 = f28779 # red +regular2 = d5ff80 # green +regular3 = ffd173 # yellow +regular4 = 73d0ff # blue +regular5 = dfbfff # magenta +regular6 = 5ccfe6 # cyan +regular7 = cccac2 # white + +bright0 = fcfcfc # bright black +bright1 = f07171 # bright red +bright2 = 86b300 # bright gree +bright3 = f2ae49 # bright yellow +bright4 = 399ee6 # bright blue +bright5 = a37acc # bright magenta +bright6 = 55b4d4 # bright cyan +bright7 = 5c6166 # bright white From 66df6fb2f6af5c1ae5e626bcb33adc4ef18b415e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 09:55:32 +0200 Subject: [PATCH 104/135] themes: ayu-mirag: disable cursor colors by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default cursor color is "inversed fg/bg", and themes aren’t supposed to change that. --- themes/ayu-mirage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/ayu-mirage b/themes/ayu-mirage index 64e85a4e..89877ce9 100644 --- a/themes/ayu-mirage +++ b/themes/ayu-mirage @@ -3,7 +3,7 @@ # description: a theme based on Ayu Mirage for Sublime Text (original: https://github.com/dempfi/ayu) [cursor] -color = ffcc66 665a44 +# color = ffcc66 665a44 [colors] foreground = cccac2 From b3745b31c72042adf523b54be7c2a297a82cca7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 09:51:06 +0200 Subject: [PATCH 105/135] =?UTF-8?q?render:=20don=E2=80=99t=20invert=20curs?= =?UTF-8?q?or=20colors=20when=20custom=20colors=20are=20being=20used?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the user has configured custom cursor colors (cursor.color is set in foot.ini), don’t invert those colors when the cell is either selected, or has the ‘reverse’ attribute set. This aligns foot’s behavior with Alacritty, Kitty and Wezterm. Contour also behaves similarly, except mouse selections override the cursor colors (turning the cursor invisible). Closes #1347 --- CHANGELOG.md | 5 +++++ render.c | 14 +++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 968dd1a3..cbae76c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,11 +94,16 @@ ([#1383][1383]). * `dpi-aware` now defaults to `no`, and the `auto` value has been removed. +* When using custom cursor colors (`cursor.color` is set in + `foot.ini`), the cursor is no longer inverted when the cell is + selected, or when the cell has the `reverse` (SGR 7) attribute set + ([#1347][1347]). [1371]: https://codeberg.org/dnkl/foot/pulls/1371 [1183]: https://codeberg.org/dnkl/foot/issues/1183 [1360]: https://codeberg.org/dnkl/foot/issues/1360 [1383]: https://codeberg.org/dnkl/foot/issues/1383 +[1347]: https://codeberg.org/dnkl/foot/issues/1347 ### Deprecated diff --git a/render.c b/render.c index 5a757a88..677856d8 100644 --- a/render.c +++ b/render.c @@ -405,19 +405,11 @@ cursor_colors_for_cell(const struct terminal *term, const struct cell *cell, const pixman_color_t *fg, const pixman_color_t *bg, pixman_color_t *cursor_color, pixman_color_t *text_color) { - bool is_selected = cell->attrs.selected; - if (term->cursor_color.cursor >> 31) { - *cursor_color = color_hex_to_pixman(term->cursor_color.cursor); - *text_color = color_hex_to_pixman( - term->cursor_color.text >> 31 - ? term->cursor_color.text : term->colors.bg); + xassert(term->cursor_color.text >> 31); - if (cell->attrs.reverse ^ is_selected) { - pixman_color_t swap = *cursor_color; - *cursor_color = *text_color; - *text_color = swap; - } + *cursor_color = color_hex_to_pixman(term->cursor_color.cursor); + *text_color = color_hex_to_pixman(term->cursor_color.text); } else { *cursor_color = *fg; *text_color = *bg; From 28ab41caad46fa8a70e8c65f5eed577ac5a4b5a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 19 Apr 2023 09:35:12 +0200 Subject: [PATCH 106/135] =?UTF-8?q?theme:=20add=20new=20theme=20=E2=80=98s?= =?UTF-8?q?tarlight=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1321 --- themes/starlight | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 themes/starlight diff --git a/themes/starlight b/themes/starlight new file mode 100644 index 00000000..cb850b45 --- /dev/null +++ b/themes/starlight @@ -0,0 +1,23 @@ +# Theme: starlight (https://github.com/CosmicToast/starlight) + +[colors] +foreground = FFFFFF +background = 242424 + +regular0 = 242424 +regular1 = CF1745 +regular2 = 3ECF5B +regular3 = CFCF17 +regular4 = 0BA6DA +regular5 = D926AC +regular6 = 17CFA1 +regular7 = E6E6E6 + +bright0 = 616161 +bright1 = FF1A53 +bright2 = 17E640 +bright3 = ECFF1A +bright4 = 1AC6FF +bright5 = F53DC7 +bright6 = 1AFFC6 +bright7 = FFFFFF From 235e0e9e60a47c9b3b4019ea6dceb5512ecb240f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 10:03:22 +0200 Subject: [PATCH 107/135] themes: starlight: add -*- conf -*- header --- themes/starlight | 1 + 1 file changed, 1 insertion(+) diff --git a/themes/starlight b/themes/starlight index cb850b45..9b30b399 100644 --- a/themes/starlight +++ b/themes/starlight @@ -1,3 +1,4 @@ +# -*- conf -*- # Theme: starlight (https://github.com/CosmicToast/starlight) [colors] From f53e7f7478b5011db67e98944a3582c4ed523417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 10:03:56 +0200 Subject: [PATCH 108/135] themes: aeroroot: add -*- conf -*- header --- themes/aeroroot | 1 + 1 file changed, 1 insertion(+) diff --git a/themes/aeroroot b/themes/aeroroot index 204ed46b..7e65f909 100644 --- a/themes/aeroroot +++ b/themes/aeroroot @@ -1,3 +1,4 @@ +# -*- conf -*- # Aero root theme [cursor] From efc619b0afc856c976ea8e4d255ab9e014089510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 10:11:30 +0200 Subject: [PATCH 109/135] =?UTF-8?q?config:=20make=20=E2=80=98starlight?= =?UTF-8?q?=E2=80=99=20the=20default=20color=20theme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1321 --- CHANGELOG.md | 4 ++++ config.c | 34 +++++++++++++++++----------------- doc/foot.ini.5.scd | 12 ++++++------ 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbae76c5..bc18908a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,9 @@ ### Changed +* Default color theme is now + [starlight](https://github.com/CosmicToast/starlight) + ([#1321][1321]). * Minimum required meson version is now 0.59 ([#1371][1371]). * `Control+Shift+u` is now bound to `unicode-input` instead of `show-urls-launch`, to follow the convention established in GTK and @@ -99,6 +102,7 @@ selected, or when the cell has the `reverse` (SGR 7) attribute set ([#1347][1347]). +[1321]: https://codeberg.org/dnkl/foot/issues/1321 [1371]: https://codeberg.org/dnkl/foot/pulls/1371 [1183]: https://codeberg.org/dnkl/foot/issues/1183 [1360]: https://codeberg.org/dnkl/foot/issues/1360 diff --git a/config.c b/config.c index 1cefba3e..5c38aef5 100644 --- a/config.c +++ b/config.c @@ -30,8 +30,8 @@ #include "xmalloc.h" #include "xsnprintf.h" -static const uint32_t default_foreground = 0x839496; -static const uint32_t default_background = 0x002b36; +static const uint32_t default_foreground = 0xffffff; +static const uint32_t default_background = 0x242424; static const size_t min_csd_border_width = 5; @@ -48,23 +48,23 @@ static const size_t min_csd_border_width = 5; static const uint32_t default_color_table[256] = { // Regular - 0x073642, - 0xdc322f, - 0x859900, - 0xb58900, - 0x268bd2, - 0xd33682, - 0x2aa198, - 0xeee8d5, + 0x242424, + 0xcf1745, + 0x3ecf5b, + 0xcfcf17, + 0x0ba6da, + 0xd926ac, + 0x17cfa1, + 0xe6e6e6, // Bright - 0x08404f, - 0xe35f5c, - 0x9fb700, - 0xd9a400, - 0x4ba1de, - 0xdc619d, - 0x32c1b6, + 0x616161, + 0xff1a53, + 0x17e640, + 0xecff1a, + 0x1ac6ff, + 0xf53dc7, + 0x1affc6, 0xffffff, // 6x6x6 RGB cube diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 273a74c2..32482aa9 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -563,15 +563,15 @@ can configure the background transparency with the _alpha_ option. *regular0*, *regular1* *..* *regular7* The eight basic ANSI colors (Black, Red, Green, Yellow, Blue, - Magenta, Cyan, White). Default: _073642_, _dc322f_, _859900_, - _b58900_, _268bd2_, _d33682_, _2aa198_ and _eee8d5_ (a variant of - the _solarized dark_ theme). + Magenta, Cyan, White). Default: _242424_, _cf1745_, _3ecf5b_, + _cfcf17_, _0ba6da_, _d926ac_, _17cfa1_, _e6e6e6_ (starlight + theme). *bright0*, *bright1* *..* *bright7* The eight bright ANSI colors (Black, Red, Green, Yellow, Blue, - Magenta, Cyan, White). Default: _08404f_, _e35f5c_, _9fb700_, - _d9a400_, _4ba1de_, _dc619d_, _32c1b6_ and _ffffff_ (a variant of - the _solarized dark_ theme). + Magenta, Cyan, White). Default: _616161_, _ff1a53_, _17e640_, + _ecff1a_, _1ac6ff_, _f53dc7_, _1affc6_, _ffffff_ (starlight + theme). *dim0*, *dim1* *..* *dim7* Custom colors to use with dimmed colors. Dimmed colors do not have From 3cd0e2adb0bbdb2e59bdf4f4818f23080c36890e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 10:20:20 +0200 Subject: [PATCH 110/135] themes: enable custom cursor colors in all themes that define such colors Not all themes have/define custom cursor colors. But of those that do, nearly all already enabled them (by setting "cursor.color"), except three themes: * aeroroot * ayu-mirage * material-amber This patch makes all themes consistent, by enabling cursor.color in these last three themes too. --- themes/aeroroot | 2 +- themes/ayu-mirage | 2 +- themes/material-amber | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/themes/aeroroot b/themes/aeroroot index 7e65f909..3b887448 100644 --- a/themes/aeroroot +++ b/themes/aeroroot @@ -2,7 +2,7 @@ # Aero root theme [cursor] -# color=1a1a1a 9fd5f5 +color=1a1a1a 9fd5f5 [colors] foreground=dedeef diff --git a/themes/ayu-mirage b/themes/ayu-mirage index 89877ce9..64e85a4e 100644 --- a/themes/ayu-mirage +++ b/themes/ayu-mirage @@ -3,7 +3,7 @@ # description: a theme based on Ayu Mirage for Sublime Text (original: https://github.com/dempfi/ayu) [cursor] -# color = ffcc66 665a44 +color = ffcc66 665a44 [colors] foreground = cccac2 diff --git a/themes/material-amber b/themes/material-amber index ee2c21b5..ad844a9a 100644 --- a/themes/material-amber +++ b/themes/material-amber @@ -2,8 +2,8 @@ # Material Amber # Based on material.io guidelines with Amber 50 background -# [cursor] -# color=fff8e1 21201d +[cursor] +color=fff8e1 21201d [colors] foreground = 21201d From 3f7be59062f00cd99d832eaa5d389b880a3c512e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 12:03:35 +0200 Subject: [PATCH 111/135] config: add csd.double-click-to-maximize=no|yes option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When enabled, double-clicking the CSD titlebar will (un)maximize the window. Defaults to ‘yes’ (since this is the old hard-coded behavior). Closes #1293 --- CHANGELOG.md | 3 +++ config.c | 4 ++++ config.h | 1 + doc/foot.ini.5.scd | 4 ++++ foot.ini | 1 + input.c | 5 ++++- tests/test-config.c | 2 ++ 7 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc18908a..891c973a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,9 +56,12 @@ * Support for the new `cursor-shape-v1` Wayland protocol, i.e. server side cursor shapes ([#1379][1379]). * Support for touchscreen input ([#517][517]). +* `csd.double-click-to-maximize` option to `foot.ini`. Defaults to + `yes` ([#1293][1293]). [1379]: https://codeberg.org/dnkl/foot/issues/1379 [517]: https://codeberg.org/dnkl/foot/issues/517 +[1293]: https://codeberg.org/dnkl/foot/issues/1293 ### Changed diff --git a/config.c b/config.c index 5c38aef5..735ccd74 100644 --- a/config.c +++ b/config.c @@ -1475,6 +1475,9 @@ parse_section_csd(struct context *ctx) else if (strcmp(key, "hide-when-maximized") == 0) return value_to_bool(ctx, &conf->csd.hide_when_maximized); + else if (strcmp(key, "double-click-to-maximize") == 0) + return value_to_bool(ctx, &conf->csd.double_click_to_maximize); + else { LOG_CONTEXTUAL_ERR("not a valid action: %s", key); return false; @@ -3009,6 +3012,7 @@ config_load(struct config *conf, const char *conf_path, .preferred = CONF_CSD_PREFER_SERVER, .font = {0}, .hide_when_maximized = false, + .double_click_to_maximize = true, .title_height = 26, .border_width = 5, .border_width_visible = 0, diff --git a/config.h b/config.h index 20c07f6c..8189e56d 100644 --- a/config.h +++ b/config.h @@ -285,6 +285,7 @@ struct config { uint16_t button_width; bool hide_when_maximized; + bool double_click_to_maximize; struct { bool title_set:1; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 32482aa9..7fee8387 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -692,6 +692,10 @@ Examples: is maximized. The completely disable the titlebar, set *size* to 0 instead. Default: _no_. +*double-click-to-maximize* + Boolean. When enabled, double-clicking the CSD titlebar will + (un)maximize the window. Default: _yes_. + *border-width* Width of the border, in pixels (subject to output scaling). Note that the border encompasses the entire window, including the title diff --git a/foot.ini b/foot.ini index 2735d370..b4e4c603 100644 --- a/foot.ini +++ b/foot.ini @@ -123,6 +123,7 @@ # font= # color= # hide-when-maximized=no +# double-click-to-maximize=yes # border-width=0 # border-color= # button-width=26 diff --git a/input.c b/input.c index 3bf6535a..b2b4adf6 100644 --- a/input.c +++ b/input.c @@ -2287,7 +2287,10 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, struct wl_window *win = term->window; /* Toggle maximized state on double-click */ - if (button == BTN_LEFT && seat->mouse.count == 2) { + if (term->conf->csd.double_click_to_maximize && + button == BTN_LEFT && + seat->mouse.count == 2) + { if (win->is_maximized) xdg_toplevel_unset_maximized(win->xdg_toplevel); else diff --git a/tests/test-config.c b/tests/test-config.c index e59c104e..54efd13a 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -777,6 +777,8 @@ test_section_csd(void) &conf.csd.color.quit); test_boolean(&ctx, &parse_section_csd, "hide-when-maximized", &conf.csd.hide_when_maximized); + test_boolean(&ctx, &parse_section_csd, "double-click-to-maximize", + &conf.csd.double_click_to_maximize); /* TODO: verify the ‘set’ bit is actually set for colors */ /* TODO: font */ From 53b0eb8e1b50341d15fcb0957c8dca9af90e6473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 12:25:16 +0200 Subject: [PATCH 112/135] changelog: prepare for 1.15.0 --- CHANGELOG.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 891c973a..206b3d52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.15.0](#1-15-0) * [1.14.0](#1-14-0) * [1.13.1](#1-13-1) * [1.13.0](#1-13-0) @@ -42,7 +42,8 @@ * [1.2.0](#1-2-0) -## Unreleased +## 1.15.0 + ### Added * VT: implemented `XTQMODKEYS` query (`CSI ? Pp m`). @@ -148,9 +149,23 @@ [1380]: https://codeberg.org/dnkl/foot/issues/1380 -### Security ### Contributors +* Antoine Beaupré +* CismonX +* Craig Barnes +* Dan Bungert +* jdevdevdev +* Kyle Gunger +* locture +* Phillip Susi +* sewn +* ShugarSkull +* Vivian Szczepanski +* Vladimir Bauer +* wout +* CosmicToast + ## 1.14.0 From 5a3706ac464049baf068d0991d62781b438fad7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 12:26:03 +0200 Subject: [PATCH 113/135] meson: bump version to 1.15.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index fc2491d2..1a00153c 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.14.0', + version: '1.15.0', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From d1df98e0cac70fb61d68acfe2412f10102e80441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 14 Jul 2023 12:40:55 +0200 Subject: [PATCH 114/135] =?UTF-8?q?changelog:=20add=20new=20=E2=80=98unrel?= =?UTF-8?q?eased=E2=80=99=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 206b3d52..b5d21bd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.15.0](#1-15-0) * [1.14.0](#1-14-0) * [1.13.1](#1-13-1) @@ -42,6 +43,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.15.0 ### Added From b7100d57160d2ae7a38d5517f1c2cd7155737d88 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Fri, 14 Jul 2023 16:53:50 -0700 Subject: [PATCH 115/135] render: use rounding for fractional scale If we truncate the buffer dimensions we may accidentally submit a buffer with inappropriate size. --- CHANGELOG.md | 3 +++ render.c | 4 ++-- terminal.c | 5 ++++- wayland.c | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d21bd6..5a1eaaa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,9 @@ ### Deprecated ### Removed ### Fixed + +* Use appropriate rounding when applying fractional scales. + ### Security ### Contributors diff --git a/render.c b/render.c index 677856d8..d084a859 100644 --- a/render.c +++ b/render.c @@ -3856,8 +3856,8 @@ maybe_resize(struct terminal *term, int width, int height, bool force) scale = term->scale; } - width *= scale; - height *= scale; + width = round(width * scale); + height = round(height * scale); if (width == 0 && height == 0) { /* diff --git a/terminal.c b/terminal.c index fff55019..485e8ca3 100644 --- a/terminal.c +++ b/terminal.c @@ -778,7 +778,10 @@ term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4]) sixel_cell_size_changed(term); /* Use force, since cell-width/height may have changed */ - render_resize_force(term, term->width / term->scale, term->height / term->scale); + render_resize_force( + term, + round(term->width / term->scale), + round(term->height / term->scale)); return true; } diff --git a/wayland.c b/wayland.c index a25ec0f6..9195797e 100644 --- a/wayland.c +++ b/wayland.c @@ -401,7 +401,9 @@ update_term_for_output_change(struct terminal *term) float old_scale = term->scale; - render_resize(term, term->width / term->scale, term->height / term->scale); + render_resize(term, + round(term->width / term->scale), + round(term->height / term->scale)); term_font_dpi_changed(term, old_scale); term_font_subpixel_changed(term); csd_reload_font(term->window, old_scale); From 8b4cb2457aa5b90d265f59207b4b592ba6adebbc Mon Sep 17 00:00:00 2001 From: CismonX Date: Sun, 16 Jul 2023 17:24:55 +0800 Subject: [PATCH 116/135] input: do not ignore touch events on the CSDs --- input.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/input.c b/input.c index b2b4adf6..53d7acf8 100644 --- a/input.c +++ b/input.c @@ -2739,8 +2739,6 @@ wl_touch_down(void *data, struct wl_touch *wl_touch, uint32_t serial, struct terminal *term = win->term; term->active_surface = term_surface_kind(term, surface); - if (term->active_surface != TERM_SURF_GRID) - return; LOG_DBG("touch_down: touch=%p, x=%d, y=%d", (void *)wl_touch, wl_fixed_to_int(surface_x), wl_fixed_to_int(surface_y)); @@ -2785,6 +2783,7 @@ wl_touch_up(void *data, struct wl_touch *wl_touch, uint32_t serial, WL_POINTER_BUTTON_STATE_RELEASED); /* fallthrough */ case TOUCH_STATE_SCROLLING: + term->active_surface = TERM_SURF_NONE; seat->touch.state = TOUCH_STATE_IDLE; break; @@ -2815,7 +2814,7 @@ wl_touch_motion(void *data, struct wl_touch *wl_touch, uint32_t time, switch (seat->touch.state) { case TOUCH_STATE_HELD: - if (time <= seat->touch.time) { + if (time <= seat->touch.time && term->active_surface == TERM_SURF_GRID) { if (touch_to_scroll(seat, term, surface_x, surface_y)) seat->touch.state = TOUCH_STATE_SCROLLING; break; From 6de69aa9b726c2e270455ac44a7b8d5177b7332e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Jul 2023 20:08:34 +0200 Subject: [PATCH 117/135] render: fix xcursor scaling with fractional-scale-v1 This worked just after the fractional-scaling branch was merged, but was then broken by the cursor-shape branch, due to a bad rebase of that branch. --- CHANGELOG.md | 3 +++ render.c | 12 +++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1eaaa5..812e483a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,9 @@ ### Fixed * Use appropriate rounding when applying fractional scales. +* Xcursor not being scaled correctly on `fractional-scale-v1` capable + compositors. + ### Security ### Contributors diff --git a/render.c b/render.c index d084a859..c40d07d0 100644 --- a/render.c +++ b/render.c @@ -4297,11 +4297,15 @@ render_xcursor_update(struct seat *seat) LOG_DBG("setting %scursor shape using a client-side cursor surface", shape == CURSOR_SHAPE_CUSTOM ? "custom " : ""); - const int scale = seat->pointer.scale; + const float scale = seat->pointer.scale; struct wl_cursor_image *image = seat->pointer.cursor->images[0]; + struct wl_buffer *buf = wl_cursor_image_get_buffer(image); - wl_surface_attach( - seat->pointer.surface.surf, wl_cursor_image_get_buffer(image), 0, 0); + wayl_surface_scale_explicit_width_height( + seat->mouse_focus->window, + &seat->pointer.surface, image->width, image->height, scale); + + wl_surface_attach(seat->pointer.surface.surf, buf, 0, 0); wl_pointer_set_cursor( seat->wl_pointer, seat->pointer.serial, @@ -4311,8 +4315,6 @@ render_xcursor_update(struct seat *seat) wl_surface_damage_buffer( seat->pointer.surface.surf, 0, 0, INT32_MAX, INT32_MAX); - wl_surface_set_buffer_scale(seat->pointer.surface.surf, scale); - xassert(seat->pointer.xcursor_callback == NULL); seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface.surf); wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat); From da81b63ec0cd51586f896f6e5f55c716cce4d9af Mon Sep 17 00:00:00 2001 From: Ayush Agarwal Date: Sat, 8 Apr 2023 03:11:10 +0530 Subject: [PATCH 118/135] themes: add chiba-dark theme --- themes/chiba-dark | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 themes/chiba-dark diff --git a/themes/chiba-dark b/themes/chiba-dark new file mode 100644 index 00000000..bc3b1420 --- /dev/null +++ b/themes/chiba-dark @@ -0,0 +1,27 @@ +# -*- conf -*- +# theme: Chiba Dark +# author: ayushnix (https://sr.ht/~ayushnix) +# description: A dark theme with bright cyberpunk colors (WCAG AAA compliant) + +[cursor] +color = 181818 cdcdcd + +[colors] +foreground = cdcdcd +background = 181818 +regular0 = 181818 +regular1 = ff8599 +regular2 = 00c545 +regular3 = de9d00 +regular4 = 00b4ff +regular5 = fd71f8 +regular6 = 00bfae +regular7 = cdcdcd +bright0 = 262626 +bright1 = ff9eb2 +bright2 = 19de5e +bright3 = f7b619 +bright4 = 19cdff +bright5 = ff8aff +bright6 = 19d8c7 +bright7 = dadada From 2fd29cbf500a5528445f82c59ac76b1762efa60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 16 Jul 2023 08:27:12 +0200 Subject: [PATCH 119/135] term: (debug): dpi_aware is no longer an enum --- terminal.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/terminal.c b/terminal.c index 485e8ca3..213d8c59 100644 --- a/terminal.c +++ b/terminal.c @@ -2099,8 +2099,7 @@ term_font_dpi_changed(struct terminal *term, int old_scale) LOG_DBG("DPI/scale change: DPI-awareness=%s, " "DPI: %.2f -> %.2f, scale: %d -> %d, " "sizing font based on monitor's %s", - term->conf->dpi_aware == DPI_AWARE_AUTO ? "auto" : - term->conf->dpi_aware == DPI_AWARE_YES ? "yes" : "no", + term->conf->dpi_aware ? "yes" : "no", term->font_dpi, dpi, old_scale, term->scale, will_scale_using_dpi ? "DPI" : "scaling factor"); } From 829353a5dad410ce9b6b1ca78f9d86beedc3a90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 16 Jul 2023 08:28:21 +0200 Subject: [PATCH 120/135] term: font_dpi_changed: scale (and old_scale) are floating point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should fix an issue where the font size wasn’t updated when moving the window between outputs whose scaling factors match when truncated. --- terminal.c | 8 ++++---- terminal.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/terminal.c b/terminal.c index 213d8c59..e15e647e 100644 --- a/terminal.c +++ b/terminal.c @@ -1298,7 +1298,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, goto err; /* Load fonts */ - if (!term_font_dpi_changed(term, 0)) + if (!term_font_dpi_changed(term, 0.)) goto err; term->font_subpixel = get_font_subpixel(term); @@ -2081,10 +2081,10 @@ term_font_size_reset(struct terminal *term) } bool -term_font_dpi_changed(struct terminal *term, int old_scale) +term_font_dpi_changed(struct terminal *term, float old_scale) { float dpi = get_font_dpi(term); - xassert(term->scale > 0); + xassert(term->scale > 0.); bool was_scaled_using_dpi = term->font_is_sized_by_dpi; bool will_scale_using_dpi = term->conf->dpi_aware; @@ -2097,7 +2097,7 @@ term_font_dpi_changed(struct terminal *term, int old_scale) if (need_font_reload) { LOG_DBG("DPI/scale change: DPI-awareness=%s, " - "DPI: %.2f -> %.2f, scale: %d -> %d, " + "DPI: %.2f -> %.2f, scale: %.2f -> %.2f, " "sizing font based on monitor's %s", term->conf->dpi_aware ? "yes" : "no", term->font_dpi, dpi, old_scale, term->scale, diff --git a/terminal.h b/terminal.h index 6dace7ac..60d3c3ac 100644 --- a/terminal.h +++ b/terminal.h @@ -739,7 +739,7 @@ bool term_paste_data_to_slave( bool term_font_size_increase(struct terminal *term); bool term_font_size_decrease(struct terminal *term); bool term_font_size_reset(struct terminal *term); -bool term_font_dpi_changed(struct terminal *term, int old_scale); +bool term_font_dpi_changed(struct terminal *term, float old_scale); void term_font_subpixel_changed(struct terminal *term); int term_pt_or_px_as_pixels( From 59f0a721c4663aafd30179e34a38165dd137a0db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Jul 2023 16:12:34 +0200 Subject: [PATCH 121/135] wayland: fractional_scale_preferred_scale(): only push update if scale has changed Also, drop wl_window::have_preferred_scale. Check for scale > 0 instead. --- wayland.c | 13 +++++++++---- wayland.h | 1 - 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/wayland.c b/wayland.c index 9195797e..f6ded585 100644 --- a/wayland.c +++ b/wayland.c @@ -1603,10 +1603,15 @@ static void fractional_scale_preferred_scale( uint32_t scale) { struct wl_window *win = data; - win->scale = (float)scale / 120.; - win->have_preferred_scale = true; - LOG_DBG("fractional scale: %.3f", win->scale); + const float new_scale = (float)scale / 120.; + + if (win->scale == new_scale) + return; + + LOG_DBG("fractional scale: %.2f -> %.2f", win->scale, new_scale); + + win->scale = new_scale; update_term_for_output_change(win->term); } @@ -1971,7 +1976,7 @@ wayl_surface_scale_explicit_width_height( int width, int height, float scale) { - if (wayl_fractional_scaling(win->term->wl) && win->have_preferred_scale) { + if (wayl_fractional_scaling(win->term->wl) && win->scale > 0.) { #if defined(HAVE_FRACTIONAL_SCALE) LOG_DBG("scaling by a factor of %.2f using fractional scaling " "(width=%d, height=%d) ", scale, width, height); diff --git a/wayland.h b/wayland.h index 6d1cd727..9e581b20 100644 --- a/wayland.h +++ b/wayland.h @@ -374,7 +374,6 @@ struct wl_window { bool unmapped; float scale; - bool have_preferred_scale; struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration; From b2a29280cbd3fcd825123e7636d511a06b2df242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Jul 2023 16:19:14 +0200 Subject: [PATCH 122/135] wayland: use physical DPI on fractional-scale capable compositors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With legacy scaling, we need to use a "scaled", or "logical" DPI value, that is basically the real DPI value scaled by the monitor’s scaling factor. This is necessary to compensate for the compositor downscaling the surface, for "fake" fractional scaling. But with true fractional scaling, *we* scale the surface to the final size. This means we should *not* use the scaled DPI, but the monitor’s actual DPI. To facilitate this, store both the scaled and the unscaled DPI value in the monitor struct. This patch also changes how we pick the DPI value. Before, we would use the highest DPI value from all the monitors we were mapped on. Now, we use the DPI value from the monitor we were *last* mapped on (typically the window we’re dragging the window *to*). --- terminal.c | 55 +++++++++++++++++++++++------------------------------- wayland.c | 46 +++++++++++++++++++++++++++------------------ wayland.h | 5 ++++- 3 files changed, 55 insertions(+), 51 deletions(-) diff --git a/terminal.c b/terminal.c index e15e647e..a557d632 100644 --- a/terminal.c +++ b/terminal.c @@ -796,41 +796,34 @@ get_font_dpi(const struct terminal *term) * Conceptually, we use the physical monitor specs to calculate * the DPI, and we ignore the output's scaling factor. * - * However, to deal with fractional scaling, where we're told to - * render at e.g. 2x, but are then downscaled by the compositor to - * e.g. 1.25, we use the scaled DPI value multiplied by the scale - * factor instead. + * However, to deal with legacy fractional scaling, where we're + * told to render at e.g. 2x, but are then downscaled by the + * compositor to e.g. 1.25, we use the scaled DPI value multiplied + * by the scale factor instead. * * For integral scaling factors the resulting DPI is the same as * if we had used the physical DPI. * - * For fractional scaling factors we'll get a DPI *larger* than - * the physical DPI, that ends up being right when later + * For legacy fractional scaling factors we'll get a DPI *larger* + * than the physical DPI, that ends up being right when later * downscaled by the compositor. + * + * With the newer fractional-scale-v1 protocol, we use the + * monitor’s real DPI, since we scale everything to the correct + * scaling factor (no downscaling done by the compositor). */ - /* Use highest DPI from outputs we're mapped on */ - double dpi = 0.0; - xassert(term->window != NULL); - tll_foreach(term->window->on_outputs, it) { - if (it->item->dpi > dpi) - dpi = it->item->dpi; - } + xassert(tll_length(term->wl->monitors) > 0); - /* If we're not mapped, use DPI from first monitor. Hopefully this is where we'll get mapped later... */ - if (dpi == 0.) { - tll_foreach(term->wl->monitors, it) { - dpi = it->item.dpi; - break; - } - } + const struct wl_window *win = term->window; + const struct monitor *mon = tll_length(win->on_outputs) > 0 + ? tll_back(win->on_outputs) + : &tll_front(term->wl->monitors); - if (dpi == 0) { - /* No monitors? */ - dpi = 96.; - } - - return dpi; + if (wayl_fractional_scaling(term->wl)) + return mon->dpi.physical; + else + return mon->dpi.scaled; } static enum fcft_subpixel @@ -1285,11 +1278,9 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, reaper_add(term->reaper, term->slave, &fdm_client_terminated, term); /* Guess scale; we're not mapped yet, so we don't know on which - * output we'll be. Pick highest scale we find for now */ - tll_foreach(term->wl->monitors, it) { - if (it->item.scale > term->scale) - term->scale = it->item.scale; - } + * output we'll be. Use scaling factor from first monitor */ + xassert(tll_length(term->wl->monitors) > 0); + term->scale = tll_front(term->wl->monitors).scale; memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table)); @@ -2096,7 +2087,7 @@ term_font_dpi_changed(struct terminal *term, float old_scale) : old_scale != term->scale); if (need_font_reload) { - LOG_DBG("DPI/scale change: DPI-awareness=%s, " + LOG_DBG("DPI/scale change: DPI-aware=%s, " "DPI: %.2f -> %.2f, scale: %.2f -> %.2f, " "sizing font based on monitor's %s", term->conf->dpi_aware ? "yes" : "no", diff --git a/wayland.c b/wayland.c index f6ded585..c4f9e402 100644 --- a/wayland.c +++ b/wayland.c @@ -435,6 +435,9 @@ output_update_ppi(struct monitor *mon) double x_inches = mon->dim.mm.width * 0.03937008; double y_inches = mon->dim.mm.height * 0.03937008; + const int width = mon->dim.px_real.width; + const int height = mon->dim.px_real.height; + mon->ppi.real.x = mon->dim.px_real.width / x_inches; mon->ppi.real.y = mon->dim.px_real.height / y_inches; @@ -457,27 +460,36 @@ output_update_ppi(struct monitor *mon) break; } - int scaled_width = mon->dim.px_scaled.width; - int scaled_height = mon->dim.px_scaled.height; - - if (scaled_width == 0 && scaled_height == 0 && mon->scale > 0) { - /* Estimate scaled width/height if none has been provided */ - scaled_width = mon->dim.px_real.width / mon->scale; - scaled_height = mon->dim.px_real.height / mon->scale; - } + const int scaled_width = mon->dim.px_scaled.width; + const int scaled_height = mon->dim.px_scaled.height; mon->ppi.scaled.x = scaled_width / x_inches; mon->ppi.scaled.y = scaled_height / y_inches; - double px_diag = sqrt(pow(scaled_width, 2) + pow(scaled_height, 2)); - mon->dpi = px_diag / mon->inch * mon->scale; + const double px_diag_physical = sqrt(pow(width, 2) + pow(height, 2)); + mon->dpi.physical = width == 0 && height == 0 + ? 96. + : px_diag_physical / mon->inch; - if (mon->dpi > 1000) { + const double px_diag_scaled = sqrt(pow(scaled_width, 2) + pow(scaled_height, 2)); + mon->dpi.scaled = scaled_width == 0 && scaled_height == 0 + ? 96. + : px_diag_scaled / mon->inch * mon->scale; + + if (mon->dpi.physical > 1000) { if (mon->name != NULL) { - LOG_WARN("%s: DPI=%f is unreasonable, using 96 instead", - mon->name, mon->dpi); + LOG_WARN("%s: DPI=%f (physical) is unreasonable, using 96 instead", + mon->name, mon->dpi.physical); } - mon->dpi = 96; + mon->dpi.physical = 96; + } + + if (mon->dpi.scaled > 1000) { + if (mon->name != NULL) { + LOG_WARN("%s: DPI=%f (logical) is unreasonable, using 96 instead", + mon->name, mon->dpi.scaled); + } + mon->dpi.scaled = 96; } } @@ -1487,14 +1499,12 @@ wayl_init(struct fdm *fdm, struct key_binding_manager *key_binding_manager, tll_foreach(wayl->monitors, it) { LOG_INFO( - "%s: %dx%d+%dx%d@%dHz %s %.2f\" scale=%d PPI=%dx%d (physical) PPI=%dx%d (logical), DPI=%.2f", + "%s: %dx%d+%dx%d@%dHz %s %.2f\" scale=%d, DPI=%.2f/%.2f (physical/scaled)", it->item.name, it->item.dim.px_real.width, it->item.dim.px_real.height, it->item.x, it->item.y, (int)round(it->item.refresh), it->item.model != NULL ? it->item.model : it->item.description, it->item.inch, it->item.scale, - it->item.ppi.real.x, it->item.ppi.real.y, - it->item.ppi.scaled.x, it->item.ppi.scaled.y, - it->item.dpi); + it->item.dpi.physical, it->item.dpi.scaled); } wayl->fd = wl_display_get_fd(wayl->display); diff --git a/wayland.h b/wayland.h index 9e581b20..275338a8 100644 --- a/wayland.h +++ b/wayland.h @@ -315,7 +315,10 @@ struct monitor { } scaled; } ppi; - float dpi; + struct { + float scaled; + float physical; + } dpi; int scale; float refresh; From c96863b1882088271dd9867c4fd258a9a5fa6a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Jul 2023 16:19:21 +0200 Subject: [PATCH 123/135] =?UTF-8?q?wayland:=20error=20out=20if=20there=20a?= =?UTF-8?q?ren=E2=80=99t=20any=20monitors=20available?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wayland.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wayland.c b/wayland.c index c4f9e402..85140d6f 100644 --- a/wayland.c +++ b/wayland.c @@ -1447,6 +1447,11 @@ wayl_init(struct fdm *fdm, struct key_binding_manager *key_binding_manager, LOG_ERR("no seats available (wl_seat interface too old?)"); goto out; } + if (tll_length(wayl->monitors) == 0) { + LOG_ERR("no monitors available"); + goto out; + } + if (wayl->primary_selection_device_manager == NULL) LOG_WARN("no primary selection available"); From 21d99f8dced335826964ca96b8ba7ccac059e598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Jul 2023 16:21:16 +0200 Subject: [PATCH 124/135] terminal: break out scaling factor updating, and reduce number of calls to render_resize() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Break out the logic that updates the terminal’s scaling factor value, from render_resize(), to a new function, term_update_scale(). This allows us to update the scaling factor without a full grid resize. We also change how we pick the scaling factor (when fractional scaling is not in use). Before, we’d use the highest scaling factor from all monitors we were mapped on. Now, we use the scaling factor from the monitor we were *last* mapped on. Then, add a boolean parameter to term_set_fonts(), and when false, *don’t* call render_resize_force(). Also change term_font_dpi_changed() to only return true if the font was changed in any way. Finally, rewrite update_term_for_output_change() to: * Call term_update_scale() before doing anything else * Call render_resize{,_force} *last*, and *only* if either the scale or the fonts were updated. This fixes several things: * A bug where we failed to update the fonts when fractional scaling was in use, and we guessed the initial scale/DPI wrong. The bug happened because updated the internal "preferred" scale value, and a later call to render_resize() updated the terminal’s scale value, but since that code path didn’t call term_font_dpi_changed() (and it shouldn’t), the fonts weren’t resized properly. * It ensures we only resize the grid *once* when the scaling factor, or DPI is changed. Before this, we’d resize it twice. And this happened when e.g. dragging the window between monitors. --- render.c | 29 ++++++------------------ terminal.c | 65 +++++++++++++++++++++++++++++++++++++++++------------- terminal.h | 1 + wayland.c | 34 +++++++++++++++++++++------- 4 files changed, 84 insertions(+), 45 deletions(-) diff --git a/render.c b/render.c index c40d07d0..11149b16 100644 --- a/render.c +++ b/render.c @@ -3841,21 +3841,7 @@ maybe_resize(struct terminal *term, int width, int height, bool force) if (term->cell_width == 0 && term->cell_height == 0) return false; - float scale = -1; - if (wayl_fractional_scaling(term->wl)) { - scale = term->window->scale; - } else { - tll_foreach(term->window->on_outputs, it) { - if (it->item->scale > scale) - scale = it->item->scale; - } - } - - if (scale < 0.) { - /* Haven't 'entered' an output yet? */ - scale = term->scale; - } - + const float scale = term->scale; width = round(width * scale); height = round(height * scale); @@ -3942,9 +3928,9 @@ maybe_resize(struct terminal *term, int width, int height, bool force) /* Drop out of URL mode */ urls_reset(term); + LOG_DBG("resized: size=%dx%d (scale=%.2f)", width, height, term->scale); term->width = width; term->height = height; - term->scale = scale; const uint32_t scrollback_lines = term->render.scrollback_lines; @@ -4148,12 +4134,11 @@ maybe_resize(struct terminal *term, int width, int height, bool force) sixel_reflow(term); -#if defined(_DEBUG) && LOG_ENABLE_DBG - LOG_DBG("resize: %dx%d, grid: cols=%d, rows=%d " + LOG_DBG("resized: grid: cols=%d, rows=%d " "(left-margin=%d, right-margin=%d, top-margin=%d, bottom-margin=%d)", - term->width, term->height, term->cols, term->rows, - term->margins.left, term->margins.right, term->margins.top, term->margins.bottom); -#endif + term->cols, term->rows, + term->margins.left, term->margins.right, + term->margins.top, term->margins.bottom); if (term->scroll_region.start >= term->rows) term->scroll_region.start = 0; @@ -4295,7 +4280,7 @@ render_xcursor_update(struct seat *seat) #endif LOG_DBG("setting %scursor shape using a client-side cursor surface", - shape == CURSOR_SHAPE_CUSTOM ? "custom " : ""); + seat->pointer.shape == CURSOR_SHAPE_CUSTOM ? "custom " : ""); const float scale = seat->pointer.scale; struct wl_cursor_image *image = seat->pointer.cursor->images[0]; diff --git a/terminal.c b/terminal.c index a557d632..86cb365a 100644 --- a/terminal.c +++ b/terminal.c @@ -733,7 +733,8 @@ term_line_height_update(struct terminal *term) } static bool -term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4]) +term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4], + bool resize_grid) { for (size_t i = 0; i < 4; i++) { xassert(fonts[i] != NULL); @@ -777,11 +778,15 @@ term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4]) sixel_cell_size_changed(term); - /* Use force, since cell-width/height may have changed */ - render_resize_force( - term, - round(term->width / term->scale), - round(term->height / term->scale)); + /* Optimization - some code paths (are forced to) call + * render_resize() after this function */ + if (resize_grid) { + /* Use force, since cell-width/height may have changed */ + render_resize_force( + term, + round(term->width / term->scale), + round(term->height / term->scale)); + } return true; } @@ -899,7 +904,7 @@ font_loader_thread(void *_data) } static bool -reload_fonts(struct terminal *term) +reload_fonts(struct terminal *term, bool resize_grid) { const struct config *conf = term->conf; @@ -1026,7 +1031,7 @@ reload_fonts(struct terminal *term) } } - return success ? term_set_fonts(term, fonts) : success; + return success ? term_set_fonts(term, fonts, resize_grid) : success; } static bool @@ -1044,7 +1049,7 @@ load_fonts_from_conf(struct terminal *term) } } - return reload_fonts(term); + return reload_fonts(term, true); } static void fdm_client_terminated( @@ -1987,7 +1992,7 @@ term_font_size_adjust_by_points(struct terminal *term, float amount) } } - return reload_fonts(term); + return reload_fonts(term, true); } static bool @@ -2010,7 +2015,7 @@ term_font_size_adjust_by_pixels(struct terminal *term, int amount) } } - return reload_fonts(term); + return reload_fonts(term, true); } static bool @@ -2034,7 +2039,7 @@ term_font_size_adjust_by_percent(struct terminal *term, bool increment, float pe } } - return reload_fonts(term); + return reload_fonts(term, true); } bool @@ -2071,6 +2076,36 @@ term_font_size_reset(struct terminal *term) return load_fonts_from_conf(term); } +bool +term_update_scale(struct terminal *term) +{ + const struct wl_window *win = term->window; + + /* + * We have a number of “sources” we can use as scale. We choose + * the scale in the following order: + * + * - “preferred” scale, from the fractional-scale-v1 protocol + * - scaling factor of output we most recently were mapped on + * - if we’re not mapped, use the scaling factor from the first + * available output. + * - if there aren’t any outputs available, use 1.0 + */ + const float new_scale = + (wayl_fractional_scaling(term->wl) && win->scale > 0. + ? win->scale + : (tll_length(win->on_outputs) > 0 + ? tll_back(win->on_outputs)->scale + : 1.)); + + if (new_scale == term->scale) + return false; + + LOG_DBG("scaling factor changed: %.2f -> %.2f", term->scale, new_scale); + term->scale = new_scale; + return true; +} + bool term_font_dpi_changed(struct terminal *term, float old_scale) { @@ -2099,9 +2134,9 @@ term_font_dpi_changed(struct terminal *term, float old_scale) term->font_is_sized_by_dpi = will_scale_using_dpi; if (!need_font_reload) - return true; + return false; - return reload_fonts(term); + return reload_fonts(term, false); } void @@ -3500,7 +3535,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"); } diff --git a/terminal.h b/terminal.h index 60d3c3ac..4b1d1d0d 100644 --- a/terminal.h +++ b/terminal.h @@ -736,6 +736,7 @@ bool term_to_slave(struct terminal *term, const void *data, size_t len); bool term_paste_data_to_slave( struct terminal *term, const void *data, size_t len); +bool term_update_scale(struct terminal *term); bool term_font_size_increase(struct terminal *term); bool term_font_size_decrease(struct terminal *term); bool term_font_size_reset(struct terminal *term); diff --git a/wayland.c b/wayland.c index 85140d6f..354a0e78 100644 --- a/wayland.c +++ b/wayland.c @@ -396,17 +396,35 @@ static const struct wl_seat_listener seat_listener = { static void update_term_for_output_change(struct terminal *term) { - if (tll_length(term->window->on_outputs) == 0) - return; + const float old_scale = term->scale; + const float logical_width = term->width / term->scale; + const float logical_height = term->height / term->scale; - float old_scale = term->scale; - - render_resize(term, - round(term->width / term->scale), - round(term->height / term->scale)); - term_font_dpi_changed(term, old_scale); + /* Note: order matters! term_update_scale() must come first */ + bool scale_updated = term_update_scale(term); + bool fonts_updated = term_font_dpi_changed(term, old_scale); term_font_subpixel_changed(term); + csd_reload_font(term->window, old_scale); + + if (fonts_updated) { + /* + * If the fonts have been updated, the cell dimensions have + * changed. This requires a “forced” resize, since the surface + * buffer dimensions may not have been updated (in which case + * render_size() normally shortcuts and returns early). + */ + render_resize_force(term, round(logical_width), round(logical_height)); + } + + else if (scale_updated) { + /* + * A scale update means the surface buffer dimensions have + * been updated, even though the window logical dimensions + * haven’t changed. + */ + render_resize(term, round(logical_width), round(logical_height)); + } } static void From 7fca81dd3fd3cfd5ee6caafacfb61fd19adb0dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Jul 2023 16:28:10 +0200 Subject: [PATCH 125/135] term: get_font_subpixel(): use subpixel from monitor we were *last* mapped on --- terminal.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/terminal.c b/terminal.c index 86cb365a..2f0e632c 100644 --- a/terminal.c +++ b/terminal.c @@ -847,7 +847,8 @@ get_font_subpixel(const struct terminal *term) * output or not. * * Thus, when determining which subpixel mode to use, we can't do - * much but select *an* output. So, we pick the first one. + * much but select *an* output. So, we pick the one we were most + * recently mapped on. * * If we're not mapped at all, we pick the first available * monitor, and hope that's where we'll eventually get mapped. @@ -857,7 +858,7 @@ get_font_subpixel(const struct terminal *term) */ if (tll_length(term->window->on_outputs) > 0) - wl_subpixel = tll_front(term->window->on_outputs)->subpixel; + wl_subpixel = tll_back(term->window->on_outputs)->subpixel; else if (tll_length(term->wl->monitors) > 0) wl_subpixel = tll_front(term->wl->monitors).subpixel; else From 5b3b89cb64cb9fb2731fdb828bdc01086432b002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Jul 2023 16:31:37 +0200 Subject: [PATCH 126/135] changelog: monitor metadata is now picked from the one we were last mapped on --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 812e483a..ca9051b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,12 @@ ## Unreleased ### Added ### Changed + +* When window is mapped, use metadata (DPI, scaling factor, subpixel + configuration) from the monitor we were most recently mapped on, + instead of the one least recently. + + ### Deprecated ### Removed ### Fixed From df96b7f4c0bbd69958615b60aaae36611b90a274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Jul 2023 16:31:54 +0200 Subject: [PATCH 127/135] changelog: wrong DPI, and wrong initial font size with fractional scaling --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca9051b4..529ddabe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,14 @@ * Use appropriate rounding when applying fractional scales. * Xcursor not being scaled correctly on `fractional-scale-v1` capable compositors. +* `dpi-aware=yes` being broken on `fractional-scale-v1` capable + compositors (and when a fractional sacling factor is being used) + ([#1404][1404]). +* Initial font size being wrong on `fractional-scale-v1` capable + compositors, with multiple monitors with different scaling factors + connected ([#1404][1404]). + +[1404]: https://codeberg.org/dnkl/foot/issues/1404 ### Security From 4a4f2b5dae6f7303e27d45a1d60aaefb7be2d7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Jul 2023 20:13:50 +0200 Subject: [PATCH 128/135] pgo: add stub for wayl_fractional_scaling() --- pgo/pgo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/pgo/pgo.c b/pgo/pgo.c index 6be60363..54618204 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -96,6 +96,7 @@ wayl_win_init(struct terminal *term, const char *token) void wayl_win_destroy(struct wl_window *win) {} void wayl_win_alpha_changed(struct wl_window *win) {} bool wayl_win_set_urgent(struct wl_window *win) { return true; } +bool wayl_fractional_scaling(const struct wayland *wayl) { return true; } bool spawn(struct reaper *reaper, const char *cwd, char *const argv[], From 0b8791d1c5d1bc71daff481874e6c9e44d0dba79 Mon Sep 17 00:00:00 2001 From: xdavidwu Date: Tue, 18 Jul 2023 21:09:24 +0800 Subject: [PATCH 129/135] wayland: fix pointer cap lost handling Before this, on compositor without cursor-shape support, a pointer capability lost of the seat makes foot crash. --- wayland.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wayland.c b/wayland.c index 354a0e78..7e51bfe9 100644 --- a/wayland.c +++ b/wayland.c @@ -341,8 +341,10 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, } else { if (seat->wl_pointer != NULL) { #if defined(HAVE_CURSOR_SHAPE) - wp_cursor_shape_device_v1_destroy(seat->pointer.shape_device); - seat->pointer.shape_device = NULL; + if (seat->pointer.shape_device != NULL) { + wp_cursor_shape_device_v1_destroy(seat->pointer.shape_device); + seat->pointer.shape_device = NULL; + } #endif wl_pointer_release(seat->wl_pointer); From 023a1b8da65f0f3d0c038dae658c1627e51ac7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 18 Jul 2023 16:12:43 +0200 Subject: [PATCH 130/135] changelog: crash on pointer capability loss --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 529ddabe..31cd9400 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,8 +65,11 @@ * Initial font size being wrong on `fractional-scale-v1` capable compositors, with multiple monitors with different scaling factors connected ([#1404][1404]). +* Crash when _pointer capability_ is removed from a seat, on + compositors without `cursor-shape-v1 support` ([#1411][1411]). [1404]: https://codeberg.org/dnkl/foot/issues/1404 +[1411]: https://codeberg.org/dnkl/foot/pulls/1411 ### Security From 27b4c2ac2da7dc7b82e014786e9a8e9919888e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 18 Jul 2023 16:18:53 +0200 Subject: [PATCH 131/135] themes: starlight: update to V4 This also updates the default theme in foot, as well as the documentation. Closes #1409 --- CHANGELOG.md | 3 +++ config.c | 24 ++++++++++++------------ doc/foot.ini.5.scd | 12 ++++++------ foot.ini | 34 +++++++++++++++++----------------- themes/starlight | 30 +++++++++++++++--------------- 5 files changed, 53 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31cd9400..8a7e33bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,9 @@ * When window is mapped, use metadata (DPI, scaling factor, subpixel configuration) from the monitor we were most recently mapped on, instead of the one least recently. +* Starlight theme (the default theme) updated to [V4][starlight-v4] + +[starlight-v4]: https://github.com/CosmicToast/starlight/blob/v4/CHANGELOG.md#v4 ### Deprecated diff --git a/config.c b/config.c index 735ccd74..58a655e6 100644 --- a/config.c +++ b/config.c @@ -49,22 +49,22 @@ static const size_t min_csd_border_width = 5; static const uint32_t default_color_table[256] = { // Regular 0x242424, - 0xcf1745, - 0x3ecf5b, - 0xcfcf17, - 0x0ba6da, - 0xd926ac, - 0x17cfa1, + 0xf62b5a, + 0x47b413, + 0xe3c401, + 0x24acd4, + 0xf2affd, + 0x13c299, 0xe6e6e6, // Bright 0x616161, - 0xff1a53, - 0x17e640, - 0xecff1a, - 0x1ac6ff, - 0xf53dc7, - 0x1affc6, + 0xff4d51, + 0x35d450, + 0xe9e836, + 0x5dc5f8, + 0xfeabf2, + 0x24dfc4, 0xffffff, // 6x6x6 RGB cube diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 7fee8387..ae91ddf5 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -563,15 +563,15 @@ can configure the background transparency with the _alpha_ option. *regular0*, *regular1* *..* *regular7* The eight basic ANSI colors (Black, Red, Green, Yellow, Blue, - Magenta, Cyan, White). Default: _242424_, _cf1745_, _3ecf5b_, - _cfcf17_, _0ba6da_, _d926ac_, _17cfa1_, _e6e6e6_ (starlight - theme). + Magenta, Cyan, White). Default: _242424_, _f62b5a_, _47b413_, + _e3c401_, _24acd4_, _f2affd_, _13c299_, _e6e6e6_ (starlight + theme, V4). *bright0*, *bright1* *..* *bright7* The eight bright ANSI colors (Black, Red, Green, Yellow, Blue, - Magenta, Cyan, White). Default: _616161_, _ff1a53_, _17e640_, - _ecff1a_, _1ac6ff_, _f53dc7_, _1affc6_, _ffffff_ (starlight - theme). + Magenta, Cyan, White). Default: _616161_, _ff4d51_, _35d450_, + _e9e836_, _5dc5f8_, _feabf2_, _24dfc4_, _ffffff_ (starlight + theme, V4). *dim0*, *dim1* *..* *dim7* Custom colors to use with dimmed colors. Dimmed colors do not have diff --git a/foot.ini b/foot.ini index b4e4c603..359b2cf7 100644 --- a/foot.ini +++ b/foot.ini @@ -75,27 +75,27 @@ [colors] # alpha=1.0 -# background=002b36 -# foreground=839496 +# background=242424 +# foreground=ffffff ## Normal/regular colors (color palette 0-7) -# regular0=073642 # black -# regular1=dc322f # red -# regular2=859900 # green -# regular3=b58900 # yellow -# regular4=268bd2 # blue -# regular5=d33682 # magenta -# regular6=2aa198 # cyan -# regular7=eee8d5 # white +# regular0=242424 # black +# regular1=f62b5a # red +# regular2=47b413 # green +# regular3=e3c401 # yellow +# regular4=24acd4 # blue +# regular5=f2affd # magenta +# regular6=13c299 # cyan +# regular7=e6e6e6 # white ## Bright colors (color palette 8-15) -# bright0=08404f # bright black -# bright1=e35f5c # bright red -# bright2=9fb700 # bright green -# bright3=d9a400 # bright yellow -# bright4=4ba1de # bright blue -# bright5=dc619d # bright magenta -# bright6=32c1b6 # bright cyan +# bright0=616161 # bright black +# bright1=ff4d51 # bright red +# bright2=35d450 # bright green +# bright3=e9e836 # bright yellow +# bright4=5dc5f8 # bright blue +# bright5=feabf2 # bright magenta +# bright6=24dfc4 # bright cyan # bright7=ffffff # bright white ## dimmed colors (see foot.ini(5) man page) diff --git a/themes/starlight b/themes/starlight index 9b30b399..ed39f277 100644 --- a/themes/starlight +++ b/themes/starlight @@ -1,24 +1,24 @@ # -*- conf -*- -# Theme: starlight (https://github.com/CosmicToast/starlight) +# Theme: starlight V4 (https://github.com/CosmicToast/starlight) [colors] foreground = FFFFFF background = 242424 regular0 = 242424 -regular1 = CF1745 -regular2 = 3ECF5B -regular3 = CFCF17 -regular4 = 0BA6DA -regular5 = D926AC -regular6 = 17CFA1 -regular7 = E6E6E6 +regular1 = f62b5a +regular2 = 47b413 +regular3 = e3c401 +regular4 = 24acd4 +regular5 = f2affd +regular6 = 13c299 +regular7 = e6e6e6 bright0 = 616161 -bright1 = FF1A53 -bright2 = 17E640 -bright3 = ECFF1A -bright4 = 1AC6FF -bright5 = F53DC7 -bright6 = 1AFFC6 -bright7 = FFFFFF +bright1 = ff4d51 +bright2 = 35d450 +bright3 = e9e836 +bright4 = 5dc5f8 +bright5 = feabf2 +bright6 = 24dfc4 +bright7 = ffffff From fdd753263b3ebe96a83c73fbf560773dce04efd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 18 Jul 2023 16:13:36 +0200 Subject: [PATCH 132/135] term: destroy: unref key bindings *after* destroying window This fixes a crash-on-exit on compositors that emit a _"keyboard leave"_ event when a surface is unmapped. In our case, destroying the window (where we unmap it) in term_destroy(), lead to a crash in term_mouse_grabbed(), due to key_binding_for() returning NULL. The call chain in this is case is, roughly: term_destroy() -> wayl_win_destroy() -> keyboard_leave() -> term_xcursor_update_for_seat() -> term_mouse_grabbed() --- CHANGELOG.md | 2 ++ terminal.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a7e33bb..9f9d040d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,8 @@ connected ([#1404][1404]). * Crash when _pointer capability_ is removed from a seat, on compositors without `cursor-shape-v1 support` ([#1411][1411]). +* Crash on exit, if the mouse is hovering over the foot window (does + not happen on all compositors) [1404]: https://codeberg.org/dnkl/foot/issues/1404 [1411]: https://codeberg.org/dnkl/foot/pulls/1411 diff --git a/terminal.c b/terminal.c index 2f0e632c..c22646f2 100644 --- a/terminal.c +++ b/terminal.c @@ -1607,8 +1607,6 @@ term_destroy(struct terminal *term) if (term == NULL) return 0; - key_binding_unref(term->wl->key_binding_manager, term->conf); - tll_foreach(term->wl->terms, it) { if (it->item == term) { tll_remove(term->wl->terms, it); @@ -1654,6 +1652,8 @@ term_destroy(struct terminal *term) } mtx_unlock(&term->render.workers.lock); + key_binding_unref(term->wl->key_binding_manager, term->conf); + urls_reset(term); free(term->vt.osc.data); From 648f6016e3cc62ff895c37206680223c531bcc5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 19 Jul 2023 16:37:25 +0200 Subject: [PATCH 133/135] changelog: spelling: sacling -> scaling --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f9d040d..5e051081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,7 +63,7 @@ * Xcursor not being scaled correctly on `fractional-scale-v1` capable compositors. * `dpi-aware=yes` being broken on `fractional-scale-v1` capable - compositors (and when a fractional sacling factor is being used) + compositors (and when a fractional scaling factor is being used) ([#1404][1404]). * Initial font size being wrong on `fractional-scale-v1` capable compositors, with multiple monitors with different scaling factors From 899b768b744c74e88e54e6d8eb32f53accea79d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 19 Jul 2023 16:34:42 +0200 Subject: [PATCH 134/135] =?UTF-8?q?render:=20disable=20transparency=20when?= =?UTF-8?q?=20we=E2=80=99re=20fullscreened?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wayland protocol recommends (or mandates?) that compositors render a black background behind fullscreened transparent windows. I.e. you never see what’s _actually_ behind the window. So, if you have a white, but semi-transparent background in foot, it’ll be rendered in a shade of gray. Given this, it’s better to simply disable transparency while we’re fullscreened. That way, we at least get the "correct" background color. Closes #1416 --- CHANGELOG.md | 3 +++ render.c | 31 +++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e051081..9958e293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,8 +51,11 @@ configuration) from the monitor we were most recently mapped on, instead of the one least recently. * Starlight theme (the default theme) updated to [V4][starlight-v4] +* Background transparency (alpha) is now disabled in fullscreened + windows ([#1416][1416]). [starlight-v4]: https://github.com/CosmicToast/starlight/blob/v4/CHANGELOG.md#v4 +[1416]: https://codeberg.org/dnkl/foot/issues/1416 ### Deprecated diff --git a/render.c b/render.c index 11149b16..7d7c6348 100644 --- a/render.c +++ b/render.c @@ -526,8 +526,35 @@ render_cell(struct terminal *term, pixman_image_t *pix, uint32_t swap = _fg; _fg = _bg; _bg = swap; - } else if (cell->attrs.bg_src == COLOR_DEFAULT) - alpha = term->colors.alpha; + } + + else if (cell->attrs.bg_src == COLOR_DEFAULT) { + if (term->window->is_fullscreen) { + /* + * Note: disable transparency when fullscreened. + * + * This is because the wayland protocol recommends + * (mandates even?) the compositor render a black + * background behind fullscreened transparent windows. + * + * In other words, transparency does not work when + * fullscreened, in the sense that you don't see + * what's behind the window. + * + * And if we keep our alpha channel, the background + * color will just look weird. For example, if the + * background color is white, and alpha is 0.5, then + * the window will be drawn in a shade of gray while + * fullscreened. + * + * By disabling the alpha channel, the window will at + * least be rendered in the intended background color. + */ + xassert(alpha == 0xffff); + } else { + alpha = term->colors.alpha; + } + } } if (unlikely(is_selected && _fg == _bg)) { From a49281ced3fb27d6fa0f59f7964c5c0cc07b3829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 19 Jul 2023 16:39:56 +0200 Subject: [PATCH 135/135] =?UTF-8?q?render:=20OSD:=20don=E2=80=99t=20mark?= =?UTF-8?q?=20surface=20as=20being=20opaque,=20when=20it=E2=80=99s=20not?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + render.c | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9958e293..7ef784f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ compositors without `cursor-shape-v1 support` ([#1411][1411]). * Crash on exit, if the mouse is hovering over the foot window (does not happen on all compositors) +* Visual glitches when CSD titlebar is transparent. [1404]: https://codeberg.org/dnkl/foot/issues/1404 [1411]: https://codeberg.org/dnkl/foot/pulls/1411 diff --git a/render.c b/render.c index 7d7c6348..11c2456a 100644 --- a/render.c +++ b/render.c @@ -1953,12 +1953,15 @@ render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf, wl_surface_attach(sub_surf->surface.surf, buf->wl_buf, 0, 0); wl_surface_damage_buffer(sub_surf->surface.surf, 0, 0, buf->width, buf->height); - struct wl_region *region = wl_compositor_create_region(term->wl->compositor); - if (region != NULL) { - wl_region_add(region, 0, 0, buf->width, buf->height); - wl_surface_set_opaque_region(sub_surf->surface.surf, region); - wl_region_destroy(region); - } + if (alpha == 0xffff) { + struct wl_region *region = wl_compositor_create_region(term->wl->compositor); + if (region != NULL) { + wl_region_add(region, 0, 0, buf->width, buf->height); + wl_surface_set_opaque_region(sub_surf->surface.surf, region); + wl_region_destroy(region); + } + } else + wl_surface_set_opaque_region(sub_surf->surface.surf, NULL); wl_surface_commit(sub_surf->surface.surf); quirk_weston_subsurface_desync_off(sub_surf->sub);