diff --git a/README.md b/README.md index fde889f..ec345c3 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,10 @@ # 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: +with any Wayland compositor which implements the ext-session-lock-v1 Wayland +protocol. -- ext-session-lock-v1, or -- wlr-layer-shell and wlr-input-inhibitor - -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 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 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 3e7292d..6561461 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -7,17 +7,22 @@ #include "cairo.h" #include "pool-buffer.h" #include "seat.h" -#include "wlr-layer-shell-unstable-v1-client-protocol.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_MSG, + AUTH_STATE_IDLE, // nothing happening + AUTH_STATE_IDLE_MSG, // showing a PAM message + 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 { @@ -63,6 +68,7 @@ struct swaylock_args { bool hide_keyboard_layout; bool show_failed_attempts; bool daemonize; + int ready_fd; bool indicator_idle_visible; }; @@ -74,13 +80,12 @@ 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; 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; @@ -89,7 +94,9 @@ 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; struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1; @@ -102,19 +109,21 @@ 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 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]; - bool frame_pending, dirty; + bool created; + 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; }; // There is exactly one swaylock_image for each -i argument @@ -127,12 +136,11 @@ 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_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 43a3a07..684984c 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,24 +94,24 @@ 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); } + 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->buffers[0]); - destroy_buffer(&surface->buffers[1]); destroy_buffer(&surface->indicator_buffers[0]); destroy_buffer(&surface->indicator_buffers[1]); - wl_output_destroy(surface->output); + wl_output_release(surface->output); 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 +138,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 && @@ -173,33 +153,9 @@ static void create_surface(struct swaylock_surface *surface) { wl_region_destroy(region); } - if (!state->ext_session_lock_v1) { - wl_surface_commit(surface->surface); - } + surface->created = true; } -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) { @@ -207,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); } } @@ -270,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); } } @@ -280,7 +197,10 @@ 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); + } } static void handle_wl_output_scale(void *data, struct wl_output *output, @@ -288,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); } } @@ -347,12 +268,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)); @@ -362,11 +277,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); @@ -579,6 +489,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 +556,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 +667,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 +701,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); @@ -1121,9 +1039,12 @@ static void comm_in(int fd, short mask, void *data) { if (reply.kind == REPLY_SUCCESS) { // Authentication succeeded state.run_display = false; + } else if (mask & (POLLHUP | POLLERR)) { + swaylock_log(LOG_ERROR, "Password checking subprocess crashed; exiting."); + exit(EXIT_FAILURE); } else if (reply.kind == REPLY_AUTH_ERR) { state.auth_state = AUTH_STATE_INVALID; - schedule_indicator_clear(&state); + schedule_auth_idle(&state); ++state.failed_attempts; damage_state(&state); } else if (reply.kind == REPLY_CONTINUE) { @@ -1196,7 +1117,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); @@ -1245,7 +1167,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); @@ -1258,10 +1184,14 @@ 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); - 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"); @@ -1278,27 +1208,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; } @@ -1310,33 +1230,37 @@ 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) { + if (write(state.args.ready_fd, "\n", 1) != 1) { + 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(); } - state.eventloop = loop_create(); loop_add_fd(state.eventloop, wl_display_get_fd(state.display), POLLIN, display_in, NULL); 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) { @@ -1347,10 +1271,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..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: [ @@ -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 = [] @@ -130,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') diff --git a/pam.c b/pam.c index cd4f25b..6beb93d 100644 --- a/pam.c +++ b/pam.c @@ -137,6 +137,12 @@ void run_pw_backend_child(void) { if (!write_comm_reply(reply)) { 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); 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; diff --git a/password.c b/password.c index a3e7674..5349e13 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,27 +100,39 @@ 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); } +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 - if (state->auth_state == AUTH_STATE_VALIDATING) { - return; - } switch (keysym) { case XKB_KEY_KP_Enter: /* fallthrough */ @@ -107,20 +141,29 @@ void swaylock_handle_key(struct swaylock_state *state, break; case XKB_KEY_Delete: case XKB_KEY_BackSpace: - if (backspace(&state->password)) { - state->auth_state = AUTH_STATE_BACKSPACE; + if (state->xkb.control) { + clear_password_buffer(&state->password); + state->input_state = INPUT_STATE_CLEAR; + cancel_password_clear(state); } else { - state->auth_state = AUTH_STATE_CLEAR; + if (backspace(&state->password) && state->password.len != 0) { + 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); - 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: @@ -133,10 +176,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: @@ -150,19 +193,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; - damage_state(state); - schedule_indicator_clear(state); + state->input_state = INPUT_STATE_LETTER; schedule_password_clear(state); + schedule_input_idle(state); + update_highlight(state); + damage_state(state); } break; } 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 c2c0a75..837f3f8 100644 --- a/render.c +++ b/render.c @@ -5,6 +5,7 @@ #include "background-image.h" #include "log.h" #include "swaylock.h" +#include "log.h" #define M_PI 3.14159265358979323846 const float TYPE_INDICATOR_RANGE = M_PI / 3.0f; @@ -12,12 +13,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); @@ -33,7 +34,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,31 +59,57 @@ 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) { + if (!surface->dirty || surface->frame) { + // Nothing to do or frame already pending return; } - cairo_t *cairo = buffer->cairo; - cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); + bool need_destroy = false; + struct pool_buffer buffer; - 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); + if (buffer_width != surface->last_buffer_width || + buffer_height != surface->last_buffer_height) { + need_destroy = true; + 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_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_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); + need_destroy = true; + + 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); + 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); + } } static void configure_font_drawing(cairo_t *cairo, struct swaylock_state *state, @@ -87,7 +130,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 @@ -98,20 +141,20 @@ void render_frame(struct swaylock_surface *surface) { const char *layout_text = &state->pam_msg[0]; swaylock_log(LOG_DEBUG, "RNDR pm %s", layout_text); - 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"; @@ -139,9 +182,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; } } @@ -203,7 +243,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 @@ -222,8 +263,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, @@ -261,15 +301,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) { - static double highlight_start = 0; - highlight_start += - (rand() % (int)(M_PI * 100)) / 100.0 + M_PI * 0.5; + 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 { @@ -348,5 +386,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; } diff --git a/shadow.c b/shadow.c index b3f898a..3399d04 100644 --- a/shadow.c +++ b/shadow.c @@ -1,4 +1,5 @@ #define _XOPEN_SOURCE // for crypt +#include #include #include #include @@ -14,40 +15,16 @@ #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"); - exit(EXIT_FAILURE); - } - - if (!spawn_comm_child()) { - exit(EXIT_FAILURE); - } - - if (setgid(getgid()) != 0) { - swaylock_log_errno(LOG_ERROR, "Unable to drop root"); - exit(EXIT_FAILURE); - } - if (setuid(getuid()) != 0) { - swaylock_log_errno(LOG_ERROR, "Unable to drop root"); - exit(EXIT_FAILURE); - } - if (setuid(0) != -1) { - swaylock_log_errno(LOG_ERROR, "Unable to drop root (we shouldn't be " - "able to restore it after setuid)"); - 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; + encpw = pwent->pw_passwd; if (strcmp(encpw, "x") == 0) { struct spwd *swent = getspnam(pwent->pw_name); if (!swent) { @@ -57,21 +34,34 @@ void run_pw_backend_child(void) { 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) { + swaylock_log_errno(LOG_ERROR, "Unable to drop root"); exit(EXIT_FAILURE); } if (setuid(getuid()) != 0) { + 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/setgid)"); 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); 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. 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. - - - - - - - - - - - - - - - - -