mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-04-27 06:46:44 -04:00
Merge branch 'master' of https://codeberg.org/dnkl/foot
This commit is contained in:
commit
fd187cc491
50 changed files with 1530 additions and 481 deletions
|
|
@ -24,7 +24,7 @@ packages:
|
|||
- font-noto-emoji
|
||||
|
||||
sources:
|
||||
- https://codeberg.org/dnkl/foot
|
||||
- https://git.sr.ht/~dnkl/foot
|
||||
|
||||
# triggers:
|
||||
# - action: email
|
||||
|
|
@ -49,4 +49,4 @@ tasks:
|
|||
- codespell: |
|
||||
pip install codespell
|
||||
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
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ packages:
|
|||
- font-noto-emoji
|
||||
|
||||
sources:
|
||||
- https://codeberg.org/dnkl/foot
|
||||
- https://git.sr.ht/~dnkl/foot
|
||||
|
||||
# triggers:
|
||||
# - action: email
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ packages:
|
|||
- noto-emoji
|
||||
|
||||
sources:
|
||||
- https://codeberg.org/dnkl/foot
|
||||
- https://git.sr.ht/~dnkl/foot
|
||||
|
||||
# triggers:
|
||||
# - action: email
|
||||
|
|
|
|||
|
|
@ -109,4 +109,4 @@ codespell:
|
|||
- apk add python3
|
||||
- apk add py3-pip
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pipeline:
|
|||
- apk add python3
|
||||
- apk add py3-pip
|
||||
- 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:
|
||||
when:
|
||||
|
|
|
|||
121
CHANGELOG.md
121
CHANGELOG.md
|
|
@ -1,6 +1,8 @@
|
|||
# Changelog
|
||||
|
||||
* [Unreleased](#unreleased)
|
||||
* [1.13.1](#1-13-1)
|
||||
* [1.13.0](#1-13-0)
|
||||
* [1.12.1](#1-12-1)
|
||||
* [1.12.0](#1-12-0)
|
||||
* [1.11.0](#1-11-0)
|
||||
|
|
@ -43,6 +45,80 @@
|
|||
|
||||
### 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]).
|
||||
* `-Dsystemd-units-dir=<path>` meson command line option.
|
||||
* Support for custom environment variables in `foot.ini`
|
||||
|
|
@ -50,10 +126,21 @@
|
|||
* Support for jumping to previous/next prompt (requires shell
|
||||
integration). By default bound to `ctrl`+`shift`+`z` and
|
||||
`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
|
||||
[1070]: https://codeberg.org/dnkl/foot/issues/1070
|
||||
[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
|
||||
|
|
@ -64,12 +151,13 @@
|
|||
mode ([#1084][1084]).
|
||||
* NUL is now stripped when pasting in non-bracketed mode
|
||||
([#1084][1084]).
|
||||
* `alt`+`escape` now emits `\E\E` instead of a `CSI 27` sequence
|
||||
([#1105][1105]).
|
||||
|
||||
[1084]: https://codeberg.org/dnkl/foot/issues/1084
|
||||
[1105]: https://codeberg.org/dnkl/foot/issues/1105
|
||||
|
||||
|
||||
### Deprecated
|
||||
### Removed
|
||||
### Fixed
|
||||
|
||||
* 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)
|
||||
allowing drag-and-drops even though foot has reported it does not
|
||||
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
|
||||
[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
|
||||
|
||||
* Craig Barnes
|
||||
* Lorenz
|
||||
* Max Gautier
|
||||
* Simon Ser
|
||||
* Stefan Prosiegel
|
||||
|
||||
|
||||
## 1.12.1
|
||||
|
||||
|
|
|
|||
23
INSTALL.md
23
INSTALL.md
|
|
@ -44,6 +44,8 @@ subprojects.
|
|||
* pixman
|
||||
* wayland (_client_ and _cursor_ libraries)
|
||||
* xkbcommon
|
||||
* utf8proc (_optional_, needed for grapheme clustering)
|
||||
* libutempter (_optional_, needed for utmp logging)
|
||||
* [fcft](https://codeberg.org/dnkl/fcft) [^1]
|
||||
|
||||
[^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:
|
||||
|
||||
| Option | Type | Default | Description | Extra dependencies |
|
||||
|--------------------------------------|---------|-------------------------|-------------------------------------------------------|--------------------|
|
||||
| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc |
|
||||
| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | none |
|
||||
| `-Dime` | bool | `true` | Enables IME support | None |
|
||||
| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc |
|
||||
| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) |
|
||||
| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | none |
|
||||
| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None |
|
||||
| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None |
|
||||
| Option | Type | Default | Description | Extra dependencies |
|
||||
|--------------------------------------|---------|-------------------------|-----------------------------------------------------------|--------------------|
|
||||
| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc |
|
||||
| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | none |
|
||||
| `-Dime` | bool | `true` | Enables IME support | None |
|
||||
| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc |
|
||||
| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) |
|
||||
| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | none |
|
||||
| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None |
|
||||
| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None |
|
||||
| `-Ddefault-utempter-path` | feature | `auto` | Default path to utempter binary (‘none’ disables default) | libutempter |
|
||||
|
||||
Documentation includes the man pages, readme, changelog and license
|
||||
files.
|
||||
|
|
|
|||
22
client.c
22
client.c
|
|
@ -408,6 +408,28 @@ main(int argc, char *const *argv)
|
|||
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) {
|
||||
for (char **e = environ; *e != NULL; e++) {
|
||||
if (!push_string(&envp, *e, &total_len))
|
||||
|
|
|
|||
97
config.c
97
config.c
|
|
@ -30,8 +30,8 @@
|
|||
#include "xmalloc.h"
|
||||
#include "xsnprintf.h"
|
||||
|
||||
static const uint32_t default_foreground = 0xdcdccc;
|
||||
static const uint32_t default_background = 0x111111;
|
||||
static const uint32_t default_foreground = 0x839496;
|
||||
static const uint32_t default_background = 0x002b36;
|
||||
|
||||
static const size_t min_csd_border_width = 5;
|
||||
|
||||
|
|
@ -48,23 +48,23 @@ static const size_t min_csd_border_width = 5;
|
|||
|
||||
static const uint32_t default_color_table[256] = {
|
||||
// Regular
|
||||
0x222222,
|
||||
0xcc9393,
|
||||
0x7f9f7f,
|
||||
0xd0bf8f,
|
||||
0x6ca0a3,
|
||||
0xdc8cc3,
|
||||
0x93e0e3,
|
||||
0xdcdccc,
|
||||
0x073642,
|
||||
0xdc322f,
|
||||
0x859900,
|
||||
0xb58900,
|
||||
0x268bd2,
|
||||
0xd33682,
|
||||
0x2aa198,
|
||||
0xeee8d5,
|
||||
|
||||
// Bright
|
||||
0x666666,
|
||||
0xdca3a3,
|
||||
0xbfebbf,
|
||||
0xf0dfaf,
|
||||
0x8cd0d3,
|
||||
0xfcace3,
|
||||
0xb3ffff,
|
||||
0x08404f,
|
||||
0xe35f5c,
|
||||
0x9fb700,
|
||||
0xd9a400,
|
||||
0x4ba1de,
|
||||
0xdc619d,
|
||||
0x32c1b6,
|
||||
0xffffff,
|
||||
|
||||
// 6x6x6 RGB cube
|
||||
|
|
@ -117,6 +117,7 @@ static const char *const binding_action_map[] = {
|
|||
[BIND_ACTION_TEXT_BINDING] = "text-binding",
|
||||
[BIND_ACTION_PROMPT_PREV] = "prompt-prev",
|
||||
[BIND_ACTION_PROMPT_NEXT] = "prompt-next",
|
||||
[BIND_ACTION_UNICODE_INPUT] = "unicode-input",
|
||||
|
||||
/* Mouse-specific actions */
|
||||
[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_CLIPBOARD_PASTE] = "clipboard-paste",
|
||||
[BIND_ACTION_SEARCH_PRIMARY_PASTE] = "primary-paste",
|
||||
[BIND_ACTION_SEARCH_UNICODE_INPUT] = "unicode-input",
|
||||
};
|
||||
|
||||
static const char *const url_binding_action_map[] = {
|
||||
|
|
@ -903,6 +905,9 @@ parse_section_main(struct context *ctx)
|
|||
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) {
|
||||
if (strcmp(value, "auto") == 0)
|
||||
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)
|
||||
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 {
|
||||
LOG_CONTEXTUAL_ERR("not a valid option: %s", key);
|
||||
return false;
|
||||
|
|
@ -1172,6 +1189,34 @@ parse_section_colors(struct context *ctx)
|
|||
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) {
|
||||
if (!value_to_color(ctx, &conf->colors.url, 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 &&
|
||||
binding1->m.count == binding2->m.count);
|
||||
break;
|
||||
|
||||
default:
|
||||
BUG("unhandled key binding type");
|
||||
}
|
||||
|
||||
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))
|
||||
error_or_continue();
|
||||
|
||||
/* For next iteration of getline() */
|
||||
errno = 0;
|
||||
}
|
||||
|
||||
if (errno != 0) {
|
||||
|
|
@ -2787,8 +2838,8 @@ config_load(struct config *conf, const char *conf_path,
|
|||
.width = 700,
|
||||
.height = 500,
|
||||
},
|
||||
.pad_x = 2,
|
||||
.pad_y = 2,
|
||||
.pad_x = 0,
|
||||
.pad_y = 0,
|
||||
.resize_delay_ms = 100,
|
||||
.bold_in_bright = {
|
||||
.enabled = false,
|
||||
|
|
@ -2802,6 +2853,7 @@ config_load(struct config *conf, const char *conf_path,
|
|||
.vertical_letter_offset = {.pt = 0, .px = 0},
|
||||
.use_custom_underline_offset = false,
|
||||
.box_drawings_uses_font_glyphs = false,
|
||||
.underline_thickness = {.pt = 0., .px = -1},
|
||||
.dpi_aware = DPI_AWARE_AUTO, /* DPI-aware when scaling-factor == 1 */
|
||||
.bell = {
|
||||
.urgent = false,
|
||||
|
|
@ -2899,6 +2951,9 @@ config_load(struct config *conf, const char *conf_path,
|
|||
},
|
||||
|
||||
.env_vars = tll_init(),
|
||||
.utempter_path = (strlen(FOOT_DEFAULT_UTEMPTER_PATH) > 0
|
||||
? xstrdup(FOOT_DEFAULT_UTEMPTER_PATH)
|
||||
: NULL),
|
||||
.notifications = tll_init(),
|
||||
};
|
||||
|
||||
|
|
@ -3187,6 +3242,9 @@ config_clone(const struct config *old)
|
|||
tll_push_back(conf->env_vars, copy);
|
||||
}
|
||||
|
||||
conf->utempter_path =
|
||||
old->utempter_path != NULL ? xstrdup(old->utempter_path) : NULL;
|
||||
|
||||
conf->notifications.length = 0;
|
||||
conf->notifications.head = conf->notifications.tail = 0;
|
||||
tll_foreach(old->notifications, it) {
|
||||
|
|
@ -3253,6 +3311,7 @@ config_free(struct config *conf)
|
|||
tll_remove(conf->env_vars, it);
|
||||
}
|
||||
|
||||
free(conf->utempter_path);
|
||||
user_notifications_free(&conf->notifications);
|
||||
}
|
||||
|
||||
|
|
|
|||
17
config.h
17
config.h
|
|
@ -150,6 +150,7 @@ struct config {
|
|||
|
||||
bool use_custom_underline_offset;
|
||||
struct pt_or_px underline_offset;
|
||||
struct pt_or_px underline_thickness;
|
||||
|
||||
bool box_drawings_uses_font_glyphs;
|
||||
bool can_shape_grapheme;
|
||||
|
|
@ -217,11 +218,25 @@ struct config {
|
|||
uint32_t bg;
|
||||
} scrollback_indicator;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
} no_match;
|
||||
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
} match;
|
||||
} search_box;
|
||||
|
||||
struct {
|
||||
bool selection:1;
|
||||
bool jump_label:1;
|
||||
bool scrollback_indicator:1;
|
||||
bool url:1;
|
||||
bool search_box_no_match:1;
|
||||
bool search_box_match:1;
|
||||
uint8_t dim;
|
||||
} use_custom;
|
||||
} colors;
|
||||
|
|
@ -304,6 +319,8 @@ struct config {
|
|||
|
||||
env_var_list_t env_vars;
|
||||
|
||||
char *utempter_path;
|
||||
|
||||
struct {
|
||||
enum fcft_scaling_filter fcft_filter;
|
||||
bool overflowing_glyphs;
|
||||
|
|
|
|||
106
csi.c
106
csi.c
|
|
@ -128,7 +128,8 @@ csi_sgr(struct terminal *term)
|
|||
term->vt.params.v[i + 1].value == 5)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +150,8 @@ csi_sgr(struct terminal *term)
|
|||
term->vt.params.v[i].sub.value[0] == 5)
|
||||
{
|
||||
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);
|
||||
term_damage_all(term);
|
||||
term_damage_view(term);
|
||||
}
|
||||
term_update_ascii_printer(term);
|
||||
break;
|
||||
|
|
@ -533,47 +535,65 @@ decrst(struct terminal *term, unsigned param)
|
|||
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) {
|
||||
case 1: *enabled = term->cursor_keys_mode == CURSOR_KEYS_APPLICATION; return true;
|
||||
case 3: *enabled = false; return true;
|
||||
case 4: *enabled = false; return true;
|
||||
case 5: *enabled = term->reverse; return true;
|
||||
case 6: *enabled = term->origin; return true;
|
||||
case 7: *enabled = term->auto_margin; return true;
|
||||
case 9: *enabled = false; /* term->mouse_tracking == MOUSE_X10; */ return true;
|
||||
case 12: *enabled = term->cursor_blink.decset; return true;
|
||||
case 25: *enabled = !term->hide_cursor; return true;
|
||||
case 45: *enabled = term->reverse_wrap; return true;
|
||||
case 66: *enabled = term->keypad_keys_mode == KEYPAD_APPLICATION; return true;
|
||||
case 80: *enabled = !term->sixel.scrolling; return true;
|
||||
case 1000: *enabled = term->mouse_tracking == MOUSE_CLICK; return true;
|
||||
case 1001: *enabled = false; return true;
|
||||
case 1002: *enabled = term->mouse_tracking == MOUSE_DRAG; return true;
|
||||
case 1003: *enabled = term->mouse_tracking == MOUSE_MOTION; return true;
|
||||
case 1004: *enabled = term->focus_events; return true;
|
||||
case 1005: *enabled = false; /* term->mouse_reporting == MOUSE_UTF8; */ return true;
|
||||
case 1006: *enabled = term->mouse_reporting == MOUSE_SGR; return true;
|
||||
case 1007: *enabled = term->alt_scrolling; return true;
|
||||
case 1015: *enabled = term->mouse_reporting == MOUSE_URXVT; return true;
|
||||
case 1016: *enabled = term->mouse_reporting == MOUSE_SGR_PIXELS; return true;
|
||||
case 1034: *enabled = term->meta.eight_bit; return true;
|
||||
case 1035: *enabled = term->num_lock_modifier; return true;
|
||||
case 1036: *enabled = term->meta.esc_prefix; return true;
|
||||
case 1042: *enabled = term->bell_action_enabled; return true;
|
||||
case 1: return decrpm(term->cursor_keys_mode == CURSOR_KEYS_APPLICATION);
|
||||
case 3: return DECRPM_PERMANENTLY_RESET;
|
||||
case 4: return DECRPM_PERMANENTLY_RESET;
|
||||
case 5: return decrpm(term->reverse);
|
||||
case 6: return decrpm(term->origin);
|
||||
case 7: return decrpm(term->auto_margin);
|
||||
case 9: return DECRPM_PERMANENTLY_RESET; /* term->mouse_tracking == MOUSE_X10; */
|
||||
case 12: return decrpm(term->cursor_blink.decset);
|
||||
case 25: return decrpm(!term->hide_cursor);
|
||||
case 45: return decrpm(term->reverse_wrap);
|
||||
case 66: return decrpm(term->keypad_keys_mode == KEYPAD_APPLICATION);
|
||||
case 80: return decrpm(!term->sixel.scrolling);
|
||||
case 1000: return decrpm(term->mouse_tracking == MOUSE_CLICK);
|
||||
case 1001: return DECRPM_PERMANENTLY_RESET;
|
||||
case 1002: return decrpm(term->mouse_tracking == MOUSE_DRAG);
|
||||
case 1003: return decrpm(term->mouse_tracking == MOUSE_MOTION);
|
||||
case 1004: return decrpm(term->focus_events);
|
||||
case 1005: return DECRPM_PERMANENTLY_RESET; /* term->mouse_reporting == MOUSE_UTF8; */
|
||||
case 1006: return decrpm(term->mouse_reporting == MOUSE_SGR);
|
||||
case 1007: return decrpm(term->alt_scrolling);
|
||||
case 1015: return decrpm(term->mouse_reporting == MOUSE_URXVT);
|
||||
case 1016: return decrpm(term->mouse_reporting == MOUSE_SGR_PIXELS);
|
||||
case 1034: return decrpm(term->meta.eight_bit);
|
||||
case 1035: return decrpm(term->num_lock_modifier);
|
||||
case 1036: return decrpm(term->meta.esc_prefix);
|
||||
case 1042: return decrpm(term->bell_action_enabled);
|
||||
case 47: /* FALLTHROUGH */
|
||||
case 1047: /* FALLTHROUGH */
|
||||
case 1049: *enabled = term->grid == &term->alt; return true;
|
||||
case 1070: *enabled = term->sixel.use_private_palette; return true;
|
||||
case 2004: *enabled = term->bracketed_paste; return true;
|
||||
case 2026: *enabled = term->render.app_sync_updates.enabled; return true;
|
||||
case 8452: *enabled = term->sixel.cursor_right_of_graphics; return true;
|
||||
case 737769: *enabled = term_ime_is_enabled(term); return true;
|
||||
case 1049: return decrpm(term->grid == &term->alt);
|
||||
case 1070: return decrpm(term->sixel.use_private_palette);
|
||||
case 2004: return decrpm(term->bracketed_paste);
|
||||
case 2026: return decrpm(term->render.app_sync_updates.enabled);
|
||||
case 8452: return decrpm(term->sixel.cursor_right_of_graphics);
|
||||
case 737769: return decrpm(term_ime_is_enabled(term));
|
||||
}
|
||||
|
||||
return false;
|
||||
return DECRPM_NOT_RECOGNIZED;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1719,15 +1739,9 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
* 3 - permanently set
|
||||
* 4 - permantently reset
|
||||
*/
|
||||
bool enabled;
|
||||
unsigned value;
|
||||
if (decrqm(term, param, &enabled))
|
||||
value = enabled ? 1 : 2;
|
||||
else
|
||||
value = 0;
|
||||
|
||||
unsigned status = decrqm(term, param);
|
||||
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);
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,18 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*.
|
|||
|
||||
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/line drawing characters itself. The are several advantages to
|
||||
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
|
||||
center*).
|
||||
|
||||
Default: _2x2_.
|
||||
Default: _0x0_.
|
||||
|
||||
*resize-delay-ms*
|
||||
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
|
||||
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
|
||||
|
||||
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*
|
||||
Default foreground color. This is the color used when no ANSI
|
||||
color is being used. Default: _dcdccc_.
|
||||
color is being used. Default: _839496_.
|
||||
|
||||
*background*
|
||||
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*
|
||||
The eight basic ANSI colors (Black, Red, Green, Yellow, Blue,
|
||||
Magenta, Cyan, White). Default: _222222_, _cc9393_, _7f9f7f_, _d0bf8f_,
|
||||
_6ca0a3_, _dc8cc3_, _93e0e3_ and _dcdccc_ (a variant of the _zenburn_ theme).
|
||||
Magenta, Cyan, White). Default: _073642_, _dc322f_, _859900_,
|
||||
_b58900_, _268bd2_, _d33682_, _2aa198_ and _eee8d5_ (a variant of
|
||||
the _solarized dark_ theme).
|
||||
|
||||
*bright0*, *bright1* *..* *bright7*
|
||||
The eight bright ANSI colors (Black, Red, Green, Yellow, Blue,
|
||||
Magenta, Cyan, White). Default: _666666_, _dca3a3_, _bfebbf_, _f0dfaf_,
|
||||
_8cd0d3_, _fcace3_, _b3ffff_ and _ffffff_ (a variant of the _zenburn_ theme).
|
||||
Magenta, Cyan, White). Default: _08404f_, _e35f5c_, _9fb700_,
|
||||
_d9a400_, _4ba1de_, _dc619d_, _32c1b6_ and _ffffff_ (a variant of
|
||||
the _solarized dark_ theme).
|
||||
|
||||
*dim0*, *dim1* *..* *dim7*
|
||||
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:
|
||||
_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*
|
||||
Color to use for the underline used to highlight URLs in URL
|
||||
mode. Default: _regular3_.
|
||||
|
|
@ -781,6 +809,32 @@ e.g. *search-start=none*.
|
|||
Jump the next prompt (requires shell integration, see
|
||||
*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
|
||||
|
||||
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
|
||||
buffer. Default: _Shift+Insert_.
|
||||
|
||||
*unicode-input*
|
||||
Unicode input mode. See _key-bindings.unicode-input_ for
|
||||
details. Default: _none_.
|
||||
|
||||
# SECTION: url-bindings
|
||||
|
||||
This section lets you override the default key bindings used in URL
|
||||
|
|
|
|||
|
|
@ -2,9 +2,16 @@ sh = find_program('sh', 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(
|
||||
{
|
||||
'default_terminfo': get_option('default-terminfo'),
|
||||
'utempter': default_utempter_value,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
[Service]
|
||||
ExecStart=@bindir@/foot --server=0
|
||||
ExecStart=@bindir@/foot --server=3
|
||||
Environment=WAYLAND_DISPLAY=%i
|
||||
UnsetEnvironment=LISTEN_PID LISTEN_FDS LISTEN_FDNAMES
|
||||
NonBlocking=true
|
||||
StandardInput=socket
|
||||
|
||||
[Unit]
|
||||
Requires=%N.socket
|
||||
|
|
|
|||
|
|
@ -218,6 +218,8 @@
|
|||
knp=\E[6~,
|
||||
kpp=\E[5~,
|
||||
kri=\E[1;2A,
|
||||
kxIN=\E[I,
|
||||
kxOUT=\E[O,
|
||||
oc=\E]104\E\\,
|
||||
op=\E[39;49m,
|
||||
rc=\E8,
|
||||
|
|
|
|||
46
foot.ini
46
foot.ini
|
|
@ -17,13 +17,14 @@
|
|||
# horizontal-letter-offset=0
|
||||
# vertical-letter-offset=0
|
||||
# underline-offset=<font metrics>
|
||||
# underline-thickness=<font underline thickness>
|
||||
# box-drawings-uses-font-glyphs=no
|
||||
# dpi-aware=auto
|
||||
|
||||
# initial-window-size-pixels=700x500 # Or,
|
||||
# initial-window-size-chars=<COLSxROWS>
|
||||
# initial-window-mode=windowed
|
||||
# pad=2x2 # optionally append 'center'
|
||||
# pad=0x0 # optionally append 'center'
|
||||
# resize-delay-ms=100
|
||||
|
||||
# notify=notify-send -a ${app-id} -i ${app-id} ${title} ${body}
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
# word-delimiters=,│`|:"'()[]{}<>
|
||||
# selection-target=primary
|
||||
# workers=<number of logical CPUs>
|
||||
# utempter=/usr/lib/utempter/utempter
|
||||
|
||||
[environment]
|
||||
# name=value
|
||||
|
|
@ -68,27 +70,27 @@
|
|||
|
||||
[colors]
|
||||
# alpha=1.0
|
||||
# foreground=dcdccc
|
||||
# background=111111
|
||||
# background=002b36
|
||||
# foreground=839496
|
||||
|
||||
## Normal/regular colors (color palette 0-7)
|
||||
# regular0=222222 # black
|
||||
# regular1=cc9393 # red
|
||||
# regular2=7f9f7f # green
|
||||
# regular3=d0bf8f # yellow
|
||||
# regular4=6ca0a3 # blue
|
||||
# regular5=dc8cc3 # magenta
|
||||
# regular6=93e0e3 # cyan
|
||||
# regular7=dcdccc # white
|
||||
# regular0=073642 # black
|
||||
# regular1=dc322f # red
|
||||
# regular2=859900 # green
|
||||
# regular3=b58900 # yellow
|
||||
# regular4=268bd2 # blue
|
||||
# regular5=d33682 # magenta
|
||||
# regular6=2aa198 # cyan
|
||||
# regular7=eee8d5 # white
|
||||
|
||||
## Bright colors (color palette 8-15)
|
||||
# bright0=666666 # bright black
|
||||
# bright1=dca3a3 # bright red
|
||||
# bright2=bfebbf # bright green
|
||||
# bright3=f0dfaf # bright yellow
|
||||
# bright4=8cd0d3 # bright blue
|
||||
# bright5=fcace3 # bright magenta
|
||||
# bright6=b3ffff # bright cyan
|
||||
# bright0=08404f # bright black
|
||||
# bright1=e35f5c # bright red
|
||||
# bright2=9fb700 # bright green
|
||||
# bright3=d9a400 # bright yellow
|
||||
# bright4=4ba1de # bright blue
|
||||
# bright5=dc619d # bright magenta
|
||||
# bright6=32c1b6 # bright cyan
|
||||
# bright7=ffffff # bright white
|
||||
|
||||
## dimmed colors (see foot.ini(5) man page)
|
||||
|
|
@ -104,9 +106,11 @@
|
|||
## Misc colors
|
||||
# selection-foreground=<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>
|
||||
# scrollback-indicator=<regular0> <bright4>
|
||||
|
||||
[csd]
|
||||
# preferred=server
|
||||
|
|
@ -148,6 +152,7 @@
|
|||
# show-urls-persistent=none
|
||||
# prompt-prev=Control+Shift+z
|
||||
# prompt-next=Control+Shift+x
|
||||
# unicode-input=none
|
||||
# noop=none
|
||||
|
||||
[search-bindings]
|
||||
|
|
@ -170,6 +175,7 @@
|
|||
# extend-to-end-line=Control+l
|
||||
# clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste
|
||||
# primary-paste=Shift+Insert
|
||||
# unicode-input=none
|
||||
|
||||
[url-bindings]
|
||||
# cancel=Control+g Control+c Control+d Escape
|
||||
|
|
|
|||
65
grid.c
65
grid.c
|
|
@ -318,7 +318,7 @@ grid_row_alloc(int cols, bool initialize)
|
|||
{
|
||||
struct row *row = xmalloc(sizeof(*row));
|
||||
row->dirty = false;
|
||||
row->linebreak = true;
|
||||
row->linebreak = false;
|
||||
row->extra = NULL;
|
||||
row->prompt_marker = false;
|
||||
|
||||
|
|
@ -538,7 +538,7 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row,
|
|||
} else {
|
||||
/* Scrollback is full, need to re-use a row */
|
||||
grid_row_reset_extra(new_row);
|
||||
new_row->linebreak = true;
|
||||
new_row->linebreak = false;
|
||||
new_row->prompt_marker = false;
|
||||
|
||||
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) {
|
||||
/* Don’t truncate logical lines */
|
||||
col_count = old_cols;
|
||||
}
|
||||
|
|
@ -878,14 +878,6 @@ grid_resize_and_reflow(
|
|||
&new_row->cells[new_col_idx], &old_row->cells[from],
|
||||
amount * sizeof(struct cell));
|
||||
|
||||
/*
|
||||
* We’ve “printed” to this line - reset linebreak.
|
||||
*
|
||||
* If the old line ends with a hard linebreak, we’ll
|
||||
* set linebreak=true on the last new row we print to.
|
||||
*/
|
||||
new_row->linebreak = false;
|
||||
|
||||
count -= amount;
|
||||
from += amount;
|
||||
new_col_idx += amount;
|
||||
|
|
@ -943,13 +935,29 @@ grid_resize_and_reflow(
|
|||
start += cols;
|
||||
}
|
||||
|
||||
|
||||
if (old_row->linebreak && col_count > 0) {
|
||||
if (old_row->linebreak) {
|
||||
/* Erase the remaining cells */
|
||||
memset(&new_row->cells[new_col_idx], 0,
|
||||
(new_cols - new_col_idx) * sizeof(new_row->cells[0]));
|
||||
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 we’re line-breaking due
|
||||
* to a hard line-break (rather than running out of
|
||||
* cells in the "new_row"), there shouldn’t 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]);
|
||||
|
|
@ -992,6 +1000,7 @@ grid_resize_and_reflow(
|
|||
|
||||
/* Set offset such that the last reflowed row is at the bottom */
|
||||
grid->offset = new_row_idx - new_screen_rows + 1;
|
||||
|
||||
while (grid->offset < 0)
|
||||
grid->offset += new_rows;
|
||||
while (new_grid[grid->offset] == NULL)
|
||||
|
|
@ -1004,23 +1013,6 @@ grid_resize_and_reflow(
|
|||
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(grid->rows);
|
||||
|
||||
|
|
@ -1028,6 +1020,17 @@ grid_resize_and_reflow(
|
|||
grid->num_rows = new_rows;
|
||||
grid->num_cols = new_cols;
|
||||
|
||||
/*
|
||||
* Set new viewport, making sure it’s 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 */
|
||||
cursor.row -= grid->offset;
|
||||
while (cursor.row < 0)
|
||||
|
|
|
|||
42
input.c
42
input.c
|
|
@ -36,6 +36,7 @@
|
|||
#include "spawn.h"
|
||||
#include "terminal.h"
|
||||
#include "tokenize.h"
|
||||
#include "unicode-mode.h"
|
||||
#include "url-mode.h"
|
||||
#include "util.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))
|
||||
goto pipe_err;
|
||||
|
||||
|
|
@ -416,6 +417,10 @@ execute_binding(struct seat *seat, struct terminal *term,
|
|||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_UNICODE_INPUT:
|
||||
unicode_mode_activate(seat);
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_SELECT_BEGIN:
|
||||
selection_start(
|
||||
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",
|
||||
seat->name, (void *)wl_keyboard, serial, (void *)surface);
|
||||
|
||||
if (seat->kbd.xkb == NULL)
|
||||
return;
|
||||
|
||||
term_kbd_focus_in(term);
|
||||
seat->kbd_focus = term;
|
||||
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",
|
||||
(void *)wl_keyboard, serial, (void *)surface);
|
||||
|
||||
if (seat->kbd.xkb == NULL)
|
||||
return;
|
||||
|
||||
xassert(
|
||||
seat->kbd_focus == NULL ||
|
||||
surface == NULL || /* Seen on Sway 1.2 */
|
||||
|
|
@ -1245,7 +1244,7 @@ emit_escapes:
|
|||
? ctx->level0_syms.syms[0]
|
||||
: sym;
|
||||
|
||||
if (composed && is_text)
|
||||
if (composed)
|
||||
key = utf32;
|
||||
else {
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
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,
|
||||
wl_fixed_t surface_x, wl_fixed_t surface_y)
|
||||
{
|
||||
xassert(surface != NULL);
|
||||
xassert(serial != 0);
|
||||
|
||||
if (surface == NULL) {
|
||||
if (unlikely(surface == NULL)) {
|
||||
/* Seen on mutter-3.38 */
|
||||
LOG_WARN("compositor sent pointer_enter event with a NULL surface");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1862,6 +1864,16 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
|
|||
struct seat *seat = data;
|
||||
struct wayland *wayl = seat->wayl;
|
||||
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
|
||||
* can’t map the enter event to a specific window). */
|
||||
return;
|
||||
}
|
||||
|
||||
struct wl_window *win = term->window;
|
||||
|
||||
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 */
|
||||
|
||||
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);
|
||||
|
||||
xkb_mod_mask_t mods;
|
||||
|
|
|
|||
|
|
@ -80,17 +80,14 @@ key_binding_new_for_seat(struct key_binding_manager *mgr,
|
|||
}
|
||||
|
||||
void
|
||||
key_binding_new_for_term(struct key_binding_manager *mgr,
|
||||
const struct terminal *term)
|
||||
key_binding_new_for_conf(struct key_binding_manager *mgr,
|
||||
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) {
|
||||
struct seat *seat = &it->item;
|
||||
|
||||
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) {
|
||||
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 */
|
||||
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 *)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));
|
||||
}
|
||||
|
||||
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 config *conf = term->conf;
|
||||
|
||||
struct key_set *last_used = mgr->last_used_set;
|
||||
if (last_used != NULL &&
|
||||
last_used->conf == conf &&
|
||||
|
|
@ -192,11 +187,8 @@ key_binding_remove_seat(struct key_binding_manager *mgr,
|
|||
}
|
||||
|
||||
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)
|
||||
{
|
||||
const struct config *conf = term->conf;
|
||||
|
||||
tll_foreach(mgr->binding_sets, it) {
|
||||
struct key_set *set = &it->item;
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ enum bind_action_normal {
|
|||
BIND_ACTION_TEXT_BINDING,
|
||||
BIND_ACTION_PROMPT_PREV,
|
||||
BIND_ACTION_PROMPT_NEXT,
|
||||
BIND_ACTION_UNICODE_INPUT,
|
||||
|
||||
/* Mouse specific actions - i.e. they require a mouse coordinate */
|
||||
BIND_ACTION_SELECT_BEGIN,
|
||||
|
|
@ -48,7 +49,7 @@ enum bind_action_normal {
|
|||
BIND_ACTION_SELECT_WORD_WS,
|
||||
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,
|
||||
};
|
||||
|
||||
|
|
@ -73,6 +74,7 @@ enum bind_action_search {
|
|||
BIND_ACTION_SEARCH_EXTEND_LINE,
|
||||
BIND_ACTION_SEARCH_CLIPBOARD_PASTE,
|
||||
BIND_ACTION_SEARCH_PRIMARY_PASTE,
|
||||
BIND_ACTION_SEARCH_UNICODE_INPUT,
|
||||
BIND_ACTION_SEARCH_COUNT,
|
||||
};
|
||||
|
||||
|
|
@ -109,6 +111,7 @@ typedef tll(struct key_binding) key_binding_list_t;
|
|||
|
||||
struct terminal;
|
||||
struct seat;
|
||||
struct wayland;
|
||||
|
||||
struct key_binding_set {
|
||||
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(
|
||||
struct key_binding_manager *mgr, const struct seat *seat);
|
||||
|
||||
void key_binding_new_for_term(
|
||||
struct key_binding_manager *mgr, const struct terminal *term);
|
||||
void key_binding_new_for_conf(
|
||||
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_manager *mgr, const struct terminal *term,
|
||||
struct key_binding_manager *mgr, const struct config *conf,
|
||||
const struct seat *seat);
|
||||
|
||||
/* Remove all key bindings tied to the specified seat */
|
||||
void key_binding_remove_seat(
|
||||
struct key_binding_manager *mgr, const struct seat *seat);
|
||||
|
||||
void key_binding_unref_term(
|
||||
struct key_binding_manager *mgr, const struct terminal *term);
|
||||
void key_binding_unref(
|
||||
struct key_binding_manager *mgr, const struct config *conf);
|
||||
|
||||
void key_binding_load_keymap(
|
||||
struct key_binding_manager *mgr, const struct seat *seat);
|
||||
|
|
|
|||
2
keymap.h
2
keymap.h
|
|
@ -24,7 +24,7 @@ struct key_data {
|
|||
|
||||
static const struct key_data key_escape[] = {
|
||||
{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_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;5;27~"},
|
||||
{MOD_SHIFT | MOD_CTRL, CURSOR_KEYS_DONTCARE, KEYPAD_DONTCARE, "\033[27;6;27~"},
|
||||
|
|
|
|||
22
main.c
22
main.c
|
|
@ -594,6 +594,28 @@ main(int argc, char *const *argv)
|
|||
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);
|
||||
|
||||
if ((fdm = fdm_init()) == NULL)
|
||||
|
|
|
|||
29
meson.build
29
meson.build
|
|
@ -1,5 +1,5 @@
|
|||
project('foot', 'c',
|
||||
version: '1.12.1',
|
||||
version: '1.13.1',
|
||||
license: 'MIT',
|
||||
meson_version: '>=0.58.0',
|
||||
default_options: [
|
||||
|
|
@ -16,9 +16,30 @@ if cc.has_function('memfd_create')
|
|||
add_project_arguments('-DMEMFD_CREATE', language: 'c')
|
||||
endif
|
||||
|
||||
utempter_path = get_option('default-utempter-path')
|
||||
if utempter_path == ''
|
||||
utempter = find_program(
|
||||
'utempter',
|
||||
required: false,
|
||||
dirs: [join_paths(get_option('prefix'), get_option('libdir'), 'utempter'),
|
||||
join_paths(get_option('prefix'), get_option('libexecdir'), 'utempter'),
|
||||
'/usr/lib/utempter',
|
||||
'/usr/libexec/utempter',
|
||||
'/lib/utempter']
|
||||
)
|
||||
if utempter.found()
|
||||
utempter_path = utempter.full_path()
|
||||
else
|
||||
utempter_path = ''
|
||||
endif
|
||||
elif utempter_path == 'none'
|
||||
utempter_path = ''
|
||||
endif
|
||||
|
||||
add_project_arguments(
|
||||
['-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
|
||||
? ['-D_DEBUG']
|
||||
: [cc.get_supported_arguments('-fno-asynchronous-unwind-tables')]) +
|
||||
|
|
@ -222,6 +243,7 @@ executable(
|
|||
'slave.c', 'slave.h',
|
||||
'spawn.c', 'spawn.h',
|
||||
'tokenize.c', 'tokenize.h',
|
||||
'unicode-mode.c', 'unicode-mode.h',
|
||||
'url-mode.c', 'url-mode.h',
|
||||
'user-notification.c', 'user-notification.h',
|
||||
'wayland.c', 'wayland.h', 'shm-formats.h',
|
||||
|
|
@ -243,7 +265,7 @@ executable(
|
|||
install: true)
|
||||
|
||||
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'))
|
||||
|
||||
systemd = dependency('systemd', required: false)
|
||||
|
|
@ -320,6 +342,7 @@ summary(
|
|||
'Themes': get_option('themes'),
|
||||
'IME': get_option('ime'),
|
||||
'Grapheme clustering': utf8proc.found(),
|
||||
'Utempter path': utempter_path,
|
||||
'Build terminfo': tic.found(),
|
||||
'Terminfo install location': terminfo_install_location,
|
||||
'Default TERM': get_option('default-terminfo'),
|
||||
|
|
|
|||
|
|
@ -21,3 +21,6 @@ option('custom-terminfo-install-location', type: 'string', value: '',
|
|||
|
||||
option('systemd-units-dir', type: 'string', value: '',
|
||||
description: 'Where to install the systemd service files (absolute path). Default: ${systemduserunitdir}')
|
||||
|
||||
option('default-utempter-path', type: 'string', value: '',
|
||||
description: 'Default path to utempter helper binary. Default: auto-detect')
|
||||
|
|
|
|||
19
misc.c
19
misc.c
|
|
@ -13,15 +13,32 @@ isword(char32_t wc, bool spaces_only, const char32_t *delimiters)
|
|||
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
|
||||
timespec_sub(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 < 0) {
|
||||
res->tv_sec--;
|
||||
res->tv_nsec += 1000 * 1000 * 1000;
|
||||
res->tv_nsec += one_sec_in_ns;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
misc.h
1
misc.h
|
|
@ -6,4 +6,5 @@
|
|||
|
||||
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);
|
||||
|
|
|
|||
45
org.codeberg.dnkl.foot.metainfo.xml
Normal file
45
org.codeberg.dnkl.foot.metainfo.xml
Normal 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>
|
||||
|
|
@ -178,15 +178,16 @@ static bool kbd_initialized = false;
|
|||
|
||||
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)
|
||||
{
|
||||
return &kbd;
|
||||
}
|
||||
|
||||
void
|
||||
key_binding_new_for_term(
|
||||
struct key_binding_manager *mgr, const struct terminal *term)
|
||||
key_binding_new_for_conf(
|
||||
struct key_binding_manager *mgr, const struct wayland *wayl,
|
||||
const struct config *conf)
|
||||
{
|
||||
if (!kbd_initialized) {
|
||||
kbd_initialized = true;
|
||||
|
|
@ -201,7 +202,7 @@ key_binding_new_for_term(
|
|||
}
|
||||
|
||||
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
252
render.c
|
|
@ -372,7 +372,10 @@ draw_underline(const struct terminal *term, pixman_image_t *pix,
|
|||
const struct fcft_font *font,
|
||||
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 */
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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(
|
||||
term->window->surface, term->margins.left, dst_y,
|
||||
term->width - term->margins.left - term->margins.right, height);
|
||||
|
||||
/*
|
||||
* TODO: remove this if re-enabling scroll damage when re-applying
|
||||
* last frame’s damage (see reapply_old_damage()
|
||||
*/
|
||||
pixman_region32_union_rect(
|
||||
&buf->dirty, &buf->dirty, 0, dst_y, buf->width, height);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1076,6 +1086,13 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf,
|
|||
wl_surface_damage_buffer(
|
||||
term->window->surface, term->margins.left, dst_y,
|
||||
term->width - term->margins.left - term->margins.right, height);
|
||||
|
||||
/*
|
||||
* TODO: remove this if re-enabling scroll damage when re-applying
|
||||
* last frame’s damage (see reapply_old_damage()
|
||||
*/
|
||||
pixman_region32_union_rect(
|
||||
&buf->dirty, &buf->dirty, 0, dst_y, buf->width, height);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1466,10 +1483,21 @@ static void
|
|||
render_overlay(struct terminal *term)
|
||||
{
|
||||
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 =
|
||||
term->is_searching ? OVERLAY_SEARCH :
|
||||
term->flash.active ? OVERLAY_FLASH :
|
||||
unicode_mode_active ? OVERLAY_UNICODE_MODE :
|
||||
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_color_t color = style == OVERLAY_SEARCH
|
||||
? (pixman_color_t){0, 0, 0, 0x7fff}
|
||||
: (pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff};
|
||||
pixman_color_t color;
|
||||
|
||||
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() */
|
||||
pixman_box32_t damage_bounds;
|
||||
|
|
@ -1517,17 +1557,18 @@ render_overlay(struct terminal *term)
|
|||
* region that needs to be *cleared* in this frame.
|
||||
*
|
||||
* 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
|
||||
* wl_surface_damage_buffer() call.
|
||||
*/
|
||||
pixman_region32_t *see_through = &term->render.last_overlay_clip;
|
||||
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 &&
|
||||
style == term->render.last_overlay_style &&
|
||||
buf->age == 0))
|
||||
{
|
||||
if (!buffer_reuse) {
|
||||
/* Can’t re-use last frame’s damage - set to full window,
|
||||
* to ensure *everything* is updated */
|
||||
pixman_region32_init_rect(
|
||||
|
|
@ -1540,8 +1581,8 @@ render_overlay(struct terminal *term)
|
|||
|
||||
pixman_region32_clear(see_through);
|
||||
|
||||
/* Build region consisting of all current search matches */
|
||||
struct search_match_iterator iter = search_matches_new_iter(term);
|
||||
|
||||
for (struct range match = search_matches_next(&iter);
|
||||
match.start.row >= 0;
|
||||
match = search_matches_next(&iter))
|
||||
|
|
@ -1569,20 +1610,28 @@ render_overlay(struct terminal *term)
|
|||
}
|
||||
}
|
||||
|
||||
/* Current see-through, minus old see-through - aka cells that
|
||||
* need to be cleared */
|
||||
/* Areas that need to be cleared: cells that were dimmed in
|
||||
* the last frame but is now see-through */
|
||||
pixman_region32_t 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);
|
||||
|
||||
/* Old see-through, minus new see-through - aka cells that
|
||||
* needs to be dimmed */
|
||||
/* Areas that need to be dimmed: cells that were cleared in
|
||||
* the last frame but is not anymore */
|
||||
pixman_region32_t new_dimmed;
|
||||
pixman_region32_init(&new_dimmed);
|
||||
pixman_region32_subtract(&new_dimmed, &old_see_through, see_through);
|
||||
pixman_region32_fini(&old_see_through);
|
||||
|
||||
/* Total affected area */
|
||||
pixman_region32_t damage;
|
||||
pixman_region32_init(&damage);
|
||||
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 &&
|
||||
style == term->render.last_overlay_style)
|
||||
{
|
||||
xassert(style == OVERLAY_FLASH);
|
||||
xassert(style == OVERLAY_FLASH || style == OVERLAY_UNICODE_MODE);
|
||||
shm_did_not_use_buf(buf);
|
||||
return;
|
||||
} 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
|
||||
? 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;
|
||||
|
||||
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;
|
||||
|
||||
switch (surf_idx) {
|
||||
|
|
@ -2510,22 +2561,27 @@ reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: remove this frame’s damage from the region we copy from
|
||||
* the old frame.
|
||||
*
|
||||
* - this frame’s dirty region is only valid *after* we’ve applied
|
||||
* its scroll damage.
|
||||
* - last frame’s dirty region is only valid *before* we’ve
|
||||
* applied this frame’s scroll damage.
|
||||
*
|
||||
* Can we transform one of the regions? It’s not trivial, since
|
||||
* scroll damage isn’t just about counting lines; there may be
|
||||
* multiple damage records, each with different scrolling regions.
|
||||
*/
|
||||
pixman_region32_t dirty;
|
||||
pixman_region32_init(&dirty);
|
||||
|
||||
/*
|
||||
* Figure out current frame’s damage region
|
||||
*
|
||||
* If current frame doesn’t have any scroll damage, we can simply
|
||||
* subtract this frame’s damage from the last frame’s damage. That
|
||||
* way, we don’t have to copy areas from the old frame that’ll
|
||||
* 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 we’re 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 don’t copy anything from the old
|
||||
* frame.
|
||||
*/
|
||||
bool full_repaint_needed = true;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < old->scroll_damage_count; i++) {
|
||||
const struct damage *dmg = &old->scroll_damage[i];
|
||||
|
||||
switch (dmg->type) {
|
||||
case DAMAGE_SCROLL:
|
||||
if (term->grid->view == term->grid->offset)
|
||||
grid_render_scroll(term, new, dmg);
|
||||
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;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* TODO: re-apply last frame’s scroll damage
|
||||
*
|
||||
* We used to do this, but it turned out to be buggy. If we decide
|
||||
* to re-add it, this is where to do it. Note that we’d also have
|
||||
* to remove the updates to buf->dirty from grid_render_scroll()
|
||||
* and grid_render_scroll_reverse().
|
||||
*/
|
||||
|
||||
if (tll_length(term->grid->scroll_damage) == 0) {
|
||||
/*
|
||||
* We can only subtract current frame’s damage from the old
|
||||
* frame’s if we don’t 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 frame’s scroll damage *first*. This is done later,
|
||||
* when rendering the frame.
|
||||
*/
|
||||
pixman_region32_subtract(&dirty, &old->dirty, &dirty);
|
||||
pixman_image_set_clip_region32(new->pix[0], &dirty);
|
||||
} else
|
||||
} else {
|
||||
/* Copy *all* of last frame’s damaged areas */
|
||||
pixman_image_set_clip_region32(new->pix[0], &old->dirty);
|
||||
}
|
||||
|
||||
pixman_image_composite32(
|
||||
PIXMAN_OP_SRC, old->pix[0], NULL, new->pix[0],
|
||||
|
|
@ -2674,38 +2726,29 @@ grid_render(struct terminal *term)
|
|||
shm_addref(buf);
|
||||
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]));
|
||||
|
||||
{
|
||||
size_t i = 0;
|
||||
tll_foreach(term->grid->scroll_damage, it) {
|
||||
buf->scroll_damage[i++] = it->item;
|
||||
|
||||
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:
|
||||
tll_foreach(term->grid->scroll_damage, it) {
|
||||
switch (it->item.type) {
|
||||
case DAMAGE_SCROLL:
|
||||
if (term->grid->view == term->grid->offset)
|
||||
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);
|
||||
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;
|
||||
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) {
|
||||
case RENDER_TIMER_LOG:
|
||||
case RENDER_TIMER_BOTH:
|
||||
LOG_INFO("frame rendered in %lds %ldns "
|
||||
"(%lds %ldns double buffering)",
|
||||
(long)render_time.tv_sec,
|
||||
render_time.tv_nsec,
|
||||
(long)double_buffering_time.tv_sec,
|
||||
double_buffering_time.tv_nsec);
|
||||
LOG_INFO(
|
||||
"frame rendered in %lds %9ldns "
|
||||
"(%lds %9ldns rendering, %lds %9ldns double buffering)",
|
||||
(long)total_render_time.tv_sec,
|
||||
total_render_time.tv_nsec,
|
||||
(long)render_time.tv_sec,
|
||||
render_time.tv_nsec,
|
||||
(long)double_buffering_time.tv_sec,
|
||||
double_buffering_time.tv_nsec);
|
||||
break;
|
||||
|
||||
case RENDER_TIMER_OSD:
|
||||
|
|
@ -2904,7 +2953,7 @@ grid_render(struct terminal *term)
|
|||
switch (term->conf->tweak.render_timer) {
|
||||
case RENDER_TIMER_OSD:
|
||||
case RENDER_TIMER_BOTH:
|
||||
render_render_timer(term, render_time);
|
||||
render_render_timer(term, total_render_time);
|
||||
break;
|
||||
|
||||
case RENDER_TIMER_LOG:
|
||||
|
|
@ -3046,10 +3095,20 @@ render_search_box(struct terminal *term)
|
|||
#define WINDOW_X(x) (margin + x)
|
||||
#define WINDOW_Y(y) (term->height - margin - height + y)
|
||||
|
||||
/* Background - yellow on empty/match, red on mismatch */
|
||||
pixman_color_t color = color_hex_to_pixman(
|
||||
term->search.match_len == text_len
|
||||
? term->colors.table[3] : term->colors.table[1]);
|
||||
const bool is_match = term->search.match_len == text_len;
|
||||
const bool custom_colors = is_match
|
||||
? term->conf->colors.use_custom.search_box_match
|
||||
: 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_OP_SRC, buf->pix[0], &color,
|
||||
|
|
@ -3065,7 +3124,12 @@ render_search_box(struct terminal *term)
|
|||
const int x_ofs = term->font_x_ofs;
|
||||
int x = x_left;
|
||||
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 */
|
||||
for (size_t i = 0, cell_idx = 0; i <= term->search.cursor; cell_idx += widths[i], i++) {
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ def main():
|
|||
|
||||
entry.add_capability(IntCapability('Co', 256))
|
||||
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
|
||||
|
||||
terminfo_parts = []
|
||||
|
|
|
|||
8
search.c
8
search.c
|
|
@ -18,6 +18,7 @@
|
|||
#include "render.h"
|
||||
#include "selection.h"
|
||||
#include "shm.h"
|
||||
#include "unicode-mode.h"
|
||||
#include "util.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
|
|
@ -371,6 +372,9 @@ find_next(struct terminal *term, enum search_direction direction,
|
|||
i += additional_chars;
|
||||
match_len += additional_chars;
|
||||
match_end_col++;
|
||||
|
||||
while (match_row->cells[match_end_col].wc > CELL_SPACER)
|
||||
match_end_col++;
|
||||
}
|
||||
|
||||
if (match_len != term->search.len) {
|
||||
|
|
@ -1000,6 +1004,10 @@ execute_binding(struct seat *seat, struct terminal *term,
|
|||
*update_search_result = *redraw = true;
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_SEARCH_UNICODE_INPUT:
|
||||
unicode_mode_activate(seat);
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_SEARCH_COUNT:
|
||||
BUG("Invalid action type");
|
||||
return true;
|
||||
|
|
|
|||
537
selection.c
537
selection.c
|
|
@ -9,6 +9,8 @@
|
|||
#include <sys/epoll.h>
|
||||
#include <sys/timerfd.h>
|
||||
|
||||
#include <pixman.h>
|
||||
|
||||
#define LOG_MODULE "selection"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
|
|
@ -64,41 +66,85 @@ selection_get_end(const struct terminal *term)
|
|||
bool
|
||||
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)",
|
||||
term->selection.coords.start.row, term->selection.coords.end.row,
|
||||
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_end += term->grid->offset;
|
||||
xassert(row_end >= row_start);
|
||||
|
||||
const struct coord *start = &term->selection.coords.start;
|
||||
const struct coord *end = &term->selection.coords.end;
|
||||
|
||||
if ((row_start <= start->row && row_end >= start->row) ||
|
||||
(row_start <= end->row && row_end >= end->row))
|
||||
const struct grid *grid = term->grid;
|
||||
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 */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* For the last check we must ensure start <= end */
|
||||
if (start->row > end->row) {
|
||||
const struct coord *tmp = start;
|
||||
start = end;
|
||||
end = tmp;
|
||||
}
|
||||
|
||||
if (row_start >= start->row && row_end <= end->row)
|
||||
if (rel_row_start >= rel_sel_start && rel_row_end <= rel_sel_end)
|
||||
return true;
|
||||
|
||||
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
|
||||
selection_view_up(struct terminal *term, int new_view)
|
||||
{
|
||||
|
|
@ -137,14 +183,14 @@ foreach_selected_normal(
|
|||
const struct coord *start = &_start;
|
||||
const struct coord *end = &_end;
|
||||
|
||||
const int scrollback_start = term->grid->offset + term->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 =
|
||||
(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 =
|
||||
(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_col, end_col;
|
||||
|
|
@ -200,14 +246,13 @@ foreach_selected_block(
|
|||
const struct coord *start = &_start;
|
||||
const struct coord *end = &_end;
|
||||
|
||||
const int scrollback_start = term->grid->offset + term->rows;
|
||||
const int grid_rows = term->grid->num_rows;
|
||||
|
||||
/* Start/end rows, relative to the scrollback start */
|
||||
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 =
|
||||
(end->row - scrollback_start + grid_rows) & (grid_rows - 1);
|
||||
grid_row_abs_to_sb(term->grid, term->rows, end->row);
|
||||
|
||||
struct coord top_left = {
|
||||
.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
|
||||
* exclude empty cells */
|
||||
struct mark_context {
|
||||
const struct row *last_row;
|
||||
int empty_count;
|
||||
uint8_t **keep_selection;
|
||||
static pixman_region32_t
|
||||
pixman_region_for_coords_normal(const struct terminal *term,
|
||||
const struct coord *start,
|
||||
const struct coord *end)
|
||||
{
|
||||
pixman_region32_t region;
|
||||
pixman_region32_init(®ion);
|
||||
|
||||
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(
|
||||
®ion, ®ion,
|
||||
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(
|
||||
®ion, ®ion,
|
||||
0, rel_start_row + 1,
|
||||
term->cols, rel_end_row - rel_start_row - 1);
|
||||
}
|
||||
|
||||
/* Last partial row (-> end) */
|
||||
pixman_region32_union_rect(
|
||||
®ion, ®ion,
|
||||
0, rel_end_row,
|
||||
end->col + 1, 1);
|
||||
|
||||
} else if (rel_start_row > rel_end_row) {
|
||||
/* First partial row (end ->) */
|
||||
pixman_region32_union_rect(
|
||||
®ion, ®ion,
|
||||
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(
|
||||
®ion, ®ion,
|
||||
0, rel_end_row + 1,
|
||||
term->cols, rel_start_row - rel_end_row - 1);
|
||||
}
|
||||
|
||||
/* Last partial row (-> start) */
|
||||
pixman_region32_union_rect(
|
||||
®ion, ®ion,
|
||||
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(
|
||||
®ion, ®ion,
|
||||
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(®ion);
|
||||
|
||||
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(
|
||||
®ion, ®ion,
|
||||
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)) {
|
||||
/* We’re 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
|
||||
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;
|
||||
ctx->empty_count = 0;
|
||||
const bool selected =
|
||||
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
|
||||
* can’t correctly update the state of empty
|
||||
* cells. The result is “random” empty cells being
|
||||
* rendered as selected when they shouldn’t.
|
||||
*
|
||||
* “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
|
||||
|
|
@ -678,33 +828,46 @@ selection_modify(struct terminal *term, struct coord start, struct coord end)
|
|||
xassert(start.row != -1 && start.col != -1);
|
||||
xassert(end.row != -1 && end.col != -1);
|
||||
|
||||
uint8_t **keep_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);
|
||||
|
||||
pixman_region32_t previous_selection;
|
||||
if (term->selection.coords.end.row >= 0) {
|
||||
/* Unmark previous selection, ignoring cells that are part of
|
||||
* the new selection */
|
||||
foreach_selected(term, term->selection.coords.start, term->selection.coords.end,
|
||||
&unmark_selected, &ctx);
|
||||
reset_modify_context(&ctx);
|
||||
}
|
||||
previous_selection = pixman_region_for_coords(
|
||||
term,
|
||||
&term->selection.coords.start,
|
||||
&term->selection.coords.end);
|
||||
} 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, ¤t_selection);
|
||||
|
||||
pixman_region32_t newly_selected;
|
||||
pixman_region32_init(&newly_selected);
|
||||
pixman_region32_subtract(
|
||||
&newly_selected, ¤t_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(¤t_selection);
|
||||
pixman_region32_fini(&previous_selection);
|
||||
|
||||
term->selection.coords.start = start;
|
||||
term->selection.coords.end = end;
|
||||
|
||||
/* Mark new selection */
|
||||
foreach_selected(term, start, end, &mark_selected, &ctx);
|
||||
render_refresh(term);
|
||||
|
||||
for (size_t i = 0; i < term->grid->num_rows; i++)
|
||||
free(keep_selection[i]);
|
||||
free(keep_selection);
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
|
||||
foreach_selected(
|
||||
term, term->selection.coords.start, term->selection.coords.end, &mark_selected,
|
||||
&(struct mark_context){0});
|
||||
pixman_region32_t selection = pixman_region_for_coords(
|
||||
term, &term->selection.coords.start, &term->selection.coords.end);
|
||||
|
||||
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
|
||||
|
|
@ -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 *end = &term->selection.coords.end;
|
||||
|
||||
if (start->row > end->row ||
|
||||
(start->row == end->row && start->col > end->col))
|
||||
const int rel_row = grid_row_abs_to_sb(term->grid, term->rows, row);
|
||||
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;
|
||||
start = end;
|
||||
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;
|
||||
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 */
|
||||
new_start = *end;
|
||||
new_end = (struct coord){col, row};
|
||||
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 */
|
||||
new_start = *start;
|
||||
new_end = (struct coord){col, row};
|
||||
|
|
@ -987,10 +1177,10 @@ selection_extend_normal(struct terminal *term, int col, int row,
|
|||
else {
|
||||
/* 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)) <
|
||||
abs(linear - (end->row * term->cols + end->col)))
|
||||
if (abs(linear - (rel_start_row * term->cols + start->col)) <
|
||||
abs(linear - (rel_end_row * term->cols + end->col)))
|
||||
{
|
||||
/* Move start point */
|
||||
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 *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 = {
|
||||
.row = min(start->row, end->row),
|
||||
.row = rel_start_row < rel_end_row ? start->row : end->row,
|
||||
.col = min(start->col, end->col),
|
||||
};
|
||||
|
||||
struct coord top_right = {
|
||||
.row = min(start->row, end->row),
|
||||
.row = top_left.row,
|
||||
.col = max(start->col, end->col),
|
||||
};
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
struct coord bottom_right = {
|
||||
.row = max(start->row, end->row),
|
||||
.row = bottom_left.row,
|
||||
.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_end;
|
||||
|
||||
enum selection_direction direction = SELECTION_UNDIR;
|
||||
|
||||
if (row <= top_left.row ||
|
||||
abs(row - top_left.row) < abs(row - bottom_left.row))
|
||||
if (rel_row <= rel_top_row ||
|
||||
abs(rel_row - rel_top_row) < abs(rel_row - rel_bottom_row))
|
||||
{
|
||||
/* 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
|
||||
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) {
|
||||
foreach_selected(
|
||||
term, term->selection.coords.start, term->selection.coords.end,
|
||||
&unmark_selected, &(struct mark_context){0});
|
||||
&unmark_selected, NULL);
|
||||
render_refresh(term);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ void selection_extend(
|
|||
|
||||
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_down(struct terminal *term, int new_view);
|
||||
|
||||
|
|
|
|||
3
shm.c
3
shm.c
|
|
@ -151,7 +151,6 @@ buffer_destroy(struct buffer_private *buf)
|
|||
pool_unref(buf->pool);
|
||||
buf->pool = NULL;
|
||||
|
||||
free(buf->public.scroll_damage);
|
||||
pixman_region32_fini(&buf->public.dirty);
|
||||
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);
|
||||
cached->busy = true;
|
||||
pixman_region32_clear(&cached->public.dirty);
|
||||
free(cached->public.scroll_damage);
|
||||
cached->public.scroll_damage = NULL;
|
||||
xassert(cached->public.pix_instances == chain->pix_instances);
|
||||
return &cached->public;
|
||||
}
|
||||
|
|
|
|||
2
shm.h
2
shm.h
|
|
@ -24,8 +24,6 @@ struct buffer {
|
|||
|
||||
unsigned age;
|
||||
|
||||
struct damage *scroll_damage;
|
||||
size_t scroll_damage_count;
|
||||
pixman_region32_t dirty;
|
||||
};
|
||||
|
||||
|
|
|
|||
1
slave.c
1
slave.c
|
|
@ -352,6 +352,7 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
|
|||
|
||||
setenv("TERM", term_env, 1);
|
||||
setenv("COLORTERM", "truecolor", 1);
|
||||
setenv("PWD", cwd, 1);
|
||||
|
||||
#if defined(FOOT_TERMINFO_PATH)
|
||||
setenv("TERMINFO", FOOT_TERMINFO_PATH, 1);
|
||||
|
|
|
|||
9
spawn.c
9
spawn.c
|
|
@ -54,9 +54,12 @@ spawn(struct reaper *reaper, const char *cwd, char *const argv[],
|
|||
goto child_err;
|
||||
}
|
||||
|
||||
if (cwd != NULL && chdir(cwd) < 0) {
|
||||
LOG_WARN("failed to change working directory to %s: %s",
|
||||
cwd, strerror(errno));
|
||||
if (cwd != NULL) {
|
||||
setenv("PWD", cwd, 1);
|
||||
if (chdir(cwd) < 0) {
|
||||
LOG_WARN("failed to change working directory to %s: %s",
|
||||
cwd, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
if (xdg_activation_token != NULL) {
|
||||
|
|
|
|||
58
terminal.c
58
terminal.c
|
|
@ -203,6 +203,30 @@ fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data)
|
|||
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
|
||||
static struct timespec last = {0};
|
||||
#endif
|
||||
|
|
@ -326,6 +350,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
|
|||
}
|
||||
|
||||
if (hup) {
|
||||
del_utmp_record(term->conf, term->reaper, term->ptmx);
|
||||
fdm_del(fdm, fd);
|
||||
term->ptmx = -1;
|
||||
}
|
||||
|
|
@ -1089,6 +1114,11 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
|
|||
goto close_fds;
|
||||
}
|
||||
|
||||
/* Need to register *very* early (before the first “goto err”), to
|
||||
* ensure term_destroy() doesn’t unref a key-binding we haven’t
|
||||
* yet ref:d */
|
||||
key_binding_new_for_conf(wayl->key_binding_manager, wayl, conf);
|
||||
|
||||
int ptmx_flags;
|
||||
if ((ptmx_flags = fcntl(ptmx, F_GETFL)) < 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;
|
||||
|
||||
add_utmp_record(conf, reaper, ptmx);
|
||||
|
||||
/* Start the slave/client */
|
||||
if ((term->slave = slave_spawn(
|
||||
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));
|
||||
|
||||
key_binding_new_for_term(wayl->key_binding_manager, term);
|
||||
|
||||
/* Initialize the Wayland window backend */
|
||||
if ((term->window = wayl_win_init(term, token)) == NULL)
|
||||
goto err;
|
||||
|
|
@ -1511,6 +1541,8 @@ term_shutdown(struct terminal *term)
|
|||
fdm_del(term->fdm, term->blink.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)
|
||||
fdm_del(term->fdm, term->ptmx);
|
||||
else
|
||||
|
|
@ -1583,7 +1615,7 @@ term_destroy(struct terminal *term)
|
|||
if (term == NULL)
|
||||
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) {
|
||||
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->render.app_sync_updates.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_cell_range(term, row, 0, term->cols - 1);
|
||||
row->linebreak = true;
|
||||
row->linebreak = 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).
|
||||
*/
|
||||
if (selection_on_top_region(term, region) ||
|
||||
selection_on_bottom_region(term, region) ||
|
||||
selection_on_rows(term, region.end - rows, region.end - 1))
|
||||
selection_on_bottom_region(term, region))
|
||||
{
|
||||
selection_cancel(term);
|
||||
}
|
||||
} else
|
||||
selection_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).
|
||||
*/
|
||||
if (selection_on_top_region(term, region) ||
|
||||
selection_on_bottom_region(term, region) ||
|
||||
selection_on_rows(term, region.start, region.start + rows - 1))
|
||||
selection_on_bottom_region(term, region))
|
||||
{
|
||||
selection_cancel(term);
|
||||
}
|
||||
} else
|
||||
selection_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);
|
||||
|
||||
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;
|
||||
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 */
|
||||
struct row *row = grid->cur_row;
|
||||
row->dirty = true;
|
||||
row->linebreak = true;
|
||||
|
||||
struct cell *cell = &row->cells[col];
|
||||
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;
|
||||
row->dirty = true;
|
||||
row->linebreak = true;
|
||||
|
||||
struct cell *cell = &row->cells[col];
|
||||
cell->wc = term->vt.last_printed = wc;
|
||||
|
|
|
|||
|
|
@ -289,9 +289,10 @@ enum term_surface {
|
|||
};
|
||||
|
||||
enum overlay_style {
|
||||
OVERLAY_NONE = 0,
|
||||
OVERLAY_SEARCH = 1,
|
||||
OVERLAY_FLASH = 2,
|
||||
OVERLAY_NONE,
|
||||
OVERLAY_SEARCH,
|
||||
OVERLAY_FLASH,
|
||||
OVERLAY_UNICODE_MODE,
|
||||
};
|
||||
|
||||
typedef tll(struct ptmx_buffer) ptmx_buffer_list_t;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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, "term", &conf.term);
|
||||
test_string(&ctx, &parse_section_main, "app-id", &conf.app_id);
|
||||
test_string(&ctx, &parse_section_main, "utempter", &conf.utempter_path);
|
||||
|
||||
test_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, "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, "underline-thickness", &conf.underline_thickness);
|
||||
|
||||
test_uint16(&ctx, &parse_section_main, "resize-delay-ms", &conf.resize_delay_ms);
|
||||
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-background", false, &conf.colors.selection_bg);
|
||||
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++) {
|
||||
char key_name[4];
|
||||
|
|
@ -627,8 +687,6 @@ test_section_colors(void)
|
|||
test_invalid_key(&ctx, &parse_section_colors, "256");
|
||||
|
||||
/* TODO: alpha (float in range 0-1, converted to uint16_t) */
|
||||
/* TODO: jump-labels (two colors) */
|
||||
/* TODO: scrollback-indicator (two colors) */
|
||||
|
||||
config_free(&conf);
|
||||
}
|
||||
|
|
|
|||
29
themes/moonfly
Normal file
29
themes/moonfly
Normal 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
29
themes/nightfly
Normal 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
106
unicode-mode.c
Normal 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
11
unicode-mode.h
Normal 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);
|
||||
16
url-mode.c
16
url-mode.c
|
|
@ -677,18 +677,23 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls)
|
|||
if (count == 0)
|
||||
return;
|
||||
|
||||
uint64_t seen_ids[count];
|
||||
char32_t *combos[count];
|
||||
generate_key_combos(conf, count, combos);
|
||||
|
||||
size_t combo_idx = 0;
|
||||
size_t id_idx = 0;
|
||||
|
||||
tll_foreach(*urls, it) {
|
||||
bool id_already_seen = false;
|
||||
|
||||
for (size_t i = 0; i < id_idx; i++) {
|
||||
if (it->item.id == seen_ids[i]) {
|
||||
/* Look for already processed URLs where both the URI and the
|
||||
* 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;
|
||||
break;
|
||||
}
|
||||
|
|
@ -696,7 +701,6 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls)
|
|||
|
||||
if (id_already_seen)
|
||||
continue;
|
||||
seen_ids[id_idx++] = it->item.id;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
61
wayland.c
61
wayland.c
|
|
@ -367,7 +367,7 @@ update_terms_on_monitor(struct monitor *mon)
|
|||
static void
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#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 = {
|
||||
.configure = &xdg_toplevel_configure,
|
||||
/*.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
|
||||
|
|
@ -896,13 +937,22 @@ handle_global(void *data, struct wl_registry *registry,
|
|||
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
|
||||
* 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->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);
|
||||
}
|
||||
|
||||
|
|
@ -1418,6 +1468,9 @@ wayl_win_init(struct terminal *term, const char *token)
|
|||
win->csd.move_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);
|
||||
if (win->surface == NULL) {
|
||||
LOG_ERR("failed to create wayland surface");
|
||||
|
|
|
|||
11
wayland.h
11
wayland.h
|
|
@ -206,6 +206,12 @@ struct seat {
|
|||
uint32_t serial;
|
||||
} ime;
|
||||
#endif
|
||||
|
||||
struct {
|
||||
bool active;
|
||||
int count;
|
||||
char32_t character;
|
||||
} unicode_mode;
|
||||
};
|
||||
|
||||
enum csd_surface {
|
||||
|
|
@ -333,6 +339,11 @@ struct wl_window {
|
|||
uint32_t serial;
|
||||
} csd;
|
||||
|
||||
struct {
|
||||
bool maximize:1;
|
||||
bool minimize:1;
|
||||
} wm_capabilities;
|
||||
|
||||
struct wl_surf_subsurf search;
|
||||
struct wl_surf_subsurf scrollback_indicator;
|
||||
struct wl_surf_subsurf render_timer;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue