Cover every output from a background shell: the session-lock secondary machinery generalizes to layer surfaces
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled

The runtime already covered outputs beyond the first, but only for `ShellMode::SessionLock`: `locked` created one `ext-session-lock` surface per output, tracked in `lock_extras`, rendered from `App::lock_secondary_view`, excluded from pointer/touch routing and repainted through the shared invalidation path. A background shell had nothing equivalent, so on a multi-monitor session its wallpaper simply did not exist on the extended screens — the compositor had to fake one by cover-scaling the primary's background, wrong scale and wrong crop included.
The machinery is shared rather than duplicated. The state and the hook lose their lock-specific names — `lock_extras` becomes `secondaries`, `main_lock_output` becomes `main_output`, `App::lock_secondary_view` becomes `App::secondary_view` — and everything downstream of creation (the draw loop in frame.rs, invalidation, per-surface scale handling, the input exclusion in pointer/touch, redraw scheduling in run.rs) is the same single copy serving both kinds.
What is genuinely new is the second creation site: in `new_output`, when the app's shell mode is `Layer( Background )`, every output beyond the main surface's gets its own background layer surface — anchored to all four edges with size delegated to the compositor, exclusive zone 0, no keyboard interactivity — bound to that output at creation and torn down in `output_destroyed`. The main surface's output is now recorded when it materializes so the runtime knows which output not to cover.
Layer-shell events learn about secondaries too, and the `configure` branch is not optional: secondaries live outside the focus map, so their configure used to fall into the `None` arm and resize the main surface. `closed` on a secondary now just drops it instead of asking the app to exit.
`secondary_view` keeps its default of `None` (surface filled with `background_color`), so nothing changes for apps that do not opt in; the session-lock behaviour is unchanged aside from the renames.
This commit is contained in:
2026-08-14 23:20:58 +02:00
parent 99a4767815
commit 78053b8b01
8 changed files with 94 additions and 42 deletions
+15 -13
View File
@@ -219,15 +219,17 @@ pub struct AppData<A: App>
/// Populated and reconciled by the run loop from [`App::overlays`]. Always
/// empty until overlay diffing is wired up.
pub overlays: HashMap<OverlayId, SurfaceState<A::Message>>,
/// Session-lock surfaces covering every output beyond the first, paired
/// with the output each one belongs to. The compositor blanks any output
/// without a lock surface, so one is created per output; these render
/// [`App::lock_secondary_view`] and take no input (the input handlers
/// find no focus for them, and keyboard falls through to `Main`).
pub lock_extras: Vec<( WlOutput, SurfaceState<A::Message> )>,
/// Output the main lock surface was created on, so a later `new_output`
/// for the same output does not add a duplicate lock surface.
pub main_lock_output: Option<WlOutput>,
/// Surfaces covering every output beyond the first, paired with the
/// output each one belongs to: session-lock surfaces while locked (the
/// compositor blanks any output without one), background layer surfaces
/// for a [`crate::Layer::Background`] shell (the wallpaper reaches
/// extended outputs). These render [`App::secondary_view`] and take no
/// input (the input handlers find no focus for them, and keyboard falls
/// through to `Main`).
pub secondaries: Vec<( WlOutput, SurfaceState<A::Message> )>,
/// Output the main surface was created on, so a later `new_output` for
/// the same output does not add a duplicate secondary surface.
pub main_output: Option<WlOutput>,
/// Input-transparent child surfaces keyed by their stable
/// [`SubsurfaceId`]. Reconciled each frame from [`App::subsurfaces`].
pub subsurfaces: HashMap<SubsurfaceId, SubsurfaceSlot>,
@@ -295,14 +297,14 @@ impl<A: App> AppData<A>
None
}
/// Whether `wl` is one of the secondary session-lock surfaces. Pointer and
/// Whether `wl` is one of the secondary per-output surfaces. Pointer and
/// touch events landing on them must be dropped rather than falling back
/// to `Main` — their coordinates would hit-test the main surface's
/// widgets. Keyboard keeps the `Main` fallback so typing works wherever
/// the compositor puts the lock focus.
pub( crate ) fn is_lock_extra_surface( &self, wl: &WlSurface ) -> bool
/// the compositor puts the focus.
pub( crate ) fn is_secondary_surface( &self, wl: &WlSurface ) -> bool
{
self.lock_extras.iter().any( |( _, ss )| ss.surface.try_wl_surface() == Some( wl ) )
self.secondaries.iter().any( |( _, ss )| ss.surface.try_wl_surface() == Some( wl ) )
}
/// Borrow the [`SurfaceState`] identified by `focus`. Panics if `focus`