diff options
author | Birger Schacht <birger@rantanplan.org> | 2019-05-18 10:00:50 -0700 |
---|---|---|
committer | Birger Schacht <birger@rantanplan.org> | 2019-05-18 10:00:50 -0700 |
commit | 606984e6876c0ca81d1666abb2c6e2de6d29bd80 (patch) | |
tree | 1d421fc9a8a8361b351960bd32a6895535c1313b |
Import swaylock_1.4.orig.tar.gz
[dgit import orig swaylock_1.4.orig.tar.gz]
37 files changed, 4268 insertions, 0 deletions
diff --git a/.build.yml b/.build.yml new file mode 100644 index 0000000..59bc722 --- /dev/null +++ b/.build.yml @@ -0,0 +1,23 @@ +image: alpine/edge +packages: + - meson + - cairo-dev + - wayland-dev + - wayland-protocols + - libxkbcommon-dev + - gdk-pixbuf-dev + - linux-pam-dev + - scdoc +sources: + - https://github.com/swaywm/swaylock +tasks: + - setup: | + cd swaylock + meson build + - build: | + cd swaylock + ninja -C build + - build-no-pam: | + cd swaylock + meson configure build -Dpam=disabled + ninja -C build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build @@ -0,0 +1,19 @@ +Copyright (c) 2016-2019 Drew DeVault + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f30cd5 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# swaylock + +swaylock is a screen locking utility for Wayland compositors. It is compatible +with any Wayland compositor which implements the following Wayland protocols: + +- wlr-layer-shell +- wlr-input-inhibitor +- xdg-output +- xdg-shell + +See the man page, `swaylock(1)`, for instructions on using swaylock. + +## Release Signatures + +Releases are signed with [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) +and published [on GitHub](https://github.com/swaywm/swaylock/releases). swaylock +releases are managed independently of sway releases. + +## Installation + +### From Packages + +Swaylock is available in many distributions. Try installing the "swaylock" +package for yours. + +If you're interested in packaging swaylock for your distribution, stop by the +IRC channel or shoot an email to sir@cmpwn.com for advice. + +### Compiling from Source + +Install dependencies: + +* meson \* +* wayland +* wayland-protocols \* +* libxkbcommon +* cairo +* gdk-pixbuf2 \*\* +* pam (optional) +* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional: man pages) \* +* git \* + +_\*Compile-time dep_ + +_\*\*optional: required for background images other than PNG_ + +Run these commands: + + meson build + ninja -C build + sudo ninja -C build install + +On systems without PAM, you need to suid the swaylock binary: + + sudo chmod a+s /usr/local/bin/swaylock + +Swaylock will drop root permissions shortly after startup. diff --git a/background-image.c b/background-image.c new file mode 100644 index 0000000..c765bdf --- /dev/null +++ b/background-image.c @@ -0,0 +1,120 @@ +#include <assert.h> +#include "background-image.h" +#include "cairo.h" +#include "log.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; + } + swaylock_log(LOG_ERROR, "Unsupported background mode: %s", mode); + return BACKGROUND_MODE_INVALID; +} + +cairo_surface_t *load_background_image(const char *path) { + cairo_surface_t *image; +#if HAVE_GDK_PIXBUF + GError *err = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err); + if (!pixbuf) { + swaylock_log(LOG_ERROR, "Failed to load background image (%s).", + err->message); + return NULL; + } + 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) { + swaylock_log(LOG_ERROR, "Failed to read background image."); + return NULL; + } + if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) { + swaylock_log(LOG_ERROR, "Failed to read background image: %s." +#if !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); + + cairo_save(cairo); + 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); + cairo_restore(cairo); +} @@ -0,0 +1,126 @@ +#include <stdint.h> +#include <cairo/cairo.h> +#include "cairo.h" +#if HAVE_GDK_PIXBUF +#include <gdk-pixbuf/gdk-pixbuf.h> +#endif + +void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { + cairo_set_source_rgba(cairo, + (color >> (3*8) & 0xFF) / 255.0, + (color >> (2*8) & 0xFF) / 255.0, + (color >> (1*8) & 0xFF) / 255.0, + (color >> (0*8) & 0xFF) / 255.0); +} + +cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel) { + switch (subpixel) { + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: + return CAIRO_SUBPIXEL_ORDER_RGB; + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: + return CAIRO_SUBPIXEL_ORDER_BGR; + case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: + return CAIRO_SUBPIXEL_ORDER_VRGB; + case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: + return CAIRO_SUBPIXEL_ORDER_VBGR; + default: + return CAIRO_SUBPIXEL_ORDER_DEFAULT; + } + return CAIRO_SUBPIXEL_ORDER_DEFAULT; +} + +#if HAVE_GDK_PIXBUF +cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf(const GdkPixbuf *gdkbuf) { + int chan = gdk_pixbuf_get_n_channels(gdkbuf); + if (chan < 3) { + return NULL; + } + + const guint8* gdkpix = gdk_pixbuf_read_pixels(gdkbuf); + if (!gdkpix) { + return NULL; + } + gint w = gdk_pixbuf_get_width(gdkbuf); + gint h = gdk_pixbuf_get_height(gdkbuf); + int stride = gdk_pixbuf_get_rowstride(gdkbuf); + + cairo_format_t fmt = (chan == 3) ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32; + cairo_surface_t * cs = cairo_image_surface_create (fmt, w, h); + cairo_surface_flush (cs); + if ( !cs || cairo_surface_status(cs) != CAIRO_STATUS_SUCCESS) { + return NULL; + } + + int cstride = cairo_image_surface_get_stride(cs); + unsigned char * cpix = cairo_image_surface_get_data(cs); + + if (chan == 3) { + int i; + for (i = h; i; --i) { + const guint8 *gp = gdkpix; + unsigned char *cp = cpix; + const guint8* end = gp + 3*w; + while (gp < end) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + cp[0] = gp[2]; + cp[1] = gp[1]; + cp[2] = gp[0]; +#else + cp[1] = gp[0]; + cp[2] = gp[1]; + cp[3] = gp[2]; +#endif + gp += 3; + cp += 4; + } + gdkpix += stride; + cpix += cstride; + } + } else { + /* premul-color = alpha/255 * color/255 * 255 = (alpha*color)/255 + * (z/255) = z/256 * 256/255 = z/256 (1 + 1/255) + * = z/256 + (z/256)/255 = (z + z/255)/256 + * # recurse once + * = (z + (z + z/255)/256)/256 + * = (z + z/256 + z/256/255) / 256 + * # only use 16bit uint operations, loose some precision, + * # result is floored. + * -> (z + z>>8)>>8 + * # add 0x80/255 = 0.5 to convert floor to round + * => (z+0x80 + (z+0x80)>>8 ) >> 8 + * ------ + * tested as equal to lround(z/255.0) for uint z in [0..0xfe02] + */ +#define PREMUL_ALPHA(x,a,b,z) \ + G_STMT_START { z = a * b + 0x80; x = (z + (z >> 8)) >> 8; } \ + G_STMT_END + int i; + for (i = h; i; --i) { + const guint8 *gp = gdkpix; + unsigned char *cp = cpix; + const guint8* end = gp + 4*w; + guint z1, z2, z3; + while (gp < end) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + PREMUL_ALPHA(cp[0], gp[2], gp[3], z1); + PREMUL_ALPHA(cp[1], gp[1], gp[3], z2); + PREMUL_ALPHA(cp[2], gp[0], gp[3], z3); + cp[3] = gp[3]; +#else + PREMUL_ALPHA(cp[1], gp[0], gp[3], z1); + PREMUL_ALPHA(cp[2], gp[1], gp[3], z2); + PREMUL_ALPHA(cp[3], gp[2], gp[3], z3); + cp[0] = gp[3]; +#endif + gp += 4; + cp += 4; + } + gdkpix += stride; + cpix += cstride; + } +#undef PREMUL_ALPHA + } + cairo_surface_mark_dirty(cs); + return cs; +} +#endif // HAVE_GDK_PIXBUF @@ -0,0 +1,109 @@ +#include <stdbool.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include "comm.h" +#include "log.h" +#include "swaylock.h" + +static int comm[2][2] = {{-1, -1}, {-1, -1}}; + +ssize_t read_comm_request(char **buf_ptr) { + size_t size; + ssize_t amt; + amt = read(comm[0][0], &size, sizeof(size)); + if (amt == 0) { + return 0; + } else if (amt < 0) { + swaylock_log_errno(LOG_ERROR, "read pw request"); + return -1; + } + swaylock_log(LOG_DEBUG, "received pw check request"); + char *buf = malloc(size); + if (!buf) { + swaylock_log_errno(LOG_ERROR, "failed to malloc pw buffer"); + return -1; + } + size_t offs = 0; + do { + amt = read(comm[0][0], &buf[offs], size - offs); + if (amt <= 0) { + swaylock_log_errno(LOG_ERROR, "failed to read pw"); + return -1; + } + offs += (size_t)amt; + } while (offs < size); + + *buf_ptr = buf; + return size; +} + +bool write_comm_reply(bool success) { + if (write(comm[1][1], &success, sizeof(success)) != sizeof(success)) { + swaylock_log_errno(LOG_ERROR, "failed to write pw check result"); + return false; + } + return true; +} + +bool spawn_comm_child(void) { + if (pipe(comm[0]) != 0) { + swaylock_log_errno(LOG_ERROR, "failed to create pipe"); + return false; + } + if (pipe(comm[1]) != 0) { + swaylock_log_errno(LOG_ERROR, "failed to create pipe"); + return false; + } + pid_t child = fork(); + if (child < 0) { + swaylock_log_errno(LOG_ERROR, "failed to fork"); + return false; + } else if (child == 0) { + close(comm[0][1]); + close(comm[1][0]); + run_pw_backend_child(); + } + close(comm[0][0]); + close(comm[1][1]); + return true; +} + +bool write_comm_request(struct swaylock_password *pw) { + bool result = false; + + size_t len = pw->len + 1; + size_t offs = 0; + if (write(comm[0][1], &len, sizeof(len)) < 0) { + swaylock_log_errno(LOG_ERROR, "Failed to request pw check"); + goto out; + } + + do { + ssize_t amt = write(comm[0][1], &pw->buffer[offs], len - offs); + if (amt < 0) { + swaylock_log_errno(LOG_ERROR, "Failed to write pw buffer"); + goto out; + } + offs += amt; + } while (offs < len); + + result = true; + +out: + clear_password_buffer(pw); + return result; +} + +bool read_comm_reply(void) { + bool result = false; + if (read(comm[1][0], &result, sizeof(result)) != sizeof(result)) { + swaylock_log_errno(LOG_ERROR, "Failed to read pw result"); + result = false; + } + return result; +} + +int get_comm_reply_fd(void) { + return comm[1][0]; +} diff --git a/completions/bash/swaylock b/completions/bash/swaylock new file mode 100644 index 0000000..3392548 --- /dev/null +++ b/completions/bash/swaylock @@ -0,0 +1,66 @@ +# swaylock(1) completion + +_swaylock() +{ + local cur prev + _get_comp_words_by_ref -n : cur prev + + short=( + -h + -c + -s + -t + -v + -i + -u + -f + ) + + long=( + --help + --color + --scaling + --tiling + --version + --image + --no-unlock-indicator + --daemonize + ) + + scaling=( + 'stretch' + 'fill' + 'fit' + 'center' + 'tile' + ) + + case $prev in + -c|--color) + return + ;; + --scaling) + COMPREPLY=($(compgen -W "${scaling[*]}" -- "$cur")) + return + ;; + -i|--image) + if grep -q : <<< "$cur"; then + output="${cur%%:*}:" + cur="${cur#*:}" + else + output= + fi + COMPREPLY=($(compgen -f -- "$cur")) + return + ;; + esac + + if [[ $cur == --* ]]; then + COMPREPLY=($(compgen -W "${long[*]}" -- "$cur")) + else + COMPREPLY=($(compgen -W "${short[*]}" -- "$cur")) + COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur")) + fi + +} && +complete -F _swaylock swaylock diff --git a/completions/fish/swaylock.fish b/completions/fish/swaylock.fish new file mode 100644 index 0000000..99dff48 --- /dev/null +++ b/completions/fish/swaylock.fish @@ -0,0 +1,39 @@ +# swaylock(1) completion + +complete -c swaylock -s C -l config --description 'The config file to use. Default: $HOME/.swaylock/config, $XDG_CONFIG_HOME/swaylock/config, and SYSCONFDIR/swaylock/config.' +complete -c swaylock -s h -l help --description "Show help message and quit." +complete -c swaylock -s f -l daemonize --description "Fork into the background after spawning. Note: this is the default bahavior of i3lock." +complete -c swaylock -s v -l version --description "Show the version number and quit." +complete -c swaylock -s s -l socket --description "Use the specified socket path. Otherwise, swaymsg will as sway where the socket is (which is the value of $SWAYSOCK, then of $I350CK)." +complete -c swaylock -s e -l ignore-empty-password --description 'When an empty password is provided by the user, do not validate it.' + +# Appearance +complete -c swaylock -s u -l no-unlock-indicator --description "Disable the unlock indicator." +complete -c swaylock -s i -l image --description "Display the given image, optionally on the given output. Use -c to set a background color." +complete -c swaylock -s s -l scaling --description "Scaling mode for images: stretch, fill, fit, center, or tile." +complete -c swaylock -s t -l tiling --description "Same as --scaling=tile." +complete -c swaylock -s c -l color --description "Turn the screen into the given color. If -i is used, this sets the background of the image into the given color. Defaults to white (ffffff), or transparent (00000000) if an image is in use." +complete -c swaylock -l bs-hl-color --description 'Sets the color of backspace highlight segments.' +complete -c swaylock -l font --description 'Sets the font of the text inside the indicator.' +complete -c swaylock -l indicator-radius --description 'Sets the radius of the indicator to radius pixels. Default: 50' +complete -c swaylock -l indicator-thickness --description 'Sets the thickness of the indicator to thickness pixels. Default: 10' +complete -c swaylock -l inside-color --description 'Sets the color of the inside of the indicator when typing or idle.' +complete -c swaylock -l inside-clear-color --description 'Sets the color of the inside of the indicator when cleared.' +complete -c swaylock -l inside-ver-color --description 'Sets the color of the inside of the indicator when verifying.' +complete -c swaylock -l inside-wrong-color --description 'Sets the color of the inside of the indicator when invalid.' +complete -c swaylock -l key-hl-color --description 'Sets the color of key press highlight segments.' +complete -c swaylock -l line-color --description 'Sets the color of the lines that separate the inside and outside of the indicator when typing or idle.' +complete -c swaylock -l line-clear-color --description 'Sets the color of the lines that separate the inside and outside of the indicator when cleared.' +complete -c swaylock -l line-ver-color --description 'Sets the color of the lines that separate the inside and outside of the indicator when verifying.' +complete -c swaylock -l line-wrong-color --description 'Sets the color of the lines that separate the inside and outside of the indicator when invalid.' +complete -c swaylock -s n -l line-uses-inside --description 'Use the color of the inside of the indicator for the line separating the inside and outside of the indicator.' +complete -c swaylock -s r -l line-uses-ring --description 'Use the outer ring\'s color for the line separating the inside and outside of the indicator.' +complete -c swaylock -l ring-color --description 'Sets the color of the outside of the indicator when typing or idle.' +complete -c swaylock -l ring-clear-color --description 'Sets the color of the outside of the indicator when cleared.' +complete -c swaylock -l ring-ver-color --description 'Sets the color of the outside of the indicator when verifying.' +complete -c swaylock -l ring-wrong-color --description 'Sets the color of the outside of the indicator when invalid.' +complete -c swaylock -l separator-color --description 'Sets the color of the lines that separate highlight segments.' +complete -c swaylock -l text-color --description 'Sets the color of the text inside the indicator when typing or idle.' +complete -c swaylock -l text-clear-color --description 'Sets the color of the text inside the indicator when cleared.' +complete -c swaylock -l text-ver-color --description 'Sets the color of the text inside the indicator when verifying.' +complete -c swaylock -l text-wrong-color --description 'Sets the color of the text inside the indicator when invalid.' diff --git a/completions/zsh/_swaylock b/completions/zsh/_swaylock new file mode 100644 index 0000000..9bc84ec --- /dev/null +++ b/completions/zsh/_swaylock @@ -0,0 +1,40 @@ +#compdef swaylock +# +# Completion script for swaylock +# + +_arguments -s \ + '(-C --config)'{-C,--config}'[Path to the config file]:filename:_files' \ + '(-c --color)'{-c,--color}'[Turn the screen into the given color instead of white]:color:' \ + '(-e --ignore-empty-password)'{-e,--ignore-empty-password}'[When an empty password is provided, do not validate it]' \ + '(-f --daemonize)'{-f,--daemonize}'[Detach from the controlling terminal after locking]' \ + '(-h --help)'{-h,--help}'[Show help message and quit]' \ + '(-i --image)'{-i,--image}'[Display an image]:filename:_files' \ + '(-s --scaling)'{-s,--scaling}'[Scaling mode]:mode:(stretch fill fit center tile)' \ + '(-t --tiling)'{-t,--tiling}'[Same as --scaling=tile]' \ + '(-u --no-unlock-indicator)'{-u,--no-unlock-indicator}'[Disable the unlock indicator]' \ + '(-v --version)'{-v,--version}'[Show the version number and quit]' \ + '(--bs-hl-color)'--bs-hl-color'[Sets the color of backspace highlights segments]:color:' \ + '(--font)'--font'[Sets the font of the text]:font:' \ + '(--indicator-radius)'--indicator-radius'[Sets the indicator radius]:radius:' \ + '(--indicator-thickness)'--indicator-thickness'[Sets the indicator thickness]:thickness:' \ + '(--inside-color)'--inside-color'[Sets the color of the inside of the indicator]:color:' \ + '(--inside-clear-color)'--inside-clear-color'[Sets the color of the inside of the indicator when cleared]:color:' \ + '(--inside-clear-color)'--inside-clear-color'[Sets the color of the inside of the indicator when verifying]:color:' \ + '(--inside-wrong-color)'--inside-wrong-color'[Sets the color of the inside of the indicator when invalid]:color:' \ + '(--key-hl-color)'--key-hl-color'[Sets the color of the key press highlight segments]:color:' \ + '(--line-color)'--line-color'[Sets the color of the line between the inside and ring]:color:' \ + '(--line-clear-color)'--line-clear-color'[Sets the color of the line between the inside and ring when cleared]:color:' \ + '(--line-ver-color)'--line-ver-color'[Sets the color of the line between the inside and ring when verifying]:color:' \ + '(--line-wrong-color)'--line-wrong-color'[Sets the color of the line between the inside and ring when invalid]:color:' \ + '(-n --line-uses-inside)'{-n,--line-uses-inside}'[Use the inside color for the line between the inside and ring]' \ + '(-r --line-uses-ring)'{-r,--line--uses-ring}'[Use the ring color for the line between the inside and ring]' \ + '(--ring-color)'--ring-color'[Sets the color of the ring of the indicator]:color:' \ + '(--ring-clear-color)'--ring-clear-color'[Sets the color of the ring of the indicator when cleared]:color:' \ + '(--ring-ver-color)'--ring-ver-color'[Sets the color of the ring of the indicator when verifying]:color:' \ + '(--ring-wrong-color)'--ring-wrong-color'[Sets the color of the ring of the indicator when invalid]:color:' \ + '(--separator-color)'--separator-color'[Sets the color of the lines that separate highlight segments]:color:' \ + '(--text-color)'--text-color'[Sets the color of the text]:color:' \ + '(--text-clear-color)'--text-clear-color'[Sets the color of the text when cleared]:color:' \ + '(--text-ver-color)'--text-ver-color'[Sets the color of the text when verifying]:color:' \ + '(--text-wrong-color)'--text-wrong-color'[Sets the color of the text when invalid]:color:'
\ No newline at end of file diff --git a/include/background-image.h b/include/background-image.h new file mode 100644 index 0000000..15935ff --- /dev/null +++ b/include/background-image.h @@ -0,0 +1,20 @@ +#ifndef _SWAY_BACKGROUND_IMAGE_H +#define _SWAY_BACKGROUND_IMAGE_H +#include "cairo.h" + +enum background_mode { + BACKGROUND_MODE_STRETCH, + BACKGROUND_MODE_FILL, + BACKGROUND_MODE_FIT, + BACKGROUND_MODE_CENTER, + BACKGROUND_MODE_TILE, + BACKGROUND_MODE_SOLID_COLOR, + BACKGROUND_MODE_INVALID, +}; + +enum background_mode parse_background_mode(const char *mode); +cairo_surface_t *load_background_image(const char *path); +void render_background_image(cairo_t *cairo, cairo_surface_t *image, + enum background_mode mode, int buffer_width, int buffer_height); + +#endif diff --git a/include/cairo.h b/include/cairo.h new file mode 100644 index 0000000..15664cd --- /dev/null +++ b/include/cairo.h @@ -0,0 +1,25 @@ +#ifndef _SWAY_CAIRO_H +#define _SWAY_CAIRO_H + +#include "config.h" +#include <stdint.h> +#include <cairo/cairo.h> +#include <wayland-client.h> +#if HAVE_GDK_PIXBUF +#include <gdk-pixbuf/gdk-pixbuf.h> +#endif + +void cairo_set_source_u32(cairo_t *cairo, uint32_t color); +cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel); + +cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, + int width, int height); + +#if HAVE_GDK_PIXBUF + +cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf( + const GdkPixbuf *gdkbuf); + +#endif // HAVE_GDK_PIXBUF + +#endif diff --git a/include/comm.h b/include/comm.h new file mode 100644 index 0000000..defc2f4 --- /dev/null +++ b/include/comm.h @@ -0,0 +1,18 @@ +#ifndef _SWAYLOCK_COMM_H +#define _SWAYLOCK_COMM_H + +#include <stdbool.h> + +struct swaylock_password; + +bool spawn_comm_child(void); +ssize_t read_comm_request(char **buf_ptr); +bool write_comm_reply(bool success); +// Requests the provided password to be checked. The password is always cleared +// when the function returns. +bool write_comm_request(struct swaylock_password *pw); +bool read_comm_reply(void); +// FD to poll for password authentication replies. +int get_comm_reply_fd(void); + +#endif diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..fc3747a --- /dev/null +++ b/include/log.h @@ -0,0 +1,36 @@ +#ifndef _SWAYLOCK_LOG_H +#define _SWAYLOCK_LOG_H + +#include <stdarg.h> +#include <string.h> +#include <errno.h> + +enum log_importance { + LOG_SILENT = 0, + LOG_ERROR = 1, + LOG_INFO = 2, + LOG_DEBUG = 3, + LOG_IMPORTANCE_LAST, +}; + +void swaylock_log_init(enum log_importance verbosity); + +#ifdef __GNUC__ +#define _ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) +#else +#define _ATTRIB_PRINTF(start, end) +#endif + +void _swaylock_log(enum log_importance verbosity, const char *format, ...) + _ATTRIB_PRINTF(2, 3); + +const char *_swaylock_strip_path(const char *filepath); + +#define swaylock_log(verb, fmt, ...) \ + _swaylock_log(verb, "[%s:%d] " fmt, _swaylock_strip_path(__FILE__), \ + __LINE__, ##__VA_ARGS__) + +#define swaylock_log_errno(verb, fmt, ...) \ + swaylock_log(verb, fmt ": %s", ##__VA_ARGS__, strerror(errno)) + +#endif diff --git a/include/loop.h b/include/loop.h new file mode 100644 index 0000000..2f608ed --- /dev/null +++ b/include/loop.h @@ -0,0 +1,54 @@ +#ifndef _SWAY_LOOP_H +#define _SWAY_LOOP_H +#include <stdbool.h> + +/** + * This is an event loop system designed for sway clients, not sway itself. + * + * The loop consists of file descriptors and timers. Typically the Wayland + * display's file descriptor will be one of the fds in the loop. + */ + +struct loop; +struct loop_timer; + +/** + * Create an event loop. + */ +struct loop *loop_create(void); + +/** + * Destroy the event loop (eg. on program termination). + */ +void loop_destroy(struct loop *loop); + +/** + * Poll the event loop. This will block until one of the fds has data. + */ +void loop_poll(struct loop *loop); + +/** + * Add a file descriptor to the loop. + */ +void loop_add_fd(struct loop *loop, int fd, short mask, + void (*func)(int fd, short mask, void *data), void *data); + +/** + * Add a timer to the loop. + * + * When the timer expires, the timer will be removed from the loop and freed. + */ +struct loop_timer *loop_add_timer(struct loop *loop, int ms, + void (*callback)(void *data), void *data); + +/** + * Remove a file descriptor from the loop. + */ +bool loop_remove_fd(struct loop *loop, int fd); + +/** + * Remove a timer from the loop. + */ +bool loop_remove_timer(struct loop *loop, struct loop_timer *timer); + +#endif diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..65ed027 --- /dev/null +++ b/include/meson.build @@ -0,0 +1 @@ +configure_file(output: 'config.h', configuration: conf_data) diff --git a/include/pool-buffer.h b/include/pool-buffer.h new file mode 100644 index 0000000..0ebf787 --- /dev/null +++ b/include/pool-buffer.h @@ -0,0 +1,22 @@ +#ifndef _SWAY_BUFFERS_H +#define _SWAY_BUFFERS_H +#include <cairo/cairo.h> +#include <stdbool.h> +#include <stdint.h> +#include <wayland-client.h> + +struct pool_buffer { + struct wl_buffer *buffer; + cairo_surface_t *surface; + cairo_t *cairo; + uint32_t width, height; + void *data; + size_t size; + bool busy; +}; + +struct pool_buffer *get_next_buffer(struct wl_shm *shm, + struct pool_buffer pool[static 2], uint32_t width, uint32_t height); +void destroy_buffer(struct pool_buffer *buffer); + +#endif diff --git a/include/seat.h b/include/seat.h new file mode 100644 index 0000000..c79afcd --- /dev/null +++ b/include/seat.h @@ -0,0 +1,21 @@ +#ifndef _SWAYLOCK_SEAT_H +#define _SWAYLOCK_SEAT_H +#include <xkbcommon/xkbcommon.h> + +struct swaylock_xkb { + bool caps_lock; + bool control; + struct xkb_state *state; + struct xkb_context *context; + struct xkb_keymap *keymap; +}; + +struct swaylock_seat { + struct swaylock_state *state; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; +}; + +extern const struct wl_seat_listener seat_listener; + +#endif diff --git a/include/swaylock.h b/include/swaylock.h new file mode 100644 index 0000000..8bb8089 --- /dev/null +++ b/include/swaylock.h @@ -0,0 +1,131 @@ +#ifndef _SWAYLOCK_H +#define _SWAYLOCK_H +#include <stdbool.h> +#include <stdint.h> +#include <wayland-client.h> +#include "background-image.h" +#include "cairo.h" +#include "pool-buffer.h" +#include "seat.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +enum auth_state { + AUTH_STATE_IDLE, + AUTH_STATE_CLEAR, + AUTH_STATE_INPUT, + AUTH_STATE_INPUT_NOP, + AUTH_STATE_BACKSPACE, + AUTH_STATE_VALIDATING, + AUTH_STATE_INVALID, +}; + +struct swaylock_colorset { + uint32_t input; + uint32_t cleared; + uint32_t caps_lock; + uint32_t verifying; + uint32_t wrong; +}; + +struct swaylock_colors { + uint32_t background; + uint32_t bs_highlight; + uint32_t key_highlight; + uint32_t caps_lock_bs_highlight; + uint32_t caps_lock_key_highlight; + uint32_t separator; + uint32_t layout_background; + uint32_t layout_border; + uint32_t layout_text; + struct swaylock_colorset inside; + struct swaylock_colorset line; + struct swaylock_colorset ring; + struct swaylock_colorset text; +}; + +struct swaylock_args { + struct swaylock_colors colors; + enum background_mode mode; + char *font; + uint32_t radius; + uint32_t thickness; + bool ignore_empty; + bool show_indicator; + bool show_caps_lock_text; + bool show_caps_lock_indicator; + bool show_keyboard_layout; + bool hide_keyboard_layout; + bool show_failed_attempts; + bool daemonize; +}; + +struct swaylock_password { + size_t len; + char buffer[1024]; +}; + +struct swaylock_state { + struct loop *eventloop; + struct loop_timer *clear_indicator_timer; // clears the indicator + struct loop_timer *clear_password_timer; // clears the password buffer + struct wl_display *display; + struct wl_compositor *compositor; + struct wl_subcompositor *subcompositor; + struct zwlr_layer_shell_v1 *layer_shell; + struct zwlr_input_inhibit_manager_v1 *input_inhibit_manager; + struct wl_shm *shm; + struct wl_list surfaces; + struct wl_list images; + struct swaylock_args args; + struct swaylock_password password; + struct swaylock_xkb xkb; + enum auth_state auth_state; + int failed_attempts; + bool run_display; + struct zxdg_output_manager_v1 *zxdg_output_manager; +}; + +struct swaylock_surface { + cairo_surface_t *image; + struct swaylock_state *state; + struct wl_output *output; + uint32_t output_global_name; + struct zxdg_output_v1 *xdg_output; + struct wl_surface *surface; + struct wl_surface *child; // surface made into subsurface + struct wl_subsurface *subsurface; + struct zwlr_layer_surface_v1 *layer_surface; + struct pool_buffer buffers[2]; + struct pool_buffer indicator_buffers[2]; + struct pool_buffer *current_buffer; + bool frame_pending, dirty; + uint32_t width, height; + int32_t scale; + enum wl_output_subpixel subpixel; + char *output_name; + struct wl_list link; +}; + +// There is exactly one swaylock_image for each -i argument +struct swaylock_image { + char *path; + char *output_name; + cairo_surface_t *cairo_surface; + struct wl_list link; +}; + +void swaylock_handle_key(struct swaylock_state *state, + xkb_keysym_t keysym, uint32_t codepoint); +void render_frame_background(struct swaylock_surface *surface); +void render_frame(struct swaylock_surface *surface); +void render_frames(struct swaylock_state *state); +void damage_surface(struct swaylock_surface *surface); +void damage_state(struct swaylock_state *state); +void clear_password_buffer(struct swaylock_password *pw); +void schedule_indicator_clear(struct swaylock_state *state); + +void initialize_pw_backend(int argc, char **argv); +void run_pw_backend_child(void); +void clear_buffer(char *buf, size_t size); + +#endif diff --git a/include/unicode.h b/include/unicode.h new file mode 100644 index 0000000..e2ee958 --- /dev/null +++ b/include/unicode.h @@ -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 + @@ -0,0 +1,68 @@ +#define _POSIX_C_SOURCE 199506L +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include "log.h" + +static enum log_importance log_importance = LOG_ERROR; + +static const char *verbosity_colors[] = { + [LOG_SILENT] = "", + [LOG_ERROR ] = "\x1B[1;31m", + [LOG_INFO ] = "\x1B[1;34m", + [LOG_DEBUG ] = "\x1B[1;30m", +}; + +void swaylock_log_init(enum log_importance verbosity) { + if (verbosity < LOG_IMPORTANCE_LAST) { + log_importance = verbosity; + } +} + +void _swaylock_log(enum log_importance verbosity, const char *fmt, ...) { + if (verbosity > log_importance) { + return; + } + + va_list args; + va_start(args, fmt); + + // prefix the time to the log message + struct tm result; + time_t t = time(NULL); + struct tm *tm_info = localtime_r(&t, &result); + char buffer[26]; + + // generate time prefix + strftime(buffer, sizeof(buffer), "%F %T - ", tm_info); + fprintf(stderr, "%s", buffer); + + unsigned c = (verbosity < LOG_IMPORTANCE_LAST) + ? verbosity : LOG_IMPORTANCE_LAST - 1; + + if (isatty(STDERR_FILENO)) { + fprintf(stderr, "%s", verbosity_colors[c]); + } + + vfprintf(stderr, fmt, args); + + if (isatty(STDERR_FILENO)) { + fprintf(stderr, "\x1B[0m"); + } + fprintf(stderr, "\n"); + + va_end(args); +} + +const char *_swaylock_strip_path(const char *filepath) { + if (*filepath == '.') { + while (*filepath == '.' || *filepath == '/') { + ++filepath; + } + } + return filepath; +} @@ -0,0 +1,193 @@ +#define _POSIX_C_SOURCE 200112L +#include <limits.h> +#include <string.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <poll.h> +#include <time.h> +#include <unistd.h> +#include <wayland-client.h> +#include "log.h" +#include "loop.h" + +struct loop_fd_event { + void (*callback)(int fd, short mask, void *data); + void *data; + struct wl_list link; // struct loop_fd_event::link +}; + +struct loop_timer { + void (*callback)(void *data); + void *data; + struct timespec expiry; + struct wl_list link; // struct loop_timer::link +}; + +struct loop { + struct pollfd *fds; + int fd_length; + int fd_capacity; + + struct wl_list fd_events; // struct loop_fd_event::link + struct wl_list timers; // struct loop_timer::link +}; + +struct loop *loop_create(void) { + struct loop *loop = calloc(1, sizeof(struct loop)); + if (!loop) { + swaylock_log(LOG_ERROR, "Unable to allocate memory for loop"); + return NULL; + } + loop->fd_capacity = 10; + loop->fds = malloc(sizeof(struct pollfd) * loop->fd_capacity); + wl_list_init(&loop->fd_events); + wl_list_init(&loop->timers); + return loop; +} + +void loop_destroy(struct loop *loop) { + struct loop_fd_event *event = NULL, *tmp_event = NULL; + wl_list_for_each_safe(event, tmp_event, &loop->fd_events, link) { + wl_list_remove(&event->link); + free(event); + } + struct loop_timer *timer = NULL, *tmp_timer = NULL; + wl_list_for_each_safe(timer, tmp_timer, &loop->timers, link) { + wl_list_remove(&timer->link); + free(timer); + } + free(loop->fds); + free(loop); +} + +void loop_poll(struct loop *loop) { + // Calculate next timer in ms + int ms = INT_MAX; + if (!wl_list_empty(&loop->timers)) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + struct loop_timer *timer = NULL; + wl_list_for_each(timer, &loop->timers, link) { + int timer_ms = (timer->expiry.tv_sec - now.tv_sec) * 1000; + timer_ms += (timer->expiry.tv_nsec - now.tv_nsec) / 1000000; + if (timer_ms < ms) { + ms = timer_ms; + } + } + } + if (ms < 0) { + ms = 0; + } + + poll(loop->fds, loop->fd_length, ms); + + // Dispatch fds + size_t fd_index = 0; + struct loop_fd_event *event = NULL; + wl_list_for_each(event, &loop->fd_events, link) { + struct pollfd pfd = loop->fds[fd_index]; + + // Always send these events + unsigned events = pfd.events | POLLHUP | POLLERR; + + if (pfd.revents & events) { + event->callback(pfd.fd, pfd.revents, event->data); + } + + ++fd_index; + } + + // Dispatch timers + if (!wl_list_empty(&loop->timers)) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + struct loop_timer *timer = NULL, *tmp_timer = NULL; + wl_list_for_each_safe(timer, tmp_timer, &loop->timers, link) { + bool expired = timer->expiry.tv_sec < now.tv_sec || + (timer->expiry.tv_sec == now.tv_sec && + timer->expiry.tv_nsec < now.tv_nsec); + if (expired) { + timer->callback(timer->data); + loop_remove_timer(loop, timer); + } + } + } +} + +void loop_add_fd(struct loop *loop, int fd, short mask, + void (*callback)(int fd, short mask, void *data), void *data) { + struct loop_fd_event *event = calloc(1, sizeof(struct loop_fd_event)); + if (!event) { + swaylock_log(LOG_ERROR, "Unable to allocate memory for event"); + return; + } + event->callback = callback; + event->data = data; + wl_list_insert(loop->fd_events.prev, &event->link); + + struct pollfd pfd = {fd, mask, 0}; + + if (loop->fd_length == loop->fd_capacity) { + loop->fd_capacity += 10; + loop->fds = realloc(loop->fds, + sizeof(struct pollfd) * loop->fd_capacity); + } + + loop->fds[loop->fd_length++] = pfd; +} + +struct loop_timer *loop_add_timer(struct loop *loop, int ms, + void (*callback)(void *data), void *data) { + struct loop_timer *timer = calloc(1, sizeof(struct loop_timer)); + if (!timer) { + swaylock_log(LOG_ERROR, "Unable to allocate memory for timer"); + return NULL; + } + timer->callback = callback; + timer->data = data; + + clock_gettime(CLOCK_MONOTONIC, &timer->expiry); + timer->expiry.tv_sec += ms / 1000; + + long int nsec = (ms % 1000) * 1000000; + if (timer->expiry.tv_nsec + nsec >= 1000000000) { + timer->expiry.tv_sec++; + nsec -= 1000000000; + } + timer->expiry.tv_nsec += nsec; + + wl_list_insert(&loop->timers, &timer->link); + + return timer; +} + +bool loop_remove_fd(struct loop *loop, int fd) { + size_t fd_index = 0; + struct loop_fd_event *event = NULL, *tmp_event = NULL; + wl_list_for_each_safe(event, tmp_event, &loop->fd_events, link) { + if (loop->fds[fd_index].fd == fd) { + wl_list_remove(&event->link); + free(event); + + loop->fd_length--; + memmove(&loop->fds[fd_index], &loop->fds[fd_index + 1], + sizeof(struct pollfd) * (loop->fd_length - fd_index)); + return true; + } + ++fd_index; + } + return false; +} + +bool loop_remove_timer(struct loop *loop, struct loop_timer *remove) { + struct loop_timer *timer = NULL, *tmp_timer = NULL; + wl_list_for_each_safe(timer, tmp_timer, &loop->timers, link) { + if (timer == remove) { + wl_list_remove(&timer->link); + free(timer); + return true; + } + } + return false; +} @@ -0,0 +1,1194 @@ +#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->child = wl_compositor_create_surface(state->compositor); + assert(surface->child); + surface->subsurface = wl_subcompositor_get_subsurface(state->subcompositor, surface->child, surface->surface); + assert(surface->subsurface); + wl_subsurface_set_sync(surface->subsurface); + + 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_background(surface); + render_frame(surface); +} + +static void layer_surface_closed(void *data, + struct zwlr_layer_surface_v1 *layer_surface) { + struct swaylock_surface *surface = data; + destroy_surface(surface); +} + +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; + +static 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_subcompositor_interface.name) == 0) { + state->subcompositor = wl_registry_bind(registry, name, + &wl_subcompositor_interface, 1); + } 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 char *join_args(char **argv, int argc) { + assert(argc > 0); + int len = 0, i; + for (i = 0; i < argc; ++i) { + len += strlen(argv[i]) + 1; + } + char *res = malloc(len); + len = 0; + for (i = 0; i < argc; ++i) { + strcpy(res + len, argv[i]); + len += strlen(argv[i]); + res[len++] = ' '; + } + res[len - 1] = '\0'; + return res; +} + +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; + } + } + + // The shell will not expand ~ to the value of $HOME when an output name is + // given. Also, any image paths given in the config file need to have shell + // expansions performed + wordexp_t p; + while (strstr(image->path, " ")) { + image->path = realloc(image->path, strlen(image->path) + 2); + char *ptr = strstr(image->path, " ") + 1; + memmove(ptr + 1, ptr, strlen(ptr) + 1); + *ptr = '\\'; + } + if (wordexp(image->path, &p, 0) == 0) { + free(image->path); + image->path = join_args(p.we_wordv, p.we_wordc); + 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->layout_background = 0x000000C0; + colors->layout_border = 0x00000000; + colors->layout_text = 0xFFFFFFFF; + 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_LAYOUT_TXT_COLOR, + LO_LAYOUT_BG_COLOR, + LO_LAYOUT_BORDER_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-keyboard-layout", no_argument, NULL, 'k'}, + {"hide-keyboard-layout", no_argument, NULL, 'K'}, + {"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}, + {"layout-bg-color", required_argument, NULL, LO_LAYOUT_BG_COLOR}, + {"layout-border-color", required_argument, NULL, LO_LAYOUT_BORDER_COLOR}, + {"layout-text-color", required_argument, NULL, LO_LAYOUT_TXT_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, --show-failed-attempts " + "Show current count of failed authentication attempts.\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" + " -k, --show-keyboard-layout " + "Display the current xkb layout while typing.\n" + " -K, --hide-keyboard-layout " + "Hide the current xkb layout while typing.\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" + " -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" + " --layout-bg-color <color> " + "Sets the background color of the box containing the layout text.\n" + " --layout-border-color <color> " + "Sets the color of the border of the box containing the layout text.\n" + " --layout-text-color <color> " + "Sets the color of the layout text.\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:kKLlnrs: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.show_failed_attempts = true; + } + break; + case 'f': + if (state) { + state->args.daemonize = true; + } + break; + case 'i': + if (state) { + load_image(optarg, state); + } + break; + case 'k': + if (state) { + state->args.show_keyboard_layout = true; + } + break; + case 'K': + if (state) { + state->args.hide_keyboard_layout = true; + } + 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 '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_LAYOUT_BG_COLOR: + if (state) { + state->args.colors.layout_background = parse_color(optarg); + } + break; + case LO_LAYOUT_BORDER_COLOR: + if (state) { + state->args.colors.layout_border = parse_color(optarg); + } + break; + case LO_LAYOUT_TXT_COLOR: + if (state) { + state->args.colors.layout_text = 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_keyboard_layout = false, + .hide_keyboard_layout = false, + .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, ®istry_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; + } + + 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; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..5483d9a --- /dev/null +++ b/meson.build @@ -0,0 +1,220 @@ +project( + 'swaylock', + 'c', + version: '1.3', + license: 'MIT', + meson_version: '>=0.48.0', + default_options: [ + 'c_std=c11', + 'warning_level=2', + 'werror=true', + ], +) + +add_project_arguments( + [ + '-Wno-unused-parameter', + '-Wno-unused-result', + '-Wundef', + '-Wvla', + ], + language: 'c', +) + +cc = meson.get_compiler('c') + +sysconfdir = get_option('sysconfdir') +prefix = get_option('prefix') +is_freebsd = host_machine.system().startswith('freebsd') + +add_project_arguments( + '-DSYSCONFDIR="/@0@"'.format(join_paths(prefix, sysconfdir)), + language : 'c') + +if is_freebsd + add_project_arguments('-D_C11_SOURCE', language: 'c') +endif + +wayland_client = dependency('wayland-client') +wayland_protos = dependency('wayland-protocols', version: '>=1.14') +xkbcommon = dependency('xkbcommon') +cairo = dependency('cairo') +gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf')) +libpam = cc.find_library('pam', required: get_option('pam')) +crypt = cc.find_library('crypt', required: not libpam.found()) +math = cc.find_library('m') + +git = find_program('git', required: false) +scdoc = find_program('scdoc', required: get_option('man-pages')) +wayland_scanner = find_program('wayland-scanner') + +version = '"@0@"'.format(meson.project_version()) +if git.found() + git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']) + git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']) + if git_commit_hash.returncode() == 0 and git_branch.returncode() == 0 + version = '"@0@ (" __DATE__ ", branch \'@1@\')"'.format(git_commit_hash.stdout().strip(), git_branch.stdout().strip()) + endif +endif +add_project_arguments('-DSWAYLOCK_VERSION=@0@'.format(version), language: 'c') + +wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') + +if wayland_client.version().version_compare('>=1.14.91') + code_type = 'private-code' +else + code_type = 'code' +endif + +wayland_scanner_code = generator( + wayland_scanner, + output: '@BASENAME@-protocol.c', + arguments: [code_type, '@INPUT@', '@OUTPUT@'], +) + +wayland_scanner_client = generator( + wayland_scanner, + output: '@BASENAME@-client-protocol.h', + arguments: ['client-header', '@INPUT@', '@OUTPUT@'], +) + +client_protos_src = [] +client_protos_headers = [] + +client_protocols = [ + [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], + [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], + ['wlr-layer-shell-unstable-v1.xml'], + ['wlr-input-inhibitor-unstable-v1.xml'], +] + +foreach p : client_protocols + xml = join_paths(p) + client_protos_src += wayland_scanner_code.process(xml) + client_protos_headers += wayland_scanner_client.process(xml) +endforeach + +lib_client_protos = static_library( + 'client_protos', + client_protos_src + client_protos_headers, + dependencies: [wayland_client] +) # for the include directory + +client_protos = declare_dependency( + link_with: lib_client_protos, + sources: client_protos_headers, +) + +conf_data = configuration_data() +conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) + +subdir('include') + +dependencies = [ + cairo, + client_protos, + gdk_pixbuf, + math, + xkbcommon, + wayland_client, +] + +sources = [ + 'background-image.c', + 'cairo.c', + 'comm.c', + 'log.c', + 'loop.c', + 'main.c', + 'password.c', + 'pool-buffer.c', + 'render.c', + 'seat.c', + 'unicode.c', +] + +if libpam.found() + sources += ['pam.c'] + dependencies += [libpam] +else + warning('The swaylock binary must be setuid when compiled without libpam') + warning('You must do this manually post-install: chmod a+s /path/to/swaylock') + sources += ['shadow.c'] + dependencies += [crypt] +endif + +swaylock_inc = include_directories('include') + +executable('swaylock', + sources, + include_directories: [swaylock_inc], + dependencies: dependencies, + install: true +) + +if is_freebsd + install_data( + 'pam/swaylock.freebsd', + install_dir: sysconfdir + '/pam.d/', + rename: 'swaylock' + ) +else + install_data( + 'pam/swaylock.linux', + install_dir: sysconfdir + '/pam.d/', + rename: 'swaylock' + ) +endif + +if scdoc.found() + sh = find_program('sh') + mandir = get_option('mandir') + man_files = [ + 'swaylock.1.scd', + ] + foreach filename : man_files + topic = filename.split('.')[-3].split('/')[-1] + section = filename.split('.')[-2] + output = '@0@.@1@'.format(topic, section) + + custom_target( + output, + input: filename, + output: output, + command: [ + sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc.path(), output) + ], + install: true, + install_dir: '@0@/man@1@'.format(mandir, section) + ) + endforeach +endif + +datadir = get_option('datadir') + +if get_option('zsh-completions') + zsh_files = files( + 'completions/zsh/_swaylock', + ) + zsh_install_dir = datadir + '/zsh/site-functions' + + install_data(zsh_files, install_dir: zsh_install_dir) +endif + +if get_option('bash-completions') + bash_files = files( + 'completions/bash/swaylock', + ) + bash_install_dir = datadir + '/bash-completion/completions' + + install_data(bash_files, install_dir: bash_install_dir) +endif + +if get_option('fish-completions') + fish_files = files( + 'completions/fish/swaylock.fish', + ) + fish_install_dir = datadir + '/fish/completions' + + install_data(fish_files, install_dir: fish_install_dir) +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..170303f --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,6 @@ +option('pam', type: 'feature', value: 'auto', description: 'Use PAM instead of shadow') +option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats') +option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') +option('zsh-completions', type: 'boolean', value: true, description: 'Install zsh shell completions') +option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions') +option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions') @@ -0,0 +1,121 @@ +#define _POSIX_C_SOURCE 200809L +#include <pwd.h> +#include <security/pam_appl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "comm.h" +#include "log.h" +#include "swaylock.h" + +static char *pw_buf = NULL; + +void initialize_pw_backend(int argc, char **argv) { + if (getuid() != geteuid() || getgid() != getegid()) { + swaylock_log(LOG_ERROR, + "swaylock is setuid, but was compiled with the PAM" + " backend. Run 'chmod a-s %s' to fix. Aborting.", argv[0]); + exit(EXIT_FAILURE); + } + if (!spawn_comm_child()) { + exit(EXIT_FAILURE); + } +} + +static int handle_conversation(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *data) { + /* PAM expects an array of responses, one for each message */ + struct pam_response *pam_reply = + calloc(num_msg, sizeof(struct pam_response)); + if (pam_reply == NULL) { + swaylock_log(LOG_ERROR, "Allocation failed"); + return PAM_ABORT; + } + *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 = strdup(pw_buf); // PAM clears and frees this + if (pam_reply[i].resp == NULL) { + swaylock_log(LOG_ERROR, "Allocation failed"); + return PAM_ABORT; + } + break; + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + break; + } + } + return PAM_SUCCESS; +} + +static const char *get_pam_auth_error(int pam_status) { + switch (pam_status) { + case PAM_AUTH_ERR: + return "invalid credentials"; + case PAM_CRED_INSUFFICIENT: + return "swaylock cannot authenticate users; check /etc/pam.d/swaylock " + "has been installed properly"; + case PAM_AUTHINFO_UNAVAIL: + return "authentication information unavailable"; + case PAM_MAXTRIES: + return "maximum number of authentication tries exceeded"; + default:; + static char msg[64]; + snprintf(msg, sizeof(msg), "unknown error (%d)", pam_status); + return msg; + } +} + +void run_pw_backend_child(void) { + struct passwd *passwd = getpwuid(getuid()); + char *username = passwd->pw_name; + + const struct pam_conv conv = { + .conv = handle_conversation, + .appdata_ptr = NULL, + }; + pam_handle_t *auth_handle = NULL; + if (pam_start("swaylock", username, &conv, &auth_handle) != PAM_SUCCESS) { + swaylock_log(LOG_ERROR, "pam_start failed"); + exit(EXIT_FAILURE); + } + + /* This code does not run as root */ + swaylock_log(LOG_DEBUG, "Prepared to authorize user %s", username); + + int pam_status = PAM_SUCCESS; + while (1) { + ssize_t size = read_comm_request(&pw_buf); + if (size < 0) { + exit(EXIT_FAILURE); + } else if (size == 0) { + break; + } + + int pam_status = pam_authenticate(auth_handle, 0); + bool success = pam_status == PAM_SUCCESS; + if (!success) { + swaylock_log(LOG_ERROR, "pam_authenticate failed: %s", + get_pam_auth_error(pam_status)); + } + + if (!write_comm_reply(success)) { + clear_buffer(pw_buf, size); + exit(EXIT_FAILURE); + } + + clear_buffer(pw_buf, size); + free(pw_buf); + pw_buf = NULL; + } + + if (pam_end(auth_handle, pam_status) != PAM_SUCCESS) { + swaylock_log(LOG_ERROR, "pam_end failed"); + exit(EXIT_FAILURE); + } + + exit((pam_status == PAM_SUCCESS) ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/pam/swaylock.freebsd b/pam/swaylock.freebsd new file mode 100644 index 0000000..603fc18 --- /dev/null +++ b/pam/swaylock.freebsd @@ -0,0 +1,6 @@ +# +# PAM configuration file for the swaylock screen locker. By default, it includes +# the 'passwd' configuration file (see /etc/pam.d/passwd) +# + +auth include passwd diff --git a/pam/swaylock.linux b/pam/swaylock.linux new file mode 100644 index 0000000..6a36b0d --- /dev/null +++ b/pam/swaylock.linux @@ -0,0 +1,6 @@ +# +# PAM configuration file for the swaylock screen locker. By default, it includes +# the 'login' configuration file (see /etc/pam.d/login) +# + +auth include login diff --git a/password.c b/password.c new file mode 100644 index 0000000..a5c0211 --- /dev/null +++ b/password.c @@ -0,0 +1,167 @@ +#include <assert.h> +#include <errno.h> +#include <pwd.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <xkbcommon/xkbcommon.h> +#include "comm.h" +#include "log.h" +#include "loop.h" +#include "seat.h" +#include "swaylock.h" +#include "unicode.h" + +void clear_buffer(char *buf, size_t size) { + // Use volatile keyword so so compiler can't optimize this out. + volatile char *buffer = buf; + volatile char zero = '\0'; + for (size_t i = 0; i < size; ++i) { + buffer[i] = zero; + } +} + +void clear_password_buffer(struct swaylock_password *pw) { + clear_buffer(pw->buffer, sizeof(pw->buffer)); + pw->len = 0; +} + +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) { + size_t utf8_size = utf8_chsize(codepoint); + if (pw->len + utf8_size + 1 >= sizeof(pw->buffer)) { + // TODO: Display error + return; + } + utf8_encode(&pw->buffer[pw->len], codepoint); + pw->buffer[pw->len + utf8_size] = 0; + pw->len += utf8_size; +} + +static void clear_indicator(void *data) { + struct swaylock_state *state = data; + state->clear_indicator_timer = NULL; + state->auth_state = AUTH_STATE_IDLE; + damage_state(state); +} + +void schedule_indicator_clear(struct swaylock_state *state) { + if (state->clear_indicator_timer) { + loop_remove_timer(state->eventloop, state->clear_indicator_timer); + } + state->clear_indicator_timer = loop_add_timer( + state->eventloop, 3000, clear_indicator, state); +} + +static void clear_password(void *data) { + struct swaylock_state *state = data; + state->clear_password_timer = NULL; + state->auth_state = AUTH_STATE_CLEAR; + clear_password_buffer(&state->password); + damage_state(state); + schedule_indicator_clear(state); +} + +static void schedule_password_clear(struct swaylock_state *state) { + if (state->clear_password_timer) { + loop_remove_timer(state->eventloop, state->clear_password_timer); + } + state->clear_password_timer = loop_add_timer( + state->eventloop, 10000, clear_password, state); +} + +static void submit_password(struct swaylock_state *state) { + if (state->args.ignore_empty && state->password.len == 0) { + return; + } + + state->auth_state = AUTH_STATE_VALIDATING; + + if (!write_comm_request(&state->password)) { + state->auth_state = AUTH_STATE_INVALID; + schedule_indicator_clear(state); + } + + damage_state(state); +} + +void swaylock_handle_key(struct swaylock_state *state, + xkb_keysym_t keysym, uint32_t codepoint) { + // Ignore input events if validating + if (state->auth_state == AUTH_STATE_VALIDATING) { + return; + } + + switch (keysym) { + case XKB_KEY_KP_Enter: /* fallthrough */ + case XKB_KEY_Return: + submit_password(state); + break; + case XKB_KEY_Delete: + case XKB_KEY_BackSpace: + if (backspace(&state->password)) { + state->auth_state = AUTH_STATE_BACKSPACE; + } else { + state->auth_state = AUTH_STATE_CLEAR; + } + damage_state(state); + schedule_indicator_clear(state); + schedule_password_clear(state); + break; + case XKB_KEY_Escape: + clear_password_buffer(&state->password); + state->auth_state = AUTH_STATE_CLEAR; + damage_state(state); + schedule_indicator_clear(state); + break; + case XKB_KEY_Caps_Lock: + case XKB_KEY_Shift_L: + case XKB_KEY_Shift_R: + case XKB_KEY_Control_L: + case XKB_KEY_Control_R: + 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: + state->auth_state = AUTH_STATE_INPUT_NOP; + damage_state(state); + schedule_indicator_clear(state); + schedule_password_clear(state); + break; + case XKB_KEY_m: /* fallthrough */ + case XKB_KEY_d: + if (state->xkb.control) { + submit_password(state); + break; + } + // fallthrough + case XKB_KEY_c: /* fallthrough */ + case XKB_KEY_u: + if (state->xkb.control) { + clear_password_buffer(&state->password); + state->auth_state = AUTH_STATE_CLEAR; + damage_state(state); + schedule_indicator_clear(state); + break; + } + // fallthrough + default: + if (codepoint) { + append_ch(&state->password, codepoint); + state->auth_state = AUTH_STATE_INPUT; + damage_state(state); + schedule_indicator_clear(state); + schedule_password_clear(state); + } + break; + } +} diff --git a/pool-buffer.c b/pool-buffer.c new file mode 100644 index 0000000..b3f1ef2 --- /dev/null +++ b/pool-buffer.c @@ -0,0 +1,143 @@ +#define _POSIX_C_SOURCE 200809 +#include <assert.h> +#include <cairo/cairo.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> +#include <wayland-client.h> +#include "pool-buffer.h" + +static bool set_cloexec(int fd) { + long flags = fcntl(fd, F_GETFD); + if (flags == -1) { + return false; + } + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + return false; + } + + return true; +} + +static int create_pool_file(size_t size, char **name) { + static const char template[] = "sway-client-XXXXXX"; + const char *path = getenv("XDG_RUNTIME_DIR"); + if (path == NULL) { + fprintf(stderr, "XDG_RUNTIME_DIR is not set\n"); + return -1; + } + + size_t name_size = strlen(template) + 1 + strlen(path) + 1; + *name = malloc(name_size); + if (*name == NULL) { + fprintf(stderr, "allocation failed\n"); + return -1; + } + snprintf(*name, name_size, "%s/%s", path, template); + + int fd = mkstemp(*name); + if (fd < 0) { + return -1; + } + + if (!set_cloexec(fd)) { + close(fd); + return -1; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static void buffer_release(void *data, struct wl_buffer *wl_buffer) { + struct pool_buffer *buffer = data; + buffer->busy = false; +} + +static const struct wl_buffer_listener buffer_listener = { + .release = buffer_release +}; + +static struct pool_buffer *create_buffer(struct wl_shm *shm, + struct pool_buffer *buf, int32_t width, int32_t height, + uint32_t format) { + uint32_t stride = width * 4; + size_t size = stride * height; + + char *name; + int fd = create_pool_file(size, &name); + assert(fd != -1); + void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); + buf->buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, stride, format); + wl_shm_pool_destroy(pool); + close(fd); + unlink(name); + free(name); + fd = -1; + + buf->size = size; + buf->width = width; + buf->height = height; + buf->data = data; + buf->surface = cairo_image_surface_create_for_data(data, + CAIRO_FORMAT_ARGB32, width, height, stride); + buf->cairo = cairo_create(buf->surface); + + wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); + return buf; +} + +void destroy_buffer(struct pool_buffer *buffer) { + if (buffer->buffer) { + wl_buffer_destroy(buffer->buffer); + } + if (buffer->cairo) { + cairo_destroy(buffer->cairo); + } + if (buffer->surface) { + cairo_surface_destroy(buffer->surface); + } + if (buffer->data) { + munmap(buffer->data, buffer->size); + } + memset(buffer, 0, sizeof(struct pool_buffer)); +} + +struct pool_buffer *get_next_buffer(struct wl_shm *shm, + struct pool_buffer pool[static 2], uint32_t width, uint32_t height) { + struct pool_buffer *buffer = NULL; + + for (size_t i = 0; i < 2; ++i) { + if (pool[i].busy) { + continue; + } + buffer = &pool[i]; + } + + if (!buffer) { + return NULL; + } + + if (buffer->width != width || buffer->height != height) { + destroy_buffer(buffer); + } + + if (!buffer->buffer) { + if (!create_buffer(shm, buffer, width, height, + WL_SHM_FORMAT_ARGB8888)) { + return NULL; + } + } + buffer->busy = true; + return buffer; +} diff --git a/render.c b/render.c new file mode 100644 index 0000000..6d65b96 --- /dev/null +++ b/render.c @@ -0,0 +1,289 @@ +#include <math.h> +#include <stdlib.h> +#include <wayland-client.h> +#include "cairo.h" +#include "background-image.h" +#include "swaylock.h" + +#define M_PI 3.14159265358979323846 +const float TYPE_INDICATOR_RANGE = M_PI / 3.0f; +const float TYPE_INDICATOR_BORDER_THICKNESS = M_PI / 128.0f; + +static void set_color_for_state(cairo_t *cairo, struct swaylock_state *state, + struct swaylock_colorset *colorset) { + if (state->auth_state == AUTH_STATE_VALIDATING) { + cairo_set_source_u32(cairo, colorset->verifying); + } else if (state->auth_state == AUTH_STATE_INVALID) { + cairo_set_source_u32(cairo, colorset->wrong); + } else if (state->auth_state == AUTH_STATE_CLEAR) { + cairo_set_source_u32(cairo, colorset->cleared); + } else { + if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { + cairo_set_source_u32(cairo, colorset->caps_lock); + } else if (state->xkb.caps_lock && !state->args.show_caps_lock_indicator && + state->args.show_caps_lock_text) { + uint32_t inputtextcolor = state->args.colors.text.input; + state->args.colors.text.input = state->args.colors.text.caps_lock; + cairo_set_source_u32(cairo, colorset->input); + state->args.colors.text.input = inputtextcolor; + } else { + cairo_set_source_u32(cairo, colorset->input); + } + } +} + +void render_frame_background(struct swaylock_surface *surface) { + struct swaylock_state *state = surface->state; + + int buffer_width = surface->width * surface->scale; + int buffer_height = surface->height * surface->scale; + if (buffer_width == 0 || buffer_height == 0) { + return; // not yet configured + } + + surface->current_buffer = get_next_buffer(state->shm, + surface->buffers, buffer_width, buffer_height); + if (surface->current_buffer == NULL) { + return; + } + + cairo_t *cairo = surface->current_buffer->cairo; + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); + + cairo_save(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_u32(cairo, state->args.colors.background); + cairo_paint(cairo); + if (surface->image && state->args.mode != BACKGROUND_MODE_SOLID_COLOR) { + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + render_background_image(cairo, surface->image, + state->args.mode, buffer_width, buffer_height); + } + cairo_restore(cairo); + cairo_identity_matrix(cairo); + + wl_surface_set_buffer_scale(surface->surface, surface->scale); + wl_surface_attach(surface->surface, surface->current_buffer->buffer, 0, 0); + wl_surface_damage(surface->surface, 0, 0, surface->width, surface->height); + wl_surface_commit(surface->surface); +} + +void render_frame(struct swaylock_surface *surface) { + struct swaylock_state *state = surface->state; + + int arc_radius = state->args.radius * surface->scale; + int arc_thickness = state->args.thickness * surface->scale; + int buffer_diameter = (arc_radius + arc_thickness) * 2; + int buffer_height = buffer_diameter * 2; + + int indicator_radius = state->args.radius + state->args.thickness; + int subsurf_xpos = surface->width / 2 - indicator_radius; + int subsurf_ypos = surface->height / 2 - indicator_radius; + wl_subsurface_set_position(surface->subsurface, subsurf_xpos, subsurf_ypos); + + surface->current_buffer = get_next_buffer(state->shm, + surface->indicator_buffers, buffer_diameter, buffer_height); + if (surface->current_buffer == NULL) { + return; + } + + // Hide subsurface until we want it visible + wl_surface_attach(surface->child, NULL, 0, 0); + wl_surface_commit(surface->child); + + cairo_t *cairo = surface->current_buffer->cairo; + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); + cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); + cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(surface->subpixel)); + cairo_set_font_options(cairo, fo); + cairo_font_options_destroy(fo); + cairo_identity_matrix(cairo); + + // Clear + cairo_save(cairo); + cairo_set_source_rgba(cairo, 0, 0, 0, 0); + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_paint(cairo); + cairo_restore(cairo); + + 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_diameter / 2, buffer_diameter / 2, arc_radius, + 0, 2 * M_PI); + set_color_for_state(cairo, state, &state->args.colors.inside); + cairo_fill_preserve(cairo); + set_color_for_state(cairo, state, &state->args.colors.ring); + cairo_stroke(cairo); + + // Draw a message + char *text = NULL; + const char *layout_text = NULL; + char attempts[4]; // like i3lock: count no more than 999 + set_color_for_state(cairo, state, &state->args.colors.text); + cairo_select_font_face(cairo, state->args.font, + 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; + case AUTH_STATE_CLEAR: + text = "cleared"; + break; + case AUTH_STATE_INPUT: + case AUTH_STATE_INPUT_NOP: + case AUTH_STATE_BACKSPACE: + // Caps Lock has higher priority + if (state->xkb.caps_lock && state->args.show_caps_lock_text) { + text = "Caps Lock"; + } else if (state->args.show_failed_attempts && + state->failed_attempts > 0) { + if (state->failed_attempts > 999) { + text = "999+"; + } else { + snprintf(attempts, sizeof(attempts), "%d", state->failed_attempts); + text = attempts; + } + } + + xkb_layout_index_t num_layout = xkb_keymap_num_layouts(state->xkb.keymap); + if (!state->args.hide_keyboard_layout && + (state->args.show_keyboard_layout || num_layout > 1)) { + xkb_layout_index_t curr_layout = 0; + + // advance to the first active layout (if any) + while (curr_layout < num_layout && + xkb_state_layout_index_is_active(state->xkb.state, + curr_layout, XKB_STATE_LAYOUT_EFFECTIVE) != 1) { + ++curr_layout; + } + // will handle invalid index if none are active + layout_text = xkb_keymap_layout_get_name(state->xkb.keymap, curr_layout); + } + break; + default: + break; + } + + if (text) { + cairo_text_extents_t extents; + cairo_font_extents_t fe; + double x, y; + cairo_text_extents(cairo, text, &extents); + cairo_font_extents(cairo, &fe); + x = (buffer_diameter / 2) - + (extents.width / 2 + extents.x_bearing); + y = (buffer_diameter / 2) + + (fe.height / 2 - fe.descent); + + 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_diameter / 2, buffer_diameter / 2, + arc_radius, highlight_start, + highlight_start + TYPE_INDICATOR_RANGE); + if (state->auth_state == AUTH_STATE_INPUT) { + if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { + cairo_set_source_u32(cairo, state->args.colors.caps_lock_key_highlight); + } else { + cairo_set_source_u32(cairo, state->args.colors.key_highlight); + } + } else { + if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { + cairo_set_source_u32(cairo, state->args.colors.caps_lock_bs_highlight); + } else { + cairo_set_source_u32(cairo, state->args.colors.bs_highlight); + } + } + cairo_stroke(cairo); + + // Draw borders + cairo_set_source_u32(cairo, state->args.colors.separator); + cairo_arc(cairo, buffer_diameter / 2, buffer_diameter / 2, + arc_radius, highlight_start, + highlight_start + type_indicator_border_thickness); + cairo_stroke(cairo); + + cairo_arc(cairo, buffer_diameter / 2, buffer_diameter / 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 + set_color_for_state(cairo, state, &state->args.colors.line); + cairo_set_line_width(cairo, 2.0 * surface->scale); + cairo_arc(cairo, buffer_diameter / 2, buffer_diameter / 2, + arc_radius - arc_thickness / 2, 0, 2 * M_PI); + cairo_stroke(cairo); + cairo_arc(cairo, buffer_diameter / 2, buffer_diameter / 2, + arc_radius + arc_thickness / 2, 0, 2 * M_PI); + cairo_stroke(cairo); + + // display layout text seperately + if (layout_text) { + cairo_text_extents_t extents; + cairo_font_extents_t fe; + double x, y; + double box_padding = 4.0 * surface->scale; + cairo_text_extents(cairo, layout_text, &extents); + cairo_font_extents(cairo, &fe); + // upper left coordinates for box + x = (buffer_diameter / 2) - (extents.width / 2) - box_padding; + y = (buffer_diameter / 2) + arc_radius + arc_thickness/2 + + box_padding; // use box_padding also as gap to indicator + + // background box + cairo_rectangle(cairo, x, y, + extents.width + 2.0 * box_padding, + fe.height + 2.0 * box_padding); + cairo_set_source_u32(cairo, state->args.colors.layout_background); + cairo_fill_preserve(cairo); + // border + cairo_set_source_u32(cairo, state->args.colors.layout_border); + cairo_stroke(cairo); + cairo_new_sub_path(cairo); + + // take font extents and padding into account + cairo_move_to(cairo, + x - extents.x_bearing + box_padding, + y + (fe.height - fe.descent) + box_padding); + cairo_set_source_u32(cairo, state->args.colors.layout_text); + cairo_show_text(cairo, layout_text); + cairo_new_sub_path(cairo); + } + } + + wl_surface_set_buffer_scale(surface->child, surface->scale); + wl_surface_attach(surface->child, surface->current_buffer->buffer, 0, 0); + wl_surface_damage(surface->child, 0, 0, surface->current_buffer->width, surface->current_buffer->height); + wl_surface_commit(surface->child); + + 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); + } +} @@ -0,0 +1,178 @@ +#include <assert.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <unistd.h> +#include <xkbcommon/xkbcommon.h> +#include "log.h" +#include "swaylock.h" +#include "seat.h" + +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); + swaylock_log(LOG_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); + swaylock_log(LOG_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); + int caps_lock = xkb_state_mod_name_is_active(state->xkb.state, + XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED); + if (caps_lock != state->xkb.caps_lock) { + state->xkb.caps_lock = caps_lock; + damage_state(state); + } + state->xkb.control = xkb_state_mod_name_is_active(state->xkb.state, + XKB_MOD_NAME_CTRL, + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + +} + +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_seat *seat = data; + if (seat->pointer) { + wl_pointer_release(seat->pointer); + seat->pointer = NULL; + } + if (seat->keyboard) { + wl_keyboard_release(seat->keyboard); + seat->keyboard = NULL; + } + if ((caps & WL_SEAT_CAPABILITY_POINTER)) { + seat->pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(seat->pointer, &pointer_listener, NULL); + } + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) { + seat->keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, seat->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, +}; diff --git a/shadow.c b/shadow.c new file mode 100644 index 0000000..0c474e6 --- /dev/null +++ b/shadow.c @@ -0,0 +1,106 @@ +#define _XOPEN_SOURCE // for crypt +#include <pwd.h> +#include <shadow.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/types.h> +#include <unistd.h> +#ifdef __GLIBC__ +// GNU, you damn slimy bastard +#include <crypt.h> +#endif +#include "comm.h" +#include "log.h" +#include "swaylock.h" + +void initialize_pw_backend(int argc, char **argv) { + if (geteuid() != 0) { + swaylock_log(LOG_ERROR, + "swaylock needs to be setuid to read /etc/shadow"); + exit(EXIT_FAILURE); + } + + if (!spawn_comm_child()) { + exit(EXIT_FAILURE); + } + + if (setgid(getgid()) != 0) { + swaylock_log_errno(LOG_ERROR, "Unable to drop root"); + exit(EXIT_FAILURE); + } + if (setuid(getuid()) != 0) { + swaylock_log_errno(LOG_ERROR, "Unable to drop root"); + exit(EXIT_FAILURE); + } + if (setuid(0) != -1) { + swaylock_log_errno(LOG_ERROR, "Unable to drop root (we shouldn't be " + "able to restore it after setuid)"); + exit(EXIT_FAILURE); + } +} + +void run_pw_backend_child(void) { + /* This code runs as root */ + struct passwd *pwent = getpwuid(getuid()); + if (!pwent) { + swaylock_log_errno(LOG_ERROR, "failed to getpwuid"); + exit(EXIT_FAILURE); + } + char *encpw = pwent->pw_passwd; + if (strcmp(encpw, "x") == 0) { + struct spwd *swent = getspnam(pwent->pw_name); + if (!swent) { + swaylock_log_errno(LOG_ERROR, "failed to getspnam"); + exit(EXIT_FAILURE); + } + encpw = swent->sp_pwdp; + } + + /* We don't need any additional logging here because the parent process will + * also fail here and will handle logging for us. */ + if (setgid(getgid()) != 0) { + exit(EXIT_FAILURE); + } + if (setuid(getuid()) != 0) { + exit(EXIT_FAILURE); + } + if (setuid(0) != -1) { + exit(EXIT_FAILURE); + } + + /* This code does not run as root */ + swaylock_log(LOG_DEBUG, "Prepared to authorize user %s", pwent->pw_name); + + while (1) { + char *buf; + ssize_t size = read_comm_request(&buf); + if (size < 0) { + exit(EXIT_FAILURE); + } else if (size == 0) { + break; + } + + char *c = crypt(buf, encpw); + if (c == NULL) { + swaylock_log_errno(LOG_ERROR, "crypt failed"); + clear_buffer(buf, size); + exit(EXIT_FAILURE); + } + bool success = strcmp(c, encpw) == 0; + + if (!write_comm_reply(success)) { + clear_buffer(buf, size); + exit(EXIT_FAILURE); + } + + // We don't want to keep it in memory longer than necessary, + // so clear *before* sleeping. + clear_buffer(buf, size); + free(buf); + + sleep(2); + } + + clear_buffer(encpw, strlen(encpw)); + exit(EXIT_SUCCESS); +}
\ No newline at end of file diff --git a/swaylock.1.scd b/swaylock.1.scd new file mode 100644 index 0000000..62e4bc7 --- /dev/null +++ b/swaylock.1.scd @@ -0,0 +1,192 @@ +swaylock(1) + +# NAME + +swaylock - Screen locker for Wayland + +# SYNOPSIS + +_swaylock_ [options...] + +Locks your Wayland session. + +# OPTIONS + +*-C, --config* <path> + The config file to use. By default, the following paths are checked: + _$HOME/.swaylock/config_, _$XDG\_CONFIG\_HOME/swaylock/config_, and + _SYSCONFDIR/swaylock/config_. All flags aside from this one are valid + options in the configuration file using the format _long-option=value_. + For options such as _ignore-empty-password_, just supply the _long-option_. + All leading dashes should be omitted and the equals sign is required for + flags that take an argument. + +*-d, --debug* + Enable debugging output. + +*-e, --ignore-empty-password* + When an empty password is provided by the user, do not validate it. + +*-F, --show-failed-attempts* + Show the number of failed authentication attempts on the indicator. + +*-f, --daemonize* + Detach from the controlling terminal after locking. + + Note: this is the default behavior of i3lock. + +*-h, --help* + Show help message and quit. + +*-v, --version* + Show the version number and quit. + +# APPEARANCE + +*-u, --no-unlock-indicator* + Disable the unlock indicator. + +*-i, --image* [[<output>]:]<path> + Display the given image, optionally only on the given output. Use -c to set + a background color. If the path potentially contains a ':', prefix it with another + ':' to prevent interpreting part of it as <output>. + +*-k, --show-keyboard-layout* + Force displaying the current xkb layout while typing, even if only one layout + is configured. + +*-K, --hide-keyboard-layout* + Force hiding the current xkb layout while typing, even if more than one layout + is configured or the show-keyboard-layout option is set. + +*-L, --disable-caps-lock-text* + Disable the Caps Lock Text. + +*-l, --indicator-caps-lock* + Show the current Caps Lock state also on the indicator. + +*-s, --scaling* + Scaling mode for images: _stretch_, _fill_, _fit_, _center_, or _tile_. Use + the additional mode _solid\_color_ to display only the background color, even + if a background image is specified. + +*-t, --tiling* + Same as --scaling=tile. + +*-c, --color* <rrggbb[aa]> + Turn the screen into the given color. If -i is used, this sets the + background of the image to the given color. Defaults to white (FFFFFF). + +*--bs-hl-color* <rrggbb[aa]> + Sets the color of backspace highlight segments. + +*--caps-lock-bs-hl-color* <rrggbb[aa]> + Sets the color of backspace highlight segments when Caps Lock is active. + +*--caps-lock-key-hl-color* <rrggbb[aa]> + Sets the color of the key press highlight segments when Caps Lock is active. + +*--font* <font> + Sets the font of the text inside the indicator. + +*--indicator-radius* <radius> + Sets the radius of the indicator to _radius_ pixels. The default value is + 50. + +*--indicator-thickness* <thickness> + Sets the thickness of the indicator to _thickness_ pixels. The default value + is 10. + +*--inside-color* <rrggbb[aa]> + Sets the color of the inside of the indicator when typing or idle. + +*--inside-clear-color* <rrggbb[aa]> + Sets the color of the inside of the indicator when cleared. + +*--inside-caps-lock-color* <rrggbb[aa]> + Sets the color of the inside of the indicator when Caps Lock is active. + +*--inside-ver-color* <rrggbb[aa]> + Sets the color of the inside of the indicator when verifying. + +*--inside-wrong-color* <rrggbb[aa]> + Sets the color of the inside of the indicator when invalid. + +*--key-hl-color* <rrggbb[aa]> + Sets the color of key press highlight segments. + +*--layout-bg-color* <rrggbb[aa]> + Sets the background color of the box containing the layout text. + +*--layout-border-color* <rrggbb[aa]> + Sets the color of the border of the box containing the layout text. + +*--layout-text-color* <rrggbb[aa]> + Sets the color of the layout text. + +*--line-color* <rrggbb[aa]> + Sets the color of the lines that separate the inside and outside of the + indicator when typing or idle. + +*--line-clear-color* <rrggbb[aa]> + Sets the color of the lines that separate the inside and outside of the + indicator when cleared. + +*--line-caps-lock-color* <rrggbb[aa]> + Sets the color of the line between the inside and ring when Caps Lock + is active. + +*--line-ver-color* <rrggbb[aa]> + Sets the color of the lines that separate the inside and outside of the + indicator when verifying. + +*--line-wrong-color* <rrggbb[aa]> + Sets the color of the lines that separate the inside and outside of the + indicator when invalid. + +*-n, --line-uses-inside* + Use the color of the inside of the indicator for the line separating the + inside and outside of the indicator. + +*-r, --line-uses-ring* + Use the outer ring's color for the line separating the inside and outside of + the indicator. + +*--ring-color* <rrggbb[aa]> + Sets the color of the outside of the indicator when typing or idle. + +*--ring-clear-color* <rrggbb[aa]> + Sets the color of the outside of the indicator when cleared. + +*--ring-caps-lock-color* <rrggbb[aa]> + Sets the color of the ring of the indicator when Caps Lock is active. + +*--ring-ver-color* <rrggbb[aa]> + Sets the color of the outside of the indicator when verifying. + +*--ring-wrong-color* <rrggbb[aa]> + Sets the color of the outside of the indicator when invalid. + +*--separator-color* <rrggbb[aa]> + Sets the color of the lines that separate highlight segments. + +*--text-color* <rrggbb[aa]> + Sets the color of the text inside the indicator when typing or idle. + +*--text-clear-color* <rrggbb[aa]> + Sets the color of the text inside the indicator when cleared. + +*--text-caps-lock-color* <rrggbb[aa]> + Sets the color of the text when Caps Lock is active. + +*--text-ver-color* <rrggbb[aa]> + Sets the color of the text inside the indicator when verifying. + +*--text-wrong-color* <rrggbb[aa]> + Sets the color of the text inside the indicator when invalid. + +# AUTHORS + +Maintained by Drew DeVault <sir@cmpwn.com>, who is assisted by other open +source contributors. For more information about swaylock development, see +https://github.com/swaywm/swaylock. diff --git a/unicode.c b/unicode.c new file mode 100644 index 0000000..3d81a34 --- /dev/null +++ b/unicode.c @@ -0,0 +1,66 @@ +#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; +} + +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) / sizeof(*sizes); ++i) { + if ((c & sizes[i].mask) == sizes[i].result) { + return sizes[i].octets; + } + } + return -1; +} diff --git a/wlr-input-inhibitor-unstable-v1.xml b/wlr-input-inhibitor-unstable-v1.xml new file mode 100644 index 0000000..b62d1bb --- /dev/null +++ b/wlr-input-inhibitor-unstable-v1.xml @@ -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> diff --git a/wlr-layer-shell-unstable-v1.xml b/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..2bb72ed --- /dev/null +++ b/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,285 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_layer_shell_v1_unstable_v1"> + <copyright> + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <interface name="zwlr_layer_shell_v1" version="1"> + <description summary="create surfaces that are layers of the desktop"> + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + </description> + + <request name="get_layer_surface"> + <description summary="create a layer_surface from a surface"> + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + </description> + <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="output" type="object" interface="wl_output" allow-null="true"/> + <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/> + <arg name="namespace" type="string" summary="namespace for the layer surface"/> + </request> + + <enum name="error"> + <entry name="role" value="0" summary="wl_surface has another role"/> + <entry name="invalid_layer" value="1" summary="layer value is invalid"/> + <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/> + </enum> + + <enum name="layer"> + <description summary="available layers for surfaces"> + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + </description> + + <entry name="background" value="0"/> + <entry name="bottom" value="1"/> + <entry name="top" value="2"/> + <entry name="overlay" value="3"/> + </enum> + </interface> + + <interface name="zwlr_layer_surface_v1" version="1"> + <description summary="layer metadata interface"> + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (size, anchor, exclusive zone, margin, interactivity) + is double-buffered, and will be applied at the time wl_surface.commit of + the corresponding wl_surface is called. + </description> + + <request name="set_size"> + <description summary="sets the size of the surface"> + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + </description> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </request> + + <request name="set_anchor"> + <description summary="configures the anchor point of the surface"> + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthoginal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + </description> + <arg name="anchor" type="uint" enum="anchor"/> + </request> + + <request name="set_exclusive_zone"> + <description summary="configures the exclusive geometry of this surface"> + Requests that the compositor avoids occluding an area of the surface + with other surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to an + edge, rather than a corner. The zone is the number of surface-local + coordinates from the edge that are considered exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive excluzive zone. If set to -1, the surface + indicates that it would not like to be moved to accomodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + </description> + <arg name="zone" type="int"/> + </request> + + <request name="set_margin"> + <description summary="sets a margin from the anchor point"> + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + </description> + <arg name="top" type="int"/> + <arg name="right" type="int"/> + <arg name="bottom" type="int"/> + <arg name="left" type="int"/> + </request> + + <request name="set_keyboard_interactivity"> + <description summary="requests keyboard events"> + Set to 1 to request that the seat send keyboard events to this layer + surface. For layers below the shell surface layer, the seat will use + normal focus semantics. For layers above the shell surface layers, the + seat will always give exclusive keyboard focus to the top-most layer + which has keyboard interactivity set to true. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Events is double-buffered, see wl_surface.commit. + </description> + <arg name="keyboard_interactivity" type="uint"/> + </request> + + <request name="get_popup"> + <description summary="assign this layer_surface as an xdg_popup parent"> + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + </description> + <arg name="popup" type="object" interface="xdg_popup"/> + </request> + + <request name="ack_configure"> + <description summary="ack a configure event"> + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + </description> + <arg name="serial" type="uint" summary="the serial from the configure event"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the layer_surface"> + This request destroys the layer surface. + </description> + </request> + + <event name="configure"> + <description summary="suggest a surface change"> + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + </description> + <arg name="serial" type="uint"/> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </event> + + <event name="closed"> + <description summary="surface should be closed"> + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + </description> + </event> + + <enum name="error"> + <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/> + <entry name="invalid_size" value="1" summary="size is invalid"/> + <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/> + </enum> + + <enum name="anchor" bitfield="true"> + <entry name="top" value="1" summary="the top edge of the anchor rectangle"/> + <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/> + <entry name="left" value="4" summary="the left edge of the anchor rectangle"/> + <entry name="right" value="8" summary="the right edge of the anchor rectangle"/> + </enum> + </interface> +</protocol> |