Add support for screenlock protocol

This will always accept lock requests, including replacing a current
locker if present.

Based a prototype by: Manuel Stoeckl <code@mstoeckl.com>
This commit is contained in:
Daniel De Graaf 2021-09-05 15:17:45 -04:00
parent 30c28ff8f7
commit 2b2df8e3d8
8 changed files with 431 additions and 3 deletions

View file

@ -15,6 +15,7 @@
#include <wlr/types/wlr_output_power_management_v1.h> #include <wlr/types/wlr_output_power_management_v1.h>
#include <wlr/types/wlr_presentation_time.h> #include <wlr/types/wlr_presentation_time.h>
#include <wlr/types/wlr_relative_pointer_v1.h> #include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_screenlocker_v1.h>
#include <wlr/types/wlr_server_decoration.h> #include <wlr/types/wlr_server_decoration.h>
#include <wlr/types/wlr_text_input_v3.h> #include <wlr/types/wlr_text_input_v3.h>
#include <wlr/types/wlr_xdg_shell.h> #include <wlr/types/wlr_xdg_shell.h>
@ -85,6 +86,10 @@ struct sway_server {
struct wl_listener output_manager_apply; struct wl_listener output_manager_apply;
struct wl_listener output_manager_test; 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 wlr_output_power_manager_v1 *output_power_manager_v1;
struct wl_listener output_power_manager_set_mode; struct wl_listener output_power_manager_set_mode;
struct wlr_input_method_manager_v2 *input_method; 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_idle_inhibitor_v1(struct wl_listener *listener, void *data);
void handle_layer_shell_surface(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); void handle_xdg_shell_surface(struct wl_listener *listener, void *data);
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
void handle_xwayland_surface(struct wl_listener *listener, void *data); void handle_xwayland_surface(struct wl_listener *listener, void *data);

View file

@ -19,6 +19,7 @@ protocols = [
['idle.xml'], ['idle.xml'],
['wlr-input-inhibitor-unstable-v1.xml'], ['wlr-input-inhibitor-unstable-v1.xml'],
['wlr-output-power-management-unstable-v1.xml'], ['wlr-output-power-management-unstable-v1.xml'],
['wp-screenlocker-unstable-v1.xml'],
] ]
client_protocols = [ client_protocols = [

View file

@ -0,0 +1,166 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="zwp_screenlocker_v1">
<copyright>
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.
</copyright>
<interface name="zwp_screenlocker_v1" version="1">
<description summary="Screen locking manager">
This interface provides notification of screen lock/unlock events,
displaying windows on a locked screen, and acting as a screenlocker.
</description>
<request name="destroy" type="destructor">
<description summary="destroy this object"/>
</request>
<event name="locked">
<description summary="Triggered when the screen is locked">
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.
</description>
</event>
<event name="unlocked">
<description summary="Triggered when the screen is unlocked">
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.
</description>
</event>
<request name="lock">
<description summary="Request that this client become the screen locker">
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.
</description>
<arg name="id" type="new_id" interface="zwp_screenlocker_lock_v1"/>
</request>
<request name="get_visibility">
<description summary="set the visibility of a surface on the lockscreen">
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.
</description>
<arg name="id" type="new_id" interface="zwp_screenlocker_visibility_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
</interface>
<interface name="zwp_screenlocker_lock_v1" version="1">
<event name="rejected">
<description summary="The compositor has rejected the request">
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.
</description>
<arg name="reason" type="string" allow-null="true" summary="reason for the rejection"/>
</event>
<event name="locked">
<description summary="The lock request was accepted and the screen is locked">
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.
</description>
</event>
<event name="closed">
<description summary="This lock handle has been invalidated">
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.
</description>
</event>
<request name="unlock">
<description summary="Unlock the screen">
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.
</description>
</request>
<request name="destroy" type="destructor">
<description summary="destroy this object">
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.
</description>
</request>
</interface>
<interface name="zwp_screenlocker_visibility_v1" version="1">
<request name="destroy" type="destructor">
<description summary="destroy this object">
Destroying this object will revert the surface to not be visible on the
lockscreen at the next commit.
</description>
</request>
<enum name="visibility">
<entry name="default" value="0" summary="Only visible when unlocked"/>
<entry name="lock_only" value="1" summary="Only visible when locked"/>
<entry name="both" value="2" summary="Visible regardless of lock state"/>
</enum>
<request name="set_visibility">
<description summary="set this surface visible on the lockscreen">
This value is double-buffered, see wl_surface.commit.
</description>
<arg name="visibility" type="uint" enum="zwp_screenlocker_visibility_v1.visibility"/>
</request>
<event name="failed">
<description summary="the locked desktop cannot show this window">
This event is sent immediately if the associated window cannot be shown
on a locked desktop. This object is inert and should be destroyed.
</description>
</event>
</interface>
</protocol>

View file

@ -27,6 +27,7 @@
#include "sway/tree/root.h" #include "sway/tree/root.h"
#include "sway/tree/view.h" #include "sway/tree/view.h"
#include "sway/tree/workspace.h" #include "sway/tree/workspace.h"
#include "wp-screenlocker-unstable-v1-protocol.h"
struct render_data { struct render_data {
pixman_region32_t *damage; pixman_region32_t *damage;
@ -143,6 +144,20 @@ static void render_surface_iterator(struct sway_output *output,
return; 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; struct wlr_fbox src_box;
wlr_surface_get_buffer_source_box(surface, &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}); 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)) { if (output_has_opaque_overlay_layer_surface(output)) {
goto render_overlay; goto render_overlay;
} }
@ -1099,11 +1147,13 @@ void output_render(struct sway_output *output, struct timespec *when,
render_layer_toplevel(output, damage, render_layer_toplevel(output, damage,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
render_workspace(output, damage, workspace, workspace->current.focused); if (!server.screenlock->locked) {
render_floating(output, damage); render_workspace(output, damage, workspace, workspace->current.focused);
render_floating(output, damage);
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
render_unmanaged(output, damage, &root->xwayland_unmanaged); render_unmanaged(output, damage, &root->xwayland_unmanaged);
#endif #endif
}
render_layer_toplevel(output, damage, render_layer_toplevel(output, damage,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);

View file

@ -290,6 +290,11 @@ static void handle_inhibit_deactivate(struct wl_listener *listener, void *data)
listener, input_manager, inhibit_deactivate); listener, input_manager, inhibit_deactivate);
struct sway_seat *seat; struct sway_seat *seat;
wl_list_for_each(seat, &input_manager->seats, link) { 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); seat_set_exclusive_client(seat, NULL);
struct sway_node *previous = seat_get_focus(seat); struct sway_node *previous = seat_get_focus(seat);
if (previous) { if (previous) {

194
sway/lock.c Normal file
View file

@ -0,0 +1,194 @@
#define _POSIX_C_SOURCE 200809L
#include <drm_fourcc.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wlr/types/wlr_output_damage.h>
#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);
}
}

View file

@ -5,6 +5,7 @@ sway_sources = files(
'decoration.c', 'decoration.c',
'ipc-json.c', 'ipc-json.c',
'ipc-server.c', 'ipc-server.c',
'lock.c',
'main.c', 'main.c',
'server.c', 'server.c',
'swaynag.c', 'swaynag.c',

View file

@ -162,6 +162,11 @@ bool server_init(struct sway_server *server) {
server->foreign_toplevel_manager = server->foreign_toplevel_manager =
wlr_foreign_toplevel_manager_v1_create(server->wl_display); 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= server->drm_lease_manager=
wlr_drm_lease_v1_manager_create(server->wl_display, server->backend); wlr_drm_lease_v1_manager_create(server->wl_display, server->backend);
if (server->drm_lease_manager) { if (server->drm_lease_manager) {