Merge CHANGELOG.md

This commit is contained in:
Max Gautier 2023-07-21 06:01:28 +02:00
commit dd647203fb
No known key found for this signature in database
52 changed files with 2907 additions and 1116 deletions

View file

@ -1,4 +1,4 @@
image: alpine/latest
image: alpine/edge
packages:
- musl-dev
- eudev-libs

View file

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

View file

@ -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 cursors vertical position after emitting a sixel, when sixel
scrolling is **enabled** (the default) has been updated to match
XTerms, and the VT382s 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

View file

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

View file

@ -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.
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>o</kbd>
: Enter URL mode, where all currently visible URLs are tagged with a
jump label with a key sequence that will open the URL.
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>
: Enter Unicode input mode.
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>z</kbd>
: 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
<kbd>tap</kbd>
: Emulates mouse left button click.
<kbd>drag</kbd>
: 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 <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd> enters _“URL
Pressing <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>o</kbd> 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.
Foots default behavior is to use the monitors 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 monitors 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 compositors scaling factor, and **not** the monitor
DPI.
When fonts are sized using the monitors 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 monitors
DPI. When the foot window is moved across monitors, the font size is
updated for the current monitors 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

View file

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

128
config.c
View file

@ -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", &nots, &overrides, false);
fcft_init(FCFT_LOG_COLORIZE_NEVER, false, FCFT_LOG_CLASS_NONE);
bool ret = config_load(&original, "/dev/null", &nots, &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);
}

View file

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

22
csi.c
View file

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

115
cursor-shape.c Normal file
View file

@ -0,0 +1,115 @@
#include <stdlib.h>
#include <string.h>
#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 */

34
cursor-shape.h Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#if defined(HAVE_CURSOR_SHAPE)
#include <cursor-shape-v1.h>
#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

3
dcs.c
View file

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

View file

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

View file

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

View file

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

View file

@ -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 = '<no utmp support in foot>'
utmp_del_args = '<no utmp support in foot>'
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

View file

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

View file

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

View file

@ -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=<font metrics>
# underline-thickness=<font underline thickness>
# box-drawings-uses-font-glyphs=no
# dpi-aware=auto
# dpi-aware=no
# initial-window-size-pixels=700x500 # Or,
# initial-window-size-chars=<COLSxROWS>
@ -34,7 +34,8 @@
# word-delimiters=,│`|:"'()[]{}<>
# selection-target=primary
# workers=<number of logical CPUs>
# 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=<primary font>
# color=<foreground color>
# hide-when-typing=no
# hide-when-maximized=no
# double-click-to-maximize=yes
# border-width=0
# border-color=<csd.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]

View file

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

69
grid.c
View file

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

292
input.c
View file

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

View file

@ -3,8 +3,9 @@
#include <stdint.h>
#include <wayland-client.h>
#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);

17
main.c
View file

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

View file

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

View file

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

View file

@ -9,4 +9,3 @@ Keywords=shell;prompt;command;commandline;
Name=Foot Server
GenericName=Terminal
Comment=A wayland native terminal emulator (server)
StartupWMClass=foot

View file

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

9
osc.c
View file

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

View file

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

View file

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

View file

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

618
render.c

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

653
sixel.c
View file

@ -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, thatll end up being the very last
* row of the sixel (which were 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 sixels 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;
}

View file

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

View file

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

View file

@ -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
* monitors 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 were not mapped, use the scaling factor from the first
* available output.
* - if there arent 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);
}

View file

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

View file

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

36
themes/aeroroot Normal file
View file

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

28
themes/ayu-mirage Normal file
View file

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

27
themes/chiba-dark Normal file
View file

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

View file

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

26
themes/srcery Normal file
View file

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

24
themes/starlight Normal file
View file

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

View file

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

135
vt.c
View file

@ -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 = &param->sub.value[param->sub.idx - 1];
} else
value = &param->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 = &param->sub.dummy;
} else
sub_param_value = &param->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 = &param->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;

515
wayland.c
View file

@ -14,6 +14,10 @@
#include <wayland-cursor.h>
#include <xkbcommon/xkbcommon-compose.h>
#if defined(HAVE_CURSOR_SHAPE)
#include <cursor-shape-v1.h>
#endif
#include <tllist.h>
#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
* havent 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] : "<unknown>");
}
#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;

110
wayland.h
View file

@ -20,13 +20,20 @@
#include <xdg-activation-v1.h>
#endif
#if defined(HAVE_FRACTIONAL_SCALE)
#include <viewporter.h>
#include <fractional-scale-v1.h>
#endif
#include <fcft/fcft.h>
#include <tllist.h>
#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(