From 74d11162945b697934d46006c8bd5372e25d2ad4 Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Sun, 4 Sep 2022 12:33:00 -0400 Subject: [PATCH] scene_graph: Port ext_session_v1 --- include/sway/output.h | 1 + include/sway/server.h | 22 ++- include/sway/tree/root.h | 1 + sway/desktop/output.c | 4 + sway/desktop/render.c | 35 ---- sway/input/input-manager.c | 2 +- sway/input/keyboard.c | 2 +- sway/input/seat.c | 6 +- sway/input/switch.c | 2 +- sway/lock.c | 358 +++++++++++++++++++++++++------------ 10 files changed, 275 insertions(+), 158 deletions(-) diff --git a/include/sway/output.h b/include/sway/output.h index e91968933..093b0737a 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -23,6 +23,7 @@ struct sway_output { struct { struct wlr_scene_tree *tiling; struct wlr_scene_tree *fullscreen; + struct wlr_scene_tree *session_lock; } layers; // when a container is fullscreen, in case the fullscreen surface is diff --git a/include/sway/server.h b/include/sway/server.h index 555074bd7..5acea791a 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -27,6 +27,18 @@ #endif struct sway_transaction; +struct sway_session_lock { + struct wlr_session_lock_v1 *lock; + struct wlr_surface *focused; + bool abandoned; + + struct wl_list outputs; // struct sway_session_lock_output + + // invalid if the session is abandoned + struct wl_listener new_surface; + struct wl_listener unlock; + struct wl_listener destroy; +}; struct sway_server { struct wl_display *wl_display; @@ -91,15 +103,9 @@ struct sway_server { struct wl_listener output_manager_test; struct { - bool locked; + struct sway_session_lock *lock; struct wlr_session_lock_manager_v1 *manager; - struct wlr_session_lock_v1 *lock; - struct wlr_surface *focused; - struct wl_listener lock_new_surface; - struct wl_listener lock_unlock; - struct wl_listener lock_destroy; - struct wl_listener new_lock; struct wl_listener manager_destroy; } session_lock; @@ -161,6 +167,8 @@ void handle_new_output(struct wl_listener *listener, void *data); void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data); void handle_layer_shell_surface(struct wl_listener *listener, void *data); void sway_session_lock_init(void); +void sway_session_lock_add_output(struct sway_session_lock *lock, + struct sway_output *output); void handle_xdg_shell_surface(struct wl_listener *listener, void *data); #if HAVE_XWAYLAND void handle_xwayland_surface(struct wl_listener *listener, void *data); diff --git a/include/sway/tree/root.h b/include/sway/tree/root.h index f87b03259..e2c43e2fe 100644 --- a/include/sway/tree/root.h +++ b/include/sway/tree/root.h @@ -41,6 +41,7 @@ struct sway_root { struct wlr_scene_tree *fullscreen; struct wlr_scene_tree *fullscreen_global; struct wlr_scene_tree *seat; + struct wlr_scene_tree *session_lock; } layers; #if HAVE_XWAYLAND diff --git a/sway/desktop/output.c b/sway/desktop/output.c index a3efcd70e..69068c57c 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -765,6 +765,10 @@ void handle_new_output(struct wl_listener *listener, void *data) { output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop, output_repaint_timer_handler, output); + if (server->session_lock.lock) { + sway_session_lock_add_output(server->session_lock.lock, output); + } + struct output_config *oc = find_output_config(output); apply_output_config(oc, output); free_output_config(oc); diff --git a/sway/desktop/render.c b/sway/desktop/render.c index 079f4f39f..cebf10553 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -1046,41 +1046,6 @@ void output_render(struct sway_output *output, struct timespec *when, wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1}); } - if (server.session_lock.locked) { - float clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f}; - if (server.session_lock.lock == NULL) { - // abandoned lock -> red BG - clear_color[0] = 1.f; - } - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); - for (int i = 0; i < nrects; ++i) { - scissor_output(wlr_output, &rects[i]); - wlr_renderer_clear(renderer, clear_color); - } - - if (server.session_lock.lock != NULL) { - struct render_data data = { - .damage = damage, - .alpha = 1.0f, - }; - - struct wlr_session_lock_surface_v1 *lock_surface; - wl_list_for_each(lock_surface, &server.session_lock.lock->surfaces, link) { - if (lock_surface->output != wlr_output) { - continue; - } - if (!lock_surface->mapped) { - continue; - } - - output_surface_for_each_surface(output, lock_surface->surface, - 0.0, 0.0, render_surface_iterator, &data); - } - } - goto renderer_end; - } - if (output_has_opaque_overlay_layer_surface(output)) { goto render_overlay; } diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index 26eefc8a1..4063a8452 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -293,7 +293,7 @@ static void handle_inhibit_deactivate(struct wl_listener *listener, void *data) struct sway_input_manager *input_manager = wl_container_of( listener, input_manager, inhibit_deactivate); struct sway_seat *seat; - if (server.session_lock.locked) { + if (server.session_lock.lock) { // Don't deactivate the grab of a screenlocker return; } diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index 5e5692f19..08b10efa4 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c @@ -405,7 +405,7 @@ static void handle_key_event(struct sway_keyboard *keyboard, bool exact_identifier = keyboard->wlr->group != NULL; seat_idle_notify_activity(seat, IDLE_SOURCE_KEYBOARD); bool input_inhibited = seat->exclusive_client != NULL || - server.session_lock.locked; + server.session_lock.lock; struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = keyboard_shortcuts_inhibitor_get_for_focused_surface(seat); bool shortcuts_inhibited = sway_inhibitor && sway_inhibitor->inhibitor->active; diff --git a/sway/input/seat.c b/sway/input/seat.c index a723f22e9..2efabf29b 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -1080,7 +1080,7 @@ bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface) { struct wl_client *client = wl_resource_get_client(surface->resource); return seat->exclusive_client == client || - (seat->exclusive_client == NULL && !server.session_lock.locked); + (seat->exclusive_client == NULL && !server.session_lock.lock); } static void send_unfocus(struct sway_container *con, void *data) { @@ -1281,8 +1281,8 @@ void seat_set_focus(struct sway_seat *seat, struct sway_node *node) { } else { seat_set_workspace_focus(seat, node); } - if (server.session_lock.locked) { - seat_set_focus_surface(seat, server.session_lock.focused, false); + if (server.session_lock.lock) { + seat_set_focus_surface(seat, server.session_lock.lock->focused, false); } } diff --git a/sway/input/switch.c b/sway/input/switch.c index fc7dfaff7..9001f0da8 100644 --- a/sway/input/switch.c +++ b/sway/input/switch.c @@ -36,7 +36,7 @@ static bool sway_switch_trigger_test(enum sway_switch_trigger trigger, static void execute_binding(struct sway_switch *sway_switch) { struct sway_seat* seat = sway_switch->seat_device->sway_seat; bool input_inhibited = seat->exclusive_client != NULL || - server.session_lock.locked; + server.session_lock.lock; list_t *bindings = config->current_mode->switch_bindings; struct sway_switch_binding *matched_binding = NULL; diff --git a/sway/lock.c b/sway/lock.c index 3c7c06cf4..d2e990010 100644 --- a/sway/lock.c +++ b/sway/lock.c @@ -1,24 +1,35 @@ #define _POSIX_C_SOURCE 200809L #include +#include #include "log.h" #include "sway/input/keyboard.h" #include "sway/input/seat.h" #include "sway/output.h" #include "sway/server.h" -struct sway_session_lock_surface { - struct wlr_session_lock_surface_v1 *lock_surface; +struct sway_session_lock_output { + struct wlr_scene_tree *tree; + struct wlr_scene_rect *background; + struct sway_session_lock *lock; + struct sway_output *output; - struct wlr_surface *surface; - struct wl_listener map; + + struct wl_list link; // sway_session_lock::outputs + struct wl_listener destroy; - struct wl_listener surface_commit; - struct wl_listener output_mode; - struct wl_listener output_commit; + struct wl_listener commit; + struct wl_listener mode; + + struct wlr_session_lock_surface_v1 *surface; + + // invalid if surface is NULL + struct wl_listener surface_destroy; + struct wl_listener surface_map; }; -static void set_lock_focused_surface(struct wlr_surface *focused) { - server.session_lock.focused = focused; +static void focus_surface(struct sway_session_lock *lock, + struct wlr_surface *focused) { + lock->focused = focused; struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { @@ -26,99 +37,198 @@ static void set_lock_focused_surface(struct wlr_surface *focused) { } } -static void handle_surface_map(struct wl_listener *listener, void *data) { - struct sway_session_lock_surface *surf = wl_container_of(listener, surf, map); - if (server.session_lock.focused == NULL) { - set_lock_focused_surface(surf->surface); +static void refocus_output(struct sway_session_lock_output *output) { + // Move the seat focus to another surface if one is available + if (output->lock->focused == output->surface->surface) { + struct wlr_surface *next_focus = NULL; + + struct sway_session_lock_output *candidate; + wl_list_for_each(candidate, &output->lock->outputs, link) { + if (candidate == output || !candidate->surface) { + continue; + } + + if (candidate->surface->mapped) { + next_focus = candidate->surface->surface; + break; + } + } + + focus_surface(output->lock, next_focus); } - output_damage_whole(surf->output); } -static void handle_surface_commit(struct wl_listener *listener, void *data) { - struct sway_session_lock_surface *surf = wl_container_of(listener, surf, surface_commit); - output_damage_surface(surf->output, 0, 0, surf->surface, false); -} - -static void handle_output_mode(struct wl_listener *listener, void *data) { - struct sway_session_lock_surface *surf = wl_container_of(listener, surf, output_mode); - wlr_session_lock_surface_v1_configure(surf->lock_surface, - surf->output->width, surf->output->height); -} - -static void handle_output_commit(struct wl_listener *listener, void *data) { - struct wlr_output_event_commit *event = data; - struct sway_session_lock_surface *surf = wl_container_of(listener, surf, output_commit); - if (event->committed & ( - WLR_OUTPUT_STATE_MODE | - WLR_OUTPUT_STATE_SCALE | - WLR_OUTPUT_STATE_TRANSFORM)) { - wlr_session_lock_surface_v1_configure(surf->lock_surface, - surf->output->width, surf->output->height); +static void handle_surface_map(struct wl_listener *listener, void *data) { + struct sway_session_lock_output *surf = wl_container_of(listener, surf, surface_map); + if (surf->lock->focused == NULL) { + focus_surface(surf->lock, surf->surface->surface); } } static void handle_surface_destroy(struct wl_listener *listener, void *data) { - struct sway_session_lock_surface *surf = wl_container_of(listener, surf, destroy); + struct sway_session_lock_output *output = + wl_container_of(listener, output, surface_destroy); + refocus_output(output); - // Move the seat focus to another surface if one is available - if (server.session_lock.focused == surf->surface) { - struct wlr_surface *next_focus = NULL; + sway_assert(output->surface, "Trying to destroy a surface that the lock doesn't think exists"); + output->surface = NULL; + wl_list_remove(&output->surface_destroy.link); + wl_list_remove(&output->surface_map.link); +} - struct wlr_session_lock_surface_v1 *other; - wl_list_for_each(other, &server.session_lock.lock->surfaces, link) { - if (other != surf->lock_surface && other->mapped) { - next_focus = other->surface; - break; - } - } - set_lock_focused_surface(next_focus); +static void lock_output_reconfigure(struct sway_session_lock_output *output) { + int width = output->output->width; + int height = output->output->height; + + wlr_scene_rect_set_size(output->background, width, height); + + if (output->surface) { + wlr_session_lock_surface_v1_configure(output->surface, width, height); } - - wl_list_remove(&surf->map.link); - wl_list_remove(&surf->destroy.link); - wl_list_remove(&surf->surface_commit.link); - wl_list_remove(&surf->output_mode.link); - wl_list_remove(&surf->output_commit.link); - output_damage_whole(surf->output); - free(surf); } static void handle_new_surface(struct wl_listener *listener, void *data) { + struct sway_session_lock *lock = wl_container_of(listener, lock, new_surface); struct wlr_session_lock_surface_v1 *lock_surface = data; - struct sway_session_lock_surface *surf = calloc(1, sizeof(*surf)); - if (surf == NULL) { - return; - } + struct sway_output *output = lock_surface->output->data; sway_log(SWAY_DEBUG, "new lock layer surface"); - struct sway_output *output = lock_surface->output->data; - wlr_session_lock_surface_v1_configure(lock_surface, output->width, output->height); + struct sway_session_lock_output *current_lock_output, *lock_output = NULL; + wl_list_for_each(current_lock_output, &lock->outputs, link) { + if (current_lock_output->output == output) { + lock_output = current_lock_output; + break; + } + } + sway_assert(lock_output, "Couldn't find output to lock"); + sway_assert(!lock_output->surface, "Tried to reassign a surface to an existing output"); - surf->lock_surface = lock_surface; - surf->surface = lock_surface->surface; - surf->output = output; - surf->map.notify = handle_surface_map; - wl_signal_add(&lock_surface->events.map, &surf->map); - surf->destroy.notify = handle_surface_destroy; - wl_signal_add(&lock_surface->events.destroy, &surf->destroy); - surf->surface_commit.notify = handle_surface_commit; - wl_signal_add(&surf->surface->events.commit, &surf->surface_commit); - surf->output_mode.notify = handle_output_mode; - wl_signal_add(&output->wlr_output->events.mode, &surf->output_mode); - surf->output_commit.notify = handle_output_commit; - wl_signal_add(&output->wlr_output->events.commit, &surf->output_commit); + 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 sway_session_lock_output_destroy(struct sway_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->mode.link); + wl_list_remove(&output->commit.link); + wl_list_remove(&output->destroy.link); + wl_list_remove(&output->link); + + free(output); +} + +static void lock_node_handle_destroy(struct wl_listener *listener, void *data) { + struct sway_session_lock_output *output = + wl_container_of(listener, output, destroy); + sway_session_lock_output_destroy(output); +} + +static void lock_output_handle_mode(struct wl_listener *listener, void *data) { + struct sway_session_lock_output *output = + wl_container_of(listener, output, mode); + lock_output_reconfigure(output); +} + +static void lock_output_handle_commit(struct wl_listener *listener, void *data) { + struct wlr_output_event_commit *event = data; + struct sway_session_lock_output *output = + wl_container_of(listener, output, commit); + if (event->committed & ( + WLR_OUTPUT_STATE_MODE | + WLR_OUTPUT_STATE_SCALE | + WLR_OUTPUT_STATE_TRANSFORM)) { + lock_output_reconfigure(output); + } +} + +static struct sway_session_lock_output *session_lock_output_create( + struct sway_session_lock *lock, struct sway_output *output) { + struct sway_session_lock_output *lock_output = + calloc(1, sizeof(struct sway_session_lock_output)); + if (!lock_output) { + sway_log(SWAY_ERROR, "failed to allocate a session lock output"); + return NULL; + } + + struct wlr_scene_tree *tree = wlr_scene_tree_create(output->layers.session_lock); + if (!tree) { + sway_log(SWAY_ERROR, "failed to allocate a session lock output scene tree"); + free(lock_output); + return NULL; + } + + float *color = (float[4]){ 0.f, 0.f, 0.f, 1.f }; + if (lock->abandoned) { + color[0] = 1.f; + } + + struct wlr_scene_rect *background = wlr_scene_rect_create(tree, 0, 0, color); + if (!background) { + sway_log(SWAY_ERROR, "failed to allocate a session lock output scene background"); + wlr_scene_node_destroy(&tree->node); + free(lock_output); + return NULL; + } + + lock_output->output = output; + lock_output->tree = tree; + lock_output->background = background; + lock_output->lock = lock; + + lock_output->destroy.notify = lock_node_handle_destroy; + wl_signal_add(&tree->node.events.destroy, &lock_output->destroy); + + lock_output->commit.notify = lock_output_handle_commit; + wl_signal_add(&output->wlr_output->events.commit, &lock_output->commit); + lock_output->mode.notify = lock_output_handle_mode; + wl_signal_add(&output->wlr_output->events.mode, &lock_output->mode); + + lock_output_reconfigure(lock_output); + + wl_list_insert(&lock->outputs, &lock_output->link); + + return lock_output; +} + +static void sway_session_lock_destroy(struct sway_session_lock* lock) { + struct sway_session_lock_output *lock_output, *tmp_lock_output; + wl_list_for_each_safe(lock_output, tmp_lock_output, &lock->outputs, link) { + // destroying the node will also destroy the whole lock output + wlr_scene_node_destroy(&lock_output->tree->node); + } + + if (server.session_lock.lock == lock) { + server.session_lock.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 sway_session_lock *lock = wl_container_of(listener, lock, unlock); sway_log(SWAY_DEBUG, "session unlocked"); - server.session_lock.locked = false; - server.session_lock.lock = NULL; - server.session_lock.focused = NULL; - wl_list_remove(&server.session_lock.lock_new_surface.link); - wl_list_remove(&server.session_lock.lock_unlock.link); - wl_list_remove(&server.session_lock.lock_destroy.link); + sway_session_lock_destroy(lock); struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { @@ -131,33 +241,27 @@ static void handle_unlock(struct wl_listener *listener, void *data) { seat_set_focus(seat, previous); } } - - // redraw everything - for (int i = 0; i < root->outputs->length; ++i) { - struct sway_output *output = root->outputs->items[i]; - output_damage_whole(output); - } } static void handle_abandon(struct wl_listener *listener, void *data) { + struct sway_session_lock *lock = wl_container_of(listener, lock, destroy); sway_log(SWAY_INFO, "session lock abandoned"); - server.session_lock.lock = NULL; - server.session_lock.focused = NULL; - - wl_list_remove(&server.session_lock.lock_new_surface.link); - wl_list_remove(&server.session_lock.lock_unlock.link); - wl_list_remove(&server.session_lock.lock_destroy.link); struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { seat->exclusive_client = NULL; } - // redraw everything - for (int i = 0; i < root->outputs->length; ++i) { - struct sway_output *output = root->outputs->items[i]; - output_damage_whole(output); + struct sway_session_lock_output *lock_output; + wl_list_for_each(lock_output, &lock->outputs, link) { + wlr_scene_rect_set_color(lock_output->background, + (float[4]){ 1.f, 0.f, 0.f, 1.f }); } + + 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_session_lock(struct wl_listener *listener, void *data) { @@ -165,44 +269,78 @@ static void handle_session_lock(struct wl_listener *listener, void *data) { struct wl_client *client = wl_resource_get_client(lock->resource); if (server.session_lock.lock) { + if (server.session_lock.lock->abandoned) { + sway_log(SWAY_INFO, "Replacing abandoned lock"); + sway_session_lock_destroy(server.session_lock.lock); + } else { + sway_log(SWAY_ERROR, "Cannot lock an already locked session"); + wlr_session_lock_v1_destroy(lock); + return; + } + } + + struct sway_session_lock *sway_lock = + calloc(1, sizeof(struct sway_session_lock)); + if (!sway_lock) { + sway_log(SWAY_ERROR, "failed to allocate a session lock object"); wlr_session_lock_v1_destroy(lock); return; } + wl_list_init(&sway_lock->outputs); + sway_log(SWAY_DEBUG, "session locked"); - server.session_lock.locked = true; - server.session_lock.lock = lock; struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { seat_set_exclusive_client(seat, client); } - wl_signal_add(&lock->events.new_surface, &server.session_lock.lock_new_surface); - wl_signal_add(&lock->events.unlock, &server.session_lock.lock_unlock); - wl_signal_add(&lock->events.destroy, &server.session_lock.lock_destroy); + struct sway_output *output; + wl_list_for_each(output, &root->all_outputs, link) { + sway_session_lock_add_output(sway_lock, output); + } + + sway_lock->new_surface.notify = handle_new_surface; + wl_signal_add(&lock->events.new_surface, &sway_lock->new_surface); + sway_lock->unlock.notify = handle_unlock; + wl_signal_add(&lock->events.unlock, &sway_lock->unlock); + sway_lock->destroy.notify = handle_abandon; + wl_signal_add(&lock->events.destroy, &sway_lock->destroy); wlr_session_lock_v1_send_locked(lock); - - // redraw everything - for (int i = 0; i < root->outputs->length; ++i) { - struct sway_output *output = root->outputs->items[i]; - output_damage_whole(output); - } + server.session_lock.lock = sway_lock; } static void handle_session_lock_destroy(struct wl_listener *listener, void *data) { - assert(server.session_lock.lock == NULL); + // if the server shuts down while a lock is active, destroy the lock + if (server.session_lock.lock) { + sway_session_lock_destroy(server.session_lock.lock); + } + wl_list_remove(&server.session_lock.new_lock.link); wl_list_remove(&server.session_lock.manager_destroy.link); + + server.session_lock.manager = NULL; +} + +void sway_session_lock_add_output(struct sway_session_lock *lock, + struct sway_output *output) { + struct sway_session_lock_output *lock_output = + session_lock_output_create(lock, output); + + // if we run out of memory while trying to lock the screen, the best we + // can do is kill the sway process. Security conscious users will have + // the sway session fall back to a login shell. + if (!lock_output) { + sway_log(SWAY_ERROR, "aborting: failed to allocate a lock output"); + abort(); + } } void sway_session_lock_init(void) { server.session_lock.manager = wlr_session_lock_manager_v1_create(server.wl_display); - server.session_lock.lock_new_surface.notify = handle_new_surface; - server.session_lock.lock_unlock.notify = handle_unlock; - server.session_lock.lock_destroy.notify = handle_abandon; server.session_lock.new_lock.notify = handle_session_lock; server.session_lock.manager_destroy.notify = handle_session_lock_destroy; wl_signal_add(&server.session_lock.manager->events.new_lock,