From 3ae1a7320a0a4f617085c333fa6893783fc77da3 Mon Sep 17 00:00:00 2001 From: Andrew Hamilton Date: Wed, 5 Aug 2026 12:49:07 +1000 Subject: [PATCH] Fix blank client frames and frame-callback stall Blit client buffers from their top-left origin instead of honoring set_window_geometry offsets: clients like foot set a geometry that extends above the buffer (CSD shadow, geom_y < 0), which made the source row pointer start before the buffer and produced a blank frame. Buffers are now copied 1:1 from origin, capped to canvas. Also dispatch wl_callback.done on the first configure commit so frame-throttled clients (foot) don't stall waiting for it. --- tabbed-wayland.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tabbed-wayland.c b/tabbed-wayland.c index 04ccc82..a96aae6 100644 --- a/tabbed-wayland.c +++ b/tabbed-wayland.c @@ -257,15 +257,13 @@ static void copy_buffer_to_display(struct tabby_surface *surface) { size_t out_size = (size_t)out->height * (size_t)out->stride; memset(out->data, 0x22, out_size); - int src_x = (surface->geom_width > 0) ? surface->geom_x : 0; - int src_y = (surface->geom_height > 0) ? surface->geom_y : 0; - int src_w = (surface->geom_width > 0) ? surface->geom_width : surface->pixel_width; - int src_h = (surface->geom_height > 0) ? surface->geom_height : surface->pixel_height; - - if (src_x + src_w > surface->pixel_width) src_w = surface->pixel_width - src_x; - if (src_y + src_h > surface->pixel_height) src_h = surface->pixel_height - src_y; - if (src_w < 0) src_w = 0; - if (src_h < 0) src_h = 0; + /* Blit the client's buffer at its top-left origin (0,0), capped to the + canvas size. We intentionally ignore the surface's window geometry for + positioning: some clients set a geometry that extends above/beside the + buffer (e.g. foot's CSD shadow, geom_y < 0), which would make the source + row pointer start before the buffer and produce a blank frame. */ + int src_w = surface->pixel_width; + int src_h = surface->pixel_height; int cw = (src_w < out->width) ? src_w : out->width; int ch = (src_h < out->height) ? src_h : out->height; @@ -273,11 +271,10 @@ static void copy_buffer_to_display(struct tabby_surface *surface) { if (cw <= 0 || ch <= 0) return; if (surface->pixel_stride <= 0 || out->stride <= 0) return; if (surface->pixels == NULL || out->data == NULL) return; - if (src_y + ch > surface->pixel_height) return; for (int y = 0; y < ch; y++) { void *dst = out->data + y * out->stride; - void *src = surface->pixels + (src_y + y) * surface->pixel_stride + src_x * 4; + void *src = surface->pixels + y * surface->pixel_stride; if ((unsigned char*)dst < (unsigned char*)out->data || (unsigned char*)dst + cw*4 > (unsigned char*)out->data + out->height*out->stride) break; if ((unsigned char*)src < (unsigned char*)surface->pixels || @@ -308,6 +305,19 @@ static void surface_commit(struct wl_client *client, struct wl_resource *resourc if (surface->tab_label && surface->title) gtk_label_set_text(GTK_LABEL(surface->tab_label), surface->title); + + /* Dispatch the pending frame callback. Several clients (notably + foot) request wl_surface.frame on the first commit and will not + render their real buffer until wl_callback.done is sent, so it + must fire even on this early configure path. */ + if (surface->frame_callback) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint32_t ms = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + wl_callback_send_done(surface->frame_callback, ms); + wl_resource_destroy(surface->frame_callback); + surface->frame_callback = NULL; + } return; }