diff --git a/include/labwc.h b/include/labwc.h index 834b1b9a..9abe32b2 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -47,6 +47,7 @@ #include "config/keybind.h" #include "config/rcxml.h" #include "regions.h" +#include "session-lock.h" #if HAVE_NLS #include #include @@ -283,6 +284,8 @@ struct server { */ int pending_output_layout_change; + struct session_lock *session_lock; + struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; struct wlr_drm_lease_v1_manager *drm_lease_manager; @@ -319,6 +322,7 @@ struct output { struct wlr_scene_tree *layer_tree[LAB_NR_LAYERS]; struct wlr_scene_tree *layer_popup_tree; struct wlr_scene_tree *osd_tree; + struct wlr_scene_tree *session_lock_tree; struct wlr_scene_buffer *workspace_osd; struct wlr_box usable_area; diff --git a/include/session-lock.h b/include/session-lock.h new file mode 100644 index 00000000..aea4e4bd --- /dev/null +++ b/include/session-lock.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __LAB_SESSION_LOCK_H +#define __LAB_SESSION_LOCK_H + +#include + +struct session_lock { + struct wlr_session_lock_v1 *lock; + struct wlr_surface *focused; + bool abandoned; + + struct wl_list session_lock_outputs; + + struct wl_listener new_surface; + struct wl_listener unlock; + struct wl_listener destroy; +}; + +void session_lock_init(struct server *server); +void session_lock_output_create(struct session_lock *lock, struct output *output); + +#endif /* __LAB_SESSION_LOCK_H */ diff --git a/src/desktop.c b/src/desktop.c index 91c15ce8..5a4acffa 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -50,7 +50,8 @@ desktop_focus_and_activate_view(struct seat *seat, struct view *view) return; } - if (input_inhibit_blocks_surface(seat, view->surface->resource)) { + if (input_inhibit_blocks_surface(seat, view->surface->resource) + || seat->server->session_lock) { return; } diff --git a/src/keyboard.c b/src/keyboard.c index f48b6dbc..3f69c8bc 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -145,6 +145,9 @@ handle_compositor_keybindings(struct keyboard *keyboard, if (seat->active_client_while_inhibited) { return false; } + if (seat->server->session_lock) { + return false; + } /* * If a user lets go of the modifier (e.g. alt) before the 'normal' key diff --git a/src/meson.build b/src/meson.build index a3f521bd..5087d331 100644 --- a/src/meson.build +++ b/src/meson.build @@ -18,6 +18,7 @@ labwc_sources = files( 'resistance.c', 'seat.c', 'server.c', + 'session-lock.c', 'touch.c', 'theme.c', 'view.c', diff --git a/src/output.c b/src/output.c index 1183add4..f5af8ab4 100644 --- a/src/output.c +++ b/src/output.c @@ -54,6 +54,7 @@ output_destroy_notify(struct wl_listener *listener, void *data) } wlr_scene_node_destroy(&output->layer_popup_tree->node); wlr_scene_node_destroy(&output->osd_tree->node); + wlr_scene_node_destroy(&output->session_lock_tree->node); struct view *view; struct server *server = output->server; @@ -183,6 +184,9 @@ new_output_notify(struct wl_listener *listener, void *data) output->osd_tree = wlr_scene_tree_create(&server->scene->tree); node_descriptor_create(&output->osd_tree->node, LAB_NODE_DESC_TREE, NULL); + output->session_lock_tree = wlr_scene_tree_create(&server->scene->tree); + node_descriptor_create(&output->session_lock_tree->node, + LAB_NODE_DESC_TREE, NULL); /* * Set the z-positions to achieve the following order (from top to @@ -228,6 +232,10 @@ new_output_notify(struct wl_listener *listener, void *data) /* Create regions from config */ regions_reconfigure_output(output); + if (server->session_lock) { + session_lock_output_create(server->session_lock, output); + } + server->pending_output_layout_change--; do_output_layout_change(server); } diff --git a/src/server.c b/src/server.c index 24aa1cb3..efc4e2f2 100644 --- a/src/server.c +++ b/src/server.c @@ -387,6 +387,8 @@ server_init(struct server *server) server->foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(server->wl_display); + session_lock_init(server); + server->drm_lease_manager = wlr_drm_lease_v1_manager_create( server->wl_display, server->backend); if (server->drm_lease_manager) { diff --git a/src/session-lock.c b/src/session-lock.c new file mode 100644 index 00000000..4e09e1ee --- /dev/null +++ b/src/session-lock.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0-only +#define _POSIX_C_SOURCE 200809L +#include +#include "common/mem.h" +#include "labwc.h" + +static struct wl_listener new_lock; +static struct wl_listener manager_destroy; +static struct wlr_session_lock_manager_v1 *wlr_session_lock_manager; + +static struct server *g_server; + +struct session_lock_output { + struct wlr_scene_tree *tree; + struct wlr_scene_rect *background; + struct session_lock *lock; + struct output *output; + struct wlr_session_lock_surface_v1 *surface; + + struct wl_list link; /* session_lock.outputs */ + + struct wl_listener destroy; + struct wl_listener commit; + struct wl_listener surface_destroy; + struct wl_listener surface_map; +}; + +static void +focus_surface(struct session_lock *lock, struct wlr_surface *focused) +{ + lock->focused = focused; + seat_focus_surface(&g_server->seat, focused); +} + +static void +refocus_output(struct session_lock_output *output) +{ + /* Try to focus another session-lock surface */ + if (output->lock->focused != output->surface->surface) { + return; + } + + struct session_lock_output *iter; + wl_list_for_each(iter, &output->lock->session_lock_outputs, link) { + if (iter == output || !iter->surface) { + continue; + } + if (iter->surface->mapped) { + focus_surface(output->lock, iter->surface->surface); + return; + } + } + focus_surface(output->lock, NULL); +} + +static void +handle_surface_map(struct wl_listener *listener, void *data) +{ + struct session_lock_output *surf = wl_container_of(listener, surf, surface_map); + if (!surf->lock->focused) { + focus_surface(surf->lock, surf->surface->surface); + } +} + +static void +handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct session_lock_output *output = + wl_container_of(listener, output, surface_destroy); + refocus_output(output); + + assert(output->surface); + output->surface = NULL; + wl_list_remove(&output->surface_destroy.link); + wl_list_remove(&output->surface_map.link); +} + +static void +lock_output_reconfigure(struct session_lock_output *output) +{ + struct wlr_box box; + wlr_output_layout_get_box(g_server->output_layout, output->output->wlr_output, &box); + wlr_scene_rect_set_size(output->background, box.width, box.height); + if (output->surface) { + wlr_session_lock_surface_v1_configure(output->surface, box.width, box.height); + } +} + +static void +handle_new_surface(struct wl_listener *listener, void *data) +{ + struct session_lock *lock = wl_container_of(listener, lock, new_surface); + struct wlr_session_lock_surface_v1 *lock_surface = data; + struct output *output = lock_surface->output->data; + struct session_lock_output *lock_output; + wl_list_for_each(lock_output, &lock->session_lock_outputs, link) { + if (lock_output->output == output) { + goto found_lock_output; + } + } + wlr_log(WLR_ERROR, "new lock surface, but no output"); + /* TODO: Consider improving security by handling this better */ + return; + +found_lock_output: + lock_output->surface = lock_surface; + wlr_scene_subsurface_tree_create(lock_output->tree, lock_surface->surface); + + lock_output->surface_destroy.notify = handle_surface_destroy; + wl_signal_add(&lock_surface->events.destroy, &lock_output->surface_destroy); + + lock_output->surface_map.notify = handle_surface_map; + wl_signal_add(&lock_surface->events.map, &lock_output->surface_map); + + lock_output_reconfigure(lock_output); +} + +static void +session_lock_output_destroy(struct session_lock_output *output) +{ + if (output->surface) { + refocus_output(output); + wl_list_remove(&output->surface_destroy.link); + wl_list_remove(&output->surface_map.link); + } + wl_list_remove(&output->commit.link); + wl_list_remove(&output->destroy.link); + wl_list_remove(&output->link); + free(output); +} + +static void +handle_destroy(struct wl_listener *listener, void *data) +{ + struct session_lock_output *output = wl_container_of(listener, output, destroy); + session_lock_output_destroy(output); +} + +static void +handle_commit(struct wl_listener *listener, void *data) +{ + struct wlr_output_event_commit *event = data; + struct session_lock_output *output = wl_container_of(listener, output, commit); + uint32_t require_reconfigure = WLR_OUTPUT_STATE_MODE + | WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_TRANSFORM; + if (event->committed & require_reconfigure) { + lock_output_reconfigure(output); + } +} + +void +session_lock_output_create(struct session_lock *lock, struct output *output) +{ + struct session_lock_output *lock_output = znew(*lock_output); + if (!lock_output) { + wlr_log(WLR_ERROR, "session-lock: out of memory"); + goto exit_session; + } + + struct wlr_scene_tree *tree = wlr_scene_tree_create(output->session_lock_tree); + if (!tree) { + wlr_log(WLR_ERROR, "session-lock: wlr_scene_tree_create()"); + free(lock_output); + goto exit_session; + } + + /* + * The ext-session-lock protocol says that the compositor should blank + * all outputs with an opaque color such that their normal content is + * fully hidden + */ + float *black = (float[4]) { 0.f, 0.f, 0.f, 1.f }; + struct wlr_scene_rect *background = wlr_scene_rect_create(tree, 0, 0, black); + if (!background) { + wlr_log(WLR_ERROR, "session-lock: wlr_scene_rect_create()"); + wlr_scene_node_destroy(&tree->node); + free(lock_output); + goto exit_session; + } + + struct wlr_box box; + wlr_output_layout_get_box(g_server->output_layout, output->wlr_output, &box); + wlr_scene_node_set_position(&output->session_lock_tree->node, box.x, box.y); + + lock_output->output = output; + lock_output->tree = tree; + lock_output->background = background; + lock_output->lock = lock; + + lock_output->destroy.notify = handle_destroy; + wl_signal_add(&tree->node.events.destroy, &lock_output->destroy); + + lock_output->commit.notify = handle_commit; + wl_signal_add(&output->wlr_output->events.commit, &lock_output->commit); + + lock_output_reconfigure(lock_output); + + wl_list_insert(&lock->session_lock_outputs, &lock_output->link); + return; + +exit_session: + /* TODO: Consider a better - but secure - way to deal with this */ + wlr_log(WLR_ERROR, "out of memory"); + exit(EXIT_FAILURE); +} + +static void +session_lock_destroy(struct session_lock *lock) +{ + struct session_lock_output *lock_output, *next; + wl_list_for_each_safe(lock_output, next, &lock->session_lock_outputs, link) { + wlr_scene_node_destroy(&lock_output->tree->node); + } + if (g_server->session_lock == lock) { + g_server->session_lock = NULL; + } + if (!lock->abandoned) { + wl_list_remove(&lock->destroy.link); + wl_list_remove(&lock->unlock.link); + wl_list_remove(&lock->new_surface.link); + } + free(lock); +} + +static void +handle_unlock(struct wl_listener *listener, void *data) +{ + struct session_lock *lock = wl_container_of(listener, lock, unlock); + session_lock_destroy(lock); + desktop_focus_topmost_mapped_view(g_server); +} + +static void +handle_session_lock_destroy(struct wl_listener *listener, void *data) +{ + struct session_lock *lock = wl_container_of(listener, lock, destroy); + + float *black = (float[4]) { 0.f, 0.f, 0.f, 1.f }; + struct session_lock_output *lock_output; + wl_list_for_each(lock_output, &lock->session_lock_outputs, link) { + wlr_scene_rect_set_color(lock_output->background, black); + } + + lock->abandoned = true; + wl_list_remove(&lock->destroy.link); + wl_list_remove(&lock->unlock.link); + wl_list_remove(&lock->new_surface.link); +} + +static void +handle_new_session_lock(struct wl_listener *listener, void *data) +{ + struct wlr_session_lock_v1 *lock = data; + + /* One already exists */ + if (g_server->session_lock) { + if (g_server->session_lock->abandoned) { + wlr_log(WLR_INFO, "replacing abandoned lock"); + session_lock_destroy(g_server->session_lock); + } else { + wlr_log(WLR_ERROR, "session already locked"); + wlr_session_lock_v1_destroy(lock); + return; + } + } + + struct session_lock *session_lock = znew(*session_lock); + if (!session_lock) { + wlr_log(WLR_ERROR, "session-lock: out of memory"); + wlr_session_lock_v1_destroy(lock); + return; + } + + wl_list_init(&session_lock->session_lock_outputs); + struct output *output; + wl_list_for_each(output, &g_server->outputs, link) { + session_lock_output_create(session_lock, output); + } + + session_lock->new_surface.notify = handle_new_surface; + wl_signal_add(&lock->events.new_surface, &session_lock->new_surface); + + session_lock->unlock.notify = handle_unlock; + wl_signal_add(&lock->events.unlock, &session_lock->unlock); + + session_lock->destroy.notify = handle_session_lock_destroy; + wl_signal_add(&lock->events.destroy, &session_lock->destroy); + + wlr_session_lock_v1_send_locked(lock); + g_server->session_lock = session_lock; +} + +static void +handle_manager_destroy(struct wl_listener *listener, void *data) +{ + if (g_server->session_lock) { + session_lock_destroy(g_server->session_lock); + } + wl_list_remove(&new_lock.link); + wl_list_remove(&manager_destroy.link); + wlr_session_lock_manager = NULL; +} + +void session_lock_init(struct server *server) +{ + g_server = server; + wlr_session_lock_manager = wlr_session_lock_manager_v1_create(server->wl_display); + + new_lock.notify = handle_new_session_lock; + wl_signal_add(&wlr_session_lock_manager->events.new_lock, &new_lock); + + manager_destroy.notify = handle_manager_destroy; + wl_signal_add(&wlr_session_lock_manager->events.destroy, &manager_destroy); +}