Compare commits

...

374 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
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
167 changed files with 6918 additions and 2464 deletions

View file

@ -29,7 +29,7 @@ body:
- type: input
id: compositor
attributes:
label: Compositor Version
label: Compositor Name and Version
description: "The name and version of your compositor"
placeholder: "sway version 1.9"
validations:
@ -38,15 +38,36 @@ body:
id: distro
attributes:
label: Distribution
description: "The name of the Linux distribution, or BSD flavor, you are running"
placeholder: "Arch Linux"
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
description: Paste your entire `foot.ini` here (do not forget to sanitize it!)
render: ini
validations:
required: true
- type: textarea

View file

@ -1,7 +1,7 @@
# -*- yaml -*-
steps:
- name: codespell
- name: pychecks
when:
- event: [manual, pull_request]
- event: [push, tag]
@ -11,10 +11,15 @@ steps:
- apk add openssl
- apk add python3
- apk add py3-pip
- python3 -m venv codespell-venv
- source codespell-venv/bin/activate
- python3 -m venv venv
- source venv/bin/activate
- python -m pip install --upgrade pip
- pip install codespell
- codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd
- pip install mypy
- pip install ruff
- codespell
- mypy
- ruff check
- deactivate
- name: subprojects

View file

@ -1,5 +1,17 @@
# Changelog
* [Unreleased](#unreleased)
* [1.26.1](#1-26-1)
* [1.26.0](#1-26-0)
* [1.25.0](#1-25-0)
* [1.24.0](#1-24-0)
* [1.23.1](#1-23-1)
* [1.23.0](#1-23-0)
* [1.22.3](#1-22-3)
* [1.22.2](#1-22-2)
* [1.22.1](#1-22-1)
* [1.22.0](#1-22-0)
* [1.21.0](#1-21-0)
* [1.20.2](#1-20-2)
* [1.20.1](#1-20-1)
* [1.20.0](#1-20-0)
@ -57,6 +69,584 @@
* [1.2.0](#1-2-0)
## Unreleased
### Added
### Changed
### Deprecated
### Removed
### Fixed
* Other output (key presses, query replies etc) being mixed with paste
data, both interactive pastes and OSC-52 ([#2307][2307]).
[2307]: https://codeberg.org/dnkl/foot/issues/2307
### Security
### Contributors
## 1.26.1
### Fixed
* Wrong documented default value for `initial-color-theme` in
`foot.ini(5)` ([#2292][2292]).
* Occasional crashes when closing a window and
`tweak.pre-apply-damage=yes` (the default) ([#2288][2288]).
[2292]: https://codeberg.org/dnkl/foot/issues/2292
[2288]: https://codeberg.org/dnkl/foot/issues/2288
### Contributors
* Roshless
* vlkrs
## 1.26.0
### Added
* `toplevel-tag` option (and `--toplevel-tag` command line options to
`foot` and `footclient`), allowing you to set a custom toplevel
tag. The compositor must implement the new `xdg-toplevel-tag-v1`
Wayland protocol ([#2212][2212]).
* `[colors-dark]` section to `foot.ini`. Replaces `[colors]`.
* `[colors-light]` section to `foot.ini`. Replaces `[colors2]`.
* `XTGETTCAP`: added `query-os-name`, returning the OS foot is
compiled for (e.g. _'Linux'_) ([#2209][2209]).
* `pad` option now supports 4-directional padding format:
`LEFTxTOPxRIGHTxBOTTOM` (e.g., `20x10x20x10`).
* `--config=PATH` option is now automatically passed to new
terminals spawned via `spawn-terminal` action ([#2259][2259]).
* Preliminary (untested) support for background blur via the new
`ext-background-effect-v1` protocol. Enable by setting
`colors-{dark,light}.blur=yes`. Foot needs to have been **built**
against `wayland-protocols >= 1.45`, and the compositor **must**
implement the `ext-background-effect-v1` protocol, **and** the
`blur` effect.
[2212]: https://codeberg.org/dnkl/foot/issues/2212
[2209]: https://codeberg.org/dnkl/foot/issues/2209
[2259]: https://codeberg.org/dnkl/foot/pulls/2259
### Changed
* When enabling _"focus mode"_ (private mode 1004), foot now sends a
focus event immediately, to inform the application what the current
state is ([#2202][2202]).
* Scrollback search is now case sensitive when the search string
contains at least one upper case character.
* Mouse tracking in SGR pixel mode no longer emits negative column/row
pixel values ([#2226][2226]).
* Foot now always uses ARGB SHM surfaces. In earlier versions, XRGB
surfaces were used for opaque surfaces. Unfortunately, several
compositors had issues when foot switched between ARGB and XRGB
surfaces (for example when switching color theme, or toggling
fullscreen).
[2202]: https://codeberg.org/dnkl/foot/issues/2202
[2226]: https://codeberg.org/dnkl/foot/issues/2226
### Deprecated
* `[colors]` section in `foot.ini`. Use `[colors-dark]` instead.
* `[colors2]` section in `foot.ini`. Use `[colors-light]` instead.
### Removed
* `cursor.color` config option (deprecated in 1.23.0). Use
`colors-{dark,light}.cursor` instead.
### Fixed
* Search mode: composing keys not ignored.
* Crash when triple-clicking a soft-wrapped line and there is a quote
character in the last column.
* Crash when reverse-scrolling (terminfo capability `rin`) such that
the current viewport ends up outside the scrollback ([#2232][2232]).
* Regression: visual glitches in rare circumstances.
* Key release events for shortcuts being sent to the client
application (kitty keyboard protocol only) ([#2257][2257]).
* Crash when application emits sixel RA with a height of 0, a width >
0, and then starts writing sixel data ([#2267][2267]).
* Crash if shutting down terminal instance while a "pre-apply damage"
thread is running ([#2263][2263]).
[2232]: https://codeberg.org/dnkl/foot/issues/2232
[2257]: https://codeberg.org/dnkl/foot/issues/2257
[2267]: https://codeberg.org/dnkl/foot/issues/2267
[2263]: https://codeberg.org/dnkl/foot/issues/2263
### Contributors
* Andrei
* Barinderpreet Singh
* c4llv07e
* Johannes Altmanninger
* nariby
* pi66
* Ronan Pigott
* Stéphane Klein
* valoq
* Whyme Lyu
* Yaakov Selkowitz
## 1.25.0
### Added
* Performance increased and input latency decreased on compositors
that do not release SHM buffers immediately ([#2188][2188]).
* `colors{,2}.dim-blend-towards=black|white` option, allowing you to
select towards which color to blend when dimming text. Defaults to
`black` in `[colors]`, and `white` in `[colors2]` ([#2187][2187]).
[2188]: https://codeberg.org/dnkl/foot/issues/2188
[2187]: https://codeberg.org/dnkl/foot/issues/2187
### Changed
* SHM buffer sizes are now rounded up to nearest page size, and their
stride is always an even multiple of 256 bytes (by default,
configurable by setting `tweak.min-stride-alignment`). This allows
compositor to directly import foot's SHM buffers to the GPU, with
e.g. integrated graphics ([#2182][2182]).
* Jump label colors in the modus-operandi theme, for improved
readability.
[2182]: https://codeberg.org/dnkl/foot/issues/2182
### Fixed
* URL labels misplaces when URL contains double-width characters
([#2179][2179]).
* One space too much consumed when copying (or pipe:ing) contents with
tabs ([#2194][2194])
* Ensure we render a new frame when changing fullscreen state. Before,
this was automatically done if the window was also resized. But, it
is possible for a compositor to change an application's fullscreen
state without resizing the window.
[2179]: https://codeberg.org/dnkl/foot/issues/2179
[2194]: https://codeberg.org/dnkl/foot/issues/2194
### Contributors
* Charalampos Mitrodimas
* Matthias Heyman
## 1.24.0
### Added
* The `uppercase-regex-insert` option controls whether an uppercase hint
character will insert the selected text into the prompt in `regex-copy`
or `show-urls-copy` mode. It defaults to `true`. ([#2159][2159]).
[2159]: https://codeberg.org/dnkl/foot/issues/2159
### Changed
* The label letters are no longer sorted before being assigned to URLs
([#2140][2140]).
* Sending SIGUSR1/SIGUSR2 to a `foot --server` process now causes
newly spawned client instances to use the selected theme, instead of
the original one.
* SIGUSR1/SIGUSR2 can now be sent to `footclient` processes, to change
the theme of that particular instance ([#2156][2156]).
[2156]: https://codeberg.org/dnkl/foot/issues/2156
### Fixed
* Invalid configuration values overriding valid ones in surprising
ways.
* Bug where the libutempter utmp backend did not record logouts
correctly.
### Contributors
* Ryan Roden-Corrent
* Tobias Mock
## 1.23.1
### Changed
* URL labels are now assigned in reverse order, from bottom to
top. This ensures the **last** URL (which is often the one you are
interested in) is always assigned the same key ([#2140][2140]).
* Sending `SIGUSR1` no longer **toggles** between `[colors]` and
`[colors2]`, but explicitly changes to `[colors]`. `SIGUSR2` changes
to `[colors2]` ([#2144][2144]).
[2140]: https://codeberg.org/dnkl/foot/issues/2140
[2144]: https://codeberg.org/dnkl/foot/issues/2144
### Fixed
* 10-bit surfaces sometimes used instead of 16-bit.
* OSC-104/110/111/112/117/119 (reset colors) not taking the currently
active theme into account.
## 1.23.0
### Added
* `colors2` config section. This section duplicates the `colors`
section, and lets you define an alternative color theme.
* `key-bindings.color-theme-switch-1`,
`key-bindings.color-theme-switch-2` and
`key-bindings.color-theme-toggle` key bindings. These can be used to
switch between the primary and alternative color themes. They are
not bound by default.
* Sending `SIGUSR1` to the foot process now triggers a theme switch
(in server mode, **all** instances toggles their themes).
* Support for private mode 2031 - [_Dark and Light Mode
Detection_](https://contour-terminal.org/vt-extensions/color-palette-update-notifications/)
([#2025][2025])
* Added `initial-color-theme=1|2` config option. `1` uses colors from
the `[colors]` section, `2` uses `[colors2]`.
* Combined dark/light theme files for (dark variant is the default,
set `initial-color-theme=2` to use the light variant by default):
- gruvbox
- nvim
- paper-color
- selenized
- solarized
* `regex-copy`/`show-urls-copy` will copy and paste the selected text if the hint
is completed with an uppercase character ([#1975][1975]).
* `16-bit` to `tweak.surface-bit-depth`. Makes foot use 16-bit image
buffers. They provide the necessary color precision required by
`gamma-correct-blending=yes`.
* New cursor shapes, from `cursor-shape-v1` version 2.
* `center-when-fullscreen` and `center-when-maximized-and-fullscreen`
to the `pad` option. This allows you to configure when the grid is
centered in more detail ([#2111][2111]).
[2025]: https://codeberg.org/dnkl/foot/issues/2025
[1975]: https://codeberg.org/dnkl/foot/issues/1975
[2111]: https://codeberg.org/dnkl/foot/issues/2111
### Changed
* `cursor.color` moved to `colors.cursor`.
* OSC-11 without an alpha value will now restore the configured
(i.e. from `foot.ini`) alpha, rather than keeping whatever the
current alpha value is, unchanged.
* `gamma-correct-blending=yes` now defaults to `16-bit` image buffers,
instead of `10-bit`.
* Allow setting either selection background, or selection foreground,
only ([#1846][1846]).
* Drop required version of libxkbcommon from 1.8.0 back to 1.0.0
([#2103][2103]).
* OSC-52: an empty payload now clears the clipboard.
* DA (Device Attributes): include `52` in the reply, to indicate
OSC-52 support (when at least _copy_ has been enabled in
`security.osc52`).
[1846]: https://codeberg.org/dnkl/foot/issues/1846
[2103]: https://codeberg.org/dnkl/foot/issues/2103
### Deprecated
* `cursor.color` config option; use `colors.cursor` instead.
### Removed
* Subsurface unmap quirk for Sway. This was a workaround added in
1.12.1, for Sway issue [#6960][sway-6960].
### Fixed
* `REP`: wrong width of repeated multi-codepoint graphemes.
* Incorrect surface commit after a configure event, under certain
conditions ([#2105][2105]).
[2105]: https://codeberg.org/dnkl/foot/issues/2105
### Contributors
* Chen Mulong
* Kirill Primak
* Ryan Roden-Corrent
* tokyo4j
## 1.22.3
### Added
* `auto` to the `tweak.surface-bit-depth` option.
### Changed
* `gamma-correct-blending` now defaults to `no` instead of `yes`.
* `tweak.surface-bit-depth` default value changed to `auto`; uses
10-bit surfaces when `gamma-correct-blending=yes`, and 8-bit
surfaces otherwise.
### Fixed
* Inaccurate colors when `gamma-correct-blending=yes` ([#2082][2082]).
[2082]: https://codeberg.org/dnkl/foot/issues/2082
## 1.22.2
### Changed
* `gamma-correct-blending=yes` now uses a pure gamma 2.2 transfer
function, instead of the piece-wise sRGB transfer function, to match
what compositors do.
### Fixed
* Wrong colors when `gamma-correct-blending=yes` (the default when
there is compositor support). Note that some colors will still be
off by a **very** small amount, due to loss of precision when
converting to a linear color space. ([#2035][2035]).
[2035]: https://codeberg.org/dnkl/foot/issues/2035
## 1.22.1
### Fixed
* `colors.alpha-mode=matching` not working as intended.
* Grapheme shaping was allowed to be "enabled" at runtime, even though
disabled at compile time. This caused mis-rendering of certain
codepoints ([#2039][2039]).
* Keyboard modifiers not being reset on keyboard leave events
([#2034][2034]).
* Fallback font (and possibly wrong color) being used when a character
was followed by a zero-width grapheme breaking codepoint (for
example, _LEFT-TO-RIGHT MARK_) ([#2049][2049]).
* Regression: alpha applied to inversed text/selections
([#2073][2073]).
[2039]: https://codeberg.org/dnkl/foot/issues/2039
[2034]: https://codeberg.org/dnkl/foot/issues/2034
[2049]: https://codeberg.org/dnkl/foot/issues/2049
[2073]: https://codeberg.org/dnkl/foot/issues/2073
### Contributors
* Jan Palus
* valoq
## 1.22.0
### Added
* Support for toplevel edge constraints. When the compositor indicates
the toplevel has edge constraints, foot will not allow the window to
be resized (via CSDs) in the constrained directions.
* Virtual modifiers (e.g. `Alt` instead of `Mod1`, `Super` instead of
`Mod4` etc) in key bindings are now recognized as being virtual, and
are automatically mapped to the corresponding real modifier. This
means you can use e.g. `Alt+b` instead of `Mod1+b`.
* `alpha-mode` option to `foot.ini`. Defaults to `default`. This
config changes how alpha is handled on background colours not set by
the terminal.(e.g. vim) ([#2026](2026))
[2026]: https://codeberg.org/dnkl/foot/issues/2026
### Changed
* UTF-8 error recovery now discards fewer bytes.
* Auto-calculated dimmed and brightened colors (e.g. when custom dim
colors has not configured) is now done by linear RGB interpolation,
rather than converting to HSL and adjusting the luminance
([#2006][2006]).
* Virtual modifiers in keyboard events from the compositor are now
supported. This works around various issues seen when running foot
under mutter (GNOME) ([#2009][2009]):
- Some key combinations generating the wrong escape sequence in the
kitty keyboard protocol.
- some of foot's default shortcuts not working (mainly those using
`Mod1`) out of the box.
* Default URL regex changed to a much more strict variant
([#2016][2016]). You can manually set the [old
one](https://codeberg.org/dnkl/foot/src/tag/1.21.0/foot.ini#L72), if
you prefer it over the new regex.
* A tiled window can now be resized in the corners (via CSDs), unless
the compositor has indicated the toplevel has edge constraints.
[2006]: https://codeberg.org/dnkl/foot/issues/2006
[2009]: https://codeberg.org/dnkl/foot/issues/2009
[2016]: https://codeberg.org/dnkl/foot/issues/2016
### Fixed
* Regression: assertion in `url-mode.c` when activating a second URL
via `show-urls-persistent` ([#2000][2000]).
* Build failure (`srgb.h` not found) when doing a parallel build.
* Regression: reflowing (changing the window size) removing empty
lines ([#2011][2011]).
* `url/regex-copy` not handling double-width characters correctly
([#2027][2027]).
[2000]: https://codeberg.org/dnkl/foot/issues/2000
[2011]: https://codeberg.org/dnkl/foot/issues/2011
[2027]: https://codeberg.org/dnkl/foot/issues/2027
### Contributors
* Alex Xu (Hello71)
* datsudo
* Dominique Martinet
* Fazzi
* llyyr
* Łukasz Wojniłowicz
* Sam McCall
## 1.21.0
### Added
* Support for the new Wayland protocol `xdg-system-bell-v1` protocol
(added in wayland-protocols 1.38), via the new config option
`bell.system=no|yes` (defaults to `yes`).
* Support for custom regex matching ([#1386][1386],
[#1872][1872])
* Support for kitty's text-sizing protocol (`w`, width, only), OSC-66.
* `cursor.style` can now be set to `hollow` ([#1965][1965]).
* `search-bindings.delete-to-start` and
`search-bindings.delete-to-end` key bindings, defaulting to
`Control+u` and `Control+k` respectively ([#1972][1972]).
* Gamma-correct font rendering. Requires compositor support
(`wp_color_management_v1`, and specifically, the `ext_linear`
transfer function). Enabled by default when compositor support is
available. Can be explicitly enabled or disabled with
`gamma-correct-blending=no|yes`.
[1386]: https://codeberg.org/dnkl/foot/issues/1386
[1872]: https://codeberg.org/dnkl/foot/issues/1872
[1965]: https://codeberg.org/dnkl/foot/issues/1965
[1972]: https://codeberg.org/dnkl/foot/issues/1972
### Changed
* Do not try to set a zero width, or height, if the compositor sends a
_configure_ event with only one dimension being zero
([#1925][1925]).
* Auto-detection of URLs (i.e. not OSC-8 based URLs) are now regex
based.
* Rename Tokyo Night Day theme to Tokyo Night Light and update colors.
* fcft >= 3.3.1 is now required.
- `tweak.scaling-filter` now supports more scaling-filters
- scaled bitmap fonts (when enabled in FontConfig) no longer have a
scaling-filter applied
* Linefeed:ing control characters (e.g. `\n`) no longer **clears** a
row's internal linebreak flag. This fixes an issue where
e.g. multi-line prompt input in fish is treated as separate lines,
rather than one logical, when selecting and copying it
([#1487][1487]).
* wayland-protocols >= 1.41 is now required.
[1925]: https://codeberg.org/dnkl/foot/issues/1925
[1487]: https://codeberg.org/dnkl/foot/issues/1487
### Removed
* `url.uri-characters` and `url.protocols`. Both options have been
replaced by `url.regex`.
* `notify` option (has been deprecated since 1.18.0).
* `notify-focus-inhibit` option (has been deprecated since 1.18.0).
### Fixed
* Kitty keyboard protocol: alternate key reporting failing to report
the alternate codepoint in some corner cases ([#1918][1918]).
* `foot` and `footclient` hanging, or terminating with `SIGABRT`, when
starting inside a directory whose total length is more than 1024
characters.
* Regression: reflowing (resizing the window) a line that ends with a
double-width glyph that was pushed to the next line due to there
being only one cell left on current line, did not remove the virtual
space inserted at the end of the current line.
* Wrong key bindings executed when using alternative keyboard layouts
([#1929][1929]).
* Foot not closing file descriptors for unrecognized or `no_keymap`
keymaps.
* Combining characters (including emojis consisting of multiple
codepoints) not being handled correctly when _insert mode_ is
enabled ([#1947][1947]).
* Reflow of the cursor (active + saved) when at the end of the line
with a pending wrap (LCF set) ([#1954][1954]).
* ~~Zero-width characters that also are grapheme breaks (e.g. U+200B,
ZERO WIDTH SPACE) being ignored (discarded and never stored in the
grid) ([#1960][1960]).~~ (reverted)
* `--server=<FD>` not working on FreeBSD ([#1956][1956]).
* Crash when resetting the terminal and an application had previously
set a custom app ID ([#1963][1963])
* Grapheme clustering state not reset on cursor movements.
* Kitty keyboard protocol: no release events emitted for composed
keys.
* IME: the initial cursor position was reported as 0,0,0,0
([#1994][1994]).
[1918]: https://codeberg.org/dnkl/foot/issues/1918
[1929]: https://codeberg.org/dnkl/foot/issues/1929
[1947]: https://codeberg.org/dnkl/foot/issues/1947
[1954]: https://codeberg.org/dnkl/foot/issues/1954
[1960]: https://codeberg.org/dnkl/foot/issues/1960
[1956]: https://codeberg.org/dnkl/foot/issues/1956
[1963]: https://codeberg.org/dnkl/foot/issues/1963
[1994]: https://codeberg.org/dnkl/foot/issues/1994
### Contributors
* Adrian fxj9a
* Alexander Orzechowski
* Attila Fidan
* camel-cdr
* Craig Barnes
* Guillaume Outters
* Johannes Altmanninger
* Ludovico Gerardi
* sewn
* Thomas Bonnefille
## 1.20.2
### Changed

View file

@ -53,7 +53,7 @@ decisions when appropriate.
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 truely correct a past
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.

View file

@ -65,7 +65,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator.
* [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
@ -641,6 +641,10 @@ 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
@ -689,8 +693,11 @@ Every now and then I post foot related updates on
# 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

@ -2,7 +2,6 @@
#include <stdio.h>
#include <math.h>
#include <fenv.h>
#include <errno.h>
#define LOG_MODULE "box-drawing"
@ -1462,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;
}

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];
@ -127,6 +135,20 @@ UNITTEST
xassert(c32cmp(dst, U"foobar12345678") == 0);
}
UNITTEST
{
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");

View file

@ -20,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);
}
@ -60,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);
}
@ -72,6 +80,13 @@ 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);

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

113
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,20 +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)
{
@ -84,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"
@ -145,6 +139,10 @@ 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)
{
@ -159,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'},
@ -228,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;
@ -328,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;
@ -399,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;
@ -522,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

@ -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"
@ -40,7 +41,7 @@ _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|--pty|--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
@ -75,7 +76,7 @@ _foot()
COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;;
--log-colorize|-l)
COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;;
--app-id|--help|--override|--pty|--title|--version|--window-size-chars|--window-size-pixels|--check-config|-[ahoTvWwC])
--app-id|--toplevel-tag|--help|--override|--pty|--title|--version|--window-size-chars|--window-size-pixels|--check-config|-[ahoTvWwC])
# Don't autocomplete for these flags
: ;;
*)

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"
@ -35,7 +36,7 @@ _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
@ -67,7 +68,7 @@ _footclient()
COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;;
--log-colorize|-l)
COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;;
--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|-[ahoTvWw])
--app-id|--toplevel-tag|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|-[ahoTvWw])
# Don't autocomplete for these flags
: ;;
*)

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"

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]' \

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]' \

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

884
config.c

File diff suppressed because it is too large Load diff

181
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>
@ -27,7 +28,7 @@ struct font_size_adjustment {
float percent;
};
enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BEAM };
enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BEAM, CURSOR_HOLLOW };
enum cursor_unfocused_style {
CURSOR_UNFOCUSED_UNCHANGED,
CURSOR_UNFOCUSED_HOLLOW,
@ -60,6 +61,7 @@ enum binding_aux_type {
BINDING_AUX_NONE,
BINDING_AUX_PIPE,
BINDING_AUX_TEXT,
BINDING_AUX_REGEX,
};
struct binding_aux {
@ -73,6 +75,8 @@ struct binding_aux {
uint8_t *data;
size_t len;
} text;
char *regex_name;
};
};
@ -120,11 +124,107 @@ struct env_var {
};
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;
@ -135,15 +235,21 @@ 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;
@ -153,6 +259,8 @@ struct config {
enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode;
bool dpi_aware;
bool gamma_correct;
bool uppercase_regex_insert;
struct config_font_list fonts[4];
struct font_size_adjustment font_size_adjustment;
@ -186,6 +294,7 @@ struct config {
bool urgent;
bool notify;
bool flash;
bool system_bell;
struct config_spawn_template command;
bool command_focused;
} bell;
@ -219,58 +328,15 @@ 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 flash;
uint32_t flash_alpha;
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];
uint32_t sixel[16];
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 selection:1;
bool jump_label:1;
bool scrollback_indicator:1;
bool url:1;
bool search_box_no_match:1;
bool search_box_match:1;
uint8_t dim;
} use_custom;
} colors;
struct color_theme colors_dark;
struct color_theme colors_light;
enum which_color_theme initial_color_theme;
struct {
enum cursor_style style;
@ -279,10 +345,6 @@ struct config {
bool enabled;
uint32_t rate_ms;
} blink;
struct {
uint32_t text;
uint32_t cursor;
} color;
struct pt_or_px beam_thickness;
struct pt_or_px underline_thickness;
} cursor;
@ -385,6 +447,9 @@ 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 {

85
csi.c
View file

@ -117,9 +117,9 @@ csi_sgr(struct terminal *term)
style > UNDERLINE_SINGLE;
break;
}
term_update_ascii_printer(term);
}
} else
term->bits_affecting_ascii_printer.underline_style = false;
term_update_ascii_printer(term);
break;
}
case 5: term->vt.attrs.blink = true; break;
@ -422,6 +422,8 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
case 1004:
term->focus_events = enable;
if (enable)
term_to_slave(term, term->kbd_focus ? "\033[I" : "\033[O", 3);
break;
case 1005:
@ -558,7 +560,13 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
break;
case 2027:
#if defined(FOOT_GRAPHEME_CLUSTERING)
term->grapheme_shaping = enable;
#endif
break;
case 2031:
term->report_theme_changes = enable;
break;
case 2048:
@ -655,6 +663,7 @@ decrqm(const struct terminal *term, unsigned param)
case 2027: return term->conf->tweak.grapheme_width_method != GRAPHEME_WIDTH_DOUBLE
? DECRPM_PERMANENTLY_RESET
: decrpm(term->grapheme_shaping);
case 2031: return decrpm(term->report_theme_changes);
case 2048: return decrpm(term->size_notifications);
case 8452: return decrpm(term->sixel.cursor_right_of_graphics);
case 737769: return decrpm(term_ime_is_enabled(term));
@ -700,6 +709,7 @@ xtsave(struct terminal *term, unsigned param)
case 2004: term->xtsave.bracketed_paste = term->bracketed_paste; break;
case 2026: term->xtsave.app_sync_updates = term->render.app_sync_updates.enabled; break;
case 2027: term->xtsave.grapheme_shaping = term->grapheme_shaping; break;
case 2031: term->xtsave.report_theme_changes = term->report_theme_changes; break;
case 2048: term->xtsave.size_notifications = term->size_notifications; break;
case 8452: term->xtsave.sixel_cursor_right_of_graphics = term->sixel.cursor_right_of_graphics; break;
case 737769: term->xtsave.ime = term_ime_is_enabled(term); break;
@ -744,6 +754,7 @@ xtrestore(struct terminal *term, unsigned param)
case 2004: enable = term->xtsave.bracketed_paste; break;
case 2026: enable = term->xtsave.app_sync_updates; break;
case 2027: enable = term->xtsave.grapheme_shaping; break;
case 2031: enable = term->xtsave.report_theme_changes; break;
case 2048: enable = term->xtsave.size_notifications; break;
case 8452: enable = term->xtsave.sixel_cursor_right_of_graphics; break;
case 737769: enable = term->xtsave.ime; break;
@ -790,10 +801,20 @@ csi_dispatch(struct terminal *term, uint8_t final)
int count = vt_param_get(term, 0, 1);
LOG_DBG("REP: '%lc' %d times", (wint_t)term->vt.last_printed, count);
const int width = c32width(term->vt.last_printed);
int width;
if (term->vt.last_printed >= CELL_COMB_CHARS_LO) {
const struct composed *comp = composed_lookup(
term->composed, term->vt.last_printed - CELL_COMB_CHARS_LO);
xassert(comp != NULL);
width = comp->forced_width > 0 ? comp->forced_width : comp->width;
} else
width = c32width(term->vt.last_printed);
if (width > 0) {
for (int i = 0; i < count; i++)
term_print(term, term->vt.last_printed, width);
term_print(term, term->vt.last_printed, width, false);
}
}
break;
@ -831,6 +852,7 @@ csi_dispatch(struct terminal *term, uint8_t final)
* - 22 ANSI color, e.g., VT525.
* - 28 Rectangular editing.
* - 29 ANSI text locator (i.e., DEC Locator mode).
* - 52 Clipboard access
*
* Note: we report ourselves as a VT220, mainly to be able
* to pass parameters, to indicate we support sixel, and
@ -841,13 +863,15 @@ csi_dispatch(struct terminal *term, uint8_t final)
*
* Note: tertiary DA responds with "FOOT".
*/
if (term->conf->tweak.sixel) {
static const char reply[] = "\033[?62;4;22;28c";
term_to_slave(term, reply, sizeof(reply) - 1);
} else {
static const char reply[] = "\033[?62;22;28c";
term_to_slave(term, reply, sizeof(reply) - 1);
}
char reply[32];
int len = snprintf(
reply, sizeof(reply), "\033[?62%s;22;28%sc",
term->conf->tweak.sixel ? ";4" : "",
(term->conf->security.osc52 == OSC52_ENABLED ||
term->conf->security.osc52 == OSC52_COPY_ENABLED ? ";52" : ""));
term_to_slave(term, reply, len);
break;
}
@ -1537,6 +1561,32 @@ csi_dispatch(struct terminal *term, uint8_t final)
break;
}
case 'n': {
const int param = vt_param_get(term, 0, 0);
switch (param) {
case 996: { /* Query current theme mode (see private mode 2031) */
/*
* 1 - dark mode
* 2 - light mode
*
* In foot, the themes aren't necessarily light/dark,
* but by convention, the primary theme is dark, and
* the alternative theme is light.
*/
char reply[16] = {0};
int chars = snprintf(
reply, sizeof(reply),
"\033[?997;%dn",
term->colors.active_theme == COLOR_THEME_DARK ? 1 : 2);
term_to_slave(term, reply, chars);
break;
}
}
break;
}
case 'p': {
/*
* Request status of ECMA-48/"ANSI" private mode (DECRQM
@ -1594,10 +1644,10 @@ csi_dispatch(struct terminal *term, uint8_t final)
* 64 - vt520
* 65 - vt525
*
* Param 2 - firmware version
* xterm uses its version number. We use an xterm
* version number too, since e.g. Emacs uses this to
* determine level of support.
* Param 2 - firmware version xterm uses its version
* number. We do to, in the format "MAJORMINORPATCH",
* where all three version numbers are always two
* digits. So e.g. 1.25.0 is reported as 012500.
*
* We report ourselves as a VT220. This must be
* synchronized with the primary DA response.
@ -1756,7 +1806,8 @@ csi_dispatch(struct terminal *term, uint8_t final)
case 1: /* blinking block */
case 2: /* steady block */
term->cursor_style = CURSOR_BLOCK;
term->cursor_style = term->conf->cursor.style == CURSOR_HOLLOW
? CURSOR_HOLLOW : CURSOR_BLOCK;
break;
case 3: /* blinking underline */

View file

@ -54,7 +54,7 @@ cursor_shape_to_server_shape(enum cursor_shape shape)
}
enum wp_cursor_shape_device_v1_shape
cursor_string_to_server_shape(const char *xcursor)
cursor_string_to_server_shape(const char *xcursor, int bound_version)
{
if (xcursor == NULL)
return 0;
@ -72,7 +72,7 @@ cursor_string_to_server_shape(const char *xcursor)
[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_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"},
@ -94,9 +94,29 @@ cursor_string_to_server_shape(const char *xcursor)
[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;

View file

@ -26,4 +26,4 @@ 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);
const char *xcursor, int bound_version);

1
dcs.c
View file

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

View file

@ -337,6 +337,9 @@ that corresponds to one of the following modes:
| 2027
: contour
: Grapheme cluster processing
| 2031
: contour
: Request color theme updates
| 2048
: TODO
: In-band window resize notifications
@ -657,6 +660,13 @@ manipulation sequences. The generic format is:
: 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
@ -729,7 +739,10 @@ 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] 99 ; _params_ ; _payload_ \\E\\
| \\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).

View file

@ -27,6 +27,9 @@ the foot command line
*-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*).
@ -67,6 +70,11 @@ the foot command line
Value to set the *app-id* property on the Wayland window
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*
are specified, the _last_ one takes precedence.
@ -252,9 +260,6 @@ These keyboard shortcuts affect the search selection:
*ctrl*+*shift*+*left*
Extend current selection to the left to the last word boundary.
*ctrl*+*shift*+*w*
Extend the current selection to the right to the last whitespace.
*shift*+*down*
Extend current selection down one line
@ -301,6 +306,12 @@ These shortcuts affect the search box in scrollback-search mode:
*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*
@ -683,6 +694,21 @@ variables may be defined in *foot.ini*(5).
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

View file

@ -24,12 +24,12 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*.
Options are set using KEY=VALUE pairs:
*\[colors\]*++
*\[colors-dark\]*++
*background=000000*++
*foreground=ffffff*
Empty values (*KEY=*) are not supported. String options do allow the
empty string to be set, but it must be quoted: *KEY=""*)
empty string to be set, but it must be quoted: *KEY=""*
# SECTION: main
@ -58,9 +58,16 @@ empty string to be set, but it must be quoted: *KEY=""*)
- Dina:weight=bold:slant=italic
- Courier New:size=12
- Fantasque Sans Mono:fontfeatures=ss01
- Iosevka:fontfeatures=cv01=1:fontfeatures=cv06=1
- Meslo LG S:size=12, Noto Color Emoji:size=12
- Courier New:pixelsize=8
Be aware that, depending on your setup, there may be global
FontConfig options that overrides options set here. If an option
appears to have no effect, ensure there is no global configuration
file that sets the same option with *assign* or *assign_replace*;
use one of the many *append* or possibly *prepend* modes.
For each option, the first font is the primary font. The remaining
fonts are fallback fonts that will be used whenever a glyph cannot
be found in the primary font.
@ -89,7 +96,7 @@ empty string to be set, but it must be quoted: *KEY=""*)
*font-size-adjustment*
Amount, in _points_, _pixels_ or _percent_, to increment/decrement
the font size when zooming in our out.
the font size when zooming in or out.
Examples:
```
@ -122,7 +129,7 @@ empty string to be set, but it must be quoted: *KEY=""*)
e.g. *line-height=12px*.
*Warning*: when changing the font size at runtime (i.e. zooming in
our out), foot will change the line height by the same
or out), foot will change the line height by the same
percentage. However, due to rounding, it is possible the line
height will be "too small" for some font sizes, causing
e.g. underscores to "disappear".
@ -154,7 +161,7 @@ empty string to be set, but it must be quoted: *KEY=""*)
*underline-offset*
Use a custom offset for underlines. The offset is, by default, in
_points_ and relative the font's baseline. A positive value
_points_ and relative to the font's baseline. A positive value
positions the underline under the baseline, while a negative value
positions it above the baseline.
@ -191,9 +198,49 @@ empty string to be set, but it must be quoted: *KEY=""*)
Default: _unset_
*gamma-correct-blending*
Boolean. When enabled, foot will do gamma-correct blending in
linear color space. This is how font glyphs are supposed to be
rendered, but since nearly no applications or toolkits are doing
it on Linux, the result may not look like you are used to.
Compared to the default (disabled), bright glyphs on a dark
background will appear thicker, and dark glyphs on a light
background will appear thinner.
FreeType can limit the effect of the latter, with a technique
called stem darkening. It is only available for CFF fonts
(OpenType, .otf) and disabled by default (in FreeType). You can
enable it by setting the environment variable
*FREETYPE_PROPERTIES="cff:no-stem-darkening=0"* before starting
foot.
Also be aware that many fonts have been developed on systems that
do not do gamma-correct blending, and may therefore look thicker
than intended when rendered with gamma-correct blending, since the
font designer set the font weight based on incorrect rendering.
In order to represent colors faithfully, higher precision image
buffers are required. By default, foot will use either 16-bit, or
10-bit color channels, depending on availability, when
gamma-correct blending is enabled. However, the high precision
buffers are slow; if you want to use gamma-correct blending, but
prefer speed (throughput and input latency) over accurate colors,
you can force 8-bit color channels by setting
*tweak.surface-bit-depth=8-bit*.
Default: _no_.
*uppercase-regex-insert*
Boolean. When enabled, inputting an uppercase hint character in
*show-urls-copy* or *regex-copy* mode will insert the selected
text into the prompt in addition to copying it to the clipboard.
Default: _yes_
*box-drawings-uses-font-glyphs*
Boolean. When disabled, foot generates box/line drawing characters
itself. The are several advantages to doing this instead of using
itself. There are several advantages to doing this instead of using
font glyphs:
- No antialiasing effects where e.g. line endpoints appear
@ -213,6 +260,7 @@ empty string to be set, but it must be quoted: *KEY=""*)
- U+02500 - U+0259F
- U+02800 - U+028FF
- U+1CD00 - U+1CDE5
- U+1Fb00 - U+1FB9B
Default: _no_.
@ -233,7 +281,7 @@ empty string to be set, but it must be quoted: *KEY=""*)
scaling factor *does* double the font size.
Note that this option typically does not work with bitmap fonts,
which only contains a pre-defined set of sizes, and cannot be
which only contain a pre-defined set of sizes, and cannot be
dynamically scaled. Whichever size (of the available ones) that
best matches the DPI or scaling factor, will be used.
@ -248,18 +296,40 @@ empty string to be set, but it must be quoted: *KEY=""*)
*pad*
Padding between border and glyphs, in pixels (subject to output
scaling), in the form _XxY_.
scaling), in the form
This will add _at least_ X pixels on both the left and right
sides, and Y pixels on the top and bottom sides. The grid content
will be anchored in the top left corner. I.e. if the window
manager forces an odd window size on foot, the additional pixels
will be added to the right and bottom sides.
```
_XxY_ [center | center-when-fullscreen | center-when-maximized-and-fullscreen]
```
or
```
RIGHTxTOPxLEFTxBOTTOM [center | center-when-fullscreen | center-when-maximized-and-fullscreen]
```
To instead center the grid content, append *center* (e.g. *pad=5x5
center*).
- `_XxY_` adds _at least_:
- X pixels on the left and right sides.
- Y pixels on the top and bottom sides.
Default: _0x0_.
- `LEFTxTOPxRIGHTxBOTTOM` adds **at least**:
- LEFT pixels to the left
- TOP pixels to the top
- RIGHT pixels to the right
- BOTTOM pixels to the bottom
When no centering is specified, the grid content is anchored to
the top left corner. I.e. if the window manager forces an odd
window size on foot, the additional pixels will be added to the
right and bottom sides.
If *center* is specified, the grid content is instead
centered. This may cause "jumpiness" when resizing the window.
With *center-when-fullscreen* and
*center-when-maximized-and-fullscreen*, the grid is anchored to
the top left corner, unless the window is maximized, or
fullscreened.
Default: _0x0_ center-when-maximized-and-fullscreen.
*resize-delay-ms*
@ -278,7 +348,7 @@ empty string to be set, but it must be quoted: *KEY=""*)
Emphasis is on _while_ here; as soon as the interactive resize
ends (i.e. when you let go of the window border), the final
dimensions is sent to the client, without any delays.
dimensions are sent to the client, without any delays.
Setting it to 0 disables the delay completely.
@ -293,7 +363,7 @@ empty string to be set, but it must be quoted: *KEY=""*)
as necessary to accommodate window sizes that are not multiples of
the cell size.
This option only applies to floating windows. Sizes of maxmized, tiled
This option only applies to floating windows. Sizes of maximized, tiled
or fullscreen windows will not be constrained to multiples of the cell
size.
@ -311,6 +381,19 @@ empty string to be set, but it must be quoted: *KEY=""*)
Default: _yes_
*initial-color-theme*
Selects which color theme to use, *dark*, or *light*.
*dark* uses the colors defined in the *colors-dark* section, while
*light* uses the colors from the *colors-light* section.
Use the *color-theme-switch-dark*, *color-theme-switch-light* and
*color-theme-toggle* key bindings to switch between the two themes
at runtime, or send SIGUSR1/SIGUSR2 to the foot process (see
*foot*(1) for details).
Default: _dark_
*initial-window-size-pixels*
Initial window width and height in _pixels_ (subject to output
scaling), in the form _WIDTHxHEIGHT_. The height _includes_ the
@ -327,7 +410,7 @@ empty string to be set, but it must be quoted: *KEY=""*)
*initial-window-size-chars*
Initial window width and height in _characters_, in the form
_WIDTHxHEIGHT_. Mutually exclusive to
*initial-window-size-pixels*.'
*initial-window-size-pixels*.
Note that if you have a multi-monitor setup, with different
scaling factors, there is a possibility the window size will not
@ -357,10 +440,15 @@ empty string to be set, but it must be quoted: *KEY=""*)
apply window management rules. Default: _foot_ (normal mode), or
_footclient_ (server mode).
*toplevel-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_
*bold-text-in-bright*
Semi-boolean. When enabled, bold text is rendered in a brighter
color (in addition to using a bold font). The color is brightened
by increasing its luminance.
by blending it with white.
If set to *palette-based*, rather than a simple *yes|true*, colors
matching one of the 8 regular palette colors will be brightened
@ -383,6 +471,10 @@ empty string to be set, but it must be quoted: *KEY=""*)
multithreading. Default: the number of available logical CPUs
(including SMT). Note that this is not always the best value. In
some cases, the number of physical _cores_ is better.
In case you have a ridiculous amount of cores and/or threads,
consider limiting the number of *workers*, since foot cannot
parallelize more than the number of visible rows.
*utmp-helper*
Path to utmp logging helper binary.
@ -445,10 +537,17 @@ Note: do not set *TERM* here; use the *term* option in the main
# SECTION: bell
*system*
Boolean, when set to _yes_, ring the system bell. The bell is rung
independent of whether the foot window has keyboard focus or
not. Exact behavior is compositor dependent.
Default: _yes_
*urgent*
When set to _yes_, foot will signal urgency to the compositor
through the XDG activation protocol whenever *BEL* is received,
and the window does NOT have keyboard focus.
Boolean, when set to _yes_, foot will signal urgency to the
compositor through the XDG activation protocol whenever *BEL* is
received, and the window does NOT have keyboard focus.
If the compositor does not implement this protocol, the margins
will be painted in red instead.
@ -459,25 +558,25 @@ Note: do not set *TERM* here; use the *term* option in the main
Default: _no_
*notify*
When set to _yes_, foot will emit a desktop notification using the
command specified in the *notify* option whenever *BEL* is
received. By default, bell notifications are shown only when the
window does *not* have keyboard focus. See
Boolean, when set to _yes_, foot will emit a desktop notification
using the command specified in the *notify* option whenever *BEL*
is received. By default, bell notifications are shown only when
the window does *not* have keyboard focus. See
_desktop-notifications.inhibit-when-focused_.
Default: _no_
*visual*
When set to _yes_, foot will flash the terminal window. Default:
_no_
Boolean, when set to _yes_, foot will flash the terminal
window. Default: _no_
*command*
When set, foot will execute this command when *BEL* is received.
Default: none
*command-focused*
Whether to run the command on *BEL* even while focused. Default:
_no_
Boolean, whether to run the command on *BEL* even while
focused. Default: _no_
# SECTION: desktop-notifications
@ -500,7 +599,7 @@ Note: do not set *TERM* here; use the *term* option in the main
option, or preferably, by setting the *image-path* hint (with
e.g. notify-send's *--hint* option).
_${category}_ is replaced by the notification's catogory. Can
_${category}_ is replaced by the notification's category. Can
be used together with e.g. notify-send's *--category* option.
_${urgency}_ is replaced with the notifications urgency;
@ -612,7 +711,7 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35
Foot recognizes this as:
- notification has the daemon assigned ID 17
- the user triggered the default action
- the notification send an XDG activation token
- the notification sent an XDG activation token
Example #2:
17++
@ -628,7 +727,7 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35
Foot recognizes this as:
- notification has the daemon assigned ID 17
- the user triggered the first custom action, "1
- the user triggered the first custom action, "1"
Default: _notify-send++
--wait++
@ -672,7 +771,7 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35
least one), *command-action-argument* will be expanded with the
action's name and label.
Then, _${action-argument}_ is expanded *command* to the full list
Then, _${action-argument}_ is expanded in *command* to the full list
of actions.
If *command-action-argument* is set to the empty string, no
@ -748,6 +847,9 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35
# SECTION: url
Note that you can also add custom regular expressions, see the 'regex'
section.
*launch*
Command to execute when opening URLs. _${url}_ will be replaced
with the actual URL. Default: _xdg-open ${url}_.
@ -775,19 +877,48 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35
Default: _sadfjklewcmpgh_.
*protocols*
Comma separated list of protocols (schemes) that should be
recognized in URL mode. Note that only auto-detected URLs are
affected by this option. OSC-8 URLs are always enabled, regardless
of protocol. Default: _http, https, ftp, ftps, file, gemini,
gopher, irc, ircs_.
*uri-characters*
Set of characters allowed in auto-detected URLs. Any character not
included in this set constitutes a URL delimiter.
*regex*
Regular expression to use when auto-detecting URLs. The format is
"POSIX-Extended Regular Expressions". Note that the first marked
subexpression is used as the URL. In other words, if you want the
whole regex match to be used as an URL, surround all of it with
parenthesis: *(regex-pattern)*.
Default:
_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-\_.,~:;/?#@!$&%\*+="'()[]_
Default: _(((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:/?#@!$&\*+,;=.~\_%^\-]\*'))_
# SECTION: regex
Similar to the 'url' mode, but with custom defined regular expressions
(and launchers).
To use a custom defined regular expression, you also need to add a key
binding for it. This is done in the *key-binding* section, see below
for details. For example, a regex to detect hash digests (e.g. git
commit hashes) could look like:
```
[regex:hashes]
regex=([a-fA-F0-9]{7,128})
launch=path-to-script-or-application ${match}
[key-bindings]
regex-launch=[hashes] Control+Shift+q
regex-copy=[hashes] Control+Mod1+Shift+q
```
*launch*
Command to execute when "launching" a regex match. _${match}_ will
be replaced with the actual URL. Default: _not set_.
*regex*
Regular expression to use when matching text. The format is
"POSIX-Extended Regular Expressions". Note that the first marked
subexpression is used as the match. In other words, if you want
the whole regex match to be used, surround all of it with
parenthesis: *(regex-pattern)*.
Default: _not set_.
# SECTION: cursor
@ -796,8 +927,8 @@ applications can change these at runtime.
*style*
Configures the default cursor style, and is one of: *block*,
*beam* or *underline*. Note that this can be overridden by
applications. Default: _block_.
*beam*, *underline* or *hollow*. Note that this can be overridden
by applications. Default: _block_.
*unfocused-style*
Configures how the cursor is rendered when the terminal window is
@ -813,19 +944,10 @@ applications can change these at runtime.
by applications. Related option: *blink-rate*. Default: _no_.
*blink-rate*
The rate at which the cursor blink, when cursor blinking has been
The rate at which the cursor blinks, when cursor blinking has been
enabled. Expressed in milliseconds between each blink. Default:
_500_.
*color*
Two space separated RRGGBB values (i.e. plain old 6-digit hex
values, without prefix) specifying the foreground (text) and
background (cursor) colors for the cursor.
Example: *ff0000 00ff00* (green cursor, red text)
Default: the regular foreground and background colors, reversed.
*beam-thickness*
Thickness (width) of the beam styled cursor. The value is in
points, and its exact value thus depends on the monitor's DPI. To
@ -876,16 +998,34 @@ applications can change these at runtime.
Default: _400_.
# SECTION: colors
# SECTION: colors-dark, colors-light
This section controls the 16 ANSI colors, the default foreground and
background colors, and the extended 256 color palette. Note that
These two sections controls the 16 ANSI colors, the default foreground
and background colors, and the extended 256 color palette. Note that
applications can change these at runtime.
The colors are in RRGGBB format (i.e. plain old 6-digit hex values,
without prefix). That is, they do *not* have an alpha component. You
can configure the background transparency with the _alpha_ option.
*colors-dark* is intended to define a dark color theme, and
*colors-light* is intended to define a light color theme. You can
switch between them using the *color-theme-switch-dark*,
*color-theme-switch-light* and *color-theme-toggle* key bindings, or
by sending SIGUSR1/SIGUSR2 to the foot process.
The default theme used is *colors-dark*, unless
*initial-color-theme=light* has been set.
*cursor*
Two space separated RRGGBB values (i.e. plain old 6-digit hex
values, without prefix) specifying the foreground (text) and
background (cursor) colors for the cursor.
Example: *ff0000 00ff00* (green cursor, red text)
Default: the regular foreground and background colors, reversed.
*foreground*
Default foreground color. This is the color used when no ANSI
color is being used. Default: _839496_.
@ -911,8 +1051,9 @@ can configure the background transparency with the _alpha_ option.
an entry in the color palette. Applications emit them by combining
a color value, and a "dim" attribute.
By default, foot implements this by reducing the luminance of the
current color. This is a generic approach that applies to both
By default, foot implements this by blending the current color
with black or white, depending on what the *dim-blend-towards*
option is set to . This is a generic approach that applies to both
colors from the 256-color palette, as well as 24-bit RGB colors.
You can change this behavior by setting the *dimN* options. When
@ -924,7 +1065,7 @@ can configure the background transparency with the _alpha_ option.
the corresponding *regularN* color will be used.
If the current color does not match any known color, it is dimmed
by reducing the luminance (i.e. the same behavior as if the *dimN*
by blending with black (i.e. the same behavior as if the *dimN*
options are unconfigured). 24-bit RGB colors will typically fall
into this category.
@ -952,10 +1093,40 @@ can configure the background transparency with the _alpha_ option.
Background translucency. A value in the range 0.0-1.0, where 0.0
means completely transparent, and 1.0 is opaque. Default: _1.0_.
*alpha-mode*
Specifies when *alpha* is applied. One of *default*, *matching* or
*all*.
*default* applies *alpha* to cells with the default background
color, excluding cells with the same RGB value as the default
background color.
*matching* is the same as *default*, but also applies *alpha* to
cells with the same RGB value as the default background color.
*all* applies *alpha* to all cells, regardless of background color.
Default: _default_
*blur*
Boolean. When enabled, foot will blur the background (main window
only, not CSDs etc), when it is transparent. This feature requires
the compositor to implement the _ext-background-effect-v1_
protocol (and specifically, the _blur_ effect).
Default: _no_
*dim-blend-towards*
Which color to blend towards when "auto" dimming a color (see
*dim0*..*dim7* above). One of *black* or *white*. Blending towards
black makes the text darker, while blending towards white makes it
whiter (but still dimmer than normal text).
Default: _black_ (*colors-dark*), _white_ (*colors-light*)
*selection-foreground*, *selection-background*
Foreground (text) and background color to use in selected
text. Note that *both* options must be set, or the default will be
used. Default: _inverse foreground/background_.
text. Default: _inverse foreground/background_.
*jump-labels*
Two color values specifying the foreground (text) and background
@ -1035,7 +1206,7 @@ Examples:
*hide-when-maximized*
Boolean. When enabled, the CSD titlebar is hidden when the window
is maximized. The completely disable the titlebar, set *size* to 0
is maximized. To completely disable the titlebar, set *size* to 0
instead. Default: _no_.
*double-click-to-maximize*
@ -1057,8 +1228,8 @@ Examples:
minimize/maximize/close buttons. Default: _26_.
*button-color*
Foreground color on the minimize/maximize/close buttons. Default:
use the default _background_ color.
Foreground color on the minimize/maximize/close buttons and the
titlebar text. Default: use the default _background_ color.
*button-minimize-color*
Minimize button's background color. Default: use the default
@ -1085,11 +1256,42 @@ Note that if *Shift* is one of the modifiers, the _key_ *must not* be
in upper case. For example, *Control+Shift+V* will never trigger, but
*Control+Shift+v* will.
Note that *Alt* is usually called *Mod1*.
The default key bindings all use "real" modifiers (*Mod1*, *Mod4*
etc), but "virtual" modifiers (*Alt*, *Super* etc) are allowed.
*xkbcli interactive-wayland* can be useful for finding keysym names.
A key combination can only be mapped to *one* action. Lets say you
When matching key presses to key bindings, foot uses a couple of
different approaches.
As an example, let's say you press ctrl+shift+c (assume plain us ASCII
layout). XKB will tell foot *Control+C* was pressed. Note the lack of
the shift modifier, and the upper case 'C'. Internally, this is called
the "translated" form.
The "untranslated" form (*Control+Shift+c*) is derived from the
translated form, and is what foot tries to match first.
If no "untranslated" key bindings can be found, foot proceeds to
checking the "translated" variant.
This means you can use either form in your foot configuration, and
that *Control+Shift+c* (and similar) has higher priority than
*Control+C*. Also note that while foot normally detects when the same
combination is assigned to multiple actions, it will not detect
*Control+C* vs. *Control+Shift+c* collisions. Call it a known bug...
Finally, foot tries to match the raw key code. Here, the primary
layout is queried for all key codes that generate a particular XKB
symbol, and the pressed key's code is matched against this. For
example, if you use the layouts *"us,de(neo)"*, the 'r' key generates
the symbol 'c' in the neo layout. I.e. to get a 'c', you press
'r'. The match logic described above will only match 'c' key bindings
(e.g. *Control+Shift+c*). The raw mode however, will match 'r' key
bindings (e.g. *Control+Shift+r*). This is useful for non-latin
layouts, where you would otherwise have to customize all key bindings.
A key combination can only be mapped to *one* action. Let's say you
want to bind *Control+Shift+R* to *fullscreen*. Since this is the
default shortcut for *search-start*, you first need to unmap the
default binding. This can be done by setting _action=none_;
@ -1100,7 +1302,8 @@ e.g. *search-start=none*.
application. Default: _none_.
*scrollback-up-page*
Scrolls up/back one page in history. Default: _Shift+Page\_Up_.
Scrolls up/back one page in history. Default: _Shift+Page\_Up
Shift+KP\_Page\_Up_.
*scrollback-up-half-page*
Scrolls up/back half of a page in history. Default: _none_.
@ -1110,7 +1313,7 @@ e.g. *search-start=none*.
*scrollback-down-page*
Scroll down/forward one page in history. Default:
_Shift+Page\_Down_.
_Shift+Page\_Down Shift+KP\_Page\_Down_.
*scrollback-down-half-page*
Scroll down/forward half of a page in history. Default: _none_.
@ -1199,14 +1402,41 @@ e.g. *search-start=none*.
*show-urls-copy*
Enter URL mode, where all currently visible URLs are tagged with a
jump label with a key sequence that will place the URL in the
clipboard. Default: _none_.
clipboard. If the hint is completed with an uppercase character,
the match will also be pasted. Default: _none_.
*regex-launch*
Enter regex mode. This works exactly the same as URL mode; all
regex matches are tagged with a jump label with a key sequence
that will "launch" to match (and exit regex mode).
The name of the regex section must be specified in the key
binding:
```
[regex:hashes]
regex=([a-fA-F0-9]{7,128})
launch=path-to-script-or-application ${match}
[key-bindings]
regex-launch=[hashes] Control+Shift+q
regex-copy=[hashes] Control+Mod1+Shift+q
```
Default: _none_.
*regex-copy*
Same as *regex-launch*, but the match is placed in the clipboard,
instead of "launched", upon activation. If the hint is completed
with an uppercase character, the match will also be pasted.
Default: _none_.
*prompt-prev*
Jump to the previous, currently not visible, prompt (requires
shell integration, see *foot*(1)). Default: _Control+Shift+z_.
*prompt-next*
Jump the next prompt (requires shell integration, see
Jump to the next prompt (requires shell integration, see
*foot*(1)). Default: _Control+Shift+x_.
*unicode-input*
@ -1235,6 +1465,25 @@ e.g. *search-start=none*.
Default: _Control+Shift+u_.
*color-theme-switch-dark*, *color-theme-switch-light*, *color-theme-toggle*
Switch between the dark color theme (defined in the *colors-dark*
section), and the light color theme (defined in the *colors-light*
section).
*color-theme-switch-dark* applies the dark color theme regardless
of which color theme is currently active.
*color-theme-switch-light* applies the light color theme
regardless of which color theme is currently active.
*color-theme-toggle* toggles between the primary and alternative
color themes.
Note: you can also send SIGUSR1/SIGUSR2 to the foot process to
change the theme (see *foot*(1) for details.)
Default: _none_
*quit*
Quit foot. Default: _none_.
@ -1252,7 +1501,8 @@ scrollback search mode. The syntax is exactly the same as the regular
*commit*
Exit search mode and copy current selection into the _primary
selection_. Viewport is **not** restored. To copy the selection to
the regular _clipboard_, use *Control+Shift+c*. Default: _Return_.
the regular _clipboard_, use *Control+Shift+c*. Default: _Return
KP_Enter_.
*find-prev*
Search **backwards** in the scrollback history for the next
@ -1300,6 +1550,12 @@ scrollback search mode. The syntax is exactly the same as the regular
Deletes the **word after** the cursor. Default: _Mod1+d
Control+Delete_.
*delete-to-start*
Deletes search input before the cursor. Default: _Ctrl+u_.
*delete-to-end*
Deletes search input after the cursor. Default: _Ctrl+k_.
*extend-char*
Extend current selection to the right, by one character. Default:
_Shift+Right_.
@ -1343,7 +1599,8 @@ scrollback search mode. The syntax is exactly the same as the regular
details. Default: _none_.
*scrollback-up-page*
Scrolls up/back one page in history. Default: _Shift+Page\_Up_.
Scrolls up/back one page in history. Default: _Shift+Page\_Up
Shift+KP\_Page\_Up_.
*scrollback-up-half-page*
Scrolls up/back half of a page in history. Default: _none_.
@ -1353,7 +1610,7 @@ scrollback search mode. The syntax is exactly the same as the regular
*scrollback-down-page*
Scroll down/forward one page in history. Default:
_Shift+Page\_Down_.
_Shift+Page\_Down Shift+KP\_Page\_Down_.
*scrollback-down-half-page*
Scroll down/forward half of a page in history. Default: _none_.
@ -1446,7 +1703,7 @@ events never generate a *COUNT* larger than 1. That is,
Foot also recognizes tiltable wheels; to map these, use
*BTN_WHEEL_LEFT* and *BTN_WHEEL_RIGHT*.
A modifier+button combination can only be mapped to *one* action. Lets
A modifier+button combination can only be mapped to *one* action. Let's
say you want to bind *BTN\_MIDDLE* to *fullscreen*. Since
*BTN\_MIDDLE* is the default binding for *primary-paste*, you first
need to unmap the default binding. This can be done by setting
@ -1579,9 +1836,8 @@ any of these options.
*scaling-filter*
Overrides the default scaling filter used when down-scaling bitmap
fonts (e.g. emoji fonts). Possible values are *none*, *nearest*,
*bilinear*, *cubic* or *lanczos3*. *cubic* and *lanczos3* produce
the best results, but are slower (with *lanczos3* being the best
_and_ slowest).
*bilinear*, *impulse*, *box*, *linear*, *cubic*, *gaussian*,
*lanczos2*, *lanczos3* or *lanczos3-stretched*.
Default: _lanczos3_.
@ -1649,8 +1905,8 @@ any of these options.
must be patched to use it.
Until this has happened, foot offers an interim workaround; an
attempt to mitigate the screen flicker *without* affecting neither
performance nor latency.
attempt to mitigate the screen flicker *without* affecting either
performance or latency.
It is based on the fact that the screen is updated at a fixed
interval (typically 60Hz). For us, this means it does not matter
@ -1800,15 +2056,103 @@ any of these options.
Default: _512_. Maximum allowed: _2048_ (2GB).
*min-stride-alignment*
This option controls the minimum stride alignment, in bytes, when
allocating SHM buffers.
In some circumstances, a compositor can import foot's SHM buffers
directly to the GPU, without copying the buffer to GPU memory
(typically on integrated graphics). Different drivers have
different requirements for this, and one of those requirements is
typically the stride alignment. At the time of writing, AMD GPUs
require 256-byte alignment.
Note that doing a direct import typically disables immediate
buffer release (if the compositor supports that), which means foot
has to double buffer. This adds a performance penalty in foot, but
the overall system performance should still be better.
If you are not using integrated graphics, or if the compositor
does not support GPU direct imports, this option has close to zero
impact. You can save a small amount of memory by setting this to
0.
Ultimately, it is up to the compositor to decide whether to do
immediate buffer releases, or try to optimize GPU imports.
Default: _256_
*sixel*
Boolean. When enabled, foot will process sixel images. Default:
_yes_
*dim-amount*
Amount by which dimmed text is darkened. Default: _1.5_.
*bold-text-in-bright-amount*
Amount by which bold fonts are brightened when
*bold-text-in-bright* is set to *yes* (the *palette-based* variant
is not affected by this option). Default: _1.3_.
*surface-bit-depth*
Selects which RGB bit depth to use for image buffers. One of
*auto*, *8-bit*, *10-bit* or *16-bit*.
*auto* chooses bit depth depending on other settings, and
availability.
*8-bit*, uses 8 bits for each color channel, alpha included. This
is the default when *gamma-correct-blending=no*.
*10-bit* uses 10 bits for each RGB channel, and 2 bits for the
alpha channel. Thus, it provides higher precision color channels,
but a lower precision alpha channel.
*16-bit* 16 bits for each color channel, alpha included. If
available, this is the default when *gamma-correct-blending=yes*.
Note that both *10-bit* and *16-bit* are much slower than *8-bit*;
if you want to use gamma-correct blending, and if you prefer speed
(throughput and input latency) over accurate colors, you can set
*surface-bit-depth=8-bit* explicitly.
Default: _auto_
*pre-apply-damage*
Boolean. When enabled, foot will attempt to "pre-apply" the damage
from the last frame when foot is forced to double-buffer
(i.e. when the compositor does not release SHM buffers
immediately). All text after this assumes the compositor is not
releasing buffers immediately.
When this option is disabled, each time foot needs to render a
frame, it has to first copy over areas that changed in the last
frame (i.e. all changes between the last two frames). This is
basically a *memcpy*(3), which can be slow if the changed area is
large. It is also done on the main thread, which means foot cannot
do anything else at the same time; no other rendering, no VT
parsing. After the changes have been brought over to the new
frame, foot proceeds with rendering the cells that has changed
between the last frame and the new frame.
When this option is enabled, the changes between the last two frames
are brought over to what will become the next frame before foot
starts rendering the next frame. As soon as the compositor
releases the previous buffer (typically right after foot has
pushed a new frame), foot kicks off a thread that copies over the
changes to the newly released buffer. Since this is done in a
thread, foot can continue processing input at the same
time. Later, when it is time to render a new frame, the changes
have already been transferred, and foot can immediately start with
the actual rendering.
Thus, having this option enabled improves both performance
(copying the last two frames' changes is threaded), and improves
input latency (rendering the next frame no longer has to first bring
over the changes between the last two frames).
Default: _yes_
# SEE ALSO
*foot*(1), *footclient*(1)

View file

@ -33,6 +33,11 @@ terminal has terminated.
Value to set the *app-id* property on the Wayland window
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_.
@ -189,6 +194,21 @@ variables may be defined in *foot.ini*(5).
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)

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

@ -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,48 +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
}
static inline bool feature_xdg_toplevel_icon(void)
{
#if defined(HAVE_XDG_TOPLEVEL_ICON)
return true;
#else
return false;
#endif
fputs(prefix, stdout);
fputs(version_and_features, stdout);
fputc('\n', stdout);
}

View file

@ -22,11 +22,13 @@
# strikeout-thickness=<font strikeout thickness>
# box-drawings-uses-font-glyphs=no
# 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=0x0 # optionally append 'center'
# pad=0x0 center-when-maximized-and-fullscreen
# resize-by-cells=yes
# resize-keep-grid=yes
# resize-delay-ms=100
@ -38,6 +40,8 @@
# 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
@ -45,6 +49,7 @@
# osc52=enabled # disabled|copy-enabled|paste-enabled|enabled
[bell]
# system=yes
# urgent=no
# notify=no
# visual=no
@ -68,12 +73,22 @@
# 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
@ -86,13 +101,16 @@
[touch]
# long-press-delay=400
[colors]
[colors-dark]
# alpha=1.0
# 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=242424 # black
# regular1=f62b5a # red
@ -114,6 +132,7 @@
# bright7=ffffff # bright white
## dimmed colors (see foot.ini(5) man page)
# dim-blend-towards=black
# dim0=<not set>
# ...
# dim7=<not-set>
@ -150,6 +169,11 @@
# search-box-match=<regular0> <regular3> # black-on-yellow
# urls=<regular3>
[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
@ -166,10 +190,10 @@
# 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
@ -195,12 +219,15 @@
# 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
@ -213,6 +240,8 @@
# delete-prev-word=Mod1+BackSpace Control+BackSpace
# delete-next=Delete
# delete-next-word=Mod1+d Control+Delete
# 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
@ -224,10 +253,10 @@
# clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste
# primary-paste=Shift+Insert
# unicode-input=none
# 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

381
grid.c
View file

@ -16,6 +16,10 @@
#define TIME_REFLOW 0
#if defined(TIME_REFLOW)
#include "misc.h"
#endif
/*
* "sb" (scrollback relative) coordinates
*
@ -36,7 +40,8 @@ grid_row_abs_to_sb(const struct grid *grid, int screen_rows, int abs_row)
return rebased_row;
}
int grid_row_sb_to_abs(const struct grid *grid, int screen_rows, int sb_rel_row)
int
grid_row_sb_to_abs(const struct grid *grid, int screen_rows, int sb_rel_row)
{
const int scrollback_start = grid->offset + screen_rows;
int abs_row = sb_rel_row + scrollback_start;
@ -434,7 +439,7 @@ grid_row_alloc(int cols, bool initialize)
{
struct row *row = xmalloc(sizeof(*row));
row->dirty = false;
row->linebreak = false;
row->linebreak = true;
row->extra = NULL;
row->shell_integration.prompt_marker = false;
row->shell_integration.cmd_start = -1;
@ -496,7 +501,6 @@ grid_resize_without_reflow(
sizeof(struct cell) * min(old_cols, new_cols));
new_row->dirty = old_row->dirty;
new_row->linebreak = false;
new_row->shell_integration.prompt_marker = old_row->shell_integration.prompt_marker;
new_row->shell_integration.cmd_start = min(old_row->shell_integration.cmd_start, new_cols - 1);
new_row->shell_integration.cmd_end = min(old_row->shell_integration.cmd_end, new_cols - 1);
@ -707,7 +711,6 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row,
} else {
/* Scrollback is full, need to reuse a row */
grid_row_reset_extra(new_row);
new_row->linebreak = false;
new_row->shell_integration.prompt_marker = false;
new_row->shell_integration.cmd_start = -1;
new_row->shell_integration.cmd_end = -1;
@ -811,7 +814,7 @@ tp_cmp(const void *_a, const void *_b)
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])
@ -889,6 +892,8 @@ grid_resize_and_reflow(
i, tracking_points[i]->row, tracking_points[i]->col);
}
int coalesced_linebreaks = 0;
/*
* Walk the old grid
*/
@ -930,7 +935,8 @@ grid_resize_and_reflow(
if (!old_row->linebreak && col_count > 0) {
/* Don't truncate logical lines */
col_count = old_cols;
while (col_count < old_cols && old_row->cells[col_count].wc == 0)
col_count++;
}
xassert(col_count >= 0 && col_count <= old_cols);
@ -954,7 +960,7 @@ grid_resize_and_reflow(
/* Does this row have any URIs? */
struct row_range *uri_range, *uri_range_terminator;
struct row_range *underline_range, *underline_range_terminator;
struct row_data *extra = old_row->extra;
const struct row_data *extra = old_row->extra;
if (extra != NULL && extra->uri_ranges.count > 0) {
uri_range = &extra->uri_ranges.v[0];
@ -978,213 +984,177 @@ grid_resize_and_reflow(
} else
underline_range = underline_range_terminator = NULL;
for (int start = 0, left = col_count; left > 0;) {
int end;
bool tp_break = false;
bool uri_break = false;
bool underline_break = false;
bool ftcs_break = false;
/* Figure out where to end this chunk */
{
const int uri_col = uri_range != uri_range_terminator
? ((uri_range->start >= start ? uri_range->start : uri_range->end) + 1)
: INT_MAX;
const int underline_col = underline_range != underline_range_terminator
? ((underline_range->start >= start ? underline_range->start : underline_range->end) + 1)
: INT_MAX;
const int tp_col = tp != NULL ? tp->col + 1 : INT_MAX;
const int ftcs_col = old_row->shell_integration.cmd_start >= start
? old_row->shell_integration.cmd_start + 1
: old_row->shell_integration.cmd_end >= start
? old_row->shell_integration.cmd_end + 1
: INT_MAX;
end = min(col_count, min(min(tp_col, min(uri_col, underline_col)), ftcs_col));
uri_break = end == uri_col;
underline_break = end == underline_col;
tp_break = end == tp_col;
ftcs_break = end == ftcs_col;
if (unlikely(col_count > 0 && coalesced_linebreaks > 0)) {
for (size_t line_no = 0; line_no < coalesced_linebreaks; line_no++) {
/* Erase the remaining cells */
memset(&new_row->cells[new_col_idx], 0,
(new_cols - new_col_idx) * sizeof(new_row->cells[0]));
new_row->linebreak = true;
line_wrap();
}
int cols = end - start;
xassert(cols > 0);
xassert(start + cols <= old_cols);
coalesced_linebreaks = 0;
}
for (int c = 0; c < col_count;) {
const struct cell *old = &old_row->cells[c];
/* Row full, emit newline and get a new, fresh, row */
xassert(new_col_idx <= new_cols);
if (unlikely(new_col_idx >= new_cols))
line_wrap();
char32_t wc = old->wc;
int width = 1;
if (unlikely(wc >= CELL_COMB_CHARS_LO && wc <= CELL_COMB_CHARS_HI)) {
const struct composed *composed =
composed_lookup(term->composed, wc - CELL_COMB_CHARS_LO);
width = composed->forced_width > 0 ? composed->forced_width : composed->width;
} else if (unlikely(c + 1 < col_count && (old + 1)->wc >= CELL_SPACER + 1)) {
/* Wide character, get its width from the next cell's
SPACER value */
width = (old + 1)->wc - CELL_SPACER + 1;
}
/*
* Copy the row chunk to the new grid. Note that there may
* be fewer cells left on the new row than what we have in
* the chunk. I.e. the chunk may have to be split up into
* multiple memcpy:ies.
*/
* Check if character fits, if not, emit spacers, and push
the character to the next row */
if (unlikely(new_col_idx + width > new_cols && width <= new_cols)) {
for (; new_col_idx < new_cols; new_col_idx++) {
new_row->cells[new_col_idx].wc = CELL_SPACER;
new_row->cells[new_col_idx].attrs = (struct attributes){0};
}
line_wrap();
}
for (int count = cols, from = start; count > 0;) {
xassert(new_col_idx <= new_cols);
int new_row_cells_left = new_cols - new_col_idx;
new_row->shell_integration.prompt_marker = old_row->shell_integration.prompt_marker;
/* Row full, emit newline and get a new, fresh, row */
if (new_row_cells_left <= 0) {
line_wrap();
new_row_cells_left = new_cols;
for (int i = 0; i < width; i++) {
if (unlikely(uri_range != NULL && uri_range != uri_range_terminator)) {
if (unlikely(uri_range->start == c)) {
reflow_range_start(
uri_range, ROW_RANGE_URI, new_row, new_col_idx);
}
if (unlikely(uri_range->end == c)) {
reflow_range_end(
uri_range, ROW_RANGE_URI, new_row, new_col_idx);
grid_row_uri_range_destroy(uri_range);
uri_range++;
}
}
/* Number of cells we can copy */
int amount = min(count, new_row_cells_left);
xassert(amount > 0);
if (unlikely(underline_range != NULL && underline_range != underline_range_terminator)) {
if (unlikely(underline_range->start == c)) {
reflow_range_start(
underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx);
}
if (unlikely(underline_range->end == c)) {
reflow_range_end(
underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx);
grid_row_underline_range_destroy(underline_range);
underline_range++;
}
}
if (unlikely(tp != NULL)) {
if (unlikely(tp->col == c)) {
do {
xassert(tp->row == old_row_idx);
tp->row = new_row_idx;
tp->col = new_col_idx;
next_tp++;
tp = *next_tp;
} while (tp->row == old_row_idx && tp->col == c);
if (tp->row != old_row_idx)
tp = NULL;
LOG_DBG("next TP (tp=%p): %dx%d",
(void*)tp, (*next_tp)->row, (*next_tp)->col);
}
}
if (unlikely(old_row->shell_integration.cmd_start == c))
new_row->shell_integration.cmd_start = new_col_idx;
if (unlikely(old_row->shell_integration.cmd_end == c))
new_row->shell_integration.cmd_end = new_col_idx;
if (unlikely(width > new_cols)) {
/* Wide character no longer fits on a row, replace
it with a single space */
new_row->cells[new_col_idx++].wc = 0;
c++;
/* Walk past the SPACER cells */
for (int i = 1; i < width; i++, c++, old++)
;
/* Continue with next character in the *old* grid */
break;
}
new_row->cells[new_col_idx++] = *old;
/*
* If we're going to reach the end of the new row, we
* need to make sure we don't end in the middle of a
* multi-column character.
* TODO: simulate LCF instead?
*
* Rows have linebreak=true by default. This is needed
* for a number of reasons. However, we want non-empty
* rows to have linebreak=false, *until* we reach the
* end of an old row with linebreak=true, at which
* point we set linebreak=true on the new row.
*/
int spacers = 0;
if (new_col_idx + amount >= new_cols) {
/*
* While the cell *after* the last cell is a CELL_SPACER
*
* This means we have a multi-column character
* that doesn't fit on the current row. We need to
* push it to the next row, and insert CELL_SPACER
* cells as padding.
*/
while (
unlikely(
amount > 1 &&
from + amount < old_cols &&
old_row->cells[from + amount].wc >= CELL_SPACER + 1))
{
amount--;
spacers++;
}
xassert(
amount == 1 ||
old_row->cells[from + amount - 1].wc <= CELL_SPACER + 1);
}
xassert(new_col_idx + amount <= new_cols);
xassert(from + amount <= old_cols);
if (from == 0)
new_row->shell_integration.prompt_marker = old_row->shell_integration.prompt_marker;
memcpy(
&new_row->cells[new_col_idx], &old_row->cells[from],
amount * sizeof(struct cell));
count -= amount;
from += amount;
new_col_idx += amount;
xassert(new_col_idx <= new_cols);
if (unlikely(spacers > 0)) {
xassert(new_col_idx + spacers == new_cols);
const struct cell *cell = &old_row->cells[from - 1];
for (int i = 0; i < spacers; i++, new_col_idx++) {
new_row->cells[new_col_idx].wc = CELL_SPACER;
new_row->cells[new_col_idx].attrs = cell->attrs;
}
}
new_row->linebreak = false;
old++;
c++;
}
xassert(new_col_idx > 0);
if (tp_break) {
do {
xassert(tp != NULL);
xassert(tp->row == old_row_idx);
xassert(tp->col == end - 1);
tp->row = new_row_idx;
tp->col = new_col_idx - 1;
next_tp++;
tp = *next_tp;
} while (tp->row == old_row_idx && tp->col == end - 1);
if (tp->row != old_row_idx)
tp = NULL;
LOG_DBG("next TP (tp=%p): %dx%d",
(void*)tp, (*next_tp)->row, (*next_tp)->col);
}
if (uri_break) {
xassert(uri_range != NULL);
if (uri_range->start == end - 1)
reflow_range_start(
uri_range, ROW_RANGE_URI, new_row, new_col_idx - 1);
if (uri_range->end == end - 1) {
reflow_range_end(
uri_range, ROW_RANGE_URI, new_row, new_col_idx - 1);
grid_row_uri_range_destroy(uri_range);
uri_range++;
}
}
if (underline_break) {
xassert(underline_range != NULL);
if (underline_range->start == end - 1)
reflow_range_start(
underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx - 1);
if (underline_range->end == end - 1) {
reflow_range_end(
underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx - 1);
grid_row_underline_range_destroy(underline_range);
underline_range++;
}
}
if (ftcs_break) {
xassert(old_row->shell_integration.cmd_start == start + cols - 1 ||
old_row->shell_integration.cmd_end == start + cols - 1);
if (old_row->shell_integration.cmd_start == start + cols - 1)
new_row->shell_integration.cmd_start = new_col_idx - 1;
if (old_row->shell_integration.cmd_end == start + cols - 1)
new_row->shell_integration.cmd_end = new_col_idx - 1;
}
left -= cols;
start += cols;
}
if (old_row->linebreak) {
/* Erase the remaining cells */
memset(&new_row->cells[new_col_idx], 0,
(new_cols - new_col_idx) * sizeof(new_row->cells[0]));
new_row->linebreak = true;
if (r + 1 < old_rows)
line_wrap();
else if (new_row->extra != NULL) {
if (new_row->extra->uri_ranges.count > 0) {
/*
* line_wrap() "closes" still-open URIs. Since
* this is the *last* row, and since we're
* line-breaking due to a hard line-break (rather
* than running out of cells in the "new_row"),
* there shouldn't be an open URI (it would have
* been closed when we reached the end of the URI
* while reflowing the last "old" row).
*/
int last_idx = new_row->extra->uri_ranges.count - 1;
xassert(new_row->extra->uri_ranges.v[last_idx].end >= 0);
}
if (new_row->extra->underline_ranges.count > 0) {
int last_idx = new_row->extra->underline_ranges.count - 1;
xassert(new_row->extra->underline_ranges.v[last_idx].end >= 0);
if (col_count > 0) {
/* Erase the remaining cells */
memset(&new_row->cells[new_col_idx], 0,
(new_cols - new_col_idx) * sizeof(new_row->cells[0]));
new_row->linebreak = true;
if (r + 1 < old_rows) {
/* Not the last (old) row */
line_wrap();
} else if (new_row->extra != NULL) {
if (new_row->extra->uri_ranges.count > 0) {
/*
* line_wrap() "closes" still-open URIs. Since
* this is the *last* row, and since we're
* line-breaking due to a hard line-break (rather
* than running out of cells in the "new_row"),
* there shouldn't be an open URI (it would have
* been closed when we reached the end of the URI
* while reflowing the last "old" row).
*/
int last_idx = new_row->extra->uri_ranges.count - 1;
xassert(new_row->extra->uri_ranges.v[last_idx].end >= 0);
}
if (new_row->extra->underline_ranges.count > 0) {
int last_idx = new_row->extra->underline_ranges.count - 1;
xassert(new_row->extra->underline_ranges.v[last_idx].end >= 0);
}
}
} else {
/*
* rows have linebreak=true by default. But we don't
* want trailing empty lines to result in actual lines
* in the new grid (think: empty window with prompt at
* the top)
*/
coalesced_linebreaks++;
}
}
@ -1274,15 +1244,26 @@ grid_resize_and_reflow(
saved_cursor.row = min(saved_cursor.row, new_screen_rows - 1);
saved_cursor.col = min(saved_cursor.col, new_cols - 1);
if (grid->cursor.lcf) {
if (cursor.col + 1 < new_cols) {
cursor.col++;
grid->cursor.lcf = false;
}
}
if (grid->saved_cursor.lcf) {
if (saved_cursor.col + 1 < new_cols) {
saved_cursor.col++;
grid->saved_cursor.lcf = false;
}
}
grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)];
xassert(grid->cur_row != NULL);
grid->cursor.point = cursor;
grid->saved_cursor.point = saved_cursor;
grid->cursor.lcf = false;
grid->saved_cursor.lcf = false;
/* Free sixels we failed to "map" to the new grid */
tll_foreach(untranslated_sixels, it)
sixel_destroy(&it->item);

2
grid.h
View file

@ -16,7 +16,7 @@ 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]);

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

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

10
ime.c
View file

@ -68,6 +68,16 @@ enter(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
/* The main grid is the *only* input-receiving surface we have */
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);
}

840
input.c

File diff suppressed because it is too large Load diff

View file

@ -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),
@ -107,6 +162,7 @@ key_binding_new_for_conf(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));
@ -405,18 +461,35 @@ sort_binding_list(key_binding_list_t *list)
}
static xkb_mod_mask_t
mods_to_mask(const struct seat *seat, const config_modifier_list_t *mods)
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) {
xkb_mod_index_t idx = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, it->item);
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;
}
mask |= 1 << idx;
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;
@ -429,7 +502,8 @@ convert_key_binding(struct key_set *set,
{
const struct seat *seat = set->seat;
xkb_mod_mask_t mods = mods_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 = {
@ -487,7 +561,7 @@ convert_mouse_binding(struct key_set *set,
.type = MOUSE_BINDING,
.action = conf_binding->action,
.aux = &conf_binding->aux,
.mods = mods_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,
@ -528,7 +602,8 @@ load_keymap(struct key_set *set)
convert_mouse_bindings(set);
set->public.selection_overrides = mods_to_mask(
set->seat, &set->conf->mouse.selection_override_modifiers);
set->seat, set->vmods, ALEN(set->vmods),
&set->conf->mouse.selection_override_modifiers);
}
void
@ -538,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

@ -41,6 +41,13 @@ enum bind_action_normal {
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,
@ -54,7 +61,7 @@ enum bind_action_normal {
BIND_ACTION_SELECT_QUOTE,
BIND_ACTION_SELECT_ROW,
BIND_ACTION_KEY_COUNT = BIND_ACTION_QUIT + 1,
BIND_ACTION_KEY_COUNT = BIND_ACTION_THEME_TOGGLE + 1,
BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1,
};
@ -82,6 +89,8 @@ 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,

65
main.c
View file

@ -31,7 +31,6 @@
#include "shm.h"
#include "terminal.h"
#include "util.h"
#include "version.h"
#include "xmalloc.h"
#include "xsnprintf.h"
@ -46,19 +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 %ctoplevel-icon %cassertions",
FOOT_VERSION,
feature_pgo() ? '+' : '-',
feature_ime() ? '+' : '-',
feature_graphemes() ? '+' : '-',
feature_xdg_toplevel_icon() ? '+' : '-',
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,6 +84,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"
" -m,--maximized start in maximized mode\n"
" -F,--fullscreen start in fullscreen mode\n"
" -L,--login-shell start shell as a login shell\n"
@ -174,6 +186,7 @@ sanitize_signals(void)
enum {
PTY_OPTION = CHAR_MAX + 1,
TOPLEVEL_TAG_OPTION = CHAR_MAX + 2,
};
int
@ -203,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'},
@ -274,6 +288,10 @@ main(int argc, char *const *argv)
tll_push_back(overrides, xstrjoin("app-id=", optarg));
break;
case TOPLEVEL_TAG_OPTION:
tll_push_back(overrides, xstrjoin("toplevel-tag=", optarg));
break;
case 'D': {
struct stat st;
if (stat(optarg, &st) < 0 || !(st.st_mode & S_IFDIR)) {
@ -378,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':
@ -406,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;
@ -516,7 +534,6 @@ 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_server_socket_path != NULL) {
free(conf.server_socket_path);
@ -551,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;
@ -587,6 +604,7 @@ main(int argc, char *const *argv)
}
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;
@ -625,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 ||
@ -660,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);

View file

@ -1,5 +1,5 @@
project('foot', 'c',
version: '1.20.2',
version: '1.26.1',
license: 'MIT',
meson_version: '>=0.59.0',
default_options: [
@ -12,6 +12,11 @@ is_debug_build = get_option('buildtype').startswith('debug')
cc = meson.get_compiler('c')
# 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>')
@ -25,6 +30,12 @@ if cc.has_function('execvpe',
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()
@ -47,7 +58,7 @@ if utmp_backend == 'none'
elif utmp_backend == 'libutempter'
utmp_add = 'add'
utmp_del = 'del'
utmp_del_have_argument = true
utmp_del_have_argument = false
if utmp_default_helper_path == 'auto'
utmp_default_helper_path = join_paths('/usr', get_option('libdir'), 'utempter', 'utempter')
endif
@ -132,7 +143,7 @@ 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', version: '>=1.32',
wayland_protocols = dependency('wayland-protocols', version: '>=1.41',
fallback: 'wayland-protocols',
default_options: ['tests=false'])
wayland_client = dependency('wayland-client')
@ -145,8 +156,12 @@ if utf8proc.found()
add_project_arguments('-DFOOT_GRAPHEME_CLUSTERING=1', language: 'c')
endif
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.0.1', '<4.0.0'], fallback: 'fcft')
fcft = dependency('fcft', version: ['>=3.3.1', '<4.0.0'], fallback: 'fcft')
wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir')
@ -169,14 +184,18 @@ wl_proto_xml = [
wayland_protocols_datadir / 'unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1
wayland_protocols_datadir / 'staging/cursor-shape/cursor-shape-v1.xml',
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.37')
add_project_arguments('-DHAVE_XDG_TOPLEVEL_ICON', language: 'c')
wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml']
xdg_toplevel_icon = true
else
xdg_toplevel_icon = false
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
@ -219,6 +238,13 @@ emoji_variation_sequences = custom_target(
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',
@ -251,7 +277,7 @@ vtlib = static_library(
'osc.c', 'osc.h',
'sixel.c', 'sixel.h',
'vt.c', 'vt.h',
builtin_terminfo, emoji_variation_sequences,
builtin_terminfo, srgb_funcs,
wl_proto_src + wl_proto_headers,
version,
dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc],
@ -263,6 +289,7 @@ 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,
@ -293,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',
@ -312,7 +339,8 @@ executable(
'url-mode.c', 'url-mode.h',
'user-notification.c', 'user-notification.h',
'wayland.c', 'wayland.h', 'shm-formats.h',
wl_proto_src + wl_proto_headers, version,
'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,
@ -321,7 +349,7 @@ executable(
executable(
'footclient',
'client.c', 'client-protocol.h',
'foot-features.h',
'foot-features.c', 'foot-features.h',
'macros.h',
'util.h',
version,
@ -413,7 +441,6 @@ summary(
'Themes': get_option('themes'),
'IME': get_option('ime'),
'Grapheme clustering': utf8proc.found(),
'Wayland: xdg-toplevel-icon-v1': xdg_toplevel_icon,
'utmp backend': utmp_backend,
'utmp helper default path': utmp_default_helper_path,
'Build terminfo': tic.found(),

View file

@ -114,7 +114,7 @@ consume_stdout(struct notification *notif, bool eof)
while (left > 0) {
line = data;
size_t len = left;
char *eol = memchr(line, '\n', left);
char *eol = (char *)memchr(line, '\n', left);
if (eol != NULL) {
*eol = '\0';

View file

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

228
osc.c
View file

@ -73,16 +73,19 @@ osc_to_clipboard(struct terminal *term, const char *target,
}
char *decoded = base64_decode(base64_data, NULL);
if (decoded == NULL) {
if (errno == EINVAL)
LOG_WARN("OSC: invalid clipboard data: %s", base64_data);
else
LOG_ERRNO("base64_decode() failed");
if (decoded == NULL || decoded[0] == '\0') {
if (decoded == NULL) {
if (errno == EINVAL)
LOG_WARN("OSC: invalid clipboard data: %s", base64_data);
else
LOG_ERRNO("base64_decode() failed");
}
if (to_clipboard)
selection_clipboard_unset(seat);
if (to_primary)
selection_primary_unset(seat);
free(decoded);
return;
}
@ -510,7 +513,7 @@ osc_uri(struct terminal *term, char *string)
key_value = strtok_r(NULL, ":", &ctx))
{
const char *key = key_value;
char *operator = strchr(key_value, '=');
char *operator = (char *)strchr(key_value, '=');
if (operator == NULL)
continue;
@ -522,12 +525,14 @@ osc_uri(struct terminal *term, char *string)
id = sdbm_hash(value);
}
LOG_DBG("OSC-8: URL=%s, id=%" PRIu64, uri, id);
if (uri[0] == '\0')
if (uri[0] == '\0') {
LOG_DBG("OSC-8: close");
term_osc8_close(term);
else
} else {
LOG_DBG("OSC-8: URL=%s, id=%" PRIu64, uri, id);
term_osc8_open(term, id, uri);
}
}
static void
@ -610,7 +615,6 @@ verify_kitty_id_is_valid(const char *id)
}
UNIGNORE_WARNINGS
static void
kitty_notification(struct terminal *term, char *string)
{
@ -1135,6 +1139,134 @@ out:
free(sound_name);
}
static void
kitty_text_size(struct terminal *term, char *string)
{
char *text = strchr(string, ';');
if (text == NULL)
return;
char *parameters = string;
*text = '\0';
text++;
char32_t *wchars = ambstoc32(text);
if (wchars == NULL)
return;
int forced_width = 0;
char *ctx = NULL;
for (char *param = strtok_r(parameters, ":", &ctx);
param != NULL;
param = strtok_r(NULL, ":", &ctx))
{
/* All parameters are on the form X=value, where X is always
exactly one character */
if (param[0] == '\0' || param[1] != '=')
continue;
char *value = &param[2];
switch (param[0]) {
case 'w': {
errno = 0;
char *end = NULL;
unsigned long w = strtoul(value, &end, 10);
if (*end == '\0' && errno == 0 && w <= 7) {
forced_width = (int)w;
break;
} else
LOG_ERR("OSC-66: invalid 'w' value, ignoring");
break;
}
case 's':
case 'n':
case 'd':
case 'v':
LOG_WARN("OSC-66: unsupported: '%c' parameter, ignoring", param[0]);
break;
}
}
const size_t len = c32len(wchars);
if (forced_width == 0) {
/*
* w=0 means we split the text up as we'd normally do... Since
* we don't support any other parameters of the text-sizing
* protocol, that means we just process the string as if it
* has been printed without this OSC.
*/
for (size_t i = 0; i < len; i++)
term_process_and_print_non_ascii(term, wchars[i]);
free(wchars);
return;
}
size_t max_cp_width = 0;
size_t all_cp_width = 0;
for (size_t i = 0; i < len; i++) {
const size_t cp_width = c32width(wchars[i]);
all_cp_width += cp_width;
max_cp_width = max(max_cp_width, cp_width);
}
size_t calculated_width = 0;
switch (term->conf->tweak.grapheme_width_method) {
case GRAPHEME_WIDTH_WCSWIDTH: calculated_width = all_cp_width; break;
case GRAPHEME_WIDTH_MAX: calculated_width = max_cp_width; break;
case GRAPHEME_WIDTH_DOUBLE: calculated_width = min(max_cp_width, 2); break;
}
const size_t width = forced_width == 0 ? calculated_width : forced_width;
LOG_DBG("len=%zu, forced=%d, calculated=%zu, using=%zu",
len, forced_width, calculated_width, width);
#if 0
if (len == 1 && calculated_width == forced_width) {
/*
* Optimization: if there's a single codepoint, and either
* w=0, or the 'w' matches the calculated width, print
* codepoint directly instead of creating a combining
* character.
*/
term_print(term, wchars[0], width);
free(wchars);
return;
}
#endif
uint32_t key = composed_key_from_chars(wchars, len);
const struct composed *composed = composed_lookup_without_collision(
term->composed, &key, wchars, len - 1, wchars[len - 1], forced_width);
if (composed == NULL) {
struct composed *new_cc = xmalloc(sizeof(*new_cc));
new_cc->chars = wchars;
new_cc->count = len;
new_cc->key = key;
new_cc->width = width;
new_cc->forced_width = forced_width;
term->composed_count++;
composed_insert(&term->composed, new_cc);
composed = new_cc;
} else if (composed->width == width) {
free(wchars);
}
term_print(
term, CELL_COMB_CHARS_LO + composed->key,
composed->forced_width > 0 ? composed->forced_width : composed->width,
false);
}
void
osc_dispatch(struct terminal *term)
{
@ -1328,15 +1460,17 @@ osc_dispatch(struct terminal *term)
case 11:
term->colors.bg = color;
if (have_alpha) {
const bool changed = term->colors.alpha != alpha;
term->colors.alpha = alpha;
if (!have_alpha)
alpha = term_theme_get(term)->alpha;
if (changed) {
wayl_win_alpha_changed(term->window);
term_font_subpixel_changed(term);
}
const bool changed = term->colors.alpha != alpha;
term->colors.alpha = alpha;
if (changed) {
wayl_win_alpha_changed(term->window);
term_font_subpixel_changed(term);
}
term_damage_color(term, COLOR_DEFAULT, 0);
term_damage_margins(term);
break;
@ -1348,12 +1482,10 @@ osc_dispatch(struct terminal *term)
case 17:
term->colors.selection_bg = color;
term->colors.use_custom_selection = true;
break;
case 19:
term->colors.selection_fg = color;
term->colors.use_custom_selection = true;
break;
}
@ -1371,6 +1503,10 @@ osc_dispatch(struct terminal *term)
osc_selection(term, string);
break;
case 66: /* text-size protocol (kitty) */
kitty_text_size(term, string);
break;
case 99: /* Kitty notifications */
kitty_notification(term, string);
break;
@ -1378,10 +1514,11 @@ osc_dispatch(struct terminal *term)
case 104: {
/* Reset Color Number 'c' (whole table if no parameter) */
const struct color_theme *theme = term_theme_get(term);
if (string[0] == '\0') {
LOG_DBG("resetting all colors");
for (size_t i = 0; i < ALEN(term->colors.table); i++)
term->colors.table[i] = term->conf->colors.table[i];
memcpy(term->colors.table, theme->table, sizeof(term->colors.table));
term_damage_view(term);
}
@ -1403,7 +1540,7 @@ osc_dispatch(struct terminal *term)
}
LOG_DBG("resetting color #%u", idx);
term->colors.table[idx] = term->conf->colors.table[idx];
term->colors.table[idx] = theme->table[idx];
term_damage_color(term, COLOR_BASE256, idx);
}
@ -1416,16 +1553,20 @@ osc_dispatch(struct terminal *term)
case 110: /* Reset default text foreground color */
LOG_DBG("resetting foreground color");
term->colors.fg = term->conf->colors.fg;
const struct color_theme *theme = term_theme_get(term);
term->colors.fg = theme->fg;
term_damage_color(term, COLOR_DEFAULT, 0);
break;
case 111: { /* Reset default text background color */
LOG_DBG("resetting background color");
bool alpha_changed = term->colors.alpha != term->conf->colors.alpha;
term->colors.bg = term->conf->colors.bg;
term->colors.alpha = term->conf->colors.alpha;
const struct color_theme *theme = term_theme_get(term);
bool alpha_changed = term->colors.alpha != theme->alpha;
term->colors.bg = theme->bg;
term->colors.alpha = theme->alpha;
if (alpha_changed) {
wayl_win_alpha_changed(term->window);
@ -1437,24 +1578,37 @@ osc_dispatch(struct terminal *term)
break;
}
case 112:
case 112: {
LOG_DBG("resetting cursor color");
term->colors.cursor_fg = term->conf->cursor.color.text;
term->colors.cursor_bg = term->conf->cursor.color.cursor;
const struct color_theme *theme = term_theme_get(term);
term->colors.cursor_fg = theme->cursor.text;
term->colors.cursor_bg = theme->cursor.cursor;
if (term->conf->colors_dark.use_custom.cursor) {
term->colors.cursor_fg |= 1u << 31;
term->colors.cursor_bg |= 1u << 31;
}
term_damage_cursor(term);
break;
}
case 117:
case 117: {
LOG_DBG("resetting selection background color");
term->colors.selection_bg = term->conf->colors.selection_bg;
term->colors.use_custom_selection = term->conf->colors.use_custom.selection;
break;
case 119:
LOG_DBG("resetting selection foreground color");
term->colors.selection_fg = term->conf->colors.selection_fg;
term->colors.use_custom_selection = term->conf->colors.use_custom.selection;
const struct color_theme *theme = term_theme_get(term);
term->colors.selection_bg = theme->selection_bg;
break;
}
case 119: {
LOG_DBG("resetting selection foreground color");
const struct color_theme *theme = term_theme_get(term);
term->colors.selection_fg = theme->selection_fg;
break;
}
case 133:
/*

View file

@ -74,6 +74,8 @@ 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)
{
@ -101,6 +103,7 @@ 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; }
pid_t
@ -127,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)
{
@ -194,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;
}

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

@ -67,6 +67,7 @@ quirk_weston_csd_off(struct terminal *term)
quirk_weston_subsurface_desync_off(term->window->csd.surface[i].sub);
}
#if 0
static bool
is_sway(void)
{
@ -82,12 +83,4 @@ is_sway(void)
return is_sway;
}
void
quirk_sway_subsurface_unmap(struct terminal *term)
{
if (!is_sway())
return;
wl_surface_damage_buffer(term->window->surface.surf, 0, 0, INT32_MAX, INT32_MAX);
}
#endif

View file

@ -21,5 +21,3 @@ void quirk_weston_subsurface_desync_off(struct wl_subsurface *sub);
/* Shortcuts to call desync_{on,off} on all CSD subsurfaces */
void quirk_weston_csd_on(struct terminal *term);
void quirk_weston_csd_off(struct terminal *term);
void quirk_sway_subsurface_unmap(struct terminal *term);

898
render.c

File diff suppressed because it is too large Load diff

View file

@ -47,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):
@ -254,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
@ -53,7 +63,7 @@ class StringCapability(Capability):
# see terminfo(5) for valid escape sequences
# Control characters
def translate_ctrl_chr(m):
def translate_ctrl_chr(m: re.Match[str]) -> str:
ctrl = m.group(1)
if ctrl == '?':
return '\\x7f'
@ -85,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:
@ -96,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'))
@ -120,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>.+?),)|'
@ -147,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
@ -166,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)
@ -185,8 +201,9 @@ def main():
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)
@ -210,4 +227,4 @@ def main():
if __name__ == '__main__':
sys.exit(main())
main()

View file

@ -1,11 +1,10 @@
#!/usr/bin/env python3
import argparse
import sys
class Codepoint:
def __init__(self, start: int, end: None|int = None):
def __init__(self, start: int, end: None | int = None):
self.start = start
self.end = start if end is None else end
self.vs15 = False
@ -15,7 +14,7 @@ class Codepoint:
return f'{self.start:x}-{self.end:x}, vs15={self.vs15}, vs16={self.vs16}'
def main():
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('input', type=argparse.FileType('r'))
parser.add_argument('output', type=argparse.FileType('w'))
@ -100,4 +99,4 @@ def main():
if __name__ == '__main__':
sys.exit(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()

View file

@ -283,8 +283,13 @@ 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 + composed->count > term->search.len)
@ -1265,6 +1270,29 @@ execute_binding(struct seat *seat, struct terminal *term,
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));
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)) {
@ -1388,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 == (mods & ~consumed)) {
if (execute_binding(seat, term, bind, serial,
&update_search_result, &search_direction,
&redraw))
{
goto update_search;
}
return;
}
if (bind->mods != mods)
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,
@ -1420,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,
@ -1442,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

@ -19,6 +19,7 @@
#include "char32.h"
#include "commands.h"
#include "config.h"
#include "debug.h"
#include "extract.h"
#include "grid.h"
#include "misc.h"
@ -558,9 +559,15 @@ selection_find_quote_left(struct terminal *term, struct coord *pos,
if (*quote_char == '\0' ? (wc == '"' || wc == '\'')
: wc == *quote_char)
{
pos->row = next_row;
pos->col = next_col + 1;
xassert(pos->col < term->cols);
xassert(next_col + 1 <= term->cols);
if (next_col + 1 == term->cols) {
xassert(next_row < pos->row);
pos->row = next_row + 1;
pos->col = 0;
} else {
pos->row = next_row;
pos->col = next_col + 1;
}
*quote_char = wc;
return true;

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>
@ -23,11 +24,13 @@
#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;
@ -153,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) {
@ -211,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);
@ -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);

View file

@ -117,5 +117,22 @@ static const struct shm_formats {
{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

169
shm.c
View file

@ -13,7 +13,6 @@
#include <pixman.h>
#include <fcft/stride.h>
#include <tllist.h>
#define LOG_MODULE "shm"
@ -21,6 +20,7 @@
#include "log.h"
#include "debug.h"
#include "macros.h"
#include "stride.h"
#include "xmalloc.h"
#if !defined(MAP_UNINITIALIZED)
@ -61,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;
@ -82,9 +84,11 @@ struct buffer_private {
struct buffer_pool *pool;
off_t offset; /* Offset into memfd where data begins */
size_t size;
bool with_alpha;
bool scrollable;
void (*release_cb)(struct buffer *buf, void *data);
void *cb_data;
};
struct buffer_chain {
@ -92,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;
@ -107,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)
{
@ -115,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);
@ -217,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);
}
}
}
@ -224,7 +245,6 @@ static const struct wl_buffer_listener buffer_listener = {
.release = &buffer_release,
};
#if __SIZEOF_POINTER__ == 8
static size_t
page_size(void)
{
@ -241,7 +261,6 @@ page_size(void)
xassert(size > 0);
return size;
}
#endif
static bool
instantiate_offset(struct buffer_private *buf, off_t new_offset)
@ -262,7 +281,7 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset)
wl_buf = wl_shm_pool_create_buffer(
pool->wl_pool, new_offset,
buf->public.width, buf->public.height, buf->public.stride,
buf->with_alpha ? WL_SHM_FORMAT_ARGB8888 : WL_SHM_FORMAT_XRGB8888);
buf->chain->shm_format);
if (wl_buf == NULL) {
LOG_ERR("failed to create SHM buffer");
@ -272,9 +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(
buf->with_alpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
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;
@ -306,8 +326,7 @@ err:
static void NOINLINE
get_new_buffers(struct buffer_chain *chain, size_t count,
int widths[static count], int heights[static count],
struct buffer *bufs[static count], bool with_alpha,
bool immediate_purge)
struct buffer *bufs[static count], bool immediate_purge)
{
xassert(count == 1 || !chain->scrollable);
/*
@ -326,7 +345,14 @@ 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(
with_alpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8, widths[i]);
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];
}
@ -368,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
@ -380,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);
@ -412,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;
@ -477,11 +509,12 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
.chain = chain,
.ref_count = immediate_purge ? 0 : 1,
.busy = true,
.with_alpha = with_alpha,
.pool = pool,
.offset = 0,
.size = sizes[i],
.scrollable = chain->scrollable,
.release_cb = chain->release_cb,
.cb_data = chain->cb_data,
};
if (!instantiate_offset(buf, offset)) {
@ -547,13 +580,13 @@ shm_did_not_use_buf(struct buffer *_buf)
void
shm_get_many(struct buffer_chain *chain, size_t count,
int widths[static count], int heights[static count],
struct buffer *bufs[static count], bool with_alpha)
struct buffer *bufs[static count])
{
get_new_buffers(chain, count, widths, heights, bufs, with_alpha, true);
get_new_buffers(chain, count, widths, heights, bufs, true);
}
struct buffer *
shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alpha)
shm_get_buffer(struct buffer_chain *chain, int width, int height)
{
LOG_DBG(
"chain=%p: looking for a reusable %dx%d buffer "
@ -564,9 +597,7 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph
tll_foreach(chain->bufs, it) {
struct buffer_private *buf = it->item;
if (buf->public.width != width || buf->public.height != height ||
with_alpha != buf->with_alpha)
{
if (buf->public.width != width || buf->public.height != height) {
LOG_DBG("purging mismatching buffer %p", (void *)buf);
if (buffer_unref_no_remove_from_chain(buf))
tll_remove(chain->bufs, it);
@ -582,9 +613,9 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph
else
#endif
{
if (cached == NULL)
if (cached == NULL) {
cached = buf;
else {
} else {
/* We have multiple buffers eligible for
* reuse. Pick the "youngest" one, and mark the
* other one for purging */
@ -608,7 +639,7 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph
}
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;
for (size_t i = 0; i < cached->public.pix_instances; i++)
pixman_region32_clear(&cached->public.dirty[i]);
@ -617,7 +648,7 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph
}
struct buffer *ret;
get_new_buffers(chain, 1, &width, &height, &ret, with_alpha, false);
get_new_buffers(chain, 1, &width, &height, &ret, false);
return ret;
}
@ -959,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;
}
@ -986,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;
}

17
shm.h
View file

@ -9,6 +9,9 @@
#include <tllist.h>
#include "config.h"
#include "wayland.h"
struct damage;
struct buffer {
@ -39,13 +42,20 @@ struct buffer {
};
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.
*
@ -55,8 +65,7 @@ void shm_chain_free(struct buffer_chain *chain);
*
* A newly allocated buffer has an age of 1234.
*/
struct buffer *shm_get_buffer(
struct buffer_chain *chain, int width, int height, bool with_alpha);
struct buffer *shm_get_buffer(struct buffer_chain *chain, int width, int height);
/*
* Returns many buffers, described by 'info', all sharing the same SHM
* buffer pool.
@ -74,7 +83,7 @@ struct buffer *shm_get_buffer(
void shm_get_many(
struct buffer_chain *chain, size_t count,
int widths[static count], int heights[static count],
struct buffer *bufs[static count], bool with_alpha);
struct buffer *bufs[static count]);
void shm_did_not_use_buf(struct buffer *buf);

115
sixel.c
View file

@ -10,6 +10,7 @@
#include "grid.h"
#include "hsl.h"
#include "render.h"
#include "srgb.h"
#include "util.h"
#include "xmalloc.h"
#include "xsnprintf.h"
@ -19,6 +20,40 @@ static size_t count;
static void sixel_put_generic(struct terminal *term, uint8_t c);
static void sixel_put_ar_11(struct terminal *term, uint8_t c);
static uint32_t
color_decode_srgb(const struct terminal *term, uint16_t r, uint16_t g, uint16_t b)
{
if (term->sixel.linear_blending) {
if (term->sixel.use_10bit) {
r = srgb_decode_8_to_16(r) >> 6;
g = srgb_decode_8_to_16(g) >> 6;
b = srgb_decode_8_to_16(b) >> 6;
} else {
r = srgb_decode_8_to_8(r);
g = srgb_decode_8_to_8(g);
b = srgb_decode_8_to_8(b);
}
} else {
if (term->sixel.use_10bit) {
r <<= 2;
g <<= 2;
b <<= 2;
}
}
uint32_t color;
if (term->sixel.use_10bit) {
if (PIXMAN_FORMAT_TYPE(term->sixel.pixman_fmt) == PIXMAN_TYPE_ARGB)
color = 0x3u << 30 | r << 20 | g << 10 | b;
else
color = 0x3u << 30 | b << 20 | g << 10 | r;
} else
color = 0xffu << 24 | r << 16 | g << 8 | b;
return color;
}
void
sixel_fini(struct terminal *term)
{
@ -75,6 +110,34 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
term->sixel.image.height = 0;
term->sixel.image.alloc_height = 0;
term->sixel.image.bottom_pixel = 0;
term->sixel.linear_blending = wayl_do_linear_blending(term->wl, term->conf);
term->sixel.pixman_fmt = PIXMAN_a8r8g8b8;
/*
* Use higher-precision sixel surfaces if we're using
* higher-precision window surfaces.
*
* This is to a) get more accurate colors when doing gamma-correct
* blending, and b) use the same pixman format as the main
* surfaces, for (hopefully) better performance.
*
* For now, don't support 16-bit surfaces (too much sixel logic
* that assumes 32-bit pixels).
*/
if (shm_chain_bit_depth(term->render.chains.grid) >= SHM_BITS_10) {
if (term->wl->shm_have_argb2101010) {
term->sixel.use_10bit = true;
term->sixel.pixman_fmt = PIXMAN_a2r10g10b10;
}
else if (term->wl->shm_have_abgr2101010) {
term->sixel.use_10bit = true;
term->sixel.pixman_fmt = PIXMAN_a2b10g10r10;
}
}
const size_t active_palette_entries = min(
ALEN(term->conf->colors_dark.sixel), term->sixel.palette_size);
if (term->sixel.use_private_palette) {
xassert(term->sixel.private_palette == NULL);
@ -82,21 +145,36 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
term->sixel.palette_size, sizeof(term->sixel.private_palette[0]));
memcpy(
term->sixel.private_palette, term->conf->colors.sixel,
min(sizeof(term->conf->colors.sixel),
term->sixel.palette_size * sizeof(term->sixel.private_palette[0])));
term->sixel.private_palette, term->conf->colors_dark.sixel,
active_palette_entries * sizeof(term->sixel.private_palette[0]));
if (term->sixel.linear_blending || term->sixel.use_10bit) {
for (size_t i = 0; i < active_palette_entries; i++) {
uint8_t r = (term->sixel.private_palette[i] >> 16) & 0xff;
uint8_t g = (term->sixel.private_palette[i] >> 8) & 0xff;
uint8_t b = (term->sixel.private_palette[i] >> 0) & 0xff;
term->sixel.private_palette[i] = color_decode_srgb(term, r, g, b);
}
}
term->sixel.palette = term->sixel.private_palette;
} else {
if (term->sixel.shared_palette == NULL) {
term->sixel.shared_palette = xcalloc(
term->sixel.palette_size, sizeof(term->sixel.shared_palette[0]));
memcpy(
term->sixel.shared_palette, term->conf->colors.sixel,
min(sizeof(term->conf->colors.sixel),
term->sixel.palette_size * sizeof(term->sixel.shared_palette[0])));
term->sixel.shared_palette, term->conf->colors_dark.sixel,
active_palette_entries * sizeof(term->sixel.shared_palette[0]));
if (term->sixel.linear_blending || term->sixel.use_10bit) {
for (size_t i = 0; i < active_palette_entries; i++) {
uint8_t r = (term->sixel.private_palette[i] >> 16) & 0xff;
uint8_t g = (term->sixel.private_palette[i] >> 8) & 0xff;
uint8_t b = (term->sixel.private_palette[i] >> 0) & 0xff;
term->sixel.private_palette[i] = color_decode_srgb(term, r, g, b);
}
}
} else {
/* Shared palette - do *not* reset palette for new sixels */
}
@ -488,7 +566,7 @@ blend_new_image_over_old(const struct terminal *term,
int stride = new_width * sizeof(uint32_t);
uint32_t *new_data = xmalloc(stride * new_height);
pixman_image_t *pix2 = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, new_width, new_height, new_data, stride);
term->sixel.pixman_fmt, new_width, new_height, new_data, stride);
#if defined(_DEBUG)
/* Fill new image with an easy-to-recognize color (green) */
@ -651,8 +729,7 @@ sixel_overwrite(struct terminal *term, struct sixel *six,
}
pixman_image_t *new_pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8,
new_width, new_height, new_data, new_width * sizeof(uint32_t));
term->sixel.pixman_fmt, new_width, new_height, new_data, new_width * sizeof(uint32_t));
struct sixel new_six = {
.pix = NULL,
@ -948,7 +1025,7 @@ sixel_sync_cache(const struct terminal *term, struct sixel *six)
uint8_t *scaled_data = xmalloc(scaled_height * scaled_stride);
pixman_image_t *scaled_pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, scaled_width, scaled_height,
term->sixel.pixman_fmt, scaled_width, scaled_height,
(uint32_t *)scaled_data, scaled_stride);
pixman_image_composite32(
@ -1232,7 +1309,7 @@ sixel_unhook(struct terminal *term)
image.pos.row, image.pos.row + image.rows);
image.original.pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, image.original.width, image.original.height,
term->sixel.pixman_fmt, image.original.width, image.original.height,
img_data, stride);
pixel_row_idx += height;
@ -1482,6 +1559,9 @@ resize(struct terminal *term, int new_width_mutable, int new_height_mutable)
new_height_mutable = term->sixel.max_height;
}
if (unlikely(new_height_mutable == 0)) {
new_height_mutable = 6 * term->sixel.pan;
}
uint32_t *old_data = term->sixel.image.data;
const int old_width = term->sixel.image.width;
@ -2006,15 +2086,14 @@ decgci(struct terminal *term, uint8_t c)
}
case 2: { /* RGB */
uint8_t r = 255 * min(c1, 100) / 100;
uint8_t g = 255 * min(c2, 100) / 100;
uint8_t b = 255 * min(c3, 100) / 100;
uint16_t r = 255 * min(c1, 100) / 100;
uint16_t g = 255 * min(c2, 100) / 100;
uint16_t b = 255 * min(c3, 100) / 100;
LOG_DBG("setting palette #%d = RGB %hhu/%hhu/%hhu",
LOG_DBG("setting palette #%d = RGB %hu/%hu/%hu",
term->sixel.color_idx, r, g, b);
term->sixel.palette[term->sixel.color_idx] =
0xffu << 24 | r << 16 | g << 8 | b;
term->sixel.palette[term->sixel.color_idx] = color_decode_srgb(term, r, g, b);
break;
}
}

50
slave.c
View file

@ -436,8 +436,54 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
add_to_env(&custom_env, "COLORTERM", "truecolor");
add_to_env(&custom_env, "PWD", cwd);
del_from_env(&custom_env, "TERM_PROGRAM");
del_from_env(&custom_env, "TERM_PROGRAM_VERSION");
del_from_env(&custom_env, "TERM_PROGRAM"); /* Wezterm, Ghostty */
del_from_env(&custom_env, "TERM_PROGRAM_VERSION"); /* Wezterm, Ghostty */
del_from_env(&custom_env, "TERMINAL_NAME"); /* Contour */
del_from_env(&custom_env, "TERMINAL_VERSION_STRING"); /* Contour */
del_from_env(&custom_env, "TERMINAL_VERSION_TRIPLE"); /* Contour */
/* XTerm specific */
del_from_env(&custom_env, "XTERM_SHELL");
del_from_env(&custom_env, "XTERM_VERSION");
del_from_env(&custom_env, "XTERM_LOCALE");
/* Mlterm specific */
del_from_env(&custom_env, "MLTERM");
/* Zutty specific */
del_from_env(&custom_env, "ZUTTY_VERSION");
/* Ghostty specific */
del_from_env(&custom_env, "GHOSTTY_BIN_DIR");
del_from_env(&custom_env, "GHOSTTY_SHELL_INTEGRATION_NO_SUDO");
del_from_env(&custom_env, "GHOSTTY_RESOURCES_DIR");
/* Kitty specific */
del_from_env(&custom_env, "KITTY_WINDOW_ID");
del_from_env(&custom_env, "KITTY_PID");
del_from_env(&custom_env, "KITTY_PUBLIC_KEY");
del_from_env(&custom_env, "KITTY_INSTALLATION_DIR");
/* Contour specific */
del_from_env(&custom_env, "CONTOUR_PROFILE");
/* Wezterm specific */
del_from_env(&custom_env, "WEZTERM_PANE");
del_from_env(&custom_env, "WEZTERM_EXECUTABLE");
del_from_env(&custom_env, "WEZTERM_CONFIG_FILE");
del_from_env(&custom_env, "WEZTERM_EXECUTABLE_DIR");
del_from_env(&custom_env, "WEZTERM_UNIX_SOCKET");
del_from_env(&custom_env, "WEZTERM_CONFIG_DIR");
/* Alacritty specific */
del_from_env(&custom_env, "ALACRITTY_LOG");
del_from_env(&custom_env, "ALACRITTY_WINDOW_ID");
del_from_env(&custom_env, "ALACRITTY_SOCKET");
/* VTE, gnome-terminal, kgx etc */
del_from_env(&custom_env, "VTE_VERSION");
del_from_env(&custom_env, "GNOME_TERMINAL_SERVICE");
del_from_env(&custom_env, "GNOME_TERMINAL_SCREEN");
#if defined(FOOT_TERMINFO_PATH)
add_to_env(&custom_env, "TERMINFO", FOOT_TERMINFO_PATH);

View file

@ -27,6 +27,7 @@
#include "commands.h"
#include "config.h"
#include "debug.h"
#include "emoji-variation-sequences.h"
#include "extract.h"
#include "grid.h"
#include "ime.h"
@ -119,7 +120,10 @@ term_to_slave(struct terminal *term, const void *data, size_t len)
return false;
}
if (tll_length(term->ptmx_buffers) > 0 || term->is_sending_paste_data) {
if (unlikely(tll_length(term->ptmx_buffers) > 0 ||
term->is_sending_paste_data ||
tll_length(term->ptmx_paste_buffers) > 0))
{
/*
* Don't even try to send data *now* if there's queued up
* data, since that would result in events arriving out of
@ -198,7 +202,7 @@ add_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx)
return true;
char *const argv[] = {conf->utmp_helper_path, UTMP_ADD, getenv("WAYLAND_DISPLAY"), NULL};
return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL, NULL, NULL) >= 0;
return spawn(reaper, NULL, argv, ptmx, -1, -1, NULL, NULL, NULL) >= 0;
#else
return true;
#endif
@ -222,7 +226,7 @@ del_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx)
;
char *const argv[] = {conf->utmp_helper_path, UTMP_DEL, del_argument, NULL};
return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL, NULL, NULL) >= 0;
return spawn(reaper, NULL, argv, ptmx, -1, -1, NULL, NULL, NULL) >= 0;
#else
return true;
#endif
@ -387,12 +391,16 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
bool
term_ptmx_pause(struct terminal *term)
{
if (term->ptmx < 0)
return false;
return fdm_event_del(term->fdm, term->ptmx, EPOLLIN);
}
bool
term_ptmx_resume(struct terminal *term)
{
if (term->ptmx < 0)
return false;
return fdm_event_add(term->fdm, term->ptmx, EPOLLIN);
}
@ -714,6 +722,9 @@ initialize_render_workers(struct terminal *term)
goto err_sem_destroy;
}
mtx_init(&term->render.workers.preapplied_damage.lock, mtx_plain);
cnd_init(&term->render.workers.preapplied_damage.cond);
term->render.workers.threads = xcalloc(
term->render.workers.count, sizeof(term->render.workers.threads[0]));
@ -986,6 +997,7 @@ struct font_load_data {
const char **names;
const char *attrs;
const struct fcft_font_options *options;
struct fcft_font **font;
};
@ -993,7 +1005,8 @@ static int
font_loader_thread(void *_data)
{
struct font_load_data *data = _data;
*data->font = fcft_from_name(data->count, data->names, data->attrs);
*data->font = fcft_from_name2(
data->count, data->names, data->attrs, data->options);
return *data->font != NULL;
}
@ -1060,14 +1073,34 @@ reload_fonts(struct terminal *term, bool resize_grid)
[1] = xstrjoin(dpi, !custom_bold ? ":weight=bold" : ""),
[2] = xstrjoin(dpi, !custom_italic ? ":slant=italic" : ""),
[3] = xstrjoin(dpi, !custom_bold_italic ? ":weight=bold:slant=italic" : ""),
};
};
struct fcft_font_options *options = fcft_font_options_create();
options->scaling_filter = conf->tweak.fcft_filter;
options->color_glyphs.format = PIXMAN_a8r8g8b8;
options->color_glyphs.srgb_decode =
wayl_do_linear_blending(term->wl, term->conf);
if (shm_chain_bit_depth(term->render.chains.grid) >= SHM_BITS_10) {
/*
* Use a high-res buffer type for emojis. We don't want to use
* an a2r10g0b10 type of surface, since we need more than 2
* bits for alpha.
*/
#if defined(HAVE_PIXMAN_RGBA_16)
options->color_glyphs.format = PIXMAN_a16b16g16r16;
#else
options->color_glyphs.format = PIXMAN_rgba_float;
#endif
}
struct fcft_font *fonts[4];
struct font_load_data data[4] = {
{count_regular, names_regular, attrs[0], &fonts[0]},
{count_bold, names_bold, attrs[1], &fonts[1]},
{count_italic, names_italic, attrs[2], &fonts[2]},
{count_bold_italic, names_bold_italic, attrs[3], &fonts[3]},
{count_regular, names_regular, attrs[0], options, &fonts[0]},
{count_bold, names_bold, attrs[1], options, &fonts[1]},
{count_italic, names_italic, attrs[2], options, &fonts[2]},
{count_bold_italic, names_bold_italic, attrs[3], options, &fonts[3]},
};
thrd_t tids[4] = {0};
@ -1092,6 +1125,8 @@ reload_fonts(struct terminal *term, bool resize_grid)
success = false;
}
fcft_font_options_destroy(options);
for (size_t i = 0; i < 4; i++) {
for (size_t j = 0; j < counts[i]; j++)
free(names[i][j]);
@ -1232,6 +1267,19 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
goto err;
}
const enum shm_bit_depth desired_bit_depth =
conf->tweak.surface_bit_depth == SHM_BITS_AUTO
? wayl_do_linear_blending(wayl, conf) ? SHM_BITS_16 : SHM_BITS_8
: conf->tweak.surface_bit_depth;
const struct color_theme *theme = NULL;
switch (conf->initial_color_theme) {
case COLOR_THEME_DARK: theme = &conf->colors_dark; break;
case COLOR_THEME_LIGHT: theme = &conf->colors_light; break;
case COLOR_THEME_1: BUG("COLOR_THEME_1 should not be used"); break;
case COLOR_THEME_2: BUG("COLOR_THEME_2 should not be used"); break;
}
/* Initialize configure-based terminal attributes */
*term = (struct terminal) {
.fdm = fdm,
@ -1249,7 +1297,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
},
.font_dpi = 0.,
.font_dpi_before_unmap = -1.,
.font_subpixel = (conf->colors.alpha == 0xffff /* Can't do subpixel rendering on transparent background */
.font_subpixel = (theme->alpha == 0xffff /* Can't do subpixel rendering on transparent background */
? FCFT_SUBPIXEL_DEFAULT
: FCFT_SUBPIXEL_NONE),
.cursor_keys_mode = CURSOR_KEYS_NORMAL,
@ -1265,14 +1313,14 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
.state = 0, /* STATE_GROUND */
},
.colors = {
.fg = conf->colors.fg,
.bg = conf->colors.bg,
.alpha = conf->colors.alpha,
.cursor_fg = conf->cursor.color.text,
.cursor_bg = conf->cursor.color.cursor,
.selection_fg = conf->colors.selection_fg,
.selection_bg = conf->colors.selection_bg,
.use_custom_selection = conf->colors.use_custom.selection,
.fg = theme->fg,
.bg = theme->bg,
.alpha = theme->alpha,
.cursor_fg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.text,
.cursor_bg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.cursor,
.selection_fg = theme->selection_fg,
.selection_bg = theme->selection_bg,
.active_theme = conf->initial_color_theme,
},
.color_stack = {
.stack = NULL,
@ -1315,13 +1363,14 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
.wl = wayl,
.render = {
.chains = {
.grid = shm_chain_new(wayl->shm, true, 1 + conf->render_worker_count),
.search = shm_chain_new(wayl->shm, false, 1),
.scrollback_indicator = shm_chain_new(wayl->shm, false, 1),
.render_timer = shm_chain_new(wayl->shm, false, 1),
.url = shm_chain_new(wayl->shm, false, 1),
.csd = shm_chain_new(wayl->shm, false, 1),
.overlay = shm_chain_new(wayl->shm, false, 1),
.grid = shm_chain_new(wayl, true, 1 + conf->render_worker_count,
desired_bit_depth, &render_buffer_release_callback, term),
.search = shm_chain_new(wayl, false, 1 ,desired_bit_depth, NULL, NULL),
.scrollback_indicator = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL),
.render_timer = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL),
.url = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL),
.csd = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL),
.overlay = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL),
},
.scrollback_lines = conf->scrollback.lines,
.app_sync_updates.timer_fd = app_sync_updates_fd,
@ -1368,6 +1417,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
pixman_region32_init(&term->render.last_overlay_clip);
term_update_ascii_printer(term);
memcpy(term->colors.table, theme->table, sizeof(term->colors.table));
for (size_t i = 0; i < 4; i++) {
const struct config_font_list *font_list = &conf->fonts[i];
@ -1402,8 +1452,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
xassert(tll_length(term->wl->monitors) > 0);
term->scale = tll_front(term->wl->monitors).scale;
memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table));
/* Initialize the Wayland window backend */
if ((term->window = wayl_win_init(term, token)) == NULL)
goto err;
@ -1463,6 +1511,9 @@ term_window_configured(struct terminal *term)
if (!term->shutdown.in_progress) {
xassert(term->window->is_configured);
fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term);
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
LOG_INFO("gamma-correct blending: %s", gamma_correct ? "enabled" : "disabled");
}
}
@ -1850,6 +1901,8 @@ term_destroy(struct terminal *term)
}
}
free(term->render.workers.threads);
mtx_destroy(&term->render.workers.preapplied_damage.lock);
cnd_destroy(&term->render.workers.preapplied_damage.cond);
mtx_destroy(&term->render.workers.lock);
sem_destroy(&term->render.workers.start);
sem_destroy(&term->render.workers.done);
@ -2024,12 +2077,25 @@ static inline void
erase_line(struct terminal *term, struct row *row)
{
erase_cell_range(term, row, 0, term->cols - 1);
row->linebreak = false;
row->linebreak = true;
row->shell_integration.prompt_marker = false;
row->shell_integration.cmd_start = -1;
row->shell_integration.cmd_end = -1;
}
static void
term_theme_apply(struct terminal *term, const struct color_theme *theme)
{
term->colors.fg = theme->fg;
term->colors.bg = theme->bg;
term->colors.alpha = theme->alpha;
term->colors.cursor_fg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.text;
term->colors.cursor_bg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.cursor;
term->colors.selection_fg = theme->selection_fg;
term->colors.selection_bg = theme->selection_bg;
memcpy(term->colors.table, theme->table, sizeof(term->colors.table));
}
void
term_reset(struct terminal *term, bool hard)
{
@ -2113,19 +2179,20 @@ term_reset(struct terminal *term, bool hard)
if (!hard)
return;
const struct color_theme *theme = NULL;
switch (term->conf->initial_color_theme) {
case COLOR_THEME_DARK: theme = &term->conf->colors_dark; break;
case COLOR_THEME_LIGHT: theme = &term->conf->colors_light; break;
case COLOR_THEME_1: BUG("COLOR_THEME_1 should not be used"); break;
case COLOR_THEME_2: BUG("COLOR_THEME_2 should not be used"); break;
}
term->flash.active = false;
term->blink.state = BLINK_ON;
fdm_del(term->fdm, term->blink.fd); term->blink.fd = -1;
term->colors.fg = term->conf->colors.fg;
term->colors.bg = term->conf->colors.bg;
term->colors.alpha = term->conf->colors.alpha;
term->colors.cursor_fg = term->conf->cursor.color.text;
term->colors.cursor_bg = term->conf->cursor.color.cursor;
term->colors.selection_fg = term->conf->colors.selection_fg;
term->colors.selection_bg = term->conf->colors.selection_bg;
term->colors.use_custom_selection = term->conf->colors.use_custom.selection;
memcpy(term->colors.table, term->conf->colors.table,
sizeof(term->colors.table));
term_theme_apply(term, theme);
term->colors.active_theme = term->conf->initial_color_theme;
free(term->color_stack.stack);
term->color_stack.stack = NULL;
term->color_stack.size = 0;
@ -2726,13 +2793,11 @@ UNITTEST
},
.kind = SELECTION_NONE,
.auto_scroll = {
.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK),
.fd = -1,
},
},
};
xassert(term.selection.auto_scroll.fd >= 0);
#define populate_scrollback() do { \
for (int i = 0; i < scrollback_rows; i++) { \
if (term.normal.rows[i] == NULL) { \
@ -2822,7 +2887,7 @@ UNITTEST
/* Cleanup */
tll_free(term.normal.sixel_images);
close(term.selection.auto_scroll.fd);
xassert(term.selection.auto_scroll.fd == -1);
for (int i = 0; i < scrollback_rows; i++)
grid_row_free(term.normal.rows[i]);
free(term.normal.rows);
@ -2855,6 +2920,8 @@ term_cursor_to(struct terminal *term, int row, int col)
term->grid->cursor.point.col = col;
term->grid->cursor.point.row = row;
term_reset_grapheme_state(term);
term->grid->cur_row = grid_row(term->grid, row);
}
@ -2871,6 +2938,7 @@ term_cursor_col(struct terminal *term, int col)
term->grid->cursor.lcf = false;
term->grid->cursor.point.col = col;
term_reset_grapheme_state(term);
}
void
@ -2880,6 +2948,7 @@ term_cursor_left(struct terminal *term, int count)
term->grid->cursor.point.col -= move_amount;
xassert(term->grid->cursor.point.col >= 0);
term->grid->cursor.lcf = false;
term_reset_grapheme_state(term);
}
void
@ -2889,6 +2958,7 @@ term_cursor_right(struct terminal *term, int count)
term->grid->cursor.point.col += move_amount;
xassert(term->grid->cursor.point.col < term->cols);
term->grid->cursor.lcf = false;
term_reset_grapheme_state(term);
}
void
@ -3102,11 +3172,17 @@ term_scroll_reverse_partial(struct terminal *term,
sixel_scroll_down(term, rows);
bool view_follows = term->grid->view == term->grid->offset;
const bool view_follows = term->grid->view == term->grid->offset;
term->grid->offset -= rows;
term->grid->offset += term->grid->num_rows;
term->grid->offset &= term->grid->num_rows - 1;
/* How many lines from the scrollback start is the current viewport? */
const int view_sb_start_distance = grid_row_abs_to_sb(
term->grid, term->rows, term->grid->view);
const int offset_sb_start_distance = grid_row_abs_to_sb(
term->grid, term->rows, term->grid->offset);
xassert(term->grid->offset >= 0);
xassert(term->grid->offset < term->grid->num_rows);
@ -3114,6 +3190,11 @@ term_scroll_reverse_partial(struct terminal *term,
term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows);
selection_view_up(term, term->grid->offset);
term->grid->view = term->grid->offset;
} else if (unlikely(view_sb_start_distance > offset_sb_start_distance)) {
/* Part of current view is being scrolled out */
int new_view = term->grid->offset;
selection_view_up(term, new_view);
term->grid->view = new_view;
}
/* Bottom non-scrolling region */
@ -3130,11 +3211,16 @@ term_scroll_reverse_partial(struct terminal *term,
erase_line(term, row);
}
if (unlikely(view_sb_start_distance > offset_sb_start_distance))
term_damage_view(term);
term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
#if defined(_DEBUG)
for (int r = 0; r < term->rows; r++)
xassert(grid_row(term->grid, r) != NULL);
for (int r = 0; r < term->rows; r++)
xassert(grid_row_in_view(term->grid, r) != NULL);
#endif
}
@ -3153,13 +3239,14 @@ term_carriage_return(struct terminal *term)
void
term_linefeed(struct terminal *term)
{
term->grid->cur_row->linebreak = true;
term->grid->cursor.lcf = false;
if (term->grid->cursor.point.row == term->scroll_region.end - 1)
term_scroll(term, 1);
else
term_cursor_down(term, 1);
term_reset_grapheme_state(term);
}
void
@ -3340,10 +3427,13 @@ report_mouse_click(struct terminal *term, int encoded_button, int row, int col,
encoded_button, col + 1, row + 1, release ? 'm' : 'M');
break;
case MOUSE_SGR_PIXELS:
case MOUSE_SGR_PIXELS: {
const int bounded_col = max(col_pixels, 0);
const int bounded_row = max(row_pixels, 0);
snprintf(response, sizeof(response), "\033[<%d;%d;%d%c",
encoded_button, col_pixels + 1, row_pixels + 1, release ? 'm' : 'M');
encoded_button, bounded_col + 1, bounded_row + 1, release ? 'm' : 'M');
break;
}
case MOUSE_URXVT:
snprintf(response, sizeof(response), "\033[%d;%d;%dM",
@ -3524,7 +3614,9 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat)
if (seat->pointer.hidden)
shape = CURSOR_SHAPE_HIDDEN;
else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 ||
else if (cursor_string_to_server_shape(
term->mouse_user_cursor,
term->wl->shape_manager_version) != 0 ||
render_xcursor_is_valid(seat, term->mouse_user_cursor))
{
shape = CURSOR_SHAPE_CUSTOM;
@ -3615,7 +3707,7 @@ term_set_app_id(struct terminal *term, const char *app_id)
term->app_id = NULL;
}
const size_t length = strlen(app_id);
const size_t length = app_id != NULL ? strlen(app_id) : 0;
if (length > 2048) {
/*
* Not sure if there's a limit in the protocol, or the
@ -3683,6 +3775,9 @@ term_bell(struct terminal *term)
}
}
if (term->conf->bell.system_bell)
wayl_win_ring_bell(term->window);
if (term->conf->bell.notify) {
notify_notify(term, &(struct notification){
.title = xstrdup("Bell"),
@ -3710,8 +3805,18 @@ term_bell(struct terminal *term)
bool
term_spawn_new(const struct terminal *term)
{
char *argv[4];
int argc = 0;
argv[argc++] = term->foot_exe;
if (term->conf->conf_path != NULL) {
argv[argc++] = "--config";
argv[argc++] = term->conf->conf_path;
}
argv[argc] = NULL;
return spawn(
term->reaper, term->cwd, (char *const []){term->foot_exe, NULL},
term->reaper, term->cwd, argv,
-1, -1, -1, NULL, NULL, NULL) >= 0;
}
@ -3819,7 +3924,7 @@ print_spacer(struct terminal *term, int col, int remaining)
struct cell *cell = &row->cells[col];
cell->wc = CELL_SPACER + remaining;
cell->attrs = term->vt.attrs;
cell->attrs = (struct attributes){0};
}
/*
@ -3889,7 +3994,7 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count,
}
void
term_print(struct terminal *term, char32_t wc, int width)
term_print(struct terminal *term, char32_t wc, int width, bool insert_mode_disable)
{
xassert(width > 0);
@ -3911,7 +4016,8 @@ term_print(struct terminal *term, char32_t wc, int width)
}
print_linewrap(term);
print_insert(term, width);
if (!insert_mode_disable)
print_insert(term, width);
int col = grid->cursor.point.col;
@ -3940,9 +4046,11 @@ term_print(struct terminal *term, char32_t wc, int width)
cell->wc = term->vt.last_printed = wc;
cell->attrs = term->vt.attrs;
if (term->vt.osc8.uri != NULL) {
grid_row_uri_range_put(
row, col, term->vt.osc8.uri, term->vt.osc8.id);
if (unlikely(term->vt.osc8.uri != NULL)) {
for (int i = 0; i < width && (col + i) < term->cols; i++) {
grid_row_uri_range_put(
row, col + i, term->vt.osc8.uri, term->vt.osc8.id);
}
switch (term->conf->url.osc8_underline) {
case OSC8_UNDERLINE_ALWAYS:
@ -3983,7 +4091,7 @@ term_print(struct terminal *term, char32_t wc, int width)
static void
ascii_printer_generic(struct terminal *term, char32_t wc)
{
term_print(term, wc, 1);
term_print(term, wc, 1, false);
}
static void
@ -4065,6 +4173,260 @@ term_single_shift(struct terminal *term, enum charset_designator idx)
term->ascii_printer = &ascii_printer_single_shift;
}
#if defined(FOOT_GRAPHEME_CLUSTERING)
static int
emoji_vs_compare(const void *_key, const void *_entry)
{
const struct emoji_vs *key = _key;
const struct emoji_vs *entry = _entry;
uint32_t cp = key->start;
if (cp < entry->start)
return -1;
else if (cp > entry->end)
return 1;
else
return 0;
}
UNITTEST
{
/* Verify the emoji_vs list is sorted */
int64_t last_end = -1;
for (size_t i = 0; i < sizeof(emoji_vs) / sizeof(emoji_vs[0]); i++) {
const struct emoji_vs *vs = &emoji_vs[i];
xassert(vs->start <= vs->end);
xassert(vs->start > last_end);
xassert(vs->vs15 || vs->vs16);
last_end = vs->end;
}
}
#endif
void
term_process_and_print_non_ascii(struct terminal *term, char32_t wc)
{
int width = c32width(wc);
bool insert_mode_disable = false;
const bool grapheme_clustering = term->grapheme_shaping;
#if !defined(FOOT_GRAPHEME_CLUSTERING)
xassert(!grapheme_clustering);
#endif
if (term->grid->cursor.point.col > 0 &&
(grapheme_clustering ||
(!grapheme_clustering && width == 0 && wc >= 0x300)))
{
int col = term->grid->cursor.point.col;
if (!term->grid->cursor.lcf)
col--;
/* Skip past spacers */
struct row *row = term->grid->cur_row;
while (row->cells[col].wc >= CELL_SPACER && col > 0)
col--;
xassert(col >= 0 && col < term->cols);
char32_t base = row->cells[col].wc;
char32_t UNUSED last = base;
/* Is base cell already a cluster? */
const struct composed *composed =
(base >= CELL_COMB_CHARS_LO && base <= CELL_COMB_CHARS_HI)
? composed_lookup(term->composed, base - CELL_COMB_CHARS_LO)
: NULL;
uint32_t key;
if (composed != NULL) {
base = composed->chars[0];
last = composed->chars[composed->count - 1];
key = composed_key_from_key(composed->key, wc);
} else
key = composed_key_from_key(base, wc);
#if defined(FOOT_GRAPHEME_CLUSTERING)
if (grapheme_clustering) {
/* Check if we're on a grapheme cluster break */
if (utf8proc_grapheme_break_stateful(
last, wc, &term->vt.grapheme_state))
{
term_reset_grapheme_state(term);
goto out;
}
}
#endif
int base_width = c32width(base);
if (base_width > 0) {
term->grid->cursor.point.col = col;
term->grid->cursor.lcf = false;
insert_mode_disable = true;
if (composed == NULL) {
bool base_from_primary;
bool comb_from_primary;
bool pre_from_primary;
char32_t precomposed = term->fonts[0] != NULL
? fcft_precompose(
term->fonts[0], base, wc, &base_from_primary,
&comb_from_primary, &pre_from_primary)
: (char32_t)-1;
int precomposed_width = c32width(precomposed);
/*
* Only use the pre-composed character if:
*
* 1. we *have* a pre-composed character
* 2. the width matches the base characters width
* 3. it's in the primary font, OR one of the base or
* combining characters are *not* from the primary
* font
*/
if (precomposed != (char32_t)-1 &&
precomposed_width == base_width &&
(pre_from_primary ||
!base_from_primary ||
!comb_from_primary))
{
wc = precomposed;
width = precomposed_width;
term_reset_grapheme_state(term);
goto out;
}
}
size_t wanted_count = composed != NULL ? composed->count + 1 : 2;
if (wanted_count > 255) {
xassert(composed != NULL);
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
LOG_WARN("combining character overflow:");
LOG_WARN(" base: 0x%04x", composed->chars[0]);
for (size_t i = 1; i < composed->count; i++)
LOG_WARN(" cc: 0x%04x", composed->chars[i]);
LOG_ERR(" new: 0x%04x", wc);
#endif
/* This is going to break anyway... */
wanted_count--;
}
xassert(wanted_count <= 255);
/* Check if we already have a match for the entire compose chain */
const struct composed *cc =
composed_lookup_without_collision(
term->composed, &key,
composed != NULL ? composed->chars : &(char32_t){base},
composed != NULL ? composed->count : 1,
wc, 0);
if (cc != NULL) {
/* We *do* have a match! */
wc = CELL_COMB_CHARS_LO + cc->key;
width = cc->width;
goto out;
} else {
/* No match - allocate a new chain below */
}
if (unlikely(term->composed_count >=
(CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO)))
{
/* We reached our maximum number of allowed composed
* character chains. Fall through here and print the
* current zero-width character to the current cell */
LOG_WARN("maximum number of composed characters reached");
term_reset_grapheme_state(term);
goto out;
}
/* Allocate new chain */
struct composed *new_cc = xmalloc(sizeof(*new_cc));
new_cc->chars = xmalloc(wanted_count * sizeof(new_cc->chars[0]));
new_cc->key = key;
new_cc->count = wanted_count;
new_cc->chars[0] = base;
new_cc->chars[wanted_count - 1] = wc;
new_cc->forced_width = composed != NULL ? composed->forced_width : 0;
if (composed != NULL) {
memcpy(&new_cc->chars[1], &composed->chars[1],
(wanted_count - 2) * sizeof(new_cc->chars[0]));
}
const int grapheme_width =
composed != NULL ? composed->width : base_width;
switch (term->conf->tweak.grapheme_width_method) {
case GRAPHEME_WIDTH_MAX:
new_cc->width = max(grapheme_width, width);
break;
case GRAPHEME_WIDTH_DOUBLE:
new_cc->width = min(grapheme_width + width, 2);
#if defined(FOOT_GRAPHEME_CLUSTERING)
/* Handle VS-15 and VS-16 variation selectors */
if (unlikely(grapheme_clustering &&
(wc == 0xfe0e || wc == 0xfe0f) &&
new_cc->count == 2))
{
const struct emoji_vs *vs =
bsearch(
&(struct emoji_vs){.start = new_cc->chars[0]},
emoji_vs, sizeof(emoji_vs) / sizeof(emoji_vs[0]),
sizeof(struct emoji_vs),
&emoji_vs_compare);
if (vs != NULL) {
xassert(new_cc->chars[0] >= vs->start &&
new_cc->chars[0] <= vs->end);
/* Force a grapheme width of 1 for VS-15, and 2 for VS-16 */
if (wc == 0xfe0e) {
if (vs->vs15)
new_cc->width = 1;
} else if (wc == 0xfe0f) {
if (vs->vs16)
new_cc->width = 2;
}
}
}
#endif
break;
case GRAPHEME_WIDTH_WCSWIDTH:
new_cc->width = grapheme_width + width;
break;
}
term->composed_count++;
composed_insert(&term->composed, new_cc);
wc = CELL_COMB_CHARS_LO + new_cc->key;
width = new_cc->forced_width > 0 ? new_cc->forced_width : new_cc->width;
xassert(wc >= CELL_COMB_CHARS_LO);
xassert(wc <= CELL_COMB_CHARS_HI);
goto out;
}
} else
term_reset_grapheme_state(term);
out:
if (width > 0)
term_print(term, wc, width, insert_mode_disable);
}
enum term_surface
term_surface_kind(const struct terminal *term, const struct wl_surface *surface)
{
@ -4395,3 +4757,76 @@ term_send_size_notification(struct terminal *term)
term->rows, term->cols, height, width);
term_to_slave(term, buf, n);
}
void
term_theme_switch_to_dark(struct terminal *term)
{
if (term->colors.active_theme == COLOR_THEME_DARK)
return;
term_theme_apply(term, &term->conf->colors_dark);
term->colors.active_theme = COLOR_THEME_DARK;
wayl_win_alpha_changed(term->window);
term_font_subpixel_changed(term);
if (term->report_theme_changes)
term_to_slave(term, "\033[?997;1n", 9);
term_damage_view(term);
term_damage_margins(term);
render_refresh(term);
}
void
term_theme_switch_to_light(struct terminal *term)
{
if (term->colors.active_theme == COLOR_THEME_LIGHT)
return;
term_theme_apply(term, &term->conf->colors_light);
term->colors.active_theme = COLOR_THEME_LIGHT;
wayl_win_alpha_changed(term->window);
term_font_subpixel_changed(term);
if (term->report_theme_changes)
term_to_slave(term, "\033[?997;2n", 9);
term_damage_view(term);
term_damage_margins(term);
render_refresh(term);
}
void
term_theme_toggle(struct terminal *term)
{
if (term->colors.active_theme == COLOR_THEME_DARK) {
term_theme_apply(term, &term->conf->colors_light);
term->colors.active_theme = COLOR_THEME_LIGHT;
if (term->report_theme_changes)
term_to_slave(term, "\033[?997;2n", 9);
} else {
term_theme_apply(term, &term->conf->colors_dark);
term->colors.active_theme = COLOR_THEME_DARK;
if (term->report_theme_changes)
term_to_slave(term, "\033[?997;1n", 9);
}
wayl_win_alpha_changed(term->window);
term_font_subpixel_changed(term);
term_damage_view(term);
term_damage_margins(term);
render_refresh(term);
}
const struct color_theme *
term_theme_get(const struct terminal *term)
{
return term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
}

View file

@ -88,7 +88,7 @@ struct range {
struct cursor {
struct coord point;
bool lcf;
bool lcf; /* Last Column Flag; https://github.com/mattiase/wraptest#basic-vt-line-wrapping-rules */
};
enum damage_type {DAMAGE_SCROLL, DAMAGE_SCROLL_REVERSE,
@ -404,7 +404,7 @@ struct colors {
uint32_t cursor_bg; /* cursor color */
uint32_t selection_fg;
uint32_t selection_bg;
bool use_custom_selection;
enum which_color_theme active_theme;
};
struct terminal {
@ -517,6 +517,7 @@ struct terminal {
bool num_lock_modifier;
bool bell_action_enabled;
bool report_theme_changes;
/* Saved DECSET modes - we save the SET state */
struct {
@ -547,6 +548,7 @@ struct terminal {
bool ime:1;
bool app_sync_updates:1;
bool grapheme_shaping:1;
bool report_theme_changes:1;
bool size_notifications:1;
@ -704,6 +706,14 @@ struct terminal {
tll(int) queue;
thrd_t *threads;
struct buffer *buf;
struct {
mtx_t lock;
cnd_t cond;
struct buffer *buf;
struct timespec start;
struct timespec stop;
} preapplied_damage;
} workers;
/* Last rendered cursor position */
@ -714,6 +724,8 @@ struct terminal {
} last_cursor;
struct buffer *last_buf; /* Buffer we rendered to last time */
size_t frames_since_last_immediate_release;
bool preapply_last_frame_damage;
enum overlay_style last_overlay_style;
struct buffer *last_overlay_buf;
@ -777,6 +789,10 @@ struct terminal {
bool transparent_bg;
bool linear_blending;
bool use_10bit;
pixman_format_code_t pixman_fmt;
/* Application configurable */
unsigned palette_size; /* Number of colors in palette */
unsigned max_width; /* Maximum image width, in pixels */
@ -789,6 +805,7 @@ struct terminal {
bool urls_show_uri_on_jump_label;
struct grid *url_grid_snapshot;
bool ime_reenable_after_url_mode;
const struct config_spawn_template *url_launch;
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
bool ime_enabled;
@ -893,7 +910,9 @@ void term_cursor_up(struct terminal *term, int count);
void term_cursor_down(struct terminal *term, int count);
void term_cursor_blink_update(struct terminal *term);
void term_print(struct terminal *term, char32_t wc, int width);
void term_process_and_print_non_ascii(struct terminal *term, char32_t wc);
void term_print(struct terminal *term, char32_t wc, int width,
bool insert_mode_disable);
void term_fill(struct terminal *term, int row, int col, uint8_t c, size_t count,
bool use_sgr_attrs);
@ -975,6 +994,11 @@ void term_enable_size_notifications(struct terminal *term);
void term_disable_size_notifications(struct terminal *term);
void term_send_size_notification(struct terminal *term);
void term_theme_switch_to_dark(struct terminal *term);
void term_theme_switch_to_light(struct terminal *term);
void term_theme_toggle(struct terminal *term);
const struct color_theme *term_theme_get(const struct terminal *term);
static inline void term_reset_grapheme_state(struct terminal *term)
{
#if defined(FOOT_GRAPHEME_CLUSTERING)

View file

@ -106,50 +106,6 @@ test_c32string(struct context *ctx, bool (*parse_fun)(struct context *ctx),
}
}
static void
test_protocols(struct context *ctx, bool (*parse_fun)(struct context *ctx),
const char *key, char32_t **const *ptr)
{
ctx->key = key;
static const struct {
const char *option_string;
int count;
const char32_t *value[2];
bool invalid;
} input[] = {
{""},
{"http", 1, {U"http://"}},
{" http", 1, {U"http://"}},
{"http, https", 2, {U"http://", U"https://"}},
{"longprotocolislong", 1, {U"longprotocolislong://"}},
};
for (size_t i = 0; i < ALEN(input); i++) {
ctx->value = input[i].option_string;
if (input[i].invalid) {
if (parse_fun(ctx)) {
BUG("[%s].%s=%s: did not fail to parse as expected",
ctx->section, ctx->key, &ctx->value[0]);
}
} else {
if (!parse_fun(ctx)) {
BUG("[%s].%s=%s: failed to parse",
ctx->section, ctx->key, &ctx->value[0]);
}
for (int c = 0; c < input[i].count; c++) {
if (c32cmp((*ptr)[c], input[i].value[c]) != 0) {
BUG("[%s].%s=%s: set value[%d] (%ls) not the expected one (%ls)",
ctx->section, ctx->key, &ctx->value[c], c,
(const wchar_t *)(*ptr)[c],
(const wchar_t *)input[i].value[c]);
}
}
}
}
}
static void
test_boolean(struct context *ctx, bool (*parse_fun)(struct context *ctx),
const char *key, const bool *ptr)
@ -443,6 +399,16 @@ test_color(struct context *ctx, bool (*parse_fun)(struct context *ctx),
BUG("[%s].%s=%s: failed to parse",
ctx->section, ctx->key, ctx->value);
}
uint32_t color = input[i].color;
if (alpha_allowed && strlen(input[i].option_string) == 6)
color |= 0xff000000;
if (*ptr != color) {
BUG("[%s].%s=%s: expected 0x%08x, got 0x%08x",
ctx->section, ctx->key, ctx->value,
color, *ptr);
}
}
}
}
@ -489,6 +455,18 @@ test_two_colors(struct context *ctx, bool (*parse_fun)(struct context *ctx),
BUG("[%s].%s=%s: failed to parse",
ctx->section, ctx->key, ctx->value);
}
if (*ptr1 != input[i].color1) {
BUG("[%s].%s=%s: expected 0x%08x, got 0x%08x",
ctx->section, ctx->key, ctx->value,
input[i].color1, *ptr1);
}
if (*ptr2 != input[i].color2) {
BUG("[%s].%s=%s: expected 0x%08x, got 0x%08x",
ctx->section, ctx->key, ctx->value,
input[i].color2, *ptr2);
}
}
}
}
@ -504,6 +482,7 @@ test_section_main(void)
test_string(&ctx, &parse_section_main, "shell", &conf.shell);
test_string(&ctx, &parse_section_main, "term", &conf.term);
test_string(&ctx, &parse_section_main, "app-id", &conf.app_id);
test_string(&ctx, &parse_section_main, "toplevel-tag", &conf.toplevel_tag);
test_string(&ctx, &parse_section_main, "utmp-helper", &conf.utmp_helper_path);
test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters);
@ -511,8 +490,9 @@ test_section_main(void)
test_boolean(&ctx, &parse_section_main, "login-shell", &conf.login_shell);
test_boolean(&ctx, &parse_section_main, "box-drawings-uses-font-glyphs", &conf.box_drawings_uses_font_glyphs);
test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title);
test_boolean(&ctx, &parse_section_main, "notify-focus-inhibit", &conf.desktop_notifications.inhibit_when_focused); /* Deprecated */
test_boolean(&ctx, &parse_section_main, "dpi-aware", &conf.dpi_aware);
test_boolean(&ctx, &parse_section_main, "gamma-correct-blending", &conf.gamma_correct);
test_boolean(&ctx, &parse_section_main, "uppercase-regex-insert", &conf.uppercase_regex_insert);
test_pt_or_px(&ctx, &parse_section_main, "font-size-adjustment", &conf.font_size_adjustment.pt_or_px); /* TODO: test N% values too */
test_pt_or_px(&ctx, &parse_section_main, "line-height", &conf.line_height);
@ -525,8 +505,6 @@ test_section_main(void)
test_uint16(&ctx, &parse_section_main, "resize-delay-ms", &conf.resize_delay_ms);
test_uint16(&ctx, &parse_section_main, "workers", &conf.render_worker_count);
test_spawn_template(&ctx, &parse_section_main, "notify", &conf.desktop_notifications.command); /* Deprecated */
test_enum(&ctx, &parse_section_main, "selection-target",
4,
(const char *[]){"none", "primary", "clipboard", "both"},
@ -543,6 +521,14 @@ test_section_main(void)
(int []){STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN},
(int *)&conf.startup_mode);
test_enum(
&ctx, &parse_section_main, "initial-color-theme",
2,
(const char *[]){"dark", "light", "1", "2"},
(int []){COLOR_THEME_DARK, COLOR_THEME_LIGHT,
COLOR_THEME_DARK, COLOR_THEME_LIGHT},
(int *)&conf.initial_color_theme);
/* TODO: font (custom) */
/* TODO: include (custom) */
/* TODO: bold-text-in-bright (enum/boolean) */
@ -579,6 +565,7 @@ test_section_bell(void)
test_boolean(&ctx, &parse_section_bell, "urgent", &conf.bell.urgent);
test_boolean(&ctx, &parse_section_bell, "notify", &conf.bell.notify);
test_boolean(&ctx, &parse_section_bell, "system", &conf.bell.system_bell);
test_boolean(&ctx, &parse_section_bell, "command-focused",
&conf.bell.command_focused);
test_spawn_template(&ctx, &parse_section_bell, "command",
@ -646,9 +633,6 @@ test_section_url(void)
(int []){OSC8_UNDERLINE_URL_MODE, OSC8_UNDERLINE_ALWAYS},
(int *)&conf.url.osc8_underline);
test_c32string(&ctx, &parse_section_url, "label-letters", &conf.url.label_letters);
test_protocols(&ctx, &parse_section_url, "protocols", &conf.url.protocols);
/* TODO: uri-characters (wchar string, but sorted) */
config_free(&conf);
}
@ -719,64 +703,161 @@ test_section_touch(void)
}
static void
test_section_colors(void)
test_section_colors_dark(void)
{
struct config conf = {0};
struct context ctx = {
.conf = &conf, .section = "colors", .path = "unittest"};
.conf = &conf, .section = "colors-dark", .path = "unittest"};
test_invalid_key(&ctx, &parse_section_colors, "invalid-key");
test_color(&ctx, &parse_section_colors, "foreground", false, &conf.colors.fg);
test_color(&ctx, &parse_section_colors, "background", false, &conf.colors.bg);
test_color(&ctx, &parse_section_colors, "regular0", false, &conf.colors.table[0]);
test_color(&ctx, &parse_section_colors, "regular1", false, &conf.colors.table[1]);
test_color(&ctx, &parse_section_colors, "regular2", false, &conf.colors.table[2]);
test_color(&ctx, &parse_section_colors, "regular3", false, &conf.colors.table[3]);
test_color(&ctx, &parse_section_colors, "regular4", false, &conf.colors.table[4]);
test_color(&ctx, &parse_section_colors, "regular5", false, &conf.colors.table[5]);
test_color(&ctx, &parse_section_colors, "regular6", false, &conf.colors.table[6]);
test_color(&ctx, &parse_section_colors, "regular7", false, &conf.colors.table[7]);
test_color(&ctx, &parse_section_colors, "bright0", false, &conf.colors.table[8]);
test_color(&ctx, &parse_section_colors, "bright1", false, &conf.colors.table[9]);
test_color(&ctx, &parse_section_colors, "bright2", false, &conf.colors.table[10]);
test_color(&ctx, &parse_section_colors, "bright3", false, &conf.colors.table[11]);
test_color(&ctx, &parse_section_colors, "bright4", false, &conf.colors.table[12]);
test_color(&ctx, &parse_section_colors, "bright5", false, &conf.colors.table[13]);
test_color(&ctx, &parse_section_colors, "bright6", false, &conf.colors.table[14]);
test_color(&ctx, &parse_section_colors, "bright7", false, &conf.colors.table[15]);
test_color(&ctx, &parse_section_colors, "dim0", false, &conf.colors.dim[0]);
test_color(&ctx, &parse_section_colors, "dim1", false, &conf.colors.dim[1]);
test_color(&ctx, &parse_section_colors, "dim2", false, &conf.colors.dim[2]);
test_color(&ctx, &parse_section_colors, "dim3", false, &conf.colors.dim[3]);
test_color(&ctx, &parse_section_colors, "dim4", false, &conf.colors.dim[4]);
test_color(&ctx, &parse_section_colors, "dim5", false, &conf.colors.dim[5]);
test_color(&ctx, &parse_section_colors, "dim6", false, &conf.colors.dim[6]);
test_color(&ctx, &parse_section_colors, "dim7", false, &conf.colors.dim[7]);
test_color(&ctx, &parse_section_colors, "selection-foreground", false, &conf.colors.selection_fg);
test_color(&ctx, &parse_section_colors, "selection-background", false, &conf.colors.selection_bg);
test_color(&ctx, &parse_section_colors, "urls", false, &conf.colors.url);
test_two_colors(&ctx, &parse_section_colors, "jump-labels", false,
&conf.colors.jump_label.fg,
&conf.colors.jump_label.bg);
test_two_colors(&ctx, &parse_section_colors, "scrollback-indicator", false,
&conf.colors.scrollback_indicator.fg,
&conf.colors.scrollback_indicator.bg);
test_two_colors(&ctx, &parse_section_colors, "search-box-no-match", false,
&conf.colors.search_box.no_match.fg,
&conf.colors.search_box.no_match.bg);
test_two_colors(&ctx, &parse_section_colors, "search-box-match", false,
&conf.colors.search_box.match.fg,
&conf.colors.search_box.match.bg);
test_color(&ctx, &parse_section_colors_dark, "foreground", false, &conf.colors_dark.fg);
test_color(&ctx, &parse_section_colors_dark, "background", false, &conf.colors_dark.bg);
test_color(&ctx, &parse_section_colors_dark, "regular0", false, &conf.colors_dark.table[0]);
test_color(&ctx, &parse_section_colors_dark, "regular1", false, &conf.colors_dark.table[1]);
test_color(&ctx, &parse_section_colors_dark, "regular2", false, &conf.colors_dark.table[2]);
test_color(&ctx, &parse_section_colors_dark, "regular3", false, &conf.colors_dark.table[3]);
test_color(&ctx, &parse_section_colors_dark, "regular4", false, &conf.colors_dark.table[4]);
test_color(&ctx, &parse_section_colors_dark, "regular5", false, &conf.colors_dark.table[5]);
test_color(&ctx, &parse_section_colors_dark, "regular6", false, &conf.colors_dark.table[6]);
test_color(&ctx, &parse_section_colors_dark, "regular7", false, &conf.colors_dark.table[7]);
test_color(&ctx, &parse_section_colors_dark, "bright0", false, &conf.colors_dark.table[8]);
test_color(&ctx, &parse_section_colors_dark, "bright1", false, &conf.colors_dark.table[9]);
test_color(&ctx, &parse_section_colors_dark, "bright2", false, &conf.colors_dark.table[10]);
test_color(&ctx, &parse_section_colors_dark, "bright3", false, &conf.colors_dark.table[11]);
test_color(&ctx, &parse_section_colors_dark, "bright4", false, &conf.colors_dark.table[12]);
test_color(&ctx, &parse_section_colors_dark, "bright5", false, &conf.colors_dark.table[13]);
test_color(&ctx, &parse_section_colors_dark, "bright6", false, &conf.colors_dark.table[14]);
test_color(&ctx, &parse_section_colors_dark, "bright7", false, &conf.colors_dark.table[15]);
test_color(&ctx, &parse_section_colors_dark, "dim0", false, &conf.colors_dark.dim[0]);
test_color(&ctx, &parse_section_colors_dark, "dim1", false, &conf.colors_dark.dim[1]);
test_color(&ctx, &parse_section_colors_dark, "dim2", false, &conf.colors_dark.dim[2]);
test_color(&ctx, &parse_section_colors_dark, "dim3", false, &conf.colors_dark.dim[3]);
test_color(&ctx, &parse_section_colors_dark, "dim4", false, &conf.colors_dark.dim[4]);
test_color(&ctx, &parse_section_colors_dark, "dim5", false, &conf.colors_dark.dim[5]);
test_color(&ctx, &parse_section_colors_dark, "dim6", false, &conf.colors_dark.dim[6]);
test_color(&ctx, &parse_section_colors_dark, "dim7", false, &conf.colors_dark.dim[7]);
test_color(&ctx, &parse_section_colors_dark, "selection-foreground", false, &conf.colors_dark.selection_fg);
test_color(&ctx, &parse_section_colors_dark, "selection-background", false, &conf.colors_dark.selection_bg);
test_color(&ctx, &parse_section_colors_dark, "urls", false, &conf.colors_dark.url);
test_two_colors(&ctx, &parse_section_colors_dark, "jump-labels", false,
&conf.colors_dark.jump_label.fg,
&conf.colors_dark.jump_label.bg);
test_two_colors(&ctx, &parse_section_colors_dark, "scrollback-indicator", false,
&conf.colors_dark.scrollback_indicator.fg,
&conf.colors_dark.scrollback_indicator.bg);
test_two_colors(&ctx, &parse_section_colors_dark, "search-box-no-match", false,
&conf.colors_dark.search_box.no_match.fg,
&conf.colors_dark.search_box.no_match.bg);
test_two_colors(&ctx, &parse_section_colors_dark, "search-box-match", false,
&conf.colors_dark.search_box.match.fg,
&conf.colors_dark.search_box.match.bg);
test_two_colors(&ctx, &parse_section_colors_dark, "cursor", false,
&conf.colors_dark.cursor.text,
&conf.colors_dark.cursor.cursor);
test_enum(&ctx, &parse_section_colors_dark, "alpha-mode", 3,
(const char *[]){"default", "matching", "all"},
(int []){ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL},
(int *)&conf.colors_dark.alpha_mode);
test_enum(&ctx, &parse_section_colors_dark, "dim-blend-towards", 2,
(const char *[]){"black", "white"},
(int []){DIM_BLEND_TOWARDS_BLACK, DIM_BLEND_TOWARDS_WHITE},
(int *)&conf.colors_dark.dim_blend_towards);
for (size_t i = 0; i < 255; i++) {
char key_name[4];
sprintf(key_name, "%zu", i);
test_color(&ctx, &parse_section_colors, key_name, false,
&conf.colors.table[i]);
test_color(&ctx, &parse_section_colors_dark, key_name, false,
&conf.colors_dark.table[i]);
}
test_invalid_key(&ctx, &parse_section_colors, "256");
test_boolean(&ctx, &parse_section_colors_dark, "blur", &conf.colors_dark.blur);
test_invalid_key(&ctx, &parse_section_colors_dark, "256");
/* TODO: alpha (float in range 0-1, converted to uint16_t) */
config_free(&conf);
}
static void
test_section_colors_light(void)
{
struct config conf = {0};
struct context ctx = {
.conf = &conf, .section = "colors-light", .path = "unittest"};
test_invalid_key(&ctx, &parse_section_colors, "invalid-key");
test_color(&ctx, &parse_section_colors_light, "foreground", false, &conf.colors_light.fg);
test_color(&ctx, &parse_section_colors_light, "background", false, &conf.colors_light.bg);
test_color(&ctx, &parse_section_colors_light, "regular0", false, &conf.colors_light.table[0]);
test_color(&ctx, &parse_section_colors_light, "regular1", false, &conf.colors_light.table[1]);
test_color(&ctx, &parse_section_colors_light, "regular2", false, &conf.colors_light.table[2]);
test_color(&ctx, &parse_section_colors_light, "regular3", false, &conf.colors_light.table[3]);
test_color(&ctx, &parse_section_colors_light, "regular4", false, &conf.colors_light.table[4]);
test_color(&ctx, &parse_section_colors_light, "regular5", false, &conf.colors_light.table[5]);
test_color(&ctx, &parse_section_colors_light, "regular6", false, &conf.colors_light.table[6]);
test_color(&ctx, &parse_section_colors_light, "regular7", false, &conf.colors_light.table[7]);
test_color(&ctx, &parse_section_colors_light, "bright0", false, &conf.colors_light.table[8]);
test_color(&ctx, &parse_section_colors_light, "bright1", false, &conf.colors_light.table[9]);
test_color(&ctx, &parse_section_colors_light, "bright2", false, &conf.colors_light.table[10]);
test_color(&ctx, &parse_section_colors_light, "bright3", false, &conf.colors_light.table[11]);
test_color(&ctx, &parse_section_colors_light, "bright4", false, &conf.colors_light.table[12]);
test_color(&ctx, &parse_section_colors_light, "bright5", false, &conf.colors_light.table[13]);
test_color(&ctx, &parse_section_colors_light, "bright6", false, &conf.colors_light.table[14]);
test_color(&ctx, &parse_section_colors_light, "bright7", false, &conf.colors_light.table[15]);
test_color(&ctx, &parse_section_colors_light, "dim0", false, &conf.colors_light.dim[0]);
test_color(&ctx, &parse_section_colors_light, "dim1", false, &conf.colors_light.dim[1]);
test_color(&ctx, &parse_section_colors_light, "dim2", false, &conf.colors_light.dim[2]);
test_color(&ctx, &parse_section_colors_light, "dim3", false, &conf.colors_light.dim[3]);
test_color(&ctx, &parse_section_colors_light, "dim4", false, &conf.colors_light.dim[4]);
test_color(&ctx, &parse_section_colors_light, "dim5", false, &conf.colors_light.dim[5]);
test_color(&ctx, &parse_section_colors_light, "dim6", false, &conf.colors_light.dim[6]);
test_color(&ctx, &parse_section_colors_light, "dim7", false, &conf.colors_light.dim[7]);
test_color(&ctx, &parse_section_colors_light, "selection-foreground", false, &conf.colors_light.selection_fg);
test_color(&ctx, &parse_section_colors_light, "selection-background", false, &conf.colors_light.selection_bg);
test_color(&ctx, &parse_section_colors_light, "urls", false, &conf.colors_light.url);
test_two_colors(&ctx, &parse_section_colors_light, "jump-labels", false,
&conf.colors_light.jump_label.fg,
&conf.colors_light.jump_label.bg);
test_two_colors(&ctx, &parse_section_colors_light, "scrollback-indicator", false,
&conf.colors_light.scrollback_indicator.fg,
&conf.colors_light.scrollback_indicator.bg);
test_two_colors(&ctx, &parse_section_colors_light, "search-box-no-match", false,
&conf.colors_light.search_box.no_match.fg,
&conf.colors_light.search_box.no_match.bg);
test_two_colors(&ctx, &parse_section_colors_light, "search-box-match", false,
&conf.colors_light.search_box.match.fg,
&conf.colors_light.search_box.match.bg);
test_two_colors(&ctx, &parse_section_colors_light, "cursor", false,
&conf.colors_light.cursor.text,
&conf.colors_light.cursor.cursor);
test_enum(&ctx, &parse_section_colors_light, "alpha-mode", 3,
(const char *[]){"default", "matching", "all"},
(int []){ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL},
(int *)&conf.colors_light.alpha_mode);
test_enum(&ctx, &parse_section_colors_light, "dim-blend-towards", 2,
(const char *[]){"black", "white"},
(int []){DIM_BLEND_TOWARDS_BLACK, DIM_BLEND_TOWARDS_WHITE},
(int *)&conf.colors_light.dim_blend_towards);
for (size_t i = 0; i < 255; i++) {
char key_name[4];
sprintf(key_name, "%zu", i);
test_color(&ctx, &parse_section_colors_light, key_name, false,
&conf.colors_light.table[i]);
}
test_boolean(&ctx, &parse_section_colors_light, "blur", &conf.colors_light.blur);
test_invalid_key(&ctx, &parse_section_colors_light, "256");
/* TODO: alpha (float in range 0-1, converted to uint16_t) */
@ -842,7 +923,7 @@ static void
test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx),
int action, int max_action, const char *const *map,
struct config_key_binding_list *bindings,
enum key_binding_type type)
enum key_binding_type type, bool need_argv, bool need_section_id)
{
xassert(map[action] != NULL);
xassert(bindings->count == 0);
@ -854,7 +935,10 @@ test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx),
const bool alt = action % 3;
const bool shift = action % 4;
const bool super = action % 5;
const bool argv = action % 6;
const bool argv = need_argv;
const bool section_id = need_section_id;
xassert(!(argv && section_id));
static const char *const args[] = {
"command", "arg1", "arg2", "arg3 has spaces"};
@ -893,7 +977,7 @@ test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx),
xkb_keysym_get_name(sym, sym_name, sizeof(sym_name));
snprintf(value, sizeof(value), "%s%s%s",
argv ? "[command arg1 arg2 \"arg3 has spaces\"] " : "",
argv ? "[command arg1 arg2 \"arg3 has spaces\"] " : section_id ? "[foobar]" : "",
modifier_string, sym_name);
break;
}
@ -902,7 +986,7 @@ test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx),
const char *const button_name = button_map[button_idx].name;
int chars = snprintf(
value, sizeof(value), "%s%s%s",
argv ? "[command arg1 arg2 \"arg3 has spaces\"] " : "",
argv ? "[command arg1 arg2 \"arg3 has spaces\"] " : section_id ? "[foobar]" : "",
modifier_string, button_name);
xassert(click_count > 0);
@ -943,6 +1027,18 @@ test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx),
ctx->section, ctx->key, ctx->value,
ALEN(args), binding->aux.pipe.args[ALEN(args)]);
}
} else if (section_id) {
if (binding->aux.regex_name == NULL) {
BUG("[%s].%s=%s: regex name is NULL",
ctx->section, ctx->key, ctx->value);
}
if (!streq(binding->aux.regex_name, "foobar")) {
BUG("[%s].%s=%s: regex name not the expected one: "
"expected=\"%s\", got=\"%s\"",
ctx->section, ctx->key, ctx->value,
"foobar", binding->aux.regex_name);
}
} else {
if (binding->aux.pipe.args != NULL) {
BUG("[%s].%s=%s: pipe argv not NULL",
@ -1138,7 +1234,9 @@ test_section_key_bindings(void)
test_key_binding(
&ctx, &parse_section_key_bindings,
action, BIND_ACTION_KEY_COUNT - 1,
binding_action_map, &conf.bindings.key, KEY_BINDING);
binding_action_map, &conf.bindings.key, KEY_BINDING,
action >= BIND_ACTION_PIPE_SCROLLBACK && action <= BIND_ACTION_PIPE_COMMAND_OUTPUT,
action >= BIND_ACTION_REGEX_LAUNCH && action <= BIND_ACTION_REGEX_COPY);
}
config_free(&conf);
@ -1173,7 +1271,8 @@ test_section_search_bindings(void)
test_key_binding(
&ctx, &parse_section_search_bindings,
action, BIND_ACTION_SEARCH_COUNT - 1,
search_binding_action_map, &conf.bindings.search, KEY_BINDING);
search_binding_action_map, &conf.bindings.search, KEY_BINDING,
false, false);
}
config_free(&conf);
@ -1209,7 +1308,8 @@ test_section_url_bindings(void)
test_key_binding(
&ctx, &parse_section_url_bindings,
action, BIND_ACTION_URL_COUNT - 1,
url_binding_action_map, &conf.bindings.url, KEY_BINDING);
url_binding_action_map, &conf.bindings.url, KEY_BINDING,
false, false);
}
config_free(&conf);
@ -1245,7 +1345,8 @@ test_section_mouse_bindings(void)
test_key_binding(
&ctx, &parse_section_mouse_bindings,
action, BIND_ACTION_COUNT - 1,
binding_action_map, &conf.bindings.mouse, MOUSE_BINDING);
binding_action_map, &conf.bindings.mouse, MOUSE_BINDING,
false, false);
}
config_free(&conf);
@ -1409,6 +1510,9 @@ test_section_tweak(void)
test_float(&ctx, &parse_section_tweak, "bold-text-in-bright-amount",
&conf.bold_in_bright.amount);
test_uint32(&ctx, &parse_section_tweak, "min-stride-alignment",
&conf.tweak.min_stride_alignment);
#if 0 /* Must be equal to, or less than INT32_MAX */
test_uint32(&ctx, &parse_section_tweak, "max-shm-pool-size-mb",
&conf.tweak.max_shm_pool_size);
@ -1431,7 +1535,8 @@ main(int argc, const char *const *argv)
test_section_cursor();
test_section_mouse();
test_section_touch();
test_section_colors();
test_section_colors_dark();
test_section_colors_light();
test_section_csd();
test_section_key_bindings();
test_section_key_bindings_collisions();

View file

@ -1,10 +1,8 @@
# -*- conf -*-
# Aero root theme
[cursor]
color=1a1a1a 9fd5f5
[colors]
[colors-dark]
cursor=1a1a1a 9fd5f5
foreground=dedeef
background=1a1a1a

57
themes/alacritty Normal file
View file

@ -0,0 +1,57 @@
# -*- conf -*-
# Alacritty
[colors-dark]
cursor = 181818 d8d8d8
background= 181818
foreground= d8d8d8
#black
regular0= 181818
#red
regular1= ac4242
#green
regular2= 90a959
#yellow
regular3= f4bf75
#blue
regular4= 6a9fb5
#magenta
regular5= aa759f
#cyan
regular6= 75b5aa
#white/grey
regular7= d8d8d8
#grey/black
bright0= 6b6b6b
#red
bright1= c55555
#green
bright2= aac474
#yellow
bright3= feca88
#blue
bright4= 82b8c8
#pink
bright5= c28cb8
#cyan
bright6= 93d3c3
#grey
bright7= f8f8f8

View file

@ -1,10 +1,8 @@
# -*- conf -*-
# https://github.com/romainl/Apprentice
[cursor]
color=262626 6c6c6c
[colors]
[colors-dark]
cursor=262626 6c6c6c
foreground=bcbcbc
background=262626
regular0=1c1c1c

View file

@ -2,10 +2,8 @@
# theme: Ayu Mirage
# description: a theme based on Ayu Mirage for Sublime Text (original: https://github.com/dempfi/ayu)
[cursor]
color = ffcc66 665a44
[colors]
[colors-dark]
cursor = ffcc66 665a44
foreground = cccac2
background = 242936

View file

@ -1,7 +1,7 @@
# _*_ conf _*_
# Catppuccin Frappe
[colors]
[colors-dark]
foreground=c6d0f5
background=303446
@ -23,6 +23,11 @@ bright5=f4b8e4
bright6=81c8be
bright7=a5adce
cursor=232634 f2d5cf
16=ef9f76
17=f2d5cf
selection-foreground=c6d0f5
selection-background=4f5369

View file

@ -1,7 +1,10 @@
# _*_ conf _*_
# Catppuccin Latte
[colors]
[main]
initial-color-theme=light
[colors-light]
foreground=4c4f69
background=eff1f5
@ -23,6 +26,11 @@ bright5=ea76cb
bright6=179299
bright7=bcc0cc
cursor=eff1f5 dc8a78
16=fe640b
17=dc8a78
selection-foreground=4c4f69
selection-background=ccced7

View file

@ -1,7 +1,7 @@
# _*_ conf _*_
# Catppuccin Macchiato
[colors]
[colors-dark]
foreground=cad3f5
background=24273a
@ -23,6 +23,11 @@ bright5=f5bde6
bright6=8bd5ca
bright7=a5adcb
cursor=181926 f4dbd6
16=f5a97f
17=f4dbd6
selection-foreground=cad3f5
selection-background=454a5f

View file

@ -1,7 +1,7 @@
# _*_ conf _*_
# Catppuccin Mocha
[colors]
[colors-dark]
foreground=cdd6f4
background=1e1e2e
@ -23,6 +23,11 @@ bright5=f5c2e7
bright6=94e2d5
bright7=a6adc8
cursor=11111b f5e0dc
16=fab387
17=f5e0dc
selection-foreground=cdd6f4
selection-background=414356

View file

@ -3,10 +3,8 @@
# author: ayushnix (https://sr.ht/~ayushnix)
# description: A dark theme with bright cyberpunk colors (WCAG AAA compliant)
[cursor]
color = 181818 cdcdcd
[colors]
[colors-dark]
cursor = 181818 cdcdcd
foreground = cdcdcd
background = 181818
regular0 = 181818

View file

@ -1,10 +1,8 @@
# -*- conf -*-
# Derp
[cursor]
color=000000 ffffff
[colors]
[colors-dark]
cursor=000000 ffffff
foreground=ffffff
background=000000
regular0=111111

View file

@ -2,10 +2,8 @@
# Deus
# Color palette based on: https://github.com/ajmwagar/vim-deus
[cursor]
color=2c323b eaeaea
[colors]
[colors-dark]
cursor=2c323b eaeaea
background=2c323b
foreground=eaeaea
regular0=242a32

View file

@ -1,10 +1,8 @@
# -*- conf -*-
# Dracula
[cursor]
color=282a36 f8f8f2
[colors]
[colors-dark]
cursor=282a36 f8f8f2
foreground=f8f8f2
background=282a36
regular0=000000 # black

View file

@ -1,10 +1,8 @@
# -*- conf -*-
# Dracula iTerm2 variant
[cursor]
color=ffffff bbbbbb
[colors]
[colors-dark]
cursor=ffffff bbbbbb
foreground=f8f8f2
background=1e1f29
regular0=000000 # black

View file

@ -5,10 +5,11 @@
# text and the white background.
# author: Eugen Rahaian <eugen@rah.ro>
[cursor]
color=ffffff 515151
[main]
initial-color-theme=light
[colors]
[colors-light]
cursor=ffffff 515151
background= ffffff
foreground= 000000

42
themes/gruvbox Normal file
View file

@ -0,0 +1,42 @@
# -*- conf -*-
# Gruvbox
[colors-dark]
background=282828
foreground=ebdbb2
regular0=282828
regular1=cc241d
regular2=98971a
regular3=d79921
regular4=458588
regular5=b16286
regular6=689d6a
regular7=a89984
bright0=928374
bright1=fb4934
bright2=b8bb26
bright3=fabd2f
bright4=83a598
bright5=d3869b
bright6=8ec07c
bright7=ebdbb2
[colors-light]
background=fbf1c7
foreground=3c3836
regular0=fbf1c7
regular1=cc241d
regular2=98971a
regular3=d79921
regular4=458588
regular5=b16286
regular6=689d6a
regular7=7c6f64
bright0=928374
bright1=9d0006
bright2=79740e
bright3=b57614
bright4=076678
bright5=8f3f71
bright6=427b58
bright7=3c3836

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Gruvbox
[colors]
[colors-dark]
background=282828
foreground=ebdbb2
regular0=282828

View file

@ -1,7 +1,10 @@
# -*- conf -*-
# Gruvbox - Light
[colors]
[main]
initial-color-theme=light
[colors-light]
background=fbf1c7
foreground=3c3836
regular0=fbf1c7

View file

@ -1,8 +1,7 @@
# -*- conf -*-
[cursor]
color=141414 c9c9c9
[colors]
[colors-dark]
cursor=141414 c9c9c9
foreground=c9c9c9
background=141414
regular0=191918 # black

View file

@ -2,7 +2,7 @@
# this foot theme is based on alacritty iterm theme:
# https://github.com/alacritty/alacritty-theme/blob/master/themes/iterm.toml
[colors]
[colors-dark]
foreground=fffbf6
background=101421

View file

@ -2,10 +2,8 @@
# JetBrains Darcula
# Palette based on the same theme from https://github.com/dexpota/kitty-themes
[cursor]
color=202020 ffffff
[colors]
[colors-dark]
cursor=202020 ffffff
background=202020
foreground=adadad
regular0=000000 # black

View file

@ -1,9 +1,7 @@
# -*- conf -*-
[cursor]
color=111111 cccccc
[colors]
[colors-dark]
cursor=111111 cccccc
foreground=dddddd
background=000000
regular0=000000 # black

View file

@ -2,10 +2,11 @@
# Material Amber
# Based on material.io guidelines with Amber 50 background
[cursor]
color=fff8e1 21201d
[main]
initial-color-theme=light
[colors]
[colors-light]
cursor=fff8e1 21201d
foreground = 21201d
background = fff8e1

View file

@ -2,7 +2,7 @@
# Material
# From https://github.com/MartinSeeler/iterm2-material-design
[colors]
[colors-dark]
foreground=ECEFF1
background=263238
regular0=546E7A # black

View file

@ -3,7 +3,11 @@
# modus-operandi
# See: https://protesilaos.com/emacs/modus-themes
#
[colors]
[main]
initial-color-theme=light
[colors-light]
background=ffffff
foreground=000000
regular0=000000
@ -22,3 +26,5 @@ bright4=2544bb
bright5=5317ac
bright6=005a5f
bright7=ffffff
jump-labels=dce0e8 0000ff

View file

@ -4,7 +4,7 @@
# See: https://protesilaos.com/emacs/modus-themes
#
[colors]
[colors-dark]
background=000000
foreground=ffffff
regular0=000000

View file

@ -0,0 +1,25 @@
# -*- conf -*-
#
# modus-vivendi-tinted
# See: https://protesilaos.com/emacs/modus-themes
#
[colors-dark]
background=0d0e1c
foreground=ffffff
regular0=000000
regular1=ff5f59
regular2=44bc44
regular3=d0bc00
regular4=2fafff
regular5=feacd0
regular6=00d3d0
regular7=a6a6a6
bright0=595959
bright1=ff6b55
bright2=ff6b55
bright3=fec43f
bright4=fec43f
bright5=b6a0ff
bright6=6ae4b9
bright7=777777

23
themes/molokai Normal file
View file

@ -0,0 +1,23 @@
# -*- conf -*-
# Molokai
# Based on zhou13's at https://github.com/zhou13/molokai-terminal/blob/master/xterm/Xresources
[colors-dark]
background=1B1D1E
foreground=CCCCCC
regular0=1B1D1E
regular1=FF0044
regular2=82B414
regular3=FD971F
regular4=266C98
regular5=AC0CB1
regular6=AE81FF
regular7=CCCCCC
bright0=808080
bright1=F92672
bright2=A6E22E
bright3=E6DB74
bright4=7070F0
bright5=D63AE1
bright6=66D9EF
bright7=F8F8F2

Some files were not shown because too many files have changed in this diff Show more