From 519038a7e903caae731007b276f5666a341498e9 Mon Sep 17 00:00:00 2001 From: Daniel De Graaf Date: Tue, 8 Mar 2022 21:14:26 -0500 Subject: [PATCH] Implement ext-session-lock-v1 --- include/sway/input/seat.h | 4 + include/sway/server.h | 15 +++ sway/desktop/output.c | 19 ++++ sway/desktop/render.c | 35 +++++++ sway/input/input-manager.c | 4 + sway/input/keyboard.c | 3 +- sway/input/seat.c | 27 +++++- sway/input/switch.c | 3 +- sway/lock.c | 184 +++++++++++++++++++++++++++++++++++++ sway/meson.build | 1 + sway/server.c | 2 + 11 files changed, 290 insertions(+), 7 deletions(-) create mode 100644 sway/lock.c diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h index 50c4be9b..47726159 100644 --- a/include/sway/input/seat.h +++ b/include/sway/input/seat.h @@ -185,6 +185,10 @@ struct sway_workspace *seat_get_last_known_workspace(struct sway_seat *seat); struct sway_container *seat_get_focused_container(struct sway_seat *seat); +// Force focus to a particular surface that is not part of the workspace +// hierarchy (used for lockscreen) +void sway_force_focus(struct wlr_surface *surface); + /** * Return the last container to be focused for the seat (or the most recently * opened if no container has received focused) that is a child of the given diff --git a/include/sway/server.h b/include/sway/server.h index 0bd860b2..d8ccd64f 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -89,6 +90,19 @@ struct sway_server { struct wl_listener output_manager_apply; struct wl_listener output_manager_test; + struct { + bool locked; + struct wlr_session_lock_manager_v1 *manager; + + struct wlr_session_lock_v1 *lock; + 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; + struct wlr_output_power_manager_v1 *output_power_manager_v1; struct wl_listener output_power_manager_set_mode; struct wlr_input_method_manager_v2 *input_method; @@ -148,6 +162,7 @@ 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 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/sway/desktop/output.c b/sway/desktop/output.c index dd2eaf08..18367a1c 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -275,6 +275,25 @@ static void for_each_surface_container_iterator(struct sway_container *con, static void output_for_each_surface(struct sway_output *output, sway_surface_iterator_func_t iterator, void *user_data) { + if (server.session_lock.locked) { + if (server.session_lock.lock == NULL) { + return; + } + struct wlr_session_lock_surface_v1 *lock_surface; + wl_list_for_each(lock_surface, &server.session_lock.lock->surfaces, link) { + if (lock_surface->output != output->wlr_output) { + continue; + } + if (!lock_surface->mapped) { + continue; + } + + output_surface_for_each_surface(output, lock_surface->surface, + 0.0, 0.0, iterator, user_data); + } + return; + } + if (output_has_opaque_overlay_layer_surface(output)) { goto overlay; } diff --git a/sway/desktop/render.c b/sway/desktop/render.c index 02397c05..ed9ad490 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -1056,6 +1056,41 @@ 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 8538d97c..4a0bce0e 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -289,6 +289,10 @@ 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) { + // Don't deactivate the grab of a screenlocker + return; + } wl_list_for_each(seat, &input_manager->seats, link) { seat_set_exclusive_client(seat, NULL); struct sway_node *previous = seat_get_focus(seat); diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index 1aa30655..8f18b8ba 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c @@ -409,7 +409,8 @@ static void handle_key_event(struct sway_keyboard *keyboard, char *device_identifier = input_device_get_identifier(wlr_device); bool exact_identifier = wlr_device->keyboard->group != NULL; seat_idle_notify_activity(seat, IDLE_SOURCE_KEYBOARD); - bool input_inhibited = seat->exclusive_client != NULL; + bool input_inhibited = seat->exclusive_client != NULL || + server.session_lock.locked; 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 85179dc7..fa83050b 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -212,6 +212,15 @@ static void seat_send_focus(struct sway_node *node, struct sway_seat *seat) { } } +void sway_force_focus(struct wlr_surface *surface) { + struct sway_seat *seat; + wl_list_for_each(seat, &server.input->seats, link) { + seat_keyboard_notify_enter(seat, surface); + seat_tablet_pads_notify_enter(seat, surface); + sway_input_method_relay_set_focus(&seat->im_relay, surface); + } +} + void seat_for_each_node(struct sway_seat *seat, void (*f)(struct sway_node *node, void *data), void *data) { struct sway_seat_node *current = NULL; @@ -814,11 +823,13 @@ static void seat_configure_keyboard(struct sway_seat *seat, sway_keyboard_configure(seat_device->keyboard); wlr_seat_set_keyboard(seat->wlr_seat, seat_device->input_device->wlr_device->keyboard); - struct sway_node *focus = seat_get_focus(seat); - if (focus && node_is_view(focus)) { - // force notify reenter to pick up the new configuration + + // force notify reenter to pick up the new configuration. This reuses + // the current focused surface to avoid breaking input grabs. + struct wlr_surface *surface = seat->wlr_seat->keyboard_state.focused_surface; + if (surface) { wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat); - seat_keyboard_notify_enter(seat, focus->sway_container->view->surface); + seat_keyboard_notify_enter(seat, surface); } } @@ -1070,7 +1081,8 @@ void seat_configure_xcursor(struct sway_seat *seat) { 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 || seat->exclusive_client == client; + return seat->exclusive_client == client || + (seat->exclusive_client == NULL && !server.session_lock.locked); } static void send_unfocus(struct sway_container *con, void *data) { @@ -1171,6 +1183,11 @@ void seat_set_focus(struct sway_seat *seat, struct sway_node *node) { return; } + // Deny setting focus when an input grab or lockscreen is active + if (container && !seat_is_input_allowed(seat, container->view->surface)) { + return; + } + struct sway_output *new_output = new_workspace ? new_workspace->output : NULL; diff --git a/sway/input/switch.c b/sway/input/switch.c index af5a2385..ac4baece 100644 --- a/sway/input/switch.c +++ b/sway/input/switch.c @@ -34,7 +34,8 @@ 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; + bool input_inhibited = seat->exclusive_client != NULL || + server.session_lock.locked; 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 new file mode 100644 index 00000000..04f80079 --- /dev/null +++ b/sway/lock.c @@ -0,0 +1,184 @@ +#define _POSIX_C_SOURCE 200809L +#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_output *output; + struct wlr_surface *surface; + struct wl_listener map; + struct wl_listener destroy; + struct wl_listener surface_commit; + struct wl_listener output_mode; + struct wl_listener output_commit; +}; + +static void handle_surface_map(struct wl_listener *listener, void *data) { + struct sway_session_lock_surface *surf = wl_container_of(listener, surf, map); + sway_force_focus(surf->surface); + 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_destroy(struct wl_listener *listener, void *data) { + struct sway_session_lock_surface *surf = wl_container_of(listener, surf, destroy); + 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 wlr_session_lock_surface_v1 *lock_surface = data; + struct sway_session_lock_surface *surf = calloc(1, sizeof(*surf)); + if (surf == NULL) { + return; + } + + 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); + + 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); +} + +static void handle_unlock(struct wl_listener *listener, void *data) { + sway_log(SWAY_DEBUG, "session unlocked"); + server.session_lock.locked = false; + server.session_lock.lock = 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_set_exclusive_client(seat, NULL); + // copied from seat_set_focus_layer -- deduplicate? + struct sway_node *previous = seat_get_focus_inactive(seat, &root->node); + if (previous) { + // Hack to get seat to re-focus the return value of get_focus + seat_set_focus(seat, NULL); + 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) { + sway_log(SWAY_INFO, "session lock abandoned"); + server.session_lock.lock = 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); + } +} + +static void handle_session_lock(struct wl_listener *listener, void *data) { + struct wlr_session_lock_v1 *lock = data; + struct wl_client *client = wl_resource_get_client(lock->resource); + + if (server.session_lock.lock) { + wlr_session_lock_v1_destroy(lock); + return; + } + + 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); + + 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); + } +} + +static void handle_session_lock_destroy(struct wl_listener *listener, void *data) { + assert(server.session_lock.lock == NULL); + wl_list_remove(&server.session_lock.new_lock.link); + wl_list_remove(&server.session_lock.manager_destroy.link); +} + +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, + &server.session_lock.new_lock); + wl_signal_add(&server.session_lock.manager->events.destroy, + &server.session_lock.manager_destroy); +} diff --git a/sway/meson.build b/sway/meson.build index 4ccb2ba1..0ad783e3 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -5,6 +5,7 @@ sway_sources = files( 'decoration.c', 'ipc-json.c', 'ipc-server.c', + 'lock.c', 'main.c', 'server.c', 'swaynag.c', diff --git a/sway/server.c b/sway/server.c index 9bfcffaf..0f0c76a8 100644 --- a/sway/server.c +++ b/sway/server.c @@ -183,6 +183,8 @@ bool server_init(struct sway_server *server) { server->foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(server->wl_display); + sway_session_lock_init(); + server->drm_lease_manager= wlr_drm_lease_v1_manager_create(server->wl_display, server->backend); if (server->drm_lease_manager) {