labwc/src/session-lock.c
John Lindgren 00ce7a2b5e session-lock: reconfigure for output layout changes
Currently, if the output layout changes while the session is locked,
the lock surfaces may end up wrongly positioned, which looks bad and
may reveal some of the user's workspace underneath.

To prevent this, re-align the scene trees and reconfigure the lock
surfaces when the output layout changes.
2023-10-05 20:10:37 +01:00

340 lines
9.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#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);
}
}
static void
align_session_lock_tree(struct output *output)
{
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);
}
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;
}
align_session_lock_tree(output);
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_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);
}
void
session_lock_update_for_layout_change(void)
{
if (!g_server->session_lock) {
return;
}
struct output *output;
wl_list_for_each(output, &g_server->outputs, link) {
align_session_lock_tree(output);
}
struct session_lock *lock = g_server->session_lock;
struct session_lock_output *lock_output;
wl_list_for_each(lock_output, &lock->session_lock_outputs, link) {
lock_output_reconfigure(lock_output);
}
}