2019-07-05 10:16:56 +02:00
|
|
|
#include "render.h"
|
|
|
|
|
|
2024-09-15 09:56:41 +02:00
|
|
|
#include <limits.h>
|
|
|
|
|
#include <signal.h>
|
2019-07-05 10:16:56 +02:00
|
|
|
#include <string.h>
|
2021-01-17 16:12:54 +01:00
|
|
|
#include <unistd.h>
|
2019-08-01 20:09:16 +02:00
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
#include <sys/ioctl.h>
|
2019-07-18 09:33:49 +02:00
|
|
|
#include <sys/time.h>
|
2019-07-21 20:11:20 +02:00
|
|
|
#include <sys/timerfd.h>
|
2021-01-17 16:12:54 +01:00
|
|
|
#include <sys/epoll.h>
|
render: set thread name in a portable way
prctl is Linux-only but pthread_setname_np is same as PR_SET_NAME.
Solaris and FreeBSD >= 13 have pthread_setname_np similar to Linux.
DragonFly, OpenBSD, FreeBSD < 13 lack pthread_setname_np but provide
pthread_set_name_np which doesn't return a value. NetBSD requires 3
arguments for pthread_setname_np where the last one is void *.
render.c:8:10: fatal error: 'sys/prctl.h' file not found
#include <sys/prctl.h>
^~~~~~~~~~~~~
render.c:1234:9: error: implicit declaration of function 'prctl' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
^
render.c:1234:15: error: use of undeclared identifier 'PR_SET_NAME'
if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
^
2021-01-19 14:49:43 +00:00
|
|
|
#include <pthread.h>
|
2021-02-09 03:12:26 +00:00
|
|
|
|
|
|
|
|
#include "macros.h"
|
|
|
|
|
#if HAS_INCLUDE(<pthread_np.h>)
|
2024-09-10 18:53:38 +02:00
|
|
|
#include <pthread_np.h>
|
|
|
|
|
#define pthread_setname_np(thread, name) (pthread_set_name_np(thread, name), 0)
|
render: set thread name in a portable way
prctl is Linux-only but pthread_setname_np is same as PR_SET_NAME.
Solaris and FreeBSD >= 13 have pthread_setname_np similar to Linux.
DragonFly, OpenBSD, FreeBSD < 13 lack pthread_setname_np but provide
pthread_set_name_np which doesn't return a value. NetBSD requires 3
arguments for pthread_setname_np where the last one is void *.
render.c:8:10: fatal error: 'sys/prctl.h' file not found
#include <sys/prctl.h>
^~~~~~~~~~~~~
render.c:1234:9: error: implicit declaration of function 'prctl' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
^
render.c:1234:15: error: use of undeclared identifier 'PR_SET_NAME'
if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
^
2021-01-19 14:49:43 +00:00
|
|
|
#elif defined(__NetBSD__)
|
2024-09-10 18:53:38 +02:00
|
|
|
#define pthread_setname_np(thread, name) pthread_setname_np(thread, "%s", (void *)name)
|
render: set thread name in a portable way
prctl is Linux-only but pthread_setname_np is same as PR_SET_NAME.
Solaris and FreeBSD >= 13 have pthread_setname_np similar to Linux.
DragonFly, OpenBSD, FreeBSD < 13 lack pthread_setname_np but provide
pthread_set_name_np which doesn't return a value. NetBSD requires 3
arguments for pthread_setname_np where the last one is void *.
render.c:8:10: fatal error: 'sys/prctl.h' file not found
#include <sys/prctl.h>
^~~~~~~~~~~~~
render.c:1234:9: error: implicit declaration of function 'prctl' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
^
render.c:1234:15: error: use of undeclared identifier 'PR_SET_NAME'
if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
^
2021-01-19 14:49:43 +00:00
|
|
|
#endif
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2024-09-10 18:53:38 +02:00
|
|
|
#include <presentation-time.h>
|
2019-07-05 10:44:09 +02:00
|
|
|
#include <wayland-cursor.h>
|
2019-07-05 10:16:56 +02:00
|
|
|
#include <xdg-shell.h>
|
2024-09-10 18:53:38 +02:00
|
|
|
#include <xdg-toplevel-icon-v1.h>
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-12-01 14:03:24 +01:00
|
|
|
#include <fcft/fcft.h>
|
2019-12-01 13:43:51 +01:00
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
#define LOG_MODULE "render"
|
|
|
|
|
#define LOG_ENABLE_DBG 0
|
|
|
|
|
#include "log.h"
|
2020-12-26 16:24:16 +01:00
|
|
|
#include "box-drawing.h"
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
#include "char32.h"
|
2020-02-15 18:58:36 +01:00
|
|
|
#include "config.h"
|
2023-06-27 17:25:57 +02:00
|
|
|
#include "cursor-shape.h"
|
2019-07-08 13:57:31 +02:00
|
|
|
#include "grid.h"
|
2021-01-31 11:12:07 +01:00
|
|
|
#include "ime.h"
|
2020-03-01 13:06:00 +01:00
|
|
|
#include "quirks.h"
|
search/render: initial support for highlighting all visible matches
Before this patch, only the currently “selected” match was
highlighted (by having the “selected” attribute, and by *not* dimming
it, like the rest of the grid during a scrollback search).
With this patch, we now highlight matches within the viewport. While
searching, only the “primary” match is searched-for, and tracked.
Then, when rendering a frame, we find all “secondary” matches as
well. “holes” are added to the search-mode overlay by the means of an
search-match iterator.
The iterator’s text matching logic is *very* similar to what we do
when the search criteria has been updated, and we re-search the
scrollback. It should be possible to refactor this, and share code.
2022-04-09 17:57:29 +02:00
|
|
|
#include "search.h"
|
2020-02-15 18:58:36 +01:00
|
|
|
#include "selection.h"
|
|
|
|
|
#include "shm.h"
|
2021-01-31 11:12:07 +01:00
|
|
|
#include "sixel.h"
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
#include "srgb.h"
|
2021-01-31 11:12:07 +01:00
|
|
|
#include "url-mode.h"
|
2020-05-01 11:46:24 +02:00
|
|
|
#include "util.h"
|
2020-08-08 20:34:30 +01:00
|
|
|
#include "xmalloc.h"
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2020-03-22 20:22:17 +01:00
|
|
|
#define TIME_SCROLL_DAMAGE 0
|
|
|
|
|
|
2020-01-04 19:49:26 +01:00
|
|
|
struct renderer {
|
|
|
|
|
struct fdm *fdm;
|
|
|
|
|
struct wayland *wayl;
|
|
|
|
|
};
|
|
|
|
|
|
2019-12-31 15:39:40 +01:00
|
|
|
static struct {
|
|
|
|
|
size_t total;
|
|
|
|
|
size_t zero; /* commits presented in less than one frame interval */
|
|
|
|
|
size_t one; /* commits presented in one frame interval */
|
|
|
|
|
size_t two; /* commits presented in two or more frame intervals */
|
2020-08-23 07:42:20 +02:00
|
|
|
} presentation_statistics = {0};
|
2019-12-31 15:39:40 +01:00
|
|
|
|
2020-01-04 22:01:19 +01:00
|
|
|
static void fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data);
|
2020-01-04 19:49:26 +01:00
|
|
|
|
|
|
|
|
struct renderer *
|
|
|
|
|
render_init(struct fdm *fdm, struct wayland *wayl)
|
|
|
|
|
{
|
2020-08-08 20:34:30 +01:00
|
|
|
struct renderer *renderer = malloc(sizeof(*renderer));
|
|
|
|
|
if (unlikely(renderer == NULL)) {
|
|
|
|
|
LOG_ERRNO("malloc() failed");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 19:49:26 +01:00
|
|
|
*renderer = (struct renderer) {
|
|
|
|
|
.fdm = fdm,
|
|
|
|
|
.wayl = wayl,
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-04 23:26:27 +01:00
|
|
|
if (!fdm_hook_add(fdm, &fdm_hook_refresh_pending_terminals, renderer,
|
|
|
|
|
FDM_HOOK_PRIORITY_NORMAL))
|
|
|
|
|
{
|
2020-01-04 19:49:26 +01:00
|
|
|
LOG_ERR("failed to register FDM hook");
|
|
|
|
|
free(renderer);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return renderer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
render_destroy(struct renderer *renderer)
|
|
|
|
|
{
|
|
|
|
|
if (renderer == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-01-04 23:26:27 +01:00
|
|
|
fdm_hook_del(renderer->fdm, &fdm_hook_refresh_pending_terminals,
|
|
|
|
|
FDM_HOOK_PRIORITY_NORMAL);
|
2020-01-04 23:41:26 +01:00
|
|
|
|
|
|
|
|
free(renderer);
|
2020-01-04 19:49:26 +01:00
|
|
|
}
|
|
|
|
|
|
2021-01-03 08:56:47 +00:00
|
|
|
static void DESTRUCTOR
|
2019-12-31 15:39:40 +01:00
|
|
|
log_presentation_statistics(void)
|
|
|
|
|
{
|
|
|
|
|
if (presentation_statistics.total == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const size_t total = presentation_statistics.total;
|
|
|
|
|
LOG_INFO("presentation statistics: zero=%f%%, one=%f%%, two=%f%%",
|
|
|
|
|
100. * presentation_statistics.zero / total,
|
|
|
|
|
100. * presentation_statistics.one / total,
|
|
|
|
|
100. * presentation_statistics.two / total);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
sync_output(void *data,
|
|
|
|
|
struct wp_presentation_feedback *wp_presentation_feedback,
|
|
|
|
|
struct wl_output *output)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-21 18:51:04 +01:00
|
|
|
struct presentation_context {
|
|
|
|
|
struct terminal *term;
|
|
|
|
|
struct timeval input;
|
|
|
|
|
struct timeval commit;
|
|
|
|
|
};
|
|
|
|
|
|
2019-12-31 15:39:40 +01:00
|
|
|
static void
|
|
|
|
|
presented(void *data,
|
|
|
|
|
struct wp_presentation_feedback *wp_presentation_feedback,
|
|
|
|
|
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec,
|
|
|
|
|
uint32_t refresh, uint32_t seq_hi, uint32_t seq_lo, uint32_t flags)
|
|
|
|
|
{
|
2020-01-21 18:51:04 +01:00
|
|
|
struct presentation_context *ctx = data;
|
|
|
|
|
struct terminal *term = ctx->term;
|
|
|
|
|
const struct timeval *input = &ctx->input;
|
|
|
|
|
const struct timeval *commit = &ctx->commit;
|
2019-12-31 15:39:40 +01:00
|
|
|
|
2020-01-01 11:37:47 +01:00
|
|
|
const struct timeval presented = {
|
2019-12-31 15:39:40 +01:00
|
|
|
.tv_sec = (uint64_t)tv_sec_hi << 32 | tv_sec_lo,
|
|
|
|
|
.tv_usec = tv_nsec / 1000,
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-21 18:51:04 +01:00
|
|
|
bool use_input = (input->tv_sec > 0 || input->tv_usec > 0) &&
|
|
|
|
|
timercmp(&presented, input, >);
|
2019-12-31 20:01:47 +01:00
|
|
|
char msg[1024];
|
|
|
|
|
int chars = 0;
|
|
|
|
|
|
2020-01-21 18:51:04 +01:00
|
|
|
if (use_input && timercmp(&presented, input, <))
|
2020-01-01 11:37:47 +01:00
|
|
|
return;
|
2020-01-21 18:51:04 +01:00
|
|
|
else if (timercmp(&presented, commit, <))
|
2020-01-01 11:37:47 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("commit: %lu s %lu µs, presented: %lu s %lu µs",
|
2020-01-21 18:51:04 +01:00
|
|
|
commit->tv_sec, commit->tv_usec, presented.tv_sec, presented.tv_usec);
|
2020-01-01 11:37:47 +01:00
|
|
|
|
2019-12-31 20:01:47 +01:00
|
|
|
if (use_input) {
|
|
|
|
|
struct timeval diff;
|
2020-01-21 18:51:04 +01:00
|
|
|
timersub(commit, input, &diff);
|
2020-08-11 17:29:56 +02:00
|
|
|
chars += snprintf(
|
|
|
|
|
&msg[chars], sizeof(msg) - chars,
|
|
|
|
|
"input - %llu µs -> ", (unsigned long long)diff.tv_usec);
|
2019-12-31 20:01:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct timeval diff;
|
2020-01-21 18:51:04 +01:00
|
|
|
timersub(&presented, commit, &diff);
|
2020-08-11 17:29:56 +02:00
|
|
|
chars += snprintf(
|
|
|
|
|
&msg[chars], sizeof(msg) - chars,
|
|
|
|
|
"commit - %llu µs -> ", (unsigned long long)diff.tv_usec);
|
2019-12-31 15:39:40 +01:00
|
|
|
|
2020-01-01 11:37:47 +01:00
|
|
|
if (use_input) {
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(timercmp(&presented, input, >));
|
2020-01-21 18:51:04 +01:00
|
|
|
timersub(&presented, input, &diff);
|
2020-01-01 11:37:47 +01:00
|
|
|
} else {
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(timercmp(&presented, commit, >));
|
2020-01-21 18:51:04 +01:00
|
|
|
timersub(&presented, commit, &diff);
|
2020-01-01 11:37:47 +01:00
|
|
|
}
|
2019-12-31 15:39:40 +01:00
|
|
|
|
2020-08-11 17:29:56 +02:00
|
|
|
chars += snprintf(
|
|
|
|
|
&msg[chars], sizeof(msg) - chars,
|
|
|
|
|
"presented (total: %llu µs)", (unsigned long long)diff.tv_usec);
|
2019-12-31 15:39:40 +01:00
|
|
|
|
2019-12-31 20:01:47 +01:00
|
|
|
unsigned frame_count = 0;
|
2019-12-31 20:04:44 +01:00
|
|
|
if (tll_length(term->window->on_outputs) > 0) {
|
2019-12-31 20:01:47 +01:00
|
|
|
const struct monitor *mon = tll_front(term->window->on_outputs);
|
2020-01-01 11:19:13 +01:00
|
|
|
frame_count = (diff.tv_sec * 1000000. + diff.tv_usec) / (1000000. / mon->refresh);
|
2019-12-31 20:01:47 +01:00
|
|
|
}
|
2019-12-31 15:39:40 +01:00
|
|
|
|
2019-12-31 20:01:47 +01:00
|
|
|
presentation_statistics.total++;
|
|
|
|
|
if (frame_count >= 2)
|
|
|
|
|
presentation_statistics.two++;
|
|
|
|
|
else if (frame_count >= 1)
|
|
|
|
|
presentation_statistics.one++;
|
|
|
|
|
else
|
|
|
|
|
presentation_statistics.zero++;
|
2019-12-31 15:39:40 +01:00
|
|
|
|
2019-12-31 20:01:47 +01:00
|
|
|
#define _log_fmt "%s (more than %u frames)"
|
2019-12-31 15:39:40 +01:00
|
|
|
|
2019-12-31 20:01:47 +01:00
|
|
|
if (frame_count >= 2)
|
|
|
|
|
LOG_ERR(_log_fmt, msg, frame_count);
|
|
|
|
|
else if (frame_count >= 1)
|
|
|
|
|
LOG_WARN(_log_fmt, msg, frame_count);
|
|
|
|
|
else
|
|
|
|
|
LOG_INFO(_log_fmt, msg, frame_count);
|
2019-12-31 15:39:40 +01:00
|
|
|
|
2019-12-31 20:01:47 +01:00
|
|
|
#undef _log_fmt
|
2019-12-31 15:39:40 +01:00
|
|
|
|
|
|
|
|
wp_presentation_feedback_destroy(wp_presentation_feedback);
|
2020-01-21 18:51:04 +01:00
|
|
|
free(ctx);
|
2019-12-31 15:39:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
discarded(void *data, struct wp_presentation_feedback *wp_presentation_feedback)
|
|
|
|
|
{
|
2020-01-21 18:51:04 +01:00
|
|
|
struct presentation_context *ctx = data;
|
2019-12-31 15:39:40 +01:00
|
|
|
wp_presentation_feedback_destroy(wp_presentation_feedback);
|
2020-01-21 18:51:04 +01:00
|
|
|
free(ctx);
|
2019-12-31 15:39:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wp_presentation_feedback_listener presentation_feedback_listener = {
|
|
|
|
|
.sync_output = &sync_output,
|
|
|
|
|
.presented = &presented,
|
|
|
|
|
.discarded = &discarded,
|
|
|
|
|
};
|
|
|
|
|
|
2020-04-21 19:29:36 +02:00
|
|
|
static struct fcft_font *
|
2019-12-19 07:28:33 +01:00
|
|
|
attrs_to_font(const struct terminal *term, const struct attributes *attrs)
|
2019-07-05 10:16:56 +02:00
|
|
|
{
|
|
|
|
|
int idx = attrs->italic << 1 | attrs->bold;
|
2019-10-16 21:52:12 +02:00
|
|
|
return term->fonts[idx];
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
|
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
static pixman_color_t
|
|
|
|
|
color_hex_to_pixman_srgb(uint32_t color, uint16_t alpha)
|
2019-08-16 20:40:32 +02:00
|
|
|
{
|
|
|
|
|
return (pixman_color_t){
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
.alpha = alpha, /* Consider alpha linear already? */
|
|
|
|
|
.red = srgb_decode_8_to_16((color >> 16) & 0xff),
|
|
|
|
|
.green = srgb_decode_8_to_16((color >> 8) & 0xff),
|
|
|
|
|
.blue = srgb_decode_8_to_16((color >> 0) & 0xff),
|
2019-08-16 20:40:32 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-16 22:06:06 +02:00
|
|
|
static inline pixman_color_t
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha, bool srgb)
|
|
|
|
|
{
|
|
|
|
|
pixman_color_t ret;
|
|
|
|
|
|
|
|
|
|
if (srgb)
|
|
|
|
|
ret = color_hex_to_pixman_srgb(color, alpha);
|
|
|
|
|
else {
|
|
|
|
|
ret = (pixman_color_t){
|
|
|
|
|
.red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)),
|
|
|
|
|
.green = ((color >> 8 & 0xff) | (color >> 0 & 0xff00)),
|
|
|
|
|
.blue = ((color >> 0 & 0xff) | (color << 8 & 0xff00)),
|
|
|
|
|
.alpha = alpha,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret.red = (uint32_t)ret.red * alpha / 0xffff;
|
|
|
|
|
ret.green = (uint32_t)ret.green * alpha / 0xffff;
|
|
|
|
|
ret.blue = (uint32_t)ret.blue * alpha / 0xffff;
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline pixman_color_t
|
|
|
|
|
color_hex_to_pixman(uint32_t color, bool srgb)
|
2019-08-16 22:06:06 +02:00
|
|
|
{
|
|
|
|
|
/* Count on the compiler optimizing this */
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
return color_hex_to_pixman_with_alpha(color, 0xffff, srgb);
|
2019-08-16 22:06:06 +02:00
|
|
|
}
|
|
|
|
|
|
2025-03-22 20:11:23 +01:00
|
|
|
static inline int i_lerp(int from, int to, float t) {
|
|
|
|
|
return from + (to - from) * t;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-15 20:05:01 +01:00
|
|
|
static inline uint32_t
|
2025-03-22 20:11:23 +01:00
|
|
|
color_blend_towards(uint32_t from, uint32_t to, float amount)
|
2019-08-16 20:40:32 +02:00
|
|
|
{
|
2025-03-22 20:11:23 +01:00
|
|
|
if (unlikely(amount == 0))
|
|
|
|
|
return from;
|
|
|
|
|
float t = 1 - 1/amount;
|
|
|
|
|
|
|
|
|
|
uint32_t alpha = from & 0xff000000;
|
|
|
|
|
uint8_t r = i_lerp((from>>16)&0xff, (to>>16)&0xff, t);
|
|
|
|
|
uint8_t g = i_lerp((from>>8)&0xff, (to>>8)&0xff, t);
|
|
|
|
|
uint8_t b = i_lerp((from>>0)&0xff, (to>>0)&0xff, t);
|
|
|
|
|
|
|
|
|
|
return alpha | (r<<16) | (g<<8) | (b<<0);
|
2019-08-16 20:40:32 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-03 14:25:38 +01:00
|
|
|
static inline uint32_t
|
|
|
|
|
color_dim(const struct terminal *term, uint32_t color)
|
|
|
|
|
{
|
|
|
|
|
const struct config *conf = term->conf;
|
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-19 09:29:06 +01:00
|
|
|
const uint8_t custom_dim = conf->colors_dark.use_custom.dim;
|
2021-11-03 14:25:38 +01:00
|
|
|
|
2025-03-22 20:11:23 +01:00
|
|
|
if (unlikely(custom_dim != 0)) {
|
|
|
|
|
for (size_t i = 0; i < 8; i++) {
|
|
|
|
|
if (((custom_dim >> i) & 1) == 0)
|
|
|
|
|
continue;
|
2021-11-03 14:25:38 +01:00
|
|
|
|
2025-03-22 20:11:23 +01:00
|
|
|
if (term->colors.table[0 + i] == color) {
|
|
|
|
|
/* "Regular" color, return the corresponding "dim" */
|
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-19 09:29:06 +01:00
|
|
|
return conf->colors_dark.dim[i];
|
2025-03-22 20:11:23 +01:00
|
|
|
}
|
2021-11-03 14:25:38 +01:00
|
|
|
|
2025-03-22 20:11:23 +01:00
|
|
|
else if (term->colors.table[8 + i] == color) {
|
|
|
|
|
/* "Bright" color, return the corresponding "regular" */
|
|
|
|
|
return term->colors.table[i];
|
|
|
|
|
}
|
2021-11-03 14:25:38 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-19 09:29:06 +01:00
|
|
|
const struct color_theme *theme = term->colors.active_theme == COLOR_THEME_DARK
|
|
|
|
|
? &conf->colors_dark
|
|
|
|
|
: &conf->colors_light;
|
2025-10-10 11:10:38 +02:00
|
|
|
|
|
|
|
|
return color_blend_towards(
|
|
|
|
|
color,
|
|
|
|
|
theme->dim_blend_towards == DIM_BLEND_TOWARDS_BLACK ? 0x00000000 : 0x00ffffff,
|
|
|
|
|
conf->dim.amount);
|
2021-11-03 14:25:38 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-15 20:05:01 +01:00
|
|
|
static inline uint32_t
|
2021-04-17 21:02:02 +02:00
|
|
|
color_brighten(const struct terminal *term, uint32_t color)
|
2020-11-14 11:22:35 +01:00
|
|
|
{
|
2021-04-17 21:02:02 +02:00
|
|
|
/*
|
|
|
|
|
* First try to match the color against the base 8 colors. If we
|
|
|
|
|
* find a match, return the corresponding bright color.
|
|
|
|
|
*/
|
2021-04-17 21:57:08 +02:00
|
|
|
if (term->conf->bold_in_bright.palette_based) {
|
|
|
|
|
for (size_t i = 0; i < 8; i++) {
|
|
|
|
|
if (term->colors.table[i] == color)
|
|
|
|
|
return term->colors.table[i + 8];
|
|
|
|
|
}
|
2021-07-22 18:22:39 +02:00
|
|
|
return color;
|
2021-04-17 21:02:02 +02:00
|
|
|
}
|
|
|
|
|
|
2025-03-22 20:11:23 +01:00
|
|
|
return color_blend_towards(color, 0x00ffffff, term->conf->bold_in_bright.amount);
|
2020-11-14 11:22:35 +01:00
|
|
|
}
|
|
|
|
|
|
2019-12-16 21:34:38 +01:00
|
|
|
static void
|
2024-04-09 16:28:54 +02:00
|
|
|
draw_hollow_block(const struct terminal *term, pixman_image_t *pix,
|
|
|
|
|
const pixman_color_t *color, int x, int y, int cell_cols)
|
2019-12-16 21:34:38 +01:00
|
|
|
{
|
2023-07-25 15:56:30 +02:00
|
|
|
const int scale = (int)roundf(term->scale);
|
2021-12-29 18:05:00 +01:00
|
|
|
const int width = min(min(scale, term->cell_width), term->cell_height);
|
2021-12-28 20:51:19 +01:00
|
|
|
|
2019-12-16 21:34:38 +01:00
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, color, 4,
|
|
|
|
|
(pixman_rectangle16_t []){
|
2021-12-29 18:05:00 +01:00
|
|
|
{x, y, cell_cols * term->cell_width, width}, /* top */
|
|
|
|
|
{x, y, width, term->cell_height}, /* left */
|
|
|
|
|
{x + cell_cols * term->cell_width - width, y, width, term->cell_height}, /* right */
|
|
|
|
|
{x, y + term->cell_height - width, cell_cols * term->cell_width, width}, /* bottom */
|
2019-12-16 21:34:38 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-08 13:57:31 +02:00
|
|
|
static void
|
2021-05-18 18:52:10 +02:00
|
|
|
draw_beam_cursor(const struct terminal *term, pixman_image_t *pix,
|
|
|
|
|
const struct fcft_font *font,
|
|
|
|
|
const pixman_color_t *color, int x, int y)
|
2019-07-08 13:57:31 +02:00
|
|
|
{
|
2023-10-10 14:23:33 +02:00
|
|
|
int baseline = y + term->font_baseline - term->fonts[0]->ascent;
|
2019-08-16 22:06:06 +02:00
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, color,
|
2019-08-29 20:41:40 +02:00
|
|
|
1, &(pixman_rectangle16_t){
|
|
|
|
|
x, baseline,
|
2021-04-30 20:31:47 +02:00
|
|
|
term_pt_or_px_as_pixels(term, &term->conf->cursor.beam_thickness),
|
|
|
|
|
term->fonts[0]->ascent + term->fonts[0]->descent});
|
2019-07-22 20:07:34 +02:00
|
|
|
}
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2022-04-05 19:18:46 +02:00
|
|
|
static int
|
|
|
|
|
underline_offset(const struct terminal *term, const struct fcft_font *font)
|
2019-07-22 20:07:34 +02:00
|
|
|
{
|
2023-10-10 14:23:33 +02:00
|
|
|
return term->font_baseline -
|
2021-06-17 17:52:38 +02:00
|
|
|
(term->conf->use_custom_underline_offset
|
|
|
|
|
? -term_pt_or_px_as_pixels(term, &term->conf->underline_offset)
|
|
|
|
|
: font->underline.position);
|
2021-05-18 18:52:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
draw_underline_cursor(const struct terminal *term, pixman_image_t *pix,
|
|
|
|
|
const struct fcft_font *font,
|
|
|
|
|
const pixman_color_t *color, int x, int y, int cols)
|
|
|
|
|
{
|
|
|
|
|
int thickness = term->conf->cursor.underline_thickness.px >= 0
|
|
|
|
|
? term_pt_or_px_as_pixels(
|
|
|
|
|
term, &term->conf->cursor.underline_thickness)
|
|
|
|
|
: font->underline.thickness;
|
|
|
|
|
|
2022-04-05 19:18:46 +02:00
|
|
|
/* Make sure the line isn't positioned below the cell */
|
|
|
|
|
const int y_ofs = min(underline_offset(term, font) + thickness,
|
|
|
|
|
term->cell_height - thickness);
|
|
|
|
|
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, color,
|
|
|
|
|
1, &(pixman_rectangle16_t){
|
|
|
|
|
x, y + y_ofs, cols * term->cell_width, thickness});
|
2021-05-18 18:52:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
draw_underline(const struct terminal *term, pixman_image_t *pix,
|
|
|
|
|
const struct fcft_font *font,
|
|
|
|
|
const pixman_color_t *color, int x, int y, int cols)
|
|
|
|
|
{
|
2022-08-19 02:54:49 +02:00
|
|
|
const int thickness = term->conf->underline_thickness.px >= 0
|
|
|
|
|
? term_pt_or_px_as_pixels(
|
|
|
|
|
term, &term->conf->underline_thickness)
|
|
|
|
|
: font->underline.thickness;
|
2022-04-05 19:18:46 +02:00
|
|
|
|
|
|
|
|
/* Make sure the line isn't positioned below the cell */
|
|
|
|
|
const int y_ofs = min(underline_offset(term, font),
|
|
|
|
|
term->cell_height - thickness);
|
|
|
|
|
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, color,
|
|
|
|
|
1, &(pixman_rectangle16_t){
|
|
|
|
|
x, y + y_ofs, cols * term->cell_width, thickness});
|
2019-07-22 20:07:34 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-23 18:55:37 +02:00
|
|
|
static void
|
|
|
|
|
draw_styled_underline(const struct terminal *term, pixman_image_t *pix,
|
|
|
|
|
const struct fcft_font *font,
|
|
|
|
|
const pixman_color_t *color,
|
2024-07-01 20:00:16 +01:00
|
|
|
enum underline_style style, int x, int y, int cols)
|
2024-06-23 18:55:37 +02:00
|
|
|
{
|
2024-07-01 20:00:16 +01:00
|
|
|
xassert(style != UNDERLINE_NONE);
|
2024-06-23 18:55:37 +02:00
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
if (style == UNDERLINE_SINGLE) {
|
2024-06-27 18:54:46 +02:00
|
|
|
draw_underline(term, pix, font, color, x, y, cols);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int thickness = term->conf->underline_thickness.px >= 0
|
|
|
|
|
? term_pt_or_px_as_pixels(
|
|
|
|
|
term, &term->conf->underline_thickness)
|
|
|
|
|
: font->underline.thickness;
|
2024-06-23 18:55:37 +02:00
|
|
|
|
|
|
|
|
int y_ofs;
|
|
|
|
|
|
|
|
|
|
/* Make sure the line isn't positioned below the cell */
|
|
|
|
|
switch (style) {
|
2024-07-01 20:00:16 +01:00
|
|
|
case UNDERLINE_DOUBLE:
|
|
|
|
|
case UNDERLINE_CURLY:
|
2024-06-23 18:55:37 +02:00
|
|
|
y_ofs = min(underline_offset(term, font),
|
|
|
|
|
term->cell_height - thickness * 3);
|
|
|
|
|
break;
|
|
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
case UNDERLINE_DASHED:
|
|
|
|
|
case UNDERLINE_DOTTED:
|
2024-06-23 18:55:37 +02:00
|
|
|
y_ofs = min(underline_offset(term, font),
|
|
|
|
|
term->cell_height - thickness);
|
|
|
|
|
break;
|
2024-06-27 18:54:46 +02:00
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
case UNDERLINE_NONE:
|
|
|
|
|
case UNDERLINE_SINGLE:
|
2024-07-03 06:55:01 +01:00
|
|
|
default:
|
|
|
|
|
BUG("unexpected underline style: %d", (int)style);
|
|
|
|
|
return;
|
2024-06-23 18:55:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int ceil_w = cols * term->cell_width;
|
|
|
|
|
|
|
|
|
|
switch (style) {
|
2024-07-01 20:00:16 +01:00
|
|
|
case UNDERLINE_DOUBLE: {
|
2024-06-23 18:55:37 +02:00
|
|
|
const pixman_rectangle16_t rects[] = {
|
|
|
|
|
{x, y + y_ofs, ceil_w, thickness},
|
|
|
|
|
{x, y + y_ofs + thickness * 2, ceil_w, thickness}};
|
|
|
|
|
pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, color, 2, rects);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
case UNDERLINE_DASHED: {
|
2024-06-23 18:55:37 +02:00
|
|
|
const int ceil_w = cols * term->cell_width;
|
|
|
|
|
const int dash_w = ceil_w / 3 + (ceil_w % 3 > 0);
|
|
|
|
|
const pixman_rectangle16_t rects[] = {
|
|
|
|
|
{x, y + y_ofs, dash_w, thickness},
|
|
|
|
|
{x + dash_w * 2, y + y_ofs, dash_w, thickness},
|
|
|
|
|
};
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, color, 2, rects);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-06-27 18:36:17 +02:00
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
case UNDERLINE_DOTTED: {
|
2024-06-27 18:36:17 +02:00
|
|
|
/* Number of dots per cell */
|
|
|
|
|
int per_cell = (term->cell_width / thickness) / 2;
|
|
|
|
|
if (per_cell == 0)
|
|
|
|
|
per_cell = 1;
|
|
|
|
|
|
|
|
|
|
xassert(per_cell >= 1);
|
|
|
|
|
|
|
|
|
|
/* Spacing between dots; start with the same width as the dots
|
|
|
|
|
themselves, then widen them if necessary, to consume unused
|
|
|
|
|
pixels */
|
|
|
|
|
int spacing[per_cell];
|
|
|
|
|
for (int i = 0; i < per_cell; i++)
|
|
|
|
|
spacing[i] = thickness;
|
2024-06-23 18:55:37 +02:00
|
|
|
|
2024-06-27 18:36:17 +02:00
|
|
|
/* Pixels remaining at the end of the cell */
|
|
|
|
|
int remaining = term->cell_width - (per_cell * 2) * thickness;
|
|
|
|
|
|
|
|
|
|
/* Spread out the left-over pixels across the spacing between
|
|
|
|
|
the dots */
|
|
|
|
|
for (int i = 0; remaining > 0; i = (i + 1) % per_cell, remaining--)
|
|
|
|
|
spacing[i]++;
|
|
|
|
|
|
|
|
|
|
xassert(remaining <= 0);
|
|
|
|
|
|
|
|
|
|
pixman_rectangle16_t rects[per_cell];
|
|
|
|
|
int dot_x = x;
|
|
|
|
|
for (int i = 0; i < per_cell; i++) {
|
2024-06-23 18:55:37 +02:00
|
|
|
rects[i] = (pixman_rectangle16_t){
|
2024-06-27 18:36:17 +02:00
|
|
|
dot_x, y + y_ofs, thickness, thickness
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dot_x += thickness + spacing[i];
|
2024-06-23 18:55:37 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 18:36:17 +02:00
|
|
|
pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, color, per_cell, rects);
|
2024-06-23 18:55:37 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
case UNDERLINE_CURLY: {
|
2024-06-23 18:55:37 +02:00
|
|
|
const int top = y + y_ofs;
|
|
|
|
|
const int bot = top + thickness * 3;
|
|
|
|
|
const int half_x = x + ceil_w / 2.0, full_x = x + ceil_w;
|
|
|
|
|
|
|
|
|
|
const double bt_2 = (bot - top) * (bot - top);
|
|
|
|
|
const double th_2 = thickness * thickness;
|
|
|
|
|
const double hx_2 = ceil_w * ceil_w / 4.0;
|
|
|
|
|
const int th = round(sqrt(th_2 + (th_2 * bt_2 / hx_2)) / 2.);
|
|
|
|
|
|
|
|
|
|
#define I(x) pixman_int_to_fixed(x)
|
|
|
|
|
const pixman_trapezoid_t traps[] = {
|
|
|
|
|
#if 0 /* characters sit within the "dips" of the curlies */
|
|
|
|
|
{
|
|
|
|
|
I(top), I(bot),
|
|
|
|
|
{{I(x), I(top + th)}, {I(half_x), I(bot + th)}},
|
|
|
|
|
{{I(x), I(top - th)}, {I(half_x), I(bot - th)}},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
I(top), I(bot),
|
|
|
|
|
{{I(half_x), I(bot - th)}, {I(full_x), I(top - th)}},
|
|
|
|
|
{{I(half_x), I(bot + th)}, {I(full_x), I(top + th)}},
|
|
|
|
|
}
|
|
|
|
|
#else /* characters sit on top of the curlies */
|
|
|
|
|
{
|
|
|
|
|
I(top), I(bot),
|
|
|
|
|
{{I(x), I(bot - th)}, {I(half_x), I(top - th)}},
|
|
|
|
|
{{I(x), I(bot + th)}, {I(half_x), I(top + th)}},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
I(top), I(bot),
|
|
|
|
|
{{I(half_x), I(top + th)}, {I(full_x), I(bot + th)}},
|
|
|
|
|
{{I(half_x), I(top - th)}, {I(full_x), I(bot - th)}},
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pixman_image_t *fill = pixman_image_create_solid_fill(color);
|
|
|
|
|
pixman_composite_trapezoids(
|
|
|
|
|
PIXMAN_OP_OVER, fill, pix, PIXMAN_a8, 0, 0, 0, 0,
|
|
|
|
|
sizeof(traps) / sizeof(traps[0]), traps);
|
|
|
|
|
|
|
|
|
|
pixman_image_unref(fill);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
case UNDERLINE_NONE:
|
|
|
|
|
case UNDERLINE_SINGLE:
|
2024-06-27 18:54:46 +02:00
|
|
|
BUG("underline styles not supposed to be handled here");
|
2024-06-23 18:55:37 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-22 20:07:34 +02:00
|
|
|
static void
|
2019-08-16 22:06:06 +02:00
|
|
|
draw_strikeout(const struct terminal *term, pixman_image_t *pix,
|
2020-04-21 19:29:36 +02:00
|
|
|
const struct fcft_font *font,
|
2019-08-16 22:06:06 +02:00
|
|
|
const pixman_color_t *color, int x, int y, int cols)
|
2019-07-22 20:07:34 +02:00
|
|
|
{
|
2024-08-25 11:28:21 +03:00
|
|
|
const int thickness = term->conf->strikeout_thickness.px >= 0
|
|
|
|
|
? term_pt_or_px_as_pixels(
|
|
|
|
|
term, &term->conf->strikeout_thickness)
|
|
|
|
|
: font->strikeout.thickness;
|
|
|
|
|
|
|
|
|
|
/* Try to center custom strikeout */
|
|
|
|
|
const int position = term->conf->strikeout_thickness.px >= 0
|
|
|
|
|
? font->strikeout.position - round(font->strikeout.thickness / 2.) + round(thickness / 2.)
|
|
|
|
|
: font->strikeout.position;
|
|
|
|
|
|
2019-08-16 22:06:06 +02:00
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, color,
|
2019-11-30 14:53:22 +01:00
|
|
|
1, &(pixman_rectangle16_t){
|
2024-08-25 11:28:21 +03:00
|
|
|
x, y + term->font_baseline - position,
|
|
|
|
|
cols * term->cell_width, thickness});
|
2019-07-22 20:07:34 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-19 07:28:49 +01:00
|
|
|
static void
|
2020-12-02 18:52:50 +01:00
|
|
|
cursor_colors_for_cell(const struct terminal *term, const struct cell *cell,
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
const pixman_color_t *fg, const pixman_color_t *bg,
|
|
|
|
|
pixman_color_t *cursor_color, pixman_color_t *text_color,
|
|
|
|
|
bool gamma_correct)
|
2019-12-19 07:28:49 +01:00
|
|
|
{
|
2024-07-01 17:24:50 +02:00
|
|
|
if (term->colors.cursor_bg >> 31)
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
*cursor_color = color_hex_to_pixman(term->colors.cursor_bg, gamma_correct);
|
2024-07-01 10:53:21 +02:00
|
|
|
else
|
2020-12-02 18:52:50 +01:00
|
|
|
*cursor_color = *fg;
|
2024-07-01 10:53:21 +02:00
|
|
|
|
2024-07-01 17:24:50 +02:00
|
|
|
if (term->colors.cursor_fg >> 31)
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
*text_color = color_hex_to_pixman(term->colors.cursor_fg, gamma_correct);
|
2024-07-01 10:53:21 +02:00
|
|
|
else {
|
2025-04-14 16:58:23 +02:00
|
|
|
xassert(bg->alpha == 0xffff);
|
2023-02-28 17:49:57 +01:00
|
|
|
*text_color = *bg;
|
2019-12-19 07:28:49 +01:00
|
|
|
}
|
2024-07-13 10:19:53 +02:00
|
|
|
|
|
|
|
|
if (text_color->red == cursor_color->red &&
|
|
|
|
|
text_color->green == cursor_color->green &&
|
|
|
|
|
text_color->blue == cursor_color->blue)
|
|
|
|
|
{
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
*text_color = color_hex_to_pixman(term->colors.bg, gamma_correct);
|
|
|
|
|
*cursor_color = color_hex_to_pixman(term->colors.fg, gamma_correct);
|
2024-07-13 10:19:53 +02:00
|
|
|
}
|
2020-12-02 18:52:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
draw_cursor(const struct terminal *term, const struct cell *cell,
|
|
|
|
|
const struct fcft_font *font, pixman_image_t *pix, pixman_color_t *fg,
|
|
|
|
|
const pixman_color_t *bg, int x, int y, int cols)
|
|
|
|
|
{
|
|
|
|
|
pixman_color_t cursor_color;
|
|
|
|
|
pixman_color_t text_color;
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
cursor_colors_for_cell(term, cell, fg, bg, &cursor_color, &text_color,
|
2025-05-01 08:34:49 +02:00
|
|
|
wayl_do_linear_blending(term->wl, term->conf));
|
2019-12-19 07:28:49 +01:00
|
|
|
|
2024-08-15 01:18:57 +09:00
|
|
|
if (unlikely(!term->kbd_focus)) {
|
|
|
|
|
switch (term->conf->cursor.unfocused_style) {
|
|
|
|
|
case CURSOR_UNFOCUSED_UNCHANGED:
|
|
|
|
|
break;
|
2024-04-09 16:28:54 +02:00
|
|
|
|
2024-08-15 01:18:57 +09:00
|
|
|
case CURSOR_UNFOCUSED_HOLLOW:
|
|
|
|
|
draw_hollow_block(term, pix, &cursor_color, x, y, cols);
|
|
|
|
|
return;
|
2024-04-09 16:28:54 +02:00
|
|
|
|
2024-08-15 01:18:57 +09:00
|
|
|
case CURSOR_UNFOCUSED_NONE:
|
|
|
|
|
return;
|
2024-04-09 16:28:54 +02:00
|
|
|
}
|
2024-08-15 01:18:57 +09:00
|
|
|
}
|
2019-12-19 07:28:49 +01:00
|
|
|
|
2024-08-15 01:18:57 +09:00
|
|
|
switch (term->cursor_style) {
|
|
|
|
|
case CURSOR_BLOCK:
|
2024-04-09 16:28:54 +02:00
|
|
|
if (likely(term->cursor_blink.state == CURSOR_BLINK_ON) ||
|
|
|
|
|
!term->kbd_focus)
|
|
|
|
|
{
|
2019-12-19 07:28:49 +01:00
|
|
|
*fg = text_color;
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, &cursor_color, 1,
|
|
|
|
|
&(pixman_rectangle16_t){x, y, cols * term->cell_width, term->cell_height});
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2021-04-30 20:31:47 +02:00
|
|
|
case CURSOR_BEAM:
|
2020-12-02 18:52:50 +01:00
|
|
|
if (likely(term->cursor_blink.state == CURSOR_BLINK_ON ||
|
|
|
|
|
!term->kbd_focus))
|
|
|
|
|
{
|
2021-05-18 18:52:10 +02:00
|
|
|
draw_beam_cursor(term, pix, font, &cursor_color, x, y);
|
2020-12-02 18:52:50 +01:00
|
|
|
}
|
2019-12-19 07:28:49 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CURSOR_UNDERLINE:
|
2020-12-02 18:52:50 +01:00
|
|
|
if (likely(term->cursor_blink.state == CURSOR_BLINK_ON ||
|
|
|
|
|
!term->kbd_focus))
|
|
|
|
|
{
|
2021-05-18 18:52:10 +02:00
|
|
|
draw_underline_cursor(term, pix, font, &cursor_color, x, y, cols);
|
2019-12-19 07:28:49 +01:00
|
|
|
}
|
|
|
|
|
break;
|
2025-02-19 11:44:38 +01:00
|
|
|
|
|
|
|
|
case CURSOR_HOLLOW:
|
|
|
|
|
if (likely(term->cursor_blink.state == CURSOR_BLINK_ON))
|
|
|
|
|
draw_hollow_block(term, pix, &cursor_color, x, y, cols);
|
|
|
|
|
break;
|
2019-12-19 07:28:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-01 20:09:39 +02:00
|
|
|
static int
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
render_cell(struct terminal *term, pixman_image_t *pix,
|
|
|
|
|
pixman_region32_t *damage, struct row *row, int row_no, int col,
|
|
|
|
|
bool has_cursor)
|
2019-07-22 20:07:34 +02:00
|
|
|
{
|
2020-05-01 11:56:13 +02:00
|
|
|
struct cell *cell = &row->cells[col];
|
2019-07-30 18:03:03 +02:00
|
|
|
if (cell->attrs.clean)
|
2019-08-01 20:09:39 +02:00
|
|
|
return 0;
|
2019-07-30 18:03:03 +02:00
|
|
|
|
|
|
|
|
cell->attrs.clean = 1;
|
2021-06-15 11:45:27 +02:00
|
|
|
cell->attrs.confined = true;
|
2019-07-30 18:03:03 +02:00
|
|
|
|
2019-08-16 22:06:06 +02:00
|
|
|
int width = term->cell_width;
|
|
|
|
|
int height = term->cell_height;
|
2021-06-15 17:52:45 +02:00
|
|
|
const int x = term->margins.left + col * width;
|
|
|
|
|
const int y = term->margins.top + row_no * height;
|
2019-07-22 20:07:34 +02:00
|
|
|
|
2019-11-28 19:22:21 +01:00
|
|
|
uint32_t _fg = 0;
|
|
|
|
|
uint32_t _bg = 0;
|
|
|
|
|
|
2021-07-15 18:23:49 +02:00
|
|
|
uint16_t alpha = 0xffff;
|
2025-05-05 12:43:02 +02:00
|
|
|
const bool is_selected = cell->attrs.selected;
|
2021-05-01 20:17:54 +02:00
|
|
|
|
2025-05-05 12:43:02 +02:00
|
|
|
/* Use cell specific color, if set, otherwise the default colors (possible reversed) */
|
|
|
|
|
switch (cell->attrs.fg_src) {
|
|
|
|
|
case COLOR_RGB:
|
|
|
|
|
_fg = cell->attrs.fg;
|
|
|
|
|
break;
|
2021-12-25 17:13:50 +01:00
|
|
|
|
2025-05-05 12:43:02 +02:00
|
|
|
case COLOR_BASE16:
|
|
|
|
|
case COLOR_BASE256:
|
|
|
|
|
xassert(cell->attrs.fg < ALEN(term->colors.table));
|
|
|
|
|
_fg = term->colors.table[cell->attrs.fg];
|
|
|
|
|
break;
|
2021-12-25 17:13:50 +01:00
|
|
|
|
2025-05-05 12:43:02 +02:00
|
|
|
case COLOR_DEFAULT:
|
|
|
|
|
_fg = term->reverse ? term->colors.bg : term->colors.fg;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-12-25 17:13:50 +01:00
|
|
|
|
2025-05-05 12:43:02 +02:00
|
|
|
switch (cell->attrs.bg_src) {
|
|
|
|
|
case COLOR_RGB:
|
|
|
|
|
_bg = cell->attrs.bg;
|
|
|
|
|
break;
|
2021-12-25 17:13:50 +01:00
|
|
|
|
2025-05-05 12:43:02 +02:00
|
|
|
case COLOR_BASE16:
|
|
|
|
|
case COLOR_BASE256:
|
|
|
|
|
xassert(cell->attrs.bg < ALEN(term->colors.table));
|
|
|
|
|
_bg = term->colors.table[cell->attrs.bg];
|
|
|
|
|
break;
|
2021-12-25 17:13:50 +01:00
|
|
|
|
2025-05-05 12:43:02 +02:00
|
|
|
case COLOR_DEFAULT:
|
|
|
|
|
_bg = term->reverse ? term->colors.fg : term->colors.bg;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unlikely(is_selected)) {
|
|
|
|
|
const uint32_t cell_fg = _fg;
|
|
|
|
|
const uint32_t cell_bg = _bg;
|
|
|
|
|
|
|
|
|
|
const bool custom_fg = term->colors.selection_fg >> 24 == 0;
|
|
|
|
|
const bool custom_bg = term->colors.selection_bg >> 24 == 0;
|
|
|
|
|
const bool custom_both = custom_fg && custom_bg;
|
|
|
|
|
|
|
|
|
|
if (custom_both) {
|
|
|
|
|
_fg = term->colors.selection_fg;
|
|
|
|
|
_bg = term->colors.selection_bg;
|
|
|
|
|
} else if (custom_bg) {
|
|
|
|
|
_bg = term->colors.selection_bg;
|
|
|
|
|
_fg = cell->attrs.reverse ? cell_bg : cell_fg;
|
|
|
|
|
} else if (custom_fg) {
|
|
|
|
|
_fg = term->colors.selection_fg;
|
|
|
|
|
_bg = cell->attrs.reverse ? cell_fg : cell_bg;
|
|
|
|
|
} else {
|
|
|
|
|
_bg = cell_fg;
|
|
|
|
|
_fg = cell_bg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unlikely(_fg == _bg)) {
|
|
|
|
|
/* Invert bg when selected/highlighted text has same fg/bg */
|
|
|
|
|
_bg = ~_bg;
|
|
|
|
|
alpha = 0xffff;
|
2021-12-25 17:13:50 +01:00
|
|
|
}
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2025-05-05 12:43:02 +02:00
|
|
|
} else {
|
|
|
|
|
if (unlikely(cell->attrs.reverse)) {
|
2020-08-12 18:53:32 +02:00
|
|
|
uint32_t swap = _fg;
|
|
|
|
|
_fg = _bg;
|
|
|
|
|
_bg = swap;
|
2023-07-19 16:34:42 +02:00
|
|
|
}
|
|
|
|
|
|
2025-04-25 19:20:36 +02:00
|
|
|
else if (!term->window->is_fullscreen && term->colors.alpha != 0xffff) {
|
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-19 09:29:06 +01:00
|
|
|
switch (term->conf->colors_dark.alpha_mode) {
|
2025-04-14 16:58:23 +02:00
|
|
|
case ALPHA_MODE_DEFAULT: {
|
|
|
|
|
if (cell->attrs.bg_src == COLOR_DEFAULT) {
|
|
|
|
|
alpha = term->colors.alpha;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-07-19 16:34:42 +02:00
|
|
|
}
|
2023-10-09 18:47:09 +01:00
|
|
|
|
2025-04-14 16:58:23 +02:00
|
|
|
case ALPHA_MODE_MATCHING: {
|
2025-04-19 07:46:06 +02:00
|
|
|
if (cell->attrs.bg_src == COLOR_DEFAULT ||
|
|
|
|
|
((cell->attrs.bg_src == COLOR_BASE16 ||
|
|
|
|
|
cell->attrs.bg_src == COLOR_BASE256) &&
|
|
|
|
|
term->colors.table[cell->attrs.bg] == term->colors.bg) ||
|
|
|
|
|
(cell->attrs.bg_src == COLOR_RGB &&
|
|
|
|
|
cell->attrs.bg == term->colors.bg))
|
|
|
|
|
{
|
2023-10-09 18:47:09 +01:00
|
|
|
alpha = term->colors.alpha;
|
2025-04-19 07:46:06 +02:00
|
|
|
}
|
2025-04-14 16:58:23 +02:00
|
|
|
break;
|
2023-10-09 18:47:09 +01:00
|
|
|
}
|
2025-04-14 16:58:23 +02:00
|
|
|
|
|
|
|
|
case ALPHA_MODE_ALL: {
|
|
|
|
|
alpha = term->colors.alpha;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/*
|
|
|
|
|
* Note: disable transparency when fullscreened.
|
|
|
|
|
*
|
|
|
|
|
* This is because the wayland protocol mandates no screen
|
|
|
|
|
* content is shown behind the fullscreened window.
|
|
|
|
|
*
|
|
|
|
|
* The _intent_ of the specification is that a black (or
|
|
|
|
|
* other static color) should be used as background.
|
|
|
|
|
*
|
|
|
|
|
* There's a bit of gray area however, and some
|
|
|
|
|
* compositors have chosen to interpret the specification
|
|
|
|
|
* in a way that allows wallpapers to be seen through a
|
|
|
|
|
* fullscreen window.
|
|
|
|
|
*
|
|
|
|
|
* Given that a) the intent of the specification, and b)
|
|
|
|
|
* we don't know what the compositor will do, we simply
|
|
|
|
|
* disable transparency while in fullscreen.
|
|
|
|
|
*
|
|
|
|
|
* To see why, consider what happens if we keep our
|
|
|
|
|
* transparency. For example, if the background color is
|
|
|
|
|
* white, and alpha is 0.5, then the window will be drawn
|
|
|
|
|
* in a shade of gray while fullscreened.
|
|
|
|
|
*
|
|
|
|
|
* See
|
|
|
|
|
* https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/116
|
|
|
|
|
* for a discussion on whether transparent, fullscreen
|
|
|
|
|
* windows should be allowed in some way or not.
|
|
|
|
|
*
|
|
|
|
|
* NOTE: if changing this, also update render_margin()
|
|
|
|
|
*/
|
|
|
|
|
xassert(alpha == 0xffff);
|
2023-10-09 18:47:09 +01:00
|
|
|
}
|
2019-07-08 13:57:31 +02:00
|
|
|
}
|
|
|
|
|
|
2020-11-15 20:05:01 +01:00
|
|
|
if (cell->attrs.dim)
|
2021-11-03 14:25:38 +01:00
|
|
|
_fg = color_dim(term, _fg);
|
2021-04-17 21:57:08 +02:00
|
|
|
if (term->conf->bold_in_bright.enabled && cell->attrs.bold)
|
2021-04-17 21:02:02 +02:00
|
|
|
_fg = color_brighten(term, _fg);
|
2020-11-15 20:05:01 +01:00
|
|
|
|
|
|
|
|
if (cell->attrs.blink && term->blink.state == BLINK_OFF)
|
2025-03-22 20:11:23 +01:00
|
|
|
_fg = color_blend_towards(_fg, 0x00000000, term->conf->dim.amount);
|
2020-11-15 20:05:01 +01:00
|
|
|
|
2025-05-01 08:34:49 +02:00
|
|
|
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct);
|
|
|
|
|
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct);
|
2019-07-16 13:17:51 +02:00
|
|
|
|
2020-04-21 19:29:36 +02:00
|
|
|
struct fcft_font *font = attrs_to_font(term, &cell->attrs);
|
unicode-combining: store seen combining chains "globally" in the term struct
Instead of storing combining data per cell, realize that most
combinations are re-occurring and that there's lots of available space
left in the unicode range, and store seen base+combining combinations
chains in a per-terminal array.
When we encounter a combining character, we first try to pre-compose,
like before. If that fails, we then search for the current
base+combining combo in the list of previously seen combinations. If
not found there either, we allocate a new combo and add it to the
list. Regardless, the result is an index into this array. We store
this index, offsetted by COMB_CHARS_LO=0x40000000ul in the cell.
When rendering, we need to check if the cell character is a plain
character, or if it's a composed character (identified by checking if
the cell character is >= COMB_CHARS_LO).
Then we render the grapheme pretty much like before.
2020-05-03 11:03:22 +02:00
|
|
|
const struct composed *composed = NULL;
|
2020-08-20 19:25:35 +02:00
|
|
|
const struct fcft_grapheme *grapheme = NULL;
|
|
|
|
|
const struct fcft_glyph *single = NULL;
|
|
|
|
|
const struct fcft_glyph **glyphs = NULL;
|
|
|
|
|
unsigned glyph_count = 0;
|
2020-04-26 12:39:42 +02:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
char32_t base = cell->wc;
|
2021-05-31 17:50:49 +02:00
|
|
|
int cell_cols = 1;
|
unicode-combining: store seen combining chains "globally" in the term struct
Instead of storing combining data per cell, realize that most
combinations are re-occurring and that there's lots of available space
left in the unicode range, and store seen base+combining combinations
chains in a per-terminal array.
When we encounter a combining character, we first try to pre-compose,
like before. If that fails, we then search for the current
base+combining combo in the list of previously seen combinations. If
not found there either, we allocate a new combo and add it to the
list. Regardless, the result is an index into this array. We store
this index, offsetted by COMB_CHARS_LO=0x40000000ul in the cell.
When rendering, we need to check if the cell character is a plain
character, or if it's a composed character (identified by checking if
the cell character is >= COMB_CHARS_LO).
Then we render the grapheme pretty much like before.
2020-05-03 11:03:22 +02:00
|
|
|
|
2021-05-31 17:10:05 +02:00
|
|
|
if (base != 0) {
|
2021-05-03 17:57:16 +02:00
|
|
|
if (unlikely(
|
|
|
|
|
/* Classic box drawings */
|
2021-09-14 09:50:49 +02:00
|
|
|
(base >= GLYPH_BOX_DRAWING_FIRST &&
|
|
|
|
|
base <= GLYPH_BOX_DRAWING_LAST) ||
|
2021-05-03 17:57:16 +02:00
|
|
|
|
box-drawing: add braille characters
Render braille ourselves, instead of using font glyphs. Decoding a
braille character is easy enough; there are 256 codepoints,
represented by an 8-bit integer (i.e. subtract the Unicode codepoint
offset, 0x2800, and you’re left with an integer in the range 0-255).
Each bit corresponds to a dot. The first 6 bits represent the upper 6
dots, while the two last bits represent the fourth (and last) row of
dots.
The hard part is sizing the dots and the spacing between them.
The aim is to have the spacing between the dots be the same size as
the dots themselves, and to have the margins on each side be half the
size of the dots.
In a perfectly sized cell, this means two braille characters next to
each other will be evenly spaced.
This is however almost never the case. The layout logic currently:
* Set dot size to either the width / 4, or height / 8, depending on
which one is smallest.
* Horizontal spacing is initialized to the width / 4
* Vertical spacing is initialized to the height / 8
* Horizontal margins are initialized to the horizontal spacing / 2
* Vertical margins are initialized to the vertical spacing / 2.
Next, we calculate the number of “remaining” pixels. That is, if we
add the left margin, two dots and the spacing between, how many pixels
are left on the horizontal axis?
These pixels are distributed in the following order (we “stop” as soon
as we run out of pixels):
* If the dot size is 0 (happens for very small font sizes), increase
it to 1.
* If the margins are 0, increase them to 1.
* If we have enough pixels (need at 2 horizontal and 4 vertical),
increase the dot size.
* Increase spacing.
* Increase margins.
Closes #702
2021-09-02 14:55:26 +02:00
|
|
|
/* Braille */
|
2021-09-14 09:50:49 +02:00
|
|
|
(base >= GLYPH_BRAILLE_FIRST &&
|
|
|
|
|
base <= GLYPH_BRAILLE_LAST) ||
|
box-drawing: add braille characters
Render braille ourselves, instead of using font glyphs. Decoding a
braille character is easy enough; there are 256 codepoints,
represented by an 8-bit integer (i.e. subtract the Unicode codepoint
offset, 0x2800, and you’re left with an integer in the range 0-255).
Each bit corresponds to a dot. The first 6 bits represent the upper 6
dots, while the two last bits represent the fourth (and last) row of
dots.
The hard part is sizing the dots and the spacing between them.
The aim is to have the spacing between the dots be the same size as
the dots themselves, and to have the margins on each side be half the
size of the dots.
In a perfectly sized cell, this means two braille characters next to
each other will be evenly spaced.
This is however almost never the case. The layout logic currently:
* Set dot size to either the width / 4, or height / 8, depending on
which one is smallest.
* Horizontal spacing is initialized to the width / 4
* Vertical spacing is initialized to the height / 8
* Horizontal margins are initialized to the horizontal spacing / 2
* Vertical margins are initialized to the vertical spacing / 2.
Next, we calculate the number of “remaining” pixels. That is, if we
add the left margin, two dots and the spacing between, how many pixels
are left on the horizontal axis?
These pixels are distributed in the following order (we “stop” as soon
as we run out of pixels):
* If the dot size is 0 (happens for very small font sizes), increase
it to 1.
* If the margins are 0, increase them to 1.
* If we have enough pixels (need at 2 horizontal and 4 vertical),
increase the dot size.
* Increase spacing.
* Increase margins.
Closes #702
2021-09-02 14:55:26 +02:00
|
|
|
|
2021-05-03 17:57:16 +02:00
|
|
|
/*
|
|
|
|
|
* Unicode 13 "Symbols for Legacy Computing"
|
|
|
|
|
* sub-ranges below.
|
|
|
|
|
*
|
|
|
|
|
* Note, the full range is U+1FB00 - U+1FBF9
|
|
|
|
|
*/
|
2021-09-14 09:50:49 +02:00
|
|
|
(base >= GLYPH_LEGACY_FIRST &&
|
2024-12-08 09:05:41 +01:00
|
|
|
base <= GLYPH_LEGACY_LAST) ||
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Unicode 16 "Symbols for Legacy Computing Supplement"
|
|
|
|
|
*
|
|
|
|
|
* Note, the full range is U+1CC00 - U+1CEAF
|
|
|
|
|
*/
|
|
|
|
|
(base >= GLYPH_OCTANTS_FIRST &&
|
|
|
|
|
base <= GLYPH_OCTANTS_LAST)) &&
|
2021-05-03 17:57:16 +02:00
|
|
|
|
2021-04-09 23:19:20 +02:00
|
|
|
likely(!term->conf->box_drawings_uses_font_glyphs))
|
|
|
|
|
{
|
2021-09-14 09:50:49 +02:00
|
|
|
struct fcft_glyph ***arr;
|
|
|
|
|
size_t count;
|
|
|
|
|
size_t idx;
|
|
|
|
|
|
2025-01-03 07:33:14 +01:00
|
|
|
if (base >= GLYPH_LEGACY_FIRST) {
|
2021-09-14 09:50:49 +02:00
|
|
|
arr = &term->custom_glyphs.legacy;
|
|
|
|
|
count = GLYPH_LEGACY_COUNT;
|
|
|
|
|
idx = base - GLYPH_LEGACY_FIRST;
|
2025-01-03 07:33:14 +01:00
|
|
|
} else if (base >= GLYPH_OCTANTS_FIRST) {
|
|
|
|
|
arr = &term->custom_glyphs.octants;
|
|
|
|
|
count = GLYPH_OCTANTS_COUNT;
|
|
|
|
|
idx = base - GLYPH_OCTANTS_FIRST;
|
2021-09-14 09:50:49 +02:00
|
|
|
} else if (base >= GLYPH_BRAILLE_FIRST) {
|
|
|
|
|
arr = &term->custom_glyphs.braille;
|
|
|
|
|
count = GLYPH_BRAILLE_COUNT;
|
|
|
|
|
idx = base - GLYPH_BRAILLE_FIRST;
|
|
|
|
|
} else {
|
|
|
|
|
arr = &term->custom_glyphs.box_drawing;
|
|
|
|
|
count = GLYPH_BOX_DRAWING_COUNT;
|
|
|
|
|
idx = base - GLYPH_BOX_DRAWING_FIRST;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unlikely(*arr == NULL))
|
|
|
|
|
*arr = xcalloc(count, sizeof((*arr)[0]));
|
|
|
|
|
|
|
|
|
|
if (likely((*arr)[idx] != NULL))
|
|
|
|
|
single = (*arr)[idx];
|
2020-12-26 16:24:16 +01:00
|
|
|
else {
|
|
|
|
|
mtx_lock(&term->render.workers.lock);
|
|
|
|
|
|
box-drawing: add braille characters
Render braille ourselves, instead of using font glyphs. Decoding a
braille character is easy enough; there are 256 codepoints,
represented by an 8-bit integer (i.e. subtract the Unicode codepoint
offset, 0x2800, and you’re left with an integer in the range 0-255).
Each bit corresponds to a dot. The first 6 bits represent the upper 6
dots, while the two last bits represent the fourth (and last) row of
dots.
The hard part is sizing the dots and the spacing between them.
The aim is to have the spacing between the dots be the same size as
the dots themselves, and to have the margins on each side be half the
size of the dots.
In a perfectly sized cell, this means two braille characters next to
each other will be evenly spaced.
This is however almost never the case. The layout logic currently:
* Set dot size to either the width / 4, or height / 8, depending on
which one is smallest.
* Horizontal spacing is initialized to the width / 4
* Vertical spacing is initialized to the height / 8
* Horizontal margins are initialized to the horizontal spacing / 2
* Vertical margins are initialized to the vertical spacing / 2.
Next, we calculate the number of “remaining” pixels. That is, if we
add the left margin, two dots and the spacing between, how many pixels
are left on the horizontal axis?
These pixels are distributed in the following order (we “stop” as soon
as we run out of pixels):
* If the dot size is 0 (happens for very small font sizes), increase
it to 1.
* If the margins are 0, increase them to 1.
* If we have enough pixels (need at 2 horizontal and 4 vertical),
increase the dot size.
* Increase spacing.
* Increase margins.
Closes #702
2021-09-02 14:55:26 +02:00
|
|
|
/* Other thread may have instantiated it while we
|
2021-09-03 21:38:08 +02:00
|
|
|
* acquired the lock */
|
2021-09-14 09:50:49 +02:00
|
|
|
single = (*arr)[idx];
|
|
|
|
|
if (likely(single == NULL))
|
|
|
|
|
single = (*arr)[idx] = box_drawing(term, base);
|
2020-12-26 16:24:16 +01:00
|
|
|
mtx_unlock(&term->render.workers.lock);
|
|
|
|
|
}
|
2020-08-20 19:25:35 +02:00
|
|
|
|
2021-09-14 09:50:49 +02:00
|
|
|
if (single != NULL) {
|
|
|
|
|
glyph_count = 1;
|
|
|
|
|
glyphs = &single;
|
|
|
|
|
cell_cols = single->cols;
|
|
|
|
|
}
|
2020-08-20 19:25:35 +02:00
|
|
|
}
|
|
|
|
|
|
composed: store compose chains in a binary search tree
The previous implementation stored compose chains in a dynamically
allocated array. Adding a chain was easy: resize the array and append
the new chain at the end. Looking up a compose chain given a compose
chain key/index was also easy: just index into the array.
However, searching for a pre-existing chain given a codepoint sequence
was very slow. Since the array wasn’t sorted, we typically had to scan
through the entire array, just to realize that there is no
pre-existing chain, and that we need to add a new one.
Since this happens for *each* codepoint in a grapheme cluster, things
quickly became really slow.
Things were ok:ish as long as the compose chain struct was small, as
that made it possible to hold all the chains in the cache. Once the
number of chains reached a certain point, or when we were forced to
bump maximum number of allowed codepoints in a chain, we started
thrashing the cache and things got much much worse.
So what can we do?
We can’t sort the array, because
a) that would invalidate all existing chain keys in the grid (and
iterating the entire scrollback and updating compose keys is *not* an
option).
b) inserting a chain becomes slow as we need to first find _where_ to
insert it, and then memmove() the rest of the array.
This patch uses a binary search tree to store the chains instead of a
simple array.
The tree is sorted on a “key”, which is the XOR of all codepoints,
truncated to the CELL_COMB_CHARS_HI-CELL_COMB_CHARS_LO range.
The grid now stores CELL_COMB_CHARS_LO+key, instead of
CELL_COMB_CHARS_LO+index.
Since the key is truncated, collisions may occur. This is handled by
incrementing the key by 1.
Lookup is of course slower than before, O(log n) instead of
O(1).
Insertion is slightly slower as well: technically it’s O(log n)
instead of O(1). However, we also need to take into account the
re-allocating the array will occasionally force a full copy of the
array when it cannot simply be growed.
But finding a pre-existing chain is now *much* faster: O(log n)
instead of O(n). In most cases, the first lookup will either
succeed (return a true match), or fail (return NULL). However, since
key collisions are possible, it may also return false matches. This
means we need to verify the contents of the chain before deciding to
use it instead of inserting a new chain. But remember that this
comparison was being done for each and every chain in the previous
implementation.
With lookups being much faster, and in particular, no longer requiring
us to check the chain contents for every singlec chain, we can now use
a dynamically allocated ‘chars’ array in the chain. This was
previously a hardcoded array of 10 chars.
Using a dynamic allocated array means looking in the array is slower,
since we now need two loads: one to load the pointer, and a second to
load _from_ the pointer.
As a result, the base size of a compose chain (i.e. an “empty” chain)
has now been reduced from 48 bytes to 32. A chain with two codepoints
is 40 bytes. This means we have up to 4 codepoints while still using
less, or the same amount, of memory as before.
Furthermore, the Unicode random test (i.e. write random “unicode”
chars) is now **faster** than current master (i.e. before text-shaping
support was added), **with** test-shaping enabled. With text-shaping
disabled, we’re _even_ faster.
2021-06-24 13:17:07 +02:00
|
|
|
else if (base >= CELL_COMB_CHARS_LO && base <= CELL_COMB_CHARS_HI)
|
2020-08-20 19:25:35 +02:00
|
|
|
{
|
composed: store compose chains in a binary search tree
The previous implementation stored compose chains in a dynamically
allocated array. Adding a chain was easy: resize the array and append
the new chain at the end. Looking up a compose chain given a compose
chain key/index was also easy: just index into the array.
However, searching for a pre-existing chain given a codepoint sequence
was very slow. Since the array wasn’t sorted, we typically had to scan
through the entire array, just to realize that there is no
pre-existing chain, and that we need to add a new one.
Since this happens for *each* codepoint in a grapheme cluster, things
quickly became really slow.
Things were ok:ish as long as the compose chain struct was small, as
that made it possible to hold all the chains in the cache. Once the
number of chains reached a certain point, or when we were forced to
bump maximum number of allowed codepoints in a chain, we started
thrashing the cache and things got much much worse.
So what can we do?
We can’t sort the array, because
a) that would invalidate all existing chain keys in the grid (and
iterating the entire scrollback and updating compose keys is *not* an
option).
b) inserting a chain becomes slow as we need to first find _where_ to
insert it, and then memmove() the rest of the array.
This patch uses a binary search tree to store the chains instead of a
simple array.
The tree is sorted on a “key”, which is the XOR of all codepoints,
truncated to the CELL_COMB_CHARS_HI-CELL_COMB_CHARS_LO range.
The grid now stores CELL_COMB_CHARS_LO+key, instead of
CELL_COMB_CHARS_LO+index.
Since the key is truncated, collisions may occur. This is handled by
incrementing the key by 1.
Lookup is of course slower than before, O(log n) instead of
O(1).
Insertion is slightly slower as well: technically it’s O(log n)
instead of O(1). However, we also need to take into account the
re-allocating the array will occasionally force a full copy of the
array when it cannot simply be growed.
But finding a pre-existing chain is now *much* faster: O(log n)
instead of O(n). In most cases, the first lookup will either
succeed (return a true match), or fail (return NULL). However, since
key collisions are possible, it may also return false matches. This
means we need to verify the contents of the chain before deciding to
use it instead of inserting a new chain. But remember that this
comparison was being done for each and every chain in the previous
implementation.
With lookups being much faster, and in particular, no longer requiring
us to check the chain contents for every singlec chain, we can now use
a dynamically allocated ‘chars’ array in the chain. This was
previously a hardcoded array of 10 chars.
Using a dynamic allocated array means looking in the array is slower,
since we now need two loads: one to load the pointer, and a second to
load _from_ the pointer.
As a result, the base size of a compose chain (i.e. an “empty” chain)
has now been reduced from 48 bytes to 32. A chain with two codepoints
is 40 bytes. This means we have up to 4 codepoints while still using
less, or the same amount, of memory as before.
Furthermore, the Unicode random test (i.e. write random “unicode”
chars) is now **faster** than current master (i.e. before text-shaping
support was added), **with** test-shaping enabled. With text-shaping
disabled, we’re _even_ faster.
2021-06-24 13:17:07 +02:00
|
|
|
composed = composed_lookup(term->composed, base - CELL_COMB_CHARS_LO);
|
2020-08-20 19:25:35 +02:00
|
|
|
base = composed->chars[0];
|
|
|
|
|
|
|
|
|
|
if (term->conf->can_shape_grapheme && term->conf->tweak.grapheme_shaping) {
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
grapheme = fcft_rasterize_grapheme_utf32(
|
|
|
|
|
font, composed->count, composed->chars, term->font_subpixel);
|
2020-08-20 19:25:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (grapheme != NULL) {
|
osc: wip: kitty text size protocol
This brings initial support for the new kitty text-sizing
protocol. Note hat only the width-parameter ('w') is supported. That
is, no font scaling, and no multi-line cells.
For now, only explicit widths are supported. That is, w=0 does not yet
work.
There are a couple of changes to the renderer, to handle e.g.
OSC 66 ; w=6 ; foobar ST
There are two ways this can get rendered, depending on whether
grapheme shaping has been enabled. We either shape it, and get an
array of glyphs back that we render. Or, we rasterize each codepoint
ourselves, and render each resulting glyph.
The two cases ends up in two different renderer loops, that worked
somewhat different. In particular, the first case has probably never
been tested/used at all...
With this patch, both are changed, and now uses some heuristic to
differentiate between multi-cell text strings (like in the example
above), or single-cell combining characters. The difference is mainly
in which offset to use for the secondary glyphs.
In a multi-cell string, each glyph is mapped to its own cell, while in
the combining case, we try to map all glyphs to the same cell.
2025-01-25 14:09:35 +01:00
|
|
|
const int forced_width = composed->forced_width;
|
|
|
|
|
|
|
|
|
|
cell_cols = forced_width > 0 ? forced_width : composed->width;
|
2021-06-24 10:08:58 +02:00
|
|
|
|
2020-08-20 19:25:35 +02:00
|
|
|
composed = NULL;
|
|
|
|
|
glyphs = grapheme->glyphs;
|
|
|
|
|
glyph_count = grapheme->count;
|
osc: wip: kitty text size protocol
This brings initial support for the new kitty text-sizing
protocol. Note hat only the width-parameter ('w') is supported. That
is, no font scaling, and no multi-line cells.
For now, only explicit widths are supported. That is, w=0 does not yet
work.
There are a couple of changes to the renderer, to handle e.g.
OSC 66 ; w=6 ; foobar ST
There are two ways this can get rendered, depending on whether
grapheme shaping has been enabled. We either shape it, and get an
array of glyphs back that we render. Or, we rasterize each codepoint
ourselves, and render each resulting glyph.
The two cases ends up in two different renderer loops, that worked
somewhat different. In particular, the first case has probably never
been tested/used at all...
With this patch, both are changed, and now uses some heuristic to
differentiate between multi-cell text strings (like in the example
above), or single-cell combining characters. The difference is mainly
in which offset to use for the secondary glyphs.
In a multi-cell string, each glyph is mapped to its own cell, while in
the combining case, we try to map all glyphs to the same cell.
2025-01-25 14:09:35 +01:00
|
|
|
|
|
|
|
|
if (forced_width > 0)
|
|
|
|
|
glyph_count = min(glyph_count, forced_width);
|
2021-06-15 07:58:41 +02:00
|
|
|
}
|
2020-08-20 19:25:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (single == NULL && grapheme == NULL) {
|
2023-01-15 14:42:48 +01:00
|
|
|
if (unlikely(base >= CELL_SPACER)) {
|
2021-05-31 17:11:58 +02:00
|
|
|
glyph_count = 0;
|
|
|
|
|
cell_cols = 1;
|
|
|
|
|
} else {
|
2023-01-15 14:42:48 +01:00
|
|
|
xassert(base != 0);
|
|
|
|
|
single = fcft_rasterize_char_utf32(font, base, term->font_subpixel);
|
|
|
|
|
if (single == NULL) {
|
|
|
|
|
glyph_count = 0;
|
|
|
|
|
cell_cols = 1;
|
|
|
|
|
} else {
|
|
|
|
|
glyph_count = 1;
|
|
|
|
|
glyphs = &single;
|
osc: wip: kitty text size protocol
This brings initial support for the new kitty text-sizing
protocol. Note hat only the width-parameter ('w') is supported. That
is, no font scaling, and no multi-line cells.
For now, only explicit widths are supported. That is, w=0 does not yet
work.
There are a couple of changes to the renderer, to handle e.g.
OSC 66 ; w=6 ; foobar ST
There are two ways this can get rendered, depending on whether
grapheme shaping has been enabled. We either shape it, and get an
array of glyphs back that we render. Or, we rasterize each codepoint
ourselves, and render each resulting glyph.
The two cases ends up in two different renderer loops, that worked
somewhat different. In particular, the first case has probably never
been tested/used at all...
With this patch, both are changed, and now uses some heuristic to
differentiate between multi-cell text strings (like in the example
above), or single-cell combining characters. The difference is mainly
in which offset to use for the secondary glyphs.
In a multi-cell string, each glyph is mapped to its own cell, while in
the combining case, we try to map all glyphs to the same cell.
2025-01-25 14:09:35 +01:00
|
|
|
|
|
|
|
|
const size_t forced_width = composed != NULL ? composed->forced_width : 0;
|
|
|
|
|
cell_cols = forced_width > 0 ? forced_width : single->cols;
|
2023-01-15 14:42:48 +01:00
|
|
|
}
|
2021-05-31 17:11:58 +02:00
|
|
|
}
|
2020-08-20 19:25:35 +02:00
|
|
|
}
|
unicode-combining: store seen combining chains "globally" in the term struct
Instead of storing combining data per cell, realize that most
combinations are re-occurring and that there's lots of available space
left in the unicode range, and store seen base+combining combinations
chains in a per-terminal array.
When we encounter a combining character, we first try to pre-compose,
like before. If that fails, we then search for the current
base+combining combo in the list of previously seen combinations. If
not found there either, we allocate a new combo and add it to the
list. Regardless, the result is an index into this array. We store
this index, offsetted by COMB_CHARS_LO=0x40000000ul in the cell.
When rendering, we need to check if the cell character is a plain
character, or if it's a composed character (identified by checking if
the cell character is >= COMB_CHARS_LO).
Then we render the grapheme pretty much like before.
2020-05-03 11:03:22 +02:00
|
|
|
}
|
2019-07-31 21:15:40 +02:00
|
|
|
|
2020-08-20 19:25:35 +02:00
|
|
|
assert(glyph_count == 0 || glyphs != NULL);
|
2021-05-30 19:37:53 +02:00
|
|
|
|
2020-06-05 08:10:38 +02:00
|
|
|
const int cols_left = term->cols - col;
|
2021-05-30 19:37:53 +02:00
|
|
|
cell_cols = max(1, min(cell_cols, cols_left));
|
2020-06-05 08:10:38 +02:00
|
|
|
|
2020-09-03 17:37:44 +02:00
|
|
|
/*
|
2021-06-15 11:45:27 +02:00
|
|
|
* Determine cells that will bleed into their right neighbor and remember
|
|
|
|
|
* them for cleanup in the next frame.
|
2020-09-03 17:37:44 +02:00
|
|
|
*/
|
2021-06-15 11:45:27 +02:00
|
|
|
int render_width = cell_cols * width;
|
|
|
|
|
if (term->conf->tweak.overflowing_glyphs &&
|
2021-07-20 11:12:38 +02:00
|
|
|
glyph_count > 0 &&
|
|
|
|
|
cols_left > cell_cols)
|
2020-09-03 17:37:44 +02:00
|
|
|
{
|
2021-06-15 11:45:27 +02:00
|
|
|
int glyph_width = 0, advance = 0;
|
|
|
|
|
for (size_t i = 0; i < glyph_count; i++) {
|
|
|
|
|
glyph_width = max(glyph_width,
|
|
|
|
|
advance + glyphs[i]->x + glyphs[i]->width);
|
|
|
|
|
advance += glyphs[i]->advance.x;
|
|
|
|
|
}
|
2021-01-02 22:24:49 +01:00
|
|
|
|
2021-06-15 11:45:27 +02:00
|
|
|
if (glyph_width > render_width) {
|
|
|
|
|
render_width = min(glyph_width, render_width + width);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < cell_cols; i++)
|
|
|
|
|
row->cells[col + i].attrs.confined = false;
|
|
|
|
|
}
|
2020-09-03 17:37:44 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-05 08:10:38 +02:00
|
|
|
pixman_region32_t clip;
|
|
|
|
|
pixman_region32_init_rect(
|
|
|
|
|
&clip, x, y,
|
2021-06-15 11:45:27 +02:00
|
|
|
render_width, term->cell_height);
|
2020-06-05 08:10:38 +02:00
|
|
|
pixman_image_set_clip_region32(pix, &clip);
|
2023-10-07 16:23:09 +02:00
|
|
|
|
|
|
|
|
if (damage != NULL) {
|
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
damage, damage, x, y, render_width, term->cell_height);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-04 20:08:23 +02:00
|
|
|
pixman_region32_fini(&clip);
|
2019-07-31 21:15:40 +02:00
|
|
|
|
2019-07-08 13:57:31 +02:00
|
|
|
/* Background */
|
2019-08-16 20:40:32 +02:00
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, &bg, 1,
|
|
|
|
|
&(pixman_rectangle16_t){x, y, cell_cols * width, height});
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2020-11-23 19:26:00 +01:00
|
|
|
if (cell->attrs.blink && term->blink.fd < 0) {
|
|
|
|
|
/* TODO: use a custom lock for this? */
|
|
|
|
|
mtx_lock(&term->render.workers.lock);
|
2019-12-17 19:11:27 +01:00
|
|
|
term_arm_blink_timer(term);
|
2020-11-23 19:26:00 +01:00
|
|
|
mtx_unlock(&term->render.workers.lock);
|
|
|
|
|
}
|
2019-07-21 20:11:20 +02:00
|
|
|
|
2025-04-14 16:58:23 +02:00
|
|
|
if (unlikely(has_cursor && term->cursor_style == CURSOR_BLOCK && term->kbd_focus)) {
|
|
|
|
|
const pixman_color_t bg_without_alpha = color_hex_to_pixman(_bg, gamma_correct);
|
|
|
|
|
draw_cursor(term, cell, font, pix, &fg, &bg_without_alpha, x, y, cell_cols);
|
|
|
|
|
}
|
2020-03-17 11:47:47 +01:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
if (cell->wc == 0 || cell->wc >= CELL_SPACER || cell->wc == U'\t' ||
|
2021-04-23 21:27:32 +02:00
|
|
|
(unlikely(cell->attrs.conceal) && !is_selected))
|
|
|
|
|
{
|
2020-03-17 11:47:47 +01:00
|
|
|
goto draw_cursor;
|
2021-04-23 21:27:32 +02:00
|
|
|
}
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2020-05-02 22:14:48 +02:00
|
|
|
pixman_image_t *clr_pix = pixman_image_create_solid_fill(&fg);
|
|
|
|
|
|
2021-06-15 17:52:45 +02:00
|
|
|
int pen_x = x;
|
2020-08-20 19:25:35 +02:00
|
|
|
for (unsigned i = 0; i < glyph_count; i++) {
|
|
|
|
|
const int letter_x_ofs = i == 0 ? term->font_x_ofs : 0;
|
|
|
|
|
|
|
|
|
|
const struct fcft_glyph *glyph = glyphs[i];
|
|
|
|
|
if (glyph == NULL)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
int g_x = glyph->x;
|
|
|
|
|
int g_y = glyph->y;
|
|
|
|
|
|
osc: wip: kitty text size protocol
This brings initial support for the new kitty text-sizing
protocol. Note hat only the width-parameter ('w') is supported. That
is, no font scaling, and no multi-line cells.
For now, only explicit widths are supported. That is, w=0 does not yet
work.
There are a couple of changes to the renderer, to handle e.g.
OSC 66 ; w=6 ; foobar ST
There are two ways this can get rendered, depending on whether
grapheme shaping has been enabled. We either shape it, and get an
array of glyphs back that we render. Or, we rasterize each codepoint
ourselves, and render each resulting glyph.
The two cases ends up in two different renderer loops, that worked
somewhat different. In particular, the first case has probably never
been tested/used at all...
With this patch, both are changed, and now uses some heuristic to
differentiate between multi-cell text strings (like in the example
above), or single-cell combining characters. The difference is mainly
in which offset to use for the secondary glyphs.
In a multi-cell string, each glyph is mapped to its own cell, while in
the combining case, we try to map all glyphs to the same cell.
2025-01-25 14:09:35 +01:00
|
|
|
if (i > 0 && glyph->x >= 0 && cell_cols == 1)
|
2020-08-20 19:25:35 +02:00
|
|
|
g_x -= term->cell_width;
|
2021-01-07 11:18:07 +01:00
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
if (unlikely(glyph->is_color_glyph)) {
|
2019-07-31 21:15:40 +02:00
|
|
|
/* Glyph surface is a pre-rendered image (typically a color emoji...) */
|
|
|
|
|
if (!(cell->attrs.blink && term->blink.state == BLINK_OFF)) {
|
2019-08-17 17:36:27 +02:00
|
|
|
pixman_image_composite32(
|
2019-08-16 20:40:32 +02:00
|
|
|
PIXMAN_OP_OVER, glyph->pix, NULL, pix, 0, 0, 0, 0,
|
2023-10-10 14:23:33 +02:00
|
|
|
pen_x + letter_x_ofs + g_x, y + term->font_baseline - g_y,
|
2020-06-04 15:39:19 +02:00
|
|
|
glyph->width, glyph->height);
|
2019-07-31 21:15:40 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
2019-08-17 17:36:27 +02:00
|
|
|
pixman_image_composite32(
|
2020-06-04 15:39:19 +02:00
|
|
|
PIXMAN_OP_OVER, clr_pix, glyph->pix, pix, 0, 0, 0, 0,
|
2023-10-10 14:23:33 +02:00
|
|
|
pen_x + letter_x_ofs + g_x, y + term->font_baseline - g_y,
|
2020-06-04 15:39:19 +02:00
|
|
|
glyph->width, glyph->height);
|
2020-06-03 17:32:57 +02:00
|
|
|
|
2020-08-20 19:25:35 +02:00
|
|
|
/* Combining characters */
|
|
|
|
|
if (composed != NULL) {
|
|
|
|
|
assert(glyph_count == 1);
|
2020-06-04 15:39:19 +02:00
|
|
|
|
osc: wip: kitty text size protocol
This brings initial support for the new kitty text-sizing
protocol. Note hat only the width-parameter ('w') is supported. That
is, no font scaling, and no multi-line cells.
For now, only explicit widths are supported. That is, w=0 does not yet
work.
There are a couple of changes to the renderer, to handle e.g.
OSC 66 ; w=6 ; foobar ST
There are two ways this can get rendered, depending on whether
grapheme shaping has been enabled. We either shape it, and get an
array of glyphs back that we render. Or, we rasterize each codepoint
ourselves, and render each resulting glyph.
The two cases ends up in two different renderer loops, that worked
somewhat different. In particular, the first case has probably never
been tested/used at all...
With this patch, both are changed, and now uses some heuristic to
differentiate between multi-cell text strings (like in the example
above), or single-cell combining characters. The difference is mainly
in which offset to use for the secondary glyphs.
In a multi-cell string, each glyph is mapped to its own cell, while in
the combining case, we try to map all glyphs to the same cell.
2025-01-25 14:09:35 +01:00
|
|
|
for (size_t j = 1; j < composed->count; j++) {
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const struct fcft_glyph *g = fcft_rasterize_char_utf32(
|
osc: wip: kitty text size protocol
This brings initial support for the new kitty text-sizing
protocol. Note hat only the width-parameter ('w') is supported. That
is, no font scaling, and no multi-line cells.
For now, only explicit widths are supported. That is, w=0 does not yet
work.
There are a couple of changes to the renderer, to handle e.g.
OSC 66 ; w=6 ; foobar ST
There are two ways this can get rendered, depending on whether
grapheme shaping has been enabled. We either shape it, and get an
array of glyphs back that we render. Or, we rasterize each codepoint
ourselves, and render each resulting glyph.
The two cases ends up in two different renderer loops, that worked
somewhat different. In particular, the first case has probably never
been tested/used at all...
With this patch, both are changed, and now uses some heuristic to
differentiate between multi-cell text strings (like in the example
above), or single-cell combining characters. The difference is mainly
in which offset to use for the secondary glyphs.
In a multi-cell string, each glyph is mapped to its own cell, while in
the combining case, we try to map all glyphs to the same cell.
2025-01-25 14:09:35 +01:00
|
|
|
font, composed->chars[j], term->font_subpixel);
|
2021-01-04 18:32:55 +01:00
|
|
|
|
2020-08-20 19:25:35 +02:00
|
|
|
if (g == NULL)
|
|
|
|
|
continue;
|
2021-01-04 18:32:55 +01:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Fonts _should_ assume the pen position is now
|
|
|
|
|
* *after* the base glyph, and thus use negative
|
|
|
|
|
* offsets for combining glyphs.
|
|
|
|
|
*
|
|
|
|
|
* Not all fonts behave like this however, and we
|
2021-01-04 19:49:24 +01:00
|
|
|
* try to accommodate both variants.
|
2021-01-04 18:32:55 +01:00
|
|
|
*
|
|
|
|
|
* Since we haven't moved our pen position yet, we
|
|
|
|
|
* add a full cell width to the offset (or two, in
|
|
|
|
|
* case of double-width characters).
|
|
|
|
|
*
|
|
|
|
|
* If the font does *not* use negative offsets,
|
|
|
|
|
* we'd normally use an offset of 0. However, to
|
|
|
|
|
* somewhat deal with double-width glyphs we use
|
|
|
|
|
* an offset of *one* cell.
|
|
|
|
|
*/
|
osc: wip: kitty text size protocol
This brings initial support for the new kitty text-sizing
protocol. Note hat only the width-parameter ('w') is supported. That
is, no font scaling, and no multi-line cells.
For now, only explicit widths are supported. That is, w=0 does not yet
work.
There are a couple of changes to the renderer, to handle e.g.
OSC 66 ; w=6 ; foobar ST
There are two ways this can get rendered, depending on whether
grapheme shaping has been enabled. We either shape it, and get an
array of glyphs back that we render. Or, we rasterize each codepoint
ourselves, and render each resulting glyph.
The two cases ends up in two different renderer loops, that worked
somewhat different. In particular, the first case has probably never
been tested/used at all...
With this patch, both are changed, and now uses some heuristic to
differentiate between multi-cell text strings (like in the example
above), or single-cell combining characters. The difference is mainly
in which offset to use for the secondary glyphs.
In a multi-cell string, each glyph is mapped to its own cell, while in
the combining case, we try to map all glyphs to the same cell.
2025-01-25 14:09:35 +01:00
|
|
|
int x_ofs = cell_cols == 1
|
|
|
|
|
? g->x < 0
|
|
|
|
|
? cell_cols * term->cell_width
|
|
|
|
|
: (cell_cols - 1) * term->cell_width
|
|
|
|
|
: 0;
|
|
|
|
|
|
|
|
|
|
if (cell_cols > 1)
|
|
|
|
|
pen_x += term->cell_width;
|
2020-08-20 19:25:35 +02:00
|
|
|
|
|
|
|
|
pixman_image_composite32(
|
|
|
|
|
PIXMAN_OP_OVER, clr_pix, g->pix, pix, 0, 0, 0, 0,
|
|
|
|
|
/* Some fonts use a negative offset, while others use a
|
|
|
|
|
* "normal" offset */
|
osc: wip: kitty text size protocol
This brings initial support for the new kitty text-sizing
protocol. Note hat only the width-parameter ('w') is supported. That
is, no font scaling, and no multi-line cells.
For now, only explicit widths are supported. That is, w=0 does not yet
work.
There are a couple of changes to the renderer, to handle e.g.
OSC 66 ; w=6 ; foobar ST
There are two ways this can get rendered, depending on whether
grapheme shaping has been enabled. We either shape it, and get an
array of glyphs back that we render. Or, we rasterize each codepoint
ourselves, and render each resulting glyph.
The two cases ends up in two different renderer loops, that worked
somewhat different. In particular, the first case has probably never
been tested/used at all...
With this patch, both are changed, and now uses some heuristic to
differentiate between multi-cell text strings (like in the example
above), or single-cell combining characters. The difference is mainly
in which offset to use for the secondary glyphs.
In a multi-cell string, each glyph is mapped to its own cell, while in
the combining case, we try to map all glyphs to the same cell.
2025-01-25 14:09:35 +01:00
|
|
|
pen_x + letter_x_ofs + x_ofs + g->x,
|
|
|
|
|
y + term->font_baseline - g->y, g->width, g->height);
|
2020-08-20 19:25:35 +02:00
|
|
|
}
|
2021-01-04 18:32:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
2020-08-20 19:25:35 +02:00
|
|
|
|
osc: wip: kitty text size protocol
This brings initial support for the new kitty text-sizing
protocol. Note hat only the width-parameter ('w') is supported. That
is, no font scaling, and no multi-line cells.
For now, only explicit widths are supported. That is, w=0 does not yet
work.
There are a couple of changes to the renderer, to handle e.g.
OSC 66 ; w=6 ; foobar ST
There are two ways this can get rendered, depending on whether
grapheme shaping has been enabled. We either shape it, and get an
array of glyphs back that we render. Or, we rasterize each codepoint
ourselves, and render each resulting glyph.
The two cases ends up in two different renderer loops, that worked
somewhat different. In particular, the first case has probably never
been tested/used at all...
With this patch, both are changed, and now uses some heuristic to
differentiate between multi-cell text strings (like in the example
above), or single-cell combining characters. The difference is mainly
in which offset to use for the secondary glyphs.
In a multi-cell string, each glyph is mapped to its own cell, while in
the combining case, we try to map all glyphs to the same cell.
2025-01-25 14:09:35 +01:00
|
|
|
pen_x += cell_cols > 1 ? term->cell_width : glyph->advance.x;
|
2020-05-01 11:56:13 +02:00
|
|
|
}
|
unicode-combining: store seen combining chains "globally" in the term struct
Instead of storing combining data per cell, realize that most
combinations are re-occurring and that there's lots of available space
left in the unicode range, and store seen base+combining combinations
chains in a per-terminal array.
When we encounter a combining character, we first try to pre-compose,
like before. If that fails, we then search for the current
base+combining combo in the list of previously seen combinations. If
not found there either, we allocate a new combo and add it to the
list. Regardless, the result is an index into this array. We store
this index, offsetted by COMB_CHARS_LO=0x40000000ul in the cell.
When rendering, we need to check if the cell character is a plain
character, or if it's a composed character (identified by checking if
the cell character is >= COMB_CHARS_LO).
Then we render the grapheme pretty much like before.
2020-05-03 11:03:22 +02:00
|
|
|
|
2020-05-02 22:14:48 +02:00
|
|
|
pixman_image_unref(clr_pix);
|
2020-05-01 11:56:13 +02:00
|
|
|
|
2019-07-16 14:20:39 +02:00
|
|
|
/* Underline */
|
2024-06-23 17:39:15 +02:00
|
|
|
if (cell->attrs.underline) {
|
|
|
|
|
pixman_color_t underline_color = fg;
|
2024-07-01 20:00:16 +01:00
|
|
|
enum underline_style underline_style = UNDERLINE_SINGLE;
|
2024-06-23 17:39:15 +02:00
|
|
|
|
|
|
|
|
/* Check if cell has a styled underline. This lookup is fairly
|
|
|
|
|
expensive... */
|
|
|
|
|
if (row->extra != NULL) {
|
2024-07-01 20:00:16 +01:00
|
|
|
for (int i = 0; i < row->extra->underline_ranges.count; i++) {
|
|
|
|
|
const struct row_range *range = &row->extra->underline_ranges.v[i];
|
2024-06-23 17:39:15 +02:00
|
|
|
|
2024-06-24 01:50:52 +02:00
|
|
|
if (range->start > col)
|
|
|
|
|
break;
|
|
|
|
|
|
2024-06-23 17:39:15 +02:00
|
|
|
if (range->start <= col && col <= range->end) {
|
2024-07-01 20:00:16 +01:00
|
|
|
switch (range->underline.color_src) {
|
2024-06-23 17:39:15 +02:00
|
|
|
case COLOR_BASE256:
|
|
|
|
|
underline_color = color_hex_to_pixman(
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
term->colors.table[range->underline.color], gamma_correct);
|
2024-06-23 17:39:15 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COLOR_RGB:
|
|
|
|
|
underline_color =
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
color_hex_to_pixman(range->underline.color, gamma_correct);
|
2024-06-23 17:39:15 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COLOR_DEFAULT:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COLOR_BASE16:
|
|
|
|
|
BUG("underline color can't be base-16");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
underline_style = range->underline.style;
|
2024-06-23 17:39:15 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-23 18:55:37 +02:00
|
|
|
draw_styled_underline(
|
|
|
|
|
term, pix, font, &underline_color, underline_style, x, y, cell_cols);
|
2024-06-23 17:39:15 +02:00
|
|
|
|
|
|
|
|
}
|
2019-07-16 15:08:02 +02:00
|
|
|
|
2020-11-19 19:13:00 +01:00
|
|
|
if (cell->attrs.strikethrough)
|
|
|
|
|
draw_strikeout(term, pix, font, &fg, x, y, cell_cols);
|
2019-08-01 20:09:39 +02:00
|
|
|
|
2021-02-06 11:51:58 +01:00
|
|
|
if (unlikely(cell->attrs.url)) {
|
|
|
|
|
pixman_color_t url_color = color_hex_to_pixman(
|
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-19 09:29:06 +01:00
|
|
|
term->conf->colors_dark.use_custom.url
|
|
|
|
|
? term->conf->colors_dark.url
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
: term->colors.table[3],
|
|
|
|
|
gamma_correct);
|
2021-02-06 11:51:58 +01:00
|
|
|
draw_underline(term, pix, font, &url_color, x, y, cell_cols);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-17 11:47:47 +01:00
|
|
|
draw_cursor:
|
2025-04-14 16:58:23 +02:00
|
|
|
if (has_cursor && (term->cursor_style != CURSOR_BLOCK || !term->kbd_focus)) {
|
|
|
|
|
const pixman_color_t bg_without_alpha = color_hex_to_pixman(_bg, gamma_correct);
|
|
|
|
|
draw_cursor(term, cell, font, pix, &fg, &bg_without_alpha, x, y, cell_cols);
|
|
|
|
|
}
|
2020-03-17 11:47:47 +01:00
|
|
|
|
2020-06-06 14:22:25 +02:00
|
|
|
pixman_image_set_clip_region32(pix, NULL);
|
2019-08-01 20:09:39 +02:00
|
|
|
return cell_cols;
|
2019-07-08 13:57:31 +02:00
|
|
|
}
|
|
|
|
|
|
2021-03-09 17:23:55 +01:00
|
|
|
static void
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
render_row(struct terminal *term, pixman_image_t *pix,
|
|
|
|
|
pixman_region32_t *damage, struct row *row,
|
|
|
|
|
int row_no, int cursor_col)
|
2021-03-09 17:23:55 +01:00
|
|
|
{
|
|
|
|
|
for (int col = term->cols - 1; col >= 0; col--)
|
2023-10-07 16:23:09 +02:00
|
|
|
render_cell(term, pix, damage, row, row_no, col, cursor_col == col);
|
2021-03-09 17:23:55 +01:00
|
|
|
}
|
|
|
|
|
|
2020-10-08 19:55:32 +02:00
|
|
|
static void
|
|
|
|
|
render_urgency(struct terminal *term, struct buffer *buf)
|
|
|
|
|
{
|
|
|
|
|
uint32_t red = term->colors.table[1];
|
2025-05-01 08:34:49 +02:00
|
|
|
pixman_color_t bg = color_hex_to_pixman(
|
|
|
|
|
red, wayl_do_linear_blending(term->wl, term->conf));
|
2020-10-08 19:55:32 +02:00
|
|
|
|
|
|
|
|
int width = min(min(term->margins.left, term->margins.right),
|
|
|
|
|
min(term->margins.top, term->margins.bottom));
|
|
|
|
|
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &bg, 4,
|
|
|
|
|
(pixman_rectangle16_t[]){
|
|
|
|
|
/* Top */
|
|
|
|
|
{0, 0, term->width, width},
|
|
|
|
|
|
|
|
|
|
/* Bottom */
|
|
|
|
|
{0, term->height - width, term->width, width},
|
|
|
|
|
|
|
|
|
|
/* Left */
|
|
|
|
|
{0, width, width, term->height - 2 * width},
|
|
|
|
|
|
|
|
|
|
/* Right */
|
|
|
|
|
{term->width - width, width, width, term->height - 2 * width},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-22 20:22:17 +01:00
|
|
|
static void
|
2020-07-13 14:03:58 +02:00
|
|
|
render_margin(struct terminal *term, struct buffer *buf,
|
2020-07-13 14:19:07 +02:00
|
|
|
int start_line, int end_line, bool apply_damage)
|
2020-03-22 20:22:17 +01:00
|
|
|
{
|
|
|
|
|
/* Fill area outside the cell grid with the default background color */
|
2020-03-23 20:14:30 +01:00
|
|
|
const int rmargin = term->width - term->margins.right;
|
|
|
|
|
const int bmargin = term->height - term->margins.bottom;
|
|
|
|
|
const int line_count = end_line - start_line;
|
2020-03-22 20:22:17 +01:00
|
|
|
|
2025-05-01 08:34:49 +02:00
|
|
|
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
|
2023-10-07 07:58:55 +02:00
|
|
|
const uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
|
|
|
|
|
uint16_t alpha = term->colors.alpha;
|
|
|
|
|
|
|
|
|
|
if (term->window->is_fullscreen) {
|
|
|
|
|
/* Disable alpha in fullscreen - see render_cell() for details */
|
|
|
|
|
alpha = 0xffff;
|
|
|
|
|
}
|
|
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct);
|
2020-10-08 19:53:11 +02:00
|
|
|
|
2020-03-22 20:22:17 +01:00
|
|
|
pixman_image_fill_rectangles(
|
2020-07-13 14:03:58 +02:00
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &bg, 4,
|
2020-03-22 20:22:17 +01:00
|
|
|
(pixman_rectangle16_t[]){
|
2020-07-13 14:03:58 +02:00
|
|
|
/* Top */
|
|
|
|
|
{0, 0, term->width, term->margins.top},
|
|
|
|
|
|
|
|
|
|
/* Bottom */
|
|
|
|
|
{0, bmargin, term->width, term->margins.bottom},
|
|
|
|
|
|
2020-03-23 20:12:19 +01:00
|
|
|
/* Left */
|
|
|
|
|
{0, term->margins.top + start_line * term->cell_height,
|
|
|
|
|
term->margins.left, line_count * term->cell_height},
|
2020-03-22 20:22:17 +01:00
|
|
|
|
2020-03-23 20:12:19 +01:00
|
|
|
/* Right */
|
|
|
|
|
{rmargin, term->margins.top + start_line * term->cell_height,
|
|
|
|
|
term->margins.right, line_count * term->cell_height},
|
|
|
|
|
});
|
|
|
|
|
|
2020-10-08 19:55:32 +02:00
|
|
|
if (term->render.urgency)
|
|
|
|
|
render_urgency(term, buf);
|
|
|
|
|
|
2021-05-10 17:56:12 +02:00
|
|
|
/* Ensure the updated regions are copied to the next frame's
|
|
|
|
|
* buffer when we're double buffering */
|
|
|
|
|
pixman_region32_union_rect(
|
2023-10-07 16:23:09 +02:00
|
|
|
&buf->dirty[0], &buf->dirty[0], 0, 0, term->width, term->margins.top);
|
2021-05-10 17:56:12 +02:00
|
|
|
pixman_region32_union_rect(
|
2023-10-07 16:23:09 +02:00
|
|
|
&buf->dirty[0], &buf->dirty[0], 0, bmargin, term->width, term->margins.bottom);
|
2021-05-10 17:56:12 +02:00
|
|
|
pixman_region32_union_rect(
|
2023-10-07 16:23:09 +02:00
|
|
|
&buf->dirty[0], &buf->dirty[0], 0, 0, term->margins.left, term->height);
|
2021-05-10 17:56:12 +02:00
|
|
|
pixman_region32_union_rect(
|
2023-10-07 16:23:09 +02:00
|
|
|
&buf->dirty[0], &buf->dirty[0],
|
2021-05-10 17:56:12 +02:00
|
|
|
rmargin, 0, term->margins.right, term->height);
|
|
|
|
|
|
2020-07-13 14:19:07 +02:00
|
|
|
if (apply_damage) {
|
|
|
|
|
/* Top */
|
2020-07-13 14:06:02 +02:00
|
|
|
wl_surface_damage_buffer(
|
2023-06-26 16:10:40 +02:00
|
|
|
term->window->surface.surf, 0, 0, term->width, term->margins.top);
|
2020-07-13 14:06:02 +02:00
|
|
|
|
2020-07-13 14:19:07 +02:00
|
|
|
/* Bottom */
|
2020-07-13 14:06:02 +02:00
|
|
|
wl_surface_damage_buffer(
|
2023-06-26 16:10:40 +02:00
|
|
|
term->window->surface.surf, 0, bmargin, term->width, term->margins.bottom);
|
2020-07-13 14:06:02 +02:00
|
|
|
|
2020-07-13 14:19:07 +02:00
|
|
|
/* Left */
|
2020-07-13 14:06:02 +02:00
|
|
|
wl_surface_damage_buffer(
|
2023-06-26 16:10:40 +02:00
|
|
|
term->window->surface.surf,
|
2020-07-13 14:06:02 +02:00
|
|
|
0, term->margins.top + start_line * term->cell_height,
|
|
|
|
|
term->margins.left, line_count * term->cell_height);
|
|
|
|
|
|
2020-07-13 14:19:07 +02:00
|
|
|
/* Right */
|
2020-07-13 14:06:02 +02:00
|
|
|
wl_surface_damage_buffer(
|
2023-06-26 16:10:40 +02:00
|
|
|
term->window->surface.surf,
|
2020-07-13 14:06:02 +02:00
|
|
|
rmargin, term->margins.top + start_line * term->cell_height,
|
|
|
|
|
term->margins.right, line_count * term->cell_height);
|
2020-07-13 14:19:07 +02:00
|
|
|
}
|
2020-03-22 20:22:17 +01:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
static void
|
|
|
|
|
grid_render_scroll(struct terminal *term, struct buffer *buf,
|
|
|
|
|
const struct damage *dmg)
|
|
|
|
|
{
|
2020-03-22 20:22:17 +01:00
|
|
|
LOG_DBG(
|
|
|
|
|
"damage: SCROLL: %d-%d by %d lines",
|
2020-04-26 12:47:19 +02:00
|
|
|
dmg->region.start, dmg->region.end, dmg->lines);
|
2020-03-22 20:22:17 +01:00
|
|
|
|
render: protect against integer underflow when calculating scroll area
When applying scroll damage, we calculate the affected region’s
height (in pixels), by subtracting the number of rows to scroll, from
the scrolling region, and finally multiply by the cell height.
If the number of rows to scroll is very large, the subtraction may
underflow, resulting in a very large height value instead of a
negative one.
This caused the check for "scrolling too many lines" to fail. That in
turn resulted in an integer overflow when calculating the source
offset into the rendered surface buffer, which typically triggered a
segfault.
This bug happened when there was continuous output in the terminal
without any new frames being rendered. This caused a buildup of scroll
damage, that triggered the underflow+overflow when we finally did
render a new frame.
For example, a compositor that doesn’t send any frame callbacks (for
example because the terminal window is minimized, or on a different
workspace/tag) would cause this.
Closes #1305
2023-03-28 18:31:24 +02:00
|
|
|
const int region_size = dmg->region.end - dmg->region.start;
|
|
|
|
|
|
|
|
|
|
if (dmg->lines >= region_size) {
|
|
|
|
|
/* The entire scroll region will be scrolled out (i.e. replaced) */
|
2020-03-22 20:22:17 +01:00
|
|
|
return;
|
render: protect against integer underflow when calculating scroll area
When applying scroll damage, we calculate the affected region’s
height (in pixels), by subtracting the number of rows to scroll, from
the scrolling region, and finally multiply by the cell height.
If the number of rows to scroll is very large, the subtraction may
underflow, resulting in a very large height value instead of a
negative one.
This caused the check for "scrolling too many lines" to fail. That in
turn resulted in an integer overflow when calculating the source
offset into the rendered surface buffer, which typically triggered a
segfault.
This bug happened when there was continuous output in the terminal
without any new frames being rendered. This caused a buildup of scroll
damage, that triggered the underflow+overflow when we finally did
render a new frame.
For example, a compositor that doesn’t send any frame callbacks (for
example because the terminal window is minimized, or on a different
workspace/tag) would cause this.
Closes #1305
2023-03-28 18:31:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int height = (region_size - dmg->lines) * term->cell_height;
|
|
|
|
|
xassert(height > 0);
|
2020-03-22 20:22:17 +01:00
|
|
|
|
|
|
|
|
#if TIME_SCROLL_DAMAGE
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec start_time;
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
2020-03-22 20:22:17 +01:00
|
|
|
#endif
|
|
|
|
|
|
2020-04-26 12:47:19 +02:00
|
|
|
int dst_y = term->margins.top + (dmg->region.start + 0) * term->cell_height;
|
|
|
|
|
int src_y = term->margins.top + (dmg->region.start + dmg->lines) * term->cell_height;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2020-03-22 20:22:17 +01:00
|
|
|
/*
|
|
|
|
|
* SHM scrolling can be *much* faster, but it depends on how many
|
|
|
|
|
* lines we're scrolling, and how much repairing we need to do.
|
|
|
|
|
*
|
|
|
|
|
* In short, scrolling a *large* number of rows is faster with a
|
|
|
|
|
* memmove, while scrolling a *small* number of lines is faster
|
|
|
|
|
* with SHM scrolling.
|
|
|
|
|
*
|
2020-03-23 20:45:27 +01:00
|
|
|
* However, since we need to restore the scrolling regions when
|
|
|
|
|
* SHM scrolling, we also need to take this into account.
|
2020-03-22 20:22:17 +01:00
|
|
|
*
|
2020-03-23 20:45:27 +01:00
|
|
|
* Finally, we also have to restore the window margins, and this
|
|
|
|
|
* is a *huge* performance hit when scrolling a large number of
|
|
|
|
|
* lines (in addition to the sloweness of SHM scrolling as
|
|
|
|
|
* method).
|
2020-03-22 20:22:17 +01:00
|
|
|
*
|
|
|
|
|
* So, we need to figure out when to SHM scroll, and when to
|
|
|
|
|
* memmove.
|
|
|
|
|
*
|
2020-08-15 19:39:00 +01:00
|
|
|
* For now, assume that the both methods perform roughly the same,
|
2020-03-22 20:22:17 +01:00
|
|
|
* given an equal number of bytes to move/allocate, and use the
|
|
|
|
|
* method that results in the least amount of bytes to touch.
|
|
|
|
|
*
|
|
|
|
|
* Since number of lines directly translates to bytes, we can
|
|
|
|
|
* simply count lines.
|
|
|
|
|
*
|
|
|
|
|
* SHM scrolling needs to first "move" (punch hole + allocate)
|
2020-04-26 12:47:19 +02:00
|
|
|
* dmg->lines number of lines, and then we need to restore
|
2020-03-22 20:22:17 +01:00
|
|
|
* the bottom scroll region.
|
|
|
|
|
*
|
|
|
|
|
* If the total number of lines is less than half the screen - use
|
|
|
|
|
* SHM. Otherwise use memmove.
|
|
|
|
|
*/
|
|
|
|
|
bool try_shm_scroll =
|
2020-03-25 18:26:58 +01:00
|
|
|
shm_can_scroll(buf) && (
|
2020-04-26 12:47:19 +02:00
|
|
|
dmg->lines +
|
|
|
|
|
dmg->region.start +
|
|
|
|
|
(term->rows - dmg->region.end)) < term->rows / 2;
|
2020-03-22 20:22:17 +01:00
|
|
|
|
|
|
|
|
bool did_shm_scroll = false;
|
|
|
|
|
|
|
|
|
|
//try_shm_scroll = false;
|
|
|
|
|
//try_shm_scroll = true;
|
|
|
|
|
|
|
|
|
|
if (try_shm_scroll) {
|
|
|
|
|
did_shm_scroll = shm_scroll(
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
buf, dmg->lines * term->cell_height,
|
2020-04-26 12:47:19 +02:00
|
|
|
term->margins.top, dmg->region.start * term->cell_height,
|
|
|
|
|
term->margins.bottom, (term->rows - dmg->region.end) * term->cell_height);
|
2020-03-22 20:22:17 +01:00
|
|
|
}
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2020-03-22 20:22:17 +01:00
|
|
|
if (did_shm_scroll) {
|
|
|
|
|
/* Restore margins */
|
|
|
|
|
render_margin(
|
2020-07-13 14:19:07 +02:00
|
|
|
term, buf, dmg->region.end - dmg->lines, term->rows, false);
|
2020-03-23 20:45:27 +01:00
|
|
|
} else {
|
|
|
|
|
/* Fallback for when we either cannot do SHM scrolling, or it failed */
|
2021-07-15 22:18:09 +02:00
|
|
|
uint8_t *raw = buf->data;
|
2019-08-16 20:40:32 +02:00
|
|
|
memmove(raw + dst_y * buf->stride,
|
|
|
|
|
raw + src_y * buf->stride,
|
|
|
|
|
height * buf->stride);
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
2020-03-22 20:22:17 +01:00
|
|
|
|
|
|
|
|
#if TIME_SCROLL_DAMAGE
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec end_time;
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
2020-03-22 20:22:17 +01:00
|
|
|
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec memmove_time;
|
|
|
|
|
timespec_sub(&end_time, &start_time, &memmove_time);
|
|
|
|
|
LOG_INFO("scrolled %dKB (%d lines) using %s in %lds %ldns",
|
2020-04-26 12:47:19 +02:00
|
|
|
height * buf->stride / 1024, dmg->lines,
|
2020-03-22 20:22:17 +01:00
|
|
|
did_shm_scroll ? "SHM" : try_shm_scroll ? "memmove (SHM failed)" : "memmove",
|
2022-01-15 14:56:13 +05:30
|
|
|
(long)memmove_time.tv_sec, memmove_time.tv_nsec);
|
2020-03-22 20:22:17 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
2023-06-26 16:10:40 +02:00
|
|
|
term->window->surface.surf, term->margins.left, dst_y,
|
2020-03-22 20:22:17 +01:00
|
|
|
term->width - term->margins.left - term->margins.right, height);
|
2022-09-22 18:34:41 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* TODO: remove this if re-enabling scroll damage when re-applying
|
2024-02-06 12:36:45 +01:00
|
|
|
* last frame's damage (see reapply_old_damage()
|
2022-09-22 18:34:41 +02:00
|
|
|
*/
|
|
|
|
|
pixman_region32_union_rect(
|
2023-10-07 16:23:09 +02:00
|
|
|
&buf->dirty[0], &buf->dirty[0], 0, dst_y, buf->width, height);
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
grid_render_scroll_reverse(struct terminal *term, struct buffer *buf,
|
|
|
|
|
const struct damage *dmg)
|
|
|
|
|
{
|
2020-03-23 21:14:51 +01:00
|
|
|
LOG_DBG(
|
csi: implement CSI Ps ; Ps ; Ps t reporting escape sequences
A lot of the escape sequences on the "CSI Ps ; Ps ; Ps t" form are
expected to return a reply. Thus, not having these implemented means
we will hang if the client sends these escapes.
Not all of these escapes can be meaningfully implemented on Wayland,
and thus this implementation is best effort.
We now support the following escapes:
* 11t - report if window is iconified (always replies "no")
* 13t - report window position (always replies 0,0)
* 13;2t - report text area position (replies with margins, since we
cannot get the window's position)
* 14t - report text area size, in pixels
* 14;2t - report window size, in pixels
* 15t - report screen size, in pixels
* 16t - report cell size, in pixels
* 18t - report text area size, in cells
* 19t - report screen size, in cells
2020-04-17 23:03:20 +02:00
|
|
|
"damage: SCROLL REVERSE: %d-%d by %d lines",
|
2020-04-26 12:47:19 +02:00
|
|
|
dmg->region.start, dmg->region.end, dmg->lines);
|
2020-03-23 21:14:51 +01:00
|
|
|
|
render: protect against integer underflow when calculating scroll area
When applying scroll damage, we calculate the affected region’s
height (in pixels), by subtracting the number of rows to scroll, from
the scrolling region, and finally multiply by the cell height.
If the number of rows to scroll is very large, the subtraction may
underflow, resulting in a very large height value instead of a
negative one.
This caused the check for "scrolling too many lines" to fail. That in
turn resulted in an integer overflow when calculating the source
offset into the rendered surface buffer, which typically triggered a
segfault.
This bug happened when there was continuous output in the terminal
without any new frames being rendered. This caused a buildup of scroll
damage, that triggered the underflow+overflow when we finally did
render a new frame.
For example, a compositor that doesn’t send any frame callbacks (for
example because the terminal window is minimized, or on a different
workspace/tag) would cause this.
Closes #1305
2023-03-28 18:31:24 +02:00
|
|
|
const int region_size = dmg->region.end - dmg->region.start;
|
|
|
|
|
|
|
|
|
|
if (dmg->lines >= region_size) {
|
|
|
|
|
/* The entire scroll region will be scrolled out (i.e. replaced) */
|
2020-03-23 21:14:51 +01:00
|
|
|
return;
|
render: protect against integer underflow when calculating scroll area
When applying scroll damage, we calculate the affected region’s
height (in pixels), by subtracting the number of rows to scroll, from
the scrolling region, and finally multiply by the cell height.
If the number of rows to scroll is very large, the subtraction may
underflow, resulting in a very large height value instead of a
negative one.
This caused the check for "scrolling too many lines" to fail. That in
turn resulted in an integer overflow when calculating the source
offset into the rendered surface buffer, which typically triggered a
segfault.
This bug happened when there was continuous output in the terminal
without any new frames being rendered. This caused a buildup of scroll
damage, that triggered the underflow+overflow when we finally did
render a new frame.
For example, a compositor that doesn’t send any frame callbacks (for
example because the terminal window is minimized, or on a different
workspace/tag) would cause this.
Closes #1305
2023-03-28 18:31:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int height = (region_size - dmg->lines) * term->cell_height;
|
|
|
|
|
xassert(height > 0);
|
2020-03-23 21:14:51 +01:00
|
|
|
|
|
|
|
|
#if TIME_SCROLL_DAMAGE
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec start_time;
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
2020-03-23 21:14:51 +01:00
|
|
|
#endif
|
|
|
|
|
|
2020-04-26 12:47:19 +02:00
|
|
|
int src_y = term->margins.top + (dmg->region.start + 0) * term->cell_height;
|
|
|
|
|
int dst_y = term->margins.top + (dmg->region.start + dmg->lines) * term->cell_height;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2020-03-23 21:14:51 +01:00
|
|
|
bool try_shm_scroll =
|
2020-03-25 18:26:58 +01:00
|
|
|
shm_can_scroll(buf) && (
|
2020-04-26 12:47:19 +02:00
|
|
|
dmg->lines +
|
|
|
|
|
dmg->region.start +
|
|
|
|
|
(term->rows - dmg->region.end)) < term->rows / 2;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2020-03-23 21:14:51 +01:00
|
|
|
bool did_shm_scroll = false;
|
|
|
|
|
|
|
|
|
|
if (try_shm_scroll) {
|
|
|
|
|
did_shm_scroll = shm_scroll(
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
buf, -dmg->lines * term->cell_height,
|
2020-04-26 12:47:19 +02:00
|
|
|
term->margins.top, dmg->region.start * term->cell_height,
|
|
|
|
|
term->margins.bottom, (term->rows - dmg->region.end) * term->cell_height);
|
2020-03-23 21:14:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (did_shm_scroll) {
|
|
|
|
|
/* Restore margins */
|
|
|
|
|
render_margin(
|
2020-07-13 14:19:07 +02:00
|
|
|
term, buf, dmg->region.start, dmg->region.start + dmg->lines, false);
|
2020-03-23 21:14:51 +01:00
|
|
|
} else {
|
|
|
|
|
/* Fallback for when we either cannot do SHM scrolling, or it failed */
|
2021-07-15 22:18:09 +02:00
|
|
|
uint8_t *raw = buf->data;
|
2019-08-16 20:40:32 +02:00
|
|
|
memmove(raw + dst_y * buf->stride,
|
|
|
|
|
raw + src_y * buf->stride,
|
|
|
|
|
height * buf->stride);
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
2020-03-23 21:14:51 +01:00
|
|
|
|
|
|
|
|
#if TIME_SCROLL_DAMAGE
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec end_time;
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
2020-03-23 21:14:51 +01:00
|
|
|
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec memmove_time;
|
|
|
|
|
timespec_sub(&end_time, &start_time, &memmove_time);
|
|
|
|
|
LOG_INFO("scrolled REVERSE %dKB (%d lines) using %s in %lds %ldns",
|
2020-04-26 12:47:19 +02:00
|
|
|
height * buf->stride / 1024, dmg->lines,
|
2020-03-23 21:14:51 +01:00
|
|
|
did_shm_scroll ? "SHM" : try_shm_scroll ? "memmove (SHM failed)" : "memmove",
|
2022-01-15 14:56:13 +05:30
|
|
|
(long)memmove_time.tv_sec, memmove_time.tv_nsec);
|
2020-03-23 21:14:51 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
2023-06-26 16:10:40 +02:00
|
|
|
term->window->surface.surf, term->margins.left, dst_y,
|
2020-03-23 21:14:51 +01:00
|
|
|
term->width - term->margins.left - term->margins.right, height);
|
2022-09-22 18:34:41 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* TODO: remove this if re-enabling scroll damage when re-applying
|
2024-02-06 12:36:45 +01:00
|
|
|
* last frame's damage (see reapply_old_damage()
|
2022-09-22 18:34:41 +02:00
|
|
|
*/
|
|
|
|
|
pixman_region32_union_rect(
|
2023-10-07 16:23:09 +02:00
|
|
|
&buf->dirty[0], &buf->dirty[0], 0, dst_y, buf->width, height);
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-21 23:48:45 +01:00
|
|
|
static void
|
2024-10-05 08:53:11 +02:00
|
|
|
render_sixel_chunk(struct terminal *term, pixman_image_t *pix,
|
|
|
|
|
pixman_region32_t *damage, const struct sixel *sixel,
|
2020-11-13 16:54:40 +01:00
|
|
|
int term_start_row, int img_start_row, int count)
|
2020-02-21 23:48:45 +01:00
|
|
|
{
|
2020-02-22 00:05:25 +01:00
|
|
|
/* Translate row/column to x/y pixel values */
|
2020-02-24 18:38:11 +01:00
|
|
|
const int x = term->margins.left + sixel->pos.col * term->cell_width;
|
2020-11-13 16:54:40 +01:00
|
|
|
const int y = term->margins.top + term_start_row * term->cell_height;
|
2020-02-21 23:48:45 +01:00
|
|
|
|
2020-02-22 00:05:25 +01:00
|
|
|
/* Width/height, in pixels - and don't touch the window margins */
|
2020-11-13 16:54:40 +01:00
|
|
|
const int width = max(
|
|
|
|
|
0,
|
|
|
|
|
min(sixel->width,
|
|
|
|
|
term->width - x - term->margins.right));
|
|
|
|
|
const int height = max(
|
|
|
|
|
0,
|
|
|
|
|
min(
|
|
|
|
|
min(count * term->cell_height, /* 'count' number of rows */
|
|
|
|
|
sixel->height - img_start_row * term->cell_height), /* What remains of the sixel */
|
|
|
|
|
term->height - y - term->margins.bottom));
|
2020-02-21 23:48:45 +01:00
|
|
|
|
2020-02-22 00:05:25 +01:00
|
|
|
/* Verify we're not stepping outside the grid */
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(x >= term->margins.left);
|
|
|
|
|
xassert(y >= term->margins.top);
|
|
|
|
|
xassert(width == 0 || x + width <= term->width - term->margins.right);
|
|
|
|
|
xassert(height == 0 || y + height <= term->height - term->margins.bottom);
|
2020-02-21 23:48:45 +01:00
|
|
|
|
2020-11-13 16:54:40 +01:00
|
|
|
//LOG_DBG("sixel chunk: %dx%d %dx%d", x, y, width, height);
|
|
|
|
|
|
2020-06-06 14:22:54 +02:00
|
|
|
pixman_image_composite32(
|
2021-04-14 11:09:02 +02:00
|
|
|
sixel->opaque ? PIXMAN_OP_SRC : PIXMAN_OP_OVER,
|
2020-02-21 23:48:45 +01:00
|
|
|
sixel->pix,
|
|
|
|
|
NULL,
|
|
|
|
|
pix,
|
2020-11-13 16:54:40 +01:00
|
|
|
0, img_start_row * term->cell_height,
|
2020-02-21 23:48:45 +01:00
|
|
|
0, 0,
|
|
|
|
|
x, y,
|
|
|
|
|
width, height);
|
|
|
|
|
|
2024-10-05 08:53:11 +02:00
|
|
|
if (damage != NULL)
|
|
|
|
|
pixman_region32_union_rect(damage, damage, x, y, width, height);
|
2020-02-21 23:48:45 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-13 16:54:40 +01:00
|
|
|
static void
|
|
|
|
|
render_sixel(struct terminal *term, pixman_image_t *pix,
|
2023-10-12 16:24:15 +02:00
|
|
|
pixman_region32_t *damage, const struct coord *cursor,
|
|
|
|
|
const struct sixel *sixel)
|
2020-11-13 16:54:40 +01:00
|
|
|
{
|
2023-06-29 14:49:54 +02:00
|
|
|
xassert(sixel->pix != NULL);
|
|
|
|
|
xassert(sixel->width >= 0);
|
|
|
|
|
xassert(sixel->height >= 0);
|
|
|
|
|
|
2020-11-13 16:54:40 +01:00
|
|
|
const int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1);
|
|
|
|
|
const bool last_row_needs_erase = sixel->height % term->cell_height != 0;
|
|
|
|
|
const bool last_col_needs_erase = sixel->width % term->cell_width != 0;
|
|
|
|
|
|
|
|
|
|
int chunk_img_start = -1; /* Image-relative start row of chunk */
|
|
|
|
|
int chunk_term_start = -1; /* Viewport relative start row of chunk */
|
|
|
|
|
int chunk_row_count = 0; /* Number of rows to emit */
|
|
|
|
|
|
|
|
|
|
#define maybe_emit_sixel_chunk_then_reset() \
|
|
|
|
|
if (chunk_row_count != 0) { \
|
|
|
|
|
render_sixel_chunk( \
|
2024-10-05 08:53:11 +02:00
|
|
|
term, pix, damage, sixel, \
|
2020-11-13 16:54:40 +01:00
|
|
|
chunk_term_start, chunk_img_start, chunk_row_count); \
|
|
|
|
|
chunk_term_start = chunk_img_start = -1; \
|
|
|
|
|
chunk_row_count = 0; \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Iterate all sixel rows:
|
|
|
|
|
*
|
|
|
|
|
* - ignore rows that aren't visible on-screen
|
|
|
|
|
* - ignore rows that aren't dirty (they have already been rendered)
|
|
|
|
|
* - chunk consecutive dirty rows into a 'chunk'
|
|
|
|
|
* - emit (render) chunk as soon as a row isn't visible, or is clean
|
|
|
|
|
* - emit final chunk after we've iterated all rows
|
|
|
|
|
*
|
|
|
|
|
* The purpose of this is to reduce the amount of pixels that
|
|
|
|
|
* needs to be composited and marked as damaged for the
|
|
|
|
|
* compositor.
|
|
|
|
|
*
|
|
|
|
|
* Since we do CPU based composition, rendering is a slow and
|
|
|
|
|
* heavy task for foot, and thus it is important to not re-render
|
|
|
|
|
* things unnecessarily.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
for (int _abs_row_no = sixel->pos.row;
|
|
|
|
|
_abs_row_no < sixel->pos.row + sixel->rows;
|
|
|
|
|
_abs_row_no++)
|
|
|
|
|
{
|
|
|
|
|
const int abs_row_no = _abs_row_no & (term->grid->num_rows - 1);
|
|
|
|
|
const int term_row_no =
|
|
|
|
|
(abs_row_no - term->grid->view + term->grid->num_rows) &
|
|
|
|
|
(term->grid->num_rows - 1);
|
|
|
|
|
|
|
|
|
|
/* Check if row is in the visible viewport */
|
|
|
|
|
if (view_end >= term->grid->view) {
|
|
|
|
|
/* Not wrapped */
|
|
|
|
|
if (!(abs_row_no >= term->grid->view && abs_row_no <= view_end)) {
|
|
|
|
|
/* Not visible */
|
|
|
|
|
maybe_emit_sixel_chunk_then_reset();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* Wrapped */
|
|
|
|
|
if (!(abs_row_no >= term->grid->view || abs_row_no <= view_end)) {
|
|
|
|
|
/* Not visible */
|
|
|
|
|
maybe_emit_sixel_chunk_then_reset();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Is the row dirty? */
|
|
|
|
|
struct row *row = term->grid->rows[abs_row_no];
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(row != NULL); /* Should be visible */
|
2020-11-13 16:54:40 +01:00
|
|
|
|
|
|
|
|
if (!row->dirty) {
|
|
|
|
|
maybe_emit_sixel_chunk_then_reset();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-09 17:32:04 +01:00
|
|
|
int cursor_col = cursor->row == term_row_no ? cursor->col : -1;
|
|
|
|
|
|
2020-11-13 16:54:40 +01:00
|
|
|
/*
|
2021-03-09 17:23:55 +01:00
|
|
|
* If image contains transparent parts, render all (dirty)
|
|
|
|
|
* cells beneath it.
|
|
|
|
|
*
|
|
|
|
|
* If image is opaque, loop cells and set their 'clean' bit,
|
|
|
|
|
* to prevent the grid rendered from overwriting the sixel
|
2020-11-13 16:54:40 +01:00
|
|
|
*
|
|
|
|
|
* If the last sixel row only partially covers the cell row,
|
|
|
|
|
* 'erase' the cell by rendering them.
|
2021-03-09 17:23:55 +01:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* In all cases, do *not* clear the 'dirty' bit on the row, to
|
2021-03-09 17:23:55 +01:00
|
|
|
* ensure the regular renderer includes them in the damage
|
|
|
|
|
* rect.
|
2020-11-13 16:54:40 +01:00
|
|
|
*/
|
2021-03-09 17:23:55 +01:00
|
|
|
if (!sixel->opaque) {
|
|
|
|
|
/* TODO: multithreading */
|
2023-10-12 16:24:15 +02:00
|
|
|
render_row(term, pix, damage, row, term_row_no, cursor_col);
|
2021-03-09 17:23:55 +01:00
|
|
|
} else {
|
|
|
|
|
for (int col = sixel->pos.col;
|
|
|
|
|
col < min(sixel->pos.col + sixel->cols, term->cols);
|
|
|
|
|
col++)
|
|
|
|
|
{
|
|
|
|
|
struct cell *cell = &row->cells[col];
|
|
|
|
|
|
|
|
|
|
if (!cell->attrs.clean) {
|
|
|
|
|
bool last_row = abs_row_no == sixel->pos.row + sixel->rows - 1;
|
|
|
|
|
bool last_col = col == sixel->pos.col + sixel->cols - 1;
|
|
|
|
|
|
|
|
|
|
if ((last_row_needs_erase && last_row) ||
|
|
|
|
|
(last_col_needs_erase && last_col))
|
|
|
|
|
{
|
2023-10-13 18:44:44 +02:00
|
|
|
render_cell(term, pix, damage, row, term_row_no, col, cursor_col == col);
|
2021-06-15 11:45:27 +02:00
|
|
|
} else {
|
2021-03-09 17:23:55 +01:00
|
|
|
cell->attrs.clean = 1;
|
2021-06-15 11:45:27 +02:00
|
|
|
cell->attrs.confined = 1;
|
|
|
|
|
}
|
2021-03-09 17:23:55 +01:00
|
|
|
}
|
2020-11-13 16:54:40 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (chunk_term_start == -1) {
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(chunk_img_start == -1);
|
2020-11-13 16:54:40 +01:00
|
|
|
chunk_term_start = term_row_no;
|
|
|
|
|
chunk_img_start = _abs_row_no - sixel->pos.row;
|
|
|
|
|
chunk_row_count = 1;
|
|
|
|
|
} else
|
|
|
|
|
chunk_row_count++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
maybe_emit_sixel_chunk_then_reset();
|
|
|
|
|
#undef maybe_emit_sixel_chunk_then_reset
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 23:48:45 +01:00
|
|
|
static void
|
2021-03-09 17:23:55 +01:00
|
|
|
render_sixel_images(struct terminal *term, pixman_image_t *pix,
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_region32_t *damage, const struct coord *cursor)
|
2020-02-21 23:48:45 +01:00
|
|
|
{
|
2020-06-28 14:19:43 +02:00
|
|
|
if (likely(tll_length(term->grid->sixel_images)) == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const int scrollback_end
|
|
|
|
|
= (term->grid->offset + term->rows) & (term->grid->num_rows - 1);
|
|
|
|
|
|
|
|
|
|
const int view_start
|
|
|
|
|
= (term->grid->view
|
|
|
|
|
- scrollback_end
|
|
|
|
|
+ term->grid->num_rows) & (term->grid->num_rows - 1);
|
2020-06-28 10:45:30 +02:00
|
|
|
|
2020-06-28 14:19:43 +02:00
|
|
|
const int view_end = view_start + term->rows - 1;
|
|
|
|
|
|
|
|
|
|
//LOG_DBG("SIXELS: %zu images, view=%d-%d",
|
|
|
|
|
// tll_length(term->grid->sixel_images), view_start, view_end);
|
2020-06-28 10:45:30 +02:00
|
|
|
|
|
|
|
|
tll_foreach(term->grid->sixel_images, it) {
|
|
|
|
|
const struct sixel *six = &it->item;
|
2020-06-28 14:19:43 +02:00
|
|
|
const int start
|
|
|
|
|
= (six->pos.row
|
|
|
|
|
- scrollback_end
|
|
|
|
|
+ term->grid->num_rows) & (term->grid->num_rows - 1);
|
2020-06-28 10:45:30 +02:00
|
|
|
const int end = start + six->rows - 1;
|
|
|
|
|
|
|
|
|
|
//LOG_DBG(" sixel: %d-%d", start, end);
|
|
|
|
|
if (start > view_end) {
|
|
|
|
|
/* Sixel starts after view ends, no need to try to render it */
|
|
|
|
|
continue;
|
|
|
|
|
} else if (end < view_start) {
|
|
|
|
|
/* Image ends before view starts. Since the image list is
|
|
|
|
|
* sorted, we can safely stop here */
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-29 14:49:54 +02:00
|
|
|
sixel_sync_cache(term, &it->item);
|
2023-10-12 16:24:15 +02:00
|
|
|
render_sixel(term, pix, damage, cursor, &it->item);
|
2020-06-28 10:45:30 +02:00
|
|
|
}
|
2020-02-21 23:48:45 +01:00
|
|
|
}
|
|
|
|
|
|
2021-03-23 13:03:07 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2020-12-02 18:52:50 +01:00
|
|
|
static void
|
2021-03-23 13:03:07 +01:00
|
|
|
render_ime_preedit_for_seat(struct terminal *term, struct seat *seat,
|
|
|
|
|
struct buffer *buf)
|
2020-12-02 18:52:50 +01:00
|
|
|
{
|
2021-03-23 13:03:07 +01:00
|
|
|
if (likely(seat->ime.preedit.cells == NULL))
|
2020-12-02 18:52:50 +01:00
|
|
|
return;
|
|
|
|
|
|
2020-12-05 11:42:21 +01:00
|
|
|
if (unlikely(term->is_searching))
|
|
|
|
|
return;
|
|
|
|
|
|
2025-05-01 08:34:49 +02:00
|
|
|
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
|
2020-12-03 18:38:26 +01:00
|
|
|
/* Adjust cursor position to viewport */
|
|
|
|
|
struct coord cursor;
|
|
|
|
|
cursor = term->grid->cursor.point;
|
|
|
|
|
cursor.row += term->grid->offset;
|
|
|
|
|
cursor.row -= term->grid->view;
|
|
|
|
|
cursor.row &= term->grid->num_rows - 1;
|
|
|
|
|
|
|
|
|
|
if (cursor.row < 0 || cursor.row >= term->rows)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-03-23 13:03:07 +01:00
|
|
|
int cells_needed = seat->ime.preedit.count;
|
2020-12-02 18:52:50 +01:00
|
|
|
|
2021-03-23 13:03:07 +01:00
|
|
|
if (seat->ime.preedit.cursor.start == cells_needed &&
|
|
|
|
|
seat->ime.preedit.cursor.end == cells_needed)
|
2021-01-25 21:57:42 +01:00
|
|
|
{
|
|
|
|
|
/* Cursor will be drawn *after* the pre-edit string, i.e. in
|
|
|
|
|
* the cell *after*. This means we need to copy, and dirty,
|
2024-02-06 12:36:45 +01:00
|
|
|
* one extra cell from the original grid, or we'll leave
|
|
|
|
|
* trailing "cursors" after us if the user deletes text while
|
2021-01-25 21:57:42 +01:00
|
|
|
* pre-editing */
|
|
|
|
|
cells_needed++;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-02 18:52:50 +01:00
|
|
|
int row_idx = cursor.row;
|
|
|
|
|
int col_idx = cursor.col;
|
2020-12-08 19:17:46 +01:00
|
|
|
int ime_ofs = 0; /* Offset into pre-edit string to start rendering at */
|
2020-12-02 18:52:50 +01:00
|
|
|
|
|
|
|
|
int cells_left = term->cols - cursor.col;
|
|
|
|
|
int cells_used = min(cells_needed, term->cols);
|
|
|
|
|
|
|
|
|
|
/* Adjust start of pre-edit text to the left if string doesn't fit on row */
|
|
|
|
|
if (cells_left < cells_used)
|
|
|
|
|
col_idx -= cells_used - cells_left;
|
|
|
|
|
|
2020-12-08 19:17:46 +01:00
|
|
|
if (cells_needed > cells_used) {
|
2021-03-23 13:03:07 +01:00
|
|
|
int start = seat->ime.preedit.cursor.start;
|
|
|
|
|
int end = seat->ime.preedit.cursor.end;
|
2020-12-08 19:17:46 +01:00
|
|
|
|
|
|
|
|
if (start == end) {
|
|
|
|
|
/* Ensure *end* of pre-edit string is visible */
|
|
|
|
|
ime_ofs = cells_needed - cells_used;
|
|
|
|
|
} else {
|
|
|
|
|
/* Ensure the *beginning* of the cursor-area is visible */
|
|
|
|
|
ime_ofs = start;
|
|
|
|
|
|
|
|
|
|
/* Display as much as possible of the pre-edit string */
|
|
|
|
|
if (cells_needed - ime_ofs < cells_used)
|
|
|
|
|
ime_ofs = cells_needed - cells_used;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Make sure we don't start in the middle of a character */
|
|
|
|
|
while (ime_ofs < cells_needed &&
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
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.
2021-05-14 14:41:02 +02:00
|
|
|
seat->ime.preedit.cells[ime_ofs].wc >= CELL_SPACER)
|
2020-12-08 19:17:46 +01:00
|
|
|
{
|
|
|
|
|
ime_ofs++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(col_idx >= 0);
|
|
|
|
|
xassert(col_idx < term->cols);
|
2020-12-02 18:52:50 +01:00
|
|
|
|
|
|
|
|
struct row *row = grid_row_in_view(term->grid, row_idx);
|
|
|
|
|
|
|
|
|
|
/* Don't start pre-edit text in the middle of a double-width character */
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
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.
2021-05-14 14:41:02 +02:00
|
|
|
while (col_idx > 0 && row->cells[col_idx].wc >= CELL_SPACER) {
|
2020-12-02 18:52:50 +01:00
|
|
|
cells_used++;
|
|
|
|
|
col_idx--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Copy original content (render_cell() reads cell data directly
|
|
|
|
|
* from grid), and mark all cells as dirty. This ensures they are
|
|
|
|
|
* re-rendered when the pre-edit text is modified or removed.
|
|
|
|
|
*/
|
2024-01-25 07:03:50 +00:00
|
|
|
struct cell *real_cells = xmalloc(cells_used * sizeof(real_cells[0]));
|
2020-12-02 18:52:50 +01:00
|
|
|
for (int i = 0; i < cells_used; i++) {
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(col_idx + i < term->cols);
|
2020-12-02 18:52:50 +01:00
|
|
|
real_cells[i] = row->cells[col_idx + i];
|
|
|
|
|
real_cells[i].attrs.clean = 0;
|
|
|
|
|
}
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
|
|
|
|
|
/* Render pre-edit text */
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
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.
2021-05-14 14:41:02 +02:00
|
|
|
xassert(seat->ime.preedit.cells[ime_ofs].wc < CELL_SPACER);
|
2021-03-23 13:03:07 +01:00
|
|
|
for (int i = 0, idx = ime_ofs; idx < seat->ime.preedit.count; i++, idx++) {
|
|
|
|
|
const struct cell *cell = &seat->ime.preedit.cells[idx];
|
2020-12-02 18:52:50 +01:00
|
|
|
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
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.
2021-05-14 14:41:02 +02:00
|
|
|
if (cell->wc >= CELL_SPACER)
|
2020-12-02 18:52:50 +01:00
|
|
|
continue;
|
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
int width = max(1, c32width(cell->wc));
|
2020-12-02 18:52:50 +01:00
|
|
|
if (col_idx + i + width > term->cols)
|
|
|
|
|
break;
|
|
|
|
|
|
2020-12-08 19:17:46 +01:00
|
|
|
row->cells[col_idx + i] = *cell;
|
2023-10-07 16:23:09 +02:00
|
|
|
render_cell(term, buf->pix[0], NULL, row, row_idx, col_idx + i, false);
|
2020-12-02 18:52:50 +01:00
|
|
|
}
|
|
|
|
|
|
2021-03-23 13:03:07 +01:00
|
|
|
int start = seat->ime.preedit.cursor.start - ime_ofs;
|
|
|
|
|
int end = seat->ime.preedit.cursor.end - ime_ofs;
|
2020-12-03 18:39:34 +01:00
|
|
|
|
2021-03-23 13:03:07 +01:00
|
|
|
if (!seat->ime.preedit.cursor.hidden) {
|
|
|
|
|
const struct cell *start_cell = &seat->ime.preedit.cells[0];
|
2020-12-02 18:52:50 +01:00
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t fg = color_hex_to_pixman(term->colors.fg, gamma_correct);
|
|
|
|
|
pixman_color_t bg = color_hex_to_pixman(term->colors.bg, gamma_correct);
|
2020-12-02 18:52:50 +01:00
|
|
|
|
|
|
|
|
pixman_color_t cursor_color, text_color;
|
|
|
|
|
cursor_colors_for_cell(
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
term, start_cell, &fg, &bg, &cursor_color, &text_color, gamma_correct);
|
2020-12-02 18:52:50 +01:00
|
|
|
|
|
|
|
|
int x = term->margins.left + (col_idx + start) * term->cell_width;
|
|
|
|
|
int y = term->margins.top + row_idx * term->cell_height;
|
|
|
|
|
|
2020-12-04 18:43:06 +01:00
|
|
|
if (end == start) {
|
|
|
|
|
/* Bar */
|
2020-12-08 19:17:46 +01:00
|
|
|
if (start >= 0) {
|
|
|
|
|
struct fcft_font *font = attrs_to_font(term, &start_cell->attrs);
|
2021-05-18 18:52:10 +02:00
|
|
|
draw_beam_cursor(term, buf->pix[0], font, &cursor_color, x, y);
|
2020-12-08 19:17:46 +01:00
|
|
|
}
|
2020-12-20 15:01:21 -07:00
|
|
|
term_ime_set_cursor_rect(term, x, y, 1, term->cell_height);
|
2020-12-04 18:43:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (end > start) {
|
|
|
|
|
/* Hollow cursor */
|
2020-12-08 19:17:46 +01:00
|
|
|
if (start >= 0 && end <= term->cols) {
|
|
|
|
|
int cols = end - start;
|
2024-04-09 16:28:54 +02:00
|
|
|
draw_hollow_block(term, buf->pix[0], &cursor_color, x, y, cols);
|
2020-12-08 19:17:46 +01:00
|
|
|
}
|
2020-12-20 15:01:21 -07:00
|
|
|
|
|
|
|
|
term_ime_set_cursor_rect(
|
|
|
|
|
term, x, y, (end - start) * term->cell_width, term->cell_height);
|
2020-12-04 18:43:06 +01:00
|
|
|
}
|
2020-12-02 18:52:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Restore original content (but do not render) */
|
|
|
|
|
for (int i = 0; i < cells_used; i++)
|
|
|
|
|
row->cells[col_idx + i] = real_cells[i];
|
|
|
|
|
free(real_cells);
|
|
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
const int damage_x = term->margins.left + col_idx * term->cell_width;
|
|
|
|
|
const int damage_y = term->margins.top + row_idx * term->cell_height;
|
|
|
|
|
const int damage_w = cells_used * term->cell_width;
|
|
|
|
|
const int damage_h = term->cell_height;
|
|
|
|
|
|
2020-12-02 18:52:50 +01:00
|
|
|
wl_surface_damage_buffer(
|
2023-06-26 16:10:40 +02:00
|
|
|
term->window->surface.surf,
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
damage_x, damage_y, damage_w, damage_h);
|
2021-03-23 13:03:07 +01:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
render_ime_preedit(struct terminal *term, struct buffer *buf)
|
|
|
|
|
{
|
|
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
|
|
|
|
tll_foreach(term->wl->seats, it) {
|
|
|
|
|
if (it->item.kbd_focus == term)
|
|
|
|
|
render_ime_preedit_for_seat(term, &it->item, buf);
|
|
|
|
|
}
|
2020-12-03 18:36:56 +01:00
|
|
|
#endif
|
2020-12-02 18:52:50 +01:00
|
|
|
}
|
|
|
|
|
|
2024-05-21 16:09:34 +02:00
|
|
|
static void
|
|
|
|
|
render_overlay_single_pixel(struct terminal *term, enum overlay_style style,
|
|
|
|
|
pixman_color_t color)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = term->wl;
|
|
|
|
|
struct wayl_sub_surface *overlay = &term->window->overlay;
|
2024-05-22 13:38:13 +02:00
|
|
|
struct wl_buffer *buf = NULL;
|
2024-05-21 16:09:34 +02:00
|
|
|
|
|
|
|
|
/*
|
2024-05-22 13:38:13 +02:00
|
|
|
* In an ideal world, we'd only update the surface (i.e. commit
|
|
|
|
|
* any changes) if anything has actually changed.
|
2024-05-21 16:09:34 +02:00
|
|
|
*
|
2024-05-22 13:38:13 +02:00
|
|
|
* For technical reasons, we can't do that, since we can't
|
|
|
|
|
* determine whether the last committed buffer is still valid
|
|
|
|
|
* (i.e. does it correspond to the current overlay style, *and*
|
|
|
|
|
* does last frame's size match the current size?)
|
2024-05-21 16:09:34 +02:00
|
|
|
*
|
2024-05-22 13:38:13 +02:00
|
|
|
* What we _can_ do is use the fact that single-pixel buffers
|
|
|
|
|
* don't have a size; you have to use a viewport to "size" them.
|
|
|
|
|
*
|
|
|
|
|
* This means we can check if the last frame's overlay style is
|
|
|
|
|
* the same as the current size. If so, then we *know* that the
|
|
|
|
|
* currently attached buffer is valid, and we *don't* have to
|
|
|
|
|
* create a new single-pixel buffer.
|
|
|
|
|
*
|
|
|
|
|
* What we do *not* know if the *size* is still valid. This means
|
|
|
|
|
* we do have to do the viewport calls, and a surface commit.
|
|
|
|
|
*
|
|
|
|
|
* This is still better than *always* creating a new buffer.
|
2024-05-21 16:09:34 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
assert(style == OVERLAY_UNICODE_MODE || style == OVERLAY_FLASH);
|
|
|
|
|
assert(wayl->single_pixel_manager != NULL);
|
|
|
|
|
assert(overlay->surface.viewport != NULL);
|
|
|
|
|
|
2024-05-22 13:38:13 +02:00
|
|
|
quirk_weston_subsurface_desync_on(overlay->sub);
|
|
|
|
|
|
|
|
|
|
if (style != term->render.last_overlay_style) {
|
|
|
|
|
buf = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(
|
|
|
|
|
wayl->single_pixel_manager,
|
2024-05-21 16:09:34 +02:00
|
|
|
(double)color.red / 0xffff * 0xffffffff,
|
|
|
|
|
(double)color.green / 0xffff * 0xffffffff,
|
|
|
|
|
(double)color.blue / 0xffff * 0xffffffff,
|
|
|
|
|
(double)color.alpha / 0xffff * 0xffffffff);
|
|
|
|
|
|
2024-05-22 13:38:13 +02:00
|
|
|
wl_surface_set_buffer_scale(overlay->surface.surf, 1);
|
|
|
|
|
wl_surface_attach(overlay->surface.surf, buf, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-21 16:09:34 +02:00
|
|
|
wp_viewport_set_destination(
|
|
|
|
|
overlay->surface.viewport,
|
|
|
|
|
roundf(term->width / term->scale),
|
|
|
|
|
roundf(term->height / term->scale));
|
|
|
|
|
|
|
|
|
|
wl_subsurface_set_position(overlay->sub, 0, 0);
|
|
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
|
|
|
|
overlay->surface.surf, 0, 0, term->width, term->height);
|
|
|
|
|
|
|
|
|
|
wl_surface_commit(overlay->surface.surf);
|
|
|
|
|
quirk_weston_subsurface_desync_off(overlay->sub);
|
|
|
|
|
|
|
|
|
|
term->render.last_overlay_style = style;
|
2024-05-22 13:38:13 +02:00
|
|
|
|
|
|
|
|
if (buf != NULL) {
|
|
|
|
|
wl_buffer_destroy(buf);
|
|
|
|
|
}
|
2024-05-21 16:09:34 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-04 22:26:00 -05:00
|
|
|
void
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
render_overlay(struct terminal *term)
|
|
|
|
|
{
|
2023-06-26 16:10:40 +02:00
|
|
|
struct wayl_sub_surface *overlay = &term->window->overlay;
|
unicode-mode: move state from seat to term
This fixes an issue where entering unicode-mode in one foot client,
also enabled unicode-mode on other foot clients. Both
visually (although glitchy), and in effect.
The reason the state was originally in the seat objects, was to fully
support multi-seat. That is, one seat/keyboard entering unicode-mode
should not affect other seats/keyboards.
The issue with this is that seat objects are Wayland global. Thus, in
server mode, all seat objects are shared between the foot clients.
There is a similarity with IME, which also keeps state in the
seat. There's one big difference, however, and that is IME has Wayland
native enter/leave events, that the compositor emits when windows are
focused/unfocused. These events allow us to reset IME state. For our
own Unicode mode, there is nothing similar.
This patch moves the Unicode state from seats, to the terminal
struct. This does mean that if one seat/keyboard enters Unicode mode,
then *all* seats/keyboards will affect the unicode state. This
potential downside is outweighed by the fact that different foot
clients no longer affect each other.
Closes #1717
2024-05-21 07:06:45 +02:00
|
|
|
const bool unicode_mode_active = term->unicode_mode.active;
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
|
|
|
|
|
const enum overlay_style style =
|
|
|
|
|
term->is_searching ? OVERLAY_SEARCH :
|
|
|
|
|
term->flash.active ? OVERLAY_FLASH :
|
2022-08-08 16:31:28 +02:00
|
|
|
unicode_mode_active ? OVERLAY_UNICODE_MODE :
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
OVERLAY_NONE;
|
|
|
|
|
|
|
|
|
|
if (likely(style == OVERLAY_NONE)) {
|
|
|
|
|
if (term->render.last_overlay_style != OVERLAY_NONE) {
|
|
|
|
|
/* Unmap overlay sub-surface */
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_attach(overlay->surface.surf, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(overlay->surface.surf);
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
term->render.last_overlay_style = OVERLAY_NONE;
|
|
|
|
|
term->render.last_overlay_buf = NULL;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-08 16:31:28 +02:00
|
|
|
pixman_color_t color;
|
|
|
|
|
|
|
|
|
|
switch (style) {
|
|
|
|
|
case OVERLAY_SEARCH:
|
|
|
|
|
case OVERLAY_UNICODE_MODE:
|
|
|
|
|
color = (pixman_color_t){0, 0, 0, 0x7fff};
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case OVERLAY_FLASH:
|
2023-10-07 19:37:04 +02:00
|
|
|
color = color_hex_to_pixman_with_alpha(
|
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-19 09:29:06 +01:00
|
|
|
term->conf->colors_dark.flash,
|
|
|
|
|
term->conf->colors_dark.flash_alpha,
|
2025-05-01 08:34:49 +02:00
|
|
|
wayl_do_linear_blending(term->wl, term->conf));
|
2022-08-08 16:31:28 +02:00
|
|
|
break;
|
2024-05-21 16:09:34 +02:00
|
|
|
|
|
|
|
|
case OVERLAY_NONE:
|
|
|
|
|
xassert(false);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool single_pixel =
|
|
|
|
|
(style == OVERLAY_UNICODE_MODE || style == OVERLAY_FLASH) &&
|
|
|
|
|
term->wl->single_pixel_manager != NULL &&
|
|
|
|
|
overlay->surface.viewport != NULL;
|
|
|
|
|
|
|
|
|
|
if (single_pixel) {
|
|
|
|
|
render_overlay_single_pixel(term, style, color);
|
|
|
|
|
return;
|
2022-08-08 16:31:28 +02:00
|
|
|
}
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
|
2024-05-21 16:09:34 +02:00
|
|
|
struct buffer *buf = shm_get_buffer(
|
2026-01-04 07:57:25 +01:00
|
|
|
term->render.chains.overlay, term->width, term->height);
|
2024-05-21 16:09:34 +02:00
|
|
|
pixman_image_set_clip_region32(buf->pix[0], NULL);
|
|
|
|
|
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
/* Bounding rectangle of damaged areas - for wl_surface_damage_buffer() */
|
|
|
|
|
pixman_box32_t damage_bounds;
|
|
|
|
|
|
|
|
|
|
if (style == OVERLAY_SEARCH) {
|
|
|
|
|
/*
|
|
|
|
|
* When possible, we only update the areas that have *changed*
|
|
|
|
|
* since the last frame. That means:
|
|
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* - clearing/erasing cells that are now selected, but weren't
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
* in the last frame
|
2024-02-06 12:36:45 +01:00
|
|
|
* - dimming cells that were selected, but aren't anymore
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* To do this, we save the last frame's selected cells as a
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
* pixman region.
|
|
|
|
|
*
|
|
|
|
|
* Then, we calculate the corresponding region for this
|
2024-02-06 12:36:45 +01:00
|
|
|
* frame's selected cells.
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Last frame's region minus this frame's region gives us the
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
* region that needs to be *dimmed* in this frame
|
|
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* This frame's region minus last frame's region gives us the
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
* region that needs to be *cleared* in this frame.
|
|
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Finally, the union of the two "diff" regions above, gives
|
2022-08-08 16:31:28 +02:00
|
|
|
* us the total region affected by a change, in either way. We
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
* use this as the bounding box for the
|
|
|
|
|
* wl_surface_damage_buffer() call.
|
|
|
|
|
*/
|
|
|
|
|
pixman_region32_t *see_through = &term->render.last_overlay_clip;
|
|
|
|
|
pixman_region32_t old_see_through;
|
2022-09-22 18:39:00 +02:00
|
|
|
const bool buffer_reuse =
|
|
|
|
|
buf == term->render.last_overlay_buf &&
|
|
|
|
|
style == term->render.last_overlay_style &&
|
|
|
|
|
buf->age == 0;
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
|
2022-09-22 18:39:00 +02:00
|
|
|
if (!buffer_reuse) {
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Can't reuse last frame's damage - set to full window,
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
* to ensure *everything* is updated */
|
|
|
|
|
pixman_region32_init_rect(
|
|
|
|
|
&old_see_through, 0, 0, buf->width, buf->height);
|
|
|
|
|
} else {
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Use last frame's saved region */
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
pixman_region32_init(&old_see_through);
|
|
|
|
|
pixman_region32_copy(&old_see_through, see_through);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pixman_region32_clear(see_through);
|
|
|
|
|
|
2022-09-22 18:39:00 +02:00
|
|
|
/* Build region consisting of all current search matches */
|
search/render: initial support for highlighting all visible matches
Before this patch, only the currently “selected” match was
highlighted (by having the “selected” attribute, and by *not* dimming
it, like the rest of the grid during a scrollback search).
With this patch, we now highlight matches within the viewport. While
searching, only the “primary” match is searched-for, and tracked.
Then, when rendering a frame, we find all “secondary” matches as
well. “holes” are added to the search-mode overlay by the means of an
search-match iterator.
The iterator’s text matching logic is *very* similar to what we do
when the search criteria has been updated, and we re-search the
scrollback. It should be possible to refactor this, and share code.
2022-04-09 17:57:29 +02:00
|
|
|
struct search_match_iterator iter = search_matches_new_iter(term);
|
|
|
|
|
for (struct range match = search_matches_next(&iter);
|
|
|
|
|
match.start.row >= 0;
|
|
|
|
|
match = search_matches_next(&iter))
|
|
|
|
|
{
|
|
|
|
|
int r = match.start.row;
|
|
|
|
|
int start_col = match.start.col;
|
|
|
|
|
const int end_row = match.end.row;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
const int end_col =
|
|
|
|
|
r == end_row ? match.end.col : term->cols - 1;
|
|
|
|
|
|
|
|
|
|
int x = term->margins.left + start_col * term->cell_width;
|
|
|
|
|
int y = term->margins.top + r * term->cell_height;
|
|
|
|
|
int width = (end_col + 1 - start_col) * term->cell_width;
|
|
|
|
|
int height = 1 * term->cell_height;
|
|
|
|
|
|
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
see_through, see_through, x, y, width, height);
|
|
|
|
|
|
|
|
|
|
if (++r > end_row)
|
|
|
|
|
break;
|
2022-04-17 19:26:54 +02:00
|
|
|
|
|
|
|
|
start_col = 0;
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-22 18:39:00 +02:00
|
|
|
/* Areas that need to be cleared: cells that were dimmed in
|
|
|
|
|
* the last frame but is now see-through */
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
pixman_region32_t new_see_through;
|
|
|
|
|
pixman_region32_init(&new_see_through);
|
2022-09-22 18:39:00 +02:00
|
|
|
|
|
|
|
|
if (buffer_reuse)
|
|
|
|
|
pixman_region32_subtract(&new_see_through, see_through, &old_see_through);
|
|
|
|
|
else {
|
|
|
|
|
/* Buffer content is unknown - explicitly clear *all*
|
|
|
|
|
* current see-through areas */
|
|
|
|
|
pixman_region32_copy(&new_see_through, see_through);
|
|
|
|
|
}
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
pixman_image_set_clip_region32(buf->pix[0], &new_see_through);
|
|
|
|
|
|
2022-09-22 18:39:00 +02:00
|
|
|
/* Areas that need to be dimmed: cells that were cleared in
|
|
|
|
|
* the last frame but is not anymore */
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
pixman_region32_t new_dimmed;
|
|
|
|
|
pixman_region32_init(&new_dimmed);
|
|
|
|
|
pixman_region32_subtract(&new_dimmed, &old_see_through, see_through);
|
|
|
|
|
pixman_region32_fini(&old_see_through);
|
|
|
|
|
|
2022-09-22 18:39:00 +02:00
|
|
|
/* Total affected area */
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
pixman_region32_t damage;
|
|
|
|
|
pixman_region32_init(&damage);
|
|
|
|
|
pixman_region32_union(&damage, &new_see_through, &new_dimmed);
|
|
|
|
|
damage_bounds = damage.extents;
|
|
|
|
|
|
|
|
|
|
/* Clear cells that became selected in this frame. */
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &(pixman_color_t){0}, 1,
|
|
|
|
|
&(pixman_rectangle16_t){0, 0, term->width, term->height});
|
|
|
|
|
|
|
|
|
|
/* Set clip region for the newly dimmed cells. The actual
|
|
|
|
|
* paint call is done below */
|
|
|
|
|
pixman_image_set_clip_region32(buf->pix[0], &new_dimmed);
|
|
|
|
|
|
|
|
|
|
pixman_region32_fini(&new_see_through);
|
|
|
|
|
pixman_region32_fini(&new_dimmed);
|
|
|
|
|
pixman_region32_fini(&damage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (buf == term->render.last_overlay_buf &&
|
|
|
|
|
style == term->render.last_overlay_style)
|
|
|
|
|
{
|
2022-08-08 16:31:28 +02:00
|
|
|
xassert(style == OVERLAY_FLASH || style == OVERLAY_UNICODE_MODE);
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
shm_did_not_use_buf(buf);
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
pixman_image_set_clip_region32(buf->pix[0], NULL);
|
|
|
|
|
damage_bounds = (pixman_box32_t){0, 0, buf->width, buf->height};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &color, 1,
|
|
|
|
|
&(pixman_rectangle16_t){0, 0, term->width, term->height});
|
|
|
|
|
|
2022-04-17 11:04:27 +02:00
|
|
|
quirk_weston_subsurface_desync_on(overlay->sub);
|
2023-06-26 17:05:16 +02:00
|
|
|
wayl_surface_scale(
|
2023-06-26 21:06:47 +02:00
|
|
|
term->window, &overlay->surface, buf, term->scale);
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
wl_subsurface_set_position(overlay->sub, 0, 0);
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_attach(overlay->surface.surf, buf->wl_buf, 0, 0);
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
2023-06-26 16:10:40 +02:00
|
|
|
overlay->surface.surf,
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
damage_bounds.x1, damage_bounds.y1,
|
|
|
|
|
damage_bounds.x2 - damage_bounds.x1,
|
|
|
|
|
damage_bounds.y2 - damage_bounds.y1);
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_commit(overlay->surface.surf);
|
2022-04-17 11:04:27 +02:00
|
|
|
quirk_weston_subsurface_desync_off(overlay->sub);
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
|
|
|
|
|
buf->age = 0;
|
|
|
|
|
term->render.last_overlay_buf = buf;
|
|
|
|
|
term->render.last_overlay_style = style;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-29 20:13:26 +02:00
|
|
|
int
|
|
|
|
|
render_worker_thread(void *_ctx)
|
|
|
|
|
{
|
|
|
|
|
struct render_worker_context *ctx = _ctx;
|
|
|
|
|
struct terminal *term = ctx->term;
|
|
|
|
|
const int my_id = ctx->my_id;
|
2019-10-28 18:48:43 +01:00
|
|
|
free(ctx);
|
2019-07-29 20:13:26 +02:00
|
|
|
|
2021-02-10 16:22:36 +01:00
|
|
|
sigset_t mask;
|
|
|
|
|
sigfillset(&mask);
|
|
|
|
|
pthread_sigmask(SIG_SETMASK, &mask, NULL);
|
|
|
|
|
|
2019-08-01 20:09:16 +02:00
|
|
|
char proc_title[16];
|
|
|
|
|
snprintf(proc_title, sizeof(proc_title), "foot:render:%d", my_id);
|
|
|
|
|
|
render: set thread name in a portable way
prctl is Linux-only but pthread_setname_np is same as PR_SET_NAME.
Solaris and FreeBSD >= 13 have pthread_setname_np similar to Linux.
DragonFly, OpenBSD, FreeBSD < 13 lack pthread_setname_np but provide
pthread_set_name_np which doesn't return a value. NetBSD requires 3
arguments for pthread_setname_np where the last one is void *.
render.c:8:10: fatal error: 'sys/prctl.h' file not found
#include <sys/prctl.h>
^~~~~~~~~~~~~
render.c:1234:9: error: implicit declaration of function 'prctl' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
^
render.c:1234:15: error: use of undeclared identifier 'PR_SET_NAME'
if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
^
2021-01-19 14:49:43 +00:00
|
|
|
if (pthread_setname_np(pthread_self(), proc_title) < 0)
|
2019-08-01 20:09:16 +02:00
|
|
|
LOG_ERRNO("render worker %d: failed to set process title", my_id);
|
|
|
|
|
|
2019-07-29 20:13:26 +02:00
|
|
|
sem_t *start = &term->render.workers.start;
|
|
|
|
|
sem_t *done = &term->render.workers.done;
|
|
|
|
|
mtx_t *lock = &term->render.workers.lock;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
sem_wait(start);
|
|
|
|
|
|
|
|
|
|
struct buffer *buf = term->render.workers.buf;
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
|
2019-07-29 20:13:26 +02:00
|
|
|
bool frame_done = false;
|
|
|
|
|
|
2020-07-13 13:44:52 +02:00
|
|
|
/* Translate offset-relative cursor row to view-relative */
|
|
|
|
|
struct coord cursor = {-1, -1};
|
|
|
|
|
if (!term->hide_cursor) {
|
|
|
|
|
cursor = term->grid->cursor.point;
|
|
|
|
|
cursor.row += term->grid->offset;
|
|
|
|
|
cursor.row -= term->grid->view;
|
|
|
|
|
cursor.row &= term->grid->num_rows - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-29 20:13:26 +02:00
|
|
|
while (!frame_done) {
|
|
|
|
|
mtx_lock(lock);
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(tll_length(term->render.workers.queue) > 0);
|
2019-07-29 20:13:26 +02:00
|
|
|
|
|
|
|
|
int row_no = tll_pop_front(term->render.workers.queue);
|
|
|
|
|
mtx_unlock(lock);
|
|
|
|
|
|
|
|
|
|
switch (row_no) {
|
render: remove most of the special handling of cursor rendering
Previously, we had to explicitly render the old cursor cell *before*
applying scrolling damage.
We then rendered all the dirty rows, *without* rendering the cursor -
even if the cursor cell was among the dirty rows.
Finally, when everything else was done, we explicitly rendered the
cursor cell.
This meant a lot of code, and unnecessary render_cell() calls, along
with unnecessary wl_surface_damage_buffer() calls.
This was a necessary in the early design of foot, but not anymore.
We can simply mark both the old cursor cell, and the current one, as
dirty and let the normal rendering framework render it. All we need to
do is pass the cursor column to render_row(), so that it can pass
has_cursor=true in the appropriate call to render_cell(). We pass -1
here for all rows, except the cursor's row, where we pass the actual
cursor column.
With this, there's no need to calculate whether the cursor is visible
or not; just mark it's cell as dirty, and if that row is visible, the
normal rendering will take care of it.
This also simplifies the state needed to be saved between two frames;
we only need a row pointer, and the cursor column index.
Part of https://codeberg.org/dnkl/foot/issues/35
2020-07-12 12:56:10 +02:00
|
|
|
default: {
|
|
|
|
|
struct row *row = grid_row_in_view(term->grid, row_no);
|
2020-07-13 13:44:52 +02:00
|
|
|
int cursor_col = cursor.row == row_no ? cursor.col : -1;
|
render: remove most of the special handling of cursor rendering
Previously, we had to explicitly render the old cursor cell *before*
applying scrolling damage.
We then rendered all the dirty rows, *without* rendering the cursor -
even if the cursor cell was among the dirty rows.
Finally, when everything else was done, we explicitly rendered the
cursor cell.
This meant a lot of code, and unnecessary render_cell() calls, along
with unnecessary wl_surface_damage_buffer() calls.
This was a necessary in the early design of foot, but not anymore.
We can simply mark both the old cursor cell, and the current one, as
dirty and let the normal rendering framework render it. All we need to
do is pass the cursor column to render_row(), so that it can pass
has_cursor=true in the appropriate call to render_cell(). We pass -1
here for all rows, except the cursor's row, where we pass the actual
cursor column.
With this, there's no need to calculate whether the cursor is visible
or not; just mark it's cell as dirty, and if that row is visible, the
normal rendering will take care of it.
This also simplifies the state needed to be saved between two frames;
we only need a row pointer, and the cursor column index.
Part of https://codeberg.org/dnkl/foot/issues/35
2020-07-12 12:56:10 +02:00
|
|
|
|
2023-10-07 16:23:09 +02:00
|
|
|
render_row(term, buf->pix[my_id], &buf->dirty[my_id],
|
|
|
|
|
row, row_no, cursor_col);
|
2019-07-29 20:13:26 +02:00
|
|
|
break;
|
render: remove most of the special handling of cursor rendering
Previously, we had to explicitly render the old cursor cell *before*
applying scrolling damage.
We then rendered all the dirty rows, *without* rendering the cursor -
even if the cursor cell was among the dirty rows.
Finally, when everything else was done, we explicitly rendered the
cursor cell.
This meant a lot of code, and unnecessary render_cell() calls, along
with unnecessary wl_surface_damage_buffer() calls.
This was a necessary in the early design of foot, but not anymore.
We can simply mark both the old cursor cell, and the current one, as
dirty and let the normal rendering framework render it. All we need to
do is pass the cursor column to render_row(), so that it can pass
has_cursor=true in the appropriate call to render_cell(). We pass -1
here for all rows, except the cursor's row, where we pass the actual
cursor column.
With this, there's no need to calculate whether the cursor is visible
or not; just mark it's cell as dirty, and if that row is visible, the
normal rendering will take care of it.
This also simplifies the state needed to be saved between two frames;
we only need a row pointer, and the cursor column index.
Part of https://codeberg.org/dnkl/foot/issues/35
2020-07-12 12:56:10 +02:00
|
|
|
}
|
2019-07-29 20:13:26 +02:00
|
|
|
|
|
|
|
|
case -1:
|
|
|
|
|
frame_done = true;
|
|
|
|
|
sem_post(done);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case -2:
|
|
|
|
|
return 0;
|
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-05 10:48:36 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2019-07-29 20:13:26 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return -1;
|
2019-07-24 20:31:21 +02:00
|
|
|
}
|
|
|
|
|
|
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-05 10:48:36 +02:00
|
|
|
static void
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-19 15:08:46 +01:00
|
|
|
struct csd_data
|
2020-02-29 18:02:38 +01:00
|
|
|
get_csd_data(const struct terminal *term, enum csd_surface surf_idx)
|
|
|
|
|
{
|
2021-06-22 18:58:38 +02:00
|
|
|
xassert(term->window->csd_mode == CSD_YES);
|
2020-02-26 12:39:17 +01:00
|
|
|
|
2022-04-16 11:26:28 +02:00
|
|
|
const bool borders_visible = wayl_win_csd_borders_visible(term->window);
|
|
|
|
|
const bool title_visible = wayl_win_csd_titlebar_visible(term->window);
|
|
|
|
|
|
2023-07-27 20:07:05 +02:00
|
|
|
const float scale = term->scale;
|
|
|
|
|
|
2022-04-16 11:26:28 +02:00
|
|
|
const int border_width = borders_visible
|
2023-07-27 20:07:05 +02:00
|
|
|
? roundf(term->conf->csd.border_width * scale) : 0;
|
2020-02-28 18:49:34 +01:00
|
|
|
|
2022-04-16 11:26:28 +02:00
|
|
|
const int title_height = title_visible
|
2023-07-27 20:07:05 +02:00
|
|
|
? roundf(term->conf->csd.title_height * scale) : 0;
|
2020-02-26 12:17:58 +01:00
|
|
|
|
2022-04-16 11:26:28 +02:00
|
|
|
const int button_width = title_visible
|
2023-07-27 20:07:05 +02:00
|
|
|
? roundf(term->conf->csd.button_width * scale) : 0;
|
2020-03-02 20:29:28 +01:00
|
|
|
|
2025-05-20 15:01:25 +03:00
|
|
|
int remaining_width = term->width;
|
2020-09-09 18:43:23 +02:00
|
|
|
|
2025-05-20 15:01:25 +03:00
|
|
|
const int button_close_width = remaining_width >= button_width ? button_width : 0;
|
|
|
|
|
remaining_width -= button_close_width;
|
|
|
|
|
const int button_close_start = remaining_width;
|
2020-09-09 18:43:23 +02:00
|
|
|
|
2025-05-20 15:01:25 +03:00
|
|
|
const int button_maximize_width = remaining_width >= button_width &&
|
|
|
|
|
term->window->wm_capabilities.maximize ? button_width : 0;
|
|
|
|
|
remaining_width -= button_maximize_width;
|
|
|
|
|
const int button_maximize_start = remaining_width;
|
|
|
|
|
|
|
|
|
|
const int button_minimize_width = remaining_width >= button_width &&
|
|
|
|
|
term->window->wm_capabilities.minimize ? button_width : 0;
|
|
|
|
|
remaining_width -= button_minimize_width;
|
|
|
|
|
const int button_minimize_start = remaining_width;
|
2023-07-27 20:07:05 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* With fractional scaling, we must ensure the offset, when
|
|
|
|
|
* divided by the scale (in set_position()), and the scaled back
|
|
|
|
|
* (by the compositor), matches the actual pixel count made up by
|
|
|
|
|
* the titlebar and the border.
|
|
|
|
|
*/
|
|
|
|
|
const int top_offset = roundf(
|
|
|
|
|
scale * (roundf(-title_height / scale) - roundf(border_width / scale)));
|
|
|
|
|
|
|
|
|
|
const int top_bottom_width = roundf(
|
|
|
|
|
scale * (roundf(term->width / scale) + 2 * roundf(border_width / scale)));
|
|
|
|
|
|
|
|
|
|
const int left_right_height = roundf(
|
|
|
|
|
scale * (roundf(title_height / scale) + roundf(term->height / scale)));
|
2020-09-09 18:43:23 +02:00
|
|
|
|
2020-03-01 12:28:01 +01:00
|
|
|
switch (surf_idx) {
|
2023-07-27 20:07:05 +02:00
|
|
|
case CSD_SURF_TITLE: return (struct csd_data){ 0, -title_height, term->width, title_height};
|
|
|
|
|
case CSD_SURF_LEFT: return (struct csd_data){-border_width, -title_height, border_width, left_right_height};
|
|
|
|
|
case CSD_SURF_RIGHT: return (struct csd_data){ term->width, -title_height, border_width, left_right_height};
|
|
|
|
|
case CSD_SURF_TOP: return (struct csd_data){-border_width, top_offset, top_bottom_width, border_width};
|
|
|
|
|
case CSD_SURF_BOTTOM: return (struct csd_data){-border_width, term->height, top_bottom_width, border_width};
|
2020-03-01 12:28:01 +01:00
|
|
|
|
2020-03-02 20:29:28 +01:00
|
|
|
/* Positioned relative to CSD_SURF_TITLE */
|
2025-05-20 15:01:25 +03:00
|
|
|
case CSD_SURF_MINIMIZE: return (struct csd_data){button_minimize_start, 0, button_minimize_width, title_height};
|
|
|
|
|
case CSD_SURF_MAXIMIZE: return (struct csd_data){button_maximize_start, 0, button_maximize_width, title_height};
|
|
|
|
|
case CSD_SURF_CLOSE: return (struct csd_data){ button_close_start, 0, button_close_width, title_height};
|
2020-03-02 20:29:28 +01:00
|
|
|
|
2020-03-01 12:28:01 +01:00
|
|
|
case CSD_SURF_COUNT:
|
2021-02-09 13:52:33 +00:00
|
|
|
break;
|
2020-03-01 12:28:01 +01:00
|
|
|
}
|
2020-02-28 18:49:34 +01:00
|
|
|
|
2021-02-09 13:52:33 +00:00
|
|
|
BUG("Invalid csd_surface type");
|
2020-08-23 07:42:20 +02:00
|
|
|
return (struct csd_data){0};
|
2020-02-29 18:02:38 +01:00
|
|
|
}
|
2020-02-28 18:51:09 +01:00
|
|
|
|
2020-03-02 21:06:15 +01:00
|
|
|
static void
|
2023-06-26 16:53:16 +02:00
|
|
|
csd_commit(struct terminal *term, struct wayl_surface *surf, struct buffer *buf)
|
2020-03-02 21:06:15 +01:00
|
|
|
{
|
2023-06-26 21:06:47 +02:00
|
|
|
wayl_surface_scale(term->window, surf, buf, term->scale);
|
2023-06-26 16:53:16 +02:00
|
|
|
wl_surface_attach(surf->surf, buf->wl_buf, 0, 0);
|
|
|
|
|
wl_surface_damage_buffer(surf->surf, 0, 0, buf->width, buf->height);
|
|
|
|
|
wl_surface_commit(surf->surf);
|
2020-03-02 21:06:15 +01:00
|
|
|
}
|
|
|
|
|
|
2020-02-29 18:02:38 +01:00
|
|
|
static void
|
|
|
|
|
render_csd_part(struct terminal *term,
|
|
|
|
|
struct wl_surface *surf, struct buffer *buf,
|
|
|
|
|
int width, int height, pixman_color_t *color)
|
|
|
|
|
{
|
2021-06-22 18:58:38 +02:00
|
|
|
xassert(term->window->csd_mode == CSD_YES);
|
2020-02-23 14:17:48 +01:00
|
|
|
|
2020-02-29 18:02:38 +01:00
|
|
|
pixman_image_fill_rectangles(
|
2020-06-04 15:39:19 +02:00
|
|
|
PIXMAN_OP_SRC, buf->pix[0], color, 1,
|
2020-02-29 18:02:38 +01:00
|
|
|
&(pixman_rectangle16_t){0, 0, buf->width, buf->height});
|
|
|
|
|
}
|
2020-02-25 19:09:29 +01:00
|
|
|
|
2021-07-22 19:22:52 +02:00
|
|
|
static void
|
2023-06-26 16:53:16 +02:00
|
|
|
render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf,
|
2021-07-22 21:23:01 +02:00
|
|
|
struct fcft_font *font, struct buffer *buf,
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const char32_t *text, uint32_t _fg, uint32_t _bg,
|
2023-07-31 16:29:08 +02:00
|
|
|
unsigned x)
|
2021-07-22 19:22:52 +02:00
|
|
|
{
|
|
|
|
|
pixman_region32_t clip;
|
|
|
|
|
pixman_region32_init_rect(&clip, 0, 0, buf->width, buf->height);
|
|
|
|
|
pixman_image_set_clip_region32(buf->pix[0], &clip);
|
|
|
|
|
pixman_region32_fini(&clip);
|
|
|
|
|
|
2025-05-01 08:34:49 +02:00
|
|
|
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
|
2021-07-22 19:22:52 +02:00
|
|
|
uint16_t alpha = _bg >> 24 | (_bg >> 24 << 8);
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct);
|
2021-07-22 19:22:52 +02:00
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &bg, 1,
|
|
|
|
|
&(pixman_rectangle16_t){0, 0, buf->width, buf->height});
|
|
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct);
|
2021-07-22 19:22:52 +02:00
|
|
|
const int x_ofs = term->font_x_ofs;
|
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const size_t len = c32len(text);
|
2021-07-22 23:35:00 +02:00
|
|
|
struct fcft_text_run *text_run = NULL;
|
|
|
|
|
const struct fcft_glyph **glyphs = NULL;
|
|
|
|
|
const struct fcft_glyph *_glyphs[len];
|
|
|
|
|
size_t glyph_count = 0;
|
2021-07-22 21:23:01 +02:00
|
|
|
|
2021-07-22 23:35:00 +02:00
|
|
|
if (fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING) {
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
text_run = fcft_rasterize_text_run_utf32(
|
|
|
|
|
font, len, (const char32_t *)text, term->font_subpixel);
|
2021-07-22 19:22:52 +02:00
|
|
|
|
2021-07-22 23:35:00 +02:00
|
|
|
if (text_run != NULL) {
|
|
|
|
|
glyphs = text_run->glyphs;
|
|
|
|
|
glyph_count = text_run->count;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (glyphs == NULL) {
|
|
|
|
|
for (size_t i = 0; i < len; i++) {
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const struct fcft_glyph *glyph = fcft_rasterize_char_utf32(
|
2021-07-22 23:35:00 +02:00
|
|
|
font, text[i], term->font_subpixel);
|
|
|
|
|
|
|
|
|
|
if (glyph == NULL)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
_glyphs[glyph_count++] = glyph;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glyphs = _glyphs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pixman_image_t *src = pixman_image_create_solid_fill(&fg);
|
|
|
|
|
|
2023-07-31 16:29:08 +02:00
|
|
|
/* Calculate baseline */
|
|
|
|
|
unsigned y;
|
|
|
|
|
{
|
|
|
|
|
const int line_height = buf->height;
|
|
|
|
|
const int font_height = max(font->height, font->ascent + font->descent);
|
|
|
|
|
const int glyph_top_y = round((line_height - font_height) / 2.);
|
|
|
|
|
y = term->font_y_ofs + glyph_top_y + font->ascent;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-22 23:35:00 +02:00
|
|
|
for (size_t i = 0; i < glyph_count; i++) {
|
|
|
|
|
const struct fcft_glyph *glyph = glyphs[i];
|
2021-07-22 19:22:52 +02:00
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
if (unlikely(glyph->is_color_glyph)) {
|
2021-07-22 23:27:10 +02:00
|
|
|
pixman_image_composite32(
|
|
|
|
|
PIXMAN_OP_OVER, glyph->pix, NULL, buf->pix[0], 0, 0, 0, 0,
|
2023-07-31 16:29:08 +02:00
|
|
|
x + x_ofs + glyph->x, y - glyph->y,
|
2021-07-22 23:27:10 +02:00
|
|
|
glyph->width, glyph->height);
|
|
|
|
|
} else {
|
|
|
|
|
pixman_image_composite32(
|
|
|
|
|
PIXMAN_OP_OVER, src, glyph->pix, buf->pix[0], 0, 0, 0, 0,
|
2023-07-31 16:29:08 +02:00
|
|
|
x + x_ofs + glyph->x, y - glyph->y,
|
2021-07-22 23:27:10 +02:00
|
|
|
glyph->width, glyph->height);
|
|
|
|
|
}
|
2021-07-22 19:22:52 +02:00
|
|
|
|
2021-07-22 23:26:07 +02:00
|
|
|
x += glyph->advance.x;
|
2021-07-22 19:22:52 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-22 23:35:00 +02:00
|
|
|
fcft_text_run_destroy(text_run);
|
2021-07-22 23:27:05 +02:00
|
|
|
pixman_image_unref(src);
|
2021-07-22 19:22:52 +02:00
|
|
|
pixman_image_set_clip_region32(buf->pix[0], NULL);
|
|
|
|
|
|
2023-06-26 16:53:16 +02:00
|
|
|
quirk_weston_subsurface_desync_on(sub_surf->sub);
|
2023-06-26 21:06:47 +02:00
|
|
|
wayl_surface_scale(term->window, &sub_surf->surface, buf, term->scale);
|
2023-06-26 16:53:16 +02:00
|
|
|
wl_surface_attach(sub_surf->surface.surf, buf->wl_buf, 0, 0);
|
|
|
|
|
wl_surface_damage_buffer(sub_surf->surface.surf, 0, 0, buf->width, buf->height);
|
2021-07-22 19:22:52 +02:00
|
|
|
|
2023-07-19 16:39:56 +02:00
|
|
|
if (alpha == 0xffff) {
|
|
|
|
|
struct wl_region *region = wl_compositor_create_region(term->wl->compositor);
|
|
|
|
|
if (region != NULL) {
|
|
|
|
|
wl_region_add(region, 0, 0, buf->width, buf->height);
|
|
|
|
|
wl_surface_set_opaque_region(sub_surf->surface.surf, region);
|
|
|
|
|
wl_region_destroy(region);
|
|
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
wl_surface_set_opaque_region(sub_surf->surface.surf, NULL);
|
2021-07-22 19:22:52 +02:00
|
|
|
|
2023-06-26 16:53:16 +02:00
|
|
|
wl_surface_commit(sub_surf->surface.surf);
|
|
|
|
|
quirk_weston_subsurface_desync_off(sub_surf->sub);
|
2021-07-22 19:22:52 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-06 19:16:54 +01:00
|
|
|
static void
|
2021-07-18 16:46:43 +02:00
|
|
|
render_csd_title(struct terminal *term, const struct csd_data *info,
|
|
|
|
|
struct buffer *buf)
|
2020-02-29 18:02:38 +01:00
|
|
|
{
|
2021-06-22 18:58:38 +02:00
|
|
|
xassert(term->window->csd_mode == CSD_YES);
|
2020-03-03 18:23:37 +01:00
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
struct wayl_sub_surface *surf = &term->window->csd.surface[CSD_SURF_TITLE];
|
2021-09-23 22:17:43 +00:00
|
|
|
if (info->width == 0 || info->height == 0)
|
|
|
|
|
return;
|
2020-02-23 14:17:48 +01:00
|
|
|
|
2021-07-22 19:25:24 +02:00
|
|
|
uint32_t bg = term->conf->csd.color.title_set
|
|
|
|
|
? term->conf->csd.color.title
|
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-19 09:29:06 +01:00
|
|
|
: 0xffu << 24 | term->conf->colors_dark.fg;
|
2021-07-22 19:25:24 +02:00
|
|
|
uint32_t fg = term->conf->csd.color.buttons_set
|
|
|
|
|
? term->conf->csd.color.buttons
|
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-19 09:29:06 +01:00
|
|
|
: term->conf->colors_dark.bg;
|
2020-03-02 18:42:49 +01:00
|
|
|
|
2021-07-22 19:25:24 +02:00
|
|
|
if (!term->visual_focus) {
|
2021-11-03 14:25:38 +01:00
|
|
|
bg = color_dim(term, bg);
|
|
|
|
|
fg = color_dim(term, fg);
|
2020-03-02 18:42:49 +01:00
|
|
|
}
|
2020-02-25 19:09:29 +01:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
char32_t *_title_text = ambstoc32(term->window_title);
|
|
|
|
|
const char32_t *title_text = _title_text != NULL ? _title_text : U"";
|
2021-07-22 19:25:24 +02:00
|
|
|
|
2021-07-22 21:23:59 +02:00
|
|
|
struct wl_window *win = term->window;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
|
|
|
|
|
const struct fcft_glyph *M = fcft_rasterize_char_utf32(
|
|
|
|
|
win->csd.font, U'M', term->font_subpixel);
|
|
|
|
|
|
|
|
|
|
const int margin = M != NULL ? M->advance.x : win->csd.font->max_advance.x;
|
2021-07-22 21:23:59 +02:00
|
|
|
|
2023-07-31 16:29:08 +02:00
|
|
|
render_osd(term, surf, win->csd.font, buf, title_text, fg, bg, margin);
|
2023-06-26 16:53:16 +02:00
|
|
|
csd_commit(term, &surf->surface, buf);
|
2021-07-22 19:25:24 +02:00
|
|
|
free(_title_text);
|
2020-02-29 18:02:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2021-07-18 16:46:43 +02:00
|
|
|
render_csd_border(struct terminal *term, enum csd_surface surf_idx,
|
|
|
|
|
const struct csd_data *info, struct buffer *buf)
|
2020-02-29 18:02:38 +01:00
|
|
|
{
|
2021-06-22 18:58:38 +02:00
|
|
|
xassert(term->window->csd_mode == CSD_YES);
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(surf_idx >= CSD_SURF_LEFT && surf_idx <= CSD_SURF_BOTTOM);
|
2020-02-29 18:02:38 +01:00
|
|
|
|
2023-06-26 16:53:16 +02:00
|
|
|
struct wayl_surface *surf = &term->window->csd.surface[surf_idx].surface;
|
2020-02-29 18:02:38 +01:00
|
|
|
|
2021-07-18 16:46:43 +02:00
|
|
|
if (info->width == 0 || info->height == 0)
|
2020-03-01 13:17:54 +01:00
|
|
|
return;
|
|
|
|
|
|
2025-05-01 08:34:49 +02:00
|
|
|
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
|
2021-10-27 18:27:08 +02:00
|
|
|
{
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
/* Fully transparent - no need to do a color space transform */
|
|
|
|
|
pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0, gamma_correct);
|
2023-06-26 16:53:16 +02:00
|
|
|
render_csd_part(term, surf->surf, buf, info->width, info->height, &color);
|
2021-10-27 18:27:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* The "visible" border.
|
2021-10-27 18:27:08 +02:00
|
|
|
*/
|
|
|
|
|
|
2023-06-22 14:27:37 +02:00
|
|
|
float scale = term->scale;
|
2023-07-25 15:56:30 +02:00
|
|
|
int bwidth = (int)roundf(term->conf->csd.border_width * scale);
|
|
|
|
|
int vwidth = (int)roundf(term->conf->csd.border_width_visible * scale); /* Visible size */
|
2021-10-27 18:27:08 +02:00
|
|
|
|
2021-12-22 20:21:46 +01:00
|
|
|
xassert(bwidth >= vwidth);
|
|
|
|
|
|
2021-10-27 18:27:08 +02:00
|
|
|
if (vwidth > 0) {
|
|
|
|
|
|
|
|
|
|
const struct config *conf = term->conf;
|
|
|
|
|
int x = 0, y = 0, w = 0, h = 0;
|
|
|
|
|
|
render: fix csd border rendering glitch when width > 5px
CSD borders are always *at least* 5px. If url.border-width=0, those
5px are all fully transparent (and act as interactive resize handles).
As csd.border-width increases, the number of transparent pixels
decrease. Once csd.border-width >= 5, the border is fully opaque.
When csd.border-width > 5, then width of the border is (obviously)
more than 5px. But, when rendering the opaque part of the border, we
still used 5px for the invisible part, which caused some pixman
rectangles to have negative x/y coordinates.
This resulted in rendering glitches due to overflows in pixman when
rendering the borders.
The fix is to ensure the total border size is always at least, but
not *always* 5px. That is, set it to max(5, csd.border-width).
This patch also fixes an issue where the CSD borders were not
dimmed (like the titlebar) when the window looses input focus.
Closes #823
2021-11-29 19:23:58 +01:00
|
|
|
|
2021-10-27 18:27:08 +02:00
|
|
|
switch (surf_idx) {
|
|
|
|
|
case CSD_SURF_TOP:
|
|
|
|
|
case CSD_SURF_BOTTOM:
|
|
|
|
|
x = bwidth - vwidth;
|
|
|
|
|
y = surf_idx == CSD_SURF_TOP ? info->height - vwidth : 0;
|
|
|
|
|
w = info->width - 2 * x;
|
|
|
|
|
h = vwidth;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CSD_SURF_LEFT:
|
|
|
|
|
case CSD_SURF_RIGHT:
|
|
|
|
|
x = surf_idx == CSD_SURF_LEFT ? bwidth - vwidth : 0;
|
|
|
|
|
y = 0;
|
|
|
|
|
w = vwidth;
|
|
|
|
|
h = info->height;
|
|
|
|
|
break;
|
|
|
|
|
|
render: fix csd border rendering glitch when width > 5px
CSD borders are always *at least* 5px. If url.border-width=0, those
5px are all fully transparent (and act as interactive resize handles).
As csd.border-width increases, the number of transparent pixels
decrease. Once csd.border-width >= 5, the border is fully opaque.
When csd.border-width > 5, then width of the border is (obviously)
more than 5px. But, when rendering the opaque part of the border, we
still used 5px for the invisible part, which caused some pixman
rectangles to have negative x/y coordinates.
This resulted in rendering glitches due to overflows in pixman when
rendering the borders.
The fix is to ensure the total border size is always at least, but
not *always* 5px. That is, set it to max(5, csd.border-width).
This patch also fixes an issue where the CSD borders were not
dimmed (like the titlebar) when the window looses input focus.
Closes #823
2021-11-29 19:23:58 +01:00
|
|
|
case CSD_SURF_TITLE:
|
|
|
|
|
case CSD_SURF_MINIMIZE:
|
|
|
|
|
case CSD_SURF_MAXIMIZE:
|
|
|
|
|
case CSD_SURF_CLOSE:
|
|
|
|
|
case CSD_SURF_COUNT:
|
|
|
|
|
BUG("unexpected CSD surface type");
|
2021-10-27 18:27:08 +02:00
|
|
|
}
|
|
|
|
|
|
render: fix csd border rendering glitch when width > 5px
CSD borders are always *at least* 5px. If url.border-width=0, those
5px are all fully transparent (and act as interactive resize handles).
As csd.border-width increases, the number of transparent pixels
decrease. Once csd.border-width >= 5, the border is fully opaque.
When csd.border-width > 5, then width of the border is (obviously)
more than 5px. But, when rendering the opaque part of the border, we
still used 5px for the invisible part, which caused some pixman
rectangles to have negative x/y coordinates.
This resulted in rendering glitches due to overflows in pixman when
rendering the borders.
The fix is to ensure the total border size is always at least, but
not *always* 5px. That is, set it to max(5, csd.border-width).
This patch also fixes an issue where the CSD borders were not
dimmed (like the titlebar) when the window looses input focus.
Closes #823
2021-11-29 19:23:58 +01:00
|
|
|
xassert(x >= 0);
|
|
|
|
|
xassert(y >= 0);
|
|
|
|
|
xassert(w >= 0);
|
|
|
|
|
xassert(h >= 0);
|
|
|
|
|
|
|
|
|
|
xassert(x + w <= info->width);
|
|
|
|
|
xassert(y + h <= info->height);
|
|
|
|
|
|
2021-10-27 18:27:08 +02:00
|
|
|
uint32_t _color =
|
|
|
|
|
conf->csd.color.border_set ? conf->csd.color.border :
|
|
|
|
|
conf->csd.color.title_set ? conf->csd.color.title :
|
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-19 09:29:06 +01:00
|
|
|
0xffu << 24 | term->conf->colors_dark.fg;
|
render: fix csd border rendering glitch when width > 5px
CSD borders are always *at least* 5px. If url.border-width=0, those
5px are all fully transparent (and act as interactive resize handles).
As csd.border-width increases, the number of transparent pixels
decrease. Once csd.border-width >= 5, the border is fully opaque.
When csd.border-width > 5, then width of the border is (obviously)
more than 5px. But, when rendering the opaque part of the border, we
still used 5px for the invisible part, which caused some pixman
rectangles to have negative x/y coordinates.
This resulted in rendering glitches due to overflows in pixman when
rendering the borders.
The fix is to ensure the total border size is always at least, but
not *always* 5px. That is, set it to max(5, csd.border-width).
This patch also fixes an issue where the CSD borders were not
dimmed (like the titlebar) when the window looses input focus.
Closes #823
2021-11-29 19:23:58 +01:00
|
|
|
if (!term->visual_focus)
|
|
|
|
|
_color = color_dim(term, _color);
|
|
|
|
|
|
2021-10-27 18:27:08 +02:00
|
|
|
uint16_t alpha = _color >> 24 | (_color >> 24 << 8);
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t color =
|
|
|
|
|
color_hex_to_pixman_with_alpha(_color, alpha, gamma_correct);
|
2021-10-27 18:27:08 +02:00
|
|
|
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &color, 1,
|
|
|
|
|
&(pixman_rectangle16_t){x, y, w, h});
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 21:06:15 +01:00
|
|
|
csd_commit(term, surf, buf);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 10:44:50 +02:00
|
|
|
static pixman_color_t
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
get_csd_button_fg_color(const struct terminal *term)
|
2021-06-20 10:44:50 +02:00
|
|
|
{
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
const struct config *conf = term->conf;
|
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-19 09:29:06 +01:00
|
|
|
uint32_t _color = conf->colors_dark.bg;
|
2021-06-20 10:44:50 +02:00
|
|
|
uint16_t alpha = 0xffff;
|
|
|
|
|
|
|
|
|
|
if (conf->csd.color.buttons_set) {
|
|
|
|
|
_color = conf->csd.color.buttons;
|
|
|
|
|
alpha = _color >> 24 | (_color >> 24 << 8);
|
|
|
|
|
}
|
|
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
return color_hex_to_pixman_with_alpha(
|
2025-05-01 08:34:49 +02:00
|
|
|
_color, alpha, wayl_do_linear_blending(term->wl, term->conf));
|
2021-06-20 10:44:50 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-02 21:06:15 +01:00
|
|
|
static void
|
|
|
|
|
render_csd_button_minimize(struct terminal *term, struct buffer *buf)
|
|
|
|
|
{
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t color = get_csd_button_fg_color(term);
|
2020-03-02 21:06:15 +01:00
|
|
|
pixman_image_t *src = pixman_image_create_solid_fill(&color);
|
|
|
|
|
|
2023-04-25 03:43:36 +00:00
|
|
|
const int max_height = buf->height / 3;
|
|
|
|
|
const int max_width = buf->width / 3;
|
2020-03-06 19:15:09 +01:00
|
|
|
|
2023-04-25 03:43:36 +00:00
|
|
|
int width = min(max_height, max_width);
|
|
|
|
|
int thick = min(width / 2, 1 * term->scale);
|
2020-03-06 19:15:09 +01:00
|
|
|
|
2023-04-25 03:43:36 +00:00
|
|
|
const int x_margin = (buf->width - width) / 2;
|
|
|
|
|
const int y_margin = (buf->height - width) / 2;
|
2020-03-06 19:15:09 +01:00
|
|
|
|
2023-04-25 03:43:36 +00:00
|
|
|
xassert(x_margin + width - thick >= 0);
|
|
|
|
|
xassert(width - 2 * thick >= 0);
|
|
|
|
|
xassert(y_margin + width - thick >= 0);
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &color, 1,
|
|
|
|
|
(pixman_rectangle16_t[]) {
|
|
|
|
|
{x_margin, y_margin + width - thick, width, thick}
|
|
|
|
|
});
|
2020-03-02 21:06:15 +01:00
|
|
|
|
|
|
|
|
pixman_image_unref(src);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2020-03-06 19:15:09 +01:00
|
|
|
render_csd_button_maximize_maximized(
|
|
|
|
|
struct terminal *term, struct buffer *buf)
|
2020-03-02 21:06:15 +01:00
|
|
|
{
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t color = get_csd_button_fg_color(term);
|
2020-03-02 21:06:15 +01:00
|
|
|
pixman_image_t *src = pixman_image_create_solid_fill(&color);
|
|
|
|
|
|
2020-03-06 19:15:09 +01:00
|
|
|
const int max_height = buf->height / 3;
|
|
|
|
|
const int max_width = buf->width / 3;
|
|
|
|
|
|
|
|
|
|
int width = min(max_height, max_width);
|
2021-12-22 20:31:38 +01:00
|
|
|
int thick = min(width / 2, 1 * term->scale);
|
2020-03-06 19:15:09 +01:00
|
|
|
|
|
|
|
|
const int x_margin = (buf->width - width) / 2;
|
|
|
|
|
const int y_margin = (buf->height - width) / 2;
|
2023-04-25 03:43:36 +00:00
|
|
|
const int shrink = 1;
|
2021-12-22 20:31:38 +01:00
|
|
|
xassert(x_margin + width - thick >= 0);
|
|
|
|
|
xassert(width - 2 * thick >= 0);
|
|
|
|
|
xassert(y_margin + width - thick >= 0);
|
|
|
|
|
|
2020-03-02 21:06:15 +01:00
|
|
|
pixman_image_fill_rectangles(
|
2020-06-04 15:39:19 +02:00
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &color, 4,
|
2020-03-06 19:15:09 +01:00
|
|
|
(pixman_rectangle16_t[]){
|
2023-04-25 03:43:36 +00:00
|
|
|
{x_margin + shrink, y_margin + shrink, width - 2 * shrink, thick},
|
|
|
|
|
{ x_margin + shrink, y_margin + thick, thick, width - 2 * thick - shrink },
|
|
|
|
|
{ x_margin + width - thick - shrink, y_margin + thick, thick, width - 2 * thick - shrink },
|
|
|
|
|
{ x_margin + shrink, y_margin + width - thick - shrink, width - 2 * shrink, thick }});
|
2023-10-07 19:37:04 +02:00
|
|
|
|
2020-03-02 21:06:15 +01:00
|
|
|
pixman_image_unref(src);
|
2020-03-06 19:15:09 +01:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
render_csd_button_maximize_window(
|
|
|
|
|
struct terminal *term, struct buffer *buf)
|
|
|
|
|
{
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t color = get_csd_button_fg_color(term);
|
2020-03-06 19:15:09 +01:00
|
|
|
pixman_image_t *src = pixman_image_create_solid_fill(&color);
|
|
|
|
|
|
2023-04-25 03:43:36 +00:00
|
|
|
const int max_height = buf->height / 3;
|
|
|
|
|
const int max_width = buf->width / 3;
|
2020-03-06 19:15:09 +01:00
|
|
|
|
2023-04-25 03:43:36 +00:00
|
|
|
int width = min(max_height, max_width);
|
|
|
|
|
int thick = min(width / 2, 1 * term->scale);
|
2020-03-06 19:15:09 +01:00
|
|
|
|
2023-04-25 03:43:36 +00:00
|
|
|
const int x_margin = (buf->width - width) / 2;
|
|
|
|
|
const int y_margin = (buf->height - width) / 2;
|
2020-03-06 19:15:09 +01:00
|
|
|
|
2023-04-25 03:43:36 +00:00
|
|
|
xassert(x_margin + width - thick >= 0);
|
|
|
|
|
xassert(width - 2 * thick >= 0);
|
|
|
|
|
xassert(y_margin + width - thick >= 0);
|
2020-03-06 19:15:09 +01:00
|
|
|
|
2023-04-25 03:43:36 +00:00
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &color, 4,
|
|
|
|
|
(pixman_rectangle16_t[]) {
|
|
|
|
|
{x_margin, y_margin, width, thick},
|
|
|
|
|
{ x_margin, y_margin + thick, thick, width - 2 * thick },
|
|
|
|
|
{ x_margin + width - thick, y_margin + thick, thick, width - 2 * thick },
|
|
|
|
|
{ x_margin, y_margin + width - thick, width, thick }
|
|
|
|
|
});
|
2020-03-06 19:15:09 +01:00
|
|
|
|
|
|
|
|
pixman_image_unref(src);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
render_csd_button_maximize(struct terminal *term, struct buffer *buf)
|
|
|
|
|
{
|
|
|
|
|
if (term->window->is_maximized)
|
|
|
|
|
render_csd_button_maximize_maximized(term, buf);
|
|
|
|
|
else
|
|
|
|
|
render_csd_button_maximize_window(term, buf);
|
2020-03-02 21:06:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
render_csd_button_close(struct terminal *term, struct buffer *buf)
|
|
|
|
|
{
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t color = get_csd_button_fg_color(term);
|
2020-03-02 21:06:15 +01:00
|
|
|
pixman_image_t *src = pixman_image_create_solid_fill(&color);
|
|
|
|
|
|
2020-03-06 19:15:09 +01:00
|
|
|
const int max_height = buf->height / 3;
|
|
|
|
|
const int max_width = buf->width / 3;
|
|
|
|
|
|
|
|
|
|
int width = min(max_height, max_width);
|
2023-04-25 03:43:36 +00:00
|
|
|
int thick = min(width / 2, 1 * term->scale);
|
2020-03-06 19:15:09 +01:00
|
|
|
const int x_margin = (buf->width - width) / 2;
|
|
|
|
|
const int y_margin = (buf->height - width) / 2;
|
2020-03-02 21:06:15 +01:00
|
|
|
|
2023-04-25 03:43:36 +00:00
|
|
|
xassert(x_margin + width - thick >= 0);
|
|
|
|
|
xassert(width - 2 * thick >= 0);
|
|
|
|
|
xassert(y_margin + width - thick >= 0);
|
|
|
|
|
|
|
|
|
|
pixman_triangle_t tri[4] = {
|
|
|
|
|
{
|
|
|
|
|
.p1 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin + thick),
|
|
|
|
|
},
|
|
|
|
|
.p2 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin + width - thick),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin + width),
|
|
|
|
|
},
|
|
|
|
|
.p3 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin + thick),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
.p1 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin + width),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin + width - thick),
|
|
|
|
|
},
|
|
|
|
|
.p2 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin + thick),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin),
|
|
|
|
|
},
|
|
|
|
|
.p3 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin + width - thick),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin + width),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
.p1 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin + width - thick),
|
|
|
|
|
},
|
|
|
|
|
.p2 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin + width),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin + thick),
|
|
|
|
|
},
|
|
|
|
|
.p3 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin + thick),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin + width),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
.p1 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin + width),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin + thick),
|
|
|
|
|
},
|
|
|
|
|
.p2 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin + width - thick),
|
|
|
|
|
},
|
|
|
|
|
.p3 = {
|
|
|
|
|
.x = pixman_int_to_fixed(x_margin + width - thick),
|
|
|
|
|
.y = pixman_int_to_fixed(y_margin),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pixman_composite_triangles(
|
|
|
|
|
PIXMAN_OP_OVER, src, buf->pix[0], PIXMAN_a1,
|
|
|
|
|
0, 0, 0, 0, 4, tri);
|
2020-03-02 21:06:15 +01:00
|
|
|
|
|
|
|
|
pixman_image_unref(src);
|
2020-02-29 18:02:38 +01:00
|
|
|
}
|
|
|
|
|
|
2024-08-09 08:20:59 +02:00
|
|
|
static bool
|
|
|
|
|
any_pointer_is_on_button(const struct terminal *term, enum csd_surface csd_surface)
|
|
|
|
|
{
|
|
|
|
|
if (unlikely(tll_length(term->wl->seats) == 0))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
tll_foreach(term->wl->seats, it) {
|
|
|
|
|
const struct seat *seat = &it->item;
|
|
|
|
|
|
|
|
|
|
if (seat->mouse.x < 0)
|
|
|
|
|
continue;
|
|
|
|
|
if (seat->mouse.y < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
struct csd_data info = get_csd_data(term, csd_surface);
|
|
|
|
|
if (seat->mouse.x > info.width)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (seat->mouse.y > info.height)
|
|
|
|
|
continue;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-06 19:16:54 +01:00
|
|
|
static void
|
2021-07-18 16:46:43 +02:00
|
|
|
render_csd_button(struct terminal *term, enum csd_surface surf_idx,
|
|
|
|
|
const struct csd_data *info, struct buffer *buf)
|
2020-03-02 20:29:28 +01:00
|
|
|
{
|
2021-06-22 18:58:38 +02:00
|
|
|
xassert(term->window->csd_mode == CSD_YES);
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(surf_idx >= CSD_SURF_MINIMIZE && surf_idx <= CSD_SURF_CLOSE);
|
2020-03-03 18:23:37 +01:00
|
|
|
|
2023-06-26 16:53:16 +02:00
|
|
|
struct wayl_surface *surf = &term->window->csd.surface[surf_idx].surface;
|
2020-03-02 20:29:28 +01:00
|
|
|
|
2021-07-18 16:46:43 +02:00
|
|
|
if (info->width == 0 || info->height == 0)
|
2020-03-02 20:29:28 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
uint32_t _color;
|
|
|
|
|
uint16_t alpha = 0xffff;
|
|
|
|
|
bool is_active = false;
|
2021-02-06 11:12:22 +01:00
|
|
|
bool is_set = false;
|
2020-03-02 20:29:28 +01:00
|
|
|
const uint32_t *conf_color = NULL;
|
|
|
|
|
|
|
|
|
|
switch (surf_idx) {
|
|
|
|
|
case CSD_SURF_MINIMIZE:
|
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-19 09:29:06 +01:00
|
|
|
_color = term->conf->colors_dark.table[4]; /* blue */
|
2021-02-06 11:12:22 +01:00
|
|
|
is_set = term->conf->csd.color.minimize_set;
|
2020-03-02 20:29:28 +01:00
|
|
|
conf_color = &term->conf->csd.color.minimize;
|
2024-08-09 08:20:59 +02:00
|
|
|
is_active = term->active_surface == TERM_SURF_BUTTON_MINIMIZE &&
|
|
|
|
|
any_pointer_is_on_button(term, CSD_SURF_MINIMIZE);
|
2020-03-02 20:29:28 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CSD_SURF_MAXIMIZE:
|
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-19 09:29:06 +01:00
|
|
|
_color = term->conf->colors_dark.table[2]; /* green */
|
2021-02-06 11:12:22 +01:00
|
|
|
is_set = term->conf->csd.color.maximize_set;
|
2020-03-02 20:29:28 +01:00
|
|
|
conf_color = &term->conf->csd.color.maximize;
|
2024-08-09 08:20:59 +02:00
|
|
|
is_active = term->active_surface == TERM_SURF_BUTTON_MAXIMIZE &&
|
|
|
|
|
any_pointer_is_on_button(term, CSD_SURF_MAXIMIZE);
|
2020-03-02 20:29:28 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CSD_SURF_CLOSE:
|
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-19 09:29:06 +01:00
|
|
|
_color = term->conf->colors_dark.table[1]; /* red */
|
2021-02-06 11:12:22 +01:00
|
|
|
is_set = term->conf->csd.color.close_set;
|
2022-02-08 20:12:05 +01:00
|
|
|
conf_color = &term->conf->csd.color.quit;
|
2024-08-09 08:20:59 +02:00
|
|
|
is_active = term->active_surface == TERM_SURF_BUTTON_CLOSE &&
|
|
|
|
|
any_pointer_is_on_button(term, CSD_SURF_CLOSE);
|
2020-03-02 20:29:28 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
2021-02-10 09:01:51 +00:00
|
|
|
BUG("unhandled surface type: %u", (unsigned)surf_idx);
|
2020-03-02 20:29:28 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_active) {
|
2021-02-06 11:12:22 +01:00
|
|
|
if (is_set) {
|
2020-03-02 20:29:28 +01:00
|
|
|
_color = *conf_color;
|
|
|
|
|
alpha = _color >> 24 | (_color >> 24 << 8);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
_color = 0;
|
|
|
|
|
alpha = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!term->visual_focus)
|
2021-11-03 14:25:38 +01:00
|
|
|
_color = color_dim(term, _color);
|
2020-11-15 20:05:01 +01:00
|
|
|
|
2025-05-01 08:34:49 +02:00
|
|
|
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha, gamma_correct);
|
2023-06-26 16:53:16 +02:00
|
|
|
render_csd_part(term, surf->surf, buf, info->width, info->height, &color);
|
2020-03-02 21:06:15 +01:00
|
|
|
|
|
|
|
|
switch (surf_idx) {
|
|
|
|
|
case CSD_SURF_MINIMIZE: render_csd_button_minimize(term, buf); break;
|
|
|
|
|
case CSD_SURF_MAXIMIZE: render_csd_button_maximize(term, buf); break;
|
|
|
|
|
case CSD_SURF_CLOSE: render_csd_button_close(term, buf); break;
|
|
|
|
|
|
|
|
|
|
default:
|
2021-02-09 15:16:19 +00:00
|
|
|
BUG("unhandled surface type: %u", (unsigned)surf_idx);
|
2020-03-02 21:06:15 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
csd_commit(term, surf, buf);
|
2020-03-02 20:29:28 +01:00
|
|
|
}
|
|
|
|
|
|
2020-03-06 19:16:54 +01:00
|
|
|
static void
|
2020-02-29 18:02:38 +01:00
|
|
|
render_csd(struct terminal *term)
|
|
|
|
|
{
|
2021-06-22 18:58:38 +02:00
|
|
|
xassert(term->window->csd_mode == CSD_YES);
|
2020-03-03 18:23:37 +01:00
|
|
|
|
2020-03-03 18:23:52 +01:00
|
|
|
if (term->window->is_fullscreen)
|
|
|
|
|
return;
|
|
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
const float scale = term->scale;
|
2021-07-18 16:46:43 +02:00
|
|
|
struct csd_data infos[CSD_SURF_COUNT];
|
|
|
|
|
int widths[CSD_SURF_COUNT];
|
|
|
|
|
int heights[CSD_SURF_COUNT];
|
|
|
|
|
|
2020-02-29 18:02:38 +01:00
|
|
|
for (size_t i = 0; i < CSD_SURF_COUNT; i++) {
|
2021-07-18 16:46:43 +02:00
|
|
|
infos[i] = get_csd_data(term, i);
|
|
|
|
|
const int x = infos[i].x;
|
|
|
|
|
const int y = infos[i].y;
|
|
|
|
|
const int width = infos[i].width;
|
|
|
|
|
const int height = infos[i].height;
|
2020-02-29 18:02:38 +01:00
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
struct wl_surface *surf = term->window->csd.surface[i].surface.surf;
|
2021-02-12 11:39:25 +01:00
|
|
|
struct wl_subsurface *sub = term->window->csd.surface[i].sub;
|
2020-02-29 18:02:38 +01:00
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(surf != NULL);
|
|
|
|
|
xassert(sub != NULL);
|
2020-03-03 18:24:31 +01:00
|
|
|
|
2020-02-29 18:02:38 +01:00
|
|
|
if (width == 0 || height == 0) {
|
2021-07-18 16:46:43 +02:00
|
|
|
widths[i] = heights[i] = 0;
|
2020-02-29 18:02:38 +01:00
|
|
|
wl_subsurface_set_position(sub, 0, 0);
|
|
|
|
|
wl_surface_attach(surf, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(surf);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-18 16:46:43 +02:00
|
|
|
widths[i] = width;
|
|
|
|
|
heights[i] = height;
|
2023-07-28 15:28:10 +02:00
|
|
|
wl_subsurface_set_position(sub, roundf(x / scale), roundf(y / scale));
|
2020-02-26 12:17:58 +01:00
|
|
|
}
|
2020-02-29 18:02:38 +01:00
|
|
|
|
2021-07-18 16:46:43 +02:00
|
|
|
struct buffer *bufs[CSD_SURF_COUNT];
|
2026-01-04 07:57:25 +01:00
|
|
|
shm_get_many(term->render.chains.csd, CSD_SURF_COUNT, widths, heights, bufs);
|
2021-07-18 16:46:43 +02:00
|
|
|
|
2020-02-29 18:02:38 +01:00
|
|
|
for (size_t i = CSD_SURF_LEFT; i <= CSD_SURF_BOTTOM; i++)
|
2021-07-18 16:46:43 +02:00
|
|
|
render_csd_border(term, i, &infos[i], bufs[i]);
|
2020-03-03 18:24:51 +01:00
|
|
|
for (size_t i = CSD_SURF_MINIMIZE; i <= CSD_SURF_CLOSE; i++)
|
2021-07-18 16:46:43 +02:00
|
|
|
render_csd_button(term, i, &infos[i], bufs[i]);
|
|
|
|
|
render_csd_title(term, &infos[CSD_SURF_TITLE], bufs[CSD_SURF_TITLE]);
|
2020-02-23 14:17:48 +01:00
|
|
|
}
|
|
|
|
|
|
2020-07-24 17:51:40 +02:00
|
|
|
static void
|
|
|
|
|
render_scrollback_position(struct terminal *term)
|
|
|
|
|
{
|
2020-07-27 20:02:51 +02:00
|
|
|
if (term->conf->scrollback.indicator.position == SCROLLBACK_INDICATOR_POSITION_NONE)
|
2020-07-24 17:51:40 +02:00
|
|
|
return;
|
|
|
|
|
|
2020-07-26 10:01:26 +02:00
|
|
|
struct wl_window *win = term->window;
|
|
|
|
|
|
|
|
|
|
if (term->grid->view == term->grid->offset) {
|
2025-06-09 07:08:24 +02:00
|
|
|
if (win->scrollback_indicator.surface.surf != NULL)
|
2021-02-12 12:00:40 +01:00
|
|
|
wayl_win_subsurface_destroy(&win->scrollback_indicator);
|
2020-07-26 10:01:26 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
if (win->scrollback_indicator.surface.surf == NULL) {
|
2022-04-16 17:41:14 +02:00
|
|
|
if (!wayl_win_subsurface_new(
|
|
|
|
|
win, &win->scrollback_indicator, false))
|
|
|
|
|
{
|
2020-07-26 10:01:26 +02:00
|
|
|
LOG_ERR("failed to create scrollback indicator surface");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
xassert(win->scrollback_indicator.surface.surf != NULL);
|
2021-02-12 12:00:40 +01:00
|
|
|
xassert(win->scrollback_indicator.sub != NULL);
|
2020-07-26 10:01:26 +02:00
|
|
|
|
2020-07-24 17:51:40 +02:00
|
|
|
/* Find absolute row number of the scrollback start */
|
|
|
|
|
int scrollback_start = term->grid->offset + term->rows;
|
2020-08-25 18:39:50 +02:00
|
|
|
int empty_rows = 0;
|
|
|
|
|
while (term->grid->rows[scrollback_start & (term->grid->num_rows - 1)] == NULL) {
|
2020-07-24 17:51:40 +02:00
|
|
|
scrollback_start++;
|
2020-08-25 18:39:50 +02:00
|
|
|
empty_rows++;
|
|
|
|
|
}
|
2020-07-24 17:51:40 +02:00
|
|
|
|
|
|
|
|
/* Rebase viewport against scrollback start (so that 0 is at
|
|
|
|
|
* the beginning of the scrollback) */
|
|
|
|
|
int rebased_view = term->grid->view - scrollback_start + term->grid->num_rows;
|
|
|
|
|
rebased_view &= term->grid->num_rows - 1;
|
|
|
|
|
|
2020-08-25 18:39:50 +02:00
|
|
|
/* How much of the scrollback is actually used? */
|
|
|
|
|
int populated_rows = term->grid->num_rows - empty_rows;
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(populated_rows > 0);
|
|
|
|
|
xassert(populated_rows <= term->grid->num_rows);
|
2020-08-25 18:39:50 +02:00
|
|
|
|
2020-07-24 17:51:40 +02:00
|
|
|
/*
|
|
|
|
|
* How far down in the scrollback we are.
|
|
|
|
|
*
|
|
|
|
|
* 0% -> at the beginning of the scrollback
|
|
|
|
|
* 100% -> at the bottom, i.e. where new lines are inserted
|
|
|
|
|
*/
|
2020-07-27 16:39:08 +02:00
|
|
|
double percent =
|
2020-08-25 18:39:50 +02:00
|
|
|
rebased_view + term->rows == populated_rows
|
2020-07-27 16:39:08 +02:00
|
|
|
? 1.0
|
2020-08-26 19:10:00 +02:00
|
|
|
: (double)rebased_view / (populated_rows - term->rows);
|
2020-07-24 17:51:40 +02:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
char32_t _text[64];
|
|
|
|
|
const char32_t *text = _text;
|
2020-07-29 07:25:56 +02:00
|
|
|
int cell_count = 0;
|
2020-07-26 10:18:05 +02:00
|
|
|
|
|
|
|
|
/* *What* to render */
|
|
|
|
|
switch (term->conf->scrollback.indicator.format) {
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
case SCROLLBACK_INDICATOR_FORMAT_PERCENTAGE: {
|
|
|
|
|
char percent_str[8];
|
|
|
|
|
snprintf(percent_str, sizeof(percent_str), "%u%%", (int)(100 * percent));
|
|
|
|
|
mbstoc32(_text, percent_str, ALEN(_text));
|
2020-07-26 10:18:05 +02:00
|
|
|
cell_count = 3;
|
|
|
|
|
break;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
}
|
2020-07-26 10:18:05 +02:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
case SCROLLBACK_INDICATOR_FORMAT_LINENO: {
|
|
|
|
|
char lineno_str[64];
|
|
|
|
|
snprintf(lineno_str, sizeof(lineno_str), "%d", rebased_view + 1);
|
|
|
|
|
mbstoc32(_text, lineno_str, ALEN(_text));
|
2023-07-25 15:56:30 +02:00
|
|
|
cell_count = (int)ceilf(log10f(term->grid->num_rows));
|
2020-07-26 10:18:05 +02:00
|
|
|
break;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
}
|
2020-07-28 19:56:53 +02:00
|
|
|
|
|
|
|
|
case SCROLLBACK_INDICATOR_FORMAT_TEXT:
|
|
|
|
|
text = term->conf->scrollback.indicator.text;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
cell_count = c32len(text);
|
2020-07-28 19:56:53 +02:00
|
|
|
break;
|
2020-07-26 10:18:05 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
const float scale = term->scale;
|
|
|
|
|
const int margin = (int)roundf(3. * scale);
|
2023-07-25 15:56:30 +02:00
|
|
|
|
|
|
|
|
int width = margin + cell_count * term->cell_width + margin;
|
|
|
|
|
int height = margin + term->cell_height + margin;
|
2021-03-24 20:52:58 +01:00
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
width = roundf(scale * ceilf(width / scale));
|
|
|
|
|
height = roundf(scale * ceilf(height / scale));
|
2020-07-26 10:01:26 +02:00
|
|
|
|
2020-07-26 10:18:05 +02:00
|
|
|
/* *Where* to render - parent relative coordinates */
|
|
|
|
|
int surf_top = 0;
|
2020-07-27 20:02:51 +02:00
|
|
|
switch (term->conf->scrollback.indicator.position) {
|
|
|
|
|
case SCROLLBACK_INDICATOR_POSITION_NONE:
|
2021-02-10 09:01:51 +00:00
|
|
|
BUG("Invalid scrollback indicator position type");
|
2020-07-26 10:18:05 +02:00
|
|
|
return;
|
|
|
|
|
|
2020-07-27 20:02:51 +02:00
|
|
|
case SCROLLBACK_INDICATOR_POSITION_FIXED:
|
2020-07-26 12:37:57 +02:00
|
|
|
surf_top = term->cell_height - margin;
|
2020-07-26 10:18:05 +02:00
|
|
|
break;
|
|
|
|
|
|
2020-07-27 20:02:51 +02:00
|
|
|
case SCROLLBACK_INDICATOR_POSITION_RELATIVE: {
|
2020-08-26 19:12:12 +02:00
|
|
|
int lines = term->rows - 2; /* Avoid using first and last rows */
|
|
|
|
|
if (term->is_searching) {
|
|
|
|
|
/* Make sure we don't collide with the scrollback search box */
|
|
|
|
|
lines--;
|
|
|
|
|
}
|
2020-07-26 10:18:05 +02:00
|
|
|
|
2021-07-15 18:26:26 +02:00
|
|
|
lines = max(lines, 0);
|
|
|
|
|
|
|
|
|
|
int pixels = max(lines * term->cell_height - height + 2 * margin, 0);
|
2020-07-27 16:39:08 +02:00
|
|
|
surf_top = term->cell_height - margin + (int)(percent * pixels);
|
2020-07-26 10:18:05 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-25 15:56:30 +02:00
|
|
|
int x = term->width - margin - width;
|
|
|
|
|
int y = term->margins.top + surf_top;
|
|
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
x = roundf(scale * ceilf(x / scale));
|
|
|
|
|
y = roundf(scale * ceilf(y / scale));
|
2021-07-15 18:26:26 +02:00
|
|
|
|
|
|
|
|
if (y + height > term->height) {
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_attach(win->scrollback_indicator.surface.surf, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(win->scrollback_indicator.surface.surf);
|
2021-07-15 18:26:26 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
struct buffer_chain *chain = term->render.chains.scrollback_indicator;
|
2026-01-04 07:57:25 +01:00
|
|
|
struct buffer *buf = shm_get_buffer(chain, width, height);
|
2021-07-15 18:26:26 +02:00
|
|
|
|
2020-07-26 10:01:26 +02:00
|
|
|
wl_subsurface_set_position(
|
2023-07-28 15:28:10 +02:00
|
|
|
win->scrollback_indicator.sub, roundf(x / scale), roundf(y / scale));
|
2020-07-26 10:01:26 +02:00
|
|
|
|
2021-10-20 20:03:15 +02:00
|
|
|
uint32_t fg = term->colors.table[0];
|
|
|
|
|
uint32_t bg = term->colors.table[8 + 4];
|
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-19 09:29:06 +01:00
|
|
|
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;
|
2021-10-20 20:03:15 +02:00
|
|
|
}
|
2021-09-27 19:05:40 +00:00
|
|
|
|
2020-08-13 18:35:17 +02:00
|
|
|
render_osd(
|
|
|
|
|
term,
|
2023-06-26 16:53:16 +02:00
|
|
|
&win->scrollback_indicator,
|
2021-07-22 21:23:29 +02:00
|
|
|
term->fonts[0], buf, text,
|
2021-09-27 19:05:40 +00:00
|
|
|
fg, 0xffu << 24 | bg,
|
2023-07-31 16:29:08 +02:00
|
|
|
width - margin - c32len(text) * term->cell_width);
|
2020-08-13 18:35:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2022-01-15 14:56:13 +05:30
|
|
|
render_render_timer(struct terminal *term, struct timespec render_time)
|
2020-08-13 18:35:17 +02:00
|
|
|
{
|
|
|
|
|
struct wl_window *win = term->window;
|
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
char usecs_str[256];
|
2022-01-15 14:56:13 +05:30
|
|
|
double usecs = render_time.tv_sec * 1000000 + render_time.tv_nsec / 1000.0;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
snprintf(usecs_str, sizeof(usecs_str), "%.2f µs", usecs);
|
|
|
|
|
|
|
|
|
|
char32_t text[256];
|
|
|
|
|
mbstoc32(text, usecs_str, ALEN(text));
|
2020-08-13 18:35:17 +02:00
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
const float scale = term->scale;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const int cell_count = c32len(text);
|
2023-07-28 15:28:10 +02:00
|
|
|
const int margin = (int)roundf(3. * scale);
|
2023-07-25 15:56:30 +02:00
|
|
|
|
|
|
|
|
int width = margin + cell_count * term->cell_width + margin;
|
|
|
|
|
int height = margin + term->cell_height + margin;
|
|
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
width = roundf(scale * ceilf(width / scale));
|
|
|
|
|
height = roundf(scale * ceilf(height / scale));
|
2020-08-13 18:35:17 +02:00
|
|
|
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
struct buffer_chain *chain = term->render.chains.render_timer;
|
2026-01-04 07:57:25 +01:00
|
|
|
struct buffer *buf = shm_get_buffer(chain, width, height);
|
2020-08-13 18:35:17 +02:00
|
|
|
|
2020-08-14 07:48:40 +02:00
|
|
|
wl_subsurface_set_position(
|
2021-02-12 12:00:40 +01:00
|
|
|
win->render_timer.sub,
|
2023-07-28 15:28:10 +02:00
|
|
|
roundf(margin / scale),
|
|
|
|
|
roundf((term->margins.top + term->cell_height - margin) / scale));
|
2020-08-14 07:48:40 +02:00
|
|
|
|
2020-08-13 18:35:17 +02:00
|
|
|
render_osd(
|
|
|
|
|
term,
|
2023-06-26 16:53:16 +02:00
|
|
|
&win->render_timer,
|
2021-07-22 21:23:29 +02:00
|
|
|
term->fonts[0], buf, text,
|
2021-07-22 19:24:20 +02:00
|
|
|
term->colors.table[0], 0xffu << 24 | term->colors.table[8 + 1],
|
2023-07-31 16:29:08 +02:00
|
|
|
margin);
|
2020-07-24 17:51:40 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
static void frame_callback(
|
|
|
|
|
void *data, struct wl_callback *wl_callback, uint32_t callback_data);
|
|
|
|
|
|
|
|
|
|
static const struct wl_callback_listener frame_listener = {
|
|
|
|
|
.done = &frame_callback,
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-04 22:01:19 +01:00
|
|
|
static void
|
2021-05-08 10:25:14 +02:00
|
|
|
force_full_repaint(struct terminal *term, struct buffer *buf)
|
2019-07-05 10:16:56 +02:00
|
|
|
{
|
2021-05-08 10:25:14 +02:00
|
|
|
tll_free(term->grid->scroll_damage);
|
|
|
|
|
render_margin(term, buf, 0, term->rows, true);
|
|
|
|
|
term_damage_view(term);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old)
|
|
|
|
|
{
|
|
|
|
|
if (new->age > 1) {
|
2021-07-15 22:30:08 +02:00
|
|
|
memcpy(new->data, old->data, new->height * new->stride);
|
2019-12-19 07:27:14 +01:00
|
|
|
return;
|
2021-05-08 10:25:14 +02:00
|
|
|
}
|
2019-12-19 07:27:14 +01:00
|
|
|
|
2022-09-22 18:34:41 +02:00
|
|
|
pixman_region32_t dirty;
|
|
|
|
|
pixman_region32_init(&dirty);
|
|
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Figure out current frame's damage region
|
2022-09-22 18:34:41 +02:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* If current frame doesn't have any scroll damage, we can simply
|
|
|
|
|
* subtract this frame's damage from the last frame's damage. That
|
|
|
|
|
* way, we don't have to copy areas from the old frame that'll
|
2022-09-22 18:34:41 +02:00
|
|
|
* just get overwritten by current frame.
|
2021-05-08 10:25:14 +02:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Note that this is row based. A "half damaged" row is not
|
2022-09-22 18:34:41 +02:00
|
|
|
* excluded. I.e. the entire row will be copied from the old frame
|
|
|
|
|
* to the new, and then when actually rendering the new frame, the
|
|
|
|
|
* updated cells will overwrite parts of the copied row.
|
2021-05-08 10:25:14 +02:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Since we're scanning the entire viewport anyway, we also track
|
2022-09-22 18:34:41 +02:00
|
|
|
* whether *all* cells are to be updated. In this case, just force
|
2024-02-06 12:36:45 +01:00
|
|
|
* a full re-rendering, and don't copy anything from the old
|
2022-09-22 18:34:41 +02:00
|
|
|
* frame.
|
2021-05-08 10:25:14 +02:00
|
|
|
*/
|
|
|
|
|
bool full_repaint_needed = true;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
const struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
|
2024-09-20 17:14:59 +02:00
|
|
|
if (!row->dirty) {
|
|
|
|
|
full_repaint_needed = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
bool row_all_dirty = true;
|
|
|
|
|
for (int c = 0; c < term->cols; c++) {
|
|
|
|
|
if (row->cells[c].attrs.clean) {
|
|
|
|
|
row_all_dirty = false;
|
|
|
|
|
full_repaint_needed = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (row_all_dirty) {
|
|
|
|
|
pixman_region32_union_rect(
|
|
|
|
|
&dirty, &dirty,
|
|
|
|
|
term->margins.left,
|
|
|
|
|
term->margins.top + r * term->cell_height,
|
|
|
|
|
term->width - term->margins.left - term->margins.right,
|
|
|
|
|
term->cell_height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (full_repaint_needed) {
|
|
|
|
|
force_full_repaint(term, new);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-22 18:34:41 +02:00
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* TODO: re-apply last frame's scroll damage
|
2022-09-22 18:34:41 +02:00
|
|
|
*
|
|
|
|
|
* We used to do this, but it turned out to be buggy. If we decide
|
2024-02-06 12:36:45 +01:00
|
|
|
* to re-add it, this is where to do it. Note that we'd also have
|
2022-09-22 18:34:41 +02:00
|
|
|
* to remove the updates to buf->dirty from grid_render_scroll()
|
|
|
|
|
* and grid_render_scroll_reverse().
|
|
|
|
|
*/
|
2019-11-02 00:33:37 +01:00
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
if (tll_length(term->grid->scroll_damage) == 0) {
|
2022-09-22 18:34:41 +02:00
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* We can only subtract current frame's damage from the old
|
|
|
|
|
* frame's if we don't have any scroll damage.
|
2022-09-22 18:34:41 +02:00
|
|
|
*
|
|
|
|
|
* If we do have scroll damage, the damage region we
|
|
|
|
|
* calculated above is not (yet) valid - we need to apply the
|
2024-02-06 12:36:45 +01:00
|
|
|
* current frame's scroll damage *first*. This is done later,
|
2022-09-22 18:34:41 +02:00
|
|
|
* when rendering the frame.
|
|
|
|
|
*/
|
2023-10-07 16:23:09 +02:00
|
|
|
pixman_region32_subtract(&dirty, &old->dirty[0], &dirty);
|
2021-05-08 10:25:14 +02:00
|
|
|
pixman_image_set_clip_region32(new->pix[0], &dirty);
|
2022-09-22 18:34:41 +02:00
|
|
|
} else {
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Copy *all* of last frame's damaged areas */
|
2023-10-07 16:23:09 +02:00
|
|
|
pixman_image_set_clip_region32(new->pix[0], &old->dirty[0]);
|
2022-09-22 18:34:41 +02:00
|
|
|
}
|
2021-05-08 10:25:14 +02:00
|
|
|
|
|
|
|
|
pixman_image_composite32(
|
|
|
|
|
PIXMAN_OP_SRC, old->pix[0], NULL, new->pix[0],
|
|
|
|
|
0, 0, 0, 0, 0, 0, term->width, term->height);
|
|
|
|
|
|
|
|
|
|
pixman_image_set_clip_region32(new->pix[0], NULL);
|
|
|
|
|
pixman_region32_fini(&dirty);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dirty_old_cursor(struct terminal *term)
|
|
|
|
|
{
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
if (term->render.last_cursor.row != NULL && !term->render.last_cursor.hidden) {
|
|
|
|
|
struct row *row = term->render.last_cursor.row;
|
|
|
|
|
struct cell *cell = &row->cells[term->render.last_cursor.col];
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Remember current cursor position, for the next frame */
|
|
|
|
|
term->render.last_cursor.row = grid_row(term->grid, term->grid->cursor.point.row);
|
|
|
|
|
term->render.last_cursor.col = term->grid->cursor.point.col;
|
|
|
|
|
term->render.last_cursor.hidden = term->hide_cursor;
|
2021-05-08 10:25:14 +02:00
|
|
|
}
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
static void
|
|
|
|
|
dirty_cursor(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
if (term->hide_cursor)
|
|
|
|
|
return;
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
const struct coord *cursor = &term->grid->cursor.point;
|
2021-05-07 18:18:35 +02:00
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
struct row *row = grid_row(term->grid, cursor->row);
|
|
|
|
|
struct cell *cell = &row->cells[cursor->col];
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
}
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
static void
|
|
|
|
|
grid_render(struct terminal *term)
|
|
|
|
|
{
|
2021-07-31 19:08:51 +02:00
|
|
|
if (term->shutdown.in_progress)
|
2021-05-08 10:25:14 +02:00
|
|
|
return;
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
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-05 10:48:36 +02:00
|
|
|
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);
|
|
|
|
|
wait_for_preapply_damage(term);
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &stop_wait_preapply);
|
|
|
|
|
}
|
2022-01-13 12:08:20 +01:00
|
|
|
|
|
|
|
|
if (term->conf->tweak.render_timer != RENDER_TIMER_NONE)
|
2022-01-15 14:56:13 +05:30
|
|
|
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
2021-05-08 09:18:45 +02:00
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
xassert(term->width > 0);
|
|
|
|
|
xassert(term->height > 0);
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
struct buffer_chain *chain = term->render.chains.grid;
|
2026-01-04 07:57:25 +01:00
|
|
|
struct buffer *buf = shm_get_buffer(chain, term->width, term->height);
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Dirty old and current cursor cell, to ensure they're repainted */
|
2021-05-08 10:25:14 +02:00
|
|
|
dirty_old_cursor(term);
|
|
|
|
|
dirty_cursor(term);
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
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-05 10:48:36 +02:00
|
|
|
LOG_DBG("buffer age: %u (%p)", buf->age, (void *)buf);
|
|
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
if (term->render.last_buf == NULL ||
|
2021-05-10 17:56:35 +02:00
|
|
|
term->render.last_buf->width != buf->width ||
|
|
|
|
|
term->render.last_buf->height != buf->height ||
|
2021-05-08 10:25:14 +02:00
|
|
|
term->render.margins)
|
|
|
|
|
{
|
|
|
|
|
force_full_repaint(term, buf);
|
|
|
|
|
}
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
else if (buf->age > 0) {
|
2021-07-16 16:47:15 +02:00
|
|
|
LOG_DBG("buffer age: %u (%p)", buf->age, (void *)buf);
|
2021-05-10 17:56:35 +02:00
|
|
|
|
|
|
|
|
xassert(term->render.last_buf != NULL);
|
2021-05-08 10:25:14 +02:00
|
|
|
xassert(term->render.last_buf != buf);
|
2021-05-10 17:56:35 +02:00
|
|
|
xassert(term->render.last_buf->width == buf->width);
|
|
|
|
|
xassert(term->render.last_buf->height == buf->height);
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
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-05 10:48:36 +02:00
|
|
|
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;
|
2025-10-16 08:45:07 +02:00
|
|
|
} else if (!have_warned && !term->render.preapply_last_frame_damage) {
|
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-05 10:48:36 +02:00
|
|
|
LOG_WARN("compositor is not releasing buffers immediately; "
|
|
|
|
|
"expect lower rendering performance");
|
|
|
|
|
have_warned = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-15 14:56:13 +05:30
|
|
|
clock_gettime(CLOCK_MONOTONIC, &start_double_buffering);
|
2021-05-10 17:56:35 +02:00
|
|
|
reapply_old_damage(term, buf, term->render.last_buf);
|
2022-01-15 14:56:13 +05:30
|
|
|
clock_gettime(CLOCK_MONOTONIC, &stop_double_buffering);
|
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-05 10:48:36 +02:00
|
|
|
} else if (!term->render.preapply_last_frame_damage) {
|
|
|
|
|
term->render.frames_since_last_immediate_release = 0;
|
2021-05-08 10:25:14 +02:00
|
|
|
}
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
if (term->render.last_buf != NULL) {
|
2021-07-16 16:47:57 +02:00
|
|
|
shm_unref(term->render.last_buf);
|
|
|
|
|
term->render.last_buf = NULL;
|
2021-05-08 10:25:14 +02:00
|
|
|
}
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
2021-05-08 10:25:14 +02:00
|
|
|
term->render.last_buf = buf;
|
2021-07-16 16:47:57 +02:00
|
|
|
shm_addref(buf);
|
2021-05-08 10:25:14 +02:00
|
|
|
buf->age = 0;
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
|
render: wip: re-apply last frame’s damage when forced to double buffer
When we are forced to swap between two buffers, re-apply the old
frame’s damage to the current buffer, before applying the current
frame’s damage.
First, while applying this frame’s scroll damage, copy it to the
buffer’s scroll damage list (so that we can access it via
term->render.last_buf).
Also, when iterating and rendering the grid, build a pixman region of
the damaged regions. This is currently done on a per-row basis. This
is also stored in the buffer.
Now, when being forced to double buffer, first iterate the old
buffer’s damage, and re-apply it to the current buffer. Then,
composite the old buffer on top of the current buffer, using the old
frame’s damage region as clip region. This effectively copies
everything that was rendered to the last frame. Remember, this is on a
per-row basis.
Then we go on and render the frame as usual.
Note that it would be _really_ nice if we could subtract the current
frame’s damage region from the clip region (no point in copying areas
we’re going to overwrite anyway). Unfortunately, that’s harder than it
looks; the current frame’s damage region is only valid *after* this
frame’s scroll damage have been applied, while the last frame’s damage
region is only valid *before* it’s been applied.
Translating one to the other isn’t easy, since scroll damage isn’t
just about counting lines - there may be multiple scroll damage
records, each with its own scrolling region. This creates very complex
scenarios.
2021-05-07 20:21:27 +02:00
|
|
|
|
2022-09-22 18:34:41 +02:00
|
|
|
tll_foreach(term->grid->scroll_damage, it) {
|
|
|
|
|
switch (it->item.type) {
|
|
|
|
|
case DAMAGE_SCROLL:
|
|
|
|
|
if (term->grid->view == term->grid->offset)
|
2019-10-28 17:58:44 +01:00
|
|
|
grid_render_scroll(term, buf, &it->item);
|
2022-09-22 18:34:41 +02:00
|
|
|
break;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2022-09-22 18:34:41 +02:00
|
|
|
case DAMAGE_SCROLL_REVERSE:
|
|
|
|
|
if (term->grid->view == term->grid->offset)
|
2019-10-28 17:58:44 +01:00
|
|
|
grid_render_scroll_reverse(term, buf, &it->item);
|
2022-09-22 18:34:41 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DAMAGE_SCROLL_IN_VIEW:
|
|
|
|
|
grid_render_scroll(term, buf, &it->item);
|
|
|
|
|
break;
|
2019-10-29 21:09:37 +01:00
|
|
|
|
2022-09-22 18:34:41 +02:00
|
|
|
case DAMAGE_SCROLL_REVERSE_IN_VIEW:
|
|
|
|
|
grid_render_scroll_reverse(term, buf, &it->item);
|
|
|
|
|
break;
|
2019-10-28 17:58:44 +01:00
|
|
|
}
|
2022-09-22 18:34:41 +02:00
|
|
|
|
|
|
|
|
tll_remove(term->grid->scroll_damage, it);
|
2019-10-29 21:09:37 +01:00
|
|
|
}
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2020-05-16 21:36:08 +02:00
|
|
|
/*
|
|
|
|
|
* Ensure selected cells have their 'selected' bit set. This is
|
|
|
|
|
* normally "automatically" true - the bit is set when the
|
|
|
|
|
* selection is made.
|
|
|
|
|
*
|
|
|
|
|
* However, if the cell is updated (printed to) while the
|
|
|
|
|
* selection is active, the 'selected' bit is cleared. Checking
|
|
|
|
|
* for this and re-setting the bit in term_print() is too
|
|
|
|
|
* expensive performance wise.
|
|
|
|
|
*
|
|
|
|
|
* Instead, we synchronize the selection bits here and now. This
|
|
|
|
|
* makes the performance impact linear to the number of selected
|
|
|
|
|
* cells rather than to the number of updated cells.
|
|
|
|
|
*
|
|
|
|
|
* (note that selection_dirty_cells() will not set the dirty flag
|
|
|
|
|
* on cells where the 'selected' bit is already set)
|
|
|
|
|
*/
|
|
|
|
|
selection_dirty_cells(term);
|
|
|
|
|
|
2020-07-13 13:44:52 +02:00
|
|
|
/* Translate offset-relative row to view-relative, unless cursor
|
|
|
|
|
* is hidden, then we just set it to -1 */
|
|
|
|
|
struct coord cursor = {-1, -1};
|
|
|
|
|
if (!term->hide_cursor) {
|
|
|
|
|
cursor = term->grid->cursor.point;
|
|
|
|
|
cursor.row += term->grid->offset;
|
|
|
|
|
cursor.row -= term->grid->view;
|
|
|
|
|
cursor.row &= term->grid->num_rows - 1;
|
|
|
|
|
}
|
2019-07-29 20:13:26 +02:00
|
|
|
|
render: run the “overflowing glyphs” prepass *before* rendering sixels
This fixes an issue where the left-most column of a sixel was
“overwritten” by the cell content.
This patch also rewrites the prepass logic, to try to reduce the
number of loads performed.
The new logic loops each row from left to right, looking for dirty
cells. When a dirty cell is found, we first scan backwards, until we
find a non-overflowing cell. That cell is unaffected by the
overflowing cell we’re currently dealing with.
We can also stop as soon as we see a dirty cell, since that cell will
already have been dealt with.
Then, we scan forward, dirtying cells until we see a non-overflowing
cell. That first non-overflowing cell is also dirtied, but after that
we break.
The last loop, that scans forward, advances the same cell pointer used
in the outer loop.
2021-08-10 18:33:18 +02:00
|
|
|
if (term->conf->tweak.overflowing_glyphs) {
|
|
|
|
|
/*
|
|
|
|
|
* Pre-pass to dirty cells affected by overflowing glyphs.
|
|
|
|
|
*
|
|
|
|
|
* Given any two pair of cells where the first cell is
|
|
|
|
|
* overflowing into the second, *both* cells must be
|
|
|
|
|
* re-rendered if any one of them is dirty.
|
|
|
|
|
*
|
|
|
|
|
* Thus, given a string of overflowing glyphs, with a single
|
|
|
|
|
* dirty cell in the middle, we need to re-render the entire
|
|
|
|
|
* string.
|
|
|
|
|
*/
|
|
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
|
|
|
|
|
if (!row->dirty)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* Loop row from left to right, looking for dirty cells */
|
|
|
|
|
for (struct cell *cell = &row->cells[0];
|
|
|
|
|
cell < &row->cells[term->cols];
|
|
|
|
|
cell++)
|
|
|
|
|
{
|
|
|
|
|
if (cell->attrs.clean)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Cell is dirty, go back and dirty previous cells, if
|
|
|
|
|
* they are overflowing.
|
|
|
|
|
*
|
|
|
|
|
* As soon as we see a non-overflowing cell we can
|
2024-02-06 12:36:45 +01:00
|
|
|
* stop, since it isn't affecting the string of
|
render: run the “overflowing glyphs” prepass *before* rendering sixels
This fixes an issue where the left-most column of a sixel was
“overwritten” by the cell content.
This patch also rewrites the prepass logic, to try to reduce the
number of loads performed.
The new logic loops each row from left to right, looking for dirty
cells. When a dirty cell is found, we first scan backwards, until we
find a non-overflowing cell. That cell is unaffected by the
overflowing cell we’re currently dealing with.
We can also stop as soon as we see a dirty cell, since that cell will
already have been dealt with.
Then, we scan forward, dirtying cells until we see a non-overflowing
cell. That first non-overflowing cell is also dirtied, but after that
we break.
The last loop, that scans forward, advances the same cell pointer used
in the outer loop.
2021-08-10 18:33:18 +02:00
|
|
|
* overflowing glyphs that follows it.
|
|
|
|
|
*
|
|
|
|
|
* As soon as we see a dirty cell, we can stop, since
|
2024-02-06 12:36:45 +01:00
|
|
|
* that means we've already handled it (remember the
|
render: run the “overflowing glyphs” prepass *before* rendering sixels
This fixes an issue where the left-most column of a sixel was
“overwritten” by the cell content.
This patch also rewrites the prepass logic, to try to reduce the
number of loads performed.
The new logic loops each row from left to right, looking for dirty
cells. When a dirty cell is found, we first scan backwards, until we
find a non-overflowing cell. That cell is unaffected by the
overflowing cell we’re currently dealing with.
We can also stop as soon as we see a dirty cell, since that cell will
already have been dealt with.
Then, we scan forward, dirtying cells until we see a non-overflowing
cell. That first non-overflowing cell is also dirtied, but after that
we break.
The last loop, that scans forward, advances the same cell pointer used
in the outer loop.
2021-08-10 18:33:18 +02:00
|
|
|
* outer loop goes from left to right).
|
|
|
|
|
*/
|
|
|
|
|
for (struct cell *c = cell - 1; c >= &row->cells[0]; c--) {
|
|
|
|
|
if (c->attrs.confined)
|
|
|
|
|
break;
|
|
|
|
|
if (!c->attrs.clean)
|
|
|
|
|
break;
|
|
|
|
|
c->attrs.clean = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Now move forward, dirtying all cells until we hit a
|
|
|
|
|
* non-overflowing cell.
|
|
|
|
|
*
|
|
|
|
|
* Note that the first non-overflowing cell must be
|
|
|
|
|
* re-rendered as well, but any cell *after* that is
|
|
|
|
|
* unaffected by the string of overflowing glyphs
|
2024-02-06 12:36:45 +01:00
|
|
|
* we're dealing with right now.
|
render: run the “overflowing glyphs” prepass *before* rendering sixels
This fixes an issue where the left-most column of a sixel was
“overwritten” by the cell content.
This patch also rewrites the prepass logic, to try to reduce the
number of loads performed.
The new logic loops each row from left to right, looking for dirty
cells. When a dirty cell is found, we first scan backwards, until we
find a non-overflowing cell. That cell is unaffected by the
overflowing cell we’re currently dealing with.
We can also stop as soon as we see a dirty cell, since that cell will
already have been dealt with.
Then, we scan forward, dirtying cells until we see a non-overflowing
cell. That first non-overflowing cell is also dirtied, but after that
we break.
The last loop, that scans forward, advances the same cell pointer used
in the outer loop.
2021-08-10 18:33:18 +02:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* For performance, this iterates the *outer* loop's
|
render: run the “overflowing glyphs” prepass *before* rendering sixels
This fixes an issue where the left-most column of a sixel was
“overwritten” by the cell content.
This patch also rewrites the prepass logic, to try to reduce the
number of loads performed.
The new logic loops each row from left to right, looking for dirty
cells. When a dirty cell is found, we first scan backwards, until we
find a non-overflowing cell. That cell is unaffected by the
overflowing cell we’re currently dealing with.
We can also stop as soon as we see a dirty cell, since that cell will
already have been dealt with.
Then, we scan forward, dirtying cells until we see a non-overflowing
cell. That first non-overflowing cell is also dirtied, but after that
we break.
The last loop, that scans forward, advances the same cell pointer used
in the outer loop.
2021-08-10 18:33:18 +02:00
|
|
|
* cell pointer - no point in re-checking all these
|
|
|
|
|
* glyphs again, in the outer loop.
|
|
|
|
|
*/
|
|
|
|
|
for (; cell < &row->cells[term->cols]; cell++) {
|
|
|
|
|
cell->attrs.clean = false;
|
|
|
|
|
if (cell->attrs.confined)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 17:13:06 +02:00
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
const struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
|
|
|
|
|
if (row->dirty) {
|
|
|
|
|
bool all_clean = true;
|
|
|
|
|
for (int c = 0; c < term->cols; c++) {
|
|
|
|
|
if (!row->cells[c].attrs.clean) {
|
|
|
|
|
all_clean = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (all_clean)
|
|
|
|
|
BUG("row #%d is dirty, but all cells are marked as clean", r);
|
|
|
|
|
} else {
|
|
|
|
|
for (int c = 0; c < term->cols; c++) {
|
|
|
|
|
if (!row->cells[c].attrs.clean)
|
|
|
|
|
BUG("row #%d is clean, but cell #%d is dirty", r, c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-09-21 09:11:28 +02:00
|
|
|
pixman_region32_t damage;
|
|
|
|
|
pixman_region32_init(&damage);
|
|
|
|
|
|
|
|
|
|
render_sixel_images(term, buf->pix[0], &damage, &cursor);
|
|
|
|
|
|
|
|
|
|
|
2020-07-13 13:44:52 +02:00
|
|
|
if (term->render.workers.count > 0) {
|
2020-07-13 13:27:23 +02:00
|
|
|
mtx_lock(&term->render.workers.lock);
|
2019-08-01 20:09:39 +02:00
|
|
|
term->render.workers.buf = buf;
|
|
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++)
|
|
|
|
|
sem_post(&term->render.workers.start);
|
2019-07-29 20:13:26 +02:00
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(tll_length(term->render.workers.queue) == 0);
|
2020-07-13 13:44:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
|
2023-10-07 16:23:09 +02:00
|
|
|
if (!row->dirty)
|
2020-07-13 13:44:52 +02:00
|
|
|
continue;
|
2019-08-01 20:09:39 +02:00
|
|
|
|
2020-07-13 13:44:52 +02:00
|
|
|
row->dirty = false;
|
2019-08-01 20:09:39 +02:00
|
|
|
|
2020-07-13 13:44:52 +02:00
|
|
|
if (term->render.workers.count > 0)
|
2019-08-01 20:09:39 +02:00
|
|
|
tll_push_back(term->render.workers.queue, r);
|
|
|
|
|
|
2020-07-13 13:44:52 +02:00
|
|
|
else {
|
2023-10-07 16:23:09 +02:00
|
|
|
/* TODO: damage region */
|
2020-07-13 13:44:52 +02:00
|
|
|
int cursor_col = cursor.row == r ? cursor.col : -1;
|
2023-10-07 16:23:09 +02:00
|
|
|
render_row(term, buf->pix[0], &damage, row, r, cursor_col);
|
2020-07-13 13:44:52 +02:00
|
|
|
}
|
2020-07-13 14:18:43 +02:00
|
|
|
}
|
2020-07-13 13:44:52 +02:00
|
|
|
|
|
|
|
|
/* Signal workers the frame is done */
|
|
|
|
|
if (term->render.workers.count > 0) {
|
2019-08-01 20:09:39 +02:00
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++)
|
|
|
|
|
tll_push_back(term->render.workers.queue, -1);
|
2019-07-29 20:13:26 +02:00
|
|
|
mtx_unlock(&term->render.workers.lock);
|
render: remove most of the special handling of cursor rendering
Previously, we had to explicitly render the old cursor cell *before*
applying scrolling damage.
We then rendered all the dirty rows, *without* rendering the cursor -
even if the cursor cell was among the dirty rows.
Finally, when everything else was done, we explicitly rendered the
cursor cell.
This meant a lot of code, and unnecessary render_cell() calls, along
with unnecessary wl_surface_damage_buffer() calls.
This was a necessary in the early design of foot, but not anymore.
We can simply mark both the old cursor cell, and the current one, as
dirty and let the normal rendering framework render it. All we need to
do is pass the cursor column to render_row(), so that it can pass
has_cursor=true in the appropriate call to render_cell(). We pass -1
here for all rows, except the cursor's row, where we pass the actual
cursor column.
With this, there's no need to calculate whether the cursor is visible
or not; just mark it's cell as dirty, and if that row is visible, the
normal rendering will take care of it.
This also simplifies the state needed to be saved between two frames;
we only need a row pointer, and the cursor column index.
Part of https://codeberg.org/dnkl/foot/issues/35
2020-07-12 12:56:10 +02:00
|
|
|
|
|
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++)
|
|
|
|
|
sem_wait(&term->render.workers.done);
|
|
|
|
|
term->render.workers.buf = NULL;
|
2019-08-01 20:09:39 +02:00
|
|
|
}
|
2019-07-29 20:13:26 +02:00
|
|
|
|
2023-10-07 16:23:09 +02:00
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++)
|
|
|
|
|
pixman_region32_union(&damage, &damage, &buf->dirty[i + 1]);
|
|
|
|
|
|
|
|
|
|
pixman_region32_union(&buf->dirty[0], &buf->dirty[0], &damage);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
int box_count = 0;
|
|
|
|
|
pixman_box32_t *boxes = pixman_region32_rectangles(&damage, &box_count);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < box_count; i++) {
|
|
|
|
|
wl_surface_damage_buffer(
|
|
|
|
|
term->window->surface.surf,
|
|
|
|
|
boxes[i].x1, boxes[i].y1,
|
|
|
|
|
boxes[i].x2 - boxes[i].x1, boxes[i].y2 - boxes[i].y1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pixman_region32_fini(&damage);
|
|
|
|
|
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
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.
2022-04-16 17:49:46 +02:00
|
|
|
render_overlay(term);
|
2020-12-03 18:38:26 +01:00
|
|
|
render_ime_preedit(term, buf);
|
2020-07-24 17:51:40 +02:00
|
|
|
render_scrollback_position(term);
|
|
|
|
|
|
2022-01-13 12:08:20 +01:00
|
|
|
if (term->conf->tweak.render_timer != RENDER_TIMER_NONE) {
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec end_time;
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
2020-08-14 07:52:08 +02:00
|
|
|
|
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-05 10:48:36 +02:00
|
|
|
struct timespec wait_time;
|
|
|
|
|
timespec_sub(&stop_wait_preapply, &start_wait_preapply, &wait_time);
|
|
|
|
|
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec render_time;
|
|
|
|
|
timespec_sub(&end_time, &start_time, &render_time);
|
2020-08-14 07:52:08 +02:00
|
|
|
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec double_buffering_time;
|
|
|
|
|
timespec_sub(&stop_double_buffering, &start_double_buffering, &double_buffering_time);
|
2021-05-08 10:25:14 +02:00
|
|
|
|
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-05 10:48:36 +02:00
|
|
|
struct timespec preapply_damage;
|
|
|
|
|
timespec_sub(&term->render.workers.preapplied_damage.stop,
|
|
|
|
|
&term->render.workers.preapplied_damage.start,
|
|
|
|
|
&preapply_damage);
|
|
|
|
|
|
2022-09-22 18:32:54 +02:00
|
|
|
struct timespec total_render_time;
|
|
|
|
|
timespec_add(&render_time, &double_buffering_time, &total_render_time);
|
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-05 10:48:36 +02:00
|
|
|
timespec_add(&wait_time, &total_render_time, &total_render_time);
|
2022-09-22 18:32:54 +02:00
|
|
|
|
2022-01-13 12:08:20 +01:00
|
|
|
switch (term->conf->tweak.render_timer) {
|
|
|
|
|
case RENDER_TIMER_LOG:
|
|
|
|
|
case RENDER_TIMER_BOTH:
|
2022-09-22 18:32:54 +02:00
|
|
|
LOG_INFO(
|
|
|
|
|
"frame rendered in %lds %9ldns "
|
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-05 10:48:36 +02:00
|
|
|
"(%lds %9ldns wait, %lds %9ldns rendering, %lds %9ldns double buffering) not included: %lds %ldns pre-apply damage",
|
2022-09-22 18:32:54 +02:00
|
|
|
(long)total_render_time.tv_sec,
|
|
|
|
|
total_render_time.tv_nsec,
|
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-05 10:48:36 +02:00
|
|
|
(long)wait_time.tv_sec,
|
|
|
|
|
wait_time.tv_nsec,
|
2022-09-22 18:32:54 +02:00
|
|
|
(long)render_time.tv_sec,
|
|
|
|
|
render_time.tv_nsec,
|
|
|
|
|
(long)double_buffering_time.tv_sec,
|
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-05 10:48:36 +02:00
|
|
|
double_buffering_time.tv_nsec,
|
|
|
|
|
(long)preapply_damage.tv_sec,
|
|
|
|
|
preapply_damage.tv_nsec);
|
2022-01-13 12:08:20 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case RENDER_TIMER_OSD:
|
|
|
|
|
case RENDER_TIMER_NONE:
|
|
|
|
|
break;
|
2020-08-14 07:52:08 +02:00
|
|
|
}
|
|
|
|
|
|
2022-01-13 12:08:20 +01:00
|
|
|
switch (term->conf->tweak.render_timer) {
|
|
|
|
|
case RENDER_TIMER_OSD:
|
|
|
|
|
case RENDER_TIMER_BOTH:
|
2022-09-22 18:32:54 +02:00
|
|
|
render_render_timer(term, total_render_time);
|
2022-01-13 12:08:20 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case RENDER_TIMER_LOG:
|
|
|
|
|
case RENDER_TIMER_NONE:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-08-14 07:52:08 +02:00
|
|
|
}
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term->grid->offset >= 0 && term->grid->offset < term->grid->num_rows);
|
|
|
|
|
xassert(term->grid->view >= 0 && term->grid->view < term->grid->num_rows);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term->window->frame_callback == NULL);
|
2023-06-26 16:10:40 +02:00
|
|
|
term->window->frame_callback = wl_surface_frame(term->window->surface.surf);
|
2019-10-27 19:08:48 +01:00
|
|
|
wl_callback_add_listener(term->window->frame_callback, &frame_listener, term);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2023-06-26 17:05:16 +02:00
|
|
|
wayl_win_scale(term->window, buf);
|
2019-12-31 15:39:40 +01:00
|
|
|
|
2022-04-17 16:29:30 +02:00
|
|
|
if (term->wl->presentation != NULL && term->conf->presentation_timings) {
|
2020-01-21 18:51:04 +01:00
|
|
|
struct timespec commit_time;
|
|
|
|
|
clock_gettime(term->wl->presentation_clock_id, &commit_time);
|
2019-12-31 15:39:40 +01:00
|
|
|
|
|
|
|
|
struct wp_presentation_feedback *feedback = wp_presentation_feedback(
|
2023-06-26 16:10:40 +02:00
|
|
|
term->wl->presentation, term->window->surface.surf);
|
2019-12-31 15:39:40 +01:00
|
|
|
|
|
|
|
|
if (feedback == NULL) {
|
|
|
|
|
LOG_WARN("failed to create presentation feedback");
|
|
|
|
|
} else {
|
2020-08-08 20:34:30 +01:00
|
|
|
struct presentation_context *ctx = xmalloc(sizeof(*ctx));
|
2020-01-21 18:51:04 +01:00
|
|
|
*ctx = (struct presentation_context){
|
|
|
|
|
.term = term,
|
|
|
|
|
.input.tv_sec = term->render.input_time.tv_sec,
|
|
|
|
|
.input.tv_usec = term->render.input_time.tv_nsec / 1000,
|
|
|
|
|
.commit.tv_sec = commit_time.tv_sec,
|
|
|
|
|
.commit.tv_usec = commit_time.tv_nsec / 1000,
|
|
|
|
|
};
|
|
|
|
|
|
2019-12-31 15:39:40 +01:00
|
|
|
wp_presentation_feedback_add_listener(
|
2020-01-21 18:51:04 +01:00
|
|
|
feedback, &presentation_feedback_listener, ctx);
|
|
|
|
|
|
|
|
|
|
term->render.input_time.tv_sec = 0;
|
|
|
|
|
term->render.input_time.tv_nsec = 0;
|
2019-12-31 15:39:40 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-06 17:52:07 +02:00
|
|
|
if (term->conf->tweak.damage_whole_window) {
|
|
|
|
|
wl_surface_damage_buffer(
|
2023-06-26 16:10:40 +02:00
|
|
|
term->window->surface.surf, 0, 0, INT32_MAX, INT32_MAX);
|
2020-09-06 17:52:07 +02:00
|
|
|
}
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_attach(term->window->surface.surf, buf->wl_buf, 0, 0);
|
|
|
|
|
wl_surface_commit(term->window->surface.surf);
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2019-08-29 20:18:06 +02:00
|
|
|
render_search_box(struct terminal *term)
|
|
|
|
|
{
|
2021-02-12 12:00:40 +01:00
|
|
|
xassert(term->window->search.sub != NULL);
|
2019-08-29 20:18:06 +02:00
|
|
|
|
2020-12-05 23:29:12 +01:00
|
|
|
/*
|
|
|
|
|
* We treat the search box pretty much like a row of cells. That
|
2024-02-06 12:36:45 +01:00
|
|
|
* is, a glyph is either 1 or 2 (or more) "cells" wide.
|
2020-12-05 23:29:12 +01:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* The search 'length', and 'cursor' (position) is in
|
2020-12-05 23:29:12 +01:00
|
|
|
* *characters*, not cells. This means we need to translate from
|
2020-12-05 23:34:27 +01:00
|
|
|
* character count to cell count when calculating the length of
|
2020-12-05 23:29:12 +01:00
|
|
|
* the search box, where in the search string we should start
|
|
|
|
|
* rendering etc.
|
|
|
|
|
*/
|
|
|
|
|
|
2020-12-06 12:18:46 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2021-03-23 13:03:07 +01:00
|
|
|
/* TODO: do we want to/need to handle multi-seat? */
|
|
|
|
|
struct seat *ime_seat = NULL;
|
|
|
|
|
tll_foreach(term->wl->seats, it) {
|
|
|
|
|
if (it->item.kbd_focus == term) {
|
|
|
|
|
ime_seat = &it->item;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-06 12:18:46 +01:00
|
|
|
size_t text_len = term->search.len;
|
2021-03-23 13:03:07 +01:00
|
|
|
if (ime_seat != NULL && ime_seat->ime.preedit.text != NULL)
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
text_len += c32len(ime_seat->ime.preedit.text);
|
2020-12-06 12:18:46 +01:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
char32_t *text = xmalloc((text_len + 1) * sizeof(char32_t));
|
|
|
|
|
text[0] = U'\0';
|
2020-12-07 18:58:01 +01:00
|
|
|
|
|
|
|
|
/* Copy everything up to the cursor */
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
c32ncpy(text, term->search.buf, term->search.cursor);
|
|
|
|
|
text[term->search.cursor] = U'\0';
|
2020-12-07 18:58:01 +01:00
|
|
|
|
|
|
|
|
/* Insert pre-edit text at cursor */
|
2021-03-23 13:03:07 +01:00
|
|
|
if (ime_seat != NULL && ime_seat->ime.preedit.text != NULL)
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
c32cat(text, ime_seat->ime.preedit.text);
|
2020-12-07 18:58:01 +01:00
|
|
|
|
|
|
|
|
/* And finally everything after the cursor */
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
c32ncat(text, &term->search.buf[term->search.cursor],
|
2020-12-07 18:58:01 +01:00
|
|
|
term->search.len - term->search.cursor);
|
2020-12-06 12:18:46 +01:00
|
|
|
#else
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const char32_t *text = term->search.buf;
|
2020-12-05 23:34:42 +01:00
|
|
|
const size_t text_len = term->search.len;
|
2020-12-06 12:18:46 +01:00
|
|
|
#endif
|
2020-12-05 23:34:42 +01:00
|
|
|
|
2020-12-07 18:58:01 +01:00
|
|
|
/* Calculate the width of each character */
|
|
|
|
|
int widths[text_len + 1];
|
|
|
|
|
for (size_t i = 0; i < text_len; i++)
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
widths[i] = max(0, c32width(text[i]));
|
2020-12-07 18:58:01 +01:00
|
|
|
widths[text_len] = 0;
|
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const size_t total_cells = c32swidth(text, text_len);
|
2020-12-05 23:29:12 +01:00
|
|
|
const size_t wanted_visible_cells = max(20, total_cells);
|
2020-01-05 15:16:40 +01:00
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
const float scale = term->scale;
|
|
|
|
|
xassert(scale >= 1.);
|
|
|
|
|
const size_t margin = (size_t)roundf(3 * scale);
|
2023-07-25 15:56:30 +02:00
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
size_t width = term->width - 2 * margin;
|
2023-07-25 15:56:30 +02:00
|
|
|
size_t height = min(
|
2020-01-05 15:16:40 +01:00
|
|
|
term->height - 2 * margin,
|
2023-07-25 15:56:30 +02:00
|
|
|
margin + 1 * term->cell_height + margin);
|
2020-01-05 15:16:40 +01:00
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
width = roundf(scale * ceilf((term->width - 2 * margin) / scale));
|
|
|
|
|
height = roundf(scale * ceilf(height / scale));
|
|
|
|
|
|
|
|
|
|
size_t visible_width = min(
|
|
|
|
|
term->width - 2 * margin,
|
|
|
|
|
margin + wanted_visible_cells * term->cell_width + margin);
|
|
|
|
|
|
2020-12-05 23:29:12 +01:00
|
|
|
const size_t visible_cells = (visible_width - 2 * margin) / term->cell_width;
|
2020-01-05 15:25:24 +01:00
|
|
|
size_t glyph_offset = term->render.search_glyph_offset;
|
2019-08-29 20:18:06 +02:00
|
|
|
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
struct buffer_chain *chain = term->render.chains.search;
|
2026-01-04 07:57:25 +01:00
|
|
|
struct buffer *buf = shm_get_buffer(chain, width, height);
|
2019-08-29 20:18:06 +02:00
|
|
|
|
2021-07-15 18:27:10 +02:00
|
|
|
pixman_region32_t clip;
|
|
|
|
|
pixman_region32_init_rect(&clip, 0, 0, width, height);
|
|
|
|
|
pixman_image_set_clip_region32(buf->pix[0], &clip);
|
|
|
|
|
pixman_region32_fini(&clip);
|
|
|
|
|
|
2020-12-20 15:01:21 -07:00
|
|
|
#define WINDOW_X(x) (margin + x)
|
|
|
|
|
#define WINDOW_Y(y) (term->height - margin - height + y)
|
|
|
|
|
|
2022-07-27 19:14:27 +02:00
|
|
|
const bool is_match = term->search.match_len == text_len;
|
|
|
|
|
const bool custom_colors = is_match
|
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-19 09:29:06 +01:00
|
|
|
? term->conf->colors_dark.use_custom.search_box_match
|
|
|
|
|
: term->conf->colors_dark.use_custom.search_box_no_match;
|
2022-07-27 19:14:27 +02:00
|
|
|
|
|
|
|
|
/* Background - yellow on empty/match, red on mismatch (default) */
|
2025-05-01 08:34:49 +02:00
|
|
|
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
|
2022-07-27 19:14:27 +02:00
|
|
|
const pixman_color_t color = color_hex_to_pixman(
|
|
|
|
|
is_match
|
|
|
|
|
? (custom_colors
|
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-19 09:29:06 +01:00
|
|
|
? term->conf->colors_dark.search_box.match.bg
|
2022-07-27 19:14:27 +02:00
|
|
|
: term->colors.table[3])
|
|
|
|
|
: (custom_colors
|
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-19 09:29:06 +01:00
|
|
|
? term->conf->colors_dark.search_box.no_match.bg
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
: term->colors.table[1]),
|
|
|
|
|
gamma_correct);
|
2019-08-29 20:18:06 +02:00
|
|
|
|
|
|
|
|
pixman_image_fill_rectangles(
|
2020-06-04 15:39:19 +02:00
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &color,
|
2020-03-01 12:28:33 +01:00
|
|
|
1, &(pixman_rectangle16_t){width - visible_width, 0, visible_width, height});
|
|
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
pixman_color_t transparent = color_hex_to_pixman_with_alpha(0, 0, gamma_correct);
|
2020-03-01 12:28:33 +01:00
|
|
|
pixman_image_fill_rectangles(
|
2020-06-04 15:39:19 +02:00
|
|
|
PIXMAN_OP_SRC, buf->pix[0], &transparent,
|
2020-03-01 12:28:33 +01:00
|
|
|
1, &(pixman_rectangle16_t){0, 0, width - visible_width, height});
|
2019-08-29 20:18:06 +02:00
|
|
|
|
2020-04-21 19:29:36 +02:00
|
|
|
struct fcft_font *font = term->fonts[0];
|
2020-12-06 12:18:46 +01:00
|
|
|
const int x_left = width - visible_width + margin;
|
2021-01-07 17:00:58 +01:00
|
|
|
const int x_ofs = term->font_x_ofs;
|
2020-12-06 12:18:46 +01:00
|
|
|
int x = x_left;
|
2019-08-29 20:18:06 +02:00
|
|
|
int y = margin;
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
|
2022-07-27 19:14:27 +02:00
|
|
|
pixman_color_t fg = color_hex_to_pixman(
|
|
|
|
|
custom_colors
|
|
|
|
|
? (is_match
|
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-19 09:29:06 +01:00
|
|
|
? term->conf->colors_dark.search_box.match.fg
|
|
|
|
|
: term->conf->colors_dark.search_box.no_match.fg)
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
: term->colors.table[0],
|
|
|
|
|
gamma_correct);
|
2019-08-29 20:18:06 +02:00
|
|
|
|
2020-12-07 18:58:01 +01:00
|
|
|
/* Move offset we start rendering at, to ensure the cursor is visible */
|
|
|
|
|
for (size_t i = 0, cell_idx = 0; i <= term->search.cursor; cell_idx += widths[i], i++) {
|
2020-12-05 23:29:12 +01:00
|
|
|
if (i != term->search.cursor)
|
|
|
|
|
continue;
|
|
|
|
|
|
2020-12-06 12:18:46 +01:00
|
|
|
#if (FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2021-03-23 13:03:07 +01:00
|
|
|
if (ime_seat != NULL && ime_seat->ime.preedit.cells != NULL) {
|
|
|
|
|
if (ime_seat->ime.preedit.cursor.start == ime_seat->ime.preedit.cursor.end) {
|
2020-12-07 18:58:01 +01:00
|
|
|
/* All IME's I've seen so far keeps the cursor at
|
|
|
|
|
* index 0, so ensure the *end* of the pre-edit string
|
|
|
|
|
* is visible */
|
2021-03-23 13:03:07 +01:00
|
|
|
cell_idx += ime_seat->ime.preedit.count;
|
2020-12-07 18:58:01 +01:00
|
|
|
} else {
|
|
|
|
|
/* Try to predict in which direction we'll shift the text */
|
2021-03-23 13:03:07 +01:00
|
|
|
if (cell_idx + ime_seat->ime.preedit.cursor.start > glyph_offset)
|
|
|
|
|
cell_idx += ime_seat->ime.preedit.cursor.end;
|
2020-12-07 18:58:01 +01:00
|
|
|
else
|
2021-03-23 13:03:07 +01:00
|
|
|
cell_idx += ime_seat->ime.preedit.cursor.start;
|
2020-12-07 18:58:01 +01:00
|
|
|
}
|
|
|
|
|
}
|
2020-12-06 12:18:46 +01:00
|
|
|
#endif
|
|
|
|
|
|
2020-12-07 18:58:01 +01:00
|
|
|
if (cell_idx < glyph_offset) {
|
|
|
|
|
/* Shift to the *left*, making *this* character the
|
|
|
|
|
* *first* visible one */
|
2020-12-05 23:29:12 +01:00
|
|
|
term->render.search_glyph_offset = glyph_offset = cell_idx;
|
2020-12-07 18:58:01 +01:00
|
|
|
}
|
|
|
|
|
|
2020-12-05 23:29:12 +01:00
|
|
|
else if (cell_idx > glyph_offset + visible_cells) {
|
2020-12-07 18:58:01 +01:00
|
|
|
/* Shift to the *right*, making *this* character the
|
|
|
|
|
* *last* visible one */
|
2020-12-05 23:29:12 +01:00
|
|
|
term->render.search_glyph_offset = glyph_offset =
|
|
|
|
|
cell_idx - min(cell_idx, visible_cells);
|
|
|
|
|
}
|
2020-12-07 18:58:01 +01:00
|
|
|
|
|
|
|
|
/* Adjust offset if there is free space available */
|
|
|
|
|
if (total_cells - glyph_offset < visible_cells) {
|
|
|
|
|
term->render.search_glyph_offset = glyph_offset =
|
|
|
|
|
total_cells - min(total_cells, visible_cells);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-05 23:29:12 +01:00
|
|
|
break;
|
2020-01-05 15:16:40 +01:00
|
|
|
}
|
|
|
|
|
|
2020-12-07 18:58:01 +01:00
|
|
|
/* Ensure offset is at a character boundary */
|
|
|
|
|
for (size_t i = 0, cell_idx = 0; i <= text_len; cell_idx += widths[i], i++) {
|
|
|
|
|
if (cell_idx >= glyph_offset) {
|
|
|
|
|
term->render.search_glyph_offset = glyph_offset = cell_idx;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-12-06 12:24:45 +01:00
|
|
|
}
|
2020-04-19 14:50:48 +02:00
|
|
|
|
2020-12-05 23:29:12 +01:00
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Render the search string, starting at 'glyph_offset'. Note that
|
2020-12-05 23:29:12 +01:00
|
|
|
* glyph_offset is in cells, not characters
|
|
|
|
|
*/
|
|
|
|
|
for (size_t i = 0,
|
|
|
|
|
cell_idx = 0,
|
2020-12-07 18:58:01 +01:00
|
|
|
width = widths[i],
|
2020-12-05 23:29:12 +01:00
|
|
|
next_cell_idx = width;
|
2020-12-05 23:34:42 +01:00
|
|
|
i < text_len;
|
2020-12-05 23:29:12 +01:00
|
|
|
i++,
|
|
|
|
|
cell_idx = next_cell_idx,
|
2020-12-07 18:58:01 +01:00
|
|
|
width = widths[i],
|
2020-12-05 23:29:12 +01:00
|
|
|
next_cell_idx += width)
|
2020-01-05 15:16:40 +01:00
|
|
|
{
|
2020-12-20 15:01:21 -07:00
|
|
|
/* Convert subsurface coordinates to window coordinates*/
|
2020-12-07 18:58:01 +01:00
|
|
|
/* Render cursor */
|
|
|
|
|
if (i == term->search.cursor) {
|
|
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2021-03-23 13:03:07 +01:00
|
|
|
bool have_preedit =
|
|
|
|
|
ime_seat != NULL && ime_seat->ime.preedit.cells != NULL;
|
|
|
|
|
bool hidden =
|
|
|
|
|
ime_seat != NULL && ime_seat->ime.preedit.cursor.hidden;
|
2020-12-07 18:58:01 +01:00
|
|
|
|
|
|
|
|
if (have_preedit && !hidden) {
|
|
|
|
|
/* Cursor may be outside the visible area:
|
|
|
|
|
* cell_idx-glyph_offset can be negative */
|
|
|
|
|
int cells_left = visible_cells - max(
|
|
|
|
|
(ssize_t)(cell_idx - glyph_offset), 0);
|
|
|
|
|
|
|
|
|
|
/* If cursor is outside the visible area, we need to
|
|
|
|
|
* adjust our rectangle's position */
|
2021-03-23 13:03:07 +01:00
|
|
|
int start = ime_seat->ime.preedit.cursor.start
|
2020-12-07 18:58:01 +01:00
|
|
|
+ min((ssize_t)(cell_idx - glyph_offset), 0);
|
2021-03-23 13:03:07 +01:00
|
|
|
int end = ime_seat->ime.preedit.cursor.end
|
2020-12-07 18:58:01 +01:00
|
|
|
+ min((ssize_t)(cell_idx - glyph_offset), 0);
|
|
|
|
|
|
|
|
|
|
if (start == end) {
|
2021-03-23 13:03:07 +01:00
|
|
|
int count = min(ime_seat->ime.preedit.count, cells_left);
|
2020-12-07 18:58:01 +01:00
|
|
|
|
|
|
|
|
/* Underline the entire (visible part of) pre-edit text */
|
|
|
|
|
draw_underline(term, buf->pix[0], font, &fg, x, y, count);
|
|
|
|
|
|
|
|
|
|
/* Bar-styled cursor, if in the visible area */
|
2021-05-18 18:52:10 +02:00
|
|
|
if (start >= 0 && start <= visible_cells) {
|
|
|
|
|
draw_beam_cursor(
|
|
|
|
|
term, buf->pix[0], font, &fg,
|
|
|
|
|
x + start * term->cell_width, y);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-20 15:01:21 -07:00
|
|
|
term_ime_set_cursor_rect(term,
|
|
|
|
|
WINDOW_X(x + start * term->cell_width), WINDOW_Y(y),
|
|
|
|
|
1, term->cell_height);
|
2020-12-07 18:58:01 +01:00
|
|
|
} else {
|
|
|
|
|
/* Underline everything before and after the cursor */
|
|
|
|
|
int count1 = min(start, cells_left);
|
|
|
|
|
int count2 = max(
|
2021-03-23 13:03:07 +01:00
|
|
|
min(ime_seat->ime.preedit.count - ime_seat->ime.preedit.cursor.end,
|
2020-12-07 18:58:01 +01:00
|
|
|
cells_left - end),
|
|
|
|
|
0);
|
|
|
|
|
draw_underline(term, buf->pix[0], font, &fg, x, y, count1);
|
|
|
|
|
draw_underline(term, buf->pix[0], font, &fg, x + end * term->cell_width, y, count2);
|
|
|
|
|
|
|
|
|
|
/* TODO: how do we handle a partially hidden rectangle? */
|
|
|
|
|
if (start >= 0 && end <= visible_cells) {
|
2024-04-09 16:28:54 +02:00
|
|
|
draw_hollow_block(
|
2020-12-07 18:58:01 +01:00
|
|
|
term, buf->pix[0], &fg, x + start * term->cell_width, y, end - start);
|
|
|
|
|
}
|
2020-12-20 15:01:21 -07:00
|
|
|
term_ime_set_cursor_rect(term,
|
|
|
|
|
WINDOW_X(x + start * term->cell_width), WINDOW_Y(y),
|
|
|
|
|
term->cell_width * (end - start), term->cell_height);
|
2020-12-07 18:58:01 +01:00
|
|
|
}
|
|
|
|
|
} else if (!have_preedit)
|
2020-12-06 12:18:46 +01:00
|
|
|
#endif
|
2020-12-07 18:58:01 +01:00
|
|
|
{
|
|
|
|
|
/* Cursor *should* be in the visible area */
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(cell_idx >= glyph_offset);
|
|
|
|
|
xassert(cell_idx <= glyph_offset + visible_cells);
|
2021-05-18 18:52:10 +02:00
|
|
|
draw_beam_cursor(term, buf->pix[0], font, &fg, x, y);
|
2020-12-20 15:01:21 -07:00
|
|
|
term_ime_set_cursor_rect(
|
|
|
|
|
term, WINDOW_X(x), WINDOW_Y(y), 1, term->cell_height);
|
2020-12-07 18:58:01 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-08-29 21:03:00 +02:00
|
|
|
|
2020-12-07 18:58:01 +01:00
|
|
|
if (next_cell_idx >= glyph_offset && next_cell_idx - glyph_offset > visible_cells) {
|
|
|
|
|
/* We're now beyond the visible area - nothing more to render */
|
2020-12-05 23:29:12 +01:00
|
|
|
break;
|
2020-12-07 18:58:01 +01:00
|
|
|
}
|
2020-12-05 23:29:12 +01:00
|
|
|
|
|
|
|
|
if (cell_idx < glyph_offset) {
|
2020-12-07 18:58:01 +01:00
|
|
|
/* We haven't yet reached the visible part of the string */
|
2020-12-05 23:29:12 +01:00
|
|
|
cell_idx = next_cell_idx;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const struct fcft_glyph *glyph = fcft_rasterize_char_utf32(
|
2020-12-05 23:34:42 +01:00
|
|
|
font, text[i], term->font_subpixel);
|
2020-04-24 10:53:34 +02:00
|
|
|
|
2020-12-05 23:29:12 +01:00
|
|
|
if (glyph == NULL) {
|
|
|
|
|
cell_idx = next_cell_idx;
|
2019-08-29 20:18:06 +02:00
|
|
|
continue;
|
2020-12-05 23:29:12 +01:00
|
|
|
}
|
2019-08-29 20:18:06 +02:00
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
if (unlikely(glyph->is_color_glyph)) {
|
2020-12-05 11:59:41 +01:00
|
|
|
pixman_image_composite32(
|
|
|
|
|
PIXMAN_OP_OVER, glyph->pix, NULL, buf->pix[0], 0, 0, 0, 0,
|
2023-10-10 14:23:33 +02:00
|
|
|
x + x_ofs + glyph->x, y + term->font_baseline - glyph->y,
|
2020-12-05 11:59:41 +01:00
|
|
|
glyph->width, glyph->height);
|
|
|
|
|
} else {
|
2021-01-25 10:00:43 +01:00
|
|
|
int combining_ofs = width == 0
|
|
|
|
|
? (glyph->x < 0
|
|
|
|
|
? width * term->cell_width
|
|
|
|
|
: (width - 1) * term->cell_width)
|
|
|
|
|
: 0; /* Not a zero-width character - no additional offset */
|
2020-12-05 11:59:41 +01:00
|
|
|
pixman_image_t *src = pixman_image_create_solid_fill(&fg);
|
|
|
|
|
pixman_image_composite32(
|
|
|
|
|
PIXMAN_OP_OVER, src, glyph->pix, buf->pix[0], 0, 0, 0, 0,
|
2021-01-25 10:00:43 +01:00
|
|
|
x + x_ofs + combining_ofs + glyph->x,
|
2023-10-10 14:23:33 +02:00
|
|
|
y + term->font_baseline - glyph->y,
|
2019-08-29 20:18:06 +02:00
|
|
|
glyph->width, glyph->height);
|
2020-12-05 11:59:41 +01:00
|
|
|
pixman_image_unref(src);
|
|
|
|
|
}
|
2019-08-29 20:18:06 +02:00
|
|
|
|
2020-12-05 23:29:12 +01:00
|
|
|
x += width * term->cell_width;
|
|
|
|
|
cell_idx = next_cell_idx;
|
2019-08-29 20:18:06 +02:00
|
|
|
}
|
|
|
|
|
|
2020-12-06 12:18:46 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2021-03-23 13:03:07 +01:00
|
|
|
if (ime_seat != NULL && ime_seat->ime.preedit.cells != NULL)
|
2020-12-07 18:58:01 +01:00
|
|
|
/* Already rendered */;
|
|
|
|
|
else
|
2020-12-06 12:18:46 +01:00
|
|
|
#endif
|
2020-12-20 15:01:21 -07:00
|
|
|
if (term->search.cursor >= term->search.len) {
|
2021-05-18 18:52:10 +02:00
|
|
|
draw_beam_cursor(term, buf->pix[0], font, &fg, x, y);
|
2020-12-20 15:01:21 -07:00
|
|
|
term_ime_set_cursor_rect(
|
|
|
|
|
term, WINDOW_X(x), WINDOW_Y(y), 1, term->cell_height);
|
|
|
|
|
}
|
2019-08-29 21:03:00 +02:00
|
|
|
|
2021-02-12 12:00:40 +01:00
|
|
|
quirk_weston_subsurface_desync_on(term->window->search.sub);
|
2020-03-01 13:06:00 +01:00
|
|
|
|
2020-03-01 12:54:50 +01:00
|
|
|
/* TODO: this is only necessary on a window resize */
|
2019-08-29 20:18:06 +02:00
|
|
|
wl_subsurface_set_position(
|
2021-02-12 12:00:40 +01:00
|
|
|
term->window->search.sub,
|
2023-07-28 15:28:10 +02:00
|
|
|
roundf(margin / scale),
|
|
|
|
|
roundf(max(0, (int32_t)term->height - height - margin) / scale));
|
2021-03-24 20:52:58 +01:00
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
wayl_surface_scale(term->window, &term->window->search.surface, buf, scale);
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_attach(term->window->search.surface.surf, buf->wl_buf, 0, 0);
|
|
|
|
|
wl_surface_damage_buffer(term->window->search.surface.surf, 0, 0, width, height);
|
2020-03-01 12:28:33 +01:00
|
|
|
|
2020-03-01 12:54:27 +01:00
|
|
|
struct wl_region *region = wl_compositor_create_region(term->wl->compositor);
|
|
|
|
|
if (region != NULL) {
|
|
|
|
|
wl_region_add(region, width - visible_width, 0, visible_width, height);
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_set_opaque_region(term->window->search.surface.surf, region);
|
2020-03-01 12:54:27 +01:00
|
|
|
wl_region_destroy(region);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_commit(term->window->search.surface.surf);
|
2021-02-12 12:00:40 +01:00
|
|
|
quirk_weston_subsurface_desync_off(term->window->search.sub);
|
2020-12-06 12:18:46 +01:00
|
|
|
|
|
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
|
|
|
|
free(text);
|
|
|
|
|
#endif
|
2020-12-20 15:01:21 -07:00
|
|
|
#undef WINDOW_X
|
|
|
|
|
#undef WINDOW_Y
|
2019-08-29 20:18:06 +02:00
|
|
|
}
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
static void
|
|
|
|
|
render_urls(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
struct wl_window *win = term->window;
|
|
|
|
|
xassert(tll_length(win->urls) > 0);
|
|
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
const float scale = term->scale;
|
|
|
|
|
const int x_margin = (int)roundf(2 * scale);
|
|
|
|
|
const int y_margin = (int)roundf(1 * scale);
|
2021-07-15 18:39:41 +02:00
|
|
|
|
2021-02-06 11:47:59 +01:00
|
|
|
/* Calculate view start, counted from the *current* scrollback start */
|
|
|
|
|
const int scrollback_end
|
|
|
|
|
= (term->grid->offset + term->rows) & (term->grid->num_rows - 1);
|
|
|
|
|
const int view_start
|
|
|
|
|
= (term->grid->view
|
|
|
|
|
- scrollback_end
|
|
|
|
|
+ term->grid->num_rows) & (term->grid->num_rows - 1);
|
|
|
|
|
const int view_end = view_start + term->rows - 1;
|
|
|
|
|
|
2021-02-14 14:18:11 +01:00
|
|
|
const bool show_url = term->urls_show_uri_on_jump_label;
|
|
|
|
|
|
2021-07-15 18:39:41 +02:00
|
|
|
/*
|
|
|
|
|
* There can potentially be a lot of URLs.
|
|
|
|
|
*
|
|
|
|
|
* Since each URL is a separate sub-surface, and requires its own
|
|
|
|
|
* SHM buffer, we may be allocating a lot of buffers.
|
|
|
|
|
*
|
|
|
|
|
* SHM buffers normally have their own, private SHM buffer
|
|
|
|
|
* pool. Each pool is mmapped, and thus allocates *at least*
|
|
|
|
|
* 4K. Since URL labels are typically small, we end up using an
|
|
|
|
|
* excessive amount of both virtual and physical memory.
|
|
|
|
|
*
|
|
|
|
|
* For this reason, we instead use shm_get_many(), which uses a
|
|
|
|
|
* single, shared pool for all buffers.
|
|
|
|
|
*
|
|
|
|
|
* To be able to use it, we need to have all the *all* the buffer
|
|
|
|
|
* dimensions up front.
|
|
|
|
|
*
|
|
|
|
|
* Thus, the first iteration through the URLs do the heavy
|
|
|
|
|
* lifting: builds the label contents and calculates both its
|
|
|
|
|
* position and size. But instead of rendering the label
|
|
|
|
|
* immediately, we store the calculated data, and then do a second
|
|
|
|
|
* pass, where we first get all our buffers, and then render to
|
|
|
|
|
* them.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Positioning data + label contents */
|
|
|
|
|
struct {
|
|
|
|
|
const struct wl_url *url;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
char32_t *text;
|
2021-07-15 18:39:41 +02:00
|
|
|
int x;
|
|
|
|
|
int y;
|
|
|
|
|
} info[tll_length(win->urls)];
|
|
|
|
|
|
|
|
|
|
/* For shm_get_many() */
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
int widths[tll_length(win->urls)];
|
|
|
|
|
int heights[tll_length(win->urls)];
|
2021-07-15 18:39:41 +02:00
|
|
|
|
|
|
|
|
size_t render_count = 0;
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
tll_foreach(win->urls, it) {
|
|
|
|
|
const struct url *url = it->item.url;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const char32_t *key = url->key;
|
|
|
|
|
const size_t entered_key_len = c32len(term->url_keys);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
2021-02-13 13:45:59 +01:00
|
|
|
if (key == NULL) {
|
|
|
|
|
/* TODO: if we decide to use the .text field, we cannot
|
|
|
|
|
* just skip the entire jump label like this */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
struct wl_surface *surf = it->item.surf.surface.surf;
|
2021-02-12 11:31:31 +01:00
|
|
|
struct wl_subsurface *sub_surf = it->item.surf.sub;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
if (surf == NULL || sub_surf == NULL)
|
|
|
|
|
continue;
|
|
|
|
|
|
2021-02-06 20:52:04 +01:00
|
|
|
bool hide = false;
|
2022-04-09 15:09:02 +02:00
|
|
|
const struct coord *pos = &url->range.start;
|
2021-02-06 11:47:59 +01:00
|
|
|
const int _row
|
|
|
|
|
= (pos->row
|
|
|
|
|
- scrollback_end
|
|
|
|
|
+ term->grid->num_rows) & (term->grid->num_rows - 1);
|
|
|
|
|
|
2021-02-06 20:52:04 +01:00
|
|
|
if (_row < view_start || _row > view_end)
|
|
|
|
|
hide = true;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
if (c32len(key) <= entered_key_len)
|
2021-02-06 20:52:04 +01:00
|
|
|
hide = true;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
if (c32ncasecmp(term->url_keys, key, entered_key_len) != 0)
|
2021-02-06 20:52:04 +01:00
|
|
|
hide = true;
|
|
|
|
|
|
|
|
|
|
if (hide) {
|
2021-02-06 11:47:59 +01:00
|
|
|
wl_surface_attach(surf, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(surf);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
int col = pos->col;
|
|
|
|
|
int row = pos->row - term->grid->view;
|
|
|
|
|
while (row < 0)
|
|
|
|
|
row += term->grid->num_rows;
|
|
|
|
|
row &= (term->grid->num_rows - 1);
|
|
|
|
|
|
|
|
|
|
/* Position label slightly above and to the left */
|
|
|
|
|
int x = col * term->cell_width - 15 * term->cell_width / 10;
|
|
|
|
|
int y = row * term->cell_height - 5 * term->cell_height / 10;
|
|
|
|
|
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Don't position it outside our window */
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
if (x < -term->margins.left)
|
|
|
|
|
x = -term->margins.left;
|
|
|
|
|
if (y < -term->margins.top)
|
|
|
|
|
y = -term->margins.top;
|
|
|
|
|
|
|
|
|
|
/* Maximum width of label, in pixels */
|
|
|
|
|
const int max_width =
|
|
|
|
|
term->width - term->margins.left - term->margins.right - x;
|
2021-07-15 18:39:41 +02:00
|
|
|
const int max_cols = max_width / term->cell_width;
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const size_t key_len = c32len(key);
|
2021-02-14 14:18:11 +01:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
size_t url_len = mbstoc32(NULL, url->url, 0);
|
2021-02-14 14:18:11 +01:00
|
|
|
if (url_len == (size_t)-1)
|
|
|
|
|
url_len = 0;
|
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
char32_t url_wchars[url_len + 1];
|
|
|
|
|
mbstoc32(url_wchars, url->url, url_len + 1);
|
2021-02-14 14:18:11 +01:00
|
|
|
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
/* Format label, not yet subject to any size limitations */
|
2021-02-14 14:18:11 +01:00
|
|
|
size_t chars = key_len + (show_url ? (2 + url_len) : 0);
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
char32_t label[chars + 1];
|
|
|
|
|
label[chars] = U'\0';
|
2021-02-14 14:18:11 +01:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
if (show_url) {
|
|
|
|
|
c32cpy(label, key);
|
|
|
|
|
c32cat(label, U": ");
|
|
|
|
|
c32cat(label, url_wchars);
|
|
|
|
|
} else
|
|
|
|
|
c32ncpy(label, key, chars);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
/* Upper case the key characters */
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
for (size_t i = 0; i < c32len(key); i++)
|
|
|
|
|
label[i] = toc32upper(label[i]);
|
2021-02-06 23:03:05 +01:00
|
|
|
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
/* Blank already entered key characters */
|
2021-02-07 10:51:28 +01:00
|
|
|
for (size_t i = 0; i < entered_key_len; i++)
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
label[i] = U' ';
|
2021-02-07 10:51:28 +01:00
|
|
|
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Don't extend outside our window
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Truncate label so that it doesn't extend outside our
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
* window.
|
|
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Do it in a way such that we don't cut the label in the
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
* middle of a double-width character.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int cols = 0;
|
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
for (size_t i = 0; i <= c32len(label); i++) {
|
|
|
|
|
int _cols = c32swidth(label, i);
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
|
|
|
|
|
if (_cols == (size_t)-1)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (_cols >= max_cols) {
|
|
|
|
|
if (i > 0)
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
label[i - 1] = U'…';
|
|
|
|
|
label[i] = U'\0';
|
render: don’t let URL jump label sub-surfaces extend outside window geometry
We have no guarantee that sub-surfaces extending outside the window
geometry are rendered correctly (if at all).
For example, both Sway and River will render the window border on top
of the sub-surface.
Future versions of Sway may clip the sub-surface.
Since jump-labels are positioned slightly above, and to the left of
the URLs first character, having a label on either the top row, or on
the first column, will likely position it outside the window. This is
handled by simply setting x/y to 0 (or, to -margin, since the label
coordinate is later offsetted with the window margins).
Second, if the label is very long, it may extend outside the
window. This is very unusual for labels only showing the key, and not
the URL itself, but could happen in this case too, if e.g. the user
has configured double-width key characters.
This is handled by calculating its maximum width, and then truncating
the label.
Although very unlikely, it is possible for a label to also extend
outside the window’s vertical size. This could happen for very small
font sizes, where the label’s own margins are large, relative to the
font size. This case is currently not handled.
Closes #443
2021-04-10 13:16:39 +02:00
|
|
|
cols = max_cols;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
cols = _cols;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cols == 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
2023-07-25 15:56:30 +02:00
|
|
|
int width = x_margin + cols * term->cell_width + x_margin;
|
|
|
|
|
int height = y_margin + term->cell_height + y_margin;
|
|
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
width = roundf(scale * ceilf(width / scale));
|
|
|
|
|
height = roundf(scale * ceilf(height / scale));
|
2021-01-31 11:12:07 +01:00
|
|
|
|
2021-07-15 18:39:41 +02:00
|
|
|
info[render_count].url = &it->item;
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
info[render_count].text = xc32dup(label);
|
2021-07-15 18:39:41 +02:00
|
|
|
info[render_count].x = x;
|
|
|
|
|
info[render_count].y = y;
|
|
|
|
|
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
widths[render_count] = width;
|
|
|
|
|
heights[render_count] = height;
|
2021-07-15 18:39:41 +02:00
|
|
|
|
|
|
|
|
render_count++;
|
|
|
|
|
}
|
|
|
|
|
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
struct buffer_chain *chain = term->render.chains.url;
|
2021-07-15 18:39:41 +02:00
|
|
|
struct buffer *bufs[render_count];
|
2026-01-04 07:57:25 +01:00
|
|
|
shm_get_many(chain, render_count, widths, heights, bufs);
|
2021-07-15 18:39:41 +02:00
|
|
|
|
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-19 09:29:06 +01:00
|
|
|
uint32_t fg = term->conf->colors_dark.use_custom.jump_label
|
|
|
|
|
? term->conf->colors_dark.jump_label.fg
|
2021-07-15 18:39:41 +02:00
|
|
|
: term->colors.table[0];
|
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-19 09:29:06 +01:00
|
|
|
uint32_t bg = term->conf->colors_dark.use_custom.jump_label
|
|
|
|
|
? term->conf->colors_dark.jump_label.bg
|
2021-07-15 18:39:41 +02:00
|
|
|
: term->colors.table[3];
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < render_count; i++) {
|
2023-06-26 16:53:16 +02:00
|
|
|
const struct wayl_sub_surface *sub_surf = &info[i].url->surf;
|
2021-07-15 18:39:41 +02:00
|
|
|
|
fcft: adapt to API changes in fcft-3.x
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
2021-08-21 14:50:42 +02:00
|
|
|
const char32_t *label = info[i].text;
|
2021-07-15 18:39:41 +02:00
|
|
|
const int x = info[i].x;
|
|
|
|
|
const int y = info[i].y;
|
|
|
|
|
|
2023-06-26 16:53:16 +02:00
|
|
|
xassert(sub_surf->surface.surf != NULL);
|
|
|
|
|
xassert(sub_surf->sub != NULL);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
wl_subsurface_set_position(
|
2023-06-26 16:53:16 +02:00
|
|
|
sub_surf->sub,
|
2023-07-28 15:28:10 +02:00
|
|
|
roundf((term->margins.left + x) / scale),
|
|
|
|
|
roundf((term->margins.top + y) / scale));
|
2021-02-01 10:04:25 +01:00
|
|
|
|
2021-03-24 20:51:18 +01:00
|
|
|
render_osd(
|
2023-06-26 16:53:16 +02:00
|
|
|
term, sub_surf, term->fonts[0], bufs[i], label,
|
2023-07-31 16:29:08 +02:00
|
|
|
fg, 0xffu << 24 | bg, x_margin);
|
2021-07-15 18:39:41 +02:00
|
|
|
|
|
|
|
|
free(info[i].text);
|
2021-01-31 11:12:07 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-25 18:23:55 +01:00
|
|
|
static void
|
|
|
|
|
render_update_title(struct terminal *term)
|
|
|
|
|
{
|
2020-07-22 21:07:57 +02:00
|
|
|
static const size_t max_len = 2048;
|
2020-03-25 18:23:55 +01:00
|
|
|
|
|
|
|
|
const char *title = term->window_title != NULL ? term->window_title : "foot";
|
|
|
|
|
char *copy = NULL;
|
|
|
|
|
|
|
|
|
|
if (strlen(title) > max_len) {
|
2020-08-08 20:34:30 +01:00
|
|
|
copy = xstrndup(title, max_len);
|
2020-03-25 18:23:55 +01:00
|
|
|
title = copy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
xdg_toplevel_set_title(term->window->xdg_toplevel, title);
|
|
|
|
|
free(copy);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-06 19:16:54 +01:00
|
|
|
static void
|
|
|
|
|
frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
|
|
|
|
|
{
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term->window->frame_callback == wl_callback);
|
2020-03-06 19:16:54 +01:00
|
|
|
wl_callback_destroy(wl_callback);
|
|
|
|
|
term->window->frame_callback = NULL;
|
|
|
|
|
|
2020-03-24 17:42:29 +01:00
|
|
|
bool grid = term->render.pending.grid;
|
|
|
|
|
bool csd = term->render.pending.csd;
|
2021-01-31 11:12:07 +01:00
|
|
|
bool search = term->is_searching && term->render.pending.search;
|
|
|
|
|
bool urls = urls_mode_is_active(term) > 0 && term->render.pending.urls;
|
2020-03-24 17:42:29 +01:00
|
|
|
|
|
|
|
|
term->render.pending.grid = false;
|
|
|
|
|
term->render.pending.csd = false;
|
|
|
|
|
term->render.pending.search = false;
|
2021-01-31 11:12:07 +01:00
|
|
|
term->render.pending.urls = false;
|
2020-03-24 17:42:29 +01:00
|
|
|
|
url-mode: snapshot screen state when entering URL mode
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.
2021-02-22 10:22:41 +01:00
|
|
|
struct grid *original_grid = term->grid;
|
|
|
|
|
if (urls_mode_is_active(term)) {
|
|
|
|
|
xassert(term->url_grid_snapshot != NULL);
|
|
|
|
|
term->grid = term->url_grid_snapshot;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-22 18:58:38 +02:00
|
|
|
if (csd && term->window->csd_mode == CSD_YES) {
|
2020-03-24 17:42:29 +01:00
|
|
|
quirk_weston_csd_on(term);
|
|
|
|
|
render_csd(term);
|
|
|
|
|
quirk_weston_csd_off(term);
|
2020-03-06 19:16:54 +01:00
|
|
|
}
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
if (search)
|
2020-03-24 17:42:29 +01:00
|
|
|
render_search_box(term);
|
delayed rendering: ignore frame callback if delayed rendering is active
Before, we applied delayed rendering (that is, we gave the client a
chance to do more writes before we scheduled a render refresh) only
when the renderer were idle.
However, with e.g. a high keyboard repeat rate, it is very much
possible to start the render loop and then never break out of it while
receiving keyboard input.
This causes screen flickering, as we're no longer even trying to
detect the clients transaction boundaries.
So, let's rewrite how this is done.
First, we give the user the ability to disable delayed rendering
altogether, by setting either the lower or upper timeout to 0.
Second, when delayed rendering is enabled, we ignore the frame
callback. That is, when receiving input, we *always* reschedule the
lower timeout timer, regardless of whether the render is idle or not.
The render's frame callback handler will *not* render the grid if the
delayed render timers are armed.
This means for longer client data bursts, we may now skip frames. That
is, we're trading screen flicker for the occasional frame hickup.
For short client data bursts we should behave roughly as before.
This greatly improves the behavior of fullscreen, or near fullscreen,
updates of large grids (example, scrolling in emacs in fullscreen,
with a vertical buffer split).
2020-03-23 19:21:41 +01:00
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
if (urls)
|
|
|
|
|
render_urls(term);
|
2020-12-20 15:01:21 -07:00
|
|
|
|
2022-04-24 12:04:06 +02:00
|
|
|
if ((grid && !term->delayed_render_timer.is_armed) || (csd | search | urls))
|
2020-09-29 10:04:41 +02:00
|
|
|
grid_render(term);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
tll_foreach(term->wl->seats, it) {
|
2022-06-15 18:41:08 +02:00
|
|
|
if (it->item.ime_focus == term)
|
2021-03-23 13:56:33 +01:00
|
|
|
ime_update_cursor_rect(&it->item);
|
2021-01-31 11:12:07 +01:00
|
|
|
}
|
url-mode: snapshot screen state when entering URL mode
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.
2021-02-22 10:22:41 +01:00
|
|
|
|
|
|
|
|
term->grid = original_grid;
|
2020-03-06 19:16:54 +01:00
|
|
|
}
|
|
|
|
|
|
2021-01-17 16:12:54 +01:00
|
|
|
static void
|
|
|
|
|
tiocswinsz(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
if (term->ptmx >= 0) {
|
|
|
|
|
if (ioctl(term->ptmx, (unsigned int)TIOCSWINSZ,
|
|
|
|
|
&(struct winsize){
|
|
|
|
|
.ws_row = term->rows,
|
|
|
|
|
.ws_col = term->cols,
|
|
|
|
|
.ws_xpixel = term->cols * term->cell_width,
|
|
|
|
|
.ws_ypixel = term->rows * term->cell_height}) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("TIOCSWINSZ");
|
|
|
|
|
}
|
2024-06-30 19:44:17 +02:00
|
|
|
|
|
|
|
|
term_send_size_notification(term);
|
2021-01-17 16:12:54 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-05 17:05:44 +02:00
|
|
|
static void
|
|
|
|
|
delayed_reflow_of_normal_grid(struct terminal *term)
|
|
|
|
|
{
|
2022-10-09 16:17:22 +02:00
|
|
|
if (term->interactive_resizing.grid == NULL)
|
2022-10-05 17:05:44 +02:00
|
|
|
return;
|
|
|
|
|
|
2022-10-09 16:17:22 +02:00
|
|
|
xassert(term->interactive_resizing.new_rows > 0);
|
|
|
|
|
|
2022-10-05 17:05:44 +02:00
|
|
|
struct coord *const tracking_points[] = {
|
|
|
|
|
&term->selection.coords.start,
|
|
|
|
|
&term->selection.coords.end,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Reflow the original (since before the resize was started) grid,
|
|
|
|
|
* to the *current* dimensions */
|
|
|
|
|
grid_resize_and_reflow(
|
2025-02-10 08:57:51 +01:00
|
|
|
term->interactive_resizing.grid, term,
|
2022-10-09 16:17:22 +02:00
|
|
|
term->interactive_resizing.new_rows, term->normal.num_cols,
|
|
|
|
|
term->interactive_resizing.old_screen_rows, term->rows,
|
2022-10-05 17:05:44 +02:00
|
|
|
term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0,
|
|
|
|
|
tracking_points);
|
|
|
|
|
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Replace the current, truncated, "normal" grid with the
|
2022-10-05 17:05:44 +02:00
|
|
|
* correctly reflowed one */
|
|
|
|
|
grid_free(&term->normal);
|
2022-10-09 16:17:22 +02:00
|
|
|
term->normal = *term->interactive_resizing.grid;
|
|
|
|
|
free(term->interactive_resizing.grid);
|
2022-10-05 17:05:44 +02:00
|
|
|
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
term->hide_cursor = term->interactive_resizing.old_hide_cursor;
|
|
|
|
|
|
2022-10-05 17:05:44 +02:00
|
|
|
/* Reset */
|
2022-10-09 16:17:22 +02:00
|
|
|
term->interactive_resizing.grid = NULL;
|
|
|
|
|
term->interactive_resizing.old_screen_rows = 0;
|
|
|
|
|
term->interactive_resizing.new_rows = 0;
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
term->interactive_resizing.old_hide_cursor = false;
|
2022-10-05 17:05:44 +02:00
|
|
|
|
|
|
|
|
/* Invalidate render pointers */
|
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-05 10:48:36 +02:00
|
|
|
wait_for_preapply_damage(term);
|
2022-10-05 17:05:44 +02:00
|
|
|
shm_unref(term->render.last_buf);
|
|
|
|
|
term->render.last_buf = NULL;
|
|
|
|
|
term->render.last_cursor.row = NULL;
|
|
|
|
|
|
2022-10-09 16:17:22 +02:00
|
|
|
tll_free(term->normal.scroll_damage);
|
|
|
|
|
sixel_reflow_grid(term, &term->normal);
|
|
|
|
|
|
2022-10-09 16:11:49 +02:00
|
|
|
if (term->grid == &term->normal) {
|
2022-10-05 17:05:44 +02:00
|
|
|
term_damage_view(term);
|
2022-10-09 16:11:49 +02:00
|
|
|
render_refresh(term);
|
|
|
|
|
}
|
2022-10-06 17:23:56 +02:00
|
|
|
|
|
|
|
|
term_ptmx_resume(term);
|
2022-10-05 17:05:44 +02:00
|
|
|
}
|
|
|
|
|
|
2021-01-17 16:12:54 +01:00
|
|
|
static bool
|
|
|
|
|
fdm_tiocswinsz(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
|
2022-10-05 17:05:44 +02:00
|
|
|
if (events & EPOLLIN) {
|
2021-01-17 16:12:54 +01:00
|
|
|
tiocswinsz(term);
|
2022-10-05 17:05:44 +02:00
|
|
|
delayed_reflow_of_normal_grid(term);
|
|
|
|
|
}
|
2021-01-17 16:12:54 +01:00
|
|
|
|
2021-07-14 20:14:10 +02:00
|
|
|
if (term->window->resize_timeout_fd >= 0) {
|
|
|
|
|
fdm_del(fdm, term->window->resize_timeout_fd);
|
|
|
|
|
term->window->resize_timeout_fd = -1;
|
|
|
|
|
}
|
2021-01-17 16:12:54 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
send_dimensions_to_client(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
struct wl_window *win = term->window;
|
|
|
|
|
|
2021-01-21 15:14:43 +01:00
|
|
|
if (!win->is_resizing || term->conf->resize_delay_ms == 0) {
|
2021-01-17 16:12:54 +01:00
|
|
|
/* Send new dimensions to client immediately */
|
|
|
|
|
tiocswinsz(term);
|
2022-10-05 17:05:44 +02:00
|
|
|
delayed_reflow_of_normal_grid(term);
|
2021-01-17 16:12:54 +01:00
|
|
|
|
|
|
|
|
/* And make sure to reset and deallocate a lingering timer */
|
|
|
|
|
if (win->resize_timeout_fd >= 0) {
|
|
|
|
|
fdm_del(term->fdm, win->resize_timeout_fd);
|
|
|
|
|
win->resize_timeout_fd = -1;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Send new dimensions to client "in a while" */
|
2021-01-21 15:14:43 +01:00
|
|
|
assert(win->is_resizing && term->conf->resize_delay_ms > 0);
|
2021-01-17 16:12:54 +01:00
|
|
|
|
|
|
|
|
int fd = win->resize_timeout_fd;
|
2021-01-21 15:14:43 +01:00
|
|
|
uint16_t delay_ms = term->conf->resize_delay_ms;
|
2021-01-17 16:12:54 +01:00
|
|
|
bool successfully_scheduled = false;
|
|
|
|
|
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
/* Lazy create timer fd */
|
|
|
|
|
fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
|
|
|
|
|
if (fd < 0)
|
|
|
|
|
LOG_ERRNO("failed to create TIOCSWINSZ timer");
|
|
|
|
|
else if (!fdm_add(term->fdm, fd, EPOLLIN, &fdm_tiocswinsz, term)) {
|
|
|
|
|
close(fd);
|
|
|
|
|
fd = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
win->resize_timeout_fd = fd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fd >= 0) {
|
2021-01-17 16:38:24 +01:00
|
|
|
/* Reset timeout */
|
2021-01-17 16:12:54 +01:00
|
|
|
const struct itimerspec timeout = {
|
2022-10-03 19:41:13 +03:00
|
|
|
.it_value = {
|
|
|
|
|
.tv_sec = delay_ms / 1000,
|
|
|
|
|
.tv_nsec = (delay_ms % 1000) * 1000000,
|
|
|
|
|
},
|
2021-01-17 16:12:54 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (timerfd_settime(fd, 0, &timeout, NULL) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to arm TIOCSWINSZ timer");
|
|
|
|
|
fdm_del(term->fdm, fd);
|
|
|
|
|
win->resize_timeout_fd = -1;
|
|
|
|
|
} else
|
|
|
|
|
successfully_scheduled = true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-09 16:16:50 +02:00
|
|
|
if (!successfully_scheduled) {
|
2021-01-17 16:12:54 +01:00
|
|
|
tiocswinsz(term);
|
2022-10-09 16:16:50 +02:00
|
|
|
delayed_reflow_of_normal_grid(term);
|
|
|
|
|
}
|
2021-01-17 16:12:54 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-17 15:00:14 -05:00
|
|
|
static void
|
|
|
|
|
set_size_from_grid(struct terminal *term, int *width, int *height, int cols, int rows)
|
|
|
|
|
{
|
2025-01-26 09:28:54 +01:00
|
|
|
int new_width, new_height;
|
|
|
|
|
|
2024-01-17 15:00:14 -05:00
|
|
|
/* Nominal grid dimensions */
|
2025-01-26 09:28:54 +01:00
|
|
|
new_width = cols * term->cell_width;
|
|
|
|
|
new_height = rows * term->cell_height;
|
2024-01-17 15:00:14 -05:00
|
|
|
|
|
|
|
|
/* Include any configured padding */
|
2025-01-26 09:28:54 +01:00
|
|
|
new_width += 2 * term->conf->pad_x * term->scale;
|
|
|
|
|
new_height += 2 * term->conf->pad_y * term->scale;
|
2024-01-17 15:00:14 -05:00
|
|
|
|
|
|
|
|
/* Round to multiples of scale */
|
2025-01-26 09:28:54 +01:00
|
|
|
new_width = round(term->scale * round(new_width / term->scale));
|
|
|
|
|
new_height = round(term->scale * round(new_height / term->scale));
|
|
|
|
|
|
|
|
|
|
if (width != NULL)
|
|
|
|
|
*width = new_width;
|
|
|
|
|
if (height != NULL)
|
|
|
|
|
*height = new_height;
|
2024-01-17 15:00:14 -05:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
/* Move to terminal.c? */
|
2024-01-17 15:00:14 -05:00
|
|
|
bool
|
|
|
|
|
render_resize(struct terminal *term, int width, int height, uint8_t opts)
|
2019-07-05 10:16:56 +02:00
|
|
|
{
|
2021-07-31 19:08:51 +02:00
|
|
|
if (term->shutdown.in_progress)
|
2020-03-17 13:27:26 +01:00
|
|
|
return false;
|
|
|
|
|
|
2020-02-26 12:21:03 +01:00
|
|
|
if (!term->window->is_configured)
|
|
|
|
|
return false;
|
|
|
|
|
|
2020-02-25 20:29:44 +01:00
|
|
|
if (term->cell_width == 0 && term->cell_height == 0)
|
2020-02-25 19:51:03 +01:00
|
|
|
return false;
|
2020-01-03 12:54:03 +01:00
|
|
|
|
2024-01-17 15:00:14 -05:00
|
|
|
const bool is_floating =
|
|
|
|
|
!term->window->is_maximized &&
|
|
|
|
|
!term->window->is_fullscreen &&
|
|
|
|
|
!term->window->is_tiled;
|
|
|
|
|
|
|
|
|
|
/* Convert logical size to physical size */
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
const float scale = term->scale;
|
2023-07-14 16:53:50 -07:00
|
|
|
width = round(width * scale);
|
|
|
|
|
height = round(height * scale);
|
2019-08-12 21:22:38 +02:00
|
|
|
|
2024-01-17 15:00:14 -05:00
|
|
|
/* If the grid should be kept, the size should be overridden */
|
|
|
|
|
if (is_floating && (opts & RESIZE_KEEP_GRID)) {
|
|
|
|
|
set_size_from_grid(term, &width, &height, term->cols, term->rows);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-26 09:28:54 +01:00
|
|
|
if (width == 0) {
|
|
|
|
|
/* The compositor is letting us choose the width */
|
|
|
|
|
if (term->stashed_width != 0) {
|
2024-01-17 15:00:14 -05:00
|
|
|
/* If a default size is requested, prefer the "last used" size */
|
2020-10-20 20:58:03 +02:00
|
|
|
width = term->stashed_width;
|
2020-02-26 20:59:11 +01:00
|
|
|
} else {
|
2024-01-17 15:00:14 -05:00
|
|
|
/* Otherwise, use a user-configured size */
|
2020-09-08 19:17:29 +02:00
|
|
|
switch (term->conf->size.type) {
|
|
|
|
|
case CONF_SIZE_PX:
|
2020-11-30 02:24:38 +00:00
|
|
|
width = term->conf->size.width;
|
2025-01-26 09:28:54 +01:00
|
|
|
|
|
|
|
|
if (wayl_win_csd_borders_visible(term->window))
|
|
|
|
|
width -= 2 * term->conf->csd.border_width_visible;
|
|
|
|
|
|
|
|
|
|
width *= scale;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CONF_SIZE_CELLS:
|
|
|
|
|
set_size_from_grid(term, &width, NULL,
|
|
|
|
|
term->conf->size.width, term->conf->size.height);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (height == 0) {
|
|
|
|
|
/* The compositor is letting us choose the height */
|
|
|
|
|
if (term->stashed_height != 0) {
|
|
|
|
|
/* If a default size is requested, prefer the "last used" size */
|
|
|
|
|
height = term->stashed_height;
|
|
|
|
|
} else {
|
|
|
|
|
/* Otherwise, use a user-configured size */
|
|
|
|
|
switch (term->conf->size.type) {
|
|
|
|
|
case CONF_SIZE_PX:
|
2020-11-30 02:24:38 +00:00
|
|
|
height = term->conf->size.height;
|
2020-09-08 19:17:29 +02:00
|
|
|
|
2022-04-16 11:26:28 +02:00
|
|
|
/* Take CSDs into account */
|
|
|
|
|
if (wayl_win_csd_titlebar_visible(term->window))
|
2020-09-08 19:17:29 +02:00
|
|
|
height -= term->conf->csd.title_height;
|
2020-09-14 17:34:04 +02:00
|
|
|
|
2025-01-26 09:28:54 +01:00
|
|
|
if (wayl_win_csd_borders_visible(term->window))
|
2022-04-16 11:37:18 +02:00
|
|
|
height -= 2 * term->conf->csd.border_width_visible;
|
|
|
|
|
|
2020-09-14 17:34:04 +02:00
|
|
|
height *= scale;
|
2020-09-08 19:17:29 +02:00
|
|
|
break;
|
2020-02-28 18:50:15 +01:00
|
|
|
|
2020-09-08 19:17:29 +02:00
|
|
|
case CONF_SIZE_CELLS:
|
2025-01-26 09:28:54 +01:00
|
|
|
set_size_from_grid(term, NULL, &height,
|
2024-01-17 15:00:14 -05:00
|
|
|
term->conf->size.width, term->conf->size.height);
|
2020-09-08 19:17:29 +02:00
|
|
|
break;
|
2020-02-28 18:50:15 +01:00
|
|
|
}
|
2020-02-26 20:59:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-25 19:10:48 +01:00
|
|
|
/* Don't shrink grid too much */
|
2025-02-10 12:42:29 +01:00
|
|
|
const int min_cols = 1;
|
2020-09-07 19:34:06 +02:00
|
|
|
const int min_rows = 1;
|
2020-02-25 19:10:48 +01:00
|
|
|
|
2023-03-02 17:22:27 +01:00
|
|
|
/* Minimum window size (must be divisible by the scaling factor)*/
|
2023-07-28 15:28:10 +02:00
|
|
|
const int min_width = roundf(scale * ceilf((min_cols * term->cell_width) / scale));
|
|
|
|
|
const int min_height = roundf(scale * ceilf((min_rows * term->cell_height) / scale));
|
2020-02-25 19:10:48 +01:00
|
|
|
|
|
|
|
|
width = max(width, min_width);
|
|
|
|
|
height = max(height, min_height);
|
|
|
|
|
|
2020-03-02 18:42:49 +01:00
|
|
|
/* Padding */
|
|
|
|
|
const int max_pad_x = (width - min_width) / 2;
|
|
|
|
|
const int max_pad_y = (height - min_height) / 2;
|
|
|
|
|
const int pad_x = min(max_pad_x, scale * term->conf->pad_x);
|
|
|
|
|
const int pad_y = min(max_pad_y, scale * term->conf->pad_y);
|
|
|
|
|
|
2024-01-17 15:00:14 -05:00
|
|
|
if (is_floating &&
|
|
|
|
|
(opts & RESIZE_BY_CELLS) &&
|
|
|
|
|
term->conf->resize_by_cells)
|
|
|
|
|
{
|
|
|
|
|
/* If resizing in cell increments, restrict the width and height */
|
|
|
|
|
width = ((width - 2 * pad_x) / term->cell_width) * term->cell_width + 2 * pad_x;
|
|
|
|
|
width = max(min_width, roundf(scale * roundf(width / scale)));
|
|
|
|
|
|
|
|
|
|
height = ((height - 2 * pad_y) / term->cell_height) * term->cell_height + 2 * pad_y;
|
|
|
|
|
height = max(min_height, roundf(scale * roundf(height / scale)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(opts & RESIZE_FORCE) &&
|
|
|
|
|
width == term->width &&
|
|
|
|
|
height == term->height &&
|
|
|
|
|
scale == term->scale)
|
|
|
|
|
{
|
2020-02-25 19:51:03 +01:00
|
|
|
return false;
|
2024-01-17 15:00:14 -05:00
|
|
|
}
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2020-01-12 12:19:38 +01:00
|
|
|
/* Cancel an application initiated "Synchronized Update" */
|
2020-01-12 12:55:19 +01:00
|
|
|
term_disable_app_sync_updates(term);
|
2020-01-12 12:19:38 +01:00
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
/* Drop out of URL mode */
|
|
|
|
|
urls_reset(term);
|
|
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
LOG_DBG("resized: size=%dx%d (scale=%.2f)", width, height, term->scale);
|
2019-07-05 10:16:56 +02:00
|
|
|
term->width = width;
|
|
|
|
|
term->height = height;
|
|
|
|
|
|
2020-02-15 19:01:26 +01:00
|
|
|
/* Screen rows/cols before resize */
|
2022-10-05 17:05:44 +02:00
|
|
|
int old_cols = term->cols;
|
|
|
|
|
int old_rows = term->rows;
|
2020-02-15 19:01:26 +01:00
|
|
|
|
|
|
|
|
/* Screen rows/cols after resize */
|
2020-03-02 18:42:49 +01:00
|
|
|
const int new_cols = (term->width - 2 * pad_x) / term->cell_width;
|
|
|
|
|
const int new_rows = (term->height - 2 * pad_y) / term->cell_height;
|
2020-02-15 19:01:26 +01:00
|
|
|
|
render: resize(): don't overflow the number of scrollback lines
The config system allows setting the scrollback lines to
2**32-1.
However, the total number of grid lines is the scrollback lines plus
the window size, and then rounded *up* to the nearest power of two.
Furthermore, the number of rows is represented with a plain 'int'
throughout the code base.
The largest positive integer that fits in an int is 2**31-1. That
however, is not a power of two.
The largest positive integer, that also is a power of two, that fits
in an int is 2**30, or 1073741824.
Ideally, we'd just cast the line count to a 64-bit integer, and call
__builtin_clzl{,l}() on it, and then take the smallest value of that,
or 2**30. But, for some reason, __builtin_clzl(), and
__builtin_clzll() appears to ignore bits above 32, despite they being
typed to long and long long. Bug?
Instead, ensure we never call __builtin_clz() on anything larger than
2**30.
Closes #1828
2024-09-09 06:51:10 +02:00
|
|
|
/*
|
|
|
|
|
* Requirements for scrollback:
|
|
|
|
|
*
|
|
|
|
|
* a) total number of rows (visible + scrollback history) must be
|
|
|
|
|
* a power of two
|
|
|
|
|
* b) must be representable in a plain int (signed)
|
|
|
|
|
*
|
|
|
|
|
* This means that on a "normal" system, where ints are 32-bit,
|
|
|
|
|
* the largest possible scrollback size is 1073741824 (0x40000000,
|
|
|
|
|
* 1 << 30).
|
|
|
|
|
*
|
|
|
|
|
* The largest *signed* int is 2147483647 (0x7fffffff), which is
|
|
|
|
|
* *not* a power of two.
|
|
|
|
|
*
|
|
|
|
|
* Note that these are theoretical limits. Most of the time,
|
|
|
|
|
* you'll get a memory allocation failure when trying to allocate
|
|
|
|
|
* the grid array.
|
|
|
|
|
*/
|
|
|
|
|
const unsigned max_scrollback = (INT_MAX >> 1) + 1;
|
|
|
|
|
const unsigned scrollback_lines_not_yet_power_of_two =
|
|
|
|
|
min((uint64_t)term->render.scrollback_lines + new_rows - 1, max_scrollback);
|
|
|
|
|
|
2020-02-15 19:01:26 +01:00
|
|
|
/* Grid rows/cols after resize */
|
render: resize(): don't overflow the number of scrollback lines
The config system allows setting the scrollback lines to
2**32-1.
However, the total number of grid lines is the scrollback lines plus
the window size, and then rounded *up* to the nearest power of two.
Furthermore, the number of rows is represented with a plain 'int'
throughout the code base.
The largest positive integer that fits in an int is 2**31-1. That
however, is not a power of two.
The largest positive integer, that also is a power of two, that fits
in an int is 2**30, or 1073741824.
Ideally, we'd just cast the line count to a 64-bit integer, and call
__builtin_clzl{,l}() on it, and then take the smallest value of that,
or 2**30. But, for some reason, __builtin_clzl(), and
__builtin_clzll() appears to ignore bits above 32, despite they being
typed to long and long long. Bug?
Instead, ensure we never call __builtin_clz() on anything larger than
2**30.
Closes #1828
2024-09-09 06:51:10 +02:00
|
|
|
const int new_normal_grid_rows =
|
|
|
|
|
min(1u << (32 - __builtin_clz(scrollback_lines_not_yet_power_of_two)),
|
|
|
|
|
max_scrollback);
|
|
|
|
|
const int new_alt_grid_rows =
|
|
|
|
|
min(1u << (32 - __builtin_clz(new_rows)), max_scrollback);
|
|
|
|
|
|
|
|
|
|
LOG_DBG("grid rows: %d", new_normal_grid_rows);
|
2019-07-09 09:12:41 +02:00
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(new_cols >= 1);
|
|
|
|
|
xassert(new_rows >= 1);
|
2020-02-15 19:01:26 +01:00
|
|
|
|
|
|
|
|
/* Margins */
|
2021-01-06 11:17:29 +01:00
|
|
|
const int grid_width = new_cols * term->cell_width;
|
|
|
|
|
const int grid_height = new_rows * term->cell_height;
|
|
|
|
|
const int total_x_pad = term->width - grid_width;
|
|
|
|
|
const int total_y_pad = term->height - grid_height;
|
|
|
|
|
|
2025-05-23 08:38:00 +02:00
|
|
|
const enum center_when center = term->conf->center_when;
|
|
|
|
|
const bool centered_padding =
|
|
|
|
|
center == CENTER_ALWAYS ||
|
|
|
|
|
(center == CENTER_MAXIMIZED_AND_FULLSCREEN &&
|
|
|
|
|
(term->window->is_fullscreen || term->window->is_maximized)) ||
|
|
|
|
|
(center == CENTER_FULLSCREEN && term->window->is_fullscreen);
|
2024-02-13 16:28:03 +01:00
|
|
|
|
|
|
|
|
if (centered_padding && !term->window->is_resizing) {
|
2021-01-06 11:17:29 +01:00
|
|
|
term->margins.left = total_x_pad / 2;
|
|
|
|
|
term->margins.top = total_y_pad / 2;
|
|
|
|
|
} else {
|
|
|
|
|
term->margins.left = pad_x;
|
|
|
|
|
term->margins.top = pad_y;
|
|
|
|
|
}
|
|
|
|
|
term->margins.right = total_x_pad - term->margins.left;
|
|
|
|
|
term->margins.bottom = total_y_pad - term->margins.top;
|
2019-08-27 15:25:35 +02:00
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term->margins.left >= pad_x);
|
|
|
|
|
xassert(term->margins.right >= pad_x);
|
|
|
|
|
xassert(term->margins.top >= pad_y);
|
|
|
|
|
xassert(term->margins.bottom >= pad_y);
|
2020-02-25 19:10:48 +01:00
|
|
|
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
if (new_cols == old_cols && new_rows == old_rows) {
|
2020-02-15 22:19:08 +01:00
|
|
|
LOG_DBG("grid layout unaffected; skipping reflow");
|
2022-10-09 16:17:22 +02:00
|
|
|
term->interactive_resizing.new_rows = new_normal_grid_rows;
|
2020-02-15 22:19:08 +01:00
|
|
|
goto damage_view;
|
render: initial support for text reflow
The algorithm is as follows:
Start at the beginning of the scrollback. That is, at the oldest
emitted lines. This is done by taking the current offset, and adding
the number of (old) screen rows, and then iterating until we find the
first allocated line.
Next, we iterate the entire old grid. At the beginning, we allocate a
line for the new grid, and setup a global pointer for that line, and
the current cell index.
For each line in the old grid, iterate its cells. Copy the the cells
over to the new line. Whenever the new line reaches its maximum number
of columns, we line break it by increasing the current row index and
allocating a new row (if necessary - we may be overwriting old
scrollback if the new grid is smaller than the old grid).
Whenever we reach the end of a line of the old grid, we insert a line
break in the new grid's line too **if** the last cell in the old line
was empty. If it was **not** empty, we **don't** line break the new
line.
Furthermore, empty cells in general need special consideration. A line
ending with a string of empty cells doesn't have to be copied the new
line. And more importantly, should **not** increase the new line's
cell index (which may cause line breaks, which is incorrect).
However, if a string of empty cells is followed by non empty cells, we
need to copy all the preceding empty cells to the line too.
When the entire scrollback history has been reflowed, we need to
figure out the new grid's offset.
This is done by trying to put the **last** emitted line at the bottom
of the screen. I.e. the new offset is typically "last_line_idx -
term->rows". However, we need to handle empty lines. So, after
subtracting the number of screen rows, we _increase_ the offset until
we see a non-empty line. This ensures we handle grid's that doesn't
fill an entire screen.
Finally, we need to re-position the cursor. This is done by trying to
place the cursor **at** (_not_ after) the last emitted line. We keep
the current cursor column as is (but possibly truncated, if the grid's
width decreased).
2020-02-10 20:35:24 +01:00
|
|
|
}
|
2019-07-09 09:12:41 +02:00
|
|
|
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
|
|
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Since text reflow is slow, don't do it *while* resizing. Only
|
|
|
|
|
* do it when done, or after "pausing" the resize for sufficiently
|
2023-10-03 14:11:55 +02:00
|
|
|
* long. We reuse the TIOCSWINSZ timer to handle this. See
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
* send_dimensions_to_client() and fdm_tiocswinsz().
|
|
|
|
|
*
|
|
|
|
|
* To be able to do the final reflow correctly, we need a copy of
|
|
|
|
|
* the original grid, before the resize started.
|
|
|
|
|
*/
|
2022-10-23 10:34:18 +02:00
|
|
|
if (term->window->is_resizing && term->conf->resize_delay_ms > 0) {
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
if (term->interactive_resizing.grid == NULL) {
|
|
|
|
|
term_ptmx_pause(term);
|
|
|
|
|
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Stash the current 'normal' grid, as-is, to be used when
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
* doing the final reflow */
|
|
|
|
|
term->interactive_resizing.old_screen_rows = term->rows;
|
|
|
|
|
term->interactive_resizing.old_cols = term->cols;
|
|
|
|
|
term->interactive_resizing.old_hide_cursor = term->hide_cursor;
|
|
|
|
|
term->interactive_resizing.grid = xmalloc(sizeof(*term->interactive_resizing.grid));
|
|
|
|
|
*term->interactive_resizing.grid = term->normal;
|
2023-06-20 15:59:16 +02:00
|
|
|
|
|
|
|
|
if (term->grid == &term->normal)
|
|
|
|
|
term->interactive_resizing.selection_coords = term->selection.coords;
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
} else {
|
2024-02-06 12:36:45 +01:00
|
|
|
/* We'll replace the current temporary grid, with a new
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
* one (again based on the original grid) */
|
|
|
|
|
grid_free(&term->normal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct grid *orig = term->interactive_resizing.grid;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Copy the current viewport (of the original grid) to a new
|
|
|
|
|
* grid that will be used during the resize. For now, throw
|
2024-02-06 12:36:45 +01:00
|
|
|
* away sixels and OSC-8 URLs. They'll be "restored" when we
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
* do the final reflow.
|
|
|
|
|
*
|
|
|
|
|
* Note that OSC-8 URLs are perfectly ok to throw away; they
|
|
|
|
|
* cannot be interacted with during the resize. And, even if
|
2024-02-06 12:36:45 +01:00
|
|
|
* url.osc8-underline=always, the "underline" attribute is
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
* part of the cell, not the URI struct (and thus our faked
|
|
|
|
|
* grid will still render OSC-8 links underlined).
|
|
|
|
|
*
|
|
|
|
|
* TODO:
|
|
|
|
|
* - sixels?
|
|
|
|
|
*/
|
|
|
|
|
struct grid g = {
|
|
|
|
|
.num_rows = 1 << (32 - __builtin_clz(term->interactive_resizing.old_screen_rows)),
|
|
|
|
|
.num_cols = term->interactive_resizing.old_cols,
|
|
|
|
|
.offset = 0,
|
|
|
|
|
.view = 0,
|
|
|
|
|
.cursor = orig->cursor,
|
|
|
|
|
.saved_cursor = orig->saved_cursor,
|
|
|
|
|
.rows = xcalloc(g.num_rows, sizeof(g.rows[0])),
|
|
|
|
|
.cur_row = NULL,
|
|
|
|
|
.scroll_damage = tll_init(),
|
|
|
|
|
.sixel_images = tll_init(),
|
|
|
|
|
.kitty_kbd = orig->kitty_kbd,
|
|
|
|
|
};
|
|
|
|
|
|
2023-03-27 16:53:41 +02:00
|
|
|
term->selection.coords.start.row -= orig->view;
|
|
|
|
|
term->selection.coords.end.row -= orig->view;
|
|
|
|
|
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
for (size_t i = 0, j = orig->view;
|
|
|
|
|
i < term->interactive_resizing.old_screen_rows;
|
|
|
|
|
i++, j = (j + 1) & (orig->num_rows - 1))
|
|
|
|
|
{
|
|
|
|
|
g.rows[i] = grid_row_alloc(g.num_cols, false);
|
|
|
|
|
memcpy(g.rows[i]->cells,
|
|
|
|
|
orig->rows[j]->cells,
|
|
|
|
|
g.num_cols * sizeof(g.rows[i]->cells[0]));
|
2024-06-24 00:57:24 +02:00
|
|
|
|
|
|
|
|
if (orig->rows[j]->extra == NULL ||
|
2024-07-01 20:00:16 +01:00
|
|
|
orig->rows[j]->extra->underline_ranges.count == 0)
|
2024-06-24 00:57:24 +02:00
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2024-07-01 20:00:16 +01:00
|
|
|
* Copy underline ranges
|
2024-06-24 00:57:24 +02:00
|
|
|
*/
|
|
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
const struct row_ranges *underline_src = &orig->rows[j]->extra->underline_ranges;
|
2024-06-24 00:57:24 +02:00
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
const int count = underline_src->count;
|
2024-06-24 00:57:24 +02:00
|
|
|
g.rows[i]->extra = xcalloc(1, sizeof(*g.rows[i]->extra));
|
2024-07-01 20:00:16 +01:00
|
|
|
g.rows[i]->extra->underline_ranges.v = xmalloc(
|
|
|
|
|
count * sizeof(g.rows[i]->extra->underline_ranges.v[0]));
|
2024-06-24 00:57:24 +02:00
|
|
|
|
2024-07-01 20:00:16 +01:00
|
|
|
struct row_ranges *underline_dst = &g.rows[i]->extra->underline_ranges;
|
|
|
|
|
underline_dst->count = underline_dst->size = count;
|
2024-06-24 00:57:24 +02:00
|
|
|
|
|
|
|
|
for (int k = 0; k < count; k++)
|
2024-07-01 20:00:16 +01:00
|
|
|
underline_dst->v[k] = underline_src->v[k];
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
term->normal = g;
|
|
|
|
|
term->hide_cursor = true;
|
|
|
|
|
}
|
|
|
|
|
|
resize: don’t reflow text on alt screen
Alt screen applications normally reflow/readjust themselves on a
window resize.
When we do it too, the result is graphical glitches/flashes since our
re-flowed text is rendered in one frame, and the application re-flowed
text soon thereafter.
We can’t avoid rendering some kind of re-flowed frame, since we don’t
know when, or even if, the application will update itself. To avoid
glitches, we need to render, as closely as possible, what the
application itself will render shortly.
This is actually pretty simple; we just need to copy the visible
content over from the old grid to the new grid. We don’t bother with
text re-flow, but simply truncate long lines.
To simplify things, we simply cancel any active selection (since often
times, it will be corrupted anyway when the application redraws
itself).
Since we’re not reflowing text, there’s no need to translate e.g. the
cursor position - we just keep the current position (but bounded to
the new dimensions).
Fun thing: ‘less’ gets corrupted if we don’t leave the cursor at
the (new) bottom row. To handle this, we check if the cursor (before
resize) is at the bottom row, and if so, we move it to the new bottom
row.
Closes #221
2020-11-24 19:00:57 +01:00
|
|
|
if (term->grid == &term->alt)
|
|
|
|
|
selection_cancel(term);
|
2022-02-07 10:38:30 +01:00
|
|
|
else {
|
|
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Don't cancel, but make sure there aren't any ongoing
|
2022-02-07 10:38:30 +01:00
|
|
|
* selections after the resize.
|
|
|
|
|
*/
|
|
|
|
|
tll_foreach(term->wl->seats, it) {
|
|
|
|
|
if (it->item.kbd_focus == term)
|
|
|
|
|
selection_finalize(&it->item, term, it->item.pointer.serial);
|
|
|
|
|
}
|
|
|
|
|
}
|
resize: don’t reflow text on alt screen
Alt screen applications normally reflow/readjust themselves on a
window resize.
When we do it too, the result is graphical glitches/flashes since our
re-flowed text is rendered in one frame, and the application re-flowed
text soon thereafter.
We can’t avoid rendering some kind of re-flowed frame, since we don’t
know when, or even if, the application will update itself. To avoid
glitches, we need to render, as closely as possible, what the
application itself will render shortly.
This is actually pretty simple; we just need to copy the visible
content over from the old grid to the new grid. We don’t bother with
text re-flow, but simply truncate long lines.
To simplify things, we simply cancel any active selection (since often
times, it will be corrupted anyway when the application redraws
itself).
Since we’re not reflowing text, there’s no need to translate e.g. the
cursor position - we just keep the current position (but bounded to
the new dimensions).
Fun thing: ‘less’ gets corrupted if we don’t leave the cursor at
the (new) bottom row. To handle this, we check if the cursor (before
resize) is at the bottom row, and if so, we move it to the new bottom
row.
Closes #221
2020-11-24 19:00:57 +01:00
|
|
|
|
2022-02-07 10:42:32 +01:00
|
|
|
/*
|
|
|
|
|
* TODO: if we remove the selection_finalize() call above (i.e. if
|
|
|
|
|
* we start allowing selections to be ongoing across resizes), the
|
2024-02-06 12:36:45 +01:00
|
|
|
* selection's pivot point coordinates *must* be added to the
|
2022-02-07 10:42:32 +01:00
|
|
|
* tracking points list.
|
|
|
|
|
*/
|
resize: don’t reflow text on alt screen
Alt screen applications normally reflow/readjust themselves on a
window resize.
When we do it too, the result is graphical glitches/flashes since our
re-flowed text is rendered in one frame, and the application re-flowed
text soon thereafter.
We can’t avoid rendering some kind of re-flowed frame, since we don’t
know when, or even if, the application will update itself. To avoid
glitches, we need to render, as closely as possible, what the
application itself will render shortly.
This is actually pretty simple; we just need to copy the visible
content over from the old grid to the new grid. We don’t bother with
text re-flow, but simply truncate long lines.
To simplify things, we simply cancel any active selection (since often
times, it will be corrupted anyway when the application redraws
itself).
Since we’re not reflowing text, there’s no need to translate e.g. the
cursor position - we just keep the current position (but bounded to
the new dimensions).
Fun thing: ‘less’ gets corrupted if we don’t leave the cursor at
the (new) bottom row. To handle this, we check if the cursor (before
resize) is at the bottom row, and if so, we move it to the new bottom
row.
Closes #221
2020-11-24 19:00:57 +01:00
|
|
|
/* Resize grids */
|
2022-10-23 10:34:18 +02:00
|
|
|
if (term->window->is_resizing && term->conf->resize_delay_ms > 0) {
|
2022-10-05 17:05:44 +02:00
|
|
|
/* Simple truncating resize, *while* an interactive resize is
|
|
|
|
|
* ongoing. */
|
2022-10-09 16:17:22 +02:00
|
|
|
xassert(term->interactive_resizing.grid != NULL);
|
|
|
|
|
xassert(new_normal_grid_rows > 0);
|
|
|
|
|
term->interactive_resizing.new_rows = new_normal_grid_rows;
|
|
|
|
|
|
2022-10-05 17:05:44 +02:00
|
|
|
grid_resize_without_reflow(
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
&term->normal, new_alt_grid_rows, new_cols,
|
|
|
|
|
term->interactive_resizing.old_screen_rows, new_rows);
|
2022-10-05 17:05:44 +02:00
|
|
|
} else {
|
|
|
|
|
/* Full text reflow */
|
|
|
|
|
|
2023-06-20 15:59:16 +02:00
|
|
|
int old_normal_rows = old_rows;
|
|
|
|
|
|
2022-10-09 16:17:22 +02:00
|
|
|
if (term->interactive_resizing.grid != NULL) {
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Throw away the current, truncated, "normal" grid, and
|
2022-10-05 17:05:44 +02:00
|
|
|
* use the original grid instead (from before the resize
|
|
|
|
|
* started) */
|
|
|
|
|
grid_free(&term->normal);
|
2022-10-09 16:17:22 +02:00
|
|
|
term->normal = *term->interactive_resizing.grid;
|
|
|
|
|
free(term->interactive_resizing.grid);
|
2022-10-05 17:05:44 +02:00
|
|
|
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
term->hide_cursor = term->interactive_resizing.old_hide_cursor;
|
2023-03-27 16:53:41 +02:00
|
|
|
term->selection.coords = term->interactive_resizing.selection_coords;
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
|
2023-06-20 15:59:16 +02:00
|
|
|
old_normal_rows = term->interactive_resizing.old_screen_rows;
|
2022-10-05 17:05:44 +02:00
|
|
|
|
2022-10-09 16:17:22 +02:00
|
|
|
term->interactive_resizing.grid = NULL;
|
|
|
|
|
term->interactive_resizing.old_screen_rows = 0;
|
|
|
|
|
term->interactive_resizing.new_rows = 0;
|
interactive resize: improve user experience
Re-initialize the temporary ‘normal’ grid instance each time we
receive a configure event while doing an interactive resize.
This way, window content will not be "erased" when the window is first
made smaller, then larger again.
And, if the viewport is up in the scrollback history, increasing the
window size will reveal more of the scrollback, instead of just being
black.
The last issue is the cursor; it’s currently not "stuck" where it
should be. Instead, it follows the window around. This is due to two
things:
1) the temporary grid we create is large enough to contain the current
viewport, but not more than that. That means we can’t "scroll up", to
hide the cursor.
2) grid_resize_without_reflow() doesn’t know anything about
"interactive resizing". As such, it will ensure the cursor is bound
to the new grid dimensions.
I don’t yet have a solution for this. This patch implements a
workaround to at least reduce the impact, by simply hiding the cursor
while we’re doing an interactive resize.
2022-10-17 18:49:57 +02:00
|
|
|
term->interactive_resizing.old_hide_cursor = false;
|
2023-03-27 16:53:41 +02:00
|
|
|
term->interactive_resizing.selection_coords = (struct range){{-1, -1}, {-1, -1}};
|
2022-10-06 17:23:56 +02:00
|
|
|
term_ptmx_resume(term);
|
2022-10-05 17:05:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct coord *const tracking_points[] = {
|
|
|
|
|
&term->selection.coords.start,
|
|
|
|
|
&term->selection.coords.end,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
grid_resize_and_reflow(
|
2025-02-10 08:57:51 +01:00
|
|
|
&term->normal, term, new_normal_grid_rows, new_cols, old_normal_rows, new_rows,
|
2022-10-05 17:05:44 +02:00
|
|
|
term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0,
|
|
|
|
|
tracking_points);
|
|
|
|
|
}
|
2020-04-17 22:18:02 +02:00
|
|
|
|
resize: don’t reflow text on alt screen
Alt screen applications normally reflow/readjust themselves on a
window resize.
When we do it too, the result is graphical glitches/flashes since our
re-flowed text is rendered in one frame, and the application re-flowed
text soon thereafter.
We can’t avoid rendering some kind of re-flowed frame, since we don’t
know when, or even if, the application will update itself. To avoid
glitches, we need to render, as closely as possible, what the
application itself will render shortly.
This is actually pretty simple; we just need to copy the visible
content over from the old grid to the new grid. We don’t bother with
text re-flow, but simply truncate long lines.
To simplify things, we simply cancel any active selection (since often
times, it will be corrupted anyway when the application redraws
itself).
Since we’re not reflowing text, there’s no need to translate e.g. the
cursor position - we just keep the current position (but bounded to
the new dimensions).
Fun thing: ‘less’ gets corrupted if we don’t leave the cursor at
the (new) bottom row. To handle this, we check if the cursor (before
resize) is at the bottom row, and if so, we move it to the new bottom
row.
Closes #221
2020-11-24 19:00:57 +01:00
|
|
|
grid_resize_without_reflow(
|
|
|
|
|
&term->alt, new_alt_grid_rows, new_cols, old_rows, new_rows);
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-11-16 10:54:56 +01:00
|
|
|
/* Reset tab stops */
|
|
|
|
|
tll_free(term->tab_stops);
|
|
|
|
|
for (int c = 0; c < new_cols; c += 8)
|
|
|
|
|
tll_push_back(term->tab_stops, c);
|
|
|
|
|
|
2019-07-08 13:57:31 +02:00
|
|
|
term->cols = new_cols;
|
|
|
|
|
term->rows = new_rows;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2020-10-04 13:14:09 +02:00
|
|
|
sixel_reflow(term);
|
|
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
LOG_DBG("resized: grid: cols=%d, rows=%d "
|
2020-02-28 18:51:51 +01:00
|
|
|
"(left-margin=%d, right-margin=%d, top-margin=%d, bottom-margin=%d)",
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
term->cols, term->rows,
|
|
|
|
|
term->margins.left, term->margins.right,
|
|
|
|
|
term->margins.top, term->margins.bottom);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-07-09 09:12:41 +02:00
|
|
|
if (term->scroll_region.start >= term->rows)
|
|
|
|
|
term->scroll_region.start = 0;
|
2023-04-12 18:09:41 +02:00
|
|
|
if (term->scroll_region.end > term->rows ||
|
|
|
|
|
term->scroll_region.end >= old_rows)
|
|
|
|
|
{
|
2019-07-05 10:16:56 +02:00
|
|
|
term->scroll_region.end = term->rows;
|
2023-04-12 18:09:41 +02:00
|
|
|
}
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2020-05-01 11:56:13 +02:00
|
|
|
term->render.last_cursor.row = NULL;
|
2020-02-15 22:19:08 +01:00
|
|
|
|
|
|
|
|
damage_view:
|
2021-01-17 16:12:54 +01:00
|
|
|
/* Signal TIOCSWINSZ */
|
|
|
|
|
send_dimensions_to_client(term);
|
|
|
|
|
|
2024-01-17 15:00:14 -05:00
|
|
|
if (is_floating) {
|
2020-10-20 20:58:03 +02:00
|
|
|
/* Stash current size, to enable us to restore it when we're
|
|
|
|
|
* being un-maximized/fullscreened/tiled */
|
|
|
|
|
term->stashed_width = term->width;
|
|
|
|
|
term->stashed_height = term->height;
|
2020-02-26 20:59:11 +01:00
|
|
|
}
|
|
|
|
|
|
2020-03-03 18:26:15 +01:00
|
|
|
{
|
2022-04-16 11:26:28 +02:00
|
|
|
const bool title_shown = wayl_win_csd_titlebar_visible(term->window);
|
|
|
|
|
const bool border_shown = wayl_win_csd_borders_visible(term->window);
|
2022-04-16 10:47:55 +02:00
|
|
|
|
2023-07-28 15:28:10 +02:00
|
|
|
const int title = title_shown
|
|
|
|
|
? roundf(term->conf->csd.title_height * scale)
|
|
|
|
|
: 0;
|
|
|
|
|
const int border = border_shown
|
|
|
|
|
? roundf(term->conf->csd.border_width_visible * scale)
|
|
|
|
|
: 0;
|
|
|
|
|
|
|
|
|
|
/* Must use surface logical coordinates (same calculations as
|
|
|
|
|
in get_csd_data(), but with different inputs) */
|
|
|
|
|
const int toplevel_min_width = roundf(border / scale) +
|
|
|
|
|
roundf(min_width / scale) +
|
|
|
|
|
roundf(border / scale);
|
|
|
|
|
|
|
|
|
|
const int toplevel_min_height = roundf(border / scale) +
|
|
|
|
|
roundf(title / scale) +
|
|
|
|
|
roundf(min_height / scale) +
|
|
|
|
|
roundf(border / scale);
|
|
|
|
|
|
|
|
|
|
const int toplevel_width = roundf(border / scale) +
|
|
|
|
|
roundf(term->width / scale) +
|
|
|
|
|
roundf(border / scale);
|
|
|
|
|
|
|
|
|
|
const int toplevel_height = roundf(border / scale) +
|
|
|
|
|
roundf(title / scale) +
|
|
|
|
|
roundf(term->height / scale) +
|
|
|
|
|
roundf(border / scale);
|
|
|
|
|
|
|
|
|
|
const int x = roundf(-border / scale);
|
|
|
|
|
const int y = roundf(-title / scale) - roundf(border / scale);
|
2020-03-03 18:26:15 +01:00
|
|
|
|
2023-03-02 17:22:27 +01:00
|
|
|
xdg_toplevel_set_min_size(
|
|
|
|
|
term->window->xdg_toplevel,
|
2023-07-28 15:28:10 +02:00
|
|
|
toplevel_min_width, toplevel_min_height);
|
2023-03-02 17:22:27 +01:00
|
|
|
|
2020-03-03 18:26:15 +01:00
|
|
|
xdg_surface_set_window_geometry(
|
|
|
|
|
term->window->xdg_surface,
|
2023-07-28 15:28:10 +02:00
|
|
|
x, y, toplevel_width, toplevel_height);
|
2020-03-03 18:29:46 +01:00
|
|
|
}
|
2020-02-29 11:42:00 +01:00
|
|
|
|
2020-02-08 18:22:14 +01:00
|
|
|
tll_free(term->normal.scroll_damage);
|
|
|
|
|
tll_free(term->alt.scroll_damage);
|
2020-02-29 11:42:00 +01:00
|
|
|
|
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-05 10:48:36 +02:00
|
|
|
wait_for_preapply_damage(term);
|
2021-07-16 16:47:57 +02:00
|
|
|
shm_unref(term->render.last_buf);
|
2020-02-08 14:08:16 +01:00
|
|
|
term->render.last_buf = NULL;
|
2019-07-26 18:51:47 +02:00
|
|
|
term_damage_view(term);
|
2020-03-06 19:16:54 +01:00
|
|
|
render_refresh_csd(term);
|
|
|
|
|
render_refresh_search(term);
|
2020-01-03 13:56:10 +01:00
|
|
|
render_refresh(term);
|
2020-02-29 11:42:00 +01:00
|
|
|
|
2020-02-25 19:51:03 +01:00
|
|
|
return true;
|
2020-02-08 14:08:16 +01:00
|
|
|
}
|
|
|
|
|
|
2020-01-04 22:01:19 +01:00
|
|
|
static void xcursor_callback(
|
|
|
|
|
void *data, struct wl_callback *wl_callback, uint32_t callback_data);
|
|
|
|
|
static const struct wl_callback_listener xcursor_listener = {
|
|
|
|
|
.done = &xcursor_callback,
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-01 13:56:15 +01:00
|
|
|
bool
|
|
|
|
|
render_xcursor_is_valid(const struct seat *seat, const char *cursor)
|
|
|
|
|
{
|
|
|
|
|
if (cursor == NULL)
|
|
|
|
|
return false;
|
2024-09-21 09:08:40 +02:00
|
|
|
if (seat->pointer.theme == NULL)
|
|
|
|
|
return false;
|
2022-01-01 13:56:15 +01:00
|
|
|
return wl_cursor_theme_get_cursor(seat->pointer.theme, cursor) != NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 22:01:19 +01:00
|
|
|
static void
|
2020-07-09 09:52:11 +02:00
|
|
|
render_xcursor_update(struct seat *seat)
|
2020-01-04 22:01:19 +01:00
|
|
|
{
|
|
|
|
|
/* If called from a frame callback, we may no longer have mouse focus */
|
2020-07-09 09:52:11 +02:00
|
|
|
if (!seat->mouse_focus)
|
2020-01-04 22:01:19 +01:00
|
|
|
return;
|
|
|
|
|
|
2023-06-27 16:57:33 +02:00
|
|
|
xassert(seat->pointer.shape != CURSOR_SHAPE_NONE);
|
2020-07-31 17:09:06 +02:00
|
|
|
|
2023-06-27 16:57:33 +02:00
|
|
|
if (seat->pointer.shape == CURSOR_SHAPE_HIDDEN) {
|
2020-07-31 17:09:06 +02:00
|
|
|
/* Hide cursor */
|
2023-06-27 17:42:47 +02:00
|
|
|
LOG_DBG("hiding cursor using client-side NULL-surface");
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_attach(seat->pointer.surface.surf, NULL, 0, 0);
|
2023-06-27 17:25:57 +02:00
|
|
|
wl_pointer_set_cursor(
|
|
|
|
|
seat->wl_pointer, seat->pointer.serial, seat->pointer.surface.surf,
|
|
|
|
|
0, 0);
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_commit(seat->pointer.surface.surf);
|
2020-07-31 17:09:06 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-28 13:25:08 +02:00
|
|
|
const enum cursor_shape shape = seat->pointer.shape;
|
|
|
|
|
const char *const xcursor = seat->pointer.last_custom_xcursor;
|
|
|
|
|
|
|
|
|
|
if (seat->pointer.shape_device != NULL) {
|
|
|
|
|
xassert(shape != CURSOR_SHAPE_CUSTOM || xcursor != NULL);
|
2020-01-04 22:01:19 +01:00
|
|
|
|
2023-06-28 13:25:08 +02:00
|
|
|
const enum wp_cursor_shape_device_v1_shape custom_shape =
|
|
|
|
|
(shape == CURSOR_SHAPE_CUSTOM && xcursor != NULL
|
2025-05-21 15:25:28 +02:00
|
|
|
? cursor_string_to_server_shape(
|
|
|
|
|
xcursor, seat->wayl->shape_manager_version)
|
|
|
|
|
: 0);
|
2023-06-26 15:51:04 +02:00
|
|
|
|
2023-06-28 13:25:08 +02:00
|
|
|
if (shape != CURSOR_SHAPE_CUSTOM || custom_shape != 0) {
|
|
|
|
|
xassert(custom_shape == 0 || shape == CURSOR_SHAPE_CUSTOM);
|
2020-01-04 22:01:19 +01:00
|
|
|
|
2023-06-28 13:25:08 +02:00
|
|
|
const enum wp_cursor_shape_device_v1_shape wp_shape = custom_shape != 0
|
|
|
|
|
? custom_shape
|
|
|
|
|
: cursor_shape_to_server_shape(shape);
|
2020-01-04 22:01:19 +01:00
|
|
|
|
2023-06-28 13:25:08 +02:00
|
|
|
LOG_DBG("setting %scursor shape using cursor-shape-v1",
|
|
|
|
|
custom_shape != 0 ? "custom " : "");
|
2020-01-04 22:01:19 +01:00
|
|
|
|
2023-06-28 13:25:08 +02:00
|
|
|
wp_cursor_shape_device_v1_set_shape(
|
|
|
|
|
seat->pointer.shape_device,
|
|
|
|
|
seat->pointer.serial,
|
|
|
|
|
wp_shape);
|
2020-01-04 22:01:19 +01:00
|
|
|
|
2023-06-28 13:25:08 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2023-06-27 17:25:57 +02:00
|
|
|
}
|
2023-06-28 13:25:08 +02:00
|
|
|
|
|
|
|
|
LOG_DBG("setting %scursor shape using a client-side cursor surface",
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
seat->pointer.shape == CURSOR_SHAPE_CUSTOM ? "custom " : "");
|
2023-06-28 13:25:08 +02:00
|
|
|
|
2024-02-29 07:49:08 +01:00
|
|
|
if (seat->pointer.cursor == NULL) {
|
|
|
|
|
/*
|
|
|
|
|
* Normally, we never get here with a NULL-cursor, because we
|
|
|
|
|
* only schedule a cursor update when we succeed to load the
|
|
|
|
|
* cursor image.
|
|
|
|
|
*
|
|
|
|
|
* However, it is possible that we did succeed to load an
|
|
|
|
|
* image, and scheduled an update. But, *before* the scheduled
|
|
|
|
|
* update triggers, the user mvoes the pointer, and we try to
|
|
|
|
|
* load a new cursor image. This time failing.
|
|
|
|
|
*
|
|
|
|
|
* In this case, we have a NULL cursor, but the scheduled
|
|
|
|
|
* update is still scheduled.
|
|
|
|
|
*/
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-17 20:08:34 +02:00
|
|
|
const float scale = seat->pointer.scale;
|
2023-06-28 13:25:08 +02:00
|
|
|
struct wl_cursor_image *image = seat->pointer.cursor->images[0];
|
2023-07-17 20:08:34 +02:00
|
|
|
struct wl_buffer *buf = wl_cursor_image_get_buffer(image);
|
2023-06-28 13:25:08 +02:00
|
|
|
|
2023-07-17 20:08:34 +02:00
|
|
|
wayl_surface_scale_explicit_width_height(
|
|
|
|
|
seat->mouse_focus->window,
|
|
|
|
|
&seat->pointer.surface, image->width, image->height, scale);
|
|
|
|
|
|
|
|
|
|
wl_surface_attach(seat->pointer.surface.surf, buf, 0, 0);
|
2023-06-28 13:25:08 +02:00
|
|
|
|
|
|
|
|
wl_pointer_set_cursor(
|
|
|
|
|
seat->wl_pointer, seat->pointer.serial,
|
|
|
|
|
seat->pointer.surface.surf,
|
|
|
|
|
image->hotspot_x / scale, image->hotspot_y / scale);
|
|
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
|
|
|
|
seat->pointer.surface.surf, 0, 0, INT32_MAX, INT32_MAX);
|
|
|
|
|
|
|
|
|
|
xassert(seat->pointer.xcursor_callback == NULL);
|
|
|
|
|
seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface.surf);
|
|
|
|
|
wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat);
|
|
|
|
|
|
|
|
|
|
wl_surface_commit(seat->pointer.surface.surf);
|
2020-01-04 22:01:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xcursor_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
|
|
|
|
|
{
|
2020-07-08 18:08:39 +02:00
|
|
|
struct seat *seat = data;
|
2020-01-04 22:01:19 +01:00
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(seat->pointer.xcursor_callback == wl_callback);
|
2020-01-04 22:01:19 +01:00
|
|
|
wl_callback_destroy(wl_callback);
|
2020-07-08 18:08:39 +02:00
|
|
|
seat->pointer.xcursor_callback = NULL;
|
2020-01-04 22:01:19 +01:00
|
|
|
|
2020-07-09 09:52:11 +02:00
|
|
|
if (seat->pointer.xcursor_pending) {
|
|
|
|
|
render_xcursor_update(seat);
|
|
|
|
|
seat->pointer.xcursor_pending = false;
|
2020-01-05 00:10:44 +01:00
|
|
|
}
|
2020-01-04 22:01:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct renderer *renderer = data;
|
2020-07-08 18:08:39 +02:00
|
|
|
struct wayland *wayl = renderer->wayl;
|
2020-01-05 00:10:44 +01:00
|
|
|
|
2020-01-04 22:01:19 +01:00
|
|
|
tll_foreach(renderer->wayl->terms, it) {
|
|
|
|
|
struct terminal *term = it->item;
|
|
|
|
|
|
2021-07-31 19:08:51 +02:00
|
|
|
if (unlikely(term->shutdown.in_progress || !term->window->is_configured))
|
2020-01-04 22:01:19 +01:00
|
|
|
continue;
|
|
|
|
|
|
2020-03-06 19:16:54 +01:00
|
|
|
bool grid = term->render.refresh.grid;
|
|
|
|
|
bool csd = term->render.refresh.csd;
|
2021-01-31 11:12:07 +01:00
|
|
|
bool search = term->is_searching && term->render.refresh.search;
|
|
|
|
|
bool urls = urls_mode_is_active(term) && term->render.refresh.urls;
|
2020-03-06 19:16:54 +01:00
|
|
|
|
render: use a timer instead of relying on the frame callback for title update throttling
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
2021-06-15 17:27:50 +02:00
|
|
|
if (!(grid | csd | search | urls))
|
2020-12-06 12:18:46 +01:00
|
|
|
continue;
|
|
|
|
|
|
render: use a timer instead of relying on the frame callback for title update throttling
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
2021-06-15 17:27:50 +02:00
|
|
|
if (term->render.app_sync_updates.enabled && !(csd | search | urls))
|
2020-12-06 12:18:46 +01:00
|
|
|
continue;
|
|
|
|
|
|
2020-03-06 19:16:54 +01:00
|
|
|
term->render.refresh.grid = false;
|
|
|
|
|
term->render.refresh.csd = false;
|
|
|
|
|
term->render.refresh.search = false;
|
2021-01-31 11:12:07 +01:00
|
|
|
term->render.refresh.urls = false;
|
2020-03-06 19:16:54 +01:00
|
|
|
|
|
|
|
|
if (term->window->frame_callback == NULL) {
|
url-mode: snapshot screen state when entering URL mode
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.
2021-02-22 10:22:41 +01:00
|
|
|
struct grid *original_grid = term->grid;
|
|
|
|
|
if (urls_mode_is_active(term)) {
|
|
|
|
|
xassert(term->url_grid_snapshot != NULL);
|
|
|
|
|
term->grid = term->url_grid_snapshot;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-22 18:58:38 +02:00
|
|
|
if (csd && term->window->csd_mode == CSD_YES) {
|
2020-03-06 19:16:54 +01:00
|
|
|
quirk_weston_csd_on(term);
|
|
|
|
|
render_csd(term);
|
|
|
|
|
quirk_weston_csd_off(term);
|
|
|
|
|
}
|
|
|
|
|
if (search)
|
|
|
|
|
render_search_box(term);
|
2021-01-31 11:12:07 +01:00
|
|
|
if (urls)
|
|
|
|
|
render_urls(term);
|
|
|
|
|
if (grid | csd | search | urls)
|
|
|
|
|
grid_render(term);
|
|
|
|
|
|
|
|
|
|
tll_foreach(term->wl->seats, it) {
|
2022-06-15 18:41:08 +02:00
|
|
|
if (it->item.ime_focus == term)
|
2021-03-23 13:56:33 +01:00
|
|
|
ime_update_cursor_rect(&it->item);
|
2021-01-31 11:12:07 +01:00
|
|
|
}
|
url-mode: snapshot screen state when entering URL mode
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.
2021-02-22 10:22:41 +01:00
|
|
|
|
|
|
|
|
term->grid = original_grid;
|
2020-03-06 19:16:54 +01:00
|
|
|
} else {
|
2020-01-05 00:10:44 +01:00
|
|
|
/* Tells the frame callback to render again */
|
2020-03-24 17:42:29 +01:00
|
|
|
term->render.pending.grid |= grid;
|
|
|
|
|
term->render.pending.csd |= csd;
|
|
|
|
|
term->render.pending.search |= search;
|
2021-01-31 11:12:07 +01:00
|
|
|
term->render.pending.urls |= urls;
|
2020-01-05 00:10:44 +01:00
|
|
|
}
|
2020-01-04 22:01:19 +01:00
|
|
|
}
|
|
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
tll_foreach(wayl->seats, it) {
|
2020-07-09 09:52:11 +02:00
|
|
|
if (it->item.pointer.xcursor_pending) {
|
2020-07-08 18:08:39 +02:00
|
|
|
if (it->item.pointer.xcursor_callback == NULL) {
|
2020-07-09 09:52:11 +02:00
|
|
|
render_xcursor_update(&it->item);
|
|
|
|
|
it->item.pointer.xcursor_pending = false;
|
2020-07-08 18:08:39 +02:00
|
|
|
} else {
|
|
|
|
|
/* Frame callback will call render_xcursor_update() */
|
|
|
|
|
}
|
2020-01-05 00:10:44 +01:00
|
|
|
}
|
|
|
|
|
}
|
2020-01-04 22:01:19 +01:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
void
|
2020-03-25 18:23:55 +01:00
|
|
|
render_refresh_title(struct terminal *term)
|
2019-07-05 10:16:56 +02:00
|
|
|
{
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec now;
|
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &now) < 0)
|
render: use a timer instead of relying on the frame callback for title update throttling
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
2021-06-15 17:27:50 +02:00
|
|
|
return;
|
|
|
|
|
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec diff;
|
|
|
|
|
timespec_sub(&now, &term->render.title.last_update, &diff);
|
render: use a timer instead of relying on the frame callback for title update throttling
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
2021-06-15 17:27:50 +02:00
|
|
|
|
2022-01-15 14:56:13 +05:30
|
|
|
if (diff.tv_sec == 0 && diff.tv_nsec < 8333 * 1000) {
|
render: use a timer instead of relying on the frame callback for title update throttling
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
2021-06-15 17:27:50 +02:00
|
|
|
const struct itimerspec timeout = {
|
2022-01-15 14:56:13 +05:30
|
|
|
.it_value = {.tv_nsec = 8333 * 1000 - diff.tv_nsec},
|
render: use a timer instead of relying on the frame callback for title update throttling
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
2021-06-15 17:27:50 +02:00
|
|
|
};
|
|
|
|
|
|
2021-06-18 15:53:47 +02:00
|
|
|
timerfd_settime(term->render.title.timer_fd, 0, &timeout, NULL);
|
render: use a timer instead of relying on the frame callback for title update throttling
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
2021-06-15 17:27:50 +02:00
|
|
|
} else {
|
|
|
|
|
term->render.title.last_update = now;
|
|
|
|
|
render_update_title(term);
|
|
|
|
|
}
|
2021-07-22 19:34:19 +02:00
|
|
|
|
|
|
|
|
render_refresh_csd(term);
|
2019-07-05 10:44:09 +02:00
|
|
|
}
|
2019-07-24 20:09:49 +02:00
|
|
|
|
2023-09-04 14:02:05 +02:00
|
|
|
void
|
|
|
|
|
render_refresh_app_id(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
struct timespec now;
|
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &now) < 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
struct timespec diff;
|
|
|
|
|
timespec_sub(&now, &term->render.app_id.last_update, &diff);
|
|
|
|
|
|
|
|
|
|
if (diff.tv_sec == 0 && diff.tv_nsec < 8333 * 1000) {
|
|
|
|
|
const struct itimerspec timeout = {
|
|
|
|
|
.it_value = {.tv_nsec = 8333 * 1000 - diff.tv_nsec},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
timerfd_settime(term->render.app_id.timer_fd, 0, &timeout, NULL);
|
2024-09-08 18:25:07 +02:00
|
|
|
return;
|
2023-09-04 14:02:05 +02:00
|
|
|
}
|
2024-09-08 18:25:07 +02:00
|
|
|
|
|
|
|
|
const char *app_id =
|
|
|
|
|
term->app_id != NULL ? term->app_id : term->conf->app_id;
|
|
|
|
|
|
|
|
|
|
xdg_toplevel_set_app_id(term->window->xdg_toplevel, app_id);
|
2024-09-10 18:53:38 +02:00
|
|
|
term->render.app_id.last_update = now;
|
|
|
|
|
}
|
2024-09-08 18:25:07 +02:00
|
|
|
|
2024-09-10 18:53:38 +02:00
|
|
|
void
|
|
|
|
|
render_refresh_icon(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
if (term->wl->toplevel_icon_manager == NULL) {
|
|
|
|
|
LOG_DBG("compositor does not implement xdg-toplevel-icon: "
|
|
|
|
|
"ignoring request to refresh window icon");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct timespec now;
|
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &now) < 0)
|
|
|
|
|
return;
|
2024-09-08 18:25:07 +02:00
|
|
|
|
2024-09-10 18:53:38 +02:00
|
|
|
struct timespec diff;
|
|
|
|
|
timespec_sub(&now, &term->render.icon.last_update, &diff);
|
2024-09-08 18:25:07 +02:00
|
|
|
|
2024-09-10 18:53:38 +02:00
|
|
|
if (diff.tv_sec == 0 && diff.tv_nsec < 8333 * 1000) {
|
|
|
|
|
const struct itimerspec timeout = {
|
|
|
|
|
.it_value = {.tv_nsec = 8333 * 1000 - diff.tv_nsec},
|
|
|
|
|
};
|
2024-09-08 18:25:07 +02:00
|
|
|
|
2024-09-10 18:53:38 +02:00
|
|
|
timerfd_settime(term->render.icon.timer_fd, 0, &timeout, NULL);
|
|
|
|
|
return;
|
2024-09-08 18:25:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-10 18:53:38 +02:00
|
|
|
const char *icon_name = term_icon(term);
|
|
|
|
|
LOG_DBG("setting toplevel icon: %s", icon_name);
|
|
|
|
|
|
|
|
|
|
struct xdg_toplevel_icon_v1 *icon =
|
|
|
|
|
xdg_toplevel_icon_manager_v1_create_icon(term->wl->toplevel_icon_manager);
|
|
|
|
|
xdg_toplevel_icon_v1_set_name(icon, icon_name);
|
|
|
|
|
xdg_toplevel_icon_manager_v1_set_icon(
|
|
|
|
|
term->wl->toplevel_icon_manager, term->window->xdg_toplevel, icon);
|
|
|
|
|
xdg_toplevel_icon_v1_destroy(icon);
|
|
|
|
|
|
|
|
|
|
term->render.icon.last_update = now;
|
2023-09-04 14:02:05 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-24 20:09:49 +02:00
|
|
|
void
|
|
|
|
|
render_refresh(struct terminal *term)
|
|
|
|
|
{
|
2020-03-06 19:16:54 +01:00
|
|
|
term->render.refresh.grid = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
render_refresh_csd(struct terminal *term)
|
|
|
|
|
{
|
2021-06-22 18:58:38 +02:00
|
|
|
if (term->window->csd_mode == CSD_YES)
|
2020-03-06 19:16:54 +01:00
|
|
|
term->render.refresh.csd = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
render_refresh_search(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
if (term->is_searching)
|
|
|
|
|
term->render.refresh.search = true;
|
2019-07-24 20:09:49 +02:00
|
|
|
}
|
2020-01-04 22:01:19 +01:00
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
void
|
|
|
|
|
render_refresh_urls(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
if (urls_mode_is_active(term))
|
|
|
|
|
term->render.refresh.urls = true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 22:01:19 +01:00
|
|
|
bool
|
2023-06-27 18:40:44 +02:00
|
|
|
render_xcursor_set(struct seat *seat, struct terminal *term,
|
|
|
|
|
enum cursor_shape shape)
|
2020-01-04 22:01:19 +01:00
|
|
|
{
|
2024-03-16 15:30:29 +01:00
|
|
|
if (seat->pointer.theme == NULL && seat->pointer.shape_device == NULL)
|
2020-01-04 22:01:19 +01:00
|
|
|
return false;
|
|
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
if (seat->mouse_focus == NULL) {
|
2023-06-27 16:57:33 +02:00
|
|
|
seat->pointer.shape = CURSOR_SHAPE_NONE;
|
2020-01-04 22:01:19 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
if (seat->mouse_focus != term) {
|
2020-01-04 22:01:19 +01:00
|
|
|
/* This terminal doesn't have mouse focus */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-28 13:25:08 +02:00
|
|
|
if (seat->pointer.shape == shape &&
|
|
|
|
|
!(shape == CURSOR_SHAPE_CUSTOM &&
|
2024-01-24 23:17:28 +00:00
|
|
|
!streq(seat->pointer.last_custom_xcursor,
|
|
|
|
|
term->mouse_user_cursor)))
|
2023-06-28 13:25:08 +02:00
|
|
|
{
|
2020-01-04 22:01:19 +01:00
|
|
|
return true;
|
2023-06-28 13:25:08 +02:00
|
|
|
}
|
2020-01-04 22:01:19 +01:00
|
|
|
|
2024-02-29 07:54:27 +01:00
|
|
|
if (shape == CURSOR_SHAPE_HIDDEN) {
|
|
|
|
|
seat->pointer.cursor = NULL;
|
|
|
|
|
free(seat->pointer.last_custom_xcursor);
|
|
|
|
|
seat->pointer.last_custom_xcursor = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (seat->pointer.shape_device == NULL) {
|
|
|
|
|
const char *const custom_xcursors[] = {term->mouse_user_cursor, NULL};
|
|
|
|
|
const char *const *xcursors = shape == CURSOR_SHAPE_CUSTOM
|
|
|
|
|
? custom_xcursors
|
2023-06-27 18:40:44 +02:00
|
|
|
: cursor_shape_to_string(shape);
|
2023-06-27 16:57:33 +02:00
|
|
|
|
2024-02-29 07:54:27 +01:00
|
|
|
xassert(xcursors[0] != NULL);
|
2022-01-01 13:51:18 +01:00
|
|
|
|
2024-02-29 07:54:27 +01:00
|
|
|
seat->pointer.cursor = NULL;
|
2023-06-27 16:57:33 +02:00
|
|
|
|
2024-02-29 07:54:27 +01:00
|
|
|
for (size_t i = 0; xcursors[i] != NULL; i++) {
|
|
|
|
|
seat->pointer.cursor =
|
|
|
|
|
wl_cursor_theme_get_cursor(seat->pointer.theme, xcursors[i]);
|
|
|
|
|
|
|
|
|
|
if (seat->pointer.cursor != NULL) {
|
|
|
|
|
LOG_DBG("loaded xcursor %s", xcursors[i]);
|
|
|
|
|
break;
|
2022-02-07 20:32:28 +05:30
|
|
|
}
|
2022-01-01 13:51:18 +01:00
|
|
|
}
|
2023-06-28 13:25:08 +02:00
|
|
|
|
2024-02-29 07:54:27 +01:00
|
|
|
if (seat->pointer.cursor == NULL) {
|
|
|
|
|
LOG_ERR(
|
|
|
|
|
"failed to load xcursor pointer '%s', and all of its fallbacks",
|
|
|
|
|
xcursors[0]);
|
|
|
|
|
return false;
|
2023-06-28 13:25:08 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
2024-02-29 07:54:27 +01:00
|
|
|
/* Server-side cursors - no need to load anything */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shape == CURSOR_SHAPE_CUSTOM) {
|
2023-06-28 13:25:08 +02:00
|
|
|
free(seat->pointer.last_custom_xcursor);
|
2024-02-29 07:54:27 +01:00
|
|
|
seat->pointer.last_custom_xcursor =
|
|
|
|
|
xstrdup(term->mouse_user_cursor);
|
2023-06-28 13:25:08 +02:00
|
|
|
}
|
2022-01-01 13:51:18 +01:00
|
|
|
|
2020-01-04 22:01:19 +01:00
|
|
|
/* FDM hook takes care of actual rendering */
|
2023-06-27 16:57:33 +02:00
|
|
|
seat->pointer.shape = shape;
|
2022-01-01 13:51:18 +01:00
|
|
|
seat->pointer.xcursor_pending = true;
|
2020-07-08 16:45:26 +02:00
|
|
|
return true;
|
2020-01-04 22:01:19 +01:00
|
|
|
}
|
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-05 10:48:36 +02:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|