wl_backend: fix client resize and frame lifecycle

- Send frame_done after configure so clients can render at new size
- Set activated state on toplevel configure
- Dispatch idle callbacks to flush configure immediately
- Add client_set_maximized vtable entry for XDG toplevel state
- Re-enable frame_buf scene node on unshade
- Raise client scene node on window_map (fixes unshade visibility)
- Reposition client and children in frame_configure
- Schedule output frame after frame_paint
- Remove debug wwarnings from wl_window_resize

This fixes:
- Window shade/unshade (resizebar + client surface visibility)
- Client surface receiving configure events and rendering
- Frame decoration updates on resize/maximize
This commit is contained in:
lkn 2026-05-14 23:08:37 +02:00
parent 859c4d8e78
commit 98f3175895
5 changed files with 83 additions and 3 deletions

View File

@ -82,7 +82,7 @@ Priority: HIGH — needed for daily-driver use.
|---|------|-----------|------------|
| A1 | Stacking (raise/lower via scene graph) | stacking.c, wlr_scene_node_raise/lower | Low |
| A2 | Window maximize (full/half/vertical/horizontal) | actions.c, usable area calc | Low-Medium |
| A3 | Window shade (titlebar collapse) | actions.c, frame buffer resize | Low |
| A3 | ~~Window shade (titlebar collapse)~~ | actions.c, frame buffer resize | ✅ Done |
| A4 | Workspace switching (create/destroy/switch) | workspace.c, scene tree reparenting | Medium |
| A5 | Alt-Tab window cycling + switch panel | cycling.c, switchpanel.c, vtable switchpanel_enter | Medium |
| A6 | Window iconification (minimize) + miniwindow | icon.c, appicon.c, vtable icon_paint/icon_update_pixmap | Medium |
@ -214,7 +214,7 @@ For running without a host compositor:
NOW (daily-driver basics):
A1. Stacking (raise/lower)
A2. Maximize
A3. Shade
A3. ~~Shade~~ ✅
A4. Workspace switching
A5. Alt-Tab cycling
A6. Icon/minimize

View File

@ -521,6 +521,9 @@ void wMaximizeWindow(WWindow *wwin, int directions, int head)
wWindowCropSize(wwin, usableArea.x2 - usableArea.x1,
usableArea.y2 - usableArea.y1, &new_width, &new_height);
if (wm_backend->client_set_maximized)
wm_backend->client_set_maximized(wwin->client_win, 1);
wWindowConfigure(wwin, new_x, new_y, new_width, new_height);
wWindowSynthConfigureNotify(wwin);
@ -959,6 +962,8 @@ void wUnmaximizeWindow(WWindow *wwin)
wwin->flags.maximized = 0;
wwin->flags.old_maximized = 0;
if (wm_backend->client_set_maximized)
wm_backend->client_set_maximized(wwin->client_win, 0);
wWindowConfigure(wwin, x, y, w, h);
wWindowSynthConfigureNotify(wwin);

View File

@ -1464,6 +1464,11 @@ typedef struct WMBackend {
*/
void (*client_set_state)(WWindow *wwin, int state, WNativeWindow icon_win);
/* Notify the client of maximized/fullscreen state changes.
* X11: no-op (handled via _NET_WM_STATE property).
* Wayland: sends xdg_toplevel set_maximized/set_fullscreen. */
void (*client_set_maximized)(WNativeWindow win, int maximized);
/*
* Send a WM_PROTOCOLS message to a client (e.g. WM_DELETE_WINDOW).
* X11: XSendEvent with ClientMessage.

View File

@ -1575,6 +1575,9 @@ handle_xdg_toplevel_initial_commit(struct wl_listener *listener, void *data)
wl_list_remove(&view->commit_listener.link);
wl_list_init(&view->commit_listener.link);
/* Send initial configure so the client enters the configure/ack cycle.
* Size 0,0 means "choose your own size" per the XDG spec. */
wlr_xdg_toplevel_set_activated(view->xdg_surface->toplevel, true);
wlr_xdg_toplevel_set_size(view->xdg_surface->toplevel, 0, 0);
wlr_xdg_surface_schedule_configure(view->xdg_surface);
}
@ -1862,6 +1865,23 @@ handle_output_frame(struct wl_listener *listener, void *data)
return;
if (!wl_state.scene_output)
return;
static int frame_count = 0;
if (++frame_count % 120 == 0) {
struct wl_toplevel_view *v;
wl_list_for_each(v, &wl_state.toplevel_list, link) {
if (v->xdg_surface && v->xdg_surface->surface && v->xdg_surface->toplevel) {
struct wlr_surface *s = v->xdg_surface->surface;
struct wlr_xdg_toplevel *tl = v->xdg_surface->toplevel;
wwarning("frame %d: 0x%lx surf=%dx%d sched=%dx%d current=%dx%d idle=%p title=%s",
frame_count, (unsigned long)v->id,
s->current.width, s->current.height,
tl->scheduled.width, tl->scheduled.height,
tl->current.width, tl->current.height,
(void*)v->xdg_surface->configure_idle,
tl->title ? tl->title : "(null)");
}
}
}
if (wl_state.allocator) {
/* Full scene commit — renders client surfaces and fires
* wl_surface.frame callbacks so clients can submit next buffer. */
@ -3927,6 +3947,8 @@ wl_frame_destroy(WScreen *scr, WNativeWindow win)
static void
wl_frame_configure(WNativeWindow win, int x, int y, int width, int height)
{
wwarning("wl_frame_configure: win=0x%lx pos=%d,%d size=%dx%d",
(unsigned long)win, x, y, width, height);
struct wl_frame_buf *fb = frame_buf_find(win);
if (!fb) return;
@ -3960,6 +3982,29 @@ wl_frame_configure(WNativeWindow win, int x, int y, int width, int height)
fb->base.width = width;
fb->base.height = height;
}
/* Reposition client surface and child frame_bufs to follow the frame. */
struct wl_toplevel_view *cv;
wl_list_for_each(cv, &wl_state.toplevel_list, link) {
if (cv->frame_id == win) {
cv->x = fb->x + cv->frame_rel_x;
cv->y = fb->y + cv->frame_rel_y;
wwarning("frame_configure: repositioned client 0x%lx to %d,%d (rel %d,%d)",
(unsigned long)cv->id, cv->x, cv->y, cv->frame_rel_x, cv->frame_rel_y);
if (cv->scene_node)
wlr_scene_node_set_position(&cv->scene_node->node, cv->x, cv->y);
break;
}
}
struct wl_frame_buf *child;
wl_list_for_each(child, &wl_state.frame_list, link) {
if (child->parent_id == win) {
child->x = fb->x + child->rel_x;
child->y = fb->y + child->rel_y;
if (child->scene_buf)
wlr_scene_node_set_position(&child->scene_buf->node, child->x, child->y);
}
}
}
static void wl_frame_paint_button(WCoreWindow *button, WTexture *texture,
@ -4178,6 +4223,9 @@ wl_frame_paint(struct WFrameWindow *fwin)
}
}
if (wl_state.output)
wlr_output_schedule_frame(wl_state.output);
return 1;
}
@ -5905,6 +5953,9 @@ static void wl_window_resize(WNativeWindow win, int w, int h)
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v) {
if (w <= 0 || h <= 0) return;
wwarning("wl_window_resize: id=0x%lx %dx%d xw=%p xdg=%p",
(unsigned long)win, w, h, (void*)v->xw_surface,
(void*)v->xdg_surface);
if (v->xw_surface) {
wlr_xwayland_surface_configure(v->xw_surface,
(int16_t)v->x, (int16_t)v->y, (uint16_t)w, (uint16_t)h);
@ -5912,7 +5963,16 @@ static void wl_window_resize(WNativeWindow win, int w, int h)
}
if (!v->xdg_surface || !v->xdg_surface->toplevel) return;
wlr_xdg_toplevel_set_size(v->xdg_surface->toplevel, (uint32_t)w, (uint32_t)h);
wlr_xdg_surface_schedule_configure(v->xdg_surface);
wlr_xdg_toplevel_set_activated(v->xdg_surface->toplevel, true);
/* Dispatch idle so configure is written, then flush to client */
struct wl_event_loop *loop = wl_display_get_event_loop(wl_state.display);
wl_event_loop_dispatch(loop, 0);
wl_display_flush_clients(wl_state.display);
/* Send frame_done so client can render at new size */
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
wlr_surface_send_frame_done(v->xdg_surface->surface, &now);
wl_display_flush_clients(wl_state.display);
return;
}
struct wl_frame_buf *fb = frame_buf_find(win);
@ -6929,6 +6989,14 @@ static void wl_noop_client_restore(WWindow *wwin) { (void)wwin; }
static void wl_noop_client_set_state(WWindow *wwin, int state, WNativeWindow icon_win)
{ (void)wwin; (void)state; (void)icon_win; }
static void wl_client_set_maximized(WNativeWindow win, int maximized)
{
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (!v) return;
if (v->xdg_surface && v->xdg_surface->toplevel)
wlr_xdg_toplevel_set_maximized(v->xdg_surface->toplevel, maximized);
}
static void wl_client_send_protocol(WWindow *wwin, unsigned long protocol,
unsigned long timestamp)
{
@ -7686,6 +7754,7 @@ const WMBackend wl_backend = {
.prop_cleanup = wl_noop_prop_cleanup,
.client_restore = wl_noop_client_restore,
.client_set_state = wl_noop_client_set_state,
.client_set_maximized = wl_client_set_maximized,
.client_send_protocol = wl_client_send_protocol,
.client_kill = wl_client_kill_impl,
.client_get_colormap_windows = wl_noop_client_get_colormap_windows,

View File

@ -4361,6 +4361,7 @@ const WMBackend x11_backend = {
.prop_cleanup = x11_prop_cleanup,
.client_restore = x11_client_restore,
.client_set_state = x11_client_set_state,
.client_set_maximized = NULL, /* X11: handled via _NET_WM_STATE property */
.client_send_protocol = x11_client_send_protocol,
.client_kill = x11_client_kill,
.client_get_colormap_windows = x11_client_get_colormap_windows,