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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 79907a6e..29380684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 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) @@ -44,16 +45,14 @@ ## Unreleased ### Added - -* VT: implemented `XTQMODKEYS` query (`CSI ? Pp m`). - - ### Changed -* 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 - since `CSI R` conflicts with the _”Cursor Position Report”_. +* 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] +* Background transparency (alpha) is now disabled in fullscreened + windows ([#1416][1416]). * Foot server systemd units now use the standard graphical-session.target ([#1281][1281]). * If `$XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock` does not exist, @@ -61,6 +60,8 @@ `/tmp/foot.sock`, even if `$WAYLAND_DISPLAY` and/or `$XDG_RUNTIME_DIR` are defined ([#1281][1281]). +[starlight-v4]: https://github.com/CosmicToast/starlight/blob/v4/CHANGELOG.md#v4 +[1416]: https://codeberg.org/dnkl/foot/issues/1416 [1281]: https://codeberg.org/dnkl/foot/pulls/1281 @@ -68,17 +69,154 @@ ### Removed ### Fixed -* Incorrect icon in dock and window switcher on Gnome ([#1317][1317]) -* Crash when scrolling after resizing the window with non-zero - scrolling regions. +* 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 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 + 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) +* Visual glitches when CSD titlebar is transparent. -[1317]: https://codeberg.org/dnkl/foot/issues/1317 +[1404]: https://codeberg.org/dnkl/foot/issues/1404 +[1411]: https://codeberg.org/dnkl/foot/pulls/1411 ### Security ### Contributors +## 1.15.0 + +### 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. +* Sixel aspect ratio. +* Support for the new `fractional-scale-v1` Wayland protocol. This + brings true fractional scaling to Wayland in general, and with this + 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]). +* `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 + +* 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 + 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 + 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`. +* 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. +* 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. +* 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]). + +[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 +[1383]: https://codeberg.org/dnkl/foot/issues/1383 +[1347]: https://codeberg.org/dnkl/foot/issues/1347 + + +### Deprecated + +* `[main].utempter` option. + + +### Removed + +* `auto` value for the `dpi-aware` option. + + +### Fixed + +* 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. +* 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]). +* 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]). +* 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 +[1377]: https://codeberg.org/dnkl/foot/issues/1377 +[1380]: https://codeberg.org/dnkl/foot/issues/1380 + + +### 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 ### Added @@ -100,7 +238,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/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/README.md b/README.md index 8d037af3..b1cfb37d 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) @@ -163,10 +164,13 @@ 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. +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). @@ -246,6 +250,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 @@ -287,7 +302,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. @@ -411,27 +426,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/client.c b/client.c index 6a292994..41be68a9 100644 --- a/client.c +++ b/client.c @@ -66,11 +66,14 @@ 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 %ccursor-shape %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', + feature_fractional_scaling() ? '+' : ':', + feature_cursor_shape() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } diff --git a/config.c b/config.c index 2ede1aa5..58a655e6 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, + 0xf62b5a, + 0x47b413, + 0xe3c401, + 0x24acd4, + 0xf2affd, + 0x13c299, + 0xe6e6e6, // Bright - 0x08404f, - 0xe35f5c, - 0x9fb700, - 0xd9a400, - 0x4ba1de, - 0xdc619d, - 0x32c1b6, + 0x616161, + 0xff4d51, + 0x35d450, + 0xe9e836, + 0x5dc5f8, + 0xfeabf2, + 0x24dfc4, 0xffffff, // 6x6x6 RGB cube @@ -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); @@ -1009,13 +1000,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; @@ -1468,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; @@ -2468,6 +2478,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) { @@ -2547,6 +2571,7 @@ enum section { SECTION_TEXT_BINDINGS, SECTION_ENVIRONMENT, SECTION_TWEAK, + SECTION_TOUCH, SECTION_COUNT, }; @@ -2572,6 +2597,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"); @@ -2784,7 +2810,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}}}, }; @@ -2889,7 +2916,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(); @@ -2898,7 +2926,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, @@ -2922,7 +2950,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, @@ -2984,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, @@ -3018,10 +3047,17 @@ config_load(struct config *conf, const char *conf_path, .sixel = true, }, + .touch = { + .long_press_delay = 400, + }, + .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 +3346,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; @@ -3329,7 +3365,9 @@ UNITTEST user_notifications_t nots = tll_init(); config_override_t overrides = tll_init(); - bool ret = config_load(&original, "/dev/null", ¬s, &overrides, false); + fcft_init(FCFT_LOG_COLORIZE_NEVER, false, FCFT_LOG_CLASS_NONE); + + bool ret = config_load(&original, "/dev/null", ¬s, &overrides, false, false); xassert(ret); struct config *clone = config_clone(&original); @@ -3340,6 +3378,8 @@ UNITTEST config_free(clone); free(clone); + fcft_fini(); + tll_free(overrides); tll_free(nots); } @@ -3379,7 +3419,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..8189e56d 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; @@ -285,6 +285,7 @@ struct config { uint16_t button_width; bool hide_when_maximized; + bool double_click_to_maximize; struct { bool title_set:1; @@ -320,7 +321,7 @@ struct config { env_var_list_t env_vars; - char *utempter_path; + char *utmp_helper_path; struct { enum fcft_scaling_filter fcft_filter; @@ -347,6 +348,10 @@ struct config { bool sixel; } tweak; + struct { + uint32_t long_press_delay; + } touch; + user_notifications_t notifications; }; @@ -355,7 +360,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/csi.c b/csi.c index 7b318d0a..153a1099 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; } @@ -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/cursor-shape.c b/cursor-shape.c new file mode 100644 index 00000000..aafeae8b --- /dev/null +++ b/cursor-shape.c @@ -0,0 +1,115 @@ +#include +#include + +#define LOG_MODULE "cursor-shape" +#define LOG_ENABLE_DBG 0 +#include "log.h" + +#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_COUNT] = { + [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)); + xassert(table[shape] != NULL); + 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)); + xassert(table[shape] != 0); + return table[shape]; +} + +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 new file mode 100644 index 00000000..a9619553 --- /dev/null +++ b/cursor-shape.h @@ -0,0 +1,34 @@ +#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, + 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, + + 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); +enum wp_cursor_shape_device_v1_shape cursor_string_to_server_shape( + const char *xcursor); +#endif 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/doc/foot.1.scd b/doc/foot.1.scd index a3d4bb42..3da6fd7e 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* @@ -202,9 +202,12 @@ 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*+*u* + Activate Unicode input. + *ctrl*+*shift*+*z* Jump to the previous, currently not visible, prompt. Requires shell integration. @@ -283,6 +286,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 @@ -298,7 +313,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. @@ -546,17 +561,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/foot.ini.5.scd b/doc/foot.ini.5.scd index 5ef62045..ae91ddf5 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 ``` @@ -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 @@ -289,7 +283,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 +309,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. @@ -343,9 +339,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 @@ -524,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 @@ -544,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_, _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: _08404f_, _e35f5c_, _9fb700_, - _d9a400_, _4ba1de_, _dc619d_, _32c1b6_ and _ffffff_ (a variant of - the _solarized dark_ 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 @@ -673,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 @@ -815,7 +838,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 @@ -858,7 +881,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/doc/footclient.1.scd b/doc/footclient.1.scd index 18832355..7d89b9ed 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_. @@ -163,17 +163,6 @@ fallback to the less specific path, with the following priority: 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/meson.build b/doc/meson.build index 86e75952..37972652 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -1,17 +1,25 @@ -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, } ) @@ -33,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/foot-features.h b/foot-features.h index ad447767..f8043c12 100644 --- a/foot-features.h +++ b/foot-features.h @@ -37,3 +37,21 @@ 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 +} + +static inline bool feature_cursor_shape(void) +{ +#if defined(HAVE_CURSOR_SHAPE) + return true; +#else + return false; +#endif +} diff --git a/foot.info b/foot.info index cf81d721..4f95bf7b 100644 --- a/foot.info +++ b/foot.info @@ -41,7 +41,8 @@ Se=\E[ q, Ss=\E[%p1%d q, Sync=\E[?2026%?%p1%{1}%-%tl%eh, - XM=\E[?1006;1000%?%p1%{1}%=%th%el%;, + TS=\E]2;, + XM=\E[?1006;1004;1000%?%p1%{1}%=%th%el%;, XR=\E[>0q, acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, bel=^G, diff --git a/foot.ini b/foot.ini index 8266b01b..359b2cf7 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 @@ -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= @@ -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 @@ -69,29 +70,32 @@ # hide-when-typing=no # alternate-scroll-mode=yes +[touch] +# long-press-delay=400 + [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) @@ -118,7 +122,8 @@ # size=26 # font= # color= -# hide-when-typing=no +# hide-when-maximized=no +# double-click-to-maximize=yes # border-width=0 # border-color= # button-width=26 @@ -148,12 +153,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] 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/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/input.c b/input.c index 7e5d204d..53d7acf8 100644 --- a/input.c +++ b/input.c @@ -1704,23 +1704,53 @@ 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; } } +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( @@ -1819,15 +1860,12 @@ 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; 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; @@ -2239,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 @@ -2559,6 +2610,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 +2642,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 +2662,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 +2681,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 +2699,166 @@ 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); + + 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: + term->active_surface = TERM_SURF_NONE; + 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 && term->active_surface == TERM_SURF_GRID) { + 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 ea488a86..906008d5 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. @@ -25,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); @@ -33,4 +35,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/main.c b/main.c index 4af200fd..fc329574 100644 --- a/main.c +++ b/main.c @@ -52,11 +52,14 @@ 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 %ccursor-shape %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', + feature_fractional_scaling() ? '+' : '-', + feature_cursor_shape() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } @@ -450,6 +453,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 +465,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,18 +480,19 @@ 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}; 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/meson.build b/meson.build index 21f98249..cdccfa7e 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('foot', 'c', - version: '1.14.0', + version: '1.15.0', license: 'MIT', - meson_version: '>=0.58.0', + meson_version: '>=0.59.0', default_options: [ 'c_std=c11', 'warning_level=1', @@ -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')]) + @@ -133,6 +157,27 @@ 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 +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', + ] + add_project_arguments('-DHAVE_CURSOR_SHAPE', language: 'c') + cursor_shape = true +else + cursor_shape = false endif foreach prot : wl_proto_xml @@ -188,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', @@ -343,7 +389,11 @@ summary( 'Themes': get_option('themes'), 'IME': get_option('ime'), 'Grapheme clustering': utf8proc.found(), - 'Utempter path': utempter_path, + '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(), 'Terminfo install location': terminfo_install_location, 'Default TERM': get_option('default-terminfo'), diff --git a/meson_options.txt b/meson_options.txt index c38a8ca8..d16e23ae 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/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 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..54618204 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 * @@ -94,7 +94,9 @@ 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[], diff --git a/quirks.c b/quirks.c index e4fe4a1f..9769f1ff 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.surf, 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..11c2456a 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" @@ -311,7 +312,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( @@ -404,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; @@ -533,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)) { @@ -905,21 +925,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 +1047,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 +1124,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,13 +1173,17 @@ 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 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 +1348,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); } } @@ -1480,7 +1505,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 +1527,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,10 +1548,14 @@ 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; + + /* Work around Sway bug - unmapping a sub-surface does not + * damage the underlying surface */ + quirk_sway_subsurface_unmap(term); } return; } @@ -1687,17 +1716,18 @@ 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->window, &overlay->surface, buf, 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_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; @@ -1824,15 +1854,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) { - xassert(buf->width % term->scale == 0); - xassert(buf->height % term->scale == 0); - - 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); + 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); } static void @@ -1848,8 +1875,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) @@ -1922,23 +1948,23 @@ 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); + quirk_weston_subsurface_desync_on(sub_surf->sub); + 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); - quirk_weston_subsurface_desync_on(sub_surf); - 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); + 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); - 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_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 @@ -1947,13 +1973,10 @@ 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; - xassert(info->width % term->scale == 0); - xassert(info->height % term->scale == 0); - uint32_t bg = term->conf->csd.color.title_set ? term->conf->csd.color.title : 0xffu << 24 | term->conf->colors.fg; @@ -1976,11 +1999,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->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->surf, buf); + csd_commit(term, &surf->surface, buf); free(_title_text); } @@ -1991,26 +2013,23 @@ 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 wayl_surface *surf = &term->window->csd.surface[surf_idx].surface; if (info->width == 0 || info->height == 0) return; - xassert(info->width % term->scale == 0); - xassert(info->height % 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); + render_csd_part(term, surf->surf, buf, info->width, info->height, &color); } /* * 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); @@ -2063,7 +2082,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}); @@ -2092,41 +2110,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); } @@ -2143,6 +2144,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; @@ -2152,58 +2185,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); } @@ -2227,13 +2214,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); } @@ -2245,14 +2298,11 @@ 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 wayl_surface *surf = &term->window->csd.surface[surf_idx].surface; if (info->width == 0 || info->height == 0) return; - xassert(info->width % term->scale == 0); - xassert(info->height % term->scale == 0); - uint32_t _color; uint16_t alpha = 0xffff; bool is_active = false; @@ -2300,7 +2350,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; @@ -2335,7 +2385,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); @@ -2374,12 +2424,17 @@ 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 + * the underlying surface */ + quirk_sway_subsurface_unmap(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)) { @@ -2388,7 +2443,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 */ @@ -2486,8 +2541,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; } @@ -2506,8 +2561,7 @@ render_scrollback_position(struct terminal *term) render_osd( term, - win->scrollback_indicator.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); @@ -2525,7 +2579,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 = @@ -2543,8 +2597,7 @@ render_render_timer(struct terminal *term, struct timespec render_time) render_osd( term, - win->render_timer.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); @@ -2891,7 +2944,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); } @@ -2919,7 +2972,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); } @@ -2986,17 +3039,17 @@ 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); - wl_surface_set_buffer_scale(term->window->surface, term->scale); + wayl_win_scale(term->window, buf); if (term->wl->presentation != NULL && term->conf->presentation_timings) { struct timespec commit_time; 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"); @@ -3020,14 +3073,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); } - xassert(buf->width % term->scale == 0); - xassert(buf->height % term->scale == 0); - - 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 @@ -3089,17 +3139,17 @@ 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 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; @@ -3346,24 +3396,21 @@ 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); + margin / term->scale, + max(0, (int32_t)term->height - height - margin) / term->scale); - xassert(buf->width % scale == 0); - xassert(buf->height % scale == 0); - - 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); + 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); 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 @@ -3379,7 +3426,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; @@ -3444,7 +3491,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) @@ -3579,23 +3626,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.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); @@ -3825,19 +3871,9 @@ 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; - 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; - } - - width *= scale; - height *= scale; + const float scale = term->scale; + width = round(width * scale); + height = round(height * scale); if (width == 0 && height == 0) { /* @@ -3879,13 +3915,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; } } @@ -3917,9 +3958,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; @@ -3986,7 +4027,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) */ @@ -4074,6 +4117,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 @@ -4085,7 +4130,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; @@ -4101,7 +4146,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); } @@ -4119,12 +4164,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; @@ -4218,38 +4262,79 @@ 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, NULL, 0, 0); - wl_surface_commit(seat->pointer.surface); + 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, + 0, 0); + wl_surface_commit(seat->pointer.surface.surf); return; } xassert(seat->pointer.cursor != NULL); - const int scale = seat->pointer.scale; - struct wl_cursor_image *image = seat->pointer.cursor->images[0]; +#if defined(HAVE_CURSOR_SHAPE) + const enum cursor_shape shape = seat->pointer.shape; + const char *const xcursor = seat->pointer.last_custom_xcursor; - wl_surface_attach( - seat->pointer.surface, wl_cursor_image_get_buffer(image), 0, 0); + if (seat->pointer.shape_device != NULL) { + xassert(shape != CURSOR_SHAPE_CUSTOM || xcursor != NULL); + + const enum wp_cursor_shape_device_v1_shape custom_shape = + (shape == CURSOR_SHAPE_CUSTOM && xcursor != NULL + ? cursor_string_to_server_shape(xcursor) + : 0); + + if (shape != CURSOR_SHAPE_CUSTOM || custom_shape != 0) { + xassert(custom_shape == 0 || shape == CURSOR_SHAPE_CUSTOM); + + const enum wp_cursor_shape_device_v1_shape wp_shape = custom_shape != 0 + ? custom_shape + : cursor_shape_to_server_shape(shape); + + LOG_DBG("setting %scursor shape using cursor-shape-v1", + custom_shape != 0 ? "custom " : ""); + + wp_cursor_shape_device_v1_set_shape( + seat->pointer.shape_device, + seat->pointer.serial, + wp_shape); + + return; + } + } +#endif + + LOG_DBG("setting %scursor shape using a client-side cursor surface", + seat->pointer.shape == CURSOR_SHAPE_CUSTOM ? "custom " : ""); + + 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_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, - 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); - - wl_surface_set_buffer_scale(seat->pointer.surface, scale); + 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 @@ -4396,13 +4481,14 @@ 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; } @@ -4411,26 +4497,48 @@ render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor return true; } - if (seat->pointer.xcursor == xcursor) + 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) { + 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); - if (xcursor != XCURSOR_HIDDEN) { 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; } } - } 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.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/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. diff --git a/search.c b/search.c index 59765c2e..6c2a2a7e 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 @@ -833,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; diff --git a/sixel.c b/sixel.c index 592f48f8..bd2ebe1d 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) { /* @@ -38,18 +41,32 @@ 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, aspect-ratio=%d:%d), " + "p2=%d (transparent=%s), " + "p3=%d (ignored)", + p1, pan, pad, pan, pad, p2, p2 == 1 ? "yes" : "no", 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 = 0; /* TODO: default palette */ @@ -73,38 +90,69 @@ 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; - - for (size_t i = 0; i < 1 * 6; i++) - term->sixel.image.data[i] = term->sixel.default_bg; + : bg; count = 0; + 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 @@ -367,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); @@ -400,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, @@ -417,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 */ @@ -465,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, @@ -474,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, @@ -548,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; @@ -568,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); @@ -583,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); @@ -597,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)); } @@ -616,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) @@ -818,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 @@ -897,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); @@ -926,13 +1062,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); @@ -1005,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); @@ -1022,13 +1165,65 @@ 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; rows_avail -= image.rows; + if (do_scroll) { + /* + * 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 + ? max(0, image.rows - 1) + : image.rows; + + xassert(rows_avail == 0 || + image.original.height % term->cell_height == 0); + + 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) { + 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.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; + + xassert(term_rows <= image.rows); + + row -= (image.rows - term_rows); + + term_cursor_to( + term, + max(0, 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]; @@ -1041,38 +1236,19 @@ 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( 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); @@ -1104,10 +1280,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; @@ -1116,11 +1288,24 @@ 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; - int alloc_height = (height + 6 - 1) / 6 * 6; + 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); xassert(alloc_height > 0); @@ -1165,9 +1350,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)); @@ -1210,10 +1400,14 @@ 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 + 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; @@ -1261,34 +1455,86 @@ 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); + const int pan = term->sixel.pan; 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; i++, sixel >>= 1, data += width) { + for (int i = 0; i < 6; i++, sixel >>= 1) { if (sixel & 1) { - *data = color; - max_non_empty_row = row + i; - } + for (int r = 0; r < pan; r++, data += width) + *data = color; + } else + data += width * pan; } xassert(sixel == 0); - - term->sixel.max_non_empty_row_no = max( - term->sixel.max_non_empty_row_no, - max_non_empty_row); } 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); + + const size_t ofs = term->sixel.row_byte_ofs + col; + uint32_t *data = &term->sixel.image.data[ofs]; + + 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 +sixel_add_many_generic(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; + count = min(count, max(width - col, 0)); + } + + uint32_t color = term->sixel.color; + 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 +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; @@ -1300,13 +1546,15 @@ 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); + sixel_add_ar_11(term, col, width, color, c); term->sixel.pos.col = col; } +IGNORE_WARNING("-Wpedantic") + static void -decsixel(struct terminal *term, uint8_t c) +decsixel_generic(struct terminal *term, uint8_t c) { switch (c) { case '"': @@ -1319,6 +1567,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 '#': @@ -1341,27 +1590,18 @@ 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; - 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(term, c - 63, 1); + case '?' ... '~': + sixel_add_many_generic(term, c - 63, 1); break; case ' ': @@ -1375,6 +1615,17 @@ decsixel(struct terminal *term, uint8_t c) } } +UNIGNORE_WARNINGS + +static void +decsixel_ar_11(struct terminal *term, uint8_t c) +{ + if (likely(c >= '?' && c <= '~')) + sixel_add_many_ar_11(term, c - 63, 1); + else + decsixel_generic(term, c); +} + static void decgra(struct terminal *term, uint8_t c) { @@ -1404,63 +1655,88 @@ decgra(struct terminal *term, uint8_t c) pan = pan > 0 ? pan : 1; pad = pad > 0 ? pad : 1; - LOG_DBG("pan=%u, pad=%u (aspect ratio = %u), size=%ux%u", - pan, pad, pan / pad, ph, pv); + pv *= pan; + ph *= pad; + + term->sixel.pan = pan; + term->sixel.pad = pad; + + 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) { 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; - 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; } } } +IGNORE_WARNING("-Wpedantic") + 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': - 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 '~': { - 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); + case '?' ... '~': { + 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; } default: term->sixel.state = SIXEL_DECSIXEL; - sixel_put(term, c); + term->vt.dcs.put_handler(term, c); break; } } +UNIGNORE_WARNINGS + +static void +decgri_ar_11(struct terminal *term, uint8_t c) +{ + if (likely(c >= '?' && c <= '~')) { + 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 + decgri_generic(term, c); +} + static void decgci(struct terminal *term, uint8_t c) { @@ -1537,19 +1813,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..ab8a5050 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); @@ -19,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/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 diff --git a/terminal.c b/terminal.c index 2e62fbb7..c22646f2 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) @@ -207,25 +193,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 @@ -406,11 +408,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; } @@ -736,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); @@ -752,9 +750,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( @@ -781,31 +776,17 @@ 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); + /* 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; } @@ -820,41 +801,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 @@ -873,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. @@ -883,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 @@ -901,42 +876,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) @@ -966,7 +905,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; @@ -989,14 +928,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); @@ -1093,7 +1032,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 @@ -1111,7 +1050,7 @@ load_fonts_from_conf(struct terminal *term) } } - return reload_fonts(term); + return reload_fonts(term, true); } static void fdm_client_terminated( @@ -1221,7 +1160,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 = { @@ -1345,11 +1284,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)); @@ -1358,7 +1295,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); @@ -1670,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); @@ -1717,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); @@ -1933,6 +1870,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; @@ -2055,7 +1993,7 @@ term_font_size_adjust_by_points(struct terminal *term, float amount) } } - return reload_fonts(term); + return reload_fonts(term, true); } static bool @@ -2078,7 +2016,7 @@ term_font_size_adjust_by_pixels(struct terminal *term, int amount) } } - return reload_fonts(term); + return reload_fonts(term, true); } static bool @@ -2102,7 +2040,7 @@ term_font_size_adjust_by_percent(struct terminal *term, bool increment, float pe } } - return reload_fonts(term); + return reload_fonts(term, true); } bool @@ -2140,13 +2078,43 @@ term_font_size_reset(struct terminal *term) } bool -term_font_dpi_changed(struct terminal *term, int old_scale) +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) { 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_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 || @@ -2155,11 +2123,10 @@ term_font_dpi_changed(struct terminal *term, int old_scale) : old_scale != term->scale); if (need_font_reload) { - LOG_DBG("DPI/scale change: DPI-awareness=%s, " - "DPI: %.2f -> %.2f, scale: %d -> %d, " + 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 == 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"); } @@ -2168,9 +2135,9 @@ term_font_dpi_changed(struct terminal *term, int 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 @@ -2545,6 +2512,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) { @@ -2693,6 +2669,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)) { @@ -2716,13 +2693,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 @@ -2779,6 +2755,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; } @@ -2797,7 +2774,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) @@ -3176,44 +3152,56 @@ 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); + case TERM_SURF_GRID: + if (seat->pointer.hidden) + shape = CURSOR_SHAPE_HIDDEN; - xcursor = seat->pointer.hidden ? XCURSOR_HIDDEN - : have_custom_cursor ? term->mouse_user_cursor - : term->is_searching ? XCURSOR_LEFT_PTR - : (seat->mouse.col >= 0 && - seat->mouse.row >= 0 && - term_mouse_grabbed(term, seat)) ? XCURSOR_TEXT - : XCURSOR_LEFT_PTR; +#if defined(HAVE_CURSOR_SHAPE) + else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 +#else + else if (false +#endif + || 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: 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 @@ -3548,7 +3536,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"); } @@ -3568,23 +3556,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; @@ -3764,6 +3752,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/terminal.h b/terminal.h index d2762a5a..4b1d1d0d 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 { @@ -180,8 +214,10 @@ struct grid { }; struct vt_subparams { - unsigned value[16]; uint8_t idx; + unsigned *cur; + unsigned value[16]; + unsigned dummy; }; struct vt_param { @@ -197,8 +233,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 */ @@ -450,7 +488,7 @@ struct terminal { int fd; } blink; - int scale; + float scale; int width; /* pixels */ int height; /* pixels */ int stashed_width; @@ -616,7 +654,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 */ @@ -630,6 +667,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 */ @@ -637,6 +683,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; @@ -671,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, @@ -703,10 +736,11 @@ 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); -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( @@ -739,6 +773,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); diff --git a/tests/test-config.c b/tests/test-config.c index 4736a46b..54efd13a 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) @@ -458,7 +502,8 @@ 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_string(&ctx, &parse_section_main, "utmp-helper", &conf.utmp_helper_path); test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters); @@ -466,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); @@ -479,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"}, @@ -577,8 +612,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); @@ -627,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) { @@ -727,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 */ @@ -1312,6 +1364,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/themes/aeroroot b/themes/aeroroot new file mode 100644 index 00000000..3b887448 --- /dev/null +++ b/themes/aeroroot @@ -0,0 +1,36 @@ +# -*- conf -*- +# 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 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 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 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 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 diff --git a/themes/starlight b/themes/starlight new file mode 100644 index 00000000..ed39f277 --- /dev/null +++ b/themes/starlight @@ -0,0 +1,24 @@ +# -*- conf -*- +# Theme: starlight V4 (https://github.com/CosmicToast/starlight) + +[colors] +foreground = FFFFFF +background = 242424 + +regular0 = 242424 +regular1 = f62b5a +regular2 = 47b413 +regular3 = e3c401 +regular4 = 24acd4 +regular5 = f2affd +regular6 = 13c299 +regular7 = e6e6e6 + +bright0 = 616161 +bright1 = ff4d51 +bright2 = 35d450 +bright3 = e9e836 +bright4 = 5dc5f8 +bright5 = feabf2 +bright6 = 24dfc4 +bright7 = ffffff 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); } } 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; diff --git a/wayland.c b/wayland.c index 68a7a4f1..7e51bfe9 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" @@ -32,12 +36,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 +56,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); @@ -74,12 +78,12 @@ 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); } - csd_reload_font(win, -1); + csd_reload_font(win, -1.); } static void @@ -187,8 +191,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); @@ -205,10 +213,17 @@ 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) 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) @@ -221,6 +236,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); } @@ -270,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) { @@ -288,31 +305,81 @@ 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; } +#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); + +#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) + 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); - wl_surface_destroy(seat->pointer.surface); + 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); seat->wl_pointer = NULL; - seat->pointer.surface = NULL; + seat->pointer.surface.surf = NULL; seat->pointer.theme = NULL; 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 @@ -331,15 +398,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; - int old_scale = term->scale; - - render_resize(term, term->width / term->scale, 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 @@ -350,11 +437,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); @@ -373,6 +455,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; @@ -395,27 +480,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; } } @@ -633,6 +727,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]; @@ -647,29 +742,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 } @@ -848,7 +949,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) @@ -1121,6 +1222,38 @@ 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(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; @@ -1204,7 +1337,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) { @@ -1214,7 +1347,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); @@ -1334,6 +1467,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"); @@ -1347,6 +1485,23 @@ 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 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; @@ -1369,14 +1524,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); @@ -1435,6 +1588,16 @@ 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_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); @@ -1469,6 +1632,29 @@ 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; + + 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); +} + +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) { @@ -1485,30 +1671,34 @@ 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; - 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; } - if (term->colors.alpha == 0xffff) { - struct wl_region *region = wl_compositor_create_region( - term->wl->compositor); + wayl_win_alpha_changed(win); - 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); - } + 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->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.surf); + wp_fractional_scale_v1_add_listener( + win->fractional_scale, &fractional_scale_listener, win); } +#endif - wl_surface_add_listener(win->surface, &surface_listener, win); - - 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); @@ -1541,12 +1731,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)) { @@ -1596,33 +1786,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); } } @@ -1630,8 +1820,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); @@ -1661,6 +1851,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->surface.viewport != NULL) + wp_viewport_destroy(win->surface.viewport); #endif if (win->frame_callback != NULL) wl_callback_destroy(win->frame_callback); @@ -1670,8 +1866,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); @@ -1681,7 +1877,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 */ @@ -1714,7 +1910,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); @@ -1798,6 +1994,86 @@ 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_explicit_width_height( + const struct wl_window *win, const struct wayl_surface *surf, + int width, int height, float 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); + + wl_surface_set_buffer_scale(surf->surf, 1); + 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 using legacy mode " + "(width=%d, height=%d)", scale, width, height); + + xassert(scale == floor(scale)); + + const int iscale = (int)scale; + xassert(width % iscale == 0); + xassert(height % iscale == 0); + + wl_surface_set_buffer_scale(surf->surf, iscale); + } +} + +void +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( + 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 float scale = term->scale; + + wayl_surface_scale(win, &win->surface, buf, scale); +} + +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.surf, region); + wl_region_destroy(region); + } + } else + wl_surface_set_opaque_region(win->surface.surf, NULL); +} + #if defined(HAVE_XDG_ACTIVATION) static void activation_token_for_urgency_done(const char *token, void *data) @@ -1806,7 +2082,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 */ @@ -1851,27 +2127,43 @@ 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 = 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); @@ -1883,31 +2175,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->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 (surf->sub != NULL) - wl_subsurface_destroy(surf->sub); - if (surf->surf != NULL) - wl_surface_destroy(surf->surf); - surf->surf = NULL; - surf->sub = NULL; +#if defined(HAVE_FRACTIONAL_SCALE) + if (surf->surface.viewport != NULL) { + wp_viewport_destroy(surf->surface.viewport); + surf->surface.viewport = NULL; + } +#endif + if (surf->sub != NULL) { + wl_subsurface_destroy(surf->sub); + surf->sub = NULL; + } + if (surf->surface.surf != NULL) { + wl_surface_destroy(surf->surface.surf); + surf->surface.surf = NULL; + } } #if defined(HAVE_XDG_ACTIVATION) @@ -1972,7 +2275,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 4b6939ab..275338a8 100644 --- a/wayland.h +++ b/wayland.h @@ -20,13 +20,20 @@ #include #endif +#if defined(HAVE_FRACTIONAL_SCALE) + #include + #include +#endif + #include #include +#include "cursor-shape.h" #include "fdm.h" /* 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 { @@ -40,6 +47,26 @@ 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) + 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 */ @@ -127,17 +154,36 @@ struct seat { struct { uint32_t serial; - struct wl_surface *surface; + /* Client-side cursor */ + struct wayl_surface surface; struct wl_cursor_theme *theme; struct wl_cursor *cursor; - int scale; - bool hidden; - const char *xcursor; + /* 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; + char *last_custom_xcursor; + struct wl_callback *xcursor_callback; 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; @@ -269,7 +315,10 @@ struct monitor { } scaled; } ppi; - float dpi; + struct { + float scaled; + float physical; + } dpi; int scale; float refresh; @@ -289,14 +338,9 @@ struct monitor { bool use_output_release; }; -struct wl_surf_subsurf { - struct wl_surface *surf; - struct wl_subsurface *sub; -}; - 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}; @@ -320,21 +364,26 @@ 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) tll(struct xdg_activation_token_context *) xdg_tokens; bool urgency_token_is_pending; +#endif +#if defined(HAVE_FRACTIONAL_SCALE) + 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; 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; @@ -345,10 +394,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; @@ -406,6 +455,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; @@ -414,6 +467,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; @@ -426,26 +484,36 @@ 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); +bool wayl_fractional_scaling(const struct wayland *wayl); +void wayl_surface_scale( + 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 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); void wayl_win_destroy(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); 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(