From c18bc59f11278bf54e0a3dd2863ab862e4fc87a2 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 22 Jul 2026 14:55:13 +0200 Subject: [PATCH 1/2] wayland: trigger redraw after every configure sequence After a configure sequence, the compositor waits for the client to commit. This is necessary to synchronize changes. For example: 1. Window is focused. 2. Window loses focus. 3. Compositor sends configuration sequence. 4. Client acks and commits. 5. Compositor redraws the window decorations as unfocused. Without the synchronization with the configuration sequence, the window contents might show an "unfocused" state while the decorations show a "focused" state and vice versa. mpv does not change its look when focused/unfocused but the compositor cannot know this and many applications do this. It would be sufficient to wl_surface_commit instead of ORing VO_EVENT_EXPOSE, but this seems to be the safer way and configuration sequences are rare. --- video/out/wayland_common.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index f60f9dad1831a..847e0dc75b452 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -1777,7 +1777,10 @@ static const struct xdg_wm_base_listener xdg_wm_base_listener = { static void handle_surface_config(void *data, struct xdg_surface *surface, uint32_t serial) { + struct vo_wayland_state *wl = data; + xdg_surface_ack_configure(surface, serial); + wl->pending_vo_events |= VO_EVENT_EXPOSE; } static const struct xdg_surface_listener xdg_surface_listener = { @@ -1934,8 +1937,6 @@ static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel, mp_rect_w(old_geometry), mp_rect_h(old_geometry), mp_rect_w(wl->geometry), mp_rect_h(wl->geometry)); wl->pending_vo_events |= VO_EVENT_RESIZE; - } else if (wl->resizing) { - wl->pending_vo_events |= VO_EVENT_EXPOSE; } wl->override_surface_local = width == 0 || height == 0 || wl->reconfigured; From a630518f20b1a22985bc11dbb638c28e3c059b32 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 22 Jul 2026 15:22:15 +0200 Subject: [PATCH 2/2] wayland: defer config sequence application xdg_surface.configure can occur with a delay after xdg_toplevel.configure and there can be more than one xdg_toplevel.configure events between xdg_surface.configure events. As per xdg_toplevel.configure: > The configured state should not be applied immediately. See > xdg_surface.configure for details. --- video/out/wayland_common.c | 87 +++++++++++++++++++++++++++----------- video/out/wayland_common.h | 19 +++++++++ 2 files changed, 82 insertions(+), 24 deletions(-) diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 847e0dc75b452..d396ef207650a 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -340,6 +340,8 @@ static void set_surface_scaling(struct vo_wayland_state *wl); static void update_output_scaling(struct vo_wayland_state *wl); static void update_output_geometry(struct vo_wayland_state *wl); static void destroy_offer(struct vo_wayland_data_offer *o); +static void apply_toplevel_config(struct vo_wayland_state *wl); +static void apply_decorations(struct vo_wayland_state *wl); #if HAVE_WAYLAND_PROTOCOLS_1_48 static char *session_file(void *talloc_ctx, const char *session, struct vo *vo); static char *read_session_id(void *talloc_ctx, struct vo_wayland_state *wl, const char *path); @@ -1779,6 +1781,8 @@ static void handle_surface_config(void *data, struct xdg_surface *surface, { struct vo_wayland_state *wl = data; + apply_decorations(wl); + apply_toplevel_config(wl); xdg_surface_ack_configure(surface, serial); wl->pending_vo_events |= VO_EVENT_EXPOSE; } @@ -1791,55 +1795,75 @@ static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { struct vo_wayland_state *wl = data; - struct mp_vo_opts *opts = wl->opts; - struct mp_rect old_geometry = wl->geometry; + struct vo_wayland_toplevel_pending_state *pending = &wl->pending_state; + struct vo_wayland_toplevel_pending_flags *flags = &pending->flags; if (width < 0 || height < 0) { MP_WARN(wl, "Compositor sent negative width/height values. Treating them as zero.\n"); width = height = 0; } - bool is_maximized = false; - bool is_fullscreen = false; - bool is_activated = false; - bool is_resizing = false; - bool is_suspended = false; - bool is_tiled = false; + pending->width = width; + pending->height = height; + *flags = (struct vo_wayland_toplevel_pending_flags){ 0 }; + enum xdg_toplevel_state *state; wl_array_for_each(state, states) { switch (*state) { case XDG_TOPLEVEL_STATE_FULLSCREEN: - is_fullscreen = true; + flags->is_fullscreen = true; break; case XDG_TOPLEVEL_STATE_RESIZING: - is_resizing = true; + flags->is_resizing = true; break; case XDG_TOPLEVEL_STATE_ACTIVATED: - is_activated = true; - /* - * If we get an ACTIVATED state, we know it cannot be - * minimized, but it may not have been minimized - * previously, so we can't detect the exact state. - */ - opts->window_minimized = false; - m_config_cache_write_opt(wl->opts_cache, - &opts->window_minimized); + flags->is_activated = true; break; case XDG_TOPLEVEL_STATE_TILED_TOP: case XDG_TOPLEVEL_STATE_TILED_LEFT: case XDG_TOPLEVEL_STATE_TILED_RIGHT: case XDG_TOPLEVEL_STATE_TILED_BOTTOM: - is_tiled = true; + flags->is_tiled = true; break; case XDG_TOPLEVEL_STATE_MAXIMIZED: - is_maximized = true; + flags->is_maximized = true; break; case XDG_TOPLEVEL_STATE_SUSPENDED: - is_suspended = true; + flags->is_suspended = true; break; } } +} +static void apply_toplevel_config(struct vo_wayland_state *wl) +{ + struct mp_vo_opts *opts = wl->opts; + struct mp_rect old_geometry = wl->geometry; + struct vo_wayland_toplevel_pending_state *pending = &wl->pending_state; + struct vo_wayland_toplevel_pending_flags *flags = &pending->flags; + + wl->bounded_width = pending->bounded_width; + wl->bounded_height = pending->bounded_height; + + int32_t width = pending->width; + int32_t height = pending->height; + bool is_maximized = flags->is_maximized; + bool is_fullscreen = flags->is_fullscreen; + bool is_activated = flags->is_activated; + bool is_resizing = flags->is_resizing; + bool is_suspended = flags->is_suspended; + bool is_tiled = flags->is_tiled; + + if (is_activated) { + /* + * If we get an ACTIVATED state, we know it cannot be + * minimized, but it may not have been minimized + * previously, so we can't detect the exact state. + */ + opts->window_minimized = false; + m_config_cache_write_opt(wl->opts_cache, + &opts->window_minimized); + } /* Only update the toplevel state values if either mpv already has * configured its initial geometry or if the compositor gives us some * initial state to use. */ @@ -1954,8 +1978,8 @@ static void handle_configure_bounds(void *data, struct xdg_toplevel *xdg_topleve int32_t width, int32_t height) { struct vo_wayland_state *wl = data; - wl->bounded_width = handle_round(wl->scaling, width); - wl->bounded_height = handle_round(wl->scaling, height); + wl->pending_state.bounded_width = handle_round(wl->scaling, width); + wl->pending_state.bounded_height = handle_round(wl->scaling, height); } static void handle_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, @@ -2489,7 +2513,22 @@ static void configure_decorations(void *data, uint32_t mode) { struct vo_wayland_state *wl = data; + struct vo_wayland_toplevel_pending_state *pending = &wl->pending_state; + + pending->new_decoration_mode = mode; +} + +static void apply_decorations(struct vo_wayland_state *wl) +{ struct mp_vo_opts *opts = wl->opts; + struct vo_wayland_toplevel_pending_state *pending = &wl->pending_state; + uint32_t mode = pending->new_decoration_mode; + + if (mode == 0) { + return; + } + + pending->new_decoration_mode = 0; if (wl->requested_decoration && mode != wl->requested_decoration) { MP_DBG(wl, diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index f3f40fb013aaf..2474e374b6a85 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -35,6 +35,24 @@ struct drm_format { uint64_t modifier; }; +struct vo_wayland_toplevel_pending_flags { + bool is_maximized; + bool is_fullscreen; + bool is_activated; + bool is_resizing; + bool is_suspended; + bool is_tiled; +}; + +struct vo_wayland_toplevel_pending_state { + int32_t bounded_width; + int32_t bounded_height; + int32_t width; + int32_t height; + struct vo_wayland_toplevel_pending_flags flags; + uint32_t new_decoration_mode; +}; + struct vo_wayland_state { struct m_config_cache *opts_cache; struct mp_log *log; @@ -69,6 +87,7 @@ struct vo_wayland_state { bool override_surface_local; /* State */ + struct vo_wayland_toplevel_pending_state pending_state; bool activated; bool focused; bool frame_wait;