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(