Before this patch, when the cell dimensions changed (i.e. when the
font size changes), sixel images were either removed (the new cell
dimensions are smaller than the old), or simply kept at their original
size (new cell dimensions are larger).
With this patch, sixels are instead resized. This means a
sixel *always* occupies the same number of rows and columns,
regardless of how much the font size is changed.
This is done by maintaining two sets of image data and pixman images,
as well as their dimensions. These two sets are the new ‘original’ and
‘scaled’ members of the sixel struct.
The "top-level" pixman image pointer, and the ‘width’ and ‘height’
members either point to the "original", or the "scaled" version.
They are invalidated as soon as the cell dimensions change. They, and
the ‘scaled’ image is updated on-demand (when we need to render a
sixel).
Note that the ‘scaled’ image is always NULL when the current cell
dimensions matches the ones used when emitting the sixel (to save
run-time memory).
Closes#1383
Images with an aspect ratio of 1:1 are by far the most common (though
not the default).
It makes a lot of sense, performance wise, to special case
them.
Specifically, the sixel_add() function benefits greatly from this, as
it is the inner most, most heavily executed function when parsing a
sixel image.
sixel_add_many() also benefits, since allows us to drop a
multiplication. Since sixel_add_many() always called first (no other
call sites call sixel_add() directly), this has a noticeable effect on
performance.
Another thing that helps (though not as much), and not specifically
with AR 1:1 images, is special casing DECGRI a bit.
Up until now, it simply updated the current sixel parameter value. The
problem is that the default parameter value is 0. But, a value of 0
should be treated as 1. By adding a special ‘repeat_count’ variable to
the sixel struct, we can initialize it to ‘1’ when we see DECGRI, and
then simply overwrite it as the parameter value gets updated. This
allows us to drop an if..else when emitting the sixel.
That is, parse P1 when initializing a new sixel, and don’t ignore
pad/pad in the raster attributes command.
The default aspect ratio is 2:1, but most sixels will override it in
the raster attributes command (to 1:1).
Set cursor column, absolute.
term_cursor_to() needs to reload the current row pointer, and is thus
not very effective when we only need to modify the column.
We’re already switching on the next VT input byte in the state
machine; no need to if...else if in action_param() too.
That is, split up action_param() into three:
* action_param_new()
* action_param_new_subparam()
* action_param()
This makes the code cleaner, and hopefully slightly faster.
Next, to improve performance further, only check for (sub)parameter
overflow in action_param_new() and action_param_subparam().
Add pointers to the VT struct that points to the currently active
parameter and sub-parameter.
When the number of parameters (or sub-parameters) overflow, warn, and
then point the parameter pointer to a "dummy" value in the VT struct.
This way, we don’t have to check anything in action_param().
When accumulating scroll damage, we check if the last scroll damage’s
scrolling region, and type, matches the new/current scroll damage. If
so, the number of lines in the last scroll damage is increased,
instead of adding a new scroll damage instance to the list.
If the scroll damage list isn’t consumed, this build up of scroll
damage would eventually overflow.
And, even if it didn’t overflow, it could become large enough, that
when later used to calculate e.g. the affected surface area, while
rendering a frame, would cause an overflow there instead.
This patch fixes both issues by:
a) do an overflow check before increasing the line count
b) limit the line count to UINT16_MAX
The selection coordinates are in absolute row numbers. As such,
selection breaks when interactively resizing the normal grid, since we
then instantiate a temporary grid mapping directly to the current
viewport (for performance reason, to avoid reflowing the entire grid
over and over again).
Fix by stashing the actual selection coordinates, and ajusting the
"active" ones to the temporary grid.
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
But also, more importantly, logical fixes:
* Stash the number of new scrollback lines the stashed ‘normal’ grid
should be resized *to*.
There’s also a couple of performance changes here:
* When doing a delayed reflow (tiocswinsz timer), call
sixel_reflow_grid(term, &term->normal) - there’s no need to reflow
sixels in the ‘alt’ screen.
* When doing a delayed reflow, free all scroll damage. It’s not
needed, since we’re damaging the entire window anyway.
* Use minimum size for the temporary ‘normal’ grid (that contains the
current viewport). We just need it to be large enough to fit the
current viewport, and be a valid grid row count (power of 2). This
just so happens to be the current ‘alt’ grid’s row count...
Reflowing a large scrollback is *slow*. During an interactive resize,
it can easily take long enough that the compositor fills the Wayland
socket with configure events. Eventually, the socket becomes full and
the compositor terminates the connection, causing foot to exit.
This patch is work-in-progress, and the first step towards alleviating
this.
It delays the reflow by:
* Snapshotting (copying) the original grid when an interactive resize
is started.
* While resizing, we apply a simple truncation resize of the
grid (like we handle the alt screen).
* When the resize is done, or paused for ‘resize-delay-ms’, the grid
is reflowed.
TODO: we *must* not allow any changes to the temporary (truncated)
grid during the resize. Any changes to the grid would be lost when the
final reflow is applied. That is, we must completely pause the ptmx
pipe while a resize is in progress.
Future improvements:
The initial copy can be slow. We should be able to avoid it by
rewriting the reflow algorithm to not free anything. This is
complicated by the fact that some resources (e.g. sixel images) are
currently *moved* to the new grid. They’d instead have to be copied.
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.
The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.
We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.
In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.
To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.
prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.
prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.
Closes#30
The match logic uses the last start coordinate to determine which end
points in the selection to update. This sometimes fails when the start
coordinate has been changed by e.g. a key binding - the new start
coordinate is incorrectly matched against the old-but-modified start
coordinate, causing foot to e.g. *not* upate the selection start
coordinate.
Example:
$ echo 'test\n\test\ntest'
Then do a scrollback search for 'test. The first match is found
correctly (the last 'test'), but searching for the previous match
(ctrl+r) does not select the middle 'test'.
Fix by passing the search direction to search_find_next(), and have
_it_ calculate the coordinate to start search. There are three possibilities:
* forward
* backward
* "backward", but at the same position
The first two are used when searching for next/prev match with ctrl+s
and ctrl+r. The last one is used when the search criteria is
updated. In this case, we don't want to move to the previous match,
*unless* the current match no longer matches.
The global config doesn’t necessarily reflect the correct
configuration to use - we should *always* use the current terminal
instance’s conf pointer.
* Move selection override modifier mask to the key_binding_set struct
* Always warn if XDG activation is unavailable, not just if
bell.urgent is set (we no longer have access to this information)
* Pass ‘bool presentation_timings’ as a parameter to wayl_init()
* Remove ‘presentation_timings’ member from the ‘terminal’ struct
Closes#932
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes#931
Search mode and ‘flash’ (OSC-555) both achieves similar visual
effects: flash tints the entire window yellow, and search mode dims
it (except the search match).
But, they do so in completely different ways. Search mode is detected
in render_cell(), and the colors are then dimmed there.
Flash is implemented by blending a yellow, semi-transparent color on
top of the rendered grid.
This patch replaces those two implementations with a single one. We
add a new sub-surface, called the ‘overlay’. In normal mode, it’s
unmapped.
When either search mode, or flash, is enabled, we enable it, and
fill it with a semi-transparent color. Yellow for ‘flash’, and
“black” (i.e. no color) for search mode.
The compositor then blends it with the grid. Hopefully on the GPU,
meaning it’ll be faster than if we blend in software.
There are more performance benefits however. By using a separate
surface, we can do much better damage tracking.
The normal grid rendering code no longer have to care about neither
search mode, nor flash. Thus, we get rid of a couple of ‘if’
statements in render_cell(), which is nice. But more importantly, we
can drop full grid repaints in a couple of circumstances:
* Entering/exiting search mode
* Every frame while flash is active
Now, when rendering the search mode overlay, we do want to do some
damage tracking, also of the overlay.
This, since search mode doesn’t dim the *entire* window. The search
match is *not* dimmed. This is implemented by punching a hole in the
overlay sub-surface. That is, we make part of it *fully*
transparent. The basic idea is to set a clip region that excludes the
search match, and then dim the rest of the overlay.
It’s slightly more complicated than that however, if we want to reuse
the last frame’s overlay buffer (i.e we don’t want to re-render
the *entire* overlay every frame).
In short, we need to:
* Clear (punch hole) in areas that are part of this frame’s search
match, but not the last frame’s (since those parts are _already_
cleared).
* Dim the areas that were part of the last frame’s search match, but
aren’t anymore (the rest of the overlay should already be dimmed).
To do this, we save the last frame’s “holes” (as a pixman
region). Then, when rendering the next frame, we first calculate the
new frame’s “holes” region.
The region to clear is “this frame’s holes minus last frame’s holes”
The region to dim is “last frame’s holes minus this frames holes”.
Finally, we compute the bounding box of all modified cells by taking
the union of the two diff regions mentioned above. This allows us to
limit the buffer damage sent to the compositor.
We have a number of sub-surfaces for which we are *not* interrested in
pointer (or touch) input.
Up until now, we’ve manually dealt with these, by recognizing these
surfaces in all pointer events, and ignoring them.
But, lo and behold, there are better ways of doing this. By clearing
the subsurface’s input region, the compositor will do this for us -
when a pointer is outside a surface’s input region, the event is
passed to the next surface underneath it.
This is exactly what we want! Do this for all subsurfaces, *except*
the CSDs.
When this option is used, the child process in the new terminal
instance will inherit its environment from the footclient process,
instead of the foot server’s.
Implemented by sending (yet another) dynamic string list as part of
the client -> server setup packet. When the new option is *not* used,
the setup packet is now 2 bytes larger than before.
On the server side, the slave process now uses execvpe() instead of
execvp(). There’s plumbing to propagate a new ‘envp’ argument from
term_init() all the way down to slave_exec(). If ‘envp’ is NULL, we
use ‘environ’ instead (thus matching the old behavior of execvp()).
Closes#1004
This function allows setting a custom mouse cursor.
This is done by adding a ‘char*’ member to the term struct. When it is
non-NULL, we *always* use that pointer (the exception being when the
pointer is hidden), while the pointer is over the grid. This is
instead of the hand/beam pointers we otherwise would use.
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
Regardless of how we exit search mode (commit or cancel), the search
string is remembered.
The next time we enter search mode, the last searched-for string will
be used when searching for the next/prev match (ctrl+r, ctrl+s), and
the search query is empty.
POSIX.1-2008 has marked gettimeofday(2) as obsolete, recommending the
use of clock_gettime(2) instead.
CLOCK_MONOTONIC has been used instead of CLOCK_REALTIME because it is
unaffected by manual changes in the system clock. This makes it better
for our purposes, namely, measuring the difference between two points in
time.
tv_sec has been casted to long in most places since POSIX does not
define the actual type of time_t.
In this mode, the “shifted” and “base layout” keys are added to the
CSIs, as sub-parameters to the “key” parameter.
Note that this PR only implements the “shifted” key, not the “base
layout key”.
This is done by converting the original XKB symbol to it’s
corresponding UTF-32 codepoint. If this codepoint is different from
the one we use as “key” in the CSI, we add it as a sub-parameter.
Related to #319
In this mode, key events that generate text now add a third CSI
parameter, indicating the actual codepoint.
Remember that we always use the *unshifted* key in the CSI
escapes. With this mode, those CSI escapes now also included the text
codepoint. I.e. what would have been emitted, had we not generated a
CSI escape.
As far as I can tell, this mode has no effect unless “report all keys
as escape sequences” is enabled (reason being, without that, there
aren’t any text events that generate CSIs - they’re always emitted
as-is).
Note that Kitty itself seems to be somewhat buggy in this mode. At
least on Wayland, with my Swedish layout. For example ‘a’ and ‘A’ does
generate the expected CSIs, but ‘å’ and ‘Å’ appears to be treated as
non-text input.
Furthermore, Kitty optimizes away the modifier parameter, if no
modifiers are pressed (e.g. CSI 97;;97u), while we always emit the
modifier (CSI 97;1;97u).
Related to #319
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes#816
Each cell now tracks it’s current color source:
* default fg/bg
* base16 fg/bg (maps to *both* the regular and bright colors)
* base256 fg/bg
* RGB
Note that we don’t have enough bits to separate the regular from the
bright colors. These _shouldn’t_ be the same, so we ought to be
fine...
Similar to modifyOtherKeys=1 (foot’s default, and only, mode), except
that:
* All modifiers (and not just Ctrl) generate \E[27;m;n~ escapes
* Regular keys (with modifiers) also generate \E[27;m;n~ escapes (for
example, C-h no longer generates ^H, but \E[27;5;104~)
For our keymap based lookups, this is handled by adding
MOD_MODIFY_OTHER_KEYS_STATE<N> variants.
For “generic” keys, we simply adjust the conditions for when to emit a
\E[27;m;n~ escape - the only requirement is that at least one modifier
is active.
This is an application of the xdg activation protocol that will allow
compositors to associate new foot toplevels with the command that
launched them.
footclient receives an activation token from the launcher which the
compositor can use to track application startup. It passes the token
to the foot server, which then activates the new window with the token
to complete the startup sequence.