This commit is contained in:
lbia.xyz 2022-10-04 02:14:53 +02:00
commit fd187cc491
No known key found for this signature in database
GPG key ID: 6774C7B38E986DF1
50 changed files with 1530 additions and 481 deletions

View file

@ -24,7 +24,7 @@ packages:
- font-noto-emoji - font-noto-emoji
sources: sources:
- https://codeberg.org/dnkl/foot - https://git.sr.ht/~dnkl/foot
# triggers: # triggers:
# - action: email # - action: email
@ -49,4 +49,4 @@ tasks:
- codespell: | - codespell: |
pip install codespell pip install codespell
cd foot cd foot
~/.local/bin/codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd ~/.local/bin/codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd

View file

@ -23,7 +23,7 @@ packages:
- font-noto-emoji - font-noto-emoji
sources: sources:
- https://codeberg.org/dnkl/foot - https://git.sr.ht/~dnkl/foot
# triggers: # triggers:
# - action: email # - action: email

View file

@ -19,7 +19,7 @@ packages:
- noto-emoji - noto-emoji
sources: sources:
- https://codeberg.org/dnkl/foot - https://git.sr.ht/~dnkl/foot
# triggers: # triggers:
# - action: email # - action: email

View file

@ -109,4 +109,4 @@ codespell:
- apk add python3 - apk add python3
- apk add py3-pip - apk add py3-pip
- pip install codespell - pip install codespell
- codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd - codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd

View file

@ -9,7 +9,7 @@ pipeline:
- apk add python3 - apk add python3
- apk add py3-pip - apk add py3-pip
- pip install codespell - pip install codespell
- codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd - codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd
subprojects: subprojects:
when: when:

View file

@ -1,6 +1,8 @@
# Changelog # Changelog
* [Unreleased](#unreleased) * [Unreleased](#unreleased)
* [1.13.1](#1-13-1)
* [1.13.0](#1-13-0)
* [1.12.1](#1-12-1) * [1.12.1](#1-12-1)
* [1.12.0](#1-12-0) * [1.12.0](#1-12-0)
* [1.11.0](#1-11-0) * [1.11.0](#1-11-0)
@ -43,6 +45,80 @@
### Added ### Added
* Support for adjusting the thickness of regular underlines ([#1136][1136]).
* Support (optional) for utmp logging with libutempter.
* `kxIN` and `kxOUT` (focus in/out events) to terminfo.
* `name` capability to `XTGETTCAP`.
[1136]: https://codeberg.org/dnkl/foot/issues/1136
### Changed
* Default color theme from a variant of the Zenburn theme, to a
variant of the Solarized dark theme.
* Default `pad` from 2x2 to 0x0 (i.e. no padding at all).
* Current working directory (as set by OSC-7) is now passed to the
program executed by the `pipe-*` key bindings ([#1166][1166]).
* `DECRPM` replies (to `DECRQM` queries) now report a value of `4`
("permanently reset") instead of `2` ("reset") for DEC private
modes that are known but unsupported.
* Set `PWD` environment variable in the slave process ([#1179][1179]).
[1166]: https://codeberg.org/dnkl/foot/issues/1166
[1179]: https://codeberg.org/dnkl/foot/issues/1179
### Deprecated
### Removed
### Fixed
* Crash in `foot --server` on key press, after another `footclient`
has terminated very early (for example, by trying to launch a
non-existing shell/client).
* Glitchy rendering when scrolling in the scrollback, on compositors
that does not allow Wayland buffer re-use (e.g. KDE/plasma)
([#1173][1173])
* Scrollback search matches not being highlighted correctly, on
compositors that does now allow Wayland buffer re-use
(e.g. KDE/plasma).
[1173]: https://codeberg.org/dnkl/foot/issues/1173
### Security
### Contributors
* Craig Barnes
## 1.13.1
### Changed
* Window is now dimmed while in Unicode input mode.
### Fixed
* Compiling against wayland-protocols < 1.25
* Crash on buggy compositors (GNOME) that sometimes send pointer-enter
events with a NULL surface. Foot now ignores these events, and the
subsequent motion and leave events.
* Regression: “random” selected empty cells being highlighted as
selected when they should not.
* Crash when either resizing the terminal window, or scrolling in the
scrollback history ([#1074][1074])
* OSC-8 URLs with matching IDs, but mismatching URIs being incorrectly
connected.
[1074]: https://codeberg.org/dnkl/foot/pulls/1074
## 1.13.0
### Added
* XDG activation support when opening URLs ([#1058][1058]). * XDG activation support when opening URLs ([#1058][1058]).
* `-Dsystemd-units-dir=<path>` meson command line option. * `-Dsystemd-units-dir=<path>` meson command line option.
* Support for custom environment variables in `foot.ini` * Support for custom environment variables in `foot.ini`
@ -50,10 +126,21 @@
* Support for jumping to previous/next prompt (requires shell * Support for jumping to previous/next prompt (requires shell
integration). By default bound to `ctrl`+`shift`+`z` and integration). By default bound to `ctrl`+`shift`+`z` and
`ctrl`+`shift`+`x` respectively ([#30][30]). `ctrl`+`shift`+`x` respectively ([#30][30]).
* `colors.search-box-no-match` and `colors.search-box-match` options
to `foot.ini` ([#1112][1112]).
* Very basic Unicode input mode via the new
`key-bindings.unicode-input` and `search-bindings.unicode-input` key
bindings. Note that there is no visual feedback, as the preferred
way of entering Unicode characters is with an IME ([#1116][1116]).
* Support for `xdg_toplevel.wm_capabilities`, to adapt the client-side
decoration buttons to the compositor capabilities ([#1061][1061]).
[1058]: https://codeberg.org/dnkl/foot/issues/1058 [1058]: https://codeberg.org/dnkl/foot/issues/1058
[1070]: https://codeberg.org/dnkl/foot/issues/1070 [1070]: https://codeberg.org/dnkl/foot/issues/1070
[30]: https://codeberg.org/dnkl/foot/issues/30 [30]: https://codeberg.org/dnkl/foot/issues/30
[1112]: https://codeberg.org/dnkl/foot/issues/1112
[1116]: https://codeberg.org/dnkl/foot/issues/1116
[1061]: https://codeberg.org/dnkl/foot/pulls/1061
### Changed ### Changed
@ -64,12 +151,13 @@
mode ([#1084][1084]). mode ([#1084][1084]).
* NUL is now stripped when pasting in non-bracketed mode * NUL is now stripped when pasting in non-bracketed mode
([#1084][1084]). ([#1084][1084]).
* `alt`+`escape` now emits `\E\E` instead of a `CSI 27` sequence
([#1105][1105]).
[1084]: https://codeberg.org/dnkl/foot/issues/1084 [1084]: https://codeberg.org/dnkl/foot/issues/1084
[1105]: https://codeberg.org/dnkl/foot/issues/1105
### Deprecated
### Removed
### Fixed ### Fixed
* Graphical corruption when viewport is at the top of the scrollback, * Graphical corruption when viewport is at the top of the scrollback,
@ -80,14 +168,41 @@
* Workaround for buggy compositors (e.g. some versions of GNOME) * Workaround for buggy compositors (e.g. some versions of GNOME)
allowing drag-and-drops even though foot has reported it does not allowing drag-and-drops even though foot has reported it does not
support the offered mime-types ([#1092][1092]). support the offered mime-types ([#1092][1092]).
* Keyboard enter/leave events being ignored if there is no keymap
([#1097][1097]).
* Crash when application emitted an invalid `CSI 38;5;<idx>m`, `CSI
38:5:<idx>m`, `CSI 48;5;<idx>m` or `CSI 48:5:<idx>m` sequence
([#1111][1111]).
* Certain dead-key combinations resulting in different escape
sequences compared to kitty, when the kitty keyboard protocol is
used ([#1120][1120]).
* Search matches ending with a double-width character not being
highlighted correctly.
* Selection not being cancelled correctly when scrolled out.
* Extending a multi-page selection behaving inconsistently.
* Poor performance when making very large selections ([#1114][1114]).
* Bogus error message when using systemd socket activation for server
mode ([#1107][1107])
* Empty line at the bottom after a window resize ([#1108][1108]).
[1055]: https://codeberg.org/dnkl/foot/issues/1055 [1055]: https://codeberg.org/dnkl/foot/issues/1055
[1092]: https://codeberg.org/dnkl/foot/issues/1092 [1092]: https://codeberg.org/dnkl/foot/issues/1092
[1097]: https://codeberg.org/dnkl/foot/issues/1097
[1111]: https://codeberg.org/dnkl/foot/issues/1111
[1120]: https://codeberg.org/dnkl/foot/issues/1120
[1114]: https://codeberg.org/dnkl/foot/issues/1114
[1107]: https://codeberg.org/dnkl/foot/issues/1107
[1108]: https://codeberg.org/dnkl/foot/issues/1108
### Security
### Contributors ### Contributors
* Craig Barnes
* Lorenz
* Max Gautier
* Simon Ser
* Stefan Prosiegel
## 1.12.1 ## 1.12.1

View file

@ -44,6 +44,8 @@ subprojects.
* pixman * pixman
* wayland (_client_ and _cursor_ libraries) * wayland (_client_ and _cursor_ libraries)
* xkbcommon * xkbcommon
* utf8proc (_optional_, needed for grapheme clustering)
* libutempter (_optional_, needed for utmp logging)
* [fcft](https://codeberg.org/dnkl/fcft) [^1] * [fcft](https://codeberg.org/dnkl/fcft) [^1]
[^1]: can also be built as subprojects, in which case they are [^1]: can also be built as subprojects, in which case they are
@ -140,16 +142,17 @@ mkdir -p bld/release && cd bld/release
Available compile-time options: Available compile-time options:
| Option | Type | Default | Description | Extra dependencies | | Option | Type | Default | Description | Extra dependencies |
|--------------------------------------|---------|-------------------------|-------------------------------------------------------|--------------------| |--------------------------------------|---------|-------------------------|-----------------------------------------------------------|--------------------|
| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc | | `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc |
| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | none | | `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | none |
| `-Dime` | bool | `true` | Enables IME support | None | | `-Dime` | bool | `true` | Enables IME support | None |
| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc | | `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc |
| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) | | `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) |
| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | none | | `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | none |
| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | 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 | | `-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 |
Documentation includes the man pages, readme, changelog and license Documentation includes the man pages, readme, changelog and license
files. files.

View file

@ -408,6 +408,28 @@ main(int argc, char *const *argv)
cwd = _cwd; cwd = _cwd;
} }
const char *pwd = getenv("PWD");
if (pwd != NULL) {
char *resolved_path_cwd = realpath(cwd, NULL);
char *resolved_path_pwd = realpath(pwd, NULL);
if (resolved_path_cwd != NULL &&
resolved_path_pwd != NULL &&
strcmp(resolved_path_cwd, resolved_path_pwd) == 0)
{
/*
* The resolved path of $PWD matches the resolved path of
* the *actual* working directory - use $PWD.
*
* This makes a difference when $PWD refers to a symlink.
*/
cwd = pwd;
}
free(resolved_path_cwd);
free(resolved_path_pwd);
}
if (client_environment) { if (client_environment) {
for (char **e = environ; *e != NULL; e++) { for (char **e = environ; *e != NULL; e++) {
if (!push_string(&envp, *e, &total_len)) if (!push_string(&envp, *e, &total_len))

View file

@ -30,8 +30,8 @@
#include "xmalloc.h" #include "xmalloc.h"
#include "xsnprintf.h" #include "xsnprintf.h"
static const uint32_t default_foreground = 0xdcdccc; static const uint32_t default_foreground = 0x839496;
static const uint32_t default_background = 0x111111; static const uint32_t default_background = 0x002b36;
static const size_t min_csd_border_width = 5; 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] = { static const uint32_t default_color_table[256] = {
// Regular // Regular
0x222222, 0x073642,
0xcc9393, 0xdc322f,
0x7f9f7f, 0x859900,
0xd0bf8f, 0xb58900,
0x6ca0a3, 0x268bd2,
0xdc8cc3, 0xd33682,
0x93e0e3, 0x2aa198,
0xdcdccc, 0xeee8d5,
// Bright // Bright
0x666666, 0x08404f,
0xdca3a3, 0xe35f5c,
0xbfebbf, 0x9fb700,
0xf0dfaf, 0xd9a400,
0x8cd0d3, 0x4ba1de,
0xfcace3, 0xdc619d,
0xb3ffff, 0x32c1b6,
0xffffff, 0xffffff,
// 6x6x6 RGB cube // 6x6x6 RGB cube
@ -117,6 +117,7 @@ static const char *const binding_action_map[] = {
[BIND_ACTION_TEXT_BINDING] = "text-binding", [BIND_ACTION_TEXT_BINDING] = "text-binding",
[BIND_ACTION_PROMPT_PREV] = "prompt-prev", [BIND_ACTION_PROMPT_PREV] = "prompt-prev",
[BIND_ACTION_PROMPT_NEXT] = "prompt-next", [BIND_ACTION_PROMPT_NEXT] = "prompt-next",
[BIND_ACTION_UNICODE_INPUT] = "unicode-input",
/* Mouse-specific actions */ /* Mouse-specific actions */
[BIND_ACTION_SELECT_BEGIN] = "select-begin", [BIND_ACTION_SELECT_BEGIN] = "select-begin",
@ -149,6 +150,7 @@ static const char *const search_binding_action_map[] = {
[BIND_ACTION_SEARCH_EXTEND_LINE] = "extend-to-end-line", [BIND_ACTION_SEARCH_EXTEND_LINE] = "extend-to-end-line",
[BIND_ACTION_SEARCH_CLIPBOARD_PASTE] = "clipboard-paste", [BIND_ACTION_SEARCH_CLIPBOARD_PASTE] = "clipboard-paste",
[BIND_ACTION_SEARCH_PRIMARY_PASTE] = "primary-paste", [BIND_ACTION_SEARCH_PRIMARY_PASTE] = "primary-paste",
[BIND_ACTION_SEARCH_UNICODE_INPUT] = "unicode-input",
}; };
static const char *const url_binding_action_map[] = { static const char *const url_binding_action_map[] = {
@ -903,6 +905,9 @@ parse_section_main(struct context *ctx)
return true; return true;
} }
else if (strcmp(key, "underline-thickness") == 0)
return value_to_pt_or_px(ctx, &conf->underline_thickness);
else if (strcmp(key, "dpi-aware") == 0) { else if (strcmp(key, "dpi-aware") == 0) {
if (strcmp(value, "auto") == 0) if (strcmp(value, "auto") == 0)
conf->dpi_aware = DPI_AWARE_AUTO; conf->dpi_aware = DPI_AWARE_AUTO;
@ -940,6 +945,18 @@ parse_section_main(struct context *ctx)
else if (strcmp(key, "box-drawings-uses-font-glyphs") == 0) else if (strcmp(key, "box-drawings-uses-font-glyphs") == 0)
return value_to_bool(ctx, &conf->box_drawings_uses_font_glyphs); 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))
return false;
if (strcmp(conf->utempter_path, "none") == 0) {
free(conf->utempter_path);
conf->utempter_path = NULL;
}
return true;
}
else { else {
LOG_CONTEXTUAL_ERR("not a valid option: %s", key); LOG_CONTEXTUAL_ERR("not a valid option: %s", key);
return false; return false;
@ -1172,6 +1189,34 @@ parse_section_colors(struct context *ctx)
return true; return true;
} }
else if (strcmp(key, "search-box-no-match") == 0) {
if (!value_to_two_colors(
ctx,
&conf->colors.search_box.no_match.fg,
&conf->colors.search_box.no_match.bg,
false))
{
return false;
}
conf->colors.use_custom.search_box_no_match = true;
return true;
}
else if (strcmp(key, "search-box-match") == 0) {
if (!value_to_two_colors(
ctx,
&conf->colors.search_box.match.fg,
&conf->colors.search_box.match.bg,
false))
{
return false;
}
conf->colors.use_custom.search_box_match = true;
return true;
}
else if (strcmp(key, "urls") == 0) { else if (strcmp(key, "urls") == 0) {
if (!value_to_color(ctx, &conf->colors.url, false)) if (!value_to_color(ctx, &conf->colors.url, false))
return false; return false;
@ -1976,6 +2021,9 @@ resolve_key_binding_collisions(struct config *conf, const char *section_name,
sym_equal = (binding1->m.button == binding2->m.button && sym_equal = (binding1->m.button == binding2->m.button &&
binding1->m.count == binding2->m.count); binding1->m.count == binding2->m.count);
break; break;
default:
BUG("unhandled key binding type");
} }
if (!mods_equal || !sym_equal) if (!mods_equal || !sym_equal)
@ -2610,6 +2658,9 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar
if (!section_parser(ctx)) if (!section_parser(ctx))
error_or_continue(); error_or_continue();
/* For next iteration of getline() */
errno = 0;
} }
if (errno != 0) { if (errno != 0) {
@ -2787,8 +2838,8 @@ config_load(struct config *conf, const char *conf_path,
.width = 700, .width = 700,
.height = 500, .height = 500,
}, },
.pad_x = 2, .pad_x = 0,
.pad_y = 2, .pad_y = 0,
.resize_delay_ms = 100, .resize_delay_ms = 100,
.bold_in_bright = { .bold_in_bright = {
.enabled = false, .enabled = false,
@ -2802,6 +2853,7 @@ config_load(struct config *conf, const char *conf_path,
.vertical_letter_offset = {.pt = 0, .px = 0}, .vertical_letter_offset = {.pt = 0, .px = 0},
.use_custom_underline_offset = false, .use_custom_underline_offset = false,
.box_drawings_uses_font_glyphs = 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 = DPI_AWARE_AUTO, /* DPI-aware when scaling-factor == 1 */
.bell = { .bell = {
.urgent = false, .urgent = false,
@ -2899,6 +2951,9 @@ config_load(struct config *conf, const char *conf_path,
}, },
.env_vars = tll_init(), .env_vars = tll_init(),
.utempter_path = (strlen(FOOT_DEFAULT_UTEMPTER_PATH) > 0
? xstrdup(FOOT_DEFAULT_UTEMPTER_PATH)
: NULL),
.notifications = tll_init(), .notifications = tll_init(),
}; };
@ -3187,6 +3242,9 @@ config_clone(const struct config *old)
tll_push_back(conf->env_vars, copy); tll_push_back(conf->env_vars, copy);
} }
conf->utempter_path =
old->utempter_path != NULL ? xstrdup(old->utempter_path) : NULL;
conf->notifications.length = 0; conf->notifications.length = 0;
conf->notifications.head = conf->notifications.tail = 0; conf->notifications.head = conf->notifications.tail = 0;
tll_foreach(old->notifications, it) { tll_foreach(old->notifications, it) {
@ -3253,6 +3311,7 @@ config_free(struct config *conf)
tll_remove(conf->env_vars, it); tll_remove(conf->env_vars, it);
} }
free(conf->utempter_path);
user_notifications_free(&conf->notifications); user_notifications_free(&conf->notifications);
} }

View file

@ -150,6 +150,7 @@ struct config {
bool use_custom_underline_offset; bool use_custom_underline_offset;
struct pt_or_px underline_offset; struct pt_or_px underline_offset;
struct pt_or_px underline_thickness;
bool box_drawings_uses_font_glyphs; bool box_drawings_uses_font_glyphs;
bool can_shape_grapheme; bool can_shape_grapheme;
@ -217,11 +218,25 @@ struct config {
uint32_t bg; uint32_t bg;
} scrollback_indicator; } scrollback_indicator;
struct {
struct {
uint32_t fg;
uint32_t bg;
} no_match;
struct {
uint32_t fg;
uint32_t bg;
} match;
} search_box;
struct { struct {
bool selection:1; bool selection:1;
bool jump_label:1; bool jump_label:1;
bool scrollback_indicator:1; bool scrollback_indicator:1;
bool url:1; bool url:1;
bool search_box_no_match:1;
bool search_box_match:1;
uint8_t dim; uint8_t dim;
} use_custom; } use_custom;
} colors; } colors;
@ -304,6 +319,8 @@ struct config {
env_var_list_t env_vars; env_var_list_t env_vars;
char *utempter_path;
struct { struct {
enum fcft_scaling_filter fcft_filter; enum fcft_scaling_filter fcft_filter;
bool overflowing_glyphs; bool overflowing_glyphs;

106
csi.c
View file

@ -128,7 +128,8 @@ csi_sgr(struct terminal *term)
term->vt.params.v[i + 1].value == 5) term->vt.params.v[i + 1].value == 5)
{ {
src = COLOR_BASE256; src = COLOR_BASE256;
color = term->vt.params.v[i + 2].value; color = min(term->vt.params.v[i + 2].value,
ALEN(term->colors.table) - 1);
i += 2; i += 2;
} }
@ -149,7 +150,8 @@ csi_sgr(struct terminal *term)
term->vt.params.v[i].sub.value[0] == 5) term->vt.params.v[i].sub.value[0] == 5)
{ {
src = COLOR_BASE256; src = COLOR_BASE256;
color = term->vt.params.v[i].sub.value[1]; color = min(term->vt.params.v[i].sub.value[1],
ALEN(term->colors.table) - 1);
} }
/* /*
@ -484,7 +486,7 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
} }
tll_free(term->alt.scroll_damage); tll_free(term->alt.scroll_damage);
term_damage_all(term); term_damage_view(term);
} }
term_update_ascii_printer(term); term_update_ascii_printer(term);
break; break;
@ -533,47 +535,65 @@ decrst(struct terminal *term, unsigned param)
decset_decrst(term, param, false); decset_decrst(term, param, false);
} }
static bool /*
decrqm(const struct terminal *term, unsigned param, bool *enabled) * These values represent the current state of a DEC private mode,
* as returned in the DECRPM reply to a DECRQM query.
*/
enum decrpm_status {
DECRPM_NOT_RECOGNIZED = 0,
DECRPM_SET = 1,
DECRPM_RESET = 2,
DECRPM_PERMANENTLY_SET = 3,
DECRPM_PERMANENTLY_RESET = 4,
};
static enum decrpm_status
decrpm(bool enabled)
{
return enabled ? DECRPM_SET : DECRPM_RESET;
}
static enum decrpm_status
decrqm(const struct terminal *term, unsigned param)
{ {
switch (param) { switch (param) {
case 1: *enabled = term->cursor_keys_mode == CURSOR_KEYS_APPLICATION; return true; case 1: return decrpm(term->cursor_keys_mode == CURSOR_KEYS_APPLICATION);
case 3: *enabled = false; return true; case 3: return DECRPM_PERMANENTLY_RESET;
case 4: *enabled = false; return true; case 4: return DECRPM_PERMANENTLY_RESET;
case 5: *enabled = term->reverse; return true; case 5: return decrpm(term->reverse);
case 6: *enabled = term->origin; return true; case 6: return decrpm(term->origin);
case 7: *enabled = term->auto_margin; return true; case 7: return decrpm(term->auto_margin);
case 9: *enabled = false; /* term->mouse_tracking == MOUSE_X10; */ return true; case 9: return DECRPM_PERMANENTLY_RESET; /* term->mouse_tracking == MOUSE_X10; */
case 12: *enabled = term->cursor_blink.decset; return true; case 12: return decrpm(term->cursor_blink.decset);
case 25: *enabled = !term->hide_cursor; return true; case 25: return decrpm(!term->hide_cursor);
case 45: *enabled = term->reverse_wrap; return true; case 45: return decrpm(term->reverse_wrap);
case 66: *enabled = term->keypad_keys_mode == KEYPAD_APPLICATION; return true; case 66: return decrpm(term->keypad_keys_mode == KEYPAD_APPLICATION);
case 80: *enabled = !term->sixel.scrolling; return true; case 80: return decrpm(!term->sixel.scrolling);
case 1000: *enabled = term->mouse_tracking == MOUSE_CLICK; return true; case 1000: return decrpm(term->mouse_tracking == MOUSE_CLICK);
case 1001: *enabled = false; return true; case 1001: return DECRPM_PERMANENTLY_RESET;
case 1002: *enabled = term->mouse_tracking == MOUSE_DRAG; return true; case 1002: return decrpm(term->mouse_tracking == MOUSE_DRAG);
case 1003: *enabled = term->mouse_tracking == MOUSE_MOTION; return true; case 1003: return decrpm(term->mouse_tracking == MOUSE_MOTION);
case 1004: *enabled = term->focus_events; return true; case 1004: return decrpm(term->focus_events);
case 1005: *enabled = false; /* term->mouse_reporting == MOUSE_UTF8; */ return true; case 1005: return DECRPM_PERMANENTLY_RESET; /* term->mouse_reporting == MOUSE_UTF8; */
case 1006: *enabled = term->mouse_reporting == MOUSE_SGR; return true; case 1006: return decrpm(term->mouse_reporting == MOUSE_SGR);
case 1007: *enabled = term->alt_scrolling; return true; case 1007: return decrpm(term->alt_scrolling);
case 1015: *enabled = term->mouse_reporting == MOUSE_URXVT; return true; case 1015: return decrpm(term->mouse_reporting == MOUSE_URXVT);
case 1016: *enabled = term->mouse_reporting == MOUSE_SGR_PIXELS; return true; case 1016: return decrpm(term->mouse_reporting == MOUSE_SGR_PIXELS);
case 1034: *enabled = term->meta.eight_bit; return true; case 1034: return decrpm(term->meta.eight_bit);
case 1035: *enabled = term->num_lock_modifier; return true; case 1035: return decrpm(term->num_lock_modifier);
case 1036: *enabled = term->meta.esc_prefix; return true; case 1036: return decrpm(term->meta.esc_prefix);
case 1042: *enabled = term->bell_action_enabled; return true; case 1042: return decrpm(term->bell_action_enabled);
case 47: /* FALLTHROUGH */ case 47: /* FALLTHROUGH */
case 1047: /* FALLTHROUGH */ case 1047: /* FALLTHROUGH */
case 1049: *enabled = term->grid == &term->alt; return true; case 1049: return decrpm(term->grid == &term->alt);
case 1070: *enabled = term->sixel.use_private_palette; return true; case 1070: return decrpm(term->sixel.use_private_palette);
case 2004: *enabled = term->bracketed_paste; return true; case 2004: return decrpm(term->bracketed_paste);
case 2026: *enabled = term->render.app_sync_updates.enabled; return true; case 2026: return decrpm(term->render.app_sync_updates.enabled);
case 8452: *enabled = term->sixel.cursor_right_of_graphics; return true; case 8452: return decrpm(term->sixel.cursor_right_of_graphics);
case 737769: *enabled = term_ime_is_enabled(term); return true; case 737769: return decrpm(term_ime_is_enabled(term));
} }
return false; return DECRPM_NOT_RECOGNIZED;
} }
static void static void
@ -1719,15 +1739,9 @@ csi_dispatch(struct terminal *term, uint8_t final)
* 3 - permanently set * 3 - permanently set
* 4 - permantently reset * 4 - permantently reset
*/ */
bool enabled; unsigned status = decrqm(term, param);
unsigned value;
if (decrqm(term, param, &enabled))
value = enabled ? 1 : 2;
else
value = 0;
char reply[32]; char reply[32];
size_t n = xsnprintf(reply, sizeof(reply), "\033[?%u;%u$y", param, value); size_t n = xsnprintf(reply, sizeof(reply), "\033[?%u;%u$y", param, status);
term_to_slave(term, reply, n); term_to_slave(term, reply, n);
break; break;

View file

@ -132,6 +132,18 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*.
Default: _unset_. Default: _unset_.
*underline-thickness*
Use a custom thickness (height) for underlines. The thickness is, by
default, in _points_.
To specify a thickness in _pixels_, append *px*:
*underline-thickness=1px*.
If left unset (the default), the thickness specified in the font is
used.
Default: _unset_
*box-drawings-uses-font-glyphs* Boolean. When disabled, foot generates *box-drawings-uses-font-glyphs* Boolean. When disabled, foot generates
box/line drawing characters itself. The are several advantages to box/line drawing characters itself. The are several advantages to
doing this instead of using font glyphs: doing this instead of using font glyphs:
@ -198,7 +210,7 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*.
To instead center the grid content, append *center* (e.g. *pad=5x5 To instead center the grid content, append *center* (e.g. *pad=5x5
center*). center*).
Default: _2x2_. Default: _0x0_.
*resize-delay-ms* *resize-delay-ms*
Time, in milliseconds, of "idle time" before foot sends the new Time, in milliseconds, of "idle time" before foot sends the new
@ -305,6 +317,10 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*.
(including SMT). Note that this is not always the best value. In (including SMT). Note that this is not always the best value. In
some cases, the number of physical _cores_ is better. some cases, the number of physical _cores_ is better.
*utempter*
Path to utempter helper binary. Set to *none* to disable utmp
records. Default: _@utempter@_.
# SECTION: environment # SECTION: environment
This section is used to define environment variables that will be set This section is used to define environment variables that will be set
@ -495,21 +511,23 @@ can configure the background transparency with the _alpha_ option.
*foreground* *foreground*
Default foreground color. This is the color used when no ANSI Default foreground color. This is the color used when no ANSI
color is being used. Default: _dcdccc_. color is being used. Default: _839496_.
*background* *background*
Default background color. This is the color used when no ANSI Default background color. This is the color used when no ANSI
color is being used. Default: _111111_. color is being used. Default: _002b36_.
*regular0*, *regular1* *..* *regular7* *regular0*, *regular1* *..* *regular7*
The eight basic ANSI colors (Black, Red, Green, Yellow, Blue, The eight basic ANSI colors (Black, Red, Green, Yellow, Blue,
Magenta, Cyan, White). Default: _222222_, _cc9393_, _7f9f7f_, _d0bf8f_, Magenta, Cyan, White). Default: _073642_, _dc322f_, _859900_,
_6ca0a3_, _dc8cc3_, _93e0e3_ and _dcdccc_ (a variant of the _zenburn_ theme). _b58900_, _268bd2_, _d33682_, _2aa198_ and _eee8d5_ (a variant of
the _solarized dark_ theme).
*bright0*, *bright1* *..* *bright7* *bright0*, *bright1* *..* *bright7*
The eight bright ANSI colors (Black, Red, Green, Yellow, Blue, The eight bright ANSI colors (Black, Red, Green, Yellow, Blue,
Magenta, Cyan, White). Default: _666666_, _dca3a3_, _bfebbf_, _f0dfaf_, Magenta, Cyan, White). Default: _08404f_, _e35f5c_, _9fb700_,
_8cd0d3_, _fcace3_, _b3ffff_ and _ffffff_ (a variant of the _zenburn_ theme). _d9a400_, _4ba1de_, _dc619d_, _32c1b6_ and _ffffff_ (a variant of
the _solarized dark_ theme).
*dim0*, *dim1* *..* *dim7* *dim0*, *dim1* *..* *dim7*
Custom colors to use with dimmed colors. Dimmed colors do not have Custom colors to use with dimmed colors. Dimmed colors do not have
@ -566,6 +584,16 @@ can configure the background transparency with the _alpha_ option.
(indicator itself) colors for the scrollback indicator. Default: (indicator itself) colors for the scrollback indicator. Default:
_regular0 bright4_. _regular0 bright4_.
*search-box-no-match*
Two color values specifying the foreground (text) and background
colors for the scrollback search box, when there are no
matches. Default: _regular0 regular1_.
*search-box-match*
Two color values specifying the foreground (text) and background
colors for the scrollback search box, when the search box is
either empty, or there are matches. Default: _regular0 regular3_.
*urls* *urls*
Color to use for the underline used to highlight URLs in URL Color to use for the underline used to highlight URLs in URL
mode. Default: _regular3_. mode. Default: _regular3_.
@ -781,6 +809,32 @@ e.g. *search-start=none*.
Jump the next prompt (requires shell integration, see Jump the next prompt (requires shell integration, see
*foot*(1)). Default: _Control+Shift+x_. *foot*(1)). Default: _Control+Shift+x_.
*unicode-input*
Input a Unicode character by typing its codepoint in hexadecimal,
followed by *Enter* or *Space*.
For example, to input the character _ö_ (LATIN SMALL LETTER O WITH
DIAERESIS, Unicode codepoint 0xf6), you would first activate this
key binding, then type: *f*, *6*, *Enter*.
Another example: to input 😍 (SMILING FACE WITH HEART-SHAPED EYES,
Unicode codepoint 0x1f60d), activate this key binding, then type:
*1*, *f*, *6*, *0*, *d*, *Enter*.
Recognized key bindings in Unicode input mode:
- Enter, Space: commit the Unicode character, then exit this mode.
- Escape, q, Ctrl+c, Ctrl+d, Ctrl+g: abort input, then exit this mode.
- 0-9, a-f: append next digit to the Unicode's codepoint.
- Backspace: undo the last digit.
Note that there is no visual feedback while in this mode. This is
by design; foot's Unicode input mode is considered to be a
fallback. The preferred way of entering Unicode characters, emojis
etc is by using an IME.
Default: _none_.
# SECTION: search-bindings # SECTION: search-bindings
This section lets you override the default key bindings used in This section lets you override the default key bindings used in
@ -863,6 +917,10 @@ scrollback search mode. The syntax is exactly the same as the regular
Paste from the _primary selection_ into the search Paste from the _primary selection_ into the search
buffer. Default: _Shift+Insert_. buffer. Default: _Shift+Insert_.
*unicode-input*
Unicode input mode. See _key-bindings.unicode-input_ for
details. Default: _none_.
# SECTION: url-bindings # SECTION: url-bindings
This section lets you override the default key bindings used in URL This section lets you override the default key bindings used in URL

View file

@ -2,9 +2,16 @@ sh = find_program('sh', native: true)
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true) scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
if utempter_path == ''
default_utempter_value = 'not set'
else
default_utempter_value = utempter_path
endif
conf_data = configuration_data( conf_data = configuration_data(
{ {
'default_terminfo': get_option('default-terminfo'), 'default_terminfo': get_option('default-terminfo'),
'utempter': default_utempter_value,
} }
) )

View file

@ -1,8 +1,8 @@
[Service] [Service]
ExecStart=@bindir@/foot --server=0 ExecStart=@bindir@/foot --server=3
Environment=WAYLAND_DISPLAY=%i Environment=WAYLAND_DISPLAY=%i
UnsetEnvironment=LISTEN_PID LISTEN_FDS LISTEN_FDNAMES
NonBlocking=true NonBlocking=true
StandardInput=socket
[Unit] [Unit]
Requires=%N.socket Requires=%N.socket

View file

@ -218,6 +218,8 @@
knp=\E[6~, knp=\E[6~,
kpp=\E[5~, kpp=\E[5~,
kri=\E[1;2A, kri=\E[1;2A,
kxIN=\E[I,
kxOUT=\E[O,
oc=\E]104\E\\, oc=\E]104\E\\,
op=\E[39;49m, op=\E[39;49m,
rc=\E8, rc=\E8,

View file

@ -17,13 +17,14 @@
# horizontal-letter-offset=0 # horizontal-letter-offset=0
# vertical-letter-offset=0 # vertical-letter-offset=0
# underline-offset=<font metrics> # underline-offset=<font metrics>
# underline-thickness=<font underline thickness>
# box-drawings-uses-font-glyphs=no # box-drawings-uses-font-glyphs=no
# dpi-aware=auto # dpi-aware=auto
# initial-window-size-pixels=700x500 # Or, # initial-window-size-pixels=700x500 # Or,
# initial-window-size-chars=<COLSxROWS> # initial-window-size-chars=<COLSxROWS>
# initial-window-mode=windowed # initial-window-mode=windowed
# pad=2x2 # optionally append 'center' # pad=0x0 # optionally append 'center'
# resize-delay-ms=100 # resize-delay-ms=100
# notify=notify-send -a ${app-id} -i ${app-id} ${title} ${body} # notify=notify-send -a ${app-id} -i ${app-id} ${title} ${body}
@ -32,6 +33,7 @@
# word-delimiters=,│`|:"'()[]{}<> # word-delimiters=,│`|:"'()[]{}<>
# selection-target=primary # selection-target=primary
# workers=<number of logical CPUs> # workers=<number of logical CPUs>
# utempter=/usr/lib/utempter/utempter
[environment] [environment]
# name=value # name=value
@ -68,27 +70,27 @@
[colors] [colors]
# alpha=1.0 # alpha=1.0
# foreground=dcdccc # background=002b36
# background=111111 # foreground=839496
## Normal/regular colors (color palette 0-7) ## Normal/regular colors (color palette 0-7)
# regular0=222222 # black # regular0=073642 # black
# regular1=cc9393 # red # regular1=dc322f # red
# regular2=7f9f7f # green # regular2=859900 # green
# regular3=d0bf8f # yellow # regular3=b58900 # yellow
# regular4=6ca0a3 # blue # regular4=268bd2 # blue
# regular5=dc8cc3 # magenta # regular5=d33682 # magenta
# regular6=93e0e3 # cyan # regular6=2aa198 # cyan
# regular7=dcdccc # white # regular7=eee8d5 # white
## Bright colors (color palette 8-15) ## Bright colors (color palette 8-15)
# bright0=666666 # bright black # bright0=08404f # bright black
# bright1=dca3a3 # bright red # bright1=e35f5c # bright red
# bright2=bfebbf # bright green # bright2=9fb700 # bright green
# bright3=f0dfaf # bright yellow # bright3=d9a400 # bright yellow
# bright4=8cd0d3 # bright blue # bright4=4ba1de # bright blue
# bright5=fcace3 # bright magenta # bright5=dc619d # bright magenta
# bright6=b3ffff # bright cyan # bright6=32c1b6 # bright cyan
# bright7=ffffff # bright white # bright7=ffffff # bright white
## dimmed colors (see foot.ini(5) man page) ## dimmed colors (see foot.ini(5) man page)
@ -104,9 +106,11 @@
## Misc colors ## Misc colors
# selection-foreground=<inverse foreground/background> # selection-foreground=<inverse foreground/background>
# selection-background=<inverse foreground/background> # selection-background=<inverse foreground/background>
# jump-labels=<regular0> <regular3> # jump-labels=<regular0> <regular3> # black-on-yellow
# scrollback-indicator=<regular0> <bright4> # black-on-bright-blue
# search-box-no-match=<regular0> <regular1> # black-on-red
# search-box-match=<regular0> <regular3> # black-on-yellow
# urls=<regular3> # urls=<regular3>
# scrollback-indicator=<regular0> <bright4>
[csd] [csd]
# preferred=server # preferred=server
@ -148,6 +152,7 @@
# show-urls-persistent=none # show-urls-persistent=none
# prompt-prev=Control+Shift+z # prompt-prev=Control+Shift+z
# prompt-next=Control+Shift+x # prompt-next=Control+Shift+x
# unicode-input=none
# noop=none # noop=none
[search-bindings] [search-bindings]
@ -170,6 +175,7 @@
# extend-to-end-line=Control+l # extend-to-end-line=Control+l
# clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste # clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste
# primary-paste=Shift+Insert # primary-paste=Shift+Insert
# unicode-input=none
[url-bindings] [url-bindings]
# cancel=Control+g Control+c Control+d Escape # cancel=Control+g Control+c Control+d Escape

65
grid.c
View file

@ -318,7 +318,7 @@ grid_row_alloc(int cols, bool initialize)
{ {
struct row *row = xmalloc(sizeof(*row)); struct row *row = xmalloc(sizeof(*row));
row->dirty = false; row->dirty = false;
row->linebreak = true; row->linebreak = false;
row->extra = NULL; row->extra = NULL;
row->prompt_marker = false; row->prompt_marker = false;
@ -538,7 +538,7 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row,
} else { } else {
/* Scrollback is full, need to re-use a row */ /* Scrollback is full, need to re-use a row */
grid_row_reset_extra(new_row); grid_row_reset_extra(new_row);
new_row->linebreak = true; new_row->linebreak = false;
new_row->prompt_marker = false; new_row->prompt_marker = false;
tll_foreach(old_grid->sixel_images, it) { tll_foreach(old_grid->sixel_images, it) {
@ -740,7 +740,7 @@ grid_resize_and_reflow(
} }
} }
if (!old_row->linebreak /*&& col_count > 0*/) { if (!old_row->linebreak && col_count > 0) {
/* Dont truncate logical lines */ /* Dont truncate logical lines */
col_count = old_cols; col_count = old_cols;
} }
@ -878,14 +878,6 @@ grid_resize_and_reflow(
&new_row->cells[new_col_idx], &old_row->cells[from], &new_row->cells[new_col_idx], &old_row->cells[from],
amount * sizeof(struct cell)); amount * sizeof(struct cell));
/*
* Weve printed to this line - reset linebreak.
*
* If the old line ends with a hard linebreak, well
* set linebreak=true on the last new row we print to.
*/
new_row->linebreak = false;
count -= amount; count -= amount;
from += amount; from += amount;
new_col_idx += amount; new_col_idx += amount;
@ -943,13 +935,29 @@ grid_resize_and_reflow(
start += cols; start += cols;
} }
if (old_row->linebreak) {
if (old_row->linebreak && col_count > 0) {
/* Erase the remaining cells */ /* Erase the remaining cells */
memset(&new_row->cells[new_col_idx], 0, memset(&new_row->cells[new_col_idx], 0,
(new_cols - new_col_idx) * sizeof(new_row->cells[0])); (new_cols - new_col_idx) * sizeof(new_row->cells[0]));
new_row->linebreak = true; new_row->linebreak = true;
line_wrap();
if (r + 1 < old_rows)
line_wrap();
else if (new_row->extra != NULL &&
new_row->extra->uri_ranges.count > 0)
{
/*
* line_wrap() "closes" still-open URIs. Since this is
* the *last* row, and since were line-breaking due
* to a hard line-break (rather than running out of
* cells in the "new_row"), there shouldnt be an open
* URI (it would have been closed when we reached the
* end of the URI while reflowing the last "old"
* row).
*/
uint32_t last_idx = new_row->extra->uri_ranges.count - 1;
xassert(new_row->extra->uri_ranges.v[last_idx].end >= 0);
}
} }
grid_row_free(old_grid[old_row_idx]); grid_row_free(old_grid[old_row_idx]);
@ -992,6 +1000,7 @@ grid_resize_and_reflow(
/* Set offset such that the last reflowed row is at the bottom */ /* Set offset such that the last reflowed row is at the bottom */
grid->offset = new_row_idx - new_screen_rows + 1; grid->offset = new_row_idx - new_screen_rows + 1;
while (grid->offset < 0) while (grid->offset < 0)
grid->offset += new_rows; grid->offset += new_rows;
while (new_grid[grid->offset] == NULL) while (new_grid[grid->offset] == NULL)
@ -1004,23 +1013,6 @@ grid_resize_and_reflow(
new_grid[idx] = grid_row_alloc(new_cols, true); new_grid[idx] = grid_row_alloc(new_cols, true);
} }
grid->view = view_follows ? grid->offset : viewport.row;
/* If enlarging the window, the old viewport may be too far down,
* with unallocated rows. Make sure this cannot happen */
while (true) {
int idx = (grid->view + new_screen_rows - 1) & (new_rows - 1);
if (new_grid[idx] != NULL)
break;
grid->view--;
if (grid->view < 0)
grid->view += new_rows;
}
for (size_t r = 0; r < new_screen_rows; r++) {
int UNUSED idx = (grid->view + r) & (new_rows - 1);
xassert(new_grid[idx] != NULL);
}
/* Free old grid (rows already free:d) */ /* Free old grid (rows already free:d) */
free(grid->rows); free(grid->rows);
@ -1028,6 +1020,17 @@ grid_resize_and_reflow(
grid->num_rows = new_rows; grid->num_rows = new_rows;
grid->num_cols = new_cols; grid->num_cols = new_cols;
/*
* Set new viewport, making sure its not too far down.
*
* This is done by using scrollback-start relative cooardinates,
* and bounding the new viewport to (grid_rows - screen_rows).
*/
int sb_view = grid_row_abs_to_sb(
grid, new_screen_rows, view_follows ? grid->offset : viewport.row);
grid->view = grid_row_sb_to_abs(
grid, new_screen_rows, min(sb_view, new_rows - new_screen_rows));
/* Convert absolute coordinates to screen relative */ /* Convert absolute coordinates to screen relative */
cursor.row -= grid->offset; cursor.row -= grid->offset;
while (cursor.row < 0) while (cursor.row < 0)

42
input.c
View file

@ -36,6 +36,7 @@
#include "spawn.h" #include "spawn.h"
#include "terminal.h" #include "terminal.h"
#include "tokenize.h" #include "tokenize.h"
#include "unicode-mode.h"
#include "url-mode.h" #include "url-mode.h"
#include "util.h" #include "util.h"
#include "vt.h" #include "vt.h"
@ -282,7 +283,7 @@ execute_binding(struct seat *seat, struct terminal *term,
} }
} }
if (!spawn(term->reaper, NULL, binding->aux->pipe.args, if (!spawn(term->reaper, term->cwd, binding->aux->pipe.args,
pipe_fd[0], stdout_fd, stderr_fd, NULL)) pipe_fd[0], stdout_fd, stderr_fd, NULL))
goto pipe_err; goto pipe_err;
@ -416,6 +417,10 @@ execute_binding(struct seat *seat, struct terminal *term,
return true; return true;
} }
case BIND_ACTION_UNICODE_INPUT:
unicode_mode_activate(seat);
return true;
case BIND_ACTION_SELECT_BEGIN: case BIND_ACTION_SELECT_BEGIN:
selection_start( selection_start(
term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false); term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false);
@ -591,9 +596,6 @@ keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
LOG_DBG("%s: keyboard_enter: keyboard=%p, serial=%u, surface=%p", LOG_DBG("%s: keyboard_enter: keyboard=%p, serial=%u, surface=%p",
seat->name, (void *)wl_keyboard, serial, (void *)surface); seat->name, (void *)wl_keyboard, serial, (void *)surface);
if (seat->kbd.xkb == NULL)
return;
term_kbd_focus_in(term); term_kbd_focus_in(term);
seat->kbd_focus = term; seat->kbd_focus = term;
seat->kbd.serial = serial; seat->kbd.serial = serial;
@ -653,9 +655,6 @@ keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
LOG_DBG("keyboard_leave: keyboard=%p, serial=%u, surface=%p", LOG_DBG("keyboard_leave: keyboard=%p, serial=%u, surface=%p",
(void *)wl_keyboard, serial, (void *)surface); (void *)wl_keyboard, serial, (void *)surface);
if (seat->kbd.xkb == NULL)
return;
xassert( xassert(
seat->kbd_focus == NULL || seat->kbd_focus == NULL ||
surface == NULL || /* Seen on Sway 1.2 */ surface == NULL || /* Seen on Sway 1.2 */
@ -1245,7 +1244,7 @@ emit_escapes:
? ctx->level0_syms.syms[0] ? ctx->level0_syms.syms[0]
: sym; : sym;
if (composed && is_text) if (composed)
key = utf32; key = utf32;
else { else {
key = xkb_keysym_to_utf32(sym_to_use); key = xkb_keysym_to_utf32(sym_to_use);
@ -1407,11 +1406,16 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial,
seat->kbd.xkb_keymap, key, layout_idx, 0, &raw_syms); seat->kbd.xkb_keymap, key, layout_idx, 0, &raw_syms);
const struct key_binding_set *bindings = key_binding_for( const struct key_binding_set *bindings = key_binding_for(
seat->wayl->key_binding_manager, term, seat); seat->wayl->key_binding_manager, term->conf, seat);
xassert(bindings != NULL); xassert(bindings != NULL);
if (pressed) { if (pressed) {
if (term->is_searching) { if (seat->unicode_mode.active) {
unicode_mode_input(seat, term, sym);
return;
}
else if (term->is_searching) {
if (should_repeat) if (should_repeat)
start_repeater(seat, key); start_repeater(seat, key);
@ -1705,11 +1709,9 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, struct wl_surface *surface, uint32_t serial, struct wl_surface *surface,
wl_fixed_t surface_x, wl_fixed_t surface_y) wl_fixed_t surface_x, wl_fixed_t surface_y)
{ {
xassert(surface != NULL); if (unlikely(surface == NULL)) {
xassert(serial != 0);
if (surface == NULL) {
/* Seen on mutter-3.38 */ /* Seen on mutter-3.38 */
LOG_WARN("compositor sent pointer_enter event with a NULL surface");
return; return;
} }
@ -1862,6 +1864,16 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
struct seat *seat = data; struct seat *seat = data;
struct wayland *wayl = seat->wayl; struct wayland *wayl = seat->wayl;
struct terminal *term = seat->mouse_focus; struct terminal *term = seat->mouse_focus;
if (unlikely(term == NULL)) {
/* Typically happens when the compositor sent a pointer enter
* event with a NULL surface - see wl_pointer_enter().
*
* In this case, we never set seat->mouse_focus (since we
* cant map the enter event to a specific window). */
return;
}
struct wl_window *win = term->window; struct wl_window *win = term->window;
LOG_DBG("pointer_motion: pointer=%p, x=%d, y=%d", (void *)wl_pointer, LOG_DBG("pointer_motion: pointer=%p, x=%d, y=%d", (void *)wl_pointer,
@ -2323,7 +2335,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
/* Seat has keyboard - use mouse bindings *with* modifiers */ /* Seat has keyboard - use mouse bindings *with* modifiers */
const struct key_binding_set *bindings = key_binding_for( const struct key_binding_set *bindings = key_binding_for(
wayl->key_binding_manager, term, seat); wayl->key_binding_manager, term->conf, seat);
xassert(bindings != NULL); xassert(bindings != NULL);
xkb_mod_mask_t mods; xkb_mod_mask_t mods;

View file

@ -80,17 +80,14 @@ key_binding_new_for_seat(struct key_binding_manager *mgr,
} }
void void
key_binding_new_for_term(struct key_binding_manager *mgr, key_binding_new_for_conf(struct key_binding_manager *mgr,
const struct terminal *term) const struct wayland *wayl, const struct config *conf)
{ {
const struct config *conf = term->conf;
const struct wayland *wayl = term->wl;
tll_foreach(wayl->seats, it) { tll_foreach(wayl->seats, it) {
struct seat *seat = &it->item; struct seat *seat = &it->item;
struct key_set *existing = struct key_set *existing =
(struct key_set *)key_binding_for(mgr, term, seat); (struct key_set *)key_binding_for(mgr, conf, seat);
if (existing != NULL) { if (existing != NULL) {
existing->conf_ref_count++; existing->conf_ref_count++;
@ -116,21 +113,19 @@ key_binding_new_for_term(struct key_binding_manager *mgr,
/* Chances are high this set will be requested next */ /* Chances are high this set will be requested next */
mgr->last_used_set = &tll_back(mgr->binding_sets); mgr->last_used_set = &tll_back(mgr->binding_sets);
LOG_DBG("new (term): set=%p, seat=%p, conf=%p, ref-count=1", LOG_DBG("new (conf): set=%p, seat=%p, conf=%p, ref-count=1",
(void *)&tll_back(mgr->binding_sets), (void *)&tll_back(mgr->binding_sets),
(void *)set.seat, (void *)set.conf); (void *)set.seat, (void *)set.conf);
} }
LOG_DBG("new (term): total number of sets: %zu", LOG_DBG("new (conf): total number of sets: %zu",
tll_length(mgr->binding_sets)); tll_length(mgr->binding_sets));
} }
struct key_binding_set * NOINLINE struct key_binding_set * NOINLINE
key_binding_for(struct key_binding_manager *mgr, const struct terminal *term, key_binding_for(struct key_binding_manager *mgr, const struct config *conf,
const struct seat *seat) const struct seat *seat)
{ {
const struct config *conf = term->conf;
struct key_set *last_used = mgr->last_used_set; struct key_set *last_used = mgr->last_used_set;
if (last_used != NULL && if (last_used != NULL &&
last_used->conf == conf && last_used->conf == conf &&
@ -192,11 +187,8 @@ key_binding_remove_seat(struct key_binding_manager *mgr,
} }
void void
key_binding_unref_term(struct key_binding_manager *mgr, key_binding_unref(struct key_binding_manager *mgr, const struct config *conf)
const struct terminal *term)
{ {
const struct config *conf = term->conf;
tll_foreach(mgr->binding_sets, it) { tll_foreach(mgr->binding_sets, it) {
struct key_set *set = &it->item; struct key_set *set = &it->item;

View file

@ -38,6 +38,7 @@ enum bind_action_normal {
BIND_ACTION_TEXT_BINDING, BIND_ACTION_TEXT_BINDING,
BIND_ACTION_PROMPT_PREV, BIND_ACTION_PROMPT_PREV,
BIND_ACTION_PROMPT_NEXT, BIND_ACTION_PROMPT_NEXT,
BIND_ACTION_UNICODE_INPUT,
/* Mouse specific actions - i.e. they require a mouse coordinate */ /* Mouse specific actions - i.e. they require a mouse coordinate */
BIND_ACTION_SELECT_BEGIN, BIND_ACTION_SELECT_BEGIN,
@ -48,7 +49,7 @@ enum bind_action_normal {
BIND_ACTION_SELECT_WORD_WS, BIND_ACTION_SELECT_WORD_WS,
BIND_ACTION_SELECT_ROW, BIND_ACTION_SELECT_ROW,
BIND_ACTION_KEY_COUNT = BIND_ACTION_PROMPT_NEXT + 1, BIND_ACTION_KEY_COUNT = BIND_ACTION_UNICODE_INPUT + 1,
BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1, BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1,
}; };
@ -73,6 +74,7 @@ enum bind_action_search {
BIND_ACTION_SEARCH_EXTEND_LINE, BIND_ACTION_SEARCH_EXTEND_LINE,
BIND_ACTION_SEARCH_CLIPBOARD_PASTE, BIND_ACTION_SEARCH_CLIPBOARD_PASTE,
BIND_ACTION_SEARCH_PRIMARY_PASTE, BIND_ACTION_SEARCH_PRIMARY_PASTE,
BIND_ACTION_SEARCH_UNICODE_INPUT,
BIND_ACTION_SEARCH_COUNT, BIND_ACTION_SEARCH_COUNT,
}; };
@ -109,6 +111,7 @@ typedef tll(struct key_binding) key_binding_list_t;
struct terminal; struct terminal;
struct seat; struct seat;
struct wayland;
struct key_binding_set { struct key_binding_set {
key_binding_list_t key; key_binding_list_t key;
@ -126,20 +129,21 @@ void key_binding_manager_destroy(struct key_binding_manager *mgr);
void key_binding_new_for_seat( void key_binding_new_for_seat(
struct key_binding_manager *mgr, const struct seat *seat); struct key_binding_manager *mgr, const struct seat *seat);
void key_binding_new_for_term( void key_binding_new_for_conf(
struct key_binding_manager *mgr, const struct terminal *term); struct key_binding_manager *mgr, const struct wayland *wayl,
const struct config *conf);
/* Returns the set of key bindings associated with this seat/term pair */ /* Returns the set of key bindings associated with this seat/conf pair */
struct key_binding_set *key_binding_for( struct key_binding_set *key_binding_for(
struct key_binding_manager *mgr, const struct terminal *term, struct key_binding_manager *mgr, const struct config *conf,
const struct seat *seat); const struct seat *seat);
/* Remove all key bindings tied to the specified seat */ /* Remove all key bindings tied to the specified seat */
void key_binding_remove_seat( void key_binding_remove_seat(
struct key_binding_manager *mgr, const struct seat *seat); struct key_binding_manager *mgr, const struct seat *seat);
void key_binding_unref_term( void key_binding_unref(
struct key_binding_manager *mgr, const struct terminal *term); struct key_binding_manager *mgr, const struct config *conf);
void key_binding_load_keymap( void key_binding_load_keymap(
struct key_binding_manager *mgr, const struct seat *seat); struct key_binding_manager *mgr, const struct seat *seat);

View file

@ -24,7 +24,7 @@ struct key_data {
static const struct key_data key_escape[] = { static const struct key_data key_escape[] = {
{MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;2;27~"}, {MOD_SHIFT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;2;27~"},
{MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;3;27~"}, {MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033\033"},
{MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;4;27~"}, {MOD_SHIFT | MOD_ALT, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;4;27~"},
{MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;5;27~"}, {MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;5;27~"},
{MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;6;27~"}, {MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;6;27~"},

22
main.c
View file

@ -594,6 +594,28 @@ main(int argc, char *const *argv)
cwd = _cwd; cwd = _cwd;
} }
const char *pwd = getenv("PWD");
if (pwd != NULL) {
char *resolved_path_cwd = realpath(cwd, NULL);
char *resolved_path_pwd = realpath(pwd, NULL);
if (resolved_path_cwd != NULL &&
resolved_path_pwd != NULL &&
strcmp(resolved_path_cwd, resolved_path_pwd) == 0)
{
/*
* The resolved path of $PWD matches the resolved path of
* the *actual* working directory - use $PWD.
*
* This makes a difference when $PWD refers to a symlink.
*/
cwd = pwd;
}
free(resolved_path_cwd);
free(resolved_path_pwd);
}
shm_set_max_pool_size(conf.tweak.max_shm_pool_size); shm_set_max_pool_size(conf.tweak.max_shm_pool_size);
if ((fdm = fdm_init()) == NULL) if ((fdm = fdm_init()) == NULL)

View file

@ -1,5 +1,5 @@
project('foot', 'c', project('foot', 'c',
version: '1.12.1', version: '1.13.1',
license: 'MIT', license: 'MIT',
meson_version: '>=0.58.0', meson_version: '>=0.58.0',
default_options: [ default_options: [
@ -16,9 +16,30 @@ if cc.has_function('memfd_create')
add_project_arguments('-DMEMFD_CREATE', language: 'c') add_project_arguments('-DMEMFD_CREATE', language: 'c')
endif 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()
else
utempter_path = ''
endif
elif utempter_path == 'none'
utempter_path = ''
endif
add_project_arguments( add_project_arguments(
['-D_GNU_SOURCE=200809L', ['-D_GNU_SOURCE=200809L',
'-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo'))] + '-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo')),
'-DFOOT_DEFAULT_UTEMPTER_PATH="@0@"'.format(utempter_path)] +
(is_debug_build (is_debug_build
? ['-D_DEBUG'] ? ['-D_DEBUG']
: [cc.get_supported_arguments('-fno-asynchronous-unwind-tables')]) + : [cc.get_supported_arguments('-fno-asynchronous-unwind-tables')]) +
@ -222,6 +243,7 @@ executable(
'slave.c', 'slave.h', 'slave.c', 'slave.h',
'spawn.c', 'spawn.h', 'spawn.c', 'spawn.h',
'tokenize.c', 'tokenize.h', 'tokenize.c', 'tokenize.h',
'unicode-mode.c', 'unicode-mode.h',
'url-mode.c', 'url-mode.h', 'url-mode.c', 'url-mode.h',
'user-notification.c', 'user-notification.h', 'user-notification.c', 'user-notification.h',
'wayland.c', 'wayland.h', 'shm-formats.h', 'wayland.c', 'wayland.h', 'shm-formats.h',
@ -243,7 +265,7 @@ executable(
install: true) install: true)
install_data( install_data(
'foot.desktop', 'foot-server.desktop', 'footclient.desktop', 'org.codeberg.dnkl.foot.desktop', 'org.codeberg.dnkl.foot-server.desktop', 'org.codeberg.dnkl.footclient.desktop',
install_dir: join_paths(get_option('datadir'), 'applications')) install_dir: join_paths(get_option('datadir'), 'applications'))
systemd = dependency('systemd', required: false) systemd = dependency('systemd', required: false)
@ -320,6 +342,7 @@ summary(
'Themes': get_option('themes'), 'Themes': get_option('themes'),
'IME': get_option('ime'), 'IME': get_option('ime'),
'Grapheme clustering': utf8proc.found(), 'Grapheme clustering': utf8proc.found(),
'Utempter path': utempter_path,
'Build terminfo': tic.found(), 'Build terminfo': tic.found(),
'Terminfo install location': terminfo_install_location, 'Terminfo install location': terminfo_install_location,
'Default TERM': get_option('default-terminfo'), 'Default TERM': get_option('default-terminfo'),

View file

@ -21,3 +21,6 @@ option('custom-terminfo-install-location', type: 'string', value: '',
option('systemd-units-dir', type: 'string', value: '', option('systemd-units-dir', type: 'string', value: '',
description: 'Where to install the systemd service files (absolute path). Default: ${systemduserunitdir}') 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')

19
misc.c
View file

@ -13,15 +13,32 @@ isword(char32_t wc, bool spaces_only, const char32_t *delimiters)
return isc32graph(wc); return isc32graph(wc);
} }
void
timespec_add(const struct timespec *a, const struct timespec *b,
struct timespec *res)
{
const long one_sec_in_ns = 1000000000;
res->tv_sec = a->tv_sec + b->tv_sec;
res->tv_nsec = a->tv_nsec + b->tv_nsec;
/* tv_nsec may be negative */
if (res->tv_nsec >= one_sec_in_ns) {
res->tv_sec++;
res->tv_nsec -= one_sec_in_ns;
}
}
void void
timespec_sub(const struct timespec *a, const struct timespec *b, timespec_sub(const struct timespec *a, const struct timespec *b,
struct timespec *res) struct timespec *res)
{ {
const long one_sec_in_ns = 1000000000;
res->tv_sec = a->tv_sec - b->tv_sec; res->tv_sec = a->tv_sec - b->tv_sec;
res->tv_nsec = a->tv_nsec - b->tv_nsec; res->tv_nsec = a->tv_nsec - b->tv_nsec;
/* tv_nsec may be negative */ /* tv_nsec may be negative */
if (res->tv_nsec < 0) { if (res->tv_nsec < 0) {
res->tv_sec--; res->tv_sec--;
res->tv_nsec += 1000 * 1000 * 1000; res->tv_nsec += one_sec_in_ns;
} }
} }

1
misc.h
View file

@ -6,4 +6,5 @@
bool isword(char32_t wc, bool spaces_only, const char32_t *delimiters); bool isword(char32_t wc, bool spaces_only, const char32_t *delimiters);
void timespec_add(const struct timespec *a, const struct timespec *b, struct timespec *res);
void timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *res); void timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *res);

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>org.codeberg.dnkl.foot</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT</project_license>
<developer_name>dnkl</developer_name>
<name>foot</name>
<summary>The fast, lightweight and minimalistic Wayland terminal emulator.</summary>
<description>
<ul>
<li>Fast</li>
<li>Lightweight, in dependencies, on-disk and in-memory</li>
<li>Wayland native</li>
<li>DE agnostic</li>
<li>Server/daemon mode</li>
<li>User configurable font fallback</li>
<li>On-the-fly font resize</li>
<li>On-the-fly DPI font size adjustment</li>
<li>Scrollback search</li>
<li>Keyboard driven URL detection</li>
<li>Color emoji support</li>
<li>IME (via text-input-v3)</li>
<li>Multi-seat</li>
<li>True Color (24bpp)</li>
<li>Synchronized Updates support</li>
<li>Sixel image support</li>
</ul>
</description>
<screenshots>
<screenshot type="default">
<caption>Foot with sixel graphics</caption>
<image>https://codeberg.org/dnkl/foot/media/branch/master/doc/sixel-wow.png</image>
</screenshot>
</screenshots>
<releases>
<release version="1.13.1" date="2022-08-31">
</release>
<release version="1.13.0" date="2022-08-07">
</release>
</releases>
<launchable type="desktop-id">org.codeberg.dnkl.foot.desktop</launchable>
<url type="homepage">https://codeberg.org/dnkl/foot</url>
<url type="bugtracker">https://codeberg.org/dnkl/foot/issues</url>
<content_rating type="oars-1.1"/>
</component>

View file

@ -178,15 +178,16 @@ static bool kbd_initialized = false;
struct key_binding_set * struct key_binding_set *
key_binding_for( key_binding_for(
struct key_binding_manager *mgr, const struct terminal *term, struct key_binding_manager *mgr, const struct config *conf,
const struct seat *seat) const struct seat *seat)
{ {
return &kbd; return &kbd;
} }
void void
key_binding_new_for_term( key_binding_new_for_conf(
struct key_binding_manager *mgr, const struct terminal *term) struct key_binding_manager *mgr, const struct wayland *wayl,
const struct config *conf)
{ {
if (!kbd_initialized) { if (!kbd_initialized) {
kbd_initialized = true; kbd_initialized = true;
@ -201,7 +202,7 @@ key_binding_new_for_term(
} }
void void
key_binding_unref_term(struct key_binding_manager *mgr, const struct terminal *term) key_binding_unref(struct key_binding_manager *mgr, const struct config *conf)
{ {
} }

252
render.c
View file

@ -372,7 +372,10 @@ draw_underline(const struct terminal *term, pixman_image_t *pix,
const struct fcft_font *font, const struct fcft_font *font,
const pixman_color_t *color, int x, int y, int cols) const pixman_color_t *color, int x, int y, int cols)
{ {
const int thickness = font->underline.thickness; const int thickness = term->conf->underline_thickness.px >= 0
? term_pt_or_px_as_pixels(
term, &term->conf->underline_thickness)
: font->underline.thickness;
/* Make sure the line isn't positioned below the cell */ /* Make sure the line isn't positioned below the cell */
const int y_ofs = min(underline_offset(term, font), const int y_ofs = min(underline_offset(term, font),
@ -699,7 +702,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
mtx_unlock(&term->render.workers.lock); mtx_unlock(&term->render.workers.lock);
} }
if (has_cursor && term->cursor_style == CURSOR_BLOCK && term->kbd_focus) if (unlikely(has_cursor && term->cursor_style == CURSOR_BLOCK && term->kbd_focus))
draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols); draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols);
if (cell->wc == 0 || cell->wc >= CELL_SPACER || cell->wc == U'\t' || if (cell->wc == 0 || cell->wc >= CELL_SPACER || cell->wc == U'\t' ||
@ -1011,6 +1014,13 @@ grid_render_scroll(struct terminal *term, struct buffer *buf,
wl_surface_damage_buffer( wl_surface_damage_buffer(
term->window->surface, term->margins.left, dst_y, term->window->surface, term->margins.left, dst_y,
term->width - term->margins.left - term->margins.right, height); term->width - term->margins.left - term->margins.right, height);
/*
* TODO: remove this if re-enabling scroll damage when re-applying
* last frames damage (see reapply_old_damage()
*/
pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, dst_y, buf->width, height);
} }
static void static void
@ -1076,6 +1086,13 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf,
wl_surface_damage_buffer( wl_surface_damage_buffer(
term->window->surface, term->margins.left, dst_y, term->window->surface, term->margins.left, dst_y,
term->width - term->margins.left - term->margins.right, height); term->width - term->margins.left - term->margins.right, height);
/*
* TODO: remove this if re-enabling scroll damage when re-applying
* last frames damage (see reapply_old_damage()
*/
pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, dst_y, buf->width, height);
} }
static void static void
@ -1466,10 +1483,21 @@ static void
render_overlay(struct terminal *term) render_overlay(struct terminal *term)
{ {
struct wl_surf_subsurf *overlay = &term->window->overlay; struct wl_surf_subsurf *overlay = &term->window->overlay;
bool unicode_mode_active = false;
/* Check if unicode mode is active on at least one seat focusing
* this terminal instance */
tll_foreach(term->wl->seats, it) {
if (it->item.unicode_mode.active) {
unicode_mode_active = true;
break;
}
}
const enum overlay_style style = const enum overlay_style style =
term->is_searching ? OVERLAY_SEARCH : term->is_searching ? OVERLAY_SEARCH :
term->flash.active ? OVERLAY_FLASH : term->flash.active ? OVERLAY_FLASH :
unicode_mode_active ? OVERLAY_UNICODE_MODE :
OVERLAY_NONE; OVERLAY_NONE;
if (likely(style == OVERLAY_NONE)) { if (likely(style == OVERLAY_NONE)) {
@ -1488,9 +1516,21 @@ render_overlay(struct terminal *term)
pixman_image_set_clip_region32(buf->pix[0], NULL); pixman_image_set_clip_region32(buf->pix[0], NULL);
pixman_color_t color = style == OVERLAY_SEARCH pixman_color_t color;
? (pixman_color_t){0, 0, 0, 0x7fff}
: (pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff}; switch (style) {
case OVERLAY_NONE:
break;
case OVERLAY_SEARCH:
case OVERLAY_UNICODE_MODE:
color = (pixman_color_t){0, 0, 0, 0x7fff};
break;
case OVERLAY_FLASH:
color = (pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff};
break;
}
/* Bounding rectangle of damaged areas - for wl_surface_damage_buffer() */ /* Bounding rectangle of damaged areas - for wl_surface_damage_buffer() */
pixman_box32_t damage_bounds; pixman_box32_t damage_bounds;
@ -1517,17 +1557,18 @@ render_overlay(struct terminal *term)
* region that needs to be *cleared* in this frame. * region that needs to be *cleared* in this frame.
* *
* Finally, the union of the two diff regions above, gives * Finally, the union of the two diff regions above, gives
* us the total region affecte by a change, in either way. We * us the total region affected by a change, in either way. We
* use this as the bounding box for the * use this as the bounding box for the
* wl_surface_damage_buffer() call. * wl_surface_damage_buffer() call.
*/ */
pixman_region32_t *see_through = &term->render.last_overlay_clip; pixman_region32_t *see_through = &term->render.last_overlay_clip;
pixman_region32_t old_see_through; pixman_region32_t old_see_through;
const bool buffer_reuse =
buf == term->render.last_overlay_buf &&
style == term->render.last_overlay_style &&
buf->age == 0;
if (!(buf == term->render.last_overlay_buf && if (!buffer_reuse) {
style == term->render.last_overlay_style &&
buf->age == 0))
{
/* Cant re-use last frames damage - set to full window, /* Cant re-use last frames damage - set to full window,
* to ensure *everything* is updated */ * to ensure *everything* is updated */
pixman_region32_init_rect( pixman_region32_init_rect(
@ -1540,8 +1581,8 @@ render_overlay(struct terminal *term)
pixman_region32_clear(see_through); pixman_region32_clear(see_through);
/* Build region consisting of all current search matches */
struct search_match_iterator iter = search_matches_new_iter(term); struct search_match_iterator iter = search_matches_new_iter(term);
for (struct range match = search_matches_next(&iter); for (struct range match = search_matches_next(&iter);
match.start.row >= 0; match.start.row >= 0;
match = search_matches_next(&iter)) match = search_matches_next(&iter))
@ -1569,20 +1610,28 @@ render_overlay(struct terminal *term)
} }
} }
/* Current see-through, minus old see-through - aka cells that /* Areas that need to be cleared: cells that were dimmed in
* need to be cleared */ * the last frame but is now see-through */
pixman_region32_t new_see_through; pixman_region32_t new_see_through;
pixman_region32_init(&new_see_through); pixman_region32_init(&new_see_through);
pixman_region32_subtract(&new_see_through, see_through, &old_see_through);
if (buffer_reuse)
pixman_region32_subtract(&new_see_through, see_through, &old_see_through);
else {
/* Buffer content is unknown - explicitly clear *all*
* current see-through areas */
pixman_region32_copy(&new_see_through, see_through);
}
pixman_image_set_clip_region32(buf->pix[0], &new_see_through); pixman_image_set_clip_region32(buf->pix[0], &new_see_through);
/* Old see-through, minus new see-through - aka cells that /* Areas that need to be dimmed: cells that were cleared in
* needs to be dimmed */ * the last frame but is not anymore */
pixman_region32_t new_dimmed; pixman_region32_t new_dimmed;
pixman_region32_init(&new_dimmed); pixman_region32_init(&new_dimmed);
pixman_region32_subtract(&new_dimmed, &old_see_through, see_through); pixman_region32_subtract(&new_dimmed, &old_see_through, see_through);
pixman_region32_fini(&old_see_through); pixman_region32_fini(&old_see_through);
/* Total affected area */
pixman_region32_t damage; pixman_region32_t damage;
pixman_region32_init(&damage); pixman_region32_init(&damage);
pixman_region32_union(&damage, &new_see_through, &new_dimmed); pixman_region32_union(&damage, &new_see_through, &new_dimmed);
@ -1605,7 +1654,7 @@ render_overlay(struct terminal *term)
else if (buf == term->render.last_overlay_buf && else if (buf == term->render.last_overlay_buf &&
style == term->render.last_overlay_style) style == term->render.last_overlay_style)
{ {
xassert(style == OVERLAY_FLASH); xassert(style == OVERLAY_FLASH || style == OVERLAY_UNICODE_MODE);
shm_did_not_use_buf(buf); shm_did_not_use_buf(buf);
return; return;
} else { } else {
@ -1726,10 +1775,12 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx)
const int button_close_width = term->width >= 1 * button_width const int button_close_width = term->width >= 1 * button_width
? button_width : 0; ? button_width : 0;
const int button_maximize_width = term->width >= 2 * button_width const int button_maximize_width =
term->width >= 2 * button_width && term->window->wm_capabilities.maximize
? button_width : 0; ? button_width : 0;
const int button_minimize_width = term->width >= 3 * button_width const int button_minimize_width =
term->width >= 3 * button_width && term->window->wm_capabilities.minimize
? button_width : 0; ? button_width : 0;
switch (surf_idx) { switch (surf_idx) {
@ -2510,22 +2561,27 @@ reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old
return; return;
} }
/*
* TODO: remove this frames damage from the region we copy from
* the old frame.
*
* - this frames dirty region is only valid *after* weve applied
* its scroll damage.
* - last frames dirty region is only valid *before* weve
* applied this frames scroll damage.
*
* Can we transform one of the regions? Its not trivial, since
* scroll damage isnt just about counting lines; there may be
* multiple damage records, each with different scrolling regions.
*/
pixman_region32_t dirty; pixman_region32_t dirty;
pixman_region32_init(&dirty); pixman_region32_init(&dirty);
/*
* Figure out current frames damage region
*
* If current frame doesnt have any scroll damage, we can simply
* subtract this frames damage from the last frames damage. That
* way, we dont have to copy areas from the old frame thatll
* just get overwritten by current frame.
*
* Note that this is row based. A half damaged row is not
* excluded. I.e. the entire row will be copied from the old frame
* to the new, and then when actually rendering the new frame, the
* updated cells will overwrite parts of the copied row.
*
* Since were scanning the entire viewport anyway, we also track
* whether *all* cells are to be updated. In this case, just force
* a full re-rendering, and dont copy anything from the old
* frame.
*/
bool full_repaint_needed = true; bool full_repaint_needed = true;
for (int r = 0; r < term->rows; r++) { for (int r = 0; r < term->rows; r++) {
@ -2555,35 +2611,31 @@ reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old
return; return;
} }
for (size_t i = 0; i < old->scroll_damage_count; i++) { /*
const struct damage *dmg = &old->scroll_damage[i]; * TODO: re-apply last frames scroll damage
*
switch (dmg->type) { * We used to do this, but it turned out to be buggy. If we decide
case DAMAGE_SCROLL: * to re-add it, this is where to do it. Note that wed also have
if (term->grid->view == term->grid->offset) * to remove the updates to buf->dirty from grid_render_scroll()
grid_render_scroll(term, new, dmg); * and grid_render_scroll_reverse().
break; */
case DAMAGE_SCROLL_REVERSE:
if (term->grid->view == term->grid->offset)
grid_render_scroll_reverse(term, new, dmg);
break;
case DAMAGE_SCROLL_IN_VIEW:
grid_render_scroll(term, new, dmg);
break;
case DAMAGE_SCROLL_REVERSE_IN_VIEW:
grid_render_scroll_reverse(term, new, dmg);
break;
}
}
if (tll_length(term->grid->scroll_damage) == 0) { if (tll_length(term->grid->scroll_damage) == 0) {
/*
* We can only subtract current frames damage from the old
* frames if we dont have any scroll damage.
*
* If we do have scroll damage, the damage region we
* calculated above is not (yet) valid - we need to apply the
* current frames scroll damage *first*. This is done later,
* when rendering the frame.
*/
pixman_region32_subtract(&dirty, &old->dirty, &dirty); pixman_region32_subtract(&dirty, &old->dirty, &dirty);
pixman_image_set_clip_region32(new->pix[0], &dirty); pixman_image_set_clip_region32(new->pix[0], &dirty);
} else } else {
/* Copy *all* of last frames damaged areas */
pixman_image_set_clip_region32(new->pix[0], &old->dirty); pixman_image_set_clip_region32(new->pix[0], &old->dirty);
}
pixman_image_composite32( pixman_image_composite32(
PIXMAN_OP_SRC, old->pix[0], NULL, new->pix[0], PIXMAN_OP_SRC, old->pix[0], NULL, new->pix[0],
@ -2674,38 +2726,29 @@ grid_render(struct terminal *term)
shm_addref(buf); shm_addref(buf);
buf->age = 0; buf->age = 0;
free(term->render.last_buf->scroll_damage);
buf->scroll_damage_count = tll_length(term->grid->scroll_damage);
buf->scroll_damage = xmalloc(
buf->scroll_damage_count * sizeof(buf->scroll_damage[0]));
{ tll_foreach(term->grid->scroll_damage, it) {
size_t i = 0; switch (it->item.type) {
tll_foreach(term->grid->scroll_damage, it) { case DAMAGE_SCROLL:
buf->scroll_damage[i++] = it->item; if (term->grid->view == term->grid->offset)
switch (it->item.type) {
case DAMAGE_SCROLL:
if (term->grid->view == term->grid->offset)
grid_render_scroll(term, buf, &it->item);
break;
case DAMAGE_SCROLL_REVERSE:
if (term->grid->view == term->grid->offset)
grid_render_scroll_reverse(term, buf, &it->item);
break;
case DAMAGE_SCROLL_IN_VIEW:
grid_render_scroll(term, buf, &it->item); grid_render_scroll(term, buf, &it->item);
break; break;
case DAMAGE_SCROLL_REVERSE_IN_VIEW: case DAMAGE_SCROLL_REVERSE:
if (term->grid->view == term->grid->offset)
grid_render_scroll_reverse(term, buf, &it->item); grid_render_scroll_reverse(term, buf, &it->item);
break; break;
}
tll_remove(term->grid->scroll_damage, it); case DAMAGE_SCROLL_IN_VIEW:
grid_render_scroll(term, buf, &it->item);
break;
case DAMAGE_SCROLL_REVERSE_IN_VIEW:
grid_render_scroll_reverse(term, buf, &it->item);
break;
} }
tll_remove(term->grid->scroll_damage, it);
} }
/* /*
@ -2885,15 +2928,21 @@ grid_render(struct terminal *term)
struct timespec double_buffering_time; struct timespec double_buffering_time;
timespec_sub(&stop_double_buffering, &start_double_buffering, &double_buffering_time); timespec_sub(&stop_double_buffering, &start_double_buffering, &double_buffering_time);
struct timespec total_render_time;
timespec_add(&render_time, &double_buffering_time, &total_render_time);
switch (term->conf->tweak.render_timer) { switch (term->conf->tweak.render_timer) {
case RENDER_TIMER_LOG: case RENDER_TIMER_LOG:
case RENDER_TIMER_BOTH: case RENDER_TIMER_BOTH:
LOG_INFO("frame rendered in %lds %ldns " LOG_INFO(
"(%lds %ldns double buffering)", "frame rendered in %lds %9ldns "
(long)render_time.tv_sec, "(%lds %9ldns rendering, %lds %9ldns double buffering)",
render_time.tv_nsec, (long)total_render_time.tv_sec,
(long)double_buffering_time.tv_sec, total_render_time.tv_nsec,
double_buffering_time.tv_nsec); (long)render_time.tv_sec,
render_time.tv_nsec,
(long)double_buffering_time.tv_sec,
double_buffering_time.tv_nsec);
break; break;
case RENDER_TIMER_OSD: case RENDER_TIMER_OSD:
@ -2904,7 +2953,7 @@ grid_render(struct terminal *term)
switch (term->conf->tweak.render_timer) { switch (term->conf->tweak.render_timer) {
case RENDER_TIMER_OSD: case RENDER_TIMER_OSD:
case RENDER_TIMER_BOTH: case RENDER_TIMER_BOTH:
render_render_timer(term, render_time); render_render_timer(term, total_render_time);
break; break;
case RENDER_TIMER_LOG: case RENDER_TIMER_LOG:
@ -3046,10 +3095,20 @@ render_search_box(struct terminal *term)
#define WINDOW_X(x) (margin + x) #define WINDOW_X(x) (margin + x)
#define WINDOW_Y(y) (term->height - margin - height + y) #define WINDOW_Y(y) (term->height - margin - height + y)
/* Background - yellow on empty/match, red on mismatch */ const bool is_match = term->search.match_len == text_len;
pixman_color_t color = color_hex_to_pixman( const bool custom_colors = is_match
term->search.match_len == text_len ? term->conf->colors.use_custom.search_box_match
? term->colors.table[3] : term->colors.table[1]); : term->conf->colors.use_custom.search_box_no_match;
/* Background - yellow on empty/match, red on mismatch (default) */
const pixman_color_t color = color_hex_to_pixman(
is_match
? (custom_colors
? term->conf->colors.search_box.match.bg
: term->colors.table[3])
: (custom_colors
? term->conf->colors.search_box.no_match.bg
: term->colors.table[1]));
pixman_image_fill_rectangles( pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix[0], &color, PIXMAN_OP_SRC, buf->pix[0], &color,
@ -3065,7 +3124,12 @@ render_search_box(struct terminal *term)
const int x_ofs = term->font_x_ofs; const int x_ofs = term->font_x_ofs;
int x = x_left; int x = x_left;
int y = margin; int y = margin;
pixman_color_t fg = color_hex_to_pixman(term->colors.table[0]); pixman_color_t fg = color_hex_to_pixman(
custom_colors
? (is_match
? term->conf->colors.search_box.match.fg
: term->conf->colors.search_box.no_match.fg)
: term->colors.table[0]);
/* Move offset we start rendering at, to ensure the cursor is visible */ /* Move offset we start rendering at, to ensure the cursor is visible */
for (size_t i = 0, cell_idx = 0; i <= term->search.cursor; cell_idx += widths[i], i++) { for (size_t i = 0, cell_idx = 0; i <= term->search.cursor; cell_idx += widths[i], i++) {

View file

@ -166,6 +166,7 @@ def main():
entry.add_capability(IntCapability('Co', 256)) entry.add_capability(IntCapability('Co', 256))
entry.add_capability(StringCapability('TN', target_entry_name)) entry.add_capability(StringCapability('TN', target_entry_name))
entry.add_capability(StringCapability('name', target_entry_name))
entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel
terminfo_parts = [] terminfo_parts = []

View file

@ -18,6 +18,7 @@
#include "render.h" #include "render.h"
#include "selection.h" #include "selection.h"
#include "shm.h" #include "shm.h"
#include "unicode-mode.h"
#include "util.h" #include "util.h"
#include "xmalloc.h" #include "xmalloc.h"
@ -371,6 +372,9 @@ find_next(struct terminal *term, enum search_direction direction,
i += additional_chars; i += additional_chars;
match_len += additional_chars; match_len += additional_chars;
match_end_col++; match_end_col++;
while (match_row->cells[match_end_col].wc > CELL_SPACER)
match_end_col++;
} }
if (match_len != term->search.len) { if (match_len != term->search.len) {
@ -1000,6 +1004,10 @@ execute_binding(struct seat *seat, struct terminal *term,
*update_search_result = *redraw = true; *update_search_result = *redraw = true;
return true; return true;
case BIND_ACTION_SEARCH_UNICODE_INPUT:
unicode_mode_activate(seat);
return true;
case BIND_ACTION_SEARCH_COUNT: case BIND_ACTION_SEARCH_COUNT:
BUG("Invalid action type"); BUG("Invalid action type");
return true; return true;

View file

@ -9,6 +9,8 @@
#include <sys/epoll.h> #include <sys/epoll.h>
#include <sys/timerfd.h> #include <sys/timerfd.h>
#include <pixman.h>
#define LOG_MODULE "selection" #define LOG_MODULE "selection"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
#include "log.h" #include "log.h"
@ -64,41 +66,85 @@ selection_get_end(const struct terminal *term)
bool bool
selection_on_rows(const struct terminal *term, int row_start, int row_end) selection_on_rows(const struct terminal *term, int row_start, int row_end)
{ {
xassert(term->selection.coords.end.row >= 0);
LOG_DBG("on rows: %d-%d, range: %d-%d (offset=%d)", LOG_DBG("on rows: %d-%d, range: %d-%d (offset=%d)",
term->selection.coords.start.row, term->selection.coords.end.row, term->selection.coords.start.row, term->selection.coords.end.row,
row_start, row_end, term->grid->offset); row_start, row_end, term->grid->offset);
if (term->selection.coords.end.row < 0)
return false;
xassert(term->selection.coords.start.row != -1);
row_start += term->grid->offset; row_start += term->grid->offset;
row_end += term->grid->offset; row_end += term->grid->offset;
xassert(row_end >= row_start);
const struct coord *start = &term->selection.coords.start; const struct coord *start = &term->selection.coords.start;
const struct coord *end = &term->selection.coords.end; const struct coord *end = &term->selection.coords.end;
if ((row_start <= start->row && row_end >= start->row) || const struct grid *grid = term->grid;
(row_start <= end->row && row_end >= end->row)) const int sb_start = grid->offset + term->rows;
/* Use scrollback relative coords when checking for overlap */
const int rel_row_start =
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, row_start);
const int rel_row_end =
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, row_start);
int rel_sel_start =
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, start->row);
int rel_sel_end =
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, end->row);
if (rel_sel_start > rel_sel_end) {
int tmp = rel_sel_start;
rel_sel_start = rel_sel_end;
rel_sel_end = tmp;
}
if ((rel_row_start <= rel_sel_start && rel_row_end >= rel_sel_start) ||
(rel_row_start <= rel_sel_end && rel_row_end >= rel_sel_end))
{ {
/* The range crosses one of the selection boundaries */ /* The range crosses one of the selection boundaries */
return true; return true;
} }
/* For the last check we must ensure start <= end */ if (rel_row_start >= rel_sel_start && rel_row_end <= rel_sel_end)
if (start->row > end->row) {
const struct coord *tmp = start;
start = end;
end = tmp;
}
if (row_start >= start->row && row_end <= end->row)
return true; return true;
return false; return false;
} }
void
selection_scroll_up(struct terminal *term, int rows)
{
xassert(term->selection.coords.end.row >= 0);
const int rel_row_start =
grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.start.row);
const int rel_row_end =
grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.end.row);
const int actual_start = min(rel_row_start, rel_row_end);
if (actual_start - rows < 0) {
/* Part of the selection will be scrolled out, cancel it */
selection_cancel(term);
}
}
void
selection_scroll_down(struct terminal *term, int rows)
{
xassert(term->selection.coords.end.row >= 0);
const int rel_row_start =
grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.start.row);
const int rel_row_end =
grid_row_abs_to_sb(term->grid, term->rows, term->selection.coords.end.row);
const int actual_end = max(rel_row_start, rel_row_end);
if (actual_end + rows <= term->grid->num_rows) {
/* Part of the selection will be scrolled out, cancel it */
selection_cancel(term);
}
}
void void
selection_view_up(struct terminal *term, int new_view) selection_view_up(struct terminal *term, int new_view)
{ {
@ -137,14 +183,14 @@ foreach_selected_normal(
const struct coord *start = &_start; const struct coord *start = &_start;
const struct coord *end = &_end; const struct coord *end = &_end;
const int scrollback_start = term->grid->offset + term->rows;
const int grid_rows = term->grid->num_rows; const int grid_rows = term->grid->num_rows;
/* Start/end rows, relative to the scrollback start */
/* Start/end rows, relative to the scrollback start */ /* Start/end rows, relative to the scrollback start */
const int rel_start_row = const int rel_start_row =
(start->row - scrollback_start + grid_rows) & (grid_rows - 1); grid_row_abs_to_sb(term->grid, term->rows, start->row);
const int rel_end_row = const int rel_end_row =
(end->row - scrollback_start + grid_rows) & (grid_rows - 1); grid_row_abs_to_sb(term->grid, term->rows, end->row);
int start_row, end_row; int start_row, end_row;
int start_col, end_col; int start_col, end_col;
@ -200,14 +246,13 @@ foreach_selected_block(
const struct coord *start = &_start; const struct coord *start = &_start;
const struct coord *end = &_end; const struct coord *end = &_end;
const int scrollback_start = term->grid->offset + term->rows;
const int grid_rows = term->grid->num_rows; const int grid_rows = term->grid->num_rows;
/* Start/end rows, relative to the scrollback start */ /* Start/end rows, relative to the scrollback start */
const int rel_start_row = const int rel_start_row =
(start->row - scrollback_start + grid_rows) & (grid_rows - 1); grid_row_abs_to_sb(term->grid, term->rows, start->row);
const int rel_end_row = const int rel_end_row =
(end->row - scrollback_start + grid_rows) & (grid_rows - 1); grid_row_abs_to_sb(term->grid, term->rows, end->row);
struct coord top_left = { struct coord top_left = {
.row = (rel_start_row < rel_end_row .row = (rel_start_row < rel_end_row
@ -564,111 +609,216 @@ selection_start(struct terminal *term, int col, int row,
} }
/* Context used while (un)marking selected cells, to be able to static pixman_region32_t
* exclude empty cells */ pixman_region_for_coords_normal(const struct terminal *term,
struct mark_context { const struct coord *start,
const struct row *last_row; const struct coord *end)
int empty_count; {
uint8_t **keep_selection; pixman_region32_t region;
pixman_region32_init(&region);
const int rel_start_row =
grid_row_abs_to_sb(term->grid, term->rows, start->row);
const int rel_end_row =
grid_row_abs_to_sb(term->grid, term->rows, end->row);
if (rel_start_row < rel_end_row) {
/* First partial row (start ->)*/
pixman_region32_union_rect(
&region, &region,
start->col, rel_start_row,
term->cols - start->col, 1);
/* Full rows between start and end */
if (rel_start_row + 1 < rel_end_row) {
pixman_region32_union_rect(
&region, &region,
0, rel_start_row + 1,
term->cols, rel_end_row - rel_start_row - 1);
}
/* Last partial row (-> end) */
pixman_region32_union_rect(
&region, &region,
0, rel_end_row,
end->col + 1, 1);
} else if (rel_start_row > rel_end_row) {
/* First partial row (end ->) */
pixman_region32_union_rect(
&region, &region,
end->col, rel_end_row,
term->cols - end->col, 1);
/* Full rows between end and start */
if (rel_end_row + 1 < rel_start_row) {
pixman_region32_union_rect(
&region, &region,
0, rel_end_row + 1,
term->cols, rel_start_row - rel_end_row - 1);
}
/* Last partial row (-> start) */
pixman_region32_union_rect(
&region, &region,
0, rel_start_row,
start->col + 1, 1);
} else {
const int start_col = min(start->col, end->col);
const int end_col = max(start->col, end->col);
pixman_region32_union_rect(
&region, &region,
start_col, rel_start_row,
end_col + 1 - start_col, 1);
}
return region;
}
static pixman_region32_t
pixman_region_for_coords_block(const struct terminal *term,
const struct coord *start, const struct coord *end)
{
pixman_region32_t region;
pixman_region32_init(&region);
const int rel_start_row =
grid_row_abs_to_sb(term->grid, term->rows, start->row);
const int rel_end_row =
grid_row_abs_to_sb(term->grid, term->rows, end->row);
pixman_region32_union_rect(
&region, &region,
min(start->col, end->col), min(rel_start_row, rel_end_row),
abs(start->col - end->col) + 1, abs(rel_start_row - rel_end_row) + 1);
return region;
}
/* Returns a pixman region representing the selection between start
* and end (given the current selection kind), in *scrollback
* relative coordinates* */
static pixman_region32_t
pixman_region_for_coords(const struct terminal *term,
const struct coord *start, const struct coord *end)
{
switch (term->selection.kind) {
default: return pixman_region_for_coords_normal(term, start, end);
case SELECTION_BLOCK: return pixman_region_for_coords_block(term, start, end);
}
}
enum mark_selection_variant {
MARK_SELECTION_MARK_AND_DIRTY,
MARK_SELECTION_UNMARK_AND_DIRTY,
MARK_SELECTION_MARK_FOR_RENDER,
}; };
static bool
unmark_selected(struct terminal *term, struct row *row, struct cell *cell,
int row_no, int col, void *data)
{
if (!cell->attrs.selected)
return true;
struct mark_context *ctx = data;
const uint8_t *keep_selection =
ctx->keep_selection != NULL ? ctx->keep_selection[row_no] : NULL;
if (keep_selection != NULL) {
unsigned idx = (unsigned)col / 8;
unsigned ofs = (unsigned)col % 8;
if (keep_selection[idx] & (1 << ofs)) {
/* Were updating the selection, and this cell is still
* going to be selected */
return true;
}
}
row->dirty = true;
cell->attrs.selected = false;
cell->attrs.clean = false;
return true;
}
static bool
premark_selected(struct terminal *term, struct row *row, struct cell *cell,
int row_no, int col, void *data)
{
struct mark_context *ctx = data;
xassert(ctx != NULL);
if (ctx->last_row != row) {
ctx->last_row = row;
ctx->empty_count = 0;
}
if (cell->wc == 0 && term->selection.kind != SELECTION_BLOCK) {
ctx->empty_count++;
return true;
}
uint8_t *keep_selection = ctx->keep_selection[row_no];
if (keep_selection == NULL) {
keep_selection = xcalloc((term->grid->num_cols + 7) / 8, sizeof(keep_selection[0]));
ctx->keep_selection[row_no] = keep_selection;
}
/* Tell unmark to leave this be */
for (int i = 0; i < ctx->empty_count + 1; i++) {
unsigned idx = (unsigned)(col - i) / 8;
unsigned ofs = (unsigned)(col - i) % 8;
keep_selection[idx] |= 1 << ofs;
}
ctx->empty_count = 0;
return true;
}
static bool
mark_selected(struct terminal *term, struct row *row, struct cell *cell,
int row_no, int col, void *data)
{
struct mark_context *ctx = data;
xassert(ctx != NULL);
if (ctx->last_row != row) {
ctx->last_row = row;
ctx->empty_count = 0;
}
if (cell->wc == 0 && term->selection.kind != SELECTION_BLOCK) {
ctx->empty_count++;
return true;
}
for (int i = 0; i < ctx->empty_count + 1; i++) {
struct cell *c = &row->cells[col - i];
if (!c->attrs.selected) {
row->dirty = true;
c->attrs.selected = true;
c->attrs.clean = false;
}
}
ctx->empty_count = 0;
return true;
}
static void static void
reset_modify_context(struct mark_context *ctx) mark_selected_region(struct terminal *term, pixman_box32_t *boxes,
size_t count, enum mark_selection_variant mark_variant)
{ {
ctx->last_row = NULL; const bool selected =
ctx->empty_count = 0; mark_variant == MARK_SELECTION_MARK_AND_DIRTY ||
mark_variant == MARK_SELECTION_MARK_FOR_RENDER;
const bool dirty_cells =
mark_variant == MARK_SELECTION_MARK_AND_DIRTY ||
mark_variant == MARK_SELECTION_UNMARK_AND_DIRTY;
const bool highlight_empty =
mark_variant != MARK_SELECTION_MARK_FOR_RENDER ||
term->selection.kind == SELECTION_BLOCK;
for (size_t i = 0; i < count; i++) {
const pixman_box32_t *box = &boxes[i];
LOG_DBG("%s selection in region: %dx%d - %dx%d",
selected ? "marking" : "unmarking",
box->x1, box->y1,
box->x2, box->y2);
int abs_row_start = grid_row_sb_to_abs(
term->grid, term->rows, box->y1);
for (int r = abs_row_start, rel_r = box->y1;
rel_r < box->y2;
r = (r + 1) & (term->grid->num_rows - 1), rel_r++)
{
struct row *row = term->grid->rows[r];
xassert(row != NULL);
if (dirty_cells)
row->dirty = true;
for (int c = box->x1, empty_count = 0; c < box->x2; c++) {
struct cell *cell = &row->cells[c];
if (cell->wc == 0 && !highlight_empty) {
/*
* We used to highlight empty cells *if* they were
* followed by non-empty cell(s), since this
* corresponds to what gets extracted when the
* selection is copied (that is, empty cells
* between non-empty cells are converted to
* spaces).
*
* However, they way we handle selection updates
* (diffing the old selection area against the
* new one, using pixman regions), means we
* cant correctly update the state of empty
* cells. The result is random empty cells being
* rendered as selected when they shouldnt.
*
* Fix by *never* highlighting selected empty
* cells (they still get converted to spaces when
* copied, if followed by non-empty cells).
*/
empty_count++;
/*
* When the selection is *modified*, empty cells
* are treated just like non-empty cells; they are
* marked as selected, and dirtied.
*
* This is due to how the algorithm for updating
* the selection works; it uses regions to
* calculate the difference between the old and
* the new selection. This makes it impossible
* to tell if an empty cell is a *trailing* empty
* cell (that should not be highlighted), or an
* empty cells between non-empty cells (that
* *should* be highlighted).
*
* Then, when a frame is rendered, we loop the
* *visibible* cells that belong to the
* selection. At this point, we *can* tell if an
* empty cell is trailing or not.
*
* So, what we need to do is check if a
* selected, and empty cell has been marked as
* selected, temporarily unmark (forcing it dirty,
* to ensure it gets re-rendered). If it is *not*
* a trailing empty cell, it will get re-tagged as
* selected in the for-loop below.
*/
cell->attrs.clean = false;
cell->attrs.selected = false;
continue;
}
for (int j = 0; j < empty_count + 1; j++) {
xassert(c - j >= 0);
struct cell *cell = &row->cells[c - j];
if (dirty_cells)
cell->attrs.clean = false;
cell->attrs.selected = selected;
}
empty_count = 0;
}
}
}
} }
static void static void
@ -678,33 +828,46 @@ selection_modify(struct terminal *term, struct coord start, struct coord end)
xassert(start.row != -1 && start.col != -1); xassert(start.row != -1 && start.col != -1);
xassert(end.row != -1 && end.col != -1); xassert(end.row != -1 && end.col != -1);
uint8_t **keep_selection = pixman_region32_t previous_selection;
xcalloc(term->grid->num_rows, sizeof(keep_selection[0]));
struct mark_context ctx = {.keep_selection = keep_selection};
/* Premark all cells that *will* be selected */
foreach_selected(term, start, end, &premark_selected, &ctx);
reset_modify_context(&ctx);
if (term->selection.coords.end.row >= 0) { if (term->selection.coords.end.row >= 0) {
/* Unmark previous selection, ignoring cells that are part of previous_selection = pixman_region_for_coords(
* the new selection */ term,
foreach_selected(term, term->selection.coords.start, term->selection.coords.end, &term->selection.coords.start,
&unmark_selected, &ctx); &term->selection.coords.end);
reset_modify_context(&ctx); } else
} pixman_region32_init(&previous_selection);
pixman_region32_t current_selection = pixman_region_for_coords(
term, &start, &end);
pixman_region32_t no_longer_selected;
pixman_region32_init(&no_longer_selected);
pixman_region32_subtract(
&no_longer_selected, &previous_selection, &current_selection);
pixman_region32_t newly_selected;
pixman_region32_init(&newly_selected);
pixman_region32_subtract(
&newly_selected, &current_selection, &previous_selection);
/* Clear selection in cells no longer selected */
int n_rects = -1;
pixman_box32_t *boxes = NULL;
boxes = pixman_region32_rectangles(&no_longer_selected, &n_rects);
mark_selected_region(term, boxes, n_rects, MARK_SELECTION_UNMARK_AND_DIRTY);
boxes = pixman_region32_rectangles(&newly_selected, &n_rects);
mark_selected_region(term, boxes, n_rects, MARK_SELECTION_MARK_AND_DIRTY);
pixman_region32_fini(&newly_selected);
pixman_region32_fini(&no_longer_selected);
pixman_region32_fini(&current_selection);
pixman_region32_fini(&previous_selection);
term->selection.coords.start = start; term->selection.coords.start = start;
term->selection.coords.end = end; term->selection.coords.end = end;
/* Mark new selection */
foreach_selected(term, start, end, &mark_selected, &ctx);
render_refresh(term); render_refresh(term);
for (size_t i = 0; i < term->grid->num_rows; i++)
free(keep_selection[i]);
free(keep_selection);
} }
static void static void
@ -945,9 +1108,26 @@ selection_dirty_cells(struct terminal *term)
if (term->selection.coords.start.row < 0 || term->selection.coords.end.row < 0) if (term->selection.coords.start.row < 0 || term->selection.coords.end.row < 0)
return; return;
foreach_selected( pixman_region32_t selection = pixman_region_for_coords(
term, term->selection.coords.start, term->selection.coords.end, &mark_selected, term, &term->selection.coords.start, &term->selection.coords.end);
&(struct mark_context){0});
pixman_region32_t view = pixman_region_for_coords(
term,
&(struct coord){0, term->grid->view},
&(struct coord){term->cols - 1, term->grid->view + term->rows - 1});
pixman_region32_t visible_and_selected;
pixman_region32_init(&visible_and_selected);
pixman_region32_intersect(&visible_and_selected, &selection, &view);
int n_rects = -1;
pixman_box32_t *boxes =
pixman_region32_rectangles(&visible_and_selected, &n_rects);
mark_selected_region(term, boxes, n_rects, MARK_SELECTION_MARK_FOR_RENDER);
pixman_region32_fini(&visible_and_selected);
pixman_region32_fini(&view);
pixman_region32_fini(&selection);
} }
static void static void
@ -957,27 +1137,37 @@ selection_extend_normal(struct terminal *term, int col, int row,
const struct coord *start = &term->selection.coords.start; const struct coord *start = &term->selection.coords.start;
const struct coord *end = &term->selection.coords.end; const struct coord *end = &term->selection.coords.end;
if (start->row > end->row || const int rel_row = grid_row_abs_to_sb(term->grid, term->rows, row);
(start->row == end->row && start->col > end->col)) int rel_start_row = grid_row_abs_to_sb(term->grid, term->rows, start->row);
int rel_end_row = grid_row_abs_to_sb(term->grid, term->rows, end->row);
if (rel_start_row > rel_end_row ||
(rel_start_row == rel_end_row && start->col > end->col))
{ {
const struct coord *tmp = start; const struct coord *tmp = start;
start = end; start = end;
end = tmp; end = tmp;
}
xassert(start->row < end->row || start->col < end->col); int tmp_row = rel_start_row;
rel_start_row = rel_end_row;
rel_end_row = tmp_row;
}
struct coord new_start, new_end; struct coord new_start, new_end;
enum selection_direction direction; enum selection_direction direction;
if (row < start->row || (row == start->row && col < start->col)) { if (rel_row < rel_start_row ||
(rel_row == rel_start_row && col < start->col))
{
/* Extend selection to start *before* current start */ /* Extend selection to start *before* current start */
new_start = *end; new_start = *end;
new_end = (struct coord){col, row}; new_end = (struct coord){col, row};
direction = SELECTION_LEFT; direction = SELECTION_LEFT;
} }
else if (row > end->row || (row == end->row && col > end->col)) { else if (rel_row > rel_end_row ||
(rel_row == rel_end_row && col > end->col))
{
/* Extend selection to end *after* current end */ /* Extend selection to end *after* current end */
new_start = *start; new_start = *start;
new_end = (struct coord){col, row}; new_end = (struct coord){col, row};
@ -987,10 +1177,10 @@ selection_extend_normal(struct terminal *term, int col, int row,
else { else {
/* Shrink selection from start or end, depending on which one is closest */ /* Shrink selection from start or end, depending on which one is closest */
const int linear = row * term->cols + col; const int linear = rel_row * term->cols + col;
if (abs(linear - (start->row * term->cols + start->col)) < if (abs(linear - (rel_start_row * term->cols + start->col)) <
abs(linear - (end->row * term->cols + end->col))) abs(linear - (rel_end_row * term->cols + end->col)))
{ {
/* Move start point */ /* Move start point */
new_start = *end; new_start = *end;
@ -1065,33 +1255,41 @@ selection_extend_block(struct terminal *term, int col, int row)
const struct coord *start = &term->selection.coords.start; const struct coord *start = &term->selection.coords.start;
const struct coord *end = &term->selection.coords.end; const struct coord *end = &term->selection.coords.end;
const int rel_start_row =
grid_row_abs_to_sb(term->grid, term->rows, start->row);
const int rel_end_row =
grid_row_abs_to_sb(term->grid, term->rows, end->row);
struct coord top_left = { struct coord top_left = {
.row = min(start->row, end->row), .row = rel_start_row < rel_end_row ? start->row : end->row,
.col = min(start->col, end->col), .col = min(start->col, end->col),
}; };
struct coord top_right = { struct coord top_right = {
.row = min(start->row, end->row), .row = top_left.row,
.col = max(start->col, end->col), .col = max(start->col, end->col),
}; };
struct coord bottom_left = { struct coord bottom_left = {
.row = max(start->row, end->row), .row = rel_start_row > rel_end_row ? start->row : end->row,
.col = min(start->col, end->col), .col = min(start->col, end->col),
}; };
struct coord bottom_right = { struct coord bottom_right = {
.row = max(start->row, end->row), .row = bottom_left.row,
.col = max(start->col, end->col), .col = max(start->col, end->col),
}; };
const int rel_row = grid_row_abs_to_sb(term->grid, term->rows, row);
const int rel_top_row = grid_row_abs_to_sb(term->grid, term->rows, top_left.row);
const int rel_bottom_row = grid_row_abs_to_sb(term->grid, term->rows, bottom_left.row);
struct coord new_start; struct coord new_start;
struct coord new_end; struct coord new_end;
enum selection_direction direction = SELECTION_UNDIR; enum selection_direction direction = SELECTION_UNDIR;
if (row <= top_left.row || if (rel_row <= rel_top_row ||
abs(row - top_left.row) < abs(row - bottom_left.row)) abs(rel_row - rel_top_row) < abs(rel_row - rel_bottom_row))
{ {
/* Move one of the top corners */ /* Move one of the top corners */
@ -1207,6 +1405,19 @@ selection_finalize(struct seat *seat, struct terminal *term, uint32_t serial)
} }
} }
static bool
unmark_selected(struct terminal *term, struct row *row, struct cell *cell,
int row_no, int col, void *data)
{
if (!cell->attrs.selected)
return true;
row->dirty = true;
cell->attrs.selected = false;
cell->attrs.clean = false;
return true;
}
void void
selection_cancel(struct terminal *term) selection_cancel(struct terminal *term)
{ {
@ -1219,7 +1430,7 @@ selection_cancel(struct terminal *term)
if (term->selection.coords.start.row >= 0 && term->selection.coords.end.row >= 0) { if (term->selection.coords.start.row >= 0 && term->selection.coords.end.row >= 0) {
foreach_selected( foreach_selected(
term, term->selection.coords.start, term->selection.coords.end, term, term->selection.coords.start, term->selection.coords.end,
&unmark_selected, &(struct mark_context){0}); &unmark_selected, NULL);
render_refresh(term); render_refresh(term);
} }

View file

@ -22,6 +22,8 @@ void selection_extend(
bool selection_on_rows(const struct terminal *term, int start, int end); bool selection_on_rows(const struct terminal *term, int start, int end);
void selection_scroll_up(struct terminal *term, int rows);
void selection_scroll_down(struct terminal *term, int rows);
void selection_view_up(struct terminal *term, int new_view); void selection_view_up(struct terminal *term, int new_view);
void selection_view_down(struct terminal *term, int new_view); void selection_view_down(struct terminal *term, int new_view);

3
shm.c
View file

@ -151,7 +151,6 @@ buffer_destroy(struct buffer_private *buf)
pool_unref(buf->pool); pool_unref(buf->pool);
buf->pool = NULL; buf->pool = NULL;
free(buf->public.scroll_damage);
pixman_region32_fini(&buf->public.dirty); pixman_region32_fini(&buf->public.dirty);
free(buf); free(buf);
} }
@ -581,8 +580,6 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height)
LOG_DBG("re-using buffer %p from cache", (void *)cached); LOG_DBG("re-using buffer %p from cache", (void *)cached);
cached->busy = true; cached->busy = true;
pixman_region32_clear(&cached->public.dirty); pixman_region32_clear(&cached->public.dirty);
free(cached->public.scroll_damage);
cached->public.scroll_damage = NULL;
xassert(cached->public.pix_instances == chain->pix_instances); xassert(cached->public.pix_instances == chain->pix_instances);
return &cached->public; return &cached->public;
} }

2
shm.h
View file

@ -24,8 +24,6 @@ struct buffer {
unsigned age; unsigned age;
struct damage *scroll_damage;
size_t scroll_damage_count;
pixman_region32_t dirty; pixman_region32_t dirty;
}; };

View file

@ -352,6 +352,7 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
setenv("TERM", term_env, 1); setenv("TERM", term_env, 1);
setenv("COLORTERM", "truecolor", 1); setenv("COLORTERM", "truecolor", 1);
setenv("PWD", cwd, 1);
#if defined(FOOT_TERMINFO_PATH) #if defined(FOOT_TERMINFO_PATH)
setenv("TERMINFO", FOOT_TERMINFO_PATH, 1); setenv("TERMINFO", FOOT_TERMINFO_PATH, 1);

View file

@ -54,9 +54,12 @@ spawn(struct reaper *reaper, const char *cwd, char *const argv[],
goto child_err; goto child_err;
} }
if (cwd != NULL && chdir(cwd) < 0) { if (cwd != NULL) {
LOG_WARN("failed to change working directory to %s: %s", setenv("PWD", cwd, 1);
cwd, strerror(errno)); if (chdir(cwd) < 0) {
LOG_WARN("failed to change working directory to %s: %s",
cwd, strerror(errno));
}
} }
if (xdg_activation_token != NULL) { if (xdg_activation_token != NULL) {

View file

@ -203,6 +203,30 @@ fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data)
return true; return true;
} }
static bool
add_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx)
{
if (ptmx < 0)
return true;
if (conf->utempter_path == NULL)
return true;
char *const argv[] = {conf->utempter_path, "add", getenv("WAYLAND_DISPLAY"), NULL};
return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL);
}
static bool
del_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx)
{
if (ptmx < 0)
return true;
if (conf->utempter_path == NULL)
return true;
char *const argv[] = {conf->utempter_path, "del", getenv("WAYLAND_DISPLAY"), NULL};
return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL);
}
#if PTMX_TIMING #if PTMX_TIMING
static struct timespec last = {0}; static struct timespec last = {0};
#endif #endif
@ -326,6 +350,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
} }
if (hup) { if (hup) {
del_utmp_record(term->conf, term->reaper, term->ptmx);
fdm_del(fdm, fd); fdm_del(fdm, fd);
term->ptmx = -1; term->ptmx = -1;
} }
@ -1089,6 +1114,11 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
goto close_fds; goto close_fds;
} }
/* Need to register *very* early (before the first “goto err”), to
* ensure term_destroy() doesnt unref a key-binding we havent
* yet ref:d */
key_binding_new_for_conf(wayl->key_binding_manager, wayl, conf);
int ptmx_flags; int ptmx_flags;
if ((ptmx_flags = fcntl(ptmx, F_GETFL)) < 0 || if ((ptmx_flags = fcntl(ptmx, F_GETFL)) < 0 ||
fcntl(ptmx, F_SETFL, ptmx_flags | O_NONBLOCK) < 0) fcntl(ptmx, F_SETFL, ptmx_flags | O_NONBLOCK) < 0)
@ -1246,6 +1276,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
} }
term->font_line_height = conf->line_height; term->font_line_height = conf->line_height;
add_utmp_record(conf, reaper, ptmx);
/* Start the slave/client */ /* Start the slave/client */
if ((term->slave = slave_spawn( if ((term->slave = slave_spawn(
term->ptmx, argc, term->cwd, argv, envp, &conf->env_vars, term->ptmx, argc, term->cwd, argv, envp, &conf->env_vars,
@ -1266,8 +1298,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table)); memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table));
key_binding_new_for_term(wayl->key_binding_manager, term);
/* Initialize the Wayland window backend */ /* Initialize the Wayland window backend */
if ((term->window = wayl_win_init(term, token)) == NULL) if ((term->window = wayl_win_init(term, token)) == NULL)
goto err; goto err;
@ -1511,6 +1541,8 @@ term_shutdown(struct terminal *term)
fdm_del(term->fdm, term->blink.fd); fdm_del(term->fdm, term->blink.fd);
fdm_del(term->fdm, term->flash.fd); fdm_del(term->fdm, term->flash.fd);
del_utmp_record(term->conf, term->reaper, term->ptmx);
if (term->window != NULL && term->window->is_configured) if (term->window != NULL && term->window->is_configured)
fdm_del(term->fdm, term->ptmx); fdm_del(term->fdm, term->ptmx);
else else
@ -1583,7 +1615,7 @@ term_destroy(struct terminal *term)
if (term == NULL) if (term == NULL)
return 0; return 0;
key_binding_unref_term(term->wl->key_binding_manager, term); key_binding_unref(term->wl->key_binding_manager, term->conf);
tll_foreach(term->wl->terms, it) { tll_foreach(term->wl->terms, it) {
if (it->item == term) { if (it->item == term) {
@ -1592,6 +1624,8 @@ term_destroy(struct terminal *term)
} }
} }
del_utmp_record(term->conf, term->reaper, term->ptmx);
fdm_del(term->fdm, term->selection.auto_scroll.fd); fdm_del(term->fdm, term->selection.auto_scroll.fd);
fdm_del(term->fdm, term->render.app_sync_updates.timer_fd); fdm_del(term->fdm, term->render.app_sync_updates.timer_fd);
fdm_del(term->fdm, term->render.title.timer_fd); fdm_del(term->fdm, term->render.title.timer_fd);
@ -1811,7 +1845,7 @@ static inline void
erase_line(struct terminal *term, struct row *row) erase_line(struct terminal *term, struct row *row)
{ {
erase_cell_range(term, row, 0, term->cols - 1); erase_cell_range(term, row, 0, term->cols - 1);
row->linebreak = true; row->linebreak = false;
row->prompt_marker = false; row->prompt_marker = false;
} }
@ -2537,11 +2571,11 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows
* scrolled in (i.e. re-used lines). * scrolled in (i.e. re-used lines).
*/ */
if (selection_on_top_region(term, region) || if (selection_on_top_region(term, region) ||
selection_on_bottom_region(term, region) || selection_on_bottom_region(term, region))
selection_on_rows(term, region.end - rows, region.end - 1))
{ {
selection_cancel(term); selection_cancel(term);
} } else
selection_scroll_up(term, rows);
} }
sixel_scroll_up(term, rows); sixel_scroll_up(term, rows);
@ -2611,11 +2645,11 @@ term_scroll_reverse_partial(struct terminal *term,
* scrolled in (i.e. re-used lines). * scrolled in (i.e. re-used lines).
*/ */
if (selection_on_top_region(term, region) || if (selection_on_top_region(term, region) ||
selection_on_bottom_region(term, region) || selection_on_bottom_region(term, region))
selection_on_rows(term, region.start, region.start + rows - 1))
{ {
selection_cancel(term); selection_cancel(term);
} } else
selection_scroll_down(term, rows);
} }
sixel_scroll_down(term, rows); sixel_scroll_down(term, rows);
@ -2885,7 +2919,7 @@ term_mouse_grabbed(const struct terminal *term, const struct seat *seat)
get_current_modifiers(seat, &mods, NULL, 0); get_current_modifiers(seat, &mods, NULL, 0);
const struct key_binding_set *bindings = const struct key_binding_set *bindings =
key_binding_for(term->wl->key_binding_manager, term, seat); key_binding_for(term->wl->key_binding_manager, term->conf, seat);
const xkb_mod_mask_t override_modmask = bindings->selection_overrides; const xkb_mod_mask_t override_modmask = bindings->selection_overrides;
bool override_mods_pressed = (mods & override_modmask) == override_modmask; bool override_mods_pressed = (mods & override_modmask) == override_modmask;
@ -3298,6 +3332,7 @@ term_print(struct terminal *term, char32_t wc, int width)
/* *Must* get current cell *after* linewrap+insert */ /* *Must* get current cell *after* linewrap+insert */
struct row *row = grid->cur_row; struct row *row = grid->cur_row;
row->dirty = true; row->dirty = true;
row->linebreak = true;
struct cell *cell = &row->cells[col]; struct cell *cell = &row->cells[col];
cell->wc = term->vt.last_printed = wc; cell->wc = term->vt.last_printed = wc;
@ -3357,6 +3392,7 @@ ascii_printer_fast(struct terminal *term, char32_t wc)
struct row *row = grid->cur_row; struct row *row = grid->cur_row;
row->dirty = true; row->dirty = true;
row->linebreak = true;
struct cell *cell = &row->cells[col]; struct cell *cell = &row->cells[col];
cell->wc = term->vt.last_printed = wc; cell->wc = term->vt.last_printed = wc;

View file

@ -289,9 +289,10 @@ enum term_surface {
}; };
enum overlay_style { enum overlay_style {
OVERLAY_NONE = 0, OVERLAY_NONE,
OVERLAY_SEARCH = 1, OVERLAY_SEARCH,
OVERLAY_FLASH = 2, OVERLAY_FLASH,
OVERLAY_UNICODE_MODE,
}; };
typedef tll(struct ptmx_buffer) ptmx_buffer_list_t; typedef tll(struct ptmx_buffer) ptmx_buffer_list_t;

View file

@ -401,6 +401,52 @@ test_color(struct context *ctx, bool (*parse_fun)(struct context *ctx),
} }
} }
static void
test_two_colors(struct context *ctx, bool (*parse_fun)(struct context *ctx),
const char *key, bool alpha_allowed,
uint32_t *ptr1, uint32_t *ptr2)
{
ctx->key = key;
const struct {
const char *option_string;
uint32_t color1;
uint32_t color2;
bool invalid;
} input[] = {
{"000000 000000", 0, 0},
/* No alpha */
{"999999 888888", 0x999999, 0x888888},
{"ffffff aaaaaa", 0xffffff, 0xaaaaaa},
/* Both colors have alpha component */
{"ffffffff 00000000", 0xffffffff, 0x00000000, !alpha_allowed},
{"aabbccdd, ee112233", 0xaabbccdd, 0xee112233, !alpha_allowed},
/* Only one color has alpha component */
{"ffffffff 112233", 0xffffffff, 0x112233, !alpha_allowed},
{"ffffff ff112233", 0x00ffffff, 0xff112233, !alpha_allowed},
{"unittest-invalid-color", 0, 0, true},
};
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);
}
} else {
if (!parse_fun(ctx)) {
BUG("[%s].%s=%s: failed to parse",
ctx->section, ctx->key, ctx->value);
}
}
}
}
static void static void
test_section_main(void) test_section_main(void)
{ {
@ -412,6 +458,7 @@ test_section_main(void)
test_string(&ctx, &parse_section_main, "shell", &conf.shell); test_string(&ctx, &parse_section_main, "shell", &conf.shell);
test_string(&ctx, &parse_section_main, "term", &conf.term); 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, "app-id", &conf.app_id);
test_string(&ctx, &parse_section_main, "utempter", &conf.utempter_path);
test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters); test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters);
@ -424,6 +471,7 @@ test_section_main(void)
test_pt_or_px(&ctx, &parse_section_main, "letter-spacing", &conf.letter_spacing); test_pt_or_px(&ctx, &parse_section_main, "letter-spacing", &conf.letter_spacing);
test_pt_or_px(&ctx, &parse_section_main, "horizontal-letter-offset", &conf.horizontal_letter_offset); test_pt_or_px(&ctx, &parse_section_main, "horizontal-letter-offset", &conf.horizontal_letter_offset);
test_pt_or_px(&ctx, &parse_section_main, "vertical-letter-offset", &conf.vertical_letter_offset); test_pt_or_px(&ctx, &parse_section_main, "vertical-letter-offset", &conf.vertical_letter_offset);
test_pt_or_px(&ctx, &parse_section_main, "underline-thickness", &conf.underline_thickness);
test_uint16(&ctx, &parse_section_main, "resize-delay-ms", &conf.resize_delay_ms); test_uint16(&ctx, &parse_section_main, "resize-delay-ms", &conf.resize_delay_ms);
test_uint16(&ctx, &parse_section_main, "workers", &conf.render_worker_count); test_uint16(&ctx, &parse_section_main, "workers", &conf.render_worker_count);
@ -616,6 +664,18 @@ test_section_colors(void)
test_color(&ctx, &parse_section_colors, "selection-foreground", false, &conf.colors.selection_fg); test_color(&ctx, &parse_section_colors, "selection-foreground", false, &conf.colors.selection_fg);
test_color(&ctx, &parse_section_colors, "selection-background", false, &conf.colors.selection_bg); test_color(&ctx, &parse_section_colors, "selection-background", false, &conf.colors.selection_bg);
test_color(&ctx, &parse_section_colors, "urls", false, &conf.colors.url); test_color(&ctx, &parse_section_colors, "urls", false, &conf.colors.url);
test_two_colors(&ctx, &parse_section_colors, "jump-labels", false,
&conf.colors.jump_label.fg,
&conf.colors.jump_label.bg);
test_two_colors(&ctx, &parse_section_colors, "scrollback-indicator", false,
&conf.colors.scrollback_indicator.fg,
&conf.colors.scrollback_indicator.bg);
test_two_colors(&ctx, &parse_section_colors, "search-box-no-match", false,
&conf.colors.search_box.no_match.fg,
&conf.colors.search_box.no_match.bg);
test_two_colors(&ctx, &parse_section_colors, "search-box-match", false,
&conf.colors.search_box.match.fg,
&conf.colors.search_box.match.bg);
for (size_t i = 0; i < 255; i++) { for (size_t i = 0; i < 255; i++) {
char key_name[4]; char key_name[4];
@ -627,8 +687,6 @@ test_section_colors(void)
test_invalid_key(&ctx, &parse_section_colors, "256"); test_invalid_key(&ctx, &parse_section_colors, "256");
/* TODO: alpha (float in range 0-1, converted to uint16_t) */ /* TODO: alpha (float in range 0-1, converted to uint16_t) */
/* TODO: jump-labels (two colors) */
/* TODO: scrollback-indicator (two colors) */
config_free(&conf); config_free(&conf);
} }

29
themes/moonfly Normal file
View file

@ -0,0 +1,29 @@
# moonfly
# Based on https://github.com/bluz71/vim-moonfly-colors
[cursor]
color = 080808 9e9e9e
[colors]
foreground = b2b2b2
background = 080808
selection-foreground = 080808
selection-background = b2ceee
regular0 = 323437
regular1 = ff5454
regular2 = 8cc85f
regular3 = e3c78a
regular4 = 80a0ff
regular5 = d183e8
regular6 = 79dac8
regular7 = c6c6c6
bright0 = 949494
bright1 = ff5189
bright2 = 36c692
bright3 = c2c292
bright4 = 74b2ff
bright5 = ae81ff
bright6 = 85dc85
bright7 = e4e4e4

29
themes/nightfly Normal file
View file

@ -0,0 +1,29 @@
# nightfly
# Based on https://github.com/bluz71/vim-nightfly-guicolors
[cursor]
color = 080808 9ca1aa
[colors]
foreground = acb4c2
background = 011627
selection-foreground = 080808
selection-background = b2ceee
regular0 = 1d3b53
regular1 = fc514e
regular2 = a1cd5e
regular3 = e3d18a
regular4 = 82aaff
regular5 = c792ea
regular6 = 7fdbca
regular7 = a1aab8
bright0 = 7c8f8f
bright1 = ff5874
bright2 = 21c7a8
bright3 = ecc48d
bright4 = 82aaff
bright5 = ae81ff
bright6 = ae81ff
bright7 = d6deeb

106
unicode-mode.c Normal file
View file

@ -0,0 +1,106 @@
#include "unicode-mode.h"
#define LOG_MODULE "unicode-input"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "render.h"
#include "search.h"
void
unicode_mode_activate(struct seat *seat)
{
if (seat->unicode_mode.active)
return;
seat->unicode_mode.active = true;
seat->unicode_mode.character = u'\0';
seat->unicode_mode.count = 0;
unicode_mode_updated(seat);
}
void
unicode_mode_deactivate(struct seat *seat)
{
if (!seat->unicode_mode.active)
return;
seat->unicode_mode.active = false;
unicode_mode_updated(seat);
}
void
unicode_mode_updated(struct seat *seat)
{
struct terminal *term = seat->kbd_focus;
if (term == NULL)
return;
if (term->is_searching)
render_refresh_search(term);
else
render_refresh(term);
}
void
unicode_mode_input(struct seat *seat, struct terminal *term,
xkb_keysym_t sym)
{
if (sym == XKB_KEY_Return ||
sym == XKB_KEY_space ||
sym == XKB_KEY_KP_Enter ||
sym == XKB_KEY_KP_Space)
{
char utf8[MB_CUR_MAX];
size_t chars = c32rtomb(
utf8, seat->unicode_mode.character, &(mbstate_t){0});
LOG_DBG("Unicode input: 0x%06x -> %.*s",
seat->unicode_mode.character, (int)chars, utf8);
if (chars != (size_t)-1) {
if (term->is_searching)
search_add_chars(term, utf8, chars);
else
term_to_slave(term, utf8, chars);
}
unicode_mode_deactivate(seat);
}
else if (sym == XKB_KEY_Escape ||
sym == XKB_KEY_q ||
(seat->kbd.ctrl && (sym == XKB_KEY_c ||
sym == XKB_KEY_d ||
sym == XKB_KEY_g)))
{
unicode_mode_deactivate(seat);
}
else if (sym == XKB_KEY_BackSpace) {
if (seat->unicode_mode.count > 0) {
seat->unicode_mode.character >>= 4;
seat->unicode_mode.count--;
unicode_mode_updated(seat);
}
}
else if (seat->unicode_mode.count < 6) {
int digit = -1;
/* 0-9, a-f, A-F */
if (sym >= XKB_KEY_0 && sym <= XKB_KEY_9)
digit = sym - XKB_KEY_0;
else if (sym >= XKB_KEY_a && sym <= XKB_KEY_f)
digit = 0xa + (sym - XKB_KEY_a);
else if (sym >= XKB_KEY_A && sym <= XKB_KEY_F)
digit = 0xa + (sym - XKB_KEY_A);
if (digit >= 0) {
xassert(digit >= 0 && digit <= 0xf);
seat->unicode_mode.character <<= 4;
seat->unicode_mode.character |= digit;
seat->unicode_mode.count++;
unicode_mode_updated(seat);
}
}
}

11
unicode-mode.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include <xkbcommon/xkbcommon-keysyms.h>
#include "wayland.h"
void unicode_mode_activate(struct seat *seat);
void unicode_mode_deactivate(struct seat *seat);
void unicode_mode_updated(struct seat *seat);
void unicode_mode_input(struct seat *seat, struct terminal *term,
xkb_keysym_t sym);

View file

@ -677,18 +677,23 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls)
if (count == 0) if (count == 0)
return; return;
uint64_t seen_ids[count];
char32_t *combos[count]; char32_t *combos[count];
generate_key_combos(conf, count, combos); generate_key_combos(conf, count, combos);
size_t combo_idx = 0; size_t combo_idx = 0;
size_t id_idx = 0;
tll_foreach(*urls, it) { tll_foreach(*urls, it) {
bool id_already_seen = false; bool id_already_seen = false;
for (size_t i = 0; i < id_idx; i++) { /* Look for already processed URLs where both the URI and the
if (it->item.id == seen_ids[i]) { * ID matches */
tll_foreach(*urls, it2) {
if (&it->item == &it2->item)
break;
if (it->item.id == it2->item.id &&
strcmp(it->item.url, it2->item.url) == 0)
{
id_already_seen = true; id_already_seen = true;
break; break;
} }
@ -696,7 +701,6 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls)
if (id_already_seen) if (id_already_seen)
continue; continue;
seen_ids[id_idx++] = it->item.id;
/* /*
* Scan previous URLs, and check if *this* URL matches any of * Scan previous URLs, and check if *this* URL matches any of
@ -730,7 +734,7 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls)
char *key = ac32tombs(it->item.key); char *key = ac32tombs(it->item.key);
xassert(key != NULL); xassert(key != NULL);
LOG_DBG("URL: %s (%s)", it->item.url, key); LOG_DBG("URL: %s (key=%s, id=%"PRIu64")", it->item.url, key, it->item.id);
free(key); free(key);
} }
#endif #endif

View file

@ -367,7 +367,7 @@ update_terms_on_monitor(struct monitor *mon)
static void static void
output_update_ppi(struct monitor *mon) output_update_ppi(struct monitor *mon)
{ {
if (mon->dim.mm.width == 0 || mon->dim.mm.height == 0) if (mon->dim.mm.width <= 0 || mon->dim.mm.height <= 0)
return; return;
int x_inches = mon->dim.mm.width * 0.03937008; int x_inches = mon->dim.mm.width * 0.03937008;
@ -706,9 +706,50 @@ xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
term_shutdown(term); term_shutdown(term);
} }
#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION)
static void
xdg_toplevel_configure_bounds(void *data,
struct xdg_toplevel *xdg_toplevel,
int32_t width, int32_t height)
{
/* TODO: ensure we don't pick a bigger size */
}
#endif
#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION)
static void
xdg_toplevel_wm_capabilities(void *data,
struct xdg_toplevel *xdg_toplevel,
struct wl_array *caps)
{
struct wl_window *win = data;
win->wm_capabilities.maximize = false;
win->wm_capabilities.minimize = false;
uint32_t *cap_ptr;
wl_array_for_each(cap_ptr, caps) {
switch (*cap_ptr) {
case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE:
win->wm_capabilities.maximize = true;
break;
case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE:
win->wm_capabilities.minimize = true;
break;
}
}
}
#endif
static const struct xdg_toplevel_listener xdg_toplevel_listener = { static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = &xdg_toplevel_configure, .configure = &xdg_toplevel_configure,
/*.close = */&xdg_toplevel_close, /* epoll-shim defines a macro close... */ /*.close = */&xdg_toplevel_close, /* epoll-shim defines a macro close... */
#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION)
.configure_bounds = &xdg_toplevel_configure_bounds,
#endif
#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION)
.wm_capabilities = xdg_toplevel_wm_capabilities,
#endif
}; };
static void static void
@ -896,13 +937,22 @@ handle_global(void *data, struct wl_registry *registry,
return; return;
/* /*
* We *require* version 1, but _can_ use version 2. Version 2 * We *require* version 1, but _can_ use version 5. Version 2
* adds 'tiled' window states. We use that information to * adds 'tiled' window states. We use that information to
* restore the window size when window is un-tiled. * restore the window size when window is un-tiled. Version 5
* adds 'wm_capabilities'. We use that information to draw
* window decorations.
*/ */
#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION)
const uint32_t preferred = XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION;
#elif defined(XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION)
const uint32_t preferred = XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION;
#else
const uint32_t preferred = required;
#endif
wayl->shell = wl_registry_bind( wayl->shell = wl_registry_bind(
wayl->registry, name, &xdg_wm_base_interface, min(version, 2)); wayl->registry, name, &xdg_wm_base_interface, min(version, preferred));
xdg_wm_base_add_listener(wayl->shell, &xdg_wm_base_listener, wayl); xdg_wm_base_add_listener(wayl->shell, &xdg_wm_base_listener, wayl);
} }
@ -1418,6 +1468,9 @@ wayl_win_init(struct terminal *term, const char *token)
win->csd.move_timeout_fd = -1; win->csd.move_timeout_fd = -1;
win->resize_timeout_fd = -1; win->resize_timeout_fd = -1;
win->wm_capabilities.maximize = true;
win->wm_capabilities.minimize = true;
win->surface = wl_compositor_create_surface(wayl->compositor); win->surface = wl_compositor_create_surface(wayl->compositor);
if (win->surface == NULL) { if (win->surface == NULL) {
LOG_ERR("failed to create wayland surface"); LOG_ERR("failed to create wayland surface");

View file

@ -206,6 +206,12 @@ struct seat {
uint32_t serial; uint32_t serial;
} ime; } ime;
#endif #endif
struct {
bool active;
int count;
char32_t character;
} unicode_mode;
}; };
enum csd_surface { enum csd_surface {
@ -333,6 +339,11 @@ struct wl_window {
uint32_t serial; uint32_t serial;
} csd; } csd;
struct {
bool maximize:1;
bool minimize:1;
} wm_capabilities;
struct wl_surf_subsurf search; struct wl_surf_subsurf search;
struct wl_surf_subsurf scrollback_indicator; struct wl_surf_subsurf scrollback_indicator;
struct wl_surf_subsurf render_timer; struct wl_surf_subsurf render_timer;