Cover every output from a background shell: the session-lock secondary machinery generalizes to layer surfaces
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:
13
src/app.rs
13
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<Element<Self::Message>>
|
||||
fn secondary_view( &self, _width: u32, _height: u32 ) -> Option<Element<Self::Message>>
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -71,11 +71,11 @@ pub( crate ) fn draw_frame<A: App>( data: &mut AppData<A> ) -> 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
|
||||
|
||||
@@ -92,9 +92,9 @@ impl<A: App> CompositorHandler for AppData<A>
|
||||
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<A: App> LayerShellHandler for AppData<A>
|
||||
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<A: App> LayerShellHandler for AppData<A>
|
||||
{
|
||||
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<A: App> OutputHandler for AppData<A>
|
||||
// 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<A: App> OutputHandler for AppData<A>
|
||||
|
||||
fn output_destroyed( &mut self, _: &Connection, _: &QueueHandle<Self>, 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<A: App> SessionLockHandler for AppData<A>
|
||||
{
|
||||
// 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<WlOutput> = 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<A: App> SessionLockHandler for AppData<A>
|
||||
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 )
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ pub( super ) fn apply_invalidation<A: App>( data: &mut AppData<A>, 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<A: App>( data: &mut AppData<A>, 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();
|
||||
}
|
||||
|
||||
@@ -320,8 +320,8 @@ pub( crate ) fn try_run<A: App>( app: A ) -> Result<(), RunError>
|
||||
pending_size_hint_unpin,
|
||||
main: SurfaceState::<A::Message>::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<A: App>( 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
|
||||
|
||||
@@ -51,7 +51,7 @@ impl<A: App> PointerHandler for AppData<A>
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -60,7 +60,7 @@ impl<A: App> TouchHandler for AppData<A>
|
||||
)
|
||||
{
|
||||
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 );
|
||||
|
||||
Reference in New Issue
Block a user