Commit e9ef7cdfe55a
tarball8 files changed+329-32
@@ -589,6 +589,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
589589
cursor,590590
cursor_status,591591
pointer_pos,592
+ lock,592593
..593594
} = state594595
else {@@ -637,17 +638,20 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
637638
elements.extend(cursor.elements(renderer, cursor_status, local));638639
}639640
let scene_began = std::time::Instant::now();640
- elements.extend(crate::scene::build(641
- layout,642
- surface.id,643
- windows,644
- anim,645
- renderer,646
- now,647
- &config.decoration,648
- borders,649
- Some(&surface.output),650
- ));641
+ match crate::lock::elements(lock, renderer, &surface.output) {642
+ Some(locked) => elements.extend(locked),643
+ None => elements.extend(crate::scene::build(644
+ layout,645
+ surface.id,646
+ windows,647
+ anim,648
+ renderer,649
+ now,650
+ &config.decoration,651
+ borders,652
+ Some(&surface.output),653
+ )),654
+ }651655
let scene_ns = scene_began.elapsed().as_nanos() as u64;652656
let animating = anim.active(now);653657
anim.prune(now);@@ -661,12 +665,15 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
661665
Ok(result) if !result.is_empty => {662666
let queue_began = std::time::Instant::now();663667
match surface.drm.queue_frame(()) {664
- Ok(()) => surface.queued = true,668
+ Ok(()) => {669
+ surface.queued = true;670
+ lock.shown(&surface.output);671
+ }665672
Err(e) => tracing::warn!("queueing a frame failed: {e}"),666673
}667674
queue_ns = queue_began.elapsed().as_nanos() as u64;668675
}669
- Ok(_) => {}676
+ Ok(_) => lock.shown(&surface.output),670677
Err(e) => tracing::warn!("rendering failed: {e}"),671678
}672679
let render_cpu_ns = thread_cpu_ns() - cpu_began;@@ -690,6 +697,10 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
690697
surface.redraw = animating;691698
let elapsed = start.elapsed();692699
let output = surface.output.clone();700
+ if lock.active() {701
+ crate::lock::send_frames(lock, &output, elapsed);702
+ return animating && !surface.queued;703
+ }693704
for window in space.elements_for_output(&output) {694705
window.send_frame(&output, elapsed, Some(Duration::ZERO), |_, _| {695706
Some(output.clone())@@ -0,0 +1,54 @@
1
+//! ext-idle-notify and idle-inhibit, so lockers and screen blankers know2
+//! when the user went away and video players can keep the screen on.3
+4
+use crate::state::State;5
+use smithay::reexports::wayland_server::Resource;6
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;7
+use smithay::wayland::compositor::get_parent;8
+use smithay::wayland::idle_inhibit::IdleInhibitHandler;9
+use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState};10
+11
+impl IdleNotifierHandler for State {12
+ fn idle_notifier_state(&mut self) -> &mut IdleNotifierState<Self> {13
+ &mut self.idle_notifier14
+ }15
+}16
+17
+impl IdleInhibitHandler for State {18
+ fn inhibit(&mut self, surface: WlSurface) {19
+ self.inhibitors.insert(surface);20
+ self.update_inhibit();21
+ }22
+23
+ fn uninhibit(&mut self, surface: WlSurface) {24
+ self.inhibitors.remove(&surface);25
+ self.update_inhibit();26
+ }27
+}28
+29
+impl State {30
+ pub fn activity(&mut self) {31
+ let seat = self.seat.clone();32
+ self.idle_notifier.notify_activity(&seat);33
+ }34
+35
+ /// An inhibitor only counts while its surface is on screen, as the36
+ /// protocol asks, so a video on a hidden tag lets the screen blank.37
+ pub fn update_inhibit(&mut self) {38
+ self.inhibitors.retain(|s| s.is_alive());39
+ let shown = self.inhibitors.iter().any(|s| {40
+ let mut root = s.clone();41
+ while let Some(parent) = get_parent(&root) {42
+ root = parent;43
+ }44
+ self.space45
+ .elements()46
+ .any(|w| w.toplevel().is_some_and(|t| t.wl_surface() == &root))47
+ || self.layer_maps_contain(&root)48
+ });49
+ if shown != self.inhibited {50
+ self.inhibited = shown;51
+ self.idle_notifier.set_is_inhibited(shown);52
+ }53
+ }54
+}@@ -14,6 +14,7 @@ use smithay::wayland::shell::wlr_layer::Layer;
1414
impl State {1515
pub fn pointer_to(&mut self, pos: Point<f64, Logical>, time: InputTime) {1616
self.pointer_pos = pos;17
+ self.activity();1718
if drag_to(self, pos) {1819
return;1920
}@@ -33,10 +34,11 @@ impl State {
3334
}3435
3536
pub fn pointer_button(&mut self, button: u32, pressed: ButtonState, time: InputTime) {37
+ self.activity();3638
if pressed == ButtonState::Released && self.drag.take().is_some() {3739
return;3840
}39
- if pressed == ButtonState::Pressed {41
+ if pressed == ButtonState::Pressed && !self.lock.active() {4042
focus_under_pointer(self);4143
if start_drag(self, button) {4244
return;@@ -57,6 +59,7 @@ impl State {
5759
}5860
5961
pub fn pointer_axis(&mut self, frame: AxisFrame) {62
+ self.activity();6063
if let Some(pointer) = self.seat.get_pointer() {6164
pointer.axis(self, frame);6265
}@@ -69,6 +72,7 @@ impl State {
6972
}7073
7174
pub fn key(&mut self, code: Keycode, pressed: KeyState, time: InputTime) {75
+ self.activity();7276
let Some(keyboard) = self.seat.get_keyboard() else {7377
return;7478
};@@ -96,6 +100,9 @@ impl State {
96100
let vt = (sym - xkb::keysyms::KEY_XF86Switch_VT_1 + 1) as i32;97101
return FilterResult::Intercept(Some(Action::Vt(vt)));98102
}103
+ if state.lock.active() {104
+ return FilterResult::Forward;105
+ }99106
match state.binds.find(mods, &handle.raw_syms()) {100107
Some(cmd) => {101108
tracing::debug!(?cmd, "bind");@@ -154,6 +154,14 @@ impl State {
154154
self.layer_focus.clone().filter(|s| s.is_alive())155155
}156156
157
+ pub fn layer_maps_contain(&self, surface: &WlSurface) -> bool {158
+ self.space.outputs().any(|o| {159
+ layer_map_for_output(o)160
+ .layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)161
+ .is_some()162
+ })163
+ }164
+157165
/// The layer surface under `pos` on `layers`, with its surface origin.158166
pub fn layer_under(159167
&self,@@ -0,0 +1,185 @@
1
+//! ext-session-lock. While locked only the locker's surfaces are drawn and2
+//! receive input, and no binds run.3
+4
+use crate::scene::Draw;5
+use crate::state::State;6
+use monolith_render_vk::VulkanRenderer;7
+use smithay::backend::renderer::element::Kind;8
+use smithay::backend::renderer::element::surface::render_elements_from_surface_tree;9
+use smithay::desktop::WindowSurfaceType;10
+use smithay::desktop::utils::{send_frames_surface_tree, under_from_surface_tree};11
+use smithay::output::Output;12
+use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;13
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;14
+use smithay::utils::{Logical, Point};15
+use smithay::wayland::session_lock::{16
+ LockSurface, SessionLockHandler, SessionLockManagerState, SessionLocker,17
+};18
+use std::collections::HashSet;19
+use std::time::Duration;20
+21
+#[derive(Default)]22
+pub struct Lock {23
+ pending: Option<SessionLocker>,24
+ locked: bool,25
+ /// Outputs that have shown a locked frame since the lock was requested.26
+ blanked: HashSet<String>,27
+ surfaces: Vec<(Output, LockSurface)>,28
+}29
+30
+impl Lock {31
+ pub fn active(&self) -> bool {32
+ self.locked || self.pending.is_some()33
+ }34
+35
+ fn surface_on(&self, output: &Output) -> Option<&LockSurface> {36
+ self.surfaces37
+ .iter()38
+ .find(|(o, s)| o == output && s.alive())39
+ .map(|(_, s)| s)40
+ }41
+42
+ /// Records a frame shown on `output`, which counts towards confirming a43
+ /// pending lock.44
+ pub fn shown(&mut self, output: &Output) {45
+ if self.pending.is_some() {46
+ self.blanked.insert(output.name());47
+ }48
+ }49
+}50
+51
+impl SessionLockHandler for State {52
+ fn lock_state(&mut self) -> &mut SessionLockManagerState {53
+ &mut self.session_lock_state54
+ }55
+56
+ fn lock(&mut self, confirmation: SessionLocker) {57
+ tracing::info!("session lock requested");58
+ self.lock.pending = Some(confirmation);59
+ self.lock.blanked.clear();60
+ self.drag = None;61
+ self.sync_focus();62
+ self.needs_redraw = true;63
+ }64
+65
+ fn unlock(&mut self) {66
+ tracing::info!("session unlocked");67
+ self.lock = Lock::default();68
+ self.sync_focus();69
+ self.needs_redraw = true;70
+ }71
+72
+ fn new_surface(&mut self, surface: LockSurface, output: WlOutput) {73
+ let Some(output) = Output::from_resource(&output) else {74
+ return;75
+ };76
+ configure(&surface, &output);77
+ self.lock78
+ .surfaces79
+ .retain(|(o, s)| o != &output && s.alive());80
+ self.lock.surfaces.push((output, surface));81
+ self.sync_focus();82
+ self.needs_redraw = true;83
+ }84
+}85
+86
+fn configure(surface: &LockSurface, output: &Output) {87
+ let Some(mode) = output.current_mode() else {88
+ return;89
+ };90
+ let size = (mode.size.w as u32, mode.size.h as u32);91
+ surface.with_pending_state(|s| s.size = Some(size.into()));92
+ surface.send_configure();93
+}94
+95
+impl State {96
+ /// Confirms a pending lock once every output has drawn without the97
+ /// windows, so nothing private is left on screen when the locker is told98
+ /// it holds the session.99
+ pub fn confirm_lock(&mut self) {100
+ if self.lock.pending.is_none() {101
+ return;102
+ }103
+ let all = self104
+ .space105
+ .outputs()106
+ .all(|o| self.lock.blanked.contains(&o.name()));107
+ if all && let Some(locker) = self.lock.pending.take() {108
+ locker.lock();109
+ self.lock.locked = true;110
+ tracing::info!("session locked");111
+ }112
+ }113
+114
+ /// Resends sizes after an output changed mode.115
+ pub fn reconfigure_lock(&self) {116
+ for (output, surface) in &self.lock.surfaces {117
+ configure(surface, output);118
+ }119
+ }120
+121
+ pub fn lock_keyboard_focus(&self) -> Option<WlSurface> {122
+ let under = self.space.output_under(self.pointer_pos).next();123
+ under124
+ .and_then(|o| self.lock.surface_on(o))125
+ .or_else(|| {126
+ self.lock127
+ .surfaces128
+ .iter()129
+ .map(|(_, s)| s)130
+ .find(|s| s.alive())131
+ })132
+ .map(|s| s.wl_surface().clone())133
+ }134
+135
+ pub fn lock_surface_under(136
+ &self,137
+ pos: Point<f64, Logical>,138
+ ) -> Option<(WlSurface, Point<f64, Logical>)> {139
+ let output = self.space.output_under(pos).next()?;140
+ let origin = self.space.output_geometry(output)?.loc;141
+ let surface = self.lock.surface_on(output)?;142
+ under_from_surface_tree(143
+ surface.wl_surface(),144
+ pos - origin.to_f64(),145
+ (0, 0),146
+ WindowSurfaceType::ALL,147
+ )148
+ .map(|(s, p)| (s, (p + origin).to_f64()))149
+ }150
+}151
+152
+/// What a locked output draws, or None when the session is not locked.153
+pub fn elements(lock: &Lock, renderer: &mut VulkanRenderer, output: &Output) -> Option<Vec<Draw>> {154
+ if !lock.active() {155
+ return None;156
+ }157
+ let Some(surface) = lock.surface_on(output) else {158
+ return Some(Vec::new());159
+ };160
+ Some(161
+ render_elements_from_surface_tree(162
+ renderer,163
+ surface.wl_surface(),164
+ (0, 0),165
+ 1.0,166
+ 1.0,167
+ Kind::Unspecified,168
+ )169
+ .into_iter()170
+ .map(Draw::Surface)171
+ .collect(),172
+ )173
+}174
+175
+pub fn send_frames(lock: &Lock, output: &Output, elapsed: Duration) {176
+ if let Some(surface) = lock.surface_on(output) {177
+ send_frames_surface_tree(178
+ surface.wl_surface(),179
+ output,180
+ elapsed,181
+ Some(Duration::ZERO),182
+ |_, _| Some(output.clone()),183
+ );184
+ }185
+}@@ -3,10 +3,12 @@ mod commands;
33
mod cursor;44
mod drm;55
mod handlers;6
+mod idle;67
mod input;78
mod ipc;89
mod launch;910
mod layers;11
+mod lock;1012
mod nested;1113
mod scene;1214
mod state;@@ -179,6 +179,7 @@ pub fn render(state: &mut State) {
179179
borders,180180
config,181181
output_id,182
+ lock,182183
..183184
} = state;184185
let Some(host) = host.as_mut() else { return };@@ -223,17 +224,19 @@ pub fn render(state: &mut State) {
223224
}224225
};225226
let Some(o) = *output_id else { return };226
- let elements = crate::scene::build(227
- layout,228
- o,229
- windows,230
- anim,231
- renderer,232
- now,233
- &config.decoration,234
- borders,235
- Some(&host.output),236
- );227
+ let elements = crate::lock::elements(lock, renderer, &host.output).unwrap_or_else(|| {228
+ crate::scene::build(229
+ layout,230
+ o,231
+ windows,232
+ anim,233
+ renderer,234
+ now,235
+ &config.decoration,236
+ borders,237
+ Some(&host.output),238
+ )239
+ });237240
match host238241
.damage239242
.render_output(renderer, &mut fb, slot.age() as usize, &elements, CLEAR)@@ -252,8 +255,10 @@ pub fn render(state: &mut State) {
252255
};253256
*needs_redraw = anim.active(now);254257
anim.prune(now);258
+ lock.shown(&host.output);255259
let elapsed = start.elapsed();256
- for window in space.elements() {260
+ crate::lock::send_frames(lock, &host.output, elapsed);261
+ for window in space.elements().filter(|_| !lock.active()) {257262
window.send_frame(&host.output, elapsed, Some(Duration::ZERO), |_, _| {258263
Some(host.output.clone())259264
});@@ -24,8 +24,11 @@ use smithay::reexports::wayland_server::{Display, DisplayHandle, Resource};
2424
use smithay::utils::{Clock, Logical, Monotonic, Point, SERIAL_COUNTER, Size};2525
use smithay::wayland::compositor::{CompositorClientState, CompositorState, with_states};2626
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufState};27
+use smithay::wayland::idle_inhibit::IdleInhibitManagerState;28
+use smithay::wayland::idle_notify::IdleNotifierState;2729
use smithay::wayland::output::OutputManagerState;2830
use smithay::wayland::selection::data_device::DataDeviceState;31
+use smithay::wayland::session_lock::SessionLockManagerState;2932
use smithay::wayland::shell::wlr_layer::{Layer, WlrLayerShellState};3033
use smithay::wayland::shell::xdg::decoration::XdgDecorationState;3134
use smithay::wayland::shell::xdg::{XdgShellState, XdgToplevelSurfaceData};@@ -79,11 +82,17 @@ pub struct State {
7982
pub awaiting: HashSet<WindowId>,8083
/// A layer surface given the keyboard by a click, like a launcher.8184
pub layer_focus: Option<WlSurface>,85
+ pub lock: crate::lock::Lock,86
+ pub inhibitors: HashSet<WlSurface>,87
+ pub inhibited: bool,8288
pub handle: LoopHandle<'static, State>,8389
8490
pub compositor_state: CompositorState,8591
pub xdg_shell_state: XdgShellState,8692
pub layer_shell_state: WlrLayerShellState,93
+ pub session_lock_state: SessionLockManagerState,94
+ pub idle_notifier: IdleNotifierState<State>,95
+ pub _idle_inhibit: IdleInhibitManagerState,8796
pub _xdg_decoration_state: XdgDecorationState,8897
pub shm_state: ShmState,8998
pub dmabuf_state: DmabufState,@@ -176,10 +185,16 @@ impl State {
176185
last_kind: HashMap::new(),177186
awaiting: HashSet::new(),178187
layer_focus: None,188
+ lock: Default::default(),189
+ inhibitors: HashSet::new(),190
+ inhibited: false,179191
handle: event_loop.handle(),180192
compositor_state: CompositorState::new::<State>(&dh),181193
xdg_shell_state: XdgShellState::new::<State>(&dh),182194
layer_shell_state: WlrLayerShellState::new::<State>(&dh),195
+ session_lock_state: SessionLockManagerState::new::<State, _>(&dh, |_| true),196
+ idle_notifier: IdleNotifierState::new(&dh, event_loop.handle()),197
+ _idle_inhibit: IdleInhibitManagerState::new::<State>(&dh),183198
_xdg_decoration_state: XdgDecorationState::new::<State>(&dh),184199
shm_state: ShmState::new::<State>(&dh, vec![]),185200
dmabuf_state,@@ -198,6 +213,7 @@ impl State {
198213
self.popups.cleanup();199214
crate::nested::render(self);200215
crate::drm::render(self);216
+ self.confirm_lock();201217
crate::ipc::broadcast(self);202218
self.publish_workspaces();203219
let _ = self.display.flush_clients();@@ -216,6 +232,7 @@ impl State {
216232
.change_current_state(Some(mode), None, None, None);217233
}218234
self.reserve_layers();235
+ self.reconfigure_lock();219236
self.apply_layout();220237
}221238
}@@ -467,6 +484,7 @@ impl State {
467484
self.space.raise_element(&self.windows[id], false);468485
}469486
self.sync_focus();487
+ self.update_inhibit();470488
self.needs_redraw = true;471489
}472490
@@ -480,12 +498,16 @@ impl State {
480498
t.send_pending_configure();481499
}482500
}483
- let target = self.layer_keyboard_focus().or_else(|| {484
- focused485
- .and_then(|id| self.windows.get(&id))486
- .and_then(|w| w.toplevel())487
- .map(|t| t.wl_surface().clone())488
- });501
+ let target = if self.lock.active() {502
+ self.lock_keyboard_focus()503
+ } else {504
+ self.layer_keyboard_focus().or_else(|| {505
+ focused506
+ .and_then(|id| self.windows.get(&id))507
+ .and_then(|w| w.toplevel())508
+ .map(|t| t.wl_surface().clone())509
+ })510
+ };489511
if let Some(keyboard) = self.seat.get_keyboard()490512
&& keyboard.current_focus() != target491513
{@@ -543,6 +565,9 @@ impl State {
543565
&self,544566
pos: Point<f64, Logical>,545567
) -> Option<(WlSurface, Point<f64, Logical>)> {568
+ if self.lock.active() {569
+ return self.lock_surface_under(pos);570
+ }546571
let fullscreen = self.layout.outputs().any(|(o, out)| {547572
let a = out.area;548573
(a.x..a.right()).contains(&pos.x)