Compare commits

...

1405 commits

Author SHA1 Message Date
Daniel Eklöf
037a2f4fa2
term: enqueue data to slave if there are queued paste data buffers
When writing paste data to the terminal (either interactively, or as
an OSC-52 reply), we enqueue other data (key presses, for examples, or
query replies) while the paste is happening.

The idea is to send the key press _after_ all paste data has been
written, to ensure consistency.

Unfortunately, we only checked for an on-going paste. I.e. where the
paste itself hasn't yet finished. It is also possible the paste itself
has finished, but we haven't yet flushed all the paste buffers. That
is, if we were able to *receive* paste data faster than the terminal
client was able to *consume* it. In this case, we've queued up paste
data in the terminal. These are in separate queues, and when emitting
e.g. a key press, we didn't check if all _those_ queues had been
flushed yet.

Closes #2307
2026-03-21 14:43:27 +01:00
Daniel Eklöf
2fb7bb0ea4
changelog: add new 'unreleased' section 2026-03-14 08:38:15 +01:00
Daniel Eklöf
5708a63c9a
Merge branch 'releases/1.26' 2026-03-14 08:37:52 +01:00
Daniel Eklöf
ef15414b30
meson: bump version to 1.26.1 2026-03-14 08:35:28 +01:00
Daniel Eklöf
370adaf697
changelog: prepare for 1.26.1 2026-03-14 08:35:15 +01:00
Daniel Eklöf
6f1157395b
Merge branch 'master' into releases/1.26 2026-03-14 08:34:05 +01:00
vlkrs
eed2d668ec OpenBSD has UTF-32 2026-03-12 18:47:50 +01:00
Daniel Eklöf
657db18a4e
wayland: do all surface unmap and roundtrips before waiting for pre-apply damage
The pre-apply damage thread may be running when we destroy a terminal
instance, and we need to wait for it to finish before destroying the
underlying buffer.

c291194a4e did this, but failed to
realize the thread may get re-started in the roundtrips done later in
wayl_win_destroy(); after the wait added in
c291194a4e, we unmap all
surfaces (including the main grid), and roundtrip. This means the
compositor will release the currently active buffer, and that means
_we_ will trigger the pre-apply damage thread on it. This introduces a
race, where wayl_win_destroy() may reach its shm_purge() calls before
the pre-apply damage thread has finished. That typically causes foot
to crash.

Closes #2288
2026-03-10 08:25:08 +01:00
Daniel Eklöf
4fd682b4e8
meson: clang: add -Wno-wc2y-extensions
Recent clang versions warn on __COUNTER__, unless compiling with
-std=c2y (which breaks other things).

"Fixes" '__COUNTER__' is a C2y extension (__COUNTER__ is used by our
UNITTEST macro).
2026-03-10 08:21:49 +01:00
Roshless
f49fdf7ca3 themes: paper-color-light: fix newline 2026-03-05 19:11:44 +01:00
Daniel Eklöf
c05bd55029
doc: foot.ini: fix default value of initial-color-theme
Closes #2292
2026-03-05 16:17:09 +01:00
Daniel Eklöf
ebacb14be8
changelog: add new 'unreleased' section 2026-03-03 17:38:46 +01:00
Daniel Eklöf
bee76db20c
Merge branch 'releases/1.26' 2026-03-03 17:38:07 +01:00
Daniel Eklöf
739cf115e6
meson: bump version to 1.26.0 2026-03-03 17:35:20 +01:00
Daniel Eklöf
3bbaa64cae
changelog: prepare for 1.26.0 2026-03-03 17:35:02 +01:00
Daniel Eklöf
c9c5c55745
Remove metainfo.xml
It hasn't been kept up to date for a long time, and no one seems to
care, so lets remove it.
2026-03-03 17:32:22 +01:00
Daniel Eklöf
e48178bec3
readme: update sixel screenshot
Closes #2215
2026-03-02 11:49:54 +01:00
Daniel Eklöf
046898f1b8
test: config: blur: fix test failure; use the correct parsing function 2026-03-02 09:42:31 +01:00
Daniel Eklöf
dea10e2e48
Add support for background blur
This patch adds a new config option: colors{,2}.blur=no|yes. When
enabled, transparent background are also blurred.

Note that this requires the brand new ext-background-effect-v1
protocol, and specifically, that the compositor implements the blur
effect.
2026-03-02 09:39:09 +01:00
Stéphane Klein
dc0c8550c3
Spawning new terminal with --config from parent instance
Reference: https://codeberg.org/dnkl/foot/issues/1622
Signed-off-by: Stéphane Klein <contact@stephane-klein.info>
2026-03-02 09:16:36 +01:00
pi66
21485fa66d
support four-sided padding (left/top/right/bottom) 2026-03-02 09:13:01 +01:00
nariby
fbf4304731
doc: foot.ini: mention titlebar text color in button-color 2026-03-02 08:55:50 +01:00
Barinderpreet Singh
1f31b43db7
doc: fix typos in foot.ini.5.scd 2026-03-02 08:52:44 +01:00
valoq
e24334a8df Fix cursor color
Default cursor color in alacritty is white (foreground) and not cyan.
2026-02-23 16:46:36 +01:00
Daniel Eklöf
c291194a4e
wayland: wait for pre-apply damage thread before destroying a terminal instance
It's possible, but unlikely, that we've pushed a "pre-apply damage"
job to the renderer thread queue (or that we've pushed it, and the
a thread is now working on it) when we shutdown a terminal instance.

This is sometimes caught in an assertion in term_destroy(), where we
check the queue length is 0. Other times, or in release builds, we
might crash in the thread, or in the shutdown logic when freeing the
buffer chains associated with the terminal instance.

Fix by ensuring there's no pre-apply damage operation queued, or
running, before shutting down a terminal instance.

Closes #2263
2026-02-02 12:40:50 +01:00
Daniel Eklöf
0bf193ef81
osc-8: don't log URL + ID when closing 2026-02-02 11:19:07 +01:00
Daniel Eklöf
6fbb9b7d3b
sixel: force a height of at least one sixel when explicitly resizing
Applications often prefix the sixel with a raster attributes (RA)
sequence, where they tell us how large the sixel is. Strictly
speaking, this just tells us the size of the area to clear, but we use
it as a hint and pre-allocates the image buffer.

It's important to stress that it is valid to emit a MxN RA, and then
write sixel data outside of that area.

Foot handles this, in _most_ cases. We didn't handle the corner case
Mx0. I.e. a width > 0, but height == 0. No image buffer was allocated,
and we also failed to detect a resize was necessary when the
application started printing sixel data.

Much of this is for performance reason; we only check the minimum
necessary. For example, we only check if going outside the
pre-allocated *column* while printing sixels. *Rows* are checked on a
graphical newline.

In other words, the *current* row has to be valid when writing
sixels. And in case of Mx0, it wasn't.

Fix by forcing a height of at least one sixel (typically 6 pixels).

Closes #2267
2026-01-28 09:33:16 +01:00
Daniel Eklöf
3a2eb80d83
input: ignore release events after a keyboard shortcut was triggered
This fixes an issue with the kitty keyboard protocol, where 'release'
events associated with a shortcut was sent to the client application.

Example: user triggers "scroll up". We scroll up. No key event(s) are
sent to the client application. Then the user releases the keys. we
don't do any shortcut handling on release events, and so we continue
with the normal input processing. If the kitty keyboard protocol has
been enabled (and specifically, release event reporting has been
enabled), then we'll emit a 'release' escape sequence. This in itself
is wrong, since the client application never saw the corresponding
press event. But we _also_ reset the viewport. The effect (in this
example), is that it's impossible to scroll up in the scrollback
history.

Note that we don't ignore _any_ release event, only the release event
for the (final) symbol that triggered the shortcut.

This should allow e.g. modifier keys release events to be processed
normally, if released before the shortcut key.

This is somewhat important, since the client application will have
received press events for the modifier keys leading up to the
shortcut (if modifier press/release events have been enabled in the
kitty keyboard protocol - _Report all keys as escape codes_).

Closes #2257
2026-01-28 09:31:32 +01:00
Daniel Eklöf
e2a989785a
input: execute: add missing 'return true' to a couple of switch cases
Without this, the input handling code won't understand the key/mouse
event was consumed (i.e. triggered a shortcut), and will continue
processing normally (e.g. sending event to the client application).
2026-01-10 07:35:25 +01:00
Daniel Eklöf
b78cc92322
shm: don't bother with xrgb surfaces, always use argb
Before this patch, foot used xrgb surfaces for all fully opaque
surfaces, and only used argb surfaces for the main window when the
user enabled translucency.

However, several compositors have damage-like issues when we switch
between opaque and non-opaque surfaces (for example, when switching
color theme, or when toggling fullscreen).

Since the performance benefit of using non-alpha aware surfaces are
likely minor (if there's any measurable performance difference at
all!), lets workaround these compositor issues by always using argb
surfaces.
2026-01-04 18:59:20 +01:00
Daniel Eklöf
42e04c5c87
csi: secondary DA: fix comment; we don't use an XTerm version number 2025-12-28 11:37:54 +01:00
Daniel Eklöf
53e8fbbdec
ci: python: upgrade pip before installing python packages 2025-12-26 17:26:17 +01:00
Daniel Eklöf
bb6968c284
ci: combine the codespell and mypy stages
They both need python and a venv, so let's combine them, to avoid
having to install the same things twice.
2025-12-26 17:26:17 +01:00
Daniel Eklöf
41679e64a8
box-drawing: fenv.h: remove, not needed anymore 2025-12-26 15:00:18 +01:00
Daniel Eklöf
b3cb180e44
codespell: use pyproject.toml to define options and exceptions 2025-12-26 14:44:56 +01:00
Daniel Eklöf
ee682abac8
mypy: no need to declare None as return type for __init__ 2025-12-26 14:13:14 +01:00
Daniel Eklöf
6ab2e2d9eb
ci: run mypy + ruff check 2025-12-26 13:15:01 +01:00
Daniel Eklöf
bbebe0f330
scripts: mypy fixes 2025-12-26 13:13:01 +01:00
Daniel Eklöf
cb1e152d99
pyproject.toml: add initial pyright configuration 2025-12-26 13:12:43 +01:00
Daniel Eklöf
ca278398b1
pyproject.toml: add initial mypy configuration 2025-12-26 13:12:31 +01:00
Daniel Eklöf
4cb17f5ae6
csi: make sure the ASCII printer function is updated on plain underlines
Otherwise, a sequence like

    \E[4:2;4m  # Enable double-underline, then immediately switch to single

Will switch to the slow printer, and then get stuck there even though
we immediately switch to plain underlines (which don't need the slow
printer).
2025-12-24 11:33:28 +01:00
Daniel Eklöf
aa26676c43
builtin terminfo: add custom 'query-os-name'
Inspired by Kitty's 'kitty-query-os_name'. Notable changes:

* Drop kitty prefix
* os_name -> os-name
* Use "uname -s" without any transformations (e.g. no lower-casing)

    $ ./utils/xtgettcap query-os-name
    reply: (44 chars): <ESC>P1+r71756572792d6f732d6e616d65=4C696E7578<ESC>\
      query-os-name=Linux

Closes #2209
2025-12-20 15:59:31 +01:00
Daniel Eklöf
1caba0d993
config: remove deprecated config option cursor.color
This option was deprecated in 1.23.0. Use colors-{dark,light}.cursor
instead.
2025-12-20 15:57:19 +01:00
Daniel Eklöf
cf2b390f6e
config: add [colors-dark] and [colors-light], replacing [colors] and [colors2]
The main reason for having two color sections is to be able to switch
between dark and light. Thus, it's better if the section names reflect
this, rather than the more generic 'colors' and 'colors2' (which was
the dark one and which was the light one, now again?)

When the second color section was added, we kept the original name,
colors, to make sure we didn't break existing configurations, and
third-party themes.

However, in the long run, it's probably better to be specific in the
section naming, to avoid confusion.

So, add 'colors-dark', and 'colors-light'. Keep 'colors' and 'colors2'
as aliases for now, but mark them as deprecated. They WILL be removed
in a future release.

Also rename the option values for initial-color-theme, from 1/2, to
dark/light. Keep the old ones for now, marked as deprecated.

Update all bundled themes to use the new names. In the light-only
themes (i.e. themes that define a single, light, theme), use
colors-light, and set initial-color-theme=light.

Possible improvements: disable color switching if only one color
section has been explicitly configured (todo: figure out how to handle
the default color theme values...)
2025-12-20 15:51:30 +01:00
Daniel Eklöf
4e96780eef
shm: revert part of 299186a654
299186a654 introduced a regression,
where we don't handle SHM buffer "hiccups" correctly.

If foot, for some reason is forced to render a frame "too soon", we
might end up having multiple buffers "in flight" (i.e. committed to
the compositor). This could happen if the compositor pushes multiple
configure events rapidly, for example. Or anything else that forces
foot to render something "immediately", without waiting for a frame
callback.

The compositor typically releases both buffers at the same time (or
close to it), so the _next_ time we want to render a frame, we
have *two* buffers to pick between. The problem here is that after
299186a654, foot no longer purges the
additional buffer(s), but keeps all of them around. This messes up
foot's age tracking, and the _next_ time we're forced to pull two
buffers (without the compositor releasing the first one in between),
we try to apply damage tracking that is no longer valid. This results
in visual glitches. This never self-repairs, and we're stuck with
visual glitches until the window is resized, and we're forced to
allocate completely new buffers.

It is unclear why 299186a654 stopped
removing the buffers. It was likely done early in the development, and
is no longer needed. So far, I haven't noticed any bugs by
re-introducing the buffer purging, but further testing is needed.
2025-12-17 08:09:23 +01:00
Yaakov Selkowitz
15ebc433ba Fix discarded const qualifiers from string functions
This is a new warning in GCC 15 that is being promoted to an error due to
the werror=true in meson.build.
2025-12-16 22:15:03 -05:00
Daniel Eklöf
6e533231b0
term: mouse SGR mode: don't emit negative CSI values
When reporting the column/row pixel value in mouse SGR mode, we
emitted negative values when the cursor was being dragged outside the
window.

Unfortunately, negative values aren't allowed in CSI parameters, as
'-' is an intermediate value.

It was done this way, to be consistent with XTerm behavior. Allegedly,
XTerm has changed its behavior in patch 404.

With that in mind, and seeing that foot has never emitted negative
values in any other mouse mode, let's stop emitting negative values in
SGR mode too.

Closes #2226
2025-12-13 09:56:29 +01:00
Daniel Eklöf
ac6d7660dd
ci: codespell: ignore 'rin' 2025-12-13 09:55:43 +01:00
Daniel Eklöf
65bd79b77d
term: reverse-scroll: fix crash when viewport ends up outside the (new) scrollback
If the viewport has been scrolled up, it is possible for a
reverse-scroll (rin) to cause the viewport to point to lines outside
the scrollback. This is an issue if the scrollback isn't full, since
in that case, the viewport will contain NULL lines. This will
potentially trigger assertions in a couple of different places.

Example backtrace:

    #2 0x555555cd230c in bug ../../debug.c:44
    #3 0x555555ad485e in grid_row_in_view ../../grid.h:83
    #4 0x555555b15a89 in grid_render ../../render.c:3465
    #5 0x555555b3b0ab in fdm_hook_refresh_pending_terminals ../../render.c:5165
    #6 0x555555a74980 in fdm_poll ../../fdm.c:435
    #7 0x555555ac2b85 in main ../../main.c:676

Detect when this happens, and force-move the viewport to ensure it is
valid.

Closes #2232
2025-12-13 09:55:27 +01:00
Whyme Lyu
55f8388694 doc: remove duplicated ctrl+shift+w in foot(1) 2025-12-01 18:38:58 +08:00
Daniel Eklöf
be19ca2b20
client: add missing <limits.h> (for CHAR_MAX)
Closes #2221
2025-11-29 09:47:22 +01:00
Daniel Eklöf
fc9625678f
config: add toplevel-tag=TAG
Add support for the new xdg-toplevel-tag-v1 Wayland protocol, by
exposing a new config option, `toplevel-tag`, and a corresponding
command option, `--toplevel-tag` (in both `foot` and `footclient`).

This can help the compositor with session management, or custom window
rules.

Closes #2212
2025-11-12 11:04:25 +01:00
Daniel Eklöf
c9abab0807
changelog: triple-click when there's a quote in the last column 2025-11-12 07:46:34 +01:00
Johannes Altmanninger
5cb8ff2e9c
Fix assertion failure triple-clicking line with quote in last column
By default, triple-click tries to select quoted strings within a
logical line.  This also works if the line spans multiple screen lines.

If there is a quote character in the last column:

	printf %"$COLUMNS"s \'; printf wrapped; sleep inf

and I triple-click on the following soft-wrapped line, there's an
assertion failure because the column next to the quote is out of range.

The quote position has been found by walking at least one cell
backwards from "pos". This means that if the quote position is in
the very last column, there must be a row below.

Also move the assertion to be a pre-condition, though that's debatable.
2025-11-12 07:41:35 +01:00
Daniel Eklöf
1fce0e69f5
changelog: case sensitive scrollback search: move to correct release 2025-11-01 08:12:52 +01:00
Daniel Eklöf
9728ada028
csi: focus mode (private mode 1004): send focus event immediate, when enabled
This lets the application now the current state, without having to
wait for the user to switch focus.

Fixes #2202
2025-11-01 08:12:03 +01:00
Ronan Pigott
143f220527
search: do not emit composing keys
When we are in the composing state for XCompose key sequences, we
should not add the compose component keys to the search buffer.
2025-11-01 08:08:55 +01:00
c4llv07e
5ae4955e83
search: use case insensitive search only if there's no uppercase in search 2025-10-30 06:36:12 +01:00
c4llv07e
71de0c45bc
char32: add helper functions to work with c32 case 2025-10-30 06:35:02 +01:00
Andrei
19466a21d8 doc: foot.ini: fix typo 2025-10-24 11:11:52 -07:00
Daniel Eklöf
5587604469
input: keymap(): use a goto-label on error, to ensure we always close the keymap FD 2025-10-18 08:23:53 +02:00
Daniel Eklöf
82e75851e4
changelog: add new 'unreleased' section 2025-10-16 08:50:31 +02:00
Daniel Eklöf
e114a5f02f
Merge branch 'releases/1.25' 2025-10-16 08:50:13 +02:00
Daniel Eklöf
b44a62724c
meson: bump version to 1.25.0 2025-10-16 08:46:58 +02:00
Daniel Eklöf
dc5a921d2c
changelog: prepare for 1.25.0 2025-10-16 08:46:36 +02:00
Daniel Eklöf
612adda384
render: don't warn about immediate buffer release if pre-apply-damage has been activated 2025-10-16 08:45:07 +02:00
Daniel Eklöf
dbf18ba444
wayland: always render a new frame after a fullscreen change
This is needed, since we disable alpha in fullscreen, and since we use
different image buffer formats (XRGB vs. ARGB) when we have alpha
vs. when we don't (and fullscreen always disables alpha).

Normally, this happens anyway, as the window is resized when going in
or out from fullscreen. But, it's technically possible for a
compositor to change an application's fullscreen state without
resizing the window.
2025-10-15 09:41:52 +02:00
Daniel Eklöf
96605bf52f
extract: number of spaces after the tab shouldn't include the tab cell itself
This fixes an off by one, where we sometimes "ate" an extra space when
extracting contents with tabs. This happened if the tab (and its
subsequent spaces) were followed by an additional space.

Closes #2194
2025-10-11 10:13:10 +02:00
Daniel Eklöf
7ed36c1033
config: add colors.dim-blend-towards=black|white
Before this patch, we always blended towards black when dimming
text. However, with light color themes, it usually looks better if we
dim towards white instead.

This option allows you to choose which color to blend towards.

The default is 'black' in '[colors]', and 'white' in '[colors2]'.

Closes #2187
2025-10-10 11:10:38 +02:00
Daniel Eklöf
371837ef7b
changelog: updated jump label colors in modus-operandi 2025-10-10 10:36:41 +02:00
Matthias Heyman
e308a4733e
fix: jump labels are more readable 2025-10-10 10:35:01 +02:00
Charalampos Mitrodimas
fd88c6c61c
wayland: restore opacity after exiting fullscreen
When exiting fullscreen mode, the window's transparency was not being
restored, leaving it opaque until another window was fullscreened.

This occurred because the Wayland opaque region was set based only on
the configured alpha value, without considering the fullscreen state.

Since commit

  899b768b74 ("render: disable transparency when we're fullscreened")

transparency is disabled during fullscreen to avoid
compositor-mandated black backgrounds affecting the intended colors.
However, the opaque region was not being updated when the fullscreen
state changed.

Fixes: https://codeberg.org/dnkl/foot/issues/2180
Signed-off-by: Charalampos Mitrodimas <charmitro@posteo.net>
2025-10-10 10:30:05 +02:00
Daniel Eklöf
299186a654
render: when double-buffering, pre-apply previous frame's damage early
Foot likes it when compositor releases buffer immediately, as that
means we only have to re-render the cells that have changed since the
last frame.

For various reasons, not all compositors do this. In this case, foot
is typically forced to switch between two buffers, i.e. double-buffer.

In this case, each frame starts with copying over the damage from the
previous frame, to the new frame. Then we start rendering the updated
cells.

Bringing over the previous frame's damage can be slow, if the changed
area was large (e.g. when scrolling one or a few lines, or on full
screen updates). It's also done single-threaded. Thus it not only
slows down frame rendering, but pauses everything else (i.e. input
processing). All in all, it reduces performance and increases input
latency.

But we don't have to wait until it's time to render a frame to copy
over the previous frame's damage. We can do that as soon as the
compositor has released the buffer (for the frame _before_ the
previous frame). And we can do this in a thread.

This frees up foot to continue processing input, and reduces frame
rendering time since we can now start rendering the modified cells
immediately, without first doing a large memcpy(3).

In worst case scenarios (or perhaps we should consider them best case
scenarios...), I've seen up to a 10x performance increase in frame
rendering times (this obviously does *not* include the time it takes
to copy over the previous frame's damage, since that doesn't affect
neither input processing nor frame rendering).

Implemented by adding a callback mechanism to the shm abstraction
layer. Use it for the grid buffers, and kick off a thread that copies
the previous frame's damage, and resets the buffers age to 0 (so that
foot understands it can start render to it immediately when it later
needs to render a frame).

Since we have certain way of knowing if a compositor releases buffers
immediately or not, use a bit of heuristics; if we see 10 consecutive
non-immediate releases (that is, we reset the counter as soon as we do
see an immediate release), this new "pre-apply damage" logic is
enabled. It can be force-disabled with tweak.pre-apply-damage=no.

We also need to take care to wait for the thread before resetting the
render's "last_buf" pointer (or we'll SEGFAULT in the thread...).

We must also ensure we wait for the thread to finish before we start
rendering a new frame. Under normal circumstances, the wait time is
always 0, the thread has almost always finished long before we need to
render the next frame. But it _can_ happen.

Closes #2188
2025-10-10 10:23:17 +02:00
Daniel Eklöf
bb314425ef
changelog: shm buffer stride alignment 2025-10-05 09:40:57 +02:00
Daniel Eklöf
e43ea3676f
doc: foot.ini: document tweak.min-stride-alignment 2025-10-05 09:40:51 +02:00
Daniel Eklöf
bd994eda1c
shm: page-align the memfd size (also needed for GPU direct import) 2025-10-05 09:40:51 +02:00
Daniel Eklöf
fac3994154
config: add tweak.min-stride-alignment
This allows the user to configure the value by which a surface
buffer's stride must be an even multiple of.

This can be used to ensure the stride meets the GPU driver's
requirements for direct import.

Defaults to 256. Set to 0 to disable.

Closes #2182
2025-10-05 09:40:20 +02:00
Daniel Eklöf
80951ab7a6
term: osc8: tag *all* cells in a multi-column character as an URI
When we print a character to the grid, we must also update its OSC-8
state if an OSC-8 URI is currently active.

For double-width characters, this was only being done for the first
cell.

This causes the labels in URL mode to be off, as the link was
effectively chopped up into multiple pieces.

Closes #2179
2025-10-04 09:24:47 +02:00
Daniel Eklöf
1dfa86c93a
Revert "term: erase: use erase_line() whenever a range corresponds to a full line"
This reverts commit 44a674edb8.

It caused a regression with prompt markers, in at least fish+starship.
2025-10-04 07:21:15 +02:00
Daniel Eklöf
44a674edb8
term: erase: use erase_line() whenever a range corresponds to a full line 2025-09-25 16:57:41 +02:00
Daniel Eklöf
c34f063307
changelog: add new 'unreleased' section 2025-09-12 10:22:21 +02:00
Daniel Eklöf
363477fa0d
Merge branch 'releases/1.24' 2025-09-12 10:22:03 +02:00
Daniel Eklöf
fa0fd2f50f
meson: bump version to 1.24.0 2025-09-12 10:18:33 +02:00
Daniel Eklöf
f715f3b55f
changelog: prepare for 1.24.0 2025-09-12 10:18:06 +02:00
Daniel Eklöf
efc39097e5
term: no need to pass ptmx as stdout to utempter 2025-09-09 17:34:54 +02:00
Daniel Eklöf
65528f455d
meson: utempter del has no argument
This fixes an issue where we didn't record a logout record when using
the libutempter backend.
2025-09-09 17:34:02 +02:00
Daniel Eklöf
1d9ac3f611
doc: foot.ini: typo: upppercase -> uppercase 2025-08-31 11:42:56 +02:00
Ryan Roden-Corrent
298196365c
config: add 'uppercase-regex-insert'
This makes the "uppercase hint character inserts selected text"
behavior added in #1975 configurable, as it can have unexpected
behavior for some users.

It defaults to "on", preserving the new behavior of `foot`, after

Fixes #2159.
2025-08-31 11:36:28 +02:00
Daniel Eklöf
f0e36e35cb
input: unit test: check pipe2() return value
Fixes compilation failures with clang, in release mode.
2025-08-30 08:18:31 +02:00
Daniel Eklöf
ed7652db50
config: value_to_*(): don't overwrite result variable on error
Some of the value_to_*() functions wrote directly to the output
variable, even when the value was invalid. This often resulted in the
an actual configuration option (i.e. a member in the config struct) to
be overwritten by an invalid value.

For example, -o initial-color-theme=0 would set
conf->initial_color_theme to -1, resulting in a crash later, when
initializing a terminal instance.
2025-08-25 15:46:19 +02:00
Daniel Eklöf
72d9a13c0c
server: fix compilation error: return value ignored 2025-08-01 09:41:37 +02:00
Daniel Eklöf
b13a8f12d2
server/client: add support for sending SIGUSR to footclient
This patch adds the IPC infrastructure necessary to propagate
SIGUSR1/SIGUSR2 from a footclient process to the server process.

By targeting a particular footclient instance, only that particular
instance changes theme. This is different from when targeting the
server process, where all instances change theme.

Closes #2156
2025-08-01 09:38:05 +02:00
Daniel Eklöf
70d99a8051
changelog: SIGUSR changes in the server 2025-07-30 12:38:14 +02:00
Daniel Eklöf
b1b2162416
doc: foot.ini: mention SIGUSR1/SIGUSR2 and reference foot(1) 2025-07-30 12:36:32 +02:00
Daniel Eklöf
3b8d59f476
doc: foot: document SIGUSR1/SIGUSR2 2025-07-30 12:36:32 +02:00
Daniel Eklöf
6eedc88d70
server: sigusr1/2: update conf object with the "new" theme
When sending SIGUSR1/SIGUSR2 to a server process, all currently
running client instances change their theme. But before this patch,
all future instances used the original theme. With this patch, the
server owned config object is updated with the selected theme, thus
making new instances use the same theme as well.
2025-07-30 12:36:32 +02:00
Daniel Eklöf
7636f264a8
slave: remove more environment variables set by other terminals
This ensures applications don't mistake foot for another terminal
emulator. Not that applications _should_ rely on environment
variables, but some do anyway...
2025-07-30 12:34:59 +02:00
Daniel Eklöf
83303bd2a4
url-mode: for some reason we sorted the label letters before assigning them
Don't do this. Now that we **don't** sort them, the first letter
chosen by the user is always assigned to the bottom most URL.

Closes #2140 (again)
2025-07-29 11:18:49 +02:00
Tobias Mock
f873aa904d
Add tinted variant of modus-vivendi theme 2025-07-24 13:51:44 +02:00
Daniel Eklöf
86d63f08ba
changelog: add new 'unreleased' section 2025-07-23 08:31:30 +02:00
Daniel Eklöf
8814b5f080
Merge branch 'releases/1.23' 2025-07-23 08:31:03 +02:00
Daniel Eklöf
43620935a1
meson: bump version to 1.23.1 2025-07-23 08:28:13 +02:00
Daniel Eklöf
95e8b18c12
changelog: prepare for 1.23.1 2025-07-23 08:27:59 +02:00
Daniel Eklöf
5a01dbc234
Merge branch 'master' into releases/1.23 2025-07-23 08:27:25 +02:00
Daniel Eklöf
fcde74a181
osc: color reset: read default color from currently active theme 2025-07-22 13:30:28 +02:00
Daniel Eklöf
42be74214a
term: make sure the color table is populated *before* the slave process is spawned 2025-07-22 13:30:00 +02:00
Daniel Eklöf
21db6a6cdc
fdm: when logging signal related errors, include the signal name
Since sigabbrev_np() is GNU only, provide a fallback function that
returns "SIG<signo>" when sigabbrev_np() doesn't exist (for example,
on FreeBSD).
2025-07-21 15:44:24 +02:00
Daniel Eklöf
7ab43ebf74
shm: don't set pixman_fmt_without_alpha twice
When selecting 16-bit surfaces, we set pixman_fmt_without_alpha twice,
and never set pixman_fmt_with_alpha.

This caused 10-bit surfaces to be used instead, since it checks if
pixman_fmt_with_alpha has been overridden or not.
2025-07-21 13:49:57 +02:00
Daniel Eklöf
57ae3bb89c
main: unregister SIGUSR2 on exit 2025-07-18 17:24:18 +02:00
Daniel Eklöf
01387f9593
main: SIGUSR1 selects the first color theme, SIGUSR2 the second
Before this patch, SIGUSR1 toggled between [colors] and
[colors2].

Now, SIGUSR1 changes to [colors], regardless of what the current color
theme is, and SIGUSR2 changes to [colors2].

Closes #2144
2025-07-18 08:33:42 +02:00
Daniel Eklöf
cc290fa9b0
url-mode: assign label keys in reverse order
The _last_ URL is often the one you are interested in, and with this
change, it is always assigned the first (and thus the same) key.

Closes #2140
2025-07-17 10:40:20 +02:00
Daniel Eklöf
692b22cbbb
changelog: add new 'unreleased' section 2025-07-16 08:31:42 +02:00
Daniel Eklöf
9b6a9db98a
Merge branch 'releases/1.23' 2025-07-16 08:31:21 +02:00
Daniel Eklöf
d62bff1440
meson: bump to 1.23.0 2025-07-16 08:15:34 +02:00
Daniel Eklöf
e72e08625d
changelog: prepare for 1.23.0 2025-07-16 08:14:54 +02:00
Daniel Eklöf
693aefa96a
config: silence valgrind-detected leak in config_font_parse() 2025-07-11 16:47:51 +02:00
Daniel Eklöf
aa579acd6e
issue template: compositor version -> compositor name and version
The existing hints and descriptions are apparently not enough; some
people still only mention the version, which is rather useless.
2025-07-11 16:30:18 +02:00
Daniel Eklöf
968bc05c32
csi: add '52' to the DA reply, to indicate PSC-52 support
Note: only *copy* is required to be enabled in security.osc52; paste
is optional, see
https://github.com/contour-terminal/contour/issues/1761#issuecomment-2944492097
2025-06-10 07:12:53 +02:00
Daniel Eklöf
499f019dea
osc: 52: clear selection if the payload is the empty string 2025-06-10 07:12:53 +02:00
Daniel Eklöf
d9675a7140
main: do a theme toggle upon receiving SIGUSR1
Caveat: in server mode, *all* instances toggle their themes.
2025-06-10 07:11:45 +02:00
Daniel Eklöf
33eefa7b45
term+input: refactor: move theme switching into term_theme_* functions 2025-06-10 07:11:45 +02:00
Daniel Eklöf
7347f4beb1
quirks: remove subsurface unmap quirk for Sway
Sway used to have an issue where unmapping a subsurface did not damage
the surface below (https://github.com/swaywm/sway/issues/6960).

This has been fixed for quite some time now, so let's remove the
quirk.
2025-06-09 07:08:24 +02:00
tokyo4j
eeaecba723 wayland: fix global listener for xdg_toplevel_icon_manager_v1 2025-05-24 19:06:29 +09:00
Daniel Eklöf
5a84f8d841
conf: pad: add center-when-fullscreen and center-when-maximized-and-fullscreen
Before this patch, the grid content was *always* centered when the
window was maximized or fullscreened, regardless of how the user had
configured padding.

Now, the behavior is controlled by the 'pad' option. Before this
patch, the syntax was

    pad MxN [center]

Now it is

    pad MxN [center|center-when-fullscreen|center-when-maximized-and-fullscreen]

The default is "pad 0x0 center-when-maximized-and-fullscreen", to
match current behavior.

Closes #2111
2025-05-24 09:56:16 +02:00
Daniel Eklöf
5621829bb0
cursor-shape: map "dnd-move" to WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE 2025-05-23 13:31:53 +02:00
Daniel Eklöf
664cdcc65c
cursor-shape: add 'dnd-ask' and 'all-resize'
These (non-css) cursor shapes were added to the cursor-shape-v1
protocol in wayland-protocols 1.42.

We don't need (or use them at all) internally, but add them to the
list we use to translate from shape names to shape enums. This allows
users to set a custom shape (via OSC-22), while still using server
side cursors (i.e. no need to fallback to client-side cursors).

If we try to set a shape not implemented by the server, we get a
protocol error and foot exits. This is bad.

So, make sure we don't do that:

1. First, we need to explicitly bind v2 if implemented by the server
2. Track the bound version number in the wayland struct
3. When matching shape enum, skip shapes not supported in the
   currently bound version of the cursor-shape protocol
2025-05-22 06:59:33 +02:00
Daniel Eklöf
d266599881
wayland: configure: don't commit if we have a pending refresh
Currently, if the following occurs:

1. foot has AxB size
2. Compositor sends CxD size
3. foot detects a resize, acks and saves CxD, but doesn't redraw immediately
4. Compositor sends CxD size again (due to a toplevel state array
   change, for example)

Then foot will detect no resize occurred, and will do an "empty"
commit immediately.

In this particular case that's wrong, since we're effectively
acking+committing the initial AxB size.

Fix by only doing the immediate commit if there's no size
change **and** there's no pending refresh.

Note: normally, we'd resize and refresh+commit immediately, but if
we're waiting for a frame callback, then the refresh+commit will be
delayed (i.e. scheduled). This is what we're checking here.

Closes #2105
2025-05-21 13:07:49 +02:00
Kirill Primak
456ac5d79f
render: improve CSD button positioning
This commit fixes titlebar button positioning when maximization isn't
available but minimization is.
2025-05-21 07:39:35 +02:00
Daniel Eklöf
3e1e3ea38c
libxkbcommon: don't require 1.8.0
The version bump was done since we now use XKB_VMOD_NAME_*; macros
added in libxkbcommon 1.8.0.

Not all distros have updated libxkbcommon yet (read: Debian). Since
it's fairly easy to work around, let's do that.

Closes #2103
2025-05-18 11:36:57 +02:00
Daniel Eklöf
8bd39b32cd
Revert "xkbcommon: require libxkbcommon >= 1.8.0"
This reverts commit 34d3f4664b.
2025-05-18 11:29:50 +02:00
Daniel Eklöf
ebd1614316
csi: when REP:ing a "combining" character, use correct width
Before this patch, we just called c32width(), which only works on
actual codepoints. If the last printed character is a "combining"
character, i.e. a key into our lookup table for multi-codepoint
graphemes, we need to lookup the grapheme and pick the width from
there.

See https://gitlab.com/AutumnMeowMeow/jexer/-/issues/119#note_2499712901
2025-05-16 18:46:26 +02:00
Daniel Eklöf
9b0d5e7c96
term: unittest: auto-scroll timer FD is created on-demand nowadays 2025-05-08 10:22:45 +02:00
Daniel Eklöf
073b637d45
render: refactor to allow setting only selection bg or fg
Before this, we only applied custom selection colors, if *both* the
selection bg and fg had been set.

Since the options are already split up into two separate options, and
since it makes sense to at least be able to keep the foreground colors
unchanged (i.e. only setting the selection background), let's allow
only having one of the selection colors set.

Closes #1846
2025-05-05 13:02:32 +02:00
Daniel Eklöf
c037836bbd
doc: foot.ini: fix description of dark/light themes 2025-05-05 13:02:04 +02:00
Chen Mulong
c6db0bed42
Update catppuccin themes
From https://github.com/catppuccin/foot
Without the 'cursor.color', those themes have problems with cursor
display problems in the zsh vi normal mode.
2025-05-05 07:21:26 +02:00
Daniel Eklöf
970e13db8d
config: tweak.surface-bit-depth: add support for 16-bit surfaces
This adds supports for 16-bit surfaces, using the new
PIXMAN_a16b16g16r16 buffer format. This maps to
WL_SHM_FORMAT_ABGR16161616 (little-endian).

Use the new 16-bit surfaces by default, when
gamma-correct-blending=yes.
2025-05-03 09:04:15 +02:00
Daniel Eklöf
7354b94f73
osc: restore configured alpha if OSC-11 has no alpha value
When parsing an OSC-11 without an alpha value (i.e. standard OSC-11,
not rxvt's extended variant), restore the alpha value from the
configuration, rather than keeping whatever the current alpha is.
2025-05-02 18:54:03 +02:00
Daniel Eklöf
5080e271c2
wayland: attempt to log protocol errors on failure to flush
When failing to flush, and the error is EPIPE, attempt to read and
dispatch events. This ensures protocol errors are logged.
2025-05-02 13:46:18 +02:00
Daniel Eklöf
237db6e771
wayland: always call wl_display_dispatch_pending() at least once, after reading
This fixes an issue where protocol errors aren't reported. I'm
guessing the read succeeds, but that prepare_read() _also_ succeeds
immediately, since there aren't any events to dispatch (only log the
protocol error).

By calling dispatch unconditionally, we ensure any error messages are
printed. Then we proceed to loop prepare_read() + dispatch_pending()
until the queue is empty.
2025-05-02 13:43:59 +02:00
Ryan Roden-Corrent
0ea572dc63
Paste URL/regex selection to prompt if key is uppercase.
In copy-regex/show-urls-copy mode, if the last input character was
uppercase, copy the selection to the clipboard _and_ paste it. This is
useful for taking a file path from a command output:(log, git, test
failure, etc.) and using it in another command.

This is inspired by the behavior of copy mode in wezterm:
https://wezterm.org/quickselect.html

I could have made it check every character in the hint, but it seemed
fine to assume that if the last character was uppercase, the user
wanted this behavior.

Closes #1975.
2025-05-01 13:53:11 +02:00
Daniel Eklöf
183fd96aba
Merge branch 'releases/1.22' 2025-05-01 10:25:53 +02:00
Daniel Eklöf
85c81042d2
meson: bump version to 1.22.3 2025-05-01 10:20:38 +02:00
Daniel Eklöf
acea863fbe
changelog: prepare for 1.22.3 2025-05-01 10:20:22 +02:00
Daniel Eklöf
2a8948a3f3
config: tweak.surface-bit-depth now defaults to 'auto'
When set to 'auto', use 10-bit surfaces if gamma-correct blending is
enabled, and 8-bit surfaces otherwise.

Note that we may still fallback to 8-bit surfaces (without disabling
gamma-correct blending) if the compositor does not support 10-bit
surfaces.

Closes #2082
2025-05-01 10:19:35 +02:00
Daniel Eklöf
7ced397089
config: gamma-correct-blending: disable by default 2025-05-01 10:18:14 +02:00
Daniel Eklöf
9ff0151055
changelog: add new 'unreleased' section 2025-05-01 10:17:20 +02:00
Daniel Eklöf
e5a0755451
config: tweak.surface-bit-depth now defaults to 'auto'
When set to 'auto', use 10-bit surfaces if gamma-correct blending is
enabled, and 8-bit surfaces otherwise.

Note that we may still fallback to 8-bit surfaces (without disabling
gamma-correct blending) if the compositor does not support 10-bit
surfaces.

Closes #2082
2025-05-01 08:54:30 +02:00
Daniel Eklöf
b07ce56321
config: gamma-correct-blending: disable by default 2025-05-01 08:09:08 +02:00
Daniel Eklöf
1dc8354534
readme: add liberapay donation button 2025-04-30 11:46:20 +02:00
Daniel Eklöf
99954534e1
Merge branch 'releases/1.22' 2025-04-30 10:30:57 +02:00
Daniel Eklöf
513e91c33a
meson: bump version to 1.22.2 2025-04-30 10:23:51 +02:00
Daniel Eklöf
fc293bad5e
changelog: prepare 1.22.2 2025-04-30 10:23:20 +02:00
Daniel Eklöf
172f67a8df
doc: foot.ini: gamma-correct: tweak wording of 8- vs. 10-bit surfaces 2025-04-30 10:21:00 +02:00
Daniel Eklöf
ce424e0990
scripts: srgb: use 2.2 gamma TF instead of piece-wise sRGB TF 2025-04-30 10:20:52 +02:00
Daniel Eklöf
1ea20b1b70
changelog: add new 'unreleased' section 2025-04-30 10:19:47 +02:00
Daniel Eklöf
eb79a27900
readme: donations: add liberapay 2025-04-30 09:28:35 +02:00
Daniel Eklöf
d7b48d3924
doc: foot.ini: gamma-correct: tweak wording of 8- vs. 10-bit surfaces 2025-04-28 12:32:40 +02:00
Daniel Eklöf
97910a5cba
scripts: srgb: use 2.2 gamma TF instead of piece-wise sRGB TF 2025-04-28 11:55:43 +02:00
Daniel Eklöf
d20fbc6807
config: parse_color_theme(): make NOINLINE 2025-04-27 07:46:09 +02:00
Daniel Eklöf
4d70bb7b42
changelog: mention the new combined dark/light theme files 2025-04-26 18:15:31 +02:00
Daniel Eklöf
8273514d3c
themes: paper-color: add dark/light combined theme file 2025-04-26 15:26:36 +02:00
Daniel Eklöf
d3e45791bd
themes: nvim: add dark/light combined theme file 2025-04-26 15:26:31 +02:00
Daniel Eklöf
6a1c3b89c2
themes: gruvbox: add dark/light combined theme file 2025-04-26 15:26:22 +02:00
Daniel Eklöf
1dc14a3001
themes: selenized: add dark/light combined theme file 2025-04-26 15:26:14 +02:00
Daniel Eklöf
537092e643
themes: solarized: add dark/light combined theme file
These themes uses the 'colors' section to define the dark variant, and
'colors2' to define the light variant.
2025-04-26 15:26:05 +02:00
Daniel Eklöf
bc5b716668
config: add initial-color-theme=1|2
This option selects which color theme to use by default. I.e. at
startup, and after a reset.

This is useful with combined theme files, where a single file defines
e.g. both a dark and light version of the theme.
2025-04-26 14:43:42 +02:00
Daniel Eklöf
10e7f29149
csi: implement private mode 2031 (dark/light mode detection)
* Recognize 'CSI ? 996 n', and respond with
  - 'CSI ? 997 ; 1 n' if the primary theme is active
  - 'CSI ? 997 ; 2 n' if the alternative theme is actice
* Implement private mode 2031, where changing the color
  theme (currently only possible via key bindings) causes the terminal
  to send the same CSI sequences as above.

In this context, foot's primary theme is considered dark, and the
alternative theme light (since the default theme is dark).

Closes #2025
2025-04-26 14:23:34 +02:00
Daniel Eklöf
6bc91b5e28
key-bindings: add bindings to switch between color themes
* color-theme-switch-1: select the primary color theme
* color-theme-switch-2: select the alternative color theme
* color-theme-toggle: toggle between the primary and alternative color themes
2025-04-26 14:20:58 +02:00
Daniel Eklöf
1423babc35
config: add new section 'colors2'
This section defines an alternative color theme. The keys are the same
as in the 'colors' section, as are the default values.

Values are *not* inherited from 'colors'. That is, if you set a value
in 'colors', but not in 'colors2', it is *not* inherited by 'colors2'.
2025-04-26 14:20:58 +02:00
Daniel Eklöf
01c43f1644
config: refactor: break out color theme parsing to a separate function 2025-04-26 14:20:57 +02:00
Daniel Eklöf
b24a9a59b9
tests: config: colors: verify loaded color is correct 2025-04-26 14:20:57 +02:00
Daniel Eklöf
5406ae3355
themes: cursor.color -> colors.cursor 2025-04-26 14:20:57 +02:00
Daniel Eklöf
624c383a1f
config: move cursor.color to colors.cursor 2025-04-26 10:46:39 +02:00
Daniel Eklöf
a7276d9dff
config: refactor: break out 'colors' to a color_theme struct 2025-04-26 10:46:39 +02:00
Daniel Eklöf
79f6b4b1de
changelog: add new 'unreleased' section 2025-04-26 10:41:14 +02:00
Daniel Eklöf
fea9f5579f
Merge branch 'releases/1.22' 2025-04-26 10:40:38 +02:00
Daniel Eklöf
c85d5d5096
meson: bump version to 1.22.1 2025-04-26 10:36:23 +02:00
Daniel Eklöf
89bfac00e7
changelog: prepare for 1.22.1 2025-04-26 10:36:13 +02:00
Daniel Eklöf
9d0b048cf9
Merge branch 'master' into releases/1.22 2025-04-26 10:34:56 +02:00
Daniel Eklöf
0020ef12b4
changelog: add missing bug ref 2025-04-26 10:31:09 +02:00
Daniel Eklöf
cb1b7ba0c5
render: regression: alpha applied to inversed text/selections
Introduced by 5f83278afd

Closes #2073
2025-04-25 19:20:36 +02:00
Daniel Eklöf
d43326d2b5
changelog: zero-width grapheme breaking codepoints causing fallback font to be used 2025-04-24 18:40:22 +02:00
Daniel Eklöf
1fec0cf5ea
Revert "term: append zero-width grapheme breaking characters to previous cell"
This reverts commit 76503fb86a.
2025-04-24 18:22:37 +02:00
Daniel Eklöf
1b15cc5f3d
Revert "term: ignore LTR+RTL markers (U+200E + U+200F)"
This reverts commit 70b324b24c.
2025-04-24 18:20:18 +02:00
Daniel Eklöf
70b324b24c
term: ignore LTR+RTL markers (U+200E + U+200F)
Foot doesn't implement RTL, and explicit LTR markers is neither
needed, nor used in anyway. In fact, they cause issues with font
lookup, as fcft often fails to find the marker codepoint in the
primary font, causing a fallback font to be used instead.

Closes #2049
2025-04-24 08:23:56 +02:00
valoq
b2dfd339e4
Add alacritty theme
This adds the default colors from alacritty  as an additional theme
2025-04-23 12:08:37 +02:00
Jan Palus
bc8d6d1ff3
build: fix race when generating emoji-variation-sequences.h
d3f692990e moved emoji-variation-sequences.h header inclusion from
vt.c to terminal.c. these two files are part of different libraries
hence target for generating emoji-variation-sequences.h needs to be
moved too.
2025-04-23 11:44:41 +02:00
Daniel Eklöf
8bded8ce8c
doc: foot.ini: add newish Unicode range to 'box-drawings-uses-font-glyphs' 2025-04-19 17:10:52 +02:00
Daniel Eklöf
ef4a680ae8
input: reset modifiers in keyboard_leave()
Closes #2034
2025-04-19 13:36:13 +02:00
Daniel Eklöf
cb2a64c585
csi: don't allow client app to enable grapheme-shaping when disabled at compile-time
Closes #2039
2025-04-19 13:35:06 +02:00
Daniel Eklöf
1a2e5f4932
render: fix colors.alpha-mode=matching
Before this patch, it only matched RGB color sources. It did not match
the default bg color, or indexed colors. That is, e.g. CSI 43m didn't
apply alpha, even if the color3 matched the default background color.
2025-04-19 13:33:23 +02:00
Daniel Eklöf
1bf9156628
doc: foot.ini: spaces -> tab (for indentation) 2025-04-19 11:59:50 +02:00
Daniel Eklöf
179e14e0a1
doc: foot.ini: gamma-correct-blending: mention colors being off 2025-04-19 09:16:28 +02:00
Daniel Eklöf
155c7c96b7
doc: foot.ini: key-bindings: untranslated symbols are tried before translated 2025-04-18 14:43:36 +02:00
Daniel Eklöf
30aafce82d
foot.ini: move alpha-mode to colors section
This is where the config parser expects it
2025-04-18 13:59:43 +02:00
Daniel Eklöf
6e5a602f67
changelog: add new 'unreleased' section 2025-04-17 14:44:05 +02:00
Daniel Eklöf
9799c4ddcd
Merge branch 'releases/1.22' 2025-04-17 14:42:52 +02:00
Daniel Eklöf
95f7b71058
meson: bump version to 1.22.0 2025-04-17 14:41:32 +02:00
Daniel Eklöf
2c8214f6ea
changelog: prepare for 1.22.0 2025-04-17 14:41:13 +02:00
datsudo
b46a9aa6d7
themes: add "Night Owl" theme 2025-04-15 07:36:41 +02:00
Daniel Eklöf
9ba8caf30b
doc: foot.ini: add colors.alpha-mode 2025-04-14 17:02:45 +02:00
Daniel Eklöf
f7807c0f4c
tests: config: test colors.alpha-mode 2025-04-14 17:00:07 +02:00
Daniel Eklöf
d2d4f53861
config+render: move alpha-mode to colors.alpha-mode, fix cursor handling
Move main.alpha-mode to colors.alpha-mode.

Fix (inverted) cursor handling, by always using the bg color without
alpha.

Do a minor optimization, where we don't even lock at colors.alpha-mode
if there's no transparency configured.
2025-04-14 16:58:23 +02:00
Daniel Eklöf
bacfba135d
changelog: move 'alpha-mode' to next-release 2025-04-14 16:48:44 +02:00
Fazzi
5f83278afd
config: add alpha_mode option 2025-04-14 16:47:45 +02:00
Daniel Eklöf
9a6227acb3
doc: foot.ini: workers: "if you have a ridiculous number of cores" 2025-04-14 07:03:37 +02:00
Daniel Eklöf
b93d2f042c
url-mode: fix double-width characters not being handled correctly
When a regex matches a string containing double-width characters, the
CELL_SPACER values were included in the URL string. This meant the
final URL (either launched, or copied) weren't handled correctly, as
invalid UTF-8 sequences were inserted in the middle of the string.

Closes #2027
2025-04-13 08:28:13 +02:00
Daniel Eklöf
bc2e0a29bb
changelog: move vmod support in config from "changed" to "added" 2025-04-10 12:18:34 +02:00
Daniel Eklöf
23431e3ecf
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.

Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.

Constrained tells us the window cannot be resized in the constrained
direction.

This patch does a couple of things:

* Recognize the new states when debug logging

* Change is_top_left() etc to look at the new constrained state
  instead of the tiled state. These functions are used when both
  choosing cursor shape, and when determining if/how to resize a
  window on a CSD edge click-and-drag.

* Update cursor shape selection to use the default (left_ptr) shape
  when on a constrained edge (or corner).

* Update CSD resize triggering, to not trigger a resize when attempted
  on a constrained edge (or corner).

See
86750c99ed:

    An edge constraint is an complementery state to the tiled state,
    meaning that it's not only tiled, but constrained in a way that it
    can't resize in that direction.

    This typically means that the constrained edge is tiled against a
    monitor edge. An example configuration is two windows tiled next
    to each other on a single monitor. Together they cover the whole
    work area.

    The left window would have the following tiled and edge constraint
    state:

        [ tiled_top, tiled_right, tiled_bottom, tiled_left,
          constrained_top, constrained_bottom, constrained_left ]

    while the right window would have the following:

        [ tiled_top, tiled_right, tiled_bottom, tiled_left,
          constrained_top, constrained_bottom, constrained_right ]

    This aims to replace and deprecate the
    `gtk_surface1.configure_edges` event and the
    `gtk_surface1.edge_constraint` enum.
2025-04-07 13:41:37 +02:00
Dominique Martinet
091aa90f1a
wayland: handle xdg-shell edge constraints
wayland-protocols commit 86750c99ed06 ("xdg-shell: Add edge
constraints") added a few more enums to handle, making the build fail
with -Werror:
../wayland.c: In function ‘xdg_toplevel_configure’:
../wayland.c:878:9: error: enumeration value ‘XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT’ not handled in switch [-Werror=switch]
  878 |         switch (*state) {
      |         ^~~~~~
../wayland.c:878:9: error: enumeration value ‘XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT’ not handled in switch [-Werror=switch]
../wayland.c:878:9: error: enumeration value ‘XDG_TOPLEVEL_STATE_CONSTRAINED_TOP’ not handled in switch [-Werror=switch]
../wayland.c:878:9: error: enumeration value ‘XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM’ not handled in switch [-Werror=switch]

(This is not part of any release yet, but can be used when building with
the submodule)

From a quick look it sounds like the meaning is the same as tiling as
far as we are concerned so handle these as we do of tiling.
2025-04-07 13:03:30 +02:00
Dominique Martinet
34d3f4664b
xkbcommon: require libxkbcommon >= 1.8.0
Trying to build with an older libxkbcommon fails as follow:
```
../input.c: In function ‘keyboard_keymap’:
../input.c:648:82: error: ‘XKB_VMOD_NAME_ALT’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_ALT’?
  648 |             xkb_mod_index_t alt = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_ALT);
      |                                                                                  ^~~~~~~~~~~~~~~~~
      |                                                                                  XKB_MOD_NAME_ALT
../input.c:648:82: note: each undeclared identifier is reported only once for each function it appears in
../input.c:649:83: error: ‘XKB_VMOD_NAME_META’ undeclared (first use in this function); did you mean XKB_MOD_NAME_ALT’?
  649 |             xkb_mod_index_t meta = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_META);
      |                                                                                   ^~~~~~~~~~~~~~~~~~
      |                                                                                   XKB_MOD_NAME_ALT
../input.c:650:84: error: ‘XKB_VMOD_NAME_SUPER’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_NUM’?
  650 |             xkb_mod_index_t super = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_SUPER);
      |                                                                                    ^~~~~~~~~~~~~~~~~~~
      |                                                                                    XKB_MOD_NAME_NUM
../input.c:651:84: error: ‘XKB_VMOD_NAME_HYPER’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_CAPS’?
  651 |             xkb_mod_index_t hyper = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_HYPER);
      |                                                                                    ^~~~~~~~~~~~~~~~~~~
      |                                                                                    XKB_MOD_NAME_CAPS
../input.c:652:87: error: ‘XKB_VMOD_NAME_NUM’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_NUM’?
  652 |             xkb_mod_index_t num_lock = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_NUM);
      |                                                                                       ^~~~~~~~~~~~~~~~~
      |                                                                                       XKB_MOD_NAME_NUM
../input.c:653:90: error: ‘XKB_VMOD_NAME_SCROLL’ undeclared (first use in this function); did you mean ‘XKB_LED_NAME_SCROLL’?
  653 |             xkb_mod_index_t scroll_lock = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_SCROLL);
      |                                                                                          ^~~~~~~~~~~~~~~~~~~~
      |                                                                                          XKB_LED_NAME_SCROLL
../input.c:654:90: error: ‘XKB_VMOD_NAME_LEVEL3’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_CTRL’?
  654 |             xkb_mod_index_t level_three = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_LEVEL3);
      |                                                                                          ^~~~~~~~~~~~~~~~~~~~
      |                                                                                          XKB_MOD_NAME_CTRL
../input.c:655:89: error: ‘XKB_VMOD_NAME_LEVEL5’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_CTRL’?
  655 |             xkb_mod_index_t level_five = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_LEVEL5);
      |                                                                                         ^~~~~~~~~~~~~~~~~~~~
      |                                                                                         XKB_MOD_NAME_CTRL
```
2025-04-07 09:25:11 +02:00
Łukasz Wojniłowicz
bdf65672c0
Themes: Add 'Molokai' theme 2025-04-07 09:19:34 +02:00
Daniel Eklöf
1760cb6ab8
config: update default URL regex
The old one is in some cases too liberal. The new one is stricter in
two ways:

1. The protocol list is now explicit, rather than matching anything://
2. Allowed characters are now limited to the "safe character set", the
   "reserved character set", and some from the "unsafe character set"

Furthermore, some of the characters are restricted in how/when they
are allowed:

1. Periods, commas, question marks etc are allowed inside an URL, but
   not at the end.
2. [ ], ( ), " " and ' ' are allowed but only when balanced. This
   allows us to match e.g. [http://foo.bar/foo[bar]] correctly.

Closes #2016
2025-04-07 09:11:16 +02:00
Daniel Eklöf
a50f78c599
Merge branch 'kbd-no-virtual-modifiers'
Closes #2009
2025-03-31 13:04:02 +02:00
Daniel Eklöf
0d8c7db962
changelog: reword, and remove section that no longer applies 2025-03-31 11:08:22 +02:00
Daniel Eklöf
a43614f098
doc: foot.ini: mention virtual modifiers are allowed 2025-03-31 10:25:14 +02:00
Daniel Eklöf
dc99cf7358
key-binding: recognize virtual modifiers, and translate to the corresponding real modifier. 2025-03-31 10:25:06 +02:00
Daniel Eklöf
58910856c8
input: xkb: ignore virtual modifiers
Some compositors (mutter/GNOME is one) adds _virtual_ modifiers to the
set of active modifiers when e.g. Alt, Meta, Super or Hyper is
pressed. For example, pressing Alt+b would result in *both* the Alt
*and* the Mod1 modifier being set.

Since foot makes close to zero assumptions on how the modifiers should
be interpreted, this causes various breakages.

For example, a foot shortcut defined as Mod1+b will not match, since
the Alt modifiers is also set. This has forced users to
redefine/override some of the default key bindings to include the
additional modifiers.

It also causes issues with the kitty keyboard protocol, for some key
combinations. Mainly whether or not to use unshifted key or not,
resulting in incorrect escape sequences.

Since all the "real" modifiers are always set as well, we can safely
ignore the virtual modifiers.

Closes #2009
2025-03-31 08:08:43 +02:00
Daniel Eklöf
c8470f40c1
grid: reflow: fix empty line coalescing
If a range of empty lines ended with a non-empty line at the very
bottom of the to-be-resized grid, all those empty lines were removed.

Closes #2011
2025-03-29 10:16:31 +01:00
Alex Xu (Hello71)
9b776f2d6d
meson: add foot (render.c) -> srgb.h dep
otherwise, depending on ninja dependency resolution order and parallel
build, srgb.h may not be built in time

Fixes: ccf625b991 ("render: gamma-correct blending")
2025-03-26 18:17:59 +01:00
Daniel Eklöf
6922ab2b8e
doc: foot.ini: gamma-correct: move section 2025-03-23 17:00:19 +01:00
Sam McCall
663c9082db
render: dim and brighten using linear rgb interpolation
Adds setting tweak.dim-amount, similar to bold-text-in-bright-amount.

Closes #2006
2025-03-23 15:24:23 +01:00
Daniel Eklöf
5f72f51ae8
changelog: url-mode: show-urls-persistent regression fix 2025-03-20 08:52:19 +01:00
llyyr
cc99db5bc4
url-mode: fix crash when opening multiple urls with persist mode
Fixes: 051cd6ecfc
Closes #2000
2025-03-20 08:52:10 +01:00
Daniel Eklöf
a02c0c8d4d
vt: utf8: insert a REPLACEMENT CHARACTER when an invalid UTF-8 sequence is detected 2025-03-18 18:28:09 +01:00
Daniel Eklöf
878e07da59
vt: utf8: don't discard current byte when an invalid UTF-8 sequence is detected
Example:

  printf "pok\xe9mon\n"

would result in 'pokon' - the 'm' has been discarded along with E9.

While correct, in some sense, it's perhaps not intuitive.

This patch changes the VT parser to instead discard everything up to
the invalid byte, but then try the invalid byte from the ground
state. This way, invalid UTF-8 sequences followed by both plain ASCII,
or longer (and valid) UTF-8 sequences are printed as expected instead
of being discarded.
2025-03-18 14:37:28 +01:00
Daniel Eklöf
6813b321f5
changelog: add new 'unreleased' section 2025-03-17 12:15:36 +01:00
Daniel Eklöf
c2a9fd5dfa
Merge branch 'releases/1.21' 2025-03-17 12:15:16 +01:00
Daniel Eklöf
68f5eab0b0
doc: foot.ini: codespell: shouuld -> should 2025-03-17 12:10:34 +01:00
Daniel Eklöf
49d2c08912
doc: foot.ini: codespell: shouuld -> should 2025-03-17 12:08:27 +01:00
Daniel Eklöf
df32cd0504
meson: bump version to 1.21.0 2025-03-17 12:04:59 +01:00
Daniel Eklöf
3eef3ec877
changelog: prepare for 1.21.0 2025-03-17 12:04:46 +01:00
Daniel Eklöf
d2ede697f9
config: remove deprecated options 'notify' and 'notify-focus-inhibit'
They've been deprecated since 1.18.0
2025-03-17 12:02:57 +01:00
Daniel Eklöf
7dbfdc73b6
doc: foot.init: surface-bit-depth: mention 10-bit surfaces are slow 2025-03-17 08:51:27 +01:00
Daniel Eklöf
cd4ee8ae49
ime: fix initial cursor rectangle being reported as 0,0,0,0
Closes #1994
2025-03-17 08:43:12 +01:00
Craig Barnes
eb9357709b main/client: simplify code for printing --version string 2025-03-14 20:15:11 +00:00
Daniel Eklöf
d48a1c53f5
meson: require wayland-protocols >= 1.41 2025-03-13 13:28:34 +01:00
Daniel Eklöf
a79fd6a7cf
meson: require fcft-3.3.1
fcft-3.3.0 is not binary compatible with 3.2.x, and earlier.
2025-03-13 13:23:25 +01:00
Daniel Eklöf
16c384b707
changelog: mention some of the side-effects the new fcft requirement brings 2025-03-12 10:06:13 +01:00
Daniel Eklöf
7f11ba59ef
fcft: require fcft >= 3.3.0, add support for new scaling-filters
Update tweak.scaling-filter to recognize the new scaling filters added
in fcft-3.3.0.

Since fcft_set_scaling_filter() is deprecated in 3.3.0, don't use it
anymore, and set the scaling filter via fcft_font_options instead.
2025-03-12 10:03:06 +01:00
Daniel Eklöf
cfa178ab25
input: kitty: unittest: don't fail if system has no compose tables 2025-03-11 08:42:03 +01:00
Daniel Eklöf
edbfdd5150
changelog: kitty: release events for composed keys 2025-03-11 08:37:42 +01:00
Daniel Eklöf
7976975a8a
input: kitty: send release events for composed keys 2025-03-11 08:36:37 +01:00
Daniel Eklöf
04fcc5f5b5
input: kitty: regression test for #1987 2025-03-11 08:23:23 +01:00
Daniel Eklöf
8d2627b1ef
input: kitty: always use shifted key when it's the result of a compose
Closes #1987
2025-03-10 15:47:20 +01:00
Daniel Eklöf
605694bc93
grid: set linebreak=false when printing to a line, not when allocating it
This ensures empty lines are treated correctly, and is also more in
line with how lines are handled at runtime, when filling the
scrollback.

For now, set linebreak=false as soon as something is printed on a
line. It will remain like that *until* we reach the end of an old row
with linebreak=true, at which point we set linebreak=true on the
current new line.
2025-03-05 18:55:01 +01:00
Daniel Eklöf
7b6efcf19a
grid: change default value of linebreak to true
This way, all lines are treated as having a hard linebreak, until it's
cleared when we do an auto-wrap.

This change alone causes issues when reflowing text, as now all
trailing lines in an otherwise empty window are treated as hard
linebreaks, causing the new grid to insert lots of unwanted, empty
lines.

Fix by doing two things:

* *clear* the linebreak flag when we pull in new lines for the new
  grid. We only want to set it explicitly, when an old row has its
  linebreak flag set.
* Coalesce empty lines with linebreak=true, and only "emit" them as
  new liens in the new grid if they are followed by non-empty lines.
2025-03-05 18:55:01 +01:00
Daniel Eklöf
a80b32d006
term: tweak linebreaking
Don't set linebreak on linefeed. Instead, rely on the default value of
true, and that it is only cleared when a character is printed while
LCF=1.

Note that printing to a row that has linebreak cleared, will set the
linebreak flag again.
2025-03-05 18:55:00 +01:00
Daniel Eklöf
ccf625b991
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.

The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).

How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.

Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.

Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.

Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.

There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.

Perhaps, in the future, we can enable it by default if:

* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-03-05 18:45:01 +01:00
Adrian fxj9a
6d39f66eb7
config: add search-bindings.delete-to-{start,end} key bindings
Defaults to ctrl+u and ctrl+k respectively.

Closes #1972
2025-03-05 08:48:23 +01:00
Daniel Eklöf
9e6d334bd8
term: reset the grapheme clustering state on cursor movements 2025-03-04 07:50:03 +01:00
Daniel Eklöf
882f4b2468
shm-format: add new shm formats 2025-03-02 10:18:00 +01:00
Daniel Eklöf
4f11d6086f
DECSCUSR+DECRQSS: treat hollow cursor as a block cursor
If the user has configured cursor.style=hollow, make DECSCUSR 1/2 set
the cursor to hollow rather than block, and make DECRQSS DECSCUSR
respond as if cursor.style=block.

The idea is to not expose the hollow variant in DECSCUSR in any way,
to avoid having to extend it with custom encodings.

Another way to think about it is this is just a slightly more
discoverable way of doing:

    cursor.style=block
    cursor.block-cursor-is-hollow=yes
2025-02-21 08:03:41 +01:00
Daniel Eklöf
c41008da31
config+render: allow cursor.style=hollow
Closes #1965
2025-02-19 11:50:25 +01:00
Craig Barnes
101bc28698 terminal: add comment/link to cursor::lcf, to clarify its purpose 2025-02-18 17:32:54 +00:00
Daniel Eklöf
9f9ffa9434
term: set_app_id(): app_id may be NULL, in which case we can't do strlen()
Closes #1963
2025-02-18 15:09:23 +01:00
Daniel Eklöf
ba5f4abdd4
changelog: --server=FD failing on FreeBSD 2025-02-16 13:56:43 +01:00
Guillaume Outters
d66a00678d
server: fix --server=<fd> on OSes returning SO_ACCEPTCONN > 1
Closes #1956
2025-02-16 13:55:00 +01:00
Daniel Eklöf
76503fb86a
term: append zero-width grapheme breaking characters to previous cell
When compiled with grapheme clustering support, zero-width characters
that also are grapheme breaks, were ignored (not stored in the
grid).

When utf8proc says the character is a grapheme break, we try to print
the character to the current cell. But this is only done when width >
0. As a result, zero width grapheme breaks were simply discarded.

This only happens when grapheme clustering is enabled; when disabled,
all zero width characters are appended.

Fix this by also requiring the width to be non-zero when if we should
append the character or not.

Closes #1960
2025-02-16 09:13:13 +01:00
Daniel Eklöf
4abbaf1345
doc: foot.ini: font: add one more fontfeatures example
Add a fontfeatures example where we:

* set multiple features
* assign a value to the features (as opposed to just enabling a
  boolean feature)
2025-02-16 09:11:52 +01:00
Daniel Eklöf
d7a4f9e99e
grid: reflow: fix cursor reflow when LCF is set
When the cursor is at the end of the line, with a pending wrap (LCF
set), the lcf flag should be cleared *and* the cursor moved one cell
to the right.

Before this patch, we cleared LCF, but didn't move the cursor.

Closes #1954
2025-02-13 08:06:21 +01:00
Ludovico Gerardi
888a6770da
themes: update Tokyo Night Light 2025-02-13 08:05:07 +01:00
Daniel Eklöf
7445471238
grid: reflow: shell integration: no need to check for >= 0 2025-02-10 13:19:57 +01:00
Daniel Eklöf
8b63869f57
render: minimum window size: 2 cols -> 1 col 2025-02-10 13:19:57 +01:00
Daniel Eklöf
eced7cf1d6
grid: reflow: don't special case the first cell in a multi-column character
Wrap *all* cell copying logic in a for-loop for the characters
width. This _should_, in theory, mean reflow of e.g. cursor
coordinates in the middle of a multi-column character works correctly.

Also fix reflow of cmd start/end integration.
2025-02-10 13:19:57 +01:00
Daniel Eklöf
6a181c9f72
grid: performance: check for non-NULL before comparing with terminator
This should be slightly faster in the normal(?) case (no styled
underlines or OSC-8).
2025-02-10 13:19:57 +01:00
Daniel Eklöf
3d66db63cc
grid: refactor reflow
We've been trying to performance optimize reflow by "chunking" cells;
try to gather as many as possible, and memcpy a chunk at once.

The problem is that a) this quickly becomes very complex, and b) is
very hard to get right for multi-column characters, especially when we
need to truncate long ones due to the window being too small.

Refactor, and once again walk and copy all cells one by one. This is
slower, but at least it's correct.
2025-02-10 13:19:50 +01:00
Daniel Eklöf
c63202ee0e
url-mode: regex: don't try to NULL-terminate an invalid vline 2025-02-10 13:09:07 +01:00
Daniel Eklöf
970d95c5a1
doc: foot.ini: fix 'hashes' regex example
It's A-F, not A-f
2025-02-10 13:08:33 +01:00
Daniel Eklöf
fce755aafe
forgejo: better names for templates 2025-02-10 12:58:35 +01:00
Daniel Eklöf
26acf41d13
grid: pull in misc.h when TIME_REFLOW is defined 2025-02-10 09:08:14 +01:00
Daniel Eklöf
98db965813
url-mode: terminate last virtual line before regex matching
If the last line doesn't have a hard linebreak, it was never NULL
terminated, causing regexec() to crash on an out-of-bounds access.
2025-02-10 08:54:42 +01:00
Johannes Altmanninger
4e5ad6e013
Fix URL detection regression on lines with NUL bytes
Commit 859b4c89 (url-mode: wip: more work on regex matching,
2025-01-30) regressed URL detection in weechat. Some of the URLs
still work but others don't. This is because regexec() stops at the
first NUL, thus skipping the rest of the line. weechat seems create
NUL cells between their UI widgets.

Work around this by replacing NUL with space. This is probably correct
because selecting and copying those cells also translates to space
(not sure where in the code).
2025-02-10 08:06:08 +01:00
Daniel Eklöf
325086291b
config: regex: fix invalid free
Zero-initialize the 'launch' spawn template before calling
value_to_spawn_template(). This is needed since
value_to_spawn_template() tries to free the old value before assigning
the new one.

Closes #1951
2025-02-10 07:43:52 +01:00
Daniel Eklöf
d84b0d4c6a
Merge branch 'text-width-protocol' 2025-02-06 14:03:33 +01:00
Daniel Eklöf
8d20b82721
changelog: text-sizing protocol 2025-02-06 14:02:38 +01:00
Daniel Eklöf
a3a404a257
render: resize: note why min_cols=7 2025-02-06 14:02:38 +01:00
Daniel Eklöf
d7e8f29ee2
grid: reflow: get number of spacers to insert from the old grid
When checking if we're breaking in the middle of a multi-column
character, we counted spacers starting from the break point. But,
the character may be wider than that. Use the fact that the spacers
cells encode how many *more* there are after them; when we get to the
first one, we know exactly how wide the character is.
2025-02-06 14:02:38 +01:00
Daniel Eklöf
9840204097
term: print-non-ascii: propagate existing forced-width
When appending to an existing composed character, "inherit" its forced
width, if set.

Also make sure to actually _use_ the forced width, if set, rather than
the calculated width.

This fixes an issue when appending zero-width codepoints to a
forced-width combining character.
2025-02-06 14:02:38 +01:00
Daniel Eklöf
0f93766614
osc: text-size: disable optimization
The optimization prevents the forced-width to be set on the new
combining character, causing issues when followed by more zero-width
codepoints.
2025-02-06 14:02:38 +01:00
Daniel Eklöf
ed35a238d6
doc: ctlseq: add OSC 66 (kitty text sizing) 2025-02-06 14:02:38 +01:00
Daniel Eklöf
3998f8570c
composed: codespell: infinitely 2025-02-06 07:46:00 +01:00
Daniel Eklöf
1260004330
osc: text-sizing: implement w=0, plus optimize single-codepoint cases
If there's a single codepoint in the text portion of the OSC sequence,
and its calculated width matches the forced width, print it directly
to the grid instead of emitting a combining character.

When w=0, we split up the text string "as we normally would". Since we
don't support any other text-sizing parameters, this means simply
printing each codepoint to the grid.
2025-02-06 07:45:58 +01:00
Daniel Eklöf
d3f692990e
term+vt: refactor: move "utf8" char processing to term_process_and_print_non_ascii()
This function "prints" any non-ascii character (i.e. any character
that ends up in the action_utf8_print() function in vt.c) to the
grid. This includes grapheme cluster processing etc.

action_utf8_print() now simply calls this function.

This allows us to re-use the same functionality from other
places (like the text-sizing protocol).
2025-02-06 07:45:20 +01:00
Daniel Eklöf
7a8d2b5e01
osc: wip: kitty text size protocol
This brings initial support for the new kitty text-sizing
protocol. Note hat only the width-parameter ('w') is supported. That
is, no font scaling, and no multi-line cells.

For now, only explicit widths are supported. That is, w=0 does not yet
work.

There are a couple of changes to the renderer, to handle e.g.

    OSC 66 ; w=6 ; foobar ST

There are two ways this can get rendered, depending on whether
grapheme shaping has been enabled. We either shape it, and get an
array of glyphs back that we render. Or, we rasterize each codepoint
ourselves, and render each resulting glyph.

The two cases ends up in two different renderer loops, that worked
somewhat different. In particular, the first case has probably never
been tested/used at all...

With this patch, both are changed, and now uses some heuristic to
differentiate between multi-cell text strings (like in the example
above), or single-cell combining characters. The difference is mainly
in which offset to use for the secondary glyphs.

In a multi-cell string, each glyph is mapped to its own cell, while in
the combining case, we try to map all glyphs to the same cell.
2025-02-06 07:42:38 +01:00
Daniel Eklöf
1111f7e918
grid: reflow: handle composed characters longer than 2 cells
The logic that tries to ensure we don't break a line in the middle of
a multi-cell character was flawed when the number of cells were larger
than 2.

In particular, if the number of cells to copy were limited by the
number of cells left on the current (new) line, and were less than the
length of the multi-cell character, then we failed to insert the
correct number of spacers, and also ended up misplacing the multi-cell
character; instead of pushing it to the next line, it was inserted on
the current line, even though it doesn't fit.

Also change how trailing SPACER cells are rendered (cells that are
"fillers" at then end of a line, when a multi-column character was
pushed over to the next line): don't copy the previous cell's
attributes (which may be wrong anyway), use default attributes
instead.
2025-02-06 07:42:38 +01:00
Daniel Eklöf
e248e73753
composed: refactor: break out lookup with collision detection 2025-02-06 07:42:37 +01:00
Daniel Eklöf
1181f74d19
composed: re-factor: break out key calculation from vt.c 2025-02-06 07:42:37 +01:00
Daniel Eklöf
88dcde3ed8
term: insert-mode: handle combining characters correctly
When the client application emits combining characters, for example
multi-codepoint emojis, in insert-mode, we ended up pushing partial
graphemes to the right, for each codepoint, resulting in too many
cells (and with the wrong content) being inserted.

The fix is fairly simple; don't "insert" when appending characters to
an existing grapheme cluster.

This isn't something we can detect easily in print_insert() (it would
require us to do grapheme clustering again). Fortunately, we do have
the required information in action_utf8_print(). So, pass this
information as a boolean to term_print().

Closes #1947
2025-02-06 07:37:55 +01:00
Daniel Eklöf
dd01783f88
Merge branch 'regex-mode'
Closes #1386
Closes #1872
2025-02-05 13:47:06 +01:00
Daniel Eklöf
9d8021de47
changelog: custom regex's 2025-02-05 13:46:00 +01:00
Daniel Eklöf
9e12f791c5
doc: regex: custom regex's aren't URLs 2025-02-05 13:43:11 +01:00
Daniel Eklöf
b1f16c84e0
doc: improve regex example 2025-02-05 13:35:17 +01:00
Daniel Eklöf
0a32dc3820
spawn template variables are on the form ${}, not {} 2025-02-05 13:35:17 +01:00
Daniel Eklöf
a984531ce5
url-mode: use the first *sub* expression as URL
When auto-matching URLs (or custom regular expressions), use the
first *subexpression* as URL, rather than the while regex match.

This allows us to write custom regular expressions with prefix/suffix
strings that should not be included in the presented match.
2025-02-05 13:35:17 +01:00
Daniel Eklöf
31f536ff8c
config: remove debug logging 2025-02-05 13:35:17 +01:00
Daniel Eklöf
cf4324e6c6
tests: config: handle regex key bindings 2025-02-05 13:35:17 +01:00
Daniel Eklöf
2f902c1f5b
doc: foot.ini: document custom regular expressions 2025-02-05 13:35:17 +01:00
Daniel Eklöf
9d0f5cbd2a
foot.ini: improve documentation of custom regex 2025-02-05 13:35:17 +01:00
Daniel Eklöf
051cd6ecfc
config+url: add support for user-defined regex patterns
Users can now define their own regex patterns, and use them via key
bindings:

    [regex:foo]
    regex=foo(bar)?
    launch=path-to-script-or-application {match}

    [key-bindings]
    regex-launch=[foo] Control+Shift+q
    regex-copy=[foo] Control+Mod1+Shift+q

That is, add a section called 'regex:', followed by an
identifier. Define a regex and a launcher command line.

Add a key-binding, regex-launch and/or regex-copy (similar to
show-urls-launch and show-urls-copy), and connect them to the regex
with the "[regex-name]" syntax (similar to how the pipe-* bindings
work).
2025-02-05 13:35:17 +01:00
Daniel Eklöf
f718cb3fb0
xmalloc: calling xrealloc() or xreallocarray() with a 0-size is UB in C23
And likely to in future POSIX too.
2025-02-05 13:35:17 +01:00
Daniel Eklöf
ab4426f987
url-mode: regex: make sure there's always room for the NULL terminator 2025-02-05 13:35:17 +01:00
Daniel Eklöf
130b05f02b
foot.ini+doc: add default value of url.regex 2025-02-05 13:35:17 +01:00
Daniel Eklöf
d41b28bd02
url-mode+config: wip: add url.regex option 2025-02-05 13:35:17 +01:00
Daniel Eklöf
e76d8dd7af
config: remove url.{uri-characters,protocols} 2025-02-05 13:35:17 +01:00
Daniel Eklöf
05207fcde3
url-mode: wip: regex: tweak debug log message 2025-02-05 13:35:17 +01:00
Daniel Eklöf
6d344f82ee
url-mode: wip: regex: mention changes from original regex 2025-02-05 13:35:16 +01:00
Daniel Eklöf
031382f428
url-mode: wip: regex: don't allow {}, do allow matched [] 2025-02-05 13:35:16 +01:00
Daniel Eklöf
859b4c8921
url-mode: wip: more work on regex matching
Remove the old auto-detection and instead use the regex matches.
2025-02-05 13:35:16 +01:00
Daniel Eklöf
1c15ee940d
url-mode: wip: convert to regex matching for auto-detection 2025-02-05 13:35:16 +01:00
Daniel Eklöf
32919b1049
grid: typo 2025-02-05 13:35:16 +01:00
Daniel Eklöf
aae794e9bd
xmalloc: add xreallocarray() 2025-02-05 13:35:16 +01:00
Thomas Bonnefille
9443ac7e29
box-drawings: handle architecture with soft-float
Currently, architecture using soft-floats doesn't support instructions
FE_INVALID, FE_DIVBYZERO, FE_OVERFLOW and FE_UNDERFLOW and so building
on those architectures results with a build error.
As the sqrt math function should set errno to EDOM if an error occurs,
fetestexcept shouldn't be mandatory.

This commit removes the float environment error handling.

Signed-off-by: Thomas Bonnefille <thomas.bonnefille@bootlin.com>
2025-02-05 13:31:46 +01:00
sewn
8de378963b
server: don't instantiate a client without a monitor 2025-02-05 14:23:17 +03:00
Daniel Eklöf
2fe72effa9
term: ptmx pause/resume: don't modify the FDM if ptmx has been closed
This fixes error message spam when resizing a terminal window executed
with --hold, and where the client application has terminated.
2025-02-05 11:39:06 +01:00
Daniel Eklöf
6f9129fa3a
Revert "forgejo: server/standalone: what happens when we set required=true?"
This reverts commit 70aa033d79.
2025-02-04 14:56:22 +01:00
Daniel Eklöf
70aa033d79
forgejo: server/standalone: what happens when we set required=true? 2025-02-04 14:55:49 +01:00
Daniel Eklöf
fcfdbeebcf
forgejo: remind user to sanitize pasted config 2025-02-04 14:55:09 +01:00
Daniel Eklöf
230d8b6f70
forgejo: server/standalone: tweak wording 2025-02-04 14:54:02 +01:00
Daniel Eklöf
9c882cfdab
forgejo: issue happens in foot --server, standalone, or both? 2025-02-04 14:52:52 +01:00
Daniel Eklöf
dc4e9fc25b
forgejo: ask user to provide distro *version*, when applicable 2025-02-04 14:48:02 +01:00
Daniel Eklöf
51128a3484
input: match unshifted key-bindings before shifted
That is, try to match e.g. Control+shift+a, before trying to match
Control+A.

In most cases, order doesn't matter. There are however a couple of
symbols where the layout consumes the shift-modifier, and the
generated symbol is the same in both the shifted and unshifted
form. One such example is backspace.

Before this patch, key-bindings with shift-backspace would be ignored,
if there were another key-binding with backspace.

So, for example, if we had one key-binding with Control+Backspace, and
another with Control+Shift+Backspace, the latter would never trigger,
as we would always match the first one.

By checking for unshifted matches first, we ensure
Control+Shift+Backspace does match.
2025-01-31 09:07:42 +01:00
Daniel Eklöf
bee17a95b8
input: ignore key-bindings without modifiers when matching untranslated/raw
When matching the unshifted symbol, or the raw key code, ignore all
key bindings that don't have any modifiers.

This fixes an issue where it was impossible to enter (some of the)
numbers on the keypad, **if** there was a key-binding for
e.g. KP_Page_Up, or KP_Page_Down.
2025-01-31 07:35:54 +01:00
Daniel Eklöf
d24f700256
key-bindings: add keypad variants to existing default key-bindings 2025-01-31 07:29:16 +01:00
Attila Fidan
5286808b6c
input: close fd on no/unrecognized keymap format 2025-01-30 10:58:19 +00:00
Daniel Eklöf
6e2bdd663a
forgejo: config: render as .ini, instead of the default markdown 2025-01-27 13:18:09 +01:00
Daniel Eklöf
c2c8d29272
forgejo: remove "e.g." from placeholder text 2025-01-27 13:16:43 +01:00
Daniel Eklöf
8b408f0039
forgejo: add optional field for terminal multiplexers 2025-01-27 13:15:59 +01:00
Daniel Eklöf
1c7c9f6c16
doc: foot.ini: describe key binding match logic 2025-01-27 12:31:50 +01:00
Daniel Eklöf
8d6f0d0583
key-bindings: try all bindings in translated mode before matching untranslated, and then finally raw
When trying to match key bindings, we do three types of matching:

* Match the _translated_ symbol (e.g. Control+C)
* Match the _untranslated_ symbol (e.g. Control+Shift+c)
* Match raw keyboard codes

This was done for *each* key binding. This meant we sometimes matched
a keybinding in raw mode, even though there was a
translated/untranslated binding that would match it too. All depending
on the internal order of the key binding list.

This patch changes it, so that we first try all bindings in translated
mode, then all bindings in untranslated mode, and finally all bindings
in raw mode.

Closes #1929
2025-01-27 12:31:40 +01:00
Daniel Eklöf
7a5353d18a
forgejo: application -> application(s) 2025-01-27 06:38:14 +01:00
Daniel Eklöf
fda9638edd
forgejo: add optional field for shell/TUI 2025-01-27 06:37:40 +01:00
Daniel Eklöf
43206e6601
config: fix memory leak on e.g. "not a valid XKB key name" errors 2025-01-27 06:34:20 +01:00
Daniel Eklöf
846271e8d3
render: resize: configure with only one dimension being zero
The protocol states:

    If the width or height arguments are zero, it means the client
    should decide its own window dimension. This may happen when the
    compositor needs to configure the state of the surface but doesn't
    have any information about any previous or expected dimension.

The wording is a bit ambiguous; does it mean we should set *both*
width and height to values we choose, even if only one dimension is
zero in the configure event? Or does it mean that we should choose the
value for the dimension that is zero in the configure event?

Regardless, it's pretty clear that it does *not* mean we should *only*
choose width and height if *both* dimensions are zero in the configure
event. This is foot's behavior before this patch, meaning if only one
of them is zero, foot assumed the compositor wanted us to set the
width (or height) to zero...

Change this, so that we now choose value for the "missing" dimension,
but do use the compositor provided value for the other dimension.

Closes #1925

Relevant issues:

* https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/155
* https://github.com/YaLTeR/niri/issues/1050
2025-01-26 09:28:54 +01:00
Daniel Eklöf
97385b007f
grid: reflow: regression: remove (truncate) SPACER cells at the end of line
When printing a double-width glyph at the end of the line, it will get
pushed to the next line if there's only one cell left on the current
line.

That last cell on the current line is filled with a SPACER value.

When reflowing the text, the SPACER cell should be "removed", so that
the double-width glyph continues directly after the text on the
previous line.

9567694bab fixed an issue where
reflowing e.g. neofetch output incorrectly removed spaces between the
logo, and the system info. But also introduced a regression where
SPACER values no longer are removed.

This patch tries to fix it, by adding back empty cells, but NOT SPACER
cells.
2025-01-25 08:46:21 +01:00
Daniel Eklöf
f39b75f296
changelog: cwd > 1024 chars 2025-01-24 06:52:52 +01:00
Daniel Eklöf
787e886ff0
client: port bfabc5450b to footclient 2025-01-24 06:51:13 +01:00
camel-cdr
bfabc5450b
fix infinite loop/oom when cwd longer then 1024
The code reads cwd into a buffer, which is expanded while errno is ERANGE, with the intent of growing the buffer until the path fits.
While getcwd will set errno on error, it will not reset it once the path fits into the buffer.
So to not get an infinite loop once errno is ERANGE, we need to make sure to reset errno, such that the loop behaves as expected.
2025-01-24 06:46:58 +01:00
Daniel Eklöf
736328ab6b
config: check for FcNameUnparse() failure 2025-01-24 06:38:02 +01:00
Daniel Eklöf
ba7ecc4669
input: kitty: refactor, try to simplify and be less confusing
Use better named variables while juggling the shifted vs. unshifted
key codes.

Switch to variable names appropriate for the kitty keyboard protocol
once we have all the unshifted, shifted and base key codes done. It's
not until then we can decide which key code to use as the main key,
and whether or not to report the alternate key code.
2025-01-22 12:37:36 +01:00
Daniel Eklöf
f301f6eccc
input: kitty: add more test cases 2025-01-22 12:24:06 +01:00
Daniel Eklöf
6ca1a2c2dc
input: kitty: only set 'alternate' if the "unshifted" code is printable
Fixes a regression where alt+backspace was reported as ^[[8:127;3u
instead of ^[[127;3u.
2025-01-22 10:26:44 +01:00
Daniel Eklöf
f62a5ed1ff
input: codespell: indiciates -> indicates 2025-01-22 08:04:17 +01:00
Daniel Eklöf
5e65f3f07e
shm: codespell: re-using -> reusing 2025-01-22 08:04:17 +01:00
Daniel Eklöf
09f718878f
input: kitty: add initial unit test
Test a couple of key combos from the se and de(neo) layouts.

This unit test isn't intended to test _all_ key combinations, for all
kitty flag combinations, but to be more of a regression test -
whenever we discover a buggy, weird combo, add it here.
2025-01-22 08:04:17 +01:00
Daniel Eklöf
786037791c
input: kitty: improve handling of alternate+base keys even more
* Always do a base key lookup. Before this, we didn't do that if we
  matched the XKB sym to a lookup table entry (i.e. keypads, cursor keys
  etc.)
* Try to retrieve the unshifted symbol also when we matched the symbol
  to a lookup table entry. When successful, we now report an alternate
  key for keypad and cursor keys; before this patch, we only did that
  for keys that didn't have an entry in the lookup table (i.e. ASCII
  chars etc).

This improves compatibility with kitty (and the kitty keyboard
protocol) in more corner cases. One particular example is the neo
keyboard layout, where part of the regular keys act as keypad keys
when num-lock is active.
2025-01-20 10:34:45 +01:00
Daniel Eklöf
2ff38e86a7
input: kitty: fix alternate codepoint sometimes not being reported
When alternate key reporting is enabled (i.e. when we're supposed to
report the shifted key along with the unshifted key), we try to figure
out whether the key really is shifted or not (and thus which xkb
keysym to use for the unshifted and shifted keys in the escape).

This was done by getting the layout's *all* modifier combinations that
produce the shifted keysym, and if any of of them contained a modifier
that isn't supported by the kitty protocol, the shifted and unshifted
keys are derived from the same keysym. This is to ensure we handle
things like AltGr-combos correctly.

The issue is, since there may be more than one modifier combination
generating the shifted keysym, we may end up using the wrong keysym
just because _another_ combination set contains modifiers not
supported by the kitty protocol. What we're interrested in is whether
the *pressed* set of modifiers contains such modifiers.

Closes #1918
2025-01-20 09:08:47 +01:00
Daniel Eklöf
22e1b1610f
vt: combining chars: ensure 'key' is within range
When there's a key collision, we increment the key and check
again. When doing this, we need to ensure the key is withing range,
and wrap around to 0 if the key value is too large.
2025-01-18 10:22:24 +01:00
Daniel Eklöf
10c4e94e1b
Merge branch 'releases/1.20' 2025-01-18 09:43:57 +01:00
Daniel Eklöf
bb60618941
changelog: move utf8proc entry to correct release 2025-01-18 09:31:05 +01:00
Daniel Eklöf
e1d9b57f83
changelog: add back entry to 1.20.1, removed in de3becef96 2025-01-18 09:31:05 +01:00
Daniel Eklöf
771af699f0
meson: bump version to 1.20.2 2025-01-18 09:25:36 +01:00
Daniel Eklöf
15d9b08307
changelog: prepare for 1.20.2 2025-01-18 09:25:19 +01:00
Daniel Eklöf
077177e8a9
forgejo: bugs: short explanation of what an IME is 2025-01-18 09:24:05 +01:00
Daniel Eklöf
9361596d02
forgejo: bugs: config: uppercase description's first letter 2025-01-18 09:24:04 +01:00
Daniel Eklöf
14cd128992
forgejo: bugs: add required field 'config'
Require all bug submitters to include their foot configs.
2025-01-18 09:24:03 +01:00
Daniel Eklöf
b808eb5162
forgejo: bugs: add required field 'distro' 2025-01-18 09:24:02 +01:00
Daniel Eklöf
c5529808c4
term: cursor_refresh(): don't try to dirty the grid if we don't have one
If the compositor sends a keyboard enter event before our window has
been mapped, foot crashes; the enter event triggers a cursor
refresh (hollow -> non-hollow block cursor), which crashes since we
haven't yet allocated a grid.

Fix by no-op:ing the refresh if the window hasn't been configured yet.

Closes #1910
2025-01-18 09:24:00 +01:00
Daniel Eklöf
39061e0422
changelog: colors.flash-alpha=1.0 2025-01-18 09:23:46 +01:00
Alexander Orzechowski
de3becef96
Revert "config: don't allow colors.flash-alpha to be 1.0"
This reverts commit 56d2c3e990.
2025-01-18 09:23:41 +01:00
Alexander Orzechowski
ab5a168dbf
terminal: Refresh only overlay when flash expires
If we call render_refresh, that will wait for a callback to the main
surface. In the case of a flash, the main surface might not get callbacks
if the compositor implements fancy culling optimizations like wlroots
wlr_scene compositors such as sway version >=1.10.
2025-01-18 09:23:40 +01:00
Alexander Orzechowski
881eb28134
render: Expose render_overlay
This function updates the overlay that foot uses. It will be used to
update the overlay when the flash effect ends.
2025-01-18 09:23:39 +01:00
Daniel Eklöf
ad1e2d7d05
changelog: 'CSI 21 t' and 'OSC 176 ?' disabled 2025-01-18 09:23:35 +01:00
Daniel Eklöf
ba81480ebb
doc: ctlseqs: remove 'CSI 21 t' 2025-01-18 09:23:34 +01:00
Daniel Eklöf
354ba8dad8
osc: ignore 'OSC 176 ?' - report app ID
It's not widely used (don't know _any_ application that uses it), and
can be used to trick users to run unwanted commands.
2025-01-18 09:23:33 +01:00
Daniel Eklöf
a62194caee
csi: ignore 'CSI 21 t' - report window title
It's not widely used (don't know _any_ application that uses it), and
can be used to trick users to run unwanted commands.
2025-01-18 09:23:33 +01:00
Daniel Eklöf
87ef869767
ci: "meson [options]" is deprecated (do "meson setup [options]" instead) 2025-01-18 09:23:31 +01:00
Daniel Eklöf
80ef366bde
changelog: utf8proc.h not found 2025-01-18 09:23:31 +01:00
Daniel Eklöf
f7031a2161
meson: fix dependencies (utf8proc missing in lots of places) 2025-01-18 09:23:29 +01:00
Daniel Eklöf
2784ae8793
ci: sr.ht: try to bring up to date, and pull from codeberg 2025-01-18 09:23:29 +01:00
Daniel Eklöf
fc154872c0
ci: sr.ht: disable x64 (rely on codeberg only) 2025-01-18 09:23:28 +01:00
Daniel Eklöf
c854f35579
changelog: add missing issue ref 2025-01-18 09:23:27 +01:00
Daniel Eklöf
45e5a4b024
changelog: add new 'unreleased' section 2025-01-18 09:23:26 +01:00
Daniel Eklöf
aeb28e33fa
features: add +/-system-bell to version output 2025-01-17 11:22:23 +01:00
Daniel Eklöf
2a07a2e6b9
Add support for the new Wayland protocol xdg-system-bell
From the release notes:

    system bell - allowing e.g. terminal emulators to hand off system
    bell alerts to the compositor for among other things accessibility
    purposes

The new protocol is used when the new config option
bell.system=yes (and the compositor implements the protocol,
obviously).

The system bell is rung independent of whether the foot window has
keyboard focus or not (thus relying on compositor configuration to
determine whether anything should be done or not in response to the
bell).

The new option is enabled by default.
2025-01-17 10:21:50 +01:00
Daniel Eklöf
7e7fd0468d
forgejo: bugs: short explanation of what an IME is 2025-01-10 13:15:02 +01:00
Daniel Eklöf
3b96de2aa4
forgejo: bugs: config: uppercase description's first letter 2025-01-10 13:14:02 +01:00
Daniel Eklöf
b5835cbd58
forgejo: bugs: add required field 'config'
Require all bug submitters to include their foot configs.
2025-01-10 13:13:05 +01:00
Daniel Eklöf
feb4dd102b
forgejo: bugs: add required field 'distro' 2025-01-10 13:05:35 +01:00
Daniel Eklöf
2c309227f1
term: cursor_refresh(): don't try to dirty the grid if we don't have one
If the compositor sends a keyboard enter event before our window has
been mapped, foot crashes; the enter event triggers a cursor
refresh (hollow -> non-hollow block cursor), which crashes since we
haven't yet allocated a grid.

Fix by no-op:ing the refresh if the window hasn't been configured yet.

Closes #1910
2025-01-09 07:56:57 +01:00
Daniel Eklöf
e136abf1ef
changelog: colors.flash-alpha=1.0 2025-01-09 07:56:10 +01:00
Daniel Eklöf
20466e7fa4
Merge branch 'opaque-flash' 2025-01-09 07:55:29 +01:00
Alexander Orzechowski
301101e7d9
Revert "config: don't allow colors.flash-alpha to be 1.0"
This reverts commit 56d2c3e990.
2025-01-09 07:53:50 +01:00
Alexander Orzechowski
c2add346ad
terminal: Refresh only overlay when flash expires
If we call render_refresh, that will wait for a callback to the main
surface. In the case of a flash, the main surface might not get callbacks
if the compositor implements fancy culling optimizations like wlroots
wlr_scene compositors such as sway version >=1.10.
2025-01-09 07:53:50 +01:00
Alexander Orzechowski
f0253633d3
render: Expose render_overlay
This function updates the overlay that foot uses. It will be used to
update the overlay when the flash effect ends.
2025-01-09 07:53:50 +01:00
Daniel Eklöf
25cd5dae26
Merge branch 'ignore-title-and-app-id-report-queries'
Related to #1894
2025-01-07 13:12:54 +01:00
Daniel Eklöf
bcc176cdf1
changelog: 'CSI 21 t' and 'OSC 176 ?' disabled 2025-01-07 13:01:25 +01:00
Daniel Eklöf
d9bd9b7ffa
doc: ctlseqs: remove 'CSI 21 t' 2025-01-07 13:01:25 +01:00
Daniel Eklöf
06a32d553e
osc: ignore 'OSC 176 ?' - report app ID
It's not widely used (don't know _any_ application that uses it), and
can be used to trick users to run unwanted commands.
2025-01-07 13:01:21 +01:00
Daniel Eklöf
8e425c4e97
csi: ignore 'CSI 21 t' - report window title
It's not widely used (don't know _any_ application that uses it), and
can be used to trick users to run unwanted commands.
2025-01-07 13:01:15 +01:00
Daniel Eklöf
42f78b7f9c
ci: "meson [options]" is deprecated (do "meson setup [options]" instead) 2025-01-04 12:06:49 +01:00
Daniel Eklöf
6999968ee5
changelog: utf8proc.h not found 2025-01-04 10:33:23 +01:00
Daniel Eklöf
a2960aa457
meson: fix dependencies (utf8proc missing in lots of places) 2025-01-04 10:22:29 +01:00
Daniel Eklöf
169471cf23
ci: sr.ht: try to bring up to date, and pull from codeberg 2025-01-04 09:50:30 +01:00
Daniel Eklöf
77e30c8b45
ci: sr.ht: disable x64 (rely on codeberg only) 2025-01-04 09:50:06 +01:00
Daniel Eklöf
9667fe2b26
changelog: add missing issue ref 2025-01-03 08:08:52 +01:00
Daniel Eklöf
8414966013
changelog: add new 'unreleased' section 2025-01-03 08:03:06 +01:00
Daniel Eklöf
bdc821c488
Merge branch 'releases/1.20' 2025-01-03 08:02:48 +01:00
Daniel Eklöf
b0e6645395
meson: bump version to 1.20.1 2025-01-03 08:01:35 +01:00
Daniel Eklöf
4ed0361b97
changelog: prepare for 1.20.1 2025-01-03 08:01:22 +01:00
Daniel Eklöf
b66a076bf1
Merge branch 'master' into releases/1.20 2025-01-03 08:00:53 +01:00
Daniel Eklöf
f5c10a2452
render: fix order we're checking custom codepoints
Fixes a crash when trying to print a "Legacy Computing"
symbol (U+1FB00-U+1FB9B).

Closes #1901
2025-01-03 07:33:14 +01:00
evplus
9cde179034 themes: add iterm theme from alacritty 2025-01-02 11:53:49 +01:00
Daniel Eklöf
9f5be55d1c
osc: disable debug logging 2025-01-02 09:12:11 +01:00
Daniel Eklöf
7c1cee0373
notify: disable debug logging 2025-01-02 09:12:06 +01:00
Daniel Eklöf
56d2c3e990
config: don't allow colors.flash-alpha to be 1.0
A compositor will not send a frame callback for our main window if it
is fully occluded (for example, by a fully opaque overlay...). This
causes the overlay to stuck.

For regular buffers, it _should_ be enough to *not* hint the
compositor it's opaque. But at least some compositor special cases
single-pixel buffers, and actually look at their pixel value.

Thus, we have two options: implement frame callback handling for the
overlay sub-surface, or ensure we don't use a fully opaque
surface. Since no overlays are fully opaque by default, and the flash
surface is the only one that can be configured to be
opaque (colors.flash-alpha), and since adding frame callback handling
adds a lot of boilerplate code... let's go with the simpler solution
of
2025-01-02 09:10:51 +01:00
Daniel Eklöf
c7ab7b3539
term: limit app-id to 2048 characters
Unsure if the protocol imposes a limit (haven't found any
documentation), or if the issue is in the libwayland implementation,
or wlroots (triggers in at least sway+river).

The issue is that setting a too long app-id causes the
compositor (river at least) to peg the CPU at 100%, and stop sending
e.g. frame callbacks to foot.

Closes #1897
2025-01-02 08:58:48 +01:00
Daniel Eklöf
f8ebe985a8
changelog: add missing issue ref 2025-01-01 09:29:11 +01:00
Daniel Eklöf
1dc922e5a4
changelog: add new 'unreleased' section 2025-01-01 09:23:33 +01:00
Daniel Eklöf
5048cb0c33
Merge branch 'releases/1.20' 2025-01-01 09:23:08 +01:00
Daniel Eklöf
9b1c31ebcb
meson: bump version to 1.20.0 2025-01-01 09:21:51 +01:00
Daniel Eklöf
ded55b7276
changelog: prepare for 1.20.0 2025-01-01 09:21:26 +01:00
Daniel Eklöf
e851d44ac9
kitty kbd: don't generate release events for plain Enter+Tab+Backspace
From the specification:

    The Enter, Tab and Backspace keys will not have release events
    unless Report all keys as escape codes is also set, so that the
    user can still type reset at a shell prompt when a program that
    sets this mode ends without resetting it.

Closes #1892
2025-01-01 08:06:52 +01:00
Daniel Eklöf
e38ec79be1
osc: add option to disable OSC-52, partially or fully
Closes #1867
2024-12-22 07:16:12 +01:00
Daniel Eklöf
67bd5dd460
selection: fix crash when tripple clicking in a region containing NUL characters
If a cell contains a NUL character, it was incorrectly treated as a
quote, later triggering an assertion.

Patch by Johannes Altmanninger
2024-12-22 07:09:37 +01:00
Daniel Eklöf
3b0c2a3543
misc: add missing include stdlib.h (for free())
Closes #1887
2024-12-20 15:22:14 +01:00
Daniel Eklöf
d523e7a676
term: set_app_id() + set_window_title(): only allow printable characters 2024-12-17 11:01:17 +01:00
Daniel Eklöf
9a1b59adae
box-drawings: implement octants 2024-12-08 12:55:57 +01:00
heather7283
768f254286 pgo: prefer full-headless-sway over full-headless-cage 2024-12-08 14:02:11 +04:00
cy
5034209087
quit should be in key-bindings 2024-12-08 09:16:15 +01:00
Denis Zharikov
ca13c7b4f5
Update rose-pine, add theme variants 2024-12-08 09:15:19 +01:00
heather7283
7e88e0bfdc
pgo: explicitly set LLVM_PROFILE_FILE envvar
When building foot with pgo under gentoo's portage, LLVM tried to
generate profile data files directly under system root, triggering
sandbox violation and causing build to fail. Setting this envvar fixes
the issue by explicitly specifying profiling data location.

Reference: https://clang.llvm.org/docs/UsersManual.html#profiling-with-instrumentation
2024-12-08 09:10:43 +01:00
heather7283
de305a7e58
pgo: run sway with --unsupported-gpu flag
Sway refuses to run if it detects an nvidia GPU on the system,
causing pgo build to fail. Adding --unsupported-gpu flag disables
this behaviour.
2024-12-08 09:10:43 +01:00
Daniel Eklöf
256749c6d0
term: get_font_dpi(): remove invalid assertion
Closes #1874
2024-11-24 08:01:31 +01:00
Daniel Eklöf
b43f19cb50
vt: don't call fcft_precompose() if font is NULL
This fixes a crash when doing a partial PGO build (where we don't have
any fonts available).
2024-11-02 20:11:14 +01:00
Daniel Eklöf
d3cd4ad933
char32: use utf8proc_charwidth() instead of wcwidth(), when available
It appears to be slightly more up-to-date with recent Unicode
versions.

In particular, it handles the new "Symbols for Legacy Computing
Supplement" block, introduced in Unicode 16.

Closes #1865
2024-11-02 15:12:39 +01:00
Daniel Eklöf
f3e443ea47
osc: 9: ignore ConEmu/Windows Terminal sequences
OSC-9 sequences starting with "<number>;" are now ignored, as they are
ConEmu sequences, and not iTerm notifications.
2024-11-02 08:33:03 +01:00
Daniel Eklöf
ab3af2af37
unicode: update data files to 16.0
Yup, there's no _actual_ changes 🤷
2024-10-31 07:26:07 +01:00
Daniel Eklöf
4aae5222fe
changelog: osc-9/777 crash when body is empty 2024-10-31 07:03:12 +01:00
Jack Wilsdon
689549bb1f
osc: notify: fix crash with no message
Closes #1866
2024-10-31 07:03:04 +01:00
Mark Stosberg
813b514f63
docs: document more default bindings in search scrollback mode.
These were introduced by #1496 but not fully documented then.
2024-10-25 08:58:40 -04:00
Daniel Eklöf
8edf273f6e
changelog: add new 'unreleased' section 2024-10-23 13:55:10 +02:00
Daniel Eklöf
e1469ed057
Merge branch 'releases/1.19' 2024-10-23 13:54:49 +02:00
Daniel Eklöf
cb91fbb4b6
meson: bump version to 1.19.0 2024-10-23 13:51:15 +02:00
Daniel Eklöf
6d11e93e2f
changelog: prepare for 1.19.0 2024-10-23 13:50:54 +02:00
Daniel Eklöf
d68da27a7f
uri: skip query/fragment parsing when dealing with file:// URIs
Also, ignore invalid query/fragments (i.e. if the fragment comes
before the query).

Closes #1840
2024-10-23 08:47:21 +02:00
Daniel Eklöf
996e5fa630
Revert "url-mode: don't strip the file:// prefix from localhost URIs"
This reverts commit 54722369d8.
2024-10-23 08:46:30 +02:00
Daniel Eklöf
511aad419b
config: add color.sixelN options
These options allows you to configure the default sixel color palette.
2024-10-23 08:35:30 +02:00
Daniel Eklöf
e891abdd6a
render: remove unnecessary call to wl_surface_damage_buffer()
The damage region(s) are translated to wl_surface_damage_buffer()
calls after the entire frame has been rendered.
2024-10-06 11:26:35 +02:00
Daniel Eklöf
ce38f5b413
render: sixels: update damage region when rendering sixels
This fixes flickering when foot is forced to double-buffer (e.g when
running under KDE, or smithay based compositors).

Since the damage region isn't updated, the sixel images aren't
included in the memcpy that is done to transfer the last frame's
updated regions to the next frame.

As a result, every other frame will have the sixels, while the others
don't.

Closes #1851
2024-10-05 08:55:48 +02:00
Daniel Eklöf
4afb94687c
changelog: #1715: "ghost" lines when selecting text 2024-09-21 09:17:38 +02:00
Daniel Eklöf
6ad84dab2d
render: do dirty/clean consistency check before rendering sixels
Since the sixels may render some of the cells, we want the check to
happen before the sixels.
2024-09-21 09:11:28 +02:00
Daniel Eklöf
49ed8b5e21
selection: set row->dirty when clearing the cell->attrs.clean bit
Closes #1715
2024-09-21 09:11:07 +02:00
Daniel Eklöf
798b44934f
render: double-buffer: optimization: skip clean rows
When scanning the grid for all-dirty rows (that we can remove from the
damage region we're about to memcpy from the old frame), check the
row->dirty bit, and skip scanning the cells of that row altogether.

We're only looking for rows where all cells are dirty - those rows can
be removed from the region we copy from the old frame, since the
entire row will be re-rendered anyway.

If a row is clean, it *must* be copied from the old frame.
2024-09-21 09:11:07 +02:00
Daniel Eklöf
a9fefcf58b
render: (debug): assert row->dirty vs. cell->attrs.clean consistency 2024-09-21 09:11:07 +02:00
Daniel Eklöf
046d959657
shm: fix compilation when FORCED_DOUBLE_BUFFERING is enabled 2024-09-21 09:11:07 +02:00
Daniel Eklöf
e2aeb7f336
render: xcursor_is_valid(): don't crash when there's no theme loaded
When trying to set a custom cursor shape, we first validate it. This
is done by checking against known server-side names, and then trying
to load the cursor from the client side cursor theme.

But, if we're using server side names, there is no theme loaded, and
foot crashed.

Fix by checking if we have a theme loaded, and if not, fail the cursor
shape name validation.
2024-09-21 09:10:48 +02:00
Daniel Eklöf
297cb370aa
render: add missing include, limits.h 2024-09-15 09:56:41 +02:00
Daniel Eklöf
33f3818520
Merge branch 'toplevel-icon' 2024-09-13 09:20:50 +02:00
Daniel Eklöf
76b58b5663
changelog: remove escape sequences we've reverted 2024-09-13 09:04:18 +02:00
Daniel Eklöf
9151685d04
csi: revert implementation of CSI 20 t 2024-09-13 09:04:18 +02:00
Daniel Eklöf
7984f08925
osc: OSC-1 does not set the icon, it sets the icon _label_
In fact, there appears there *is* no escape sequence to set the icon.

Keep most of the logic in place, but in practice, we'll always set the
icon to the app-id. That is, at startup, we set it to the configured
app-id (either from config, or the command line).

OSC-176, which sets the app-id, also updates the icon (to the app-id).
2024-09-13 09:04:18 +02:00
Daniel Eklöf
c6208a98c8
main: include toplevel-icon support in --version output 2024-09-13 09:04:18 +02:00
Daniel Eklöf
f5caa2d265
pgo: add missing stub for render_refresh_icon() 2024-09-13 09:04:18 +02:00
Daniel Eklöf
97ec375c67
toplevel-icon: implement OSC-1, CSI 20/21/22/23 t
* The toplevel icon is now set to the app-id, unless "overridden" by
  OSC-1 or OSC-0.
* Implemented OSC-1
* OSC-0 extended to also set the icon
* Implemented CSI 20 t - report window icon
* Implemented CSI 21 t - report window title
* Implemented CSI 22 ; 1 t - push window icon
* Implemented CS 23 ; 1 t - pop window icon
* Extended CSI 22/23 ; 0 t to also push/pop the icon
* Verify app-id set by OSC-176 is valid UTF-8
* Verify icon set by OSC-0/1 is valid UTF-8
2024-09-13 09:04:17 +02:00
Daniel Eklöf
3f8a1fc85b
changelog: xdg-toplevel-icon-v1 2024-09-13 09:04:17 +02:00
Daniel Eklöf
b34137dde3
toplevel-icon: set to app-id, instead of hardcoding to "foot"
And, special case "footclient", and map it to "foot".
2024-09-13 09:04:17 +02:00
Daniel Eklöf
0cb07027f2
wayland: set toplevel icon
If the xdg-toplevel-icon-v1 protocol is available, and we have the
corresponding manager global, set the toplevel icon to "foot".

Note: we do *not* provide any pixel data. This is by design; we want
to keep things simple.

To be able to provide pixel data, we would have to either:

* embed the raw pixel data in the foot binary
* link against either libpng or/and e.g. nanosvg, locate, at run-time,
  the paths to our own icons, and load them at run-time.
* link against either libpng or/and e.g. nanosvg, and, at run-time, do
  a full icon lookup. This would also require us to add a config option
  for which icon theme to use.

Of the two, I would prefer the first option. But, let's skip this
completely for now.

By providing the icon as a name, the compositor will have to lookup
the icon itself. Compositors supporting icons is likely to already
support this.

So what do we gain by implementing this protocol? Compositors no
longer has to parse .desktop files and map our app-id to find the icon
to use.

There's one question remaining. With this patch, the icon name is
hardcoded to "foot", just like our .desktop files. But, perhaps we
should use the app-id instead? And if so, should we also change the
icon when the app-id changes?

My gut feeling is, yes, we should use the app-id instead, and yes, we
should update the icon when the app-id is changed at run-time.
2024-09-13 09:04:17 +02:00
Daniel Eklöf
28a1c67dd5
wayland: bind the xdg-toplevel-icon manager global 2024-09-13 09:04:17 +02:00
Daniel Eklöf
5ef69fc591
meson: detect wayland-protocols >= 1.37, and conditionally enable xdg-toplevel-icon-v1 2024-09-13 09:04:17 +02:00
Craig Barnes
d4a1283797
xsnprintf: various improvements related to xvsnprintf() and xsnprintf()
Summary of changes:

* Make xvsnprintf() static
* restrict-qualify pointer arguments (as done by the libc equivalents)
* Make comments and spec references more thorough
* Remove pointless `n <= INT_MAX` assertion (see comment)
* Use FATAL_ERROR() instead of xassert() (since the assertion is inside
  a shared util function but the caller is responsible for ensuring the
  condition holds true)
* Change some callers to use size_t instead of int for the return value
  (negative returns are impossible and all subsequent uses are size_t)

The updated comments and code were taken (and adapted) from:

49260bb154/src/util/xsnprintf.c (L6-50)

This work was entirely authored by me and I hereby license this
contribution under the MIT license (stated explicitly, so that
there's no ambiguity w.r.t. the original license).
2024-09-13 09:01:15 +02:00
Craig Barnes
31f88e636c readme: typo: foot-ctlseq -> foot-ctlseqs 2024-09-11 22:08:45 +01:00
Daniel Eklöf
1925593a37
render: resize(): don't overflow the number of scrollback lines
The config system allows setting the scrollback lines to
2**32-1.

However, the total number of grid lines is the scrollback lines plus
the window size, and then rounded *up* to the nearest power of two.

Furthermore, the number of rows is represented with a plain 'int'
throughout the code base.

The largest positive integer that fits in an int is 2**31-1. That
however, is not a power of two.

The largest positive integer, that also is a power of two, that fits
in an int is 2**30, or 1073741824.

Ideally, we'd just cast the line count to a 64-bit integer, and call
__builtin_clzl{,l}() on it, and then take the smallest value of that,
or 2**30. But, for some reason, __builtin_clzl(), and
__builtin_clzll() appears to ignore bits above 32, despite they being
typed to long and long long. Bug?

Instead, ensure we never call __builtin_clz() on anything larger than
2**30.

Closes #1828
2024-09-09 06:58:52 +02:00
Daniel Eklöf
8a4bbbf5cb
readme: update mastodon link 2024-09-08 13:45:20 +02:00
Daniel Eklöf
c8185aec1d
desktop: rename to foot{,client,-server}.desktop
That is, skip the reverse DNS naming scheme suggested by the .desktop
specification, and directly match our app-ids ("foot", and
"footclient").

This simplifies .desktop -> window instance mapping, allowing DEs to
match the filenames directly, without having to look at the
StartupWMClass key in the .desktop files.

These are the original names of the .desktop files. There were
renamed (to use the reverse DNS syntax) to please the flathub people,
who *required* this scheme to accept the foot package.

But, since:

* We don't package foot ourselves
* We don't go out of our way to support non-distro packaging schemes
* Flathub still hasn't merged the foot PR (it's now 2 years old)
* There are no know issues in any known DE that prevents a non-reverse
  DNS .desktop filename from working
* There are plenty of other applications that doesn't use reverse DNS
  names (a very clear majority, in my case)

Let's just revert back to the simpler naming scheme.

Closes #1607
2024-09-08 13:06:27 +02:00
Daniel Eklöf
11ff9ba7ec
.desktop: remove StartupWMClass cludge 2024-09-08 10:22:29 +02:00
Daniel Eklöf
a916a6a8ca
metainfo: add recent releases, update feature list 2024-09-08 10:21:28 +02:00
Daniel Eklöf
c41f55c3a0
sixel: default bg color is now taken from the sixel palette, not the ANSI bg color
The wording in the original VT340 documentation is vague:

    Pixel positions specified as 0 are set to the current background
    color.

What does that mean? We _thought_ it meant the current ANSI background
color, as set with e.g. CSI 4x m.

It's still all a bit vague, but seeing that we have separate palettes
for text and graphic (should we?), it doesn't make sense to use the
ANSI background color as the default sixel background color.

So, use entry 0 from the sixel palette instead.
2024-09-05 07:33:05 +02:00
Daniel Eklöf
c5bb1fb2ed
notifications: BEL and OSC-777 now focuses the window on notification activation
Or put more propertly; if the notification daemon, and the
notification helper used by foot has been configured
properly (i.e. they both support XDG activation tokens), notifications
generated by BEL and OSC-777 will now raise/focus the window when the
default action of the notification is activated - typically by
clicking the notification.

Closes #1822
2024-09-05 07:16:15 +02:00
Daniel Eklöf
c15ebbfa2e
changelog: strikeout-thickness 2024-08-26 19:36:26 +02:00
Oleh Hushchenkov
b47a4dd255
add setting for strikeout thickness 2024-08-26 19:34:47 +02:00
Daniel Eklöf
01fa59b6b7
changelog: mbsntoc32() failure checks 2024-08-20 07:17:58 +02:00
Daniel Eklöf
8f9f3dbd9d
term_set_window_title(): fix bad check for invalid UTF-8
mbsntoc32() returns (size_t)-1 on failure, not (char32_t)-1.
2024-08-20 07:15:22 +02:00
Daniel Eklöf
7dd204fd31
osc: notify: fix bad check for invalid UTF-8
mbsntoc32() returns (size_t)-1 on failure, not (char32_t)-1.
2024-08-20 07:14:53 +02:00
Daniel Eklöf
be13788a4f
changelog: resize-keep-grid 2024-08-20 07:12:19 +02:00
Daniel Eklöf
b3fd994fd3
changelog: add missing reference to #1787 2024-08-20 07:12:09 +02:00
Andrew J. Hesford
1969717527
feature: add resize-keep-grid to allow text reflow on font changes 2024-08-20 07:07:29 +02:00
tokyo4j
a2fc2a986e
render: follow cursor.unfocused-style regardless of cursor.style
Before this commit, cursor.unfocused-style was effective only with
cursor.style=block
2024-08-20 06:59:33 +02:00
Daniel Eklöf
96c30cd410
term: thrd_join() returns thrd_success on success, not 0
Closes #1812
2024-08-15 17:20:12 +02:00
Daniel Eklöf
dc5ff7db28
changelog: add new 'unreleased' section 2024-08-14 12:02:56 +02:00
Daniel Eklöf
4cd65f0e70
Merge branch 'releases/1.18' 2024-08-14 12:02:36 +02:00
Daniel Eklöf
ce9c9f6be6
meson: bump version to 1.18.1 2024-08-14 12:00:20 +02:00
Daniel Eklöf
447b02b530
changelog: prepare for 1.18.1 2024-08-14 12:00:08 +02:00
Daniel Eklöf
54c1660a1e
Merge branch 'master' into releases/1.18 2024-08-14 11:59:23 +02:00
Daniel Eklöf
2896c18981
osc: regression: fix OSC-111 handling of alpha changes
When the background alpha changes from fully opaque, to transparent,
or vice versa, we need to do more than just repaint the affected
cells.

For example, we need to create new surfaces with the correct pixel
format.

OSC-11 (set background color) already does this, but the same alpha
checking logic was missing in OSC-111 (reset background color).

Fixes #1801
2024-08-14 08:53:45 +02:00
Daniel Eklöf
eb185bfa47
utils/xtgettcap: fix possible NULL deref, found by -fanalyzer 2024-08-14 08:53:21 +02:00
Daniel Eklöf
cee0c5423a
main: spell out the most common reason for setlocale() to fail 2024-08-13 17:33:31 +02:00
Daniel Eklöf
6d351ffc43
main: invalid locale != non-UTF-8 locale
When doing locale fallback, and printing user notifications and log
warnings, better separate the case "locale is invalid" from "locale is
valid but not UTF-8".

Closes #1798
2024-08-13 17:33:23 +02:00
Daniel Eklöf
7e1894978f
slave: prefix user notifications with 'foot'
To make it clearer _who_ is emitting the warning/errors
2024-08-09 13:55:21 +02:00
Daniel Eklöf
ee9c76ded0
osc: kitty: update 's' to the latest spec
* The spec now defines a couple of "standard" names. Translate these
  to the freedesktop compliant names.
* The query response no longer contains 'xdg-names', but instead list
  the supported standard names.
2024-08-09 08:25:36 +02:00
Daniel Eklöf
481ce82d66
doc: foot.ini: document the Unicode ranges covered by the builtin glyphs 2024-08-09 08:24:53 +02:00
Daniel Eklöf
bef613e656
csd: track pointer when rendering and handling CSD button clicks
* Render button as highlighted only when pointer is above them
* Releasing the mouse button while *not* on the button does *not*
  activate the button

When pressing, and holding, a mouse button, the compositor keeps
sending motion events for the surface where the button was pressed,
even if the mouse has moved outside it.

We also don't get any surface leave/enter events.

This meant that the button was rendered as highlighted, and a click
registered, if the user pressed the mouse button while on the button,
and then moved the cursor away from the button before releasing the
button.

This patch fixes it, by checking if the current cursor coordinates
fall within the button surface.
2024-08-09 08:20:59 +02:00
Daniel Eklöf
7ec9ca2b95
input: CSD buttons are now triggered when releasing the mouse button
This is how most UIs work.

Note that we (at least on River) don't get any surface enter/leave
events while a button is held. This means we can't detect if the user
pressed the mouse button while on a CSD button, but then moves the
mouse outside. Releasing the mouse button will still activate the CSD
button.

Closes #1787
2024-08-09 08:20:36 +02:00
Daniel Eklöf
803f712332
meson: add "prefix: '#include <unistd.h>" to cc.has_function()
When checking for execvpe, include <unistd.h>
2024-08-09 08:15:13 +02:00
Shogo Yamazaki
9d5d84a835
meson: fix false positive detection of memfd_create
Add the arguments to `has_function` to properly detect availability of the
function on SDK environments.
2024-08-09 08:14:12 +02:00
Daniel Eklöf
01eca82d33
readme: remove CI badges for gitlab and sr.ht 2024-08-07 17:15:00 +02:00
Daniel Eklöf
8607fb6312
readme: repology: use four columns instead of three 2024-08-07 17:14:26 +02:00
Daniel Eklöf
a0b5f79f32
readme: repology: use three columns instead of one 2024-08-07 17:13:38 +02:00
Daniel Eklöf
fb74a2df27
changelog: osc-99: sound support 2024-08-04 15:23:33 +02:00
Daniel Eklöf
84d36606cb
osc: kitty notifications: add support for XDG sound names 2024-08-04 15:23:31 +02:00
Daniel Eklöf
6349262491
osc: kitty notifications: implement s=silent
This implements part of the new 's' (sound) parameter; the 'silent'
value. When s=silent, we set the ${muted} template argument to
"true". It is intended to set the 'suppress-sound' hint:

    notify-send --hint BOOLEAN:suppress-sound:${muted}
2024-08-04 14:20:35 +02:00
Daniel Eklöf
a3a35f2c8c
term: reload_fonts(): don't ignore return value of thrd_join()
This should fix the

    ‘ret’ may be used uninitialized

warning

Closes #1789
2024-08-04 11:55:25 +02:00
Craig Barnes
b27841e1b5 Merge branch 'xstrjoin3' 2024-08-03 10:42:37 +01:00
Daniel Eklöf
6b72108ee2
osc: kitty notifications: ignore invalid IDs
Notification IDs must only use characters from [a-zA-Z0-9_-+.]

    Terminals **must** sanitize ids received from client programs
    before sending them back in responses, to mitigate input injection
    based attacks. That is, they must either reject ids containing
    characters not from the above set, or remove bad characters when
    reading ids sent to them.

Foot implements the first: reject IDs containing characters not from
the above set.
2024-08-03 11:05:58 +02:00
Craig Barnes
f87c9bb9f7 xmalloc: remove delim param from xstrjoin() and add separate xstrjoin3()
This avoids the need for an unused third argument for most xstrjoin()
calls and replaces the cases where it's needed with a more flexible
function. Code generation is the same in both cases, when there are 2
string params and a compile-time known delimiter.

This commit also converts 4 uses of xasprintf() to use xstrjoin*().

See also: https://godbolt.org/z/xsjrhv9b6
2024-08-03 08:51:06 +01:00
Daniel Eklöf
62b0b65d47
changelog: add new 'unreleased' section 2024-08-02 14:33:26 +02:00
Daniel Eklöf
bf51d8c19b
Merge branch 'releases/1.18' 2024-08-02 14:33:08 +02:00
Daniel Eklöf
b5e692ef8b
meson: bump version to 1.18.0 2024-08-02 14:28:16 +02:00
Daniel Eklöf
1272632f3b
changelog: prepare for 1.18.0 2024-08-02 14:27:57 +02:00
Daniel Eklöf
a176d8fbdb
readme: typo: foot -> foot's 2024-08-02 12:06:08 +02:00
Daniel Eklöf
aabb239c0f
readme: xtgettcap: mention tigetstr() compability 2024-08-02 10:33:18 +02:00
Daniel Eklöf
901daefd96
changelog: more template parameters we've added to desktop-notifications 2024-08-02 10:18:59 +02:00
Daniel Eklöf
a9e462d952
Remove a number of unused includes 2024-08-02 08:28:13 +02:00
Daniel Eklöf
09ab8c6c7c
notify: codespell: programatically -> programmatically 2024-08-02 08:25:29 +02:00
Daniel Eklöf
1a89538700
notify: codespell: notificaton -> notification 2024-08-02 08:24:03 +02:00
Daniel Eklöf
ebd8ad8937
doc: foot.ini: codespell: furhermore -> furthermore 2024-08-02 08:24:03 +02:00
Daniel Eklöf
ea2f0e7c3f
osc: kitty notifications: cleanup and update to latest version of spec
* Don't store a list of unfinished notifications. Use a single one. If
  the notification ID of the 'current' notification doesn't match the
  previous, unfinished one, the 'current' notification replaces the
  previous one, instead of updating it.

* Update xstrjoin() to take an optional delimiter (for example ','),
  and use that when joining categories and 'alive IDs'.

* Rename ${action-arg} to ${action-argument}

* Update handling of the 'n' parameter (symbolic icon name); the spec
  allows it to be used multiple times, and the terminal is supposed to
  pick the first one it can resolve. Foot can't resolve icons at all,
  neither can 'notify-send' or 'fyi' (which is what foot typically
  executes to display a notification); it's the notification daemon that
  resolves icons.

  The spec _could_ be interpreted to mean the terminal should lookup
  .desktop files, and use the value of the 'Icon' key from the first
  matching .desktop files. But foot doesn't read .desktop files, and I
  don't intend to implement XDG directory scanning and parsing of
  .desktop files just to figure out which icon to use.

  Instead, use a simple heuristics; use the *shortest* symbolic
  names. The idea is pretty simple: plain icon names are typically
  shorter than .desktop file IDs.
2024-08-02 08:07:13 +02:00
Daniel Eklöf
18b87b2e20
notify: don't forget terminating NULL when patching notify helper's argv 2024-07-31 18:33:24 +02:00
Daniel Eklöf
76ac910b11
osc: kitty notifications: buttons, icons, app-name, categories etc
First, icons have been finalized in the specification. There were only
three things we needed to adjust:

* symbolic names are base64 encoded
* there are a couple of OSC-99 defined symbolic names that need to be
  translated to the corresponding XDG icon name.
* allow in-band icons without a cache ID (that is, allow applications
  to use p=icon without having to cache the icon first).

Second, add support for the following new additions to the protocol:

* 'f': custom app-name, overrides the terminal's app-id
* 't': categories
* 'p=alive': lets applications poll for currently active notifications
* 'id' is now 'unset' by default, rather than "0"
* 'w': expire time (i.e. notification timeout)
* "buttons": aka actions. This lets applications add additional (to
  the terminal defined "default" action) actions. The 'activated' event
  has been updated to report which button/action was used to activate
  the notification.

To support button/actions, desktop-notifications.command had to be
reworked a bit.

There's now a new config option:
desktop-notifications.command-action-arg. It has two template
arguments ${action-name} and ${action-label}.

command-action-arg gets expanded for *each* action.

${action-name} and ${action-label} has been replaced by ${action-arg}
in command. This is a somewhat special template, in that it gets
replaced by *all* instances of the expanded actions.
2024-07-31 16:22:17 +02:00
Daniel Eklöf
d87b81dd52
notify: disable debug logging 2024-07-30 17:31:15 +02:00
Daniel Eklöf
259a75e957
notify: remove debug assertion
(FD 0 _is_ valid, after all)
2024-07-30 17:24:48 +02:00
Daniel Eklöf
ba79bf1602
notify: ${icon}: don't fallback to app-id
* Revert --icon to ${app-id}
* Use --hint STRING:image-path:${icon} instead
* ${icon} no longer expands to ${app-id} if notification has no icon
2024-07-30 17:16:02 +02:00
Daniel Eklöf
0a5ba708e4
notify: don't close FD 0
This fixes a regression where closing a terminal instance, or hard- or
soft-resetting a terminal caused FD 0 to be closed.

This meant it became re-usable. Usually, by memfd_create() when
allocating a new surface buffer. So far nothing _really_ bad has
happened.

But what if FD 0 is now used by a memfd, and we close _another_
terminal instance?

This causes our memfd to be closed. And then, when e.g. trying to
scroll the terminal content: fallocate() fails with bad FD.
2024-07-30 16:33:19 +02:00
Daniel Eklöf
a3ad740251
readme: IRC: update link to point to the web interface 2024-07-27 08:48:32 +02:00
Daniel Eklöf
00ec2a8f09
readme: update mastodon link 2024-07-27 08:37:41 +02:00
Daniel Eklöf
9cf99ea4bf
notify: close notification by sending SIGINT to helper
If the user hasn't configured a custom 'desktop-notifications.close'
command, try to close the notification by sending SIGINT to the
notification helper.

This is best-effort:

* If there's no helper running, we do nothing (except warn)
* We don't verify, in any way, the notification is actually closed
* We don't send any other signals, under any circumstances. That is,
  no SIGTERM, no SIGKILL. Ever.
2024-07-26 16:28:47 +02:00
Daniel Eklöf
8f16fe54d3
pgo: add notify_close() stub 2024-07-25 19:31:27 +02:00
Daniel Eklöf
c4d9f8a8ff
osc: kitty notifications: implement the 'close' request
Add a new config option, desktop-notifications.close, defining what to
execute to close a notification. It has a single template parameter,
${id}, that is expanded to the external notification ID foot may have
picked up from the notification helper.

notify-send does not support closing notifications, and it appears
impossible to pass an *unsigned* integer as argument to gdbus. Hence
no default value for the new 'close' option.

Example:

    printf '\e]99;i=123;this is a notification\e\\'
    printf '\e]99;i=123:p=close;\e\\'
2024-07-25 19:24:28 +02:00
Daniel Eklöf
c797222930
osc: kitty notification: implement 'close' events
Application can now request to receive a 'close' event when the
notification is closed (but not necessarily activated), by adding
'c=1' to the notification request.
2024-07-25 18:47:23 +02:00
Daniel Eklöf
d53f0aea75
notify: rename 'report' -> 'report_activated' 2024-07-25 18:45:58 +02:00
Daniel Eklöf
0ce4ef6000
notify: kitty notifications: fix ID string in activation event
We forgot to prefix the notification ID with 'i='
2024-07-25 18:45:04 +02:00
Daniel Eklöf
a213e14ca3
Merge branch 'kitty-notifications' 2024-07-25 18:32:57 +02:00
Daniel Eklöf
a6bc9cafaf
osc+notify: strcmp() -> streq() 2024-07-24 16:04:14 +02:00
Daniel Eklöf
f56da385fe
notify: try to read the daemon assigned notification ID from stdout
And document the things we recognize in the notification helper's
stdout.
2024-07-24 16:02:19 +02:00
Daniel Eklöf
e271027c0c
config: notify-send: it's "action=label", not "action,label" 2024-07-24 16:01:42 +02:00
Daniel Eklöf
37ab3b1603
notify: don't create icon file on disk when we're not going to use it
We always prefer the symbolic name. Thus, there's no need to write raw
PNG data to disk if we have a symbolic name.

Furthermore, keep the file open until the cache entry is evicted.
2024-07-24 15:59:52 +02:00
Daniel Eklöf
24168ed86e
osc: kitty notifications: don't include '?' in the query reply
No need to say we support queries, in the query reply...
2024-07-24 15:58:19 +02:00
Daniel Eklöf
ecbec57a47
notify: split up the ${action} template parameter
Split it up into two, ${action-name} and ${action-label}.

Dunstify, for example, has a different syntax compared to notify-send:

notify-send: default=foobar
dunstify: default,foobar
2024-07-23 19:08:21 +02:00
Daniel Eklöf
d5c773a58b
notify: bug: always adjust amount of data left in stdout buffer 2024-07-23 18:48:45 +02:00
Daniel Eklöf
70b4638a75
osc: kitty notifications: implement query 2024-07-23 18:32:23 +02:00
Daniel Eklöf
511d4817d3
doc: foot.ini: desktop-notification: remove 'notification dismissal' 2024-07-23 16:52:18 +02:00
Daniel Eklöf
79832c16e2
notify: name the activation action 'default'
This is less unique, but also works better with notification daemons
that trigger the 'default' action when e.g. clicked.
2024-07-23 16:48:15 +02:00
Daniel Eklöf
045ead985c
notify: don't focus/report on notification dismissal
Only do it when the notification was activated.

Here, activated means the 'click to activate' notification action was
triggered.

How do we tie everything together?

First, we add a new template parameter, ${action}. It's intended to be
used with e.g. notify-send's --action option.

When the action is triggered, notify-send prints its name on stdout,
on a separate line. Look for this in stdout. Only if we've seen it do
we focus/report the notification.
2024-07-23 16:41:52 +02:00
Daniel Eklöf
55a4e59ef9
notify: if there's a symbolic icon name, use it
even if there's no graphical ID set.

In other words, if there *is* a graphical ID, use the icon cache. Only
if there is no graphical ID in the notification request do we fallback
to the symbolic name. This means no icon will be displayed if there's
no matching icon in the cache.

Some examples. You can either pre-load the cache (with inline PNG
data, a symbolic name, or both):

    printf '\e]99;g=123:n=firefox:p=icon:e=1;<PNG data>\e\\'
    printf '\e]99;g=123;this is a notification\e\\'

or

    printf '\e]99;n=firefox;this is a notification\e\\'
2024-07-23 15:30:01 +02:00
Daniel Eklöf
fabfef9c82
notify: consume_stdout(): fix ASAN warning
This is an ASAN false positive; size is always 0 when we're passing a
NULL pointer.

Still, the warning is easy to avoid, so let's do that to reduce the
noise level.
2024-07-23 15:29:08 +02:00
Daniel Eklöf
e59efb1233
osc: remove unused includes 2024-07-23 15:28:47 +02:00
Daniel Eklöf
50efd9726d
pgo: updated stubs for notification functions 2024-07-23 12:15:50 +02:00
Daniel Eklöf
d0a5425155
notify: add_icon(): check return value of write() 2024-07-23 12:15:29 +02:00
Daniel Eklöf
9814cf5779
notify: clean up logging messages 2024-07-23 12:12:50 +02:00
Daniel Eklöf
efa5b9cea6
osc: cleanup 2024-07-23 12:12:38 +02:00
Daniel Eklöf
b3108e1ad2
notify: separate active notifications from unfinished kitty notifications
This fixes an issue where it wasn't possible to trigger multiple
notifications with the same kitty notification ID. This is something
that works in kitty, and there's no reason why it shouldn't work.

The issue was that we track stdout, and the notification helper's PID
in the notification struct. Thus, when a notification is being
displayed, we can't re-use the same notification struct instance for
another notification.

This patch fixes this by adding a new notification list,
'active_notifications'. Whenever we detect that we need to track the
helper (notification want's to either focus the window on activation,
or send an event to the application), we add a copy of the
notification to the 'active' list.

The notification can then be removed from the 'kitty' list, allowing
kitty notifications to re-use the same ID over and over again, even if
old notifications are still being displayed.
2024-07-23 11:53:30 +02:00
Daniel Eklöf
ccb184ae64
osc: kitty notifications: updated support for icons
This implements the suggested protocol discussed in
https://github.com/kovidgoyal/kitty/issues/7657.

Icons are handled by loading a cache. Both in-band PNG data, and
symbolic names are allowed.

Applications use a graphical ID to reference the icon both when
loading the cache, and when showing a notification.

* 'g' is the graphical ID
* 'n' is optional, and assigns a symbolic name to the icon
* 'p=icon' - the payload is icon PNG data. It needs to be base64
  encoded, but this is *not* implied. I.e. the application *must* use
  e=1 explicitly.

To load an icon (in-band PNG data):

    printf '\e]99;g=123:p=icon;<base64-encoded-png-data>\e\\'

or (symbolic name)

    printf '\e]99;g=123:n=firefox:p=icon;\e\\'

Of course, we can combine the two, assigning *both* a symbolic
name, *and* PNG data:

    printf '\e]99;g=123:n=firefox:p=icon;<base64-encoded-png>\e\\'

Then, to use the icon in a notification:

    printf '\e]99;g=123;this is a notification\e\\'

Foot also allows a *symbolic* icon to be defined and used at the same
time:

    printf '\e]99;g=123:n=firefox;this is a notification\e\\'

This obviously won't work with PNG data, since it uses the payload
portion of the escape sequence.
2024-07-23 11:29:05 +02:00
Daniel Eklöf
c7cffea9ee
notify: stdout is a bad name 2024-07-23 09:42:14 +02:00
Daniel Eklöf
b319618af1
notify: XDG token is now expected to be prefixed with xdgtoken=
This patch modifies our stdout reader to consume input as we go,
instead of all at once when stdout is closed. This will make it easier
to add support for reading e.g. the daemon assigned notification ID in
the future, and also ensures we see the XDG activation token "as soon
as possible".

Furthermore, to be more future proof, require the XDG activation token
to be prefixed with 'xdgtoken=', and ignore other lines.

Thus, instead of treating *all* of stdout as the XDG activation token,
parse stdout line-by-line, and ignore everything that does not begin
with 'xdgtoken='. Everything (on that line) following 'xdgtoken=' is
treated as the activation token.
2024-07-23 09:33:18 +02:00
Daniel Eklöf
e52d6e3fb8
osc: kitty notifications: use xstrjoin() instead of xasprintf() 2024-07-23 08:05:19 +02:00
Daniel Eklöf
e88ec86c93
pgo: update notify_notify() prototype, add notify_free() 2024-07-23 07:43:56 +02:00
Daniel Eklöf
7268ee9078
pgo: update spawn() prototype 2024-07-23 07:43:42 +02:00
Daniel Eklöf
0209458cc0
changelog: new desktop-notifications config section 2024-07-23 07:17:21 +02:00
Daniel Eklöf
5905ea0d84
osc: kitty notifications: implement focus|report
This patch adds support for window focusing, and sending events back
to the client application when a notification is closed.

* Refactor notification related configuration options:
    - add desktop-notifications sub-section
    - deprecate 'notify' in favor of 'desktop-notifications.command'
    - deprecate 'notify-focus-inhibit' in favor of
      'desktop-notifications.inhibit-when-focused'
* Refactor: rename 'struct kitty_notification' to 'struct
  notification'
* Pass a 'struct notification' to notify_notify(), instead of many
  arguments.
* notify_notify() now registers a reaper callback. When the notifier
  process has terminated, the notification is considered closed, and we
  either try to focus (activate) the window, or send an event to the
  client application, depending on the notification setting.
* For the window activation, we need an XDG activation token. For now,
  assume *everything* written on stdout is part of the token.
* Refactor: remove much of the warnings from OSC-99; we don't
  typically log anything when an OSC/CSI has invalid values.
* Add icon support to OSC-99. This isn't part of the upstream
  spec. Foot's implementation:
    - uses the 'I' parameter
    - the value is expected to be a symbolic icon name
    - a quick check for absolute paths is done, and such icon requests
      are ignored.
* Added ${icon} to the 'desktop-notifications.command' template. Uses
  the icon specified in the notification, or ${app-id} if not set.
2024-07-23 07:17:21 +02:00
Daniel Eklöf
12152a8ae4
unicode-mode: disable debug logging 2024-07-23 07:17:21 +02:00
Daniel Eklöf
69f56b86b7
wayland: add wayl_activate()
wayl_activate() takes an XDG activation token and does an XDG
activation request.
2024-07-23 07:17:21 +02:00
Daniel Eklöf
a42f990818
spawn: add optional reaper callback, return pid_t
This will allow spawn() callers to do things when the spawned process
has terminated.
2024-07-23 07:17:21 +02:00
Daniel Eklöf
57af75f988
osc: kitty notifications: use body as title, if no title is set
This mirrors kitty's behavior; if the user didn't set a title, but did
set the body/text, use the body as title instead.
2024-07-23 07:17:21 +02:00
Daniel Eklöf
b0bf8ca5f7
osc/notify: add support for OSC-99, kitty desktop notifications
This adds limited support for OSC-99, kitty desktop notifications[^1]. We
support everything defined by the "protocol", except:

* 'a': action to perform on notification activation. Since we don't
  trigger the notification ourselves (over D-Bus), we don't know a)
  which ID the notification got, or b) when it is clicked.
* ... and that's it. Everything else is supported

To be explicit, we *do* support:

* Chunked notifications (d=0|1), allowing the application to append
  data to a notification in chunks, before it's finally displayed.
* Plain UTF-8, or base64-encoded UTF-8 payload (e=0|1).
* Notification identifier (i=xyz).
* Payload type (p=title|body).
* When to honor the notification (o=always|unfocused|invisible), with
  the following quirks:
    - we don't know when the window is invisible, thus it's treated as
      'unfocused'.
    - the foot option 'notify-focus-inhibit' overrides 'always'
* Urgency (u=0|1|2)

[^1]: https://sw.kovidgoyal.net/kitty/desktop-notifications/
2024-07-23 07:17:21 +02:00
Daniel Eklöf
45c7cd3f74
input: allow mouse selections to start inside the margins
Before this, margins were special cased:

* The mouse cursor was always a pointer, and never an I-beam (thus
  signaling selections cannot be made).
* The internal mouse coords where set to -1 when the cursor was inside
  the margins, causing:
    - text selections from being made
    - mouse events being passed to mouse grabbing applications

In particular, even with a one-pixel margin, making selections was
unnecessarily hard in e.g. fullscreen mode, where you'd expect to be
able to throw the cursor into the corner of the screen and then start
a selection.

With this patch, the cursor is treated as if it was in the first/last
column/row, when inside the margin(s).

An unintended side-effect of this, initially, was that auto-scrolling
selections where way too easy to trigger, since part of its logic is
checking if the cursor is inside the margins.

That problem has been reduced by two things:

* auto-scrolling does not occur unless a selection has been
  started. That is, just holding down the mouse in the margins and
  moving up/down doesn't cause scrolling. You have to first select at
  least one cell in the visible viewport.
* A selection isn't fully started (i.e. a cell is actually selected)
  unless the cursor is inside the actual grid, and *not* in the
  margins.

What does the last point mean? We now allow a selection to be
_started_ when clicking in the margin. What this means internally is
we set a start coordinate for a selection, but *not* and end
coordinate. At this point, we don't have an actual selection. Nothing
is selected, and no cells are highlighted, graphically.

This happens when we set an end coordinate. Without the last bullet
point, that would happen as soon as the cursor was _moved_, even if
still inside the margins. Now, we require the cursor to leave the
margins and touch an actual cell before we set an end coordinate.

Closes #1702
2024-07-19 06:55:28 +02:00
Daniel Eklöf
38461eef6f
csi: in-band window resize notifications, private mode 2048
This implements
https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83,
in-band window resize notifications.

When user enables private mode 2048 (in-band resize
notifications), *always* send current size, even if the mode is
already active.

This ensures applications can rely on getting a reply from the
terminal.
2024-07-19 06:54:32 +02:00
Daniel Eklöf
e11a4ab6af
wayland: #ifdef guard code related to wl_shm_release() 2024-07-18 14:27:40 +02:00
Daniel Eklöf
87aac8708d
foot.info: add setal (colored underlines)
This is an alias to Setulc. Upstream (ncurses) terminfo uses setal
instead of Setulc :/
2024-07-18 09:04:39 +02:00
Daniel Eklöf
065eb05e3e
meson/pgo: fix PGO build errors with recent meson(?) versions
The back-reference to 'tokenize.c' in the parent directory causes PGO
build failures, where gcc can't find the PGO data. Likely due to
path/naming issues caused by meson's generated build directories.
2024-07-18 09:02:42 +02:00
Daniel Eklöf
36e4435bbf
log: respect the NO_COLOR environment variable
http://no-color.org/

Closes #1771
2024-07-18 08:44:30 +02:00
Daniel Eklöf
4f25e1ba9f
wayland: use wl_shm v2 if available 2024-07-18 08:23:25 +02:00
abs3nt
1fd4076082
themes: catppuccin: replace with updated flavors
Pulled from https://github.com/catppuccin/foot
2024-07-18 08:21:14 +02:00
Daniel Eklöf
1136108c97
input: don't map wheel events to BTN_{BACK,FORWARD}
BTN_BACK and BTN_FORWARD are separate buttons. The scroll wheel don't
have any button mappings in libinput/wayland, so make up our own
defines.

This allows us to map them in mouse bindings.

Also expose BTN_WHEEL_{LEFT,RIGHT}. These were already defined, and
used, internally, to handle wheel tilt events. With this, they can
also be used in mouse bindings.

Finally, fix encoding used for BTN_{BACK,FORWARD} when sending mouse
button events to the client application. Before this, they were mapped
to buttons 4/5. But, button 4/5 are for the scroll wheel, and as
mentioned above, BTN_{BACK,FORWARD} are not the same as scroll wheel
"buttons".

Closes #1763
2024-07-13 10:41:10 +02:00
Daniel Eklöf
15c0078c2d
changelog: remove entry for change that hasn't yet been merged 2024-07-13 10:40:37 +02:00
Daniel Eklöf
56556e5f23
render: hollow-cursor: use correct cursor color 2024-07-13 10:37:21 +02:00
Daniel Eklöf
c46c124363
render: cursor: use default fg/bg if cell fg/bg are the same
When deciding which colors to use for the cursor, and the cursor text
and background colors are the same, use the default fg/bg instead.

Closes #1761
2024-07-13 10:30:23 +02:00
Nicolas Kolling Ribas
f066fe47f0 themes: add nvim-dark and nvim-light themes
Both based on the new "Nvim branded" default color scheme in
Neovim 0.10.
2024-07-09 01:22:37 -03:00
Daniel Eklöf
85b2fb1e32
doc: foot.ini: line-height: add warning about runtime font size changes 2024-07-07 16:31:40 +02:00
Daniel Eklöf
22c8637610
osc: extend damage-cells-by-color to default fg/bg as well
When changing part of the color palette, through either OSC-4, or
OSC-10 and OSC-11 (and the corresponding reset OSCs: 104, 110 and
111), only dirty affected cells.

We've always done this, but only for OSC-4.

This patch breaks out that logic, and extends it to handle default
fg/bg too.

It also fixes a bug where cells with colored underlines were not
dirtied if the underline was the only part of the cell that was
affected by a OSC-4 change.
2024-07-03 10:53:33 +02:00
Daniel Eklöf
e5ed387426
Merge branch 'color-stack'
Closes #856
2024-07-03 09:59:56 +02:00
Daniel Eklöf
dd58dad15a
csi: redraw margins after restoring the palette 2024-07-03 09:47:51 +02:00
Daniel Eklöf
d440d5aa2c
osc: 10/11/12/17/19: don't apply overly much damage 2024-07-03 09:47:51 +02:00
Daniel Eklöf
5edb0deffe
changelog: XTPUSHCOLORS, XTPOPCOLORS and XTREPORTCOLORS 2024-07-03 09:47:49 +02:00
Daniel Eklöf
bebd5ce415
doc: foot-ctlseq: document XTPUSHCOLORS, XTPOPCOLORS and XTREPORTCOLORS 2024-07-03 09:47:30 +02:00
Daniel Eklöf
dd6fc99ae1
csi: implement XTPUSHCOLORS+XTPOPCOLORS+XTREPORTCOLORS
The documentation of these sequences are vague and lacking, as is
often the case with XTerm invented control sequences.

I've tried to replicate what XTerm does (as of xterm-392).

The stack represents *stashed/stored* palettes. The currently active
palette is *not* stored on the stack.

The stack is dynamically allocated, and starts out with zero elements.

Now, XTerm has a somewhat weird definition of "pushing" and "popping"
in this context, and the documentation is somewhat misleading.

What a push does is this: it stores the current palette to the stack
at the specified slot. If the specified slot number (Pm) is 0, the
slot used is the current slot index incremented by 1.

The "current" slot index is then set to the specified slot (which is
current slot + 1 if Pm == 0).

Thus, "push" (i.e. when Pm == 0 is used) means store to the "next"
slot. This is true even if the current slot index points into the
middle of stack.

Pop works in a similar way. The palette is restored from the specified
slot index. If the specified slot number is 0, we use the current slot
index.

The "current" slot index is then set to the specified slot -
1 (current slot - 1 if Pm == 0).

XTREPORTCOLORS return the current slot index, and the number of
palettes stored on the stack, on the format

    CSI ? <slot index> ; <palette count> # Q

When XTPUSHCOLORS grows the stack with more than one element (i.e. via
a 'CSI N # P' sequence), make sure *all* new slots are initialized (to
the current color palette). This avoids uninitialized slots, that
could then be popped with XTPOPCOLORS.

Closes #856
2024-07-03 09:47:30 +02:00
Daniel Eklöf
5d4a002413
osc: merge OSC 10/11/12/17/19 handling
10/11/17/19 were already merged, so this patch just stops special
casing 12 (cursor color).

In preparation for XTPUSHCOLORS/XTPOPCOLORS, the cursor colors are
moved from their own struct, into the 'colors' struct.

Also fix a bug where OSC 17/19 queries returned OSC-11 data.
2024-07-03 09:47:30 +02:00
Daniel Eklöf
e708d19ea3
csi: implement SGR 21, double underlines 2024-07-03 09:46:41 +02:00
Craig Barnes
970b95509c render: fix "maybe-uninitialized" error in draw_styled_underline()
Reproducer:

    CFLAGS='-Og -g' meson setup --buildtype=debug bld
    ninja -C bld

Error message:

    ../render.c: In function ‘draw_styled_underline’:
    ../render.c:490:19: error: ‘y_ofs’ may be used uninitialized
    [-Werror=maybe-uninitialized]
      490 |         const int top = y + y_ofs;
          |                   ^~~
    ../render.c:405:9: note: ‘y_ofs’ was declared here
      405 |     int y_ofs;
          |         ^~~~~
2024-07-03 06:55:01 +01:00
Craig Barnes
ab4b3cbd34 doc: foot-ctlseq: add missing "E" to DECRQSS sequence
This omission seems to have been a typo in commit add530e66d.
2024-07-03 06:09:59 +01:00
Craig Barnes
674a535fc3 Rename various uses of "curly" in the source code with "underline"
Since "curly" could make it seem as if all underline styles are curled
(to people unfamiliar with the codebase), whereas in reality only 1 is.
2024-07-01 20:00:16 +01:00
Daniel Eklöf
64e7f25124
dcs: DECRQSS: styled+colored underlines 2024-07-01 16:25:45 +02:00
Daniel Eklöf
7341ba5ee3
render: cursor color: remove assertion that both cursor fg/bg be set
Before this patch, we asserted both the cursor foreground, and
background colors had been set. This is true in most cases; the config
system enforces it.

It is however possible to set only the cursor background color, while
leaving the foreground (text) color unset:

* Use a foot config that does *not* configure the cursor colors. This
  means foot will invert the default fg/bg colors when rendering the
  cursor.
* Override the cursor color using an OSC-12 sequence. OSC-12 only sets
  the background color of the cursor, and there is no other OSC sequence
  to set the cursor's text color.

To handle this, remove the assertion, and simply split the logic for
the cursor backgound and foreground colors:

* Use the configured background color if set (either through config or
  OSC-12), otherwise use the default foreground color.
* Use the configured foreground color if set (through config),
  otherwise use the default background color.
2024-07-01 10:53:21 +02:00
Daniel Eklöf
94b9014f95
Merge branch 'curly-underlines'
Closes #828
2024-07-01 10:09:44 +02:00
Daniel Eklöf
b503c0d6d9
reaadme: add styled and colored underlines to the feature list 2024-06-27 19:28:04 +02:00
Daniel Eklöf
73f6982cbe
grid: merge reflow_{uri,curly}_range_start(), and reflow_{uri,curly}_range_end() 2024-06-27 19:22:01 +02:00
Daniel Eklöf
0c7725217a
render: draw_styled_underline(): respect main.underline-thickness
Also refactor a bit, and break out early to draw_underline() for
legacy underlines.
2024-06-27 19:21:23 +02:00
Daniel Eklöf
19bf558e6c
render: underlines: improve the appearance of the 'dotted' style
Try to make the 'dotted' style appear more even, and less like each
cell is rendered separately (even though they are).

Algorithm:

Each dot is a square; it's sides are that of the font's line
thickness.

The spacing (gaps) between the dots is initially the same width as the
dots themselves.

This means the number of dots per cell is the cell width divided by
the dots' length/width, divided by two.

At this point, there may be "left-over" pixels.I.e. the widths of the
dots and the gaps between them may not add up to the width of the
cell.

These pixels are evenly (as possible) across the gaps.

There are still visual inaccuracies at small font sizes. This is
impossible to fix without changing the way underlines are rendered, to
render an entire line in one go. This is not something we want to do,
since it'll make styled underlines, for a specific cell/character,
look differently, depending on the surrounding context.
2024-06-27 18:36:17 +02:00
Daniel Eklöf
0d3f2f27e3
grid: refactor: replace {uri,curly}_range_append() with range_append()
Also add range_append_by_ref(), with replaces
uri_range_append_no_strdup().
2024-06-26 19:01:54 +02:00
Daniel Eklöf
cb4a74e10b
grid: row_range_put(): use correct type in call to range_delete() 2024-06-26 18:39:24 +02:00
Daniel Eklöf
45f4eb48fb
grid: refactor: remove union range_data_for_insertion
This union is identical to row_range_data, except the URI char pointer
is const. Let's ignore that, and re-use row_range_data, casting the
URI pointer when necessary.

Also remove uri_range_insert() and curly_range_insert(), and use the
generic version of range_insert() everywhere.
2024-06-26 18:39:24 +02:00
Daniel Eklöf
6a0110446c
grid: grid_row_{uri,curly}_range_put(): share code
grid_row_uri_range_put() and grid_row_curly_range_put() now share the
same base logic.

Range specific data is passed through a union, and range specific
checks are done through switched functions.
2024-06-26 18:39:24 +02:00
Daniel Eklöf
8e4ca90680
foot.info: add 'Setulc' (set underline color) capability 2024-06-26 18:39:24 +02:00
Daniel Eklöf
1297b13cd2
foot.info: add 'Su' (Styled Underlines) boolean capability 2024-06-26 18:39:24 +02:00
Daniel Eklöf
5e046e6a84
csi: sgr_reset(): avoid using memset()
This _should_ be what the compiler does anyway (i.e. it _should_
replace the memset() with inline MOVs). But let's be sure.
2024-06-26 18:39:24 +02:00
Daniel Eklöf
08138e9546
render: underlines: minor perf-tweak: early break out
When looking up the extender underline range, break out early if we
see that there can't possibly be any ranges matching the current column.
2024-06-26 18:39:24 +02:00
Daniel Eklöf
0759caec6e
changelog: styled + colored underlines 2024-06-26 18:39:24 +02:00
Daniel Eklöf
a33954a8f4
doc: foot-ctlseq: add 58/59 (styled + colored underlines) 2024-06-26 18:39:24 +02:00
Daniel Eklöf
48cf57818d
term: performance: use a bitfield to track which ascii printer to use
The things affecting which ASCII printer we use have grown...

Instead of checking everything inside term_update_ascii_printer(), use
a bitfield.

Anything affecting the printer used, must now set a bit in this
bitfield. This makes term_update_ascii_printer() much faster, since
all it needs to do is check if the bitfield is zero or not.
2024-06-26 18:39:24 +02:00
Daniel Eklöf
22302d8bcc
term: term_fill(): only set OSC-8 + styled hyperlinks when use_sgr_attrs is set 2024-06-26 18:39:24 +02:00
Daniel Eklöf
3b738c6e68
terminal: term_fill(): fix osc8 erase bug + handle styled underlines
Only clear OSC-8 hyperlinks at the target columns if we don't have an
active OSC-8 URI. This corresponds to normal VT attributes; the
currently active attributes are set, and all others are cleared.

Handle styled underlines in the same way
2024-06-26 18:39:24 +02:00
Daniel Eklöf
963ce45f3f
render: resize: copy styled underlines to temporary grid
When doing an interactive resize, we create a small grid copy of the
current viewport, and then do a non-reflow resize.

When the interactive resize is done, we do a proper reflow. This is
for performance reasons.

When creating the viewport copy, we also need to copy the styled
underlines. Otherwise, styled underlines will be rendered as plain
underlines *while resizing*.
2024-06-26 18:39:23 +02:00
Daniel Eklöf
b20302c2a7
grid: reflow: handle styled underlines 2024-06-26 18:39:23 +02:00
Daniel Eklöf
8e2402605e
render: styled underlines
This was originally contributed by @kraftwerk28 in
https://codeberg.org/dnkl/foot/pulls/1099

Here, we re-use the rendering logic only, as attribute tracking has
been completely rewritten.
2024-06-26 18:39:23 +02:00
Daniel Eklöf
a45ccfaed0
csi: remove debug log 2024-06-26 18:39:23 +02:00
Daniel Eklöf
05f9774416
foot.info: add smulx (styled underlines) 2024-06-26 18:39:23 +02:00
Daniel Eklöf
32effc6657
csi: wip: styled underlines
This is work in progress, and fairly untested.

This adds initial tracking of styled underlines. Setting attributes
seems to work (both color and underline style). Grid reflow has *not*
been tested.

When rendering, style is currently ignored (all styles are rendered as
a plain, legacy underline).

Color however, *is* applied.
2024-06-26 18:39:23 +02:00
Daniel Eklöf
20923bb2e8
grid: refactor: first step towards a more generic range handling 2024-06-26 18:39:23 +02:00
Daniel Eklöf
cbe399ecd9
Merge branch 'vs15-16'
Closes #1742
2024-06-26 18:38:47 +02:00
Daniel Eklöf
085c60a334
scripts: generate-emoji-variation-sequences: don't assume single codepoint sequences
Right now (Unicode 15.1), all valid variation sequences consist of a
single Unicode codepoint (followed by either VS-15 or VS-16).

Don't assume this is the case.

We don't actually handle longer sequences. But now we at least catch
such escapes, and error out.
2024-06-26 18:33:23 +02:00
Daniel Eklöf
aed9c392eb
scripts: generate-emoji-variation-sequences: compact the C struct
Lookups in this table is not performance critical at all.

Thus, let's compact it to bring down the binary size of foot.

This brings the size of each entry down from 9 bytes to 6, bringing
the size of the whole thing down from 1647 bytes to 1098 bytes, saving
us 549 bytes.
2024-06-26 18:32:10 +02:00
Daniel Eklöf
7378ecf9a7
vt: unittest: verify emoji_vs list is sorted 2024-06-25 08:23:40 +02:00
Daniel Eklöf
ecb1ca61af
scripts: generate-emoji-variation-sequences: don't assume input is sorted 2024-06-25 08:23:27 +02:00
Daniel Eklöf
9665661445
vt: only apply VS-15/16 to valid sequences
At compile time, build a lookup table from the Unicode data file
'emoji-variation-sequences.txt'.

At run-time, when we detect a VS-15/16 sequence, do a lookup in this
table, and enforce the variation selector iff the sequence is valid.

Closes #1742
2024-06-25 08:20:21 +02:00
Daniel Eklöf
94583703e1
vt: don't ignore VS-15 (text presentation)
When we encounter either VS-15 or VS-16, set the grapheme width to 1
or 2 explicitly.
2024-06-25 08:20:20 +02:00
Daniel Eklöf
519e9b8b5e
doc: foot.ini: fix typos 2024-06-25 08:20:03 +02:00
Daniel Eklöf
911af53c5c
doc: foot.ini: document issues with fractional scaling and initial-window-size-*
Since most compositors don't report the correct scaling factor
until *after* the window has been mapped, these options often do not
work.
2024-06-25 08:12:40 +02:00
Daniel Eklöf
795e39de1a
shm: discard shm buffers with mis-matching alpha-setting 2024-06-24 20:09:37 +02:00
Daniel Eklöf
c45231ef89
input: don't reset the XKB compose state in keymap()
When the compositor sends a new keymap, don't reset the XKB compose
state.

This is done by initializing the XKB context, along with the compose
state, when binding the seat, instead of in keymap().

Then, in keymap(), simply stop destroying the old xkb state. Only
destroy, and re-create the keymap state.

Closes #1744
2024-06-22 08:00:13 +02:00
Daniel Eklöf
aea16ba5d2
input: implement wl_pointer::axis_value120()
This implements high resolution mouse wheel scroll events. A "normal"
scroll step corresponds to the value 120. Anything less than that is a
partial scroll step.

This event replaces axis_discrete(), when we bind wl_seat v8 (which we
now do, when available).

We calculate the number of degrees that is required to scroll a single
line, based off of the scrollback.multiplier value.

Each high-res event accumulates, until we have at least the number of
degress required to scroll one, or more lines.

The remaining degrees are kept, and added to in the next scroll event.

Closes #1738
2024-06-18 14:09:03 +02:00
Daniel Eklöf
f3d848da01
osc: 52: treat OSC-52 replies as paste data (after all, they are)
This fixes an issue where other data (such as replies to other
requests) being interleaved with the OSC-52 reply.

The patch piggy backs on the already existing mechanism for handling
regular pastes, where other data is queued up until the paste is done.

There's one corner case that won't work; if the user *just* did a
normal paste (i.e. at virtually the same time the application
requested OSC-52 data), the OSC-52 request will return an empty reply.

Likewise, if there are multiple OSC-52 requests at the same time, only
the first will return data.

Closes #1734
2024-06-09 10:14:57 +02:00
Daniel Eklöf
4bb68282be
foot.ini: add missing option resize-by-cells
Closes #1731
2024-06-08 08:54:12 +02:00
Daniel Eklöf
0755aa7e83
config: codespell: varios -> various 2024-06-08 08:49:43 +02:00
Daniel Eklöf
ba424e8494
ime: codespell: surroundin -> surrounding 2024-06-08 08:49:33 +02:00
Daniel Eklöf
1b4f97d263
issue-template: let's see if we can disable the default template 2024-06-08 08:21:55 +02:00
Jan Beich
f67449700b
meson: auto-detect execvpe on FreeBSD
0667d0e0e3
2024-06-07 16:31:11 +02:00
Daniel Eklöf
bb2e0d64e1
doc: foot.ini: document pixelsize
People are apparently too lazy to read fontconfig's documentation, and
don't understand how to configure font sizes in foot.
2024-06-07 16:18:28 +02:00
Daniel Eklöf
0bf5a7e902
sixel: comment: document the P1 parameter
(and no, it's no longer unimplemented)
2024-05-22 14:56:10 +02:00
Daniel Eklöf
91561d7ba7
issue-template: link "debug build", not just "build" 2024-05-22 14:47:57 +02:00
Daniel Eklöf
5a4af31d18
issue-template: formatting; hopefully makes it easier to read 2024-05-22 14:47:07 +02:00
Daniel Eklöf
585fac7af0
issue-template: ask user to provide info on tmux, zellij, IMEs etc 2024-05-22 14:44:16 +02:00
Daniel Eklöf
713d8d59fb
issue-template: add input-field for TERM 2024-05-22 14:43:51 +02:00
Daniel Eklöf
f64cc04fe6
shm: minor optimization
Don't retry memfd_create() without MFD_NOEXEC_SEAL is 0.

The overall logic is this:

* Try memfd_create() with MFD_NOEXEC_SEAL
* If that fails (which it does, on older kernels), try without the flag

If compiling against an older kernel, or on a system that doesn't
support the noexec seal, MFD_NOEXEC_SEAL is 0.

In this case, there's little point in retrying memfd_create a second
time, with the exact same set of flags.
2024-05-22 14:06:15 +02:00
Daniel Eklöf
fb2ad83d79
changelog: wp-single-pixel-buffer-v1 2024-05-22 13:48:47 +02:00
Daniel Eklöf
708ca3d650
render: single-pixel: minor optimization
Don't re-create the single-pixel buffer, unless necessary.

The buffer itself doesn't have a size. That means we can re-use the
buffer if the last frame's overlay style matches the current frame's
style.

What we *do not* know is whether the current frame's size is the same
as the last frame's.

This means we still have to set the viewport destination, and commit
the surface.
2024-05-22 13:48:46 +02:00
Daniel Eklöf
3c96d0b68e
render: use single-pixel buffers for overlays, when possible
The unicode-mode, and flash overlays are single color buffers. This
means we can use the single-pixel buffer protocol.

It's undefined whether the compositor will release the buffer or not;
to make things easier, simply destroy the buffer as soon as we've
committed it.

Note that since compositors don't necessarily release single-pixel
buffers, we can't plug them into our own buffer interface. This means
we can't use buffer pointers to check if we can re-use the previous
buffer (i.e. we can skip comitting a new buffer), or if we have to
create a new one.

It's _almost_ enough to just check if the last overlay style is the
same as the current one. Except that that doesn't take window resizes
into account...
2024-05-22 13:48:46 +02:00
Craig Barnes
6944d5f901 issue-template: fix typo 2024-05-21 17:48:04 +01:00
Daniel Eklöf
8716ca5784
url-mode: disable IME mode while URL-mode is active
This prevents the IME from stealing "our" key-presses, and thus
preventing the user from opening URLs.

Closes #1718, hopefully.
2024-05-21 08:37:39 +02:00
Daniel Eklöf
18b702b249
unicode-mode: move state from seat to term
This fixes an issue where entering unicode-mode in one foot client,
also enabled unicode-mode on other foot clients. Both
visually (although glitchy), and in effect.

The reason the state was originally in the seat objects, was to fully
support multi-seat. That is, one seat/keyboard entering unicode-mode
should not affect other seats/keyboards.

The issue with this is that seat objects are Wayland global. Thus, in
server mode, all seat objects are shared between the foot clients.

There is a similarity with IME, which also keeps state in the
seat. There's one big difference, however, and that is IME has Wayland
native enter/leave events, that the compositor emits when windows are
focused/unfocused. These events allow us to reset IME state. For our
own Unicode mode, there is nothing similar.

This patch moves the Unicode state from seats, to the terminal
struct. This does mean that if one seat/keyboard enters Unicode mode,
then *all* seats/keyboards will affect the unicode state. This
potential downside is outweighed by the fact that different foot
clients no longer affect each other.

Closes #1717
2024-05-21 08:36:56 +02:00
Daniel Eklöf
cf65ad49e8
issue-template: feature: use yaml instead 2024-05-21 08:31:08 +02:00
Daniel Eklöf
7982433c71
issue-template: try to add another template, for feature requests. 2024-05-21 08:27:24 +02:00
Daniel Eklöf
ad7e0f7f32
issue-template: it's validation*s* 2024-05-21 08:18:28 +02:00
Daniel Eklöf
dffe2e0b7c
issue-template: try to fix link to INSTALL.md, attempt 3 2024-05-21 08:14:58 +02:00
Daniel Eklöf
5b0eb7b42d
issue-template: try to fix link to INSTALL.md, attempt 2 2024-05-21 08:14:15 +02:00
Daniel Eklöf
14b84dd7c5
issue-template: try to fix link to INSTALL.md 2024-05-21 08:13:11 +02:00
Daniel Eklöf
26e22b74b1
forgejo: issue report templates 2024-05-21 08:11:39 +02:00
Daniel Eklöf
7b983be3d8
foot.ini: add commented out 'blink-rate' 2024-05-20 11:00:02 +02:00
Daniel Eklöf
c4f1380943
config: add cursor.blink-rate option
The default is 500ms, which corresponds to the old, hardcoded default.

Closes #1707
2024-05-20 09:17:42 +02:00
Mariusz Bialonczyk
bc193c7be5 themes: add onehalf-dark 2024-05-17 10:56:20 +02:00
Artturin
3a7ea1f44b scripts: generate-builtin-terminfo: fix syntax error 2024-05-01 21:18:41 +03:00
Daniel Eklöf
3c4669061b
scripts: generate-builtin-terminfo: use \xNN for control characters
Instead of emitting raw control characters (for e.g. bel, cub1 and
kbs), use \xNN C string escapes.
2024-04-30 10:50:31 +02:00
Daniel Eklöf
a3debf7741
dcs: xtgettcap: always reply with tigetstr(3) formatted "strings"
That is, instead of sometimes replying with a "source" encoded
string (where e.g. '\E' are returned just like that, and not as an
actual ESC), always unescape all string values.

This also includes \n \r \t \b \f \s, \^ \\ \ \:, as well as ^x-styled
escapes.

Closes #1701
2024-04-27 09:38:55 +02:00
Daniel Eklöf
4d4ef5eed5
dcs: XTGETTCAP: handle empty request
If the XTGETTCAP request is empty (no capabilities in it), reply with
an empty error reply.

Closes #1694
2024-04-20 08:27:25 +02:00
Daniel Eklöf
128c5c3efa
term: default to DPI 96, if the monitor's DPI is 0
This can happen in virtualized environments, or when running a nested
Wayland session.
2024-04-20 08:26:44 +02:00
Daniel Eklöf
acbb3cbb70
char32: mbsntoc32() returns a size_t, not a char32_t 2024-04-20 08:26:15 +02:00
Daniel Eklöf
edb28479cc
doc: foot: simplify example where we run a non-default command
To reduce the risk of people mistakenly believing you have to/should
use "sh -c".
2024-04-20 08:17:02 +02:00
Daniel Eklöf
a1ac37e771
changelog: add new 'unreleased' section 2024-04-17 11:28:22 +02:00
Daniel Eklöf
ec0e6d1744
Merge branch 'releases/1.17' 2024-04-17 11:28:06 +02:00
Daniel Eklöf
b88f0d672f
meson: bump version to 1.17.2 2024-04-17 11:26:45 +02:00
Daniel Eklöf
f2fbef1f82
changelog: prepare for 1.17.2 2024-04-17 11:26:25 +02:00
Daniel Eklöf
883fc6be27
Merge branch 'master' into releases/1.17 2024-04-17 11:25:50 +02:00
Daniel Eklöf
7c20fb247c
term: stash last known DPI, and use after a unmapped/mapped sequence
A compositor may unmap, and then remap the window, for example when
the window is minimized, or if the user switches workspace.

With DPI aware rendering, we *need* to know on which output we're
mapped, in order to use the correct DPI. This means the first frame we
render, before being mapped, always guesses the DPI.

In an unmap/map sequence, guessing the wrong DPI means the window will
flicker.

Fix by stashing the last used DPI value, and use that instead of
guessing.

This means the *only* time we _actually_ guess the DPI, is the very
first frame, when starting up foot.
2024-04-17 09:14:52 +02:00
Daniel Eklöf
a5b369ede4
ci: set explicit 'event' filters an all 'when'-statements
This should fix the CI warnings we've started to see lately:

    [bad_habit] Please set an event filter on all when branches
2024-04-17 09:13:13 +02:00
Daniel Eklöf
3507c72492
ci: explicitly install openssl
Let's see if this fixes the missing SSL module in Python...
2024-04-17 08:52:31 +02:00
Daniel Eklöf
3d2588edf8
sixel: don't allow pan/pad changes after sixel data has been emitted
Changing pan/pad changes the sixel's aspect ratio. While I don't know
for certain what a real VT340 would do, I suspect it would change the
aspect ratio of all subsequent sixels, but not those already emitted.

The way we implement sixels in foot, makes this behavior hard to
implement. We currently don't resize the image properly if the aspect
ratio is changed, but not the RA area. We have code that assumes all
sixel lines have the same aspect ratio, etc.

Since no "normal" applications change the aspect ratio in the middle
of a sixel, simply disallow it, and print a warning.

This also fixes a crash, when writing sixels after having modified the
aspect ratio.
2024-04-15 16:07:47 +02:00
Daniel Eklöf
0ab05f4807
sixel: also set 'alloc_height', when short-cutting a resize operation
In some cases, a sixel may be resized vertically, while still having a
zero-width. In this case, the resize operations are short-cutted, and
no actual allocations are done.

However, we forgot to set 'alloc_height' when doing so. As a result,
the trimming code (when the sixel is "done"), trimmed away the entire
sixel.
2024-04-15 16:05:56 +02:00
Daniel Eklöf
71ce17d977
term: don't print outside grid when printing multi-column characters
When auto-wrap is disabled, a multi-column character may be printed on
a line that doesn't fit the entire character. That is, the "spacers"
we print, as place holders in the columns after the first one, may
reach outside the grid.

We did (try to) check for this, but the check was off by one. Meaning,
we could, in some cases, print outside the grid.
2024-04-15 16:03:30 +02:00
Daniel Eklöf
23ada09d14
osc: reject notifications with invalid UTF-8 strings 2024-04-15 16:02:54 +02:00
Daniel Eklöf
e753bb953b
wayland: remove has_wl_compositor_v6
We deviate slightly from the specification, in that we don't assume a
preferred buffer scale of 1. Instead, we "guess" the scale *until we
receive a surface_preferred_buffer_scale event.

Because of this, we don't need the has_wl_compositor_v6 member, as
it's enough to check if we have a non-zero 'preferred buffer scale'.
2024-04-12 15:35:25 +02:00
Daniel Eklöf
4fd26c251c
changelog: add a new 'unreleased' section 2024-04-11 15:32:15 +02:00
Daniel Eklöf
40a0dfedea
Merge branch 'releases/1.17' 2024-04-11 15:31:50 +02:00
Daniel Eklöf
04ebd02874
meson: bump version to 1.17.1 2024-04-11 15:28:17 +02:00
Daniel Eklöf
accefc3ae1
changelog: prepare for 1.17.1 2024-04-11 15:28:08 +02:00
Daniel Eklöf
385fc0078f
Merge branch 'master' into releases/1.17 2024-04-11 15:26:50 +02:00
Daniel Eklöf
b400903e25
config: add new key-binding 'quit', unbound by default
Closes #1475
2024-04-11 15:12:45 +02:00
izmyname
fa07c1ec67 Typo fix 2024-04-11 00:22:07 +00:00
izmyname
94f749a40d
Add noirblaze theme 2024-04-10 16:27:08 +02:00
Daniel Eklöf
a4046e0c3d
code of conduct: initial code-of-conduct
Based on [River's](https://codeberg.org/river/river) code of conduct,
which is adapted from the
[Contributor Covenant](https://www.contributor-covenant.org/),
version 2.1, available at
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
2024-04-10 16:26:03 +02:00
Daniel Eklöf
e5a2ac4b57
config: add cursor.unfocused-style
This option controls how we render the cursor when the terminal window
is unfocused.

Possible values are:

* hollow: the default, and how we rendered the cursor before this
  patch.
* unchanged: render the cursor exactly the same way as when the window
  is focused.
* none: do not render any cursor at all

Closes #1582
2024-04-10 16:24:10 +02:00
Daniel Eklöf
9287946b36
dcs: DECRQSS: fix off-by-one when checking for space in the DCS buffer 2024-04-10 05:44:33 +02:00
Marcin Puc
09e45794bc themes: add dracula-iterm 2024-04-07 09:47:19 +02:00
Holger Weiß
ed5717c4cd
themes: add xterm theme
Include a theme for using xterm’s default palette (based on the
XTerm-col.ad file included with the xterm source code, version 390).
2024-04-06 15:24:02 +02:00
Daniel Eklöf
7f4328e0b1
term: send SIGHUP before SIGTERM when shutting down
If we're the ones initiating shutdown, start by sending SIGHUP. Only
if the client application does not terminate, send SIGTERM (and if it
still refuses to terminate, send SIGKILL).

Also reduce the timeout between the signals from 60s to 30s.
2024-04-05 19:34:17 +02:00
Daniel Eklöf
c7848c4e75
term: don't shutdown terminal when PTY is closed, unless --pty was used
Unless --pty has been used, we do *not* want to shutdown the terminal
when the PTY is closed by the client application; we want to wait for
the client application to actually terminate.

This was the behavior before the --pty patch.

This was changed in the --pty patch, since then, we don't *have* a
client application. That is, foot has not forked+exec:ed anything. The
only way to trigger a shutdown (from the client side) is to close the
PTY.

This patch restores the old behavior, when --pty is *not* used.

Closes #1666
2024-04-05 18:45:31 +02:00
Daniel Eklöf
88a3b54ca3
wayland: only use the surface preferred scale if set by the compositor
Before this patch, we would, in some cases, fallback to the surface
preferred (not fractional) scaling, even though the compositor hadn't
actually published a preferred buffer scale; the presence of a v6
compositor interface doesn't mean we've actually received a preferred
scale yet.
2024-04-05 16:19:47 +02:00
Daniel Eklöf
4f1aaccf81
log: fix syslog not respecting the configured log level 2024-04-05 16:19:24 +02:00
tunjan
3cc94ab4e8 fix typo in README 2024-04-05 11:11:21 +00:00
Daniel Eklöf
51e8b4f533
changelog: add new 'unreleased' section 2024-04-02 16:34:08 +02:00
Daniel Eklöf
61c8d6ec8c
Merge branch 'releases/1.17' 2024-04-02 16:33:42 +02:00
Daniel Eklöf
21951feb2b
meson: bump version to 1.17.0 2024-04-02 16:27:54 +02:00
Daniel Eklöf
d5dc0b2f49
changelog: prepare for 1.17.0 2024-04-02 16:27:25 +02:00
Daniel Eklöf
82c1a28e6f
changelog: move DECRQM for private mode 67 from "added" to "changed" 2024-04-01 08:49:27 +02:00
Matheus Afonso Martins Moreira
2138273c16 themes: add neon theme
Add a neon theme file for foot, originally from xcolors.net/neon
and also available at terminal.love.
2024-03-31 20:36:55 -03:00
Daniel Eklöf
3d0d9036fd
terminfo: tighten up the rv/xr regular expressions 2024-03-29 11:40:47 +01:00
Daniel Eklöf
8e79ceba9e
terminfo: add 'nel' capability 2024-03-29 11:40:35 +01:00
Daniel Eklöf
a34ae5d527
terminfo: add fe/fd (focus enable/disable)
These are new capabilities, recently added to ncurses. Applications
are supposed to use these, instead of XM (to enable focus events). So,
remove "CSI ? 1004h" from XM.
2024-03-29 11:40:15 +01:00
Daniel Eklöf
a99434929c
sixel: abuse wmemset() when initializing a freshly allocated image buffer
wmemset() is heavily optimized, and in some cases, *much* faster than
manually initializing the new image pixels.

Furthermore, assume calloc() is better at initializing memory to zero,
and use that when initializing new pixels in a transparent image.
2024-03-25 16:33:15 +01:00
Daniel Eklöf
dcd4ab4ab8
scripts: generate-alt-random: generate both opaque and transparent sixels 2024-03-25 16:33:00 +01:00
Daniel Eklöf
9fcf5977c0
sixel: fix cursor positioning when image is split up into several 2024-03-18 17:12:11 +01:00
Daniel Eklöf
bce1d7313d
sixel: hopefully handle image height correctly when trimming
When trimming trailing empty rows from the final image, handle the RA
region correctly:

* If image is transparent, trim down to the last non-empty pixel row,
  regardless of whether it is inside or outside the RA region.

* If image is opaque, trim down to either the last non-empty pixel
  row, or the RA region, whatever is the largest height.
2024-03-18 17:11:27 +01:00
Daniel Eklöf
cb820a498b
sixel: RA region does not affect the text cursor position
The raster attributes is, really, just a way to erase an area. And, if
the sixel is transparent, it's a nop.

The final text cursor position depends, not on our image size (which
is based on RA), but on the final graphical cursor position.

However, we do want to continue using it as a hint of the final image
size, to be able to pre-allocate the backing buffer.

So, here's what we do:

* When trimming trailing transparent rows, only trim the *image*, if
  the graphical cursor is positioned on the last sixel row, *and* the
  sixel is transparent.
* Opaque sixels aren't trimmed at all, since RA in this acts as an
  erase that fills the RA region with the background color.
* The graphical cursor position is always adjusted (i.e. trimmed),
  since it affects the text cursor position.
* The text cursor position is now calculated from the graphical cursor
  position, instead of the image height.
2024-03-18 17:11:26 +01:00
Daniel Eklöf
cc660bc7c1
sixel: trim trailing, fully transparent sixel rows
See https://github.com/hpjansson/chafa/issues/192
2024-03-18 17:09:20 +01:00
Daniel Eklöf
282c55aa4a
sixel: place cursor on the last character row touched by the sixel
After emitting a sixel, place the cursor on the character row touched
by the last sixel. The last sixel _may_ not be a multiple of 6
pixels, *if* the sixel had an explicit width/height set via raster
attributes.

This is an intended deviation from the DEC cursor placement algorithm,
where the cursor is placed on the character row touched by the last
sixel's *upper* pixel.

The adjusted algorithm implemented here makes it much easier for
applications to handle text-after-sixels, since they can now simply
assume a single text newline is required to move the cursor to a
character row not touched by the sixel.
2024-03-18 16:58:54 +01:00
Craig Barnes
5f41eb798b base64: simplify lookup table initializer 2024-03-17 15:27:22 +00:00
Craig Barnes
e8b04e0e2c
xmalloc: add xmemdup() and use to replace some uses of xmalloc+memcpy 2024-03-17 12:31:02 +01:00
Craig Barnes
853be450bb main/config: replace some uses of xasprintf() with xstrjoin() 2024-03-16 20:17:53 +00:00
Daniel Eklöf
578765ad83
wayland: skip loading cursor theme when using server side cursors
We don't need the client side cursor theme when using server side
cursors.
2024-03-16 15:36:44 +01:00
Craig Barnes
27330a5dd6
csi: indicate "permanently reset" for DECRQM queries of mode 67 (DECBKM)
This allows dynamic querying for the equivalent of terminfo's "kbs"
capability.
2024-03-16 15:36:00 +01:00
Daniel Eklöf
f17b989650
sixel: disable debug logging 2024-03-16 08:57:15 +01:00
Daniel Eklöf
60fd4a262c
sixel: initialize the color table to colors used by the VT340 2024-03-15 15:19:43 +01:00
Daniel Eklöf
dd3bb13d97
completions: fish: fix path completion for --pty 2024-03-14 07:37:57 +01:00
Alyssa Ross
86894a1cd2
Add support for opening an existing PTY
Virtual machine monitor programs (e.g. QEMU, Cloud Hypervisor) expose
guest consoles as PTYs.  With this patch, foot can access these guest
consoles.

Usually, the program used for accessing these PTYs is screen, but
screen is barely developed, doesn't support resizing, and has a bunch
of other unrelated stuff going on.  It would be nice to have a
terminal emulator that properly supported opening an existing PTY.
The VMM controls the master end of the PTY, so to the other end (in
this case foot), it just behaves like any application running in a
directly-opened PTY, and all that's needed is to change foot's code to
support opening an existing PTY rather than creating one.

Co-authored-by: tanto <tanto@ccc.ac>
2024-03-14 07:31:03 +01:00
Daniel Eklöf
712bc95db3
csi: the CSI-t family of queries now report unscaled pixel values
Before this patch, we reported scaled pixel values. This was rather
useless, since applications have no way of getting the scaling factor,
to "scale up" e.g. images.

Furthermore, the most common use of these queries are probably to
calculate the dimensions to use when emitting sixels.

Closes #1643
2024-03-14 07:26:37 +01:00
Daniel Eklöf
f72555f29a
Merge branch 'rectangular-edit'
Closes #1633
2024-03-14 07:25:49 +01:00
Daniel Eklöf
e2b3eb91dd
csi: params_to_rectangular_area(): ensure left/right is within bounds 2024-03-10 17:37:11 +01:00
Daniel Eklöf
4ea4e5da4e
changelog: rectangular functions: add bug ref 2024-03-07 16:29:39 +01:00
Daniel Eklöf
cbf55ccacf
changeloge: move DECERA to the 'unreleased' section 2024-03-07 16:28:59 +01:00
Daniel Eklöf
6ff307b3b5
doc: ctlseq: DECCARA, DECRARA, DECCRA, DECFRA and DECERA 2024-03-07 16:26:11 +01:00
Daniel Eklöf
8d7ab86182
term_fill(): no need to set attrs.clean = 0
The VT state’s attribute is always 0
2024-03-07 16:25:04 +01:00
Daniel Eklöf
6a01642a6f
csi: DECCARA+DECRARA: dirty cells 2024-03-07 16:25:04 +01:00
Daniel Eklöf
aac24bfa1b
foot.info: add non-standard capability ‘Rect’ (used by tmux)
This tells tmux it can use DECFRA to erase rectangular regions.
2024-03-07 16:25:02 +01:00
Daniel Eklöf
60c5d889ec
vt: DECALN: erase sixels, reset margins, home the cursor
https://vt100.net/docs/vt510-rm/DECALN.html:

  Notes on DECALN

  DECALN sets the margins to the extremes of the page, and moves the
  cursor to the home position.
2024-03-07 16:24:34 +01:00
Daniel Eklöf
d6c5bc3262
csi: DECRARA: fix comment: DECCARA -> DECRARA 2024-03-07 16:24:33 +01:00
Daniel Eklöf
f5c574cd94
csi: DECCRA: no need for a counter here 2024-03-07 16:24:33 +01:00
Daniel Eklöf
23908d9277
term_fill(): change ‘character’ parameter from char -> uint8_t 2024-03-07 16:24:33 +01:00
Daniel Eklöf
1b13deff04
term_fill(): make sure the filled cells have their ‘clean’ bit reset 2024-03-07 16:24:33 +01:00
Daniel Eklöf
74a1fa9e00
vt: update DECALN to use term_fill() 2024-03-07 16:24:33 +01:00
Daniel Eklöf
b3a84ba71b
term: modify term_fill() to optionally reset the SGR attributes 2024-03-07 16:24:33 +01:00
Daniel Eklöf
189cfd717f
term: replace term_put_char() with term_fill() 2024-03-07 16:24:33 +01:00
Daniel Eklöf
1b66c6a3ac
csi: rectangular: add helper function params_to_rectangular_area()
This functions reads the four top/left/bottom/right parameters,
validates them, and converts relative row numbers to absolute.

Returns true if the params are valid, otherwise false.
2024-03-07 16:24:33 +01:00
Daniel Eklöf
df5dd94789
term: codespell: limitiations -> limitations 2024-03-07 16:24:33 +01:00
Daniel Eklöf
4b4fe9d493
changelog: rectangular edit functions 2024-03-07 16:24:33 +01:00
Daniel Eklöf
95293f142a
csi: add ‘28’ (rectangular edit) to primary DA response 2024-03-07 16:24:33 +01:00
Daniel Eklöf
926d88fd30
csi: implement rectangular edit escapes
* DECCARA - change attributes in rectangular area
* DECRARA - reverse attributes in rectangular area
* DECCRA - copy rectangular area
* DECFRA - fill rectangular area
* DECERA - erase rectangular area

Not implemented:

* DECSERA - selective erase rectangular area
2024-03-07 16:24:33 +01:00
Daniel Eklöf
b30b8a2944
put_char fixup 2024-03-07 16:24:32 +01:00
Daniel Eklöf
b4dbfb58b8
grid: remove prototype for non-existing function 2024-03-07 16:24:32 +01:00
Daniel Eklöf
e6c372b14f
term: print: spacers may be printed all the way up to the last column 2024-03-07 16:24:32 +01:00
Daniel Eklöf
ea851962c1
term: add term_put_char()
This function prints a single, non-double width, character to the
grid. It handles OSC-8 hyperlinks, but does not:

* update the cursor location
* erase sixels
2024-03-07 16:24:32 +01:00
Daniel Eklöf
3e6f0e63f3
sixel: don't try to emit a sixel if we're outside the image's boundaries
Closes #1634
2024-03-07 16:21:06 +01:00
Daniel Eklöf
75fd59df3f
sixel: debug: sixel image _may_ be zero-sized
For example, and single GNL (Graphical New Line) will result in a
sixel with a non-zero height, but a zero width.
2024-03-07 16:20:29 +01:00
Daniel Eklöf
a2fa667f45
sixel: we no longer need the extra newline
Since we never place the cursor *under* the sixel anymore.
2024-03-07 16:19:56 +01:00
Daniel Eklöf
1421ba504d
sixel: debug: fix logged width/height values when emitting sixel
image.width and image.height are the scaled dimensions, and these
haven't been set when the log message is printed.
2024-03-07 16:18:35 +01:00
Daniel Eklöf
1568518ab3
sixel: performance improvements
* Store pointer to current pixel (i.e. pixel we're about to write to),
  instead of a row-byte-offset. This way, we don't have to calculate the
  offset into the backing image every time we emit a sixel band.

* Pass data pointer directly to sixel_add_*(), to avoid having to
  calculate an offset into the backing image.

* Special case adding a single 1:1 sixel. This removes a for loop, and
  simplifies state (position) updates. It is likely LTO does this for
  us, but this way, we get it optimized in non-LTO builds as well.
2024-03-06 16:37:54 +01:00
Daniel Eklöf
8ff8ec5b70
sixel: fix row height calculation in resize_vertically()
In resize_vertically(), we assumed a sixel is 6 pixels tall. This is
mostly true, but not for non-1:1 sixels. Or, to be more precise, not
for sixels where 'pan' != 1.

This caused us to allocate too little backing memory, resulting in a
crash when we later tried to write to the image.
2024-03-06 16:36:30 +01:00
Daniel Eklöf
702d3ae6ca
kitty kbd: update handling of locked modifiers
The kitty keyboard specification has been updated/clarified yet
again. Locked modifiers are to be ignored if the key event would
result in plain text without the locked modifier being enabled.

In short, locked modifiers are included in the set of modifiers
reported in a key event. But having a locked modifier enabled doesn't
turn all key events into CSIu sequences.

For example, with only the disambiguate mode enabled, pressing 'a', or
'shift+a' results in a/A regardless of the state of Caps- or NumLock.

But 'ctrl+a', which always results in a CSIu, will have a different
modifier list, depending on whether Caps- or NumLock are enabled.
2024-03-06 16:33:24 +01:00
Daniel Eklöf
ec73e4d10d
kitty kbd: switch from GTK to XKB mode for 'consumed' modifiers
This fixes an issue where some key combinations resulted in different
output (e.g. escape code vs. plain text) depending on the state of
e.g. the NumLock key. One such example is Shift+space. Another example
is Shift+BackSpace.

This patch also removes the hardcoded CapsLock filter, when
determining whether a key combo produces text or not, and instead uses
the locked modifiers as reported by XKB.
2024-03-02 10:28:25 +01:00
Daniel Eklöf
d3b348a5b1
cursor-shape: improve xcursor fallback support, and prefer CSS names
Before this patch, we used legacy X11 xcursor names, and didn't really
have any fallback handling in place (we only tried to fallback to
"xterm", regardless of which cursor shape we were trying to load).

This patch changes two things:

1. Improved fallback support. cursor_shape_to_string() now returns a
   list of strings. This allows us to have per-shape fallbacks, and any
   number of fallbacks.

2. We prefer CSS xcursor names over legacy X11 names.
2024-03-01 07:04:48 +01:00
Daniel Eklöf
6fd533ce13
render: don't try to set a NULL xcursor image
render_xcursor_update() is called when we've loaded a new xcursor
image, and needs to display it. The reason it's not pushed to the
compositor immediately is to ensure we don't flood the Wayland socket
with xcursor updates.

Normally, it's only called when we *succeed* to load a new xcursor
image. I.e. if we try to load a non-existing xcursor image, we never
schedule an update.

However, we _can_ still end up in render_xcursor_update() without a
valid xcursor image. For example, we have loaded a valid xcursor
image, and scheduled an update. But before the update runs, the user
moves the cursor, and we try to load a new xcursor image. If it fails,
we crash when the previously scheduled update finally runs.

Closes #1624
2024-02-29 08:16:20 +01:00
Daniel Eklöf
4bb3b5383f
ci: run x86+x64 builds in parallel
... by making them depend on the 'subprojects' step.
2024-02-24 10:05:05 +01:00
Daniel Eklöf
3d9aa1c29c
config: don't try to free key combos we haven't allocated
When erroring out due to a key combo being "empty", we incorrectly
tried to free one key binding. This is because 'used_combos' is
initialized to '1'. And it should, but an empty key binding is a
special case.

Fixes a test failure, and closes #1620
2024-02-24 09:58:04 +01:00
Daniel Eklöf
a5ab490380
ci: remove deprecated 'group' 2024-02-24 09:54:51 +01:00
Daniel Eklöf
1c985537ec
ci: 'steps' is a list 2024-02-23 18:02:18 +01:00
Daniel Eklöf
0dc105d0e4
ci: rename .woodpecker.yml -> .woodpecker.yaml
According to the latest woodpecker docs, this is the only supported
extension.
2024-02-23 17:59:01 +01:00
Daniel Eklöf
678bdb7c3f
config: on error, correctly free partially parsed key combos 2024-02-23 17:49:24 +01:00
Daniel Eklöf
d31ccf12d0
changelog: kitty modifier no longer clears selection + viewport 2024-02-23 17:48:01 +01:00
Tim Culverhouse
749d36d321 input: don't clear text selection on modifier keypresses
When kitty keyboard is enabled, pressing a modifier key will clear the
text selection. This makes it difficult to copy text because the
selection clears as soon as the user presses "ctrl".

Tested-by: Robin Jarry <robin@jarry.cc>
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
2024-02-22 15:23:40 -06:00
Daniel Eklöf
67f97cbca1
shm: use XRGB surfaces when we know we wont be using transparency 2024-02-21 16:29:10 +01:00
Daniel Eklöf
09d856f2ff
input: improved debug logging of key press/release
* Log which it is: press or release
* Include locked modifiers
* Include human-readable names of the modifiers
2024-02-20 16:18:55 +01:00
Daniel Eklöf
0c94bf43f2
vt: ignore VS16 (U+FE0F) when grapheme clustering is disabled
This fixes:

a) a compilation error with -Dgrapheme-clustering=disabled

b) ensures U+FE0F does *not* allocate a two cells when grapheme
   clustering has been disabled (either compile time, in config, or
   run-time).
2024-02-16 07:13:32 +01:00
Daniel Eklöf
aca9af0202
vt: VS16 - variation selector 16 (emoji representation) should only affect emojis 2024-02-15 16:56:30 +01:00
Daniel Eklöf
9ca84e6b48
config: map Control+wheel to font increase/decrease
This is in addition to the already existing keyboard shortcuts.

Also add missing default keyboard/mouse bindings to readme + foot(1).
2024-02-15 16:55:40 +01:00
Daniel Eklöf
729bd57cae
term: erase-scrollback: handle non-existing scrollback history
If scrollback.lines == 0, and the window size (number of rows) is a
power of two, all rows are always visible. I.e. there is no scrollback
history.

This threw off the scrollback erase logic, causing visible rows to be
erased, and set to NULL. This triggered a crash when trying to update
the view.

Closes #1610
2024-02-15 16:54:41 +01:00
Daniel Eklöf
c114afadbd
render: always center grid when fullscreened or maximized 2024-02-15 16:54:01 +01:00
Craig Barnes
fa01bf2b75 csi: support DECRQM queries for ECMA-48 (SM/RM) modes
This is in addition to the existing support for DECRQM queries
of DEC private modes.
2024-02-11 15:09:28 +00:00
Daniel Eklöf
9d9690410a
pgo: render_resize_force() has been removed, adjust stub accordingly
Closes #1601
2024-02-08 16:44:55 +01:00
Daniel Eklöf
69df42c51b
doc: better examples for pipe-* commands 2024-02-07 17:09:01 +01:00
Daniel Eklöf
bc8c2e0112
wayland: regression: use correct scaling factor when calling render_resize()
When an output property (such as scaling factor) has changed, we need
to call render_resize() to ensure the window surface is correct (for
example, we may have to change its scale).

The width/height parameters are in *logical* pixels (i.e. already
scaled). For render_resize() to work correctly when the scale is being
changed, it needs to be called with *current* logical size. This means
we need to scale our current width/height using the *old* scaling
factor.
2024-02-07 16:22:33 +01:00
Daniel Eklöf
ebbee61f14
input: remove debug logging 2024-02-06 14:04:59 +01:00
Daniel Eklöf
3b5d83a3ec
pgo: add missing stub for render_refresh_app_id() 2024-02-06 14:04:22 +01:00
Daniel Eklöf
af114f81a0
pgo: fix function prototype for stub function get_current_modifiers() 2024-02-06 14:03:07 +01:00
Daniel Eklöf
4b075bb075
osc: 176 (app-id): implement query+reply
Applications can now query for the current app-id with:

    \E] 176 ; ? \E\\

The reply is

    \E] 176 ; <title> \E\\
2024-02-06 13:55:30 +01:00
Daniel Eklöf
0b95e72073
Merge branch 'app-id' 2024-02-06 13:50:38 +01:00
Daniel Eklöf
22f04f6a84
changelog: OSC-176 - set app-id 2024-02-06 13:50:19 +01:00
delthas
6c56b04b3f
osc: add support for osc 176 (app ID)
This adds support for a new OSC escape sequence: OSC 176, that lets
terminal programs tell the terminal the name of the app that is
running. foot then sets the app ID of the toplevel to that ID,
which lets the compositor know which app is running, and typically
sets the appropriate icon, window grouping, ...

See: https://gist.github.com/delthas/d451e2cc1573bb2364839849c7117239
2024-02-06 13:50:09 +01:00
Daniel Eklöf
4801d3a305
term: drop term->render.title.is_armed
This boolean isn't needed. The idea was probably to not re-program the
timer unnecessarily, or even to prevent it from being moved forward in
time indefinitely.

However, the logic has (probably) gone through some changes, that now
makes it irrelevant.

The timer isn't moved forward indefinitely; it is always set to 8ms
from the last title update. The closer we get to that point in time,
the smaller the timeout we set.

Now, is_armed _did_ prevent the timer from being re-programmed. But
that tiny performance tweak isn't really necessary, as the title
should, in normal cases, not be set that often anyway.
2024-02-06 13:41:09 +01:00
Daniel Eklöf
41dc259744
doc: foot-ctlseq: add OSC 133 C/D (command output start/end) 2024-02-06 13:38:08 +01:00
Daniel Eklöf
316136f428
term: ignore attempts to set a title that contains an invalid UTF-8 sequence
Setting the title ultimately leads to a call to
xdg_toplevel::set_title(). It is a protocol violation to try to set a
title that contains an invalid UTF-8 sequence:

    The string must be encoded in UTF-8.

Closes #1552
2024-02-06 13:10:35 +01:00
Daniel Eklöf
756da87346
changelog: smm+rmm 2024-02-06 12:38:31 +01:00
Daniel Eklöf
9f4eb13e9e
terminfo: smm: enable 8-bit Meta mode
To enable 8-bit meta mode, we need to:

* disable "send ESC when meta modifies a key" (private mode 1036)
* enable "8-bit meta mode" (private mode 1034)

rmm reverses the above.

Closes #1584
2024-02-06 12:38:19 +01:00
Daniel Eklöf
7999975016
Don't use fancy Unicode quotes, stick to ASCII 2024-02-06 12:36:45 +01:00
Daniel Eklöf
d6939dd634
readme: shell integration: fix wiki link 2024-02-06 12:26:00 +01:00
Daniel Eklöf
84e681f028
readme: add "Piping last command's output" to index 2024-02-06 12:14:48 +01:00
Daniel Eklöf
0a302265ec
Merge branch 'pipe-command-output' 2024-02-06 12:13:27 +01:00
Daniel Eklöf
231e6eb3f1
grid: resize with reflow: reflow FTCS_COMMAND_{EXECUTED,FINISHED} 2024-02-06 12:13:09 +01:00
Daniel Eklöf
110a6dd6f0
grid: resize without reflow: truncate shell_integration.cmd_{start,end}
This ensures the cmd start/end columns are valid in the new grid.
2024-02-06 12:13:09 +01:00
Daniel Eklöf
d5308a0493
term: command_output_to_text(): don’t skip FTCS_COMMAND_FINISHED on last row 2024-02-06 12:13:09 +01:00
Daniel Eklöf
1393942de3
readme, doc/foot.1: document shell-integration:command-output tracking 2024-02-06 12:13:09 +01:00
Daniel Eklöf
d7dbb91e65
changelog: pipe-command-output 2024-02-06 12:13:08 +01:00
Daniel Eklöf
0fed2451ea
doc: foot.ini: document pipe-command-output 2024-02-06 12:12:57 +01:00
Daniel Eklöf
f2a8368759
foot.ini: add pipe-command-output key binding 2024-02-06 12:12:57 +01:00
Daniel Eklöf
1c70a84fde
config: add pipe-command-output key-binding 2024-02-06 12:12:57 +01:00
Daniel Eklöf
e9607de5ae
osc: store column of FTCS_COMMAND_{EXECUTED,FINISHED} in row struct 2024-02-06 12:12:57 +01:00
Daniel Eklöf
f8e875a7cd
term: move row->prompt_marker into new struct, row->shell_integration 2024-02-06 12:12:56 +01:00
Daniel Eklöf
0f10c4fd6c
Merge branch 'modifiers'
Closes #1348
2024-02-06 12:11:51 +01:00
Daniel Eklöf
1685f38ee6
changelog: custom modifiers in key bindings 2024-02-06 11:10:36 +01:00
Daniel Eklöf
0aefc2c65d
input: remove the concept of "significant" modifiers
For the purpose of matching key bindings, "significant" modifiers are
no more.

We're really only interested in filtering out "locked"
modifiers. We're already doing this, so there's no need to *also*
match against a set of "significant" modifiers.

Furthermore, we *never* want to consider locked keys (e.g. when
emitting escapes to the client application), thus we can filter those
out already when retrieving the set of active modifiers.

The exception is the kitty keyboard protocol, which has support for
CapsLock and NumLock. Since we're already re-retrieving the "consumed"
modifiers (using the GTK style, rather than normal "XKB" style, to
better match the kitty terminal), we might as well re-retrieve the
effective modifiers as well.
2024-02-06 11:08:42 +01:00
Daniel Eklöf
4730ff8d08
input/config: support *all* modifier names
That is, allow custom modifiers (i.e. other than ctrl/shift/alt etc)
in key bindings.

This is done by no longer validating/translating modifier names to
booleans for a pre-configured set of modifiers (ctrl, shift, alt,
super).

Instead, we keep the modifier *names* in a list, in the key binding
struct.

When a keymap is loaded, and we "convert" the key binding, _then_ we
do modifier translation. For invalid modifier names, we print an
error, and then ignore it. I.e. we no longer fail to load a config due
to invalid modifier names.

We also need to update how we determine the set of significant
modifiers. Any modifier not in this list will be ignored when matching
key bindings.

Before this patch, we hardcoded this to shift/alt/ctrl/super. Now, to
handle custom modifiers as well, we simply treat *all* modifiers
defined by the current layout as significant.

Typically, the only unwanted modifiers are "locked" modifiers. We are
already filtering these out.
2024-02-06 11:05:20 +01:00
Andrew J. Hesford
21a8d832ce
feature: add resize-by-cells option to constrain window sizes...
...to multiples of the cell size, and preserve grid size when changing
fonts or display scales in floating windows.
2024-02-05 12:14:53 +01:00
Craig Barnes
e0f3703ae6
util: add streq() function and use in place of strcmp(...) == 0 2024-02-05 12:09:52 +01:00
Craig Barnes
44c0cf594b
csi: simplify handling of Set/Reset Mode (SM/RM) sequences 2024-01-27 08:43:32 +01:00
Craig Barnes
91b22ae21a Replace unchecked allocations with calls to xmalloc.h functions 2024-01-25 07:03:50 +00:00
Daniel Eklöf
43e27a8843
wayland: 'mode' is unused when LOG_ENABLE_DBG is not set 2024-01-24 20:00:18 +01:00
Leonardo Hernández Hernández
7e3da3007b
wayland: use wl_compositor version 6 when available 2024-01-24 20:00:18 +01:00
Daniel Eklöf
4ee4f47065
input: kitty: update to latest version of the spec
Starting with kitty 0.32.0, the modifier bits during modifier key
events behave differently, compared to before. Or, rather, they have
now been spec:ed; before, behavior was different on e.g. MacOS, and
Linux.

The new behavior is this:

On key press, the modifier bits in the kitty key event *includes* the
pressed modifier key.

On key release, the modifier bits in the kitty key event does *not*
include the released modifier key.

In other words, The modifier bits reflects the state *after* the key
event.

This is the exact opposite of what foot did before this patch.

The patch is really pretty small: in order to include the key in the
modifier set, we simulate a key press to update the XKB state, using
xkb_state_uppate_key(). For key pressed, we simulate an XKB_KEY_DOWN
event, and for key releases we simulate an XKB_KEY_UP event.

Then we re-retrieve the modifers, both the full set, and the consumed
set.

Closes #1561
2024-01-24 19:57:32 +01:00
Craig Barnes
6ed1c28d2c terminal: simplify some string-related code in reload_fonts() 2024-01-23 22:04:41 +00:00
Artturin
8073ad352b slave: fix typo
```
../slave.c: In function 'slave_spawn':
../slave.c:441:21: error: 'custom_envp' undeclared (first use in this function); did you mean 'custom_env'?
  441 |         add_to_env(&custom_envp, "TERMINFO", FOOT_TERMINFO_PATH);
      |                     ^~~~~~~~~~~
      |                     custom_env
../slave.c:441:21: note: each undeclared identifier is reported only once for each function it appears in
```
2024-01-16 22:40:35 +02:00
Daniel Eklöf
9da7152f83
slave: don't skip setting environment variables when using a custom environment
When launching footclient with -E,--client-environment the environment
variables that should be set by foot, wasn't.

Those variables are:

* TERM
* COLORTERM
* PWD
* SHELL

and all variables defined by the user in the [environment] section in
foot.ini.

In the same way, we did not *unset* TERM_PROGRAM and
TERM_PROGRAM_VERSION.

This patch fixes it by "cloning" the custom environment, making it
mutable, and then adding/removing the variables above from it.

Instead of calling setenv()/unsetenv() directly, we add the wrapper
functions add_to_env() and del_from_env().

When *not* using a custom environment, they simply call
setenv()/unsetenv().

When we *are* using a custom environment, add_to_env() first loops all
existing variables, looking for a match. If a match is found, it's
updated with the new value. If it's not found, a new entry is added.

del_from_env() loops all entries, and removes it when a match is
found. If no match is found, nothing is done.

The mutable environment is allocated on the heap, but never free:d. We
don't need to free it, since it's only allocated after forking, in the
child process.

Closes #1568
2024-01-11 16:37:17 +01:00
Daniel Eklöf
208008d717
config: fix cloning of env_vars tllist
When cloning a config struct, the env_vars tllist wasn't correctly
copied. We did correctly iterate and duplicate all old entries, but we
did *not* reset the list in the cloned struct before doing so.

This meant the list contained entries shared with the original list,
causing double free:s in --server mode.
2024-01-10 16:41:03 +01:00
Daniel Eklöf
a2283c8229
wayland: surface_scale_explicit_width_height(): dont assert width/height are valid for scale, take 2
764248bb0d modified
wayl_surface_scale_explicit_width_height() to not assert the surface
size is valid for the given scaling factor. This, since that function
is only used when scaling a mouse pointer surface.

However, that commit only updated the code path run when fractional
scaling is available (i.e. when the compositor implements the
fractional-scale-v1 protocol).

The legacy code path, that does integer scaling, was still asserting
the surface width/height were divisible with the scaling factor.

For the same reasons this isn't true with fractional scaling
available, it's not true with integer scaling. Fix by skipping the
assertions.

This patch also converts the assertions to more verbose BUG() calls,
that prints more information on the numbers involved.

Closes #1573
2024-01-09 16:47:41 +01:00
Daniel Eklöf
0ca4633898
slave: ignore return value of chdir()
It's not critical.

Fixes

    ../slave.c: In function ‘slave_spawn’:
    ../slave.c:410:9: error: ignoring return value of ‘chdir’ declared with attribute ‘warn_unused_result’ [-Werror=unused-result]
      410 |         chdir("/");
          |         ^~~~~~~~~~
2024-01-05 08:12:32 +01:00
Daniel Eklöf
66f25bb434
slave: chdir to / after spawning the client application
With this patch, the terminal process now changes PWD to / after
spawning the client application.

This ensures the terminal process itself does not "lock" a
directory. For example, we may keep a mount point from being
unmounted.

Closes #1528
2024-01-03 14:07:15 +01:00
LmbMaxim
14472cdbd9
Add poimandres theme 2024-01-03 13:33:31 +01:00
Daniel Eklöf
e5f5a74e81
changelog: formatting 2024-01-03 13:31:35 +01:00
Sivecano
05e6fd969a
support numpad in unicode mode 2024-01-03 13:30:51 +01:00
Craig Barnes
3b3477d657 appstream: update releases list 2023-12-06 16:23:18 +00:00
eugenrh
242767d373 theme: electrophoretic
A theme for grayscale electrophoretic displays (e-ink), which aims
to maximize the contrast between the text and the white background.
2023-11-14 18:30:28 +00:00
Gregory Anders
8b2b65bbbc terminfo: add terminator to conditional in Sync
I'm not sure if this is _strictly_ necessary, but according to the
terminfo specification [1] a conditional string should be terminated
with `%;`.

[1]: https://man7.org/linux/man-pages/man5/terminfo.5.html
2023-11-13 19:10:15 -06:00
Daniel Eklöf
ca46edfe6f
changelog: double close config file descriptor 2023-10-27 16:23:57 +02:00
Jan Palus
8e1b51be10
config: reset conf file descriptor after closing file stream
conf file descriptor is closed once again during cleanup at the end of
config_load() if descriptor >= 0. avoid double closing by assigning
negative value to fd after first close.

by the time second close happens some other descriptor might be opened
reusing previous number ie it might happen during foot server startup
when syslog message is logged between one close and the other. in this
particular situation, as of this writing, it considers fd=3 for which
following events apply:

1. conf file is opened and fclosed()
2. warning is logged with syslog which leads to opening socket to
   /dev/log which is kept open by glibc (gets fd=3)
3. second close during config_load() closes /dev/log socket descriptor
4. epoll_create() in fdm.c reuses fd=3 again
5. another message is being logged with syslog. glibc notices sendto()
   failure on saved /dev/log descriptor hence it closes it and opens
   new one

Due to epoll descriptor closure foot starts a chain of errors that
lead to startup failure.

Fixes #1531
2023-10-27 16:21:46 +02:00
Jan Palus
ee02e7b07d
main: correct short option case in help output for disabling syslog 2023-10-27 16:20:23 +02:00
xnuk
7ffbb3cc27
man foo.ini.5: Add 'Comma separated list of fonts' example 2023-10-27 16:18:41 +02:00
Daniel Eklöf
02fff24b4f
config: improve validation of color values, default alpha to 0xff
Reject color values that aren't in either RGB, or ARGB format. That
is, color values that aren't hexadecimal numbers with either 6 or 8
digits.

Also, if a color value is allowed to have an alpha component, and the
user left it out, default to 0xff (opaque) rather than 0x00 (fully
transparent).

Closes #1526
2023-10-26 16:25:57 +02:00
Fazzi
85a4e4ccc1 add CCACHE_DISABLE=1 to pgo.sh to avoid errors when ccache is enabled 2023-10-20 10:06:05 +01:00
Daniel Eklöf
642f9910c2
changelog: add new 'unreleased' section 2023-10-17 17:25:47 +02:00
Daniel Eklöf
fe11baa4bc
Merge branch 'releases/1.16' 2023-10-17 17:25:22 +02:00
Daniel Eklöf
8b3dbf0972
meson: bump version to 1.16.2 2023-10-17 17:24:12 +02:00
Daniel Eklöf
47bc28ce55
changelog: prepare for 1.16.2 2023-10-17 17:24:00 +02:00
Daniel Eklöf
f1e7d78c96
Merge branch 'master' into releases/1.16 2023-10-17 17:23:18 +02:00
Daniel Eklöf
3dbb86914c
render: sixel: regression: wrong cell color behind opaque sixels
An opaque sixel that isn't a multiple of the cell size will have some
cells partially visible (either the entire last row, the entire last
column, or both).

These must be rendered before blitting the sixel.

f5f2f5a954 introduced a regression,
where all such cells were rendered as if the cursor was there, giving
them the wrong appearance.

Closes #1520
2023-10-13 18:44:44 +02:00
Daniel Eklöf
857ac224c5
changelog: add new 'unreleased' section 2023-10-12 20:36:28 +02:00
Daniel Eklöf
667095e429
Merge branch 'releases/1.16' 2023-10-12 20:35:48 +02:00
Daniel Eklöf
195eb3356a
meson: bump version to 1.16.1 2023-10-12 16:36:18 +02:00
Daniel Eklöf
c26c6e285a
changelog: prepare for 1.16.1 2023-10-12 16:36:07 +02:00
Daniel Eklöf
ee7e6e7234
Merge branch 'master' into releases/1.16 2023-10-12 16:35:37 +02:00
Daniel Eklöf
f5f2f5a954
render: fix surface damage when rendering sixels.
Pass a damage region to render_row()/render_cell() when rendering
partially visible cells underneath a sixel.

This ensures the affected regions are later reported as 'damaged' to
the Wayland compositor.

Closes #1515
2023-10-12 16:32:00 +02:00
Daniel Eklöf
4aa67e464a
sixel: erase: fix clearing of cell->attrs.clean
When erasing a sixel, the cells underneath it must be marked as
'dirty', in order to be re-rendered.

This was not being done correctly; the for loop loops *from* the start
col, meaning the *end* col is *not* sixel->pos.col, as that's
the *number* of columns, not the *end* column.
2023-10-12 16:31:37 +02:00
Daniel Eklöf
c006ac3a07
shm: memfd_create: fallback to not using MFD_NOEXEC_SEAL
MFD_NOEXEC_SEAL was introduced in linux 6.3. Kernels before that
will *reject* memfd_create() calls that set it.

This caused foot to exit (i.e. not start at all), when compiled on
linux >= 6.3, but run on linux < 6.3.

We _do_ want to use MFD_NOEXEC_SEAL, since a) our memory mapped really
shouldn't be executable, and b) to silence a warning on linux >= 6.3.

To handle all cases, first try *with* MFD_NOEXEC_SEAL. If that fails
with EINVAL, retry *without* it.

Closes #1514
2023-10-12 16:16:11 +02:00
Daniel Eklöf
7d7b48f104
changelog: fix link to issue 1077 2023-10-11 18:39:43 +02:00
Daniel Eklöf
4847cc3bd1
changelog: add new 'unreleased' section 2023-10-11 18:19:31 +02:00
Daniel Eklöf
dff9e4ec59
Merge branch 'releases/1.16' 2023-10-11 18:19:10 +02:00
Daniel Eklöf
a9d6eaf937
meson: bump version to 1.16.0 2023-10-11 18:15:01 +02:00
Daniel Eklöf
7131c96b26
changelog: prepare for 1.16.0 2023-10-11 18:14:40 +02:00
Daniel Eklöf
1cafadea6c
changelog: emphasize the new key bindings are for search mode 2023-10-11 18:12:18 +02:00
Daniel Eklöf
6e58bd8351
Merge branch 'fix-font-baseline-calculation'
Closes #1511
Closes #1463
2023-10-11 16:31:53 +02:00
Daniel Eklöf
4449177517
term: cache font baseline
No need to redo the calculation for every single cell we render,
every frame...
2023-10-10 14:23:33 +02:00
Daniel Eklöf
34aa979f46
term_font_baseline(): only center glyph when a custom line-height is being used
When using the font's own line-height, simply set the baseline
'descent' pixels above the bottom of the cell.

This fixes an issue where some fonts appeared "glued" to the top of
the cell, and sometimes getting partially clipped.
2023-10-10 13:52:24 +02:00
Daniel Eklöf
7d126ff414
changelog: fixed font baseline calculation 2023-10-10 10:55:26 +02:00
Daniel Eklöf
41932287cf
Revert "font baseline: use max(font->height, font->ascent + font->descent) when calculating font height"
This reverts commit fd813d0e6c.

The intent of the reverted commit was to align font height calculation
with cell height calculation. However, it turns out this breaks some
fonts. Typically those with large:ish differences in their 'height'
attribute, and their ascent+descent value.

Closes #1511
2023-10-10 10:52:35 +02:00
Daniel Eklöf
4cf2c45baa
render: better description of why we disable transparency in fullscreen 2023-10-10 09:27:00 +02:00
Daniel Eklöf
5c58fc2a28
Merge branch 'visual-bell-remastered'
Closes #1337
2023-10-10 08:15:11 +02:00
Daniel Eklöf
af0feed3e5
changelog: fix issue number for visual bell
1508 refers to the pull request, not the feature request.
2023-10-10 08:14:43 +02:00
Daniel Eklöf
ce64da2fe1
doc: foot.ini.5: move flash{,-alpha} to the bottom of the 'colors' section 2023-10-10 08:13:35 +02:00
Daniel Eklöf
0c6a3731c3
doc: foot.ini.5: flash_alpha -> flash-alpha 2023-10-10 08:13:35 +02:00
Daniel Eklöf
9cf22df784
foot.ini: flash_alpha -> flash-alpha 2023-10-10 08:13:35 +02:00
Daniel Eklöf
eea995637d
term: remove unneeded (and mostly unused) term->flash{,_alpha} 2023-10-10 08:13:35 +02:00
Daniel Eklöf
8a2a450778
doc: foot.ini: flash: tweak grammar, use consistent formatting 2023-10-10 08:13:35 +02:00
Raimund Sacherer
8273962372
Enable the use of flash as visual bell
With this patch we can configure flash in the bell section. The colors
section allow now to configure the color and translucency of the
flash.
2023-10-10 08:12:48 +02:00
Daniel Eklöf
c50b1f9900
render: more fine-grained wayland surface damage tracking
Before this patch. Wayland surface damage tracking was done on a
per-row basis. That is, even if just one cell was updated, the entire
row was "damaged".

Now, damage is per cell. This hopefully results in lower latencies in
many use cases, and especially on high DPI monitors.
2023-10-10 07:56:27 +02:00
Daniel Eklöf
1c9d98d57e
config: log_contextual_errno(): sync with log_contextual()
... in terms of whether to print section/value separators
2023-10-08 16:52:21 +02:00
Daniel Eklöf
e41555fe0f
shm: move definition of FOOT_MFD_FLAGS to the top 2023-10-08 11:03:13 +02:00
Daniel Eklöf
e36d95a4c8
Merge branch 'search-extend-selection' 2023-10-08 10:58:33 +02:00
Daniel Eklöf
6970055dca
changelog: add the remaining scrollback-up/down bindings 2023-10-08 10:39:57 +02:00
Daniel Eklöf
3e67415e3e
config: add remaining search.scrollback key bindings
All scrollback up/down key bindings are now available in search mode.
2023-10-08 10:38:39 +02:00
Daniel Eklöf
a772179b6c
search: fix mixup in search_extend_find_line()
The has_wrapped_around_{right,left} functions were mixed up, causing
false positives and false negatives, resulting in bad search matches.

Also make all search_extend_find* functions return a boolean; false
means no change in the selection. In this case, we can skip trying to
extend the selection, and updating the UI.
2023-10-08 10:38:39 +02:00
Daniel Eklöf
6a708b35ee
foot.ini: document all the new search.extend* bindings 2023-10-08 10:38:39 +02:00
Daniel Eklöf
419f0be441
config: map ctrl+shift+right to extend-to-word-boundary 2023-10-08 10:38:39 +02:00
Daniel Eklöf
ca128ae380
selection: find_word_boundary: ensure row number is bounded 2023-10-08 10:38:39 +02:00
Daniel Eklöf
ddf4eb3b78
search: don't try to extend a search match when there is none 2023-10-08 10:38:39 +02:00
Daniel Eklöf
5e013cad78
selection: selection_update() uses view-local coordinates 2023-10-08 10:38:38 +02:00
Daniel Eklöf
78665a7e80
search: add more key bindings to extend the current match
This patch adds the following new search key bindings:

* extend-char (shift+right)
* extend-line-down (shift+down)
* extend-backward-char (shift+left)
* extend-backward-to-word-boundary (ctrl+shift+left)
* extend-backward-to-next-whitespace (ctrl+shift+alt+left)
* extend-line-up (shift+up)

They can be used to extend the search match (i.e. the selection).

This patch also adds an initial set of key bindings to scroll in the
scrollback history:

* scrollback-up-page
* scrollback-down-page

These work just like the key bindings for the normal mode. Also note
that it was already possible to scroll using the mouse.

This patch also fixes a couple of search mode bugs:

* crashing when a search match ends in the last column
* grapheme clusters not being matched correctly
* Search match not being "extendable" after a pointer leave event
* A few others, related to either large matches, or extending matches
  after moving the viewport.

There are still a couple of (known) issues:

* A search match isn't correctly highlighted if its *starting* point
  is outside the viewport.
* Extending the match to end of the scrollback (i.e. the most recent
  output) is simply buggy.

Related to #419
2023-10-08 10:38:37 +02:00
Daniel Eklöf
56d5d4cc21
render: disable transparency in margins when in fullscreen
This amends 899b768b74, where we started
disabling transparency in fullscreen

Closes #1503
2023-10-07 07:58:55 +02:00
6t8k
61eb56dfda
shm: if defined, set MFD_NOEXEC_SEAL flag for memfd_create
Effective from Linux 6.3.0 onward, this creates the memfd without
execute permissions and prevents that setting from ever being changed.

This is a defense-in-depth security measure and prevents a respective
kernel warning from being emitted.

See https://lwn.net/Articles/918106/ for more information.
2023-10-05 12:33:05 +02:00
Daniel Eklöf
33a5a369f2
term_reset: log hard vs. soft reset 2023-10-04 08:23:27 +02:00
Daniel Eklöf
5e1d73f3cd
Codespell fixes 2023-10-03 14:12:58 +02:00
Daniel Eklöf
58d967b2f3
Codespell fixes 2023-10-03 14:11:55 +02:00
Daniel Eklöf
883368572f
wayland: debug: log wm-capabilities as human-readable strings 2023-10-02 16:34:54 +02:00
Daniel Eklöf
b95a7cb84f
term: get_font_dpi(): don't crash when there aren't any available monitors
Seen on plasma; monitor is turned off, and then back on again. Before
the "new" output global is emitted, the compositor calls
fractional_scale::preferred_scale().

This results in a call to get_font_dpi(), where we crash, since it
assumes there is at least one monitor available.

Fix by falling back to a DPI of 96.

Hopefully closes #1498
2023-10-01 09:20:13 +02:00
Alyssa Ross
400a3f5ad2
config: apply overrides even if there's no file
Previously, foot -a test wouldn't actually set the app ID if there was
no config file and the defaults were used, which was very
counterintuitive.

Now, load_config() will carry on until the end, even if there's no
config file, so overrides still work.
2023-09-25 18:15:46 +02:00
Daniel Eklöf
7fcbca808b
csi: decrqm: 2027: permanently disabled when grapheme-width-method != double-width 2023-09-25 16:50:50 +02:00
Daniel Eklöf
4eef001d58
csi: implement DECSET/DECRST/DECRQM 2027 - grapheme cluster processing
This implements private mode 2027 - grapheme cluster processing, as
defined in the "Terminal Unicode Core"[1] specification.

Internally, we just flip the already existing option "grapheme
shaping". Since it's now runtime changeable, we need a copy of it in
the terminal struct, rather than referencing the conf object.

[1]: 13fc5a8993/spec/terminal-unicode-core.tex (L50-L53)
2023-09-25 16:50:44 +02:00
Daniel Eklöf
8a5f2915e9
dcs: xtgettcap: ignore queries with invalid hex encodings
When we receive an XTGETTCAP query, where the capability is not
correctly hex encoded, ignore it.

Before this patch, we echo:ed it back to the TTY inside an error
resonse.
2023-09-25 16:37:32 +02:00
Daniel Eklöf
54722369d8
url-mode: don't strip the file:// prefix from localhost URIs
Before this patch, the file://<host> prefix was stripped from URIs,
when the hostname matched the current host (that is, for "local"
URLs).

Unfortunately, the way this was done caused other parts of the URI to
be stripped as well. For example, the 'query' and 'fragment' parts.

This patch simply removes all special casing of file:// URIs.

Since the URL is passed to a generic opener (i.e. we don't have a
special opener application for file:// URIs), the opener helper must
handle the file:// prefix anyway.

Closes #1474
2023-09-21 18:32:57 +02:00
Daniel Eklöf
b2963bbf80
changelog: crash when xdg token is set, but compositor does not support activation 2023-09-21 18:31:46 +02:00
Alyssa Ross
9257273d84
wayland: check activation supported before activating
It's possible for token to be set when the compositor doesn't support
activation, and this caused a segfault.  For example, this can happen
when overriding WAYLAND_DISPLAY to point to a compositor that doesn't
support activation, in a terminal running under one that does, and so
has set XDG_ACTIVATION_TOKEN.
2023-09-21 06:59:48 +00:00
Sertonix
a4843ef418 doc: keybings default none 2023-09-20 13:20:48 +00:00
Daniel Eklöf
1719ff93a7
selection: add support for selecting the contents of a quote
This patch changes the default of triple clicking, from selecting the
current logical row, to first trying to select the contents of the
quote under the cursor, and if failing to find a quote, selecting the
current row (like before).

This is implemented by adding a new key binding, 'select-quote'.

It will search for surrounding quote characters, and if one is found
on each side of the cursor, the quote is selected. If not, the entire
row is selected instead.

Subsequent selection operations will behave as if the selection is
either a word selection (a quote was found), or a row selection (no
quote found).

Escaped quote characters are not supported: "foo \" bar" will match
'foo \', and not 'foo " bar'.

Mismatched quotes are not custom handled. They will simply not match.

Nested quotes ("123 'abc def' 456") are supported.

Closes #1364
2023-09-19 16:23:34 +02:00
Daniel Eklöf
fe7aa25ad8
input: make wheel events mappable
Un-grabbed wheel events are now passed through the mouse binding
matching logic, instead of being hardcoded to scrolling the terminal
contents.

They are mappable through the BTN_BACK and BTN_FORWARD buttons.

Since they're not actually button *presses*, they never generate a
click count other than 1. This limitation is documented, but not
checked in the config. This means it's possible to create bindings
like "BTN_BACK+3" (i.e. triple "click"). They will however never
trigger.

The old, hardcoded logic is now accessible through the new
scrollback-up-mouse and scrollback-down-mouse mouse
bindings. They (obiously) default to BTN_BACK and BTN_FORWARD,
respectively.

Example usage: keep the default of scrolling terminal contents with
the wheel, when used without modifiers, but map Control+wheel to font
zoom in/out:

  [mouse-bindings]
  font-increase=Control+BTN_FORWARD
  font-decrease=Control+BTN_BACK

(this also keeps the default key bindings to zoom in/out; ctrl-+ and
ctrl+-)

Closes #1077
2023-09-18 16:36:39 +02:00
CismonX
f0f0d02bf7
input: improve touch handling on pointer presense
No longer inhibits touch event handling when terminal window
has pointer focus.  Instead, inhibit touch event when at least
one pointer button is held down.

This change improves user experience when using foot with both
a mouse and a touchscreen.

Closes #1428.
2023-08-24 00:45:20 +08:00
Daniel Eklöf
4f3f614457
url-mode: handle wide chars and grapheme clusters when auto-detecting URLs
* Skip spacer cells. This fixes an issue where characters following a
  double-width character weren't detect properly.

* Unpack grapheme clusters (i.e. cells with multiple codepoints), and
  iterate all their codepoints.

Closes #1465
2023-08-21 16:26:18 +02:00
raggedmyth
482a032d1a
add panda theme 2023-08-19 08:03:54 +02:00
Daniel Eklöf
86ef638102
term: improve fallback logic when selecting scaling factor while unmapped
The foot window may, for various reasons, become completely
unmapped (that is, being removed from all outputs) at run time.

One example is wlroots based compositors; they unmap all other windows
when an opaque window is fullscreened.

21d99f8dce introduced a regression,
where instead of picking the scaling factor from one of the available
outputs (at random), we started falling back to '1' as soon as we were
unmapped.

This patch restores the original logic, but also improves upon it.

As soon as a scaling factor has been assigned to the window, we store
a copy of it in the term struct ('scale_before_unmap').

When unmapped, we check if it has a valid value (the only time it
doesn't is before the initial map). If so, we use it.

Only if it hasn't been set do we fall back to picking an output at
random, and using its scaling factor.

Closes #1464
2023-08-18 16:48:18 +02:00
Daniel Eklöf
50a28fe1e8
ci: replace 'pipeline' with 'steps'
Hopefully fixes:

  failed to parse pipeline: "pipeline:" got removed, use "steps:" instead
2023-08-18 16:46:09 +02:00
Daniel Eklöf
95bce2b20c
Merge branch 'systemd-service-ordering'
Closes #1436
2023-08-11 16:41:11 +02:00
Max Gautier
f1075377d3
changelog: systemd units in GNOME 2023-08-10 11:38:08 +02:00
Max Gautier
be4797d619
systemd: skip foot-server.service when not in a Wayland context 2023-08-10 11:29:08 +02:00
Max Gautier
14ea4322fe
Order the systemd services after graphical-session.target
This fixes services in Wayland session where WAYLAND_DISPLAY is only
imported into the systemd user instance environment after
graphical-session.target is reached (such as GNOME).
2023-08-10 11:29:06 +02:00
Daniel Eklöf
34520aa16e
meson: allow building with wayland-protocols as a subproject 2023-08-08 19:32:45 +02:00
Daniel Eklöf
1faad5add7
Merge branch 'wayland-protocols-1.32'
Closes #1391
2023-08-07 16:53:58 +02:00
Daniel Eklöf
698c5b54f3
wayland: cursor-shape-v1 is now always available
Since we're requiring wayland-protocols >= 1.32
2023-08-07 16:53:19 +02:00
Daniel Eklöf
7eee415b75
wayland: fractional-scale-v1 is now always available
Since we're requiring wayland-protocols >= 1.32
2023-08-07 16:53:19 +02:00
Daniel Eklöf
d59a4e7a77
wayland: xdg-activation is now always available
Since we're requiring wayland-protocols >= 1.32
2023-08-07 16:53:19 +02:00
Daniel Eklöf
e0475a5421
meson: require wayland-protocols >= 1.32 2023-08-07 16:53:19 +02:00
Daniel Eklöf
becd8ed049
Merge branch 'releases/1.15' 2023-08-07 16:44:24 +02:00
Daniel Eklöf
f314699945
meson: bump version to 1.15.3 2023-08-07 16:39:54 +02:00
Daniel Eklöf
341a5eeefd
changelog: prepare for 1.15.3 2023-08-07 16:39:42 +02:00
Daniel Eklöf
5334e3d1aa
main: “title%s” -> “title=%s”
Fix regression of --title,-T option. This broken when command line
parsing was switched to using overrides, in
0b4f1b4af2.

Closes #1457
2023-08-07 16:38:06 +02:00
Daniel Eklöf
d00a2a222e
vt: fix ASAN UB warning
../vt.c:648:13: runtime error: signed integer overflow: 3924432811 * 2654435761 cannot be represented in type 'long'
  SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ../vt.c:648:13 in

Closes #1456
2023-08-07 16:38:04 +02:00
Daniel Eklöf
a3d54614c7
render: OSD: center text vertically
Rewrite render_osd(), and instead of passing in an y-offset, let
render_osd() itself center the text inside the OSD buffer.

This is done using the same baseline calculation term_font_baseline()
does, except we use the buffer height instead of the line height.

Note that most OSDs are sized based on the line height...

Closes #1430
2023-08-07 16:38:03 +02:00
Daniel Eklöf
e567250449
main: translate command line options to overrides
Instead of special casing configuration affecting command line
options (like --font, --fullscreen, --maximized etc), translate them
to overrides, and let the configuration system handle them.

This also fixes an issue where -f,--font did not set csd.font, if
csd.font were otherwise unset.
2023-08-07 16:37:59 +02:00
Daniel Eklöf
e1d66ad0c1
changelog: add new ‘unreleased section’ 2023-08-07 16:37:46 +02:00
Daniel Eklöf
eea21070ee
changelog: “config” -> “config option” 2023-08-05 07:25:36 +02:00
Daniel Eklöf
be22736f23
main: “title%s” -> “title=%s”
Fix regression of --title,-T option. This broken when command line
parsing was switched to using overrides, in
0b4f1b4af2.

Closes #1457
2023-08-05 07:23:11 +02:00
Daniel Eklöf
12e0edd6e1
vt: fix ASAN UB warning
../vt.c:648:13: runtime error: signed integer overflow: 3924432811 * 2654435761 cannot be represented in type 'long'
  SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ../vt.c:648:13 in

Closes #1456
2023-08-05 07:19:51 +02:00
Daniel Eklöf
90ad3d6491
render: OSD: center text vertically
Rewrite render_osd(), and instead of passing in an y-offset, let
render_osd() itself center the text inside the OSD buffer.

This is done using the same baseline calculation term_font_baseline()
does, except we use the buffer height instead of the line height.

Note that most OSDs are sized based on the line height...

Closes #1430
2023-07-31 16:48:49 +02:00
Daniel Eklöf
ddcbf2a7b4
config: remove deprecated option 'utempter' 2023-07-31 16:47:51 +02:00
Daniel Eklöf
0b4f1b4af2
main: translate command line options to overrides
Instead of special casing configuration affecting command line
options (like --font, --fullscreen, --maximized etc), translate them
to overrides, and let the configuration system handle them.

This also fixes an issue where -f,--font did not set csd.font, if
csd.font were otherwise unset.
2023-07-31 16:26:17 +02:00
Daniel Eklöf
33dcb4d49a
changelog: add new ‘unreleased section’ 2023-07-30 13:27:42 +02:00
Daniel Eklöf
8aa61ef45c
Merge branch 'releases/1.15' 2023-07-30 13:27:16 +02:00
Daniel Eklöf
53a5d62e5a
meson: bump version to 1.15.2 2023-07-30 13:18:55 +02:00
Daniel Eklöf
3111bc89e5
changelog: prepare for 1.15.2 2023-07-30 13:18:41 +02:00
Daniel Eklöf
89d0fe561f
Merge branch 'master' into releases/1.15 2023-07-30 13:17:31 +02:00
Daniel Eklöf
1af0277564
--window-size-chars: ensure width/height are valid for current scaling factor
Before this patch, we didn’t ensure width and height were valid for
the current scaling factor, when fractional scaling _is_
available. That is, we didn’t ensure the width/height values
multiplied back to their original values after dividing with the
scaling factor.

Closes #1446
2023-07-30 07:53:33 +02:00
Daniel Eklöf
05131a9b0c
Merge branch 'csd-fractional-scaling'
Closes #1441
2023-07-29 09:10:56 +02:00
Daniel Eklöf
aea687c0a1
changelog: CSDs with fractional scaling 2023-07-29 09:09:59 +02:00
Daniel Eklöf
764248bb0d
wayl_surface_scale_explicit_width_height(): don’t assert width/height are valid for scale
This function is only called directly when scaling the mouse
pointer. The mouse pointer is never guaranteed to have a valid width
and height, so skip the width/height assertions for it.
2023-07-29 08:18:00 +02:00
Daniel Eklöf
1782474481
fractional scaling: another round(!) of rounding fixes
* Ensure buffer sizes are valid. That is, ensure that
  size / scale * scale == size.
* Do size calculation of the window geometry in the same way we
  calculate the CSD offsets.
2023-07-28 16:03:13 +02:00
Daniel Eklöf
753c4b5d4f
render: round scaled border/title/button widths
And calculation of compounded offsets/widths/heights, to compensate
for compositor rounding when positioning and scaling/sizing
subsurfaces.

Closes #1441
2023-07-28 16:03:08 +02:00
Daniel Eklöf
9d75c55146
wayland: don't try to use a non-existing viewporter interface
When instantiating the viewport for a pointer surface, we didn't first
check if the compositor implements the viewporter interface.

This triggered a crash when a) foot was compiled with fractional
scaling, and b) the compositor did not implement the viewporter
interface.

Closes #1444
2023-07-28 15:42:28 +02:00
Daniel Eklöf
139fd6d55c
meson: add -Dterminfo-base-name option
This defines the base name of the generated terminfo files. It
defaults to the value of -Ddefault-terminfo (i.e. 'foot')

Example:

  meson -Ddefault-terminfo=foot-bananas -Dterminfo-base-name=foot-apples

The generated terminfo files will be

* terminfo/f/foot-apples
* terminfo/f/foot-apples-direct

The default value of $TERM will be 'foot-bananas'
2023-07-28 15:40:53 +02:00
Daniel Eklöf
f3c5b82c82
config: add tweak.bold-text-in-bright-amount
By how much to increase the luminance when brightening bold
fonts. This was previously hard-coded to a factor of 1.3, which is now
the default value of the new config option.

Closes #1434
2023-07-28 15:40:07 +02:00
Daniel Eklöf
e912656682
render: revert part of a36f67cbe3
render_osd() shouldn't use term_font_baseline().

This is because term_font_baseline() uses the line height to determine
the position, while render_osd() renders to surfaces that aren't sized
like the grid.

This fixes a regression, where the CSD title were sometimes rendered
too high up, and sometimes too low.
2023-07-28 15:37:48 +02:00
Daniel Eklöf
fd813d0e6c
font baseline: use max(font->height, font->ascent + font->descent) when calculating font height
This is how it's done when calculating the cell height, and we should
do the same thing when calculating the font baseline.
2023-07-28 15:36:48 +02:00
Daniel Eklöf
613c61abb4
scaling: always round the scaling factor when converting to int
* In all calls to wl_subsurface_set_position()
* (wp_viewport_set_destination() already does this)
* Whenever we use the scale to calculate margins (search box,
  scrollback indicator etc)
* Since the scaling factor is stored as a float (and not a double),
  use roundf() instead of round()
2023-07-25 16:48:50 +02:00
Daniel Eklöf
391bc119de
ci (sr.ht): alpine no longer allows pip installing to the system installation 2023-07-25 16:47:40 +02:00
Daniel Eklöf
76e471c4bc
ci: alpine no longer allows pip installing to the system installation 2023-07-25 16:45:29 +02:00
Daniel Eklöf
a36f67cbe3
render: apply new baseline calculation everywhere
* URL jump labels
* Scrollback position indicator
* Line/box drawings characters

Closes #1430
2023-07-25 15:53:29 +02:00
Daniel Eklöf
0a61cfc3be
wayland: update terminals (fonts etc) on xdg_output_handle_done()
Monitor DPI depends on information from both the wl_output and the
xdg_output interfaces.

Before this patch, terminals were only updated after changes to the
wl_output interfaces (thus depending on xdg output changes being
pushed by the compositor before wl_output changes).

That assumption (xdg_output happening before wl_output) isn’t always
true.

This patch fixes the issue by updating the terminals in the
xdg_output’s “done” event.

Closes #1431
2023-07-23 20:12:13 +02:00
Daniel Eklöf
57d8f74554
Merge branch 'invalid-utf8'
Closes #1423
2023-07-23 20:10:57 +02:00
Daniel Eklöf
8223b4b76c
changelog: ignore invalid UTF-8 in input 2023-07-22 11:23:22 +02:00
Daniel Eklöf
b59fd7c388
vt: detect and ignore invalid UTF-8 sequences
This patch detects invalid codepoints in the UTF-8 EDxxxx range, and
the F4xxxxxx range.

Note that we still allow the E0xxxx and F0xxxxxx ranges. These
contains overlong encodings. We allow them, because they still decode
into correct UTF-32.

Closes #1423
2023-07-22 11:21:41 +02:00
Daniel Eklöf
fc973a3bb9
selection: send_clipboard_or_primary(): handle selection text being NULL 2023-07-22 11:21:12 +02:00
Daniel Eklöf
15d7885c78
changelog: add new ‘unreleased’ section 2023-07-21 09:00:57 +02:00
Daniel Eklöf
d68d5faa05
Merge branch 'releases/1.15' 2023-07-21 09:00:39 +02:00
Daniel Eklöf
9e4d82a484
meson: bump version to 1.15.1 2023-07-21 08:57:03 +02:00
Daniel Eklöf
fa97df0eab
changelog: prepare for 1.15.1 2023-07-21 08:56:49 +02:00
Daniel Eklöf
62c6c9a78a
Merge branch 'master' into releases/1.15 2023-07-21 08:55:57 +02:00
Daniel Eklöf
b3255465f1
render: change baseline calculation, to center it within the line
Before this patch, fonts were anchored to the top of the line. With
this patch, it is instead centered.

Closes #1302
2023-07-21 08:17:32 +02:00
Daniel Eklöf
098f0aafd4
Merge branch 'systemd-units-socket-activation' 2023-07-21 08:13:51 +02:00
Max Gautier
478474d0ce
Changelog: standard system target + footclient fallback 2023-07-21 08:13:36 +02:00
Max Gautier
555edd60d4
Update documentation regarding systemd units 2023-07-21 08:12:55 +02:00
Max Gautier
d3ffb0bde1
Ties systemd units to graphical-session.target
- wayland-instance template target was a mistake.
  Systemd does not support simultaneous same user session, so stop
  trying to go against that.
- Only start systemd units in Wayland environments.
2023-07-21 08:12:55 +02:00
Max Gautier
c12db68363
footclient: fallback logic when socket paths don't exist
Even if WAYLAND_DISPLAY / XDG_RUNTIME_DIR are defined, if we can't
find the corresponding socket, we fallback to the path used when they
are not defined.
2023-07-21 08:12:49 +02:00
CismonX
c3b119ea81
vt: improve handling of HTS
Do not insert existing positions into the tab stop list.

This prevents a performance issue when iterating through
an extremely long tab stop list.

Also corrects the behaviour of CBT.
2023-07-20 08:47:40 +08:00
Daniel Eklöf
a49281ced3
render: OSD: don’t mark surface as being opaque, when it’s not 2023-07-19 16:43:18 +02:00
Daniel Eklöf
899b768b74
render: disable transparency when we’re fullscreened
The wayland protocol recommends (or mandates?) that compositors render
a black background behind fullscreened transparent windows. I.e. you
never see what’s _actually_ behind the window.

So, if you have a white, but semi-transparent background in foot,
it’ll be rendered in a shade of gray.

Given this, it’s better to simply disable transparency while we’re
fullscreened. That way, we at least get the "correct" background
color.

Closes #1416
2023-07-19 16:42:41 +02:00
Daniel Eklöf
648f6016e3
changelog: spelling: sacling -> scaling 2023-07-19 16:37:25 +02:00
Daniel Eklöf
fdd753263b
term: destroy: unref key bindings *after* destroying window
This fixes a crash-on-exit on compositors that emit a _"keyboard
leave"_ event when a surface is unmapped.

In our case, destroying the window (where we unmap it) in
term_destroy(), lead to a crash in term_mouse_grabbed(), due to
key_binding_for() returning NULL.

The call chain in this is case is, roughly:

  term_destroy() ->
  wayl_win_destroy() ->
  keyboard_leave() ->
  term_xcursor_update_for_seat() ->
  term_mouse_grabbed()
2023-07-18 18:26:28 +02:00
Daniel Eklöf
27b4c2ac2d
themes: starlight: update to V4
This also updates the default theme in foot, as well as the
documentation.

Closes #1409
2023-07-18 16:20:33 +02:00
Daniel Eklöf
023a1b8da6
changelog: crash on pointer capability loss 2023-07-18 16:12:43 +02:00
xdavidwu
0b8791d1c5
wayland: fix pointer cap lost handling
Before this, on compositor without cursor-shape support, a pointer
capability lost of the seat makes foot crash.
2023-07-18 16:09:31 +02:00
Daniel Eklöf
e6d1e0cc27
Merge branch 'scale-is-floating-point'
Closes #1404
2023-07-18 16:06:37 +02:00
Daniel Eklöf
4a4f2b5dae
pgo: add stub for wayl_fractional_scaling() 2023-07-18 05:48:22 +02:00
Daniel Eklöf
df96b7f4c0
changelog: wrong DPI, and wrong initial font size with fractional scaling 2023-07-18 05:48:21 +02:00
Daniel Eklöf
5b3b89cb64
changelog: monitor metadata is now picked from the one we were last mapped on 2023-07-18 05:48:02 +02:00
Daniel Eklöf
7fca81dd3f
term: get_font_subpixel(): use subpixel from monitor we were *last* mapped on 2023-07-18 05:48:02 +02:00
Daniel Eklöf
21d99f8dce
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.

We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.

Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().

Also change term_font_dpi_changed() to only return true if the font
was changed in any way.

Finally, rewrite update_term_for_output_change() to:

* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
  or the fonts were updated.

This fixes several things:

* A bug where we failed to update the fonts when fractional scaling
  was in use, and we guessed the initial scale/DPI wrong. The bug
  happened because updated the internal "preferred" scale value, and a
  later call to render_resize() updated the terminal’s scale value,
  but since that code path didn’t call term_font_dpi_changed() (and it
  shouldn’t), the fonts weren’t resized properly.

* It ensures we only resize the grid *once* when the scaling factor,
  or DPI is changed. Before this, we’d resize it twice. And this
  happened when e.g. dragging the window between monitors.
2023-07-18 05:48:01 +02:00
Daniel Eklöf
c96863b188
wayland: error out if there aren’t any monitors available 2023-07-18 05:48:01 +02:00
Daniel Eklöf
b2a29280cb
wayland: use physical DPI on fractional-scale capable compositors
With legacy scaling, we need to use a "scaled", or "logical" DPI
value, that is basically the real DPI value scaled by the monitor’s
scaling factor.

This is necessary to compensate for the compositor downscaling the
surface, for "fake" fractional scaling.

But with true fractional scaling, *we* scale the surface to the final
size. This means we should *not* use the scaled DPI, but the monitor’s
actual DPI.

To facilitate this, store both the scaled and the unscaled DPI value
in the monitor struct.

This patch also changes how we pick the DPI value. Before, we would
use the highest DPI value from all the monitors we were mapped
on. Now, we use the DPI value from the monitor we were *last* mapped
on (typically the window we’re dragging the window *to*).
2023-07-18 05:48:01 +02:00
Daniel Eklöf
59f0a721c4
wayland: fractional_scale_preferred_scale(): only push update if scale has changed
Also, drop wl_window::have_preferred_scale. Check for scale > 0 instead.
2023-07-18 05:48:01 +02:00
Daniel Eklöf
829353a5da
term: font_dpi_changed: scale (and old_scale) are floating point
This should fix an issue where the font size wasn’t updated when
moving the window between outputs whose scaling factors match when
truncated.
2023-07-18 05:48:01 +02:00
Daniel Eklöf
2fd29cbf50
term: (debug): dpi_aware is no longer an enum 2023-07-18 05:48:01 +02:00
Ayush Agarwal
da81b63ec0
themes: add chiba-dark theme 2023-07-18 05:46:00 +02:00
Daniel Eklöf
6de69aa9b7
render: fix xcursor scaling with fractional-scale-v1
This worked just after the fractional-scaling branch was merged, but
was then broken by the cursor-shape branch, due to a bad rebase of
that branch.
2023-07-18 05:44:17 +02:00
CismonX
8b4cb2457a
input: do not ignore touch events on the CSDs 2023-07-16 20:41:33 +08:00
Ronan Pigott
b7100d5716 render: use rounding for fractional scale
If we truncate the buffer dimensions we may accidentally submit a
buffer with inappropriate size.
2023-07-15 20:03:16 -07:00
Daniel Eklöf
d1df98e0ca
changelog: add new ‘unreleased’ section 2023-07-14 12:40:55 +02:00
Daniel Eklöf
bee4a96b45
Merge branch 'releases/1.15' 2023-07-14 12:40:35 +02:00
Daniel Eklöf
5a3706ac46
meson: bump version to 1.15.0 2023-07-14 12:26:03 +02:00
Daniel Eklöf
53b0eb8e1b
changelog: prepare for 1.15.0 2023-07-14 12:25:16 +02:00
Daniel Eklöf
3f7be59062
config: add csd.double-click-to-maximize=no|yes option
When enabled, double-clicking the CSD titlebar will (un)maximize the
window.

Defaults to ‘yes’ (since this is the old hard-coded behavior).

Closes #1293
2023-07-14 12:03:35 +02:00
Daniel Eklöf
3cd0e2adb0
themes: enable custom cursor colors in all themes that define such colors
Not all themes have/define custom cursor colors. But of those that do,
nearly all already enabled them (by setting "cursor.color"), except
three themes:

* aeroroot
* ayu-mirage
* material-amber

This patch makes all themes consistent, by enabling cursor.color in
these last three themes too.
2023-07-14 10:20:20 +02:00
Daniel Eklöf
efc619b0af
config: make ‘starlight’ the default color theme
Closes #1321
2023-07-14 10:11:43 +02:00
Daniel Eklöf
f53e7f7478
themes: aeroroot: add -*- conf -*- header 2023-07-14 10:03:56 +02:00
Daniel Eklöf
235e0e9e60
themes: starlight: add -*- conf -*- header 2023-07-14 10:03:22 +02:00
Daniel Eklöf
28ab41caad
theme: add new theme ‘starlight’
Closes #1321
2023-07-14 10:02:21 +02:00
Daniel Eklöf
b3745b31c7
render: don’t invert cursor colors when custom colors are being used
When the user has configured custom cursor colors (cursor.color is set
in foot.ini), don’t invert those colors when the cell is either
selected, or has the ‘reverse’ attribute set.

This aligns foot’s behavior with Alacritty, Kitty and Wezterm. Contour
also behaves similarly, except mouse selections override the cursor
colors (turning the cursor invisible).

Closes #1347
2023-07-14 09:57:10 +02:00
Daniel Eklöf
66df6fb2f6
themes: ayu-mirag: disable cursor colors by default
Default cursor color is "inversed fg/bg", and themes aren’t supposed
to change that.
2023-07-14 09:55:57 +02:00
ShugarSkull
98dfeb05ab
ayu-mirage theme added 2023-07-14 09:55:54 +02:00
Daniel Eklöf
50f47dcba9
themes: aeroroot: disable cursor colors by default
Default cursor color is "inversed fg/bg", and themes aren’t supposed
to change that.
2023-07-14 09:11:30 +02:00
Kyle Gunger
efc89a7317
Aero root theme 2023-07-14 09:11:25 +02:00
CismonX
dbee099eeb
sixel: fix regression for DECGRI with a repeat count of 0 2023-07-11 00:51:32 +08:00
Daniel Eklöf
7fa4a36c08
Merge branch 'control-shift-u'
Closes #1183
2023-07-10 12:44:33 +02:00
Daniel Eklöf
3609017c38
changelog: mention the new default key binding for show-urls-launch under “fixed”
It “fixes” the key binding conflict seen on e.g. GNOME, and increases
the exposure of the change to, hopefully, more users.
2023-07-10 12:42:36 +02:00
Daniel Eklöf
58898c0633
changelog: split up key binding changes for show-urls-launch and unicode-input 2023-07-10 12:42:10 +02:00
Daniel Eklöf
0e1dbbbd06
doc: foot: add default key binding for unicode input 2023-07-10 12:37:10 +02:00
Daniel Eklöf
5b74808ed0
doc: foot: update default key binding for URL mode 2023-07-10 12:36:55 +02:00
Daniel Eklöf
87d45c2a01
readme: add default shortcut for unicode input 2023-07-10 12:36:41 +02:00
Daniel Eklöf
19e37b17aa
readme: a few more places mentioning the default URL mode shortcut 2023-07-10 12:36:18 +02:00
Antoine Beaupré
080a11eb73
bind control-shift-u to unicode-input, move urls to o
Having a keybinding to invoke arbitrary unicode characters is very
useful. It's often used as a method of last resort to communicate with
people outside of your main language. For example, if you want to type
the last letter of my real name, you can invoke the latin-1 character
0xe9 or unicode 0x00e9.

You can also use this to type special characters, for example, unicode
U+1F4A9 is of course, the infamous PILE OF POO, which is sure to
produce million laughs everywhere you go.

In foot, there's no keybinding by default to invoke the very useful
unicode-input command. There is no "standard" (as in "ISO") keybinding
this either. But there *is* a de-facto standard currently deployed
by *both* GTK and Qt (a rare feat) *and* Chrome OS (an even rarer
feat) and it's control-shift-u.

Alternatives include Control-x 8 (emacs), Control V u (vim),
Alt (Windows, LibreOffice), or Option (Mac). I doubt we want to adopt
any of those.

So let's use control-shift-u for this. Unfortunately, it's currently
assigned to show-urls-launch, which is unfortunate, but
insurmountable. We can reassign this keybinding elsewhere. I have
picked control-shift-o in my configuration, because "o" is a good
mnemonic for "open URLs". Others have suggested "m" instead.

Closes: #1183
2023-07-10 12:31:51 +02:00
CismonX
d2fcb5343f
input: add basic support for touchscreen input
Closes #517
2023-07-05 16:22:28 +02:00
Daniel Eklöf
4a73828911
changelog: fractional-scaling-v1 -> fractional-scale-v1 2023-07-04 08:38:07 +02:00
Craig Barnes
247035e9e4 meson: fix typo in meson_options.txt
When using muon[1] instead of meson, this was causing the following
error:

    $ muon setup bld
    .../meson_options.txt:26:124: error unterminated hex escape
    .../meson_options.txt:26:223: error unterminated string

[1]: https://muon.build/
2023-07-03 20:11:20 +01:00
Daniel Eklöf
e00a20465b
Merge branch 'cursor-shape'
Closes #1379
2023-07-03 15:01:49 +02:00
Daniel Eklöf
3800b279d6
meson: move cursor-shape.{c,h} from ‘foot’ binary to vtlib
This should fix a build issue when doing partial PGO builds, when
cursor-shape-v1 is *available*.
2023-07-03 14:42:22 +02:00
Daniel Eklöf
ba09d55aab
term_xcursor_update_for_seat(): fix missing evaluation of render_xcursor_is_valid()
When compiling *without* cursor-shape-v1 support,
term_xcursor_update_for_seat() would incorrectly set
shape=CURSOR_SHAPE_CUSTOM, even though no custom cursor had been set
by the user.

This resulted in a crash in render_xcursor_set(), when trying to use a
NULL-string as custom cursor.
2023-07-03 14:36:33 +02:00
Daniel Eklöf
8fc43ccd2d
meson: log availability of cursor-shape-v1 2023-07-03 14:36:33 +02:00
Daniel Eklöf
a361d7917b
main/client: add a version feature flag for cursor-shape 2023-07-03 14:36:33 +02:00
Daniel Eklöf
6388954e8f
render: move variables inside #ifdef, as they’re not used outside of it 2023-07-03 14:36:33 +02:00
Daniel Eklöf
7bfa700c55
terminal: #elif -> #else 2023-07-03 14:36:33 +02:00
Daniel Eklöf
c2e481fb6a
meson: bump wayland-protocols version required for cursor-shape to 1.32 2023-07-03 14:36:32 +02:00
Daniel Eklöf
c2baaff3c1
cursor-shape: use server-side cursors for custom (OSC-22), if possible
Using a lookup table, try to map the user-provided xcursor string to a
cursor-shape-v1 known shape.

If we succeed, set the user’s custom cursor using server side
cursors (i.e. using cursor-shape-v1).

If not, fallback to trying to load the image ourselves (using
wl_cursor_theme_get_cursor()), and set it using the legacy
wl_pointer_set_cursor().
2023-07-03 14:36:32 +02:00
Daniel Eklöf
bf83a0b2bd
meson: cursor-shape: use .xml from wayland-protocols
This patch assumes a git snapshot of wayland-protocols are
installed.

We need to bump the version number as soon as the next version of
wayland-protocols have been released.
2023-07-03 14:36:32 +02:00
Daniel Eklöf
ddd6004b27
render: don’t (can’t) use cursor-shape-v1 when user has set a custom cursor
Well, we _could_, but we’d have to reverse map the string to a
cursor-shape-v1 enum value. Let’s not do that, for now at least.
2023-07-03 14:36:32 +02:00
Daniel Eklöf
9155948ac8
cursor-shape: assert lookup succeeded 2023-07-03 14:36:32 +02:00
Daniel Eklöf
803b250652
pgo: update xcursor stubs to use enum instead of char pointer 2023-07-03 14:36:32 +02:00
Daniel Eklöf
6ed5dce5ab
render: debug log which method we use to set the xcursor 2023-07-03 14:36:32 +02:00
Daniel Eklöf
c8e13ad393
cursor-shape: add support for server side cursor shapes
This implements support for the new cursor-shape-v1 protocol. When
available, we use it, instead of client-side cursor surfaces, to
select the xcursor shape.

Note that we still need to keep client side pointers, for:

* backward compatibility
* to be able to "hide" the cursor

Closes #1379
2023-07-03 14:36:32 +02:00
Daniel Eklöf
ee794a121e
refactor: track current xcursor using an enum, instead of a char pointer 2023-07-03 14:36:32 +02:00
Daniel Eklöf
72bc0acfbd
wayland: handle enum value XDG_TOPLEVEL_STATE_SUSPENDED
Added in wayland-protocols-1.32
2023-07-03 14:36:03 +02:00
Daniel Eklöf
49fb0cf359
sixel: re-scale images when the cell dimensions change
Before this patch, when the cell dimensions changed (i.e. when the
font size changes), sixel images were either removed (the new cell
dimensions are smaller than the old), or simply kept at their original
size (new cell dimensions are larger).

With this patch, sixels are instead resized. This means a
sixel *always* occupies the same number of rows and columns,
regardless of how much the font size is changed.

This is done by maintaining two sets of image data and pixman images,
as well as their dimensions. These two sets are the new ‘original’ and
‘scaled’ members of the sixel struct.

The "top-level" pixman image pointer, and the ‘width’ and ‘height’
members either point to the "original", or the "scaled" version.

They are invalidated as soon as the cell dimensions change. They, and
the ‘scaled’ image is updated on-demand (when we need to render a
sixel).

Note that the ‘scaled’ image is always NULL when the current cell
dimensions matches the ones used when emitting the sixel (to save
run-time memory).

Closes #1383
2023-06-30 08:29:35 +02:00
Daniel Eklöf
7a37e6891f
meson: log availability of optional wayland protocols 2023-06-30 08:28:20 +02:00
Daniel Eklöf
5e305fa854
wayland: typo: ‘.’ -> ‘;’
Closes #1392
2023-06-30 08:24:02 +02:00
Daniel Eklöf
df71d9c6de
Merge branch 'sixel-performance'
Closes #1385
2023-06-29 15:40:21 +02:00
Daniel Eklöf
5e9d68695c
sixel: add_ar_11(): manually unroll loop
This generates both smaller, and faster code
2023-06-29 15:40:00 +02:00
Daniel Eklöf
fc46087ce9
scripts: generate-alt-random: set P2=1 when emitting sixels
P2=1 means "empty sixels remain at their current color". This is
usually the case with modern sixel encoders.
2023-06-29 15:40:00 +02:00
Daniel Eklöf
75f9bed6b6
sixel: refactor: shorten very verbose switch case statements 2023-06-29 15:40:00 +02:00
Daniel Eklöf
3555e81fee
sixel: special case parsing of images with an aspect ratio of 1:1
Images with an aspect ratio of 1:1 are by far the most common (though
not the default).

It makes a lot of sense, performance wise, to special case
them.

Specifically, the sixel_add() function benefits greatly from this, as
it is the inner most, most heavily executed function when parsing a
sixel image.

sixel_add_many() also benefits, since allows us to drop a
multiplication. Since sixel_add_many() always called first (no other
call sites call sixel_add() directly), this has a noticeable effect on
performance.

Another thing that helps (though not as much), and not specifically
with AR 1:1 images, is special casing DECGRI a bit.

Up until now, it simply updated the current sixel parameter value. The
problem is that the default parameter value is 0. But, a value of 0
should be treated as 1. By adding a special ‘repeat_count’ variable to
the sixel struct, we can initialize it to ‘1’ when we see DECGRI, and
then simply overwrite it as the parameter value gets updated. This
allows us to drop an if..else when emitting the sixel.
2023-06-29 15:40:00 +02:00
Daniel Eklöf
a44a0b4ebe
Merge branch 'fractional-scaling' 2023-06-29 15:38:42 +02:00
Daniel Eklöf
ce31cc518a
wayland: surface_scale(): reset buffer scale when using fractional scaling
Since the first frame uses legacy scaling, the surface may have a
buffer scale > 1, which isn’t allowed.
2023-06-29 15:38:24 +02:00
Daniel Eklöf
c61247f317
wayland: surface_scale(): improve debug logging 2023-06-29 15:38:24 +02:00
Daniel Eklöf
8f74b1090a
wayland: use legacy scaling until fractional_scale::preferred_scale() has been called
This way, the initial frame is more likely to get scaled correctly;
foot will guess the initial (integer) scale from the available
monitors, and use that. By using legacy scaling, we force the
compositor to down-scale the image to the correct scale factor.

If we use the new fraction scaling method with an integer scaling
factor, the initial frame gets rendered way too big.
2023-06-29 15:38:24 +02:00
Daniel Eklöf
d71e588800
wayland: refactor: surface_scale(): pass wl_window pointer, instead of wayland global 2023-06-29 15:38:24 +02:00
Daniel Eklöf
c309c9f572
wayland: surface_scale(): assert surface width/height is a multiple of scale
When doing legacy scaling (non-fractional scaling), assert the
surface’s width and height are multiples of the (integer) scale.
2023-06-29 15:38:24 +02:00
Daniel Eklöf
8a4efb3427
wayland: warn when fractional scaling isn’t available 2023-06-29 15:38:24 +02:00
Daniel Eklöf
9db92bd942
feature: add a feature flag (for --version) for fractional scaling 2023-06-29 15:38:24 +02:00
Daniel Eklöf
27a92b1158
changelog: dpi-aware’s default value is now ‘no’ 2023-06-29 15:38:23 +02:00
Daniel Eklöf
64b6b5d2a7
config: dpi-aware: remove ‘auto’ value, and default to ‘no’
We now default to scaling fonts using the scaling factor, not monitor
DPI.

The ‘auto’ value for dpi-aware has been removed.

Documentation (man pages and README) have been updated to reflect the
new default.
2023-06-29 15:38:23 +02:00
Daniel Eklöf
32b8c5c9b6
changelog: mention the newly added support for fractional-scaling-v1 2023-06-29 15:38:23 +02:00
Daniel Eklöf
0bdb6580bd
wayland: update terminal when preferred scaling factor changes
When the window’s preferred scaling factor is changed (through the
fractional-scaling protocol), update the terminal; resize font, resize
sub-surfaces etc.
2023-06-29 15:38:23 +02:00
Daniel Eklöf
8ccabb7974
wayland: surface_scale(): implement fractional scaling
This is done by setting the surface’s viewport destination
2023-06-29 15:38:23 +02:00
Daniel Eklöf
36818459e5
wayland: initialize window scale to -1 2023-06-29 15:38:23 +02:00
Daniel Eklöf
e5989d81b9
wayland: instantiate+destroy viewport for pointer surface 2023-06-29 15:38:23 +02:00
Daniel Eklöf
5a60bbc119
wayland: refactor: add a buffer argument to wayl_*_scale() functions
This will be needed later, when using fractional scaling + viewporter
to scale.
2023-06-29 15:38:23 +02:00
Daniel Eklöf
434fd6aa1f
wayland: refactor: wayl_surface_scale(): pass wayl_surface pointer
Instead of passing a raw wl_surface pointer, pass a wayl_surface
pointer.

This is needed later, when using fractional scaling to scale the
surface (since then we need the surface’s viewport object).
2023-06-29 15:38:23 +02:00
Daniel Eklöf
ba46a039ac
wayland: refactor: wrap wl_surface pointers in a wayl_surface struct
And add a viewport object to accompany the surface (to be used when
scaling the surface).

Also rename the wl_surf_subsurf struct to wayl_sub_surface, and add a
wayl_surface object to it, rather than a plain wl_surface pointer (to
also get the viewport pointer).
2023-06-29 15:38:23 +02:00
Daniel Eklöf
c5d533ec71
wayland: add viewport object to sub-surface struct 2023-06-29 15:38:23 +02:00
Daniel Eklöf
0a5073f570
wayland: add wayl_surface_scale(), and wayl_win_scale()
These functions scale a surface+buffer.

For now, only using the legacy scaling
method (wl_surface_set_buffer_scale()).
2023-06-29 15:38:23 +02:00
Daniel Eklöf
4bd62b1005
render: maybe_resize(): convert local variable ‘scale’ to float 2023-06-29 15:38:23 +02:00
Daniel Eklöf
913ae94cf9
wayland: add wayl_fractional_scaling()
Returns true if fractional scaling is available.
2023-06-29 15:38:22 +02:00
Daniel Eklöf
29a14632d3
wayland: csd_reload_font(): ‘scale’ is now a float 2023-06-29 15:38:22 +02:00
Daniel Eklöf
424d045084
term: reload_fonts(): ‘scale’ is not a float 2023-06-29 15:38:22 +02:00
Daniel Eklöf
2bb7b28837
render: xcursor_update(): convert local ‘scale’ variable to float 2023-06-29 15:38:22 +02:00
Daniel Eklöf
d8f64d1047
render: urls(): round scaling factor 2023-06-29 15:38:22 +02:00
Daniel Eklöf
30c8d3e652
render: search_box(): round scaling factor 2023-06-29 15:38:22 +02:00
Daniel Eklöf
cf280e6655
render: render_timer(): round scaling factor 2023-06-29 15:38:22 +02:00
Daniel Eklöf
b656124791
render: csd_border: round scaled border width, instead of truncating 2023-06-29 15:38:22 +02:00
Daniel Eklöf
44743b5635
render: draw_unfocused_block(): round scale, instead of truncating 2023-06-29 15:38:22 +02:00
Daniel Eklöf
6e2a47287a
wayland: pointer.scale: convert to float 2023-06-29 15:38:22 +02:00
Daniel Eklöf
c1f374cc8d
term: convert ‘scale’ to a float 2023-06-29 15:38:22 +02:00
Daniel Eklöf
a9ecf1449e
wayland: plumbing for wp-fractional-scale
* Bind the wp-viewporter and wp-fractional-scale-manager globals.
* Create a viewport and fractional-scale when instantiating a window.
* Add fractional-scale listener (that does nothing at the moment).
* Destroy everything on teardown.
2023-06-29 15:38:22 +02:00
Daniel Eklöf
1e6204e1ac
meson: generate bindings for wp-fractional-scale + wp-viewport 2023-06-29 15:38:17 +02:00
Vladimir Bauer
1dddb63d9f correct csd section entry: hide-when-maximized 2023-06-27 17:00:31 +05:00
Daniel Eklöf
d63a00a649
config: unittest: explicitly call fcft_init() + fcft_fini()
This plugs a memory leak, caused by fontconfig functions being called
as part of the unit test implicitly allocating global objects.
2023-06-26 20:15:36 +02:00
Daniel Eklöf
2388015b10
sixel: assert upper pixel of last sixel maps to last image row, *or lower* 2023-06-24 07:31:08 +02:00
Daniel Eklöf
c15e75357a
sixel: ensure enough rows have been scrolled in, to fit the image
When emitting a sixel, we need to:

a) scroll terminal content to ensure the new image fits
b) position the text cursor

Recent changes in the cursor positioning logic meant we reduced the
number of linefeeds, to ensure 1) sixels could be printed to the
bottom row without scrolling the terminal contents, and 2) the cursor
was positioned on the last sixel row.

Except, we’re not actually positioning the cursor on the last sixel
row. We’re positioning it on the text row that maps to the *upper*
pixel of the last sixel.

In most cases, this _is_ the last row of the sixel. But for certain
combinations of font and image sizes, it may be higher up.

This patch fixes a regression, where the terminal contents weren’t
scrolled up enough for certain images, causing a crash when trying to
dirty a not-yet scrolled in row.

The fix is this:

* Always scroll by the number of rows occupied by the image, minus
  one. This ensures the image "fits".
* Adjust the cursor position, if necessary.
2023-06-24 07:31:08 +02:00
Daniel Eklöf
425cf894d4
sixel: resize(): handle no size change 2023-06-24 07:31:08 +02:00
Daniel Eklöf
5d576fccba
sixel: regression: linefeed count for chunked up sixel image
All image chunks but the last *should* scroll the screen content.
2023-06-24 07:31:08 +02:00
Daniel Eklöf
1eb90b2405
sixel: minor fixes after implementing support for non-1:1 aspect ratios
* Lazy initialize image height. This is necessary to prevent garbage
  from being rendered for "empty" sixels.
* Fix plotting of non-1:1 pixels
* Fix calculation of height in resize(), for non-1:1 aspect ratios
2023-06-24 07:31:08 +02:00
Daniel Eklöf
774570ec41
sixel: stop cropping images to the last non-transparent row 2023-06-24 07:31:08 +02:00
Daniel Eklöf
d6d143e2a6
sixel: respect sixel aspect ratio
That is, parse P1 when initializing a new sixel, and don’t ignore
pad/pad in the raster attributes command.

The default aspect ratio is 2:1, but most sixels will override it in
the raster attributes command (to 1:1).
2023-06-24 07:31:08 +02:00
Daniel Eklöf
66d9b8da60
sixel: fix cursor positioning logic
This adjusts the logic that positions the text cursor after emitting a
sixel, when sixel scrolling mode is *enabled*.

We’ve always mimicked XTerm’s behavior. However, XTerm recently
changed its behavior, to better match that of an VT382.

Now, the cursor is placed *on* the last row of the sixel, instead of
on a new row after the sixel.

This allows applications to print sixels to the bottom row of the
terminal, without causing the content to scroll.

Finally, there was a bug in the horizontal positioning of the cursor;
it was placed on the *first* column of the row, instead of on the
first column of the sixel.
2023-06-24 07:31:02 +02:00
Daniel Eklöf
8a3620bafa
term: scroll: only record scroll damage when viewport is at the bottom
We don’t need to record scroll damage if the viewport isn’t at the
bottom, since in this case, the renderer ignores the scroll damage
anyway.

This fixes a performance corner case, when the viewport is at the top
of the scrollback history.

When application scrolls the terminal contents, and the scrollback
history is full, and the viewport is at top of the history, then the
viewport needs to be moved (the scrollback history is a circular
buffer, and thus the top of the history “moves” when we’re scrolling
in new contents).

Moving the viewport typically results in another type of scroll
damage (DAMAGE_SCROLL_IN_VIEW, instead of the “normal” DAMAGE_SCROLL).

Thus, each application triggered scroll, will result in two scroll
damage records: one DAMAGE_SCROLL, and one
DAMAGE_SCROLL_IN_VIEW. These two are incompatible, meaning they can’t
be merged. What’s worse, it also means the DAMAGE_SCROLL records from
two application triggered scrolls cannot be merged (since there’s a
DAMAGE_SCROLL_IN_VIEW in between).

As a result, the renderer will not see one, or “a few” scroll damage
events, but a *ton*. _Each_ one typically a single line, or so. And
each one resulting in lots of traffic on the wayland socket, as we
create and destroy new buffer pools, when doing “shm scrolling”.

This eventually leads to the socket not being able to keep up, and the
socket is closed on us, forcing us to exit.

The fix is really simple: don’t record “normal” scroll damage when
scrolling, _unless_ the viewport is at the bottom (and thus “follows”
the application output).

As soon as the user scrolls up in the history, we’ll stop emitting
normal scroll damage records. This is just fine, since, as mentioned
above, the renderer ignores them when the viewport isn’t at the
bottom.

What if the viewport is moved back down again, before the next frame
has been rendered? Wont there be “missing” scroll damage records? No,
because moving the viewport results in scroll damage records by
itself.

Closes #1380
2023-06-23 20:38:03 +02:00
wout
70ffc2632f Fixed a type for the pixel fontsize change
xp -> px
2023-06-23 18:10:19 +00:00
Daniel Eklöf
3a59cbbaa3
render: resize: fix crash when reflowing the alt screen
When doing an interactive resize, and `resize-delay-ms` > 0 (the
default), we would crash if the original screen size (i.e. the size
before the interactive resize started) was larger than the last window
size.

For example, if we interactively go from 85 rows to 75, and then
non-interactively went from 75 to 80, we’d crash.

The resizes had to be made in a single go. One way to trigger this was
to start an interactive resize on a floating window, and then *while
resizing* toggle the window’s floating mode.

Closes #1377
2023-06-20 15:59:16 +02:00
Antoine Beaupré
67b3663f39
add srcery theme
Based on https://srcery.sh/
2023-06-20 14:57:23 +02:00
Daniel Eklöf
2c0c4ce821
csi: CHA+HPA (cursor horizontal absolute): use term_cursor_col() 2023-06-20 14:55:32 +02:00
Daniel Eklöf
24f12c7b5e
term: add term_cursor_col()
Set cursor column, absolute.

term_cursor_to() needs to reload the current row pointer, and is thus
not very effective when we only need to modify the column.
2023-06-20 14:55:32 +02:00
Daniel Eklöf
d88bea5e22
vt: split up action_param() to three separate functions
We’re already switching on the next VT input byte in the state
machine; no need to if...else if in action_param() too.

That is, split up action_param() into three:

* action_param_new()
* action_param_new_subparam()
* action_param()

This makes the code cleaner, and hopefully slightly faster.

Next, to improve performance further, only check for (sub)parameter
overflow in action_param_new() and action_param_subparam().

Add pointers to the VT struct that points to the currently active
parameter and sub-parameter.

When the number of parameters (or sub-parameters) overflow, warn, and
then point the parameter pointer to a "dummy" value in the VT struct.

This way, we don’t have to check anything in action_param().
2023-06-16 16:26:13 +02:00
Dan Bungert
690d78edfa
test: config: add test for url.protocols option 2023-06-15 14:40:47 +02:00
Daniel Eklöf
b91bde8a65
terminfo: add TS capability
This is a new ‘extended’ capability. ‘TS’ has been around for a while,
but was originally not part of foot’s terminfo. Not sure when it was
added to ncurses’ foot terminfo.

In any case, ncurses has this to say about TS:

  These building-blocks allow access to the X titlebar and icon name as a
  status line.  There are a few problems in using them in entries:

   a) tsl should have a parameter to denote the column on which to transfer to
      the status line.

   ...

  But that issue regarding the parameter for tsl means that applications may
  not rely on it.  The SVr4 documentation says tsl will "move to status line,
  column #1".  At the point in time when ESR added DJM's "pseudo-color" entry
  with the split-up escape sequence for tsl/fsl, there were 65 entries using
  tsl:
     32 used a parameter, matching the documentation (including x10term).
     21 used a parameterless control, exiting from the status line on ^M.
      6 used parameterless controls for tsl and fsl
      6 used a split-up escape sequence, e.g., the same approach.

  The extension "TS" is preferable, because it does not accept a parameter.
  However, if you are using a non-extended terminfo, "TS" is not visible.
2023-06-15 14:31:23 +02:00
Daniel Eklöf
93b6883896
terminfo: XM: add private mode 1004
This was added to ncurses (to the xterm+sm+1006 fragment) in
2023-05-08.
2023-06-15 14:31:15 +02:00
sewn
16872ecc41
meson: use meson feed feature for scdoc input
Removes the need for a shell dependency.
2023-06-14 17:15:20 +00:00
Phillip Susi
8859e134ef
Fix non UTF-8 locale complaint
If the locale isn't UTF-8, foot tries to fall back to C.UTF-8 and
prints a warning.  The warning was garbled because the name of the
original locale is no longer valid after calling setlocale() a
second time.  Use strdup to stash the original string.

Closes #1362
2023-06-01 12:12:49 +02:00
Daniel Eklöf
1433a81c08
sixel: apply background alpha when P2=0 or P2=2, and current bg color is the default bg color
Closes #1360
2023-05-31 16:27:48 +02:00
Daniel Eklöf
b4e418f251
ci: try alpine edge instead of latest 2023-05-26 10:20:05 +02:00
Daniel Eklöf
c51050a9bc
osc: update font subpixel mode, and window opaque compositor hint, on alpha changes
When background alpha is changed at runtime (using OSC-11), we (may)
have to update the opaque hint we send to the compositor.

We must also update the subpixel mode used when rendering font
glyphs.

Why?

When the window is fully opaque, we use wl_surface_set_opaque_region()
on the entire surface, to hint to the compositor that it doesn’t have
to blend the window content with whatever is behind the
window. Obviously, if alpha is changed from opaque, to transparent (or
semi-transparent), that hint must be removed.

Sub-pixel mode is harder to explain, but in short, we can’t do
subpixel hinting with a (semi-)transparent background. Thus, similar
to the opaque hint, subpixel antialiasing must be enabled/disabled
when background alpha is changed.
2023-05-26 10:01:32 +02:00
jdevdevdev
134b54dfe0
.desktop: remove StartupWMClass from server, use distinct StartupWMClass for foot and footclient
For this to work, the default app-id of footclient has been changed
from ‘foot’ to ‘footclient’.

By using distinct StartupWMClasses, the compositor can connect a
running foot/footclient instance to the correct .desktop-file. This
ensures the correct icon is being used in e.g. docks, and that actions
like “open another window” works correctly.

Note that the user can override the app-id, either by setting app-id
in foot.ini, or with the -a,--app-id command line option.

Closes #1355
2023-05-22 18:57:54 +02:00
Daniel Eklöf
f4b8e4f4d6
test: config: add test for main.utmp-helper option 2023-05-22 18:47:38 +02:00
Daniel Eklöf
e78319fccd
utmp: rewrite utmp logging
This patch generalizes the utmp support, to not only support
libutempter, but also ulog (and in the future, even more interfaces).

* Rename config option main.utempter to main.utmp-helper
* Add meson option -Dutmp-backend=none|libutempter|ulog|auto
* Rename meson option -Ddefault-utempter-path to -Dutmp-default-helper-path
* utmp is no longer detected at compile time, but at runtime instead.

Meson will configure the following pre-processor macros, based on the
selected utmp backend:

* UTMP_ADD - argument to pass to utmp helper when adding a record (starting foot)
* UTMP_DEL - argument to pass to utmp helper when removing a record (exiting foot)
* UTMP_DEL_HAVE_ARGUMENT - if defined, UTMP_DEL expects an extra argument ($WAYLAND_DISPLAY)
* UTMP_DEFAULT_HELPER_PATH - path to the default utmp helper binary

The documentation has been updated to mention which arguments are
passed to the helper binary.

Closes #1314
2023-05-22 18:47:25 +02:00
Daniel Eklöf
a2f765b72a
slave: unset TERM_PROGRAM{,_VERSION}
Foot’s policy is to not set environment variables that identifies
it (except the well-known and established `TERM` variable).

We encourage applications to use terminfo to determine capabilities,
or terminal queries, when available. Or, at least use terminal queries
to detect the terminal and its version.

Setting environment variables is a bad idea since they are inherited
by all applications started by the terminal (which is the whole
point). But, this includes other terminal emulators, making it very
possible a terminal emulator gets mis-detected just because it was
started from another terminal.

Since there are a couple of terminal emulators that _do_ set
TERM_PROGRAM and TERM_PROGRAM_VERSION, unset these environment
variables to avoid being misdetected.

Closes #1349
2023-05-18 17:50:48 +02:00
locture
d2f81443f1
customized gnome-like csd buttons 2023-05-18 17:49:44 +02:00
Daniel Eklöf
738deb2368
search: regression: refresh current view when canceling a scrollback search
3b41379be4 introduced a regression,
where canceling a scrollback search didn’t refresh the viewport
correctly; the viewport was changed, but the screen content was not
refreshed.

This worked before, because the workaround for
https://github.com/swaywm/sway/issues/6960 always called
term_damage_view() when exiting scrollback search mode.

3b41379be4 removed that call since it’s
no longer required. *Except* when executing the
BIND_ACTION_SEARCH_CANCEL binding, since then the viewport may be
moved.

Note that this regression affected *all* compositors, not just Sway.

Closes #1354
2023-05-15 20:34:58 +02:00
Daniel Eklöf
3b41379be4
quirks: sway does not damage surface beneath sub-surface, when unmapped
When unmapping a sub-surface, Sway <= 1.8 does not damage the surface
beneath the sub-surface.

https://github.com/swaywm/sway/issues/6960

The workaround is to manually damage the main surface. Previously,
this was done when exiting scrollback search, and after the ‘flash’
OSC. But other sub-surfaces, that may also be unmapped, did not.

This patch adds a quirk handler that does this, and calls it when:

* Exiting scrollback search
* Ending the ‘flash’ OSC
* Exiting unicode input mode
* Clearing URL labels
* Removing the scrollback position indicator

Closes #1335
2023-05-12 14:51:05 +02:00
Daniel Eklöf
7eea69df89
term: reset: switch modifyOtherKeys back to level 1 2023-05-12 09:42:35 +02:00
Daniel Eklöf
c13495e26e
kitty: F3 is no longer allowed to emit CSI R
The original kitty keyboard specification allowed F3 to emit either
CSI R, or CSI 13~.

Support for CSI R was removed in later revisions of the protocol,
since it collides with "Cursor Position Report" sequences.
2023-04-26 18:34:03 +02:00
Daniel Eklöf
dc7642f2a5
csi: implement "CSI ? m" 2023-04-26 18:30:09 +02:00
Daniel Eklöf
a2db3cdd5b
render: regression: keep empty bottom scroll margin empty after resize 2023-04-12 18:09:41 +02:00
Daniel Eklöf
e2baa65238
render: ensure scroll region’s endpoint is valid after a window resize
If we had a non-empty bottom scroll region, and the window was resized
to a smaller size, the scroll region was not reset correctly.

This led to a crash when scrolling the screen content.

Fix by making sure the scroll region’s endpoint is within range.
2023-04-12 17:57:53 +02:00
Vivian Szczepanski
98528da5e5
meson: bump tllist dependency version to 1.1.0
The tll_sort() macro was added to tllist in version 1.1.0, and so
building with a previous version causes a linking error.
2023-04-09 09:05:50 +02:00
Daniel Eklöf
479b3c8ee1
*.desktop: add StartupWMClass=foot
At least Gnome needs this in order to link running instances of foot
to their corresponding .desktop file, used e.g. when determining which
icon to display for running applications.

Closes #1317
2023-04-05 14:42:34 +02:00
Daniel Eklöf
a858934c04
changelog: add a new ‘unreleased’ section 2023-04-03 18:57:50 +02:00
Daniel Eklöf
446288d9a1
Merge branch 'releases/1.14' 2023-04-03 18:57:32 +02:00
Daniel Eklöf
ae6bbce6c2
meson: bump version to 1.14.0 2023-04-03 18:52:42 +02:00
Daniel Eklöf
862a003b5b
changelog: prepare for 1.14.0 2023-04-03 18:52:22 +02:00
Daniel Eklöf
ae81f5af4c
terminfo: remove DECRST of DECCOLM+DECSCLM
We’ve never supported neither 132-column mode, nor smooth
scrolling. But we _did_ recognize the escape sequences.

We don’t, anymore. Thus it makes very little sense to include these
escapes in any of our terminfo capabilities. So, remove them.
2023-04-02 18:00:38 +02:00
Daniel Eklöf
f114068a46
csi: DECCOLM+DECSCLM: remove all support
We don’t support neither 132 column mode, nor smooth scrolling. Thus
it makes little sense to recognize these control condes.

Note that while XTerm does support 132 columns, it is disabled by
default. In this mode, XTerm also doesn’t trigger the
side-effects (i.e. clearing the screen).

Closes #1265
2023-03-31 13:16:43 +02:00
Daniel Eklöf
0bc934070c
ci (woodpecker): do a second release build, using clang instead of gcc 2023-03-31 13:16:11 +02:00
Daniel Eklöf
e71e7f5cf6
input: kitty: don’t treat zero-length utf8/utf32 strings as text
This is a regression introduced in 3215d54f31

Symptoms: e.g. arrow keys not working in vim/neovim
2023-03-31 11:34:04 +02:00
Daniel Eklöf
deb43c8dc3
changelog: typo: now -> not 2023-03-31 10:43:39 +02:00
Daniel Eklöf
03b23ed6e5
changeloge: remove bad escape char 2023-03-31 10:42:50 +02:00
Daniel Eklöf
a5dd003627
changelog: remove trailing back-tick 2023-03-31 10:41:17 +02:00
Daniel Eklöf
27c52fb4e3
test: config: call FcIni() + FcFini()
Some of the config options we’re testing result in calls to FontConfig
APIs. Without calling FcIni()+FcFini(), we leak memory:

  Direct leak of 768 byte(s) in 3 object(s) allocated from:
      #0 0x7f7e95cbfa89 in __interceptor_malloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:69
      #1 0x7f7e95bd1fe5  (/usr/lib/libfontconfig.so.1+0x20fe5)

  Indirect leak of 96 byte(s) in 3 object(s) allocated from:
      #0 0x7f7e95cbf411 in __interceptor_calloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:77
      #1 0x7f7e95bd63fd  (/usr/lib/libfontconfig.so.1+0x253fd)

  Indirect leak of 19 byte(s) in 2 object(s) allocated from:
      #0 0x7f7e95c72faa in __interceptor_strdup /build/gcc/src/gcc/libsanitizer/asan/asan_interceptors.cpp:439
      #1 0x7f7e95bd1898 in FcValueSave (/usr/lib/libfontconfig.so.1+0x20898)
2023-03-31 10:30:58 +02:00
Daniel Eklöf
981e4b77cb
term: protect against integer overflow when accumulating scroll damage
When accumulating scroll damage, we check if the last scroll damage’s
scrolling region, and type, matches the new/current scroll damage. If
so, the number of lines in the last scroll damage is increased,
instead of adding a new scroll damage instance to the list.

If the scroll damage list isn’t consumed, this build up of scroll
damage would eventually overflow.

And, even if it didn’t overflow, it could become large enough, that
when later used to calculate e.g. the affected surface area, while
rendering a frame, would cause an overflow there instead.

This patch fixes both issues by:

a) do an overflow check before increasing the line count
b) limit the line count to UINT16_MAX
2023-03-30 16:13:19 +02:00
Daniel Eklöf
7bc22862fa
render: protect against integer underflow when calculating scroll area
When applying scroll damage, we calculate the affected region’s
height (in pixels), by subtracting the number of rows to scroll, from
the scrolling region, and finally multiply by the cell height.

If the number of rows to scroll is very large, the subtraction may
underflow, resulting in a very large height value instead of a
negative one.

This caused the check for "scrolling too many lines" to fail. That in
turn resulted in an integer overflow when calculating the source
offset into the rendered surface buffer, which typically triggered a
segfault.

This bug happened when there was continuous output in the terminal
without any new frames being rendered. This caused a buildup of scroll
damage, that triggered the underflow+overflow when we finally did
render a new frame.

For example, a compositor that doesn’t send any frame callbacks (for
example because the terminal window is minimized, or on a different
workspace/tag) would cause this.

Closes #1305
2023-03-30 16:13:18 +02:00
Daniel Eklöf
3215d54f31
input: (kitty kbd): the resulting UTF-8 string may translate to multiple UTF-32 codepoints
When this happened (for example, by specifying a custom compose
sequence), the kitty keyboard protocol didn’t emit any text at all.

This was caused by the utf32 codepoint being -1. This in turned was
caused by us trying to convert the utf8 sequence to a *single* utf32
codepoint.

This patch replaces the use of mbrtoc32() with a call to
ambstoc32(), and the utf32 codepoint with an utf32 string.

The kitty keyboard protocol is updated:

* When determining if we’re dealing with text, check *all* codepoints
  in the utf32 string.

* Add support for multiple codepoints when reporting "associated
  text". The first codepoint is the actual parameter in the emitted
  sequence, and the remaining codepoints are sub-parameters. I.e. the
  codepoints are colon separated.

Closes #1288
2023-03-30 16:12:17 +02:00
Harri Nieminen
ae26915916 fix typos 2023-03-29 00:45:18 +03:00
Daniel Eklöf
296e75f4f5
render: fix glitchy selection while resizing the ‘normal’ screen
The selection coordinates are in absolute row numbers. As such,
selection breaks when interactively resizing the normal grid, since we
then instantiate a temporary grid mapping directly to the current
viewport (for performance reason, to avoid reflowing the entire grid
over and over again).

Fix by stashing the actual selection coordinates, and ajusting the
"active" ones to the temporary grid.
2023-03-27 16:53:41 +02:00
Craig Barnes
5b2f02d826 slave: set $TERM_PROGRAM and $TERM_PROGRAM_VERSION environment variables
These are already being set by iTerm2, WezTerm, tmux and likely some
others. Even though using yet more environment variables seems rather
questionable, if we don't set these we run the risk of inheriting them
from other terminals.

See also:

* 97a6078df8/sources/PTYSession.m (L2568-2570)
* 1d0f68dee9/environ.c (L263-L264)
* https://github.com/search?q=TERM_PROGRAM&type=code
2023-03-20 14:40:36 +00:00
Daniel Eklöf
9f3ce9236f
config: apply fontconfig rules if user didn’t set an explicit font size
If the user didn’t explicitly set the font size (e.g. font=monospace,
instead of font=monospace:size=12), our initial attempt to read the
FC_SIZE and FC_PIXEL_SIZE attributes will fail, and we used to
fallback to setting the size to 8pt.

Change this slightly, so that when we fail to read the FC_*_SIZE
attributes, apply the fontconfig rules, but *without expanding*
them (i.e. without calling FcDefaultSubstitute()).

Then try reading FC_*_SIZE again.

If that too fails, _then_ set size to 8pt. This allows us to pick up
rules that set a default {pixel}size:

    <fontconfig>
      <match>
        <edit name="pixelsize" mode="append"><double>14</double></edit>
    </fontconfig>

Closes #1287
2023-03-06 18:02:10 +01:00
jaroeichler
9da1b1cec3
themes: add Material Amber 2023-03-03 18:31:31 +01:00
Daniel Eklöf
9a5a2d9957
key-binding: sort binding lists
Sort bindings such that bindings with the same symbol are sorted with
the binding having the most modifiers comes first.

This fixes an issue where the “wrong” key binding are triggered when
used with “consumed” modifiers.

For example: if Control+BackSpace is bound before
Control+Shift+BackSpace, then the latter binding is never triggered.

Why? Because Shift is a consumed modifier. This means
Control+BackSpace is “the same” as Control+Shift+BackSpace.

By sorting bindings with more modifiers first, we work around the
problem. But note that it is *just* a workaround, and I’m not
confident there aren’t cases where it doesn’t work.

Closes #1280
2023-03-03 18:29:22 +01:00
Daniel Eklöf
514fcc20a7
render: resize: call xdg_toplevel_set_min_size()
This is a hint to the compositor, not to set a smaller size than this.
2023-03-02 17:22:27 +01:00
Daniel Eklöf
8a849b4b08
render: fix inversed cursor fg color when alpha != 1.0, take #2
No need to check if terminal colors have been reversed - this is done
by the cell rendering logic.

This hopefully fixes all remaining issues with invisible text when
background alpha < 1.0
2023-02-28 17:49:57 +01:00
Daniel Eklöf
7a43737745
render: fix selected cursor cell being ‘invisble’ when background alpha is used
... by taking the cell ‘selected’ state into account when determining
whether to use the default fg or bg as the ‘text’ color.
2023-02-27 17:51:29 +01:00
Daniel Eklöf
7f26914583
wayland: ignore configure events for unmapped surfaces
Closes #1249

Note that it is still unclear whether ack:ing a configure event for an
unmapped surface is a protocol violation, or something that should be
handled by the compositor.

According to
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/108,
Kwin, Mutter and Weston handles it, while wlroots does not.
2023-02-25 09:22:20 +01:00
Craig Barnes
f2356adee3 doc: foot.ini: fix spelling mistake in [bell].urgent description 2023-02-16 09:12:53 +00:00
Craig Barnes
25154a8150 doc: ctlseq: fix capitalization in description of DA3 sequence 2023-02-16 08:50:53 +00:00
Daniel Eklöf
1c16e4a575
Tag a couple variables with UNUSED, to fix warnings with clang-15
Closes #1278
2023-02-12 19:09:48 +01:00
Craig Barnes
1823fa846a completions: bash: simplify awk command used to filter terminfo names 2023-01-27 11:47:12 +00:00
Craig Barnes
b81b98d47c render: fix incorrect indent introduced by previous commit 2023-01-17 23:49:32 +00:00
Daniel Eklöf
a9298959a1
render: fix double-width glyphs glitching when surrounding cells overflow into it
If cells overflowed (for example, by using an italic font that isn’t
truly monospaced) into a double-width glyph (that itself is *not*
overflowing), then the double-width glyph would glitch when being
rendered; typically the second half of it would occasionally
disappear.

This happened because we tried to rasterize the second cell of the
double-width glyph. This cell contains a special “spacer”
value. Rasterizing that typically results the font’s “not available”
glyph.

If _that_ glyph overflows, things broke; we’d later end up forcing a
re-render of it (thus erasing half the double-width glyph). But since
the double-width glyph _itself_ doesn’t overflow, _it_ wouldn’t be
re-rendered, leaving it half erased.

Fix by recognizing spacer cells, and not trying to rasterize them (set
glyph count to 0, and cell count to 1).

Closes #1256
2023-01-17 19:43:47 +01:00
Daniel Eklöf
d1220aebfd
terminfo: sync with ncurses 2023-01-14
* RV/rv: report DA2
* XR/xr: report version (XTVERSION)
* XF: boolean, focus in/out events available
2023-01-15 14:09:08 +01:00
Daniel Eklöf
09f3475ad1
config: don’t double-free key binding auxiliary data
Key bindings with multiple key mappings share auxiliary data (e.g. the
command to execute in pipe-* bindings, or the escape sequence in
text-bindings).

The first one is the designated “master” copy. Only that one should be
freed.

This fixed a double-free on exit, with e.g.

  [text-bindings]
  \x1b\x23=Mod4+space Mod4+equal

Closes #1259
2023-01-15 10:24:01 +01:00
Daniel Eklöf
ffaf08e07c
config: remove unused struct 2023-01-15 10:23:44 +01:00
EuCaue
3f57afbf60
add rose-pine theme 2023-01-13 11:41:05 +01:00
Craig Barnes
becdcd9bb7 completions: bash: complete option arguments for short options 2023-01-10 19:56:12 +00:00
Craig Barnes
8acc10b9d4 completions: bash: use "case" instead of long if/elif/else chain 2023-01-10 19:44:24 +00:00
Craig Barnes
7d28da5006 Use "command -v" instead of "which" in bash completion scripts
The former is a built-in command in bash, whereas the latter is an
external command and isn't always necessarily available.
2023-01-10 18:34:25 +00:00
Grigory Kirillov
a38b8d0222 doc: fix a typo 2023-01-08 00:55:01 +03:00
Craig Barnes
f19768e304 wayland: avoid passing NULL to log_msg() in wayl_reload_xcursor_theme()
This pointer ends up being passed to various printf-family functions,
where passing a NULL pointer for an "%s" format specifier invokes
undefined behaviour.
2023-01-06 23:43:51 +00:00
woojiq
c9465e4c5c
themes: add Onedark 2023-01-06 10:49:45 +01:00
Daniel Eklöf
63bef0dc8c
ci: drop gitlab CI
We’re no longer mirroring to gitlab.
2023-01-02 13:52:56 +01:00
Craig Barnes
1d3023ec5e changelog: amend terminfo names, in accordance with previous commit 2023-01-01 15:21:17 +00:00
Daniel Eklöf
88641005fe
terminfo: PD/PE -> PE/PS
Ncurses 2022-12-24 had the names wrong. It was corrected on
2022-12-29.
2023-01-01 15:21:05 +01:00
Daniel Eklöf
e7c1a93d29
terminfo: add entries for bracketed paste
Ncurses added these in 2022-12-24, but they have been used/supported
by vim since 2017.

* BE - Bracketed paste Enable
* BD - Bracketed paste Disable
* PE - Paste Enable (i.e. "begin")
* PD - Paste Disable (i.e. "end")
2023-01-01 15:07:02 +01:00
Daniel Eklöf
6259d59b4d
config: change default grapheme-width-method from wcswidth to double-width
The old default, wcswidth, simply calls wcswidth() on the grapheme
cluster. This was supposedly the implementation with the highest
application compatibility. Except we never even tried to measure
it. It was just assumed.

A lot of modern applications have better implementations. Let’s try to
push support for better emoji support by changing our default method
from wcswith to double-width.

While far from correct (it’s not based on the Unicode tables), the
‘double-width’ method produces accurate results anyway.

double-width is like wcswidth(), in that it adds together the
individual wcwidths of all codepoints in the grapheme cluster. But, it
limits the maximum width to 2.
2023-01-01 10:29:27 +01:00
Daniel Eklöf
9e4270cd48
themes: comment out selection-{foreground,background}
That is, mouse selections should default to inverse fg/bg
2022-12-26 11:03:17 +01:00
Daniel Eklöf
135d4478a1
themes: add ‘conf’ modeline 2022-12-26 10:59:37 +01:00
Daniel Eklöf
da7b393a03
themes: remove alpha
Alpha isn’t really part of the theme. Leave it up to the user to set
alpha.
2022-12-26 10:54:02 +01:00
argosatcore
6ebe5cf621
Add Deus theme.
New color palette based on: https://github.com/ajmwagar/vim-deus
2022-12-26 10:51:42 +01:00
Daniel Eklöf
4ee0b28b02
config: font-size-adjustment: don’t allow empty %-values (key=%) 2022-12-17 11:00:05 +01:00
Daniel Eklöf
7bf150c11a
config: value_to_pt_or_px(): don’t allow empty px values (key=px) 2022-12-17 11:00:05 +01:00
Daniel Eklöf
59018446fd
foot.ini: add font-size-adjustment 2022-12-17 11:00:05 +01:00
Daniel Eklöf
f6ca8c90e1
config: add ‘font-size-adjustment=N[px|%]’ option
This patch adds a new config option, font-size-adjustment.

It lets you configure how much the font size should be
incremented/decremented when zooming in or out (ctrl-+, ctrl+-).

Values can be specified in points, pixels or percent.

Closes #1188
2022-12-17 10:59:17 +01:00
Joakim Nohlgård
7bb5c80d04 main: Graceful fallback if user has configured an invalid locale 2022-12-16 08:38:37 +01:00
Daniel Eklöf
3b9aca6a3d
doc: foot-ctlseq: expand last column to fill screen in all tables 2022-12-14 12:20:52 +01:00
Daniel Eklöf
ccfb953bb0
slave: unsetenv() env vars that have been set to the empty string
That is, users can now *clear* environment variables by doing:

  [environment]
  VAR=””

Note that the quotes are required.

Closes #1225
2022-12-06 19:36:15 +01:00
Daniel Eklöf
646314469a
doc: foot.ini: add example, and mention string options can be quoted 2022-12-06 19:36:07 +01:00
Daniel Eklöf
57d9a7451f
foot.ini: use a quoted, empty string for “indicator-format”
We don’t allow empty values, but we do allow quoted, empty values.
2022-12-06 19:36:07 +01:00
Daniel Eklöf
051e862420
config: allow string values to be quoted
* Both double and single quotes are recognized. There’s no difference
  in how they are handled.
* The entire string must be quoted:
  - “a quoted string” - OK
  - quotes “in the middle” of a string - NOT ok
* Two escape characters are regonized:
  - Backslash
  - The quote character itself
2022-12-06 19:35:58 +01:00
Daniel Eklöf
1486c57bdb
doc: foot: add PWD to list of env vars set in child process 2022-12-04 19:49:02 +01:00
Daniel Eklöf
b43a41df6a
log: don’t default to syslog enabled
Initialize the global ‘do_syslog’ variable to false. This ensures any
log calls done before log_init() has been called (e.g. unit tests)
doesn’t syslog anything.

As a side effect, such log calls no longer open an implicit syslog
file descriptor; this is how this “bug” was found: valgrind detected
an unclosed file descriptor at exit.

Finally, completely disable syslogging if log-level is “none”.
2022-12-02 11:45:10 +01:00
Daniel Eklöf
76d494484f
url-mode: tag cells after snapshot:ing the grid
Before this patch, hyperlinked cells were tagged with the “URL”
attribute (thus instructing the renderer to draw an
underline) *before* the grid was snapshot.

When exiting URL mode, the cells were once again updated, this time
removing the URL attribute.

But what if an escape sequence had modified the grid _while we were in
URL mode_? Depending on the sequence, it could move cells around in
such a way, that when exiting URL mode, the affected cells weren’t
updated correctly. I.e. we left some cells with the URL attribute
still set.

The fix is simple: tag cells in the snapshot:ed grid only (which isn’t
affected by any escape sequence received while in URL mode). Not in
the *actual* grid (which _is_ affected).
2022-11-30 10:51:45 +01:00
Craig Barnes
1b24cf4fcb doc: ctlseq: add trailing space to fix XTGETTCAP entry in DCS table
Without the trailing space, both the sequence and the description
were getting packed into a single table column.
2022-11-24 20:34:41 +00:00
Daniel Eklöf
0fc8b65a2b
selection: selection_on_rows(): typo: row_start -> row_end
This fixes an issue where selections in the scroll margins were not
detected correctly. This meant they weren’t canceled as they should
have been, which in turn caused a visual glitch where text appeared to
be selected, but were in fact not.
2022-11-24 19:18:20 +01:00
Daniel Eklöf
4d03b6c611
Merge branch 'line-height-adjust'
Closes #1218
2022-11-24 19:17:09 +01:00
Daniel Eklöf
db2627ea26
changelog: scaling factor not being applied when converting px-to-pt 2022-11-24 17:21:53 +01:00
Daniel Eklöf
fa6b07abea
term: apply scale factor when converting a px value to pt 2022-11-24 17:20:05 +01:00
Daniel Eklöf
e85257bcae
term: initialize term->font_line_height when there’s no user-set line-height 2022-11-24 17:09:31 +01:00
Daniel Eklöf
94bac0513a
term: update user-set line-height just before calculating the cell dimensions
This ensures *all* font-size affecting changes (DPI, output scaling,
font size increment/decrement) also updates the line-height.
2022-11-24 14:34:31 +01:00
Daniel Eklöf
f31ea4f56d
changelog: line-height adjustment with user-set line-height 2022-11-23 16:29:42 +01:00
Daniel Eklöf
5a54423000
term: adjust user-set line-height by the same percentage as the primary font
Before this patch, a user-set line-height was increased/decreased by
the exact same amount of pt’s as the font(s).

This means, that when there’s a large discrepancy between the
line-height and the font size, the proportion between the line’s
height and the font size will change as we increase or decrease the
font size.

This patch changes how the line height is adjusted when the font size
is incremented or decremented. We calculate the difference, in
percent, between the primary font’s original (default) size, and its
current size, and then apply that to the configured line-height.

Closes #1218
2022-11-23 16:29:42 +01:00
Daniel Eklöf
dfabc5d754
readme/foot.1: mention that we now need “-d info” to get log output 2022-11-23 16:27:50 +01:00
Antoine Beaupré
b80c7f75fe
change default log level to WARNING
The default foot output looks like this, in Debian testing "bookworm"
at the time of writing:

    anarcat@angela:pubpaste$ foot true
    info: main.c:421: version: 1.13.1 +pgo +ime +graphemes -assertions
    info: main.c:428: arch: Linux x86_64/64-bit
    info: main.c:440: locale: fr_CA.UTF-8
    info: config.c:3003: loading configuration from /home/anarcat/.config/foot/foot.ini
    info: fcft.c:338: fcft: 3.1.5 +graphemes -runs +svg(nanosvg) -assertions
    info: fcft.c:377: fontconfig: 2.13.1, freetype: 2.12.1, harfbuzz: 5.2.0
    info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Regular.otf: size=8.00pt/8px, dpi=75.00
    info: wayland.c:1353: eDP-1: 2256x1504+0x0@60Hz 0x095F 13.32" scale=2 PPI=205x214 (physical) PPI=136x143 (logical), DPI=271.31
    info: wayland.c:1509: requesting SSD decorations
    info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Bold.otf: size=24.00pt/32px, dpi=96.00
    info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Regular.otf: size=24.00pt/32px, dpi=96.00
    info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Bold.otf: size=24.00pt/32px, dpi=96.00
    info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Regular.otf: size=24.00pt/32px, dpi=96.00
    info: terminal.c:700: cell width=19, height=39
    info: terminal.c:588: using 16 rendering threads
    info: wayland.c:859: using SSD decorations
    info: main.c:680: goodbye
    anarcat@angela:pubpaste$

That's 17 lines of output that are *mostly* useless for most use
cases. I might understand having this output during the project's
startup, when it's helpful for diagnostics, but now Foot just mostly
works everywhere, and I've never had a use for any of that stuff in
the (arguably short) time I've been using Foot so far.

And if I do, there's the `--log-level` commandline option to tweak
this. At first, I looked at tweaking the log level through the config
file. But as explained in this issue:

https://codeberg.org/dnkl/foot/issues/1142

... there's a chicken and egg problem there that makes it hard to
implement and possibly confusing for users as well.

There's also the possibility for users to change the shortcut with
which they start foot, for example a `.desktop` file so that menu
systems that support those start foot properly. But that only works in
that environment, and not through the so many things that will just
call `foot` and hope it will do the right thing.

In my case, I have `foot` hardcoded in a lot of places now, between
sway and waybar, and this is only going to grow. Others have suggested
adding the flag to a $TERMINAL global variable, but that won't help
.desktop users.

So, instead of playing whack-a-mole with the log levels, just make it
so that, by default, foot is silent. This is actually one of the
[basics of UNIX philosophy][1]:

> Rule of Silence: When a program has nothing surprising to say, it
> should say nothing.

And yes, I am aware I am severely violating that principle by writing
a way too long commit log for a one-line patch, but there you go, I
figured it was good to document the why of this properly.

[1]: https://web.archive.org/web/20031102053334/http://www.faqs.org/docs/artu/ch01s06.html
2022-11-22 10:22:22 -05:00
Daniel Eklöf
4d2b8a993f
Merge branch 'fp-calc-dpi'
Closes #1209
2022-11-22 09:34:53 +01:00
Daniel Eklöf
42c6af0914
wayland: force monitor DPI to 96 when it’s unreasonably high
If an output has a bogus physical width or height, the DPI can become
so high that the cell width/height is too large for
pixman_image_fill_rectangles(), resulting in a crash in pixman_fill().

Since it doesn’t make any sense to use a DPI that is obviously bogus,
don’t. Force it 96 instead.
2022-11-04 17:49:01 +01:00
Daniel Eklöf
2910ca354c
wayland: use fp math all the way when calculating DPI
This fixes an FPE when the monitor’s physical width/height is so small
that the conversion from mm to inch resulted in inches being zero.
2022-11-04 17:42:52 +01:00
Soren A D
fa9beae3a6
added modus themes 2022-11-02 19:11:12 +01:00
Craig Barnes
8f2bda6703 wayland: use BUG() instead of xassert(false)
The latter will expand to the former anyway, so we may as well provide
an explicit error message instead of "assertion failed: 'false'".
2022-11-01 21:04:22 +00:00
Daniel Eklöf
30d088376c
render: maybe_resize(): remove debug assert
This, depending on which compiler being used, caused issues not only
in debug builds, but release builds as well (with NDEBUG defined).
2022-11-01 17:12:16 +01:00
Daniel Eklöf
2c2a39317b
render: never apply alpha to text color
When drawing a block cursor using inversed fg/bg colors, we didn’t
strip the alpha from the background color. This meant that the text
"behind" the cursor was rendered with transparency. If alpha was set
to 0, the text was completely invisible.

We should never apply alpha to the text color. So, detect this, and
force alpha to 1.0.

Normally, when selecting the cursor’s color, we don’t really know
_where_ the background color is coming from (or more accurately,
_what_ it is).

However, the *only* background color that can have a non-1.0 alpha is
the *default* background color.

This is why we can ignore the bg parameter, and use term->colors.fg/bg
instead.

Closes #1205
2022-10-30 19:43:18 +01:00
Andrea Pappacoda
49fa751953
chore: use MIT license for appstream metadata
The Appstream metadata file introduced in commit
335612cfa4
has been submitted as licensed under the CC0-1.0 license. People
generally use the CC0-1.0 to put the file in the "public domain", but
this isn't actually possible in lots of countries, so the file ends up
being licensed under CC0's fallback permissive license; unfortunately,
the fallback license contains some terms that are seen as problematic to
some (notably, Fedora has recently decided to consider the license
pretty much non-free).

As foot is already MIT-licensed, and since this license is in the list
of allowed [metadata licenses], this patch changes the license of the
metadata file from CC0-1.0 to MIT.

[metadata licenses]: https://freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-metadata_license
2022-10-27 13:58:52 +02:00
Andrea Pappacoda
1313e6352a
build: fix GCC detection in pgo.sh
On my system, GCC doesn't output its name when passing the --version
flag:

    $ cc --version
    cc (Debian 12.2.0-3) 12.2.0
    Copyright (C) 2022 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

As the Free Software Foundation is unlikely to write another compiler, I
think that searching for the foundation's name instead of GCC is a good
enough fix (and I'm almost sure we wouldn't be the first ones to do so).
2022-10-23 23:56:34 +02:00
Daniel Eklöf
59c9dfe109
render: resize: do full text reflow immediately if resize-delay-ms == 0
That is, skip all custom grid handling when doing an interactive
resize, if resize-delay-ms == 0.
2022-10-23 10:34:18 +02:00
Daniel Eklöf
3ba03901b8
pgo: don’t re-use the rows between the ‘normal’ and ‘alt’ grids
This used to work because we never free:d any of the rows. Now
however, we do free (some of) them when reverse scrolling. This means
we can no longer re-use the rows between the two screens.

Closes #1196
2022-10-18 18:31:18 +02:00
Daniel Eklöf
c4f08a3b9a
grid_free(): allow being called with grid == NULL 2022-10-18 18:30:02 +02:00
Daniel Eklöf
09d52d5db6
term_destroy(): free interactive_resizing.grid
This grid is normally unallocated, but may be allocated if we are
exiting (for whatever reason) in the middle of an interactive resize.
2022-10-18 18:29:20 +02:00
Daniel Eklöf
2e9b3ceb95
fdm_ptmx(): regression: don’t return false when an interactive resize is in progress 2022-10-18 18:28:51 +02:00
Daniel Eklöf
b0c30c7ed2
doc: foot.ini: improve documentation of cursor.color 2022-10-17 20:16:53 +02:00
Daniel Eklöf
0ac0d0647a
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.

This way, window content will not be "erased" when the window is first
made smaller, then larger again.

And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.

The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:

1) the temporary grid we create is large enough to contain the current
   viewport, but not more than that. That means we can’t "scroll up", to
   hide the cursor.

2) grid_resize_without_reflow() doesn’t know anything about
   "interactive resizing". As such, it will ensure the cursor is bound
   to the new grid dimensions.

I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
Daniel Eklöf
3c9a51afa6
changelog: crash after reverse-scrolling in the normal screen 2022-10-16 10:31:15 +02:00
Daniel Eklöf
89744f8123
selection: scroll down: handle non-full scrollback correctly
When determining whether or not to cancel a selection (due to it, or
part of it, being "scrolled out"), we assumed the scrollback was full.

This patch changes the implementation to compare the scrollback
relative selection coordinates with the scrollback relative
end-of-the-scrollback coordinates.
2022-10-16 10:31:15 +02:00
Daniel Eklöf
3d9a429499
term: reverse scroll: free scrolled out lines
This ensures *everything* in the circular scrollback history, after
the bottom of the screen, is either NULL rows, or belong to the
scrollback *history* (as opposed to the future history).

This fixes an issue when calculating the scrollback start, which for
example would trigger a crash when moving the viewport (i.e. scrolling
with the mouse, or PgUp/PgDown).

Closes #1190
2022-10-16 10:31:08 +02:00
Daniel Eklöf
43a48f53d4
sixel: don’t crash when sixel image exceeds current sixel max height
When we try to resize a sixel past the current max height, we set
col > image-width to signal this.

This means ‘width’ could be smaller than ‘col’. When calculating how
many sixels to emit in sixel_add_many(), we didnt’ account for this.

The resulting value was -1, converted to ‘unsigned’. I.e. a very large
value. This resulted in an assert triggering in sixel_add() in debug
builds, and a crash in release builds.
2022-10-13 17:52:34 +02:00
Daniel Eklöf
3949e34271
Merge branch 'delayed-reflow' 2022-10-10 17:19:43 +02:00
Daniel Eklöf
298f210ed9
render: rename term->render.resizing -> term->interactive_resizing
But also, more importantly, logical fixes:

* Stash the number of new scrollback lines the stashed ‘normal’ grid
  should be resized *to*.

There’s also a couple of performance changes here:

* When doing a delayed reflow (tiocswinsz timer), call
  sixel_reflow_grid(term, &term->normal) - there’s no need to reflow
  sixels in the ‘alt’ screen.

* When doing a delayed reflow, free all scroll damage. It’s not
  needed, since we’re damaging the entire window anyway.

* Use minimum size for the temporary ‘normal’ grid (that contains the
  current viewport). We just need it to be large enough to fit the
  current viewport, and be a valid grid row count (power of 2). This
  just so happens to be the current ‘alt’ grid’s row count...
2022-10-10 17:19:18 +02:00
Daniel Eklöf
c550d67cd8
render: resize: do delayed reflow immediately when failing to arm tiocswinsz timer 2022-10-10 17:19:18 +02:00
Daniel Eklöf
c5c97c2fd4
term_ptmx_{pause,resume}: return success/fail 2022-10-10 17:19:18 +02:00
Daniel Eklöf
54d637e2b4
term: ptmx: don’t consume anything while doing an interactive resize
The ‘normal’ grid in use during an interactive resize is temporary;
all changes done to it will be lost when the resize is finished.
2022-10-10 17:19:18 +02:00
Daniel Eklöf
66e4592d91
term: use SIZE_MAX instead of (size_t)-1ll 2022-10-10 17:19:18 +02:00
Daniel Eklöf
18ef36523f
grid: resize: assert grid->cur_row is not NULL after a grid resize 2022-10-10 17:19:18 +02:00
Daniel Eklöf
d4b0b0887e
render: delayed reflow: not enough to damage current view; need to refresh too 2022-10-10 17:19:18 +02:00
Daniel Eklöf
f70c34c5a8
sixel: add sixel_reflow_grid()
This function reflows all sixels in the specified grid.

The pre-existing sixel_reflow() function is a shortcut for

  sixel_reflow_grid(term, &term->normal)
  sixel_reflow_grid(term, &term->alt);
2022-10-10 17:19:18 +02:00
Daniel Eklöf
b52262da8e
changelog: fixed crash when resizing window with a very large scrollback 2022-10-10 17:19:18 +02:00
Daniel Eklöf
f4f1989b6e
render: resize: ignore ptmx read events during interactive resize 2022-10-10 17:19:18 +02:00
Daniel Eklöf
3565cbd636
render: performance improvements during interactive resize
Instead of copying the entire grid when an interactive resize is
started, stash the complete grid (to be used in the final reflow).

Copy the current viewport only, to be used during the interactive
resize.

This gets rid of the initial "pause" when snapshotting the grid when
an interactive resize is started.
2022-10-10 17:19:17 +02:00
Daniel Eklöf
8179d73daa
render: delay reflow for ‘resize-delay-ms’ milliseconds
Reflowing a large scrollback is *slow*. During an interactive resize,
it can easily take long enough that the compositor fills the Wayland
socket with configure events. Eventually, the socket becomes full and
the compositor terminates the connection, causing foot to exit.

This patch is work-in-progress, and the first step towards alleviating
this.

It delays the reflow by:

* Snapshotting (copying) the original grid when an interactive resize
  is started.
* While resizing, we apply a simple truncation resize of the
  grid (like we handle the alt screen).
* When the resize is done, or paused for ‘resize-delay-ms’, the grid
  is reflowed.

TODO: we *must* not allow any changes to the temporary (truncated)
grid during the resize. Any changes to the grid would be lost when the
final reflow is applied. That is, we must completely pause the ptmx
pipe while a resize is in progress.

Future improvements:

The initial copy can be slow. We should be able to avoid it by
rewriting the reflow algorithm to not free anything. This is
complicated by the fact that some resources (e.g. sixel images) are
currently *moved* to the new grid. They’d instead have to be copied.
2022-10-10 17:19:17 +02:00
Daniel Eklöf
a9fc7ce180
pgo: run xtgettcap without any arguments
We execute xtgettcap in the parent terminal. Thus we don’t know if it
implements XTGETTCAP, and thus it’s not guaranteed to exit - it may
hang indefinitely waiting for a reply.

Fix by not actually quering anything.
2022-10-10 17:18:04 +02:00
Daniel Eklöf
807e193854
xtgettcap: exit immediately when there are no capabilities to query for 2022-10-10 17:17:38 +02:00
Daniel Eklöf
4fca380585
install.md: add ./utils/xtgettcap name to "full PGO" instructions too 2022-10-09 16:27:10 +02:00
Daniel Eklöf
f747650b77
install.md: add xtgettcap to PGO build instructions 2022-10-08 16:56:28 +02:00
Daniel Eklöf
503740f836
pgo: execute xtgettcap utility, to get profiling data
Fixes:

  ../utils/xtgettcap.c:175:1: error: ‘/home/daniel/src/foot/src/utils/xtgettcap.p/xtgettcap.c.gcda’ profile count data file not found [-Werror=missing-profile]
2022-10-07 21:47:56 +02:00
Daniel Eklöf
9937d92c85
utils: xtgettcap: new utility, to send XTGETTCAP queries 2022-10-07 14:40:22 +02:00
Daniel Eklöf
f359a8d6bc
scripts: generate-builtin-terminfo: escape fixes
* Remove ‘:’ escaping only in raw (non-parameterized) sequences
* Double-escape *all* escape characters in parameterized sequences
2022-10-05 16:53:55 +02:00
Daniel Eklöf
fd743b5173
scripts: generate-builtin-terminfo: double-escape backslash in ST
Fixes an issue with XTGETTCAP, where escape sequences terminated with
ST, and containing parameters were missing a trailing backslash.
2022-10-05 16:53:54 +02:00
Alexey Sakovets
37218be648
render: fix nanosec "overflow" when calculating timeout value 2022-10-05 16:51:25 +02:00
Hugo Osvaldo Barrera
2d4f0535c6
Add zenburn theme
This is the one included by default in previous releases.
2022-10-04 21:23:07 +02:00
Nick Hastings
cbebafbfe8 doc: fix tiny typo 2022-10-04 13:04:03 +09:00
Daniel Eklöf
332cb90134
spawn: set $PWD, in addition to calling chdir(cwd) 2022-10-02 20:11:16 +02:00
Daniel Eklöf
90ce4f3008
main/client: use $PWD for cwd, when $PWD is valid
If $PWD is set, and its resolved path matches the *actual* working
directory, use $PWD for cwd when instantiating the terminal.

This makes a difference when $PWD refers to a symlink; before this
patch, we’d instantiate the terminal in the *resolved* path. Now it’ll
use the symlink instead.
2022-10-02 20:11:16 +02:00
Daniel Eklöf
9e58661093
slave: spawn: set PWD environment variable
This improves handling of symlinks (in CWD) when launching a new
terminal instance, either through ctrl+shift+n, or using the
--working-directory command line option.

Closes #1179
2022-10-02 20:11:07 +02:00
Daniel Eklöf
88c3128515
scripts: generate-builtin-terminfo: add synthetic ‘name’ capability
Same as ‘TN’; reports the terminfo name.
2022-09-28 21:09:35 +02:00
Daniel Eklöf
bb02b319d0
terminfo: add kxIN + kxOUT (focus in/out events)
These capabilities are not included in the standard ‘xterm’ or
‘xterm-256color’ terminfos. They’re used in

‘xterm+focus’ ->
‘xterm+sm+1002’ ->
‘xterm-1002|xterm+sm+1003’ ->
‘xterm-1003’

(https://invisible-island.net/ncurses/terminfo.ti.html#tic-xterm_focus)

However, as far as I can tell, ncurses doesn’t use these capabilities
at all.
2022-09-26 18:54:03 +02:00
Daniel Eklöf
c93eb45b42
term: utmp: set ‘host’ to WAYLAND_DISPLAY
This is similar to what XTerm does (setting it to DISPLAY)
2022-09-23 23:04:10 +02:00
Daniel Eklöf
aa10b1d2da
Add support for creating utmp records
This patch adds support for creating utmp records using the ‘utempter’
helper binary from the ‘libutempter’ package.

* New config option ‘main.utempter’
* New meson command line option, -Ddefault-utempter-path. Defaults to
  auto-detecting the path.

The default value of the new ‘main.utempter’ config option depends on
the meson command line option ‘-Ddefault-utempter-path’.

If ‘main.utempter’ is *not* set to ‘none’, foot will try to execute
the utempter helper binary to create utmp records when a new terminal
is instantiated. The record is removed when the terminal instance is
destroyed.
2022-09-23 23:02:25 +02:00
Daniel Eklöf
77b74734a4
Merge branch 'nightfly-and-moonfly' 2022-09-23 20:39:52 +02:00
Daniel Eklöf
3be44fb316
render: overlay: fix visual glitches when double buffering
When rendering the overlay for scrollback search, the logic assumed
buffer re-use. On some compositors this isn’t happening (on
e.g. KDE/plasma we’re forced to double buffer).

This resulted in matches not being highlighted correctly.

The problem is in how we calculated the region for which areas to
clear ("un-dim"). It uses the "previous frame’s see-through area"
minus the current frame’s see-through area.

However, when we’ve detected that the current buffer isn’t the same as
the last one, we set the last frame’s see-through region to "the
entire buffer". Thus, when calculating the diff, we end up with an
empty region, and nothing is highlighted.

Fix by simply using the current frame’s see-through region as-is when
we’ve detected we’re not re-using the last frame’s buffer.
2022-09-23 20:33:02 +02:00
Daniel Eklöf
4340f8a3b4
render: fix application of old scroll damage when double buffering
On compositors that forces us to double buffer, we need to re-apply
the last frame’s damage to the current frame (which uses the buffer
from the next-to-last frame).

General cell updates are handled by simply copying from the last
frame’s pixman buffer to the current frame’s.

In an attempt to improve performance, scroll damage were up until now
handled by re-playing the last frame’s scroll damage (on the current
frame’s buffer). This does not work, and resulted in glitches when
scrolling in the scrollback.

This patch does the following:

* grid_render_scroll{,_reverse}() now update the buffer’s "dirty"
  region. This means the generic copy-old-frames-buffer handles the
  scroll damage (albeit in, potentially, a less efficient way).

* Tracking of, and re-applying old scroll damage is completely
  removed.

Closes #1173
2022-09-23 20:33:02 +02:00
Daniel Eklöf
50ae277d90
wayland: don’t crash with a division-by-zero when monitor dimensions are negative
Some compositors set the physical dimensions to -1...
2022-09-23 20:31:08 +02:00
Daniel Eklöf
b7ba842237
render: timer: print/log *total* rendering time 2022-09-23 20:31:08 +02:00
Daniel Eklöf
4db1dde25c
misc: add timespec_add() 2022-09-23 20:31:08 +02:00
Daniel Eklöf
020148d67c
install.md: add ‘utf8proc’ 2022-09-23 20:20:56 +02:00
Torsten Trautwein
cee59bfb3f Add the moonfly theme 2022-09-22 08:44:21 +02:00
Torsten Trautwein
05aedd52da Add the nightfly theme 2022-09-22 08:44:04 +02:00
Vladimír Magyar
debf1b8453
chore: rename desktop files 2022-09-18 17:57:11 +02:00
Vladimír Magyar
335612cfa4
chore: add appstream file
Appstream file is usefull for many package management tools to show
additional metadata and screenshots.
Also it would solve the packaging for flathub.

References: https://codeberg.org/dnkl/foot/issues/1129#issuecomment-602089
2022-09-18 17:57:06 +02:00
Craig Barnes
46f6bad728
csi: reply with 4 instead of 2 for DECRQM queries of unsupported modes
The status value 4 means "permanently reset", as opposed to 2, which
means "reset". The former implies that there's no way to enable the
mode because it's unsupported (but recognized).

Note: this commit also fixes an unrelated typo in CHANGELOG.md.
2022-09-18 17:55:03 +02:00
Craig Barnes
8dcfa259a2 config: fix "maybe-uninitialized" error when compiling with CFLAGS=-Og
Using GCC 12.2.0 with the following build steps:

    CFLAGS=-Og meson bld
    ninja -C bld

...produces the compiler error:

    ../config.c:2000:18: error: ‘sym_equal’ may be used uninitialized
    [-Werror=maybe-uninitialized]

This commit fixes that by using BUG() to assert that all possible
values are accounted for in the offending switch statement.
2022-09-17 06:34:25 +01:00
Daniel Eklöf
1c072856eb
input: pipe-*: set current CWD when spawning the sub-process
Closes #1166
2022-09-10 12:04:39 +02:00
Daniel Eklöf
d2e67689ea
terminal: don’t unref a not-yet-referenced key-binding set
Key-binding sets are bound to a seat/configuration pair. The conf
reference is done when a new terminal instance is created.

When that same terminal instance is destroyed, the key binding set is
unref:ed.

If the terminal instance is destroyed *before* the key binding set has
been referenced, we’ll still unref it. This creates an imbalance.

In particular, when the there is exactly one other terminal instance
referencing that same key binding set, that terminal instance will
trigger a foot server crash as soon as it receives a key press/release
event. This happens because the next-to-last terminal instance brought
the reference count of the binding set down to 0, causing it to be
free:d.

Thus, we *must* reference the binding set *before* we can error
out (when instantiating a new terminal instance).

At this point, we don’t yet have a valid terminal instance. But,
that’s ok, because all the key_binding_new_for_term() did with the
terminal instance was get the "struct wayland" and "struct config"
pointers. So, rename the function and simply pass these pointers
explicitly.

Similarly, change key_binding_for() to take a "struct config" pointer,
rather than a "struct terminal" pointer.

Also rename key_binding_unref_term() -> key_binding_unref().
2022-09-06 20:44:05 +02:00
Daniel Eklöf
c311229b9e
csi: damage *viewport* when exiting the alt screen
This fixes a redraw issue when the normal screen were somewhere in the
scrollback history when exiting the alt screen.
2022-09-06 17:37:19 +02:00
Daniel Eklöf
2d1ded183a
config: change default ‘pad’ to 0x0 (i.e. no padding) 2022-09-03 12:17:46 +02:00
Daniel Eklöf
473c5f4e76
Merge branch 'releases/1.13' 2022-08-31 19:26:21 +02:00
Daniel Eklöf
cd1933baf1
meson: bump version to 1.13.1 2022-08-31 19:19:15 +02:00
Daniel Eklöf
864de72b17
changelog: prepare for 1.13.1 2022-08-31 19:17:38 +02:00
Daniel Eklöf
5706141d0a
url-mode: connect osc-8 links only when both ID and URI matches
Before this, two OSC-8 links with a matching ID would be connected
even if their URIs weren’t the same. This is against the spec:

  The same id is only used for connecting character cells whose URIs
  is also the same. Character cells pointing to different URIs should
  never be underlined together when hovering over.

https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#hover-underlining-and-the-id-parameter
2022-08-31 19:16:19 +02:00
Daniel Eklöf
688bf1bd59
changelog: crash when resizing window, or scrolling 2022-08-31 19:16:06 +02:00
Daniel Eklöf
23871d4db5
grid: reflow: assert there aren’t any open URIs on the last row 2022-08-31 19:16:05 +02:00
Daniel Eklöf
22989ef9b3
grid: reflow: don’t line-wrap the last row
Before this patch, we would line-wrap the last row, just like any
other row, and then afterwards try to reverse this, by adjusting the
offset and free:ing and NULL:ing the "last row".

The problem with this is if the scrollback is full. In this case, the
row we’re freeing is the first row in the scrollback history. This
means we’ll crash as soon as the viewport is moved to the top of the
scrollback.

The fix is fairly, simple. Skip the post-processing logic, and instead
detect when we’re line-wrapping the last row, and skip the call to
line_wrap().

This way, the last row in the new grid corresponds to the last row in
the old grid.
2022-08-31 19:16:04 +02:00
Daniel Eklöf
649d56a4e5
grid: when setting the new viewport, ensure it’s correctly bounded
Do this by using scrollback relative coordinates, and ensure the new
viewport is not larger than (grid_rows - screen_rows), as that would
mean the viewport crosses the scrollback wrap-around.
2022-08-31 19:16:02 +02:00
Daniel Eklöf
d810e4fc8e
selection: mark_selected_region(): use an enum to encode how the cells are to be updated 2022-08-31 19:15:54 +02:00
Daniel Eklöf
2354298eca
selection: restore <= 1.12 behavior in block selection wrt empty cells
That is, highlight empty cells, regardless of whether they’re trailing
or not.
2022-08-31 19:15:51 +02:00
Daniel Eklöf
03d3887e6b
ci (sr.ht): pull directly from git.sr.ht 2022-08-31 19:15:48 +02:00
Daniel Eklöf
6f1cf6fc56
selection: once again highlight non-trailing empty cells
Foot 1.13.0 introduced a regression where non-trailing empty cells
were highlighted inconsistently (cells that shouldn’t be highlighted,
were, seemingly at random).

86663522d5 “fixed” this by never
highlighting *any* empty cells.

This meant the behavior, compared to foot 1.12 and earlier,
changed. In foot 1.12 and older versions, non-trailing empty cells
were highlighted, as long as the selection covered at least one of the
trailing non-empty cells.

This patch restores that behavior.

To understand how this works, lets first take a look at how selection
works:

When a selection is made, and updated (i.e. the mouse is dragged, or
the selection is extended through RMB etc), we need to (un)tag and dirty
the cells that are a) newly selected, or b) newly deselected. That is,
we look at the diff between the “old” and the “new” selection, and
only update those cells.

This is for performance reasons: iterating the entire selection is not
feasible with large selections. However, it also means we cannot
reason about empty cells; we simply don’t know if an empty cells is a
trailing empty cell, or a non-trailing one.

Then, when we render a frame, we iterate all the *visible*
and *selected* cells, once again tagging them as selected (this is
needed since a selected cell might have lost its selected tag if the
cell was written to, by the client application, after the selection
was made).

At this point, we *can* reason about empty cells.

So, to restore the highlighting behavior to that of foot 1.12, we do
this:

When working with the selection diffs when a selection is updated, we
don’t special case empty cells at all. Thus, all empty cells covered
by the selection is highlighted, and dirtied.

But, when rendering the frame, we _do_ special case them. The only
difference (compared to foot 1.12) is that we *must*
explicitly *clear* the selection tag, and dirty the empty cells. This
is to ensure the empty cells that were incorrectly highlighted by the
selection update algorithm, isn’t rendered as that.

This does have a slight performance impact, as empty cells are now
always re-rendered. The impact should however be small.
2022-08-31 19:15:44 +02:00
Daniel Eklöf
736babcdbc
selection: never highlight selected, empty cells
This fixes a regression, where empty cells "between" non-empty
cells (i.e. non-trailing empty cells) sometimes were incorrectly
highlighted.

The idea has always been to highlight exactly those cells that will
get extracted when they’re copied. This means we’ve not highlighted
trailing empty cells, but we _have_ highlighted other empty cells,
since they are converted to spaces when copied (whereas trailing empty
cells are skipped).

fa2d9f8699 changed how a selection is
updated. That is, which cells gets marked as selected, and which ones
gets unmarked.

Since we no longer walk all the cells, but instead work with pixman
regions representing selection diffs, we can no longer determine (with
certainty) which empty cells should be selected and which shouldn’t.

Before this patch (but after
fa2d9f8699), we sometimes incorrectly
highlighted empty cells that should not have been highlighted. This
happened when we’ve first (correctly) highlighted a region of empty
cells, but then shrink the selection such that all those empty cells
should be de-selected.

This patch changes the selection behavior to *never* highlight empty
cells. This fixes the regression, but also means slightly different
behavior, compared to pre-fa2d9f86996467ba33cc381f810ea966a4323381.

The other alternative is to always highlight all empty cells. But,
since I personally like the fact that we’re skipping trailing empty
cells, I prefer the approach taken by this patch.
2022-08-31 19:14:18 +02:00
Daniel Eklöf
ccef435736
ci: codespell: ignore ‘zar’ (user who contributed) 2022-08-31 19:14:03 +02:00
Daniel Eklöf
c753cf8f45
url-mode: connect osc-8 links only when both ID and URI matches
Before this, two OSC-8 links with a matching ID would be connected
even if their URIs weren’t the same. This is against the spec:

  The same id is only used for connecting character cells whose URIs
  is also the same. Character cells pointing to different URIs should
  never be underlined together when hovering over.

https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#hover-underlining-and-the-id-parameter
2022-08-30 17:55:14 +02:00
Daniel Eklöf
4dd63bcc91
Merge branch 'grid-reflow-viewport-crash-regression'
Closes #1074
2022-08-30 17:52:55 +02:00
Daniel Eklöf
524474728a
changelog: crash when resizing window, or scrolling 2022-08-29 21:04:56 +02:00
Daniel Eklöf
454c82f0f5
grid: reflow: assert there aren’t any open URIs on the last row 2022-08-29 21:03:21 +02:00
Daniel Eklöf
5c86358cd1
grid: reflow: don’t line-wrap the last row
Before this patch, we would line-wrap the last row, just like any
other row, and then afterwards try to reverse this, by adjusting the
offset and free:ing and NULL:ing the "last row".

The problem with this is if the scrollback is full. In this case, the
row we’re freeing is the first row in the scrollback history. This
means we’ll crash as soon as the viewport is moved to the top of the
scrollback.

The fix is fairly, simple. Skip the post-processing logic, and instead
detect when we’re line-wrapping the last row, and skip the call to
line_wrap().

This way, the last row in the new grid corresponds to the last row in
the old grid.
2022-08-29 20:47:33 +02:00
Daniel Eklöf
13281f327b
grid: when setting the new viewport, ensure it’s correctly bounded
Do this by using scrollback relative coordinates, and ensure the new
viewport is not larger than (grid_rows - screen_rows), as that would
mean the viewport crosses the scrollback wrap-around.
2022-08-29 20:46:19 +02:00
Daniel Eklöf
d5df86f785
selection: mark_selected_region(): use an enum to encode how the cells are to be updated 2022-08-26 21:07:20 +02:00
Daniel Eklöf
db2737b96a
selection: restore <= 1.12 behavior in block selection wrt empty cells
That is, highlight empty cells, regardless of whether they’re trailing
or not.
2022-08-26 17:48:00 +02:00
Daniel Eklöf
4f06d413e2
ci (sr.ht): pull directly from git.sr.ht 2022-08-23 16:38:39 +02:00
Daniel Eklöf
21ab16239d
selection: once again highlight non-trailing empty cells
Foot 1.13.0 introduced a regression where non-trailing empty cells
were highlighted inconsistently (cells that shouldn’t be highlighted,
were, seemingly at random).

86663522d5 “fixed” this by never
highlighting *any* empty cells.

This meant the behavior, compared to foot 1.12 and earlier,
changed. In foot 1.12 and older versions, non-trailing empty cells
were highlighted, as long as the selection covered at least one of the
trailing non-empty cells.

This patch restores that behavior.

To understand how this works, lets first take a look at how selection
works:

When a selection is made, and updated (i.e. the mouse is dragged, or
the selection is extended through RMB etc), we need to (un)tag and dirty
the cells that are a) newly selected, or b) newly deselected. That is,
we look at the diff between the “old” and the “new” selection, and
only update those cells.

This is for performance reasons: iterating the entire selection is not
feasible with large selections. However, it also means we cannot
reason about empty cells; we simply don’t know if an empty cells is a
trailing empty cell, or a non-trailing one.

Then, when we render a frame, we iterate all the *visible*
and *selected* cells, once again tagging them as selected (this is
needed since a selected cell might have lost its selected tag if the
cell was written to, by the client application, after the selection
was made).

At this point, we *can* reason about empty cells.

So, to restore the highlighting behavior to that of foot 1.12, we do
this:

When working with the selection diffs when a selection is updated, we
don’t special case empty cells at all. Thus, all empty cells covered
by the selection is highlighted, and dirtied.

But, when rendering the frame, we _do_ special case them. The only
difference (compared to foot 1.12) is that we *must*
explicitly *clear* the selection tag, and dirty the empty cells. This
is to ensure the empty cells that were incorrectly highlighted by the
selection update algorithm, isn’t rendered as that.

This does have a slight performance impact, as empty cells are now
always re-rendered. The impact should however be small.
2022-08-22 20:14:06 +02:00
Daniel Eklöf
3cf11bfea9
theme: change default color theme to solarized-dark-normal-brights
This is my variant of the solarized theme, were only the first eight
colors (i.e. the "normal") colors are from the solarized theme. The
remaining eight (the "bright" colors) are brightened versions of the
"normal" colors. This results in a theme that is usually in all
applications, not just those that are "aware" that the terminal color
theme is "solarized".
2022-08-22 13:37:12 +02:00
Daniel Eklöf
86663522d5
selection: never highlight selected, empty cells
This fixes a regression, where empty cells "between" non-empty
cells (i.e. non-trailing empty cells) sometimes were incorrectly
highlighted.

The idea has always been to highlight exactly those cells that will
get extracted when they’re copied. This means we’ve not highlighted
trailing empty cells, but we _have_ highlighted other empty cells,
since they are converted to spaces when copied (whereas trailing empty
cells are skipped).

fa2d9f8699 changed how a selection is
updated. That is, which cells gets marked as selected, and which ones
gets unmarked.

Since we no longer walk all the cells, but instead work with pixman
regions representing selection diffs, we can no longer determine (with
certainty) which empty cells should be selected and which shouldn’t.

Before this patch (but after
fa2d9f8699), we sometimes incorrectly
highlighted empty cells that should not have been highlighted. This
happened when we’ve first (correctly) highlighted a region of empty
cells, but then shrink the selection such that all those empty cells
should be de-selected.

This patch changes the selection behavior to *never* highlight empty
cells. This fixes the regression, but also means slightly different
behavior, compared to pre-fa2d9f86996467ba33cc381f810ea966a4323381.

The other alternative is to always highlight all empty cells. But,
since I personally like the fact that we’re skipping trailing empty
cells, I prefer the approach taken by this patch.
2022-08-22 13:35:52 +02:00
Yorick Peterse
a0942f950d
config: add setting for underline thickness
This adds an "underline-thickness" setting to the "main" section,
similar to the existing "underline-offset" setting. This setting is used
to specify a custom height for regular (= non-cursor) underlines.

Fixes #1136
2022-08-20 22:16:32 +02:00
Daniel Eklöf
65ecb77737
ci: codespell: ignore ‘zar’ (user who contributed) 2022-08-20 18:25:05 +02:00
Daniel Eklöf
20b8ca1601
input: ignore pointer motion events on unknown surfaces
In some cases, the compositor sends a pointer enter event with a NULL
surface. It’s unclear if this is a compositor bug, or a race (where
the compositor sends an enter event on a CSD surface at the same time
foot unmaps the CSDs). Regardless, this causes seat->mouse_focus to be
unset, which triggers a crash in foot on the next pointer motion
event.

This patch does two things:

a) log a warning when we receive a pointer event with a NULL surface
b) ignore motion events where seat->mouse_focus is NULL
2022-08-12 16:15:55 +02:00
Daniel Eklöf
5697348b46
wayland: #ifdef on XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION
This enables us to compile against wayland-protocols < 1.25
2022-08-12 16:15:53 +02:00
Daniel Eklöf
1cf22846a0
render: apply a dimmed overlay while in Unicode input mode 2022-08-12 16:15:50 +02:00
Daniel Eklöf
157b64098a
changelog: add new ‘unreleased’ section 2022-08-12 16:15:47 +02:00
Daniel Eklöf
45803791cf
input: ignore pointer motion events on unknown surfaces
In some cases, the compositor sends a pointer enter event with a NULL
surface. It’s unclear if this is a compositor bug, or a race (where
the compositor sends an enter event on a CSD surface at the same time
foot unmaps the CSDs). Regardless, this causes seat->mouse_focus to be
unset, which triggers a crash in foot on the next pointer motion
event.

This patch does two things:

a) log a warning when we receive a pointer event with a NULL surface
b) ignore motion events where seat->mouse_focus is NULL
2022-08-12 16:13:25 +02:00
Daniel Eklöf
eafff70439
wayland: #ifdef on XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION
This enables us to compile against wayland-protocols < 1.25
2022-08-12 16:12:36 +02:00
Daniel Eklöf
e249b52abd
render: apply a dimmed overlay while in Unicode input mode 2022-08-08 16:31:28 +02:00
Daniel Eklöf
a36848a4ad
changelog: add new ‘unreleased’ section 2022-08-07 09:38:25 +02:00
Daniel Eklöf
8b6e6f1073
Merge branch 'releases/1.13' 2022-08-07 09:38:01 +02:00
Daniel Eklöf
e0465d3a7a
meson: bump version to 1.13.0 2022-08-07 09:32:02 +02:00
Daniel Eklöf
e3eefdacde
changelog: prepare for 1.13.0 2022-08-07 09:31:56 +02:00
Daniel Eklöf
aaf5894ad9
grid: get rid of empty row at the bottom after reflowing
When the window is resized and we reflow the text, we ended up
inserting an empty row at the bottom.

This happens whenever the actual last row has a hard linebreak (which
almost always is the case); we then end the reflow with a line break,
causing an extra, empty, row to be allocated and inserted.

This patch fixes this by detecting when:

1) the last row is empty
2) the next to last row has a hard line break

In this case, we roll back the last line break, by adjusting the new
offset we just calculated, and free:ing the empty row.

TODO: it would be nice if we could detect this in the reflow loop
instead, and avoid doing the last line break all together. I haven’t
yet been able to find a way to do this correctly.

Closes #1108
2022-08-05 18:26:37 +02:00
Simon Ser
129e1a9b8e Add support for xdg_toplevel.wm_capabilities
See https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/122
2022-08-04 14:23:03 +02:00
Simon Ser
2f68b421bf Add no-op xdg_toplevel.configure_bounds handler
Next commit uses v5.
2022-08-03 13:50:13 +02:00
Max Gautier
ffdac61e2a
server: Use "normal" socket activation, not inetd
Systemd, when doing socket activation, pass file descriptors in a
non-stable order when there is multiples ones.

But we only use one, so we don't need to identify it, and the file
descriptors always start at 3.
So use 3 for the systemd service.

Source : sd_listen_fds (systemd man pages)

We also need to unset variables systemd pass to socket activated
process, since we don't need them and sub-process (footclient and
theirs forks) could be confused by those.

Closes #1107
2022-07-30 15:13:56 +02:00
Daniel Eklöf
c0a7c7bf0d
config: reset errno before calling getline() again
Related to #1107
2022-07-29 21:27:27 +02:00
Daniel Eklöf
0cbd99710b
unicode-mode: ‘q’ aborts Unicode input mode 2022-07-29 11:56:41 +02:00
Daniel Eklöf
001f96c4e3
unicode-input: move input (key press) handling to unicode_mode_input() 2022-07-29 09:26:42 +02:00
Daniel Eklöf
8967dd9cfe
input: add new Unicode input mode
This mode is activated through the new key-bindings.unicode-input and
search-bindings.unicode-input key bindings.

When active, the user can “build” a Unicode codepoint by typing its
hexadecimal value.

Note that there’s no visual feedback in this mode. This is
intentional. This mode is intended to be a fallback for users that
don’t use an IME.

Closes #1116
2022-07-29 09:26:31 +02:00
Daniel Eklöf
fa2d9f8699
selection: rework how we update a selection
Before this patch, each selection update would result in grid covered
by the selection being walked *three* times. First to “premark” the
area that *will* be selected after the update, then again to unmark
the previous selection (excluding the cells that were premarked - but
the cells are still iterated), and then one more time to finalize the
selection state in the grid.

Furthermore, each time a frame is rendered, the entire selection were
iterated again, to ensure all the cells have their ‘selected’ bit
set.

This quickly gets *very* slow.

This patch takes a completely different approach. Instead of looking
at the selection as a range of cells to iterate, we view it as an
area, or region. Thus, on each update, we have to regions: the region
representing the previous selection, and the region representing the
to-be selection.

By diffing these two regions, we get two new regions: one that
represents the cells that were selected, but aren’t any more, and one
that represents the cells that previously were not selected, but now
will be.

We implement the regions using pixman regions. By subtracting the
current selection from the previous selection, we get the region
representing the cells that are no longer selected, and that should be
unmarked.

By subtracting the previous selection from the current, we get the
region representing the cells that was added to the selection in this
update, and that should be marked.

selection_dirty_cells() is rewritten in a similar manner. We create
pixman regions for the selection, and the current scrollback view. The
intersection represents the (selected) cells that are visible. These
need to iterated and marked as being selected.

Closes #1114
2022-07-28 21:06:58 +02:00
Daniel Eklöf
b8506bbea0
selection: extend: use scrollback relative coordinates
When extending a selection, we determine *how* to extend it (which
endpoint to move, and whether to grow or shrink the selection) by
comparing the extension point with the old start and end coordinates.

For this to work correctly, we need to use scrollback relative
coordinates.

This fixes an issue where extending a very large selection (covering
many pages) sometimes shrunk the selection instead of growing it, or
just misbehaving in general.
2022-07-28 21:06:58 +02:00
Daniel Eklöf
6ebf55572e
selection: foreach_selected_*(): refactor: use grid_row_abs_to_sb() 2022-07-28 21:06:58 +02:00
Daniel Eklöf
a05eaf28bd
selection: selection_on_rows(): use scrollback relative coords
When checking if the current selection intersects with the
region (passed as parameter to the function), use scrollback relative
coordinates.

This fixes an issue where selections crossing the scrollback
wrap-around being misdetected, resulting in either the selection being
canceled while scrolling, even though it wasn’t scrolled out, or the
selection _not_ being canceled, when it _was_ scrolled out.
2022-07-28 21:06:57 +02:00
Daniel Eklöf
632c4839cd
search: find_next(): handle trailing SPACER cells
Make sure to increment match_end_col to account for trailing SPACER
cells after a match.

This fixes an issue where search matches weren’t highlighted correctly
when the match *ends* with a double-width character.
2022-07-28 21:05:06 +02:00
Daniel Eklöf
d79a3b9350
config: add colors.search-box-{no-,}match
Closes #1112
2022-07-28 19:02:56 +02:00
Daniel Eklöf
4873004c37
test: config: test colors.{jump-labels,scrollback-indicator} 2022-07-28 19:02:55 +02:00
Daniel Eklöf
801970aa33
input: kitty: always treat composed characters as ‘printable’
Certain dead key combinations results different escape sequences in
foot, compared to kitty, when the kitty keyboard protocol is used.

        if (composed && is_text)
            key = utf32;
        else {
            key = xkb_keysym_to_utf32(sym_to_use);
            if (key == 0)
                return false;

            /* The *shifted* key. May be the same as the unshifted
             * key - if so, this is filtered out below, when
             * emitting the CSI */
            alternate = xkb_keysym_to_utf32(sym);
        }

If is_text=false, we’ll fall through to the non-composed
logic. is_text is set to true if the character is printable *and*
there aren’t any non-consumed modifiers enabled.

shift+space is one example where shift is *not* consumed (typically -
may be layouts where it is).

As a result, pressing ", followed by shift+space with the
international english keyboard layout (where " is a dead key) results
in different sequences in foot and kitty.

This patch fixes this by always treating composed characters as
printable.

Closes #1120
2022-07-28 19:02:25 +02:00
Daniel Eklöf
4abf46955f
keymap: change alt+escape to emit \E\E instead of a CSI 27 sequence
Closes #1105
2022-07-28 19:02:05 +02:00
Daniel Eklöf
24c2d56804
render: it’s unlikely() the current cell is where the cursor is 2022-07-28 18:55:34 +02:00
Daniel Eklöf
87e4004960
csi: clamp color index for ‘CSI 38/48 ; 5 ; idx m’ sequences
Indexed color values are stored in the cell attributes as color
indices (into the 256-color table). However, the index from the CSI
was not validated in any way, meaning you can do something like this:

  echo -e ‘\e[38:5:1024m CRASH \e[m’

and foot will crash on an out-of-bounds access.

Fix by clamping the color index.

Closes #1111
2022-07-22 10:44:33 +02:00
Daniel Eklöf
0c60bb3f29
grid: reflow: require col count > 0 when skipping line truncation
When reflowing the grid, we truncate lines with a hard linebreak after
the last non-empty cell. This way we don’t reflow trailing empty cells
to a new line when resizing the window to a smaller size.

However, “logical” lines (i.e. those without a hard linebreak)
are *not* truncated. This is to ensure we don’t trim empty cells in
the middle of a logical line spanning multiple physical lines.

Since newly allocated rows are initialized with linebreak=false, we
need to ensure _those_ are still truncated - otherwise all that empty
space under the current prompt will be reflowed.

Note that this is a temporary workaround. The correct solution, I
think, is to track whether a line has been printed to or not, and
simply ignore (not reflow) lines that haven’t yet been touched.
2022-06-30 19:47:18 +02:00
Daniel Eklöf
37f094280b
Revert "grid: invert the default value of ‘linebreak’, from false to true"
This reverts commit cdd46cdf85.
2022-06-30 19:37:01 +02:00
Daniel Eklöf
d58290ea12
input: don’t ignore keyboard enter/leave events when there’s no keymap
The compositor _usually_ sends the keymap event *before* the enter
event. But not always.

Not (yet) having a keymap is not a reason to ignore the enter
event (now, on the other hand, getting a key press/release event
without a keymap is a compositor bug).

Closes #1097
2022-06-28 20:57:48 +02:00
Daniel Eklöf
206e9a1050
selection: wl_data_offer_set_action: use enum values instead of magic integers 2022-06-21 19:46:31 +02:00
Daniel Eklöf
6d4d4502e9
selection: reject dnd offers with unsupported mime-types
We were already doing this, implicitly, *if* the offer was for the
main grid.

This patch makes it more clear that we do not accept the offer.
2022-06-20 20:57:26 +02:00
Daniel Eklöf
0d22e9fa01
selection: explicitly reject *all* dnd offers not targeting the grid 2022-06-20 20:57:23 +02:00
Daniel Eklöf
2e4da6fbf6
selection: ignore drag-and-drops with unsupported mime-types
Specifically, make sure we do *not* call wl_data_offer_receive() with
a NULL mime-type, as this causes libwayland to error out, which in
turn causes foot to exit.

Closes #1092
2022-06-20 20:56:11 +02:00
Daniel Eklöf
bfc53d1e71
shm-formats: #ifdef on formats added in 1.20 2022-06-20 19:20:28 +02:00
Daniel Eklöf
fabffd626b
wayland: log human readable SHM format names in debug builds 2022-06-20 18:58:40 +02:00
Daniel Eklöf
8689389523
key-binding: set BIND_ACTION_KEY_COUNT correctly
When the new promp-prev/next bindings were added, we forgot to update
BIND_ACTION_KEY_COUNT to reflect this.
2022-06-17 18:42:42 +02:00
Daniel Eklöf
bdb79e8b9f
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.

The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.

We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.

In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.

To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.

prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.

prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.

Closes #30
2022-06-16 19:02:10 +02:00
Daniel Eklöf
96f23b4c64
ime: track IME focus independently from keyboard focus
Replace the seat->ime.focused boolean with a terminal instace pointer,
seat->ime_focus.

Set and reset this on ime::enter() and ime::leave() events, and use
this instead of seat->kbd_focus on all other IME events.

This fixes two issues:

a) buggy compositors that sometimes sends an IME enter event without
  first having sent a keyboard enter event.

b) seats may be IME capable while still lacking the keyboard
  capability. Such seats will *always* see IME enter events without a
  corresponding keyboard enter event.
2022-06-15 19:25:33 +02:00
Daniel Eklöf
d852178540
ime: ime_reset_pending_{preedit,commit} is not used outside ime.c 2022-06-15 19:25:32 +02:00
Daniel Eklöf
9dc4f48e7a
vt: tag collision-count check with ‘unlikely’ 2022-06-15 19:25:00 +02:00
Daniel Eklöf
fbcb30bf98
vt: improve key calculation for compose sequences
* Don’t assume 32 bits when rotating the old key. Use the number of
  actual bits available, as determined by CELL_COMB_CHARS_{HI,LO}
* Multiply with magic hash constant

This greatly reduces the number of collisions seen. For example, the
Emoji test file (from the Unicode specification), now has zero
collisions.
2022-06-15 19:25:00 +02:00
Daniel Eklöf
edd68732ad
vt: prevent potential endless loop when finding a slot for a composed character
Composed characters are stored in a tree structure, using a key as
identifier. The key is calculated from the individual characters that
make up the composed character sequence.

Since the address space for keys is limited, collisions may occur. In
this case, we simply increment the key and try again.

It is theoretically possible to saturate the key space, in which case
we’ll get stuck in an endless loop.

Even if the key space isn’t fully saturated, we fairly easy reach a
point where there are so many collisions for each insertion, that
performance drops significantly.

Since key space is limited (it’s not like a hash table that we can
grow), our only option is to limit the number of collisions. If we
can’t find a slot within a hard code amount of collisions, the
character is simply dropped.
2022-06-15 19:25:00 +02:00
Daniel Eklöf
dbe2c0a068
selection: allow HT, VT and FF, disallow NUL in non-bracketed paste mode
This syncs foot with more recent versions of XTerm, where it’s
“disallowedPasteControls” resource has changed its default value to

  BS,DEL,ENQ,EOT,ESC,NULL

Note that we’re already stripping out ENQ,EOT,ESC in all modes.

What does it mean for foot:

* HT, VT and FF are now allowed, regardless of paste mode
* NUL is now stripped in non-bracketed paste mode

Closes #1084
2022-06-15 19:09:40 +02:00
Daniel Eklöf
d3c51b51b7
pgo: slave_spawn(): sync function signature 2022-06-13 16:32:59 +02:00
Daniel Eklöf
8436e6acea
test: config: new test for the new ‘environment’ section 2022-06-13 11:55:24 +02:00
Daniel Eklöf
604a3cdd84
changelog: new ‘environment’ section in foot.ini 2022-06-13 11:55:24 +02:00
Daniel Eklöf
5760bcb3bf
doc: document the new ‘environment’ config section 2022-06-13 11:55:23 +02:00
Daniel Eklöf
755f96321a
config: add a new ‘environment’ section
This section allows the user to define custom environment variables to
be set in the child process:

  [environment]
  name=value
2022-06-13 11:55:23 +02:00
Daniel Eklöf
497c31d9fc
commands: scroll up: simplify viewport clamping logic
When scrolling the viewport up, we need to ensure we don’t go past the
scrollback wrap around.

This was previously done using “custom” logic that tried to calculate
many rows away from the scrollback start the current viewport is.

But this is *exactly* what grid_row_abs_to_sb() does, except it
doesn’t account for uninitialized scrollback.

So, copy the logic from grid_row_abs_to_sb(), but ensure scrollback
start points to valid scrollback data. The maximum number of rows
we’re allowed to scroll is now the same as the current viewport’s
sb-relative coordinate.

Maybe closes #1074
2022-06-13 11:53:37 +02:00
Daniel Eklöf
9567694bab
grid: reflow: don’t trim trailing empty cells from logical lines
When a line in the old grid (the one being reflowed) doesn’t have a
hard linebreak, don’t trim trailing empty cells.

Doing so means we’ll “compress” (remove) empty cells between text
if/when we revert to a larger window size.

The output from neofetch suffers from this; it prints a logo to the
left, and system information to the right. The logo and the system
info column is separated by empty cells (i.e. *not* spaces).

If the window is reduced in size such that the system info is pushed
to a new line, each logo line ends with a number of empty cells. The
next time the window is resized, these empty cells were
ignored (i.e. removed).

That meant that once the window was enlarged again, the system info
column was a) no longer aligned, and b) had been pulled closer to the
logo.

This patch doesn’t special case trailing empty cells when the line
being reflowed doesn’t have a hard linebreak. This means e.g. ‘ls’
output is unaffected.

Closes #1055
2022-06-11 12:02:20 +02:00
Daniel Eklöf
cdd46cdf85
grid: invert the default value of ‘linebreak’, from false to true 2022-06-11 12:02:20 +02:00
Daniel Eklöf
377997be9d
Merge branch 'tokyonight' 2022-06-01 20:01:51 +02:00
Lorenz
e521fe5394
Add 'themes/tokyonight-storm' 2022-06-01 20:01:30 +02:00
Lorenz
b4443c7daa
Add 'themes/tokyonight-night' 2022-06-01 20:01:30 +02:00
Lorenz
45b6d91eef
Add 'themes/tokyonight-day' 2022-06-01 20:01:23 +02:00
Stefan Prosiegel
8d03652a18
themes: add catppuccin 2022-05-25 17:05:36 +02:00
Daniel Eklöf
bc7214cd88
config: use $HOME instead of getpwuid() to retrieve users’s home dir
When searching for foot.ini, use $HOME instead of getpwuid() to
retrieve the user’s home directory.
2022-05-25 17:02:41 +02:00
Daniel Eklöf
7e8b5f9610
main: minor rewording of non-UTF8 locale warnings and errors 2022-05-14 09:14:57 +02:00
Daniel Eklöf
15d45d5704
meson: -Dsystemd-units-dir installs even if systemd is not found
If -Dsystemd-units-dir is explicitly set, then install the systemd
service files regardless of whether systemd is available or not.
2022-05-12 13:46:06 +02:00
Daniel Eklöf
62fe452cc2
meson: add -Dsystemd-units-dir=<path> meson command line option
This allows package maintainers to override the location to which our
systemd service files are installed.

It’s value is an *absolute* path, and *not* relative ${prefix}.

The default is ${systemduserunitdir}.
2022-05-12 13:45:53 +02:00
Daniel Eklöf
56e5855fff
doc: benchmarks: update with desktop results for 1.12.1 (r9) 2022-05-12 13:10:24 +02:00
Daniel Eklöf
834beb966e
doc: benchmarks: update with laptop results for 1.12.1 (r9) 2022-05-12 11:53:08 +02:00
Daniel Eklöf
200c5cbc79
wayland: throttle xdg activation token requests for window urgency
When XDG activation support was added to URL mode, we introduced a
regression, where it is possible to flood the Wayland socket with XDG
activation token requests.

Start foot with “foot -o bell.urgency=yes”, then run:

  while true; do echo -en ‘\a’; done

Finally, switch keyboard focus to another window. Foot crashes.

Throttle the token requests by limiting the number of outstanding
urgency token requests to 1.

Closes #1065
2022-05-11 21:17:52 +02:00
Daniel Eklöf
fc67bff9c0
terminal: move viewport when part of it is scrolled out
If the viewport is at the top of the scrollback, and a program is
scrolling (e.g. by emitting newlines), the viewport needs to be
moved. Otherwise, the top of the viewport will show the *bottom* of
the scrollback (i.e. the newly emitted lines), and the bottom of the
viewport shows the top of the scrollback.

How do we detect if the viewport needs to be moved?

We convert its absolute row number to a scrollback relative row. This
number is also the maximum number of rows we can scroll without the
viewport being scrolled out.

In other words, if the number of rows to scroll is larger than the
viewports scrollback relative row number, the viewport needs to
moved.

How much do we need to move it? The difference between the number of
rows to scroll, and the viewports scrollback relative row number.

Example:

if the viewport is at the very top of the scrollback, its scrollback
relative row number is 0. In this case, it needs to be moved the same
number of rows as is being scrolled.
2022-05-11 17:58:18 +02:00
Lorenz
3431619d07 Themes: Add 'Monokai Pro' theme
Signed-off-by: Lorenz <lorenz@noreply.codeberg.org>
2022-05-09 06:16:05 +02:00
Daniel Eklöf
f14fc120ad
pgo: add xdg_activation_token parameter to spawn() stub 2022-05-06 10:39:49 +02:00
Daniel Eklöf
ea1aac88db
url-mode: add support for XDG activation when opening URLs
First, add a ‘token’ argument to spawn(). When non-NULL, spawn() will
set the ‘XDG_ACTIVATION_TOKEN’ environment variable in the forked
process. If DISPLAY is non-NULL, we also set DESKTOP_STARTUP_ID, for
compatibility with X11 applications. Note that failing to set either
of these environment variables are considered non-fatal - i.e. we
ignore failures.

Next, add a helper function, wayl_get_activation_token(), to generate
an XDG activation token, and call a user-provided callback when it’s
‘done (since token generation is asynchronous). This function takes an
optional ‘seat’ and ‘serial’ arguments - when both are non-NULL/zero,
we set the serial on the token. ‘win’ is a required argument, used to
set the surface on the token.

Re-write wayl_win_set_urgent() to use the new helper function.

Finally, rewrite activate_url() to first try to get an activation
token (and spawn the URL launcher in the token callback). If that
fails, or if we don’t have XDG activation support, spawn the URL
launcher immediately (like before this patch).

Closes #1058
2022-05-05 10:02:28 +02:00
Daniel Eklöf
bd8dd9ff7e
changelog: fix URL in #1047 issue link 2022-04-28 19:29:06 +02:00
Craig Barnes
7045c177fd
commands: fix LOG_DBG() usage in cmd_scrollback_{up,down}
The "end" variable was removed from both of these functions in
commit cb43c58150, but the references to it in the expansion
of LOG_DBG() weren't.
2022-04-27 20:44:14 +02:00
Daniel Eklöf
de201ead2e
changelog: add new ‘unreleased’ section 2022-04-27 20:09:16 +02:00
Daniel Eklöf
0056c3426c
Merge branch 'releases/1.12' 2022-04-27 20:08:55 +02:00
Daniel Eklöf
e95269447f
meson: bump version to 1.12.1 2022-04-27 20:06:09 +02:00
Daniel Eklöf
225f8e659e
changelog: prepare for 1.12.1 2022-04-27 20:05:51 +02:00
Daniel Eklöf
cd513e1761
Merge branch 'master' into releases/1.12 2022-04-27 20:03:53 +02:00
Daniel Eklöf
5308b8cdb8
changelog: changed behavior of “extend-to-word-boundary” 2022-04-27 18:52:08 +02:00
Daniel Eklöf
7630510448
selection: find_word_boundary_right: add “stop-on-space-to-word-boundary”
When true, selection_find_word_boundary_right() behaves as before - it
stops as soon as it encounters a character that isn’t of the
same *type* as the “initial” character (the last character in the
selection).

Take this, for example:

  The Quick Brown Fox

The selection will first stop at the end of “the”, then just *before*
“quick”, then at the end of “quick”. Then just *before* “brown”, and
then at the end of “brown”, and so on.

This suits mouse selections pretty good. But when
selection_find_word_boundary_right() is used to extend a search match,
it’s better to ignore space-to-word character transitions. That is, we
want

  The Quick Brown Fox

to first extend to the end of “the”, then immediately to the end of
“quick”, then to the end of “brown”, and so on.

Setting the ‘stop_to_space_to_word_boundary’ argument to false results
in latter behavior.

This is now done by search, when executing the
“extend-to-word-boundary” and “extend-to-next-whitespace” key
bindings.
2022-04-27 18:44:57 +02:00
Daniel Eklöf
8356dfac2f
Disable debug logging 2022-04-27 18:44:17 +02:00
Daniel Eklöf
32d9895697
term: reset sixel options when hard resetting the terminal state 2022-04-26 21:05:17 +02:00
Daniel Eklöf
aa4c7c5a30
config: add ctrl+shift+v and XF86 paste to SEARCH_CLIPBOARD_PASTE
We now bind ctrl+v, ctrl+shift+v, ctrl+y and XF86Paste to pasting from
the clipboard into the scrollback search buffer.

Why all these? Because we can, and because all are common shortcuts
for pasting:

* ctrl+v: “normal” apps use this by default
* ctrl+shift+v: used in terminals (including foot)
* ctrl+y: Emacs
* XF86Paste: special keyboard key, for pasting
2022-04-26 20:37:30 +02:00
Daniel Eklöf
0e9ebf433b
search: fix infinite loop when highlighting all matches
find_next() did not always terminate correctly, causing
search_matches_next() to never terminate, which finally leads to an
infinite loop when rendering the search overlay surface, while finding
all matches to highlight.

The problem is that find_next(), after having found the initial
matching characters, enters a nested while loop that tries to match
the rest of the search criteria. This inner while loop did not check
if we’ve reached the last cell, and happily continued past
it (eventually wrappping around the scrollback buffer).

Closes #1047
2022-04-26 20:36:28 +02:00
Daniel Eklöf
694938b85b
search: assert that the match is *inside* the new viewport 2022-04-26 19:47:02 +02:00
Daniel Eklöf
c82c6116ed
search: regression: crash when moving viewport
5c4ddebc3c refactored
search_update_selection(), specifically, the logic that moves the
viewport.

It did so by converting the absolute row number (of the match) to
scrollback relative coordinates. This way we could ensure the viewport
wasn’t moved “too much” (e.g. beyond the scrollback start).

However, grid_row_abs_to_sb() and grid_row_sb_to_abs() doesn’t take a
partially filled scrollback into account. This means the row (numbers)
it returns may refer to *uninitialized* rows.

Since:

* The match row itself is valid (we *know* it has text on it)
* We *subtract* from it, when setting the new viewport (to center the
  match on the screen).

it’s only the *upper* part of the new viewport that may be
uninitialized. I.e. we may have adjusted it too much.

So, what we need to do is move the viewport forward until its *first*
row is initialized. Then we know the rest will be too.
2022-04-26 19:32:08 +02:00
Daniel Eklöf
93dcb7dc9c
changelog: typo: space on the wrong side of the parenthesis 2022-04-26 17:52:00 +02:00
Daniel Eklöf
29f07c791e
Merge branch 'sway-sub-surface-damage-workaround'
Closes #1046
2022-04-26 17:44:16 +02:00
Daniel Eklöf
3abb23c81c
changelog: workaround for Sway bug #6960 2022-04-26 17:42:46 +02:00
Daniel Eklöf
398d96fdb2
term: flash: work around Sway sub-surface unmap bug
Unmapping a sub-surface in Sway does not damage the underlying
surface.

As a result, the OSC-555 escape (“flash”) will leave yellow margins on
~every second frame.

Out of sway, river, weston and mutter, only Sway needs this
workaround.

This is a workaround for https://github.com/swaywm/sway/issues/6960

Closes #1046
2022-04-26 17:41:38 +02:00
Daniel Eklöf
1e87dbc4dc
search: work around Sway sub-surface unmap bug
Unmapping a sub-surface in Sway does not damage the underlying
surface.

As a result, "committing" a scrollback search will typically leave
most of the foot window dimmed.

It can be seen when "cancelling" a search as well, but there it's less
obvious - only the margins are left dimmed. This is because cancelling
a search damaged the current viewport (something that shouldn't be
needed).

Out of sway, river, weston and mutter, only Sway needs this
workaround.

This is a workaround for https://github.com/swaywm/sway/issues/6960
2022-04-26 17:41:38 +02:00
Daniel Eklöf
57543c4290
Merge branch 'search-crashes'
Closes #1036
2022-04-26 17:40:58 +02:00
Daniel Eklöf
b94f540113
changelog: search mode not always highlighting all matches correctly 2022-04-26 17:40:20 +02:00
Daniel Eklöf
1b5b1d5d92
changelog: crash when extending selection in search mode 2022-04-26 17:40:00 +02:00
Daniel Eklöf
5c4ddebc3c
search: fix multiple crashes
* When extending the selection to the next word boundary, ensure the
  row numbers are valid:

  - use selection_get_end() when retrieving the current end
    coordinate. This alone fixes a crash where we previously would
    crash in an out-of-bounds array access in grid->rows[], due to
    term->selection.coords.end being unbounded.

  - ensure the new end coordinate is bounded before and after calling
    selection_find_word_boundary_right().

* When moving the viewport (to ensure a new match is visible), make
  sure we don’t end up with the match outside the new viewport.

  Under certain, unusual, circumstances, the moved viewport _still_
  did not contain the match. This resulted in assertions triggering
  later, that assumed the match(es) are *always* visible.

  It’s fairly easy to trigger this one by running foot with e.g.

    foot -o scrollback.lines=0 --window-size-chars 25x3

  and then hitting enter a couple of times, to fill the scrollback
  history (which should consist of a single row in the example above),
  and the do a scrollback search for (part of) the prompt, and keep
  searching backward until it crashes.

  This would happen if calculated (new) viewport had to be adjusted
  (for example, to ensure it didn’t go past the scrollback end).

  This patch changes the logic used when calculating the new
  viewport. Instead of calculating the wanted viewport (match is
  vertically centered) and then trying to adjust it to ensure the new
  viewport is valid, start with a “safe” new viewport value, and then
  determine how much we can move it, if at all, to center the match.

  This is done by using scrollback relative coordinates. In this
  coordinate system, the new viewport must be

    >= 0, and < grid->num_lines - term->rows

  This makes it very easy to limit the amount by which the viewport is
  adjusted.

  As a side-effect, we can remove all the old re-adjustment logic.

* The match iterator no longer special cases the primary match. This
  was needed before, when the search iterator did not handle
  overlapping matches correctly. Now that we do, the iterator is
  guaranteed to find the primary match, and thus we no longer need to
  special case it.

  This fixes a bug where the primary match was returned twice, due to
  the logic checking if a secondary match is the same as the primary
  match was flawed...

Closes #1036
2022-04-25 20:00:47 +02:00
Daniel Eklöf
1d4e1b921d
sixel/terminal: use the new grid and selection APIs
Use grid_row_abs_to_sb() instead of manually “rebasing” row numbers.

Use selection_get_{start,end}() to retrieve the current selection
coordinates.
2022-04-25 20:00:14 +02:00
Daniel Eklöf
6316a5eb0c
selection: add start/end coordinate getters
Internally, selection coordinates are *unbounded* (that is, the row
numbers may be larger than grid->num_rows) while a selection is
ongoing. Only after it has been finalized are the coordinates bounded.

This means it isn’t safe to use term->selection.coords.* directly.
2022-04-25 19:59:23 +02:00
Daniel Eklöf
b4f666118f
grid: add abs-to-sb and sb-to-abs utility function
These functions convert row numbers between absolute coordinates and
“scrollback relative” coordinates.

Absolute row numbers can be used to index into the grid->rows[] array.

Scrollback relative numbers are ordered with the *oldest* row first,
and the *newest* row last. That is, in these coordinates, row 0 is the
*first* (oldest) row in the scrollback history, and row N is the
*last* (newest) row.

Scrollback relative numbers are used when we need to sort things after
their age, when determining if something has scrolled out, or when
limiting an operation to ensure we don’t go past the scrollback
wrap-around.
2022-04-25 19:57:18 +02:00
Daniel Eklöf
a26eb1ea09
input: assert serial received from compositor is non-zero 2022-04-24 20:18:51 +02:00
Daniel Eklöf
9c0f1a671c
selection: assert serial is non-zero before copying data to the clipboard 2022-04-24 20:18:51 +02:00
Daniel Eklöf
312f0dbcfd
changelog: scrollback mode freezing, with 100% CPU 2022-04-24 20:18:49 +02:00
Daniel Eklöf
1d48b7b77c
search: matches_next: assert start’s ‘col’ is valid 2022-04-24 20:17:37 +02:00
Daniel Eklöf
082e242ce5
search: matches_next: stop searching when start.row >= term->rows
As this means the last call to sarch_matches_next() found a match at
the bottom of the view, and then set the iter’s *next* start position
to a row outside the view.

This is fine, but we need to handle it, by immediately stopping the
iter.
2022-04-24 20:17:37 +02:00
Daniel Eklöf
d068e821d6
search: matches_next: don’t wrap around grid->num_rows
When bumping the iter’s start.row, we’re working with view-local
coordinates. That is, 0 >= row < term->rows.

This means it is wrong to and with grid->num_rows - 1, because a),
‘row’ should **never** be that big. And b), if we do, we’ll just end
up in an infinite loop, where the next call to matches_next() just
starts over from the beginning again (and eventually hitting the exact
same place that got us started).
2022-04-24 20:17:37 +02:00
Daniel Eklöf
f7c29ee394
search: maches_next: assert match coordinates are valid
* They are within range (i.e. ‘row’ does not exceed term->rows-1)
* ‘end’ comes after ‘start’
2022-04-24 20:17:37 +02:00
Daniel Eklöf
8c0fca30db
selection: find_word_boundary: assert ‘pos’ is valid 2022-04-24 20:17:29 +02:00
Daniel Eklöf
47d1ba58e5
changelog: UI not refreshing when pasting into the scrollback search box 2022-04-24 12:08:23 +02:00
Daniel Eklöf
2cbcfb3159
render: fix refresh logic of pending csd|search|url
Our CSDs, the search-box and URL labels are all implemented using
sub-surfaces, synchronized with the main grid.

This means we *must* commit the main surface as well, when updating
one of these sub-surfaces.

The logic for doing so in the frame callback was flawed, and only
triggered when the main grid was actually dirty.

That is, e.g. search box updates that did not also resulted in grid
updates (for example - pasting a search criteria that doesn’t match),
did not result in a UI refresh.

Closes #1040
2022-04-24 12:04:06 +02:00
Daniel Eklöf
b68d5da71b
search: fix debug log
This has been broken since the forward/backward search logic was
refactored.
2022-04-24 12:03:31 +02:00
Daniel Eklöf
f0f0fac77f
doc: foot.ini: drop empty line after *show-urls-launch* 2022-04-23 20:08:09 +02:00
Daniel Eklöf
155a2e4790
ci: enable -Db_pgo=generate on release builds
Hopefully, this’ll catch missing stubs in pgo/pgo.c in the future.
2022-04-23 11:24:44 +02:00
Daniel Eklöf
1913fb6efd
changelog: hyperlink lists under their corresponding sub-section 2022-04-23 11:13:25 +02:00
Daniel Eklöf
4ca0407945
raedme: add a reference to foot-ctlseq(7) 2022-04-23 11:11:34 +02:00
Daniel Eklöf
ce4fd6df3f
readme: add OSC 22 2022-04-23 11:10:37 +02:00
Daniel Eklöf
ae2999740e
readme: default foot.ini is now installed to /etc/xdg/foot/foot.ini 2022-04-23 11:10:09 +02:00
Daniel Eklöf
18de702aeb
changelog: pgo helper binary build fix (missing key-binding stubs) 2022-04-23 00:50:41 +02:00
Daniel Eklöf
9483a3a7c0
changelog: pgo helper binary build fix (missing key-binding stubs) 2022-04-23 00:49:52 +02:00
Daniel Eklöf
0da081f194
Merge branch 'master' into releases/1.12 2022-04-23 00:46:11 +02:00
Daniel Eklöf
8ceb6e45a4
pgo: add missing stubs for key-binding functions
* key_binding_new_for_term()
* key_binding_unref_term()
2022-04-23 00:44:46 +02:00
Daniel Eklöf
1383def2a0
changelog: convert all issue links to reference links in the 1.12.0 release 2022-04-22 20:05:33 +02:00
Daniel Eklöf
61446df895
Revert "changelog: convert all issue links to reference links in the 1.12.0 release"
This reverts commit 6652a836ad.

We only added the actual links to the 1.12.0 release, meaning all
other issue hyperlinks broke.
2022-04-22 20:02:15 +02:00
Daniel Eklöf
c8a2c8c8b1
Merge branch 'master' into releases/1.12 2022-04-22 18:56:37 +02:00
Daniel Eklöf
6652a836ad
changelog: convert all issue links to reference links in the 1.12.0 release 2022-04-22 18:54:49 +02:00
Daniel Eklöf
e284c764b7
changelog: replace all bug refs with markdown hyperlinks 2022-04-22 18:36:28 +02:00
Daniel Eklöf
2d4d919687
changelog: add new ‘unreleased’ section 2022-04-22 17:19:04 +02:00
Daniel Eklöf
d8c4e21090
Merge branch 'releases/1.12' 2022-04-22 17:18:26 +02:00
212 changed files with 25041 additions and 6440 deletions

View file

@ -1,4 +1,4 @@
image: alpine/latest
image: alpine/edge
packages:
- musl-dev
- eudev-libs
@ -24,7 +24,7 @@ packages:
- font-noto-emoji
sources:
- https://codeberg.org/dnkl/foot
- https://git.sr.ht/~dnkl/foot
# triggers:
# - action: email
@ -43,10 +43,13 @@ tasks:
meson test -C bld/debug --print-errorlogs
- release: |
mkdir -p bld/release
meson --buildtype=minsize -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
meson --buildtype=minsize -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
ninja -C bld/release -k0
meson test -C bld/release --print-errorlogs
- codespell: |
python3 -m venv codespell-venv
source codespell-venv/bin/activate
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
deactivate

View file

@ -23,7 +23,7 @@ packages:
- font-noto-emoji
sources:
- https://codeberg.org/dnkl/foot
- https://git.sr.ht/~dnkl/foot
# triggers:
# - action: email
@ -38,6 +38,6 @@ tasks:
meson test -C bld/debug --print-errorlogs
- release: |
mkdir -p bld/release
meson --buildtype=minsize -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
meson --buildtype=minsize -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
ninja -C bld/release -k0
meson test -C bld/release --print-errorlogs

View file

@ -19,7 +19,7 @@ packages:
- noto-emoji
sources:
- https://codeberg.org/dnkl/foot
- https://codeberg.org/dnkl/foot.git
# triggers:
# - action: email
@ -29,11 +29,12 @@ sources:
tasks:
- fcft: |
cd foot/subprojects
git clone https://codeberg.org/dnkl/tllist.git
git clone https://codeberg.org/dnkl/fcft.git
cd ../..
- debug: |
mkdir -p bld/debug
meson --buildtype=debug -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug
meson setup --buildtype=debug -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug
ninja -C bld/debug -k0
meson test -C bld/debug --print-errorlogs
bld/debug/foot --version
@ -41,7 +42,7 @@ tasks:
- release: |
mkdir -p bld/release
meson --buildtype=minsize -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
meson setup --buildtype=minsize -Db_pgo=generate -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
ninja -C bld/release -k0
meson test -C bld/release --print-errorlogs
bld/release/foot --version

View file

@ -0,0 +1,127 @@
name: Bug Report
description: File a bug report
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Please provide as many details as possible, we must be able to
understand the bug in order to fix it.
Don't forget to search the issue tracker in case there is
already an open issue for the bug you found.
- type: input
id: version
attributes:
label: Foot Version
description: "The output of `foot --version`"
placeholder: "foot version: 1.17.2-11-gc4f13809 (May 20 2024, branch 'master') +pgo +ime +graphemes -assertions"
validations:
required: true
- type: input
id: term
attributes:
label: TERM environment variable
description: "The output of `echo $TERM`"
placeholder: "foot"
validations:
required: true
- type: input
id: compositor
attributes:
label: Compositor Name and Version
description: "The name and version of your compositor"
placeholder: "sway version 1.9"
validations:
required: true
- type: input
id: distro
attributes:
label: Distribution
description: "The name of the Linux distribution, or BSD flavor, you are running. And, if applicable, the version"
placeholder: "Fedora Workstation 41"
validations:
required: true
- type: input
id: multiplexer
attributes:
label: Terminal multiplexer
description: "Terminal multiplexers are terminal emulators themselves, therefore the issue may be in the multiplexer, not foot. Please list which multiplexer(s) you use here (and mention in the problem description below if the issue only occurs in the multiplexer, but not in bare metal foot)"
placeholder: "tmux, zellij"
- type: input
id: application
attributes:
label: Shell, TUI, application
description: "Application(s) in which the problem occurs (list all known)"
placeholder: "bash, neovim"
- type: checkboxes
id: server
attributes:
label: Server/standalone mode
description: Does the issue occur in foot server, or standalone mode, or both? Note that you **cannot** test standalone mode by manually running `foot` from a `footclient` instance, since then the standalone foot will simply inherit the server process' context.
options:
- label: Standalone
- label: Server
- type: textarea
id: config
attributes:
label: Foot config
description: Paste your entire `foot.ini` here (do not forget to sanitize it!)
render: ini
validations:
required: true
- type: textarea
id: repro
attributes:
label: Description of Bug and Steps to Reproduce
description: |
Exactly what steps can someone else take to see the bug
themselves? What happens?
validations:
required: true
- type: markdown
attributes:
value: |
Please provide as many details as possible, we must be able to
understand the bug in order to fix it.
Other software
--------------
**Compositors**: have you tested other compositors? Does the
issue happen on all of them, or only your main compositor?
**Terminal multiplexers**: are you using tmux, zellij, or any
other terminal multiplexer? Does the bug happen in a plain
foot instance?
**IME** do you use an IME (e.g. fcitx5, ibus etc)? Which one?
Does the bug happen if you disable the IME?
Obtaining logs and stacktraces
------------------------------
Use a [debug
build](https://codeberg.org/dnkl/foot/src/branch/master/INSTALL.md#debug-build)
of foot if possible, to get a better quality stacktrace in
case of a crash.
Run foot with logging enabled:
```sh
foot -d info 2> foot.log
```
In many cases, tracing the Wayland communication is extremely helpful:
```sh
WAYLAND_DEBUG=1 foot -d info 2> foot.wayland.log
```
Reproduce your problem as quickly as possible, and then exit foot.
- type: textarea
id: logs
attributes:
label: Relevant logs, stacktraces, etc.
- type: markdown
attributes:
value: |
Please attach files instead of pasting the logs, if the logs are large

View file

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: IRC
url: https://web.libera.chat/?channels=#foot
about: Join the IRC channel for foot-related discussion and support

View file

@ -0,0 +1,26 @@
name: Feature Request
description: Request a new feature
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Please search the man page (`foot.ini(5)` and `foot(1)`);
maybe the feature already exists?
If the feature does not exist in your installed version of
foot, please check the **latest** version of foot; maybe the
feature has already been added?
Please describe your feature request in as much details as
possible. Describe your use case. Explain why the existing
feature set is not sufficient. Foot is (trying to be) a
minimalistic terminal emulator; explain how your desired
feature does not add bloat.
- type: textarea
id: request
attributes:
label: Describe your feature request
validations:
required: true

View file

@ -1,112 +0,0 @@
stages:
- build
variables:
GIT_SUBMODULE_STRATEGY: normal
before_script:
- apk update
- apk add musl-dev linux-headers meson ninja gcc scdoc ncurses
- apk add libxkbcommon-dev pixman-dev freetype-dev fontconfig-dev harfbuzz-dev utf8proc-dev
- apk add wayland-dev wayland-protocols
- apk add git
- apk add check-dev
- apk add ttf-hack font-noto-emoji
debug-x64:
image: alpine:edge
stage: build
script:
- cd subprojects
- git clone https://codeberg.org/dnkl/fcft.git
- cd ..
- mkdir -p bld/debug
- cd bld/debug
- meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../
- ninja -v -k0
- ninja -v test
artifacts:
reports:
junit: bld/debug/meson-logs/testlog.junit.xml
debug-x64-no-grapheme-clustering:
image: alpine:edge
stage: build
script:
- apk del harfbuzz harfbuzz-dev utf8proc utf8proc-dev
- cd subprojects
- git clone https://codeberg.org/dnkl/fcft.git
- cd ..
- mkdir -p bld/debug
- cd bld/debug
- meson --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../../
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
artifacts:
reports:
junit: bld/debug/meson-logs/testlog.junit.xml
release-x64:
image: alpine:edge
stage: build
script:
- cd subprojects
- git clone https://codeberg.org/dnkl/fcft.git
- cd ..
- mkdir -p bld/release
- cd bld/release
- meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
artifacts:
reports:
junit: bld/release/meson-logs/testlog.junit.xml
debug-x86:
image: i386/alpine:edge
stage: build
script:
- cd subprojects
- git clone https://codeberg.org/dnkl/fcft.git
- cd ..
- mkdir -p bld/debug
- cd bld/debug
- meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
artifacts:
reports:
junit: bld/debug/meson-logs/testlog.junit.xml
release-x86:
image: i386/alpine:edge
stage: build
script:
- cd subprojects
- git clone https://codeberg.org/dnkl/fcft.git
- cd ..
- mkdir -p bld/release
- cd bld/release
- meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
artifacts:
reports:
junit: bld/release/meson-logs/testlog.junit.xml
codespell:
image: alpine:edge
stage: build
script:
- apk add python3
- apk add py3-pip
- pip install codespell
- codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd

139
.woodpecker.yaml Normal file
View file

@ -0,0 +1,139 @@
# -*- yaml -*-
steps:
- name: pychecks
when:
- event: [manual, pull_request]
- event: [push, tag]
branch: [master, releases/*]
image: alpine:edge
commands:
- apk add openssl
- apk add python3
- apk add py3-pip
- python3 -m venv venv
- source venv/bin/activate
- python -m pip install --upgrade pip
- pip install codespell
- pip install mypy
- pip install ruff
- codespell
- mypy
- ruff check
- deactivate
- name: subprojects
when:
- event: [manual, pull_request]
- event: [push, tag]
branch: [master, releases/*]
image: alpine:edge
commands:
- apk add git
- mkdir -p subprojects && cd subprojects
- git clone https://codeberg.org/dnkl/tllist.git
- git clone https://codeberg.org/dnkl/fcft.git
- cd ..
- name: x64
when:
- event: [manual, pull_request]
- event: [push, tag]
branch: [master, releases/*]
depends_on: [subprojects]
image: alpine:edge
commands:
- apk update
- apk add musl-dev linux-headers meson ninja gcc clang scdoc ncurses
- apk add libxkbcommon-dev pixman-dev freetype-dev fontconfig-dev harfbuzz-dev utf8proc-dev
- apk add wayland-dev wayland-protocols
- apk add git
- apk add check-dev
- apk add ttf-hack font-noto-emoji
# Debug
- mkdir -p bld/debug-x64
- cd bld/debug-x64
- meson setup --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..
# Release (gcc)
- mkdir -p bld/release-x64
- cd bld/release-x64
- meson setup --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..
# Release (clang)
- mkdir -p bld/release-x64-clang
- cd bld/release-x64-clang
- CC=clang meson setup --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..
# no grapheme clustering
- apk del harfbuzz harfbuzz-dev utf8proc utf8proc-dev
- mkdir -p bld/debug
- cd bld/debug
- meson setup --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..
- name: x86
when:
- event: [manual, pull_request]
- event: [push, tag]
branch: [master, releases/*]
depends_on: [subprojects]
image: i386/alpine:edge
commands:
- apk update
- apk add musl-dev linux-headers meson ninja gcc clang scdoc ncurses
- apk add libxkbcommon-dev pixman-dev freetype-dev fontconfig-dev harfbuzz-dev utf8proc-dev
- apk add wayland-dev wayland-protocols
- apk add git
- apk add check-dev
- apk add ttf-hack font-noto-emoji
# Debug
- mkdir -p bld/debug-x86
- cd bld/debug-x86
- meson setup --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..
# Release (gcc)
- mkdir -p bld/release-x86
- cd bld/release-x86
- meson setup --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..
# Release (clang)
- mkdir -p bld/release-x86-clang
- cd bld/release-x86-clang
- CC=clang meson setup --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..

View file

@ -1,108 +0,0 @@
pipeline:
codespell:
when:
branch:
- master
- releases/*
image: alpine:latest
commands:
- apk add python3
- apk add py3-pip
- pip install codespell
- codespell -Lser,doas README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd
subprojects:
when:
branch:
- master
- releases/*
image: alpine:latest
commands:
- apk add git
- mkdir -p subprojects && cd subprojects
- git clone https://codeberg.org/dnkl/tllist.git
- git clone https://codeberg.org/dnkl/fcft.git
- cd ..
x64:
when:
branch:
- master
- releases/*
group: build
image: alpine:latest
commands:
- apk update
- apk add musl-dev linux-headers meson ninja gcc scdoc ncurses
- apk add libxkbcommon-dev pixman-dev freetype-dev fontconfig-dev harfbuzz-dev utf8proc-dev
- apk add wayland-dev wayland-protocols
- apk add git
- apk add check-dev
- apk add ttf-hack font-noto-emoji
# Debug
- mkdir -p bld/debug-x64
- cd bld/debug-x64
- meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..
# Release
- mkdir -p bld/release-x64
- cd bld/release-x64
- meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..
# no grapheme clustering
- apk del harfbuzz harfbuzz-dev utf8proc utf8proc-dev
- mkdir -p bld/debug
- cd bld/debug
- meson --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..
x86:
when:
branch:
- master
- releases/*
group: build
image: i386/alpine:latest
commands:
- apk update
- apk add musl-dev linux-headers meson ninja gcc scdoc ncurses
- apk add libxkbcommon-dev pixman-dev freetype-dev fontconfig-dev harfbuzz-dev utf8proc-dev
- apk add wayland-dev wayland-protocols
- apk add git
- apk add check-dev
- apk add ttf-hack font-noto-emoji
# Debug
- mkdir -p bld/debug-x86
- cd bld/debug-x86
- meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..
# Release
- mkdir -p bld/release-x86
- cd bld/release-x86
- meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
- ninja -v -k0
- ninja -v test
- ./foot --version
- ./footclient --version
- cd ../..

File diff suppressed because it is too large Load diff

83
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,83 @@
# Foot Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
Participants in the foot community are expected to uphold the described
standards not only in official community spaces (issue trackers, IRC channels,
etc.) but in all public spaces. The Code of Conduct however does acknowledge
that people are fallible and that it is possible to truly correct a past
pattern of unacceptable behavior. That is to say, the scope of the Code of
Conduct does not necessarily extend into the distant past.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported to the community leaders responsible for enforcement
at [daniel@ekloef.se](mailto:daniel@ekloef.se). All complaints will
be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
The consequences for Code of Conduct violations will be decided upon and
enforced by community leaders. These may include a formal warning, a temporary
ban from community spaces, a permanent ban from community spaces, etc.
There are no hard and fast rules for exactly what behavior in which space will
result in what consequences, it is up to the community leaders to enforce the
Code of Conduct in a way that they believe best promotes a healthy community.
## Attribution
This Code of Conduct is adapted from the
[Contributor Covenant](https://www.contributor-covenant.org/),
version 2.1, available at
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.

View file

@ -44,6 +44,9 @@ subprojects.
* pixman
* wayland (_client_ and _cursor_ libraries)
* xkbcommon
* utf8proc (_optional_, needed for grapheme clustering)
* libutempter (_optional_, needed for utmp logging on Linux)
* ulog (_optional_, needed for utmp logging on FreeBSD)
* [fcft](https://codeberg.org/dnkl/fcft) [^1]
[^1]: can also be built as subprojects, in which case they are
@ -91,24 +94,24 @@ A note on terminfo; the terminfo database exposes terminal
capabilities to the applications running inside the terminal. As such,
it is important that the terminfo used reflects the actual
terminal. Using the `xterm-256color` terminfo will, in many cases,
work, but I still recommend using foots own terminfo. There are two
work, but I still recommend using foot's own terminfo. There are two
reasons for this:
* foots terminfo contains a couple of non-standard capabilities,
* foot's terminfo contains a couple of non-standard capabilities,
used by e.g. tmux.
* New capabilities added to the `xterm-256color` terminfo could
potentially break foot.
* There may be future additions or changes to foots terminfo.
* There may be future additions or changes to foot's terminfo.
As of ncurses 2021-07-31, ncurses includes a version of foots
As of ncurses 2021-07-31, ncurses includes a version of foot's
terminfo. **The recommendation is to use those**, and only install the
terminfo definitions from this git repo if the systems ncurses
terminfo definitions from this git repo if the system's ncurses
predates 2021-07-31.
But, note that the foot terminfo definitions in ncurses lack the
But, note that the foot terminfo definitions in ncurses' lack the
non-standard capabilities. This mostly affects tmux; without them,
`terminal-overrides` must be configured to enable truecolor
support. For this reason, it _is_ possible to install “our” terminfo
support. For this reason, it _is_ possible to install "our" terminfo
definitions as well, either in a non-default location, or under a
different name.
@ -121,10 +124,10 @@ details.
Installing them under a different name generally works well, but will
break applications that check if `$TERM == foot`.
Hence the recommendation to simply use ncurses terminfo definitions
Hence the recommendation to simply use ncurses' terminfo definitions
if available.
If packaging “our” terminfo definitions, I recommend doing that as a
If packaging "our" terminfo definitions, I recommend doing that as a
separate package, to allow them to be installed on remote systems
without having to install foot itself.
@ -140,15 +143,19 @@ 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 |
| 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 |
| `-Dterminfo-base-name` | string | `-Ddefault-terminfo` | Base name of the generated terminfo files | None |
| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None |
| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None |
| `-Dutmp-backend` | combo | `auto` | Which utmp backend to use (`none`, `libutempter`, `ulog` or `auto`) | libutempter or ulog |
| `-Dutmp-default-helper-path` | string | `auto` | Default path to utmp helper binary. `auto` selects path based on `utmp-backend` | None |
Documentation includes the man pages, readme, changelog and license
files.
@ -159,9 +166,19 @@ under a different name. Setting this changes the default value of
`$TERM`, and the names of the terminfo files (if
`-Dterminfo=enabled`).
`-Dcustom-terminfo-install-location` enables foots terminfo to
co-exist with ncurses version, without changing the terminfo
names. The idea is that you install foots terminfo to a non-standard
If you want foot to use the terminfo files from ncurses, but still
package foot's own terminfo files under a different name, you can use
the `-Dterminfo-base-name` option. Many distributions use the name
`foot-extra`, and thus it might be a good idea to reuse that:
```sh
meson ... -Ddefault-terminfo=foot -Dterminfo-base-name=foot-extra
```
(or just leave out `-Ddefault-terminfo`, since it defaults to `foot` anyway).
Finally, `-Dcustom-terminfo-install-location` enables foot's terminfo
to co-exist with ncurses' version, without changing the terminfo
names. The idea is that you install foot's terminfo to a non-standard
location, for example `/usr/share/foot/terminfo`. Use
`-Dcustom-terminfo-install-location` to tell foot where the terminfo
is. Foot will set the environment variable `TERMINFO` to this value
@ -177,7 +194,7 @@ in the meson build. It does **not** change the default value of
`TERM`, and it does **not** disable `TERMINFO`, if
`-Dcustom-terminfo-install-location` has been set. Use this if
packaging the terminfo definitions in a separate package (and the
build script isnt shared with the foot package).
build script isn't shared with the 'foot' package).
Example:
@ -252,7 +269,7 @@ reason there are a number of helper scripts available.
scripts in the `pgo` directory to do a complete PGO build. This script
is intended to be used when doing manual builds.
Note that all “full” PGO builds (which `auto` will prefer, if
Note that all "full" PGO builds (which `auto` will prefer, if
possible) **require** `LC_CTYPE` to be set to an UTF-8 locale. This is
**not** done automatically.
@ -323,6 +340,7 @@ We will use the `pgo` binary along with input corpus generated by
`scripts/generate-alt-random-writes.py`:
```sh
./utils/xtgettcap
./footclient --version
./foot --version
tmp_file=$(mktemp)
@ -345,13 +363,14 @@ rm ${tmp_file}
```
The first step, running `./foot --version` and `./footclient
--version` might seem unnecessary, but is needed to ensure we have
_some_ profiling data for functions not covered by the PGO helper
binary. Without this, the final link phase will fail.
--version` etc, might seem unnecessary, but is needed to ensure we
have _some_ profiling data for functions not covered by the PGO helper
binary, for **all** binaries. Without this, the final link phase will
fail.
The snippet above then creates an (empty) temporary file. Then, it
runs a script that generates random escape sequences (if you cat
`${tmp_file}` in a terminal, youll see random colored characters all
`${tmp_file}` in a terminal, you'll see random colored characters all
over the screen). Finally, we feed the randomly generated escape
sequences to the PGO helper. This is what generates the profiling data
used in the next step.
@ -367,6 +386,7 @@ This method requires a running Wayland session.
We will use the script `scripts/generate-alt-random-writes.py`:
```sh
./utils/xtgettcap
./footclient --version
foot_tmp_file=$(mktemp)
./foot \
@ -380,9 +400,10 @@ rm ${foot_tmp_file}
You should see a foot window open up, with random colored text. The
window should close after ~1-2s.
The first step, `./footclient --version` might seem unnecessary, but
is needed to ensure we have _some_ profiling data for
`footclient`. Without this, the final link phase will fail.
The first step, `./utils/xtgettcap && ./footclient --version`
might seem unnecessary, but is needed to ensure we have _some_
profiling data for **all** binaries we build. Without this, the final
link phase will fail.
##### Use the generated PGO data
@ -429,10 +450,10 @@ sed 's/@default_terminfo@/foot/g' foot.info | \
tic -o <output-directory> -x -e foot,foot-direct -
```
Where _”output-directory”_ **must** match the value passed to
Where _"output-directory"_ **must** match the value passed to
`-Dcustom-terminfo-install-location` in the foot build. If
`-Dcustom-terminfo-install-location` has not been set, `-o
<output-directoty>` can simply be omitted.
<output-directory>` can simply be omitted.
Or, if packaging:

245
README.md
View file

@ -3,10 +3,8 @@
The fast, lightweight and minimalistic Wayland terminal emulator.
[![CI status](https://ci.codeberg.org/api/badges/dnkl/foot/status.svg)](https://ci.codeberg.org/dnkl/foot)
[![Pipeline status](https://gitlab.com/dnkl/foot/badges/master/pipeline.svg)](https://gitlab.com/dnkl/foot/commits/master)
[![builds.sr.ht status](https://builds.sr.ht/~dnkl/foot.svg)](https://builds.sr.ht/~dnkl/foot?)
[![Packaging status](https://repology.org/badge/vertical-allrepos/foot.svg)](https://repology.org/project/foot/versions)
[![Packaging status](https://repology.org/badge/vertical-allrepos/foot.svg?columns=4)](https://repology.org/project/foot/versions)
## Index
@ -22,8 +20,13 @@ The fast, lightweight and minimalistic Wayland terminal emulator.
1. [Normal mode](#normal-mode)
1. [Scrollback search](#scrollback-search)
1. [Mouse](#mouse)
1. [Touchscreen](#touchscreen)
1. [Server (daemon) mode](#server-daemon-mode)
1. [URLs](#urls)
1. [Shell integration](#shell-integration)
1. [Current working directory](#current-working-directory)
1. [Jumping between prompts](#jumping-between-prompts)
1. [Piping last command's output](#piping-last-command-s-output)
1. [Alt/meta](#alt-meta)
1. [Backspace](#backspace)
1. [Keypad](#keypad)
@ -32,6 +35,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator.
1. [Programmatically checking if running in foot](#programmatically-checking-if-running-in-foot)
1. [XTGETTCAP](#xtgettcap)
1. [Credits](#Credits)
1. [Code of Conduct](#code-of-conduct)
1. [Bugs](#bugs)
1. [Contact](#contact)
1. [IRC](#irc)
@ -57,10 +61,11 @@ The fast, lightweight and minimalistic Wayland terminal emulator.
* IME (via `text-input-v3`)
* Multi-seat
* True Color (24bpp)
* [Styled and colored underlines](https://sw.kovidgoyal.net/kitty/underlines/)
* [Synchronized Updates](https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/2) support
* [Sixel image support](https://en.wikipedia.org/wiki/Sixel)
![wow](doc/sixel-wow.png "Sixel screenshot")
![tux-with-foot](doc/sixel-tux-foot.png "Sixel screenshot")
# Installing
@ -73,7 +78,7 @@ See [INSTALL.md](INSTALL.md).
**foot** can be configured by creating a file
`$XDG_CONFIG_HOME/foot/foot.ini` (defaulting to
`~/.config/foot/foot.ini`). A template for that can usually be found
in `/usr/share/foot/foot.ini` or
in `/etc/xdg/foot/foot.ini` or
[here](https://codeberg.org/dnkl/foot/src/branch/master/foot.ini).
Further information can be found in foot's man page `foot.ini(5)`.
@ -146,10 +151,10 @@ These are the default shortcuts. See `man foot.ini` and the example
: Start a scrollback search
<kbd>ctrl</kbd>+<kbd>+</kbd>, <kbd>ctrl</kbd>+<kbd>=</kbd>
: Increase font size by 0,5pt
: Increase font size
<kbd>ctrl</kbd>+<kbd>-</kbd>
: Decrease font size by 0,5pt
: Decrease font size
<kbd>ctrl</kbd>+<kbd>0</kbd>
: Reset font size
@ -157,13 +162,24 @@ These are the default shortcuts. See `man foot.ini` and the example
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>n</kbd>
: Spawn a new terminal. If the shell has been [configured to emit the
OSC 7 escape
sequence](https://codeberg.org/dnkl/foot/wiki#user-content-how-to-configure-my-shell-to-emit-the-osc-7-escape-sequence),
sequence](https://codeberg.org/dnkl/foot/wiki#user-content-spawning-new-terminal-instances-in-the-current-working-directory),
the new terminal will start in the current working directory.
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>o</kbd>
: Enter URL mode, where all currently visible URLs are tagged with a
jump label with a key sequence that will open the URL.
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>
: Enter Unicode input mode.
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>z</kbd>
: Jump to the previous, currently not visible, prompt. Requires [shell
integration](https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts).
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>x</kbd>
: Jump to the next prompt. Requires [shell
integration](https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts).
#### Scrollback search
@ -182,7 +198,7 @@ These are the default shortcuts. See `man foot.ini` and the example
: Same as <kbd>ctrl</kbd>+<kbd>w</kbd>, except that the only word
separating characters are whitespace characters.
<kbd>ctrl</kbd>+<kbd>v</kbd>
<kbd>ctrl</kbd>+<kbd>v</kbd>, <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>v</kbd>, <kbd>ctrl</kbd>+<kbd>y</kbd>, <kbd>XF86Paste</kbd>
: Paste from clipboard into the search buffer.
<kbd>shift</kbd>+<kbd>insert</kbd>
@ -221,7 +237,11 @@ These are the default shortcuts. See `man foot.ini` and the example
under the pointer up to, and until, the next space characters.
<kbd>left</kbd> - **triple-click**
: Selects the entire row
: Selects the everything between enclosing quotes, or the entire row
if not inside a quote.
<kbd>left</kbd> - **quad-click**
: Selects the entire row.
<kbd>middle</kbd>
: Paste from _primary_ selection
@ -231,9 +251,27 @@ These are the default shortcuts. See `man foot.ini` and the example
selection, while hold-and-drag allows you to interactively resize
the selection.
<kbd>ctrl</kbd>+<kbd>right</kbd>
: Extend the current selection, but force it to be character wise,
rather than depending on the original selection mode.
<kbd>wheel</kbd>
: Scroll up/down in history
<kbd>ctrl</kbd>+<kbd>wheel</kbd>
: Increase/decrease font size
### Touchscreen
<kbd>tap</kbd>
: Emulates mouse left button click.
<kbd>drag</kbd>
: Scrolls up/down in history.
: Holding for a while before dragging (time delay can be configured)
emulates mouse dragging with left button held.
## Server (daemon) mode
@ -266,7 +304,7 @@ when starting your Wayland compositor (i.e. logging in to your
desktop), and then run `footclient` instead of `foot` whenever you
want to launch a new terminal.
Foot support socket activation, which means `foot --server` will only be
Foot supports socket activation, which means `foot --server` will only be
started the first time you'll run `footclient`. (systemd user units are
included, but it can work with other supervision suites).
@ -276,10 +314,10 @@ Foot supports URL detection. But, unlike many other terminal
emulators, where URLs are highlighted when they are hovered and opened
by clicking on them, foot uses a keyboard driven approach.
Pressing <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd> enters _“URL
mode_, where all currently visible URLs are underlined, and is
associated with a _“jump-label”_. The jump-label indicates the _key
sequence_ (e.g. **”AF”**) to use to activate the URL.
Pressing <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>o</kbd> enters _"URL
mode"_, where all currently visible URLs are underlined, and is
associated with a _"jump-label"_. The jump-label indicates the _key
sequence_ (e.g. **"AF"**) to use to activate the URL.
The key binding can, of course, be customized, like all other key
bindings in foot. See `show-urls-launch` and `show-urls-copy` in the
@ -296,6 +334,80 @@ Jump label colors, the URL underline color, and the letters used in
the jump label key sequences can be configured.
## Shell integration
### Current working directory
New foot terminal instances (bound to
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>n</kbd> by default) will open in
the current working directory, **if** the shell in the "parent"
terminal reports directory changes.
This is done with the OSC-7 escape sequence. Most shells can be
scripted to do this, if they do not support it natively. See the
[wiki](https://codeberg.org/dnkl/foot/wiki#user-content-spawning-new-terminal-instances-in-the-current-working-directory)
for details.
### Jumping between prompts
Foot can move the current viewport to focus prompts of already
executed commands (bound to
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>z</kbd>/<kbd>x</kbd> by
default).
For this to work, the shell needs to emit an OSC-133;A
(`\E]133;A\E\\`) sequence before each prompt.
In zsh, one way to do this is to add a `precmd` hook:
```zsh
precmd() {
print -Pn "\e]133;A\e\\"
}
```
See the
[wiki](https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts)
for details, and examples for other shells.
### Piping last command's output
The key binding `pipe-command-output` can pipe the last command's
output to an application of your choice (similar to the other `pipe-*`
key bindings):
```ini
[key-bindings]
pipe-command-output=[sh -c "f=$(mktemp); cat - > $f; footclient emacsclient -nw $f; rm $f"] Control+Shift+g
```
When pressing <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>g</kbd>, the last
command's output is written to a temporary file, then an emacsclient
is started in a new footclient instance. The temporary file is removed
after the footclient instance has closed.
For this to work, the shell must emit an OSC-133;C (`\E]133;C\E\\`)
sequence before command output starts, and an OSC-133;D
(`\E]133;D\E\\`) when the command output ends.
In fish, one way to do this is to add `preexec` and `postexec` hooks:
```fish
function foot_cmd_start --on-event fish_preexec
echo -en "\e]133;C\e\\"
end
function foot_cmd_end --on-event fish_postexec
echo -en "\e]133;D\e\\"
end
```
See the
[wiki](https://codeberg.org/dnkl/foot/wiki#user-content-piping-last-command-s-output)
for details, and examples for other shells
## Alt/meta
By default, foot prefixes _Meta characters_ with ESC. This corresponds
@ -316,13 +428,13 @@ mode_, `\E[?1034l`), and enabled again with `smm` (_set meta mode_,
## Backspace
Foot transmits DEL (`^?`) on <kbd>backspace</kbd>. This corresponds to
XTerm's `backarrowKey` option set to `false`, and to DECBKM being
_reset_.
XTerm's `backarrowKey` option set to `false`, and to
[`DECBKM`](https://vt100.net/docs/vt510-rm/DECBKM.html) being _reset_.
To instead transmit BS (`^H`), press
<kbd>ctrl</kbd>+<kbd>backspace</kbd>.
Note that foot does **not** implement DECBKM, and that the behavior
Note that foot does **not** implement `DECBKM`, and that the behavior
described above **cannot** be changed.
Finally, pressing <kbd>alt</kbd> will prefix the transmitted byte with
@ -362,27 +474,53 @@ This is not how it is meant to be. Fonts are measured in _point sizes_
**for a reason**; a given point size should have the same height on
all mediums, be it printers or monitors, regardless of their DPI.
Foots default behavior is to use the monitors DPI to size fonts when
output scaling has been disabled on **all** monitors. If at least one
monitor has output scaling enabled, fonts will instead by sized using
the scaling factor.
That said, on Wayland, Hi-DPI monitors are typically handled by
configuring a _"scaling factor"_ in the compositor. This is usually
expressed as either a rational value (e.g. _1.5_), or as a percentage
(e.g. _150%_), by which all fonts and window sizes are supposed to be
multiplied.
This can be changed to either **always** use the monitors DPI
(regardless of scaling factor), or to **never** use it, with the
`dpi-aware` option in `foot.ini`. See the man page, **foot.ini**(5)
for more information.
For this reason, and because of the new _fractional scaling_ protocol
(see below for details), and because this is how Wayland applications
are expected to behave, foot >= 1.15 will default to scaling fonts
using the compositor's scaling factor, and **not** the monitor
DPI.
When fonts are sized using the monitors DPI, glyphs should always
have the same physical height, regardless of monitor.
This means the (assuming the monitors are at the same viewing
distance) the font size will appear to change when you move the foot
window across different monitors, **unless** you have configured the
monitors' scaling factors correctly in the compositor.
Furthermore, foot will re-size the fonts on-the-fly when the window is
moved between screens with different DPIs values. If the window covers
multiple screens, with different DPIs, the highest DPI will be used.
This can be changed by setting the `dpi-aware` option to `yes` in
`foot.ini`. When enabled, fonts will **not** be sized using the
scaling factor, but will instead be sized using the monitor's
DPI. When the foot window is moved across monitors, the font size is
updated for the current monitor's DPI.
This means that, assuming the monitors are **at the same viewing
distance**, the font size will appear to be the same, at all times.
_Note_: if you configure **pixelsize**, rather than **size**, then DPI
changes will **not** change the font size. Pixels are always pixels.
### Fractional scaling on Wayland
For a long time, there was no **true** support for _fractional
scaling_. That is, values like 1.5 (150%), 1.8 (180%) etc, only
integer values, like 2 (200%).
Compositors that _did_ support fractional scaling did so using a hack;
all applications were told to scale to 200%, and then the compositor
would down-scale the rendered image to e.g. 150%. This works OK for
everything **except fonts**, which ended up blurry.
With _wayland-protocols 1.32_, a new protocol was introduced, that
allows compositors to tell applications the _actual_ scaling
factor. Applications can then scale the image using a _viewport_
object, instead of setting a scale factor on the raw pixel buffer.
## Supported OSCs
OSC, _Operating System Command_, are escape sequences that interacts
@ -392,7 +530,7 @@ with the terminal emulator itself. Foot implements the following OSCs:
supported)
* `OSC 2` - change window title
* `OSC 4` - change color palette
* `OSC 7` - report CWD
* `OSC 7` - report CWD (see [shell integration](#shell-integration))
* `OSC 8` - hyperlink
* `OSC 9` - desktop notification
* `OSC 10` - change (default) foreground color
@ -400,6 +538,7 @@ with the terminal emulator itself. Foot implements the following OSCs:
* `OSC 12` - change cursor color
* `OSC 17` - change highlight (selection) background color
* `OSC 19` - change highlight (selection) foreground color
* `OSC 22` - set the xcursor (mouse) pointer
* `OSC 52` - copy/paste clipboard data
* `OSC 104` - reset color palette
* `OSC 110` - reset default foreground color
@ -407,10 +546,15 @@ with the terminal emulator itself. Foot implements the following OSCs:
* `OSC 112` - reset cursor color
* `OSC 117` - reset highlight background color
* `OSC 119` - reset highlight foreground color
* `OSC 133` - [shell integration](#shell-integration)
* `OSC 176` - set app ID
* `OSC 555` - flash screen (**foot specific**)
* `OSC 777` - desktop notification (only the `;notify` sub-command of
OSC 777 is supported.)
See the **foot-ctlseqs**(7) man page for a complete list of supported
control sequences.
## Programmatically checking if running in foot
@ -442,7 +586,7 @@ emulator actually responded to.
Starting with version 1.7.0, foot also implements `XTVERSION`, to
which it will reply with `\EP>|foot(version)\E\\`. Version is
e.g. “1.8.2” for a regular release, or “1.8.2-36-g7db8e06f” for a git
e.g. "1.8.2" for a regular release, or "1.8.2-36-g7db8e06f" for a git
build.
@ -455,9 +599,9 @@ It allows querying the terminal for terminfo
capabilities. Applications using this feature do not need to use the
classic, file-based, terminfo definition. For example, if all
applications used this feature, you would no longer have to install
foots terminfo on remote hosts you SSH into.
foot's terminfo on remote hosts you SSH into.
XTerms implementation (as of XTerm-370) only supports querying key
XTerm's implementation (as of XTerm-370) only supports querying key
(as in keyboard keys) capabilities, and three custom capabilities:
* `TN` - terminal name
@ -469,7 +613,7 @@ Kitty has extended this, and also supports querying all integer and
string capabilities.
Foot supports this, and extends it even further, to also include
boolean capabilities. This means foots entire terminfo can be queried
boolean capabilities. This means foot's entire terminfo can be queried
via `XTGETTCAP`.
Note that both Kitty and foot handles **responses** to
@ -481,7 +625,7 @@ capability/value pairs. There are a couple of issues with this:
* The success/fail flag in the beginning of the response is always `1`
(success), unless the very **first** queried capability is invalid.
* XTerm will not respond **at all** to an invalid capability, unless
its the first one in the `XTGETTCAP` query.
it's the first one in the `XTGETTCAP` query.
* XTerm will end the response at the first invalid capability.
In other words, if you send a large multi-capability query, you will
@ -493,6 +637,14 @@ capability in the multi query. This allows us to send a proper
success/fail flag for each queried capability. Responses for **all**
queried capabilities are **always** sent. No queries are ever dropped.
All replies are in `tigetstr()` format. That is, given the same
capability name, foot's reply is identical to what `tigetstr()` would
have returned.
In addition to queries for terminfo entries, the `query-os-name` query
will be answered with a response of the form `uname=$(uname -s)`,
where `$(uname -s)` is the name of the OS foot was compiled for.
# Credits
@ -500,6 +652,11 @@ queried capabilities are **always** sent. No queries are ever dropped.
contributing foot's [logo](icons/hicolor/48x48/apps/foot.png).
# Code of Conduct
See [Code of Conduct](CODE_OF_CONDUCT.md)
# Bugs
Please report bugs to https://codeberg.org/dnkl/foot/issues
@ -511,7 +668,7 @@ reported the same issue.
The report should contain the following:
- Foot version (`foot --version`).
- Log output from foot (start foot from another terminal).
- Log output from foot (run `foot -d info` from another terminal).
- Which Wayland compositor (and version) you are running.
- If reporting a crash, please try to provide a `bt full` backtrace
with symbols.
@ -523,20 +680,24 @@ The report should contain the following:
## IRC
Ask questions, hang out, sing praise or just say hi in the `#foot`
channel on [irc.libera.chat](https://libera.chat). Logs are available
at https://libera.irclog.whitequark.org/foot.
channel on
[irc.libera.chat](https://web.libera.chat/?channels=#foot). Logs are
available at https://libera.irclog.whitequark.org/foot.
## Mastodon
Every now and then I post foot related updates on
[@dnkl@linuxrocks.online](https://linuxrocks.online/@dnkl)
[@dnkl@social.treehouse.systems](https://social.treehouse.systems/@dnkl)
# Sponsoring/donations
* Liberapay: https://liberapay.com/dnkl
* GitHub Sponsors: https://github.com/sponsors/dnkl
[![Donate using Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/dnkl/donate)
# License

View file

@ -36,16 +36,13 @@ static const uint8_t reverse_lookup[256] = {
};
static const char lookup[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'+', '/',
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/"
};
char *
base64_decode(const char *s)
base64_decode(const char *s, size_t *size)
{
const size_t len = strlen(s);
if (unlikely(len % 4 != 0)) {
@ -57,6 +54,9 @@ base64_decode(const char *s)
if (unlikely(ret == NULL))
return NULL;
if (unlikely(size != NULL))
*size = len / 4 * 3;
for (size_t i = 0, o = 0; i < len; i += 4, o += 3) {
unsigned a = reverse_lookup[(unsigned char)s[i + 0]];
unsigned b = reverse_lookup[(unsigned char)s[i + 1]];
@ -71,6 +71,13 @@ base64_decode(const char *s)
if (unlikely(i + 4 != len || (a | b) & P || (c & P && !(d & P))))
goto invalid;
if (unlikely(size != NULL)) {
if (c & P)
*size = len / 4 * 3 - 2;
else
*size = len / 4 * 3 - 1;
}
c &= 63;
d &= 63;
}

View file

@ -3,6 +3,6 @@
#include <stdint.h>
#include <stddef.h>
char *base64_decode(const char *s);
char *base64_decode(const char *s, size_t *out_len);
char *base64_encode(const uint8_t *data, size_t size);
void base64_encode_final(const uint8_t *data, size_t size, char result[4]);

View file

@ -2,7 +2,6 @@
#include <stdio.h>
#include <math.h>
#include <fenv.h>
#include <errno.h>
#define LOG_MODULE "box-drawing"
@ -33,9 +32,12 @@ struct buf {
int thickness[2];
/* For sextants and wedges */
/* For octants, sextants and wedges */
int x_halfs[2];
int y_thirds[2];
/* For octants */
int y_quads[3];
};
static const pixman_color_t white = {0xffff, 0xffff, 0xffff, 0xffff};
@ -1459,14 +1461,12 @@ draw_box_drawings_light_arc(struct buf *buf, char32_t wc)
*/
for (double i = y_min*16; i <= y_max*16; i++) {
errno = 0;
feclearexcept(FE_ALL_EXCEPT);
double y = i / 16.;
double x = circle_hemisphere * sqrt(c_r2 - (y - c_y) * (y - c_y)) + c_x;
/* See math_error(7) */
if (errno != 0 ||
fetestexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW))
if (errno != 0)
{
continue;
}
@ -2098,7 +2098,7 @@ draw_braille(struct buf *buf, char32_t wc)
if (x_px_left >= 1) { x_spacing++; x_px_left--; }
if (y_px_left >= 3) { y_spacing++; y_px_left -= 3; }
/* Fourth, margins (“spacing”, but on the sides) */
/* Fourth, margins ("spacing", but on the sides) */
if (x_px_left >= 2) { x_margin++; x_px_left -= 2; }
if (y_px_left >= 2) { y_margin++; y_px_left -= 2; }
@ -2213,6 +2213,7 @@ draw_sextant(struct buf *buf, char32_t wc)
LOWER_RIGHT = 1 << 5,
};
/* TODO: move this to a separate file? */
static const uint8_t matrix[60] = {
/* U+1fb00 - U+1fb0f */
UPPER_LEFT,
@ -2308,6 +2309,398 @@ draw_sextant(struct buf *buf, char32_t wc)
sextant_lower_right(buf);
}
static void
octant_upper_left(struct buf *buf)
{
rect(0, 0, buf->x_halfs[0], buf->y_quads[0]);
}
static void
octant_middle_up_left(struct buf *buf)
{
rect(0, buf->y_quads[0], buf->x_halfs[0], buf->y_quads[1]);
}
static void
octant_middle_down_left(struct buf *buf)
{
rect(0, buf->y_quads[1], buf->x_halfs[0], buf->y_quads[2]);
}
static void
octant_lower_left(struct buf *buf)
{
rect(0, buf->y_quads[2], buf->x_halfs[0], buf->height);
}
static void
octant_upper_right(struct buf *buf)
{
rect(buf->x_halfs[1], 0, buf->width, buf->y_quads[0]);
}
static void
octant_middle_up_right(struct buf *buf)
{
rect(buf->x_halfs[1], buf->y_quads[0], buf->width, buf->y_quads[1]);
}
static void
octant_middle_down_right(struct buf *buf)
{
rect(buf->x_halfs[1], buf->y_quads[1], buf->width, buf->y_quads[2]);
}
static void
octant_lower_right(struct buf *buf)
{
rect(buf->x_halfs[1], buf->y_quads[2], buf->width, buf->height);
}
static void NOINLINE
draw_octant(struct buf *buf, char32_t wc)
{
/*
* Each byte encodes one octant:
*
* Bit octant part
* 0 upper left
* 1 middle, upper left
* 2 middle, lower left
* 3 lower, left
* 4 upper right
* 5 middle, upper right
* 6 middle, lower right
* 7 lower right
*/
enum {
UPPER_LEFT = 1 << 0,
MIDDLE_UP_LEFT = 1 << 1,
MIDDLE_DOWN_LEFT = 1 << 2,
LOWER_LEFT = 1 << 3,
UPPER_RIGHT = 1 << 4,
MIDDLE_UP_RIGHT = 1 << 5,
MIDDLE_DOWN_RIGHT = 1 << 6,
LOWER_RIGHT = 1 << 7,
};
/* TODO: move this to a separate file */
static const uint8_t matrix[230] = {
/* U+1CD00 - U+1CD0F */
MIDDLE_UP_LEFT,
MIDDLE_UP_LEFT | UPPER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | UPPER_RIGHT,
MIDDLE_UP_RIGHT,
UPPER_LEFT | MIDDLE_UP_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT,
MIDDLE_DOWN_LEFT,
UPPER_LEFT | MIDDLE_DOWN_LEFT,
UPPER_RIGHT | MIDDLE_DOWN_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT,
/* U+1CD10 - U+1CD1F */
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
MIDDLE_DOWN_RIGHT,
UPPER_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_RIGHT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT,
/* U+1CD20 - U+1CD2F */
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
/* U+1CD30 - U+1CD3F */
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
UPPER_LEFT | LOWER_LEFT,
UPPER_RIGHT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | LOWER_LEFT,
MIDDLE_UP_LEFT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_LEFT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_LEFT,
MIDDLE_UP_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_LEFT,
/* U+1CD40 - U+1CD4F */
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_LEFT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
/* U+1CD50 - U+1CD5F */
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
/* U+1CD60 - U+1CD6F */
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
/* U+1CD70 - U+1CD7F */
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
UPPER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | LOWER_RIGHT,
MIDDLE_UP_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_RIGHT,
MIDDLE_UP_RIGHT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
/* U+1CD80 - U+1CD8F */
MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
/* U+1CD90 - U+1CD9F */
UPPER_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
/* U+1CDA0 - U+1CDAF */
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
UPPER_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_LEFT | LOWER_LEFT | LOWER_RIGHT,
/* U+1CDB0 - U+1CDBF */
UPPER_LEFT | MIDDLE_UP_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
/* U+1CDC0 - U+1CDCF */
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
/* U+1CDD0 - U+1CDDF */
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
/* U+1CDE0 - U+1CDE5 */
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
};
_Static_assert(ALEN(matrix) == 230, "incorrect number of codepoints");
#if defined(_DEBUG)
const size_t last_implemented = 0x1cde5;
for (size_t i = 0; i < sizeof(matrix) / sizeof(matrix[0]); i++) {
if (i + 0x1cd00 > last_implemented)
break;
for (size_t j = 0; j < sizeof(matrix) / sizeof(matrix[0]); j++) {
if (j + 0x1cd00 > last_implemented)
break;
if (i == j)
continue;
if (matrix[i] == matrix[j]) {
BUG("octant U+%05x (idx=%zu) is the same as U+%05x (idx=%zu)",
matrix[i], i, matrix[j], j);
}
}
}
#endif
xassert(wc >= 0x1cd00 && wc <= 0x1cde5);
const size_t idx = wc - 0x1cd00;
xassert(idx < ALEN(matrix));
uint8_t encoded = matrix[idx];
if (encoded & UPPER_LEFT)
octant_upper_left(buf);
if (encoded & MIDDLE_UP_LEFT)
octant_middle_up_left(buf);
if (encoded & MIDDLE_DOWN_LEFT)
octant_middle_down_left(buf);
if (encoded & LOWER_LEFT)
octant_lower_left(buf);
if (encoded & UPPER_RIGHT)
octant_upper_right(buf);
if (encoded & MIDDLE_UP_RIGHT)
octant_middle_up_right(buf);
if (encoded & MIDDLE_DOWN_RIGHT)
octant_middle_down_right(buf);
if (encoded & LOWER_RIGHT)
octant_lower_right(buf);
}
static void NOINLINE
draw_wedge_triangle(struct buf *buf, char32_t wc)
{
@ -2856,6 +3249,7 @@ draw_glyph(struct buf *buf, char32_t wc)
case 0x2800 ... 0x28ff: draw_braille(buf, wc); break;
case 0x1cd00 ... 0x1cde5: draw_octant(buf, wc); break;
case 0x1fb00 ... 0x1fb3b: draw_sextant(buf, wc); break;
case 0x1fb3c ... 0x1fb40:
@ -2957,24 +3351,51 @@ box_drawing(const struct terminal *term, char32_t wc)
(double)term->conf->tweak.box_drawing_base_thickness * scale * cell_size * dpi / 72.0;
base_thickness = max(base_thickness, 1);
int y0 = 0, y1 = 0;
int y_third_0 = 0, y_third_1 = 0;
switch (height % 3) {
case 0:
y0 = height / 3;
y1 = 2 * height / 3;
y_third_0 = height / 3;
y_third_1 = 2 * height / 3;
break;
case 1:
y0 = height / 3;
y1 = 2 * height / 3 + 1;
y_third_0 = height / 3;
y_third_1 = 2 * height / 3 + 1;
break;
case 2:
y0 = height / 3 + 1;
y1 = y0 + height / 3;
y_third_0 = height / 3 + 1;
y_third_1 = y_third_0 + height / 3;
break;
}
/* TODO */
int y_quad_0 = 0, y_quad_1 = 0, y_quad_2 = 0;
switch (height % 4) {
case 0:
y_quad_0 = height / 4;
y_quad_1 = height / 2;
y_quad_2 = 3 * height / 4;
break;
case 1:
y_quad_0 = height / 4;
y_quad_1 = height / 2;
y_quad_2 = 3 * height / 4;
break;
case 2:
y_quad_0 = height / 4;
y_quad_1 = height / 2;
y_quad_2 = 3 * height / 4;
break;
case 3:
y_quad_0 = height / 4;
y_quad_1 = height / 2;
y_quad_2 = 3 * height / 4;
break;
}
struct buf buf = {
.data = data,
.pix = pix,
@ -2996,8 +3417,14 @@ box_drawing(const struct terminal *term, char32_t wc)
},
.y_thirds = {
y0, /* Endpoint first third, start point second third */
y1, /* Endpoint second third, start point last third */
y_third_0, /* Endpoint first third, start point second third */
y_third_1, /* Endpoint second third, start point last third */
},
.y_quads = {
y_quad_0,
y_quad_1,
y_quad_2,
},
};
@ -3011,7 +3438,7 @@ box_drawing(const struct terminal *term, char32_t wc)
.cols = 1,
.pix = buf.pix,
.x = -term->font_x_ofs,
.y = term->font_y_ofs + term->fonts[0]->ascent,
.y = term->font_baseline,
.width = width,
.height = height,
.advance = {

View file

@ -34,7 +34,7 @@ _Static_assert(
#if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__
#error "char32_t does not use UTF-32"
#endif
#if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__)
#if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__) && !defined(__OpenBSD__)
#error "wchar_t does not use UTF-32"
#endif
@ -53,6 +53,14 @@ UNITTEST
xassert(c32cmp(U"b", U"a") > 0);
}
UNITTEST
{
xassert(c32ncmp(U"foo", U"foot", 3) == 0);
xassert(c32ncmp(U"foot", U"FOOT", 4) > 0);
xassert(c32ncmp(U"a", U"b", 1) < 0);
xassert(c32ncmp(U"bb", U"aa", 2) > 0);
}
UNITTEST
{
char32_t copy[16];
@ -129,11 +137,25 @@ UNITTEST
UNITTEST
{
char32_t *c = c32dup(U"foobar");
xassert(!isc32upper(U'a'));
xassert(isc32upper(U'A'));
xassert(!isc32upper(U'a'));
}
UNITTEST
{
xassert(hasc32upper(U"abc1A"));
xassert(!hasc32upper(U"abc1_aaa"));
xassert(!hasc32upper(U""));
}
UNITTEST
{
char32_t *c = xc32dup(U"foobar");
xassert(c32cmp(c, U"foobar") == 0);
free(c);
c = c32dup(U"");
c = xc32dup(U"");
xassert(c32cmp(c, U"") == 0);
free(c);
}
@ -176,7 +198,7 @@ done:
return chars;
err:
return (char32_t)-1;
return (size_t)-1;
}
UNITTEST

View file

@ -8,6 +8,10 @@
#include <wchar.h>
#include <wctype.h>
#if defined(FOOT_GRAPHEME_CLUSTERING)
#include <utf8proc.h>
#endif
static inline size_t c32len(const char32_t *s) {
return wcslen((const wchar_t *)s);
}
@ -16,6 +20,10 @@ static inline int c32cmp(const char32_t *s1, const char32_t *s2) {
return wcscmp((const wchar_t *)s1, (const wchar_t *)s2);
}
static inline int c32ncmp(const char32_t *s1, const char32_t *s2, size_t n) {
return wcsncmp((const wchar_t *)s1, (const wchar_t *)s2, n);
}
static inline char32_t *c32ncpy(char32_t *dst, const char32_t *src, size_t n) {
return (char32_t *)wcsncpy((wchar_t *)dst, (const wchar_t *)src, n);
}
@ -56,6 +64,10 @@ static inline char32_t toc32upper(char32_t c) {
return (char32_t)towupper((wint_t)c);
}
static inline bool isc32upper(char32_t c32) {
return iswupper((wint_t)c32);
}
static inline bool isc32space(char32_t c32) {
return iswspace((wint_t)c32);
}
@ -68,12 +80,30 @@ static inline bool isc32graph(char32_t c32) {
return iswgraph((wint_t)c32);
}
static inline bool hasc32upper(const char32_t *s) {
for (int i = 0; s[i] != '\0'; i++) {
if (isc32upper(s[i])) return true;
}
return false;
}
static inline int c32width(char32_t c) {
#if defined(FOOT_GRAPHEME_CLUSTERING)
return utf8proc_charwidth((utf8proc_int32_t)c);
#else
return wcwidth((wchar_t)c);
#endif
}
static inline int c32swidth(const char32_t *s, size_t n) {
#if defined(FOOT_GRAPHEME_CLUSTERING)
int width = 0;
for (size_t i = 0; i < n; i++)
width += utf8proc_charwidth((utf8proc_int32_t)s[i]);
return width;
#else
return wcswidth((const wchar_t *)s, n);
#endif
}
size_t mbsntoc32(char32_t *dst, const char *src, size_t nms, size_t len);

View file

@ -29,3 +29,17 @@ struct client_data {
} __attribute__((packed));
_Static_assert(sizeof(struct client_data) == 10, "protocol struct size error");
enum client_ipc_code {
FOOT_IPC_SIGUSR,
};
struct client_ipc_hdr {
enum client_ipc_code ipc_code;
uint8_t size;
} __attribute__((packed));
struct client_ipc_sigusr {
int signo;
} __attribute__((packed));

161
client.c
View file

@ -1,12 +1,13 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
@ -22,7 +23,6 @@
#include "foot-features.h"
#include "macros.h"
#include "util.h"
#include "version.h"
#include "xmalloc.h"
extern char **environ;
@ -34,13 +34,20 @@ struct string {
typedef tll(struct string) string_list_t;
static volatile sig_atomic_t aborted = 0;
static volatile sig_atomic_t sigusr = 0;
static void
sig_handler(int signo)
sigint_handler(int signo)
{
aborted = 1;
}
static void
sigusr_handler(int signo)
{
sigusr = signo;
}
static ssize_t
sendall(int sock, const void *_buf, size_t len)
{
@ -62,19 +69,6 @@ sendall(int sock, const void *_buf, size_t len)
return len;
}
static const char *
version_and_features(void)
{
static char buf[256];
snprintf(buf, sizeof(buf), "version: %s %cpgo %cime %cgraphemes %cassertions",
FOOT_VERSION,
feature_pgo() ? '+' : '-',
feature_ime() ? '+' : '-',
feature_graphemes() ? '+' : '-',
feature_assertions() ? '+' : '-');
return buf;
}
static void
print_usage(const char *prog_name)
{
@ -83,6 +77,7 @@ print_usage(const char *prog_name)
" -t,--term=TERM value to set the environment variable TERM to (" FOOT_DEFAULT_TERM ")\n"
" -T,--title=TITLE initial window title (foot)\n"
" -a,--app-id=ID window application ID (foot)\n"
" --toplevel-tag=TAG set a custom toplevel tag\n"
" -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n"
" -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n"
" -m,--maximized start in maximized mode\n"
@ -94,7 +89,7 @@ print_usage(const char *prog_name)
" -N,--no-wait detach the client process from the running terminal, exiting immediately\n"
" -o,--override=[section.]key=value override configuration option\n"
" -E, --client-environment exec shell using footclient's environment, instead of the server's\n"
" -d,--log-level={info|warning|error|none} log level (info)\n"
" -d,--log-level={info|warning|error|none} log level (warning)\n"
" -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n"
" -v,--version show the version number and quit\n"
" -e ignored (for compatibility with xterm -e)\n";
@ -144,11 +139,15 @@ send_string_list(int fd, const string_list_t *string_list)
return true;
}
enum {
TOPLEVEL_TAG_OPTION = CHAR_MAX + 1,
};
int
main(int argc, char *const *argv)
{
/* Custom exit code, to enable users to differentiate between foot
* itself failing, and the client application failiing */
* itself failing, and the client application failing */
static const int foot_exit_failure = -36;
int ret = foot_exit_failure;
@ -158,6 +157,7 @@ main(int argc, char *const *argv)
{"term", required_argument, NULL, 't'},
{"title", required_argument, NULL, 'T'},
{"app-id", required_argument, NULL, 'a'},
{"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION},
{"window-size-pixels", required_argument, NULL, 'w'},
{"window-size-chars", required_argument, NULL, 'W'},
{"maximized", no_argument, NULL, 'm'},
@ -178,7 +178,7 @@ main(int argc, char *const *argv)
const char *custom_cwd = NULL;
const char *server_socket_path = NULL;
enum log_class log_level = LOG_CLASS_INFO;
enum log_class log_level = LOG_CLASS_WARNING;
enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
bool hold = false;
bool client_environment = false;
@ -227,6 +227,12 @@ main(int argc, char *const *argv)
goto err;
break;
case TOPLEVEL_TAG_OPTION:
snprintf(buf, sizeof(buf), "toplevel-tag=%s", optarg);
if (!push_string(&overrides, buf, &total_len))
goto err;
break;
case 'L':
if (!push_string(&overrides, "login-shell=yes", &total_len))
goto err;
@ -314,11 +320,11 @@ main(int argc, char *const *argv)
}
case 'l':
if (optarg == NULL || strcmp(optarg, "auto") == 0)
if (optarg == NULL || streq(optarg, "auto"))
log_colorize = LOG_COLORIZE_AUTO;
else if (strcmp(optarg, "never") == 0)
else if (streq(optarg, "never"))
log_colorize = LOG_COLORIZE_NEVER;
else if (strcmp(optarg, "always") == 0)
else if (streq(optarg, "always"))
log_colorize = LOG_COLORIZE_ALWAYS;
else {
fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg);
@ -327,7 +333,7 @@ main(int argc, char *const *argv)
break;
case 'v':
printf("footclient %s\n", version_and_features());
print_version_and_features("footclient ");
ret = EXIT_SUCCESS;
goto err;
@ -371,16 +377,19 @@ main(int argc, char *const *argv)
const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
if (xdg_runtime != NULL) {
const char *wayland_display = getenv("WAYLAND_DISPLAY");
if (wayland_display != NULL)
if (wayland_display != NULL) {
snprintf(addr.sun_path, sizeof(addr.sun_path),
"%s/foot-%s.sock", xdg_runtime, wayland_display);
else
connected = (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0);
}
if (!connected) {
LOG_WARN("%s: failed to connect, will now try %s/foot.sock",
addr.sun_path, xdg_runtime);
snprintf(addr.sun_path, sizeof(addr.sun_path),
"%s/foot.sock", xdg_runtime);
if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0)
connected = true;
else
connected = (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0);
}
if (!connected)
LOG_WARN("%s: failed to connect, will now try /tmp/foot.sock", addr.sun_path);
}
@ -395,10 +404,10 @@ main(int argc, char *const *argv)
const char *cwd = custom_cwd;
if (cwd == NULL) {
errno = 0;
size_t buf_len = 1024;
do {
_cwd = xrealloc(_cwd, buf_len);
errno = 0;
if (getcwd(_cwd, buf_len) == NULL && errno != ERANGE) {
LOG_ERRNO("failed to get current working directory");
goto err;
@ -408,6 +417,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 &&
streq(resolved_path_cwd, resolved_path_pwd))
{
/*
* 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))
@ -496,15 +527,63 @@ main(int argc, char *const *argv)
if (!send_string_list(fd, &envp))
goto err;
struct sigaction sa = {.sa_handler = &sig_handler};
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
struct sigaction sa_int = {.sa_handler = &sigint_handler};
struct sigaction sa_usr = {.sa_handler = &sigusr_handler};
sigemptyset(&sa_int.sa_mask);
sigemptyset(&sa_usr.sa_mask);
if (sigaction(SIGINT, &sa_int, NULL) < 0 ||
sigaction(SIGTERM, &sa_int, NULL) < 0 ||
sigaction(SIGUSR1, &sa_usr, NULL) < 0 ||
sigaction(SIGUSR2, &sa_usr, NULL) < 0)
{
LOG_ERRNO("failed to register signal handlers");
goto err;
}
int exit_code;
ssize_t rcvd = recv(fd, &exit_code, sizeof(exit_code), 0);
ssize_t rcvd = -1;
while (true) {
rcvd = recv(fd, &exit_code, sizeof(exit_code), 0);
const int got_sigusr = sigusr;
sigusr = 0;
if (rcvd < 0 && errno == EINTR) {
if (aborted)
break;
else if (got_sigusr != 0) {
LOG_DBG("sending sigusr %d to server", got_sigusr);
struct {
struct client_ipc_hdr hdr;
struct client_ipc_sigusr sigusr;
} ipc = {
.hdr = {
.ipc_code = FOOT_IPC_SIGUSR,
.size = sizeof(struct client_ipc_sigusr),
},
.sigusr = {
.signo = got_sigusr,
},
};
ssize_t count = send(fd, &ipc, sizeof(ipc), 0);
if (count < 0) {
LOG_ERRNO("failed to send SIGUSR IPC to server");
goto err;
} else if ((size_t)count != sizeof(ipc)) {
LOG_ERR("failed to send SIGUSR IPC to server");
goto err;
}
}
continue;
}
break;
}
if (rcvd == -1 && errno == EINTR)
xassert(aborted);

View file

@ -19,33 +19,16 @@ cmd_scrollback_up(struct terminal *term, int rows)
return;
const struct grid *grid = term->grid;
const int offset = grid->offset;
const int view = grid->view;
const int grid_rows = grid->num_rows;
const int screen_rows = term->rows;
int scrollback_start = (offset + screen_rows) & (grid_rows - 1);
/* The view row number in scrollback relative coordinates. This is
* the maximum number of rows we're allowed to scroll */
int sb_start = grid_sb_start_ignore_uninitialized(grid, term->rows);
int view_sb_rel =
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, view);
/* Part of the scrollback may be uninitialized */
while (grid->rows[scrollback_start] == NULL) {
scrollback_start++;
scrollback_start &= grid_rows - 1;
}
/* Number of rows to scroll, without going past the scrollback start */
int max_rows = 0;
if (view + screen_rows >= grid_rows) {
/* View crosses scrollback wrap-around */
xassert(scrollback_start <= view);
max_rows = view - scrollback_start;
} else {
if (scrollback_start <= view)
max_rows = view - scrollback_start;
else
max_rows = view + (grid_rows - scrollback_start);
}
rows = min(rows, max_rows);
rows = min(rows, view_sb_rel);
if (rows == 0)
return;
@ -59,8 +42,8 @@ cmd_scrollback_up(struct terminal *term, int rows)
xassert(grid->rows[(new_view + r) & (grid->num_rows - 1)] != NULL);
#endif
LOG_DBG("scrollback UP: %d -> %d (offset = %d, end = %d, rows = %d)",
view, new_view, offset, end, grid_rows);
LOG_DBG("scrollback UP: %d -> %d (offset = %d, rows = %d)",
view, new_view, offset, grid_rows);
selection_view_up(term, new_view);
term->grid->view = new_view;
@ -113,8 +96,8 @@ cmd_scrollback_down(struct terminal *term, int rows)
xassert(grid->rows[(new_view + r) & (grid_rows - 1)] != NULL);
#endif
LOG_DBG("scrollback DOWN: %d -> %d (offset = %d, end = %d, rows = %d)",
view, new_view, offset, end, grid_rows);
LOG_DBG("scrollback DOWN: %d -> %d (offset = %d, rows = %d)",
view, new_view, offset, grid_rows);
selection_view_down(term, new_view);
term->grid->view = new_view;

View file

@ -6,6 +6,7 @@ _foot()
local cur prev flags word commands match previous_words i offset
flags=(
"--app-id"
"--toplevel-tag"
"--check-config"
"--config"
"--font"
@ -19,6 +20,7 @@ _foot()
"--maximized"
"--override"
"--print-pid"
"--pty"
"--server"
"--term"
"--title"
@ -31,7 +33,7 @@ _foot()
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
# check if positional argument is completed
# Check if positional argument is completed
previous_words=( "${COMP_WORDS[@]}" )
unset previous_words[-1]
commands=$(compgen -c | grep -vFx "$(compgen -k)" | grep -vE '^([.:[]|foot)$' | sort -u)
@ -39,45 +41,49 @@ _foot()
for word in "${previous_words[@]}" ; do
match=$(printf "$commands" | grep -Fx "$word" 2>/dev/null)
if [[ ! -z "$match" ]] ; then
if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--config|--font|--log-level|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then
if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--toplevel-tag|--config|--font|--log-level|--pty|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then
(( i++ ))
continue
fi
# positional argument found
# Positional argument found
offset=$i
fi
(( i++ ))
done
if [[ ! -z "$offset" ]] ; then
# depends on bash_completion being available
# Depends on bash_completion being available
declare -F _command_offset >/dev/null || return 1
_command_offset $offset
return 0
elif [[ ${cur} == --* ]] ; then
COMPREPLY=( $(compgen -W "${flags}" -- ${cur}) )
elif [[ ${prev} =~ ^(--config|--print-pid|--server)$ ]] ; then
compopt -o default
elif [[ ${prev} == '--working-directory' ]] ; then
compopt -o dirnames
elif [[ ${prev} == '--term' ]] ; then
# check if toe is available
which toe > /dev/null || return 1
COMPREPLY=( $(compgen -W "$(toe -a | awk '$1 ~ /[+]/ {next}; {print $1}')" -- ${cur}) )
elif [[ ${prev} == '--font' ]] ; then
# check if fc-list is available
which fc-list > /dev/null || return 1
COMPREPLY=( $(compgen -W "$(fc-list : family | sed 's/,/\n/g' | uniq | tr -d ' ')" -- ${cur}) )
elif [[ ${prev} == '--log-level' ]] ; then
COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) )
elif [[ ${prev} == '--log-colorize' ]] ; then
COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) )
elif [[ ${prev} =~ ^(--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|--check-config)$ ]] ; then
: # don't autocomplete for these flags
else
# complete commands from $PATH
COMPREPLY=( $(compgen -c -- ${cur}) )
return 0
fi
case "$prev" in
--config|--print-pid|--server|-[cps])
compopt -o default ;;
--working-directory|-D)
compopt -o dirnames ;;
--term|-t)
command -v toe > /dev/null || return 1
COMPREPLY=( $(compgen -W "$(toe -a | awk '$1 !~ /[+]/ {print $1}')" -- ${cur}) ) ;;
--font|-f)
command -v fc-list > /dev/null || return 1
COMPREPLY=( $(compgen -W "$(fc-list : family | sed 's/,/\n/g' | uniq | tr -d ' ')" -- ${cur}) ) ;;
--log-level|-d)
COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;;
--log-colorize|-l)
COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;;
--app-id|--toplevel-tag|--help|--override|--pty|--title|--version|--window-size-chars|--window-size-pixels|--check-config|-[ahoTvWwC])
# Don't autocomplete for these flags
: ;;
*)
# Complete commands from $PATH
COMPREPLY=( $(compgen -c -- ${cur}) ) ;;
esac
return 0
}

View file

@ -6,6 +6,7 @@ _footclient()
local cur prev flags word commands match previous_words i offset
flags=(
"--app-id"
"--toplevel-tag"
"--fullscreen"
"--help"
"--hold"
@ -27,7 +28,7 @@ _footclient()
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
# check if positional argument is completed
# Check if positional argument is completed
previous_words=( "${COMP_WORDS[@]}" )
unset previous_words[-1]
commands=$(compgen -c | grep -vFx "$(compgen -k)" | grep -vE '^([.:[]|footclient)$' | sort -u)
@ -35,41 +36,46 @@ _footclient()
for word in "${previous_words[@]}" ; do
match=$(printf "$commands" | grep -Fx "$word" 2>/dev/null)
if [[ ! -z "$match" ]] ; then
if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--log-level|--server-socket|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then
if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--toplevel-tag|--log-level|--server-socket|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then
(( i++ ))
continue
fi
# positional argument found
# Positional argument found
offset=$i
fi
(( i++ ))
done
if [[ ! -z "$offset" ]] ; then
# depends on bash_completion being available
# Depends on bash_completion being available
declare -F _command_offset >/dev/null || return 1
_command_offset $offset
return 0
elif [[ ${cur} == --* ]] ; then
COMPREPLY=( $(compgen -W "${flags}" -- ${cur}) )
elif [[ ${prev} == '--server-socket' ]] ; then
compopt -o default
elif [[ ${prev} == '--working-directory' ]] ; then
compopt -o dirnames
elif [[ ${prev} == '--term' ]] ; then
# check if toe is available
which toe > /dev/null || return 1
COMPREPLY=( $(compgen -W "$(toe -a | awk '$1 ~ /[+]/ {next}; {print $1}')" -- ${cur}) )
elif [[ ${prev} == '--log-level' ]] ; then
COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) )
elif [[ ${prev} == '--log-colorize' ]] ; then
COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) )
elif [[ ${prev} =~ ^(--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|)$ ]] ; then
: # don't autocomplete for these flags
else
# complete commands from $PATH
COMPREPLY=( $(compgen -c -- ${cur}) )
return 0
fi
case "$prev" in
--server-socket|-s)
compopt -o default ;;
--working-directory|-D)
compopt -o dirnames ;;
--term|-t)
command -v toe > /dev/null || return 1
COMPREPLY=( $(compgen -W "$(toe -a | awk '$1 ~ /[+]/ {next}; {print $1}')" -- ${cur}) ) ;;
--log-level|-d)
COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;;
--log-colorize|-l)
COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;;
--app-id|--toplevel-tag|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|-[ahoTvWw])
# Don't autocomplete for these flags
: ;;
*)
# Complete commands from $PATH
COMPREPLY=( $(compgen -c -- ${cur}) ) ;;
esac
return 0
}

View file

@ -6,6 +6,7 @@ complete -c foot -x -s f -l font -a "(fc-list : family | sed 's/,/
complete -c foot -x -s t -l term -a '(find /usr/share/terminfo -type f -printf "%f\n")' -d "value to set the environment variable TERM to (foot)"
complete -c foot -x -s T -l title -d "initial window title"
complete -c foot -x -s a -l app-id -d "value to set the app-id property on the Wayland window to (foot)"
complete -c foot -x -l toplevel-tag -d "value to set the toplevel-tag property on the Wayland window to"
complete -c foot -s m -l maximized -d "start in maximized mode"
complete -c foot -s F -l fullscreen -d "start in fullscreen mode"
complete -c foot -s L -l login-shell -d "start shell as a login shell"
@ -15,8 +16,9 @@ complete -c foot -x -s W -l window-size-chars
complete -c foot -F -s s -l server -d "run as server; open terminals by running footclient"
complete -c foot -s H -l hold -d "remain open after child process exits"
complete -c foot -r -s p -l print-pid -d "print PID to this file or FD when up and running (server mode only)"
complete -c foot -x -s d -l log-level -a "info warning error none" -d "log-level (info)"
complete -c foot -x -s d -l log-level -a "info warning error none" -d "log-level (warning)"
complete -c foot -x -s l -l log-colorize -a "always never auto" -d "enable or disable colorization of log output on stderr"
complete -c foot -s S -l log-no-syslog -d "disable syslog logging (server mode only)"
complete -c foot -r -l pty -d "display an existing pty instead of creating one"
complete -c foot -s v -l version -d "show the version number and quit"
complete -c foot -s h -l help -d "show help message and quit"

View file

@ -2,6 +2,7 @@ complete -c footclient -x -a "(__fish_complete_subcom
complete -c footclient -x -s t -l term -a '(find /usr/share/terminfo -type f -printf "%f\n")' -d "value to set the environment variable TERM to (foot)"
complete -c footclient -x -s T -l title -d "initial window title"
complete -c footclient -x -s a -l app-id -d "value to set the app-id property on the Wayland window to (foot)"
complete -c footclient -x -l toplevel-tag -d "value to set the toplevel-tag property on the Wayland window to"
complete -c footclient -s m -l maximized -d "start in maximized mode"
complete -c footclient -s F -l fullscreen -d "start in fullscreen mode"
complete -c footclient -s L -l login-shell -d "start shell as a login shell"

View file

@ -9,6 +9,7 @@ _arguments \
'(-t --term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \
'(-T --title)'{-T,--title}'[initial window title]:()' \
'(-a --app-id)'{-a,--app-id}'[value to set the app-id property on the Wayland window to (foot)]:()' \
'--toplevel-tag=[value to set the toplevel-tag property on the Wayland window to]:()' \
'(-m --maximized)'{-m,--maximized}'[start in maximized mode]' \
'(-F --fullscreen)'{-F,--fullscreen}'[start in fullscreen mode]' \
'(-L --login-shell)'{-L,--login-shell}'[start shell as a login shell]' \
@ -18,7 +19,8 @@ _arguments \
'(-s --server)'{-s,--server}'[run as server; open terminals by running footclient]:server:_files' \
'(-H --hold)'{-H,--hold}'[remain open after child process exits]' \
'(-p --print-pid)'{-p,--print-pid}'[print PID to this file or FD when up and running (server mode only)]:pidfile:_files' \
'(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error none)' \
'--pty=[display an existing pty instead of creating one]:pty:_files' \
'(-d --log-level)'{-d,--log-level}'[log level (warning)]:loglevel:(info warning error none)' \
'(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \
'(-S --log-no-syslog)'{-s,--log-no-syslog}'[disable syslog logging (server mode only)]' \
'(-v --version)'{-v,--version}'[show the version number and quit]' \

View file

@ -5,6 +5,7 @@ _arguments \
'(-t --term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \
'(-T --title)'{-T,--title}'[initial window title]:()' \
'(-a --app-id)'{-a,--app-id}'[value to set the app-id property on the Wayland window to (foot)]:()' \
'--toplevel-tag=[value to set the toplevel-tag property on the Wayland window to]:()' \
'(-m --maximized)'{-m,--maximized}'[start in maximized mode]' \
'(-F --fullscreen)'{-F,--fullscreen}'[start in fullscreen mode]' \
'(-L --login-shell)'{-L,--login-shell}'[start shell as a login shell]' \
@ -16,7 +17,7 @@ _arguments \
'(-N --no-wait)'{-N,--no-wait}'[detach the client process from the running terminal, exiting immediately]' \
'(-o --override)'{-o,--override}'[configuration option to override, in form SECTION.KEY=VALUE]:()' \
'(-E --client-environment)'{-E,--client-environment}"[child process inherits footclient's environment, instead of the server's]" \
'(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error none)' \
'(-d --log-level)'{-d,--log-level}'[log level (warning)]:loglevel:(info warning error none)' \
'(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \
'(-v --version)'{-v,--version}'[show the version number and quit]' \
'(-h --help)'{-h,--help}'[show help message and quit]' \

View file

@ -4,8 +4,54 @@
#include <stdbool.h>
#include "debug.h"
#include "terminal.h"
struct composed *
uint32_t
composed_key_from_chars(const uint32_t chars[], size_t count)
{
if (count == 0)
return 0;
uint32_t key = chars[0];
for (size_t i = 1; i < count; i++)
key = composed_key_from_key(key, chars[i]);
return key;
}
uint32_t
composed_key_from_key(uint32_t prev_key, uint32_t next_char)
{
unsigned bits = 32 - __builtin_clz(CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO);
/* Rotate old key 8 bits */
uint32_t new_key = (prev_key << 8) | (prev_key >> (bits - 8));
/* xor with new char */
new_key ^= next_char;
/* Multiply with magic hash constant */
new_key *= 2654435761ul;
/* And mask, to ensure the new value is within range */
new_key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO;
return new_key;
}
UNITTEST
{
const char32_t chars[] = U"abcdef";
uint32_t k1 = composed_key_from_key(chars[0], chars[1]);
uint32_t k2 = composed_key_from_chars(chars, 2);
xassert(k1 == k2);
uint32_t k3 = composed_key_from_key(k2, chars[2]);
uint32_t k4 = composed_key_from_chars(chars, 3);
xassert(k3 == k4);
}
const struct composed *
composed_lookup(struct composed *root, uint32_t key)
{
struct composed *node = root;
@ -20,6 +66,41 @@ composed_lookup(struct composed *root, uint32_t key)
return NULL;
}
const struct composed *
composed_lookup_without_collision(struct composed *root, uint32_t *key,
const char32_t *prefix_text, size_t prefix_len,
char32_t wc, int forced_width)
{
while (true) {
const struct composed *cc = composed_lookup(root, *key);
if (cc == NULL)
return NULL;
bool match = cc->count == prefix_len + 1 &&
cc->forced_width == forced_width &&
cc->chars[prefix_len] == wc;
if (match) {
for (size_t i = 0; i < prefix_len; i++) {
if (cc->chars[i] != prefix_text[i]) {
match = false;
break;
}
}
}
if (match)
return cc;
(*key)++;
*key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO;
/* TODO: this will loop infinitely if the composed table is full */
}
return NULL;
}
void
composed_insert(struct composed **root, struct composed *node)
{

View file

@ -10,9 +10,16 @@ struct composed {
uint32_t key;
uint8_t count;
uint8_t width;
uint8_t forced_width;
};
struct composed *composed_lookup(struct composed *root, uint32_t key);
uint32_t composed_key_from_chars(const uint32_t chars[], size_t count);
uint32_t composed_key_from_key(uint32_t prev_key, uint32_t next_char);
const struct composed *composed_lookup(struct composed *root, uint32_t key);
const struct composed *composed_lookup_without_collision(
struct composed *root, uint32_t *key,
const char32_t *prefix, size_t prefix_len, char32_t wc, int forced_width);
void composed_insert(struct composed **root, struct composed *node);
void composed_free(struct composed *root);

1970
config.c

File diff suppressed because it is too large Load diff

245
config.h
View file

@ -1,7 +1,8 @@
#pragma once
#include <stdint.h>
#include <regex.h>
#include <stdbool.h>
#include <stdint.h>
#include <uchar.h>
#include <xkbcommon/xkbcommon.h>
@ -22,7 +23,17 @@ struct pt_or_px {
float pt;
};
enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BEAM };
struct font_size_adjustment {
struct pt_or_px pt_or_px;
float percent;
};
enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BEAM, CURSOR_HOLLOW };
enum cursor_unfocused_style {
CURSOR_UNFOCUSED_UNCHANGED,
CURSOR_UNFOCUSED_HOLLOW,
CURSOR_UNFOCUSED_NONE
};
enum conf_size_type {CONF_SIZE_PX, CONF_SIZE_CELLS};
@ -33,12 +44,14 @@ struct config_font {
};
DEFINE_LIST(struct config_font);
#if 0
struct config_key_modifiers {
bool shift;
bool alt;
bool ctrl;
bool super;
};
#endif
struct argv {
char **args;
@ -48,6 +61,7 @@ enum binding_aux_type {
BINDING_AUX_NONE,
BINDING_AUX_PIPE,
BINDING_AUX_TEXT,
BINDING_AUX_REGEX,
};
struct binding_aux {
@ -61,6 +75,8 @@ struct binding_aux {
uint8_t *data;
size_t len;
} text;
char *regex_name;
};
};
@ -69,14 +85,12 @@ enum key_binding_type {
MOUSE_BINDING,
};
struct config_key_binding_text {
char *text;
bool master_copy;
};
typedef tll(char *) config_modifier_list_t;
struct config_key_binding {
int action; /* One of the varios bind_action_* enums from wayland.h */
struct config_key_modifiers modifiers;
int action; /* One of the various bind_action_* enums from wayland.h */
//struct config_key_modifiers modifiers;
config_modifier_list_t modifiers;
union {
/* Key bindings */
struct {
@ -104,11 +118,113 @@ struct config_spawn_template {
struct argv argv;
};
struct env_var {
char *name;
char *value;
};
typedef tll(struct env_var) env_var_list_t;
struct custom_regex {
char *name;
char *regex;
regex_t preg;
struct config_spawn_template launch;
};
struct color_theme {
uint32_t fg;
uint32_t bg;
uint32_t flash;
uint32_t flash_alpha;
uint32_t table[256];
uint16_t alpha;
uint32_t selection_fg;
uint32_t selection_bg;
uint32_t url;
uint32_t dim[8];
uint32_t sixel[16];
enum {
DIM_BLEND_TOWARDS_BLACK,
DIM_BLEND_TOWARDS_WHITE,
} dim_blend_towards;
enum {
ALPHA_MODE_DEFAULT,
ALPHA_MODE_MATCHING,
ALPHA_MODE_ALL
} alpha_mode;
struct {
uint32_t text;
uint32_t cursor;
} cursor;
struct {
uint32_t fg;
uint32_t bg;
} jump_label;
struct {
uint32_t fg;
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 cursor: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;
bool blur;
};
enum which_color_theme {
COLOR_THEME_DARK,
COLOR_THEME_LIGHT,
COLOR_THEME_1, /* Deprecated */
COLOR_THEME_2, /* Deprecated */
};
enum shm_bit_depth {
SHM_BITS_AUTO,
SHM_BITS_8,
SHM_BITS_10,
SHM_BITS_16,
};
enum center_when {
CENTER_INVALID,
CENTER_NEVER,
CENTER_FULLSCREEN,
CENTER_MAXIMIZED_AND_FULLSCREEN,
CENTER_ALWAYS,
};
struct config {
char *conf_path;
char *term;
char *shell;
char *title;
char *app_id;
char *toplevel_tag;
char32_t *word_delimiters;
bool login_shell;
bool locked_title;
@ -119,20 +235,34 @@ struct config {
uint32_t height;
} size;
unsigned pad_x;
unsigned pad_y;
bool center;
unsigned pad_left;
unsigned pad_top;
unsigned pad_right;
unsigned pad_bottom;
enum center_when center_when;
bool resize_by_cells;
bool resize_keep_grid;
uint16_t resize_delay_ms;
struct {
float amount;
} dim;
struct {
bool enabled;
bool palette_based;
float amount;
} bold_in_bright;
enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode;
enum {DPI_AWARE_AUTO, DPI_AWARE_YES, DPI_AWARE_NO} dpi_aware;
bool dpi_aware;
bool gamma_correct;
bool uppercase_regex_insert;
struct config_font_list fonts[4];
struct font_size_adjustment font_size_adjustment;
/* Custom font metrics (-1 = use real font metrics) */
struct pt_or_px line_height;
@ -144,13 +274,27 @@ struct config {
bool use_custom_underline_offset;
struct pt_or_px underline_offset;
struct pt_or_px underline_thickness;
struct pt_or_px strikeout_thickness;
bool box_drawings_uses_font_glyphs;
bool can_shape_grapheme;
struct {
enum {
OSC52_DISABLED,
OSC52_COPY_ENABLED,
OSC52_PASTE_ENABLED,
OSC52_ENABLED,
} osc52;
} security;
struct {
bool urgent;
bool notify;
bool flash;
bool system_bell;
struct config_spawn_template command;
bool command_focused;
} bell;
@ -184,49 +328,23 @@ struct config {
OSC8_UNDERLINE_ALWAYS,
} osc8_underline;
char32_t **protocols;
char32_t *uri_characters;
size_t prot_count;
size_t max_prot_len;
char *regex;
regex_t preg;
} url;
struct {
uint32_t fg;
uint32_t bg;
uint32_t table[256];
uint16_t alpha;
uint32_t selection_fg;
uint32_t selection_bg;
uint32_t url;
tll(struct custom_regex) custom_regexes;
uint32_t dim[8];
struct {
uint32_t fg;
uint32_t bg;
} jump_label;
struct {
uint32_t fg;
uint32_t bg;
} scrollback_indicator;
struct {
bool selection:1;
bool jump_label:1;
bool scrollback_indicator:1;
bool url:1;
uint8_t dim;
} use_custom;
} colors;
struct color_theme colors_dark;
struct color_theme colors_light;
enum which_color_theme initial_color_theme;
struct {
enum cursor_style style;
bool blink;
enum cursor_unfocused_style unfocused_style;
struct {
uint32_t text;
uint32_t cursor;
} color;
bool enabled;
uint32_t rate_ms;
} blink;
struct pt_or_px beam_thickness;
struct pt_or_px underline_thickness;
} cursor;
@ -234,7 +352,8 @@ struct config {
struct {
bool hide_when_typing;
bool alternate_scroll_mode;
struct config_key_modifiers selection_override_modifiers;
//struct config_key_modifiers selection_override_modifiers;
config_modifier_list_t selection_override_modifiers;
} mouse;
struct {
@ -263,6 +382,7 @@ struct config {
uint16_t button_width;
bool hide_when_maximized;
bool double_click_to_maximize;
struct {
bool title_set:1;
@ -275,7 +395,7 @@ struct config {
uint32_t buttons;
uint32_t minimize;
uint32_t maximize;
uint32_t quit; /* close collides with #define in epoll-shim */
uint32_t quit; /* 'close' collides with #define in epoll-shim */
uint32_t border;
} color;
@ -293,8 +413,16 @@ struct config {
SELECTION_TARGET_BOTH
} selection_target;
struct config_spawn_template notify;
bool notify_focus_inhibit;
struct {
struct config_spawn_template command;
struct config_spawn_template command_action_arg;
struct config_spawn_template close;
bool inhibit_when_focused;
} desktop_notifications;
env_var_list_t env_vars;
char *utmp_helper_path;
struct {
enum fcft_scaling_filter fcft_filter;
@ -319,8 +447,15 @@ struct config {
bool box_drawing_solid_shades;
bool font_monospace_warn;
bool sixel;
enum shm_bit_depth surface_bit_depth;
uint32_t min_stride_alignment;
bool preapply_damage;
} tweak;
struct {
uint32_t long_press_delay;
} touch;
user_notifications_t notifications;
};
@ -329,17 +464,19 @@ bool config_override_apply(struct config *conf, config_override_t *overrides,
bool config_load(
struct config *conf, const char *path,
user_notifications_t *initial_user_notifications,
config_override_t *overrides, bool errors_are_fatal);
config_override_t *overrides, bool errors_are_fatal,
bool as_server);
void config_free(struct config *conf);
struct config *config_clone(const struct config *old);
bool config_font_parse(const char *pattern, struct config_font *font);
void config_font_list_destroy(struct config_font_list *font_list);
#if 0
struct seat;
xkb_mod_mask_t
conf_modifiers_to_mask(
const struct seat *seat, const struct config_key_modifiers *modifiers);
#endif
bool check_if_font_is_monospaced(
const char *pattern, user_notifications_t *notifications);

769
csi.c

File diff suppressed because it is too large Load diff

128
cursor-shape.c Normal file
View file

@ -0,0 +1,128 @@
#include <stdlib.h>
#include <string.h>
#define LOG_MODULE "cursor-shape"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "cursor-shape.h"
#include "debug.h"
#include "util.h"
const char *const *
cursor_shape_to_string(enum cursor_shape shape)
{
static const char *const table[][CURSOR_SHAPE_COUNT]= {
[CURSOR_SHAPE_NONE] = {NULL},
[CURSOR_SHAPE_HIDDEN] = {"hidden", NULL},
[CURSOR_SHAPE_LEFT_PTR] = {"default", "left_ptr", NULL},
[CURSOR_SHAPE_TEXT] = {"text", "xterm", NULL},
[CURSOR_SHAPE_TOP_LEFT_CORNER] = {"nw-resize", "top_left_corner", NULL},
[CURSOR_SHAPE_TOP_RIGHT_CORNER] = {"ne-resize", "top_right_corner", NULL},
[CURSOR_SHAPE_BOTTOM_LEFT_CORNER] = {"sw-resize", "bottom_left_corner", NULL},
[CURSOR_SHAPE_BOTTOM_RIGHT_CORNER] = {"se-resize", "bottom_right_corner", NULL},
[CURSOR_SHAPE_LEFT_SIDE] = {"w-resize", "left_side", NULL},
[CURSOR_SHAPE_RIGHT_SIDE] = {"e-resize", "right_side", NULL},
[CURSOR_SHAPE_TOP_SIDE] = {"n-resize", "top_side", NULL},
[CURSOR_SHAPE_BOTTOM_SIDE] = {"s-resize", "bottom_side", NULL},
};
xassert(shape <= ALEN(table));
return table[shape];
}
enum wp_cursor_shape_device_v1_shape
cursor_shape_to_server_shape(enum cursor_shape shape)
{
static const enum wp_cursor_shape_device_v1_shape table[CURSOR_SHAPE_COUNT] = {
[CURSOR_SHAPE_LEFT_PTR] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT,
[CURSOR_SHAPE_TEXT] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT,
[CURSOR_SHAPE_TOP_LEFT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE,
[CURSOR_SHAPE_TOP_RIGHT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE,
[CURSOR_SHAPE_BOTTOM_LEFT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE,
[CURSOR_SHAPE_BOTTOM_RIGHT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE,
[CURSOR_SHAPE_LEFT_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE,
[CURSOR_SHAPE_RIGHT_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE,
[CURSOR_SHAPE_TOP_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE,
[CURSOR_SHAPE_BOTTOM_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE,
};
xassert(shape <= ALEN(table));
xassert(table[shape] != 0);
return table[shape];
}
enum wp_cursor_shape_device_v1_shape
cursor_string_to_server_shape(const char *xcursor, int bound_version)
{
if (xcursor == NULL)
return 0;
static const char *const table[][2] = {
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT] = {"default", "left_ptr"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU] = {"context-menu"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP] = {"help", "question_arrow"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER] = {"pointer", "hand"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS] = {"progress", "left_ptr_watch"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT] = {"wait", "watch"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL] = {"cell"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR] = {"crosshair", "cross"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT] = {"text", "xterm"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT] = {"vertical-text"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS] = {"alias", "dnd-link"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY] = {"copy", "dnd-copy"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE] = {"move", "dnd-move"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP] = {"no-drop", "dnd-no-drop"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED] = {"not-allowed", "crossed_circle"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB] = {"grab", "hand1"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING] = {"grabbing"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE] = {"e-resize", "right_side"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE] = {"n-resize", "top_side"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE] = {"ne-resize", "top_right_corner"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE] = {"nw-resize", "top_left_corner"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE] = {"s-resize", "bottom_side"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE] = {"se-resize", "bottom_right_corner"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE] = {"sw-resize", "bottom_left_corner"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE] = {"w-resize", "left_side"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE] = {"ew-resize", "sb_h_double_arrow"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE] = {"ns-resize", "sb_v_double_arrow"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE] = {"nesw-resize", "fd_double_arrow"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE] = {"nwse-resize", "bd_double_arrow"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE] = {"col-resize", "sb_h_double_arrow"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE] = {"row-resize", "sb_v_double_arrow"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL] = {"all-scroll", "fleur"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN] = {"zoom-in"},
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT] = {"zoom-out"},
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION) /* 1.42 */
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK] = {"dnd-ask"},
#endif
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION) /* 1.42 */
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE] = {"all-resize"},
#endif
};
for (size_t i = 0; i < ALEN(table); i++) {
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION)
if (i == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK &&
bound_version < WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION)
{
continue;
}
#endif
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION)
if (i == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE &&
bound_version < WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION)
{
continue;
}
#endif
for (size_t j = 0; j < ALEN(table[i]); j++) {
if (table[i][j] != NULL && streq(xcursor, table[i][j])) {
return i;
}
}
}
return 0;
}

29
cursor-shape.h Normal file
View file

@ -0,0 +1,29 @@
#pragma once
#include <cursor-shape-v1.h>
enum cursor_shape {
CURSOR_SHAPE_NONE,
CURSOR_SHAPE_CUSTOM,
CURSOR_SHAPE_HIDDEN,
CURSOR_SHAPE_LEFT_PTR,
CURSOR_SHAPE_TEXT,
CURSOR_SHAPE_TOP_LEFT_CORNER,
CURSOR_SHAPE_TOP_RIGHT_CORNER,
CURSOR_SHAPE_BOTTOM_LEFT_CORNER,
CURSOR_SHAPE_BOTTOM_RIGHT_CORNER,
CURSOR_SHAPE_LEFT_SIDE,
CURSOR_SHAPE_RIGHT_SIDE,
CURSOR_SHAPE_TOP_SIDE,
CURSOR_SHAPE_BOTTOM_SIDE,
CURSOR_SHAPE_COUNT,
};
const char *const *cursor_shape_to_string(enum cursor_shape shape);
enum wp_cursor_shape_device_v1_shape cursor_shape_to_server_shape(
enum cursor_shape shape);
enum wp_cursor_shape_device_v1_shape cursor_string_to_server_shape(
const char *xcursor, int bound_version);

91
dcs.c
View file

@ -9,6 +9,7 @@
#include "util.h"
#include "vt.h"
#include "xmalloc.h"
#include "xsnprintf.h"
static bool
ensure_size(struct terminal *term, size_t required_size)
@ -111,14 +112,11 @@ static void
xtgettcap_reply(struct terminal *term, const char *hex_cap_name, size_t len)
{
char *name = hex_decode(hex_cap_name, len);
if (name == NULL)
goto err;
if (name == NULL) {
LOG_WARN("XTGETTCAP: invalid hex encoding, ignoring capability");
return;
}
#if 0
const struct foot_terminfo_entry *entry =
bsearch(name, terminfo_capabilities, ALEN(terminfo_capabilities),
sizeof(*entry), &terminfo_entry_compar);
#endif
const char *value;
bool valid_capability = lookup_capability(name, &value);
xassert(!valid_capability || value != NULL);
@ -141,12 +139,12 @@ xtgettcap_reply(struct terminal *term, const char *hex_cap_name, size_t len)
/*
* Reply format:
* \EP 1 + r cap=value \E\\
* Where cap and value are hex encoded ascii strings
* Where 'cap' and 'value are hex encoded ascii strings
*/
char *reply = xmalloc(
5 + /* DCS 1 + r (\EP1+r) */
len + /* capability name, hex encoded */
1 + /* = */
1 + /* '=' */
strlen(value) * 2 + /* capability value, hex encoded */
2 + /* ST (\E\\) */
1);
@ -203,6 +201,12 @@ xtgettcap_unhook(struct terminal *term)
const char *const end = (const char *)&term->vt.dcs.data[left];
const char *p = (const char *)term->vt.dcs.data;
if (p == NULL) {
/* Request is empty; send an error reply, without any capabilities */
term_to_slave(term, "\033P0+r\033\\", 7);
return;
}
while (true) {
const char *sep = memchr(p, ';', left);
size_t cap_len;
@ -242,7 +246,7 @@ decrqss_put(struct terminal *term, uint8_t c)
return;
struct vt *vt = &term->vt;
if (vt->dcs.idx > 2)
if (vt->dcs.idx >= 2)
return;
vt->dcs.data[vt->dcs.idx++] = c;
}
@ -256,8 +260,8 @@ decrqss_unhook(struct terminal *term)
/*
* A note on the Ps parameter in the reply: many DEC manual
* instances (e.g. https://vt100.net/docs/vt510-rm/DECRPSS) claim
* that 0 means request is valid, and 1 means request is
* invalid.
* that 0 means "request is valid", and 1 means "request is
* invalid".
*
* However, this appears to be a typo; actual hardware inverts the
* response (as does XTerm and mlterm):
@ -267,9 +271,9 @@ decrqss_unhook(struct terminal *term)
if (n == 1 && query[0] == 'r') {
/* DECSTBM - Set Top and Bottom Margins */
char reply[64];
int len = snprintf(reply, sizeof(reply), "\033P1$r%d;%dr\033\\",
term->scroll_region.start + 1,
term->scroll_region.end);
size_t len = xsnprintf(reply, sizeof(reply), "\033P1$r%d;%dr\033\\",
term->scroll_region.start + 1,
term->scroll_region.end);
term_to_slave(term, reply, len);
}
@ -293,8 +297,15 @@ decrqss_unhook(struct terminal *term)
append_sgr_attr("2");
if (a->italic)
append_sgr_attr("3");
if (a->underline)
append_sgr_attr("4");
if (a->underline) {
if (term->vt.underline.style > UNDERLINE_SINGLE) {
char value[4];
size_t val_len =
xsnprintf(value, sizeof(value), "4:%d", term->vt.underline.style);
append_sgr_attr_n(&reply, &len, value, val_len);
} else
append_sgr_attr("4");
}
if (a->blink)
append_sgr_attr("5");
if (a->reverse)
@ -310,7 +321,7 @@ decrqss_unhook(struct terminal *term)
case COLOR_BASE16: {
char value[4];
int val_len = snprintf(
size_t val_len = xsnprintf(
value, sizeof(value), "%u",
a->fg >= 8 ? a->fg - 8 + 90 : a->fg + 30);
append_sgr_attr_n(&reply, &len, value, val_len);
@ -319,7 +330,7 @@ decrqss_unhook(struct terminal *term)
case COLOR_BASE256: {
char value[16];
int val_len = snprintf(value, sizeof(value), "38:5:%u", a->fg);
size_t val_len = xsnprintf(value, sizeof(value), "38:5:%u", a->fg);
append_sgr_attr_n(&reply, &len, value, val_len);
break;
}
@ -330,7 +341,7 @@ decrqss_unhook(struct terminal *term)
uint8_t b = a->fg >> 0;
char value[32];
int val_len = snprintf(
size_t val_len = xsnprintf(
value, sizeof(value), "38:2::%hhu:%hhu:%hhu", r, g, b);
append_sgr_attr_n(&reply, &len, value, val_len);
break;
@ -343,7 +354,7 @@ decrqss_unhook(struct terminal *term)
case COLOR_BASE16: {
char value[4];
int val_len = snprintf(
size_t val_len = xsnprintf(
value, sizeof(value), "%u",
a->bg >= 8 ? a->bg - 8 + 100 : a->bg + 40);
append_sgr_attr_n(&reply, &len, value, val_len);
@ -352,7 +363,7 @@ decrqss_unhook(struct terminal *term)
case COLOR_BASE256: {
char value[16];
int val_len = snprintf(value, sizeof(value), "48:5:%u", a->bg);
size_t val_len = xsnprintf(value, sizeof(value), "48:5:%u", a->bg);
append_sgr_attr_n(&reply, &len, value, val_len);
break;
}
@ -363,13 +374,39 @@ decrqss_unhook(struct terminal *term)
uint8_t b = a->bg >> 0;
char value[32];
int val_len = snprintf(
size_t val_len = xsnprintf(
value, sizeof(value), "48:2::%hhu:%hhu:%hhu", r, g, b);
append_sgr_attr_n(&reply, &len, value, val_len);
break;
}
}
switch (term->vt.underline.color_src) {
case COLOR_DEFAULT:
case COLOR_BASE16:
break;
case COLOR_BASE256: {
char value[16];
size_t val_len = xsnprintf(
value, sizeof(value), "58:5:%u", term->vt.underline.color);
append_sgr_attr_n(&reply, &len, value, val_len);
break;
}
case COLOR_RGB: {
uint8_t r = term->vt.underline.color >> 16;
uint8_t g = term->vt.underline.color >> 8;
uint8_t b = term->vt.underline.color >> 0;
char value[32];
size_t val_len = xsnprintf(
value, sizeof(value), "58:2::%hhu:%hhu:%hhu", r, g, b);
append_sgr_attr_n(&reply, &len, value, val_len);
break;
}
}
#undef append_sgr_attr_n
reply[len - 1] = 'm';
@ -385,6 +422,7 @@ decrqss_unhook(struct terminal *term)
int mode;
switch (term->cursor_style) {
case CURSOR_HOLLOW: /* FALLTHROUGH */
case CURSOR_BLOCK: mode = 2; break;
case CURSOR_UNDERLINE: mode = 4; break;
case CURSOR_BEAM: mode = 6; break;
@ -395,7 +433,7 @@ decrqss_unhook(struct terminal *term)
mode--;
char reply[16];
int len = snprintf(reply, sizeof(reply), "\033P1$r%d q\033\\", mode);
size_t len = xsnprintf(reply, sizeof(reply), "\033P1$r%d q\033\\", mode);
term_to_slave(term, reply, len);
}
@ -424,11 +462,10 @@ dcs_hook(struct terminal *term, uint8_t final)
break;
}
int p1 = vt_param_get(term, 0, 0);
int p2 = vt_param_get(term, 1,0);
int p2 = vt_param_get(term, 1, 0);
int p3 = vt_param_get(term, 2, 0);
sixel_init(term, p1, p2, p3);
term->vt.dcs.put_handler = &sixel_put;
term->vt.dcs.put_handler = sixel_init(term, p1, p2, p3);
term->vt.dcs.unhook_handler = &sixel_unhook;
break;
}

View file

@ -8,7 +8,7 @@ All benchmarks are done using [vtebench](https://github.com/alacritty/vtebench):
./target/release/vtebench -b ./benchmarks --dat /tmp/<terminal>
```
## 2021-06-25
## 2022-05-12
### System
@ -30,21 +30,21 @@ Scrollback: 10000 lines
### Results
| Benchmark (times in ms) | Foot (GCC+PGO) 1.9.2 | Foot 1.9.2 | Alacritty 0.9.0 | URxvt 9.26 | XTerm 369 |
|-------------------------------|---------------------:|-----------:|----------------:|-----------:|----------:|
| cursor motion | 13.69 | 15.63 | 29.16 | 23.69 | 1341.75 |
| dense cells | 40.77 | 50.76 | 92.39 | 13912.00 | 1959.00 |
| light cells | 5.41 | 6.49 | 12.25 | 16.14 | 66.21 |
| scrollling | 125.43 | 133.00 | 110.90 | 98.29 | 4010.67 |
| scrolling bottom region | 111.90 | 103.95 | 106.35 | 103.65 | 3787.00 |
| scrolling bottom small region | 120.93 | 112.48 | 129.61 | 137.21 | 3796.67 |
| scrolling fullscreen | 5.42 | 5.67 | 11.52 | 12.00 | 124.33 |
| scrolling top region | 110.66 | 107.61 | 100.52 | 340.90 | 3835.33 |
| scrolling top small region | 120.48 | 111.66 | 129.62 | 213.72 | 3805.33 |
| unicode | 10.19 | 11.27 | 14.72 | 787.77 | 4741.00 |
| Benchmark (times in ms) | Foot (GCC+PGO) 1.12.1 | Foot 1.12.1 | Alacritty 0.10.1 | URxvt 9.26 | XTerm 372 |
|-------------------------------|----------------------:|------------:|-----------------:|-----------:|----------:|
| cursor motion | 10.40 | 14.07 | 24.97 | 23.38 | 1622.86 |
| dense cells | 29.58 | 45.46 | 97.45 | 10828.00 | 2323.00 |
| light cells | 4.34 | 4.40 | 12.84 | 12.17 | 49.81 |
| scrollling | 135.31 | 116.35 | 121.69 | 108.30 | 4041.33 |
| scrolling bottom region | 118.19 | 109.70 | 105.26 | 118.80 | 3875.00 |
| scrolling bottom small region | 132.41 | 122.11 | 122.83 | 151.30 | 3839.67 |
| scrolling fullscreen | 5.70 | 5.66 | 10.92 | 12.09 | 124.25 |
| scrolling top region | 144.19 | 121.78 | 135.81 | 159.24 | 3858.33 |
| scrolling top small region | 135.95 | 119.01 | 115.46 | 216.55 | 3872.67 |
| unicode | 11.56 | 10.92 | 15.94 | 1012.27 | 4779.33 |
## 2021-03-20
## 2022-05-12
### System
@ -67,15 +67,15 @@ Scrollback=10000 lines
### Results
| Benchmark (times in ms) | Foot (GCC+PGO) 1.9.2 | Foot 1.9.2 | Alacritty 0.9.0 | URxvt 9.26 | XTerm 369 |
|-------------------------------|---------------------:|-----------:|----------------:|-----------:|----------:|
| cursor motion | 13.50 | 16.32 | 27.10 | 23.46 | 1415.38 |
| dense cells | 38.77 | 53.13 | 89.36 | 2007.00 | 2126.60 |
| light cells | 7.73 | 8.72 | 20.35 | 21.06 | 113.34 |
| scrollling | 150.27 | 153.76 | 145.07 | 139.23 | 10088.00 |
| scrolling bottom region | 144.88 | 148.44 | 129.13 | 156.86 | 10166.00 |
| scrolling bottom small region | 142.45 | 137.81 | 167.63 | 183.35 | 9831.50 |
| scrolling fullscreen | 11.23 | 11.91 | 20.12 | 21.21 | 290.80 |
| scrolling top region | 143.80 | 147.37 | 148.63 | 489.57 | 10029.00 |
| scrolling top small region | 139.76 | 144.37 | 165.97 | 308.76 | 9877.00 |
| unicode | 21.94 | 21.50 | 27.72 | 1344.88 | 7402.00 |
| Benchmark (times in ms) | Foot (GCC+PGO) 1.12.1 | Foot 1.12.1 | Alacritty 0.10.1 | URxvt 9.26 | XTerm 372 |
|-------------------------------|----------------------:|------------:|-----------------:|-----------:|----------:|
| cursor motion | 15.03 | 16.74 | 23.22 | 24.14 | 1381.63 |
| dense cells | 43.56 | 54.10 | 89.43 | 1807.17 | 1945.50 |
| light cells | 7.96 | 9.66 | 20.19 | 21.31 | 122.44 |
| scrollling | 146.02 | 150.47 | 129.22 | 129.84 | 10140.00 |
| scrolling bottom region | 138.36 | 137.42 | 117.06 | 141.87 | 10136.00 |
| scrolling bottom small region | 137.40 | 134.66 | 128.97 | 208.77 | 9930.00 |
| scrolling fullscreen | 11.66 | 12.02 | 19.69 | 21.96 | 315.80 |
| scrolling top region | 143.81 | 133.47 | 132.51 | 475.81 | 10267.00 |
| scrolling top small region | 133.72 | 135.32 | 145.10 | 314.13 | 10074.00 |
| unicode | 20.89 | 21.78 | 26.11 | 5687.00 | 15740.00 |

View file

@ -22,7 +22,7 @@ This document describes all the control sequences supported by foot.
[[ *Sequence*
:[ *Name*
:[ *Description*
:< *Description*
| \\a
: BEL
: Depends on what *bell* in *foot.ini*(5) is set to.
@ -60,7 +60,7 @@ equivalent to 8-bit C1 controls.
[[ *Sequence*
:[ *Name*
:[ *Origin*
:[ *Description*
:< *Description*
| \\E 7
: DECSC
: VT100
@ -140,7 +140,7 @@ single CSI sequence by separating them with semicolons: *\\E[ 1;2;3
m*.
[[ *Parameter*
:[ *Description*
:< *Description*
| 0
: Reset all attributes
| 1
@ -150,7 +150,7 @@ m*.
| 3
: Italic
| 4
: Underline
: Underline, including styled underlines
| 5
: Blink
| 7
@ -159,6 +159,8 @@ m*.
: Conceal; text is not visible, but is copiable
| 9
: Crossed-out/strike
| 21
: Double underline
| 22
: Disable *bold* and *dim*
| 23
@ -176,15 +178,19 @@ m*.
| 30-37
: Select foreground color (using *regularN* in *foot.ini*(5))
| 38
: See "indexed and RGB colors" below
: Select foreground color, see "indexed and RGB colors" below
| 39
: Use the default foreground color (*foreground* in *foot.ini*(5))
| 40-47
: Select background color (using *regularN* in *foot.ini*(5))
| 48
: See "indexed and RGB colors" below
: Select background color, see "indexed and RGB colors" below
| 49
: Use the default background color (*background* in *foot.ini*(5))
| 58
: Select underline color, see "indexed and RGB colors" below
| 59
: Use the default underline color
| 90-97
: Select foreground color (using *brightN* in *foot.ini*(5))
| 100-107
@ -223,7 +229,7 @@ following 4 escape sequences:
[[ *Sequence*
:[ *Name*
:[ *Description*
:< *Description*
| \\E[ ? _Pm_ h
: DECSET
: Enable private mode
@ -243,7 +249,7 @@ that corresponds to one of the following modes:
[[ *Parameter*
:[ *Origin*
:[ *Description*
:< *Description*
| 1
: VT100
: Cursor keys mode (DECCKM)
@ -328,6 +334,15 @@ that corresponds to one of the following modes:
| 2026
: terminal-wg
: Application synchronized updates mode
| 2027
: contour
: Grapheme cluster processing
| 2031
: contour
: Request color theme updates
| 2048
: TODO
: In-band window resize notifications
| 8452
: xterm
: Position cursor to the right of sixels, instead of on the next line
@ -344,7 +359,7 @@ manipulation sequences. The generic format is:
[[ *Parameter 1*
:[ *Parameter 2*
:[ *Description*
:< *Description*
| 11
: -
: Report if window is iconified. Foot always reports *1* - not iconified.
@ -376,15 +391,24 @@ manipulation sequences. The generic format is:
| 19
: -
: Report screen size, in characters.
| 20
: -
: Report icon label.
| 22
: -
: Push window title+icon. Foot does not support pushing the icon.
: Push window title+icon.
| 22
: 1
: Push window icon.
| 22
: 2
: Push window title.
| 23
: -
: Pop window title+icon. Foot does not support popping the icon.
: Pop window title+icon.
| 23
: 1
: Pop window icon.
| 23
: 2
: Pop window title.
@ -394,7 +418,7 @@ manipulation sequences. The generic format is:
[[ *Parameter*
:[ *Name*
:[ *Origin*
:[ *Description*
:< *Description*
| \\E[ _Ps_ c
: DA
: VT100
@ -440,13 +464,13 @@ manipulation sequences. The generic format is:
| \\E[ _Pm_ h
: SM
: VT100
: Set mode. _Pm_=4 -> enable IRM (insert mode). All other values of
_Pm_ are unsupported.
: Set mode. _Pm_=4 -> enable IRM (Insertion Replacement Mode). All
other values of _Pm_ are unsupported.
| \\E[ _Pm_ l
: RM
: VT100
: Reset mode. _Pm_=4 -> disable IRM (insert mode). All other values of
_Pm_ are unsupported.
: Reset mode. _Pm_=4 -> disable IRM (Insertion Replacement Mode). All
other values of _Pm_ are unsupported.
| \\E[ _Ps_ n
: DSR
: VT100
@ -484,7 +508,39 @@ manipulation sequences. The generic format is:
| \\E[ ? _Ps_ $ p
: DECRQM
: VT320
: Request DEC private mode.
: Request status of DEC private mode. The _Ps_ parameter corresponds
to one of the values mentioned in the "Private Modes" section above
(as set with DECSET/DECRST).
| \\E[ _Ps_ $ p
: DECRQM
: VT320
: Request status of ECMA-48/ANSI mode. See the descriptions for SM/RM
above for recognized _Ps_ values.
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ ; _Pm_ $ r
: DECCARA
: VT400
: Change attributes in rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_
denotes the rectangle, _Pm_ denotes the SGR attributes.
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ ; _Pm_ $ t
: DECRARA
: VT400
: Invert attributes in rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_
denotes the rectangle, _Pm_ denotes the SGR attributes.
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ ; _Pp_ ; _Pt_ ; _Pl_ ; _Pp_ $ v
: DECCRA
: VT400
: Copy rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_ denotes the
rectangle, _Pt_ and _Pl_ denotes the target location.
| \\E[ _Pc_ ; _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ $ x
: DECFRA
: VT420
: Fill rectangular area. _Pc_ is the character to use, _Pt_, _Pl_,
_Pb_ and _Pr_ denotes the rectangle.
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ $ z
: DECERA
: VT400
: Erase rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_ denotes the
rectangle.
| \\E[ _Ps_ T
: SD
: VT420
@ -504,7 +560,7 @@ manipulation sequences. The generic format is:
| \\E[ = _Ps_ c
: DA3
: VT510
: send tertiary device attributes. Foot responds with "FOOT", in
: Send tertiary device attributes. Foot responds with "FOOT", in
hexadecimal.
| \\E[ _Pm_ d
: VPA
@ -565,6 +621,10 @@ manipulation sequences. The generic format is:
: xterm
: Set level of the _modifyOtherKeys_ property to _Pv_. Note that foot
only supports level 1 and 2, where level 1 is the default setting.
| \\E[ ? _Pp_ m
: XTQMODKEYS
: xterm
: Query key modifier options
| \\E[ > 4 n
: <unnamed>
: xterm
@ -587,6 +647,26 @@ manipulation sequences. The generic format is:
: <unnamed>
: kitty
: Update current Kitty keyboard flags, according to _mode_.
| \\E[ # P
: XTPUSHCOLORS
: xterm
: Push current color palette onto stack
| \\E[ # Q
: XTPOPCOLORS
: xterm
: Pop color palette from stack
| \\E[ # R
: XTREPORTCOLORS
: xterm
: Report the current entry on the palette stack, and the number of
palettes stored on the stack.
| \\E[ ? 996 n
: Query the current (color) theme mode
: contour
: The current color theme mode (light or dark) is reported as *CSI ?
997 ; 1|2 n*, where *1* means dark and *2* light. By convention, the
primary theme in foot is considered dark, and the alternative theme
light.
# OSC
@ -595,11 +675,13 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_.
[[ *Sequence*
:[ *Origin*
:[ *Description*
:< *Description*
| \\E] 0 ; _Pt_ \\E\\
: xterm
: Set window icon and title to _Pt_ (foot does not support setting the
icon)
: Set window icon and title to _Pt_.
| \\E] 1 ; _Pt_ \\E\\
: xterm
: Set window icon to _Pt_.
| \\E] 2 ; _Pt_ \\E\\
: xterm
: Set window title to _Pt_
@ -657,6 +739,13 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_.
: Copy _Pd_ (base64 encoded text) to the clipboard. _Pc_ denotes the
target: *c* targets the clipboard and *s* and *p* the primary
selection.
| \\E] 66 ; _params_ ; text \\E\\
: kitty
: Text sizing protocol (only 'w', width, supported)
| \\E] 99 ; _params_ ; _payload_ \\E\\
: kitty
: Desktop notification; uses *desktop-notifications.command* in
*foot.ini*(5).
| \\E] 104 ; _c_ \\E\\
: xterm
: Reset color number _c_ (multiple semicolon separated _c_ values may
@ -677,12 +766,27 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_.
| \\E] 119 \\E\\
: xterm
: Reset selection foreground color
| \\E] 133 ; A \\E\\
: FinalTerm
: Mark start of shell prompt
| \\E] 133 ; C \\E\\
: FinalTerm
: Mark start of command output
| \\E] 133 ; D \\E\\
: FinalTerm
: Mark end of command output
| \\E] 176 ; _app-id_ \\E\\
: foot
: Set app ID. _app-id_ is optional; if assigned,
the terminal window App ID will be set to the value.
An empty App ID resets the value to the default.
| \\E] 555 \\E\\
: foot
: Flash the entire terminal (foot extension)
| \\E] 777;notify;_title_;_msg_ \\E\\
: urxvt
: Desktop notification, uses *notify* in *foot.ini*(5).
: Desktop notification, uses *desktop-notifications.command* in
*foot.ini*(5).
# DCS
@ -690,17 +794,17 @@ All _DCS_ sequences begin with *\\EP* (sometimes abbreviated _DCS_),
and are terminated by *\\E\\* (ST).
[[ *Sequence*
:[ *Description*
:< *Description*
| \\EP q <sixel data> \\E\\
: Emit a sixel image at the current cursor position
| \\P $ q <query> \\E\\
| \\EP $ q <query> \\E\\
: Request selection or setting (DECRQSS). Implemented queries:
DECSTBM, SGR and DECSCUSR.
| \\EP = _C_ s \\E\\
: Begin (_C_=*1*) or end (_C_=*2*) application synchronized updates.
This sequence is supported for compatibility reasons, but it's
recommended to use private mode 2026 (see above) instead.
| \\EP + q <hex encoded capability name> \\E\\
| \\EP + q <hex encoded capability name> \\E\\
: Query builtin terminfo database (XTGETTCAP)

View file

@ -20,13 +20,16 @@ will start a new terminal window with your default shell.
You can override the default shell by appending a custom command to
the foot command line
*foot sh -c "echo hello world && sleep 5"*
*foot htop*
# OPTIONS
*-c*,*--config*=_PATH_
Path to configuration file, see *foot.ini*(5) for details.
The configuration file is automatically passed to new terminals
spawned via *spawn-terminal* (see *foot.ini*(5)).
*-C*,*--check-config*
Verify configuration and then exit with 0 if ok, otherwise exit
with 230 (see *EXIT STATUS*).
@ -65,7 +68,12 @@ the foot command line
*-a*,*--app-id*=_ID_
Value to set the *app-id* property on the Wayland window
to. Default: _foot_.
to. Default: _foot_ (normal mode), or _footclient_ (server mode).
*toplevel-tag*=_TAG_
Value to set the *toplevel-tag* property on the Wayland window
to. The compositor can use this value for session management,
window rules etc. Default: _not set_
*-m*,*--maximized*
Start in maximized mode. If both *--maximized* and *--fullscreen*
@ -78,6 +86,13 @@ the foot command line
*-L*,*--login-shell*
Start a login shell, by prepending a '-' to argv[0].
*--pty*
Display an existing pty instead of creating one. This is useful
for interacting with VM consoles.
This option is not currently supported in combination with
*-s*,*--server*.
*-D*,*--working-directory*=_DIR_
Initial working directory for the client application. Default:
_CWD of foot_.
@ -121,14 +136,14 @@ the foot command line
of a socket provided by a supervision daemon (such as systemd or s6), and
use that socket as it's own.
Two systemd units (foot-server@.{service,socket}) are provided to use that
feature with systemd. They need to be instantiated with the value of
$WAYLAND_DISPLAY (multiples instances can co-exists).
Two systemd units (foot-server.{service,socket}) are provided to use that
feature with systemd. To use socket activation, only enable the
socket unit.
Note that starting *foot --server* as a systemd service will use
the environment of the systemd user instance; thus, if you need specific
environment variables, you'll need to import them using *systemctl --user
import-environment* or use a drop-in for the foot-server service.
the environment of the systemd user instance; thus, you'll need
to import *$WAYLAND_DISPLAY* in it using *systemctl --user
import-environment WAYLAND_DISPLAY*.
*-H*,*--hold*
Remain open after child process exits.
@ -142,7 +157,7 @@ the foot command line
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
Log level, used both for log output on stderr as well as
syslog. Default: _info_.
syslog. Default: _warning_.
*-l*,*--log-colorize*=[{*never*,*always*,*auto*}]
Enables or disables colorization of log output on stderr. Default:
@ -183,16 +198,16 @@ default) available; see *foot.ini*(5).
Paste from _clipboard_
*shift*+*insert*
Paste from the _primary selection_.
Paste from the _primary selection_
*ctrl*+*shift*+*r*
Start a scrollback search
*ctrl*+*+*, *ctrl*+*=*
Increase font size by 0.5pt
Increase font size
*ctrl*+*-*
Decrease font size by 0.5pt
Decrease font size
*ctrl*+*0*
Reset font size
@ -202,11 +217,23 @@ default) available; see *foot.ini*(5).
_OSC 7_ escape sequence, the new terminal will start in the
current working directory.
*ctrl*+*shift*+*u*
*ctrl*+*shift*+*o*
Activate URL mode, allowing you to "launch" URLs.
*ctrl*+*shift*+*u*
Activate Unicode input.
*ctrl*+*shift*+*z*
Jump to the previous, currently not visible, prompt. Requires
shell integration.
*ctrl*+*shift*+*x*
Jump to the next prompt. Requires shell integration.
## SCROLLBACK SEARCH
These keyboard shortcuts affect the search selection:
*ctrl*+*r*
Search _backward_ for the next match. If the search string is
empty, the last searched-for string is used.
@ -215,7 +242,13 @@ default) available; see *foot.ini*(5).
Search _forward_ for the next match. If the search string is
empty, the last searched-for string is used.
*ctrl*+*w*
*shift*+*right*
Extend current selection to the right by one character.
*shift*+*left*
Extend current selection to the left by one character.
*ctrl*+*w*, *ctrl*+*shift*+*right*
Extend current selection (and thus the search criteria) to the end
of the word, or the next word if currently at a word separating
character.
@ -224,7 +257,16 @@ default) available; see *foot.ini*(5).
Same as *ctrl*+*w*, except that the only word separating
characters are whitespace characters.
*ctrl*+*v*, *ctrl*+*y*
*ctrl*+*shift*+*left*
Extend current selection to the left to the last word boundary.
*shift*+*down*
Extend current selection down one line
*shift*+*up*
Extend current selection up one line.
*ctrl*+*v*, *ctrl*+*shift*+*v*, *ctrl*+*y*, *XF86Paste*
Paste from clipboard into the search buffer.
*shift*+*insert*
@ -238,6 +280,46 @@ default) available; see *foot.ini*(5).
selection. The terminal selection is kept, allowing you to press
*ctrl*+*shift*+*c* to copy it to the clipboard.
These shortcuts affect the search box in scrollback-search mode:
*ctrl*+*b*
Moves the cursor in the search box one **character** to the left.
*ctrl*+*left*, *alt*+*b*
Moves the cursor in the search box one **word** to the left.
*ctrl*+*f*
Moves the cursor in the search box one **character** to the right.
*ctrl*+*right*, *alt*+*f*
Moves the cursor in the search box one **word** to the right.
*Home*, *ctrl*+*a*
Moves the cursor in the search box to the beginning of the input.
*End*, *ctrl*+*e*
Moves the cursor in the search box to the end of the input.
*alt*+*backspace*, *ctrl*+*backspace*
Deletes the **word before** the cursor.
*alt*+*delete*, *ctrl*+*delete*
Deletes the **word after** the cursor.
*ctrl*+*u*
Deletes from the cursor to the start of the input
*ctrl*+*k*
Deletes from the cursor to the end of the input
These shortcuts affect scrolling in scrollback-search mode:
*shift*+*page-up*
Scrolls up/back one page in history.
*shift*+*page-down*
Scroll down/forward one page in history.
## URL MODE
*t*
@ -263,6 +345,10 @@ default) available; see *foot.ini*(5).
characters.
*left*, triple-click
Selects the everything between enclosing quotes, or the entire row
if not inside a quote.
*left*, quad-click
Selects the entire row
*middle*
@ -273,9 +359,28 @@ default) available; see *foot.ini*(5).
selection, while hold-and-drag allows you to interactively resize
the selection.
*ctrl*+*right*
Extend the current selection, but force it to be character wise,
rather than depending on the original selection mode.
*wheel*
Scroll up/down in history
*ctrl*+*wheel*
Increase/decrease font size
## TOUCHSCREEN
*tap*
Emulates mouse left button click.
*drag*
Scrolls up/down in history.
Holding for a while before dragging (time delay can be configured)
emulates mouse dragging with left button held.
# FONT FORMAT
The font is specified in FontConfig syntax. That is, a colon-separated
@ -291,10 +396,10 @@ Foot supports URL detection. But, unlike many other terminal
emulators, where URLs are highlighted when they are hovered and opened
by clicking on them, foot uses a keyboard driven approach.
Pressing *ctrl*+*shift*+*u* enters _“URL mode”_, where all currently
Pressing *ctrl*+*shift*+*o* enters _"Open URL mode"_, where all currently
visible URLs are underlined, and is associated with a
_“jump-label”_. The jump-label indicates the _key sequence_
(e.g. *”AF”*) to use to activate the URL.
_"jump-label"_. The jump-label indicates the _key sequence_
(e.g. *"AF"*) to use to activate the URL.
The key binding can, of course, be customized, like all other key
bindings in foot. See *show-urls-launch* and *show-urls-copy* in
@ -361,7 +466,7 @@ foot will search for a configuration file in the following locations,
in this order:
- *XDG_CONFIG_HOME/foot/foot.ini* (defaulting to
*~/.config/foot/foot.ini* if unset)
*$HOME/.config/foot/foot.ini* if unset)
- *XDG_CONFIG_DIRS/foot/foot.ini* (defaulting to
*/etc/xdg/foot/foot.ini* if unset)
@ -370,6 +475,70 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*.
For more information, see *foot.ini*(5).
# SHELL INTEGRATION
## Current working directory
New foot terminal instances (bound to *ctrl*+*shift*+*n* by default)
will open in the current working directory, if the shell in the
"parent" terminal reports directory changes.
This is done with the OSC-7 escape sequence. Most shells can be
scripted to do this, if they do not support it natively. See the wiki
(https://codeberg.org/dnkl/foot/wiki#user-content-spawning-new-terminal-instances-in-the-current-working-directory)
for details.
## Jumping between prompts
Foot can move the current viewport to focus prompts of already
executed commands (bound to *ctrl*+*shift*+*z*/*x* by default).
For this to work, the shell needs to emit an OSC-133;A
(*\\E]133;A\\E\\\\*) sequence before each prompt.
In zsh, one way to do this is to add a _precmd_ hook:
*precmd() {
print -Pn "\\e]133;A\\e\\\\"
}*
See the wiki
(https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts)
for details, and examples for other shells.
## Piping last command's output
The key binding *pipe-command-output* can pipe the last command's
output to an application of your choice (similar to the other
*pipe-\** key bindings):
*\[key-bindings\]++
pipe-command-output=[sh -c "f=$(mktemp); cat - > $f; footclient emacsclient -nw $f; rm $f"] Control+Shift+g*
When pressing *ctrl*+*shift*+*g*, the last command's output is written
to a temporary file, then an emacsclient is started in a new
footclient instance. The temporary file is removed after the
footclient instance has closed.
For this to work, the shell must emit an OSC-133;C (*\\E]133;C\\E\\\\*)
sequence before command output starts, and an OSC-133;D
(*\\E]133;D\\E\\\\*) when the command output ends.
In fish, one way to do this is to add _preexec_ and _postexec_ hooks:
*function foot_cmd_start --on-event fish_preexec
echo -en "\\e]133;C\\e\\\\"
end*
*function foot_cmd_end --on-event fish_postexec
echo -en "\\e]133;D\\e\\\\"
end*
See the wiki
(https://codeberg.org/dnkl/foot/wiki#user-content-piping-last-commands-output)
for details, and examples for other shells
# TERMINFO
Client applications use the terminfo identifier specified by the
@ -410,10 +579,10 @@ also implemented (and extended, to some degree) by Kitty.
It allows querying the terminal for terminfo classic, file-based,
terminfo definition. For example, if all applications used this
feature, you would no longer have to install foots terminfo on remote
feature, you would no longer have to install foot's terminfo on remote
hosts you SSH into.
XTerms implementation (as of XTerm-370) only supports querying key
XTerm's implementation (as of XTerm-370) only supports querying key
(as in keyboard keys) capabilities, and three custom capabilities:
- TN - terminal name
@ -425,7 +594,7 @@ Kitty has extended this, and also supports querying all integer and
string capabilities.
Foot supports this, and extends it even further, to also include
boolean capabilities. This means foots entire terminfo can be queried
boolean capabilities. This means foot's entire terminfo can be queried
via *XTGETTCAP*.
Note that both Kitty and foot handles responses to multi-capability
@ -436,7 +605,7 @@ capability/value pairs. There are a couple of issues with this:
- The success/fail flag in the beginning of the response is always 1
(success), unless the very first queried capability is invalid.
- XTerm will not respond at all to an invalid capability, unless its
- XTerm will not respond at all to an invalid capability, unless it's
the first one in the XTGETTCAP query.
- XTerm will end the response at the first invalid capability.
@ -458,12 +627,16 @@ In all other cases, the exit code is that of the client application
# ENVIRONMENT
The following environment variables are used by foot:
## Variables used by foot
*SHELL*
The default child process to run, when no _command_ argument is
specified and the *shell* option in *foot.ini*(5) is not set.
*HOME*
Used to determine the location of the configuration file, see
*foot.ini*(5) for details.
*XDG\_CONFIG\_HOME*
Used to determine the location of the configuration file, see
*foot.ini*(5) for details.
@ -488,7 +661,7 @@ The following environment variables are used by foot:
The size to use for *Xcursor*(3) pointers (typically set by the
Wayland compositor).
The following environment variables are set in the child process:
## Variables set in the child process
*TERM*
terminfo/termcap identifier. This is used by client applications
@ -500,6 +673,42 @@ The following environment variables are set in the child process:
This variable is set to *truecolor*, to indicate to client
applications that 24-bit RGB colors are supported.
*PWD*
Current working directory (at the time of launching foot)
*SHELL*
Set to the launched shell, if the shell is valid (it is listed in
*/etc/shells*).
In addition to the variables listed above, custom environment
variables may be defined in *foot.ini*(5).
## Variables *unset* in the child process
*TERM_PROGRAM*
*TERM_PROGRAM_VERSION*
These environment variables are set by certain other terminal
emulators. We unset them, to prevent applications from
misdetecting foot.
In addition to the variables listed above, custom environment
variables to unset may be defined in *foot.ini*(5).
# Signals
The following signals have special meaning in foot:
- SIGUSR1: switch to the dark color theme (*[colors-dark]*).
- SIGUSR2: switch to the light color theme (*[colors-light]*).
Note: you can send SIGUSR1/SIGUSR2 to a *foot --server* process too,
in which case all client instances will switch theme. Furthermore, all
future client instances will also use the selected theme.
You can also send SIGUSR1/SIGUSR2 to a footclient instance, see
*footclient*(1) for details.
# BUGS
Please report bugs to https://codeberg.org/dnkl/foot/issues
@ -511,7 +720,7 @@ the same issue.
The report should contain the following:
- Foot version (*foot --version*).
- Log output from foot (start foot from another terminal).
- Log output from foot (run *foot -d info* from another terminal).
- Which Wayland compositor (and version) you are running.
- If reporting a crash, please try to provide a *bt full* backtrace
with symbols.

File diff suppressed because it is too large Load diff

View file

@ -31,7 +31,12 @@ terminal has terminated.
*-a*,*--app-id*=_ID_
Value to set the *app-id* property on the Wayland window
to. Default: _foot_.
to. Default: _foot_ (normal mode), or _footclient_ (server mode).
*toplevel-tag*=_TAG_
Value to set the *toplevel-tag* property on the Wayland window
to. The compositor can use this value for session management,
window rules etc. Default: _not set_
*-w*,*--window-size-pixels*=_WIDTHxHEIGHT_
Set initial window width and height, in pixels. Default: _700x500_.
@ -73,9 +78,14 @@ terminal has terminated.
The child process in the new terminal instance will use
footclient's environment, instead of the server's.
Environment variables listed in the *Variables set in the child
process* section will be overwritten by the foot server. For
example, the new terminal will use *TERM* from the configuration,
not footclient's environment.
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
Log level, used both for log output on stderr as well as
syslog. Default: _info_.
syslog. Default: _warning_.
*-l*,*--log-colorize*=[{*never*,*always*,*auto*}]
Enables or disables colorization of log output on stderr.
@ -89,7 +99,7 @@ terminal has terminated.
# EXIT STATUS
Footlient will exit with code 220 if there is a failure in footclient
Footclient will exit with code 220 if there is a failure in footclient
itself (for example, the server socket does not exist).
If *-N*,*--no-wait* is used, footclient exits with code 0 as soon as
@ -136,7 +146,7 @@ terminfo entries manually, by copying *foot* and *foot-direct* to
# ENVIRONMENT
The following environment variables are used by footclient:
## Variables used by footclient
*XDG\_RUNTIME\_DIR*
Used to construct the default _PATH_ for the *--server-socket*
@ -146,7 +156,12 @@ The following environment variables are used by footclient:
Used to construct the default _PATH_ for the *--server-socket*
option, when no explicit argument is given (see above).
The following environment variables are set in the child process:
If the socket at default _PATH_ does not exist, *footclient* will
fallback to the less specific path, with the following priority:
*$XDG\_RUNTIME\_DIR/foot-$WAYLAND\_DISPLAY.sock*,
*$XDG\_RUNTIME\_DIR/foot.sock*, */tmp/foot.sock*.
## Variables set in the child process
*TERM*
terminfo/termcap identifier. This is used by client applications
@ -158,6 +173,42 @@ The following environment variables are set in the child process:
This variable is set to *truecolor*, to indicate to client
applications that 24-bit RGB colors are supported.
*PWD*
Current working directory (at the time of launching foot)
*SHELL*
Set to the launched shell, if the shell is valid (it is listed in
*/etc/shells*).
In addition to the variables listed above, custom environment
variables may be defined in *foot.ini*(5).
## Variables *unset* in the child process
*TERM_PROGRAM*
*TERM_PROGRAM_VERSION*
These environment variables are set by certain other terminal
emulators. We unset them, to prevent applications from
misdetecting foot.
In addition to the variables listed above, custom environment
variables to unset may be defined in *foot.ini*(5).
# Signals
The following signals have special meaning in footclient:
- SIGUSR1: switch to the dark color theme (*[colors-dark]*).
- SIGUSR2: switch to the light color theme (*[colors-light]*).
When sending SIGUSR1/SIGUSR2 to a footclient instance, the theme is
changed in that instance only. This is different from when you send
SIGUSR1/SIGUSR2 to the server process, where all instances change the
theme.
Note: for obvious reasons, this is not supported when footclient is
started with *--no-wait*.
# SEE ALSO
*foot*(1)

View file

@ -1,10 +1,25 @@
sh = find_program('sh', native: true)
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
if utmp_backend != 'none'
utmp_add_args = '@0@ $WAYLAND_DISPLAY'.format(utmp_add)
utmp_del_args = (utmp_del_have_argument
? '@0@ $WAYLAND_DISPLAY'.format(utmp_del)
: '@0@'.format(utmp_del))
utmp_path = utmp_default_helper_path
else
utmp_add_args = '<no utmp support in foot>'
utmp_del_args = '<no utmp support in foot>'
utmp_path = 'none'
endif
conf_data = configuration_data(
{
'default_terminfo': get_option('default-terminfo'),
'utmp_backend': utmp_backend,
'utmp_add_args': utmp_add_args,
'utmp_del_args': utmp_del_args,
'utmp_helper_path': utmp_path,
}
)
@ -26,8 +41,9 @@ foreach man_src : [{'name': 'foot', 'section' : 1},
out,
output: out,
input: preprocessed,
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())],
command: scdoc_prog.full_path(),
capture: true,
feed: true,
install: true,
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section)))
endforeach

BIN
doc/sixel-tux-foot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

BIN
doc/tux-foot-ok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

View file

@ -2,7 +2,7 @@
#include <string.h>
#define LOG_MODULE "extract"
#define LOG_ENABLE_DBG 1
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "char32.h"
@ -256,8 +256,8 @@ extract_one(const struct terminal *term, const struct row *row,
}
}
xassert(next_tab_stop >= col);
ctx->tab_spaces_left = next_tab_stop - col;
if (next_tab_stop > col)
ctx->tab_spaces_left = next_tab_stop - col - 1;
}
}

30
fdm.c
View file

@ -18,6 +18,18 @@
#include "debug.h"
#include "xmalloc.h"
#if !defined(SIGABBREV_NP)
#include <stdio.h>
static const char *
sigabbrev_np(int sig)
{
static char buf[16];
snprintf(buf, sizeof(buf), "<%d>", sig);
return buf;
}
#endif
struct fd_handler {
int fd;
int events;
@ -113,7 +125,8 @@ fdm_destroy(struct fdm *fdm)
for (int i = 0; i < SIGRTMAX; i++) {
if (fdm->signal_handlers[i].callback != NULL)
LOG_WARN("handler for signal %d not removed", i);
LOG_WARN("handler for signal %d (SIG%s) not removed",
i, sigabbrev_np(i));
}
if (tll_length(fdm->hooks_low) > 0 ||
@ -338,7 +351,8 @@ bool
fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data)
{
if (fdm->signal_handlers[signo].callback != NULL) {
LOG_ERR("signal %d already has a handler", signo);
LOG_ERR("signal %d (SIG%s) already has a handler",
signo, sigabbrev_np(signo));
return false;
}
@ -347,14 +361,16 @@ fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *d
sigaddset(&mask, signo);
if (sigprocmask(SIG_BLOCK, &mask, &original) < 0) {
LOG_ERRNO("failed to block signal %d", signo);
LOG_ERRNO("failed to block signal %d (SIG%s)",
signo, sigabbrev_np(signo));
return false;
}
struct sigaction action = {.sa_handler = &signal_handler};
sigemptyset(&action.sa_mask);
if (sigaction(signo, &action, NULL) < 0) {
LOG_ERRNO("failed to set signal handler for signal %d", signo);
LOG_ERRNO("failed to set signal handler for signal %d (SIG%s)",
signo, sigabbrev_np(signo));
sigprocmask(SIG_SETMASK, &original, NULL);
return false;
}
@ -374,7 +390,8 @@ fdm_signal_del(struct fdm *fdm, int signo)
struct sigaction action = {.sa_handler = SIG_DFL};
sigemptyset(&action.sa_mask);
if (sigaction(signo, &action, NULL) < 0) {
LOG_ERRNO("failed to restore signal handler for signal %d", signo);
LOG_ERRNO("failed to restore signal handler for signal %d (SIG%s)",
signo, sigabbrev_np(signo));
return false;
}
@ -386,7 +403,8 @@ fdm_signal_del(struct fdm *fdm, int signo)
sigemptyset(&mask);
sigaddset(&mask, signo);
if (sigprocmask(SIG_UNBLOCK, &mask, NULL) < 0) {
LOG_ERRNO("failed to unblock signal %d", signo);
LOG_ERRNO("failed to unblock signal %d (SIG%s)",
signo, sigabbrev_np(signo));
return false;
}

42
foot-features.c Normal file
View file

@ -0,0 +1,42 @@
#include "foot-features.h"
#include "version.h"
const char version_and_features[] =
"version: " FOOT_VERSION
#if defined(FOOT_PGO_ENABLED) && FOOT_PGO_ENABLED
" +pgo"
#else
" -pgo"
#endif
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
" +ime"
#else
" -ime"
#endif
#if defined(FOOT_GRAPHEME_CLUSTERING) && FOOT_GRAPHEME_CLUSTERING
" +graphemes"
#else
" -graphemes"
#endif
#if defined(HAVE_XDG_TOPLEVEL_TAG)
" +toplevel-tag"
#else
" -toplevel-tag"
#endif
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
" +blur"
#else
" -blur"
#endif
#if !defined(NDEBUG)
" +assertions"
#else
" -assertions"
#endif
;

View file

@ -1,39 +1,13 @@
#pragma once
#include <stdbool.h>
#include <stdio.h>
static inline bool feature_assertions(void)
{
#if defined(NDEBUG)
return false;
#else
return true;
#endif
}
extern const char version_and_features[];
static inline bool feature_ime(void)
static inline void
print_version_and_features(const char *prefix)
{
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
return true;
#else
return false;
#endif
}
static inline bool feature_pgo(void)
{
#if defined(FOOT_PGO_ENABLED) && FOOT_PGO_ENABLED
return true;
#else
return false;
#endif
}
static inline bool feature_graphemes(void)
{
#if defined(FOOT_GRAPHEME_CLUSTERING) && FOOT_GRAPHEME_CLUSTERING
return true;
#else
return false;
#endif
fputs(prefix, stdout);
fputs(version_and_features, stdout);
fputc('\n', stdout);
}

15
foot-server.service.in Normal file
View file

@ -0,0 +1,15 @@
[Service]
ExecStart=@bindir@/foot --server=3
UnsetEnvironment=LISTEN_PID LISTEN_FDS LISTEN_FDNAMES
NonBlocking=true
[Unit]
Requires=%N.socket
Description=Foot terminal server mode
Documentation=man:foot(1)
PartOf=graphical-session.target
After=graphical-session.target
ConditionEnvironment=WAYLAND_DISPLAY
[Install]
WantedBy=graphical-session.target

10
foot-server.socket Normal file
View file

@ -0,0 +1,10 @@
[Socket]
ListenStream=%t/foot.sock
[Unit]
PartOf=graphical-session.target
After=graphical-session.target
ConditionEnvironment=WAYLAND_DISPLAY
[Install]
WantedBy=graphical-session.target

View file

@ -1,13 +0,0 @@
[Service]
ExecStart=@bindir@/foot --server=0
Environment=WAYLAND_DISPLAY=%i
NonBlocking=true
StandardInput=socket
[Unit]
Requires=%N.socket
Description=Foot terminal server mode for WAYLAND_DISPLAY=%i
Documentation=man:foot(1)
[Install]
WantedBy=wayland-instance@.target

View file

@ -1,5 +0,0 @@
[Socket]
ListenStream=%t/foot-%i.sock
[Install]
WantedBy=wayland-instance@.target

View file

@ -12,6 +12,11 @@
setaf=\E[%?%p1%{8}%<%t3%p1%d%e38\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m,
@default_terminfo@+base|foot base fragment,
AX,
Su,
Tc,
XF,
XT,
am,
bce,
bw,
@ -21,21 +26,28 @@
msgr,
npc,
xenl,
AX,
XT,
Tc,
cols#80,
it#8,
lines#24,
pairs#0x10000,
BD=\E[?2004l,
BE=\E[?2004h,
Cr=\E]112\E\\,
Cs=\E]12;%p1%s\E\\,
E3=\E[3J,
Ms=\E]52;%p1%s;%p2%s\E\\,
PE=\E[201~,
PS=\E[200~,
RV=\E[>c,
Rect=\E[%p1%d;%p2%d;%p3%d;%p4%d;%p5%d$x,
Se=\E[ q,
Setulc=\E[58\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m,
Smulx=\E[4:%p1%dm,
Ss=\E[%p1%d q,
Sync=\E[?2026%?%p1%{1}%-%tl%eh,
Sync=\E[?2026%?%p1%{1}%-%tl%eh%;,
TS=\E]2;,
XM=\E[?1006;1000%?%p1%{1}%=%th%el%;,
XR=\E[>0q,
acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
bel=^G,
blink=\E[5m,
@ -66,6 +78,8 @@
ed=\E[J,
el1=\E[1K,
el=\E[K,
fd=\E[?1004l,
fe=\E[?1004h,
flash=\E]555\E\\,
fsl=\E\\,
home=\E[H,
@ -79,7 +93,7 @@
indn=\E[%p1%dS,
initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\,
invis=\E[8m,
is2=\E[!p\E[?3;4l\E[4l\E>,
is2=\E[!p\E[4l\E>,
kDC3=\E[3;3~,
kDC4=\E[3;4~,
kDC5=\E[3;5~,
@ -218,6 +232,9 @@
knp=\E[6~,
kpp=\E[5~,
kri=\E[1;2A,
kxIN=\E[I,
kxOUT=\E[O,
nel=\EE,
oc=\E]104\E\\,
op=\E[39;49m,
rc=\E8,
@ -231,12 +248,15 @@
rmcup=\E[?1049l\E[23;0;0t,
rmir=\E[4l,
rmkx=\E[?1l\E>,
rmm=\E[?1036h\E[?1034l,
rmso=\E[27m,
rmul=\E[24m,
rmxx=\E[29m,
rs1=\Ec,
rs2=\E[!p\E[?3;4l\E[4l\E>,
rs2=\E[!p\E[4l\E>,
rv=\E\\[>1;[0-9][0-9][0-9][0-9][0-9][0-9];0c,
sc=\E7,
setal=\E[58\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m,
setrgbb=\E[48\:2\:\:%p1%d\:%p2%d\:%p3%dm,
setrgbf=\E[38\:2\:\:%p1%d\:%p2%d\:%p3%dm,
sgr0=\E(B\E[m,
@ -247,6 +267,7 @@
smcup=\E[?1049h\E[22;0;0t,
smir=\E[4h,
smkx=\E[?1h\E=,
smm=\E[?1036l\E[?1034h,
smso=\E[7m,
smul=\E[4m,
smxx=\E[9m,
@ -258,6 +279,7 @@
u9=\E[c,
vpa=\E[%i%p1%dd,
xm=\E[<%i%p3%d;%p1%d;%p2%d;%?%p4%tM%em%;,
xr=\EP>\\|foot\\([0-9]+\\.[0-9]+\\.[0-9]+(-[0-9]+-g[a-f[0-9]+)?\\)?\E\\\\,
# XT,
# AX,

176
foot.ini
View file

@ -4,7 +4,7 @@
# term=foot (or xterm-256color if built with -Dterminfo=disabled)
# login-shell=no
# app-id=foot
# app-id=foot # globally set wayland app-id. Default values are "foot" and "footclient" for desktop and server mode
# title=foot
# locked-title=no
@ -12,50 +12,85 @@
# font-bold=<bold variant of regular font>
# font-italic=<italic variant of regular font>
# font-bold-italic=<bold+italic variant of regular font>
# font-size-adjustment=0.5
# line-height=<font metrics>
# letter-spacing=0
# horizontal-letter-offset=0
# vertical-letter-offset=0
# underline-offset=<font metrics>
# underline-thickness=<font underline thickness>
# strikeout-thickness=<font strikeout thickness>
# box-drawings-uses-font-glyphs=no
# dpi-aware=auto
# dpi-aware=no
# gamma-correct-blending=no
# initial-color-theme=dark
# initial-window-size-pixels=700x500 # Or,
# initial-window-size-chars=<COLSxROWS>
# initial-window-mode=windowed
# pad=2x2 # optionally append 'center'
# pad=0x0 center-when-maximized-and-fullscreen
# resize-by-cells=yes
# resize-keep-grid=yes
# resize-delay-ms=100
# notify=notify-send -a ${app-id} -i ${app-id} ${title} ${body}
# bold-text-in-bright=no
# word-delimiters=,│`|:"'()[]{}<>
# selection-target=primary
# workers=<number of logical CPUs>
# utmp-helper=/usr/lib/utempter/utempter # When utmp backend is libutempter (Linux)
# utmp-helper=/usr/libexec/ulog-helper # When utmp backend is ulog (FreeBSD)
# uppercase-regex-insert=yes
[environment]
# name=value
[security]
# osc52=enabled # disabled|copy-enabled|paste-enabled|enabled
[bell]
# system=yes
# urgent=no
# notify=no
# visual=no
# command=
# command-focused=no
[desktop-notifications]
# command=notify-send --wait --app-name ${app-id} --icon ${app-id} --category ${category} --urgency ${urgency} --expire-time ${expire-time} --hint STRING:image-path:${icon} --hint BOOLEAN:suppress-sound:${muted} --hint STRING:sound-name:${sound-name} --replace-id ${replace-id} ${action-argument} --print-id -- ${title} ${body}
# command-action-argument=--action ${action-name}=${action-label}
# close=""
# inhibit-when-focused=yes
[scrollback]
# lines=1000
# multiplier=3.0
# indicator-position=relative
# indicator-format=
# indicator-format=""
[url]
# launch=xdg-open ${url}
# label-letters=sadfjklewcmpgh
# osc8-underline=url-mode
# protocols=http, https, ftp, ftps, file, gemini, gopher
# uri-characters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.,~:;/?#@!$&%*+="'()[]
# regex=(((https?://|mailto:|ftp://|file:|ssh:|ssh://|git://|tel:|magnet:|ipfs://|ipns://|gemini://|gopher://|news:)|www\.)([0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]+|\([]\["0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]*')+([0-9a-zA-Z/#@$&*+=~_%^\-]|\([]\["0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]*'))
# You can define your own regex's, by adding a section called
# 'regex:<ID>' with a 'regex' and 'launch' key. These can then be tied
# to a key-binding. See foot.ini(5) for details
# [regex:your-fancy-name]
# regex=<a POSIX-Extended Regular Expression>
# launch=<path to script or application> ${match}
#
# [key-bindings]
# regex-launch=[your-fancy-name] Control+Shift+q
# regex-copy=[your-fancy-name] Control+Alt+Shift+q
[cursor]
# style=block
# color=<inverse foreground/background>
# blink=no
# blink-rate=500
# beam-thickness=1.5
# underline-thickness=<font underline thickness>
@ -63,32 +98,41 @@
# hide-when-typing=no
# alternate-scroll-mode=yes
[colors]
[touch]
# long-press-delay=400
[colors-dark]
# alpha=1.0
# foreground=dcdccc
# background=111111
# alpha-mode=default # Can be `default`, `matching` or `all`
# background=242424
# foreground=ffffff
# flash=7f7f00
# flash-alpha=0.5
# cursor=<inverse foreground/background>
## 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=242424 # black
# regular1=f62b5a # red
# regular2=47b413 # green
# regular3=e3c401 # yellow
# regular4=24acd4 # blue
# regular5=f2affd # magenta
# regular6=13c299 # cyan
# regular7=e6e6e6 # white
## Bright colors (color palette 8-15)
# bright0=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=616161 # bright black
# bright1=ff4d51 # bright red
# bright2=35d450 # bright green
# bright3=e9e836 # bright yellow
# bright4=5dc5f8 # bright blue
# bright5=feabf2 # bright magenta
# bright6=24dfc4 # bright cyan
# bright7=ffffff # bright white
## dimmed colors (see foot.ini(5) man page)
# dim-blend-towards=black
# dim0=<not set>
# ...
# dim7=<not-set>
@ -98,19 +142,45 @@
# ...
# 255 = <256-color palette #255>
## Sixel colors
# sixel0 = 000000
# sixel1 = 3333cc
# sixel2 = cc2121
# sixel3 = 33cc33
# sixel4 = cc33cc
# sixel5 = 33cccc
# sixel6 = cccc33
# sixel7 = 878787
# sixel8 = 424242
# sixel9 = 545499
# sixel10 = 994242
# sixel11 = 549954
# sixel12 = 995499
# sixel13 = 549999
# sixel14 = 999954
# sixel15 = cccccc
## 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>
[colors-light]
# Alternative color theme, see man page foot.ini(5)
# Same builtin defaults as [color], except for:
# dim-blend-towards=white
[csd]
# preferred=server
# size=26
# font=<primary font>
# color=<foreground color>
# hide-when-typing=no
# hide-when-maximized=no
# double-click-to-maximize=yes
# border-width=0
# border-color=<csd.color>
# button-width=26
@ -120,12 +190,14 @@
# button-close-color=<regular1>
[key-bindings]
# scrollback-up-page=Shift+Page_Up
# scrollback-up-page=Shift+Page_Up Shift+KP_Page_Up
# scrollback-up-half-page=none
# scrollback-up-line=none
# scrollback-down-page=Shift+Page_Down
# scrollback-down-page=Shift+Page_Down Shift+KP_Page_Down
# scrollback-down-half-page=none
# scrollback-down-line=none
# scrollback-home=none
# scrollback-end=none
# clipboard-copy=Control+Shift+c XF86Copy
# clipboard-paste=Control+Shift+v XF86Paste
# primary-paste=Shift+Insert
@ -140,14 +212,22 @@
# pipe-visible=[sh -c "xurls | fuzzel | xargs -r firefox"] none
# pipe-scrollback=[sh -c "xurls | fuzzel | xargs -r firefox"] none
# pipe-selected=[xargs -r firefox] none
# show-urls-launch=Control+Shift+u
# pipe-command-output=[wl-copy] none # Copy last command's output to the clipboard
# show-urls-launch=Control+Shift+o
# show-urls-copy=none
# show-urls-persistent=none
# prompt-prev=Control+Shift+z
# prompt-next=Control+Shift+x
# unicode-input=Control+Shift+u
# color-theme-switch-1=none
# color-theme-switch-2=none
# color-theme-toggle=none
# noop=none
# quit=none
[search-bindings]
# cancel=Control+g Control+c Escape
# commit=Return
# commit=Return KP_Enter
# find-prev=Control+r
# find-next=Control+s
# cursor-left=Left Control+b
@ -160,10 +240,27 @@
# delete-prev-word=Mod1+BackSpace Control+BackSpace
# delete-next=Delete
# delete-next-word=Mod1+d Control+Delete
# extend-to-word-boundary=Control+w
# delete-to-start=Control+u
# delete-to-end=Control+k
# extend-char=Shift+Right
# extend-to-word-boundary=Control+w Control+Shift+Right
# extend-to-next-whitespace=Control+Shift+w
# clipboard-paste=Control+v Control+y
# extend-line-down=Shift+Down
# extend-backward-char=Shift+Left
# extend-backward-to-word-boundary=Control+Shift+Left
# extend-backward-to-next-whitespace=none
# extend-line-up=Shift+Up
# clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste
# primary-paste=Shift+Insert
# unicode-input=none
# scrollback-up-page=Shift+Page_Up Shift+KP_Page_Up
# scrollback-up-half-page=none
# scrollback-up-line=none
# scrollback-down-page=Shift+Page_Down Shift+KP_Page_Down
# scrollback-down-half-page=none
# scrollback-down-line=none
# scrollback-home=none
# scrollback-end=none
[url-bindings]
# cancel=Control+g Control+c Control+d Escape
@ -173,6 +270,10 @@
# \x03=Mod4+c # Map Super+c -> Ctrl+c
[mouse-bindings]
# scrollback-up-mouse=BTN_WHEEL_BACK
# scrollback-down-mouse=BTN_WHEEL_FORWARD
# font-increase=Control+BTN_WHEEL_BACK
# font-decrease=Control+BTN_WHEEL_FORWARD
# selection-override-modifiers=Shift
# primary-paste=BTN_MIDDLE
# select-begin=BTN_LEFT
@ -181,6 +282,7 @@
# select-extend-character-wise=Control+BTN_RIGHT
# select-word=BTN_LEFT-2
# select-word-whitespace=Control+BTN_LEFT-2
# select-row=BTN_LEFT-3
# select-quote = BTN_LEFT-3
# select-row=BTN_LEFT-4
# vim: ft=dosini

1098
grid.c

File diff suppressed because it is too large Load diff

48
grid.h
View file

@ -16,11 +16,21 @@ void grid_resize_without_reflow(
int old_screen_rows, int new_screen_rows);
void grid_resize_and_reflow(
struct grid *grid, int new_rows, int new_cols,
struct grid *grid, const struct terminal *term, int new_rows, int new_cols,
int old_screen_rows, int new_screen_rows,
size_t tracking_points_count,
struct coord *const _tracking_points[static tracking_points_count]);
/* Convert row numbers between scrollback-relative and absolute coordinates */
int grid_row_abs_to_sb(const struct grid *grid, int screen_rows, int abs_row);
int grid_row_sb_to_abs(const struct grid *grid, int screen_rows, int sb_rel_row);
int grid_sb_start_ignore_uninitialized(const struct grid *grid, int screen_rows);
int grid_row_abs_to_sb_precalc_sb_start(
const struct grid *grid, int sb_start, int abs_row);
int grid_row_sb_to_abs_precalc_sb_start(
const struct grid *grid, int sb_start, int sb_rel_row);
static inline int
grid_row_absolute(const struct grid *grid, int row_no)
{
@ -76,13 +86,38 @@ grid_row_in_view(struct grid *grid, int row_no)
void grid_row_uri_range_put(
struct row *row, int col, const char *uri, uint64_t id);
void grid_row_uri_range_add(struct row *row, struct row_uri_range range);
void grid_row_uri_range_erase(struct row *row, int start, int end);
void grid_row_underline_range_put(
struct row *row, int col, struct underline_range_data data);
void grid_row_underline_range_erase(struct row *row, int start, int end);
static inline void
grid_row_uri_range_destroy(struct row_uri_range *range)
grid_row_uri_range_destroy(struct row_range *range)
{
free(range->uri);
free(range->uri.uri);
}
static inline void
grid_row_underline_range_destroy(struct row_range *range)
{
}
static inline void
grid_row_range_destroy(struct row_range *range, enum row_range_type type)
{
switch (type) {
case ROW_RANGE_URI: grid_row_uri_range_destroy(range); break;
case ROW_RANGE_UNDERLINE: grid_row_underline_range_destroy(range); break;
}
}
static inline void
grid_row_ranges_destroy(struct row_ranges *ranges, enum row_range_type type)
{
for (int i = 0; i < ranges->count; i++) {
grid_row_range_destroy(&ranges->v[i], type);
}
}
static inline void
@ -93,9 +128,10 @@ grid_row_reset_extra(struct row *row)
if (likely(extra == NULL))
return;
for (size_t i = 0; i < extra->uri_ranges.count; i++)
grid_row_uri_range_destroy(&extra->uri_ranges.v[i]);
grid_row_ranges_destroy(&extra->uri_ranges, ROW_RANGE_URI);
grid_row_ranges_destroy(&extra->underline_ranges, ROW_RANGE_UNDERLINE);
free(extra->uri_ranges.v);
free(extra->underline_ranges.v);
free(extra);
row->extra = NULL;

41
hsl.c
View file

@ -2,41 +2,6 @@
#include <math.h>
#include "util.h"
void
rgb_to_hsl(uint32_t rgb, int *hue, int *sat, int *lum)
{
double r = (double)((rgb >> 16) & 0xff) / 255.;
double g = (double)((rgb >> 8) & 0xff) / 255.;
double b = (double)((rgb >> 0) & 0xff) / 255.;
double x_max = max(max(r, g), b);
double x_min = min(min(r, g), b);
double V = x_max;
double C = x_max - x_min;
double L = (x_max + x_min) / 2.;
*lum = 100 * L;
if (C == 0.0)
*hue = 0;
else if (V == r)
*hue = 60. * (0. + (g - b) / C);
else if (V == g)
*hue = 60. * (2. + (b - r) / C);
else if (V == b)
*hue = 60. * (4. + (r - g) / C);
if (*hue < 0)
*hue += 360;
double S = C == 0.0
? 0
: C / (1. - fabs(2. * L - 1.));
*sat = 100 * S;
}
uint32_t
hsl_to_rgb(int hue, int sat, int lum)
{
@ -83,7 +48,7 @@ hsl_to_rgb(int hue, int sat, int lum)
b += m;
return (
(int)round(r * 255.) << 16 |
(int)round(g * 255.) << 8 |
(int)round(b * 255.) << 0);
(uint8_t)round(r * 255.) << 16 |
(uint8_t)round(g * 255.) << 8 |
(uint8_t)round(b * 255.) << 0);
}

1
hsl.h
View file

@ -2,5 +2,4 @@
#include <stdint.h>
void rgb_to_hsl(uint32_t rgb, int *hue, int *sat, int *lum);
uint32_t hsl_to_rgb(int hue, int sat, int lum);

108
ime.c
View file

@ -17,17 +17,67 @@
#include "wayland.h"
#include "xmalloc.h"
static void
ime_reset_pending_preedit(struct seat *seat)
{
free(seat->ime.preedit.pending.text);
seat->ime.preedit.pending.text = NULL;
}
static void
ime_reset_pending_commit(struct seat *seat)
{
free(seat->ime.commit.pending.text);
seat->ime.commit.pending.text = NULL;
}
void
ime_reset_pending(struct seat *seat)
{
ime_reset_pending_preedit(seat);
ime_reset_pending_commit(seat);
}
void
ime_reset_preedit(struct seat *seat)
{
if (seat->ime.preedit.cells == NULL)
return;
free(seat->ime.preedit.text);
free(seat->ime.preedit.cells);
seat->ime.preedit.text = NULL;
seat->ime.preedit.cells = NULL;
seat->ime.preedit.count = 0;
}
static void
enter(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
struct wl_surface *surface)
{
struct seat *seat = data;
struct wl_window *win = wl_surface_get_user_data(surface);
struct terminal *term = win->term;
LOG_DBG("enter: seat=%s", seat->name);
LOG_DBG("enter: seat=%s, term=%p", seat->name, (const void *)term);
if (seat->kbd_focus != term) {
LOG_WARN("compositor sent ime::enter() event before the "
"corresponding keyboard_enter() event");
}
/* The main grid is the *only* input-receiving surface we have */
xassert(seat->kbd_focus != NULL);
seat->ime.focused = true;
seat->ime_focus = term;
const struct coord *cursor = &term->grid->cursor.point;
term_ime_set_cursor_rect(
term,
term->margins.left + cursor->col * term->cell_width,
term->margins.top + cursor->row * term->cell_height,
term->cell_width,
term->cell_height);
ime_enable(seat);
}
@ -39,7 +89,7 @@ leave(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
LOG_DBG("leave: seat=%s", seat->name);
ime_disable(seat);
seat->ime.focused = false;
seat->ime_focus = NULL;
}
static void
@ -104,7 +154,7 @@ done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
LOG_DBG("done: serial=%u", serial);
struct seat *seat = data;
struct terminal *term = seat->kbd_focus;
struct terminal *term = seat->ime_focus;
if (seat->ime.serial != serial) {
LOG_DBG("IME serial mismatch: expected=0x%08x, got 0x%08x",
@ -135,7 +185,7 @@ done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
}
/*
* 2. Delete requested surroundin text
* 2. Delete requested surrounding text
*
* We don't support deleting surrounding text. But, we also never
* call set_surrounding_text() so hopefully we should never
@ -324,50 +374,16 @@ done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
}
}
void
ime_reset_pending_preedit(struct seat *seat)
{
free(seat->ime.preedit.pending.text);
seat->ime.preedit.pending.text = NULL;
}
void
ime_reset_pending_commit(struct seat *seat)
{
free(seat->ime.commit.pending.text);
seat->ime.commit.pending.text = NULL;
}
void
ime_reset_pending(struct seat *seat)
{
ime_reset_pending_preedit(seat);
ime_reset_pending_commit(seat);
}
void
ime_reset_preedit(struct seat *seat)
{
if (seat->ime.preedit.cells == NULL)
return;
free(seat->ime.preedit.text);
free(seat->ime.preedit.cells);
seat->ime.preedit.text = NULL;
seat->ime.preedit.cells = NULL;
seat->ime.preedit.count = 0;
}
static void
ime_send_cursor_rect(struct seat *seat)
{
if (unlikely(seat->wayl->text_input_manager == NULL))
return;
if (!seat->ime.focused)
if (seat->ime_focus == NULL)
return;
struct terminal *term = seat->kbd_focus;
struct terminal *term = seat->ime_focus;
if (!term->ime_enabled)
return;
@ -399,10 +415,10 @@ ime_enable(struct seat *seat)
if (unlikely(seat->wayl->text_input_manager == NULL))
return;
if (!seat->ime.focused)
if (seat->ime_focus == NULL)
return;
struct terminal *term = seat->kbd_focus;
struct terminal *term = seat->ime_focus;
if (term == NULL)
return;
@ -437,7 +453,7 @@ ime_disable(struct seat *seat)
if (unlikely(seat->wayl->text_input_manager == NULL))
return;
if (!seat->ime.focused)
if (seat->ime_focus == NULL)
return;
ime_reset_pending(seat);
@ -451,7 +467,7 @@ ime_disable(struct seat *seat)
void
ime_update_cursor_rect(struct seat *seat)
{
struct terminal *term = seat->kbd_focus;
struct terminal *term = seat->ime_focus;
/* Set in render_ime_preedit() */
if (seat->ime.preedit.cells != NULL)

2
ime.h
View file

@ -15,7 +15,5 @@ void ime_enable(struct seat *seat);
void ime_disable(struct seat *seat);
void ime_update_cursor_rect(struct seat *seat);
void ime_reset_pending_preedit(struct seat *seat);
void ime_reset_pending_commit(struct seat *seat);
void ime_reset_pending(struct seat *seat);
void ime_reset_preedit(struct seat *seat);

2079
input.c

File diff suppressed because it is too large Load diff

14
input.h
View file

@ -3,34 +3,38 @@
#include <stdint.h>
#include <wayland-client.h>
#include "wayland.h"
#include "cursor-shape.h"
#include "misc.h"
#include "wayland.h"
/*
* Custom defines for mouse wheel left/right buttons.
*
* Libinput does not define these. On Wayland, all scroll events (both
* vertical and horizontal) are reported not as buttons, as axis
* vertical and horizontal) are reported not as buttons, as 'axis'
* events.
*
* Libinput _does_ define BTN_BACK and BTN_FORWARD, which is
* what we use for vertical scroll events. But for horizontal scroll
* events, there arent any pre-defined mouse buttons.
* events, there aren't any pre-defined mouse buttons.
*
* Mouse buttons are in the range 0x110 - 0x11f, with joystick defines
* starting at 0x120.
*/
#define BTN_WHEEL_BACK 0x11c
#define BTN_WHEEL_FORWARD 0x11d
#define BTN_WHEEL_LEFT 0x11e
#define BTN_WHEEL_RIGHT 0x11f
extern const struct wl_keyboard_listener keyboard_listener;
extern const struct wl_pointer_listener pointer_listener;
extern const struct wl_touch_listener touch_listener;
void input_repeat(struct seat *seat, uint32_t key);
void get_current_modifiers(const struct seat *seat,
xkb_mod_mask_t *effective,
xkb_mod_mask_t *consumed,
uint32_t key);
uint32_t key, bool filter_locked);
const char *xcursor_for_csd_border(struct terminal *term, int x, int y);
enum cursor_shape xcursor_for_csd_border(struct terminal *term, int x, int y);

View file

@ -3,7 +3,7 @@
#include <stdlib.h>
#define LOG_MODULE "key-binding"
#define LOG_ENABLE_DBG 1
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "config.h"
@ -11,14 +11,24 @@
#include "terminal.h"
#include "util.h"
#include "wayland.h"
#include "xkbcommon-vmod.h"
#include "xmalloc.h"
struct vmod_map {
const char *name;
xkb_mod_mask_t virtual_mask;
xkb_mod_mask_t real_mask;
};
struct key_set {
struct key_binding_set public;
const struct config *conf;
const struct seat *seat;
size_t conf_ref_count;
/* Virtual to real modifier mappings */
struct vmod_map vmods[8];
};
typedef tll(struct key_set) bind_set_list_t;
@ -44,6 +54,50 @@ key_binding_manager_destroy(struct key_binding_manager *mgr)
free(mgr);
}
static void
initialize_vmod_mappings(struct key_set *set)
{
if (set->seat == NULL || set->seat->kbd.xkb_keymap == NULL)
return;
set->vmods[0].name = XKB_VMOD_NAME_ALT;
set->vmods[1].name = XKB_VMOD_NAME_HYPER;
set->vmods[2].name = XKB_VMOD_NAME_LEVEL3;
set->vmods[3].name = XKB_VMOD_NAME_LEVEL5;
set->vmods[4].name = XKB_VMOD_NAME_META;
set->vmods[5].name = XKB_VMOD_NAME_NUM;
set->vmods[6].name = XKB_VMOD_NAME_SCROLL;
set->vmods[7].name = XKB_VMOD_NAME_SUPER;
struct xkb_state *scratch_state = xkb_state_new(set->seat->kbd.xkb_keymap);
xassert(scratch_state != NULL);
for (size_t i = 0; i < ALEN(set->vmods); i++) {
xkb_mod_index_t virt_idx = xkb_keymap_mod_get_index(
set->seat->kbd.xkb_keymap, set->vmods[i].name);
if (virt_idx != XKB_MOD_INVALID) {
xkb_mod_mask_t vmask = 1 << virt_idx;
xkb_state_update_mask(scratch_state, vmask, 0, 0, 0, 0, 0);
set->vmods[i].real_mask = xkb_state_serialize_mods(
scratch_state, XKB_STATE_MODS_DEPRESSED) & ~vmask;
set->vmods[i].virtual_mask = vmask;
LOG_DBG("%s: 0x%04x -> 0x%04x",
set->vmods[i].name,
set->vmods[i].virtual_mask,
set->vmods[i].real_mask);
} else {
set->vmods[i].virtual_mask = 0;
set->vmods[i].real_mask = 0;
LOG_DBG("%s: virtual modifier not available", set->vmods[i].name);
}
}
xkb_state_unref(scratch_state);
}
void
key_binding_new_for_seat(struct key_binding_manager *mgr,
const struct seat *seat)
@ -67,6 +121,7 @@ key_binding_new_for_seat(struct key_binding_manager *mgr,
};
tll_push_back(mgr->binding_sets, set);
initialize_vmod_mappings(&tll_back(mgr->binding_sets));
LOG_DBG("new (seat): set=%p, seat=%p, conf=%p, ref-count=1",
(void *)&tll_back(mgr->binding_sets),
@ -80,17 +135,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++;
@ -110,27 +162,26 @@ key_binding_new_for_term(struct key_binding_manager *mgr,
};
tll_push_back(mgr->binding_sets, set);
initialize_vmod_mappings(&tll_back(mgr->binding_sets));
load_keymap(&tll_back(mgr->binding_sets));
/* 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 +243,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;
@ -251,27 +299,27 @@ maybe_repair_key_combo(const struct seat *seat,
* modifier, and replace the shifted symbol with its unshifted
* variant.
*
* For example, the combo is Control+Shift+U. In this case,
* Shift is the modifier used to shift u to U, after which
* Shift will have been consumed. Since we filter out consumed
* For example, the combo is "Control+Shift+U". In this case,
* Shift is the modifier used to "shift" 'u' to 'U', after which
* 'Shift' will have been "consumed". Since we filter out consumed
* modifiers when matching key combos, this key combo will never
* trigger (we will never be able to match the Shift modifier).
* trigger (we will never be able to match the 'Shift' modifier).
*
* There are two correct variants of the above key combo:
* - Control+U (upper case U)
* - Control+Shift+u (lower case u)
* - "Control+U" (upper case 'U')
* - "Control+Shift+u" (lower case 'u')
*
* What we do here is, for each key *code*, check if there are any
* (shifted) levels where it produces sym. If there are, check
* (shifted) levels where it produces 'sym'. If there are, check
* *which* sets of modifiers are needed to produce it, and compare
* with mods.
* with 'mods'.
*
* If there is at least one common modifier, it means sym is a
* shifted symbol, with the corresponding shifting modifier
* If there is at least one common modifier, it means 'sym' is a
* "shifted" symbol, with the corresponding shifting modifier
* explicitly included in the key combo. I.e. the key combo will
* never trigger.
*
* We then proceed and repair the key combo by replacing sym
* We then proceed and "repair" the key combo by replacing 'sym'
* with the corresponding unshifted symbol.
*
* To reduce the noise, we ignore all key codes where the shifted
@ -291,7 +339,7 @@ maybe_repair_key_combo(const struct seat *seat,
seat->kbd.xkb_keymap, code, layout_idx, 0, &base_syms);
if (base_count == 0 || sym == base_syms[0]) {
/* No unshifted symbols, or unshifted symbol is same as sym */
/* No unshifted symbols, or unshifted symbol is same as 'sym' */
continue;
}
@ -321,7 +369,7 @@ maybe_repair_key_combo(const struct seat *seat,
seat->kbd.xkb_keymap, code, layout_idx, level_idx,
mod_masks, ALEN(mod_masks));
/* Check if key combos modifier set intersects */
/* Check if key combo's modifier set intersects */
for (size_t j = 0; j < mod_mask_count; j++) {
if ((mod_masks[j] & mods) != mod_masks[j])
continue;
@ -358,6 +406,95 @@ maybe_repair_key_combo(const struct seat *seat,
return sym;
}
static int
key_cmp(struct key_binding a, struct key_binding b)
{
xassert(a.type == b.type);
/*
* Sort bindings such that bindings with the same symbol are
* sorted with the binding having the most modifiers comes first.
*
* This fixes an issue where the "wrong" key binding are triggered
* when used with "consumed" modifiers.
*
* For example: if Control+BackSpace is bound before
* Control+Shift+BackSpace, then the latter binding is never
* triggered.
*
* Why? Because Shift is a consumed modifier. This means
* Control+BackSpace is "the same" as Control+Shift+BackSpace.
*
* By sorting bindings with more modifiers first, we work around
* the problem. But note that it is *just* a workaround, and I'm
* not confident there aren't cases where it doesn't work.
*
* See https://codeberg.org/dnkl/foot/issues/1280
*/
const int a_mod_count = __builtin_popcount(a.mods);
const int b_mod_count = __builtin_popcount(b.mods);
switch (a.type) {
case KEY_BINDING:
if (a.k.sym != b.k.sym)
return b.k.sym - a.k.sym;
return b_mod_count - a_mod_count;
case MOUSE_BINDING: {
if (a.m.button != b.m.button)
return b.m.button - a.m.button;
if (a_mod_count != b_mod_count)
return b_mod_count - a_mod_count;
return b.m.count - a.m.count;
}
}
BUG("invalid key binding type");
return 0;
}
static void NOINLINE
sort_binding_list(key_binding_list_t *list)
{
tll_sort(*list, key_cmp);
}
static xkb_mod_mask_t
mods_to_mask(const struct seat *seat,
const struct vmod_map *vmods, size_t vmod_count,
const config_modifier_list_t *mods)
{
xkb_mod_mask_t mask = 0;
tll_foreach(*mods, it) {
const xkb_mod_index_t idx = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, it->item);
if (idx == XKB_MOD_INVALID) {
LOG_ERR("%s: invalid modifier name", it->item);
continue;
}
xkb_mod_mask_t mod = 1 << idx;
/* Check if this is a virtual modifier, and if so, use the
real modifier it maps to instead */
for (size_t i = 0; i < vmod_count; i++) {
if (vmods[i].virtual_mask == mod) {
mask |= vmods[i].real_mask;
mod = 0;
LOG_DBG("%s: virtual modifier, mapped to 0x%04x",
it->item, vmods[i].real_mask);
break;
}
}
mask |= mod;
}
return mask;
}
static void NOINLINE
convert_key_binding(struct key_set *set,
const struct config_key_binding *conf_binding,
@ -365,7 +502,8 @@ convert_key_binding(struct key_set *set,
{
const struct seat *seat = set->seat;
xkb_mod_mask_t mods = conf_modifiers_to_mask(seat, &conf_binding->modifiers);
xkb_mod_mask_t mods = mods_to_mask(
seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers);
xkb_keysym_t sym = maybe_repair_key_combo(seat, conf_binding->k.sym, mods);
struct key_binding binding = {
@ -379,6 +517,7 @@ convert_key_binding(struct key_set *set,
},
};
tll_push_back(*bindings, binding);
sort_binding_list(bindings);
}
static void
@ -422,13 +561,14 @@ convert_mouse_binding(struct key_set *set,
.type = MOUSE_BINDING,
.action = conf_binding->action,
.aux = &conf_binding->aux,
.mods = conf_modifiers_to_mask(set->seat, &conf_binding->modifiers),
.mods = mods_to_mask(set->seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers),
.m = {
.button = conf_binding->m.button,
.count = conf_binding->m.count,
},
};
tll_push_back(set->public.mouse, binding);
sort_binding_list(&set->public.mouse);
}
static void
@ -461,8 +601,9 @@ load_keymap(struct key_set *set)
convert_url_bindings(set);
convert_mouse_bindings(set);
set->public.selection_overrides = conf_modifiers_to_mask(
set->seat, &set->conf->mouse.selection_override_modifiers);
set->public.selection_overrides = mods_to_mask(
set->seat, set->vmods, ALEN(set->vmods),
&set->conf->mouse.selection_override_modifiers);
}
void
@ -472,8 +613,10 @@ key_binding_load_keymap(struct key_binding_manager *mgr,
tll_foreach(mgr->binding_sets, it) {
struct key_set *set = &it->item;
if (set->seat == seat)
if (set->seat == seat) {
initialize_vmod_mappings(set);
load_keymap(set);
}
}
}

View file

@ -32,26 +32,49 @@ enum bind_action_normal {
BIND_ACTION_PIPE_SCROLLBACK,
BIND_ACTION_PIPE_VIEW,
BIND_ACTION_PIPE_SELECTED,
BIND_ACTION_PIPE_COMMAND_OUTPUT,
BIND_ACTION_SHOW_URLS_COPY,
BIND_ACTION_SHOW_URLS_LAUNCH,
BIND_ACTION_SHOW_URLS_PERSISTENT,
BIND_ACTION_TEXT_BINDING,
BIND_ACTION_PROMPT_PREV,
BIND_ACTION_PROMPT_NEXT,
BIND_ACTION_UNICODE_INPUT,
BIND_ACTION_QUIT,
BIND_ACTION_REGEX_LAUNCH,
BIND_ACTION_REGEX_COPY,
BIND_ACTION_THEME_SWITCH_1,
BIND_ACTION_THEME_SWITCH_2,
BIND_ACTION_THEME_SWITCH_DARK,
BIND_ACTION_THEME_SWITCH_LIGHT,
BIND_ACTION_THEME_TOGGLE,
/* Mouse specific actions - i.e. they require a mouse coordinate */
BIND_ACTION_SCROLLBACK_UP_MOUSE,
BIND_ACTION_SCROLLBACK_DOWN_MOUSE,
BIND_ACTION_SELECT_BEGIN,
BIND_ACTION_SELECT_BEGIN_BLOCK,
BIND_ACTION_SELECT_EXTEND,
BIND_ACTION_SELECT_EXTEND_CHAR_WISE,
BIND_ACTION_SELECT_WORD,
BIND_ACTION_SELECT_WORD_WS,
BIND_ACTION_SELECT_QUOTE,
BIND_ACTION_SELECT_ROW,
BIND_ACTION_KEY_COUNT = BIND_ACTION_TEXT_BINDING + 1,
BIND_ACTION_KEY_COUNT = BIND_ACTION_THEME_TOGGLE + 1,
BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1,
};
enum bind_action_search {
BIND_ACTION_SEARCH_NONE,
BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE,
BIND_ACTION_SEARCH_SCROLLBACK_UP_HALF_PAGE,
BIND_ACTION_SEARCH_SCROLLBACK_UP_LINE,
BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE,
BIND_ACTION_SEARCH_SCROLLBACK_DOWN_HALF_PAGE,
BIND_ACTION_SEARCH_SCROLLBACK_DOWN_LINE,
BIND_ACTION_SEARCH_SCROLLBACK_HOME,
BIND_ACTION_SEARCH_SCROLLBACK_END,
BIND_ACTION_SEARCH_CANCEL,
BIND_ACTION_SEARCH_COMMIT,
BIND_ACTION_SEARCH_FIND_PREV,
@ -66,10 +89,19 @@ enum bind_action_search {
BIND_ACTION_SEARCH_DELETE_PREV_WORD,
BIND_ACTION_SEARCH_DELETE_NEXT,
BIND_ACTION_SEARCH_DELETE_NEXT_WORD,
BIND_ACTION_SEARCH_DELETE_TO_START,
BIND_ACTION_SEARCH_DELETE_TO_END,
BIND_ACTION_SEARCH_EXTEND_CHAR,
BIND_ACTION_SEARCH_EXTEND_WORD,
BIND_ACTION_SEARCH_EXTEND_WORD_WS,
BIND_ACTION_SEARCH_EXTEND_LINE_DOWN,
BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR,
BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD,
BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS,
BIND_ACTION_SEARCH_EXTEND_LINE_UP,
BIND_ACTION_SEARCH_CLIPBOARD_PASTE,
BIND_ACTION_SEARCH_PRIMARY_PASTE,
BIND_ACTION_SEARCH_UNICODE_INPUT,
BIND_ACTION_SEARCH_COUNT,
};
@ -106,6 +138,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;
@ -123,20 +156,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);

View file

@ -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~"},

View file

@ -13,7 +13,7 @@ struct kitty_key_data {
_Static_assert(sizeof(struct kitty_key_data) == 7, "bad size");
/* Note! *Must* Be kept sorted (on sym) */
/* Note! *Must* Be kept sorted (on 'sym') */
static const struct kitty_key_data kitty_keymap[] = {
{XKB_KEY_ISO_Level3_Shift, 57453, 'u', true},
{XKB_KEY_ISO_Level5_Shift, 57454, 'u', true},
@ -70,7 +70,7 @@ static const struct kitty_key_data kitty_keymap[] = {
{XKB_KEY_F1, 1, 'P', false},
{XKB_KEY_F2, 1, 'Q', false},
{XKB_KEY_F3, 1, 'R', false},
{XKB_KEY_F3, 13, '~', false},
{XKB_KEY_F4, 1, 'S', false},
{XKB_KEY_F5, 15, '~', false},
{XKB_KEY_F6, 17, '~', false},

22
log.c
View file

@ -15,7 +15,7 @@
#include "xsnprintf.h"
static bool colorize = false;
static bool do_syslog = true;
static bool do_syslog = false;
static enum log_class log_level = LOG_CLASS_NONE;
static const struct {
@ -40,13 +40,24 @@ log_init(enum log_colorize _colorize, bool _do_syslog,
[LOG_FACILITY_DAEMON] = LOG_DAEMON,
};
colorize = _colorize == LOG_COLORIZE_ALWAYS || (_colorize == LOG_COLORIZE_AUTO && isatty(STDERR_FILENO));
/* Don't use colors if NO_COLOR is defined and not empty */
const char *no_color_str = getenv("NO_COLOR");
const bool no_color = no_color_str != NULL && no_color_str[0] != '\0';
colorize = _colorize == LOG_COLORIZE_ALWAYS
|| (_colorize == LOG_COLORIZE_AUTO
&& !no_color && isatty(STDERR_FILENO));
do_syslog = _do_syslog;
log_level = _log_level;
int slvl = log_level_map[_log_level].syslog_equivalent;
if (do_syslog && slvl != -1) {
if (slvl < 0)
do_syslog = false;
if (do_syslog) {
openlog(NULL, /*LOG_PID*/0, facility_map[syslog_facility]);
xassert(slvl >= 0);
setlogmask(LOG_UPTO(slvl));
}
}
@ -100,6 +111,9 @@ _sys_log(enum log_class log_class, const char *module,
if (!do_syslog)
return;
if (log_class > log_level)
return;
/* Map our log level to syslog's level */
int level = log_level_map[log_class].syslog_equivalent;
@ -194,7 +208,7 @@ log_level_from_string(const char *str)
return -1;
for (int i = 0, n = map_len(); i < n; i++)
if (strcmp(str, log_level_map[i].name) == 0)
if (streq(str, log_level_map[i].name))
return i;
return -1;

310
main.c
View file

@ -1,8 +1,8 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
#include <limits.h>
#include <locale.h>
#include <getopt.h>
#include <signal.h>
@ -31,12 +31,9 @@
#include "shm.h"
#include "terminal.h"
#include "util.h"
#include "version.h"
#include "xmalloc.h"
#include "xsnprintf.h"
#include "char32.h"
#if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__
#error "char32_t does not use UTF-32"
#endif
@ -48,17 +45,31 @@ fdm_sigint(struct fdm *fdm, int signo, void *data)
return true;
}
static const char *
version_and_features(void)
struct sigusr_context {
struct terminal *term;
struct server *server;
};
static bool
fdm_sigusr(struct fdm *fdm, int signo, void *data)
{
static char buf[256];
snprintf(buf, sizeof(buf), "version: %s %cpgo %cime %cgraphemes %cassertions",
FOOT_VERSION,
feature_pgo() ? '+' : '-',
feature_ime() ? '+' : '-',
feature_graphemes() ? '+' : '-',
feature_assertions() ? '+' : '-');
return buf;
xassert(signo == SIGUSR1 || signo == SIGUSR2);
struct sigusr_context *ctx = data;
if (ctx->server != NULL) {
if (signo == SIGUSR1)
server_global_theme_switch_to_dark(ctx->server);
else
server_global_theme_switch_to_light(ctx->server);
} else {
if (signo == SIGUSR1)
term_theme_switch_to_dark(ctx->term);
else
term_theme_switch_to_light(ctx->term);
}
return true;
}
static void
@ -73,9 +84,11 @@ print_usage(const char *prog_name)
" -t,--term=TERM value to set the environment variable TERM to (" FOOT_DEFAULT_TERM ")\n"
" -T,--title=TITLE initial window title (foot)\n"
" -a,--app-id=ID window application ID (foot)\n"
" --toplevel-tag=TAG set a custom toplevel tag\n"
" -m,--maximized start in maximized mode\n"
" -F,--fullscreen start in fullscreen mode\n"
" -L,--login-shell start shell as a login shell\n"
" --pty=PATH display an existing PTY instead of creating one\n"
" -D,--working-directory=DIR directory to start in (CWD)\n"
" -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n"
" -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n"
@ -83,9 +96,9 @@ print_usage(const char *prog_name)
" Without PATH, $XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock will be used.\n"
" -H,--hold remain open after child process exits\n"
" -p,--print-pid=FILE|FD print PID to file or FD (only applicable in server mode)\n"
" -d,--log-level={info|warning|error|none} log level (info)\n"
" -d,--log-level={info|warning|error|none} log level (warning)\n"
" -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n"
" -s,--log-no-syslog disable syslog logging (only applicable in server mode)\n"
" -S,--log-no-syslog disable syslog logging (only applicable in server mode)\n"
" -v,--version show the version number and quit\n"
" -e ignored (for compatibility with xterm -e)\n";
@ -171,11 +184,16 @@ sanitize_signals(void)
sigaction(i, &dfl, NULL);
}
enum {
PTY_OPTION = CHAR_MAX + 1,
TOPLEVEL_TAG_OPTION = CHAR_MAX + 2,
};
int
main(int argc, char *const *argv)
{
/* Custom exit code, to enable users to differentiate between foot
* itself failing, and the client application failiing */
* itself failing, and the client application failing */
static const int foot_exit_failure = -26;
int ret = foot_exit_failure;
@ -198,6 +216,7 @@ main(int argc, char *const *argv)
{"term", required_argument, NULL, 't'},
{"title", required_argument, NULL, 'T'},
{"app-id", required_argument, NULL, 'a'},
{"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION},
{"login-shell", no_argument, NULL, 'L'},
{"working-directory", required_argument, NULL, 'D'},
{"font", required_argument, NULL, 'f'},
@ -208,6 +227,7 @@ main(int argc, char *const *argv)
{"maximized", no_argument, NULL, 'm'},
{"fullscreen", no_argument, NULL, 'F'},
{"presentation-timings", no_argument, NULL, 'P'}, /* Undocumented */
{"pty", required_argument, NULL, PTY_OPTION},
{"print-pid", required_argument, NULL, 'p'},
{"log-level", required_argument, NULL, 'd'},
{"log-colorize", optional_argument, NULL, 'l'},
@ -219,24 +239,15 @@ main(int argc, char *const *argv)
bool check_config = false;
const char *conf_path = NULL;
const char *conf_term = NULL;
const char *conf_title = NULL;
const char *conf_app_id = NULL;
const char *custom_cwd = NULL;
bool login_shell = false;
tll(char *) conf_fonts = tll_init();
enum conf_size_type conf_size_type = CONF_SIZE_PX;
int conf_width = -1;
int conf_height = -1;
const char *pty_path = NULL;
bool as_server = false;
const char *conf_server_socket_path = NULL;
bool presentation_timings = false;
bool hold = false;
bool maximized = false;
bool fullscreen = false;
bool unlink_pid_file = false;
const char *pid_file = NULL;
enum log_class log_level = LOG_CLASS_INFO;
enum log_class log_level = LOG_CLASS_WARNING;
enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
bool log_syslog = true;
user_notifications_t user_notifications = tll_init();
@ -258,23 +269,27 @@ main(int argc, char *const *argv)
break;
case 'o':
tll_push_back(overrides, optarg);
tll_push_back(overrides, xstrdup(optarg));
break;
case 't':
conf_term = optarg;
tll_push_back(overrides, xstrjoin("term=", optarg));
break;
case 'L':
login_shell = true;
tll_push_back(overrides, xstrdup("login-shell=yes"));
break;
case 'T':
conf_title = optarg;
tll_push_back(overrides, xstrjoin("title=", optarg));
break;
case 'a':
conf_app_id = optarg;
tll_push_back(overrides, xstrjoin("app-id=", optarg));
break;
case TOPLEVEL_TAG_OPTION:
tll_push_back(overrides, xstrjoin("toplevel-tag=", optarg));
break;
case 'D': {
@ -287,27 +302,11 @@ main(int argc, char *const *argv)
break;
}
case 'f':
tll_free_and_free(conf_fonts, free);
for (char *font = strtok(optarg, ","); font != NULL; font = strtok(NULL, ",")) {
/* Strip leading spaces */
while (*font != '\0' && isspace(*font))
font++;
/* Strip trailing spaces */
char *end = font + strlen(font);
xassert(*end == '\0');
end--;
while (end > font && isspace(*end))
*(end--) = '\0';
if (strlen(font) == 0)
continue;
tll_push_back(conf_fonts, font);
}
case 'f': {
char *font_override = xstrjoin("font=", optarg);
tll_push_back(overrides, font_override);
break;
}
case 'w': {
unsigned width, height;
@ -316,9 +315,9 @@ main(int argc, char *const *argv)
return ret;
}
conf_size_type = CONF_SIZE_PX;
conf_width = width;
conf_height = height;
tll_push_back(
overrides, xasprintf("initial-window-size-pixels=%ux%u",
width, height));
break;
}
@ -329,9 +328,9 @@ main(int argc, char *const *argv)
return ret;
}
conf_size_type = CONF_SIZE_CELLS;
conf_width = width;
conf_height = height;
tll_push_back(
overrides, xasprintf("initial-window-size-chars=%ux%u",
width, height));
break;
}
@ -341,6 +340,10 @@ main(int argc, char *const *argv)
conf_server_socket_path = optarg;
break;
case PTY_OPTION:
pty_path = optarg;
break;
case 'P':
presentation_timings = true;
break;
@ -350,13 +353,11 @@ main(int argc, char *const *argv)
break;
case 'm':
maximized = true;
fullscreen = false;
tll_push_back(overrides, xstrdup("initial-window-mode=maximized"));
break;
case 'F':
fullscreen = true;
maximized = false;
tll_push_back(overrides, xstrdup("initial-window-mode=fullscreen"));
break;
case 'p':
@ -378,11 +379,11 @@ main(int argc, char *const *argv)
}
case 'l':
if (optarg == NULL || strcmp(optarg, "auto") == 0)
if (optarg == NULL || streq(optarg, "auto"))
log_colorize = LOG_COLORIZE_AUTO;
else if (strcmp(optarg, "never") == 0)
else if (streq(optarg, "never"))
log_colorize = LOG_COLORIZE_NEVER;
else if (strcmp(optarg, "always") == 0)
else if (streq(optarg, "always"))
log_colorize = LOG_COLORIZE_ALWAYS;
else {
fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg);
@ -395,7 +396,7 @@ main(int argc, char *const *argv)
break;
case 'v':
printf("foot %s\n", version_and_features());
print_version_and_features("foot ");
return EXIT_SUCCESS;
case 'h':
@ -410,6 +411,11 @@ main(int argc, char *const *argv)
}
}
if (as_server && pty_path) {
fputs("error: --pty is incompatible with server mode\n", stderr);
return ret;
}
log_init(log_colorize, as_server && log_syslog,
as_server ? LOG_FACILITY_DAEMON : LOG_FACILITY_USER, log_level);
@ -418,7 +424,7 @@ main(int argc, char *const *argv)
argv += optind;
}
LOG_INFO("%s", version_and_features());
LOG_INFO("%s", version_and_features);
{
struct utsname name;
@ -433,35 +439,51 @@ main(int argc, char *const *argv)
const char *locale = setlocale(LC_CTYPE, "");
if (locale == NULL) {
LOG_ERR("setlocale() failed");
return ret;
/*
* If the user has configured an invalid locale, or a name of a locale
* that does not exist on this system, then the above call may return
* NULL. We should just continue with the fallback method below.
*/
LOG_ERR("setlocale() failed. The most common cause is that the "
"configured locale is not available, or has been misspelled");
}
LOG_INFO("locale: %s", locale);
LOG_INFO("locale: %s", locale != NULL ? locale : "<invalid>");
bool bad_locale = !locale_is_utf8();
bool bad_locale = locale == NULL || !locale_is_utf8();
if (bad_locale) {
static const char fallback_locales[][12] = {
"C.UTF-8",
"en_US.UTF-8",
};
char *saved_locale = locale != NULL ? xstrdup(locale) : NULL;
/*
* Try to force an UTF-8 locale. If we succeed, launch the
* users shell as usual, but add a user-notification saying
* user's shell as usual, but add a user-notification saying
* the locale has been changed.
*/
for (size_t i = 0; i < ALEN(fallback_locales); i++) {
const char *const fallback_locale = fallback_locales[i];
if (setlocale(LC_CTYPE, fallback_locale) != NULL) {
LOG_WARN("locale '%s' is not UTF-8, using '%s' instead",
locale, fallback_locale);
if (saved_locale != NULL) {
LOG_WARN(
"'%s' is not a UTF-8 locale, falling back to '%s'",
saved_locale, fallback_locale);
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_WARNING,
"locale '%s' is not UTF-8, using '%s' instead",
locale, fallback_locale);
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_WARNING,
"'%s' is not a UTF-8 locale, falling back to '%s'",
saved_locale, fallback_locale);
} else {
LOG_WARN(
"invalid locale, falling back to '%s'", fallback_locale);
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_WARNING,
"invalid locale, falling back to '%s'", fallback_locale);
}
bad_locale = false;
break;
@ -469,22 +491,31 @@ main(int argc, char *const *argv)
}
if (bad_locale) {
LOG_ERR("locale '%s' is not UTF-8, "
"and failed to enable a fallback locale", locale);
if (saved_locale != NULL) {
LOG_ERR(
"'%s' is not a UTF-8 locale, and failed to find a fallback",
saved_locale);
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_ERROR,
"locale '%s' is not UTF-8, "
"and failed to enable a fallback locale",
locale);
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_ERROR,
"'%s' is not a UTF-8 locale, and failed to find a fallback",
saved_locale);
} else {
LOG_ERR("invalid locale, and failed to find a fallback");
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_ERROR,
"invalid locale, and failed to find a fallback");
}
}
free(saved_locale);
}
struct config conf = {NULL};
bool conf_successful = config_load(
&conf, conf_path, &user_notifications, &overrides, check_config);
&conf, conf_path, &user_notifications, &overrides, check_config, as_server);
tll_free(overrides);
tll_free_and_free(overrides, free);
if (!conf_successful) {
config_free(&conf);
return ret;
@ -503,55 +534,11 @@ main(int argc, char *const *argv)
(enum fcft_log_colorize)log_colorize,
as_server && log_syslog,
(enum fcft_log_class)log_level);
fcft_set_scaling_filter(conf.tweak.fcft_filter);
if (conf_term != NULL) {
free(conf.term);
conf.term = xstrdup(conf_term);
}
if (conf_title != NULL) {
free(conf.title);
conf.title = xstrdup(conf_title);
}
if (conf_app_id != NULL) {
free(conf.app_id);
conf.app_id = xstrdup(conf_app_id);
}
if (login_shell)
conf.login_shell = true;
if (tll_length(conf_fonts) > 0) {
for (size_t i = 0; i < ALEN(conf.fonts); i++)
config_font_list_destroy(&conf.fonts[i]);
struct config_font_list *font_list = &conf.fonts[0];
xassert(font_list->count == 0);
xassert(font_list->arr == NULL);
font_list->arr = xmalloc(
tll_length(conf_fonts) * sizeof(font_list->arr[0]));
tll_foreach(conf_fonts, it) {
struct config_font font;
if (!config_font_parse(it->item, &font)) {
LOG_ERR("%s: invalid font specification", it->item);
} else
font_list->arr[font_list->count++] = font;
}
tll_free(conf_fonts);
}
if (conf_width > 0 && conf_height > 0) {
conf.size.type = conf_size_type;
conf.size.width = conf_width;
conf.size.height = conf_height;
}
if (conf_server_socket_path != NULL) {
free(conf.server_socket_path);
conf.server_socket_path = xstrdup(conf_server_socket_path);
}
if (maximized)
conf.startup_mode = STARTUP_MAXIMIZED;
else if (fullscreen)
conf.startup_mode = STARTUP_FULLSCREEN;
conf.presentation_timings = presentation_timings;
conf.hold_at_exit = hold;
@ -581,10 +568,10 @@ main(int argc, char *const *argv)
char *_cwd = NULL;
if (cwd == NULL) {
errno = 0;
size_t buf_len = 1024;
do {
_cwd = xrealloc(_cwd, buf_len);
errno = 0;
if (getcwd(_cwd, buf_len) == NULL && errno != ERANGE) {
LOG_ERRNO("failed to get current working directory");
goto out;
@ -594,7 +581,30 @@ 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 &&
streq(resolved_path_cwd, resolved_path_pwd))
{
/*
* The resolved path of $PWD matches the resolved path of
* the *actual* working directory - use $PWD.
*
* This makes a difference when $PWD refers to a symlink.
*/
cwd = pwd;
}
free(resolved_path_cwd);
free(resolved_path_pwd);
}
shm_set_max_pool_size(conf.tweak.max_shm_pool_size);
shm_set_min_stride_alignment(conf.tweak.min_stride_alignment);
if ((fdm = fdm_init()) == NULL)
goto out;
@ -615,7 +625,7 @@ main(int argc, char *const *argv)
goto out;
if (!as_server && (term = term_init(
&conf, fdm, reaper, wayl, "foot", cwd, token,
&conf, fdm, reaper, wayl, "foot", cwd, token, pty_path,
argc, argv, NULL,
&term_shutdown_cb, &shutdown_ctx)) == NULL) {
goto out;
@ -633,6 +643,17 @@ main(int argc, char *const *argv)
goto out;
}
struct sigusr_context sigusr_context = {
.term = term,
.server = server,
};
if (!fdm_signal_add(fdm, SIGUSR1, &fdm_sigusr, &sigusr_context) ||
!fdm_signal_add(fdm, SIGUSR2, &fdm_sigusr, &sigusr_context))
{
goto out;
}
struct sigaction sig_ign = {.sa_handler = SIG_IGN};
sigemptyset(&sig_ign.sa_mask);
if (sigaction(SIGHUP, &sig_ign, NULL) < 0 ||
@ -668,6 +689,8 @@ out:
wayl_destroy(wayl);
key_binding_manager_destroy(key_binding_manager);
reaper_destroy(reaper);
fdm_signal_del(fdm, SIGUSR1);
fdm_signal_del(fdm, SIGUSR2);
fdm_signal_del(fdm, SIGTERM);
fdm_signal_del(fdm, SIGINT);
fdm_destroy(fdm);
@ -682,3 +705,22 @@ out:
log_deinit();
return ret == EXIT_SUCCESS && !as_server ? shutdown_ctx.exit_code : ret;
}
UNITTEST
{
char *s = xstrjoin("foo", "bar");
xassert(streq(s, "foobar"));
free(s);
s = xstrjoin3("foo", " ", "bar");
xassert(streq(s, "foo bar"));
free(s);
s = xstrjoin3("foo", ",", "bar");
xassert(streq(s, "foo,bar"));
free(s);
s = xstrjoin3("foo", "bar", "baz");
xassert(streq(s, "foobarbaz"));
free(s);
}

View file

@ -1,7 +1,7 @@
project('foot', 'c',
version: '1.12.0',
version: '1.26.1',
license: 'MIT',
meson_version: '>=0.58.0',
meson_version: '>=0.59.0',
default_options: [
'c_std=c11',
'warning_level=1',
@ -12,13 +12,78 @@ is_debug_build = get_option('buildtype').startswith('debug')
cc = meson.get_compiler('c')
if cc.has_function('memfd_create')
# Newer clang versions warns when using __COUNTER__ without -std=c2y
if cc.has_argument('-Wc2y-extensions')
add_project_arguments('-Wno-c2y-extensions', language: 'c')
endif
if cc.has_function('memfd_create',
args: ['-D_GNU_SOURCE'],
prefix: '#include <sys/mman.h>')
add_project_arguments('-DMEMFD_CREATE', language: 'c')
endif
# Missing on DragonFly, FreeBSD < 14.1
if cc.has_function('execvpe',
args: ['-D_GNU_SOURCE'],
prefix: '#include <unistd.h>')
add_project_arguments('-DEXECVPE', language: 'c')
endif
if cc.has_function('sigabbrev_np',
args: ['-D_GNU_SOURCE'],
prefix: '#include <string.h>')
add_project_arguments('-DSIGABBREV_NP', language: 'c')
endif
utmp_backend = get_option('utmp-backend')
if utmp_backend == 'auto'
host_os = host_machine.system()
if host_os == 'linux'
utmp_backend = 'libutempter'
elif host_os == 'freebsd'
utmp_backend = 'ulog'
else
utmp_backend = 'none'
endif
endif
utmp_default_helper_path = get_option('utmp-default-helper-path')
if utmp_backend == 'none'
utmp_add = ''
utmp_del = ''
utmp_del_have_argument = false
utmp_default_helper_path = ''
elif utmp_backend == 'libutempter'
utmp_add = 'add'
utmp_del = 'del'
utmp_del_have_argument = false
if utmp_default_helper_path == 'auto'
utmp_default_helper_path = join_paths('/usr', get_option('libdir'), 'utempter', 'utempter')
endif
elif utmp_backend == 'ulog'
utmp_add = 'login'
utmp_del = 'logout'
utmp_del_have_argument = false
if utmp_default_helper_path == 'auto'
utmp_default_helper_path = join_paths('/usr', get_option('libexecdir'), 'ulog-helper')
endif
else
error('invalid utmp backend')
endif
add_project_arguments(
['-D_GNU_SOURCE=200809L',
'-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo'))] +
(utmp_backend != 'none'
? ['-DUTMP_ADD="@0@"'.format(utmp_add),
'-DUTMP_DEL="@0@"'.format(utmp_del),
'-DUTMP_DEFAULT_HELPER_PATH="@0@"'.format(utmp_default_helper_path)]
: []) +
(utmp_del_have_argument
? ['-DUTMP_DEL_HAVE_ARGUMENT=1']
: []) +
(is_debug_build
? ['-D_DEBUG']
: [cc.get_supported_arguments('-fno-asynchronous-unwind-tables')]) +
@ -78,7 +143,9 @@ math = cc.find_library('m')
threads = [dependency('threads'), cc.find_library('stdthreads', required: false)]
libepoll = dependency('epoll-shim', required: false)
pixman = dependency('pixman-1')
wayland_protocols = dependency('wayland-protocols')
wayland_protocols = dependency('wayland-protocols', version: '>=1.41',
fallback: 'wayland-protocols',
default_options: ['tests=false'])
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
xkb = dependency('xkbcommon', version: '>=1.0.0')
@ -89,8 +156,12 @@ if utf8proc.found()
add_project_arguments('-DFOOT_GRAPHEME_CLUSTERING=1', language: 'c')
endif
tllist = dependency('tllist', version: '>=1.0.4', fallback: 'tllist')
fcft = dependency('fcft', version: ['>=3.0.1', '<4.0.0'], fallback: 'fcft')
if pixman.version().version_compare('>=0.46.0')
add_project_arguments('-DHAVE_PIXMAN_RGBA_16', language: 'c')
endif
tllist = dependency('tllist', version: '>=1.1.0', fallback: 'tllist')
fcft = dependency('fcft', version: ['>=3.3.1', '<4.0.0'], fallback: 'fcft')
wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir')
@ -101,17 +172,30 @@ wscanner_prog = find_program(
wl_proto_headers = []
wl_proto_src = []
wl_proto_xml = [
wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml',
wayland_protocols_datadir + '/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml',
wayland_protocols_datadir + '/unstable/xdg-output/xdg-output-unstable-v1.xml',
wayland_protocols_datadir + '/unstable/primary-selection/primary-selection-unstable-v1.xml',
wayland_protocols_datadir + '/stable/presentation-time/presentation-time.xml',
wayland_protocols_datadir + '/unstable/text-input/text-input-unstable-v3.xml',
]
wayland_protocols_datadir / 'stable/xdg-shell/xdg-shell.xml',
wayland_protocols_datadir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml',
wayland_protocols_datadir / 'unstable/xdg-output/xdg-output-unstable-v1.xml',
wayland_protocols_datadir / 'unstable/primary-selection/primary-selection-unstable-v1.xml',
wayland_protocols_datadir / 'stable/presentation-time/presentation-time.xml',
wayland_protocols_datadir / 'unstable/text-input/text-input-unstable-v3.xml',
wayland_protocols_datadir / 'staging/xdg-activation/xdg-activation-v1.xml',
wayland_protocols_datadir / 'stable/viewporter/viewporter.xml',
wayland_protocols_datadir / 'staging/fractional-scale/fractional-scale-v1.xml',
wayland_protocols_datadir / 'unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1
wayland_protocols_datadir / 'staging/cursor-shape/cursor-shape-v1.xml',
wayland_protocols_datadir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml',
wayland_protocols_datadir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml',
wayland_protocols_datadir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml',
wayland_protocols_datadir / 'staging/color-management/color-management-v1.xml',
]
if wayland_protocols.version().version_compare('>=1.21')
add_project_arguments('-DHAVE_XDG_ACTIVATION', language: 'c')
wl_proto_xml += [wayland_protocols_datadir + '/staging/xdg-activation/xdg-activation-v1.xml']
if (wayland_protocols.version().version_compare('>=1.43'))
wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml']
add_project_arguments('-DHAVE_XDG_TOPLEVEL_TAG=1', language: 'c')
endif
if (wayland_protocols.version().version_compare('>=1.45'))
wl_proto_xml += [wayland_protocols_datadir / 'staging/ext-background-effect/ext-background-effect-v1.xml']
add_project_arguments('-DHAVE_EXT_BACKGROUND_EFFECT=1', language: 'c')
endif
foreach prot : wl_proto_xml
@ -146,13 +230,30 @@ builtin_terminfo = custom_target(
'@default_terminfo@', foot_terminfo, 'foot', '@OUTPUT@']
)
generate_emoji_variation_sequences = files('scripts/generate-emoji-variation-sequences.py')
emoji_variation_sequences = custom_target(
'generate_emoji_variation_sequences',
input: 'unicode/emoji-variation-sequences.txt',
output: 'emoji-variation-sequences.h',
command: [python, generate_emoji_variation_sequences, '@INPUT@', '@OUTPUT@']
)
generate_srgb_funcs = files('scripts/srgb.py')
srgb_funcs = custom_target(
'generate_srgb_funcs',
output: ['srgb.c', 'srgb.h'],
command: [python, generate_srgb_funcs, '@OUTPUT0@', '@OUTPUT1@']
)
common = static_library(
'common',
'log.c', 'log.h',
'char32.c', 'char32.h',
'debug.c', 'debug.h',
'macros.h',
'xmalloc.c', 'xmalloc.h',
'xsnprintf.c', 'xsnprintf.h'
'xsnprintf.c', 'xsnprintf.h',
dependencies: [utf8proc]
)
misc = static_library(
@ -160,20 +261,24 @@ misc = static_library(
'hsl.c', 'hsl.h',
'macros.h',
'misc.c', 'misc.h',
'uri.c', 'uri.h'
'uri.c', 'uri.h',
dependencies: [utf8proc],
link_with: [common]
)
vtlib = static_library(
'vtlib',
'base64.c', 'base64.h',
'composed.c', 'composed.h',
'cursor-shape.c', 'cursor-shape.h',
'csi.c', 'csi.h',
'dcs.c', 'dcs.h',
'macros.h',
'osc.c', 'osc.h',
'sixel.c', 'sixel.h',
'vt.c', 'vt.h',
builtin_terminfo, wl_proto_src + wl_proto_headers,
builtin_terminfo, srgb_funcs,
wl_proto_src + wl_proto_headers,
version,
dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc],
link_with: [common, misc],
@ -184,11 +289,19 @@ pgolib = static_library(
'grid.c', 'grid.h',
'selection.c', 'selection.h',
'terminal.c', 'terminal.h',
emoji_variation_sequences,
wl_proto_src + wl_proto_headers,
dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc],
link_with: vtlib,
)
tokenize = static_library(
'tokenizelib',
'tokenize.c',
dependencies: [utf8proc],
link_with: [common],
)
if get_option('b_pgo') == 'generate'
executable(
'pgo',
@ -207,7 +320,7 @@ executable(
'commands.c', 'commands.h',
'extract.c', 'extract.h',
'fdm.c', 'fdm.h',
'foot-features.h',
'foot-features.c', 'foot-features.h',
'ime.c', 'ime.h',
'input.c', 'input.h',
'key-binding.c', 'key-binding.h',
@ -222,10 +335,12 @@ 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',
wl_proto_src + wl_proto_headers, version,
'wayland.c', 'wayland.h', 'shm-formats.h',
'xkbcommon-vmod.h',
srgb_funcs, wl_proto_src + wl_proto_headers, version,
dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc,
tllist, fcft],
link_with: pgolib,
@ -234,11 +349,11 @@ executable(
executable(
'footclient',
'client.c', 'client-protocol.h',
'foot-features.h',
'foot-features.c', 'foot-features.h',
'macros.h',
'util.h',
version,
dependencies: [tllist],
dependencies: [tllist, utf8proc],
link_with: common,
install: true)
@ -247,21 +362,27 @@ install_data(
install_dir: join_paths(get_option('datadir'), 'applications'))
systemd = dependency('systemd', required: false)
if systemd.found()
custom_systemd_units_dir = get_option('systemd-units-dir')
if systemd.found() or custom_systemd_units_dir != ''
configuration = configuration_data()
configuration.set('bindir', join_paths(get_option('prefix'), get_option('bindir')))
systemd_units_dir = systemd.get_variable('systemduserunitdir')
if (custom_systemd_units_dir == '')
systemd_units_dir = systemd.get_variable('systemduserunitdir')
else
systemd_units_dir = custom_systemd_units_dir
endif
configure_file(
configuration: configuration,
input: 'foot-server@.service.in',
input: 'foot-server.service.in',
output: '@BASENAME@',
install_dir: systemd_units_dir
)
install_data(
'foot-server@.socket',
'foot-server.socket',
install_dir: systemd_units_dir)
endif
@ -278,11 +399,16 @@ if get_option('themes')
install_subdir('themes', install_dir: join_paths(get_option('datadir'), 'foot'))
endif
terminfo_base_name = get_option('terminfo-base-name')
if terminfo_base_name == ''
terminfo_base_name = get_option('default-terminfo')
endif
tic = find_program('tic', native: true, required: get_option('terminfo'))
if tic.found()
conf_data = configuration_data(
{
'default_terminfo': get_option('default-terminfo'),
'default_terminfo': terminfo_base_name
}
)
@ -293,9 +419,9 @@ if tic.found()
)
custom_target(
'terminfo',
output: get_option('default-terminfo')[0],
output: terminfo_base_name[0],
input: preprocessed,
command: [tic, '-x', '-o', '@OUTDIR@', '-e', '@0@,@0@-direct'.format(get_option('default-terminfo')), '@INPUT@'],
command: [tic, '-x', '-o', '@OUTDIR@', '-e', '@0@,@0@-direct'.format(terminfo_base_name), '@INPUT@'],
install: true,
install_dir: terminfo_install_location
)
@ -303,6 +429,7 @@ endif
subdir('completions')
subdir('icons')
subdir('utils')
if (get_option('tests'))
subdir('tests')
@ -314,7 +441,10 @@ summary(
'Themes': get_option('themes'),
'IME': get_option('ime'),
'Grapheme clustering': utf8proc.found(),
'utmp backend': utmp_backend,
'utmp helper default path': utmp_default_helper_path,
'Build terminfo': tic.found(),
'Terminfo base name': terminfo_base_name,
'Terminfo install location': terminfo_install_location,
'Default TERM': get_option('default-terminfo'),
'Set TERMINFO': get_option('custom-terminfo-install-location') != '',

View file

@ -15,6 +15,15 @@ option('tests', type: 'boolean', value: true, description: 'Build tests')
option('terminfo', type: 'feature', value: 'enabled', description: 'Build and install foot\'s terminfo files.')
option('default-terminfo', type: 'string', value: 'foot',
description: 'Default value of the "term" option in foot.ini.')
option('terminfo-base-name', type: 'string',
description: 'Base name of the generated terminfo files. Defaults to the value of the \'default-terminfo\' meson option')
option('custom-terminfo-install-location', type: 'string', value: '',
description: 'Path to foot\'s terminfo, relative to ${prefix}. If set, foot will set $TERMINFO to this value in the client process.')
option('systemd-units-dir', type: 'string', value: '',
description: 'Where to install the systemd service files (absolute path). Default: ${systemduserunitdir}')
option('utmp-backend', type: 'combo', value: 'auto', choices: ['none', 'libutempter', 'ulog', 'auto'],
description: 'Which utmp logging backend to use. This affects how (with what arguments) the utmp helper binary (see \'utmp-default-helper-path\')is called. Default: auto (linux=libutempter, freebsd=ulog, others=none)')
option('utmp-default-helper-path', type: 'string', value: 'auto',
description: 'Default path to the utmp helper binary. Default: auto-detect')

38
misc.c
View file

@ -1,5 +1,6 @@
#include "misc.h"
#include "char32.h"
#include <stdlib.h>
bool
isword(char32_t wc, bool spaces_only, const char32_t *delimiters)
@ -13,15 +14,50 @@ 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;
}
}
bool
is_valid_utf8_and_printable(const char *value)
{
char32_t *wide = ambstoc32(value);
if (wide == NULL)
return false;
for (const char32_t *c = wide; *c != U'\0'; c++) {
if (!isc32print(*c)) {
free(wide);
return false;
}
}
free(wide);
return true;
}

3
misc.h
View file

@ -6,4 +6,7 @@
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);
bool is_valid_utf8_and_printable(const char *value);

734
notify.c
View file

@ -1,9 +1,11 @@
#include "notify.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <fcntl.h>
@ -12,43 +14,574 @@
#include "log.h"
#include "config.h"
#include "spawn.h"
#include "terminal.h"
#include "util.h"
#include "wayland.h"
#include "xmalloc.h"
#include "xsnprintf.h"
void
notify_notify(const struct terminal *term, const char *title, const char *body)
notify_free(struct terminal *term, struct notification *notif)
{
LOG_DBG("notify: title=\"%s\", msg=\"%s\"", title, body);
if (notif->pid > 0)
fdm_del(term->fdm, notif->stdout_fd);
if (term->conf->notify_focus_inhibit && term->kbd_focus) {
/* No notifications while were focused */
return;
free(notif->id);
free(notif->title);
free(notif->body);
free(notif->category);
free(notif->app_id);
free(notif->icon_cache_id);
free(notif->icon_symbolic_name);
free(notif->icon_data);
free(notif->sound_name);
free(notif->xdg_token);
free(notif->stdout_data);
tll_free_and_free(notif->actions, free);
if (notif->icon_path != NULL) {
unlink(notif->icon_path);
free(notif->icon_path);
if (notif->icon_fd >= 0)
close(notif->icon_fd);
}
if (title == NULL || body == NULL)
return;
memset(notif, 0, sizeof(*notif));
}
if (term->conf->notify.argv.args == NULL)
static bool
write_icon_file(const void *data, size_t data_sz, int *fd, char **filename,
char **symbolic_name)
{
xassert(*filename == NULL);
xassert(*symbolic_name == NULL);
char name[64] = "/tmp/foot-notification-icon-XXXXXX";
*filename = NULL;
*symbolic_name = NULL;
*fd = mkostemp(name, O_CLOEXEC);
if (*fd < 0) {
LOG_ERRNO("failed to create temporary file for icon cache");
return false;
}
if (write(*fd, data, data_sz) != (ssize_t)data_sz) {
LOG_ERRNO("failed to write icon data to temporary file");
close(*fd);
*fd = -1;
return false;
}
LOG_DBG("wrote icon data to %s", name);
*filename = xstrdup(name);
*symbolic_name = xstrjoin("file://", *filename);
return true;
}
static bool
to_integer(const char *line, size_t len, uint32_t *res)
{
bool is_id = true;
uint32_t maybe_id = 0;
for (size_t i = 0; i < len; i++) {
char digit = line[i];
if (digit < '0' || digit > '9') {
is_id = false;
break;
}
maybe_id *= 10;
maybe_id += digit - '0';
}
*res = maybe_id;
return is_id;
}
static void
consume_stdout(struct notification *notif, bool eof)
{
char *data = notif->stdout_data;
const char *line = data;
size_t left = notif->stdout_sz;
/* Process stdout, line-by-line */
while (left > 0) {
line = data;
size_t len = left;
char *eol = (char *)memchr(line, '\n', left);
if (eol != NULL) {
*eol = '\0';
len = strlen(line);
data = eol + 1;
} else if (!eof)
break;
uint32_t maybe_id = 0;
uint32_t maybe_button_nr = 0;
/* Check for daemon assigned ID, either '123', or 'id=123' */
if ((notif->external_id == 0 && to_integer(line, len, &maybe_id)) ||
(len > 3 && memcmp(line, "id=", 3) == 0 &&
to_integer(&line[3], len - 3, &maybe_id)))
{
notif->external_id = maybe_id;
LOG_DBG("external ID: %u", notif->external_id);
}
/* Check for triggered action, either 'default' or 'action=default' */
else if ((len == 7 && memcmp(line, "default", 7) == 0) ||
(len == 7 + 7 && memcmp(line, "action=default", 7 + 7) == 0))
{
notif->activated = true;
LOG_DBG("notification's default action was triggered");
}
else if (len > 7 && memcmp(line, "action=", 7) == 0) {
notif->activated = true;
if (to_integer(&line[7], len - 7, &maybe_button_nr)) {
notif->activated_button = maybe_button_nr;
LOG_DBG("custom action %u triggered", notif->activated_button);
} else {
LOG_DBG("unrecognized action triggered: %.*s",
(int)(len - 7), &line[7]);
}
}
else if (notif->external_id > 0 &&
to_integer(line, len, &maybe_button_nr) &&
maybe_button_nr > 0 &&
maybe_button_nr <= notif->button_count)
{
/* Single integer, appearing *after* the ID, and is within
the custom button/action range */
notif->activated = true;
notif->activated_button = maybe_button_nr;
LOG_DBG("custom action %u triggered", notif->activated_button);
}
/* Check for XDG activation token, 'xdgtoken=xyz' */
else if (len > 9 && memcmp(line, "xdgtoken=", 9) == 0) {
notif->xdg_token = xstrndup(&line[9], len - 9);
LOG_DBG("XDG token: \"%s\"", notif->xdg_token);
}
left -= len + (eol != NULL ? 1 : 0);
}
if (left > 0)
memmove(notif->stdout_data, data, left);
notif->stdout_sz = left;
}
static bool
fdm_notify_stdout(struct fdm *fdm, int fd, int events, void *data)
{
const struct terminal *term = data;
struct notification *notif = NULL;
/* Find notification */
tll_foreach(term->active_notifications, it) {
if (it->item.stdout_fd == fd) {
notif = &it->item;
break;
}
}
if (events & EPOLLIN) {
char buf[512];
ssize_t count = read(fd, buf, sizeof(buf) - 1);
if (count < 0) {
if (errno == EINTR)
return true;
LOG_ERRNO("failed to read notification activation token");
return false;
}
if (count > 0 && notif != NULL) {
if (notif->stdout_data == NULL) {
xassert(notif->stdout_sz == 0);
notif->stdout_data = xmemdup(buf, count);
} else {
notif->stdout_data = xrealloc(notif->stdout_data, notif->stdout_sz + count);
memcpy(&notif->stdout_data[notif->stdout_sz], buf, count);
}
notif->stdout_sz += count;
consume_stdout(notif, false);
}
}
if (events & EPOLLHUP) {
fdm_del(fdm, fd);
if (notif != NULL) {
notif->stdout_fd = -1;
consume_stdout(notif, true);
}
}
return true;
}
static void
notif_done(struct reaper *reaper, pid_t pid, int status, void *data)
{
struct terminal *term = data;
tll_foreach(term->active_notifications, it) {
struct notification *notif = &it->item;
if (notif->pid != pid)
continue;
LOG_DBG("notification %s closed",
notif->id != NULL ? notif->id : "<unset>");
if (notif->activated && notif->focus) {
LOG_DBG("focus window on notification activation: \"%s\"",
notif->xdg_token);
if (notif->xdg_token == NULL)
LOG_WARN("cannot focus window: no activation token available");
else
wayl_activate(term->wl, term->window, notif->xdg_token);
}
if (notif->activated && notif->report_activated) {
LOG_DBG("sending notification activation event to client");
const char *id = notif->id != NULL ? notif->id : "0";
char button_nr[16] = {0};
if (notif->activated_button > 0) {
xsnprintf(
button_nr, sizeof(button_nr), "%u", notif->activated_button);
}
char reply[7 + strlen(id) + 1 + strlen(button_nr) + 2 + 1];
size_t n = xsnprintf(
reply, sizeof(reply), "\033]99;i=%s;%s\033\\", id, button_nr);
term_to_slave(term, reply, n);
}
if (notif->report_closed) {
LOG_DBG("sending notification close event to client");
const char *id = notif->id != NULL ? notif->id : "0";
char reply[7 + strlen(id) + 1 + 7 + 1 + 2 + 1];
size_t n = xsnprintf(
reply, sizeof(reply), "\033]99;i=%s:p=close;\033\\", id);
term_to_slave(term, reply, n);
}
notify_free(term, notif);
tll_remove(term->active_notifications, it);
return;
}
}
static bool
expand_action_to_argv(struct terminal *term, const char *name, const char *label,
size_t *argc, char ***argv)
{
char **expanded = NULL;
size_t count = 0;
if (!spawn_expand_template(
&term->conf->desktop_notifications.command_action_arg, 2,
(const char *[]){"action-name", "action-label"},
(const char *[]){name, label},
&count, &expanded))
{
return false;
}
/* Append to the "global" actions argv */
*argv = xrealloc(*argv, (*argc + count) * sizeof((*argv)[0]));
memcpy(&(*argv)[*argc], expanded, count * sizeof(expanded[0]));
*argc += count;
free(expanded);
return true;
}
bool
notify_notify(struct terminal *term, struct notification *notif)
{
xassert(notif->xdg_token == NULL);
xassert(notif->external_id == 0);
xassert(notif->pid == 0);
xassert(notif->stdout_fd <= 0);
xassert(notif->stdout_data == NULL);
xassert(notif->icon_path == NULL);
xassert(notif->icon_fd <= 0);
notif->pid = -1;
notif->stdout_fd = -1;
notif->icon_fd = -1;
if (term->conf->desktop_notifications.command.argv.args == NULL)
return false;
if ((term->conf->desktop_notifications.inhibit_when_focused ||
notif->when != NOTIFY_ALWAYS)
&& term->kbd_focus)
{
/* No notifications while we're focused */
return false;
}
const char *app_id = notif->app_id != NULL
? notif->app_id
: term->app_id != NULL
? term->app_id
: term->conf->app_id;
const char *title = notif->title != NULL ? notif->title : notif->body;
const char *body = notif->title != NULL && notif->body != NULL ? notif->body : "";
/* Icon: symbolic name if present, otherwise a filename */
const char *icon_name_or_path = "";
if (notif->icon_cache_id != NULL) {
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
const struct notification_icon *icon = &term->notification_icons[i];
if (icon->id != NULL && streq(icon->id, notif->icon_cache_id)) {
/* For now, we set the symbolic name to 'file:///path'
* when using a file based icon. */
xassert(icon->symbolic_name != NULL);
icon_name_or_path = icon->symbolic_name;
LOG_DBG("using icon from cache (cache ID: %s): %s",
icon->id, icon_name_or_path);
break;
}
}
} else if (notif->icon_symbolic_name != NULL) {
icon_name_or_path = notif->icon_symbolic_name;
LOG_DBG("using symbolic icon from notification: %s", icon_name_or_path);
} else if (notif->icon_data_sz > 0) {
xassert(notif->icon_data != NULL);
if (write_icon_file(
notif->icon_data, notif->icon_data_sz,
&notif->icon_fd,
&notif->icon_path,
&notif->icon_symbolic_name))
icon_name_or_path = notif->icon_symbolic_name;
LOG_DBG("using icon data from notification: %s", icon_name_or_path);
}
bool track_notification = notif->focus ||
notif->report_activated ||
notif->may_be_programatically_closed;
uint32_t replaces_id = 0;
if (notif->id != NULL) {
tll_foreach(term->active_notifications, it) {
struct notification *existing = &it->item;
if (existing->id == NULL)
continue;
/*
* When replacing/updating a notification, we may have
* *multiple* notification helpers running for the "same"
* notification. Make sure only the *last* notification's
* report closed/activated are honored, to avoid sending
* multiple reports.
*
* This also means we cannot 'break' out of the loop - we
* must check *all* notifications.
*/
if (existing->external_id != 0 && streq(existing->id, notif->id)) {
replaces_id = existing->external_id;
existing->report_activated = false;
existing->report_closed = false;
}
}
}
char replaces_id_str[16];
xsnprintf(replaces_id_str, sizeof(replaces_id_str), "%u", replaces_id);
const char *urgency_str =
notif->urgency == NOTIFY_URGENCY_LOW
? "low"
: notif->urgency == NOTIFY_URGENCY_NORMAL
? "normal" : "critical";
LOG_DBG("notify: title=\"%s\", body=\"%s\", app-id=\"%s\", category=\"%s\", "
"urgency=\"%s\", icon=\"%s\", expires=%d, replaces=%u, muted=%s, "
"sound-name=%s (tracking: %s)",
title, body, app_id, notif->category, urgency_str, icon_name_or_path,
notif->expire_time, replaces_id,
notif->muted ? "yes" : "no", notif->sound_name,
track_notification ? "yes" : "no");
xassert(title != NULL);
if (title == NULL)
return false;
char **argv = NULL;
size_t argc = 0;
char **action_argv = NULL;
size_t action_argc = 0;
char expire_time[16];
xsnprintf(expire_time, sizeof(expire_time), "%d", notif->expire_time);
if (term->conf->desktop_notifications.command_action_arg.argv.args) {
if (!expand_action_to_argv(
term, "default", "Activate", &action_argc, &action_argv))
{
return false;
}
size_t action_idx = 1;
tll_foreach(notif->actions, it) {
/* Custom actions use a numerical name, starting at 1 */
char name[16];
xsnprintf(name, sizeof(name), "%zu", action_idx++);
if (!expand_action_to_argv(
term, name, it->item, &action_argc, &action_argv))
{
for (size_t i = 0; i < action_argc; i++)
free(action_argv[i]);
free(action_argv);
return false;
}
}
}
if (!spawn_expand_template(
&term->conf->notify, 4,
(const char *[]){"app-id", "window-title", "title", "body"},
(const char *[]){term->conf->app_id, term->window_title, title, body},
&argc, &argv))
&term->conf->desktop_notifications.command, 12,
(const char *[]){
"app-id", "window-title", "icon", "title", "body", "category",
"urgency", "muted", "sound-name", "expire-time", "replace-id",
"action-argument"},
(const char *[]){
app_id, term->window_title, icon_name_or_path, title,
body != NULL ? body : "",
notif->category != NULL ? notif->category : "", urgency_str,
notif->muted ? "true" : "false",
notif->sound_name != NULL ? notif->sound_name : "",
expire_time, replaces_id_str,
/* Custom expansion below, since we need to expand to multiple arguments */
"${action-argument}"},
&argc, &argv))
{
return;
return false;
}
/* Post-process the expanded argv, and patch in all the --action
arguments we expanded earlier */
for (size_t i = 0; i < argc; i++) {
if (!streq(argv[i], "${action-argument}"))
continue;
if (action_argc == 0) {
free(argv[i]);
/* Remove ${command-argument}, but include terminating NULL */
memmove(&argv[i], &argv[i + 1], (argc - i) * sizeof(argv[0]));
argc--;
break;
}
/* Remove the "${action-argument}" entry, add all actions argument
from earlier, but include terminating NULL */
argv = xrealloc(argv, (argc + action_argc) * sizeof(argv[0]));
/* Move remaining arguments to after the action arguments */
memmove(&argv[i + action_argc],
&argv[i + 1],
(argc - i) * sizeof(argv[0])); /* Include terminating NULL */
free(argv[i]); /* Free xstrdup("${action-argument}"); */
/* Insert the action arguments */
for (size_t j = 0; j < action_argc; j++) {
argv[i + j] = action_argv[j];
action_argv[j] = NULL;
}
argc += action_argc;
argc--; /* The ${action-argument} option has been removed */
break;
}
LOG_DBG("notify command:");
for (size_t i = 0; i < argc; i++)
LOG_DBG(" argv[%zu] = \"%s\"", i, argv[i]);
xassert(argv[argc] == NULL);
int stdout_fds[2] = {-1, -1};
if (track_notification) {
if (pipe2(stdout_fds, O_CLOEXEC | O_NONBLOCK) < 0) {
LOG_WARN("failed to create stdout pipe");
track_notification = false;
/* Non-fatal */
} else {
tll_push_back(term->active_notifications, *notif);
/* We've taken over ownership of all data; clear, so that
notify_free() doesn't double free */
notif->id = NULL;
notif->title = NULL;
notif->body = NULL;
notif->category = NULL;
notif->app_id = NULL;
notif->icon_cache_id = NULL;
notif->icon_symbolic_name = NULL;
notif->icon_data = NULL;
notif->icon_data_sz = 0;
notif->icon_path = NULL;
notif->sound_name = NULL;
notif->icon_fd = -1;
notif->stdout_fd = -1;
struct notification *new_notif = &tll_back(term->active_notifications);
/* We don't need these anymore. They'll be free:d by the caller */
new_notif->button_count = tll_length(notif->actions);
memset(&new_notif->actions, 0, sizeof(new_notif->actions));
notif = new_notif;
}
}
if (stdout_fds[0] >= 0) {
fdm_add(term->fdm, stdout_fds[0], EPOLLIN,
&fdm_notify_stdout, (void *)term);
}
/* Redirect stdin to /dev/null, but ignore failure to open */
int devnull = open("/dev/null", O_RDONLY);
spawn(term->reaper, NULL, argv, devnull, -1, -1);
pid_t pid = spawn(
term->reaper, NULL, argv, devnull, stdout_fds[1], -1,
track_notification ? &notif_done : NULL, (void *)term, NULL);
if (stdout_fds[1] >= 0) {
/* Close write-end of stdout pipe */
close(stdout_fds[1]);
}
if (pid < 0 && stdout_fds[0] >= 0) {
/* Remove FDM callback if we failed to spawn */
fdm_del(term->fdm, stdout_fds[0]);
}
if (devnull >= 0)
close(devnull);
@ -56,4 +589,177 @@ notify_notify(const struct terminal *term, const char *title, const char *body)
for (size_t i = 0; i < argc; i++)
free(argv[i]);
free(argv);
for (size_t i = 0; i < action_argc; i++)
free(action_argv[i]);
free(action_argv);
notif->pid = pid;
notif->stdout_fd = stdout_fds[0];
return true;
}
void
notify_close(struct terminal *term, const char *id)
{
xassert(id != NULL);
LOG_DBG("close notification %s", id);
tll_foreach(term->active_notifications, it) {
const struct notification *notif = &it->item;
if (notif->id == NULL || !streq(notif->id, id))
continue;
if (term->conf->desktop_notifications.close.argv.args == NULL) {
LOG_DBG(
"trying to close notification \"%s\" by sending SIGINT to %u",
id, notif->pid);
if (notif->pid == 0) {
LOG_WARN(
"cannot close notification \"%s\": no helper process running",
id);
} else {
/* Best-effort... */
kill(notif->pid, SIGINT);
}
} else {
LOG_DBG(
"trying to close notification \"%s\" "
"by running user defined command", id);
if (notif->external_id == 0) {
LOG_WARN("cannot close notification \"%s\": "
"no daemon assigned notification ID available", id);
return;
}
char **argv = NULL;
size_t argc = 0;
char external_id[16];
xsnprintf(external_id, sizeof(external_id), "%u", notif->external_id);
if (!spawn_expand_template(
&term->conf->desktop_notifications.close, 1,
(const char *[]){"id"},
(const char *[]){external_id},
&argc, &argv))
{
return;
}
int devnull = open("/dev/null", O_RDONLY);
spawn(
term->reaper, NULL, argv, devnull, -1, -1,
NULL, (void *)term, NULL);
if (devnull >= 0)
close(devnull);
for (size_t i = 0; i < argc; i++)
free(argv[i]);
free(argv);
}
return;
}
LOG_WARN("cannot close notification \"%s\": no such notification", id);
}
static void
add_icon(struct notification_icon *icon, const char *id, const char *symbolic_name,
const uint8_t *data, size_t data_sz)
{
icon->id = xstrdup(id);
icon->symbolic_name = symbolic_name != NULL ? xstrdup(symbolic_name) : NULL;
icon->tmp_file_name = NULL;
icon->tmp_file_fd = -1;
/*
* Dump in-line data to a temporary file. This allows us to pass
* the filename as a parameter to notification helpers
* (i.e. notify-send -i <path>).
*
* Optimization: since we always prefer (i.e. use) the symbolic
* name if present, there's no need to create a file on disk if we
* have a symbolic name.
*/
if (symbolic_name == NULL && data_sz > 0) {
write_icon_file(
data, data_sz,
&icon->tmp_file_fd,
&icon->tmp_file_name,
&icon->symbolic_name);
}
LOG_DBG("added icon to cache: ID=%s: sym=%s, file=%s",
icon->id, icon->symbolic_name, icon->tmp_file_name);
}
void
notify_icon_add(struct terminal *term, const char *id,
const char *symbolic_name, const uint8_t *data, size_t data_sz)
{
#if defined(_DEBUG)
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
struct notification_icon *icon = &term->notification_icons[i];
if (icon->id != NULL && streq(icon->id, id)) {
BUG("notification icon cache already contains \"%s\"", id);
}
}
#endif
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
struct notification_icon *icon = &term->notification_icons[i];
if (icon->id == NULL) {
add_icon(icon, id, symbolic_name, data, data_sz);
return;
}
}
/* Cache full - throw out first entry, add new entry last */
notify_icon_free(&term->notification_icons[0]);
memmove(&term->notification_icons[0],
&term->notification_icons[1],
((ALEN(term->notification_icons) - 1) *
sizeof(term->notification_icons[0])));
add_icon(
&term->notification_icons[ALEN(term->notification_icons) - 1],
id, symbolic_name, data, data_sz);
}
void
notify_icon_del(struct terminal *term, const char *id)
{
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
struct notification_icon *icon = &term->notification_icons[i];
if (icon->id == NULL || !streq(icon->id, id))
continue;
LOG_DBG("expelled %s from the notification icon cache", icon->id);
notify_icon_free(icon);
return;
}
}
void
notify_icon_free(struct notification_icon *icon)
{
if (icon->tmp_file_name != NULL) {
unlink(icon->tmp_file_name);
if (icon->tmp_file_fd >= 0)
close(icon->tmp_file_fd);
}
free(icon->id);
free(icon->symbolic_name);
free(icon->tmp_file_name);
icon->id = NULL;
icon->symbolic_name = NULL;
icon->tmp_file_name = NULL;
icon->tmp_file_fd = -1;
}

View file

@ -1,6 +1,95 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include "terminal.h"
#include <tllist.h>
void notify_notify(
const struct terminal *term, const char *title, const char *body);
struct terminal;
enum notify_when {
/* First, so that it can be left out of initializer and still be
the default */
NOTIFY_ALWAYS,
NOTIFY_UNFOCUSED,
NOTIFY_INVISIBLE
};
enum notify_urgency {
/* First, so that it can be left out of initializer and still be
the default */
NOTIFY_URGENCY_NORMAL,
NOTIFY_URGENCY_LOW,
NOTIFY_URGENCY_CRITICAL,
};
struct notification {
/*
* Set by caller of notify_notify()
*/
char *id; /* Internal notification ID */
char *app_id; /* Custom app-id, overrides the terminal's app-id if set */
char *title; /* Required */
char *body;
char *category;
enum notify_when when;
enum notify_urgency urgency;
int32_t expire_time;
tll(char *) actions;
char *icon_cache_id;
char *icon_symbolic_name;
uint8_t *icon_data;
size_t icon_data_sz;
bool focus; /* Focus the foot window when notification is activated */
bool may_be_programatically_closed; /* OSC-99: notification may be programmatically closed by the client */
bool report_activated; /* OSC-99: report notification activation to client */
bool report_closed; /* OSC-99: report notification closed to client */
bool muted; /* Explicitly mute the notification */
char *sound_name; /* Should be set to NULL if muted == true */
/*
* Used internally by notify
*/
uint32_t external_id; /* Daemon assigned notification ID */
bool activated; /* User 'activated' the notification */
uint32_t button_count; /* Number of buttons (custom actions) in notification */
uint32_t activated_button; /* User activated one of the custom actions */
char *xdg_token; /* XDG activation token, from daemon */
pid_t pid; /* Notifier command PID */
int stdout_fd; /* Notifier command's stdout */
char *stdout_data; /* Data we've reado from command's stdout */
size_t stdout_sz;
/* Used when notification provides raw icon data, and it's
bypassing the icon cache */
char *icon_path;
int icon_fd;
};
struct notification_icon {
char *id;
char *symbolic_name;
char *tmp_file_name;
int tmp_file_fd;
};
bool notify_notify(struct terminal *term, struct notification *notif);
void notify_close(struct terminal *term, const char *id);
void notify_free(struct terminal *term, struct notification *notif);
void notify_icon_add(struct terminal *term, const char *id,
const char *symbolic_name, const uint8_t *data,
size_t data_sz);
void notify_icon_del(struct terminal *term, const char *id);
void notify_icon_free(struct notification_icon *icon);

1083
osc.c

File diff suppressed because it is too large Load diff

View file

@ -10,5 +10,5 @@ trap "rm -rf '${runtime_dir}'" EXIT INT HUP TERM
XDG_RUNTIME_DIR="${runtime_dir}" WLR_RENDERER=pixman WLR_BACKENDS=headless cage "${srcdir}"/pgo/full-inner.sh "${srcdir}" "${blddir}"
# Cages exit code doesnt reflect our scripts exit code
# Cage's exit code doesn't reflect our script's exit code
[ -f "${blddir}"/pgo-ok ] || exit 1

View file

@ -17,8 +17,8 @@ trap cleanup EXIT INT HUP TERM
# Generate a custom config that executes our generate-pgo-data script
> "${sway_conf}" echo "exec '${srcdir}'/pgo/full-headless-sway-inner.sh '${srcdir}' '${blddir}'"
# Run Sway. full-headless-sway-inner.sh ends with a swaymsg exit
XDG_RUNTIME_DIR="${runtime_dir}" WLR_RENDERER=pixman WLR_BACKENDS=headless sway -c "${sway_conf}"
# Run Sway. full-headless-sway-inner.sh ends with a 'swaymsg exit'
XDG_RUNTIME_DIR="${runtime_dir}" WLR_RENDERER=pixman WLR_BACKENDS=headless sway -c "${sway_conf}" --unsupported-gpu
# Sways exit code doesnt reflect our scripts exit code
# Sway's exit code doesn't reflect our script's exit code
[ -f "${blddir}"/pgo-ok ] || exit 1

View file

@ -15,6 +15,7 @@ rm -f "${blddir}"/pgo-ok
# To ensure profiling data is generated in the build directory
cd "${blddir}"
"${blddir}"/utils/xtgettcap
"${blddir}"/footclient --version
"${blddir}"/foot \
--config=/dev/null \

View file

@ -21,6 +21,7 @@ rm -f "${blddir}"/pgo-ok
# To ensure profiling data is generated in the build directory
cd "${blddir}"
"${blddir}"/utils/xtgettcap
"${blddir}"/footclient --version
"${blddir}"/foot --version
"${blddir}"/pgo "${pgo_data}"

118
pgo/pgo.c
View file

@ -60,7 +60,8 @@ fdm_event_del(struct fdm *fdm, int fd, int events)
}
bool
render_resize_force(struct terminal *term, int width, int height)
render_resize(
struct terminal *term, int width, int height, uint8_t resize_options)
{
return true;
}
@ -68,6 +69,12 @@ render_resize_force(struct terminal *term, int width, int height)
void render_refresh(struct terminal *term) {}
void render_refresh_csd(struct terminal *term) {}
void render_refresh_title(struct terminal *term) {}
void render_refresh_app_id(struct terminal *term) {}
void render_refresh_icon(struct terminal *term) {}
void render_overlay(struct terminal *term) {}
void render_buffer_release_callback(struct buffer *buf, void *data) {}
bool
render_xcursor_is_valid(const struct seat *seat, const char *cursor)
@ -76,15 +83,15 @@ render_xcursor_is_valid(const struct seat *seat, const char *cursor)
}
bool
render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor)
render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape shape)
{
return true;
}
const char *
enum cursor_shape
xcursor_for_csd_border(struct terminal *term, int x, int y)
{
return XCURSOR_LEFT_PTR;
return CURSOR_SHAPE_LEFT_PTR;
}
struct wl_window *
@ -94,19 +101,24 @@ wayl_win_init(struct terminal *term, const char *token)
}
void wayl_win_destroy(struct wl_window *win) {}
void wayl_win_alpha_changed(struct wl_window *win) {}
bool wayl_win_set_urgent(struct wl_window *win) { return true; }
bool wayl_win_ring_bell(const struct wl_window *win) { return true; }
bool wayl_fractional_scaling(const struct wayland *wayl) { return true; }
bool
pid_t
spawn(struct reaper *reaper, const char *cwd, char *const argv[],
int stdin_fd, int stdout_fd, int stderr_fd)
int stdin_fd, int stdout_fd, int stderr_fd,
reaper_cb cb, void *cb_data, const char *xdg_activation_token)
{
return true;
return 2;
}
pid_t
slave_spawn(
int ptmx, int argc, const char *cwd, char *const *argv, char *const *envp,
const char *term_env, const char *conf_shell, bool login_shell,
const env_var_list_t *extra_env_vars, const char *term_env,
const char *conf_shell, bool login_shell,
const user_notifications_t *notifications)
{
return 0;
@ -118,6 +130,12 @@ render_worker_thread(void *_ctx)
return 0;
}
bool
wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf)
{
return false;
}
struct extraction_context *
extract_begin(enum selection_kind kind, bool strip_trailing_empty)
{
@ -145,8 +163,36 @@ void ime_enable(struct seat *seat) {}
void ime_disable(struct seat *seat) {}
void ime_reset_preedit(struct seat *seat) {}
bool
notify_notify(struct terminal *term, struct notification *notif)
{
return true;
}
void
notify_notify(const struct terminal *term, const char *title, const char *body)
notify_close(struct terminal *term, const char *id)
{
}
void
notify_free(struct terminal *term, struct notification *notif)
{
}
void
notify_icon_add(struct terminal *term, const char *id,
const char *symbolic_name, const uint8_t *data,
size_t data_sz)
{
}
void
notify_icon_del(struct terminal *term, const char *id)
{
}
void
notify_icon_free(struct notification_icon *icon)
{
}
@ -157,9 +203,13 @@ void urls_reset(struct terminal *term) {}
void shm_unref(struct buffer *buf) {}
void shm_chain_free(struct buffer_chain *chain) {}
enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain) { return SHM_BITS_8; }
struct buffer_chain *
shm_chain_new(struct wl_shm *shm, bool scrollable, size_t pix_instances)
shm_chain_new(
struct wayland *wayl, bool scrollable, size_t pix_instances,
enum shm_bit_depth desired_bit_depth,
void (*release_cb)(struct buffer *buf, void *data), void *cb_data)
{
return NULL;
}
@ -169,15 +219,24 @@ void search_selection_cancelled(struct terminal *term) {}
void get_current_modifiers(const struct seat *seat,
xkb_mod_mask_t *effective,
xkb_mod_mask_t *consumed, uint32_t key) {}
xkb_mod_mask_t *consumed, uint32_t key,
bool filter_locked) {}
static struct key_binding_set kbd;
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_conf(
struct key_binding_manager *mgr, const struct wayland *wayl,
const struct config *conf)
{
if (!kbd_initialized) {
kbd_initialized = true;
@ -189,8 +248,11 @@ key_binding_for(
.selection_overrides = 0,
};
}
}
return &kbd;
void
key_binding_unref(struct key_binding_manager *mgr, const struct config *conf)
{
}
int
@ -215,10 +277,14 @@ main(int argc, const char *const *argv)
return EXIT_FAILURE;
}
struct row **rows = calloc(grid_row_count, sizeof(rows[0]));
struct row **normal_rows = calloc(grid_row_count, sizeof(normal_rows[0]));
struct row **alt_rows = calloc(grid_row_count, sizeof(alt_rows[0]));
for (int i = 0; i < grid_row_count; i++) {
rows[i] = calloc(1, sizeof(*rows[i]));
rows[i]->cells = calloc(col_count, sizeof(rows[i]->cells[0]));
normal_rows[i] = calloc(1, sizeof(*normal_rows[i]));
normal_rows[i]->cells = calloc(col_count, sizeof(normal_rows[i]->cells[0]));
alt_rows[i] = calloc(1, sizeof(*alt_rows[i]));
alt_rows[i]->cells = calloc(col_count, sizeof(alt_rows[i]->cells[0]));
}
struct config conf = {
@ -241,14 +307,14 @@ main(int argc, const char *const *argv)
.normal = {
.num_rows = grid_row_count,
.num_cols = col_count,
.rows = rows,
.cur_row = rows[0],
.rows = normal_rows,
.cur_row = normal_rows[0],
},
.alt = {
.num_rows = grid_row_count,
.num_cols = col_count,
.rows = rows,
.cur_row = rows[0],
.rows = alt_rows,
.cur_row = alt_rows[0],
},
.scale = 1,
.width = col_count * 8,
@ -358,11 +424,17 @@ out:
tll_free(wayl.terms);
for (int i = 0; i < grid_row_count; i++) {
free(rows[i]->cells);
free(rows[i]);
if (normal_rows[i] != NULL)
free(normal_rows[i]->cells);
free(normal_rows[i]);
if (alt_rows[i] != NULL)
free(alt_rows[i]->cells);
free(alt_rows[i]);
}
free(rows);
free(normal_rows);
free(alt_rows);
close(lower_fd);
close(upper_fd);
return ret;

View file

@ -30,7 +30,7 @@ do_pgo=no
CFLAGS="${CFLAGS-} -O3"
case $(${CC-cc} --version) in
*GCC*)
*Free\ Software\ Foundation*)
compiler=gcc
do_pgo=yes
;;
@ -54,13 +54,10 @@ case ${mode} in
;;
auto)
# TODO: once Sway 1.6.2 has been released, prefer
# full-headless-sway
if [ -n "${WAYLAND_DISPLAY+x}" ]; then
mode=full-current-session
# elif command -v sway > /dev/null; then # Requires 1.6.2
# mode=full-headless-sway
elif command -v sway > /dev/null; then
mode=full-headless-sway
elif command -v cage > /dev/null; then
mode=full-headless-cage
else
@ -82,6 +79,7 @@ set -x
# echo "CFLAGS: ${CFLAGS}"
export CFLAGS
export CCACHE_DISABLE=1
meson setup --buildtype=release -Db_lto=true "${@}" "${blddir}" "${srcdir}"
if [ ${do_pgo} = yes ]; then
@ -97,11 +95,12 @@ if [ ${do_pgo} = yes ]; then
ninja -C "${blddir}"
# If fcft/tllist are subprojects, we need to ensure their tests
# have been executed, or well get “profile count data file not
# found errors.
# have been executed, or we'll get "profile count data file not
# found" errors.
ninja -C "${blddir}" test
# Run mode-dependent script to generate profiling data
export LLVM_PROFILE_FILE="${blddir}/default_%m.profraw"
"${srcdir}"/pgo/${mode}.sh "${srcdir}" "${blddir}"
if [ ${compiler} = clang ]; then

10
pyproject.toml Normal file
View file

@ -0,0 +1,10 @@
[tool.pyright]
strict = ['scripts']
[tool.mypy]
files = '$MYPY_CONFIG_FILE_DIR/scripts'
strict = true
[tool.codespell]
skip = 'pyproject.toml,./subprojects,./pkg,./src,./bld,foot.info,./unicode,./venv'
ignore-regex = 'terminfo capability `rin`|\* Simon Ser|\* \[zar\]\(https://codeberg.org/zar\)|iterm theme|iterm.toml|iterm/OneHalfDark.itermcolors'

View file

@ -66,3 +66,21 @@ quirk_weston_csd_off(struct terminal *term)
for (int i = 0; i < ALEN(term->window->csd.surface); i++)
quirk_weston_subsurface_desync_off(term->window->csd.surface[i].sub);
}
#if 0
static bool
is_sway(void)
{
static bool is_sway = false;
static bool initialized = false;
if (!initialized) {
initialized = true;
is_sway = getenv("SWAYSOCK") != NULL;
if (is_sway)
LOG_WARN("applying wl_surface_damage_buffer() workaround for Sway");
}
return is_sway;
}
#endif

2852
render.c

File diff suppressed because it is too large Load diff

View file

@ -10,18 +10,29 @@ struct renderer;
struct renderer *render_init(struct fdm *fdm, struct wayland *wayl);
void render_destroy(struct renderer *renderer);
bool render_resize(struct terminal *term, int width, int height);
bool render_resize_force(struct terminal *term, int width, int height);
enum resize_options {
RESIZE_NORMAL = 0,
RESIZE_FORCE = 1 << 0,
RESIZE_BY_CELLS = 1 << 1,
RESIZE_KEEP_GRID = 1 << 2,
};
bool render_resize(
struct terminal *term, int width, int height, uint8_t resize_options);
void render_refresh(struct terminal *term);
void render_refresh_app_id(struct terminal *term);
void render_refresh_icon(struct terminal *term);
void render_refresh_csd(struct terminal *term);
void render_refresh_search(struct terminal *term);
void render_refresh_title(struct terminal *term);
void render_refresh_urls(struct terminal *term);
bool render_xcursor_set(
struct seat *seat, struct terminal *term, const char *xcursor);
struct seat *seat, struct terminal *term, enum cursor_shape shape);
bool render_xcursor_is_valid(const struct seat *seat, const char *cursor);
void render_overlay(struct terminal *term);
struct render_worker_context {
int my_id;
struct terminal *term;
@ -36,3 +47,6 @@ struct csd_data {
};
struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx);
void render_buffer_release_callback(struct buffer *buf, void *data);
void render_wait_for_preapply_damage(struct terminal *term);

View file

@ -11,7 +11,7 @@ import termios
from datetime import datetime
def main():
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('files', type=argparse.FileType('rb'), nargs='+')
parser.add_argument('--iterations', type=int, default=20)
@ -24,12 +24,12 @@ def main():
termios.TIOCGWINSZ,
struct.pack('HHHH', 0, 0, 0, 0)))
times = {name: [] for name in [f.name for f in args.files]}
times: dict[str, list[float]] = {name: [] for name in [f.name for f in args.files]}
for f in args.files:
bench_bytes = f.read()
for i in range(args.iterations):
for _ in range(args.iterations):
start = datetime.now()
sys.stdout.buffer.write(bench_bytes)
stop = datetime.now()
@ -48,4 +48,4 @@ def main():
if __name__ == '__main__':
sys.exit(main())
main()

View file

@ -8,6 +8,8 @@ import struct
import sys
import termios
from typing import Any
class ColorVariant(enum.IntEnum):
NONE = enum.auto()
@ -17,7 +19,7 @@ class ColorVariant(enum.IntEnum):
RGB = enum.auto()
def main():
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
'out', type=argparse.FileType(mode='w'), nargs='?', help='name of output file')
@ -38,10 +40,16 @@ def main():
opts = parser.parse_args()
out = opts.out if opts.out is not None else sys.stdout
lines: int | None = None
cols: int | None = None
width: int | None = None
height: int | None = None
if opts.rows is None or opts.cols is None:
try:
def dummy(*args):
def dummy(*args: Any) -> None:
"""Need a handler installed for sigwait() to trigger."""
_ = args
pass
signal.signal(signal.SIGWINCH, dummy)
@ -53,6 +61,9 @@ def main():
termios.TIOCGWINSZ,
struct.pack('HHHH', 0, 0, 0, 0)))
assert width is not None
assert height is not None
if width > 0 and height > 0:
break
@ -71,9 +82,11 @@ def main():
if opts.rows is not None:
lines = opts.rows
assert lines is not None
height = 15 * lines # PGO helper binary hardcodes cell height to 15px
if opts.cols is not None:
cols = opts.cols
assert cols is not None
width = 8 * cols # PGO help binary hardcodes cell width to 8px
if lines is None or cols is None or height is None or width is None:
@ -190,8 +203,8 @@ def main():
# The sixel 'alphabet'
sixels = '?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
last_pos = None
last_size = None
last_pos: tuple[int, int] | None = None
last_size: tuple[int, int] = 0, 0
for _ in range(20):
if last_pos is not None and random.randrange(2):
@ -207,8 +220,9 @@ def main():
six_height, six_width = last_size
six_rows = (six_height + 5) // 6 # Round up; each sixel is 6 pixels
# Begin sixel
out.write('\033Pq')
# Begin sixel (with P2 set to either 0 or 1 - opaque or transparent)
sixel_p2 = random.randrange(2)
out.write(f'\033P;{sixel_p2}q')
# Sixel size. Without this, sixels will be
# auto-resized on cell-boundaries.
@ -253,4 +267,4 @@ def main():
if __name__ == '__main__':
sys.exit(main())
main()

View file

@ -1,14 +1,12 @@
#!/usr/bin/env python3
import argparse
import os
import re
import sys
from typing import Dict, Union
class Capability:
def __init__(self, name: str, value: Union[bool, int, str]):
def __init__(self, name: str, value: bool | int | str):
self._name = name
self._value = value
@ -17,25 +15,37 @@ class Capability:
return self._name
@property
def value(self) -> Union[bool, int, str]:
def value(self) -> bool | int | str:
return self._value
def __lt__(self, other):
def __lt__(self, other: object) -> bool:
if not isinstance(other, Capability):
return NotImplemented
return self._name < other._name
def __le__(self, other):
def __le__(self, other: object) -> bool:
if not isinstance(other, Capability):
return NotImplemented
return self._name <= other._name
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if not isinstance(other, Capability):
return NotImplemented
return self._name == other._name
def __ne__(self, other):
return self._name != other._name
def __ne__(self, other: object) -> bool:
if not isinstance(other, Capability):
return NotImplemented
return bool(self._name != other._name)
def __gt__(self, other):
return self._name > other._name
def __gt__(self, other: object) -> bool:
if not isinstance(other, Capability):
return NotImplemented
return bool(self._name > other._name)
def __ge__(self, other):
def __ge__(self, other: object) -> bool:
if not isinstance(other, Capability):
return NotImplemented
return self._name >= other._name
@ -50,16 +60,33 @@ class IntCapability(Capability):
class StringCapability(Capability):
def __init__(self, name: str, value: str):
# Expand \E to literal ESC in non-parameterized capabilities
if '%' not in value:
value = re.sub(r'\\E([0-7])', r'\\033" "\1', value)
value = re.sub(r'\\E', r'\\033', value)
else:
# Need to double-escape \E in C string literals
value = value.replace('\\E', '\\\\E')
# see terminfo(5) for valid escape sequences
# Dont escape :
value = value.replace('\\:', ':')
# Control characters
def translate_ctrl_chr(m: re.Match[str]) -> str:
ctrl = m.group(1)
if ctrl == '?':
return '\\x7f'
return f'\\x{ord(ctrl) - ord("@"):02x}'
value = re.sub(r'\^([@A-Z[\\\\\]^_?])', translate_ctrl_chr, value)
# Ensure e.g. \E7 (or \e7) doesnt get translated to “\0337”,
# which would be interpreted as octal 337 by the C compiler
value = re.sub(r'(\\E|\\e)([0-7])', r'\\033" "\2', value)
# Replace \E and \e with ESC
value = re.sub(r'\\E|\\e', r'\\033', value)
# Unescape ,:^
value = re.sub(r'\\(,|:|\^)', r'\1', value)
# Replace \s with space
value = value.replace('\\s', ' ')
# Let \\, \n, \r, \t, \b and \f "fall through", to the C string literal
if re.search(r'\\l', value):
raise NotImplementedError('\\l escape sequence')
super().__init__(name, value)
@ -68,7 +95,7 @@ class Fragment:
def __init__(self, name: str, description: str):
self._name = name
self._description = description
self._caps = {}
self._caps = dict[str, Capability]()
@property
def name(self) -> str:
@ -79,18 +106,18 @@ class Fragment:
return self._description
@property
def caps(self) -> Dict[str, Capability]:
def caps(self) -> dict[str, Capability]:
return self._caps
def add_capability(self, cap: Capability):
def add_capability(self, cap: Capability) -> None:
assert cap.name not in self._caps
self._caps[cap.name] = cap
def del_capability(self, name: str):
def del_capability(self, name: str) -> None:
del self._caps[name]
def main():
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('source_entry_name')
parser.add_argument('source', type=argparse.FileType('r'))
@ -103,15 +130,15 @@ def main():
source = opts.source
target = opts.target
lines = []
for l in source.readlines():
l = l.strip()
if l.startswith('#'):
lines = list[str]()
for line in source.readlines():
line = line.strip()
if line.startswith('#'):
continue
lines.append(l)
lines.append(line)
fragments = {}
cur_fragment = None
fragments = dict[str, Fragment]()
cur_fragment: Fragment | None = None
for m in re.finditer(
r'(?P<name>(?P<entry_name>[-+\w@]+)\|(?P<entry_desc>.+?),)|'
@ -130,17 +157,20 @@ def main():
elif m.group('bool_cap') is not None:
name = m.group('bool_name')
assert cur_fragment is not None
cur_fragment.add_capability(BoolCapability(name))
elif m.group('int_cap') is not None:
name = m.group('int_name')
value = int(m.group('int_val'), 0)
cur_fragment.add_capability(IntCapability(name, value))
int_value = int(m.group('int_val'), 0)
assert cur_fragment is not None
cur_fragment.add_capability(IntCapability(name, int_value))
elif m.group('str_cap') is not None:
name = m.group('str_name')
value = m.group('str_val')
cur_fragment.add_capability(StringCapability(name, value))
str_value = m.group('str_val')
assert cur_fragment is not None
cur_fragment.add_capability(StringCapability(name, str_value))
else:
assert False
@ -149,6 +179,9 @@ def main():
for frag in fragments.values():
for cap in frag.caps.values():
if cap.name == 'use':
assert isinstance(cap, StringCapability)
assert isinstance(cap.value, str)
use_frag = fragments[cap.value]
for use_cap in use_frag.caps.values():
frag.add_capability(use_cap)
@ -166,9 +199,11 @@ 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
entry.add_capability(StringCapability('query-os-name', os.uname().sysname))
terminfo_parts = []
terminfo_parts = list[str]()
for cap in sorted(entry.caps.values()):
name = cap.name
value = str(cap.value)
@ -192,4 +227,4 @@ def main():
if __name__ == '__main__':
sys.exit(main())
main()

View file

@ -0,0 +1,102 @@
#!/usr/bin/env python3
import argparse
class Codepoint:
def __init__(self, start: int, end: None | int = None):
self.start = start
self.end = start if end is None else end
self.vs15 = False
self.vs16 = False
def __repr__(self) -> str:
return f'{self.start:x}-{self.end:x}, vs15={self.vs15}, vs16={self.vs16}'
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('input', type=argparse.FileType('r'))
parser.add_argument('output', type=argparse.FileType('w'))
opts = parser.parse_args()
codepoints: dict[int, Codepoint] = {}
for line in opts.input:
line = line.rstrip()
if not line:
continue
if line[0] == '#':
continue
# Example: "0023 FE0E ; text style; # (1.1) NUMBER SIGN"
cps, _ = line.split(';', maxsplit=1) # cps = "0023 FE0F "
cps = cps.strip().split(' ') # cps = ["0023", "FE0F"]
if len(cps) != 2:
raise NotImplementedError(f'emoji variation sequences with more than one base codepoint: {cps}')
cp, vs = cps # cp = "0023", vs = "FE0F"
cp = int(cp, 16) # cp = 0x23
vs = int(vs, 16) # vs = 0xfe0f
assert vs in [0xfe0e, 0xfe0f]
if cp not in codepoints:
codepoints[cp] = Codepoint(cp)
assert codepoints[cp].start == cp
if vs == 0xfe0e:
codepoints[cp].vs15 = True
else:
codepoints[cp].vs16 = True
sorted_list = sorted(codepoints.values(), key=lambda cp: cp.start)
compacted: list[Codepoint] = []
for i, cp in enumerate(sorted_list):
assert cp.end == cp.start
if i == 0:
compacted.append(cp)
continue
last_cp = compacted[-1]
if last_cp.end == cp.start - 1 and last_cp.vs15 == cp.vs15 and last_cp.vs16 == cp.vs16:
compacted[-1].end = cp.start
else:
compacted.append(cp)
opts.output.write('#pragma once\n')
opts.output.write('#include <stdint.h>\n')
opts.output.write('#include <stdbool.h>\n')
opts.output.write('\n')
opts.output.write('struct emoji_vs {\n')
opts.output.write(' uint32_t start:21;\n')
opts.output.write(' uint32_t end:21;\n')
opts.output.write(' bool vs15:1;\n')
opts.output.write(' bool vs16:1;\n')
opts.output.write('} __attribute__((packed));\n')
opts.output.write('_Static_assert(sizeof(struct emoji_vs) == 6, "unexpected struct size");\n')
opts.output.write('\n')
opts.output.write('#if defined(FOOT_GRAPHEME_CLUSTERING)\n')
opts.output.write('\n')
opts.output.write(f'static const struct emoji_vs emoji_vs[{len(compacted)}] = {{\n')
for cp in compacted:
opts.output.write(' {\n')
opts.output.write(f' .start = 0x{cp.start:X},\n')
opts.output.write(f' .end = 0x{cp.end:x},\n')
opts.output.write(f' .vs15 = {"true" if cp.vs15 else "false"},\n')
opts.output.write(f' .vs16 = {"true" if cp.vs16 else "false"},\n')
opts.output.write(' },\n')
opts.output.write('};\n')
opts.output.write('\n')
opts.output.write('#endif /* FOOT_GRAPHEME_CLUSTERING */\n')
if __name__ == '__main__':
main()

70
scripts/srgb.py Executable file
View file

@ -0,0 +1,70 @@
#!/usr/bin/env python3
import argparse
import math
# Note: we use a pure gamma 2.2 function, rather than the piece-wise
# sRGB transfer function, since that is what all compositors do.
def srgb_to_linear(f: float) -> float:
assert(f >= 0 and f <= 1.0)
return math.pow(f, 2.2)
def linear_to_srgb(f: float) -> float:
return math.pow(f, 1 / 2.2)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('c_output', type=argparse.FileType('w'))
parser.add_argument('h_output', type=argparse.FileType('w'))
opts = parser.parse_args()
linear_table: list[int] = []
for i in range(256):
linear_table.append(int(srgb_to_linear(float(i) / 255) * 65535 + 0.5))
opts.h_output.write("#pragma once\n")
opts.h_output.write("#include <stdint.h>\n")
opts.h_output.write("\n")
opts.h_output.write('/* 8-bit input, 16-bit output */\n')
opts.h_output.write("extern const uint16_t srgb_decode_8_to_16_table[256];")
opts.h_output.write('\n')
opts.h_output.write('static inline uint16_t\n')
opts.h_output.write('srgb_decode_8_to_16(uint8_t v)\n')
opts.h_output.write('{\n')
opts.h_output.write(' return srgb_decode_8_to_16_table[v];\n')
opts.h_output.write('}\n')
opts.h_output.write('\n')
opts.h_output.write('/* 8-bit input, 8-bit output */\n')
opts.h_output.write("extern const uint8_t srgb_decode_8_to_8_table[256];\n")
opts.h_output.write('\n')
opts.h_output.write('static inline uint8_t\n')
opts.h_output.write('srgb_decode_8_to_8(uint8_t v)\n')
opts.h_output.write('{\n')
opts.h_output.write(' return srgb_decode_8_to_8_table[v];\n')
opts.h_output.write('}\n')
opts.c_output.write('#include "srgb.h"\n')
opts.c_output.write('\n')
opts.c_output.write("const uint16_t srgb_decode_8_to_16_table[256] = {\n")
for i in range(256):
opts.c_output.write(f' {linear_table[i]},\n')
opts.c_output.write('};\n')
opts.c_output.write("const uint8_t srgb_decode_8_to_8_table[256] = {\n")
for i in range(256):
opts.c_output.write(f' {linear_table[i] >> 8},\n')
opts.c_output.write('};\n')
if __name__ == '__main__':
main()

747
search.c
View file

@ -9,15 +9,18 @@
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "char32.h"
#include "commands.h"
#include "config.h"
#include "extract.h"
#include "grid.h"
#include "input.h"
#include "key-binding.h"
#include "misc.h"
#include "quirks.h"
#include "render.h"
#include "selection.h"
#include "shm.h"
#include "unicode-mode.h"
#include "util.h"
#include "xmalloc.h"
@ -80,13 +83,16 @@ search_ensure_size(struct terminal *term, size_t wanted_size)
}
static bool
has_wrapped_around(const struct terminal *term, int abs_row_no)
has_wrapped_around_left(const struct terminal *term, int abs_row_no)
{
const struct grid *grid = term->grid;
int scrollback_start = grid->offset + term->rows;
int rebased_row = abs_row_no - scrollback_start + grid->num_rows;
rebased_row &= grid->num_rows - 1;
int rebased_row = grid_row_abs_to_sb(term->grid, term->rows, abs_row_no);
return rebased_row == term->grid->num_rows - 1 || term->grid->rows[abs_row_no] == NULL;
}
static bool
has_wrapped_around_right(const struct terminal *term, int abs_row_no)
{
int rebased_row = grid_row_abs_to_sb(term->grid, term->rows, abs_row_no);
return rebased_row == 0;
}
@ -182,6 +188,9 @@ search_update_selection(struct terminal *term, const struct range *match)
int end_row = match->end.row;
int end_col = match->end.col;
xassert(start_row >= 0);
xassert(start_row < grid->num_rows);
bool move_viewport = true;
int view_end = (grid->view + term->rows - 1) & (grid->num_rows - 1);
@ -196,23 +205,20 @@ search_update_selection(struct terminal *term, const struct range *match)
}
if (move_viewport) {
int old_view = grid->view;
int new_view = start_row - term->rows / 2;
int rebased_new_view = grid_row_abs_to_sb(grid, term->rows, start_row);
while (new_view < 0)
new_view += grid->num_rows;
rebased_new_view -= term->rows / 2;
rebased_new_view =
min(max(rebased_new_view, 0), grid->num_rows - term->rows);
new_view = ensure_view_is_allocated(term, new_view);
const int old_view = grid->view;
int new_view = grid_row_sb_to_abs(grid, term->rows, rebased_new_view);
/* Don't scroll past scrollback history */
int end = (grid->offset + term->rows - 1) & (grid->num_rows - 1);
if (end >= grid->offset) {
/* Not wrapped */
if (new_view >= grid->offset && new_view <= end)
new_view = grid->offset;
} else {
if (new_view >= grid->offset || new_view <= end)
new_view = grid->offset;
/* Scrollback may not be completely filled yet */
{
const int mask = grid->num_rows - 1;
while (grid->rows[new_view] == NULL)
new_view = (new_view + 1) & mask;
}
#if defined(_DEBUG)
@ -221,35 +227,35 @@ search_update_selection(struct terminal *term, const struct range *match)
xassert(grid->rows[(new_view + r) & (grid->num_rows - 1)] != NULL);
#endif
#if defined(_DEBUG)
{
int rel_start_row = grid_row_abs_to_sb(grid, term->rows, start_row);
int rel_view = grid_row_abs_to_sb(grid, term->rows, new_view);
xassert(rel_view <= rel_start_row);
xassert(rel_start_row < rel_view + term->rows);
}
#endif
/* Update view */
grid->view = new_view;
if (new_view != old_view)
term_damage_view(term);
}
#if 0
/* Selection endpoint is inclusive */
if (--end_col < 0) {
end_col = term->cols - 1;
end_row--;
}
#endif
/*
* Begin a new selection if the start coords changed
*
* Note: check column against selection.coords, since our old
* start column isnt reliable - we modify it when searching
* next or prev.
*/
if (start_row != term->search.match.row ||
start_col != term->selection.coords.start.col)
start_col != term->search.match.col ||
/* Pointer leave events trigger selection_finalize() :/ */
!term->selection.ongoing)
{
int selection_row = start_row - grid->view + grid->num_rows;
selection_row &= grid->num_rows - 1;
selection_start(
term, start_col, selection_row, SELECTION_CHAR_WISE, false);
term->search.match.row = start_row;
term->search.match.col = start_col;
}
/* Update selection endpoint */
@ -277,20 +283,25 @@ matches_cell(const struct terminal *term, const struct cell *cell, size_t search
if (composed == NULL && base == 0 && term->search.buf[search_ofs] == U' ')
return 1;
if (c32ncasecmp(&base, &term->search.buf[search_ofs], 1) != 0)
return -1;
if (hasc32upper(term->search.buf)) {
if (c32ncmp(&base, &term->search.buf[search_ofs], 1) != 0)
return -1;
} else {
if (c32ncasecmp(&base, &term->search.buf[search_ofs], 1) != 0)
return -1;
}
if (composed != NULL) {
if (search_ofs + 1 + composed->count > term->search.len)
if (search_ofs + composed->count > term->search.len)
return -1;
for (size_t j = 1; j < composed->count; j++) {
if (composed->chars[j] != term->search.buf[search_ofs + 1 + j])
if (composed->chars[j] != term->search.buf[search_ofs + j])
return -1;
}
}
return composed != NULL ? 1 + composed->count : 1;
return composed != NULL ? composed->count : 1;
}
static bool
@ -376,10 +387,23 @@ find_next(struct terminal *term, enum search_direction direction,
i += additional_chars;
match_len += additional_chars;
match_end_col++;
while (match_end_col < term->cols &&
match_row->cells[match_end_col].wc > CELL_SPACER)
{
match_end_col++;
}
}
if (match_len != term->search.len) {
/* Didn't match (completely) */
if (match_start_row == abs_end.row &&
match_start_col == abs_end.col)
{
break;
}
continue;
}
@ -467,7 +491,8 @@ search_find_next(struct terminal *term, enum search_direction direction)
LOG_DBG(
"update: %s: starting at row=%d col=%d "
"(offset = %d, view = %d)",
backward ? "backward" : "forward", start.row, start.col,
direction != SEARCH_FORWARD ? "backward" : "forward",
start.row, start.col,
grid->offset, grid->view);
struct coord end = start;
@ -515,7 +540,7 @@ search_matches_new_iter(struct terminal *term)
{
return (struct search_match_iterator){
.term = term,
.start = {-2, -2},
.start = {0, 0},
};
}
@ -528,62 +553,65 @@ search_matches_next(struct search_match_iterator *iter)
if (term->search.match_len == 0)
goto no_match;
if (iter->start.row >= term->rows)
goto no_match;
xassert(iter->start.row >= 0);
xassert(iter->start.row < term->rows);
xassert(iter->start.col >= 0);
xassert(iter->start.col < term->cols);
struct coord abs_start = iter->start;
abs_start.row = grid_row_absolute_in_view(grid, abs_start.row);
struct coord abs_end = {
term->cols - 1,
grid_row_absolute_in_view(grid, term->rows - 1)};
/* BUG: matches *starting* outside the view, but ending *inside*, aren't matched */
struct range match;
bool found;
bool found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match);
if (!found)
goto no_match;
const bool return_primary_match =
iter->start.row == -2 && term->selection.coords.end.row >= 0;
LOG_DBG("match at (absolute coordinates) %dx%d-%dx%d",
match.start.row, match.start.col,
match.end.row, match.end.col);
if (return_primary_match) {
/* First, return the primary match */
match = term->selection.coords;
found = true;
} else {
struct coord abs_start = iter->start;
abs_start.row = grid_row_absolute_in_view(grid, abs_start.row);
/* Convert absolute row numbers back to view relative */
match.start.row = match.start.row - grid->view + grid->num_rows;
match.start.row &= grid->num_rows - 1;
match.end.row = match.end.row - grid->view + grid->num_rows;
match.end.row &= grid->num_rows - 1;
struct coord abs_end = {
term->cols - 1,
grid_row_absolute_in_view(grid, term->rows - 1)};
LOG_DBG("match at (view-local coordinates) %dx%d-%dx%d, view=%d",
match.start.row, match.start.col,
match.end.row, match.end.col, grid->view);
found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match);
/* Assert match end comes *after* the match start */
xassert(match.end.row > match.start.row ||
(match.end.row == match.start.row &&
match.end.col >= match.start.col));
/* Assert the match starts at, or after, the iterator position */
xassert(match.start.row > iter->start.row ||
(match.start.row == iter->start.row &&
match.start.col >= iter->start.col));
/* Continue at next column, next time */
iter->start.row = match.start.row;
iter->start.col = match.start.col + 1;
if (iter->start.col >= term->cols) {
iter->start.col = 0;
iter->start.row++; /* Overflow is caught in next iteration */
}
if (found) {
LOG_DBG("match at %dx%d-%dx%d",
match.start.row, match.start.col,
match.end.row, match.end.col);
/* Convert absolute row numbers back to view relative */
match.start.row = match.start.row - grid->view + grid->num_rows;
match.start.row &= grid->num_rows - 1;
match.end.row = match.end.row - grid->view + grid->num_rows;
match.end.row &= grid->num_rows - 1;
if (return_primary_match) {
iter->start.row = 0;
iter->start.col = 0;
} else {
/* Continue at next column, next time */
iter->start.row = match.start.row;
iter->start.col = match.start.col + 1;
if (iter->start.col >= term->cols) {
iter->start.col = 0;
iter->start.row++;
iter->start.row &= grid->num_rows - 1;
}
if (match.start.row == term->search.match.row &&
match.start.col == term->search.match.col)
{
/* Primary match is handled explicitly */
LOG_DBG("primary match: skipping");
return search_matches_next(iter);
}
}
return match;
}
xassert(iter->start.row >= 0);
xassert(iter->start.row <= term->rows);
xassert(iter->start.col >= 0);
xassert(iter->start.col < term->cols);
return match;
no_match:
iter->start.row = -1;
@ -632,60 +660,299 @@ search_add_chars(struct terminal *term, const char *src, size_t count)
add_wchars(term, c32s, chars);
}
enum extend_direction {SEARCH_EXTEND_LEFT, SEARCH_EXTEND_RIGHT};
static bool
coord_advance_left(const struct terminal *term, struct coord *pos,
const struct row **row)
{
const struct grid *grid = term->grid;
struct coord new_pos = *pos;
if (--new_pos.col < 0) {
new_pos.row = (new_pos.row - 1 + grid->num_rows) & (grid->num_rows - 1);
new_pos.col = term->cols - 1;
if (has_wrapped_around_left(term, new_pos.row))
return false;
if (row != NULL)
*row = grid->rows[new_pos.row];
}
*pos = new_pos;
return true;
}
static bool
coord_advance_right(const struct terminal *term, struct coord *pos,
const struct row **row)
{
const struct grid *grid = term->grid;
struct coord new_pos = *pos;
if (++new_pos.col >= term->cols) {
new_pos.row = (new_pos.row + 1) & (grid->num_rows - 1);
new_pos.col = 0;
if (has_wrapped_around_right(term, new_pos.row))
return false;
if (row != NULL)
*row = grid->rows[new_pos.row];
}
*pos = new_pos;
return true;
}
static bool
search_extend_find_char(const struct terminal *term, struct coord *target,
enum extend_direction direction)
{
if (term->search.match_len == 0)
return false;
struct coord pos = direction == SEARCH_EXTEND_LEFT
? selection_get_start(term) : selection_get_end(term);
xassert(pos.row >= 0);
xassert(pos.row < term->grid->num_rows);
*target = pos;
const struct row *row = term->grid->rows[pos.row];
while (true) {
switch (direction) {
case SEARCH_EXTEND_LEFT:
if (!coord_advance_left(term, &pos, &row))
return false;
break;
case SEARCH_EXTEND_RIGHT:
if (!coord_advance_right(term, &pos, &row))
return false;
break;
}
const char32_t wc = row->cells[pos.col].wc;
if (wc >= CELL_SPACER || wc == U'\0')
continue;
*target = pos;
return true;
}
}
static bool
search_extend_find_char_left(const struct terminal *term, struct coord *target)
{
return search_extend_find_char(term, target, SEARCH_EXTEND_LEFT);
}
static bool
search_extend_find_char_right(const struct terminal *term, struct coord *target)
{
return search_extend_find_char(term, target, SEARCH_EXTEND_RIGHT);
}
static bool
search_extend_find_word(const struct terminal *term, bool spaces_only,
struct coord *target, enum extend_direction direction)
{
if (term->search.match_len == 0)
return false;
struct grid *grid = term->grid;
struct coord pos = direction == SEARCH_EXTEND_LEFT
? selection_get_start(term)
: selection_get_end(term);
xassert(pos.row >= 0);
xassert(pos.row < grid->num_rows);
*target = pos;
/* First character to consider is the *next* character */
switch (direction) {
case SEARCH_EXTEND_LEFT:
if (!coord_advance_left(term, &pos, NULL))
return false;
break;
case SEARCH_EXTEND_RIGHT:
if (!coord_advance_right(term, &pos, NULL))
return false;
break;
}
xassert(pos.row >= 0);
xassert(pos.row < grid->num_rows);
xassert(grid->rows[pos.row] != NULL);
/* Find next word boundary */
switch (direction) {
case SEARCH_EXTEND_LEFT:
selection_find_word_boundary_left(term, &pos, spaces_only);
break;
case SEARCH_EXTEND_RIGHT:
selection_find_word_boundary_right(term, &pos, spaces_only, false);
break;
}
*target = pos;
return true;
}
static bool
search_extend_find_word_left(const struct terminal *term, bool spaces_only,
struct coord *target)
{
return search_extend_find_word(term, spaces_only, target, SEARCH_EXTEND_LEFT);
}
static bool
search_extend_find_word_right(const struct terminal *term, bool spaces_only,
struct coord *target)
{
return search_extend_find_word(term, spaces_only, target, SEARCH_EXTEND_RIGHT);
}
static bool
search_extend_find_line(const struct terminal *term, struct coord *target,
enum extend_direction direction)
{
if (term->search.match_len == 0)
return false;
struct coord pos = direction == SEARCH_EXTEND_LEFT
? selection_get_start(term) : selection_get_end(term);
xassert(pos.row >= 0);
xassert(pos.row < term->grid->num_rows);
*target = pos;
const struct grid *grid = term->grid;
switch (direction) {
case SEARCH_EXTEND_LEFT:
pos.row = (pos.row - 1 + grid->num_rows) & (grid->num_rows - 1);
if (has_wrapped_around_left(term, pos.row))
return false;
break;
case SEARCH_EXTEND_RIGHT:
pos.row = (pos.row + 1) & (grid->num_rows - 1);
if (has_wrapped_around_right(term, pos.row))
return false;
break;
}
*target = pos;
return true;
}
static bool
search_extend_find_line_up(const struct terminal *term, struct coord *target)
{
return search_extend_find_line(term, target, SEARCH_EXTEND_LEFT);
}
static bool
search_extend_find_line_down(const struct terminal *term, struct coord *target)
{
return search_extend_find_line(term, target, SEARCH_EXTEND_RIGHT);
}
static void
search_match_to_end_of_word(struct terminal *term, bool spaces_only)
search_extend_left(struct terminal *term, const struct coord *target)
{
if (term->search.match_len == 0)
return;
xassert(term->selection.coords.end.row != -1);
const struct coord last_coord = selection_get_start(term);
struct coord pos = *target;
const struct row *row = term->grid->rows[pos.row];
struct grid *grid = term->grid;
const bool move_cursor = term->search.cursor == term->search.len;
const bool move_cursor = term->search.cursor != 0;
const struct coord old_end = term->selection.coords.end;
struct coord new_end = old_end;
struct row *row = NULL;
/* Advances a coordinate by one column, to the right. Returns
* false if weve reached the scrollback wrap-around */
#define advance_pos(coord) __extension__ \
({ \
bool wrapped_around = false; \
if (++(coord).col >= term->cols) { \
(coord).row = ((coord).row + 1) & (grid->num_rows - 1); \
(coord).col = 0; \
row = grid->rows[(coord).row]; \
if (has_wrapped_around(term, (coord.row))) \
wrapped_around = true; \
} \
!wrapped_around; \
})
/* First character to consider is the *next* character */
if (!advance_pos(new_end))
struct extraction_context *ctx = extract_begin(SELECTION_NONE, false);
if (ctx == NULL)
return;
xassert(grid->rows[new_end.row] != NULL);
while (pos.col != last_coord.col || pos.row != last_coord.row) {
if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx))
break;
if (!coord_advance_right(term, &pos, &row))
break;
}
/* Find next word boundary */
new_end.row -= grid->view;
selection_find_word_boundary_right(term, &new_end, spaces_only);
new_end.row += grid->view;
char32_t *new_text;
size_t new_len;
struct coord pos = old_end;
row = grid->rows[pos.row];
if (!extract_finish_wide(ctx, &new_text, &new_len))
return;
if (!search_ensure_size(term, term->search.len + new_len))
return;
memmove(&term->search.buf[new_len], &term->search.buf[0],
term->search.len * sizeof(term->search.buf[0]));
size_t actually_copied = 0;
for (size_t i = 0; i < new_len; i++) {
if (new_text[i] == U'\n') {
/* extract() adds newlines, which we never match against */
continue;
}
term->search.buf[actually_copied++] = new_text[i];
term->search.len++;
}
xassert(actually_copied <= new_len);
if (actually_copied < new_len) {
memmove(
&term->search.buf[actually_copied], &term->search.buf[new_len],
(term->search.len - actually_copied) * sizeof(term->search.buf[0]));
}
term->search.buf[term->search.len] = U'\0';
free(new_text);
if (move_cursor)
term->search.cursor += actually_copied;
struct range match = {.start = *target, .end = selection_get_end(term)};
search_update_selection(term, &match);
term->search.match_len = term->search.len;
}
static void
search_extend_right(struct terminal *term, const struct coord *target)
{
if (term->search.match_len == 0)
return;
struct coord pos = selection_get_end(term);
const struct row *row = term->grid->rows[pos.row];
const bool move_cursor = term->search.cursor == term->search.len;
struct extraction_context *ctx = extract_begin(SELECTION_NONE, false);
if (ctx == NULL)
return;
do {
if (!advance_pos(pos))
if (!coord_advance_right(term, &pos, &row))
break;
if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx))
break;
} while (pos.col != new_end.col || pos.row != new_end.row);
} while (pos.col != target->col || pos.row != target->row);
char32_t *new_text;
size_t new_len;
@ -711,12 +978,9 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only)
if (move_cursor)
term->search.cursor = term->search.len;
struct range match = {.start = term->search.match, .end = new_end};
struct range match = {.start = term->search.match, .end = *target};
search_update_selection(term, &match);
term->search.match_len = term->search.len;
#undef advance_pos
}
static size_t
@ -805,6 +1069,62 @@ execute_binding(struct seat *seat, struct terminal *term,
case BIND_ACTION_SEARCH_NONE:
return false;
case BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE:
if (term->grid == &term->normal) {
cmd_scrollback_up(term, term->rows);
return true;
}
return false;
case BIND_ACTION_SEARCH_SCROLLBACK_UP_HALF_PAGE:
if (term->grid == &term->normal) {
cmd_scrollback_up(term, max(term->rows / 2, 1));
return true;
}
break;
case BIND_ACTION_SEARCH_SCROLLBACK_UP_LINE:
if (term->grid == &term->normal) {
cmd_scrollback_up(term, 1);
return true;
}
break;
case BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE:
if (term->grid == &term->normal) {
cmd_scrollback_down(term, term->rows);
return true;
}
return false;
case BIND_ACTION_SEARCH_SCROLLBACK_DOWN_HALF_PAGE:
if (term->grid == &term->normal) {
cmd_scrollback_down(term, max(term->rows / 2, 1));
return true;
}
break;
case BIND_ACTION_SEARCH_SCROLLBACK_DOWN_LINE:
if (term->grid == &term->normal) {
cmd_scrollback_down(term, 1);
return true;
}
break;
case BIND_ACTION_SEARCH_SCROLLBACK_HOME:
if (term->grid == &term->normal) {
cmd_scrollback_up(term, term->grid->num_rows);
return true;
}
break;
case BIND_ACTION_SEARCH_SCROLLBACK_END:
if (term->grid == &term->normal) {
cmd_scrollback_down(term, term->grid->num_rows);
return true;
}
break;
case BIND_ACTION_SEARCH_CANCEL:
if (term->search.view_followed_offset)
grid->view = grid->offset;
@ -950,17 +1270,108 @@ execute_binding(struct seat *seat, struct terminal *term,
return true;
}
case BIND_ACTION_SEARCH_EXTEND_WORD:
search_match_to_end_of_word(term, false);
*update_search_result = false;
*redraw = true;
return true;
case BIND_ACTION_SEARCH_DELETE_TO_START: {
if (term->search.cursor > 0) {
memmove(&term->search.buf[0],
&term->search.buf[term->search.cursor],
(term->search.len - term->search.cursor)
* sizeof(char32_t));
case BIND_ACTION_SEARCH_EXTEND_WORD_WS:
search_match_to_end_of_word(term, true);
*update_search_result = false;
*redraw = true;
term->search.len -= term->search.cursor;
term->search.cursor = 0;
*update_search_result = *redraw = true;
}
return true;
}
case BIND_ACTION_SEARCH_DELETE_TO_END: {
if (term->search.cursor < term->search.len) {
term->search.buf[term->search.cursor] = '\0';
term->search.len = term->search.cursor;
*update_search_result = *redraw = true;
}
return true;
}
case BIND_ACTION_SEARCH_EXTEND_CHAR: {
struct coord target;
if (search_extend_find_char_right(term, &target)) {
search_extend_right(term, &target);
*update_search_result = false;
*redraw = true;
}
return true;
}
case BIND_ACTION_SEARCH_EXTEND_WORD: {
struct coord target;
if (search_extend_find_word_right(term, false, &target)) {
search_extend_right(term, &target);
*update_search_result = false;
*redraw = true;
}
return true;
}
case BIND_ACTION_SEARCH_EXTEND_WORD_WS: {
struct coord target;
if (search_extend_find_word_right(term, true, &target)) {
search_extend_right(term, &target);
*update_search_result = false;
*redraw = true;
}
return true;
}
case BIND_ACTION_SEARCH_EXTEND_LINE_DOWN: {
struct coord target;
if (search_extend_find_line_down(term, &target)) {
search_extend_right(term, &target);
*update_search_result = false;
*redraw = true;
}
return true;
}
case BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR: {
struct coord target;
if (search_extend_find_char_left(term, &target)) {
search_extend_left(term, &target);
*update_search_result = false;
*redraw = true;
}
return true;
}
case BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD: {
struct coord target;
if (search_extend_find_word_left(term, false, &target)) {
search_extend_left(term, &target);
*update_search_result = false;
*redraw = true;
}
return true;
}
case BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS: {
struct coord target;
if (search_extend_find_word_left(term, true, &target)) {
search_extend_left(term, &target);
*update_search_result = false;
*redraw = true;
}
return true;
}
case BIND_ACTION_SEARCH_EXTEND_LINE_UP: {
struct coord target;
if (search_extend_find_line_up(term, &target)) {
search_extend_left(term, &target);
*update_search_result = false;
*redraw = true;
}
return true;
}
case BIND_ACTION_SEARCH_CLIPBOARD_PASTE:
text_from_clipboard(
@ -974,6 +1385,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(term);
return true;
case BIND_ACTION_SEARCH_COUNT:
BUG("Invalid action type");
return true;
@ -987,17 +1402,12 @@ void
search_input(struct seat *seat, struct terminal *term,
const struct key_binding_set *bindings, uint32_t key,
xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed,
xkb_mod_mask_t locked,
const xkb_keysym_t *raw_syms, size_t raw_count,
uint32_t serial)
{
LOG_DBG("search: input: sym=%d/0x%x, mods=0x%08x, consumed=0x%08x",
sym, sym, mods, consumed);
const xkb_mod_mask_t bind_mods =
mods & seat->kbd.bind_significant & ~locked;
const xkb_mod_mask_t bind_consumed =
consumed & seat->kbd.bind_significant & ~locked;
enum xkb_compose_status compose_status = seat->kbd.xkb_compose_state != NULL
? xkb_compose_state_get_status(seat->kbd.xkb_compose_state)
: XKB_COMPOSE_NOTHING;
@ -1006,27 +1416,17 @@ search_input(struct seat *seat, struct terminal *term,
bool update_search_result = false;
bool redraw = false;
/* Key bindings */
/*
* Key bindings
*/
/* Match untranslated symbols */
tll_foreach(bindings->search, it) {
const struct key_binding *bind = &it->item;
/* Match translated symbol */
if (bind->k.sym == sym &&
bind->mods == (bind_mods & ~bind_consumed)) {
if (execute_binding(seat, term, bind, serial,
&update_search_result, &search_direction,
&redraw))
{
goto update_search;
}
return;
}
if (bind->mods != bind_mods || bind_mods != (mods & ~locked))
if (bind->mods != mods || bind->mods == 0)
continue;
/* Match untranslated symbols */
for (size_t i = 0; i < raw_count; i++) {
if (bind->k.sym == raw_syms[i]) {
if (execute_binding(seat, term, bind, serial,
@ -1038,8 +1438,32 @@ search_input(struct seat *seat, struct terminal *term,
return;
}
}
}
/* Match translated symbol */
tll_foreach(bindings->search, it) {
const struct key_binding *bind = &it->item;
if (bind->k.sym == sym &&
bind->mods == (mods & ~consumed)) {
if (execute_binding(seat, term, bind, serial,
&update_search_result, &search_direction,
&redraw))
{
goto update_search;
}
return;
}
}
/* Match raw key code */
tll_foreach(bindings->search, it) {
const struct key_binding *bind = &it->item;
if (bind->mods != mods || bind->mods == 0)
continue;
/* Match raw key code */
tll_foreach(bind->k.key_codes, code) {
if (code->item == key) {
if (execute_binding(seat, term, bind, serial,
@ -1060,7 +1484,8 @@ search_input(struct seat *seat, struct terminal *term,
count = xkb_compose_state_get_utf8(
seat->kbd.xkb_compose_state, (char *)buf, sizeof(buf));
xkb_compose_state_reset(seat->kbd.xkb_compose_state);
} else if (compose_status == XKB_COMPOSE_CANCELLED) {
} else if (compose_status == XKB_COMPOSE_CANCELLED ||
compose_status == XKB_COMPOSE_COMPOSING) {
count = 0;
} else {
count = xkb_state_key_get_utf8(

View file

@ -11,7 +11,6 @@ void search_input(
struct seat *seat, struct terminal *term,
const struct key_binding_set *bindings, uint32_t key,
xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed,
xkb_mod_mask_t locked,
const xkb_keysym_t *raw_syms, size_t raw_count,
uint32_t serial);
void search_add_chars(struct terminal *term, const char *text, size_t len);

File diff suppressed because it is too large Load diff

View file

@ -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);
@ -76,6 +78,10 @@ void selection_start_scroll_timer(
void selection_stop_scroll_timer(struct terminal *term);
void selection_find_word_boundary_left(
struct terminal *term, struct coord *pos, bool spaces_only);
const struct terminal *term, struct coord *pos, bool spaces_only);
void selection_find_word_boundary_right(
struct terminal *term, struct coord *pos, bool spaces_only);
const struct terminal *term, struct coord *pos, bool spaces_only,
bool stop_on_space_to_word_boundary);
struct coord selection_get_start(const struct terminal *term);
struct coord selection_get_end(const struct terminal *term);

102
server.c
View file

@ -4,6 +4,7 @@
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/socket.h>
@ -18,17 +19,18 @@
#include "log.h"
#include "client-protocol.h"
#include "shm.h"
#include "terminal.h"
#include "util.h"
#include "wayland.h"
#include "xmalloc.h"
#define NON_ZERO_OPT (INT_MIN / 7)
struct client;
struct terminal_instance;
struct server {
const struct config *conf;
struct config *conf;
struct fdm *fdm;
struct reaper *reaper;
struct wayland *wayl;
@ -154,10 +156,61 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
xassert(events & EPOLLIN);
if (client->instance != NULL) {
uint8_t dummy[128];
ssize_t count = read(fd, dummy, sizeof(dummy));
LOG_WARN("client unexpectedly sent %zd bytes", count);
return true; /* TODO: shutdown instead? */
struct client_ipc_hdr ipc_hdr;
ssize_t count = read(fd, &ipc_hdr, sizeof(ipc_hdr));
if (count != sizeof(ipc_hdr)) {
LOG_WARN("client unexpectedly sent %zd bytes", count);
return true; /* TODO: shutdown instead? */
}
switch (ipc_hdr.ipc_code) {
case FOOT_IPC_SIGUSR: {
xassert(ipc_hdr.size == sizeof(struct client_ipc_sigusr));
struct client_ipc_sigusr sigusr;
count = read(fd, &sigusr, sizeof(sigusr));
if (count < 0) {
LOG_ERRNO("failed to read SIGUSR IPC data from client");
return true; /* TODO: shutdown instead? */
}
if ((size_t)count != sizeof(sigusr)) {
LOG_ERR("failed to read SIGUSR IPC data from client");
return true; /* TODO: shutdown instead? */
}
switch (sigusr.signo) {
case SIGUSR1:
term_theme_switch_to_dark(client->instance->terminal);
break;
case SIGUSR2:
term_theme_switch_to_light(client->instance->terminal);
break;
default:
LOG_ERR(
"client sent bad SIGUSR number: %d "
"(expected SIGUSR1=%d or SIGUSR2=%d)",
sigusr.signo, SIGUSR1, SIGUSR2);
break;
}
return true;
}
default:
LOG_WARN(
"client sent unrecognized IPC (0x%04x), ignoring %hhu bytes",
ipc_hdr.ipc_code, ipc_hdr.size);
/* TODO: slightly broken, since not all data is guaranteed
to be readable yet */
uint8_t dummy[ipc_hdr.size];
(void)!!read(fd, dummy, ipc_hdr.size);
return true;
}
}
if (client->buffer.data == NULL) {
@ -212,6 +265,12 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
return true;
}
if (tll_length(server->wayl->monitors) == 0) {
LOG_ERR("no monitors available for new terminal");
client_send_exit_code(client, -26);
goto shutdown;
}
/* All initialization data received - time to instantiate a terminal! */
xassert(client->instance == NULL);
@ -301,7 +360,7 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
#undef CHECK_BUF_AND_NULL
#undef CHECK_BUF
struct terminal_instance *instance = malloc(sizeof(struct terminal_instance));
struct terminal_instance *instance = xmalloc(sizeof(struct terminal_instance));
const bool need_to_clone_conf =
tll_length(overrides)> 0 ||
@ -332,7 +391,8 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
instance->terminal = term_init(
conf != NULL ? conf : server->conf,
server->fdm, server->reaper, server->wayl, "footclient", cwd, token,
cdata.argc, argv, envp, &term_shutdown_handler, instance);
NULL, cdata.argc, argv, (const char *const *)envp,
&term_shutdown_handler, instance);
if (instance->terminal == NULL) {
LOG_ERR("failed to instantiate new terminal");
@ -468,7 +528,7 @@ prepare_socket(int fd)
}
int const socket_options[] = { SO_DOMAIN, SO_ACCEPTCONN, SO_TYPE };
int const socket_options_values[] = { AF_UNIX, 1, SOCK_STREAM};
int const socket_options_values[] = { AF_UNIX, NON_ZERO_OPT, SOCK_STREAM};
char const * const socket_options_names[] = { "SO_DOMAIN", "SO_ACCEPTCONN", "SO_TYPE" };
xassert(ALEN(socket_options) == ALEN(socket_options_values));
@ -483,6 +543,8 @@ prepare_socket(int fd)
LOG_ERRNO("failed to read socket option from passed file descriptor");
return false;
}
if (socket_options_values[i] == NON_ZERO_OPT && socket_option)
socket_option = NON_ZERO_OPT;
if (socket_option != socket_options_values[i]) {
LOG_ERR("wrong socket value for socket option '%s' on passed file descriptor",
socket_options_names[i]);
@ -494,7 +556,7 @@ prepare_socket(int fd)
}
struct server *
server_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
server_init(struct config *conf, struct fdm *fdm, struct reaper *reaper,
struct wayland *wayl)
{
int fd;
@ -606,3 +668,23 @@ server_destroy(struct server *server)
unlink(server->sock_path);
free(server);
}
void
server_global_theme_switch_to_dark(struct server *server)
{
server->conf->initial_color_theme = COLOR_THEME_DARK;
tll_foreach(server->clients, it)
term_theme_switch_to_dark(it->item->instance->terminal);
tll_foreach(server->terminals, it)
term_theme_switch_to_dark(it->item->terminal);
}
void
server_global_theme_switch_to_light(struct server *server)
{
server->conf->initial_color_theme = COLOR_THEME_LIGHT;
tll_foreach(server->clients, it)
term_theme_switch_to_light(it->item->instance->terminal);
tll_foreach(server->terminals, it)
term_theme_switch_to_light(it->item->terminal);
}

View file

@ -6,6 +6,9 @@
#include "wayland.h"
struct server;
struct server *server_init(const struct config *conf, struct fdm *fdm,
struct server *server_init(struct config *conf, struct fdm *fdm,
struct reaper *reaper, struct wayland *wayl);
void server_destroy(struct server *server);
void server_global_theme_switch_to_dark(struct server *server);
void server_global_theme_switch_to_light(struct server *server);

138
shm-formats.h Normal file
View file

@ -0,0 +1,138 @@
#pragma once
#include <wayland-version.h>
#if defined(_DEBUG)
static const struct shm_formats {
uint32_t format;
const char *description;
} shm_formats[] = {
{WL_SHM_FORMAT_ARGB8888, "ARGB8888"},
{WL_SHM_FORMAT_XRGB8888, "XRGB8888"},
{WL_SHM_FORMAT_C8, "C8"},
{WL_SHM_FORMAT_RGB332, "RGB332"},
{WL_SHM_FORMAT_BGR233, "BGR233"},
{WL_SHM_FORMAT_XRGB4444, "XRGB4444"},
{WL_SHM_FORMAT_XBGR4444, "XBGR4444"},
{WL_SHM_FORMAT_RGBX4444, "RGBX4444"},
{WL_SHM_FORMAT_BGRX4444, "BGRX4444"},
{WL_SHM_FORMAT_ARGB4444, "ARGB4444"},
{WL_SHM_FORMAT_ABGR4444, "ABGR4444"},
{WL_SHM_FORMAT_RGBA4444, "RGBA4444"},
{WL_SHM_FORMAT_BGRA4444, "BGRA4444"},
{WL_SHM_FORMAT_XRGB1555, "XRGB1555"},
{WL_SHM_FORMAT_XBGR1555, "XBGR1555"},
{WL_SHM_FORMAT_RGBX5551, "RGBX5551"},
{WL_SHM_FORMAT_BGRX5551, "BGRX5551"},
{WL_SHM_FORMAT_ARGB1555, "ARGB1555"},
{WL_SHM_FORMAT_ABGR1555, "ABGR1555"},
{WL_SHM_FORMAT_RGBA5551, "RGBA5551"},
{WL_SHM_FORMAT_BGRA5551, "BGRA5551"},
{WL_SHM_FORMAT_RGB565, "RGB565"},
{WL_SHM_FORMAT_BGR565, "BGR565"},
{WL_SHM_FORMAT_RGB888, "RGB888"},
{WL_SHM_FORMAT_BGR888, "BGR888"},
{WL_SHM_FORMAT_XBGR8888, "XBGR8888"},
{WL_SHM_FORMAT_RGBX8888, "RGBX8888"},
{WL_SHM_FORMAT_BGRX8888, "BGRX8888"},
{WL_SHM_FORMAT_ABGR8888, "ABGR8888"},
{WL_SHM_FORMAT_RGBA8888, "RGBA8888"},
{WL_SHM_FORMAT_BGRA8888, "BGRA8888"},
{WL_SHM_FORMAT_XRGB2101010, "XRGB2101010"},
{WL_SHM_FORMAT_XBGR2101010, "XBGR2101010"},
{WL_SHM_FORMAT_RGBX1010102, "RGBX1010102"},
{WL_SHM_FORMAT_BGRX1010102, "BGRX1010102"},
{WL_SHM_FORMAT_ARGB2101010, "ARGB2101010"},
{WL_SHM_FORMAT_ABGR2101010, "ABGR2101010"},
{WL_SHM_FORMAT_RGBA1010102, "RGBA1010102"},
{WL_SHM_FORMAT_BGRA1010102, "BGRA1010102"},
{WL_SHM_FORMAT_YUYV, "YUYV"},
{WL_SHM_FORMAT_YVYU, "YVYU"},
{WL_SHM_FORMAT_UYVY, "UYVY"},
{WL_SHM_FORMAT_VYUY, "VYUY"},
{WL_SHM_FORMAT_AYUV, "AYUV"},
{WL_SHM_FORMAT_NV12, "NV12"},
{WL_SHM_FORMAT_NV21, "NV21"},
{WL_SHM_FORMAT_NV16, "NV16"},
{WL_SHM_FORMAT_NV61, "NV61"},
{WL_SHM_FORMAT_YUV410, "YUV410"},
{WL_SHM_FORMAT_YVU410, "YVU410"},
{WL_SHM_FORMAT_YUV411, "YUV411"},
{WL_SHM_FORMAT_YVU411, "YVU411"},
{WL_SHM_FORMAT_YUV420, "YUV420"},
{WL_SHM_FORMAT_YVU420, "YVU420"},
{WL_SHM_FORMAT_YUV422, "YUV422"},
{WL_SHM_FORMAT_YVU422, "YVU422"},
{WL_SHM_FORMAT_YUV444, "YUV444"},
{WL_SHM_FORMAT_YVU444, "YVU444"},
{WL_SHM_FORMAT_R8, "R8"},
{WL_SHM_FORMAT_R16, "R16"},
{WL_SHM_FORMAT_RG88, "RG88"},
{WL_SHM_FORMAT_GR88, "GR88"},
{WL_SHM_FORMAT_RG1616, "RG1616"},
{WL_SHM_FORMAT_GR1616, "GR1616"},
{WL_SHM_FORMAT_XRGB16161616F, "XRGB16161616F"},
{WL_SHM_FORMAT_XBGR16161616F, "XBGR16161616F"},
{WL_SHM_FORMAT_ARGB16161616F, "ARGB16161616F"},
{WL_SHM_FORMAT_ABGR16161616F, "ABGR16161616F"},
{WL_SHM_FORMAT_XYUV8888, "XYUV8888"},
{WL_SHM_FORMAT_VUY888, "VUY888"},
{WL_SHM_FORMAT_VUY101010, "VUY101010"},
{WL_SHM_FORMAT_Y210, "Y210"},
{WL_SHM_FORMAT_Y212, "Y212"},
{WL_SHM_FORMAT_Y216, "Y216"},
{WL_SHM_FORMAT_Y410, "Y410"},
{WL_SHM_FORMAT_Y412, "Y412"},
{WL_SHM_FORMAT_Y416, "Y416"},
{WL_SHM_FORMAT_XVYU2101010, "XVYU2101010"},
{WL_SHM_FORMAT_XVYU12_16161616, "XVYU12_16161616"},
{WL_SHM_FORMAT_XVYU16161616, "XVYU16161616"},
{WL_SHM_FORMAT_Y0L0, "Y0L0"},
{WL_SHM_FORMAT_X0L0, "X0L0"},
{WL_SHM_FORMAT_Y0L2, "Y0L2"},
{WL_SHM_FORMAT_X0L2, "X0L2"},
{WL_SHM_FORMAT_YUV420_8BIT, "YUV420_8BIT"},
{WL_SHM_FORMAT_YUV420_10BIT, "YUV420_10BIT"},
{WL_SHM_FORMAT_XRGB8888_A8, "XRGB8888_A8"},
{WL_SHM_FORMAT_XBGR8888_A8, "XBGR8888_A8"},
{WL_SHM_FORMAT_RGBX8888_A8, "RGBX8888_A8"},
{WL_SHM_FORMAT_BGRX8888_A8, "BGRX8888_A8"},
{WL_SHM_FORMAT_RGB888_A8, "RGB888_A8"},
{WL_SHM_FORMAT_BGR888_A8, "BGR888_A8"},
{WL_SHM_FORMAT_RGB565_A8, "RGB565_A8"},
{WL_SHM_FORMAT_BGR565_A8, "BGR565_A8"},
{WL_SHM_FORMAT_NV24, "NV24"},
{WL_SHM_FORMAT_NV42, "NV42"},
{WL_SHM_FORMAT_P210, "P210"},
{WL_SHM_FORMAT_P010, "P010"},
{WL_SHM_FORMAT_P012, "P012"},
{WL_SHM_FORMAT_P016, "P016"},
{WL_SHM_FORMAT_AXBXGXRX106106106106, "AXBXGXRX106106106106"},
{WL_SHM_FORMAT_NV15, "NV15"},
{WL_SHM_FORMAT_Q410, "Q410"},
{WL_SHM_FORMAT_Q401, "Q401"},
#if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 20
{WL_SHM_FORMAT_XRGB16161616, "XRGB16161616"},
{WL_SHM_FORMAT_XBGR16161616, "XBGR16161616"},
{WL_SHM_FORMAT_ARGB16161616, "ARGB16161616"},
{WL_SHM_FORMAT_ABGR16161616, "ABGR16161616"},
#endif
#if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 23
{WL_SHM_FORMAT_C1, "C1"},
{WL_SHM_FORMAT_C2, "C2"},
{WL_SHM_FORMAT_C4, "C4"},
{WL_SHM_FORMAT_D1, "D1"},
{WL_SHM_FORMAT_D2, "D2"},
{WL_SHM_FORMAT_D4, "D4"},
{WL_SHM_FORMAT_D8, "D8"},
{WL_SHM_FORMAT_R1, "R1"},
{WL_SHM_FORMAT_R2, "R2"},
{WL_SHM_FORMAT_R4, "R4"},
{WL_SHM_FORMAT_R10, "R10"},
{WL_SHM_FORMAT_R12, "R12"},
{WL_SHM_FORMAT_AVUY8888, "AVUY8888"},
{WL_SHM_FORMAT_XVUY8888, "XVUY8888"},
{WL_SHM_FORMAT_P030, "P030"},
#endif
};
#endif

206
shm.c
View file

@ -13,7 +13,6 @@
#include <pixman.h>
#include <fcft/stride.h>
#include <tllist.h>
#define LOG_MODULE "shm"
@ -21,12 +20,17 @@
#include "log.h"
#include "debug.h"
#include "macros.h"
#include "stride.h"
#include "xmalloc.h"
#if !defined(MAP_UNINITIALIZED)
#define MAP_UNINITIALIZED 0
#endif
#if !defined(MFD_NOEXEC_SEAL)
#define MFD_NOEXEC_SEAL 0
#endif
#define TIME_SCROLL 0
#define FORCED_DOUBLE_BUFFERING 0
@ -57,6 +61,8 @@ static off_t max_pool_size = 512 * 1024 * 1024;
static bool can_punch_hole = false;
static bool can_punch_hole_initialized = false;
static size_t min_stride_alignment = 0;
struct buffer_pool {
int fd; /* memfd */
struct wl_shm_pool *wl_pool;
@ -80,6 +86,9 @@ struct buffer_private {
size_t size;
bool scrollable;
void (*release_cb)(struct buffer *buf, void *data);
void *cb_data;
};
struct buffer_chain {
@ -87,6 +96,12 @@ struct buffer_chain {
struct wl_shm *shm;
size_t pix_instances;
bool scrollable;
pixman_format_code_t pixman_fmt;
enum wl_shm_format shm_format;
void (*release_cb)(struct buffer *buf, void *data);
void *cb_data;
};
static tll(struct buffer_private *) deferred;
@ -102,6 +117,12 @@ shm_set_max_pool_size(off_t _max_pool_size)
max_pool_size = _max_pool_size;
}
void
shm_set_min_stride_alignment(size_t _min_stride_alignment)
{
min_stride_alignment = _min_stride_alignment;
}
static void
buffer_destroy_dont_close(struct buffer *buf)
{
@ -110,6 +131,7 @@ buffer_destroy_dont_close(struct buffer *buf)
if (buf->pix[i] != NULL)
pixman_image_unref(buf->pix[i]);
}
if (buf->wl_buf != NULL)
wl_buffer_destroy(buf->wl_buf);
@ -151,8 +173,9 @@ buffer_destroy(struct buffer_private *buf)
pool_unref(buf->pool);
buf->pool = NULL;
free(buf->public.scroll_damage);
pixman_region32_fini(&buf->public.dirty);
for (size_t i = 0; i < buf->public.pix_instances; i++)
pixman_region32_fini(&buf->public.dirty[i]);
free(buf->public.dirty);
free(buf);
}
@ -211,6 +234,10 @@ buffer_release(void *data, struct wl_buffer *wl_buffer)
xassert(found);
if (!found)
LOG_WARN("deferred delete: buffer not on the 'deferred' list");
} else {
if (buffer->release_cb != NULL) {
buffer->release_cb(&buffer->public, buffer->cb_data);
}
}
}
@ -218,7 +245,6 @@ static const struct wl_buffer_listener buffer_listener = {
.release = &buffer_release,
};
#if __SIZEOF_POINTER__ == 8
static size_t
page_size(void)
{
@ -235,7 +261,6 @@ page_size(void)
xassert(size > 0);
return size;
}
#endif
static bool
instantiate_offset(struct buffer_private *buf, off_t new_offset)
@ -249,14 +274,14 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset)
void *mmapped = MAP_FAILED;
struct wl_buffer *wl_buf = NULL;
pixman_image_t **pix = xcalloc(buf->public.pix_instances, sizeof(*pix));
pixman_image_t **pix = xcalloc(buf->public.pix_instances, sizeof(pix[0]));
mmapped = (uint8_t *)pool->real_mmapped + new_offset;
wl_buf = wl_shm_pool_create_buffer(
pool->wl_pool, new_offset,
buf->public.width, buf->public.height, buf->public.stride,
WL_SHM_FORMAT_ARGB8888);
buf->chain->shm_format);
if (wl_buf == NULL) {
LOG_ERR("failed to create SHM buffer");
@ -266,8 +291,10 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset)
/* One pixman image for each worker thread (do we really need multiple?) */
for (size_t i = 0; i < buf->public.pix_instances; i++) {
pix[i] = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, buf->public.width, buf->public.height,
buf->chain->pixman_fmt,
buf->public.width, buf->public.height,
(uint32_t *)mmapped, buf->public.stride);
if (pix[i] == NULL) {
LOG_ERR("failed to create pixman image");
goto err;
@ -317,7 +344,15 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
size_t total_size = 0;
for (size_t i = 0; i < count; i++) {
stride[i] = stride_for_format_and_width(PIXMAN_a8r8g8b8, widths[i]);
stride[i] = stride_for_format_and_width(
chain->pixman_fmt, widths[i]);
if (min_stride_alignment > 0) {
const size_t m = min_stride_alignment;
stride[i] = (stride[i] + m - 1) / m * m;
}
xassert(min_stride_alignment == 0 || stride[i] % min_stride_alignment == 0);
sizes[i] = stride[i] * heights[i];
total_size += sizes[i];
}
@ -332,7 +367,20 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
/* Backing memory for SHM */
#if defined(MEMFD_CREATE)
pool_fd = memfd_create("foot-wayland-shm-buffer-pool", MFD_CLOEXEC | MFD_ALLOW_SEALING);
/*
* Older kernels reject MFD_NOEXEC_SEAL with EINVAL. Try first
* *with* it, and if that fails, try again *without* it.
*/
errno = 0;
pool_fd = memfd_create(
"foot-wayland-shm-buffer-pool",
MFD_CLOEXEC | MFD_ALLOW_SEALING | MFD_NOEXEC_SEAL);
if (pool_fd < 0 && errno == EINVAL && MFD_NOEXEC_SEAL != 0) {
pool_fd = memfd_create(
"foot-wayland-shm-buffer-pool", MFD_CLOEXEC | MFD_ALLOW_SEALING);
}
#elif defined(__FreeBSD__)
// memfd_create on FreeBSD 13 is SHM_ANON without sealing support
pool_fd = shm_open(SHM_ANON, O_RDWR | O_CLOEXEC, 0600);
@ -346,9 +394,11 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
goto err;
}
const size_t page_sz = page_size();
#if __SIZEOF_POINTER__ == 8
off_t offset = chain->scrollable && max_pool_size > 0
? (max_pool_size / 4) & ~(page_size() - 1)
? (max_pool_size / 4) & ~(page_sz - 1)
: 0;
off_t memfd_size = chain->scrollable && max_pool_size > 0
? max_pool_size
@ -358,7 +408,8 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
off_t memfd_size = total_size;
#endif
xassert(chain->scrollable || (offset == 0 && memfd_size == total_size));
/* Page align */
memfd_size = (memfd_size + page_sz - 1) & ~(page_sz - 1);
LOG_DBG("memfd-size: %lu, initial offset: %lu", memfd_size, offset);
@ -390,6 +441,9 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
memfd_size = total_size;
chain->scrollable = false;
/* Page align */
memfd_size = (memfd_size + page_sz - 1) & ~(page_sz - 1);
if (ftruncate(pool_fd, memfd_size) < 0) {
LOG_ERRNO("failed to set size of SHM backing memory file");
goto err;
@ -459,6 +513,8 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
.offset = 0,
.size = sizes[i],
.scrollable = chain->scrollable,
.release_cb = chain->release_cb,
.cb_data = chain->cb_data,
};
if (!instantiate_offset(buf, offset)) {
@ -471,7 +527,12 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
else
tll_push_front(chain->bufs, buf);
pixman_region32_init(&buf->public.dirty);
buf->public.dirty = xmalloc(
chain->pix_instances * sizeof(buf->public.dirty[0]));
for (size_t j = 0; j < chain->pix_instances; j++)
pixman_region32_init(&buf->public.dirty[j]);
pool->ref_count++;
offset += buf->size;
bufs[i] = &buf->public;
@ -488,7 +549,7 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
#endif
if (!(bufs[0] && shm_can_scroll(bufs[0]))) {
/* We only need to keep the pool FD open if were going to SHM
/* We only need to keep the pool FD open if we're going to SHM
* scroll it */
close(pool_fd);
pool->fd = -1;
@ -528,7 +589,7 @@ struct buffer *
shm_get_buffer(struct buffer_chain *chain, int width, int height)
{
LOG_DBG(
"chain=%p: looking for a re-usable %dx%d buffer "
"chain=%p: looking for a reusable %dx%d buffer "
"among %zu potential buffers",
(void *)chain, width, height, tll_length(chain->bufs));
@ -547,16 +608,16 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height)
buf->public.age++;
else
#if FORCED_DOUBLE_BUFFERING
if (buf->age == 0)
buf->age++;
if (buf->public.age == 0)
buf->public.age++;
else
#endif
{
if (cached == NULL)
if (cached == NULL) {
cached = buf;
else {
} else {
/* We have multiple buffers eligible for
* re-use. Pick the youngest one, and mark the
* reuse. Pick the "youngest" one, and mark the
* other one for purging */
if (buf->public.age < cached->public.age) {
shm_unref(&cached->public);
@ -566,8 +627,8 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height)
* TODO: I think we _can_ use shm_unref()
* here...
*
* shm_unref() may remove it, but that
* should be safe; our tll_foreach() already
* shm_unref() may remove 'it', but that
* should be safe; "our" tll_foreach() already
* holds the next pointer.
*/
if (buffer_unref_no_remove_from_chain(buf))
@ -578,11 +639,10 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height)
}
if (cached != NULL) {
LOG_DBG("re-using buffer %p from cache", (void *)cached);
LOG_DBG("reusing 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;
for (size_t i = 0; i < cached->public.pix_instances; i++)
pixman_region32_clear(&cached->public.dirty[i]);
xassert(cached->public.pix_instances == chain->pix_instances);
return &cached->public;
}
@ -930,14 +990,90 @@ shm_unref(struct buffer *_buf)
}
struct buffer_chain *
shm_chain_new(struct wl_shm *shm, bool scrollable, size_t pix_instances)
shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances,
enum shm_bit_depth desired_bit_depth,
void (*release_cb)(struct buffer *buf, void *data), void *cb_data)
{
pixman_format_code_t pixman_fmt = PIXMAN_a8r8g8b8;
enum wl_shm_format shm_fmt = WL_SHM_FORMAT_ARGB8888;
static bool have_logged = false;
static bool have_logged_10_fallback = false;
#if defined(HAVE_PIXMAN_RGBA_16)
static bool have_logged_16_fallback = false;
if (desired_bit_depth == SHM_BITS_16) {
if (wayl->shm_have_abgr161616) {
pixman_fmt = PIXMAN_a16b16g16r16;
shm_fmt = WL_SHM_FORMAT_ABGR16161616;
if (!have_logged) {
have_logged = true;
LOG_INFO("using 16-bit BGR surfaces");
}
} else {
if (!have_logged_16_fallback) {
have_logged_16_fallback = true;
LOG_WARN(
"16-bit surfaces requested, but compositor does not "
"implement ABGR161616+XBGR161616");
}
}
}
#endif
if (desired_bit_depth >= SHM_BITS_10 && pixman_fmt == PIXMAN_a8r8g8b8) {
if (wayl->shm_have_argb2101010) {
pixman_fmt = PIXMAN_a2r10g10b10;
shm_fmt = WL_SHM_FORMAT_ARGB2101010;
if (!have_logged) {
have_logged = true;
LOG_INFO("using 10-bit RGB surfaces");
}
}
else if (wayl->shm_have_abgr2101010) {
pixman_fmt = PIXMAN_a2b10g10r10;
shm_fmt = WL_SHM_FORMAT_ABGR2101010;
if (!have_logged) {
have_logged = true;
LOG_INFO("using 10-bit BGR surfaces");
}
}
else {
if (!have_logged_10_fallback) {
have_logged_10_fallback = true;
LOG_WARN(
"10-bit surfaces requested, but compositor does not "
"implement ARGB2101010+XRGB2101010, or "
"ABGR2101010+XBGR2101010");
}
}
} else {
if (!have_logged) {
have_logged = true;
LOG_INFO("using 8-bit RGB surfaces");
}
}
struct buffer_chain *chain = xmalloc(sizeof(*chain));
*chain = (struct buffer_chain){
.bufs = tll_init(),
.shm = shm,
.shm = wayl->shm,
.pix_instances = pix_instances,
.scrollable = scrollable,
.pixman_fmt = pixman_fmt,
.shm_format = shm_fmt,
.release_cb = release_cb,
.cb_data = cb_data,
};
return chain;
}
@ -957,3 +1093,17 @@ shm_chain_free(struct buffer_chain *chain)
free(chain);
}
enum shm_bit_depth
shm_chain_bit_depth(const struct buffer_chain *chain)
{
const pixman_format_code_t fmt = chain->pixman_fmt;
return fmt == PIXMAN_a8r8g8b8
? SHM_BITS_8
#if defined(HAVE_PIXMAN_RGBA_16)
: fmt == PIXMAN_a16b16g16r16
? SHM_BITS_16
#endif
: SHM_BITS_10;
}

31
shm.h
View file

@ -9,6 +9,9 @@
#include <tllist.h>
#include "config.h"
#include "wayland.h"
struct damage;
struct buffer {
@ -24,23 +27,39 @@ struct buffer {
unsigned age;
struct damage *scroll_damage;
size_t scroll_damage_count;
pixman_region32_t dirty;
/*
* First item in the array is used to track frame-to-frame
* damage. This is used when re-applying damage from the last
* frame, when the compositor doesn't release buffers immediately
* (forcing us to double buffer)
*
* The remaining items are used to track surface damage. Each
* worker thread adds its own cell damage to "its" region. When
* the frame is done, all damage is converted to a single region,
* which is then used in calls to wl_surface_damage_buffer().
*/
pixman_region32_t *dirty;
};
void shm_fini(void);
/* TODO: combine into shm_init() */
void shm_set_max_pool_size(off_t max_pool_size);
void shm_set_min_stride_alignment(size_t min_stride_alignment);
struct buffer_chain;
struct buffer_chain *shm_chain_new(
struct wl_shm *shm, bool scrollable, size_t pix_instances);
struct wayland *wayl, bool scrollable, size_t pix_instances,
enum shm_bit_depth desired_bit_depth,
void (*release_cb)(struct buffer *buf, void *data), void *cb_data);
void shm_chain_free(struct buffer_chain *chain);
enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain);
/*
* Returns a single buffer.
*
* May returned a cached buffer. If so, the buffers age indicates how
* May returned a cached buffer. If so, the buffer's age indicates how
* many shm_get_buffer() calls have been made for the same
* width/height while the buffer was still busy.
*
@ -48,7 +67,7 @@ void shm_chain_free(struct buffer_chain *chain);
*/
struct buffer *shm_get_buffer(struct buffer_chain *chain, int width, int height);
/*
* Returns many buffers, described by info, all sharing the same SHM
* Returns many buffers, described by 'info', all sharing the same SHM
* buffer pool.
*
* Never returns cached buffers. However, the newly created buffers

1302
sixel.c

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more