fix(region-selector): render first frame synchronously to avoid frame-callback deadlock - #220
Conversation
…-callback deadlock Some compositors (e.g. Hyprland >= 0.56) never fire wl_surface.frame callbacks for a layer surface that has not committed a buffer yet. request_render() only drew a frame after such a callback, so the selector deadlocked before its first frame: the overlay never became visible, input never arrived, and the plugin killed the process after its 60s timeout (surfacing as a misleading "backend not installed" toast). Draw the first frame for each output synchronously; subsequent redraws still go through frame callbacks as before.
Reviewer's GuideRenders the region selector’s first frame synchronously instead of waiting for a wl_surface.frame callback, by tracking an initial-render flag per output and short‑circuiting the frame-callback path to directly invoke render_output when needed, preventing a deadlock on compositors that never send frame events for buffer-less layer surfaces. Sequence diagram for synchronous first-frame render in region selectorsequenceDiagram
participant Compositor
participant SelectorState
participant OutputEntry
Compositor->>SelectorState: zwlr_layer_surface_v1.configure
SelectorState->>SelectorState: request_render(output_idx)
SelectorState->>OutputEntry: mark dirty
SelectorState->>OutputEntry: check initial_render_done
alt initial_render_done is false and surface exists and no frame_callback
SelectorState->>SelectorState: render_output(state, qh, output_idx)
SelectorState->>Compositor: wl_surface.commit
SelectorState->>OutputEntry: set initial_render_done = true
else initial_render_done is true or no surface or has frame_callback
SelectorState-->>OutputEntry: wait for wl_surface.frame callback
end
rect rgb(230,230,230)
Compositor-->>SelectorState: wl_surface.frame callback
SelectorState->>SelectorState: request_render(output_idx)
SelectorState->>OutputEntry: reuse frame_callback path
SelectorState->>SelectorState: render_output(state, qh, output_idx)
SelectorState->>Compositor: wl_surface.commit
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In
request_render, consider restructuring to avoid the multiplestate.outputs.get/get_mutcalls per invocation; a single mutable borrow captured up front would simplify the logic and reduce the chance of subtle borrowing or state bugs. - The new
initial_render_doneflag is only set inrender_output; confirm it is reset or reinitialized appropriately when an output or surface is recreated so that a replaced surface still forces a synchronous first frame.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `request_render`, consider restructuring to avoid the multiple `state.outputs.get/get_mut` calls per invocation; a single mutable borrow captured up front would simplify the logic and reduce the chance of subtle borrowing or state bugs.
- The new `initial_render_done` flag is only set in `render_output`; confirm it is reset or reinitialized appropriately when an output or surface is recreated so that a replaced surface still forces a synchronous first frame.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
…g on new surface Address review feedback: - request_render now uses a single scoped mutable borrow for the decision (configured/closed/dirty/frame-callback/first-frame) and only re-borrows to register a frame callback on the deferred path. - Reset initial_render_done when a layer surface is created in setup_layer_surfaces. Surfaces are in practice only created once per run, so this is defensive, but it makes the invariant explicit: a fresh surface always forces a synchronous first frame.
|
I investigated both points against the code before changing anything: 1. 2. Both changes are behavior-preserving; rebuilt and re-tested (region capture works end-to-end on the affected setup, no new warnings). |
|
Thanks for the PR. I don't use Hyprland, so having someone like you submit a fix is really helpful. If you find any other bugs related to this backend on Hyprland, feel free to open another issue, preferably with some screenshots or a video so I can better understand the issue. |
I really like the plugin so i thought it worth the fix, thanks for taking time working on it. |
Summary
Render the region selector's first frame synchronously instead of waiting for a
wl_surface.framecallback, fixing a deadlock that leaves the selection overlay invisible (and input-less) on Hyprland 0.56 and similar compositors.Use Case
Region/scroll capture is completely unusable on affected compositors: the overlay never appears, and after the plugin's 60s timeout the user gets a misleading "Rust screenshot backend is not installed" toast even though the backend is installed and working. See #219.
Changes
dms-screenshot-rs/src/region_selector/backend.rs:request_render(): if the output has not committed its initial buffer yet, callrender_output()directly instead of waiting for a frame callback (which some compositors never fire for buffer-less surfaces).initial_render_doneflag per output, set after the first successful commit.Related Issues
Closes #219
Testing
zwlr_layer_surface_v1.configurearriving for both outputs, butrender_output()never runs — no buffer committed,hyprctl layersshows theselectionlayers at alpha 0, capture killed at 60s (exitCode=124), "backend not installed" toast.{"status":"success",...}and opens the annotation modal.fullmode unaffected (still works as before).backend/<arch>/per the README flow.Screenshots / Screen Recordings
Not available (Wayland screen content from the affected overlay was, by definition, not rendered).
Checklist
Summary by Sourcery
Ensure the region selector displays and accepts input reliably on compositors that do not issue frame callbacks before the first buffer commit.
Bug Fixes:
Enhancements: