diff --git a/src/app.rs b/src/app.rs index 2ee422c..e006e82 100644 --- a/src/app.rs +++ b/src/app.rs @@ -794,15 +794,16 @@ pub trait App: 'static /// returning `true` from [`requested_exit`](Self::requested_exit). fn shell_mode( &self ) -> ShellMode { ShellMode::Window } - /// Content for the lock surfaces the runtime creates on every output - /// beyond the first while in [`ShellMode::SessionLock`]. The compositor - /// blanks any output without a lock surface, so the runtime covers each - /// one; this view is what gets painted there (typically the same - /// wallpaper as the main surface, without the interactive form). + /// Content for the surfaces the runtime creates on every output beyond + /// the first. In [`ShellMode::SessionLock`] these are lock surfaces — + /// the compositor blanks any output without one, so each is covered. In + /// [`ShellMode::Layer`] on [`Layer::Background`] they are background + /// layer surfaces, so a shell's wallpaper reaches extended outputs + /// (typically without the grid or any chrome). They take no input. /// `width` / `height` are that output's surface size in physical pixels. /// Default `None`: the surface is filled with /// [`background_color`](Self::background_color). - fn lock_secondary_view( &self, _width: u32, _height: u32 ) -> Option> + fn secondary_view( &self, _width: u32, _height: u32 ) -> Option> { None } diff --git a/src/event_loop/app_data.rs b/src/event_loop/app_data.rs index d07de2d..42b07ca 100644 --- a/src/event_loop/app_data.rs +++ b/src/event_loop/app_data.rs @@ -219,15 +219,17 @@ pub struct AppData /// Populated and reconciled by the run loop from [`App::overlays`]. Always /// empty until overlay diffing is wired up. pub overlays: HashMap>, - /// 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 )>, - /// 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, + /// 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 )>, + /// 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, /// Input-transparent child surfaces keyed by their stable /// [`SubsurfaceId`]. Reconciled each frame from [`App::subsurfaces`]. pub subsurfaces: HashMap, @@ -295,14 +297,14 @@ impl AppData 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` diff --git a/src/event_loop/frame.rs b/src/event_loop/frame.rs index 64ecdf9..653be6c 100644 --- a/src/event_loop/frame.rs +++ b/src/event_loop/frame.rs @@ -71,11 +71,11 @@ pub( crate ) fn draw_frame( data: &mut AppData ) -> bool } } - for ( _, ss ) in data.lock_extras.iter_mut() + for ( _, ss ) in data.secondaries.iter_mut() { if !ss.configured || !ss.needs_redraw || ss.frame_pending { continue; } let scale = ss.scale_factor.max( 1 ) as u32; - let view = data.app.lock_secondary_view( ss.width * scale, ss.height * scale ) + let view = data.app.secondary_view( ss.width * scale, ss.height * scale ) .unwrap_or_else( || crate::spacer().into() ); // No frame callback: these surfaces are static between invalidations, // so there is no per-frame pacing to keep — and a callback that never diff --git a/src/event_loop/handlers.rs b/src/event_loop/handlers.rs index 25cf14e..f618d08 100644 --- a/src/event_loop/handlers.rs +++ b/src/event_loop/handlers.rs @@ -92,9 +92,9 @@ impl CompositorHandler for AppData if new_factor <= 0 { return; } let Some( focus ) = self.focus_for_surface( surface ) else { - // Lock extras live outside the focus map. + // Secondary per-output surfaces live outside the focus map. let shm = &self.shm; - if let Some( ( _, ss ) ) = self.lock_extras.iter_mut() + if let Some( ( _, ss ) ) = self.secondaries.iter_mut() .find( |( _, ss )| ss.surface.try_wl_surface() == Some( surface ) ) { apply_surface_scale( ss, shm, surface, new_factor ); @@ -165,6 +165,11 @@ impl LayerShellHandler for AppData layer: &LayerSurface, ) { + if self.is_secondary_surface( layer.wl_surface() ) + { + self.secondaries.retain( |( _, ss )| ss.surface.try_wl_surface() != Some( layer.wl_surface() ) ); + return; + } match self.focus_for_surface( layer.wl_surface() ) { Some( super::SurfaceFocus::Main ) | None => @@ -200,6 +205,17 @@ impl LayerShellHandler for AppData { let ( w, h ) = configure.new_size; let ( w, h ) = ( w.max( 1 ), h.max( 1 ) ); + // Secondaries live outside the focus map; without this branch their + // configure would fall into the `None` arm and resize the main + // surface instead. + for ( _, ss ) in self.secondaries.iter_mut() + { + if ss.surface.try_wl_surface() == Some( layer.wl_surface() ) + { + ss.on_configure( &self.shm, self.egl_context.as_ref(), w, h ); + return; + } + } match self.focus_for_surface( layer.wl_surface() ) { Some( super::SurfaceFocus::Main ) | None => @@ -290,23 +306,56 @@ impl OutputHandler for AppData // Same for any overlays that were created before an output existed. if let Some( ref layer_shell ) = self.layer_shell { - self.main.surface.materialize( &self.compositor_state, layer_shell, qh, &output ); + if self.main.surface.materialize( &self.compositor_state, layer_shell, qh, &output ) + { + self.main_output = Some( output.clone() ); + } for ss in self.overlays.values_mut() { ss.surface.materialize( &self.compositor_state, layer_shell, qh, &output ); } + // A background shell covers every output: each one beyond the + // main's gets its own background layer surface, painted with + // `App::secondary_view` — the wallpaper without any chrome. + if self.app.shell_mode() == crate::app::ShellMode::Layer( crate::app::Layer::Background ) + && self.main_output.is_some() + && self.main_output.as_ref() != Some( &output ) + && !self.secondaries.iter().any( |( o, _ )| o == &output ) + { + use smithay_client_toolkit::shell::wlr_layer; + let surface = self.compositor_state.create_surface( qh ); + let layer_surface = layer_shell.create_layer_surface( + qh, + surface, + wlr_layer::Layer::Background, + Some( "ltk-secondary" ), + Some( &output ), + ); + layer_surface.set_anchor( + wlr_layer::Anchor::TOP | wlr_layer::Anchor::BOTTOM + | wlr_layer::Anchor::LEFT | wlr_layer::Anchor::RIGHT, + ); + layer_surface.set_exclusive_zone( 0 ); + layer_surface.set_keyboard_interactivity( wlr_layer::KeyboardInteractivity::None ); + layer_surface.set_size( 0, 0 ); + layer_surface.commit(); + self.secondaries.push( ( + output.clone(), + super::SurfaceState::new( super::SurfaceKind::Layer( layer_surface ), 0.0, String::new() ), + ) ); + } } // An output hotplugged while the session is locked would be blanked by // the compositor until it gets a lock surface of its own. if let Some( ref lock ) = self.session_lock { if matches!( self.main.surface, super::SurfaceKind::Lock( .. ) ) - && self.main_lock_output.as_ref() != Some( &output ) - && !self.lock_extras.iter().any( |( o, _ )| o == &output ) + && self.main_output.as_ref() != Some( &output ) + && !self.secondaries.iter().any( |( o, _ )| o == &output ) { let surface = self.compositor_state.create_surface( qh ); let lock_surface = lock.create_lock_surface( surface, &output, qh ); - self.lock_extras.push( ( + self.secondaries.push( ( output, super::SurfaceState::new( super::SurfaceKind::Lock( lock_surface ), 0.0, String::new() ), ) ); @@ -318,10 +367,10 @@ impl OutputHandler for AppData fn output_destroyed( &mut self, _: &Connection, _: &QueueHandle, output: WlOutput ) { - self.lock_extras.retain( |( o, _ )| o != &output ); - if self.main_lock_output.as_ref() == Some( &output ) + self.secondaries.retain( |( o, _ )| o != &output ); + if self.main_output.as_ref() == Some( &output ) { - self.main_lock_output = None; + self.main_output = None; } } } @@ -484,21 +533,21 @@ impl SessionLockHandler for AppData { // Every output needs its own lock surface — the compositor blanks any // output left without one. The first hosts the app's main surface; - // the rest render `App::lock_secondary_view`. + // the rest render `App::secondary_view`. let outputs: Vec = self.output_state.outputs().collect(); let mut outputs = outputs.into_iter(); if let Some( output ) = outputs.next() { let surface = self.compositor_state.create_surface( qh ); let lock_surface = session_lock.create_lock_surface( surface, &output, qh ); - self.main.surface = super::SurfaceKind::Lock( lock_surface ); - self.main_lock_output = Some( output ); + self.main.surface = super::SurfaceKind::Lock( lock_surface ); + self.main_output = Some( output ); } for output in outputs { let surface = self.compositor_state.create_surface( qh ); let lock_surface = session_lock.create_lock_surface( surface, &output, qh ); - self.lock_extras.push( ( + self.secondaries.push( ( output, super::SurfaceState::new( super::SurfaceKind::Lock( lock_surface ), 0.0, String::new() ), ) ); @@ -529,7 +578,7 @@ impl SessionLockHandler for AppData self.on_configure( w, h ); return; } - for ( _, ss ) in self.lock_extras.iter_mut() + for ( _, ss ) in self.secondaries.iter_mut() { if ss.surface.try_wl_surface() == Some( wl ) { diff --git a/src/event_loop/invalidation.rs b/src/event_loop/invalidation.rs index bfcced0..7320fef 100644 --- a/src/event_loop/invalidation.rs +++ b/src/event_loop/invalidation.rs @@ -27,7 +27,7 @@ pub( super ) fn apply_invalidation( data: &mut AppData, scope: Invali { ss.request_redraw(); } - for ( _, ss ) in data.lock_extras.iter_mut() + for ( _, ss ) in data.secondaries.iter_mut() { ss.request_redraw(); } @@ -44,7 +44,7 @@ pub( super ) fn apply_invalidation( data: &mut AppData, scope: Invali data.main.request_redraw(); // Lock extras mirror the main surface's backdrop, so // they follow its invalidations. - for ( _, ss ) in data.lock_extras.iter_mut() + for ( _, ss ) in data.secondaries.iter_mut() { ss.request_redraw(); } diff --git a/src/event_loop/run.rs b/src/event_loop/run.rs index 0917681..6121bc0 100644 --- a/src/event_loop/run.rs +++ b/src/event_loop/run.rs @@ -320,8 +320,8 @@ pub( crate ) fn try_run( app: A ) -> Result<(), RunError> pending_size_hint_unpin, main: SurfaceState::::new( surface_kind, titlebar_height, titlebar_title ), overlays: std::collections::HashMap::new(), - lock_extras: Vec::new(), - main_lock_output: None, + secondaries: Vec::new(), + main_output: None, subsurfaces: std::collections::HashMap::new(), subsurface_gles_canvas: None, pointer_focus: SurfaceFocus::Main, @@ -600,7 +600,7 @@ pub( crate ) fn try_run( app: A ) -> Result<(), RunError> // surface qualifies we just loop back to `dispatch(None)` and sleep. let any_drawable = ( data.main.configured && data.main.needs_redraw && !data.main.frame_pending ) || data.overlays.values().any( |ss| ss.configured && ss.needs_redraw && !ss.frame_pending ) - || data.lock_extras.iter().any( |( _, ss )| ss.configured && ss.needs_redraw && !ss.frame_pending ); + || data.secondaries.iter().any( |( _, ss )| ss.configured && ss.needs_redraw && !ss.frame_pending ); if any_drawable { // Rebuild while motion is in progress and on the first frame diff --git a/src/input/pointer/mod.rs b/src/input/pointer/mod.rs index 5a02c5a..cfaa850 100644 --- a/src/input/pointer/mod.rs +++ b/src/input/pointer/mod.rs @@ -51,7 +51,7 @@ impl PointerHandler for AppData { for event in events { - if self.is_lock_extra_surface( &event.surface ) { continue; } + if self.is_secondary_surface( &event.surface ) { continue; } let focus = self.focus_for_surface( &event.surface ) .unwrap_or( SurfaceFocus::Main ); self.pointer_focus = focus; diff --git a/src/input/touch/mod.rs b/src/input/touch/mod.rs index 31a6b12..1890082 100644 --- a/src/input/touch/mod.rs +++ b/src/input/touch/mod.rs @@ -60,7 +60,7 @@ impl TouchHandler for AppData ) { self.last_input_serial = serial; - if self.is_lock_extra_surface( &surface ) { return; } + if self.is_secondary_surface( &surface ) { return; } let focus = self.focus_for_surface( &surface ).unwrap_or( SurfaceFocus::Main ); self.touch_focus.insert( id, focus ); let pos = self.surface( focus ).to_physical( position.0, position.1 );