Skip to content

Conversation

YaLTeR
Copy link
Contributor

@YaLTeR YaLTeR commented Sep 5, 2025

Corresponding niri update: YaLTeR/niri@5a4d794

Fixes my fullscreen anim bugs that I found with mpv and adwaita-1-demo.


The last-acked configure is part of the committed state, and should be tracked as such. Before this commit, it would be possible to observe a newer "current state" in a commit handler if the surface acked the next state while the compositor was waiting on a blocker set in a pre-commit handler.

As part of the change, the current_state() getter was removed, because it's confusing and likely not what you actually want. For instance, several uses in Anvil actually needed to access a different state.

The WmCapabilities checks in Anvil were removed because they are serverside and don't change; not sure what the intention was with those.

Also, added forced configures in unmaximize() and unfullscreen() because they too are required by the protocol.


If this is good I'll do a similar refactor for layer-shell and session-lock.

One kinda weird part is current_server_state() that I had to change to owned return, tbh I would change it to return an Option<&State>, but that would involve changing a bunch of callers. I can do that though if that's better.

Also, there's a question of whether any other state needs to be moved into Cached?

@YaLTeR
Copy link
Contributor Author

YaLTeR commented Sep 5, 2025

Another question: should this last_acked cached state be reset somewhere upon unmapping? I guess a pre-commit hook would be needed here? (The configured bool in the role should also be reset at that point. Is that bool needed, or is ToplevelCachedState::last_acked.is_some() the more correct version of it?)

Copy link
Member

@Drakulix Drakulix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WmCapabilities checks in Anvil were removed because they are serverside and don't change; not sure what the intention was with those.

Also, added forced configures in unmaximize() and unfullscreen() because they too are required by the protocol.

If that is not too cumbersome, I'd love to split off fixes like this into separate commits.

One kinda weird part is current_server_state() that I had to change to owned return, tbh I would change it to return an Option<&State>, but that would involve changing a bunch of callers. I can do that though if that's better.

It seems like we already cloned it after requesting it in a bunch of places anyway? Seems fine to me honestly.

Another question: should this last_acked cached state be reset somewhere upon unmapping? I guess a pre-commit hook would be needed here?

We had this logic introduced not too (in the grand scheme of things) recently. I guess that logic simply needs to be extended a bit? #1571

@YaLTeR YaLTeR force-pushed the toplevel-cached-state branch from 386db48 to 688cd1b Compare September 5, 2025 16:58
@YaLTeR
Copy link
Contributor Author

YaLTeR commented Sep 5, 2025

If that is not too cumbersome, I'd love to split off fixes like this into separate commits.

Done.

@YaLTeR YaLTeR force-pushed the toplevel-cached-state branch from 688cd1b to dec7479 Compare September 6, 2025 08:21
@YaLTeR
Copy link
Contributor Author

YaLTeR commented Sep 6, 2025

Pushed a commit that also tracks the configured state through MultiCache last_acked.

Also:

  • Removes dependency on renderer abstractions for tracking unmaps.
  • Removes manual reset_initial_configure() because it's now always automatic.
  • Makes the check for map before initial configure automatic: fixes Map before initial configure does not disconnect the client #1717.
  • Adds missing role attributes reset on unmap (it only happened on role destroy previously).

Currently testing it on my machine with niri, so far so good.

@YaLTeR
Copy link
Contributor Author

YaLTeR commented Sep 6, 2025

I removed popup_handle because it wasn't actually set. It would be good to have popup_handle (and a corresponding toplevel_handle in the toplevel) for the error emitting path, but I'm not sure off the bat if setting them would cause a refcycle, so I opted for a loop over known toplevels for now.

@YaLTeR
Copy link
Contributor Author

YaLTeR commented Sep 6, 2025

Would be good to also move popup parent into MultiCache and get rid of committed (and along the way fix #1761) but that sounds like a much deeper dive than I'm prepared to do at the moment :p

@YaLTeR
Copy link
Contributor Author

YaLTeR commented Sep 6, 2025

Uhm so apparently wlcs doesn't bother to ack the initial configure?

$ WAYLAND_DEBUG=client ./wlcs/wlcs ./target/debug/libwlcs_anvil.so --gtest_filter="FrameSubmission.post_one_frame_at_a_time"
(...)
[4128002.287] {Default Queue}  -> wl_compositor#4.create_surface(new id wl_surface#11)
[4128002.325] {Default Queue}  -> xdg_wm_base#7.get_xdg_surface(new id xdg_surface#9, wl_surface#11)
[4128002.352] {Default Queue}  -> xdg_surface#9.get_toplevel(new id xdg_toplevel#13)
[4128002.360] {Default Queue}  -> wl_surface#11.commit()
[4128002.378] {Default Queue}  -> wl_shm#6.create_pool(new id wl_shm_pool#14, fd 16, 160000)
[4128002.384] {Default Queue}  -> wl_shm_pool#14.create_buffer(new id wl_buffer#15, 0, 200, 200, 800, 0)
[4128002.388] {Default Queue}  -> wl_shm_pool#14.destroy()
[4128002.394] {Default Queue}  -> wl_surface#11.attach(wl_buffer#15, 0, 0)
[4128002.400] {Default Queue}  -> wl_surface#11.frame(new id wl_callback#16)
[4128002.405] {Default Queue}  -> wl_surface#11.commit()
error in client communication (pid 105972)
[4128002.829] {Display Queue} wl_display#1.delete_id(14)
[4128002.835] {Display Queue} wl_display#1.error(xdg_surface#9, 3, "must ack the initial configure before attaching buffer")

After creating a role-specific object and setting it up (e.g. by sending the title, app ID, size constraints, parent, etc), the client must perform an initial commit without any buffer attached. The compositor will reply with initial wl_surface state such as wl_surface.preferred_buffer_scale followed by an xdg_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface.

(and the server debug for completeness)

[4251864.961] wl_compositor#4.create_surface(new id wl_surface#11)
[4251864.986] xdg_wm_base#7.get_xdg_surface(new id xdg_surface#9, wl_surface#11)
[4251865.020] xdg_surface#9.get_toplevel(new id xdg_toplevel#13)
[4251865.100] wl_surface#11.commit()
[4251865.187]  -> xdg_toplevel#13.configure_bounds(800, 600)
[4251865.201]  -> xdg_toplevel#13.wm_capabilities(array[16])
[4251865.211]  -> xdg_toplevel#13.configure(0, 0, array[4])
[4251865.220]  -> xdg_surface#9.configure(1)
[4251865.229] wl_shm#6.create_pool(new id wl_shm_pool#14, fd 15, 160000)
[4251865.251] wl_shm_pool#14.create_buffer(new id wl_buffer#15, 0, 200, 200, 800, 0)
[4251865.265] wl_shm_pool#14.destroy()
[4251865.272]  -> wl_display#1.delete_id(14)
[4251865.276] wl_surface#11.attach(wl_buffer#15, 0, 0)
[4251865.284] wl_surface#11.frame(new id wl_callback#16)
[4251865.291] wl_surface#11.commit()
[4251865.302]  -> wl_display#1.error(xdg_surface#9, 3, "must ack the initial configure before attaching buffer")

@YaLTeR YaLTeR force-pushed the toplevel-cached-state branch from dec7479 to 3ee763d Compare September 7, 2025 07:11
@YaLTeR YaLTeR changed the title wayland/shell/xdg: Track last acked configure in MultiCache xdg-shell, layer-shell, session-lock: Track last acked configure in MultiCache Sep 7, 2025
@YaLTeR YaLTeR force-pushed the toplevel-cached-state branch from 3ee763d to 8b93492 Compare September 7, 2025 07:19
@YaLTeR
Copy link
Contributor Author

YaLTeR commented Sep 7, 2025

  • Did the same refactor in layer-shell and session-lock.
  • Exported more of the usual types from session-lock.
  • Fixed an edge case (introduced in the refactor) with clients attaching a null buffer for the initial configure—Waybar does this.
  • Added with_cached_state() to all three (layer-shell had a similar function so might as well).

Tested on:

  • xdg: everything running on my desktop
  • layer-shell: waybar, sfwbar, lxqt-panel, xfce4-panel, quickshell, xfdesktop, pcmanfm-qt --desktop
  • session-lock: swaylock, hyprlock, gtklock

Would be good to also move popup parent into MultiCache and get rid of committed

Added a FIXME comment mentioning this.

@YaLTeR YaLTeR force-pushed the toplevel-cached-state branch from 8b93492 to 4bd0c6f Compare September 7, 2025 07:31
@YaLTeR YaLTeR marked this pull request as ready for review September 7, 2025 07:38
@YaLTeR
Copy link
Contributor Author

YaLTeR commented Sep 7, 2025

I think newer WLCS fixes the ack_configure issue? At least from a quick glance at its repo. How is it usually updated?

@codecov-commenter
Copy link

Codecov Report

❌ Patch coverage is 0% with 423 lines in your changes missing coverage. Please review.
✅ Project coverage is 6.81%. Comparing base (ec35248) to head (4bd0c6f).
⚠️ Report is 15 commits behind head on master.

Files with missing lines Patch % Lines
src/wayland/shell/xdg/mod.rs 0.00% 143 Missing ⚠️
src/wayland/session_lock/surface.rs 0.00% 106 Missing ⚠️
anvil/src/shell/xdg.rs 0.00% 69 Missing ⚠️
src/wayland/shell/wlr_layer/mod.rs 0.00% 66 Missing ⚠️
src/wayland/shell/xdg/handlers/surface.rs 0.00% 9 Missing ⚠️
src/desktop/wayland/layer.rs 0.00% 7 Missing ⚠️
src/desktop/wayland/popup/manager.rs 0.00% 6 Missing ⚠️
anvil/src/input_handler.rs 0.00% 4 Missing ⚠️
src/wayland/shell/wlr_layer/handlers.rs 0.00% 4 Missing ⚠️
src/wayland/shell/xdg/handlers/surface/popup.rs 0.00% 3 Missing ⚠️
... and 3 more

❗ There is a different number of reports uploaded between BASE (ec35248) and HEAD (4bd0c6f). Click for more details.

HEAD has 3 uploads less than BASE
Flag BASE (ec35248) HEAD (4bd0c6f)
wlcs-buffer 1 0
wlcs-core 1 0
wlcs-pointer-input 1 0
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #1817       +/-   ##
==========================================
- Coverage   18.89%   6.81%   -12.09%     
==========================================
  Files         176     177        +1     
  Lines       27672   27839      +167     
==========================================
- Hits         5230    1896     -3334     
- Misses      22442   25943     +3501     
Flag Coverage Δ
wlcs-buffer ?
wlcs-core ?
wlcs-output 6.81% <0.00%> (-0.04%) ⬇️
wlcs-pointer-input ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Member

@Drakulix Drakulix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks all good to me, thanks a lot for working on this.

However I would like another review before merging this. @ids1024 @cmeissl one of you maybe up for the task?

Mandated by the protocol, same as fullscreen/maximize.
Unclear what they were for. This is compositor-side state that Anvil
doesn't change after startup.
The last-acked configure is part of the committed state, and should be tracked
as such. Before this commit, it would be possible to observe a newer "current
state" in a commit handler if the surface acked the next state while the
compositor was waiting on a blocker set in a pre-commit handler.

As part of the change, the current_state() getter was replaced by
with_committed_state(). Since the "current" state wasn't really a
correct thing most of the time, most existing uses of current_state()
actually wanted something different, and were corrected in previous
commits.
Also:
- Removes dependency on renderer abstractions for tracking unmaps.
- Removes manual reset_initial_configure() because it's now always
  automatic.
- Makes the check for map before initial configure automatic.
- Adds missing role attributes reset on unmap (it only happened on role
  destroy previously).
Same idea as with xdg-shell in the previous commit.
In theory, allows reusing the wl_surface, consistent with other shells.
Same idea as with xdg-shell in the previous commit.
Contains fixes required for enforcing ack before commit as mandated by the
protocols.
@YaLTeR YaLTeR force-pushed the toplevel-cached-state branch from 3281377 to 1efb695 Compare September 12, 2025 14:30
@YaLTeR
Copy link
Contributor Author

YaLTeR commented Sep 12, 2025

Rebased, wrote changelog.

@Drakulix Drakulix requested review from ids1024 and cmeissl September 12, 2025 16:25
Copy link
Collaborator

@cmeissl cmeissl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks for working on this!

Qt layer-shell calls grab() first, only then sets popup parent via
zwlr_layer_surface_v1::get_popup(). Before this commit, grab() would call the
handler immediately, which would see that the popup has no parent, and fail.
However, it works on other compositors.

The xdg_popup docs don't mention the desired behavior explicitly, though this
line could be interpreted in favor of "applying" the grab request at commit
time:

> The client must call wl_surface.commit on the corresponding wl_surface for
> the xdg_popup state to take effect.

Fixes grabs from first-level popups on layer-shell Qt clients such as
pcmanfm-qt --desktop and lxqt-panel.
@YaLTeR
Copy link
Contributor Author

YaLTeR commented Sep 15, 2025

Added one commit on top that resolves the popup FIXME and delays the grab() handler until pre-commit. This fixes #1761.

I kept the parent and the new field in the role attributes because they can only be set during the initial commit and cannot change afterward (not sure this is currently enforced in Smithay), so I figured there's no need to put them in the cached state.

Tested with: pcmanfm-qt --desktop, lxqt-panel, Quickshell with Menu { popupType: Popup.Native, a bunch of random apps and tools with popups that did not have issues before

@Drakulix Drakulix merged commit 91c1d36 into Smithay:master Sep 17, 2025
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants