session lock: ext-session-lock-v1
Blank the screen and hold input when a lock client (swaylock) grabs ext_session_lock_manager_v1. While locked an opaque black rect covers the output with the lock client's per-output surface floating above it; no window may take keyboard focus and the compositor grab chords are swallowed so tabs can't be cycled behind the lock. kick manages a single output, so a single lock surface is tracked. If the lock client dies without unlocking the screen stays blanked (a deliberate security choice); only a fresh lock request may take over. On unlock the previously active tab is refocused. Includes a wltest --lock mode (binds the manager, locks, draws a buffer on the lock surface, and optionally --lock-unlock) and two smoke tests: advertised-globals and lock/surface-config acceptance.
This commit is contained in:
parent
4ea5df0e62
commit
143a1053c2
4 changed files with 437 additions and 2 deletions
233
kick.c
233
kick.c
|
|
@ -99,6 +99,7 @@
|
|||
#include <wlr/types/wlr_relative_pointer_v1.h>
|
||||
#include <wlr/types/wlr_scene.h>
|
||||
#include <wlr/types/wlr_seat.h>
|
||||
#include <wlr/types/wlr_session_lock_v1.h>
|
||||
#include <wlr/types/wlr_touch.h>
|
||||
#include <wlr/types/wlr_shm.h>
|
||||
#include <wlr/types/wlr_subcompositor.h>
|
||||
|
|
@ -393,6 +394,29 @@ struct kick_server {
|
|||
/* Layer surfaces, in reservation order (head = highest layer / lowest
|
||||
overlap priority); see kick_layer. */
|
||||
struct wl_list layer_surfaces;
|
||||
|
||||
/* ================= ext-session-lock-v1 =================
|
||||
When a lock client (swaylock) grabs the session, the screen is blanked
|
||||
and input is held. lock_surface_* mirror the lock client's surface for
|
||||
the single output; lock_surface_tree is a scene tree that floats above
|
||||
everything (layer_tree and popup_tree) while locked. Background blanking
|
||||
uses an opaque black rect beneath the lock surface. The scene grows no
|
||||
persistent overhead here: lock_surface_tree and lock_backdrop are only
|
||||
created while a lock request is in flight. */
|
||||
struct wlr_session_lock_manager_v1 *session_lock_manager;
|
||||
struct wl_listener session_lock_new_lock;
|
||||
struct wlr_session_lock_v1 *session_lock;
|
||||
bool session_locked;
|
||||
struct wlr_scene_tree *lock_surface_tree;
|
||||
struct wlr_scene_rect *lock_backdrop;
|
||||
struct wlr_scene_tree *lock_surface_ssd;
|
||||
struct wlr_session_lock_surface_v1 *lock_surface;
|
||||
struct wl_listener lock_surface_destroy;
|
||||
struct wl_listener lock_surface_map;
|
||||
struct wl_listener session_lock_new_surface;
|
||||
struct wl_listener session_lock_unlock;
|
||||
struct wl_listener session_lock_destroy;
|
||||
|
||||
/* The command launched via --panel (a layer-shell panel such as waybar);
|
||||
NULL when no panel was requested. */
|
||||
char *panel_cmd;
|
||||
|
|
@ -2619,7 +2643,10 @@ static void show_window(struct kick_window *w) {
|
|||
maps, window_enter runs again (its own tree is still disabled) to show
|
||||
and activate it. */
|
||||
static void window_enter(struct kick_window *w) {
|
||||
if (!w) return;
|
||||
/* While the session is locked nothing may take keyboard focus from the
|
||||
lock surface (which is focused directly, outside the tab model); the
|
||||
lock restore path clears session_locked before refocusing. */
|
||||
if (!w || server.session_locked) return;
|
||||
struct kick_tab *tab = w->tab;
|
||||
/* A genuine no-op: already focused AND already drawn. The black ->
|
||||
mapped promotion below is the one same-window case with something
|
||||
|
|
@ -2652,7 +2679,7 @@ static void window_enter(struct kick_window *w) {
|
|||
promote it black -> mapped (the one case with something still to do), so
|
||||
callers that want a true no-op must guard first. */
|
||||
static void set_active_tab(struct kick_tab *tab) {
|
||||
if (!tab) return;
|
||||
if (!tab || server.session_locked) return;
|
||||
struct kick_tab *old = server.active_tab;
|
||||
if (old && old != tab) {
|
||||
struct kick_window *w;
|
||||
|
|
@ -5862,6 +5889,192 @@ static void handle_ext_foreign_handle_destroy(struct wl_listener *listener, void
|
|||
w->ext_handle = NULL;
|
||||
}
|
||||
|
||||
/* ======================= ext-session-lock-v1 =======================
|
||||
The session is locked by an external lock client (e.g. swaylock) that
|
||||
grabbed ext_session_lock_manager_v1. While locked the screen is blanked
|
||||
with an opaque black rect and the lock client's per-output surface floats
|
||||
above it; every grab chord is swallowed (see process_key_event) and no
|
||||
window may take keyboard focus (window_enter / set_active_tab guard on
|
||||
server.session_locked). kick manages a single output, so a single lock
|
||||
surface is tracked. If the lock client dies without unlocking, the screen
|
||||
stays blanked (server.session_locked remains true) and only a fresh lock
|
||||
request may take over. */
|
||||
|
||||
/* Create the lock scene tree and blank the output with an opaque black rect
|
||||
rooted at the output origin. Idempotent. */
|
||||
static void session_lock_blank(void) {
|
||||
if (server.lock_surface_tree)
|
||||
return;
|
||||
server.lock_surface_tree = wlr_scene_tree_create(&server.scene->tree);
|
||||
if (server.output)
|
||||
server.lock_backdrop = wlr_scene_rect_create(server.lock_surface_tree,
|
||||
server.output->width, server.output->height,
|
||||
(float[4]){ 0, 0, 0, 1 });
|
||||
wlr_scene_node_raise_to_top(&server.lock_surface_tree->node);
|
||||
kick_request_redraw();
|
||||
}
|
||||
|
||||
/* Destroy the lock scene tree (removing the black rect and the lock surface
|
||||
subtree with it). Idempotent. */
|
||||
static void session_lock_unblank(void) {
|
||||
if (server.lock_surface_tree)
|
||||
wlr_scene_node_destroy(&server.lock_surface_tree->node);
|
||||
server.lock_surface_tree = NULL;
|
||||
server.lock_backdrop = NULL;
|
||||
server.lock_surface_ssd = NULL;
|
||||
kick_request_redraw();
|
||||
}
|
||||
|
||||
/* Put the lock surface's scene subtree in place, above the black backdrop,
|
||||
and configure it to the output's current dimensions. */
|
||||
static void session_lock_surface_place(struct wlr_session_lock_surface_v1 *ls) {
|
||||
if (!ls || !ls->surface)
|
||||
return;
|
||||
session_lock_blank();
|
||||
if (!server.lock_surface_ssd) {
|
||||
server.lock_surface_ssd = wlr_scene_subsurface_tree_create(
|
||||
server.lock_surface_tree, ls->surface);
|
||||
wlr_scene_node_raise_to_top(&server.lock_surface_ssd->node);
|
||||
}
|
||||
if (server.output)
|
||||
wlr_session_lock_surface_v1_configure(ls, server.output->width,
|
||||
server.output->height);
|
||||
}
|
||||
|
||||
/* The lock surface rendered and the whole output is blanked: give it the
|
||||
keyboard and pointer so the lock client (swaylock's prompt) receives input.
|
||||
Delegated to an idle callback because at map time wlroots has only processed
|
||||
the role commit and the surface/buffer isn't settable-focusable yet. */
|
||||
static void session_lock_focus_surface(void *data) {
|
||||
(void)data;
|
||||
struct wlr_session_lock_surface_v1 *ls = server.lock_surface;
|
||||
if (!ls || !ls->surface)
|
||||
return;
|
||||
struct wlr_keyboard *kbd = wlr_seat_get_keyboard(server.seat);
|
||||
if (!kbd) kbd = &server.keyboard;
|
||||
wlr_seat_keyboard_notify_enter(server.seat, ls->surface,
|
||||
kbd->keycodes, kbd->num_keycodes, &kbd->modifiers);
|
||||
wlr_seat_pointer_notify_enter(server.seat, ls->surface, 0, 0);
|
||||
wlr_seat_pointer_notify_motion(server.seat, 0, 0, 0);
|
||||
kick_request_redraw();
|
||||
}
|
||||
|
||||
static void handle_session_lock_surface_map(struct wl_listener *listener, void *data) {
|
||||
(void)listener; (void)data;
|
||||
wl_event_loop_add_idle(server.loop, session_lock_focus_surface, NULL);
|
||||
}
|
||||
|
||||
static void handle_session_lock_surface_destroy(struct wl_listener *listener, void *data) {
|
||||
(void)listener; (void)data;
|
||||
wlr_seat_keyboard_notify_clear_focus(server.seat);
|
||||
wlr_seat_pointer_notify_clear_focus(server.seat);
|
||||
wl_list_remove(&server.lock_surface_destroy.link);
|
||||
wl_list_remove(&server.lock_surface_map.link);
|
||||
server.lock_surface = NULL;
|
||||
server.lock_surface_ssd = NULL;
|
||||
kick_request_redraw();
|
||||
}
|
||||
|
||||
static void handle_session_lock_new_surface(struct wl_listener *listener, void *data) {
|
||||
(void)listener;
|
||||
struct wlr_session_lock_surface_v1 *ls = data;
|
||||
server.lock_surface = ls;
|
||||
server.lock_surface_destroy.notify = handle_session_lock_surface_destroy;
|
||||
wl_signal_add(&ls->events.destroy, &server.lock_surface_destroy);
|
||||
server.lock_surface_map.notify = handle_session_lock_surface_map;
|
||||
wl_signal_add(&ls->surface->events.map, &server.lock_surface_map);
|
||||
session_lock_surface_place(ls);
|
||||
}
|
||||
|
||||
/* Hand the lock to the next lock in line: replay the focus onto the tab model
|
||||
now that session_locked is cleared, and show the previously active window. */
|
||||
static void session_lock_restore_focus(void) {
|
||||
server.session_locked = false;
|
||||
struct kick_tab *tab = server.active_tab;
|
||||
if (tab) {
|
||||
struct kick_window *enter = tab_current_window(tab);
|
||||
if (enter)
|
||||
window_enter(enter);
|
||||
else
|
||||
show_window(NULL);
|
||||
}
|
||||
kick_request_redraw();
|
||||
}
|
||||
|
||||
static void handle_session_lock_unlock(struct wl_listener *listener, void *data) {
|
||||
(void)listener; (void)data;
|
||||
struct wlr_session_lock_v1 *lock = server.session_lock;
|
||||
if (!lock) return;
|
||||
if (server.lock_surface) {
|
||||
wl_list_remove(&server.lock_surface_destroy.link);
|
||||
wl_list_remove(&server.lock_surface_map.link);
|
||||
server.lock_surface = NULL;
|
||||
server.lock_surface_ssd = NULL;
|
||||
}
|
||||
session_lock_unblank();
|
||||
session_lock_restore_focus();
|
||||
}
|
||||
|
||||
static void handle_session_lock_destroy(struct wl_listener *listener, void *data) {
|
||||
(void)listener; (void)data;
|
||||
/* The lock client died without unlocking: the screen stays locked and
|
||||
blanked (session_locked remains true) as a deliberate security choice.
|
||||
Only a fresh lock request may take over. */
|
||||
if (server.session_lock) {
|
||||
wl_list_remove(&server.session_lock_unlock.link);
|
||||
wl_list_remove(&server.session_lock_new_surface.link);
|
||||
wl_list_remove(&server.session_lock_destroy.link);
|
||||
}
|
||||
server.session_lock = NULL;
|
||||
if (server.lock_surface) {
|
||||
wl_list_remove(&server.lock_surface_destroy.link);
|
||||
wl_list_remove(&server.lock_surface_map.link);
|
||||
server.lock_surface = NULL;
|
||||
server.lock_surface_ssd = NULL;
|
||||
}
|
||||
kick_request_redraw();
|
||||
}
|
||||
|
||||
static void handle_session_lock_new_lock(struct wl_listener *listener, void *data) {
|
||||
(void)listener;
|
||||
struct wlr_session_lock_v1 *lock = data;
|
||||
/* A second lock while already locked is refused outright. If an earlier
|
||||
lock client died (session_locked true, session_lock NULL), replace the
|
||||
blanked state: tear down the old lock surfaces and proceed. */
|
||||
if (server.session_locked && server.session_lock) {
|
||||
wlr_session_lock_v1_destroy(lock);
|
||||
return;
|
||||
}
|
||||
if (server.session_locked) {
|
||||
session_lock_unblank();
|
||||
if (server.lock_surface) {
|
||||
wl_list_remove(&server.lock_surface_destroy.link);
|
||||
wl_list_remove(&server.lock_surface_map.link);
|
||||
server.lock_surface = NULL;
|
||||
server.lock_surface_ssd = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
server.session_lock = lock;
|
||||
server.session_locked = true;
|
||||
server.session_lock_new_surface.notify = handle_session_lock_new_surface;
|
||||
wl_signal_add(&lock->events.new_surface, &server.session_lock_new_surface);
|
||||
server.session_lock_unlock.notify = handle_session_lock_unlock;
|
||||
wl_signal_add(&lock->events.unlock, &server.session_lock_unlock);
|
||||
server.session_lock_destroy.notify = handle_session_lock_destroy;
|
||||
wl_signal_add(&lock->events.destroy, &server.session_lock_destroy);
|
||||
|
||||
/* Draw nothing but the black blank while locked; no window may grab
|
||||
focus (window_enter / set_active_tab are gated on session_locked). */
|
||||
show_window(NULL);
|
||||
struct wlr_keyboard *kbd = wlr_seat_get_keyboard(server.seat);
|
||||
if (kbd) wlr_seat_keyboard_notify_clear_focus(server.seat);
|
||||
wlr_seat_pointer_notify_clear_focus(server.seat);
|
||||
session_lock_blank();
|
||||
wlr_session_lock_v1_send_locked(lock);
|
||||
}
|
||||
|
||||
|
||||
/* Create the taskbar-visible handles for a freshly-claimed window and seed
|
||||
their initial state. Called once a window has the client's app_id/title
|
||||
(i.e. at claim time). */
|
||||
|
|
@ -7056,6 +7269,13 @@ static bool process_key_event(uint32_t time, uint32_t key, uint32_t state,
|
|||
}
|
||||
return true;
|
||||
}
|
||||
/* Session locked: swallow every compositor grab chord so the user can't
|
||||
cycle tabs, close windows or open terminals behind the lock screen
|
||||
(passive keys still flow to the focused lock surface). The quit chord
|
||||
and confirm modal above run first and remain available, as does the VT
|
||||
switch below (a matching compositor session action). */
|
||||
if (server.session_locked)
|
||||
return true;
|
||||
/* Ctrl+Alt+F1..F12 switch virtual terminals in standalone mode. wlroots
|
||||
has no built-in handler; the compositor must notice the key and ask the
|
||||
session (libseat/logind) to activate another VT. Both the press and
|
||||
|
|
@ -9590,6 +9810,15 @@ int main(int argc, char *argv[]) {
|
|||
wl_signal_add(&server.idle_inhibit_mgr->events.new_inhibitor,
|
||||
&server.idle_new_inhibitor);
|
||||
|
||||
/* Session lock (ext-session-lock-v1): let a lock client (swaylock) grab
|
||||
the session and blank the screen. While locked, no window may take
|
||||
focus and the grab chords are swallowed (see process_key_event). */
|
||||
server.session_lock_manager =
|
||||
wlr_session_lock_manager_v1_create(server.wl_display);
|
||||
server.session_lock_new_lock.notify = handle_session_lock_new_lock;
|
||||
wl_signal_add(&server.session_lock_manager->events.new_lock,
|
||||
&server.session_lock_new_lock);
|
||||
|
||||
/* The output layout exists in both modes so xdg-output can advertise
|
||||
logical output geometry to clients; nested mode simply has one output
|
||||
(the host window) in the layout. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue