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

Reply via email to