From db9ee6d1277b119d213f58bfce6052458cdfecd1 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 27 Jan 2023 13:29:33 +0100 Subject: [PATCH 01/28] Add --ready-fd This implements a readiness notification mechanism which works on both systemd and s6. References: https://github.com/swaywm/swaylock/pull/42 References: https://github.com/swaywm/swaylock/pull/275 --- include/swaylock.h | 1 + main.c | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/include/swaylock.h b/include/swaylock.h index b9ddda7..e38dfe0 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -62,6 +62,7 @@ struct swaylock_args { bool hide_keyboard_layout; bool show_failed_attempts; bool daemonize; + int ready_fd; bool indicator_idle_visible; }; diff --git a/main.c b/main.c index 7b86168..68c7bac 100644 --- a/main.c +++ b/main.c @@ -579,6 +579,7 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, {"debug", no_argument, NULL, 'd'}, {"ignore-empty-password", no_argument, NULL, 'e'}, {"daemonize", no_argument, NULL, 'f'}, + {"ready-fd", required_argument, NULL, 'R'}, {"help", no_argument, NULL, 'h'}, {"image", required_argument, NULL, 'i'}, {"disable-caps-lock-text", no_argument, NULL, 'L'}, @@ -645,6 +646,8 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, "Show current count of failed authentication attempts.\n" " -f, --daemonize " "Detach from the controlling terminal after locking.\n" + " -R, --ready-fd " + "File descriptor to send readiness notifications to.\n" " -h, --help " "Show help message and quit.\n" " -i, --image [[]:] " @@ -754,7 +757,7 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, optind = 1; while (1) { int opt_idx = 0; - c = getopt_long(argc, argv, "c:deFfhi:kKLlnrs:tuvC:", long_options, + c = getopt_long(argc, argv, "c:deFfhi:kKLlnrs:tuvC:R:", long_options, &opt_idx); if (c == -1) { break; @@ -788,6 +791,11 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, state->args.daemonize = true; } break; + case 'R': + if (state) { + state->args.ready_fd = strtol(optarg, NULL, 10); + } + break; case 'i': if (state) { load_image(optarg, state); @@ -1182,7 +1190,8 @@ int main(int argc, char **argv) { .show_keyboard_layout = false, .hide_keyboard_layout = false, .show_failed_attempts = false, - .indicator_idle_visible = false + .indicator_idle_visible = false, + .ready_fd = -1, }; wl_list_init(&state.images); set_default_colors(&state.args.colors); @@ -1311,6 +1320,17 @@ int main(int argc, char **argv) { state.locked = true; } + if (state.args.ready_fd >= 0) { + // s6 wants a newline and ignores any text before that, systemd wants + // READY=1, so use the least common denominator + const char ready_str[] = "READY=1\n"; + if (write(state.args.ready_fd, ready_str, strlen(ready_str)) != strlen(ready_str)) { + swaylock_log(LOG_ERROR, "Failed to send readiness notification"); + return 2; + } + close(state.args.ready_fd); + state.args.ready_fd = -1; + } if (state.args.daemonize) { daemonize(); } From ac3b49b6571ceda3f8db11a98bfe320106996280 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 27 Nov 2022 14:18:29 +0100 Subject: [PATCH 02/28] Drop support for layer-shell Superseded by ext-session-lock-v1 --- README.md | 7 +- include/swaylock.h | 4 - main.c | 106 ++--------- meson.build | 3 - wlr-input-inhibitor-unstable-v1.xml | 67 ------- wlr-layer-shell-unstable-v1.xml | 285 ---------------------------- 6 files changed, 17 insertions(+), 455 deletions(-) delete mode 100644 wlr-input-inhibitor-unstable-v1.xml delete mode 100644 wlr-layer-shell-unstable-v1.xml diff --git a/README.md b/README.md index fde889f..d574ca5 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ # swaylock swaylock is a screen locking utility for Wayland compositors. It is compatible -with any Wayland compositor which implements one of the following Wayland -protocols: - -- ext-session-lock-v1, or -- wlr-layer-shell and wlr-input-inhibitor +with any Wayland compositor which implements the ext-session-lock-v1 Wayland +protocol. See the man page, `swaylock(1)`, for instructions on using swaylock. diff --git a/include/swaylock.h b/include/swaylock.h index e38dfe0..9de8ab6 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -7,7 +7,6 @@ #include "cairo.h" #include "pool-buffer.h" #include "seat.h" -#include "wlr-layer-shell-unstable-v1-client-protocol.h" enum auth_state { AUTH_STATE_IDLE, @@ -79,8 +78,6 @@ struct swaylock_state { struct wl_display *display; struct wl_compositor *compositor; struct wl_subcompositor *subcompositor; - struct zwlr_layer_shell_v1 *layer_shell; - struct zwlr_input_inhibit_manager_v1 *input_inhibit_manager; struct wl_shm *shm; struct wl_list surfaces; struct wl_list images; @@ -104,7 +101,6 @@ struct swaylock_surface { struct wl_surface *surface; struct wl_surface *child; // surface made into subsurface struct wl_subsurface *subsurface; - struct zwlr_layer_surface_v1 *layer_surface; struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1; struct pool_buffer buffers[2]; struct pool_buffer indicator_buffers[2]; diff --git a/main.c b/main.c index 68c7bac..26d30a5 100644 --- a/main.c +++ b/main.c @@ -25,8 +25,6 @@ #include "pool-buffer.h" #include "seat.h" #include "swaylock.h" -#include "wlr-input-inhibitor-unstable-v1-client-protocol.h" -#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "ext-session-lock-v1-client-protocol.h" static uint32_t parse_color(const char *color) { @@ -96,9 +94,6 @@ static void daemonize(void) { static void destroy_surface(struct swaylock_surface *surface) { wl_list_remove(&surface->link); - if (surface->layer_surface != NULL) { - zwlr_layer_surface_v1_destroy(surface->layer_surface); - } if (surface->ext_session_lock_surface_v1 != NULL) { ext_session_lock_surface_v1_destroy(surface->ext_session_lock_surface_v1); } @@ -113,7 +108,6 @@ static void destroy_surface(struct swaylock_surface *surface) { free(surface); } -static const struct zwlr_layer_surface_v1_listener layer_surface_listener; static const struct ext_session_lock_surface_v1_listener ext_session_lock_surface_v1_listener; static cairo_surface_t *select_image(struct swaylock_state *state, @@ -140,28 +134,10 @@ static void create_surface(struct swaylock_surface *surface) { assert(surface->subsurface); wl_subsurface_set_sync(surface->subsurface); - if (state->ext_session_lock_v1) { - surface->ext_session_lock_surface_v1 = ext_session_lock_v1_get_lock_surface( - state->ext_session_lock_v1, surface->surface, surface->output); - ext_session_lock_surface_v1_add_listener(surface->ext_session_lock_surface_v1, - &ext_session_lock_surface_v1_listener, surface); - } else { - surface->layer_surface = zwlr_layer_shell_v1_get_layer_surface( - state->layer_shell, surface->surface, surface->output, - ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "lockscreen"); - - zwlr_layer_surface_v1_set_size(surface->layer_surface, 0, 0); - zwlr_layer_surface_v1_set_anchor(surface->layer_surface, - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); - zwlr_layer_surface_v1_set_exclusive_zone(surface->layer_surface, -1); - zwlr_layer_surface_v1_set_keyboard_interactivity( - surface->layer_surface, true); - zwlr_layer_surface_v1_add_listener(surface->layer_surface, - &layer_surface_listener, surface); - } + surface->ext_session_lock_surface_v1 = ext_session_lock_v1_get_lock_surface( + state->ext_session_lock_v1, surface->surface, surface->output); + ext_session_lock_surface_v1_add_listener(surface->ext_session_lock_surface_v1, + &ext_session_lock_surface_v1_listener, surface); if (surface_is_opaque(surface) && surface->state->args.mode != BACKGROUND_MODE_CENTER && @@ -172,34 +148,8 @@ static void create_surface(struct swaylock_surface *surface) { wl_surface_set_opaque_region(surface->surface, region); wl_region_destroy(region); } - - if (!state->ext_session_lock_v1) { - wl_surface_commit(surface->surface); - } } -static void layer_surface_configure(void *data, - struct zwlr_layer_surface_v1 *layer_surface, - uint32_t serial, uint32_t width, uint32_t height) { - struct swaylock_surface *surface = data; - surface->width = width; - surface->height = height; - zwlr_layer_surface_v1_ack_configure(layer_surface, serial); - render_frame_background(surface); - render_frame(surface); -} - -static void layer_surface_closed(void *data, - struct zwlr_layer_surface_v1 *layer_surface) { - struct swaylock_surface *surface = data; - destroy_surface(surface); -} - -static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { - .configure = layer_surface_configure, - .closed = layer_surface_closed, -}; - static void ext_session_lock_surface_v1_handle_configure(void *data, struct ext_session_lock_surface_v1 *lock_surface, uint32_t serial, uint32_t width, uint32_t height) { @@ -347,12 +297,6 @@ static void handle_global(void *data, struct wl_registry *registry, calloc(1, sizeof(struct swaylock_seat)); swaylock_seat->state = state; wl_seat_add_listener(seat, &seat_listener, swaylock_seat); - } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { - state->layer_shell = wl_registry_bind( - registry, name, &zwlr_layer_shell_v1_interface, 1); - } else if (strcmp(interface, zwlr_input_inhibit_manager_v1_interface.name) == 0) { - state->input_inhibit_manager = wl_registry_bind( - registry, name, &zwlr_input_inhibit_manager_v1_interface, 1); } else if (strcmp(interface, wl_output_interface.name) == 0) { struct swaylock_surface *surface = calloc(1, sizeof(struct swaylock_surface)); @@ -1273,27 +1217,17 @@ int main(int argc, char **argv) { return 1; } - if (state.ext_session_lock_manager_v1) { - swaylock_log(LOG_DEBUG, "Using ext-session-lock-v1"); - state.ext_session_lock_v1 = ext_session_lock_manager_v1_lock(state.ext_session_lock_manager_v1); - ext_session_lock_v1_add_listener(state.ext_session_lock_v1, - &ext_session_lock_v1_listener, &state); - } else if (state.layer_shell && state.input_inhibit_manager) { - swaylock_log(LOG_DEBUG, "Using wlr-layer-shell + wlr-input-inhibitor"); - zwlr_input_inhibit_manager_v1_get_inhibitor(state.input_inhibit_manager); - } else { - swaylock_log(LOG_ERROR, "Missing ext-session-lock-v1, wlr-layer-shell " - "and wlr-input-inhibitor"); + if (!state.ext_session_lock_manager_v1) { + swaylock_log(LOG_ERROR, "Missing ext-session-lock-v1"); return 1; } + state.ext_session_lock_v1 = ext_session_lock_manager_v1_lock(state.ext_session_lock_manager_v1); + ext_session_lock_v1_add_listener(state.ext_session_lock_v1, + &ext_session_lock_v1_listener, &state); + if (wl_display_roundtrip(state.display) == -1) { free(state.args.font); - if (state.input_inhibit_manager) { - swaylock_log(LOG_ERROR, "Exiting - failed to inhibit input:" - " is another lockscreen already running?"); - return 2; - } return 1; } @@ -1305,19 +1239,11 @@ int main(int argc, char **argv) { create_surface(surface); } - if (state.ext_session_lock_manager_v1) { - while (!state.locked) { - if (wl_display_dispatch(state.display) < 0) { - swaylock_log(LOG_ERROR, "wl_display_dispatch() failed"); - return 2; - } - } - } else { - if (wl_display_roundtrip(state.display) < 0) { - swaylock_log(LOG_ERROR, "wl_display_roundtrip() failed"); + while (!state.locked) { + if (wl_display_dispatch(state.display) < 0) { + swaylock_log(LOG_ERROR, "wl_display_dispatch() failed"); return 2; } - state.locked = true; } if (state.args.ready_fd >= 0) { @@ -1353,10 +1279,8 @@ int main(int argc, char **argv) { loop_poll(state.eventloop); } - if (state.ext_session_lock_v1) { - ext_session_lock_v1_unlock_and_destroy(state.ext_session_lock_v1); - wl_display_roundtrip(state.display); - } + ext_session_lock_v1_unlock_and_destroy(state.ext_session_lock_v1); + wl_display_roundtrip(state.display); free(state.args.font); cairo_destroy(state.test_cairo); diff --git a/meson.build b/meson.build index cb45faf..ad7c4c7 100644 --- a/meson.build +++ b/meson.build @@ -68,10 +68,7 @@ wayland_scanner_client = generator( ) client_protocols = [ - wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', - 'wlr-layer-shell-unstable-v1.xml', - 'wlr-input-inhibitor-unstable-v1.xml', ] protos_src = [] diff --git a/wlr-input-inhibitor-unstable-v1.xml b/wlr-input-inhibitor-unstable-v1.xml deleted file mode 100644 index b62d1bb..0000000 --- a/wlr-input-inhibitor-unstable-v1.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - Copyright © 2018 Drew DeVault - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - Clients can use this interface to prevent input events from being sent to - any surfaces but its own, which is useful for example in lock screen - software. It is assumed that access to this interface will be locked down - to whitelisted clients by the compositor. - - - - - Activates the input inhibitor. As long as the inhibitor is active, the - compositor will not send input events to other clients. - - - - - - - - - - - - While this resource exists, input to clients other than the owner of the - inhibitor resource will not receive input events. The client that owns - this resource will receive all input events normally. The compositor will - also disable all of its own input processing (such as keyboard shortcuts) - while the inhibitor is active. - - The compositor may continue to send input events to selected clients, - such as an on-screen keyboard (via the input-method protocol). - - - - - Destroy the inhibitor and allow other clients to receive input. - - - - diff --git a/wlr-layer-shell-unstable-v1.xml b/wlr-layer-shell-unstable-v1.xml deleted file mode 100644 index f29eb87..0000000 --- a/wlr-layer-shell-unstable-v1.xml +++ /dev/null @@ -1,285 +0,0 @@ - - - - Copyright © 2017 Drew DeVault - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - Clients can use this interface to assign the surface_layer role to - wl_surfaces. Such surfaces are assigned to a "layer" of the output and - rendered with a defined z-depth respective to each other. They may also be - anchored to the edges and corners of a screen and specify input handling - semantics. This interface should be suitable for the implementation of - many desktop shell components, and a broad number of other applications - that interact with the desktop. - - - - - Create a layer surface for an existing surface. This assigns the role of - layer_surface, or raises a protocol error if another role is already - assigned. - - Creating a layer surface from a wl_surface which has a buffer attached - or committed is a client error, and any attempts by a client to attach - or manipulate a buffer prior to the first layer_surface.configure call - must also be treated as errors. - - You may pass NULL for output to allow the compositor to decide which - output to use. Generally this will be the one that the user most - recently interacted with. - - Clients can specify a namespace that defines the purpose of the layer - surface. - - - - - - - - - - - - - - - - - These values indicate which layers a surface can be rendered in. They - are ordered by z depth, bottom-most first. Traditional shell surfaces - will typically be rendered between the bottom and top layers. - Fullscreen shell surfaces are typically rendered at the top layer. - Multiple surfaces can share a single layer, and ordering within a - single layer is undefined. - - - - - - - - - - - - An interface that may be implemented by a wl_surface, for surfaces that - are designed to be rendered as a layer of a stacked desktop-like - environment. - - Layer surface state (size, anchor, exclusive zone, margin, interactivity) - is double-buffered, and will be applied at the time wl_surface.commit of - the corresponding wl_surface is called. - - - - - Sets the size of the surface in surface-local coordinates. The - compositor will display the surface centered with respect to its - anchors. - - If you pass 0 for either value, the compositor will assign it and - inform you of the assignment in the configure event. You must set your - anchor to opposite edges in the dimensions you omit; not doing so is a - protocol error. Both values are 0 by default. - - Size is double-buffered, see wl_surface.commit. - - - - - - - - Requests that the compositor anchor the surface to the specified edges - and corners. If two orthoginal edges are specified (e.g. 'top' and - 'left'), then the anchor point will be the intersection of the edges - (e.g. the top left corner of the output); otherwise the anchor point - will be centered on that edge, or in the center if none is specified. - - Anchor is double-buffered, see wl_surface.commit. - - - - - - - Requests that the compositor avoids occluding an area of the surface - with other surfaces. The compositor's use of this information is - implementation-dependent - do not assume that this region will not - actually be occluded. - - A positive value is only meaningful if the surface is anchored to an - edge, rather than a corner. The zone is the number of surface-local - coordinates from the edge that are considered exclusive. - - Surfaces that do not wish to have an exclusive zone may instead specify - how they should interact with surfaces that do. If set to zero, the - surface indicates that it would like to be moved to avoid occluding - surfaces with a positive excluzive zone. If set to -1, the surface - indicates that it would not like to be moved to accommodate for other - surfaces, and the compositor should extend it all the way to the edges - it is anchored to. - - For example, a panel might set its exclusive zone to 10, so that - maximized shell surfaces are not shown on top of it. A notification - might set its exclusive zone to 0, so that it is moved to avoid - occluding the panel, but shell surfaces are shown underneath it. A - wallpaper or lock screen might set their exclusive zone to -1, so that - they stretch below or over the panel. - - The default value is 0. - - Exclusive zone is double-buffered, see wl_surface.commit. - - - - - - - Requests that the surface be placed some distance away from the anchor - point on the output, in surface-local coordinates. Setting this value - for edges you are not anchored to has no effect. - - The exclusive zone includes the margin. - - Margin is double-buffered, see wl_surface.commit. - - - - - - - - - - Set to 1 to request that the seat send keyboard events to this layer - surface. For layers below the shell surface layer, the seat will use - normal focus semantics. For layers above the shell surface layers, the - seat will always give exclusive keyboard focus to the top-most layer - which has keyboard interactivity set to true. - - Layer surfaces receive pointer, touch, and tablet events normally. If - you do not want to receive them, set the input region on your surface - to an empty region. - - Events is double-buffered, see wl_surface.commit. - - - - - - - This assigns an xdg_popup's parent to this layer_surface. This popup - should have been created via xdg_surface::get_popup with the parent set - to NULL, and this request must be invoked before committing the popup's - initial state. - - See the documentation of xdg_popup for more details about what an - xdg_popup is and how it is used. - - - - - - - When a configure event is received, if a client commits the - surface in response to the configure event, then the client - must make an ack_configure request sometime before the commit - request, passing along the serial of the configure event. - - If the client receives multiple configure events before it - can respond to one, it only has to ack the last configure event. - - A client is not required to commit immediately after sending - an ack_configure request - it may even ack_configure several times - before its next surface commit. - - A client may send multiple ack_configure requests before committing, but - only the last request sent before a commit indicates which configure - event the client really is responding to. - - - - - - - This request destroys the layer surface. - - - - - - The configure event asks the client to resize its surface. - - Clients should arrange their surface for the new states, and then send - an ack_configure request with the serial sent in this configure event at - some point before committing the new surface. - - The client is free to dismiss all but the last configure event it - received. - - The width and height arguments specify the size of the window in - surface-local coordinates. - - The size is a hint, in the sense that the client is free to ignore it if - it doesn't resize, pick a smaller size (to satisfy aspect ratio or - resize in steps of NxM pixels). If the client picks a smaller size and - is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the - surface will be centered on this axis. - - If the width or height arguments are zero, it means the client should - decide its own window dimension. - - - - - - - - - The closed event is sent by the compositor when the surface will no - longer be shown. The output may have been destroyed or the user may - have asked for it to be removed. Further changes to the surface will be - ignored. The client should destroy the resource after receiving this - event, and create a new surface if they so choose. - - - - - - - - - - - - - - - - - From 1d3e62c67f07a180fca02a6df1cf9648f2b50349 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Sun, 12 Mar 2023 16:48:58 -0400 Subject: [PATCH 03/28] Stop pooling background surface buffers The wl_buffers for the background surface only need to be updated when the output dimensions change. Using the fixed pool of two buffers to cache these buffers does not help, since if a new buffer is needed, it will have a different size than whatever buffers were cached. Furthermore, because the pool has fixed size, it is possible to run out of buffers if configure events arrive faster than pool buffers are marked not busy, which can lead to protocol errors when the background surface is committed after acknowledging a new size, but without attaching a buffer that matches that size. --- include/pool-buffer.h | 4 +++- include/swaylock.h | 7 ++++--- main.c | 2 -- pool-buffer.c | 2 +- render.c | 49 ++++++++++++++++++++++++++----------------- 5 files changed, 38 insertions(+), 26 deletions(-) diff --git a/include/pool-buffer.h b/include/pool-buffer.h index 0ebf787..38e8218 100644 --- a/include/pool-buffer.h +++ b/include/pool-buffer.h @@ -15,8 +15,10 @@ struct pool_buffer { bool busy; }; +struct pool_buffer *create_buffer(struct wl_shm *shm, struct pool_buffer *buf, + int32_t width, int32_t height, uint32_t format); struct pool_buffer *get_next_buffer(struct wl_shm *shm, - struct pool_buffer pool[static 2], uint32_t width, uint32_t height); + struct pool_buffer pool[static 2], uint32_t width, uint32_t height); void destroy_buffer(struct pool_buffer *buffer); #endif diff --git a/include/swaylock.h b/include/swaylock.h index 9de8ab6..bb71c8c 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -98,11 +98,10 @@ struct swaylock_surface { struct swaylock_state *state; struct wl_output *output; uint32_t output_global_name; - struct wl_surface *surface; - struct wl_surface *child; // surface made into subsurface + struct wl_surface *surface; // surface for background + struct wl_surface *child; // indicator surface made into subsurface struct wl_subsurface *subsurface; struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1; - struct pool_buffer buffers[2]; struct pool_buffer indicator_buffers[2]; bool frame_pending, dirty; uint32_t width, height; @@ -110,6 +109,8 @@ struct swaylock_surface { enum wl_output_subpixel subpixel; char *output_name; struct wl_list link; + // Dimensions of last wl_buffer committed to background surface + int last_buffer_width, last_buffer_height; }; // There is exactly one swaylock_image for each -i argument diff --git a/main.c b/main.c index 26d30a5..ca89be7 100644 --- a/main.c +++ b/main.c @@ -100,8 +100,6 @@ static void destroy_surface(struct swaylock_surface *surface) { if (surface->surface != NULL) { wl_surface_destroy(surface->surface); } - destroy_buffer(&surface->buffers[0]); - destroy_buffer(&surface->buffers[1]); destroy_buffer(&surface->indicator_buffers[0]); destroy_buffer(&surface->indicator_buffers[1]); wl_output_destroy(surface->output); diff --git a/pool-buffer.c b/pool-buffer.c index 02884de..dfb4d0d 100644 --- a/pool-buffer.c +++ b/pool-buffer.c @@ -46,7 +46,7 @@ static const struct wl_buffer_listener buffer_listener = { .release = buffer_release }; -static struct pool_buffer *create_buffer(struct wl_shm *shm, +struct pool_buffer *create_buffer(struct wl_shm *shm, struct pool_buffer *buf, int32_t width, int32_t height, uint32_t format) { uint32_t stride = width * 4; diff --git a/render.c b/render.c index 3ce7a80..b7febc1 100644 --- a/render.c +++ b/render.c @@ -4,6 +4,7 @@ #include "cairo.h" #include "background-image.h" #include "swaylock.h" +#include "log.h" #define M_PI 3.14159265358979323846 const float TYPE_INDICATOR_RANGE = M_PI / 3.0f; @@ -41,30 +42,40 @@ void render_frame_background(struct swaylock_surface *surface) { return; // not yet configured } - struct pool_buffer *buffer = get_next_buffer(state->shm, - surface->buffers, buffer_width, buffer_height); - if (buffer == NULL) { - return; - } + if (buffer_width != surface->last_buffer_width || + buffer_height != surface->last_buffer_height) { + struct pool_buffer buffer; + if (!create_buffer(state->shm, &buffer, buffer_width, buffer_height, + WL_SHM_FORMAT_ARGB8888)) { + swaylock_log(LOG_ERROR, + "Failed to create new buffer for frame background."); + return; + } - cairo_t *cairo = buffer->cairo; - cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); + cairo_t *cairo = buffer.cairo; + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); - cairo_save(cairo); - cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); - cairo_set_source_u32(cairo, state->args.colors.background); - cairo_paint(cairo); - if (surface->image && state->args.mode != BACKGROUND_MODE_SOLID_COLOR) { - cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); - render_background_image(cairo, surface->image, - state->args.mode, buffer_width, buffer_height); + cairo_save(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_u32(cairo, state->args.colors.background); + cairo_paint(cairo); + if (surface->image && state->args.mode != BACKGROUND_MODE_SOLID_COLOR) { + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + render_background_image(cairo, surface->image, + state->args.mode, buffer_width, buffer_height); + } + cairo_restore(cairo); + cairo_identity_matrix(cairo); + + wl_surface_attach(surface->surface, buffer.buffer, 0, 0); + wl_surface_damage_buffer(surface->surface, 0, 0, INT32_MAX, INT32_MAX); + destroy_buffer(&buffer); + + surface->last_buffer_width = buffer_width; + surface->last_buffer_height = buffer_height; } - cairo_restore(cairo); - cairo_identity_matrix(cairo); wl_surface_set_buffer_scale(surface->surface, surface->scale); - wl_surface_attach(surface->surface, buffer->buffer, 0, 0); - wl_surface_damage_buffer(surface->surface, 0, 0, INT32_MAX, INT32_MAX); wl_surface_commit(surface->surface); } From 75e837c31abe2fa06167b6f1f1a253ab397faf81 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Tue, 28 Mar 2023 19:42:19 -0400 Subject: [PATCH 04/28] Synchronize highlight position between outputs This change has the additional benefit of ensuring that the position of the highlight only changes in reaction to a letter key or backspace being pressed, and not when the compositor sends a new configure event or the output needs to be redrawn for some other reason. --- include/swaylock.h | 1 + password.c | 8 ++++++++ render.c | 4 +--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/include/swaylock.h b/include/swaylock.h index bb71c8c..d855627 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -87,6 +87,7 @@ struct swaylock_state { cairo_surface_t *test_surface; cairo_t *test_cairo; // used to estimate font/text sizes enum auth_state auth_state; + uint32_t highlight_start; // position of highlight; 2048 = 1 full turn int failed_attempts; bool run_display, locked; struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1; diff --git a/password.c b/password.c index a3e7674..aa3d101 100644 --- a/password.c +++ b/password.c @@ -93,6 +93,12 @@ static void submit_password(struct swaylock_state *state) { damage_state(state); } +static void update_highlight(struct swaylock_state *state) { + // Advance a random amount between 1/4 and 3/4 of a full turn + state->highlight_start = + (state->highlight_start + (rand() % 1024) + 512) % 2048; +} + void swaylock_handle_key(struct swaylock_state *state, xkb_keysym_t keysym, uint32_t codepoint) { // Ignore input events if validating @@ -109,6 +115,7 @@ void swaylock_handle_key(struct swaylock_state *state, case XKB_KEY_BackSpace: if (backspace(&state->password)) { state->auth_state = AUTH_STATE_BACKSPACE; + update_highlight(state); } else { state->auth_state = AUTH_STATE_CLEAR; } @@ -160,6 +167,7 @@ void swaylock_handle_key(struct swaylock_state *state, if (codepoint) { append_ch(&state->password, codepoint); state->auth_state = AUTH_STATE_INPUT; + update_highlight(state); damage_state(state); schedule_indicator_clear(state); schedule_password_clear(state); diff --git a/render.c b/render.c index b7febc1..fddacf3 100644 --- a/render.c +++ b/render.c @@ -271,9 +271,7 @@ void render_frame(struct swaylock_surface *surface) { // Typing indicator: Highlight random part on keypress if (state->auth_state == AUTH_STATE_INPUT || state->auth_state == AUTH_STATE_BACKSPACE) { - static double highlight_start = 0; - highlight_start += - (rand() % (int)(M_PI * 100)) / 100.0 + M_PI * 0.5; + double highlight_start = state->highlight_start * (M_PI / 1024.0); cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, arc_radius, highlight_start, highlight_start + TYPE_INDICATOR_RANGE); From 876965f944f127a32c0265bcaa47f716d9e1451b Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Fri, 2 Sep 2022 18:42:49 +0200 Subject: [PATCH 05/28] Accept input while validating Allow typing new input while the previous one is validating. Can be tested with: ninja -C build && sway -c sway.conf Where sway.config is: exec ./build/swaylock Fixes: https://github.com/swaywm/swaylock/issues/241 --- password.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/password.c b/password.c index aa3d101..c4950b3 100644 --- a/password.c +++ b/password.c @@ -101,10 +101,6 @@ static void update_highlight(struct swaylock_state *state) { void swaylock_handle_key(struct swaylock_state *state, xkb_keysym_t keysym, uint32_t codepoint) { - // Ignore input events if validating - if (state->auth_state == AUTH_STATE_VALIDATING) { - return; - } switch (keysym) { case XKB_KEY_KP_Enter: /* fallthrough */ From 31ebd85fe09701146b7275de1ec8c8e9ce3c15c6 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Tue, 2 May 2023 21:00:41 -0400 Subject: [PATCH 06/28] Separate input and auth state This commit establishes separate state machines for auth state (whether the password submitted is being verified or is wrong) and input state (typing indicators and clear message -- things relevant to the state of the password being typed in, before it is submitted.) This makes it possible to display the auth state while updating the input state (for example, show that the previously submitted password is 'verifying' or 'wrong' while typing another.) The two state machines interact only when submitting a password. There is some interference with the rendering code -- a 'cleared' message from the input state machine supersedes verifying/wrong messages from the auth state machine; although since the 'clear' state has a shorter timeout than the auth 'invalid' state, this is unlikely to hide the 'wrong' message. --- include/swaylock.h | 28 ++++++++++------ main.c | 2 +- password.c | 83 ++++++++++++++++++++++++++++++++-------------- render.c | 44 +++++++++++------------- 4 files changed, 98 insertions(+), 59 deletions(-) diff --git a/include/swaylock.h b/include/swaylock.h index d855627..0eea1b3 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -8,14 +8,20 @@ #include "pool-buffer.h" #include "seat.h" +// Indicator state: status of authentication attempt enum auth_state { - AUTH_STATE_IDLE, - AUTH_STATE_CLEAR, - AUTH_STATE_INPUT, - AUTH_STATE_INPUT_NOP, - AUTH_STATE_BACKSPACE, - AUTH_STATE_VALIDATING, - AUTH_STATE_INVALID, + AUTH_STATE_IDLE, // nothing happening + AUTH_STATE_VALIDATING, // currently validating password + AUTH_STATE_INVALID, // displaying message: password was wrong +}; + +// Indicator state: status of password buffer / typing letters +enum input_state { + INPUT_STATE_IDLE, // nothing happening; other states decay to this after time + INPUT_STATE_CLEAR, // displaying message: password buffer was cleared + INPUT_STATE_LETTER, // pressed a key that input a letter + INPUT_STATE_BACKSPACE, // pressed backspace and removed a letter + INPUT_STATE_NEUTRAL, // pressed a key (like Ctrl) that did nothing }; struct swaylock_colorset { @@ -73,7 +79,8 @@ struct swaylock_password { struct swaylock_state { struct loop *eventloop; - struct loop_timer *clear_indicator_timer; // clears the indicator + struct loop_timer *input_idle_timer; // timer to reset input state to IDLE + struct loop_timer *auth_idle_timer; // timer to stop displaying AUTH_STATE_INVALID struct loop_timer *clear_password_timer; // clears the password buffer struct wl_display *display; struct wl_compositor *compositor; @@ -86,7 +93,8 @@ struct swaylock_state { struct swaylock_xkb xkb; cairo_surface_t *test_surface; cairo_t *test_cairo; // used to estimate font/text sizes - enum auth_state auth_state; + enum auth_state auth_state; // state of the authentication attempt + enum input_state input_state; // state of the password buffer and key inputs uint32_t highlight_start; // position of highlight; 2048 = 1 full turn int failed_attempts; bool run_display, locked; @@ -129,7 +137,7 @@ void render_frame(struct swaylock_surface *surface); void damage_surface(struct swaylock_surface *surface); void damage_state(struct swaylock_state *state); void clear_password_buffer(struct swaylock_password *pw); -void schedule_indicator_clear(struct swaylock_state *state); +void schedule_auth_idle(struct swaylock_state *state); void initialize_pw_backend(int argc, char **argv); void run_pw_backend_child(void); diff --git a/main.c b/main.c index ca89be7..fe018b5 100644 --- a/main.c +++ b/main.c @@ -1072,7 +1072,7 @@ static void comm_in(int fd, short mask, void *data) { state.run_display = false; } else { state.auth_state = AUTH_STATE_INVALID; - schedule_indicator_clear(&state); + schedule_auth_idle(&state); ++state.failed_attempts; damage_state(&state); } diff --git a/password.c b/password.c index c4950b3..8dabb52 100644 --- a/password.c +++ b/password.c @@ -46,28 +46,50 @@ static void append_ch(struct swaylock_password *pw, uint32_t codepoint) { pw->len += utf8_size; } -static void clear_indicator(void *data) { +static void set_input_idle(void *data) { struct swaylock_state *state = data; - state->clear_indicator_timer = NULL; + state->input_idle_timer = NULL; + state->input_state = INPUT_STATE_IDLE; + damage_state(state); +} + +static void set_auth_idle(void *data) { + struct swaylock_state *state = data; + state->auth_idle_timer = NULL; state->auth_state = AUTH_STATE_IDLE; damage_state(state); } -void schedule_indicator_clear(struct swaylock_state *state) { - if (state->clear_indicator_timer) { - loop_remove_timer(state->eventloop, state->clear_indicator_timer); +static void schedule_input_idle(struct swaylock_state *state) { + if (state->input_idle_timer) { + loop_remove_timer(state->eventloop, state->input_idle_timer); } - state->clear_indicator_timer = loop_add_timer( - state->eventloop, 3000, clear_indicator, state); + state->input_idle_timer = loop_add_timer( + state->eventloop, 1500, set_input_idle, state); +} + +static void cancel_input_idle(struct swaylock_state *state) { + if (state->input_idle_timer) { + loop_remove_timer(state->eventloop, state->input_idle_timer); + state->input_idle_timer = NULL; + } +} + +void schedule_auth_idle(struct swaylock_state *state) { + if (state->auth_idle_timer) { + loop_remove_timer(state->eventloop, state->auth_idle_timer); + } + state->auth_idle_timer = loop_add_timer( + state->eventloop, 3000, set_auth_idle, state); } static void clear_password(void *data) { struct swaylock_state *state = data; state->clear_password_timer = NULL; - state->auth_state = AUTH_STATE_CLEAR; + state->input_state = INPUT_STATE_CLEAR; + schedule_input_idle(state); clear_password_buffer(&state->password); damage_state(state); - schedule_indicator_clear(state); } static void schedule_password_clear(struct swaylock_state *state) { @@ -78,16 +100,26 @@ static void schedule_password_clear(struct swaylock_state *state) { state->eventloop, 10000, clear_password, state); } +static void cancel_password_clear(struct swaylock_state *state) { + if (state->clear_password_timer) { + loop_remove_timer(state->eventloop, state->clear_password_timer); + state->clear_password_timer = NULL; + } +} + static void submit_password(struct swaylock_state *state) { if (state->args.ignore_empty && state->password.len == 0) { return; } + state->input_state = INPUT_STATE_IDLE; state->auth_state = AUTH_STATE_VALIDATING; + cancel_password_clear(state); + cancel_input_idle(state); if (!write_comm_request(&state->password)) { state->auth_state = AUTH_STATE_INVALID; - schedule_indicator_clear(state); + schedule_auth_idle(state); } damage_state(state); @@ -110,20 +142,22 @@ void swaylock_handle_key(struct swaylock_state *state, case XKB_KEY_Delete: case XKB_KEY_BackSpace: if (backspace(&state->password)) { - state->auth_state = AUTH_STATE_BACKSPACE; + state->input_state = INPUT_STATE_BACKSPACE; + schedule_password_clear(state); update_highlight(state); } else { - state->auth_state = AUTH_STATE_CLEAR; + state->input_state = INPUT_STATE_CLEAR; + cancel_password_clear(state); } + schedule_input_idle(state); damage_state(state); - schedule_indicator_clear(state); - schedule_password_clear(state); break; case XKB_KEY_Escape: clear_password_buffer(&state->password); - state->auth_state = AUTH_STATE_CLEAR; + state->input_state = INPUT_STATE_CLEAR; + cancel_password_clear(state); + schedule_input_idle(state); damage_state(state); - schedule_indicator_clear(state); break; case XKB_KEY_Caps_Lock: case XKB_KEY_Shift_L: @@ -136,10 +170,10 @@ void swaylock_handle_key(struct swaylock_state *state, case XKB_KEY_Alt_R: case XKB_KEY_Super_L: case XKB_KEY_Super_R: - state->auth_state = AUTH_STATE_INPUT_NOP; - damage_state(state); - schedule_indicator_clear(state); + state->input_state = INPUT_STATE_NEUTRAL; schedule_password_clear(state); + schedule_input_idle(state); + damage_state(state); break; case XKB_KEY_m: /* fallthrough */ case XKB_KEY_d: @@ -153,20 +187,21 @@ void swaylock_handle_key(struct swaylock_state *state, case XKB_KEY_u: if (state->xkb.control) { clear_password_buffer(&state->password); - state->auth_state = AUTH_STATE_CLEAR; + state->input_state = INPUT_STATE_CLEAR; + cancel_password_clear(state); + schedule_input_idle(state); damage_state(state); - schedule_indicator_clear(state); break; } // fallthrough default: if (codepoint) { append_ch(&state->password, codepoint); - state->auth_state = AUTH_STATE_INPUT; + state->input_state = INPUT_STATE_LETTER; + schedule_password_clear(state); + schedule_input_idle(state); update_highlight(state); damage_state(state); - schedule_indicator_clear(state); - schedule_password_clear(state); } break; } diff --git a/render.c b/render.c index fddacf3..b7d1847 100644 --- a/render.c +++ b/render.c @@ -12,12 +12,12 @@ const float TYPE_INDICATOR_BORDER_THICKNESS = M_PI / 128.0f; static void set_color_for_state(cairo_t *cairo, struct swaylock_state *state, struct swaylock_colorset *colorset) { - if (state->auth_state == AUTH_STATE_VALIDATING) { + if (state->input_state == INPUT_STATE_CLEAR) { + cairo_set_source_u32(cairo, colorset->cleared); + } else if (state->auth_state == AUTH_STATE_VALIDATING) { cairo_set_source_u32(cairo, colorset->verifying); } else if (state->auth_state == AUTH_STATE_INVALID) { cairo_set_source_u32(cairo, colorset->wrong); - } else if (state->auth_state == AUTH_STATE_CLEAR) { - cairo_set_source_u32(cairo, colorset->cleared); } else { if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { cairo_set_source_u32(cairo, colorset->caps_lock); @@ -107,20 +107,20 @@ void render_frame(struct swaylock_surface *surface) { char *text = NULL; const char *layout_text = NULL; - if (state->args.show_indicator) { - switch (state->auth_state) { - case AUTH_STATE_VALIDATING: - text = "Verifying"; - break; - case AUTH_STATE_INVALID: - text = "Wrong"; - break; - case AUTH_STATE_CLEAR: + bool draw_indicator = state->args.show_indicator && + (state->auth_state != AUTH_STATE_IDLE || + state->input_state != INPUT_STATE_IDLE || + state->args.indicator_idle_visible); + + if (draw_indicator) { + if (state->input_state == INPUT_STATE_CLEAR) { + // This message has highest priority text = "Cleared"; - break; - case AUTH_STATE_INPUT: - case AUTH_STATE_INPUT_NOP: - case AUTH_STATE_BACKSPACE: + } else if (state->auth_state == AUTH_STATE_VALIDATING) { + text = "Verifying"; + } else if (state->auth_state == AUTH_STATE_INVALID) { + text = "Wrong"; + } else { // Caps Lock has higher priority if (state->xkb.caps_lock && state->args.show_caps_lock_text) { text = "Caps Lock"; @@ -148,9 +148,6 @@ void render_frame(struct swaylock_surface *surface) { // will handle invalid index if none are active layout_text = xkb_keymap_layout_get_name(state->xkb.keymap, curr_layout); } - break; - default: - break; } } @@ -230,8 +227,7 @@ void render_frame(struct swaylock_surface *surface) { float type_indicator_border_thickness = TYPE_INDICATOR_BORDER_THICKNESS * surface->scale; - if (state->args.show_indicator && (state->auth_state != AUTH_STATE_IDLE || - state->args.indicator_idle_visible)) { + if (draw_indicator) { // Fill inner circle cairo_set_line_width(cairo, 0); cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, @@ -269,13 +265,13 @@ void render_frame(struct swaylock_surface *surface) { } // Typing indicator: Highlight random part on keypress - if (state->auth_state == AUTH_STATE_INPUT - || state->auth_state == AUTH_STATE_BACKSPACE) { + if (state->input_state == INPUT_STATE_LETTER || + state->input_state == INPUT_STATE_BACKSPACE) { double highlight_start = state->highlight_start * (M_PI / 1024.0); cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, arc_radius, highlight_start, highlight_start + TYPE_INDICATOR_RANGE); - if (state->auth_state == AUTH_STATE_INPUT) { + if (state->input_state == INPUT_STATE_LETTER) { if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { cairo_set_source_u32(cairo, state->args.colors.caps_lock_key_highlight); } else { From 2018673e1df020fba73870a9f65314d195d72769 Mon Sep 17 00:00:00 2001 From: Sophie Winter Date: Thu, 8 Jun 2023 23:32:52 -0400 Subject: [PATCH 07/28] Don't drop the buffer until after surface commit --- render.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/render.c b/render.c index b7d1847..e2d76f7 100644 --- a/render.c +++ b/render.c @@ -42,6 +42,8 @@ void render_frame_background(struct swaylock_surface *surface) { return; // not yet configured } + wl_surface_set_buffer_scale(surface->surface, surface->scale); + if (buffer_width != surface->last_buffer_width || buffer_height != surface->last_buffer_height) { struct pool_buffer buffer; @@ -69,14 +71,14 @@ void render_frame_background(struct swaylock_surface *surface) { wl_surface_attach(surface->surface, buffer.buffer, 0, 0); wl_surface_damage_buffer(surface->surface, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_commit(surface->surface); destroy_buffer(&buffer); surface->last_buffer_width = buffer_width; surface->last_buffer_height = buffer_height; + } else { + wl_surface_commit(surface->surface); } - - wl_surface_set_buffer_scale(surface->surface, surface->scale); - wl_surface_commit(surface->surface); } static void configure_font_drawing(cairo_t *cairo, struct swaylock_state *state, From 10df946671a12b4c0f4c13d4556a8bae6fc30394 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Sun, 6 Aug 2023 13:13:07 -0400 Subject: [PATCH 08/28] Fix Wayland object leaks when outputs are destroyed --- main.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index fe018b5..d6eeae6 100644 --- a/main.c +++ b/main.c @@ -97,12 +97,18 @@ static void destroy_surface(struct swaylock_surface *surface) { if (surface->ext_session_lock_surface_v1 != NULL) { ext_session_lock_surface_v1_destroy(surface->ext_session_lock_surface_v1); } + if (surface->subsurface) { + wl_subsurface_destroy(surface->subsurface); + } + if (surface->child) { + wl_surface_destroy(surface->child); + } if (surface->surface != NULL) { wl_surface_destroy(surface->surface); } destroy_buffer(&surface->indicator_buffers[0]); destroy_buffer(&surface->indicator_buffers[1]); - wl_output_destroy(surface->output); + wl_output_release(surface->output); free(surface); } From f692ee00757d38022d0f634e6c41072cf4814d1b Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 5 Oct 2023 17:11:03 +0200 Subject: [PATCH 09/28] Don't send READY=1 for readiness notifications Just send a singular newline like s6 expects. systemd doesn't support spawning a process with an FD to send readiness notifications to, instead it provides a socket name. IOW, this cannot be used directly with systemd after all. Closes: https://github.com/swaywm/swaylock/issues/312 --- main.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/main.c b/main.c index d6eeae6..7d5f3bb 100644 --- a/main.c +++ b/main.c @@ -1251,10 +1251,7 @@ int main(int argc, char **argv) { } if (state.args.ready_fd >= 0) { - // s6 wants a newline and ignores any text before that, systemd wants - // READY=1, so use the least common denominator - const char ready_str[] = "READY=1\n"; - if (write(state.args.ready_fd, ready_str, strlen(ready_str)) != strlen(ready_str)) { + if (write(state.args.ready_fd, "\n", 1) != 1) { swaylock_log(LOG_ERROR, "Failed to send readiness notification"); return 2; } From 0569a47ef78346c1f79506ac1c0d0782febe42cf Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Sat, 7 Oct 2023 13:01:22 -0400 Subject: [PATCH 10/28] Configure SIGUSR1 with sigaction() instead of signal() If signal() is used to set a signal handler, only the first signal received will use the handler; any later signals use the default behavior (making swaylock terminate immediately, without unlocking). Using sigaction() ensures that the handler will be used every time. --- main.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index 7d5f3bb..71c4c97 100644 --- a/main.c +++ b/main.c @@ -1269,7 +1269,12 @@ int main(int argc, char **argv) { loop_add_fd(state.eventloop, get_comm_reply_fd(), POLLIN, comm_in, NULL); loop_add_fd(state.eventloop, sigusr_fds[0], POLLIN, term_in, NULL); - signal(SIGUSR1, do_sigusr); + + struct sigaction sa; + sa.sa_handler = do_sigusr; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGUSR1, &sa, NULL); state.run_display = true; while (state.run_display) { From ccd31553f335f5a67d01e4ddf00b5ed73415e4ca Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Fri, 6 Oct 2023 06:18:06 -0400 Subject: [PATCH 11/28] Make self-pipe nonblocking to prevent deadlock If a large number of signals is sent, it is possible to fill the buffer for sigusr_fds[1] before the main loop has a chance to read from it; then the signal handler do_sigusr() will block on write. --- main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index 71c4c97..d27cddf 100644 --- a/main.c +++ b/main.c @@ -1188,7 +1188,11 @@ int main(int argc, char **argv) { if (pipe(sigusr_fds) != 0) { swaylock_log(LOG_ERROR, "Failed to pipe"); - return 1; + return EXIT_FAILURE; + } + if (fcntl(sigusr_fds[1], F_SETFL, O_NONBLOCK) == -1) { + swaylock_log(LOG_ERROR, "Failed to make pipe end nonblocking"); + return EXIT_FAILURE; } wl_list_init(&state.surfaces); From 51e9e6ceda19b3c0c5f67ec4b0885edf2a2cca37 Mon Sep 17 00:00:00 2001 From: Max Kunzelmann Date: Fri, 17 Nov 2023 13:40:25 +0100 Subject: [PATCH 12/28] Fix retry behaviour in while loop with mlock() If mlock() fails with errno EAGAIN, it should be retried up to five times, which is tracked in the retries variable. However, the `return false` statement after the switch case makes the function return false after the first failed mlock() call. Remove this statement to actually retry up to five times. Signed-off-by: Max Kunzelmann --- password-buffer.c | 1 - 1 file changed, 1 deletion(-) diff --git a/password-buffer.c b/password-buffer.c index b8b7f94..7b1e465 100644 --- a/password-buffer.c +++ b/password-buffer.c @@ -38,7 +38,6 @@ static bool password_buffer_lock(char *addr, size_t size) { swaylock_log_errno(LOG_ERROR, "Unable to mlock() password memory."); return false; } - return false; } return true; From 7cecd395a2ad64b25f40638be25d0e5f8080cd18 Mon Sep 17 00:00:00 2001 From: Lyle Hanson Date: Sun, 26 Nov 2023 16:36:30 -0500 Subject: [PATCH 13/28] Link to manpage The README for `swayidle` has a convenient link to the manpage, following suit here. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d574ca5..ec345c3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ swaylock is a screen locking utility for Wayland compositors. It is compatible with any Wayland compositor which implements the ext-session-lock-v1 Wayland protocol. -See the man page, `swaylock(1)`, for instructions on using swaylock. +See the man page, [swaylock(1)](swaylock.1.scd), for instructions on using swaylock. ## Release Signatures From f2298bdbf788678a9e08fd17a3e37600767f5a9b Mon Sep 17 00:00:00 2001 From: prezmop <60825986+prezmop@users.noreply.github.com> Date: Sat, 9 Dec 2023 09:30:42 +0100 Subject: [PATCH 14/28] Clear password on ctrl+backpace and ctrl+delete --- password.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/password.c b/password.c index 8dabb52..cae568e 100644 --- a/password.c +++ b/password.c @@ -141,13 +141,19 @@ void swaylock_handle_key(struct swaylock_state *state, break; case XKB_KEY_Delete: case XKB_KEY_BackSpace: - if (backspace(&state->password)) { - state->input_state = INPUT_STATE_BACKSPACE; - schedule_password_clear(state); - update_highlight(state); - } else { + if (state->xkb.control) { + clear_password_buffer(&state->password); state->input_state = INPUT_STATE_CLEAR; cancel_password_clear(state); + } else { + if (backspace(&state->password)) { + state->input_state = INPUT_STATE_BACKSPACE; + schedule_password_clear(state); + update_highlight(state); + } else { + state->input_state = INPUT_STATE_CLEAR; + cancel_password_clear(state); + } } schedule_input_idle(state); damage_state(state); From ba921312c538e2857ef1bc9e8a6f3bbb5799cff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=B8rum?= Date: Thu, 15 Oct 2020 14:26:02 +0200 Subject: [PATCH 15/28] Fix output-specific images when output reappears When setting an image with `--image :`, the image used to fail to apply if the relevant output appears some time after swaylock executes. Co-authored-by: Alexander Bakker --- include/swaylock.h | 1 + main.c | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/swaylock.h b/include/swaylock.h index 0eea1b3..c373fff 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -112,6 +112,7 @@ struct swaylock_surface { struct wl_subsurface *subsurface; struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1; struct pool_buffer indicator_buffers[2]; + bool created; bool frame_pending, dirty; uint32_t width, height; int32_t scale; diff --git a/main.c b/main.c index d27cddf..cf072f1 100644 --- a/main.c +++ b/main.c @@ -152,6 +152,8 @@ static void create_surface(struct swaylock_surface *surface) { wl_surface_set_opaque_region(surface->surface, region); wl_region_destroy(region); } + + surface->created = true; } static void ext_session_lock_surface_v1_handle_configure(void *data, @@ -234,7 +236,11 @@ static void handle_wl_output_mode(void *data, struct wl_output *output, } static void handle_wl_output_done(void *data, struct wl_output *output) { - // Who cares + struct swaylock_surface *surface = data; + if (!surface->created && surface->state->run_display) { + create_surface(surface); + wl_display_roundtrip(surface->state->display); + } } static void handle_wl_output_scale(void *data, struct wl_output *output, @@ -310,11 +316,6 @@ static void handle_global(void *data, struct wl_registry *registry, surface->output_global_name = name; wl_output_add_listener(surface->output, &_wl_output_listener, surface); wl_list_insert(&state->surfaces, &surface->link); - - if (state->run_display) { - create_surface(surface); - wl_display_roundtrip(state->display); - } } else if (strcmp(interface, ext_session_lock_manager_v1_interface.name) == 0) { state->ext_session_lock_manager_v1 = wl_registry_bind(registry, name, &ext_session_lock_manager_v1_interface, 1); From 9b4b3905885777b7e419b99e2a21f1cd1629c167 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 14 Dec 2023 11:32:46 +0100 Subject: [PATCH 16/28] Remove unnecessary wl_display_roundtrip() call Calling wl_display_roundtrip() from an event handler is a bad practice in general because it nests multiple dispatches. We don't need to block here anyways. --- main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/main.c b/main.c index cf072f1..df040e1 100644 --- a/main.c +++ b/main.c @@ -239,7 +239,6 @@ static void handle_wl_output_done(void *data, struct wl_output *output) { struct swaylock_surface *surface = data; if (!surface->created && surface->state->run_display) { create_surface(surface); - wl_display_roundtrip(surface->state->display); } } From 7b4a5c44476615c1b0169e3c3b5e18810daba97e Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 14 Dec 2023 11:35:15 +0100 Subject: [PATCH 17/28] Check initial wl_display_roundtrip() return value On error, print a message and exit. --- main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index df040e1..5057da0 100644 --- a/main.c +++ b/main.c @@ -1208,7 +1208,10 @@ int main(int argc, char **argv) { struct wl_registry *registry = wl_display_get_registry(state.display); wl_registry_add_listener(registry, ®istry_listener, &state); - wl_display_roundtrip(state.display); + if (wl_display_roundtrip(state.display) == -1) { + swaylock_log(LOG_ERROR, "wl_display_roundtrip() failed"); + return EXIT_FAILURE; + } if (!state.compositor) { swaylock_log(LOG_ERROR, "Missing wl_compositor"); From 91bb96811054e66022b109e269a4fabaa2221e2b Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 12 Dec 2023 17:17:47 +0100 Subject: [PATCH 18/28] Document --ready-fd in man page --- swaylock.1.scd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/swaylock.1.scd b/swaylock.1.scd index 61691eb..33ec5a0 100644 --- a/swaylock.1.scd +++ b/swaylock.1.scd @@ -35,6 +35,13 @@ Locks your Wayland session. Note: this is the default behavior of i3lock. +*-R, --ready-fd* + File descriptor to send readiness notifications to. + + When the session has been locked, a single newline is written to the FD. + At this point, the compositor guarantees that no security sensitive content + is visible on-screen. + *-h, --help* Show help message and quit. From bb32fd1d5063e6f53a5bde644b7d0ddd16fe8c49 Mon Sep 17 00:00:00 2001 From: Sertonix <83883937+Sertonix@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:10:46 +0000 Subject: [PATCH 19/28] Install pam config only if pam is enabled --- meson.build | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index ad7c4c7..a0f02a2 100644 --- a/meson.build +++ b/meson.build @@ -127,10 +127,12 @@ executable('swaylock', install: true ) -install_data( - 'pam/swaylock', - install_dir: get_option('sysconfdir') / 'pam.d' -) +if libpam.found() + install_data( + 'pam/swaylock', + install_dir: get_option('sysconfdir') / 'pam.d' + ) +endif if scdoc.found() mandir = get_option('mandir') From b63aaffcd17b4115aa779e49016c6c4dcf06ecd9 Mon Sep 17 00:00:00 2001 From: Sertonix Date: Tue, 16 Jan 2024 12:40:32 +0000 Subject: [PATCH 20/28] Check setgid too after dropping root --- shadow.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadow.c b/shadow.c index b3f898a..be1ac0f 100644 --- a/shadow.c +++ b/shadow.c @@ -33,9 +33,9 @@ void initialize_pw_backend(int argc, char **argv) { swaylock_log_errno(LOG_ERROR, "Unable to drop root"); exit(EXIT_FAILURE); } - if (setuid(0) != -1) { + if (setuid(0) != -1 || setgid(0) != -1) { swaylock_log_errno(LOG_ERROR, "Unable to drop root (we shouldn't be " - "able to restore it after setuid)"); + "able to restore it after setuid/setgid)"); exit(EXIT_FAILURE); } } From 376cc5fcd486a3065b4c04ee4a1573606fb09038 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Fri, 16 Feb 2024 21:26:35 +0100 Subject: [PATCH 21/28] Init eventloop directly after wl_connect this makes sure the eventloop is initialized before any event dispatching. fixes occasional segfaults I observed on resume. --- main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.c b/main.c index 5057da0..549d6f6 100644 --- a/main.c +++ b/main.c @@ -1205,6 +1205,7 @@ int main(int argc, char **argv) { "WAYLAND_DISPLAY environment variable."); return EXIT_FAILURE; } + state.eventloop = loop_create(); struct wl_registry *registry = wl_display_get_registry(state.display); wl_registry_add_listener(registry, ®istry_listener, &state); @@ -1269,7 +1270,6 @@ int main(int argc, char **argv) { daemonize(); } - state.eventloop = loop_create(); loop_add_fd(state.eventloop, wl_display_get_fd(state.display), POLLIN, display_in, NULL); From f9ce3f193bc2f035790372b550547686075f4923 Mon Sep 17 00:00:00 2001 From: Sertonix Date: Tue, 16 Jan 2024 12:25:01 +0100 Subject: [PATCH 22/28] Read password hash before fork This ensures that the parent properly errors only if the password cannot be read. --- shadow.c | 62 ++++++++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/shadow.c b/shadow.c index be1ac0f..3399d04 100644 --- a/shadow.c +++ b/shadow.c @@ -1,4 +1,5 @@ #define _XOPEN_SOURCE // for crypt +#include #include #include #include @@ -14,15 +15,23 @@ #include "password-buffer.h" #include "swaylock.h" +char *encpw = NULL; + void initialize_pw_backend(int argc, char **argv) { - if (geteuid() != 0) { - swaylock_log(LOG_ERROR, - "swaylock needs to be setuid to read /etc/shadow"); + /* This code runs as root */ + struct passwd *pwent = getpwuid(getuid()); + if (!pwent) { + swaylock_log_errno(LOG_ERROR, "failed to getpwuid"); exit(EXIT_FAILURE); } - - if (!spawn_comm_child()) { - exit(EXIT_FAILURE); + encpw = pwent->pw_passwd; + if (strcmp(encpw, "x") == 0) { + struct spwd *swent = getspnam(pwent->pw_name); + if (!swent) { + swaylock_log_errno(LOG_ERROR, "failed to getspnam"); + exit(EXIT_FAILURE); + } + encpw = swent->sp_pwdp; } if (setgid(getgid()) != 0) { @@ -38,40 +47,21 @@ void initialize_pw_backend(int argc, char **argv) { "able to restore it after setuid/setgid)"); exit(EXIT_FAILURE); } -} - -void run_pw_backend_child(void) { - /* This code runs as root */ - struct passwd *pwent = getpwuid(getuid()); - if (!pwent) { - swaylock_log_errno(LOG_ERROR, "failed to getpwuid"); - exit(EXIT_FAILURE); - } - char *encpw = pwent->pw_passwd; - if (strcmp(encpw, "x") == 0) { - struct spwd *swent = getspnam(pwent->pw_name); - if (!swent) { - swaylock_log_errno(LOG_ERROR, "failed to getspnam"); - exit(EXIT_FAILURE); - } - encpw = swent->sp_pwdp; - } - - /* We don't need any additional logging here because the parent process will - * also fail here and will handle logging for us. */ - if (setgid(getgid()) != 0) { - exit(EXIT_FAILURE); - } - if (setuid(getuid()) != 0) { - exit(EXIT_FAILURE); - } - if (setuid(0) != -1) { - exit(EXIT_FAILURE); - } /* This code does not run as root */ swaylock_log(LOG_DEBUG, "Prepared to authorize user %s", pwent->pw_name); + if (!spawn_comm_child()) { + exit(EXIT_FAILURE); + } + + /* Buffer is only used by the child */ + clear_buffer(encpw, strlen(encpw)); + encpw = NULL; +} + +void run_pw_backend_child(void) { + assert(encpw != NULL); while (1) { char *buf; ssize_t size = read_comm_request(&buf); From b70d85ec145520ba3ebaa4c51921a2761bc0c7f0 Mon Sep 17 00:00:00 2001 From: Neil Muller Date: Mon, 3 Jun 2024 17:21:15 +0200 Subject: [PATCH 23/28] Correct for image orientation when loading image PEG and other image formats may include an EXIF orientation tag which indicates in what orientation (rotation and mirroring) the image should be displayed. libgdk-pixbuf does not correct for this when loading an image, but provides a function to apply the transform after the fact, which this commit uses. Pulled from https://github.com/swaywm/swaybg/pull/68 --- background-image.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/background-image.c b/background-image.c index fc2ca0d..572aceb 100644 --- a/background-image.c +++ b/background-image.c @@ -31,8 +31,12 @@ cairo_surface_t *load_background_image(const char *path) { err->message); return NULL; } - image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); + // Correct for embedded image orientation; typical images are not + // rotated and will be handled efficiently + GdkPixbuf *oriented = gdk_pixbuf_apply_embedded_orientation(pixbuf); g_object_unref(pixbuf); + image = gdk_cairo_image_surface_create_from_pixbuf(oriented); + g_object_unref(oriented); #else image = cairo_image_surface_create_from_png(path); #endif // HAVE_GDK_PIXBUF From c2dcd40c341bc7a8ce00030cea2feb2f4c5c3026 Mon Sep 17 00:00:00 2001 From: iyzana <16743652+iyzana@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:21:10 +0200 Subject: [PATCH 24/28] Show cleared state when backspacing last character --- password.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/password.c b/password.c index cae568e..5349e13 100644 --- a/password.c +++ b/password.c @@ -146,7 +146,7 @@ void swaylock_handle_key(struct swaylock_state *state, state->input_state = INPUT_STATE_CLEAR; cancel_password_clear(state); } else { - if (backspace(&state->password)) { + if (backspace(&state->password) && state->password.len != 0) { state->input_state = INPUT_STATE_BACKSPACE; schedule_password_clear(state); update_highlight(state); From de0731de6a44d99532fcd46c5894cff5f10e65a6 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 23 Aug 2024 22:39:33 +0200 Subject: [PATCH 25/28] build: bump version to v1.8.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index a0f02a2..f69ebfb 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'swaylock', 'c', - version: '1.7.2', + version: '1.8.0', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From cca2436ba535d17c49b7d7ba75ebfa6a18ec9209 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sat, 7 Sep 2024 00:27:37 +0200 Subject: [PATCH 26/28] Improve frame callback and commit handling The few places that required a surface commit relied on render_frame and render_frame_background to form it form them after they had set up frame callback events. This would fail if render_frame ran out of buffers and exited early, as the caller would still wait indefinitely on the frame callback. swaylock would quite consistently run out of buffers when rendering immediately after a configure event, such as when the keypress causing render also caused outputs to be enabled from idle. Restructure the render and commit handling slightly so that the whole frame callback and commit setup is handled by the render code, which now has a single render entrypoint. This both avoids stalls from lacking commits, but also fixes the case the configure path to respect frame callbacks so we do not run out of buffers in the first place. --- include/swaylock.h | 8 +++---- main.c | 54 +++++++--------------------------------------- render.c | 51 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 53 insertions(+), 60 deletions(-) diff --git a/include/swaylock.h b/include/swaylock.h index c373fff..cd4e73e 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -113,12 +113,13 @@ struct swaylock_surface { struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1; struct pool_buffer indicator_buffers[2]; bool created; - bool frame_pending, dirty; + bool dirty; uint32_t width, height; int32_t scale; enum wl_output_subpixel subpixel; char *output_name; struct wl_list link; + struct wl_callback *frame; // Dimensions of last wl_buffer committed to background surface int last_buffer_width, last_buffer_height; }; @@ -133,9 +134,8 @@ struct swaylock_image { void swaylock_handle_key(struct swaylock_state *state, xkb_keysym_t keysym, uint32_t codepoint); -void render_frame_background(struct swaylock_surface *surface); -void render_frame(struct swaylock_surface *surface); -void damage_surface(struct swaylock_surface *surface); + +void render(struct swaylock_surface *surface); void damage_state(struct swaylock_state *state); void clear_password_buffer(struct swaylock_password *pw); void schedule_auth_idle(struct swaylock_state *state); diff --git a/main.c b/main.c index 549d6f6..2f32f49 100644 --- a/main.c +++ b/main.c @@ -163,59 +163,19 @@ static void ext_session_lock_surface_v1_handle_configure(void *data, surface->width = width; surface->height = height; ext_session_lock_surface_v1_ack_configure(lock_surface, serial); - render_frame_background(surface); - render_frame(surface); + surface->dirty = true; + render(surface); } static const struct ext_session_lock_surface_v1_listener ext_session_lock_surface_v1_listener = { .configure = ext_session_lock_surface_v1_handle_configure, }; -static const struct wl_callback_listener surface_frame_listener; - -static void surface_frame_handle_done(void *data, struct wl_callback *callback, - uint32_t time) { - struct swaylock_surface *surface = data; - - wl_callback_destroy(callback); - surface->frame_pending = false; - - if (surface->dirty) { - // Schedule a frame in case the surface is damaged again - struct wl_callback *callback = wl_surface_frame(surface->surface); - wl_callback_add_listener(callback, &surface_frame_listener, surface); - surface->frame_pending = true; - - render_frame(surface); - surface->dirty = false; - } -} - -static const struct wl_callback_listener surface_frame_listener = { - .done = surface_frame_handle_done, -}; - -void damage_surface(struct swaylock_surface *surface) { - if (surface->width == 0 || surface->height == 0) { - // Not yet configured - return; - } - - surface->dirty = true; - if (surface->frame_pending) { - return; - } - - struct wl_callback *callback = wl_surface_frame(surface->surface); - wl_callback_add_listener(callback, &surface_frame_listener, surface); - surface->frame_pending = true; - wl_surface_commit(surface->surface); -} - void damage_state(struct swaylock_state *state) { struct swaylock_surface *surface; wl_list_for_each(surface, &state->surfaces, link) { - damage_surface(surface); + surface->dirty = true; + render(surface); } } @@ -226,7 +186,8 @@ static void handle_wl_output_geometry(void *data, struct wl_output *wl_output, struct swaylock_surface *surface = data; surface->subpixel = subpixel; if (surface->state->run_display) { - damage_surface(surface); + surface->dirty = true; + render(surface); } } @@ -247,7 +208,8 @@ static void handle_wl_output_scale(void *data, struct wl_output *output, struct swaylock_surface *surface = data; surface->scale = factor; if (surface->state->run_display) { - damage_surface(surface); + surface->dirty = true; + render(surface); } } diff --git a/render.c b/render.c index e2d76f7..1aa8ae5 100644 --- a/render.c +++ b/render.c @@ -33,7 +33,23 @@ static void set_color_for_state(cairo_t *cairo, struct swaylock_state *state, } } -void render_frame_background(struct swaylock_surface *surface) { +static void surface_frame_handle_done(void *data, struct wl_callback *callback, + uint32_t time) { + struct swaylock_surface *surface = data; + + wl_callback_destroy(callback); + surface->frame = NULL; + + render(surface); +} + +static const struct wl_callback_listener surface_frame_listener = { + .done = surface_frame_handle_done, +}; + +static bool render_frame(struct swaylock_surface *surface); + +void render(struct swaylock_surface *surface) { struct swaylock_state *state = surface->state; int buffer_width = surface->width * surface->scale; @@ -42,11 +58,17 @@ void render_frame_background(struct swaylock_surface *surface) { return; // not yet configured } - wl_surface_set_buffer_scale(surface->surface, surface->scale); + if (!surface->dirty || surface->frame) { + // Nothing to do or frame already pending + return; + } + + bool need_destroy = false; + struct pool_buffer buffer; if (buffer_width != surface->last_buffer_width || buffer_height != surface->last_buffer_height) { - struct pool_buffer buffer; + need_destroy = true; if (!create_buffer(state->shm, &buffer, buffer_width, buffer_height, WL_SHM_FORMAT_ARGB8888)) { swaylock_log(LOG_ERROR, @@ -69,15 +91,23 @@ void render_frame_background(struct swaylock_surface *surface) { cairo_restore(cairo); cairo_identity_matrix(cairo); + wl_surface_set_buffer_scale(surface->surface, surface->scale); wl_surface_attach(surface->surface, buffer.buffer, 0, 0); wl_surface_damage_buffer(surface->surface, 0, 0, INT32_MAX, INT32_MAX); - wl_surface_commit(surface->surface); - destroy_buffer(&buffer); + need_destroy = true; surface->last_buffer_width = buffer_width; surface->last_buffer_height = buffer_height; - } else { - wl_surface_commit(surface->surface); + } + + render_frame(surface); + surface->dirty = false; + surface->frame = wl_surface_frame(surface->surface); + wl_callback_add_listener(surface->frame, &surface_frame_listener, surface); + wl_surface_commit(surface->surface); + + if (need_destroy) { + destroy_buffer(&buffer); } } @@ -99,7 +129,7 @@ static void configure_font_drawing(cairo_t *cairo, struct swaylock_state *state, cairo_font_options_destroy(fo); } -void render_frame(struct swaylock_surface *surface) { +static bool render_frame(struct swaylock_surface *surface) { struct swaylock_state *state = surface->state; // First, compute the text that will be drawn, if any, since this @@ -210,7 +240,8 @@ void render_frame(struct swaylock_surface *surface) { struct pool_buffer *buffer = get_next_buffer(state->shm, surface->indicator_buffers, buffer_width, buffer_height); if (buffer == NULL) { - return; + swaylock_log(LOG_ERROR, "No buffer"); + return false; } // Render the buffer @@ -352,5 +383,5 @@ void render_frame(struct swaylock_surface *surface) { wl_surface_damage_buffer(surface->child, 0, 0, INT32_MAX, INT32_MAX); wl_surface_commit(surface->child); - wl_surface_commit(surface->surface); + return true; } From fc6fbc98fba775e152443a3d587600767d451fa1 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Sat, 12 Oct 2024 09:24:10 -0400 Subject: [PATCH 27/28] Stop processing auth requests after success The child process handling PAM authentication can have multiple requests queued up. As the first success will unlock the screen, there is no point in processing anything afterwards. --- pam.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pam.c b/pam.c index 0e48fcc..5472a9e 100644 --- a/pam.c +++ b/pam.c @@ -109,6 +109,12 @@ void run_pw_backend_child(void) { if (!write_comm_reply(success)) { exit(EXIT_FAILURE); } + + if (success) { + /* Unsuccessful requests may be queued after a successful one; + * do not process them. */ + break; + } } pam_setcred(auth_handle, PAM_REFRESH_CRED); From 96f0a0f9c6a9412fc1886bf54e37ece75b0e9f99 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Fri, 1 Nov 2024 15:46:15 -0400 Subject: [PATCH 28/28] Exit when password handling subprocess crashes It is better to have swaylock crash immediately and the compositor show a red screen than to operate in a degraded state where passwords cannot be checked and the screen cannot be unlocked. --- main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.c b/main.c index 2f32f49..c0d7226 100644 --- a/main.c +++ b/main.c @@ -1038,6 +1038,9 @@ static void comm_in(int fd, short mask, void *data) { if (read_comm_reply()) { // Authentication succeeded state.run_display = false; + } else if (mask & (POLLHUP | POLLERR)) { + swaylock_log(LOG_ERROR, "Password checking subprocess crashed; exiting."); + exit(EXIT_FAILURE); } else { state.auth_state = AUTH_STATE_INVALID; schedule_auth_idle(&state);