diff --git a/include/sway/server.h b/include/sway/server.h index f3522a49b..0f04e4f1f 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,10 @@ struct sway_server { struct wl_listener output_manager_apply; struct wl_listener output_manager_test; + struct wlr_screenlock_manager_v1 *screenlock; + struct wlr_texture *permalock_message; + struct wl_listener screenlock_set_mode; + 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; @@ -141,6 +146,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 handle_lock_set_mode(struct wl_listener *listener, void *data); 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/protocols/meson.build b/protocols/meson.build index 8e9e65be1..02e2b4dfc 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -19,6 +19,7 @@ protocols = [ ['idle.xml'], ['wlr-input-inhibitor-unstable-v1.xml'], ['wlr-output-power-management-unstable-v1.xml'], + ['wp-screenlocker-unstable-v1.xml'], ] client_protocols = [ diff --git a/protocols/wp-screenlocker-unstable-v1.xml b/protocols/wp-screenlocker-unstable-v1.xml new file mode 100644 index 000000000..58ef53bbf --- /dev/null +++ b/protocols/wp-screenlocker-unstable-v1.xml @@ -0,0 +1,166 @@ + + + + Copyright © 2021 Daniel De Graaf + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This interface provides notification of screen lock/unlock events, + displaying windows on a locked screen, and acting as a screenlocker. + + + + + + + + + This event will be sent on creation if the screen is currently locked, + and at the start of any lock action. + + Note: windows on the unlocked desktop may still be visible (due to a + locking animation) even after this event is sent, so this event should + not be used to trigger post-lock actions such as a system sleep request. + + + + + + This event will be sent on creation if the screen is currently unlocked, + and at the end of any unlock action. + + Note: windows on the unlocked desktop may become visible (due to an + unlocking animation) prior to this event being sent. + + + + + + Requesting a new lock handle will only succeed if this client has + permission to lock the screen. Compositors that handle screen locking + internally may choose to always reject this request. If an existing + client is acting as a screen locker, the compositor may choose to accept + or reject the new request. + + + + + + + By default, no surfaces are visible on the locked desktop. Any surface + that should be visible on the locked desktop must set its visibility + using this visibility object. + + This request is required for any toplevel surface displayed on the + locked desktop to ensure that sensitive information is not present on + the surface. The exact definition of sensitive may be defined by other + configuration, but might include notification details, keyboard + autocompletion, or configuration interfaces. + + + + + + + + + + This object is inert and should be destroyed. + + If a compositor does not support external lockers, this event will always + be sent on the creation of a lock object. + + + + + + + The lock handle is active and only the locked desktop is visible on all + outputs. If a compositor implements a locking animation or similar + effect that results in both the locked and unlocked desktops being + visible, this event is sent after the effect completes. + + + + + + This object is inert and should be destroyed. + + This will be sent if the compositor has unlocked the screen due to some + external event (for exmple, a dbus unlock message from logind), or if + another client has taken ownership of the lock. + + + + + + Let the compositor know that it should unlock the screen. + + It is valid to call this on a newly created lock handle without waiting + for an event; this will either cancel the lock or do nothing if the lock + handle is inert. + + After this request has been sent by the client, this object will become + inert and should be destroyed. + + + + + + Destroying an active lock handle without first unlocking it will abandon + the lock. If a lock is abandoned, the compositor should take some + fallback action such as launching a new locker client in order to allow + the user to authenticate and unlock the session. + + + + + + + + Destroying this object will revert the surface to not be visible on the + lockscreen at the next commit. + + + + + + + + + + + + This value is double-buffered, see wl_surface.commit. + + + + + + + This event is sent immediately if the associated window cannot be shown + on a locked desktop. This object is inert and should be destroyed. + + + + diff --git a/sway/desktop/render.c b/sway/desktop/render.c index 17fc8f6fb..4a501f673 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -27,6 +27,7 @@ #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" +#include "wp-screenlocker-unstable-v1-protocol.h" struct render_data { pixman_region32_t *damage; @@ -143,6 +144,20 @@ static void render_surface_iterator(struct sway_output *output, return; } + struct wlr_screenlock_lock_surface *lock_surf; + uint32_t lock_mode = ZWP_SCREENLOCKER_VISIBILITY_V1_VISIBILITY_DEFAULT; + wl_list_for_each(lock_surf, &server.screenlock->lock_surfaces, link) { + if (surface == lock_surf->surface) { + lock_mode = lock_surf->current_mode; + break; + } + } + if (lock_mode == ZWP_SCREENLOCKER_VISIBILITY_V1_VISIBILITY_DEFAULT && server.screenlock->locked) { + return; + } else if (lock_mode == ZWP_SCREENLOCKER_VISIBILITY_V1_VISIBILITY_LOCK_ONLY && !server.screenlock->locked) { + return; + } + struct wlr_fbox src_box; wlr_surface_get_buffer_source_box(surface, &src_box); @@ -1048,6 +1063,39 @@ void output_render(struct sway_output *output, struct timespec *when, wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1}); } + if (server.screenlock->locked) { + // repaint the background, to hide old data + float clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f}; + if (!server.screenlock->lock_resource) { + // 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.screenlock->lock_resource) { + // abandoned lock, show error message + wlr_renderer_scissor(renderer, NULL); + + float matrix[9]; + wlr_matrix_identity(matrix); + float x_expand = output->width / (float)server.permalock_message->width; + float y_expand = output->height / (float)server.permalock_message->height; + float expand = fmin(1.0, x_expand > y_expand ? y_expand : x_expand); + + wlr_matrix_scale(matrix, expand, expand); + int x = output->width / 2 / expand - server.permalock_message->width / 2; + int y = output->height / 2 / expand - server.permalock_message->height / 2; + + wlr_render_texture(renderer, server.permalock_message, matrix, x, y, 1.0); + + goto render_overlay; + } + } if (output_has_opaque_overlay_layer_surface(output)) { goto render_overlay; } @@ -1099,11 +1147,13 @@ void output_render(struct sway_output *output, struct timespec *when, render_layer_toplevel(output, damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); - render_workspace(output, damage, workspace, workspace->current.focused); - render_floating(output, damage); + if (!server.screenlock->locked) { + render_workspace(output, damage, workspace, workspace->current.focused); + render_floating(output, damage); #if HAVE_XWAYLAND - render_unmanaged(output, damage, &root->xwayland_unmanaged); + render_unmanaged(output, damage, &root->xwayland_unmanaged); #endif + } render_layer_toplevel(output, damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index f04a8ce09..84893af5d 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -290,6 +290,11 @@ static void handle_inhibit_deactivate(struct wl_listener *listener, void *data) listener, input_manager, inhibit_deactivate); struct sway_seat *seat; wl_list_for_each(seat, &input_manager->seats, link) { + if (seat->exclusive_client != input_manager->inhibit->active_client) { + // the permalock screen can also set exclusive clients, + // so don't undo its work. + continue; + } seat_set_exclusive_client(seat, NULL); struct sway_node *previous = seat_get_focus(seat); if (previous) { diff --git a/sway/lock.c b/sway/lock.c new file mode 100644 index 000000000..a2c549d17 --- /dev/null +++ b/sway/lock.c @@ -0,0 +1,194 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include "cairo_util.h" +#include "log.h" +#include "pango.h" +#include "sway/input/seat.h" +#include "sway/config.h" +#include "sway/commands.h" +#include "sway/output.h" +#include "sway/server.h" +#include "util.h" + +#define PERMALOCK_CLIENT (struct wl_client *)(-1) + +static struct wlr_texture *draw_permalock_message(void) { + sway_log(SWAY_DEBUG, "CREATING PERMALOCK MESSAGE"); + struct sway_output *output = root->outputs->items[0]; + + int scale = output->wlr_output->scale; + int width = 0; + int height = 0; + + const char* permalock_msg = "Lock screen crashed. Start a new lockscreen to unlock."; + + // We must use a non-nil cairo_t for cairo_set_font_options to work. + // Therefore, we cannot use cairo_create(NULL). + cairo_surface_t *dummy_surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, 0, 0); + cairo_t *c = cairo_create(dummy_surface); + cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_NONE); + cairo_set_font_options(c, fo); + get_text_size(c, config->font, &width, &height, NULL, scale, + config->pango_markup, "%s", permalock_msg); + cairo_surface_destroy(dummy_surface); + cairo_destroy(c); + + cairo_surface_t *surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, width, height); + cairo_t *cairo = cairo_create(surface); + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); + cairo_set_font_options(cairo, fo); + cairo_font_options_destroy(fo); + cairo_set_source_rgba(cairo, 1.0,1.0,1.0,0.0); + cairo_paint(cairo); + PangoContext *pango = pango_cairo_create_context(cairo); + cairo_set_source_rgba(cairo, 0.,0.,0.,1.0); + cairo_move_to(cairo, 0, 0); + + pango_printf(cairo, config->font, scale, config->pango_markup, + "%s", permalock_msg); + + cairo_surface_flush(surface); + unsigned char *data = cairo_image_surface_get_data(surface); + int stride = cairo_image_surface_get_stride(surface); + struct wlr_renderer *renderer = wlr_backend_get_renderer( + output->wlr_output->backend); + struct wlr_texture *tex = wlr_texture_from_pixels( + renderer, DRM_FORMAT_ARGB8888, stride, width, height, data); + cairo_surface_destroy(surface); + g_object_unref(pango); + cairo_destroy(cairo); + return tex; +} + +struct lock_finished_delay; +struct lock_finished_item { + struct wl_listener listener; + struct lock_finished_delay *delay; +}; + +struct lock_finished_delay { + struct wl_listener canceller; + int nr_outputs; + int nr_pending; + struct lock_finished_item items[0]; +}; + +static void lock_finished_cleanup(struct lock_finished_delay *delay) { + for (int i = 0; i < delay->nr_outputs; ++i) { + if (delay->items[i].listener.notify) { + wl_list_remove(&delay->items[i].listener.link); + } + } + wl_list_remove(&delay->canceller.link); + free(delay); +} + +static void lock_finished_frame(struct wl_listener *listener, void *data) { + struct lock_finished_item *item = wl_container_of(listener, item, listener); + struct lock_finished_delay *delay = item->delay; + wl_list_remove(&listener->link); + listener->notify = NULL; + delay->nr_pending--; + if (delay->nr_pending == 0) { + wlr_screenlock_send_lock_finished(server.screenlock); + lock_finished_cleanup(delay); + } +} + +static void lock_finished_abort(struct wl_listener *listener, void *data) { + struct wlr_screenlock_change *signal = data; + if (signal->how == WLR_SCREENLOCK_MODE_CHANGE_UNLOCK) { + struct lock_finished_delay *delay = wl_container_of(listener, delay, canceller); + lock_finished_cleanup(delay); + } + // any other 'how' means we should continue +} + +void handle_lock_set_mode(struct wl_listener *listener, void *data) +{ + struct wlr_screenlock_change *signal = data; + struct wl_client *client = signal->new_client; + struct sway_seat *seat; + int nr_outputs = root->outputs->length; + + switch (signal->how) { + case WLR_SCREENLOCK_MODE_CHANGE_LOCK: + wl_list_for_each(seat, &server.input->seats, link) { + seat_set_exclusive_client(seat, client); + } + + // delay lock_finished until the frame is displayed on all enabled displays + struct lock_finished_delay *delay = calloc(1, sizeof(*delay) + nr_outputs * sizeof(delay->items[0])); + delay->nr_outputs = nr_outputs; + + for (int i = 0; i < nr_outputs; ++i) { + struct sway_output *output = root->outputs->items[i]; + if (output && output->enabled && output->wlr_output && output->damage) { + wl_signal_add(&output->damage->events.frame, &delay->items[i].listener); + delay->items[i].listener.notify = lock_finished_frame; + delay->items[i].delay = delay; + delay->nr_pending++; + } + } + + if (delay->nr_pending) { + wl_signal_add(&server.screenlock->events.change_request, + &delay->canceller); + delay->canceller.notify = lock_finished_abort; + } else { + wlr_screenlock_send_lock_finished(server.screenlock); + free(delay); + } + + break; + + case WLR_SCREENLOCK_MODE_CHANGE_REPLACE: + case WLR_SCREENLOCK_MODE_CHANGE_RESPAWN: + wl_list_for_each(seat, &server.input->seats, link) { + seat_set_exclusive_client(seat, client); + } + + wlr_screenlock_send_lock_finished(server.screenlock); + break; + + case WLR_SCREENLOCK_MODE_CHANGE_UNLOCK: + wl_list_for_each(seat, &server.input->seats, link) { + seat_set_exclusive_client(seat, NULL); + // copied from input_manager -- deduplicate? + struct sway_node *previous = seat_get_focus(seat); + 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); + } + } + // No animation, send finished now + wlr_screenlock_send_unlock_finished(server.screenlock); + break; + + case WLR_SCREENLOCK_MODE_CHANGE_ABANDON: + sway_log(SWAY_ERROR, "Lockscreen client died, showing fallback message"); + wl_list_for_each(seat, &server.input->seats, link) { + seat_set_exclusive_client(seat, PERMALOCK_CLIENT); + } + if (!server.permalock_message) { + server.permalock_message = draw_permalock_message(); + } + break; + } + + // redraw everything + for (int i = 0; i < root->outputs->length; ++i) { + struct sway_output *output = root->outputs->items[i]; + output_damage_whole(output); + } +} diff --git a/sway/meson.build b/sway/meson.build index 1402db154..e7b1adeda 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 b187fcd52..37f4a7c48 100644 --- a/sway/server.c +++ b/sway/server.c @@ -162,6 +162,11 @@ bool server_init(struct sway_server *server) { server->foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(server->wl_display); + server->screenlock = wlr_screenlock_manager_v1_create(server->wl_display); + server->screenlock_set_mode.notify = handle_lock_set_mode; + wl_signal_add(&server->screenlock->events.change_request, + &server->screenlock_set_mode); + server->drm_lease_manager= wlr_drm_lease_v1_manager_create(server->wl_display, server->backend); if (server->drm_lease_manager) {