diff --git a/common/background-image.c b/common/background-image.c new file mode 100644 index 000000000..e5fb4433e --- /dev/null +++ b/common/background-image.c @@ -0,0 +1,119 @@ +#include +#include +#include +#include "background-image.h" +#include "cairo.h" + +enum background_mode parse_background_mode(const char *mode) { + if (strcmp(mode, "stretch") == 0) { + return BACKGROUND_MODE_STRETCH; + } else if (strcmp(mode, "fill") == 0) { + return BACKGROUND_MODE_FILL; + } else if (strcmp(mode, "fit") == 0) { + return BACKGROUND_MODE_FIT; + } else if (strcmp(mode, "center") == 0) { + return BACKGROUND_MODE_CENTER; + } else if (strcmp(mode, "tile") == 0) { + return BACKGROUND_MODE_TILE; + } else if (strcmp(mode, "solid_color") == 0) { + return BACKGROUND_MODE_SOLID_COLOR; + } + wlr_log(L_ERROR, "Unsupported background mode: %s", mode); + return BACKGROUND_MODE_INVALID; +} + +cairo_surface_t *load_background_image(const char *path) { + cairo_surface_t *image; +#ifdef HAVE_GDK_PIXBUF + GError *err = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err); + if (!pixbuf) { + wlr_log(L_ERROR, "Failed to load background image (%s).", + err->message); + return false; + } + image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); + g_object_unref(pixbuf); +#else + image = cairo_image_surface_create_from_png(path); +#endif //HAVE_GDK_PIXBUF + if (!image) { + wlr_log(L_ERROR, "Failed to read background image."); + return NULL; + } + if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) { + wlr_log(L_ERROR, "Failed to read background image: %s." +#ifndef HAVE_GDK_PIXBUF + "\nSway was compiled without gdk_pixbuf support, so only" + "\nPNG images can be loaded. This is the likely cause." +#endif //HAVE_GDK_PIXBUF + , cairo_status_to_string(cairo_surface_status(image))); + return NULL; + } + return image; +} + +void render_background_image(cairo_t *cairo, cairo_surface_t *image, + enum background_mode mode, int buffer_width, int buffer_height) { + double width = cairo_image_surface_get_width(image); + double height = cairo_image_surface_get_height(image); + + switch (mode) { + case BACKGROUND_MODE_STRETCH: + cairo_scale(cairo, + (double)buffer_width / width, + (double)buffer_height / height); + cairo_set_source_surface(cairo, image, 0, 0); + break; + case BACKGROUND_MODE_FILL: { + double window_ratio = (double)buffer_width / buffer_height; + double bg_ratio = width / height; + + if (window_ratio > bg_ratio) { + double scale = (double)buffer_width / width; + cairo_scale(cairo, scale, scale); + cairo_set_source_surface(cairo, image, + 0, (double)buffer_height / 2 / scale - height / 2); + } else { + double scale = (double)buffer_height / height; + cairo_scale(cairo, scale, scale); + cairo_set_source_surface(cairo, image, + (double)buffer_width / 2 / scale - width / 2, 0); + } + break; + } + case BACKGROUND_MODE_FIT: { + double window_ratio = (double)buffer_width / buffer_height; + double bg_ratio = width / height; + + if (window_ratio > bg_ratio) { + double scale = (double)buffer_height / height; + cairo_scale(cairo, scale, scale); + cairo_set_source_surface(cairo, image, + (double)buffer_width / 2 / scale - width / 2, 0); + } else { + double scale = (double)buffer_width / width; + cairo_scale(cairo, scale, scale); + cairo_set_source_surface(cairo, image, + 0, (double)buffer_height / 2 / scale - height / 2); + } + break; + } + case BACKGROUND_MODE_CENTER: + cairo_set_source_surface(cairo, image, + (double)buffer_width / 2 - width / 2, + (double)buffer_height / 2 - height / 2); + break; + case BACKGROUND_MODE_TILE: { + cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image); + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); + cairo_set_source(cairo, pattern); + break; + } + case BACKGROUND_MODE_SOLID_COLOR: + case BACKGROUND_MODE_INVALID: + assert(0); + break; + } + cairo_paint(cairo); +} diff --git a/common/meson.build b/common/meson.build index 4ad470774..44a295084 100644 --- a/common/meson.build +++ b/common/meson.build @@ -1,17 +1,7 @@ -deps = [ - cairo, - pango, - pangocairo, - wlroots -] - -if gdk_pixbuf.found() - deps += [gdk_pixbuf] -endif - lib_sway_common = static_library( 'sway-common', files( + 'background-image.c', 'cairo.c', 'ipc-client.c', 'log.c', @@ -19,8 +9,15 @@ lib_sway_common = static_library( 'pango.c', 'readline.c', 'stringop.c', + 'unicode.c', 'util.c' ), - dependencies: deps, + dependencies: [ + cairo, + gdk_pixbuf, + pango, + pangocairo, + wlroots + ], include_directories: sway_inc ) diff --git a/common/unicode.c b/common/unicode.c new file mode 100644 index 000000000..38a9b48eb --- /dev/null +++ b/common/unicode.c @@ -0,0 +1,101 @@ +#include +#include +#include "unicode.h" + +size_t utf8_chsize(uint32_t ch) { + if (ch < 0x80) { + return 1; + } else if (ch < 0x800) { + return 2; + } else if (ch < 0x10000) { + return 3; + } + return 4; +} + +static const uint8_t masks[] = { + 0x7F, + 0x1F, + 0x0F, + 0x07, + 0x03, + 0x01 +}; + +uint32_t utf8_decode(const char **char_str) { + uint8_t **s = (uint8_t **)char_str; + + uint32_t cp = 0; + if (**s < 128) { + // shortcut + cp = **s; + ++*s; + return cp; + } + int size = utf8_size((char *)*s); + if (size == -1) { + ++*s; + return UTF8_INVALID; + } + uint8_t mask = masks[size - 1]; + cp = **s & mask; + ++*s; + while (--size) { + cp <<= 6; + cp |= **s & 0x3f; + ++*s; + } + return cp; +} + +size_t utf8_encode(char *str, uint32_t ch) { + size_t len = 0; + uint8_t first; + + if (ch < 0x80) { + first = 0; + len = 1; + } else if (ch < 0x800) { + first = 0xc0; + len = 2; + } else if (ch < 0x10000) { + first = 0xe0; + len = 3; + } else { + first = 0xf0; + len = 4; + } + + for (size_t i = len - 1; i > 0; --i) { + str[i] = (ch & 0x3f) | 0x80; + ch >>= 6; + } + + str[0] = ch | first; + return len; +} + + +static const struct { + uint8_t mask; + uint8_t result; + int octets; +} sizes[] = { + { 0x80, 0x00, 1 }, + { 0xE0, 0xC0, 2 }, + { 0xF0, 0xE0, 3 }, + { 0xF8, 0xF0, 4 }, + { 0xFC, 0xF8, 5 }, + { 0xFE, 0xF8, 6 }, + { 0x80, 0x80, -1 }, +}; + +int utf8_size(const char *s) { + uint8_t c = (uint8_t)*s; + for (size_t i = 0; i < sizeof(sizes) / 2; ++i) { + if ((c & sizes[i].mask) == sizes[i].result) { + return sizes[i].octets; + } + } + return -1; +} diff --git a/include/background-image.h b/include/background-image.h new file mode 100644 index 000000000..15935ffd3 --- /dev/null +++ b/include/background-image.h @@ -0,0 +1,20 @@ +#ifndef _SWAY_BACKGROUND_IMAGE_H +#define _SWAY_BACKGROUND_IMAGE_H +#include "cairo.h" + +enum background_mode { + BACKGROUND_MODE_STRETCH, + BACKGROUND_MODE_FILL, + BACKGROUND_MODE_FIT, + BACKGROUND_MODE_CENTER, + BACKGROUND_MODE_TILE, + BACKGROUND_MODE_SOLID_COLOR, + BACKGROUND_MODE_INVALID, +}; + +enum background_mode parse_background_mode(const char *mode); +cairo_surface_t *load_background_image(const char *path); +void render_background_image(cairo_t *cairo, cairo_surface_t *image, + enum background_mode mode, int buffer_width, int buffer_height); + +#endif diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h index 8e39a4a7a..89a3ac71b 100644 --- a/include/sway/input/input-manager.h +++ b/include/sway/input/input-manager.h @@ -1,6 +1,7 @@ #ifndef _SWAY_INPUT_INPUT_MANAGER_H #define _SWAY_INPUT_INPUT_MANAGER_H #include +#include #include "sway/server.h" #include "sway/config.h" #include "list.h" @@ -23,7 +24,11 @@ struct sway_input_manager { struct wl_list devices; struct wl_list seats; + struct wlr_input_inhibit_manager *inhibit; + struct wl_listener new_input; + struct wl_listener inhibit_activate; + struct wl_listener inhibit_deactivate; }; struct sway_input_manager *input_manager_create(struct sway_server *server); diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h index 137fcd229..d1cfbe4c2 100644 --- a/include/sway/input/seat.h +++ b/include/sway/input/seat.h @@ -32,6 +32,9 @@ struct sway_seat { // If the focused layer is set, views cannot receive keyboard focus struct wlr_layer_surface *focused_layer; + // If exclusive_client is set, no other clients will receive input events + struct wl_client *exclusive_client; + struct wl_listener focus_destroy; struct wl_listener new_container; @@ -64,6 +67,9 @@ void seat_set_focus_warp(struct sway_seat *seat, void seat_set_focus_layer(struct sway_seat *seat, struct wlr_layer_surface *layer); +void seat_set_exclusive_client(struct sway_seat *seat, + struct wl_client *client); + struct sway_container *seat_get_focus(struct sway_seat *seat); /** @@ -85,4 +91,6 @@ void seat_apply_config(struct sway_seat *seat, struct seat_config *seat_config); struct seat_config *seat_get_config(struct sway_seat *seat); +bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface); + #endif diff --git a/include/swaylock/seat.h b/include/swaylock/seat.h new file mode 100644 index 000000000..44bc37d55 --- /dev/null +++ b/include/swaylock/seat.h @@ -0,0 +1,38 @@ +#ifndef _SWAYLOCK_SEAT_H +#define _SWAYLOCK_SEAT_H +#include + +enum mod_bit { + MOD_SHIFT = 1<<0, + MOD_CAPS = 1<<1, + MOD_CTRL = 1<<2, + MOD_ALT = 1<<3, + MOD_MOD2 = 1<<4, + MOD_MOD3 = 1<<5, + MOD_LOGO = 1<<6, + MOD_MOD5 = 1<<7, +}; + +enum mask { + MASK_SHIFT, + MASK_CAPS, + MASK_CTRL, + MASK_ALT, + MASK_MOD2, + MASK_MOD3, + MASK_LOGO, + MASK_MOD5, + MASK_LAST +}; + +struct swaylock_xkb { + uint32_t modifiers; + struct xkb_state *state; + struct xkb_context *context; + struct xkb_keymap *keymap; + xkb_mod_mask_t masks[MASK_LAST]; +}; + +extern const struct wl_seat_listener seat_listener; + +#endif diff --git a/include/swaylock/swaylock.h b/include/swaylock/swaylock.h index eeed094e0..173e8b125 100644 --- a/include/swaylock/swaylock.h +++ b/include/swaylock/swaylock.h @@ -1,66 +1,64 @@ #ifndef _SWAYLOCK_H #define _SWAYLOCK_H - -#include "client/cairo.h" - -enum scaling_mode { - SCALING_MODE_STRETCH, - SCALING_MODE_FILL, - SCALING_MODE_FIT, - SCALING_MODE_CENTER, - SCALING_MODE_TILE, -}; +#include +#include +#include +#include "background-image.h" +#include "cairo.h" +#include "pool-buffer.h" +#include "swaylock/seat.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" enum auth_state { - AUTH_STATE_IDLE, - AUTH_STATE_INPUT, - AUTH_STATE_BACKSPACE, - AUTH_STATE_VALIDATING, - AUTH_STATE_INVALID, + AUTH_STATE_IDLE, + AUTH_STATE_INPUT, + AUTH_STATE_BACKSPACE, + AUTH_STATE_VALIDATING, + AUTH_STATE_INVALID, }; -enum line_source { - LINE_SOURCE_DEFAULT, - LINE_SOURCE_RING, - LINE_SOURCE_INSIDE, -}; - -struct render_data { - list_t *surfaces; - // Output specific images - cairo_surface_t **images; - // OR one image for all outputs: - cairo_surface_t *image; - int num_images; - int color_set; +struct swaylock_args { uint32_t color; - enum scaling_mode scaling_mode; + enum background_mode mode; + bool show_indicator; +}; + +struct swaylock_password { + size_t size; + size_t len; + char *buffer; +}; + +struct swaylock_state { + struct wl_display *display; + struct wl_compositor *compositor; + 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 swaylock_args args; + struct swaylock_password password; + struct swaylock_xkb xkb; enum auth_state auth_state; + bool run_display; }; -struct lock_colors { - uint32_t inner_ring; - uint32_t outer_ring; +struct swaylock_surface { + cairo_surface_t *image; + struct swaylock_state *state; + struct wl_output *output; + struct wl_surface *surface; + struct zwlr_layer_surface_v1 *layer_surface; + struct pool_buffer buffers[2]; + struct pool_buffer *current_buffer; + uint32_t width, height; + int32_t scale; + struct wl_list link; }; -struct lock_config { - char *font; - - struct { - uint32_t text; - uint32_t line; - uint32_t separator; - uint32_t input_cursor; - uint32_t backspace_cursor; - struct lock_colors normal; - struct lock_colors validating; - struct lock_colors invalid; - } colors; - - int radius; - int thickness; -}; - -void render(struct render_data* render_data, struct lock_config *config); +void swaylock_handle_key(struct swaylock_state *state, + xkb_keysym_t keysym, uint32_t codepoint); +void render_frame(struct swaylock_surface *surface); +void render_frames(struct swaylock_state *state); #endif diff --git a/include/unicode.h b/include/unicode.h new file mode 100644 index 000000000..e2ee9588c --- /dev/null +++ b/include/unicode.h @@ -0,0 +1,33 @@ +#ifndef _SWAY_UNICODE_H +#define _SWAY_UNICODE_H +#include +#include + +// Technically UTF-8 supports up to 6 byte codepoints, but Unicode itself +// doesn't really bother with more than 4. +#define UTF8_MAX_SIZE 4 + +#define UTF8_INVALID 0x80 + +/** + * Grabs the next UTF-8 character and advances the string pointer + */ +uint32_t utf8_decode(const char **str); + +/** + * Encodes a character as UTF-8 and returns the length of that character. + */ +size_t utf8_encode(char *str, uint32_t ch); + +/** + * Returns the size of the next UTF-8 character + */ +int utf8_size(const char *str); + +/** + * Returns the size of a UTF-8 character + */ +size_t utf8_chsize(uint32_t ch); + +#endif + diff --git a/meson.build b/meson.build index 01788fd9f..d02a446b5 100644 --- a/meson.build +++ b/meson.build @@ -35,6 +35,7 @@ gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: false) pixman = dependency('pixman-1') libcap = dependency('libcap') libinput = dependency('libinput') +libpam = cc.find_library('libpam') math = cc.find_library('m') rt = cc.find_library('rt') git = find_program('git', required: false) @@ -105,6 +106,7 @@ subdir('swaymsg') subdir('client') subdir('swaybg') subdir('swaybar') +subdir('swaylock') config = configuration_data() config.set('sysconfdir', join_paths(prefix, sysconfdir)) diff --git a/protocols/meson.build b/protocols/meson.build index 0887cf866..7f83b16b6 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -22,12 +22,14 @@ wayland_scanner_server = generator( client_protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], - ['wlr-layer-shell-unstable-v1.xml'] + ['wlr-layer-shell-unstable-v1.xml'], + ['wlr-input-inhibitor-unstable-v1.xml'] ] server_protocols = [ [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], - ['wlr-layer-shell-unstable-v1.xml'] + ['wlr-layer-shell-unstable-v1.xml'], + ['wlr-input-inhibitor-unstable-v1.xml'] ] client_protos_src = [] diff --git a/protocols/wlr-input-inhibitor-unstable-v1.xml b/protocols/wlr-input-inhibitor-unstable-v1.xml new file mode 100644 index 000000000..b62d1bb44 --- /dev/null +++ b/protocols/wlr-input-inhibitor-unstable-v1.xml @@ -0,0 +1,67 @@ + + + + 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/sway/input/cursor.c b/sway/input/cursor.c index 9229e92d1..195ddce93 100644 --- a/sway/input/cursor.c +++ b/sway/input/cursor.c @@ -146,8 +146,10 @@ static void cursor_send_pointer_motion(struct sway_cursor *cursor, // send pointer enter/leave if (surface != NULL) { - wlr_seat_pointer_notify_enter(seat, surface, sx, sy); - wlr_seat_pointer_notify_motion(seat, time, sx, sy); + if (seat_is_input_allowed(cursor->seat, surface)) { + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } } else { wlr_seat_pointer_clear_focus(seat); } diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index c3507f651..f71a06e49 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "sway/config.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" @@ -263,6 +264,32 @@ static void handle_new_input(struct wl_listener *listener, void *data) { input_device->device_destroy.notify = handle_device_destroy; } +static void handle_inhibit_activate(struct wl_listener *listener, void *data) { + struct sway_input_manager *input_manager = wl_container_of( + listener, input_manager, inhibit_activate); + struct sway_seat *seat; + wl_list_for_each(seat, &input_manager->seats, link) { + seat_set_exclusive_client(seat, input_manager->inhibit->active_client); + } +} + +static void handle_inhibit_deactivate(struct wl_listener *listener, void *data) { + struct sway_input_manager *input_manager = wl_container_of( + listener, input_manager, inhibit_deactivate); + struct sway_seat *seat; + wl_list_for_each(seat, &input_manager->seats, link) { + seat_set_exclusive_client(seat, NULL); + struct sway_container *previous = seat_get_focus(seat); + if (previous) { + wlr_log(L_DEBUG, "Returning focus to %p %s '%s'", previous, + container_type_to_str(previous->type), previous->name); + // Hack to get seat to re-focus the return value of get_focus + seat_set_focus(seat, previous->parent); + seat_set_focus(seat, previous); + } + } +} + struct sway_input_manager *input_manager_create( struct sway_server *server) { struct sway_input_manager *input = @@ -281,6 +308,14 @@ struct sway_input_manager *input_manager_create( input->new_input.notify = handle_new_input; wl_signal_add(&server->backend->events.new_input, &input->new_input); + input->inhibit = wlr_input_inhibit_manager_create(server->wl_display); + input->inhibit_activate.notify = handle_inhibit_activate; + wl_signal_add(&input->inhibit->events.activate, + &input->inhibit_activate); + input->inhibit_deactivate.notify = handle_inhibit_deactivate; + wl_signal_add(&input->inhibit->events.deactivate, + &input->inhibit_deactivate); + return input; } diff --git a/sway/input/seat.c b/sway/input/seat.c index 4a99e9eb4..0699324ab 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -1,5 +1,7 @@ #define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 199309L #include +#include #include #include #include @@ -9,6 +11,7 @@ #include "sway/input/input-manager.h" #include "sway/input/keyboard.h" #include "sway/ipc-server.h" +#include "sway/layers.h" #include "sway/output.h" #include "sway/tree/container.h" #include "sway/tree/view.h" @@ -350,6 +353,12 @@ void seat_configure_xcursor(struct sway_seat *seat) { seat->cursor->cursor->y); } +bool seat_is_input_allowed(struct sway_seat *seat, + struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + return !seat->exclusive_client || seat->exclusive_client == client; +} + void seat_set_focus_warp(struct sway_seat *seat, struct sway_container *container, bool warp) { if (seat->focused_layer) { @@ -371,6 +380,12 @@ void seat_set_focus_warp(struct sway_seat *seat, wl_list_remove(&seat_con->link); wl_list_insert(&seat->focus_stack, &seat_con->link); + if (container->type == C_VIEW && !seat_is_input_allowed( + seat, container->sway_view->surface)) { + wlr_log(L_DEBUG, "Refusing to set focus, input is inhibited"); + return; + } + if (container->type == C_VIEW) { seat_send_focus(seat, container); } @@ -424,11 +439,18 @@ void seat_set_focus(struct sway_seat *seat, void seat_set_focus_layer(struct sway_seat *seat, struct wlr_layer_surface *layer) { - if (!layer) { + if (!layer && seat->focused_layer) { seat->focused_layer = NULL; + struct sway_container *previous = seat_get_focus(seat); + if (previous) { + wlr_log(L_DEBUG, "Returning focus to %p %s '%s'", previous, + container_type_to_str(previous->type), previous->name); + // Hack to get seat to re-focus the return value of get_focus + seat_set_focus(seat, previous->parent); + seat_set_focus(seat, previous); + } return; - } - if (seat->focused_layer == layer) { + } else if (!layer || seat->focused_layer == layer) { return; } if (seat->has_focus) { @@ -453,6 +475,51 @@ void seat_set_focus_layer(struct sway_seat *seat, } } +void seat_set_exclusive_client(struct sway_seat *seat, + struct wl_client *client) { + if (!client) { + seat->exclusive_client = client; + // Triggers a refocus of the topmost surface layer if necessary + // TODO: Make layer surface focus per-output based on cursor position + for (int i = 0; i < root_container.children->length; ++i) { + struct sway_container *output = root_container.children->items[i]; + if (!sway_assert(output->type == C_OUTPUT, + "root container has non-output child")) { + continue; + } + arrange_layers(output->sway_output); + } + return; + } + if (seat->focused_layer) { + if (wl_resource_get_client(seat->focused_layer->resource) != client) { + seat_set_focus_layer(seat, NULL); + } + } + if (seat->has_focus) { + struct sway_container *focus = seat_get_focus(seat); + if (focus->type == C_VIEW && wl_resource_get_client( + focus->sway_view->surface->resource) != client) { + seat_set_focus(seat, NULL); + } + } + if (seat->wlr_seat->pointer_state.focused_client) { + if (seat->wlr_seat->pointer_state.focused_client->client != client) { + wlr_seat_pointer_clear_focus(seat->wlr_seat); + } + } + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + struct wlr_touch_point *point; + wl_list_for_each(point, &seat->wlr_seat->touch_state.touch_points, link) { + if (point->client->client != client) { + wlr_seat_touch_point_clear_focus(seat->wlr_seat, + now.tv_nsec / 1000, point->touch_id); + } + } + seat->exclusive_client = client; +} + struct sway_container *seat_get_focus_inactive(struct sway_seat *seat, struct sway_container *container) { return seat_get_focus_by_type(seat, container, C_TYPES); diff --git a/swaybg/main.c b/swaybg/main.c index c282a7074..679b8c205 100644 --- a/swaybg/main.c +++ b/swaybg/main.c @@ -7,20 +7,12 @@ #include #include #include +#include "background-image.h" #include "pool-buffer.h" #include "cairo.h" #include "util.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" -enum background_mode { - BACKGROUND_MODE_STRETCH, - BACKGROUND_MODE_FILL, - BACKGROUND_MODE_FIT, - BACKGROUND_MODE_CENTER, - BACKGROUND_MODE_TILE, - BACKGROUND_MODE_SOLID_COLOR, -}; - struct swaybg_args { int output_idx; const char *path; @@ -71,85 +63,18 @@ bool is_valid_color(const char *color) { return true; } -static void render_image(struct swaybg_state *state) { - cairo_t *cairo = state->current_buffer->cairo; - cairo_surface_t *image = state->context.image; - double width = cairo_image_surface_get_width(image); - double height = cairo_image_surface_get_height(image); - int buffer_width = state->width * state->scale; - int buffer_height = state->height * state->scale; - - switch (state->args->mode) { - case BACKGROUND_MODE_STRETCH: - cairo_scale(cairo, (double)buffer_width / width, - (double)buffer_height / height); - cairo_set_source_surface(cairo, image, 0, 0); - break; - case BACKGROUND_MODE_FILL: { - double window_ratio = (double)buffer_width / buffer_height; - double bg_ratio = width / height; - - if (window_ratio > bg_ratio) { - double scale = (double)buffer_width / width; - cairo_scale(cairo, scale, scale); - cairo_set_source_surface(cairo, image, - 0, (double)buffer_height / 2 / scale - height / 2); - } else { - double scale = (double)buffer_height / height; - cairo_scale(cairo, scale, scale); - cairo_set_source_surface(cairo, image, - (double)buffer_width / 2 / scale - width / 2, 0); - } - break; - } - case BACKGROUND_MODE_FIT: { - double window_ratio = (double)buffer_width / buffer_height; - double bg_ratio = width / height; - - if (window_ratio > bg_ratio) { - double scale = (double)buffer_height / height; - cairo_scale(cairo, scale, scale); - cairo_set_source_surface(cairo, image, - (double)buffer_width / 2 / scale - width / 2, 0); - } else { - double scale = (double)buffer_width / width; - cairo_scale(cairo, scale, scale); - cairo_set_source_surface(cairo, image, - 0, (double)buffer_height / 2 / scale - height / 2); - } - break; - } - case BACKGROUND_MODE_CENTER: - cairo_set_source_surface(cairo, image, - (double)buffer_width / 2 - width / 2, - (double)buffer_height / 2 - height / 2); - break; - case BACKGROUND_MODE_TILE: { - cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image); - cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); - cairo_set_source(cairo, pattern); - break; - } - case BACKGROUND_MODE_SOLID_COLOR: - assert(0); - break; - } - cairo_paint(cairo); -} - static void render_frame(struct swaybg_state *state) { - state->current_buffer = get_next_buffer(state->shm, state->buffers, - state->width * state->scale, state->height * state->scale); + int buffer_width = state->width * state->scale, + buffer_height = state->height * state->scale; + state->current_buffer = get_next_buffer(state->shm, + state->buffers, buffer_width, buffer_height); cairo_t *cairo = state->current_buffer->cairo; - - switch (state->args->mode) { - case BACKGROUND_MODE_SOLID_COLOR: + if (state->args->mode == BACKGROUND_MODE_SOLID_COLOR) { cairo_set_source_u32(cairo, state->context.color); cairo_paint(cairo); - break; - default: - render_image(state); - break; + } else { + render_background_image(cairo, state->context.image, + state->args->mode, buffer_width, buffer_height); } wl_surface_set_buffer_scale(state->surface, state->scale); @@ -163,31 +88,7 @@ static bool prepare_context(struct swaybg_state *state) { state->context.color = parse_color(state->args->path); return is_valid_color(state->args->path); } -#ifdef HAVE_GDK_PIXBUF - GError *err = NULL; - GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(state->args->path, &err); - if (!pixbuf) { - wlr_log(L_ERROR, "Failed to load background image."); - return false; - } - state->context.image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); - g_object_unref(pixbuf); -#else - state->context.image = cairo_image_surface_create_from_png( - state->args->path); -#endif //HAVE_GDK_PIXBUF - if (!state->context.image) { - wlr_log(L_ERROR, "Failed to read background image."); - return false; - } - if (cairo_surface_status(state->context.image) != CAIRO_STATUS_SUCCESS) { - wlr_log(L_ERROR, "Failed to read background image: %s." -#ifndef HAVE_GDK_PIXBUF - "\nSway was compiled without gdk_pixbuf support, so only" - "\nPNG images can be loaded. This is the likely cause." -#endif //HAVE_GDK_PIXBUF - , cairo_status_to_string( - cairo_surface_status(state->context.image))); + if (!(state->context.image = load_background_image(state->args->path))) { return false; } return true; @@ -294,24 +195,10 @@ int main(int argc, const char **argv) { args.output_idx = atoi(argv[1]); args.path = argv[2]; - args.mode = BACKGROUND_MODE_STRETCH; - if (strcmp(argv[3], "stretch") == 0) { - args.mode = BACKGROUND_MODE_STRETCH; - } else if (strcmp(argv[3], "fill") == 0) { - args.mode = BACKGROUND_MODE_FILL; - } else if (strcmp(argv[3], "fit") == 0) { - args.mode = BACKGROUND_MODE_FIT; - } else if (strcmp(argv[3], "center") == 0) { - args.mode = BACKGROUND_MODE_CENTER; - } else if (strcmp(argv[3], "tile") == 0) { - args.mode = BACKGROUND_MODE_TILE; - } else if (strcmp(argv[3], "solid_color") == 0) { - args.mode = BACKGROUND_MODE_SOLID_COLOR; - } else { - wlr_log(L_ERROR, "Unsupported background mode: %s", argv[3]); + args.mode = parse_background_mode(argv[3]); + if (args.mode == BACKGROUND_MODE_INVALID) { return 1; } - if (!prepare_context(&state)) { return 1; } @@ -345,10 +232,10 @@ int main(int argc, const char **argv) { zwlr_layer_surface_v1_set_exclusive_zone(state.layer_surface, -1); zwlr_layer_surface_v1_add_listener(state.layer_surface, &layer_surface_listener, &state); - state.run_display = true; wl_surface_commit(state.surface); wl_display_roundtrip(state.display); + state.run_display = true; while (wl_display_dispatch(state.display) != -1 && state.run_display) { // This space intentionally left blank } diff --git a/swaylock/main.c b/swaylock/main.c index c26159519..1d5221849 100644 --- a/swaylock/main.c +++ b/swaylock/main.c @@ -1,387 +1,144 @@ -#define _XOPEN_SOURCE 500 -#include "wayland-swaylock-client-protocol.h" -#include -#include -#include -#include +#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200112L +#include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include +#include +#include #include -#include "client/window.h" -#include "client/registry.h" -#include "client/cairo.h" +#include +#include +#include "swaylock/seat.h" #include "swaylock/swaylock.h" -#include "ipc-client.h" -#include "log.h" +#include "background-image.h" +#include "pool-buffer.h" +#include "cairo.h" #include "util.h" +#include "wlr-input-inhibitor-unstable-v1-client-protocol.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" -struct registry *registry; -struct render_data render_data; -struct lock_config *config; -bool show_indicator = true; - -void wl_dispatch_events() { - wl_display_flush(registry->display); - if (wl_display_dispatch(registry->display) == -1) { - sway_log(L_ERROR, "failed to run wl_display_dispatch"); - exit(1); +static void daemonize() { + if (fork() == 0) { + int devnull = open("/dev/null", O_RDWR); + dup2(STDOUT_FILENO, devnull); + dup2(STDERR_FILENO, devnull); + chdir("/"); + } else { + exit(0); } } -void sigalarm_handler(int sig) { - signal(SIGALRM, SIG_IGN); - // Hide typing indicator - render_data.auth_state = AUTH_STATE_IDLE; - render(&render_data, config); - wl_display_flush(registry->display); - signal(SIGALRM, sigalarm_handler); +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(surface); } -void sway_terminate(int exit_code) { - int i; - for (i = 0; i < render_data.surfaces->length; ++i) { - struct window *window = render_data.surfaces->items[i]; - window_teardown(window); - } - list_free(render_data.surfaces); - if (registry) { - registry_teardown(registry); - } - exit(exit_code); +static void layer_surface_closed(void *data, + struct zwlr_layer_surface_v1 *layer_surface) { + struct swaylock_surface *surface = data; + zwlr_layer_surface_v1_destroy(surface->layer_surface); + wl_surface_destroy(surface->surface); + surface->state->run_display = false; } -char *password; -int password_size; -enum line_source line_source = LINE_SOURCE_DEFAULT; +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; -struct lock_config *init_config() { - struct lock_config *config = calloc(1, sizeof(struct lock_config)); - - config->font = strdup("sans-serif"); - config->colors.text = 0x000000FF; - - config->colors.line = 0x000000FF; - config->colors.separator = 0x000000FF; - - config->colors.input_cursor = 0x33DB00FF; - config->colors.backspace_cursor = 0xDB3300FF; - - config->colors.normal.inner_ring = 0x000000BF; - config->colors.normal.outer_ring = 0x337D00FF; - - config->colors.validating.inner_ring = 0x0072FFBF; - config->colors.validating.outer_ring = 0x3300FAFF; - - config->colors.invalid.inner_ring = 0xFA0000BF; - config->colors.invalid.outer_ring = 0x7D3300FF; - - config->radius = 50; - config->thickness = 10; - - return config; +static void output_geometry(void *data, struct wl_output *output, int32_t x, + int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel, + const char *make, const char *model, int32_t transform) { + // Who cares } -void free_config(struct lock_config *config) { - free(config->font); - free(config); +static void output_mode(void *data, struct wl_output *output, uint32_t flags, + int32_t width, int32_t height, int32_t refresh) { + // Who cares } -int function_conversation(int num_msg, const struct pam_message **msg, - struct pam_response **resp, void *appdata_ptr) { - - const char* msg_style_names[] = { - NULL, - "PAM_PROMPT_ECHO_OFF", - "PAM_PROMPT_ECHO_ON", - "PAM_ERROR_MSG", - "PAM_TEXT_INFO", - }; - - /* PAM expects an array of responses, one for each message */ - struct pam_response *pam_reply = calloc(num_msg, sizeof(struct pam_response)); - *resp = pam_reply; - - for(int i=0; imsg_style], - msg[i]->msg); - - switch (msg[i]->msg_style) { - case PAM_PROMPT_ECHO_OFF: - case PAM_PROMPT_ECHO_ON: - pam_reply[i].resp = password; - break; - - case PAM_ERROR_MSG: - case PAM_TEXT_INFO: - break; - } - } - - return PAM_SUCCESS; +static void output_done(void *data, struct wl_output *output) { + // Who cares } -/** - * Note: PAM will free() 'password' during the process - */ -bool verify_password() { - struct passwd *passwd = getpwuid(getuid()); - char *username = passwd->pw_name; - - const struct pam_conv local_conversation = { function_conversation, NULL }; - pam_handle_t *local_auth_handle = NULL; - int pam_err; - if ((pam_err = pam_start("swaylock", username, &local_conversation, &local_auth_handle)) != PAM_SUCCESS) { - sway_abort("PAM returned %d\n", pam_err); - } - if ((pam_err = pam_authenticate(local_auth_handle, 0)) != PAM_SUCCESS) { - return false; - } - if ((pam_err = pam_end(local_auth_handle, pam_err)) != PAM_SUCCESS) { - return false; - } - return true; -} - -void notify_key(enum wl_keyboard_key_state state, xkb_keysym_t sym, uint32_t code, uint32_t codepoint) { - int redraw_screen = 0; - char *password_realloc; - int i; - - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - switch (sym) { - case XKB_KEY_KP_Enter: - case XKB_KEY_Return: - render_data.auth_state = AUTH_STATE_VALIDATING; - - render(&render_data, config); - // Make sure our render call will actually be displayed on the screen - wl_dispatch_events(); - - if (verify_password()) { - exit(0); - } - - render_data.auth_state = AUTH_STATE_INVALID; - redraw_screen = 1; - - password_size = 1024; - password = malloc(password_size); - password[0] = '\0'; - break; - case XKB_KEY_BackSpace: - i = strlen(password); - if (i > 0) { - password[i - 1] = '\0'; - render_data.auth_state = AUTH_STATE_BACKSPACE; - redraw_screen = 1; - } - break; - case XKB_KEY_Control_L: - case XKB_KEY_Control_R: - case XKB_KEY_Shift_L: - case XKB_KEY_Shift_R: - case XKB_KEY_Caps_Lock: - case XKB_KEY_Shift_Lock: - case XKB_KEY_Meta_L: - case XKB_KEY_Meta_R: - case XKB_KEY_Alt_L: - case XKB_KEY_Alt_R: - case XKB_KEY_Super_L: - case XKB_KEY_Super_R: - case XKB_KEY_Hyper_L: - case XKB_KEY_Hyper_R: - break; // don't draw screen on modifier keys - case XKB_KEY_Escape: - case XKB_KEY_u: - case XKB_KEY_U: - // clear password buffer on ctrl-u (or escape for i3lock compatibility) - if (sym == XKB_KEY_Escape || xkb_state_mod_name_is_active(registry->input->xkb.state, - XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0) { - render_data.auth_state = AUTH_STATE_BACKSPACE; - redraw_screen = 1; - - password_size = 1024; - free(password); - password = malloc(password_size); - password[0] = '\0'; - break; - } - /* fallthrough */ - default: - render_data.auth_state = AUTH_STATE_INPUT; - redraw_screen = 1; - i = strlen(password); - if (i + 1 == password_size) { - password_size += 1024; - password_realloc = realloc(password, password_size); - // reset password if realloc fails. - if (password_realloc == NULL) { - password_size = 1024; - free(password); - password = malloc(password_size); - password[0] = '\0'; - break; - } else { - password = password_realloc; - } - } - password[i] = (char)codepoint; - password[i + 1] = '\0'; - break; - } - if (redraw_screen) { - render(&render_data, config); - wl_dispatch_events(); - // Hide the indicator after a couple of seconds - alarm(5); - } +static void output_scale(void *data, struct wl_output *output, int32_t factor) { + struct swaylock_surface *surface = data; + surface->scale = factor; + if (surface->state->run_display) { + render_frames(surface->state); } } -void render_color(struct window *window, uint32_t color) { - cairo_set_source_u32(window->cairo, color); - cairo_paint(window->cairo); +struct wl_output_listener output_listener = { + .geometry = output_geometry, + .mode = output_mode, + .done = output_done, + .scale = output_scale, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + struct swaylock_state *state = data; + if (strcmp(interface, wl_compositor_interface.name) == 0) { + state->compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, 3); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + state->shm = wl_registry_bind(registry, name, + &wl_shm_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + struct wl_seat *seat = wl_registry_bind( + registry, name, &wl_seat_interface, 1); + wl_seat_add_listener(seat, &seat_listener, state); + } 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)); + surface->state = state; + surface->output = wl_registry_bind(registry, name, + &wl_output_interface, 3); + wl_output_add_listener(surface->output, &output_listener, surface); + wl_list_insert(&state->surfaces, &surface->link); + } } -void render_image(struct window *window, cairo_surface_t *image, enum scaling_mode scaling_mode) { - double width = cairo_image_surface_get_width(image); - double height = cairo_image_surface_get_height(image); - int wwidth = window->width * window->scale; - int wheight = window->height * window->scale; - - switch (scaling_mode) { - case SCALING_MODE_STRETCH: - cairo_scale(window->cairo, - (double) wwidth / width, - (double) wheight / height); - cairo_set_source_surface(window->cairo, image, 0, 0); - break; - case SCALING_MODE_FILL: - { - double window_ratio = (double) wwidth / wheight; - double bg_ratio = width / height; - - if (window_ratio > bg_ratio) { - double scale = (double) wwidth / width; - cairo_scale(window->cairo, scale, scale); - cairo_set_source_surface(window->cairo, image, - 0, - (double) wheight/2 / scale - height/2); - } else { - double scale = (double) wheight / height; - cairo_scale(window->cairo, scale, scale); - cairo_set_source_surface(window->cairo, image, - (double) wwidth/2 / scale - width/2, - 0); - } - break; - } - case SCALING_MODE_FIT: - { - double window_ratio = (double) wwidth / wheight; - double bg_ratio = width / height; - - if (window_ratio > bg_ratio) { - double scale = (double) wheight / height; - cairo_scale(window->cairo, scale, scale); - cairo_set_source_surface(window->cairo, image, - (double) wwidth/2 / scale - width/2, - 0); - } else { - double scale = (double) wwidth / width; - cairo_scale(window->cairo, scale, scale); - cairo_set_source_surface(window->cairo, image, - 0, - (double) wheight/2 / scale - height/2); - } - break; - } - case SCALING_MODE_CENTER: - cairo_set_source_surface(window->cairo, image, - (double) wwidth/2 - width/2, - (double) wheight/2 - height/2); - break; - case SCALING_MODE_TILE: - { - cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image); - cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); - cairo_set_source(window->cairo, pattern); - break; - } - } - - cairo_paint(window->cairo); +static void handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) { + // who cares } -cairo_surface_t *load_image(char *image_path) { - cairo_surface_t *image = NULL; +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; -#ifdef WITH_GDK_PIXBUF - GError *err = NULL; - GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err); - if (!pixbuf) { - sway_abort("Failed to load background image: %s", err->message); - } - image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); - g_object_unref(pixbuf); -#else - image = cairo_image_surface_create_from_png(image_path); -#endif //WITH_GDK_PIXBUF - if (!image) { - sway_abort("Failed to read background image."); - } - - return image; -} +static struct swaylock_state state; int main(int argc, char **argv) { - const char *scaling_mode_str = "fit", *socket_path = NULL; - int i; - void *images = NULL; - config = init_config(); - - render_data.num_images = 0; - render_data.color_set = 0; - render_data.color = 0xFFFFFFFF; - render_data.auth_state = AUTH_STATE_IDLE; - - init_log(L_INFO); - // Install SIGALARM handler (for hiding the typing indicator) - signal(SIGALRM, sigalarm_handler); - static struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"color", required_argument, NULL, 'c'}, {"image", required_argument, NULL, 'i'}, - {"scaling", required_argument, NULL, 0}, + {"scaling", required_argument, NULL, 's'}, {"tiling", no_argument, NULL, 't'}, {"version", no_argument, NULL, 'v'}, {"socket", required_argument, NULL, 'p'}, {"no-unlock-indicator", no_argument, NULL, 'u'}, {"daemonize", no_argument, NULL, 'f'}, - {"font", required_argument, NULL, 0}, - {"line-uses-ring", no_argument, NULL, 'r'}, - {"line-uses-inside", no_argument, NULL, 's'}, - {"textcolor", required_argument, NULL, 0}, - {"insidevercolor", required_argument, NULL, 0}, - {"insidewrongcolor", required_argument, NULL, 0}, - {"insidecolor", required_argument, NULL, 0}, - {"ringvercolor", required_argument, NULL, 0}, - {"ringwrongcolor", required_argument, NULL, 0}, - {"ringcolor", required_argument, NULL, 0}, - {"linecolor", required_argument, NULL, 0}, - {"separatorcolor", required_argument, NULL, 0}, - {"keyhlcolor", required_argument, NULL, 0}, - {"bshlcolor", required_argument, NULL, 0}, - {"indicator-radius", required_argument, NULL, 0}, - {"indicator-thickness", required_argument, NULL, 0}, {0, 0, 0, 0} }; @@ -390,415 +147,124 @@ int main(int argc, char **argv) { "\n" " -h, --help Show help message and quit.\n" " -c, --color Turn the screen into the given color instead of white.\n" - " --scaling Scaling mode: stretch, fill, fit, center, tile.\n" + " -s, --scaling Scaling mode: stretch, fill, fit, center, tile.\n" " -t, --tiling Same as --scaling=tile.\n" " -v, --version Show the version number and quit.\n" " -i, --image [:] Display the given image.\n" " -u, --no-unlock-indicator Disable the unlock indicator.\n" - " -f, --daemonize Detach from the controlling terminal.\n" - " --socket Use the specified socket.\n" - " For more information see `man swaylock`\n"; + " -f, --daemonize Detach from the controlling terminal.\n" + " --socket Use the specified socket.\n"; - - registry = registry_poll(); + struct swaylock_args args = { + .mode = BACKGROUND_MODE_SOLID_COLOR, + .color = 0xFFFFFFFF, + .show_indicator = true, + }; + cairo_surface_t *background_image = NULL; + state.args = args; + wlr_log_init(L_DEBUG, NULL); int c; while (1) { int option_index = 0; - c = getopt_long(argc, argv, "hc:i:srtvuf", long_options, &option_index); + c = getopt_long(argc, argv, "hc:i:s:tvuf", long_options, &option_index); if (c == -1) { break; } switch (c) { - case 'c': - { - render_data.color = parse_color(optarg); - render_data.color_set = 1; + case 'c': { + state.args.color = parse_color(optarg); + state.args.mode = BACKGROUND_MODE_SOLID_COLOR; break; } case 'i': - { - char *image_path = strchr(optarg, ':'); - if (image_path == NULL) { - if (render_data.num_images == 0) { - // Provided image without output - render_data.image = load_image(optarg); - render_data.num_images = -1; - } else { - sway_log(L_ERROR, "output must be defined for all --images or no --images"); - exit(EXIT_FAILURE); - } - } else { - // Provided image for all outputs - if (render_data.num_images == 0) { - images = calloc(registry->outputs->length, sizeof(char*) * 2); - } else if (render_data.num_images == -1) { - sway_log(L_ERROR, "output must be defined for all --images or no --images"); - exit(EXIT_FAILURE); - } - - image_path[0] = '\0'; - ((char**) images)[render_data.num_images * 2] = optarg; - ((char**) images)[render_data.num_images++ * 2 + 1] = ++image_path; + // TODO: Multiple background images (bleh) + background_image = load_background_image(optarg); + if (!background_image) { + return 1; } - break; - } - case 't': - scaling_mode_str = "tile"; - break; - case 'p': - socket_path = optarg; - break; - case 'v': - fprintf(stdout, "swaylock version " SWAY_VERSION "\n"); - exit(EXIT_SUCCESS); - break; - case 'u': - show_indicator = false; - break; - case 'f': { - pid_t t = fork(); - if (t == -1) { - sway_log(L_ERROR, "daemon call failed"); - exit(EXIT_FAILURE); - } else if (t > 0) { - exit(0); - } - break; - } - case 'r': - if (line_source != LINE_SOURCE_DEFAULT) { - sway_log(L_ERROR, "line source options conflict"); - exit(EXIT_FAILURE); - } - line_source = LINE_SOURCE_RING; + state.args.mode = BACKGROUND_MODE_FILL; break; case 's': - if (line_source != LINE_SOURCE_DEFAULT) { - sway_log(L_ERROR, "line source options conflict"); - exit(EXIT_FAILURE); + state.args.mode = parse_background_mode(optarg); + if (state.args.mode == BACKGROUND_MODE_INVALID) { + return 1; } - line_source = LINE_SOURCE_INSIDE; break; - case 0: - if (strcmp(long_options[option_index].name, "font") == 0) { - free(config->font); - config->font = strdup(optarg); - } else if (strcmp(long_options[option_index].name, "scaling") == 0) { - scaling_mode_str = optarg; - } else if (strcmp(long_options[option_index].name, "textcolor") == 0) { - config->colors.text = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "insidevercolor") == 0) { - config->colors.validating.inner_ring = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "insidewrongcolor") == 0) { - config->colors.invalid.inner_ring = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "insidecolor") == 0) { - config->colors.normal.inner_ring = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "ringvercolor") == 0) { - config->colors.validating.outer_ring = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "ringwrongcolor") == 0) { - config->colors.invalid.outer_ring = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "ringcolor") == 0) { - config->colors.normal.outer_ring = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "linecolor") == 0) { - config->colors.line = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "separatorcolor") == 0) { - config->colors.separator = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "keyhlcolor") == 0) { - config->colors.input_cursor = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "bshlcolor") == 0) { - config->colors.backspace_cursor = parse_color(optarg); - } else if (strcmp(long_options[option_index].name, "indicator-radius") == 0) { - config->radius = atoi(optarg); - } else if (strcmp(long_options[option_index].name, "indicator-thickness") == 0) { - config->thickness = atoi(optarg); - } + case 't': + state.args.mode = BACKGROUND_MODE_TILE; + break; + case 'v': +#if defined SWAY_GIT_VERSION && defined SWAY_GIT_BRANCH && defined SWAY_VERSION_DATE + fprintf(stdout, "swaylock version %s (%s, branch \"%s\")\n", + SWAY_GIT_VERSION, SWAY_VERSION_DATE, SWAY_GIT_BRANCH); +#else + fprintf(stdout, "version unknown\n"); +#endif + return 0; + case 'u': + state.args.show_indicator = false; + break; + case 'f': + daemonize(); break; default: fprintf(stderr, "%s", usage); - exit(EXIT_FAILURE); + return 1; } } - render_data.scaling_mode = SCALING_MODE_STRETCH; - if (strcmp(scaling_mode_str, "stretch") == 0) { - render_data.scaling_mode = SCALING_MODE_STRETCH; - } else if (strcmp(scaling_mode_str, "fill") == 0) { - render_data.scaling_mode = SCALING_MODE_FILL; - } else if (strcmp(scaling_mode_str, "fit") == 0) { - render_data.scaling_mode = SCALING_MODE_FIT; - } else if (strcmp(scaling_mode_str, "center") == 0) { - render_data.scaling_mode = SCALING_MODE_CENTER; - } else if (strcmp(scaling_mode_str, "tile") == 0) { - render_data.scaling_mode = SCALING_MODE_TILE; - } else { - sway_abort("Unsupported scaling mode: %s", scaling_mode_str); + wl_list_init(&state.surfaces); + state.xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + state.display = wl_display_connect(NULL); + assert(state.display); + + struct wl_registry *registry = wl_display_get_registry(state.display); + wl_registry_add_listener(registry, ®istry_listener, &state); + wl_display_roundtrip(state.display); + assert(state.compositor && state.layer_shell && state.shm); + if (!state.input_inhibit_manager) { + wlr_log(L_ERROR, "Compositor does not support the input inhibitor " + "protocol, refusing to run insecurely"); } - password_size = 1024; - password = malloc(password_size); - password[0] = '\0'; - render_data.surfaces = create_list(); - if (!socket_path) { - socket_path = get_socketpath(); - if (!socket_path) { - sway_abort("Unable to retrieve socket path"); - } + if (wl_list_empty(&state.surfaces)) { + wlr_log(L_DEBUG, "Exiting - no outputs to show on."); + return 0; } - if (!registry) { - sway_abort("Unable to connect to wayland compositor"); + struct swaylock_surface *surface; + wl_list_for_each(surface, &state.surfaces, link) { + surface->image = background_image; + + surface->surface = wl_compositor_create_surface(state.compositor); + assert(surface->surface); + + surface->layer_surface = zwlr_layer_shell_v1_get_layer_surface( + state.layer_shell, surface->surface, surface->output, + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "lockscreen"); + assert(surface->layer_surface); + + 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); + wl_surface_commit(surface->surface); + wl_display_roundtrip(state.display); } - if (!registry->swaylock) { - sway_abort("swaylock requires the compositor to support the swaylock extension."); + zwlr_input_inhibit_manager_v1_get_inhibitor(state.input_inhibit_manager); + + state.run_display = true; + while (wl_display_dispatch(state.display) != -1 && state.run_display) { + // This space intentionally left blank } - - if (registry->pointer) { - // We don't want swaylock to have a pointer - wl_pointer_destroy(registry->pointer); - registry->pointer = NULL; - } - - for (i = 0; i < registry->outputs->length; ++i) { - struct output_state *output = registry->outputs->items[i]; - struct window *window = window_setup(registry, - output->width, output->height, output->scale, true); - if (!window) { - sway_abort("Failed to create surfaces."); - } - list_add(render_data.surfaces, window); - } - - registry->input->notify = notify_key; - - // Different background for the output - if (render_data.num_images >= 1) { - char **displays_paths = images; - render_data.images = calloc(registry->outputs->length, sizeof(cairo_surface_t*)); - - int socketfd = ipc_open_socket(socket_path); - uint32_t len = 0; - char *outputs = ipc_single_command(socketfd, IPC_GET_OUTPUTS, "", &len); - struct json_object *json_outputs = json_tokener_parse(outputs); - - for (i = 0; i < registry->outputs->length; ++i) { - if (displays_paths[i * 2] != NULL) { - for (int j = 0;; ++j) { - if (j >= json_object_array_length(json_outputs)) { - sway_log(L_ERROR, "%s is not an extant output", displays_paths[i * 2]); - exit(EXIT_FAILURE); - } - - struct json_object *dsp_name, *at_j = json_object_array_get_idx(json_outputs, j); - if (!json_object_object_get_ex(at_j, "name", &dsp_name)) { - sway_abort("output doesn't have a name field"); - } - if (!strcmp(displays_paths[i * 2], json_object_get_string(dsp_name))) { - render_data.images[j] = load_image(displays_paths[i * 2 + 1]); - break; - } - } - } - } - - json_object_put(json_outputs); - close(socketfd); - free(displays_paths); - } - - render(&render_data, config); - bool locked = false; - while (wl_display_dispatch(registry->display) != -1) { - if (!locked) { - for (i = 0; i < registry->outputs->length; ++i) { - struct output_state *output = registry->outputs->items[i]; - struct window *window = render_data.surfaces->items[i]; - lock_set_lock_surface(registry->swaylock, output->output, window->surface); - } - locked = true; - } - } - - // Free surfaces - if (render_data.num_images == -1) { - cairo_surface_destroy(render_data.image); - } else if (render_data.num_images >= 1) { - for (i = 0; i < registry->outputs->length; ++i) { - if (render_data.images[i] != NULL) { - cairo_surface_destroy(render_data.images[i]); - } - } - free(render_data.images); - } - - for (i = 0; i < render_data.surfaces->length; ++i) { - struct window *window = render_data.surfaces->items[i]; - window_teardown(window); - } - list_free(render_data.surfaces); - registry_teardown(registry); - - free_config(config); - return 0; } - -void render(struct render_data *render_data, struct lock_config *config) { - int i; - for (i = 0; i < render_data->surfaces->length; ++i) { - sway_log(L_DEBUG, "Render surface %d of %d", i, render_data->surfaces->length); - struct window *window = render_data->surfaces->items[i]; - if (!window_prerender(window) || !window->cairo) { - continue; - } - int wwidth = window->width * window->scale; - int wheight = window->height * window->scale; - - cairo_save(window->cairo); - cairo_set_operator(window->cairo, CAIRO_OPERATOR_CLEAR); - cairo_paint(window->cairo); - cairo_restore(window->cairo); - - // Reset the transformation matrix - cairo_identity_matrix(window->cairo); - - if (render_data->num_images == 0 || render_data->color_set) { - render_color(window, render_data->color); - } - - if (render_data->num_images == -1) { - // One background for all - render_image(window, render_data->image, render_data->scaling_mode); - } else if (render_data->num_images >= 1) { - // Different backgrounds - if (render_data->images[i] != NULL) { - render_image(window, render_data->images[i], render_data->scaling_mode); - } - } - - // Reset the transformation matrix again - cairo_identity_matrix(window->cairo); - - // Draw specific values (copied from i3) - const float TYPE_INDICATOR_RANGE = M_PI / 3.0f; - const float TYPE_INDICATOR_BORDER_THICKNESS = M_PI / 128.0f; - - // Add visual indicator - if (show_indicator && render_data->auth_state != AUTH_STATE_IDLE) { - // Draw circle - cairo_set_line_width(window->cairo, config->thickness); - cairo_arc(window->cairo, wwidth/2, wheight/2, config->radius, 0, 2 * M_PI); - switch (render_data->auth_state) { - case AUTH_STATE_INPUT: - case AUTH_STATE_BACKSPACE: { - cairo_set_source_u32(window->cairo, config->colors.normal.inner_ring); - cairo_fill_preserve(window->cairo); - cairo_set_source_u32(window->cairo, config->colors.normal.outer_ring); - cairo_stroke(window->cairo); - } break; - case AUTH_STATE_VALIDATING: { - cairo_set_source_u32(window->cairo, config->colors.validating.inner_ring); - cairo_fill_preserve(window->cairo); - cairo_set_source_u32(window->cairo, config->colors.validating.outer_ring); - cairo_stroke(window->cairo); - } break; - case AUTH_STATE_INVALID: { - cairo_set_source_u32(window->cairo, config->colors.invalid.inner_ring); - cairo_fill_preserve(window->cairo); - cairo_set_source_u32(window->cairo, config->colors.invalid.outer_ring); - cairo_stroke(window->cairo); - } break; - default: break; - } - - // Draw a message - char *text = NULL; - cairo_set_source_u32(window->cairo, config->colors.text); - cairo_select_font_face(window->cairo, config->font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); - cairo_set_font_size(window->cairo, config->radius/3.0f); - switch (render_data->auth_state) { - case AUTH_STATE_VALIDATING: - text = "verifying"; - break; - case AUTH_STATE_INVALID: - text = "wrong"; - break; - default: break; - } - - if (text) { - cairo_text_extents_t extents; - double x, y; - - cairo_text_extents(window->cairo, text, &extents); - x = wwidth/2 - ((extents.width/2) + extents.x_bearing); - y = wheight/2 - ((extents.height/2) + extents.y_bearing); - - cairo_move_to(window->cairo, x, y); - cairo_show_text(window->cairo, text); - cairo_close_path(window->cairo); - cairo_new_sub_path(window->cairo); - } - - // Typing indicator: Highlight random part on keypress - if (render_data->auth_state == AUTH_STATE_INPUT || render_data->auth_state == AUTH_STATE_BACKSPACE) { - static double highlight_start = 0; - highlight_start += (rand() % (int)(M_PI * 100)) / 100.0 + M_PI * 0.5; - cairo_arc(window->cairo, wwidth/2, wheight/2, config->radius, highlight_start, highlight_start + TYPE_INDICATOR_RANGE); - if (render_data->auth_state == AUTH_STATE_INPUT) { - cairo_set_source_u32(window->cairo, config->colors.input_cursor); - } else { - cairo_set_source_u32(window->cairo, config->colors.backspace_cursor); - } - cairo_stroke(window->cairo); - - // Draw borders - cairo_set_source_u32(window->cairo, config->colors.separator); - cairo_arc(window->cairo, wwidth/2, wheight/2, config->radius, highlight_start, highlight_start + TYPE_INDICATOR_BORDER_THICKNESS); - cairo_stroke(window->cairo); - - cairo_arc(window->cairo, wwidth/2, wheight/2, config->radius, highlight_start + TYPE_INDICATOR_RANGE, (highlight_start + TYPE_INDICATOR_RANGE) + TYPE_INDICATOR_BORDER_THICKNESS); - cairo_stroke(window->cairo); - } - - switch(line_source) { - case LINE_SOURCE_RING: - switch(render_data->auth_state) { - case AUTH_STATE_VALIDATING: - cairo_set_source_u32(window->cairo, config->colors.validating.outer_ring); - break; - case AUTH_STATE_INVALID: - cairo_set_source_u32(window->cairo, config->colors.invalid.outer_ring); - break; - default: - cairo_set_source_u32(window->cairo, config->colors.normal.outer_ring); - } - break; - case LINE_SOURCE_INSIDE: - switch(render_data->auth_state) { - case AUTH_STATE_VALIDATING: - cairo_set_source_u32(window->cairo, config->colors.validating.inner_ring); - break; - case AUTH_STATE_INVALID: - cairo_set_source_u32(window->cairo, config->colors.invalid.inner_ring); - break; - default: - cairo_set_source_u32(window->cairo, config->colors.normal.inner_ring); - break; - } - break; - default: - cairo_set_source_u32(window->cairo, config->colors.line); - break; - } - // Draw inner + outer border of the circle - cairo_set_line_width(window->cairo, 2.0); - cairo_arc(window->cairo, wwidth/2, wheight/2, config->radius - config->thickness/2, 0, 2*M_PI); - cairo_stroke(window->cairo); - cairo_arc(window->cairo, wwidth/2, wheight/2, config->radius + config->thickness/2, 0, 2*M_PI); - cairo_stroke(window->cairo); - } - window_render(window); - } -} diff --git a/swaylock/meson.build b/swaylock/meson.build new file mode 100644 index 000000000..3cde47a44 --- /dev/null +++ b/swaylock/meson.build @@ -0,0 +1,23 @@ +executable( + 'swaylock', [ + 'main.c', + 'password.c', + 'render.c', + 'seat.c' + ], + include_directories: [sway_inc], + dependencies: [ + cairo, + client_protos, + gdk_pixbuf, + libpam, + math, + pango, + pangocairo, + xkbcommon, + wayland_client, + wlroots, + ], + link_with: [lib_sway_common, lib_sway_client], + install: true +) diff --git a/swaylock/password.c b/swaylock/password.c new file mode 100644 index 000000000..1839f991b --- /dev/null +++ b/swaylock/password.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include +#include "swaylock/swaylock.h" +#include "swaylock/seat.h" +#include "unicode.h" + +static int function_conversation(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *data) { + struct swaylock_password *pw = data; + /* PAM expects an array of responses, one for each message */ + struct pam_response *pam_reply = calloc( + num_msg, sizeof(struct pam_response)); + *resp = pam_reply; + for (int i = 0; i < num_msg; ++i) { + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + pam_reply[i].resp = pw->buffer; + break; + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + break; + } + } + return PAM_SUCCESS; +} + +static bool attempt_password(struct swaylock_password *pw) { + struct passwd *passwd = getpwuid(getuid()); + char *username = passwd->pw_name; + const struct pam_conv local_conversation = { + function_conversation, pw + }; + pam_handle_t *local_auth_handle = NULL; + int pam_err; + if ((pam_err = pam_start("swaylock", username, + &local_conversation, &local_auth_handle)) != PAM_SUCCESS) { + wlr_log(L_ERROR, "PAM returned error %d", pam_err); + } + if ((pam_err = pam_authenticate(local_auth_handle, 0)) != PAM_SUCCESS) { + wlr_log(L_ERROR, "pam_authenticate failed"); + goto fail; + } + if ((pam_err = pam_end(local_auth_handle, pam_err)) != PAM_SUCCESS) { + wlr_log(L_ERROR, "pam_end failed"); + goto fail; + } + // PAM frees this + pw->buffer = NULL; + pw->len = pw->size = 0; + return true; +fail: + // PAM frees this + pw->buffer = NULL; + pw->len = pw->size = 0; + return false; +} + +static bool backspace(struct swaylock_password *pw) { + if (pw->len != 0) { + pw->buffer[--pw->len] = 0; + return true; + } + return false; +} + +static void append_ch(struct swaylock_password *pw, uint32_t codepoint) { + if (!pw->buffer) { + pw->size = 8; + if (!(pw->buffer = malloc(pw->size))) { + // TODO: Display error + return; + } + pw->buffer[0] = 0; + } + size_t utf8_size = utf8_chsize(codepoint); + if (pw->len + utf8_size + 1 >= pw->size) { + size_t size = pw->size * 2; + char *buffer = realloc(pw->buffer, size); + if (!buffer) { + // TODO: Display error + return; + } + pw->size = size; + pw->buffer = buffer; + } + utf8_encode(&pw->buffer[pw->len], codepoint); + pw->buffer[pw->len + utf8_size] = 0; + pw->len += utf8_size; +} + +void swaylock_handle_key(struct swaylock_state *state, + xkb_keysym_t keysym, uint32_t codepoint) { + switch (keysym) { + case XKB_KEY_KP_Enter: /* fallthrough */ + case XKB_KEY_Return: + state->auth_state = AUTH_STATE_VALIDATING; + render_frames(state); + wl_display_roundtrip(state->display); + if (attempt_password(&state->password)) { + state->run_display = false; + break; + } + state->auth_state = AUTH_STATE_INVALID; + render_frames(state); + break; + case XKB_KEY_BackSpace: + if (backspace(&state->password)) { + state->auth_state = AUTH_STATE_BACKSPACE; + render_frames(state); + } + break; + default: + if (codepoint) { + append_ch(&state->password, codepoint); + state->auth_state = AUTH_STATE_INPUT; + render_frames(state); + } + break; + } +} diff --git a/swaylock/render.c b/swaylock/render.c new file mode 100644 index 000000000..cd387be55 --- /dev/null +++ b/swaylock/render.c @@ -0,0 +1,150 @@ +#define _POSIX_C_SOURCE 199506L +#include +#include +#include +#include "cairo.h" +#include "background-image.h" +#include "swaylock/swaylock.h" + +#define M_PI 3.14159265358979323846 +const int ARC_RADIUS = 50; +const int ARC_THICKNESS = 10; +const float TYPE_INDICATOR_RANGE = M_PI / 3.0f; +const float TYPE_INDICATOR_BORDER_THICKNESS = M_PI / 128.0f; + +void render_frame(struct swaylock_surface *surface) { + struct swaylock_state *state = surface->state; + + int buffer_width = surface->width * surface->scale; + int buffer_height = surface->height * surface->scale; + + surface->current_buffer = get_next_buffer(state->shm, + surface->buffers, buffer_width, buffer_height); + cairo_t *cairo = surface->current_buffer->cairo; + cairo_identity_matrix(cairo); + + if (state->args.mode == BACKGROUND_MODE_SOLID_COLOR) { + cairo_set_source_u32(cairo, state->args.color); + cairo_paint(cairo); + } else { + render_background_image(cairo, surface->image, + state->args.mode, buffer_width, buffer_height); + } + cairo_identity_matrix(cairo); + + int arc_radius = ARC_RADIUS * surface->scale; + int arc_thickness = ARC_THICKNESS * surface->scale; + float type_indicator_border_thickness = + TYPE_INDICATOR_BORDER_THICKNESS * surface->scale; + + if (state->args.show_indicator && state->auth_state != AUTH_STATE_IDLE) { + // Draw circle + cairo_set_line_width(cairo, arc_thickness); + cairo_arc(cairo, buffer_width / 2, buffer_height / 2, arc_radius, 0, 2 * M_PI); + switch (state->auth_state) { + case AUTH_STATE_INPUT: + case AUTH_STATE_BACKSPACE: { + cairo_set_source_rgba(cairo, 0, 0, 0, 0.75); + cairo_fill_preserve(cairo); + cairo_set_source_rgb(cairo, 51.0 / 255, 125.0 / 255, 0); + cairo_stroke(cairo); + } break; + case AUTH_STATE_VALIDATING: { + cairo_set_source_rgba(cairo, 0, 114.0 / 255, 255.0 / 255, 0.75); + cairo_fill_preserve(cairo); + cairo_set_source_rgb(cairo, 51.0 / 255, 0, 250.0 / 255); + cairo_stroke(cairo); + } break; + case AUTH_STATE_INVALID: { + cairo_set_source_rgba(cairo, 250.0 / 255, 0, 0, 0.75); + cairo_fill_preserve(cairo); + cairo_set_source_rgb(cairo, 125.0 / 255, 51.0 / 255, 0); + cairo_stroke(cairo); + } break; + default: break; + } + + // Draw a message + char *text = NULL; + cairo_set_source_rgb(cairo, 0, 0, 0); + cairo_select_font_face(cairo, "sans-serif", + CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cairo, arc_radius / 3.0f); + switch (state->auth_state) { + case AUTH_STATE_VALIDATING: + text = "verifying"; + break; + case AUTH_STATE_INVALID: + text = "wrong"; + break; + default: break; + } + + if (text) { + cairo_text_extents_t extents; + double x, y; + cairo_text_extents(cairo, text, &extents); + x = (buffer_width / 2) - + (extents.width / 2 + extents.x_bearing); + y = (buffer_height / 2) - + (extents.height / 2 + extents.y_bearing); + + cairo_move_to(cairo, x, y); + cairo_show_text(cairo, text); + cairo_close_path(cairo); + cairo_new_sub_path(cairo); + } + + // 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; + cairo_arc(cairo, buffer_width / 2, buffer_height / 2, + arc_radius, highlight_start, + highlight_start + TYPE_INDICATOR_RANGE); + if (state->auth_state == AUTH_STATE_INPUT) { + cairo_set_source_rgb(cairo, 51.0 / 255, 219.0 / 255, 0); + } else { + cairo_set_source_rgb(cairo, 219.0 / 255, 51.0 / 255, 0); + } + cairo_stroke(cairo); + + // Draw borders + cairo_set_source_rgb(cairo, 0, 0, 0); + cairo_arc(cairo, buffer_width / 2, buffer_height / 2, + arc_radius, highlight_start, + highlight_start + type_indicator_border_thickness); + cairo_stroke(cairo); + + cairo_arc(cairo, buffer_width / 2, buffer_height / 2, + arc_radius, highlight_start + TYPE_INDICATOR_RANGE, + highlight_start + TYPE_INDICATOR_RANGE + + type_indicator_border_thickness); + cairo_stroke(cairo); + } + + // Draw inner + outer border of the circle + cairo_set_source_rgb(cairo, 0, 0, 0); + cairo_set_line_width(cairo, 2.0 * surface->scale); + cairo_arc(cairo, buffer_width / 2, buffer_height / 2, + arc_radius - arc_thickness / 2, 0, 2 * M_PI); + cairo_stroke(cairo); + cairo_arc(cairo, buffer_width / 2, buffer_height / 2, + arc_radius + arc_thickness / 2, 0, 2 * M_PI); + cairo_stroke(cairo); + } + + wl_surface_set_buffer_scale(surface->surface, surface->scale); + wl_surface_attach(surface->surface, surface->current_buffer->buffer, 0, 0); + wl_surface_damage(surface->surface, 0, 0, surface->width, surface->height); + wl_surface_commit(surface->surface); +} + +void render_frames(struct swaylock_state *state) { + struct swaylock_surface *surface; + wl_list_for_each(surface, &state->surfaces, link) { + render_frame(surface); + } +} diff --git a/swaylock/seat.c b/swaylock/seat.c new file mode 100644 index 000000000..21db7c4fb --- /dev/null +++ b/swaylock/seat.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include +#include "swaylock/swaylock.h" +#include "swaylock/seat.h" + +const char *XKB_MASK_NAMES[MASK_LAST] = { + XKB_MOD_NAME_SHIFT, + XKB_MOD_NAME_CAPS, + XKB_MOD_NAME_CTRL, + XKB_MOD_NAME_ALT, + "Mod2", + "Mod3", + XKB_MOD_NAME_LOGO, + "Mod5", +}; + +const enum mod_bit XKB_MODS[MASK_LAST] = { + MOD_SHIFT, + MOD_CAPS, + MOD_CTRL, + MOD_ALT, + MOD_MOD2, + MOD_MOD3, + MOD_LOGO, + MOD_MOD5 +}; + +static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) { + struct swaylock_state *state = data; + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + wlr_log(L_ERROR, "Unknown keymap format %d, aborting", format); + exit(1); + } + char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (map_shm == MAP_FAILED) { + close(fd); + wlr_log(L_ERROR, "Unable to initialize keymap shm, aborting"); + exit(1); + } + struct xkb_keymap *keymap = xkb_keymap_new_from_string( + state->xkb.context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0); + munmap(map_shm, size); + close(fd); + assert(keymap); + struct xkb_state *xkb_state = xkb_state_new(keymap); + assert(xkb_state); + xkb_keymap_unref(state->xkb.keymap); + xkb_state_unref(state->xkb.state); + state->xkb.keymap = keymap; + state->xkb.state = xkb_state; +} + +static void keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { + // Who cares +} + +static void keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface) { + // Who cares +} + +static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) { + struct swaylock_state *state = data; + enum wl_keyboard_key_state key_state = _key_state; + xkb_keysym_t sym = xkb_state_key_get_one_sym(state->xkb.state, key + 8); + uint32_t keycode = key_state == WL_KEYBOARD_KEY_STATE_PRESSED ? + key + 8 : 0; + uint32_t codepoint = xkb_state_key_get_utf32(state->xkb.state, keycode); + if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED) { + swaylock_handle_key(state, sym, codepoint); + } +} + +static void keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) { + struct swaylock_state *state = data; + xkb_state_update_mask(state->xkb.state, + mods_depressed, mods_latched, mods_locked, 0, 0, group); + xkb_mod_mask_t mask = xkb_state_serialize_mods(state->xkb.state, + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + state->xkb.modifiers = 0; + for (uint32_t i = 0; i < MASK_LAST; ++i) { + if (mask & state->xkb.masks[i]) { + state->xkb.modifiers |= XKB_MODS[i]; + } + } +} + +static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) { + // TODO +} + +static const struct wl_keyboard_listener keyboard_listener = { + .keymap = keyboard_keymap, + .enter = keyboard_enter, + .leave = keyboard_leave, + .key = keyboard_key, + .modifiers = keyboard_modifiers, + .repeat_info = keyboard_repeat_info, +}; + +static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) { + wl_pointer_set_cursor(wl_pointer, serial, NULL, 0, 0); +} + +static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) { + // Who cares +} + +static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { + // Who cares +} + +static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + // Who cares +} + +static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) { + // Who cares +} + +static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { + // Who cares +} + +static void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, + uint32_t axis_source) { + // Who cares +} + +static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) { + // Who cares +} + +static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t discrete) { + // Who cares +} + +static const struct wl_pointer_listener pointer_listener = { + .enter = wl_pointer_enter, + .leave = wl_pointer_leave, + .motion = wl_pointer_motion, + .button = wl_pointer_button, + .axis = wl_pointer_axis, + .frame = wl_pointer_frame, + .axis_source = wl_pointer_axis_source, + .axis_stop = wl_pointer_axis_stop, + .axis_discrete = wl_pointer_axis_discrete, +}; + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) { + struct swaylock_state *state = data; + if ((caps & WL_SEAT_CAPABILITY_POINTER)) { + struct wl_pointer *pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(pointer, &pointer_listener, NULL); + } + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) { + struct wl_keyboard *keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(keyboard, &keyboard_listener, state); + } +} + +static void seat_handle_name(void *data, struct wl_seat *wl_seat, + const char *name) { + // Who cares +} + +const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = seat_handle_name, +};