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.
This commit is contained in:
Andrew Hamilton 2026-08-05 12:49:07 +10:00
parent 1206ab41db
commit 3ae1a7320a

View file

@ -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;
}