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. */
|
||||
|
|
|
|||
|
|
@ -134,6 +134,13 @@ protocols = {
|
|||
wl_protocols_dir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml',
|
||||
'idle-inhibit-unstable-v1',
|
||||
],
|
||||
# Session lock: the lock client (swaylock) grabs the session and private
|
||||
# protocol surfaces cover each output; the compositor blanking the screen
|
||||
# while locked.
|
||||
'ext-session-lock': [
|
||||
wl_protocols_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml',
|
||||
'ext-session-lock-v1',
|
||||
],
|
||||
'cursor-shape': [
|
||||
wl_protocols_dir / 'staging/cursor-shape/cursor-shape-v1.xml',
|
||||
'cursor-shape-v1',
|
||||
|
|
@ -235,6 +242,7 @@ executable('wltest',
|
|||
code_by_name['ext-foreign-toplevel-list'],
|
||||
code_by_name['ext-idle-notify'],
|
||||
code_by_name['idle-inhibit-unstable-v1'],
|
||||
code_by_name['ext-session-lock'],
|
||||
client_headers['xdg-shell'],
|
||||
client_headers['cursor-shape'],
|
||||
client_headers['wlr-layer-shell-unstable-v1'],
|
||||
|
|
@ -242,6 +250,7 @@ executable('wltest',
|
|||
client_headers['ext-foreign-toplevel-list'],
|
||||
client_headers['ext-idle-notify'],
|
||||
client_headers['idle-inhibit-unstable-v1'],
|
||||
client_headers['ext-session-lock'],
|
||||
dependencies: dependency('wayland-client'))
|
||||
|
||||
# xleader: X11 twin-window probe used by the leader-window regression test --
|
||||
|
|
|
|||
|
|
@ -3259,3 +3259,56 @@ def test_standalone_headless_idle_inhibit(tmp_path):
|
|||
"ext-idle-notify fired despite idle-inhibit being held"
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
# --- Session-lock smoke tests ------------------------------------------------
|
||||
|
||||
def test_standalone_headless_session_lock_globals(tmp_path):
|
||||
"""The compositor advertises the ext_session_lock_manager_v1 global so a
|
||||
lock client (swaylock) can grab the session."""
|
||||
out = tmp_path / "globals.txt"
|
||||
s = _spawn_standalone({}, "%s --globals-out '%s' '#112233'" % (WL, out))
|
||||
try:
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
if out.exists():
|
||||
break
|
||||
time.sleep(0.1)
|
||||
assert out.exists(), "wltest --globals-out never wrote its file"
|
||||
names = {line.split()[0] for line in out.read_text().splitlines()}
|
||||
assert "ext_session_lock_manager_v1" in names, \
|
||||
f"missing ext_session_lock_manager_v1; advertised: {sorted(names)}"
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def test_standalone_headless_session_lock(tmp_path):
|
||||
"""A lock client that grabs ext-session-lock-v1 is accepted: the compositor
|
||||
replies with the locked event, then configures a private lock surface for
|
||||
the output (confirmed by the client's ack_configure + buffer commit)."""
|
||||
out = tmp_path / "lock.out"
|
||||
s = _spawn_standalone(
|
||||
{},
|
||||
"%s --app-id testwin '#ff0000' 320x240" % WL,
|
||||
"%s --lock --lock-out '%s'" % (WL, out))
|
||||
try:
|
||||
s.wait_any_active()
|
||||
deadline = time.monotonic() + 10.0
|
||||
got_surface = False
|
||||
while time.monotonic() < deadline:
|
||||
if out.exists():
|
||||
text = out.read_text().strip()
|
||||
if "surface-config" in text:
|
||||
got_surface = True
|
||||
break
|
||||
time.sleep(0.1)
|
||||
assert got_surface, \
|
||||
f"lock client never saw its lock surface configure: " \
|
||||
f"{(out.read_text().strip() if out.exists() else '(missing)')!r}"
|
||||
# The lock client must have received the locked event too (the
|
||||
# compositor only sends it after setting up the blanked session).
|
||||
assert s.proc.poll() is None, \
|
||||
f"compositor exited under lock; log tail:\n{s.read_log()[-400:]}"
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
|
|
|||
144
tests/wltest.c
144
tests/wltest.c
|
|
@ -22,6 +22,7 @@
|
|||
#include "ext-foreign-toplevel-list-v1-client.h"
|
||||
#include "ext-idle-notify-v1-client.h"
|
||||
#include "idle-inhibit-unstable-v1-client.h"
|
||||
#include "ext-session-lock-v1-client.h"
|
||||
|
||||
/* Minimal deterministic xdg toplevel test client: shows a solid-color shm
|
||||
buffer and runs until killed. Used by test.sh to give the smoke test two
|
||||
|
|
@ -114,6 +115,8 @@ static struct zwlr_foreign_toplevel_manager_v1 *ft_mgr;
|
|||
static struct ext_foreign_toplevel_list_v1 *ext_list;
|
||||
static struct ext_idle_notifier_v1 *idle_mgr;
|
||||
static struct zwp_idle_inhibit_manager_v1 *inhib_mgr;
|
||||
static struct ext_session_lock_manager_v1 *lock_mgr;
|
||||
static struct wl_output *wl_output;
|
||||
|
||||
struct foreign_state {
|
||||
char app_id[64];
|
||||
|
|
@ -709,6 +712,15 @@ static void registry_global(void *data, struct wl_registry *reg, uint32_t name,
|
|||
inhib_mgr = wl_registry_bind(reg, name,
|
||||
&zwp_idle_inhibit_manager_v1_interface,
|
||||
version < 1 ? version : 1);
|
||||
} else if (strcmp(interface,
|
||||
ext_session_lock_manager_v1_interface.name) == 0) {
|
||||
lock_mgr = wl_registry_bind(reg, name,
|
||||
&ext_session_lock_manager_v1_interface,
|
||||
version < 1 ? version : 1);
|
||||
} else if (strcmp(interface, wl_output_interface.name) == 0) {
|
||||
if (!wl_output)
|
||||
wl_output = wl_registry_bind(reg, name, &wl_output_interface,
|
||||
version < 2 ? version : 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1068,6 +1080,119 @@ static void idle_run(void) {
|
|||
}
|
||||
}
|
||||
|
||||
/* --- Session lock (ext-session-lock-v1) ---
|
||||
--lock [--lock-out <file>]: act as the swaylock side of the compositor.
|
||||
Binds ext_session_lock_manager_v1, calls lock, waits for the compositor's
|
||||
locked event, then creates a lock surface on the single wl_output and draws
|
||||
a solid buffer on it (proving the compositor blanked the session and
|
||||
accepted the surface). Writes "locked" then "surface-config" to --lock-out
|
||||
as each stage completes; the strongest observable is the locked event the
|
||||
compositor only sends once it has set up the blanked session. */
|
||||
static bool lock_mode;
|
||||
static const char *lock_out;
|
||||
static bool lock_unlock_after;
|
||||
static struct ext_session_lock_v1 *lock;
|
||||
static struct ext_session_lock_surface_v1 *lock_surface;
|
||||
static struct wl_surface *lock_wl_surface;
|
||||
static bool lock_conf_acked;
|
||||
static bool lock_received_locked;
|
||||
|
||||
static void lock_note(const char *s) {
|
||||
if (lock_out) write_outfile(lock_out, s, strlen(s));
|
||||
}
|
||||
|
||||
/* Forward declaration: lock_locked (called on the locked event) sets up the
|
||||
lock surface and subscribes to its configure callback. */
|
||||
static const struct ext_session_lock_surface_v1_listener lock_surface_listener;
|
||||
|
||||
/* Type for lock-window shm buffers: solid black, so the screen is visibly
|
||||
blanked while the session is locked. */
|
||||
static void lock_fill_buffer(void) {
|
||||
size_t stride = (size_t)W * 4;
|
||||
size_t size = stride * (size_t)H;
|
||||
int fd;
|
||||
struct wl_shm_pool *pool = make_pool(size, &fd);
|
||||
if (!pool) return;
|
||||
uint8_t *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (data != MAP_FAILED) {
|
||||
memset(data, 0, size); /* opaque black (XRGB) */
|
||||
munmap(data, size);
|
||||
}
|
||||
close(fd);
|
||||
struct wl_buffer *b = wl_shm_pool_create_buffer(pool, 0, W, H,
|
||||
(int32_t)stride, WL_SHM_FORMAT_XRGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
wl_surface_attach(lock_wl_surface, b, 0, 0);
|
||||
wl_surface_commit(lock_wl_surface);
|
||||
}
|
||||
|
||||
static void lock_locked(void *data, struct ext_session_lock_v1 *l) {
|
||||
(void)data; (void)l;
|
||||
lock_received_locked = true;
|
||||
lock_note("locked");
|
||||
/* The compositor accepted the lock; create a private surface on the
|
||||
output so the session is visibly blanked and the compositor gets a
|
||||
surface to focus. */
|
||||
if (W == 0) W = 64;
|
||||
if (H == 0) H = 64;
|
||||
lock_wl_surface = wl_compositor_create_surface(compositor);
|
||||
if (!lock_wl_surface || !wl_output) {
|
||||
lock_note("surface-fail");
|
||||
return;
|
||||
}
|
||||
lock_surface = ext_session_lock_v1_get_lock_surface(lock, lock_wl_surface,
|
||||
wl_output);
|
||||
ext_session_lock_surface_v1_add_listener(lock_surface,
|
||||
&lock_surface_listener, NULL);
|
||||
}
|
||||
|
||||
static void lock_finished(void *data, struct ext_session_lock_v1 *l) {
|
||||
(void)data; (void)l;
|
||||
lock_note("finished");
|
||||
}
|
||||
|
||||
static const struct ext_session_lock_v1_listener lock_listener = {
|
||||
.locked = lock_locked,
|
||||
.finished = lock_finished,
|
||||
};
|
||||
|
||||
static void lock_surface_configure(void *data,
|
||||
struct ext_session_lock_surface_v1 *l,
|
||||
uint32_t serial, uint32_t w, uint32_t h) {
|
||||
(void)data;
|
||||
if (w > 0 && h > 0) { W = (int)w; H = (int)h; }
|
||||
lock_conf_acked = true;
|
||||
ext_session_lock_surface_v1_ack_configure(l, serial);
|
||||
lock_fill_buffer();
|
||||
lock_note("surface-config");
|
||||
if (lock_unlock_after)
|
||||
ext_session_lock_v1_unlock_and_destroy(lock);
|
||||
}
|
||||
|
||||
static const struct ext_session_lock_surface_v1_listener lock_surface_listener = {
|
||||
.configure = lock_surface_configure,
|
||||
};
|
||||
static void lock_run(void) {
|
||||
display = wl_display_connect(NULL);
|
||||
if (!display) {
|
||||
fprintf(stderr, "wltest: lock: cannot connect\n");
|
||||
exit(1);
|
||||
}
|
||||
struct wl_registry *reg = wl_display_get_registry(display);
|
||||
wl_registry_add_listener(reg, ®istry_listener, NULL);
|
||||
wl_display_roundtrip(display);
|
||||
if (!lock_mgr) {
|
||||
fprintf(stderr, "wltest: lock: ext_session_lock_manager_v1 not advertised\n");
|
||||
exit(1);
|
||||
}
|
||||
lock = ext_session_lock_manager_v1_lock(lock_mgr);
|
||||
ext_session_lock_v1_add_listener(lock, &lock_listener, NULL);
|
||||
for (;;) {
|
||||
if (wl_display_dispatch(display) < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pull the current offer and act on it. In --primary-demand mode write the
|
||||
payload to the file and exit; in --primary-demand-expect mode keep waiting
|
||||
until the payload matches the expected text (the host may already hold its
|
||||
|
|
@ -1340,10 +1465,29 @@ int main(int argc, char **argv) {
|
|||
} else if (strcmp(argv[i], "--idle-inhibit") == 0) {
|
||||
idle_inhibit = true;
|
||||
i += 1;
|
||||
} else if (strcmp(argv[i], "--lock") == 0) {
|
||||
lock_mode = true;
|
||||
i += 1;
|
||||
} else if (strcmp(argv[i], "--lock-out") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fprintf(stderr, "wltest: --lock-out needs a path\n");
|
||||
return 1;
|
||||
}
|
||||
lock_out = argv[i + 1];
|
||||
i += 2;
|
||||
} else if (strcmp(argv[i], "--lock-unlock") == 0) {
|
||||
lock_unlock_after = true;
|
||||
i += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Session-lock mode: no color/size argument. Act as the swaylock side
|
||||
of ext-session-lock-v1 and never open a normal toplevel. */
|
||||
if (lock_mode) {
|
||||
lock_run();
|
||||
return 0;
|
||||
}
|
||||
/* Taskbar mode: no color/size argument. Run as a foreign-toplevel
|
||||
listener and never create a window. */
|
||||
if (foreign_mode) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue