Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package foot for openSUSE:Factory checked in at 2025-10-17 17:25:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/foot (Old) and /work/SRC/openSUSE:Factory/.foot.new.18484 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "foot" Fri Oct 17 17:25:37 2025 rev:48 rq:1311742 version:1.25.0 Changes: -------- --- /work/SRC/openSUSE:Factory/foot/foot.changes 2025-09-12 21:11:57.865386106 +0200 +++ /work/SRC/openSUSE:Factory/.foot.new.18484/foot.changes 2025-10-17 17:26:01.018585148 +0200 @@ -1,0 +2,16 @@ +Thu Oct 16 14:35:32 UTC 2025 - Arnav Singh <[email protected]> + +- Update to v1.25.0: + * foot.ini options: + * Added new colors{,2}.dim-blend-towards options to select which color to + blend towards when dimming text. Defaults to black for colors and + white for colors2. + * Added new tweak.min-stride-alignment option to control the stride of + SHM buffers. This controls whether the compositor can directly import + the buffers into the GPU without copying. + * Fixed URL labels being misplaced when the URL contains + double-width characters. + * Fixed spaces being missing when copying or piping contents with tabs. + * See https://codeberg.org/dnkl/foot/releases/tag/1.25.0 for more details. + +------------------------------------------------------------------- Old: ---- foot-1.24.0.tar.gz foot-1.24.0.tar.gz.sig New: ---- foot-1.25.0.tar.gz foot-1.25.0.tar.gz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ foot.spec ++++++ --- /var/tmp/diff_new_pack.0iLlUp/_old 2025-10-17 17:26:01.774616993 +0200 +++ /var/tmp/diff_new_pack.0iLlUp/_new 2025-10-17 17:26:01.778617161 +0200 @@ -20,7 +20,7 @@ %define _distconfdir %{_sysconfdir} %endif Name: foot -Version: 1.24.0 +Version: 1.25.0 Release: 0 Summary: A Wayland terminal emulator License: MIT ++++++ foot-1.24.0.tar.gz -> foot-1.25.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/CHANGELOG.md new/foot-1.25.0/CHANGELOG.md --- old/foot-1.24.0/CHANGELOG.md 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/CHANGELOG.md 2025-10-16 08:46:58.000000000 +0200 @@ -1,5 +1,6 @@ # Changelog +* [1.25.0](#1-25-0) * [1.24.0](#1-24-0) * [1.23.1](#1-23-1) * [1.23.0](#1-23-0) @@ -65,6 +66,54 @@ * [1.2.0](#1-2-0) +## 1.25.0 + +### Added + +* Performance increased and input latency decreased on compositors + that do not release SHM buffers immediately ([#2188][2188]). +* `colors{,2}.dim-blend-towards=black|white` option, allowing you to + select towards which color to blend when dimming text. Defaults to + `black` in `[colors]`, and `white` in `[colors2]` ([#2187][2187]). + +[2188]: https://codeberg.org/dnkl/foot/issues/2188 +[2187]: https://codeberg.org/dnkl/foot/issues/2187 + + +### Changed + +* SHM buffer sizes are now rounded up to nearest page size, and their + stride is always an even multiple of 256 bytes (by default, + configurable by setting `tweak.min-stride-alignment`). This allows + compositor to directly import foot's SHM buffers to the GPU, with + e.g. integrated graphics ([#2182][2182]). +* Jump label colors in the modus-operandi theme, for improved + readability. + +[2182]: https://codeberg.org/dnkl/foot/issues/2182 + + +### Fixed + +* URL labels misplaces when URL contains double-width characters + ([#2179][2179]). +* One space too much consumed when copying (or pipe:ing) contents with + tabs ([#2194][2194]) +* Ensure we render a new frame when changing fullscreen state. Before, + this was automatically done if the window was also resized. But, it + is possible for a compositor to change an application's fullscreen + state without resizing the window. + +[2179]: https://codeberg.org/dnkl/foot/issues/2179 +[2194]: https://codeberg.org/dnkl/foot/issues/2194 + + +### Contributors + +* Charalampos Mitrodimas +* Matthias Heyman + + ## 1.24.0 ### Added diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/config.c new/foot-1.25.0/config.c --- old/foot-1.24.0/config.c 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/config.c 2025-10-16 08:46:58.000000000 +0200 @@ -1519,7 +1519,7 @@ return true; } - else if (strcmp(key, "alpha-mode") == 0) { + else if (streq(key, "alpha-mode")) { _Static_assert(sizeof(theme->alpha_mode) == sizeof(int), "enum is not 32-bit"); @@ -1529,6 +1529,16 @@ (int *)&theme->alpha_mode); } + else if (streq(key, "dim-blend-towards")) { + _Static_assert(sizeof(theme->dim_blend_towards) == sizeof(int), + "enum is not 32-bit"); + + return value_to_enum( + ctx, + (const char *[]){"black", "white", NULL}, + (int *)&theme->dim_blend_towards); + } + else { LOG_CONTEXTUAL_ERR("not valid option"); return false; @@ -2848,6 +2858,12 @@ #endif } + else if (streq(key, "min-stride-alignment")) + return value_to_uint32(ctx, 10, &conf->tweak.min_stride_alignment); + + else if (streq(key, "pre-apply-damage")) + return value_to_bool(ctx, &conf->tweak.preapply_damage); + else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -3422,6 +3438,7 @@ .flash_alpha = 0x7fff, .alpha = 0xffff, .alpha_mode = ALPHA_MODE_DEFAULT, + .dim_blend_towards = DIM_BLEND_TOWARDS_BLACK, .selection_fg = 0x80000000, /* Use default bg */ .selection_bg = 0x80000000, /* Use default fg */ .cursor = { @@ -3496,6 +3513,8 @@ .font_monospace_warn = true, .sixel = true, .surface_bit_depth = SHM_BITS_AUTO, + .min_stride_alignment = 256, + .preapply_damage = true, }, .touch = { @@ -3515,6 +3534,8 @@ memcpy(conf->colors.table, default_color_table, sizeof(default_color_table)); memcpy(conf->colors.sixel, default_sixel_colors, sizeof(default_sixel_colors)); memcpy(&conf->colors2, &conf->colors, sizeof(conf->colors)); + conf->colors2.dim_blend_towards = DIM_BLEND_TOWARDS_WHITE; + parse_modifiers(XKB_MOD_NAME_SHIFT, 5, &conf->mouse.selection_override_modifiers); tokenize_cmdline( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/config.h new/foot-1.25.0/config.h --- old/foot-1.24.0/config.h 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/config.h 2025-10-16 08:46:58.000000000 +0200 @@ -146,6 +146,11 @@ uint32_t sixel[16]; enum { + DIM_BLEND_TOWARDS_BLACK, + DIM_BLEND_TOWARDS_WHITE, + } dim_blend_towards; + + enum { ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL @@ -435,6 +440,8 @@ bool font_monospace_warn; bool sixel; enum shm_bit_depth surface_bit_depth; + uint32_t min_stride_alignment; + bool preapply_damage; } tweak; struct { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/doc/foot.ini.5.scd new/foot-1.25.0/doc/foot.ini.5.scd --- old/foot-1.24.0/doc/foot.ini.5.scd 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/doc/foot.ini.5.scd 2025-10-16 08:46:58.000000000 +0200 @@ -1031,7 +1031,8 @@ a color value, and a "dim" attribute. By default, foot implements this by blending the current color - with black. This is a generic approach that applies to both + with black or white, depending on what the *dim-blend-towards* + option is set to . This is a generic approach that applies to both colors from the 256-color palette, as well as 24-bit RGB colors. You can change this behavior by setting the *dimN* options. When @@ -1086,6 +1087,14 @@ Default: _default_ +*dim-blend-towards* + Which color to blend towards when "auto" dimming a color (see + *dim0*..*dim7* above). One of *black* or *white*. Blending towards + black makes the text darker, while blending towards white makes it + whiter (but still dimmer than normal text). + + Default: _black_ (*colors*), _white_ (*colors2*) + *selection-foreground*, *selection-background* Foreground (text) and background color to use in selected text. Default: _inverse foreground/background_. @@ -1124,7 +1133,8 @@ # SECTION: colors2 This section defines an alternative color theme. It has the exact same -keys as the *colors* section. The default values are the same. +keys as the *colors* section. The default values are the same, except +for *dim-blend-towards*, which defaults to *white* instead. Note that values are not inherited. That is, if you set a value in *colors*, but not in *colors2*, the value from *colors* is not @@ -2031,6 +2041,32 @@ Default: _512_. Maximum allowed: _2048_ (2GB). +*min-stride-alignment* + This option controls the minimum stride alignment, in bytes, when + allocating SHM buffers. + + In some circumstances, a compositor can import foot's SHM buffers + directly to the GPU, without copying the buffer to GPU memory + (typically on integrated graphics). Different drivers have + different requirements for this, and one of those requirements is + typically the stride alignment. At the time of writing, AMD GPUs + require 256-byte alignment. + + Note that doing a direct import typically disables immediate + buffer release (if the compositor supports that), which means foot + has to double buffer. This adds a performance penalty in foot, but + the overall system performance should still be better. + + If you are not using integrated graphics, or if the compositor + does not support GPU direct imports, this option has close to zero + impact. You can save a small amount of memory by setting this to + 0. + + Ultimately, it is up to the compositor to decide whether to do + immediate buffer releases, or try to optimize GPU imports. + + Default: _256_ + *sixel* Boolean. When enabled, foot will process sixel images. Default: _yes_ @@ -2067,6 +2103,41 @@ Default: _auto_ +*pre-apply-damage* + Boolean. When enabled, foot will attempt to "pre-apply" the damage + from the last frame when foot is forced to double-buffer + (i.e. when the compositor does not release SHM buffers + immediately). All text after this assumes the compositor is not + releasing buffers immediately. + + When this option is disabled, each time foot needs to render a + frame, it has to first copy over areas that changed in the last + frame (i.e. all changes between the last two frames). This is + basically a *memcpy*(3), which can be slow if the changed area is + large. It is also done on the main thread, which means foot cannot + do anything else at the same time; no other rendering, no VT + parsing. After the changes have been brought over to the new + frame, foot proceeds with rendering the cells that has changed + between the last frame and the new frame. + + When this open is enabled, the changes between the last two frames + are brought over to what will become the next frame before foot + starts rendering the next frame. As soon as the compositor + releases the previous buffer (typically right after foot has + pushed a new frame), foot kicks off a thread that copies over the + changes to the newly released buffer. Since this is done in a + thread, foot can continue processing input at the same + time. Later, when it is time to render a new frame, the changes + have already been transferred, and foot can immediately start with + the actual rendering. + + Thus, having this option enabled improves both performance + (copying the last two frames' changes is threaded), and improves + input latency (rending the next frame no longer has to first bring + over the changes between the last two frames). + + Default: _yes_ + # SEE ALSO *foot*(1), *footclient*(1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/extract.c new/foot-1.25.0/extract.c --- old/foot-1.24.0/extract.c 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/extract.c 2025-10-16 08:46:58.000000000 +0200 @@ -256,8 +256,8 @@ } } - xassert(next_tab_stop >= col); - ctx->tab_spaces_left = next_tab_stop - col; + if (next_tab_stop > col) + ctx->tab_spaces_left = next_tab_stop - col - 1; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/foot.ini new/foot-1.25.0/foot.ini --- old/foot-1.24.0/foot.ini 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/foot.ini 2025-10-16 08:46:58.000000000 +0200 @@ -132,6 +132,7 @@ # bright7=ffffff # bright white ## dimmed colors (see foot.ini(5) man page) +# dim-blend-towards=black # dim0=<not set> # ... # dim7=<not-set> @@ -170,6 +171,8 @@ [colors2] # Alternative color theme, see man page foot.ini(5) +# Same builtin defaults as [color], except for: +# dim-blend-towards=white [csd] # preferred=server diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/main.c new/foot-1.25.0/main.c --- old/foot-1.24.0/main.c 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/main.c 2025-10-16 08:46:58.000000000 +0200 @@ -597,6 +597,7 @@ } shm_set_max_pool_size(conf.tweak.max_shm_pool_size); + shm_set_min_stride_alignment(conf.tweak.min_stride_alignment); if ((fdm = fdm_init()) == NULL) goto out; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/meson.build new/foot-1.25.0/meson.build --- old/foot-1.24.0/meson.build 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/meson.build 2025-10-16 08:46:58.000000000 +0200 @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.24.0', + version: '1.25.0', license: 'MIT', meson_version: '>=0.59.0', default_options: [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/pgo/pgo.c new/foot-1.25.0/pgo/pgo.c --- old/foot-1.24.0/pgo/pgo.c 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/pgo/pgo.c 2025-10-16 08:46:58.000000000 +0200 @@ -74,6 +74,8 @@ void render_overlay(struct terminal *term) {} +void render_buffer_release_callback(struct buffer *buf, void *data) {} + bool render_xcursor_is_valid(const struct seat *seat, const char *cursor) { @@ -206,7 +208,8 @@ struct buffer_chain * shm_chain_new( struct wayland *wayl, bool scrollable, size_t pix_instances, - enum shm_bit_depth desired_bit_depth) + enum shm_bit_depth desired_bit_depth, + void (*release_cb)(struct buffer *buf, void *data), void *cb_data) { return NULL; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/render.c new/foot-1.25.0/render.c --- old/foot-1.24.0/render.c 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/render.c 2025-10-16 08:46:58.000000000 +0200 @@ -312,7 +312,14 @@ } } - return color_blend_towards(color, 0x00000000, conf->dim.amount); + const struct color_theme *theme = term->colors.active_theme == COLOR_THEME1 + ? &conf->colors + : &conf->colors2; + + return color_blend_towards( + color, + theme->dim_blend_towards == DIM_BLEND_TOWARDS_BLACK ? 0x00000000 : 0x00ffffff, + conf->dim.amount); } static inline uint32_t @@ -2224,6 +2231,56 @@ case -2: return 0; + + case -3: { + if (term->conf->tweak.render_timer != RENDER_TIMER_NONE) + clock_gettime(CLOCK_MONOTONIC, &term->render.workers.preapplied_damage.start); + + mtx_lock(&term->render.workers.preapplied_damage.lock); + buf = term->render.workers.preapplied_damage.buf; + xassert(buf != NULL); + + if (likely(term->render.last_buf != NULL)) { + mtx_unlock(&term->render.workers.preapplied_damage.lock); + + pixman_region32_t dmg; + pixman_region32_init(&dmg); + + if (buf->age == 0) + ; /* No need to do anything */ + else if (buf->age == 1) + pixman_region32_copy(&dmg, + &term->render.last_buf->dirty[0]); + else + pixman_region32_init_rect(&dmg, 0, 0, buf->width, + buf->height); + + pixman_image_set_clip_region32(buf->pix[my_id], &dmg); + pixman_image_composite32(PIXMAN_OP_SRC, + term->render.last_buf->pix[my_id], + NULL, buf->pix[my_id], 0, 0, 0, 0, 0, + 0, buf->width, buf->height); + + pixman_region32_fini(&dmg); + + buf->age = 0; + shm_unref(term->render.last_buf); + shm_addref(buf); + term->render.last_buf = buf; + + mtx_lock(&term->render.workers.preapplied_damage.lock); + } + + term->render.workers.preapplied_damage.buf = NULL; + cnd_signal(&term->render.workers.preapplied_damage.cond); + mtx_unlock(&term->render.workers.preapplied_damage.lock); + + if (term->conf->tweak.render_timer != RENDER_TIMER_NONE) + clock_gettime(CLOCK_MONOTONIC, &term->render.workers.preapplied_damage.stop); + + frame_done = true; + break; + } } } }; @@ -2231,6 +2288,22 @@ return -1; } +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); +} + struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx) { @@ -3113,14 +3186,6 @@ static void reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old) { - static int counter = 0; - static bool have_warned = false; - if (!have_warned && ++counter > 5) { - LOG_WARN("compositor is not releasing buffers immediately; " - "expect lower rendering performance"); - have_warned = true; - } - if (new->age > 1) { memcpy(new->data, old->data, new->height * new->stride); return; @@ -3251,7 +3316,18 @@ if (term->shutdown.in_progress) return; - struct timespec start_time, start_double_buffering = {0}, stop_double_buffering = {0}; + struct timespec start_time; + struct timespec start_wait_preapply = {0}, stop_wait_preapply = {0}; + struct timespec start_double_buffering = {0}, stop_double_buffering = {0}; + + /* Might be a thread doing pre-applied damage */ + if (unlikely(term->render.preapply_last_frame_damage && + term->render.workers.preapplied_damage.buf != NULL)) + { + clock_gettime(CLOCK_MONOTONIC, &start_wait_preapply); + wait_for_preapply_damage(term); + clock_gettime(CLOCK_MONOTONIC, &stop_wait_preapply); + } if (term->conf->tweak.render_timer != RENDER_TIMER_NONE) clock_gettime(CLOCK_MONOTONIC, &start_time); @@ -3269,6 +3345,8 @@ dirty_old_cursor(term); dirty_cursor(term); + LOG_DBG("buffer age: %u (%p)", buf->age, (void *)buf); + if (term->render.last_buf == NULL || term->render.last_buf->width != buf->width || term->render.last_buf->height != buf->height || @@ -3285,9 +3363,27 @@ xassert(term->render.last_buf->width == buf->width); xassert(term->render.last_buf->height == buf->height); + if (++term->render.frames_since_last_immediate_release > 10) { + static bool have_warned = false; + + if (!term->render.preapply_last_frame_damage && + term->conf->tweak.preapply_damage && + term->render.workers.count > 0) + { + LOG_INFO("enabling pre-applied frame damage"); + term->render.preapply_last_frame_damage = true; + } else if (!have_warned && !term->render.preapply_last_frame_damage) { + LOG_WARN("compositor is not releasing buffers immediately; " + "expect lower rendering performance"); + have_warned = true; + } + } + clock_gettime(CLOCK_MONOTONIC, &start_double_buffering); reapply_old_damage(term, buf, term->render.last_buf); clock_gettime(CLOCK_MONOTONIC, &stop_double_buffering); + } else if (!term->render.preapply_last_frame_damage) { + term->render.frames_since_last_immediate_release = 0; } if (term->render.last_buf != NULL) { @@ -3515,27 +3611,40 @@ struct timespec end_time; clock_gettime(CLOCK_MONOTONIC, &end_time); + struct timespec wait_time; + timespec_sub(&stop_wait_preapply, &start_wait_preapply, &wait_time); + struct timespec render_time; timespec_sub(&end_time, &start_time, &render_time); struct timespec double_buffering_time; timespec_sub(&stop_double_buffering, &start_double_buffering, &double_buffering_time); + struct timespec preapply_damage; + timespec_sub(&term->render.workers.preapplied_damage.stop, + &term->render.workers.preapplied_damage.start, + &preapply_damage); + struct timespec total_render_time; timespec_add(&render_time, &double_buffering_time, &total_render_time); + timespec_add(&wait_time, &total_render_time, &total_render_time); switch (term->conf->tweak.render_timer) { case RENDER_TIMER_LOG: case RENDER_TIMER_BOTH: LOG_INFO( "frame rendered in %lds %9ldns " - "(%lds %9ldns rendering, %lds %9ldns double buffering)", + "(%lds %9ldns wait, %lds %9ldns rendering, %lds %9ldns double buffering) not included: %lds %ldns pre-apply damage", (long)total_render_time.tv_sec, total_render_time.tv_nsec, + (long)wait_time.tv_sec, + wait_time.tv_nsec, (long)render_time.tv_sec, render_time.tv_nsec, (long)double_buffering_time.tv_sec, - double_buffering_time.tv_nsec); + double_buffering_time.tv_nsec, + (long)preapply_damage.tv_sec, + preapply_damage.tv_nsec); break; case RENDER_TIMER_OSD: @@ -4295,6 +4404,7 @@ term->interactive_resizing.old_hide_cursor = false; /* Invalidate render pointers */ + wait_for_preapply_damage(term); shm_unref(term->render.last_buf); term->render.last_buf = NULL; term->render.last_cursor.row = NULL; @@ -4869,6 +4979,7 @@ tll_free(term->normal.scroll_damage); tll_free(term->alt.scroll_damage); + wait_for_preapply_damage(term); shm_unref(term->render.last_buf); term->render.last_buf = NULL; term_damage_view(term); @@ -5267,3 +5378,77 @@ seat->pointer.xcursor_pending = true; return true; } + +void +render_buffer_release_callback(struct buffer *buf, void *data) +{ + /* + * Called from shm.c when a buffer is released + * + * We use it to pre-apply last-frame's damage to it, when we're + * forced to double buffer (compositor doesn't release buffers + * immediately). + * + * The timeline is thus: + * 1. We render and push a new frame + * 2. Some (hopefully short) time after that, the compositor releases the previous buffer + * 3. We're called, and kick off the thread that copies the changes from (1) to the just freed buffer + * 4. Time passes.... + * 5. The compositor calls our frame callback, signalling to us that it's time to start rendering the next frame + * 6. Hopefully, our thread is already done with copying the changes, otherwise we stall, waiting for it + * 7. We render the frame as if the compositor does immediate releases. + * + * What's the gain? Reduced latency, by applying the previous + * frame's damage as soon as possible, we shorten the time it + * takes to render the frame after the frame callback. + * + * This means the compositor can, in theory, push the frame + * callback closer to the vblank deadline, and thus reduce input + * latency. Not all compositors (most, in fact?) don't adapt like + * this, unfortunately. But some allows the user to manually + * configure the deadline. + */ + struct terminal *term = data; + + if (likely(buf->age != 1)) + return; + + if (likely(!term->render.preapply_last_frame_damage)) + return; + + if (term->render.last_buf == NULL) + return; + + if (term->render.last_buf->age != 0) + return; + + if (buf->width != term->render.last_buf->width) + return; + + if (buf->height != term->render.last_buf->height) + return; + + xassert(term->render.workers.count > 0); + xassert(term->render.last_buf != NULL); + + xassert(term->render.last_buf->age == 0); + xassert(term->render.last_buf != buf); + + mtx_lock(&term->render.workers.preapplied_damage.lock); + if (term->render.workers.preapplied_damage.buf != NULL) { + mtx_unlock(&term->render.workers.preapplied_damage.lock); + return; + } + + xassert(term->render.workers.preapplied_damage.buf == NULL); + term->render.workers.preapplied_damage.buf = buf; + term->render.workers.preapplied_damage.start = (struct timespec){0}; + term->render.workers.preapplied_damage.stop = (struct timespec){0}; + mtx_unlock(&term->render.workers.preapplied_damage.lock); + + mtx_lock(&term->render.workers.lock); + sem_post(&term->render.workers.start); + xassert(tll_length(term->render.workers.queue) == 0); + tll_push_back(term->render.workers.queue, -3); + mtx_unlock(&term->render.workers.lock); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/render.h new/foot-1.25.0/render.h --- old/foot-1.24.0/render.h 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/render.h 2025-10-16 08:46:58.000000000 +0200 @@ -47,3 +47,5 @@ }; struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx); + +void render_buffer_release_callback(struct buffer *buf, void *data); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/shm.c new/foot-1.25.0/shm.c --- old/foot-1.24.0/shm.c 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/shm.c 2025-10-16 08:46:58.000000000 +0200 @@ -13,7 +13,6 @@ #include <pixman.h> -#include <fcft/stride.h> #include <tllist.h> #define LOG_MODULE "shm" @@ -21,6 +20,7 @@ #include "log.h" #include "debug.h" #include "macros.h" +#include "stride.h" #include "xmalloc.h" #if !defined(MAP_UNINITIALIZED) @@ -61,6 +61,8 @@ static bool can_punch_hole = false; static bool can_punch_hole_initialized = false; +static size_t min_stride_alignment = 0; + struct buffer_pool { int fd; /* memfd */ struct wl_shm_pool *wl_pool; @@ -85,6 +87,9 @@ bool with_alpha; bool scrollable; + + void (*release_cb)(struct buffer *buf, void *data); + void *cb_data; }; struct buffer_chain { @@ -98,6 +103,9 @@ pixman_format_code_t pixman_fmt_with_alpha; enum wl_shm_format shm_format_with_alpha; + + void (*release_cb)(struct buffer *buf, void *data); + void *cb_data; }; static tll(struct buffer_private *) deferred; @@ -113,6 +121,12 @@ max_pool_size = _max_pool_size; } +void +shm_set_min_stride_alignment(size_t _min_stride_alignment) +{ + min_stride_alignment = _min_stride_alignment; +} + static void buffer_destroy_dont_close(struct buffer *buf) { @@ -224,6 +238,10 @@ xassert(found); if (!found) LOG_WARN("deferred delete: buffer not on the 'deferred' list"); + } else { + if (buffer->release_cb != NULL) { + buffer->release_cb(&buffer->public, buffer->cb_data); + } } } @@ -231,7 +249,6 @@ .release = &buffer_release, }; -#if __SIZEOF_POINTER__ == 8 static size_t page_size(void) { @@ -248,7 +265,6 @@ xassert(size > 0); return size; } -#endif static bool instantiate_offset(struct buffer_private *buf, off_t new_offset) @@ -342,6 +358,13 @@ ? chain->pixman_fmt_with_alpha : chain->pixman_fmt_without_alpha, widths[i]); + + if (min_stride_alignment > 0) { + const size_t m = min_stride_alignment; + stride[i] = (stride[i] + m - 1) / m * m; + } + + xassert(min_stride_alignment == 0 || stride[i] % min_stride_alignment == 0); sizes[i] = stride[i] * heights[i]; total_size += sizes[i]; } @@ -383,9 +406,11 @@ goto err; } + const size_t page_sz = page_size(); + #if __SIZEOF_POINTER__ == 8 off_t offset = chain->scrollable && max_pool_size > 0 - ? (max_pool_size / 4) & ~(page_size() - 1) + ? (max_pool_size / 4) & ~(page_sz - 1) : 0; off_t memfd_size = chain->scrollable && max_pool_size > 0 ? max_pool_size @@ -395,7 +420,8 @@ off_t memfd_size = total_size; #endif - xassert(chain->scrollable || (offset == 0 && memfd_size == total_size)); + /* Page align */ + memfd_size = (memfd_size + page_sz - 1) & ~(page_sz - 1); LOG_DBG("memfd-size: %lu, initial offset: %lu", memfd_size, offset); @@ -427,6 +453,9 @@ memfd_size = total_size; chain->scrollable = false; + /* Page align */ + memfd_size = (memfd_size + page_sz - 1) & ~(page_sz - 1); + if (ftruncate(pool_fd, memfd_size) < 0) { LOG_ERRNO("failed to set size of SHM backing memory file"); goto err; @@ -497,6 +526,8 @@ .offset = 0, .size = sizes[i], .scrollable = chain->scrollable, + .release_cb = chain->release_cb, + .cb_data = chain->cb_data, }; if (!instantiate_offset(buf, offset)) { @@ -604,7 +635,7 @@ * reuse. Pick the "youngest" one, and mark the * other one for purging */ if (buf->public.age < cached->public.age) { - shm_unref(&cached->public); + //shm_unref(&cached->public); cached = buf; } else { /* @@ -615,8 +646,8 @@ * should be safe; "our" tll_foreach() already * holds the next pointer. */ - if (buffer_unref_no_remove_from_chain(buf)) - tll_remove(chain->bufs, it); + //if (buffer_unref_no_remove_from_chain(buf)) + // tll_remove(chain->bufs, it); } } } @@ -975,7 +1006,8 @@ struct buffer_chain * shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, - enum shm_bit_depth desired_bit_depth) + enum shm_bit_depth desired_bit_depth, + void (*release_cb)(struct buffer *buf, void *data), void *cb_data) { pixman_format_code_t pixman_fmt_without_alpha = PIXMAN_x8r8g8b8; enum wl_shm_format shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB8888; @@ -1071,6 +1103,9 @@ .pixman_fmt_with_alpha = pixman_fmt_with_alpha, .shm_format_with_alpha = shm_fmt_with_alpha, + + .release_cb = release_cb, + .cb_data = cb_data, }; return chain; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/shm.h new/foot-1.25.0/shm.h --- old/foot-1.24.0/shm.h 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/shm.h 2025-10-16 08:46:58.000000000 +0200 @@ -42,12 +42,16 @@ }; void shm_fini(void); + +/* TODO: combine into shm_init() */ void shm_set_max_pool_size(off_t max_pool_size); +void shm_set_min_stride_alignment(size_t min_stride_alignment); struct buffer_chain; struct buffer_chain *shm_chain_new( struct wayland *wayl, bool scrollable, size_t pix_instances, - enum shm_bit_depth desired_bit_depth); + enum shm_bit_depth desired_bit_depth, + void (*release_cb)(struct buffer *buf, void *data), void *cb_data); void shm_chain_free(struct buffer_chain *chain); enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/terminal.c new/foot-1.25.0/terminal.c --- old/foot-1.24.0/terminal.c 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/terminal.c 2025-10-16 08:46:58.000000000 +0200 @@ -719,6 +719,9 @@ goto err_sem_destroy; } + mtx_init(&term->render.workers.preapplied_damage.lock, mtx_plain); + cnd_init(&term->render.workers.preapplied_damage.cond); + term->render.workers.threads = xcalloc( term->render.workers.count, sizeof(term->render.workers.threads[0])); @@ -1356,13 +1359,13 @@ .render = { .chains = { .grid = shm_chain_new(wayl, true, 1 + conf->render_worker_count, - desired_bit_depth), - .search = shm_chain_new(wayl, false, 1 ,desired_bit_depth), - .scrollback_indicator = shm_chain_new(wayl, false, 1, desired_bit_depth), - .render_timer = shm_chain_new(wayl, false, 1, desired_bit_depth), - .url = shm_chain_new(wayl, false, 1, desired_bit_depth), - .csd = shm_chain_new(wayl, false, 1, desired_bit_depth), - .overlay = shm_chain_new(wayl, false, 1, desired_bit_depth), + desired_bit_depth, &render_buffer_release_callback, term), + .search = shm_chain_new(wayl, false, 1 ,desired_bit_depth, NULL, NULL), + .scrollback_indicator = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL), + .render_timer = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL), + .url = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL), + .csd = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL), + .overlay = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL), }, .scrollback_lines = conf->scrollback.lines, .app_sync_updates.timer_fd = app_sync_updates_fd, @@ -1893,6 +1896,8 @@ } } free(term->render.workers.threads); + mtx_destroy(&term->render.workers.preapplied_damage.lock); + cnd_destroy(&term->render.workers.preapplied_damage.cond); mtx_destroy(&term->render.workers.lock); sem_destroy(&term->render.workers.start); sem_destroy(&term->render.workers.done); @@ -4005,9 +4010,11 @@ cell->wc = term->vt.last_printed = wc; cell->attrs = term->vt.attrs; - if (term->vt.osc8.uri != NULL) { - grid_row_uri_range_put( - row, col, term->vt.osc8.uri, term->vt.osc8.id); + if (unlikely(term->vt.osc8.uri != NULL)) { + for (int i = 0; i < width && (col + i) < term->cols; i++) { + grid_row_uri_range_put( + row, col + i, term->vt.osc8.uri, term->vt.osc8.id); + } switch (term->conf->url.osc8_underline) { case OSC8_UNDERLINE_ALWAYS: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/terminal.h new/foot-1.25.0/terminal.h --- old/foot-1.24.0/terminal.h 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/terminal.h 2025-10-16 08:46:58.000000000 +0200 @@ -706,6 +706,14 @@ tll(int) queue; thrd_t *threads; struct buffer *buf; + + struct { + mtx_t lock; + cnd_t cond; + struct buffer *buf; + struct timespec start; + struct timespec stop; + } preapplied_damage; } workers; /* Last rendered cursor position */ @@ -716,6 +724,8 @@ } last_cursor; struct buffer *last_buf; /* Buffer we rendered to last time */ + size_t frames_since_last_immediate_release; + bool preapply_last_frame_damage; enum overlay_style last_overlay_style; struct buffer *last_overlay_buf; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/tests/test-config.c new/foot-1.25.0/tests/test-config.c --- old/foot-1.24.0/tests/test-config.c 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/tests/test-config.c 2025-10-16 08:46:58.000000000 +0200 @@ -753,6 +753,11 @@ (int []){ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL}, (int *)&conf.colors.alpha_mode); + test_enum(&ctx, &parse_section_colors, "dim-blend-towards", 2, + (const char *[]){"black", "white"}, + (int []){DIM_BLEND_TOWARDS_BLACK, DIM_BLEND_TOWARDS_WHITE}, + (int *)&conf.colors.dim_blend_towards); + for (size_t i = 0; i < 255; i++) { char key_name[4]; sprintf(key_name, "%zu", i); @@ -1413,6 +1418,9 @@ test_float(&ctx, &parse_section_tweak, "bold-text-in-bright-amount", &conf.bold_in_bright.amount); + test_uint32(&ctx, &parse_section_tweak, "min-stride-alignment", + &conf.tweak.min_stride_alignment); + #if 0 /* Must be equal to, or less than INT32_MAX */ test_uint32(&ctx, &parse_section_tweak, "max-shm-pool-size-mb", &conf.tweak.max_shm_pool_size); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/themes/modus-operandi new/foot-1.25.0/themes/modus-operandi --- old/foot-1.24.0/themes/modus-operandi 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/themes/modus-operandi 2025-10-16 08:46:58.000000000 +0200 @@ -22,3 +22,5 @@ bright5=5317ac bright6=005a5f bright7=ffffff + +jump-labels=dce0e8 0000ff diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/foot-1.24.0/wayland.c new/foot-1.25.0/wayland.c --- old/foot-1.24.0/wayland.c 2025-09-12 10:18:33.000000000 +0200 +++ new/foot-1.25.0/wayland.c 2025-10-16 08:46:58.000000000 +0200 @@ -1064,6 +1064,7 @@ bool wasnt_configured = !win->is_configured; bool was_resizing = win->is_resizing; + bool was_fullscreen = win->is_fullscreen; bool csd_was_enabled = win->csd_mode == CSD_YES && !win->is_fullscreen; int new_width = win->configure.width; int new_height = win->configure.height; @@ -1134,11 +1135,26 @@ else term_visual_focus_out(term); - if (!resized && !term->render.pending.grid) { + /* + * Update opaque region if fullscreen state changed, also need to + * render, since we use different buffer types with and without + * alpha + */ + if (was_fullscreen != win->is_fullscreen) { + wayl_win_alpha_changed(win); + render_refresh(term); + } + + const bool will_render_soon = resized || + term->render.refresh.grid || + term->render.pending.grid; + + if (!will_render_soon) { /* - * If we didn't resize, we won't be committing a new surface - * anytime soon. Some compositors require a commit in - * combination with an ack - make them happy. + * If we didn't resize, and aren't refreshing for other + * reasons, we won't be committing a new surface anytime + * soon. Some compositors require a commit in combination with + * an ack - make them happy. */ wl_surface_commit(win->surface.surf); } @@ -2401,7 +2417,13 @@ { struct terminal *term = win->term; - if (term->colors.alpha == 0xffff) { + /* + * When fullscreened, transparency is disabled (see render.c). + * Update the opaque region to match. + */ + bool is_opaque = term->colors.alpha == 0xffff || win->is_fullscreen; + + if (is_opaque) { struct wl_region *region = wl_compositor_create_region( term->wl->compositor);
