Merge pull request #1705 from swaywm/swaylock-layers

Port swaylock to layer shell
This commit is contained in:
emersion 2018-04-04 20:16:35 -04:00 committed by GitHub
commit f2153f3f28
Failed to generate hash of commit
21 changed files with 1253 additions and 917 deletions

119
common/background-image.c Normal file
View file

@ -0,0 +1,119 @@
#include <assert.h>
#include <stdbool.h>
#include <wlr/util/log.h>
#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);
}

View file

@ -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
)

101
common/unicode.c Normal file
View file

@ -0,0 +1,101 @@
#include <stdint.h>
#include <stddef.h>
#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;
}

View file

@ -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

View file

@ -1,6 +1,7 @@
#ifndef _SWAY_INPUT_INPUT_MANAGER_H
#define _SWAY_INPUT_INPUT_MANAGER_H
#include <libinput.h>
#include <wlr/types/wlr_input_inhibitor.h>
#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);

View file

@ -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

38
include/swaylock/seat.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef _SWAYLOCK_SEAT_H
#define _SWAYLOCK_SEAT_H
#include <xkbcommon/xkbcommon.h>
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

View file

@ -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 <stdbool.h>
#include <stdint.h>
#include <wayland-client.h>
#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

33
include/unicode.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef _SWAY_UNICODE_H
#define _SWAY_UNICODE_H
#include <stddef.h>
#include <stdint.h>
// 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

View file

@ -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))

View file

@ -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 = []

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_input_inhibit_unstable_v1">
<copyright>
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.
</copyright>
<interface name="zwlr_input_inhibit_manager_v1" version="1">
<description summary="inhibits input events to other clients">
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.
</description>
<request name="get_inhibitor">
<description summary="inhibit input to other clients">
Activates the input inhibitor. As long as the inhibitor is active, the
compositor will not send input events to other clients.
</description>
<arg name="id" type="new_id" interface="zwlr_input_inhibitor_v1"/>
</request>
<enum name="error">
<entry name="already_inhibited" value="0" summary="an input inhibitor is already in use on the compositor"/>
</enum>
</interface>
<interface name="zwlr_input_inhibitor_v1" version="1">
<description summary="inhibits input 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).
</description>
<request name="destroy" type="destructor">
<description summary="destroy the input inhibitor object">
Destroy the inhibitor and allow other clients to receive input.
</description>
</request>
</interface>
</protocol>

View file

@ -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);
}

View file

@ -7,6 +7,7 @@
#include <libinput.h>
#include <math.h>
#include <wlr/backend/libinput.h>
#include <wlr/types/wlr_input_inhibitor.h>
#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;
}

View file

@ -1,5 +1,7 @@
#define _XOPEN_SOURCE 700
#define _POSIX_C_SOURCE 199309L
#include <assert.h>
#include <time.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_xcursor_manager.h>
@ -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);

View file

@ -7,20 +7,12 @@
#include <time.h>
#include <wayland-client.h>
#include <wlr/util/log.h>
#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
}

View file

@ -1,387 +1,144 @@
#define _XOPEN_SOURCE 500
#include "wayland-swaylock-client-protocol.h"
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-names.h>
#include <security/pam_appl.h>
#include <json-c/json.h>
#define _XOPEN_SOURCE 700
#define _POSIX_C_SOURCE 200112L
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <pwd.h>
#include <getopt.h>
#include <signal.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "client/window.h"
#include "client/registry.h"
#include "client/cairo.h"
#include <wayland-client.h>
#include <wlr/util/log.h>
#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; i<num_msg; ++i) {
sway_log(L_DEBUG, "msg[%d]: (%s) %s", i,
msg_style_names[msg[i]->msg_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 <rrggbb[aa]> 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 [<output>:]<path> Display the given image.\n"
" -u, --no-unlock-indicator Disable the unlock indicator.\n"
" -f, --daemonize Detach from the controlling terminal.\n"
" --socket <socket> Use the specified socket.\n"
" For more information see `man swaylock`\n";
" -f, --daemonize Detach from the controlling terminal.\n"
" --socket <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, &registry_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);
}
}

23
swaylock/meson.build Normal file
View file

@ -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
)

126
swaylock/password.c Normal file
View file

@ -0,0 +1,126 @@
#include <assert.h>
#include <pwd.h>
#include <security/pam_appl.h>
#include <stdlib.h>
#include <unistd.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
#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;
}
}

150
swaylock/render.c Normal file
View file

@ -0,0 +1,150 @@
#define _POSIX_C_SOURCE 199506L
#include <math.h>
#include <stdlib.h>
#include <wayland-client.h>
#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);
}
}

190
swaylock/seat.c Normal file
View file

@ -0,0 +1,190 @@
#include <assert.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
#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,
};