Compare commits

...

77 commits

Author SHA1 Message Date
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
138 changed files with 1803 additions and 566 deletions

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,8 @@
# Changelog
* [Unreleased](#unreleased)
* [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)
@ -64,6 +67,162 @@
* [1.2.0](#1-2-0)
## Unreleased
### 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]).
[2212]: https://codeberg.org/dnkl/foot/issues/2212
[2209]: https://codeberg.org/dnkl/foot/issues/2209
### 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
### Security
### Contributors
## 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

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

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

View file

@ -2,7 +2,6 @@
#include <stdio.h>
#include <math.h>
#include <fenv.h>
#include <errno.h>
#define LOG_MODULE "box-drawing"

View file

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

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>
@ -33,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)
{
@ -69,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"
@ -130,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)
{
@ -144,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'},
@ -213,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;
@ -507,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]' \

195
config.c
View file

@ -144,6 +144,8 @@ static const char *const binding_action_map[] = {
[BIND_ACTION_REGEX_COPY] = "regex-copy",
[BIND_ACTION_THEME_SWITCH_1] = "color-theme-switch-1",
[BIND_ACTION_THEME_SWITCH_2] = "color-theme-switch-2",
[BIND_ACTION_THEME_SWITCH_DARK] = "color-theme-switch-dark",
[BIND_ACTION_THEME_SWITCH_LIGHT] = "color-theme-switch-light",
[BIND_ACTION_THEME_TOGGLE] = "color-theme-toggle",
/* Mouse-specific actions */
@ -474,8 +476,12 @@ str_to_ulong(const char *s, int base, unsigned long *res)
errno = 0;
char *end = NULL;
*res = strtoul(s, &end, base);
return errno == 0 && *end == '\0';
unsigned long v = strtoul(s, &end, base);
if (!(errno == 0 && *end == '\0'))
return false;
*res = v;
return true;
}
static bool NOINLINE
@ -544,12 +550,13 @@ value_to_float(struct context *ctx, float *res)
errno = 0;
char *end = NULL;
*res = strtof(s, &end);
float v = strtof(s, &end);
if (!(errno == 0 && *end == '\0')) {
LOG_CONTEXTUAL_ERR("invalid decimal value");
return false;
}
*res = v;
return true;
}
@ -641,7 +648,6 @@ value_to_enum(struct context *ctx, const char **value_map, int *res)
valid_values[idx - 2] = '\0';
LOG_CONTEXTUAL_ERR("not one of %s", valid_values);
*res = -1;
return false;
}
@ -690,14 +696,18 @@ value_to_two_colors(struct context *ctx,
goto out;
}
uint32_t a, b;
ctx->value = first_as_str;
if (!value_to_color(ctx, first, allow_alpha))
if (!value_to_color(ctx, &a, allow_alpha))
goto out;
ctx->value = second_as_str;
if (!value_to_color(ctx, second, allow_alpha))
if (!value_to_color(ctx, &b, allow_alpha))
goto out;
*first = a;
*second = b;
ret = true;
out:
@ -915,6 +925,9 @@ parse_section_main(struct context *ctx)
else if (streq(key, "app-id"))
return value_to_str(ctx, &conf->app_id);
else if (streq(key, "toplevel-tag"))
return value_to_str(ctx, &conf->toplevel_tag);
else if (streq(key, "initial-window-size-pixels")) {
if (!value_to_dimensions(ctx, &conf->size.width, &conf->size.height))
return false;
@ -1107,10 +1120,45 @@ parse_section_main(struct context *ctx)
sizeof(conf->initial_color_theme) == sizeof(int),
"enum is not 32-bit");
return value_to_enum(ctx, (const char*[]){"1", "2", NULL},
(int *)&conf->initial_color_theme);
if (!value_to_enum(ctx, (const char*[]){
"dark", "light", "1", "2", NULL},
(int *)&conf->initial_color_theme))
return false;
if (streq(ctx->value, "1")) {
LOG_WARN("%s:%d: [main].initial-color-theme=1 deprecated, "
"use [main].initial-color-theme=dark instead",
ctx->path, ctx->lineno);
user_notification_add(
&ctx->conf->notifications,
USER_NOTIFICATION_DEPRECATED,
xstrdup("[main].initial-color-theme=1: "
"use [main].initial-color-theme=dark instead"));
conf->initial_color_theme = COLOR_THEME_DARK;
}
else if (streq(ctx->value, "2")) {
LOG_WARN("%s:%d: [main].initial-color-theme=2 deprecated, "
"use [main].initial-color-theme=light instead",
ctx->path, ctx->lineno);
user_notification_add(
&ctx->conf->notifications,
USER_NOTIFICATION_DEPRECATED,
xstrdup("[main].initial-color-theme=2: "
"use [main].initial-color-theme=light instead"));
conf->initial_color_theme = COLOR_THEME_LIGHT;
}
return true;
}
else if (streq(key, "uppercase-regex-insert"))
return value_to_bool(ctx, &conf->uppercase_regex_insert);
else {
LOG_CONTEXTUAL_ERR("not a valid option: %s", key);
return false;
@ -1508,7 +1556,7 @@ parse_color_theme(struct context *ctx, struct color_theme *theme)
return true;
}
else if (strcmp(key, "alpha-mode") == 0) {
else if (streq(key, "alpha-mode")) {
_Static_assert(sizeof(theme->alpha_mode) == sizeof(int),
"enum is not 32-bit");
@ -1518,6 +1566,16 @@ parse_color_theme(struct context *ctx, struct color_theme *theme)
(int *)&theme->alpha_mode);
}
else if (streq(key, "dim-blend-towards")) {
_Static_assert(sizeof(theme->dim_blend_towards) == sizeof(int),
"enum is not 32-bit");
return value_to_enum(
ctx,
(const char *[]){"black", "white", NULL},
(int *)&theme->dim_blend_towards);
}
else {
LOG_CONTEXTUAL_ERR("not valid option");
return false;
@ -1531,16 +1589,44 @@ parse_color_theme(struct context *ctx, struct color_theme *theme)
return true;
}
static bool
parse_section_colors_dark(struct context *ctx)
{
return parse_color_theme(ctx, &ctx->conf->colors_dark);
}
static bool
parse_section_colors_light(struct context *ctx)
{
return parse_color_theme(ctx, &ctx->conf->colors_light);
}
static bool
parse_section_colors(struct context *ctx)
{
return parse_color_theme(ctx, &ctx->conf->colors);
LOG_WARN("%s:%d: [colors]: deprecated; use [colors-dark] instead",
ctx->path, ctx->lineno);
user_notification_add(
&ctx->conf->notifications,
USER_NOTIFICATION_DEPRECATED,
xstrdup("[colors]: use [colors-dark] instead"));
return parse_color_theme(ctx, &ctx->conf->colors_dark);
}
static bool
parse_section_colors2(struct context *ctx)
{
return parse_color_theme(ctx, &ctx->conf->colors2);
LOG_WARN("%s:%d: [colors2]: deprecated; use [colors-light] instead",
ctx->path, ctx->lineno);
user_notification_add(
&ctx->conf->notifications,
USER_NOTIFICATION_DEPRECATED,
xstrdup("[colors2]: use [colors-light] instead"));
return parse_color_theme(ctx, &ctx->conf->colors_light);
}
static bool
@ -1575,28 +1661,6 @@ parse_section_cursor(struct context *ctx)
else if (streq(key, "blink-rate"))
return value_to_uint32(ctx, 10, &conf->cursor.blink.rate_ms);
else if (streq(key, "color")) {
LOG_WARN("%s:%d: cursor.color: deprecated; use colors.cursor instead",
ctx->path, ctx->lineno);
user_notification_add(
&conf->notifications,
USER_NOTIFICATION_DEPRECATED,
xstrdup("cursor.color: use colors.cursor instead"));
if (!value_to_two_colors(
ctx,
&conf->colors.cursor.text,
&conf->colors.cursor.cursor,
false))
{
return false;
}
conf->colors.use_custom.cursor = true;
return true;
}
else if (streq(key, "beam-thickness"))
return value_to_pt_or_px(ctx, &conf->cursor.beam_thickness);
@ -2244,6 +2308,29 @@ parse_key_binding_section(struct context *ctx,
aux.regex_name = regex_name;
}
if (action_map == binding_action_map &&
action >= BIND_ACTION_THEME_SWITCH_1 &&
action <= BIND_ACTION_THEME_SWITCH_2)
{
const char *use_instead =
action_map[action == BIND_ACTION_THEME_SWITCH_1
? BIND_ACTION_THEME_SWITCH_DARK
: BIND_ACTION_THEME_SWITCH_LIGHT];
const char *notif = action == BIND_ACTION_THEME_SWITCH_1
? "[key-bindings].color-theme-switch-1: use [key-bindings].color-theme-switch-dark instead"
: "[key-bindings].color-theme-switch-2: use [key-bindings].color-theme-switch-light instead";
LOG_WARN("%s:%d: [key-bindings].%s: deprecated, use %s instead",
ctx->path, ctx->lineno,
action_map[action], use_instead);
user_notification_add(
&ctx->conf->notifications,
USER_NOTIFICATION_DEPRECATED,
xstrdup(notif));
}
if (!value_to_key_combos(ctx, action, &aux, bindings, KEY_BINDING)) {
free_binding_aux(&aux);
return false;
@ -2837,6 +2924,12 @@ parse_section_tweak(struct context *ctx)
#endif
}
else if (streq(key, "min-stride-alignment"))
return value_to_uint32(ctx, 10, &conf->tweak.min_stride_alignment);
else if (streq(key, "pre-apply-damage"))
return value_to_bool(ctx, &conf->tweak.preapply_damage);
else {
LOG_CONTEXTUAL_ERR("not a valid option: %s", key);
return false;
@ -2928,8 +3021,8 @@ enum section {
SECTION_SCROLLBACK,
SECTION_URL,
SECTION_REGEX,
SECTION_COLORS,
SECTION_COLORS2,
SECTION_COLORS_DARK,
SECTION_COLORS_LIGHT,
SECTION_CURSOR,
SECTION_MOUSE,
SECTION_CSD,
@ -2941,6 +3034,11 @@ enum section {
SECTION_ENVIRONMENT,
SECTION_TWEAK,
SECTION_TOUCH,
/* Deprecated */
SECTION_COLORS,
SECTION_COLORS2,
SECTION_COUNT,
};
@ -2959,8 +3057,8 @@ static const struct {
[SECTION_SCROLLBACK] = {&parse_section_scrollback, "scrollback"},
[SECTION_URL] = {&parse_section_url, "url"},
[SECTION_REGEX] = {&parse_section_regex, "regex", true},
[SECTION_COLORS] = {&parse_section_colors, "colors"},
[SECTION_COLORS2] = {&parse_section_colors2, "colors2"},
[SECTION_COLORS_DARK] = {&parse_section_colors_dark, "colors-dark"},
[SECTION_COLORS_LIGHT] = {&parse_section_colors_light, "colors-light"},
[SECTION_CURSOR] = {&parse_section_cursor, "cursor"},
[SECTION_MOUSE] = {&parse_section_mouse, "mouse"},
[SECTION_CSD] = {&parse_section_csd, "csd"},
@ -2972,6 +3070,10 @@ static const struct {
[SECTION_ENVIRONMENT] = {&parse_section_environment, "environment"},
[SECTION_TWEAK] = {&parse_section_tweak, "tweak"},
[SECTION_TOUCH] = {&parse_section_touch, "touch"},
/* Deprecated */
[SECTION_COLORS] = {&parse_section_colors, "colors"},
[SECTION_COLORS2] = {&parse_section_colors2, "colors2"},
};
static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch");
@ -3344,6 +3446,7 @@ config_load(struct config *conf, const char *conf_path,
.shell = get_shell(),
.title = xstrdup("foot"),
.app_id = (as_server ? xstrdup("footclient") : xstrdup("foot")),
.toplevel_tag = xstrdup(""),
.word_delimiters = xc32dup(U",│`|:\"'()[]{}<>"),
.size = {
.type = CONF_SIZE_PX,
@ -3375,6 +3478,7 @@ config_load(struct config *conf, const char *conf_path,
.strikeout_thickness = {.pt = 0., .px = -1},
.dpi_aware = false,
.gamma_correct = false,
.uppercase_regex_insert = true,
.security = {
.osc52 = OSC52_ENABLED,
},
@ -3403,13 +3507,14 @@ config_load(struct config *conf, const char *conf_path,
},
.multiplier = 3.,
},
.colors = {
.colors_dark = {
.fg = default_foreground,
.bg = default_background,
.flash = 0x7f7f00,
.flash_alpha = 0x7fff,
.alpha = 0xffff,
.alpha_mode = ALPHA_MODE_DEFAULT,
.dim_blend_towards = DIM_BLEND_TOWARDS_BLACK,
.selection_fg = 0x80000000, /* Use default bg */
.selection_bg = 0x80000000, /* Use default fg */
.cursor = {
@ -3422,7 +3527,7 @@ config_load(struct config *conf, const char *conf_path,
.url = false,
},
},
.initial_color_theme = COLOR_THEME1,
.initial_color_theme = COLOR_THEME_DARK,
.cursor = {
.style = CURSOR_BLOCK,
.unfocused_style = CURSOR_UNFOCUSED_HOLLOW,
@ -3484,6 +3589,8 @@ config_load(struct config *conf, const char *conf_path,
.font_monospace_warn = true,
.sixel = true,
.surface_bit_depth = SHM_BITS_AUTO,
.min_stride_alignment = 256,
.preapply_damage = true,
},
.touch = {
@ -3500,9 +3607,11 @@ config_load(struct config *conf, const char *conf_path,
.notifications = tll_init(),
};
memcpy(conf->colors.table, default_color_table, sizeof(default_color_table));
memcpy(conf->colors.sixel, default_sixel_colors, sizeof(default_sixel_colors));
memcpy(&conf->colors2, &conf->colors, sizeof(conf->colors));
memcpy(conf->colors_dark.table, default_color_table, sizeof(default_color_table));
memcpy(conf->colors_dark.sixel, default_sixel_colors, sizeof(default_sixel_colors));
memcpy(&conf->colors_light, &conf->colors_dark, sizeof(conf->colors_dark));
conf->colors_light.dim_blend_towards = DIM_BLEND_TOWARDS_WHITE;
parse_modifiers(XKB_MOD_NAME_SHIFT, 5, &conf->mouse.selection_override_modifiers);
tokenize_cmdline(
@ -3790,6 +3899,7 @@ config_clone(const struct config *old)
conf->shell = xstrdup(old->shell);
conf->title = xstrdup(old->title);
conf->app_id = xstrdup(old->app_id);
conf->toplevel_tag = xstrdup(old->toplevel_tag);
conf->word_delimiters = xc32dup(old->word_delimiters);
conf->scrollback.indicator.text = xc32dup(old->scrollback.indicator.text);
conf->server_socket_path = xstrdup(old->server_socket_path);
@ -3889,6 +3999,7 @@ config_free(struct config *conf)
free(conf->shell);
free(conf->title);
free(conf->app_id);
free(conf->toplevel_tag);
free(conf->word_delimiters);
spawn_template_free(&conf->bell.command);
free(conf->scrollback.indicator.text);

View file

@ -145,6 +145,11 @@ struct color_theme {
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,
@ -190,8 +195,10 @@ struct color_theme {
};
enum which_color_theme {
COLOR_THEME1,
COLOR_THEME2,
COLOR_THEME_DARK,
COLOR_THEME_LIGHT,
COLOR_THEME_1, /* Deprecated */
COLOR_THEME_2, /* Deprecated */
};
enum shm_bit_depth {
@ -214,6 +221,7 @@ struct config {
char *shell;
char *title;
char *app_id;
char *toplevel_tag;
char32_t *word_delimiters;
bool login_shell;
bool locked_title;
@ -247,6 +255,7 @@ struct config {
bool dpi_aware;
bool gamma_correct;
bool uppercase_regex_insert;
struct config_font_list fonts[4];
struct font_size_adjustment font_size_adjustment;
@ -320,8 +329,8 @@ struct config {
tll(struct custom_regex) custom_regexes;
struct color_theme colors;
struct color_theme colors2;
struct color_theme colors_dark;
struct color_theme colors_light;
enum which_color_theme initial_color_theme;
struct {
@ -434,6 +443,8 @@ struct config {
bool font_monospace_warn;
bool sixel;
enum shm_bit_depth surface_bit_depth;
uint32_t min_stride_alignment;
bool preapply_damage;
} tweak;
struct {

18
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:
@ -1576,7 +1578,7 @@ csi_dispatch(struct terminal *term, uint8_t final)
int chars = snprintf(
reply, sizeof(reply),
"\033[?997;%dn",
term->colors.active_theme == COLOR_THEME1 ? 1 : 2);
term->colors.active_theme == COLOR_THEME_DARK ? 1 : 2);
term_to_slave(term, reply, chars);
break;
@ -1642,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.

View file

@ -67,6 +67,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 +257,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
@ -689,6 +691,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,7 +24,7 @@ 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*
@ -231,6 +231,13 @@ empty string to be set, but it must be quoted: *KEY=""*)
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
@ -364,14 +371,15 @@ empty string to be set, but it must be quoted: *KEY=""*)
Default: _yes_
*initial-color-theme*
Selects which color theme to use, *1*, or *2*.
Selects which color theme to use, *dark*, or *light*.
*1* uses the colors defined in the *colors* section, while *2*
uses the colors from the *colors2* section.
*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-1*, *color-theme-switch-2* and
Use the *color-theme-switch-dark*, *color-theme-switch-light* and
*color-theme-toggle* key bindings to switch between the two themes
at runtime.
at runtime, or send SIGUSR1/SIGUSR2 to the foot process (see
*foot*(1) for details).
Default: _1_
@ -421,6 +429,11 @@ 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
@ -974,19 +987,24 @@ 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.
In the context of private mode 2031 (Dark and Light Mode detection),
the primary theme (i.e. the *colors* section) is considered to be the
dark theme (since the default theme is dark).
*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
@ -1023,7 +1041,8 @@ dark theme (since the default theme is dark).
a color value, and a "dim" attribute.
By default, foot implements this by blending the current color
with black. This is a generic approach that applies to both
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
@ -1078,6 +1097,14 @@ dark theme (since the default theme is dark).
Default: _default_
*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. Default: _inverse foreground/background_.
@ -1113,19 +1140,6 @@ dark theme (since the default theme is dark).
Flash translucency. A value in the range 0.0-1.0, where 0.0 means
completely transparent, and 1.0 is opaque. Default: _0.5_.
# SECTION: colors2
This section defines an alternative color theme. It has the exact same
keys as the *colors* section. The default values are the same.
Note that values are not inherited. That is, if you set a value in
*colors*, but not in *colors2*, the value from *colors* is not
inherited by *colors2*.
In the context of private mode 2031 (Dark and Light Mode detection),
the alternative theme (i.e. the *colors2* section) is considered to be
the light theme (since the default, the primary theme, is dark).
# SECTION: csd
This section controls the look of the _CSDs_ (Client Side
@ -1432,20 +1446,23 @@ e.g. *search-start=none*.
Default: _Control+Shift+u_.
*color-theme-switch-1*, *color-theme-switch-2*, *color-theme-toggle*
Switch between the primary color theme (defined in the *colors*
section), and the alternative color theme (defined in the
*colors2* section).
*color-theme-switch-dark*, *color-theme-switch-dark*, *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-1* applies the primary color theme regardless
*color-theme-switch-dark* applies the dark color theme regardless
of which color theme is currently active.
*color-theme-switch-2* applies the alternative 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*
@ -2020,6 +2037,32 @@ 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_
@ -2056,6 +2099,41 @@ any of these options.
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 (rending 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)

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

View file

@ -22,6 +22,12 @@ const char version_and_features[] =
" -graphemes"
#endif
#if defined(HAVE_XDG_TOPLEVEL_TAG)
" +toplevel-tag"
#else
" -toplevel-tag"
#endif
#if !defined(NDEBUG)
" +assertions"
#else

View file

@ -24,7 +24,7 @@
# dpi-aware=no
# gamma-correct-blending=no
# initial-color-theme=1
# initial-color-theme=dark
# initial-window-size-pixels=700x500 # Or,
# initial-window-size-chars=<COLSxROWS>
# initial-window-mode=windowed
@ -40,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
@ -99,7 +101,7 @@
[touch]
# long-press-delay=400
[colors]
[colors-dark]
# alpha=1.0
# alpha-mode=default # Can be `default`, `matching` or `all`
# background=242424
@ -130,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>
@ -166,8 +169,10 @@
# search-box-match=<regular0> <regular3> # black-on-yellow
# urls=<regular3>
[colors2]
[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

69
input.c
View file

@ -120,10 +120,14 @@ execute_binding(struct seat *seat, struct terminal *term,
case BIND_ACTION_SCROLLBACK_UP_MOUSE:
if (term->grid == &term->alt) {
if (term->alt_scrolling)
if (term->alt_scrolling) {
alternate_scroll(seat, amount, BTN_BACK);
} else
cmd_scrollback_up(term, amount);
return true;
}
} else {
cmd_scrollback_up(term, amount);
return true;
}
break;
case BIND_ACTION_SCROLLBACK_DOWN_PAGE:
@ -149,10 +153,14 @@ execute_binding(struct seat *seat, struct terminal *term,
case BIND_ACTION_SCROLLBACK_DOWN_MOUSE:
if (term->grid == &term->alt) {
if (term->alt_scrolling)
if (term->alt_scrolling) {
alternate_scroll(seat, amount, BTN_FORWARD);
} else
return true;
}
} else {
cmd_scrollback_down(term, amount);
return true;
}
break;
case BIND_ACTION_SCROLLBACK_HOME:
@ -486,11 +494,13 @@ execute_binding(struct seat *seat, struct terminal *term,
return true;
case BIND_ACTION_THEME_SWITCH_1:
term_theme_switch_to_1(term);
case BIND_ACTION_THEME_SWITCH_DARK:
term_theme_switch_to_dark(term);
return true;
case BIND_ACTION_THEME_SWITCH_2:
term_theme_switch_to_2(term);
case BIND_ACTION_THEME_SWITCH_LIGHT:
term_theme_switch_to_light(term);
return true;
case BIND_ACTION_THEME_TOGGLE:
@ -533,7 +543,7 @@ execute_binding(struct seat *seat, struct terminal *term,
case BIND_ACTION_SELECT_QUOTE:
selection_start(
term, seat->mouse.col, seat->mouse.row, SELECTION_QUOTE_WISE, false);
break;
return true;
case BIND_ACTION_SELECT_ROW:
selection_start(
@ -576,23 +586,20 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
/* Verify keymap is in a format we understand */
switch ((enum wl_keyboard_keymap_format)format) {
case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP:
close(fd);
return;
goto err;
case WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1:
break;
default:
LOG_WARN("unrecognized keymap format: %u", format);
close(fd);
return;
goto err;
}
char *map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map_str == MAP_FAILED) {
LOG_ERRNO("failed to mmap keyboard keymap");
close(fd);
return;
goto err;
}
while (map_str[size - 1] == '\0')
@ -605,6 +612,8 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
}
munmap(map_str, size);
if (seat->kbd.xkb_keymap != NULL) {
seat->kbd.xkb_state = xkb_state_new(seat->kbd.xkb_keymap);
@ -685,10 +694,10 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN");
}
munmap(map_str, size);
close(fd);
key_binding_load_keymap(wayl->key_binding_manager, seat);
err:
close(fd);
}
static void
@ -1596,6 +1605,9 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial,
if (released)
stop_repeater(seat, key);
if (pressed)
seat->kbd.last_shortcut_sym = XKB_KEYSYM_MAX + 1;
bool should_repeat =
pressed && xkb_keymap_key_repeats(seat->kbd.xkb_keymap, key);
@ -1697,6 +1709,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial,
if (bind->k.sym == raw_syms[i] &&
execute_binding(seat, term, bind, serial, 1))
{
seat->kbd.last_shortcut_sym = sym;
goto maybe_repeat;
}
}
@ -1710,6 +1723,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial,
bind->mods == (mods & ~consumed) &&
execute_binding(seat, term, bind, serial, 1))
{
seat->kbd.last_shortcut_sym = sym;
goto maybe_repeat;
}
}
@ -1725,12 +1739,31 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial,
if (code->item == key &&
execute_binding(seat, term, bind, serial, 1))
{
seat->kbd.last_shortcut_sym = sym;
goto maybe_repeat;
}
}
}
}
if (released && seat->kbd.last_shortcut_sym == sym) {
/*
* Don't process a release event, if it corresponds to a
* triggered shortcut.
*
* 1. If we consumed a key (press) event, we shouldn't emit an
* escape for its release event.
* 2. Ignoring the incorrectness of doing so; this also caused
* us to reset the viewport.
*
* Background: if the kitty keyboard protocol was enabled,
* then the viewport was instantly reset to the bottom, after
* scrolling up.
*/
//seat->kbd.last_shortcut_sym = XKB_KEYSYM_MAX + 1;
goto maybe_repeat;
}
/*
* Keys generating escape sequences
*/
@ -1878,7 +1911,7 @@ keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
UNITTEST
{
int chan[2];
pipe2(chan, O_CLOEXEC);
xassert(pipe2(chan, O_CLOEXEC) == 0);
xassert(chan[0] >= 0);
xassert(chan[1] >= 0);

View file

@ -45,6 +45,8 @@ enum bind_action_normal {
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 */

44
main.c
View file

@ -45,23 +45,28 @@ fdm_sigint(struct fdm *fdm, int signo, void *data)
return true;
}
struct sigusr_context {
struct terminal *term;
struct server *server;
};
static bool
fdm_sigusr(struct fdm *fdm, int signo, void *data)
{
struct wayland *wayl = data;
xassert(signo == SIGUSR1 || signo == SIGUSR2);
if (signo == SIGUSR1) {
tll_foreach(wayl->terms, it) {
struct terminal *term = it->item;
term_theme_switch_to_1(term);
}
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 {
tll_foreach(wayl->terms, it) {
struct terminal *term = it->item;
term_theme_switch_to_2(term);
}
if (signo == SIGUSR1)
term_theme_switch_to_dark(ctx->term);
else
term_theme_switch_to_light(ctx->term);
}
return true;
@ -79,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"
@ -180,6 +186,7 @@ sanitize_signals(void)
enum {
PTY_OPTION = CHAR_MAX + 1,
TOPLEVEL_TAG_OPTION = CHAR_MAX + 2,
};
int
@ -209,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'},
@ -280,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)) {
@ -592,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;
@ -630,8 +643,13 @@ main(int argc, char *const *argv)
goto out;
}
if (!fdm_signal_add(fdm, SIGUSR1, &fdm_sigusr, wayl) ||
!fdm_signal_add(fdm, SIGUSR2, &fdm_sigusr, wayl))
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;
}

View file

@ -1,5 +1,5 @@
project('foot', 'c',
version: '1.23.1',
version: '1.25.0',
license: 'MIT',
meson_version: '>=0.59.0',
default_options: [
@ -53,7 +53,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
@ -182,7 +182,12 @@ wl_proto_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.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
foreach prot : wl_proto_xml
wl_proto_headers += custom_target(

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

54
osc.c
View file

@ -513,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;
@ -525,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
@ -1459,9 +1461,9 @@ osc_dispatch(struct terminal *term)
case 11:
term->colors.bg = color;
if (!have_alpha) {
alpha = term->colors.active_theme == COLOR_THEME1
? term->conf->colors.alpha
: term->conf->colors2.alpha;
alpha = term->colors.active_theme == COLOR_THEME_DARK
? term->conf->colors_dark.alpha
: term->conf->colors_light.alpha;
}
const bool changed = term->colors.alpha != alpha;
@ -1516,9 +1518,9 @@ osc_dispatch(struct terminal *term)
/* Reset Color Number 'c' (whole table if no parameter) */
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME1
? &term->conf->colors
: &term->conf->colors2;
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
if (string[0] == '\0') {
LOG_DBG("resetting all colors");
@ -1559,9 +1561,9 @@ osc_dispatch(struct terminal *term)
LOG_DBG("resetting foreground color");
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME1
? &term->conf->colors
: &term->conf->colors2;
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
term->colors.fg = theme->fg;
term_damage_color(term, COLOR_DEFAULT, 0);
@ -1571,9 +1573,9 @@ osc_dispatch(struct terminal *term)
LOG_DBG("resetting background color");
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME1
? &term->conf->colors
: &term->conf->colors2;
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
bool alpha_changed = term->colors.alpha != theme->alpha;
@ -1594,14 +1596,14 @@ osc_dispatch(struct terminal *term)
LOG_DBG("resetting cursor color");
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME1
? &term->conf->colors
: &term->conf->colors2;
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
term->colors.cursor_fg = theme->cursor.text;
term->colors.cursor_bg = theme->cursor.cursor;
if (term->conf->colors.use_custom.cursor) {
if (term->conf->colors_dark.use_custom.cursor) {
term->colors.cursor_fg |= 1u << 31;
term->colors.cursor_bg |= 1u << 31;
}
@ -1614,9 +1616,9 @@ osc_dispatch(struct terminal *term)
LOG_DBG("resetting selection background color");
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME1
? &term->conf->colors
: &term->conf->colors2;
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
term->colors.selection_bg = theme->selection_bg;
break;
@ -1626,9 +1628,9 @@ osc_dispatch(struct terminal *term)
LOG_DBG("resetting selection foreground color");
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME1
? &term->conf->colors
: &term->conf->colors2;
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
term->colors.selection_fg = theme->selection_fg;
break;

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)
{
@ -206,7 +208,8 @@ enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain) { retur
struct buffer_chain *
shm_chain_new(
struct wayland *wayl, bool scrollable, size_t pix_instances,
enum shm_bit_depth desired_bit_depth)
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'

280
render.c
View file

@ -293,7 +293,7 @@ static inline uint32_t
color_dim(const struct terminal *term, uint32_t color)
{
const struct config *conf = term->conf;
const uint8_t custom_dim = conf->colors.use_custom.dim;
const uint8_t custom_dim = conf->colors_dark.use_custom.dim;
if (unlikely(custom_dim != 0)) {
for (size_t i = 0; i < 8; i++) {
@ -302,7 +302,7 @@ color_dim(const struct terminal *term, uint32_t color)
if (term->colors.table[0 + i] == color) {
/* "Regular" color, return the corresponding "dim" */
return conf->colors.dim[i];
return conf->colors_dark.dim[i];
}
else if (term->colors.table[8 + i] == color) {
@ -312,7 +312,14 @@ color_dim(const struct terminal *term, uint32_t color)
}
}
return color_blend_towards(color, 0x00000000, conf->dim.amount);
const struct color_theme *theme = term->colors.active_theme == COLOR_THEME_DARK
? &conf->colors_dark
: &conf->colors_light;
return color_blend_towards(
color,
theme->dim_blend_towards == DIM_BLEND_TOWARDS_BLACK ? 0x00000000 : 0x00ffffff,
conf->dim.amount);
}
static inline uint32_t
@ -769,7 +776,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
}
else if (!term->window->is_fullscreen && term->colors.alpha != 0xffff) {
switch (term->conf->colors.alpha_mode) {
switch (term->conf->colors_dark.alpha_mode) {
case ALPHA_MODE_DEFAULT: {
if (cell->attrs.bg_src == COLOR_DEFAULT) {
alpha = term->colors.alpha;
@ -1168,8 +1175,8 @@ render_cell(struct terminal *term, pixman_image_t *pix,
if (unlikely(cell->attrs.url)) {
pixman_color_t url_color = color_hex_to_pixman(
term->conf->colors.use_custom.url
? term->conf->colors.url
term->conf->colors_dark.use_custom.url
? term->conf->colors_dark.url
: term->colors.table[3],
gamma_correct);
draw_underline(term, pix, font, &url_color, x, y, cell_cols);
@ -1984,8 +1991,8 @@ render_overlay(struct terminal *term)
case OVERLAY_FLASH:
color = color_hex_to_pixman_with_alpha(
term->conf->colors.flash,
term->conf->colors.flash_alpha,
term->conf->colors_dark.flash,
term->conf->colors_dark.flash_alpha,
wayl_do_linear_blending(term->wl, term->conf));
break;
@ -2005,7 +2012,7 @@ render_overlay(struct terminal *term)
}
struct buffer *buf = shm_get_buffer(
term->render.chains.overlay, term->width, term->height, true);
term->render.chains.overlay, term->width, term->height);
pixman_image_set_clip_region32(buf->pix[0], NULL);
/* Bounding rectangle of damaged areas - for wl_surface_damage_buffer() */
@ -2224,6 +2231,56 @@ render_worker_thread(void *_ctx)
case -2:
return 0;
case -3: {
if (term->conf->tweak.render_timer != RENDER_TIMER_NONE)
clock_gettime(CLOCK_MONOTONIC, &term->render.workers.preapplied_damage.start);
mtx_lock(&term->render.workers.preapplied_damage.lock);
buf = term->render.workers.preapplied_damage.buf;
xassert(buf != NULL);
if (likely(term->render.last_buf != NULL)) {
mtx_unlock(&term->render.workers.preapplied_damage.lock);
pixman_region32_t dmg;
pixman_region32_init(&dmg);
if (buf->age == 0)
; /* No need to do anything */
else if (buf->age == 1)
pixman_region32_copy(&dmg,
&term->render.last_buf->dirty[0]);
else
pixman_region32_init_rect(&dmg, 0, 0, buf->width,
buf->height);
pixman_image_set_clip_region32(buf->pix[my_id], &dmg);
pixman_image_composite32(PIXMAN_OP_SRC,
term->render.last_buf->pix[my_id],
NULL, buf->pix[my_id], 0, 0, 0, 0, 0,
0, buf->width, buf->height);
pixman_region32_fini(&dmg);
buf->age = 0;
shm_unref(term->render.last_buf);
shm_addref(buf);
term->render.last_buf = buf;
mtx_lock(&term->render.workers.preapplied_damage.lock);
}
term->render.workers.preapplied_damage.buf = NULL;
cnd_signal(&term->render.workers.preapplied_damage.cond);
mtx_unlock(&term->render.workers.preapplied_damage.lock);
if (term->conf->tweak.render_timer != RENDER_TIMER_NONE)
clock_gettime(CLOCK_MONOTONIC, &term->render.workers.preapplied_damage.stop);
frame_done = true;
break;
}
}
}
};
@ -2231,6 +2288,22 @@ render_worker_thread(void *_ctx)
return -1;
}
void
render_wait_for_preapply_damage(struct terminal *term)
{
if (!term->render.preapply_last_frame_damage)
return;
if (term->render.workers.preapplied_damage.buf == NULL)
return;
mtx_lock(&term->render.workers.preapplied_damage.lock);
while (term->render.workers.preapplied_damage.buf != NULL) {
cnd_wait(&term->render.workers.preapplied_damage.cond,
&term->render.workers.preapplied_damage.lock);
}
mtx_unlock(&term->render.workers.preapplied_damage.lock);
}
struct csd_data
get_csd_data(const struct terminal *term, enum csd_surface surf_idx)
{
@ -2437,10 +2510,10 @@ render_csd_title(struct terminal *term, const struct csd_data *info,
uint32_t bg = term->conf->csd.color.title_set
? term->conf->csd.color.title
: 0xffu << 24 | term->conf->colors.fg;
: 0xffu << 24 | term->conf->colors_dark.fg;
uint32_t fg = term->conf->csd.color.buttons_set
? term->conf->csd.color.buttons
: term->conf->colors.bg;
: term->conf->colors_dark.bg;
if (!term->visual_focus) {
bg = color_dim(term, bg);
@ -2534,7 +2607,7 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx,
uint32_t _color =
conf->csd.color.border_set ? conf->csd.color.border :
conf->csd.color.title_set ? conf->csd.color.title :
0xffu << 24 | term->conf->colors.fg;
0xffu << 24 | term->conf->colors_dark.fg;
if (!term->visual_focus)
_color = color_dim(term, _color);
@ -2554,7 +2627,7 @@ static pixman_color_t
get_csd_button_fg_color(const struct terminal *term)
{
const struct config *conf = term->conf;
uint32_t _color = conf->colors.bg;
uint32_t _color = conf->colors_dark.bg;
uint16_t alpha = 0xffff;
if (conf->csd.color.buttons_set) {
@ -2799,7 +2872,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx,
switch (surf_idx) {
case CSD_SURF_MINIMIZE:
_color = term->conf->colors.table[4]; /* blue */
_color = term->conf->colors_dark.table[4]; /* blue */
is_set = term->conf->csd.color.minimize_set;
conf_color = &term->conf->csd.color.minimize;
is_active = term->active_surface == TERM_SURF_BUTTON_MINIMIZE &&
@ -2807,7 +2880,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx,
break;
case CSD_SURF_MAXIMIZE:
_color = term->conf->colors.table[2]; /* green */
_color = term->conf->colors_dark.table[2]; /* green */
is_set = term->conf->csd.color.maximize_set;
conf_color = &term->conf->csd.color.maximize;
is_active = term->active_surface == TERM_SURF_BUTTON_MAXIMIZE &&
@ -2815,7 +2888,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx,
break;
case CSD_SURF_CLOSE:
_color = term->conf->colors.table[1]; /* red */
_color = term->conf->colors_dark.table[1]; /* red */
is_set = term->conf->csd.color.close_set;
conf_color = &term->conf->csd.color.quit;
is_active = term->active_surface == TERM_SURF_BUTTON_CLOSE &&
@ -2897,7 +2970,7 @@ render_csd(struct terminal *term)
}
struct buffer *bufs[CSD_SURF_COUNT];
shm_get_many(term->render.chains.csd, CSD_SURF_COUNT, widths, heights, bufs, true);
shm_get_many(term->render.chains.csd, CSD_SURF_COUNT, widths, heights, bufs);
for (size_t i = CSD_SURF_LEFT; i <= CSD_SURF_BOTTOM; i++)
render_csd_border(term, i, &infos[i], bufs[i]);
@ -3037,16 +3110,16 @@ render_scrollback_position(struct terminal *term)
}
struct buffer_chain *chain = term->render.chains.scrollback_indicator;
struct buffer *buf = shm_get_buffer(chain, width, height, false);
struct buffer *buf = shm_get_buffer(chain, width, height);
wl_subsurface_set_position(
win->scrollback_indicator.sub, roundf(x / scale), roundf(y / scale));
uint32_t fg = term->colors.table[0];
uint32_t bg = term->colors.table[8 + 4];
if (term->conf->colors.use_custom.scrollback_indicator) {
fg = term->conf->colors.scrollback_indicator.fg;
bg = term->conf->colors.scrollback_indicator.bg;
if (term->conf->colors_dark.use_custom.scrollback_indicator) {
fg = term->conf->colors_dark.scrollback_indicator.fg;
bg = term->conf->colors_dark.scrollback_indicator.bg;
}
render_osd(
@ -3080,7 +3153,7 @@ render_render_timer(struct terminal *term, struct timespec render_time)
height = roundf(scale * ceilf(height / scale));
struct buffer_chain *chain = term->render.chains.render_timer;
struct buffer *buf = shm_get_buffer(chain, width, height, false);
struct buffer *buf = shm_get_buffer(chain, width, height);
wl_subsurface_set_position(
win->render_timer.sub,
@ -3113,14 +3186,6 @@ force_full_repaint(struct terminal *term, struct buffer *buf)
static void
reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old)
{
static int counter = 0;
static bool have_warned = false;
if (!have_warned && ++counter > 5) {
LOG_WARN("compositor is not releasing buffers immediately; "
"expect lower rendering performance");
have_warned = true;
}
if (new->age > 1) {
memcpy(new->data, old->data, new->height * new->stride);
return;
@ -3251,7 +3316,18 @@ grid_render(struct terminal *term)
if (term->shutdown.in_progress)
return;
struct timespec start_time, start_double_buffering = {0}, stop_double_buffering = {0};
struct timespec start_time;
struct timespec start_wait_preapply = {0}, stop_wait_preapply = {0};
struct timespec start_double_buffering = {0}, stop_double_buffering = {0};
/* Might be a thread doing pre-applied damage */
if (unlikely(term->render.preapply_last_frame_damage &&
term->render.workers.preapplied_damage.buf != NULL))
{
clock_gettime(CLOCK_MONOTONIC, &start_wait_preapply);
render_wait_for_preapply_damage(term);
clock_gettime(CLOCK_MONOTONIC, &stop_wait_preapply);
}
if (term->conf->tweak.render_timer != RENDER_TIMER_NONE)
clock_gettime(CLOCK_MONOTONIC, &start_time);
@ -3260,15 +3336,14 @@ grid_render(struct terminal *term)
xassert(term->height > 0);
struct buffer_chain *chain = term->render.chains.grid;
bool use_alpha = !term->window->is_fullscreen &&
term->colors.alpha != 0xffff;
struct buffer *buf = shm_get_buffer(
chain, term->width, term->height, use_alpha);
struct buffer *buf = shm_get_buffer(chain, term->width, term->height);
/* Dirty old and current cursor cell, to ensure they're repainted */
dirty_old_cursor(term);
dirty_cursor(term);
LOG_DBG("buffer age: %u (%p)", buf->age, (void *)buf);
if (term->render.last_buf == NULL ||
term->render.last_buf->width != buf->width ||
term->render.last_buf->height != buf->height ||
@ -3285,9 +3360,27 @@ grid_render(struct terminal *term)
xassert(term->render.last_buf->width == buf->width);
xassert(term->render.last_buf->height == buf->height);
if (++term->render.frames_since_last_immediate_release > 10) {
static bool have_warned = false;
if (!term->render.preapply_last_frame_damage &&
term->conf->tweak.preapply_damage &&
term->render.workers.count > 0)
{
LOG_INFO("enabling pre-applied frame damage");
term->render.preapply_last_frame_damage = true;
} else if (!have_warned && !term->render.preapply_last_frame_damage) {
LOG_WARN("compositor is not releasing buffers immediately; "
"expect lower rendering performance");
have_warned = true;
}
}
clock_gettime(CLOCK_MONOTONIC, &start_double_buffering);
reapply_old_damage(term, buf, term->render.last_buf);
clock_gettime(CLOCK_MONOTONIC, &stop_double_buffering);
} else if (!term->render.preapply_last_frame_damage) {
term->render.frames_since_last_immediate_release = 0;
}
if (term->render.last_buf != NULL) {
@ -3515,27 +3608,40 @@ grid_render(struct terminal *term)
struct timespec end_time;
clock_gettime(CLOCK_MONOTONIC, &end_time);
struct timespec wait_time;
timespec_sub(&stop_wait_preapply, &start_wait_preapply, &wait_time);
struct timespec render_time;
timespec_sub(&end_time, &start_time, &render_time);
struct timespec double_buffering_time;
timespec_sub(&stop_double_buffering, &start_double_buffering, &double_buffering_time);
struct timespec preapply_damage;
timespec_sub(&term->render.workers.preapplied_damage.stop,
&term->render.workers.preapplied_damage.start,
&preapply_damage);
struct timespec total_render_time;
timespec_add(&render_time, &double_buffering_time, &total_render_time);
timespec_add(&wait_time, &total_render_time, &total_render_time);
switch (term->conf->tweak.render_timer) {
case RENDER_TIMER_LOG:
case RENDER_TIMER_BOTH:
LOG_INFO(
"frame rendered in %lds %9ldns "
"(%lds %9ldns rendering, %lds %9ldns double buffering)",
"(%lds %9ldns wait, %lds %9ldns rendering, %lds %9ldns double buffering) not included: %lds %ldns pre-apply damage",
(long)total_render_time.tv_sec,
total_render_time.tv_nsec,
(long)wait_time.tv_sec,
wait_time.tv_nsec,
(long)render_time.tv_sec,
render_time.tv_nsec,
(long)double_buffering_time.tv_sec,
double_buffering_time.tv_nsec);
double_buffering_time.tv_nsec,
(long)preapply_damage.tv_sec,
preapply_damage.tv_nsec);
break;
case RENDER_TIMER_OSD:
@ -3678,7 +3784,7 @@ render_search_box(struct terminal *term)
size_t glyph_offset = term->render.search_glyph_offset;
struct buffer_chain *chain = term->render.chains.search;
struct buffer *buf = shm_get_buffer(chain, width, height, true);
struct buffer *buf = shm_get_buffer(chain, width, height);
pixman_region32_t clip;
pixman_region32_init_rect(&clip, 0, 0, width, height);
@ -3690,18 +3796,18 @@ render_search_box(struct terminal *term)
const bool is_match = term->search.match_len == text_len;
const bool custom_colors = is_match
? term->conf->colors.use_custom.search_box_match
: term->conf->colors.use_custom.search_box_no_match;
? term->conf->colors_dark.use_custom.search_box_match
: term->conf->colors_dark.use_custom.search_box_no_match;
/* Background - yellow on empty/match, red on mismatch (default) */
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
const pixman_color_t color = color_hex_to_pixman(
is_match
? (custom_colors
? term->conf->colors.search_box.match.bg
? term->conf->colors_dark.search_box.match.bg
: term->colors.table[3])
: (custom_colors
? term->conf->colors.search_box.no_match.bg
? term->conf->colors_dark.search_box.no_match.bg
: term->colors.table[1]),
gamma_correct);
@ -3723,8 +3829,8 @@ render_search_box(struct terminal *term)
pixman_color_t fg = color_hex_to_pixman(
custom_colors
? (is_match
? term->conf->colors.search_box.match.fg
: term->conf->colors.search_box.no_match.fg)
? term->conf->colors_dark.search_box.match.fg
: term->conf->colors_dark.search_box.no_match.fg)
: term->colors.table[0],
gamma_correct);
@ -4143,13 +4249,13 @@ render_urls(struct terminal *term)
struct buffer_chain *chain = term->render.chains.url;
struct buffer *bufs[render_count];
shm_get_many(chain, render_count, widths, heights, bufs, false);
shm_get_many(chain, render_count, widths, heights, bufs);
uint32_t fg = term->conf->colors.use_custom.jump_label
? term->conf->colors.jump_label.fg
uint32_t fg = term->conf->colors_dark.use_custom.jump_label
? term->conf->colors_dark.jump_label.fg
: term->colors.table[0];
uint32_t bg = term->conf->colors.use_custom.jump_label
? term->conf->colors.jump_label.bg
uint32_t bg = term->conf->colors_dark.use_custom.jump_label
? term->conf->colors_dark.jump_label.bg
: term->colors.table[3];
for (size_t i = 0; i < render_count; i++) {
@ -4295,6 +4401,7 @@ delayed_reflow_of_normal_grid(struct terminal *term)
term->interactive_resizing.old_hide_cursor = false;
/* Invalidate render pointers */
render_wait_for_preapply_damage(term);
shm_unref(term->render.last_buf);
term->render.last_buf = NULL;
term->render.last_cursor.row = NULL;
@ -4869,6 +4976,7 @@ damage_view:
tll_free(term->normal.scroll_damage);
tll_free(term->alt.scroll_damage);
render_wait_for_preapply_damage(term);
shm_unref(term->render.last_buf);
term->render.last_buf = NULL;
term_damage_view(term);
@ -5267,3 +5375,77 @@ render_xcursor_set(struct seat *seat, struct terminal *term,
seat->pointer.xcursor_pending = true;
return true;
}
void
render_buffer_release_callback(struct buffer *buf, void *data)
{
/*
* Called from shm.c when a buffer is released
*
* We use it to pre-apply last-frame's damage to it, when we're
* forced to double buffer (compositor doesn't release buffers
* immediately).
*
* The timeline is thus:
* 1. We render and push a new frame
* 2. Some (hopefully short) time after that, the compositor releases the previous buffer
* 3. We're called, and kick off the thread that copies the changes from (1) to the just freed buffer
* 4. Time passes....
* 5. The compositor calls our frame callback, signalling to us that it's time to start rendering the next frame
* 6. Hopefully, our thread is already done with copying the changes, otherwise we stall, waiting for it
* 7. We render the frame as if the compositor does immediate releases.
*
* What's the gain? Reduced latency, by applying the previous
* frame's damage as soon as possible, we shorten the time it
* takes to render the frame after the frame callback.
*
* This means the compositor can, in theory, push the frame
* callback closer to the vblank deadline, and thus reduce input
* latency. Not all compositors (most, in fact?) don't adapt like
* this, unfortunately. But some allows the user to manually
* configure the deadline.
*/
struct terminal *term = data;
if (likely(buf->age != 1))
return;
if (likely(!term->render.preapply_last_frame_damage))
return;
if (term->render.last_buf == NULL)
return;
if (term->render.last_buf->age != 0)
return;
if (buf->width != term->render.last_buf->width)
return;
if (buf->height != term->render.last_buf->height)
return;
xassert(term->render.workers.count > 0);
xassert(term->render.last_buf != NULL);
xassert(term->render.last_buf->age == 0);
xassert(term->render.last_buf != buf);
mtx_lock(&term->render.workers.preapplied_damage.lock);
if (term->render.workers.preapplied_damage.buf != NULL) {
mtx_unlock(&term->render.workers.preapplied_damage.lock);
return;
}
xassert(term->render.workers.preapplied_damage.buf == NULL);
term->render.workers.preapplied_damage.buf = buf;
term->render.workers.preapplied_damage.start = (struct timespec){0};
term->render.workers.preapplied_damage.stop = (struct timespec){0};
mtx_unlock(&term->render.workers.preapplied_damage.lock);
mtx_lock(&term->render.workers.lock);
sem_post(&term->render.workers.start);
xassert(tll_length(term->render.workers.queue) == 0);
tll_push_back(term->render.workers.queue, -3);
mtx_unlock(&term->render.workers.lock);
}

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

View file

@ -2,7 +2,6 @@
import argparse
import math
import sys
# Note: we use a pure gamma 2.2 function, rather than the piece-wise
@ -17,7 +16,7 @@ def linear_to_srgb(f: float) -> float:
return math.pow(f, 1 / 2.2)
def main():
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('c_output', type=argparse.FileType('w'))
parser.add_argument('h_output', type=argparse.FileType('w'))
@ -68,4 +67,4 @@ def main():
if __name__ == '__main__':
sys.exit(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)
@ -1479,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

@ -30,7 +30,7 @@ struct client;
struct terminal_instance;
struct server {
const struct config *conf;
struct config *conf;
struct fdm *fdm;
struct reaper *reaper;
struct wayland *wayl;
@ -156,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) {
@ -505,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;
@ -617,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);

131
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 {
@ -93,11 +97,11 @@ struct buffer_chain {
size_t pix_instances;
bool scrollable;
pixman_format_code_t pixman_fmt_without_alpha;
enum wl_shm_format shm_format_without_alpha;
pixman_format_code_t pixman_fmt;
enum wl_shm_format shm_format;
pixman_format_code_t pixman_fmt_with_alpha;
enum wl_shm_format shm_format_with_alpha;
void (*release_cb)(struct buffer *buf, void *data);
void *cb_data;
};
static tll(struct buffer_private *) deferred;
@ -113,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)
{
@ -224,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);
}
}
}
@ -231,7 +245,6 @@ static const struct wl_buffer_listener buffer_listener = {
.release = &buffer_release,
};
#if __SIZEOF_POINTER__ == 8
static size_t
page_size(void)
{
@ -248,7 +261,6 @@ page_size(void)
xassert(size > 0);
return size;
}
#endif
static bool
instantiate_offset(struct buffer_private *buf, off_t new_offset)
@ -269,9 +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
? buf->chain->shm_format_with_alpha
: buf->chain->shm_format_without_alpha);
buf->chain->shm_format);
if (wl_buf == NULL) {
LOG_ERR("failed to create SHM buffer");
@ -281,9 +291,7 @@ 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
? buf->chain->pixman_fmt_with_alpha
: buf->chain->pixman_fmt_without_alpha,
buf->chain->pixman_fmt,
buf->public.width, buf->public.height,
(uint32_t *)mmapped, buf->public.stride);
@ -318,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);
/*
@ -338,10 +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
? chain->pixman_fmt_with_alpha
: chain->pixman_fmt_without_alpha,
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];
}
@ -383,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
@ -395,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);
@ -427,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;
@ -492,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)) {
@ -562,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 "
@ -579,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);
@ -597,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 */
@ -632,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;
}
@ -975,13 +991,11 @@ shm_unref(struct buffer *_buf)
struct buffer_chain *
shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances,
enum shm_bit_depth desired_bit_depth)
enum shm_bit_depth desired_bit_depth,
void (*release_cb)(struct buffer *buf, void *data), void *cb_data)
{
pixman_format_code_t pixman_fmt_without_alpha = PIXMAN_x8r8g8b8;
enum wl_shm_format shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB8888;
pixman_format_code_t pixman_fmt_with_alpha = PIXMAN_a8r8g8b8;
enum wl_shm_format shm_fmt_with_alpha = WL_SHM_FORMAT_ARGB8888;
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;
@ -990,12 +1004,9 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances,
static bool have_logged_16_fallback = false;
if (desired_bit_depth == SHM_BITS_16) {
if (wayl->shm_have_abgr161616 && wayl->shm_have_xbgr161616) {
pixman_fmt_without_alpha = PIXMAN_a16b16g16r16;
shm_fmt_without_alpha = WL_SHM_FORMAT_XBGR16161616;
pixman_fmt_with_alpha = PIXMAN_a16b16g16r16;
shm_fmt_with_alpha = WL_SHM_FORMAT_ABGR16161616;
if (wayl->shm_have_abgr161616) {
pixman_fmt = PIXMAN_a16b16g16r16;
shm_fmt = WL_SHM_FORMAT_ABGR16161616;
if (!have_logged) {
have_logged = true;
@ -1013,15 +1024,10 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances,
}
#endif
if (desired_bit_depth >= SHM_BITS_10 &&
pixman_fmt_with_alpha == PIXMAN_a8r8g8b8)
{
if (wayl->shm_have_argb2101010 && wayl->shm_have_xrgb2101010) {
pixman_fmt_without_alpha = PIXMAN_x2r10g10b10;
shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB2101010;
pixman_fmt_with_alpha = PIXMAN_a2r10g10b10;
shm_fmt_with_alpha = WL_SHM_FORMAT_ARGB2101010;
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;
@ -1029,12 +1035,9 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances,
}
}
else if (wayl->shm_have_abgr2101010 && wayl->shm_have_xbgr2101010) {
pixman_fmt_without_alpha = PIXMAN_x2b10g10r10;
shm_fmt_without_alpha = WL_SHM_FORMAT_XBGR2101010;
pixman_fmt_with_alpha = PIXMAN_a2b10g10r10;
shm_fmt_with_alpha = WL_SHM_FORMAT_ABGR2101010;
else if (wayl->shm_have_abgr2101010) {
pixman_fmt = PIXMAN_a2b10g10r10;
shm_fmt = WL_SHM_FORMAT_ABGR2101010;
if (!have_logged) {
have_logged = true;
@ -1066,11 +1069,11 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances,
.pix_instances = pix_instances,
.scrollable = scrollable,
.pixman_fmt_without_alpha = pixman_fmt_without_alpha,
.shm_format_without_alpha = shm_fmt_without_alpha,
.pixman_fmt = pixman_fmt,
.shm_format = shm_fmt,
.pixman_fmt_with_alpha = pixman_fmt_with_alpha,
.shm_format_with_alpha = shm_fmt_with_alpha,
.release_cb = release_cb,
.cb_data = cb_data,
};
return chain;
}
@ -1094,7 +1097,7 @@ shm_chain_free(struct buffer_chain *chain)
enum shm_bit_depth
shm_chain_bit_depth(const struct buffer_chain *chain)
{
const pixman_format_code_t fmt = chain->pixman_fmt_with_alpha;
const pixman_format_code_t fmt = chain->pixman_fmt;
return fmt == PIXMAN_a8r8g8b8
? SHM_BITS_8

11
shm.h
View file

@ -42,12 +42,16 @@ 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 wayland *wayl, bool scrollable, size_t pix_instances,
enum shm_bit_depth desired_bit_depth);
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);
@ -61,8 +65,7 @@ enum shm_bit_depth shm_chain_bit_depth(const 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.
@ -80,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);

13
sixel.c
View file

@ -125,19 +125,19 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
* that assumes 32-bit pixels).
*/
if (shm_chain_bit_depth(term->render.chains.grid) >= SHM_BITS_10) {
if (term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) {
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->wl->shm_have_xbgr2101010) {
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.sixel), term->sixel.palette_size);
ALEN(term->conf->colors_dark.sixel), term->sixel.palette_size);
if (term->sixel.use_private_palette) {
xassert(term->sixel.private_palette == NULL);
@ -145,7 +145,7 @@ 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,
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) {
@ -164,7 +164,7 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
term->sixel.palette_size, sizeof(term->sixel.shared_palette[0]));
memcpy(
term->sixel.shared_palette, term->conf->colors.sixel,
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) {
@ -1559,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;

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

@ -199,7 +199,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
@ -223,7 +223,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
@ -719,6 +719,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]));
@ -1268,8 +1271,10 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
const struct color_theme *theme = NULL;
switch (conf->initial_color_theme) {
case COLOR_THEME1: theme = &conf->colors; break;
case COLOR_THEME2: theme = &conf->colors2; break;
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 */
@ -1356,13 +1361,13 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
.render = {
.chains = {
.grid = shm_chain_new(wayl, true, 1 + conf->render_worker_count,
desired_bit_depth),
.search = shm_chain_new(wayl, false, 1 ,desired_bit_depth),
.scrollback_indicator = shm_chain_new(wayl, false, 1, desired_bit_depth),
.render_timer = shm_chain_new(wayl, false, 1, desired_bit_depth),
.url = shm_chain_new(wayl, false, 1, desired_bit_depth),
.csd = shm_chain_new(wayl, false, 1, desired_bit_depth),
.overlay = shm_chain_new(wayl, false, 1, desired_bit_depth),
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,
@ -1893,6 +1898,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);
@ -2172,8 +2179,10 @@ term_reset(struct terminal *term, bool hard)
const struct color_theme *theme = NULL;
switch (term->conf->initial_color_theme) {
case COLOR_THEME1: theme = &term->conf->colors; break;
case COLOR_THEME2: theme = &term->conf->colors2; break;
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;
@ -3160,11 +3169,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);
@ -3172,6 +3187,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 */
@ -3188,11 +3208,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
}
@ -3399,10 +3424,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",
@ -4005,9 +4033,11 @@ term_print(struct terminal *term, char32_t wc, int width, bool insert_mode_disab
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:
@ -4716,13 +4746,13 @@ term_send_size_notification(struct terminal *term)
}
void
term_theme_switch_to_1(struct terminal *term)
term_theme_switch_to_dark(struct terminal *term)
{
if (term->colors.active_theme == COLOR_THEME1)
if (term->colors.active_theme == COLOR_THEME_DARK)
return;
term_theme_apply(term, &term->conf->colors);
term->colors.active_theme = COLOR_THEME1;
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);
@ -4736,13 +4766,13 @@ term_theme_switch_to_1(struct terminal *term)
}
void
term_theme_switch_to_2(struct terminal *term)
term_theme_switch_to_light(struct terminal *term)
{
if (term->colors.active_theme == COLOR_THEME2)
if (term->colors.active_theme == COLOR_THEME_LIGHT)
return;
term_theme_apply(term, &term->conf->colors2);
term->colors.active_theme = COLOR_THEME2;
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);
@ -4758,15 +4788,15 @@ term_theme_switch_to_2(struct terminal *term)
void
term_theme_toggle(struct terminal *term)
{
if (term->colors.active_theme == COLOR_THEME1) {
term_theme_apply(term, &term->conf->colors2);
term->colors.active_theme = COLOR_THEME2;
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);
term->colors.active_theme = COLOR_THEME1;
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);

View file

@ -706,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 */
@ -716,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;
@ -984,8 +994,8 @@ 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_1(struct terminal *term);
void term_theme_switch_to_2(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);
static inline void term_reset_grapheme_state(struct terminal *term)

View file

@ -482,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);
@ -491,6 +492,7 @@ test_section_main(void)
test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title);
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);
@ -519,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) */
@ -693,73 +703,157 @@ 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, "cursor", false,
&conf.colors.cursor.text,
&conf.colors.cursor.cursor);
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, "alpha-mode", 3,
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.alpha_mode);
(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_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_invalid_key(&ctx, &parse_section_colors_light, "256");
/* TODO: alpha (float in range 0-1, converted to uint16_t) */
@ -1412,6 +1506,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);
@ -1434,7 +1531,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,7 +1,7 @@
# -*- conf -*-
# Aero root theme
[colors]
[colors-dark]
cursor=1a1a1a 9fd5f5
foreground=dedeef
background=1a1a1a

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Alacritty
[colors]
[colors-dark]
cursor = 181818 56d8c9
background= 181818
foreground= d8d8d8

View file

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

View file

@ -2,7 +2,7 @@
# theme: Ayu Mirage
# description: a theme based on Ayu Mirage for Sublime Text (original: https://github.com/dempfi/ayu)
[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

View file

@ -1,7 +1,10 @@
# _*_ conf _*_
# Catppuccin Latte
[colors]
[main]
initial-color-theme=light
[colors-light]
foreground=4c4f69
background=eff1f5

View file

@ -1,7 +1,7 @@
# _*_ conf _*_
# Catppuccin Macchiato
[colors]
[colors-dark]
foreground=cad3f5
background=24273a

View file

@ -1,7 +1,7 @@
# _*_ conf _*_
# Catppuccin Mocha
[colors]
[colors-dark]
foreground=cdd6f4
background=1e1e2e

View file

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

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Derp
[colors]
[colors-dark]
cursor=000000 ffffff
foreground=ffffff
background=000000

View file

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

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Dracula
[colors]
[colors-dark]
cursor=282a36 f8f8f2
foreground=f8f8f2
background=282a36

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Dracula iTerm2 variant
[colors]
[colors-dark]
cursor=ffffff bbbbbb
foreground=f8f8f2
background=1e1f29

View file

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

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Gruvbox
[colors]
[colors-dark]
background=282828
foreground=ebdbb2
regular0=282828
@ -21,7 +21,7 @@ bright5=d3869b
bright6=8ec07c
bright7=ebdbb2
[colors2]
[colors-light]
background=fbf1c7
foreground=3c3836
regular0=fbf1c7

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,6 +1,6 @@
# -*- conf -*-
[colors]
[colors-dark]
cursor=141414 c9c9c9
foreground=c9c9c9
background=141414

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,7 +2,7 @@
# JetBrains Darcula
# Palette based on the same theme from https://github.com/dexpota/kitty-themes
[colors]
[colors-dark]
cursor=202020 ffffff
background=202020
foreground=adadad

View file

@ -1,6 +1,6 @@
# -*- conf -*-
[colors]
[colors-dark]
cursor=111111 cccccc
foreground=dddddd
background=000000

View file

@ -2,7 +2,10 @@
# Material Amber
# Based on material.io guidelines with Amber 50 background
[colors]
[main]
initial-color-theme=light
[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

View file

@ -2,7 +2,7 @@
# Molokai
# Based on zhou13's at https://github.com/zhou13/molokai-terminal/blob/master/xterm/Xresources
[colors]
[colors-dark]
background=1B1D1E
foreground=CCCCCC
regular0=1B1D1E

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Monokai Pro
[colors]
[colors-dark]
background=2D2A2E
foreground=FCFCFA
regular0=403E41

View file

@ -2,7 +2,7 @@
# moonfly
# Based on https://github.com/bluz71/vim-moonfly-colors
[colors]
[colors-dark]
cursor = 080808 9e9e9e
foreground = b2b2b2
background = 080808

View file

@ -6,7 +6,7 @@
# https://xcolors.net/neon
#
[colors]
[colors-dark]
foreground=f8f8f8
background=171717
regular0=171717

View file

@ -1,7 +1,7 @@
# _*_ conf _*_
# Night Owl
[colors]
[colors-dark]
cursor=011627 80a4c2
foreground=d6deeb
background=011627

View file

@ -2,7 +2,7 @@
# nightfly
# Based on https://github.com/bluz71/vim-nightfly-guicolors
[colors]
[colors-dark]
cursor = 080808 9ca1aa
foreground = acb4c2
background = 011627

View file

@ -3,7 +3,7 @@
# https://github.com/n1ghtmare/noirblaze-kitty
[colors]
[colors-dark]
cursor=121212 ff0088
foreground=d5d5d5
background=121212

View file

@ -6,7 +6,7 @@
# this specific foot theme is based on nord-alacritty:
# https://github.com/arcticicestudio/nord-alacritty/blob/develop/src/nord.yml
[colors]
[colors-dark]
cursor = 2e3440 d8dee9
foreground = d8dee9
background = 2e3440

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Nordiq
[colors]
[colors-dark]
cursor=eeeeee 9f515a
foreground=dbdee9
background=0e1420

View file

@ -3,7 +3,7 @@
# Uses the dark color palette from the default Neovim color scheme
# See: https://github.com/neovim/neovim/blob/fb6c059dc55c8d594102937be4dd70f5ff51614a/src/nvim/highlight_group.c#L419
[colors]
[colors-dark]
cursor=14161b e0e2ea # NvimDarkGrey2 NvimLightGrey2
foreground=e0e2ea # NvimLightGrey2
background=14161b # NvimDarkGrey2
@ -29,7 +29,7 @@ bright5=ffcaff # NvimLightMagenta
bright6=8cf8f7 # NvimLightCyan
bright7=eef1f8 # NvimLightGrey1
[colors2]
[colors-light]
cursor=e0e2ea 14161b # NvimLightGrey2 NvimDarkGrey2
foreground=14161b # NvimDarkGrey2
background=e0e2ea # NvimLightGrey2

View file

@ -3,7 +3,7 @@
# Uses the dark color palette from the default Neovim color scheme
# See: https://github.com/neovim/neovim/blob/fb6c059dc55c8d594102937be4dd70f5ff51614a/src/nvim/highlight_group.c#L419
[colors]
[colors-dark]
cursor=14161b e0e2ea # NvimDarkGrey2 NvimLightGrey2
foreground=e0e2ea # NvimLightGrey2
background=14161b # NvimDarkGrey2

View file

@ -3,7 +3,10 @@
# Uses the light color palette from the default Neovim color scheme
# See: https://github.com/neovim/neovim/blob/fb6c059dc55c8d594102937be4dd70f5ff51614a/src/nvim/highlight_group.c#L334
[colors]
[main]
initial-color-theme=light
[colors-light]
cursor=e0e2ea 14161b # NvimLightGrey2 NvimDarkGrey2
foreground=14161b # NvimDarkGrey2
background=e0e2ea # NvimLightGrey2

View file

@ -1,7 +1,7 @@
# OneDark
# Palette based on the same theme from https://github.com/dexpota/kitty-themes
[colors]
[colors-dark]
cursor=111111 cccccc
foreground=979eab
background=282c34

View file

@ -7,7 +7,7 @@
# + cursor colors from:
# https://github.com/sonph/onehalf/blob/master/iterm/OneHalfDark.itermcolors
[colors]
[colors-dark]
cursor=dcdfe4 a3b3cc
foreground=dcdfe4
background=282c34

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# http://panda.siamak.me/
[colors]
[colors-dark]
# alpha=1.0
background=1D1E20
foreground=F0F0F0

View file

@ -2,7 +2,7 @@
# PaperColorDark
# Palette based on https://github.com/NLKNguyen/papercolor-theme
[colors]
[colors-dark]
cursor=1c1c1c eeeeee
background=1c1c1c
foreground=eeeeee
@ -25,7 +25,7 @@ bright7=5f8787 # bright white
# selection-foreground=1c1c1c
# selection-background=af87d7
[colors2]
[colors-light]
cursor=eeeeee 444444
background=eeeeee
foreground=444444

View file

@ -2,7 +2,7 @@
# PaperColorDark
# Palette based on https://github.com/NLKNguyen/papercolor-theme
[colors]
[colors-dark]
cursor=1c1c1c eeeeee
background=1c1c1c
foreground=eeeeee

View file

@ -2,7 +2,10 @@
# PaperColor Light
# Palette based on https://github.com/NLKNguyen/papercolor-theme
[colors]
[main]
initial-color-theme=light
xs
[colors-light]
cursor=eeeeee 444444
background=eeeeee
foreground=444444

View file

@ -1,7 +1,7 @@
# Based on Poimandres color theme for kitti terminal emulator
# https://github.com/ubmit/poimandres-kitty
[colors]
[colors-dark]
cursor=1b1e28 ffffff
foreground=a6accd
background=1b1e28

View file

@ -13,7 +13,7 @@
# and also posted here:
# https://forums.debian.net/viewtopic.php?t=29981
[colors]
[colors-dark]
foreground = cccccc
background = 191911

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Rosé Pine
[colors]
[colors-dark]
cursor=191724 e0def4
background=191724
foreground=e0def4

View file

@ -1,7 +1,11 @@
# -*- conf -*-
# Rosé Pine Dawn
[colors]
[main]
initial-color-theme=light
[colors-light]
cursor=faf4ed 575279
background=faf4ed
foreground=575279

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Rosé Pine Moon
[colors]
[colors-dark]
cursor=232136 e0def4
background=232136
foreground=e0def4

View file

@ -1,7 +1,7 @@
# -*- conf -*-
# Selenized dark
[colors]
[colors-dark]
cursor = 103c48 53d6c7
background= 103c48
foreground= adbcbc
@ -24,7 +24,7 @@ bright5= ff84cd
bright6= 53d6c7
bright7= cad8d9
[colors2]
[colors-light]
cursor=fbf3db 00978a
background= fbf3db
foreground= 53676d

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