Before the grapheme cluster segmentation work, we limited the number
of combining characters to base+5. I.e. 6 in total.
For a while now, we’ve had it bumped all the way up to 20. This was
the reason the unicode-random benchmark ran so much slower (i.e. cache
contention).
Looking at emoji’s, there are a couple that need 6 code points,
and *three* that needs 7.
Now, with the limit at 7 chars, and the new ‘width’ member, the
composed struct is 8 bytes larger than before.
Using the frame callback works most of the time, but e.g. Sway doesn’t
call it while the window is hidden, and thus prevents us from updating
the title in e.g. stacked views.
This patch uses a timer FD instead. We store a timestamp from when the
title was last updated. When the application wants to update the
title, we first check if we already have a timer running, and if so,
does nothing.
If no timer is running, check the timestamp. If enough time has
passed, update the title immediately.
If not, instantiate a timer and wait for it to trigger.
Set the minimum time between two updates to ~8ms (twice per frame, for
a 60Hz output, and ~once per frame on a 120Hz output).
Closes#591
This commit also renames the term_set_single_shift_ascii_printer()
function to term_single_shift(), since the former is overly verbose
and not really even accurate.
These sequences are supposed to affect the next printable ASCII
character and then reset to the previous character set, but before
this commit they were behaving like locking shifts.
echo -e '\e]8;;https://www.foo.bar\e\\https://www.foo\e]8;;\e\\.bar'
will produce an OSC-8 URL (https://www.foo) that is slightly shorter
than the auto-detected one (https://www.foo.bar).
This produces strange results in URL mode. For example, if
url.osc8-underline=always, the OSC8 underline will be removed when
url-mode is exited.
This patch changes the behavior so that auto-detected URLs that
overlap OSC-8 URLs are removed.
Note that OSC-8 URLs cannot overlap with each other, and that
auto-detected URLs also cannot overlap with each other.
We only needed term->font_scale to be able to detect scaling factor
changes (term->font_scale != term->scale).
But, we already have the old scaling factor in all places where
term_font_dpi_changed() is called, so let’s pass the old scaling
factor as an argument instead.
get_font_scale() was used to get the new scaling factor when loading
fonts. This was then compared to the last seen font scaling factor. If
there was no difference, the fonts were not reloaded.
The problem was, the initial term->scale was set differently. This
sometimes led to term->scale=2, while get_font_scale() return 1. That
meant, fonts were initially scaled by 2 (when dpi-aware=no). Later,
when mapped on an output (and thus term->scale being set to 1), the
fonts weren’t reloaded with the correct scaling factor since the
cached term->font_scale value was already 1.
Since term->scale always reflects the *new* scaling factor when
term_font_dpi_changed() is called, use that directly, and remove
get_font_scale().
Also rename the following functions:
* font_should_size_by_dpi() -> font_size_by_dpi_for_scale()
* font_size_by_dpi() -> font_sized_by_dpi()
* font_size_by_scale() -> font_sized_by_scale()
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
* Rename cursor.style value ‘bar’ to ‘beam’. ‘bar’ remains recognized,
but should eventually be deprecated and then removed.
* Add ‘cursor.beam-thickness’ option, a pt-or-px value specifying the
thickness of the beam cursor. Defaults to 1.5pt.
* Rename (and export) pt_or_px_as_pixels() to
term_pt_or_px_as_pixels()
* Change term_pt_or_px_as_pixels() to round point values instead of
truncating them.
This ensures different seat’s don’t step on each others IME pre-edit
state.
It also removes most dependencies on having a valid term pointer for
many IME operations.
We’re still not all the way, since we support disabling IME with a
private mode, which is per terminal, not seat.
Thus, we still require the seat to have keyboard focus on one of our
windows.
Closes#324. But note that *rendering* of multiple seat’s IME pre-edit
strings is still broken.
When the user has set a custom line-height, we now adjust it when
increasing/decreasing (“zooming”) the font size at run-time.
Previously, the line-height was fixed at the size specified in
foot.ini.
term_print() is called whenever the client application “prints”
something to the grid. It is called for both ASCII and UTF-8
characters, and needs to handle sixels, insert mode and ASCII
vs. graphical charsets.
Since it’s on the hot path, this becomes unnecessarily slow.
This patch adds a “fast” version of term_print(), tailored for the
common case: ASCII characters in non-insert mode, without any sixels
and non-graphical charsets.
A new function, term_update_ascii_printer(), has been added, and must
be called whenever:
* The currently selected charset *index* changes
* The currently selected charset changes (from ASCII to graphical, or
vice verse)
* Sixels are added to the grid
* Sixels are removed from the grid
* Insert mode is enabled/disabled
When P2=1, empty pixels are transparent.
This patch also changes the behavior of P2=0|2, from setting empty
pixels to the default background color, to instead use the *current*
background color.
To implement this, a couple of changes are needed:
* Sixel pixels always use alpha=1.0, except for *empty* cells when
P2=1 (i.e. transparent pixels).
* The renderer draws sixels with the OVER operator, instead of the SRC
operator.
* The renderer *must* now render the cells beneath the sixel. As an
optimization, this is only done for sixels where P2=1. I.e. for
fully opaque sixels, there’s no need to render the cells beneath.
The sixel renderer isn’t yet hooked into the multi-threaded
renderer. This means *rows* (not just the cells) beneath
maybe-transparent sixels are rendered single-threaded.
Closes#391.
All-empty pixels rows in the last sixel row should not be included in
the final sixel image.
This allows applications to emit sixels whose height is not a multiple
of 6, and is how XTerm works.
This is done by tracking the largest row number that contains
non-empty pixels.
In unhook, when emitting the image, the image height is adjusted based
on this value.
By storing the current row’s byte offset into the backing image in the
terminal struct.
This replaces the ‘imul’ with a load, which can potentially be
slow. But, this data should already be in the cache.
Foot has, up until now, used a fixed image size when the application
used DECGRA (Raster Attributes) to “configure” the image size.
This was based on a misunderstanding, that this was how you emitted
sixels where the height was *not* a multiple of 6.
This isn’t the case. The VT340 documentation is actually pretty clear
about this:
Ph and Pv do not limit the size of the image defined by the sixel
data. However, Ph and Pv let you omit background sixel data from the
image definition and still have a color background. They also
provide a concise way for the application or terminal to encode the
size of an image.
This is also how XTerm behaves. Test image:
\EPq
"1;1;1;1
#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0
#1~~@@vv@@~~@@~~$
#2??}}GG}}??}}??-
#1!14@
\E\
This uses DECGRA to set the image size to 1x1. The sixel however
is *not* clipped to 1x1, but is resized to 14x12
When enabled (the default), sixels behave much like normal output; the
start where the cursor is, and the cursor moves with the
sixel. I.e. after emitting a sixel the cursor is left after the image;
either to the right, if private mode 8452 is enabled, or otherwise on
the next line. Terminal content is scrolled up if the sixel is larger
than the screen.
When disabled, sixels *always* start at (0,0), the cursor never moves,
and the terminal content never scrolls.
In other words, the ‘disabled’ mode is a much simpler mode.
All we need to do to support both modes is re-write the sixel-emitting
loop to:
* break early if we’re “out of rows”, i.e. we’ve reached the bottom of
the screen.
* not linefeed, or move the cursor when scrolling is disabled
This patch also fixes a bug in the (new) implementation of private
mode 8452.
When emitting a sixel, we may break it up into smaller pieces, to
ensure a single sixel (as tracked internally) does not cross the
scrollback wrap-around.
The code that checked if we should do a linefeed or not, would skip
the linefeed on the last row of *each* such sixel piece. The correct
thing to do is to skip it only on the last row of the *last* piece.
I chose not to fix this bug in a separate patch since doing so would
have meant re-writing it again when implementing private mode 80.
Previously, we automatically exited URL mode whenever we received data
on the PTY. This was done since we don’t know _what_ has changed on
the screen, and we don’t want to display misleading jump labels.
However, this becomes a problem in curses-like applications that
periodically updates part of the screen. For example, a statusbar with
a clock.
This patch changes this behavior; instead of cancelling URL mode when
receiving PTY data, we snapshot the grid when entering URL mode.
When *rendering*, we use the snapshot:ed grid, while PTY updates
modify the “real” grid.
Snapshot:ing the grid means taking a full/deep copy of the current
grid, including sixel images etc.
Finally, it isn’t necessary to “damage” the entire view
when *entering* URL mode, since we’re at that point the renderer is in
sync with the grid. But we *do* need to damage the entire view when
exiting URL mode, since the grid changes on the “real” grid hasn’t
been tracked by the renderer.
When disabled (the default), the cursor is positioned on a new line
after emitting a sixel image.
When enabled, the cursor is positioned to the right of the sixel
image.
Closes#363
When enabled (the default), sixels use private color registers. That
is, the color palette from the last sixel is *not* re-used.
When disabled, sixels share (i.e. re-use) the same color palette.
Closes#362
This patch adds a new configuration option,
‘osc8-underline=url-mode|always’.
When set to ‘url-mode’, OSC-8 URLs are only
highlighted (i.e. underlined) in url-mode, just like auto-detected
URLs.
When set to ‘always’, they are always underlined, regardless of mode,
and regardless of their other attributes.
This is implemented by tagging collected URLs with a boolean,
instructing urls_render() and urls_reset() whether they should update
the cells’ ‘url’ attribute or not.
The OSC-8 collecter sets this based on the value of ‘osc8-underline’.
Finally, when closing an OSC-8 URL, the cells are immediately tagged
with the ‘url’ attribute if ‘osc8-underline’ is set to ‘always’.
By default, the URL isn’t shown on the jump-label. For auto-detect
URLs, doing so is virtually always useless, as the URL is already
visible in the grid.
For OSC-8 URLs however, the URL is often _not_ visible in the
grid. Many times, seeing the URL is still not needed (if you’re doing
‘ls --hyperlink’, you already know what the URIs are).
But it is still useful to have a way to show the URLs.
This patch adds a new key binding action that can be used in url-mode
to toggle the URL on and off in the jump label.
It is bound to ctrl+t by default.
In case an URL is split up into multiple parts, those parts are now
treated as a single URL when it comes to key assignment.
Only the *first* URL part is actually assigned a key combo. The other
parts are ignored.
We still highlight them, but for all other purposes they are ignored.
These functions update the OSC-8 URI state in the terminal.
term_osc8_open() tracks the beginning of an URL, by storing the start
coordinate (i.e. the current cursor location), along with the URL
itself.
Note that term_osc8_open() may not be called with an empty URL. This
is important to notice, since the way OSC-8 works, applications close
an URL by “opening” a new, empty one:
\E]8;;https://foo.bar\e\\this is an OSC-8 URL\E]8;;\e\\
It is up to the caller to check for this, and call term_osc8_close()
instead of term_osc8_open() when the URL is empty.
However, it is *also* valid to switch directly from one URL to
another:
\E]8;;http://123\e\\First URL\E]8;;http//456\e\\Second URL\E]8;;\e\\
This use-case *is* handled by term_osc8_open().
term_osc8_close() uses the information from term_osc8_open() to add
per-row URL data (using the new ‘extra’ row data).
This patch adds an ‘extra’ member to the row struct. It is a pointer
to a struct containing extra data to be associated with this row.
Initially, this struct contains a list of URL ranges. These
define (OSC-8) URLs on this row.
The ‘extra’ data is allocated on-demand. I.e. the pointer is NULL by
default; it is *not* allocated by grid_row_alloc().
The only time the URL is actually in UTF-32 is when we’re collecting
it (auto-detecting it) from the grid, since cells store their
character(s) in UTF-32.
Everything *after* that prefers the URL in UTF-8. So, do the
conversion while collecting the URL.
This patch also changes the URL activation code to strip the
‘file://user@host/’ prefix from file URIs that refer to files
on the *local* computer.
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
* We were using the ‘back’ element of the list as prefix for the next
iteration of sequences, instead of the element at index ‘offset’
* ALEN() on a wchar_t[] includes the NULL terminator. We don’t want
that.
This is implemented by allocating one of the (few!) remaining bits in
the cells’ attribute struct to indicate the cell should be “URL
highlighted”.
render_cell() looks at this bit and draws an underline using the color
from colors.urls (defaults to regular3 - i.e. yellow).
A new function, url_tag_cells(), iterates the currently detected URLs
and sets the new ‘url’ attribute flag on the affected cells.
Note: this is done in a separate function to keep urls_collect() free
from as many dependencies as possible.
urls_reset() is updated to *clear* the ‘url’ flag (and thus implicitly
also results in a grid refresh, _if_ there were any URLs).
We now exit URL mode on *any* client application input. This needs to
be so since we can’t know if the URLs we previously detected are still
valid.
This works just like show-urls-launch, except that instead of opening
the URL (typically using xdg-open), it is placed in the clipboard when
activated.