swaylock/main.c
Dominik Bendle 40e9098a74 Add option show-failed-attempts
Keeps track of unsuccessful authentication attempts via an int counter
in the state struct. Displays on the unlock indicator, but will be
replaced by the Caps Lock text if enabled.
2019-01-29 20:44:32 +01:00

1118 lines
32 KiB
C

#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wordexp.h>
#include "background-image.h"
#include "cairo.h"
#include "comm.h"
#include "log.h"
#include "loop.h"
#include "pool-buffer.h"
#include "seat.h"
#include "swaylock.h"
#include "wlr-input-inhibitor-unstable-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
static uint32_t parse_color(const char *color) {
if (color[0] == '#') {
++color;
}
int len = strlen(color);
if (len != 6 && len != 8) {
swaylock_log(LOG_DEBUG, "Invalid color %s, defaulting to 0xFFFFFFFF",
color);
return 0xFFFFFFFF;
}
uint32_t res = (uint32_t)strtoul(color, NULL, 16);
if (strlen(color) == 6) {
res = (res << 8) | 0xFF;
}
return res;
}
int lenient_strcmp(char *a, char *b) {
if (a == b) {
return 0;
} else if (!a) {
return -1;
} else if (!b) {
return 1;
} else {
return strcmp(a, b);
}
}
static void daemonize(void) {
int fds[2];
if (pipe(fds) != 0) {
swaylock_log(LOG_ERROR, "Failed to pipe");
exit(1);
}
if (fork() == 0) {
setsid();
close(fds[0]);
int devnull = open("/dev/null", O_RDWR);
dup2(STDOUT_FILENO, devnull);
dup2(STDERR_FILENO, devnull);
close(devnull);
uint8_t success = 0;
if (chdir("/") != 0) {
write(fds[1], &success, 1);
exit(1);
}
success = 1;
if (write(fds[1], &success, 1) != 1) {
exit(1);
}
close(fds[1]);
} else {
close(fds[1]);
uint8_t success;
if (read(fds[0], &success, 1) != 1 || !success) {
swaylock_log(LOG_ERROR, "Failed to daemonize");
exit(1);
}
close(fds[0]);
exit(0);
}
}
static void destroy_surface(struct swaylock_surface *surface) {
wl_list_remove(&surface->link);
if (surface->layer_surface != NULL) {
zwlr_layer_surface_v1_destroy(surface->layer_surface);
}
if (surface->surface != NULL) {
wl_surface_destroy(surface->surface);
}
destroy_buffer(&surface->buffers[0]);
destroy_buffer(&surface->buffers[1]);
wl_output_destroy(surface->output);
free(surface);
}
static const struct zwlr_layer_surface_v1_listener layer_surface_listener;
static cairo_surface_t *select_image(struct swaylock_state *state,
struct swaylock_surface *surface);
static bool surface_is_opaque(struct swaylock_surface *surface) {
if (surface->image) {
return cairo_surface_get_content(surface->image) == CAIRO_CONTENT_COLOR;
}
return (surface->state->args.colors.background & 0xff) == 0xff;
}
static void create_layer_surface(struct swaylock_surface *surface) {
struct swaylock_state *state = surface->state;
surface->image = select_image(state, surface);
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);
if (surface_is_opaque(surface) &&
surface->state->args.mode != BACKGROUND_MODE_CENTER &&
surface->state->args.mode != BACKGROUND_MODE_FIT) {
struct wl_region *region =
wl_compositor_create_region(surface->state->compositor);
wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
wl_surface_set_opaque_region(surface->surface, region);
wl_region_destroy(region);
}
wl_surface_commit(surface->surface);
}
static void layer_surface_configure(void *data,
struct zwlr_layer_surface_v1 *layer_surface,
uint32_t serial, uint32_t width, uint32_t height) {
struct swaylock_surface *surface = data;
surface->width = width;
surface->height = height;
zwlr_layer_surface_v1_ack_configure(layer_surface, serial);
render_frame(surface);
}
static void layer_surface_closed(void *data,
struct zwlr_layer_surface_v1 *layer_surface) {
struct swaylock_surface *surface = data;
destroy_surface(surface);
}
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layer_surface_configure,
.closed = layer_surface_closed,
};
static const struct wl_callback_listener surface_frame_listener;
static void surface_frame_handle_done(void *data, struct wl_callback *callback,
uint32_t time) {
struct swaylock_surface *surface = data;
wl_callback_destroy(callback);
surface->frame_pending = false;
if (surface->dirty) {
// Schedule a frame in case the surface is damaged again
struct wl_callback *callback = wl_surface_frame(surface->surface);
wl_callback_add_listener(callback, &surface_frame_listener, surface);
surface->frame_pending = true;
render_frame(surface);
surface->dirty = false;
}
}
static const struct wl_callback_listener surface_frame_listener = {
.done = surface_frame_handle_done,
};
void damage_surface(struct swaylock_surface *surface) {
surface->dirty = true;
if (surface->frame_pending) {
return;
}
struct wl_callback *callback = wl_surface_frame(surface->surface);
wl_callback_add_listener(callback, &surface_frame_listener, surface);
surface->frame_pending = true;
wl_surface_commit(surface->surface);
}
void damage_state(struct swaylock_state *state) {
struct swaylock_surface *surface;
wl_list_for_each(surface, &state->surfaces, link) {
damage_surface(surface);
}
}
static void handle_wl_output_geometry(void *data, struct wl_output *wl_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) {
struct swaylock_surface *surface = data;
surface->subpixel = subpixel;
if (surface->state->run_display) {
damage_surface(surface);
}
}
static void handle_wl_output_mode(void *data, struct wl_output *output,
uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
// Who cares
}
static void handle_wl_output_done(void *data, struct wl_output *output) {
// Who cares
}
static void handle_wl_output_scale(void *data, struct wl_output *output,
int32_t factor) {
struct swaylock_surface *surface = data;
surface->scale = factor;
if (surface->state->run_display) {
damage_surface(surface);
}
}
struct wl_output_listener _wl_output_listener = {
.geometry = handle_wl_output_geometry,
.mode = handle_wl_output_mode,
.done = handle_wl_output_done,
.scale = handle_wl_output_scale,
};
static void handle_xdg_output_logical_size(void *data, struct zxdg_output_v1 *output,
int width, int height) {
// Who cares
}
static void handle_xdg_output_logical_position(void *data,
struct zxdg_output_v1 *output, int x, int y) {
// Who cares
}
static void handle_xdg_output_name(void *data, struct zxdg_output_v1 *output,
const char *name) {
swaylock_log(LOG_DEBUG, "output name is %s", name);
struct swaylock_surface *surface = data;
surface->xdg_output = output;
surface->output_name = strdup(name);
}
static void handle_xdg_output_description(void *data, struct zxdg_output_v1 *output,
const char *description) {
// Who cares
}
static void handle_xdg_output_done(void *data, struct zxdg_output_v1 *output) {
// Who cares
}
struct zxdg_output_v1_listener _xdg_output_listener = {
.logical_position = handle_xdg_output_logical_position,
.logical_size = handle_xdg_output_logical_size,
.done = handle_xdg_output_done,
.name = handle_xdg_output_name,
.description = handle_xdg_output_description,
};
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, 3);
struct swaylock_seat *swaylock_seat =
calloc(1, sizeof(struct swaylock_seat));
swaylock_seat->state = state;
wl_seat_add_listener(seat, &seat_listener, swaylock_seat);
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
state->layer_shell = wl_registry_bind(
registry, name, &zwlr_layer_shell_v1_interface, 1);
} else if (strcmp(interface, zwlr_input_inhibit_manager_v1_interface.name) == 0) {
state->input_inhibit_manager = wl_registry_bind(
registry, name, &zwlr_input_inhibit_manager_v1_interface, 1);
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
state->zxdg_output_manager = wl_registry_bind(
registry, name, &zxdg_output_manager_v1_interface, 2);
} 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);
surface->output_global_name = name;
wl_output_add_listener(surface->output, &_wl_output_listener, surface);
wl_list_insert(&state->surfaces, &surface->link);
if (state->run_display) {
create_layer_surface(surface);
wl_display_roundtrip(state->display);
}
}
}
static void handle_global_remove(void *data, struct wl_registry *registry,
uint32_t name) {
struct swaylock_state *state = data;
struct swaylock_surface *surface;
wl_list_for_each(surface, &state->surfaces, link) {
if (surface->output_global_name == name) {
destroy_surface(surface);
break;
}
}
}
static const struct wl_registry_listener registry_listener = {
.global = handle_global,
.global_remove = handle_global_remove,
};
static cairo_surface_t *select_image(struct swaylock_state *state,
struct swaylock_surface *surface) {
struct swaylock_image *image;
cairo_surface_t *default_image = NULL;
wl_list_for_each(image, &state->images, link) {
if (lenient_strcmp(image->output_name, surface->output_name) == 0) {
return image->cairo_surface;
} else if (!image->output_name) {
default_image = image->cairo_surface;
}
}
return default_image;
}
static void load_image(char *arg, struct swaylock_state *state) {
// [[<output>]:]<path>
struct swaylock_image *image = calloc(1, sizeof(struct swaylock_image));
char *separator = strchr(arg, ':');
if (separator) {
*separator = '\0';
image->output_name = separator == arg ? NULL : strdup(arg);
image->path = strdup(separator + 1);
} else {
image->output_name = NULL;
image->path = strdup(arg);
}
struct swaylock_image *iter_image, *temp;
wl_list_for_each_safe(iter_image, temp, &state->images, link) {
if (lenient_strcmp(iter_image->output_name, image->output_name) == 0) {
if (image->output_name) {
swaylock_log(LOG_DEBUG,
"Replacing image defined for output %s with %s",
image->output_name, image->path);
} else {
swaylock_log(LOG_DEBUG, "Replacing default image with %s",
image->path);
}
wl_list_remove(&iter_image->link);
free(iter_image->cairo_surface);
free(iter_image->output_name);
free(iter_image->path);
free(iter_image);
break;
}
}
// Bash doesn't replace the ~ with $HOME if the output name is supplied
wordexp_t p;
if (wordexp(image->path, &p, 0) == 0) {
free(image->path);
image->path = strdup(p.we_wordv[0]);
wordfree(&p);
}
// Load the actual image
image->cairo_surface = load_background_image(image->path);
if (!image->cairo_surface) {
free(image);
return;
}
wl_list_insert(&state->images, &image->link);
swaylock_log(LOG_DEBUG, "Loaded image %s for output %s", image->path,
image->output_name ? image->output_name : "*");
}
static void set_default_colors(struct swaylock_colors *colors) {
colors->background = 0xFFFFFFFF;
colors->bs_highlight = 0xDB3300FF;
colors->key_highlight = 0x33DB00FF;
colors->caps_lock_bs_highlight = 0xDB3300FF;
colors->caps_lock_key_highlight = 0x33DB00FF;
colors->separator = 0x000000FF;
colors->inside = (struct swaylock_colorset){
.input = 0x000000C0,
.cleared = 0xE5A445C0,
.caps_lock = 0x000000C0,
.verifying = 0x0072FFC0,
.wrong = 0xFA0000C0,
};
colors->line = (struct swaylock_colorset){
.input = 0x000000FF,
.cleared = 0x000000FF,
.caps_lock = 0x000000FF,
.verifying = 0x000000FF,
.wrong = 0x000000FF,
};
colors->ring = (struct swaylock_colorset){
.input = 0x337D00FF,
.cleared = 0xE5A445FF,
.caps_lock = 0xE5A445FF,
.verifying = 0x3300FFFF,
.wrong = 0x7D3300FF,
};
colors->text = (struct swaylock_colorset){
.input = 0xE5A445FF,
.cleared = 0x000000FF,
.caps_lock = 0xE5A445FF,
.verifying = 0x000000FF,
.wrong = 0x000000FF,
};
}
enum line_mode {
LM_LINE,
LM_INSIDE,
LM_RING,
};
static int parse_options(int argc, char **argv, struct swaylock_state *state,
enum line_mode *line_mode, char **config_path) {
enum long_option_codes {
LO_BS_HL_COLOR = 256,
LO_CAPS_LOCK_BS_HL_COLOR,
LO_CAPS_LOCK_KEY_HL_COLOR,
LO_FONT,
LO_IND_RADIUS,
LO_IND_THICKNESS,
LO_INSIDE_COLOR,
LO_INSIDE_CLEAR_COLOR,
LO_INSIDE_CAPS_LOCK_COLOR,
LO_INSIDE_VER_COLOR,
LO_INSIDE_WRONG_COLOR,
LO_KEY_HL_COLOR,
LO_LINE_COLOR,
LO_LINE_CLEAR_COLOR,
LO_LINE_CAPS_LOCK_COLOR,
LO_LINE_VER_COLOR,
LO_LINE_WRONG_COLOR,
LO_RING_COLOR,
LO_RING_CLEAR_COLOR,
LO_RING_CAPS_LOCK_COLOR,
LO_RING_VER_COLOR,
LO_RING_WRONG_COLOR,
LO_SEP_COLOR,
LO_TEXT_COLOR,
LO_TEXT_CLEAR_COLOR,
LO_TEXT_CAPS_LOCK_COLOR,
LO_TEXT_VER_COLOR,
LO_TEXT_WRONG_COLOR,
};
static struct option long_options[] = {
{"config", required_argument, NULL, 'C'},
{"color", required_argument, NULL, 'c'},
{"debug", no_argument, NULL, 'd'},
{"ignore-empty-password", no_argument, NULL, 'e'},
{"daemonize", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"image", required_argument, NULL, 'i'},
{"disable-caps-lock-text", no_argument, NULL, 'L'},
{"indicator-caps-lock", no_argument, NULL, 'l'},
{"line-uses-inside", no_argument, NULL, 'n'},
{"socket", required_argument, NULL, 'p'},
{"line-uses-ring", no_argument, NULL, 'r'},
{"scaling", required_argument, NULL, 's'},
{"tiling", no_argument, NULL, 't'},
{"no-unlock-indicator", no_argument, NULL, 'u'},
{"show-failed-attempts", no_argument, NULL, 'F'},
{"version", no_argument, NULL, 'v'},
{"bs-hl-color", required_argument, NULL, LO_BS_HL_COLOR},
{"caps-lock-bs-hl-color", required_argument, NULL, LO_CAPS_LOCK_BS_HL_COLOR},
{"caps-lock-key-hl-color", required_argument, NULL, LO_CAPS_LOCK_KEY_HL_COLOR},
{"font", required_argument, NULL, LO_FONT},
{"indicator-radius", required_argument, NULL, LO_IND_RADIUS},
{"indicator-thickness", required_argument, NULL, LO_IND_THICKNESS},
{"inside-color", required_argument, NULL, LO_INSIDE_COLOR},
{"inside-clear-color", required_argument, NULL, LO_INSIDE_CLEAR_COLOR},
{"inside-caps-lock-color", required_argument, NULL, LO_INSIDE_CAPS_LOCK_COLOR},
{"inside-ver-color", required_argument, NULL, LO_INSIDE_VER_COLOR},
{"inside-wrong-color", required_argument, NULL, LO_INSIDE_WRONG_COLOR},
{"key-hl-color", required_argument, NULL, LO_KEY_HL_COLOR},
{"line-color", required_argument, NULL, LO_LINE_COLOR},
{"line-clear-color", required_argument, NULL, LO_LINE_CLEAR_COLOR},
{"line-caps-lock-color", required_argument, NULL, LO_LINE_CAPS_LOCK_COLOR},
{"line-ver-color", required_argument, NULL, LO_LINE_VER_COLOR},
{"line-wrong-color", required_argument, NULL, LO_LINE_WRONG_COLOR},
{"ring-color", required_argument, NULL, LO_RING_COLOR},
{"ring-clear-color", required_argument, NULL, LO_RING_CLEAR_COLOR},
{"ring-caps-lock-color", required_argument, NULL, LO_RING_CAPS_LOCK_COLOR},
{"ring-ver-color", required_argument, NULL, LO_RING_VER_COLOR},
{"ring-wrong-color", required_argument, NULL, LO_RING_WRONG_COLOR},
{"separator-color", required_argument, NULL, LO_SEP_COLOR},
{"text-color", required_argument, NULL, LO_TEXT_COLOR},
{"text-clear-color", required_argument, NULL, LO_TEXT_CLEAR_COLOR},
{"text-caps-lock-color", required_argument, NULL, LO_TEXT_CAPS_LOCK_COLOR},
{"text-ver-color", required_argument, NULL, LO_TEXT_VER_COLOR},
{"text-wrong-color", required_argument, NULL, LO_TEXT_WRONG_COLOR},
{0, 0, 0, 0}
};
const char usage[] =
"Usage: swaylock [options...]\n"
"\n"
" -C, --config <config_file> "
"Path to the config file.\n"
" -c, --color <color> "
"Turn the screen into the given color instead of white.\n"
" -d, --debug "
"Enable debugging output.\n"
" -e, --ignore-empty-password "
"When an empty password is provided, do not validate it.\n"
" -f, --daemonize "
"Detach from the controlling terminal after locking.\n"
" -h, --help "
"Show help message and quit.\n"
" -i, --image [[<output>]:]<path> "
"Display the given image.\n"
" -L, --disable-caps-lock-text "
"Disable the Caps Lock text.\n"
" -l, --indicator-caps-lock "
"Show the current Caps Lock state also on the indicator.\n"
" -s, --scaling <mode> "
"Scaling mode: stretch, fill, fit, center, tile.\n"
" -t, --tiling "
"Same as --scaling=tile.\n"
" -u, --no-unlock-indicator "
"Disable the unlock indicator.\n"
" -F, --show-failed-attempts "
"Show current count of failed authentication attempts.\n"
" -v, --version "
"Show the version number and quit.\n"
" --bs-hl-color <color> "
"Sets the color of backspace highlight segments.\n"
" --caps-lock-bs-hl-color <color> "
"Sets the color of backspace highlight segments when Caps Lock "
"is active.\n"
" --caps-lock-key-hl-color <color> "
"Sets the color of the key press highlight segments when "
"Caps Lock is active.\n"
" --font <font> "
"Sets the font of the text.\n"
" --indicator-radius <radius> "
"Sets the indicator radius.\n"
" --indicator-thickness <thick> "
"Sets the indicator thickness.\n"
" --inside-color <color> "
"Sets the color of the inside of the indicator.\n"
" --inside-clear-color <color> "
"Sets the color of the inside of the indicator when cleared.\n"
" --inside-caps-lock-color <color> "
"Sets the color of the inside of the indicator when Caps Lock "
"is active.\n"
" --inside-ver-color <color> "
"Sets the color of the inside of the indicator when verifying.\n"
" --inside-wrong-color <color> "
"Sets the color of the inside of the indicator when invalid.\n"
" --key-hl-color <color> "
"Sets the color of the key press highlight segments.\n"
" --line-color <color> "
"Sets the color of the line between the inside and ring.\n"
" --line-clear-color <color> "
"Sets the color of the line between the inside and ring when "
"cleared.\n"
" --line-caps-lock-color <color> "
"Sets the color of the line between the inside and ring when "
"Caps Lock is active.\n"
" --line-ver-color <color> "
"Sets the color of the line between the inside and ring when "
"verifying.\n"
" --line-wrong-color <color> "
"Sets the color of the line between the inside and ring when "
"invalid.\n"
" -n, --line-uses-inside "
"Use the inside color for the line between the inside and ring.\n"
" -r, --line-uses-ring "
"Use the ring color for the line between the inside and ring.\n"
" --ring-color <color> "
"Sets the color of the ring of the indicator.\n"
" --ring-clear-color <color> "
"Sets the color of the ring of the indicator when cleared.\n"
" --ring-caps-lock-color <color> "
"Sets the color of the ring of the indicator when Caps Lock "
"is active.\n"
" --ring-ver-color <color> "
"Sets the color of the ring of the indicator when verifying.\n"
" --ring-wrong-color <color> "
"Sets the color of the ring of the indicator when invalid.\n"
" --separator-color <color> "
"Sets the color of the lines that separate highlight segments.\n"
" --text-color <color> "
"Sets the color of the text.\n"
" --text-clear-color <color> "
"Sets the color of the text when cleared.\n"
" --text-caps-lock-color <color> "
"Sets the color of the text when Caps Lock is active.\n"
" --text-ver-color <color> "
"Sets the color of the text when verifying.\n"
" --text-wrong-color <color> "
"Sets the color of the text when invalid.\n"
"\n"
"All <color> options are of the form <rrggbb[aa]>.\n";
int c;
optind = 1;
while (1) {
int opt_idx = 0;
c = getopt_long(argc, argv, "c:deFfhi:Llnrs:tuvC:", long_options,
&opt_idx);
if (c == -1) {
break;
}
switch (c) {
case 'C':
if (config_path) {
*config_path = strdup(optarg);
}
break;
case 'c':
if (state) {
state->args.colors.background = parse_color(optarg);
}
break;
case 'd':
swaylock_log_init(LOG_DEBUG);
break;
case 'e':
if (state) {
state->args.ignore_empty = true;
}
break;
case 'f':
if (state) {
state->args.daemonize = true;
}
break;
case 'i':
if (state) {
load_image(optarg, state);
}
break;
case 'L':
if (state) {
state->args.show_caps_lock_text = false;
}
break;
case 'l':
if (state) {
state->args.show_caps_lock_indicator = true;
}
break;
case 'n':
if (line_mode) {
*line_mode = LM_INSIDE;
}
break;
case 'r':
if (line_mode) {
*line_mode = LM_RING;
}
break;
case 's':
if (state) {
state->args.mode = parse_background_mode(optarg);
if (state->args.mode == BACKGROUND_MODE_INVALID) {
return 1;
}
}
break;
case 't':
if (state) {
state->args.mode = BACKGROUND_MODE_TILE;
}
break;
case 'u':
if (state) {
state->args.show_indicator = false;
}
break;
case 'F':
if (state) {
state->args.show_failed_attempts = true;
}
break;
case 'v':
fprintf(stdout, "swaylock version " SWAYLOCK_VERSION "\n");
exit(EXIT_SUCCESS);
break;
case LO_BS_HL_COLOR:
if (state) {
state->args.colors.bs_highlight = parse_color(optarg);
}
break;
case LO_CAPS_LOCK_BS_HL_COLOR:
if (state) {
state->args.colors.caps_lock_bs_highlight = parse_color(optarg);
}
break;
case LO_CAPS_LOCK_KEY_HL_COLOR:
if (state) {
state->args.colors.caps_lock_key_highlight = parse_color(optarg);
}
break;
case LO_FONT:
if (state) {
free(state->args.font);
state->args.font = strdup(optarg);
}
break;
case LO_IND_RADIUS:
if (state) {
state->args.radius = strtol(optarg, NULL, 0);
}
break;
case LO_IND_THICKNESS:
if (state) {
state->args.thickness = strtol(optarg, NULL, 0);
}
break;
case LO_INSIDE_COLOR:
if (state) {
state->args.colors.inside.input = parse_color(optarg);
}
break;
case LO_INSIDE_CLEAR_COLOR:
if (state) {
state->args.colors.inside.cleared = parse_color(optarg);
}
break;
case LO_INSIDE_CAPS_LOCK_COLOR:
if (state) {
state->args.colors.inside.caps_lock = parse_color(optarg);
}
break;
case LO_INSIDE_VER_COLOR:
if (state) {
state->args.colors.inside.verifying = parse_color(optarg);
}
break;
case LO_INSIDE_WRONG_COLOR:
if (state) {
state->args.colors.inside.wrong = parse_color(optarg);
}
break;
case LO_KEY_HL_COLOR:
if (state) {
state->args.colors.key_highlight = parse_color(optarg);
}
break;
case LO_LINE_COLOR:
if (state) {
state->args.colors.line.input = parse_color(optarg);
}
break;
case LO_LINE_CLEAR_COLOR:
if (state) {
state->args.colors.line.cleared = parse_color(optarg);
}
break;
case LO_LINE_CAPS_LOCK_COLOR:
if (state) {
state->args.colors.line.caps_lock = parse_color(optarg);
}
break;
case LO_LINE_VER_COLOR:
if (state) {
state->args.colors.line.verifying = parse_color(optarg);
}
break;
case LO_LINE_WRONG_COLOR:
if (state) {
state->args.colors.line.wrong = parse_color(optarg);
}
break;
case LO_RING_COLOR:
if (state) {
state->args.colors.ring.input = parse_color(optarg);
}
break;
case LO_RING_CLEAR_COLOR:
if (state) {
state->args.colors.ring.cleared = parse_color(optarg);
}
break;
case LO_RING_CAPS_LOCK_COLOR:
if (state) {
state->args.colors.ring.caps_lock = parse_color(optarg);
}
break;
case LO_RING_VER_COLOR:
if (state) {
state->args.colors.ring.verifying = parse_color(optarg);
}
break;
case LO_RING_WRONG_COLOR:
if (state) {
state->args.colors.ring.wrong = parse_color(optarg);
}
break;
case LO_SEP_COLOR:
if (state) {
state->args.colors.separator = parse_color(optarg);
}
break;
case LO_TEXT_COLOR:
if (state) {
state->args.colors.text.input = parse_color(optarg);
}
break;
case LO_TEXT_CLEAR_COLOR:
if (state) {
state->args.colors.text.cleared = parse_color(optarg);
}
break;
case LO_TEXT_CAPS_LOCK_COLOR:
if (state) {
state->args.colors.text.caps_lock = parse_color(optarg);
}
break;
case LO_TEXT_VER_COLOR:
if (state) {
state->args.colors.text.verifying = parse_color(optarg);
}
break;
case LO_TEXT_WRONG_COLOR:
if (state) {
state->args.colors.text.wrong = parse_color(optarg);
}
break;
default:
fprintf(stderr, "%s", usage);
return 1;
}
}
return 0;
}
static bool file_exists(const char *path) {
return path && access(path, R_OK) != -1;
}
static char *get_config_path(void) {
static const char *config_paths[] = {
"$HOME/.swaylock/config",
"$XDG_CONFIG_HOME/swaylock/config",
SYSCONFDIR "/swaylock/config",
};
char *config_home = getenv("XDG_CONFIG_HOME");
if (!config_home || config_home[0] == '\0') {
config_paths[1] = "$HOME/.config/swaylock/config";
}
wordexp_t p;
char *path;
for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) {
if (wordexp(config_paths[i], &p, 0) == 0) {
path = strdup(p.we_wordv[0]);
wordfree(&p);
if (file_exists(path)) {
return path;
}
free(path);
}
}
return NULL;
}
static int load_config(char *path, struct swaylock_state *state,
enum line_mode *line_mode) {
FILE *config = fopen(path, "r");
if (!config) {
swaylock_log(LOG_ERROR, "Failed to read config. Running without it.");
return 0;
}
char *line = NULL;
size_t line_size = 0;
ssize_t nread;
int line_number = 0;
int result = 0;
while ((nread = getline(&line, &line_size, config)) != -1) {
line_number++;
if (line[nread - 1] == '\n') {
line[--nread] = '\0';
}
if (!*line || line[0] == '#') {
continue;
}
swaylock_log(LOG_DEBUG, "Config Line #%d: %s", line_number, line);
char *flag = malloc(nread + 3);
if (flag == NULL) {
free(line);
free(config);
swaylock_log(LOG_ERROR, "Failed to allocate memory");
return 0;
}
sprintf(flag, "--%s", line);
char *argv[] = {"swaylock", flag};
result = parse_options(2, argv, state, line_mode, NULL);
free(flag);
if (result != 0) {
break;
}
}
free(line);
fclose(config);
return 0;
}
static struct swaylock_state state;
static void display_in(int fd, short mask, void *data) {
if (wl_display_dispatch(state.display) == -1) {
state.run_display = false;
}
}
static void comm_in(int fd, short mask, void *data) {
if (read_comm_reply()) {
// Authentication succeeded
state.run_display = false;
} else {
state.auth_state = AUTH_STATE_INVALID;
schedule_indicator_clear(&state);
++state.failed_attempts;
damage_state(&state);
}
}
int main(int argc, char **argv) {
swaylock_log_init(LOG_ERROR);
initialize_pw_backend(argc, argv);
enum line_mode line_mode = LM_LINE;
state.failed_attempts = 0;
state.args = (struct swaylock_args){
.mode = BACKGROUND_MODE_FILL,
.font = strdup("sans-serif"),
.radius = 50,
.thickness = 10,
.ignore_empty = false,
.show_indicator = true,
.show_caps_lock_indicator = false,
.show_caps_lock_text = true,
.show_failed_attempts = false
};
wl_list_init(&state.images);
set_default_colors(&state.args.colors);
char *config_path = NULL;
int result = parse_options(argc, argv, NULL, NULL, &config_path);
if (result != 0) {
free(config_path);
return result;
}
if (!config_path) {
config_path = get_config_path();
}
if (config_path) {
swaylock_log(LOG_DEBUG, "Found config at %s", config_path);
int config_status = load_config(config_path, &state, &line_mode);
free(config_path);
if (config_status != 0) {
free(state.args.font);
return config_status;
}
}
if (argc > 1) {
swaylock_log(LOG_DEBUG, "Parsing CLI Args");
int result = parse_options(argc, argv, &state, &line_mode, NULL);
if (result != 0) {
free(state.args.font);
return result;
}
}
if (line_mode == LM_INSIDE) {
state.args.colors.line = state.args.colors.inside;
} else if (line_mode == LM_RING) {
state.args.colors.line = state.args.colors.ring;
}
#ifdef __linux__
// Most non-linux platforms require root to mlock()
if (mlock(state.password.buffer, sizeof(state.password.buffer)) != 0) {
swaylock_log(LOG_ERROR, "Unable to mlock() password memory.");
return EXIT_FAILURE;
}
#endif
wl_list_init(&state.surfaces);
state.xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
state.display = wl_display_connect(NULL);
if (!state.display) {
free(state.args.font);
swaylock_log(LOG_ERROR, "Unable to connect to the compositor. "
"If your compositor is running, check or set the "
"WAYLAND_DISPLAY environment variable.");
return EXIT_FAILURE;
}
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) {
free(state.args.font);
swaylock_log(LOG_ERROR, "Compositor does not support the input "
"inhibitor protocol, refusing to run insecurely");
return 1;
}
if (wl_list_empty(&state.surfaces)) {
free(state.args.font);
swaylock_log(LOG_ERROR, "Exiting - no outputs to show on.");
return 0;
}
zwlr_input_inhibit_manager_v1_get_inhibitor(state.input_inhibit_manager);
if (wl_display_roundtrip(state.display) == -1) {
free(state.args.font);
swaylock_log(LOG_ERROR, "Exiting - failed to inhibit input:"
" is another lockscreen already running?");
return 2;
}
if (state.zxdg_output_manager) {
struct swaylock_surface *surface;
wl_list_for_each(surface, &state.surfaces, link) {
surface->xdg_output = zxdg_output_manager_v1_get_xdg_output(
state.zxdg_output_manager, surface->output);
zxdg_output_v1_add_listener(
surface->xdg_output, &_xdg_output_listener, surface);
}
wl_display_roundtrip(state.display);
} else {
swaylock_log(LOG_INFO, "Compositor does not support zxdg output "
"manager, images assigned to named outputs will not work");
}
struct swaylock_surface *surface;
wl_list_for_each(surface, &state.surfaces, link) {
create_layer_surface(surface);
}
if (state.args.daemonize) {
wl_display_roundtrip(state.display);
daemonize();
}
state.eventloop = loop_create();
loop_add_fd(state.eventloop, wl_display_get_fd(state.display), POLLIN,
display_in, NULL);
loop_add_fd(state.eventloop, get_comm_reply_fd(), POLLIN, comm_in, NULL);
state.run_display = true;
while (state.run_display) {
errno = 0;
if (wl_display_flush(state.display) == -1 && errno != EAGAIN) {
break;
}
loop_poll(state.eventloop);
}
free(state.args.font);
return 0;
}