├── .build.yml ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── background-image.c ├── cairo.c ├── comm.c ├── completions ├── bash │ └── swaylock ├── fish │ └── swaylock.fish ├── meson.build └── zsh │ └── _swaylock ├── include ├── background-image.h ├── cairo.h ├── comm.h ├── log.h ├── loop.h ├── meson.build ├── password-buffer.h ├── pool-buffer.h ├── seat.h ├── swaylock.h └── unicode.h ├── log.c ├── loop.c ├── main.c ├── meson.build ├── meson_options.txt ├── pam.c ├── pam └── swaylock ├── password-buffer.c ├── password.c ├── pool-buffer.c ├── render.c ├── seat.c ├── shadow.c ├── swaylock.1.scd └── unicode.c /.build.yml: -------------------------------------------------------------------------------- 1 | image: alpine/edge 2 | packages: 3 | - meson 4 | - cairo-dev 5 | - wayland-dev 6 | - wayland-protocols 7 | - libxkbcommon-dev 8 | - gdk-pixbuf-dev 9 | - linux-pam-dev 10 | - scdoc 11 | sources: 12 | - https://github.com/swaywm/swaylock 13 | tasks: 14 | - setup: | 15 | cd swaylock 16 | meson build 17 | - build: | 18 | cd swaylock 19 | ninja -C build 20 | - build-no-pam: | 21 | cd swaylock 22 | meson configure build -Dpam=disabled 23 | ninja -C build 24 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # For the full list of code style requirements, see sway's CONTRIBUTING.md 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | 9 | [*.{c,h,cmake,txt}] 10 | indent_style = tab 11 | indent_size = 4 12 | 13 | [*.{xml,yml}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [config] 18 | indent_style = space 19 | indent_size = 4 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2019 Drew DeVault 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swaylock 2 | 3 | swaylock is a screen locking utility for Wayland compositors. It is compatible 4 | with any Wayland compositor which implements the ext-session-lock-v1 Wayland 5 | protocol. 6 | 7 | See the man page, [swaylock(1)](swaylock.1.scd), for instructions on using swaylock. 8 | 9 | ## Release Signatures 10 | 11 | Releases are signed with [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) 12 | and published [on GitHub](https://github.com/swaywm/swaylock/releases). swaylock 13 | releases are managed independently of sway releases. 14 | 15 | ## Installation 16 | 17 | ### From Packages 18 | 19 | Swaylock is available in many distributions. Try installing the "swaylock" 20 | package for yours. 21 | 22 | ### Compiling from Source 23 | 24 | Install dependencies: 25 | 26 | * meson \* 27 | * wayland 28 | * wayland-protocols \* 29 | * libxkbcommon 30 | * cairo 31 | * gdk-pixbuf2 \*\* 32 | * pam (optional) 33 | * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional: man pages) \* 34 | * git \* 35 | 36 | _\* Compile-time dep_ 37 | _\*\* Optional: required for background images other than PNG_ 38 | 39 | Run these commands: 40 | 41 | meson build 42 | ninja -C build 43 | sudo ninja -C build install 44 | 45 | On systems without PAM, you need to suid the swaylock binary: 46 | 47 | sudo chmod a+s /usr/local/bin/swaylock 48 | 49 | Swaylock will drop root permissions shortly after startup. 50 | -------------------------------------------------------------------------------- /background-image.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "background-image.h" 3 | #include "cairo.h" 4 | #include "log.h" 5 | 6 | enum background_mode parse_background_mode(const char *mode) { 7 | if (strcmp(mode, "stretch") == 0) { 8 | return BACKGROUND_MODE_STRETCH; 9 | } else if (strcmp(mode, "fill") == 0) { 10 | return BACKGROUND_MODE_FILL; 11 | } else if (strcmp(mode, "fit") == 0) { 12 | return BACKGROUND_MODE_FIT; 13 | } else if (strcmp(mode, "center") == 0) { 14 | return BACKGROUND_MODE_CENTER; 15 | } else if (strcmp(mode, "tile") == 0) { 16 | return BACKGROUND_MODE_TILE; 17 | } else if (strcmp(mode, "solid_color") == 0) { 18 | return BACKGROUND_MODE_SOLID_COLOR; 19 | } 20 | swaylock_log(LOG_ERROR, "Unsupported background mode: %s", mode); 21 | return BACKGROUND_MODE_INVALID; 22 | } 23 | 24 | cairo_surface_t *load_background_image(const char *path) { 25 | cairo_surface_t *image; 26 | #if HAVE_GDK_PIXBUF 27 | GError *err = NULL; 28 | GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err); 29 | if (!pixbuf) { 30 | swaylock_log(LOG_ERROR, "Failed to load background image (%s).", 31 | err->message); 32 | return NULL; 33 | } 34 | // Correct for embedded image orientation; typical images are not 35 | // rotated and will be handled efficiently 36 | GdkPixbuf *oriented = gdk_pixbuf_apply_embedded_orientation(pixbuf); 37 | g_object_unref(pixbuf); 38 | image = gdk_cairo_image_surface_create_from_pixbuf(oriented); 39 | g_object_unref(oriented); 40 | #else 41 | image = cairo_image_surface_create_from_png(path); 42 | #endif // HAVE_GDK_PIXBUF 43 | if (!image) { 44 | swaylock_log(LOG_ERROR, "Failed to read background image."); 45 | return NULL; 46 | } 47 | if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) { 48 | swaylock_log(LOG_ERROR, "Failed to read background image: %s." 49 | #if !HAVE_GDK_PIXBUF 50 | "\nSway was compiled without gdk_pixbuf support, so only" 51 | "\nPNG images can be loaded. This is the likely cause." 52 | #endif // !HAVE_GDK_PIXBUF 53 | , cairo_status_to_string(cairo_surface_status(image))); 54 | return NULL; 55 | } 56 | return image; 57 | } 58 | 59 | void render_background_image(cairo_t *cairo, cairo_surface_t *image, 60 | enum background_mode mode, int buffer_width, int buffer_height) { 61 | double width = cairo_image_surface_get_width(image); 62 | double height = cairo_image_surface_get_height(image); 63 | 64 | cairo_save(cairo); 65 | switch (mode) { 66 | case BACKGROUND_MODE_STRETCH: 67 | cairo_scale(cairo, 68 | (double)buffer_width / width, 69 | (double)buffer_height / height); 70 | cairo_set_source_surface(cairo, image, 0, 0); 71 | break; 72 | case BACKGROUND_MODE_FILL: { 73 | double window_ratio = (double)buffer_width / buffer_height; 74 | double bg_ratio = width / height; 75 | 76 | if (window_ratio > bg_ratio) { 77 | double scale = (double)buffer_width / width; 78 | cairo_scale(cairo, scale, scale); 79 | cairo_set_source_surface(cairo, image, 80 | 0, (double)buffer_height / 2 / scale - height / 2); 81 | } else { 82 | double scale = (double)buffer_height / height; 83 | cairo_scale(cairo, scale, scale); 84 | cairo_set_source_surface(cairo, image, 85 | (double)buffer_width / 2 / scale - width / 2, 0); 86 | } 87 | break; 88 | } 89 | case BACKGROUND_MODE_FIT: { 90 | double window_ratio = (double)buffer_width / buffer_height; 91 | double bg_ratio = width / height; 92 | 93 | if (window_ratio > bg_ratio) { 94 | double scale = (double)buffer_height / height; 95 | cairo_scale(cairo, scale, scale); 96 | cairo_set_source_surface(cairo, image, 97 | (double)buffer_width / 2 / scale - width / 2, 0); 98 | } else { 99 | double scale = (double)buffer_width / width; 100 | cairo_scale(cairo, scale, scale); 101 | cairo_set_source_surface(cairo, image, 102 | 0, (double)buffer_height / 2 / scale - height / 2); 103 | } 104 | break; 105 | } 106 | case BACKGROUND_MODE_CENTER: 107 | /* 108 | * Align the unscaled image to integer pixel boundaries 109 | * in order to prevent loss of clarity (this only matters 110 | * for odd-sized images). 111 | */ 112 | cairo_set_source_surface(cairo, image, 113 | (int)((double)buffer_width / 2 - width / 2), 114 | (int)((double)buffer_height / 2 - height / 2)); 115 | break; 116 | case BACKGROUND_MODE_TILE: { 117 | cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image); 118 | cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); 119 | cairo_set_source(cairo, pattern); 120 | break; 121 | } 122 | case BACKGROUND_MODE_SOLID_COLOR: 123 | case BACKGROUND_MODE_INVALID: 124 | assert(0); 125 | break; 126 | } 127 | cairo_paint(cairo); 128 | cairo_restore(cairo); 129 | } 130 | -------------------------------------------------------------------------------- /cairo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "cairo.h" 4 | #if HAVE_GDK_PIXBUF 5 | #include 6 | #endif 7 | 8 | void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { 9 | cairo_set_source_rgba(cairo, 10 | (color >> (3*8) & 0xFF) / 255.0, 11 | (color >> (2*8) & 0xFF) / 255.0, 12 | (color >> (1*8) & 0xFF) / 255.0, 13 | (color >> (0*8) & 0xFF) / 255.0); 14 | } 15 | 16 | cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel) { 17 | switch (subpixel) { 18 | case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: 19 | return CAIRO_SUBPIXEL_ORDER_RGB; 20 | case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: 21 | return CAIRO_SUBPIXEL_ORDER_BGR; 22 | case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: 23 | return CAIRO_SUBPIXEL_ORDER_VRGB; 24 | case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: 25 | return CAIRO_SUBPIXEL_ORDER_VBGR; 26 | default: 27 | return CAIRO_SUBPIXEL_ORDER_DEFAULT; 28 | } 29 | return CAIRO_SUBPIXEL_ORDER_DEFAULT; 30 | } 31 | 32 | #if HAVE_GDK_PIXBUF 33 | cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf(const GdkPixbuf *gdkbuf) { 34 | int chan = gdk_pixbuf_get_n_channels(gdkbuf); 35 | if (chan < 3) { 36 | return NULL; 37 | } 38 | 39 | const guint8* gdkpix = gdk_pixbuf_read_pixels(gdkbuf); 40 | if (!gdkpix) { 41 | return NULL; 42 | } 43 | gint w = gdk_pixbuf_get_width(gdkbuf); 44 | gint h = gdk_pixbuf_get_height(gdkbuf); 45 | int stride = gdk_pixbuf_get_rowstride(gdkbuf); 46 | 47 | cairo_format_t fmt = (chan == 3) ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32; 48 | cairo_surface_t * cs = cairo_image_surface_create (fmt, w, h); 49 | cairo_surface_flush (cs); 50 | if ( !cs || cairo_surface_status(cs) != CAIRO_STATUS_SUCCESS) { 51 | return NULL; 52 | } 53 | 54 | int cstride = cairo_image_surface_get_stride(cs); 55 | unsigned char * cpix = cairo_image_surface_get_data(cs); 56 | 57 | if (chan == 3) { 58 | int i; 59 | for (i = h; i; --i) { 60 | const guint8 *gp = gdkpix; 61 | unsigned char *cp = cpix; 62 | const guint8* end = gp + 3*w; 63 | while (gp < end) { 64 | #if G_BYTE_ORDER == G_LITTLE_ENDIAN 65 | cp[0] = gp[2]; 66 | cp[1] = gp[1]; 67 | cp[2] = gp[0]; 68 | #else 69 | cp[1] = gp[0]; 70 | cp[2] = gp[1]; 71 | cp[3] = gp[2]; 72 | #endif 73 | gp += 3; 74 | cp += 4; 75 | } 76 | gdkpix += stride; 77 | cpix += cstride; 78 | } 79 | } else { 80 | /* premul-color = alpha/255 * color/255 * 255 = (alpha*color)/255 81 | * (z/255) = z/256 * 256/255 = z/256 (1 + 1/255) 82 | * = z/256 + (z/256)/255 = (z + z/255)/256 83 | * # recurse once 84 | * = (z + (z + z/255)/256)/256 85 | * = (z + z/256 + z/256/255) / 256 86 | * # only use 16bit uint operations, loose some precision, 87 | * # result is floored. 88 | * -> (z + z>>8)>>8 89 | * # add 0x80/255 = 0.5 to convert floor to round 90 | * => (z+0x80 + (z+0x80)>>8 ) >> 8 91 | * ------ 92 | * tested as equal to lround(z/255.0) for uint z in [0..0xfe02] 93 | */ 94 | #define PREMUL_ALPHA(x,a,b,z) \ 95 | G_STMT_START { z = a * b + 0x80; x = (z + (z >> 8)) >> 8; } \ 96 | G_STMT_END 97 | int i; 98 | for (i = h; i; --i) { 99 | const guint8 *gp = gdkpix; 100 | unsigned char *cp = cpix; 101 | const guint8* end = gp + 4*w; 102 | guint z1, z2, z3; 103 | while (gp < end) { 104 | #if G_BYTE_ORDER == G_LITTLE_ENDIAN 105 | PREMUL_ALPHA(cp[0], gp[2], gp[3], z1); 106 | PREMUL_ALPHA(cp[1], gp[1], gp[3], z2); 107 | PREMUL_ALPHA(cp[2], gp[0], gp[3], z3); 108 | cp[3] = gp[3]; 109 | #else 110 | PREMUL_ALPHA(cp[1], gp[0], gp[3], z1); 111 | PREMUL_ALPHA(cp[2], gp[1], gp[3], z2); 112 | PREMUL_ALPHA(cp[3], gp[2], gp[3], z3); 113 | cp[0] = gp[3]; 114 | #endif 115 | gp += 4; 116 | cp += 4; 117 | } 118 | gdkpix += stride; 119 | cpix += cstride; 120 | } 121 | #undef PREMUL_ALPHA 122 | } 123 | cairo_surface_mark_dirty(cs); 124 | return cs; 125 | } 126 | #endif // HAVE_GDK_PIXBUF 127 | -------------------------------------------------------------------------------- /comm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "comm.h" 7 | #include "log.h" 8 | #include "swaylock.h" 9 | #include "password-buffer.h" 10 | 11 | static int comm[2][2] = {{-1, -1}, {-1, -1}}; 12 | 13 | static ssize_t read_full(int fd, void *dst, size_t size) { 14 | char *buf = dst; 15 | size_t offset = 0; 16 | while (offset < size) { 17 | ssize_t n = read(fd, &buf[offset], size - offset); 18 | if (n < 0) { 19 | if (errno == EINTR) { 20 | continue; 21 | } 22 | swaylock_log_errno(LOG_ERROR, "read() failed"); 23 | return -1; 24 | } else if (n == 0) { 25 | if (offset == 0) { 26 | return 0; 27 | } 28 | swaylock_log(LOG_ERROR, "read() failed: unexpected EOF"); 29 | return -1; 30 | } 31 | offset += n; 32 | } 33 | return offset; 34 | } 35 | 36 | static bool write_full(int fd, const void *src, size_t size) { 37 | const char *buf = src; 38 | size_t offset = 0; 39 | while (offset < size) { 40 | ssize_t n = write(fd, &buf[offset], size - offset); 41 | if (n <= 0) { 42 | assert(n != 0); 43 | if (errno == EINTR) { 44 | continue; 45 | } 46 | swaylock_log_errno(LOG_ERROR, "write() failed"); 47 | return false; 48 | } 49 | offset += n; 50 | } 51 | return true; 52 | } 53 | 54 | ssize_t read_comm_request(char **buf_ptr) { 55 | int fd = comm[0][0]; 56 | 57 | size_t size; 58 | ssize_t n = read_full(fd, &size, sizeof(size)); 59 | if (n <= 0) { 60 | return n; 61 | } 62 | assert(size > 0); 63 | 64 | swaylock_log(LOG_DEBUG, "received pw check request"); 65 | 66 | char *buf = password_buffer_create(size); 67 | if (!buf) { 68 | return -1; 69 | } 70 | 71 | if (read_full(fd, buf, size) <= 0) { 72 | swaylock_log_errno(LOG_ERROR, "failed to read pw"); 73 | return -1; 74 | } 75 | 76 | assert(buf[size - 1] == '\0'); 77 | *buf_ptr = buf; 78 | return size; 79 | } 80 | 81 | bool write_comm_reply(bool success) { 82 | return write_full(comm[1][1], &success, sizeof(success)); 83 | } 84 | 85 | bool spawn_comm_child(void) { 86 | if (pipe(comm[0]) != 0) { 87 | swaylock_log_errno(LOG_ERROR, "failed to create pipe"); 88 | return false; 89 | } 90 | if (pipe(comm[1]) != 0) { 91 | swaylock_log_errno(LOG_ERROR, "failed to create pipe"); 92 | return false; 93 | } 94 | pid_t child = fork(); 95 | if (child < 0) { 96 | swaylock_log_errno(LOG_ERROR, "failed to fork"); 97 | return false; 98 | } else if (child == 0) { 99 | struct sigaction sa = { 100 | .sa_handler = SIG_IGN, 101 | }; 102 | sigaction(SIGUSR1, &sa, NULL); 103 | close(comm[0][1]); 104 | close(comm[1][0]); 105 | run_pw_backend_child(); 106 | } 107 | close(comm[0][0]); 108 | close(comm[1][1]); 109 | return true; 110 | } 111 | 112 | bool write_comm_request(struct swaylock_password *pw) { 113 | bool result = false; 114 | int fd = comm[0][1]; 115 | 116 | size_t size = pw->len + 1; 117 | if (!write_full(fd, &size, sizeof(size))) { 118 | swaylock_log_errno(LOG_ERROR, "Failed to write pw size"); 119 | goto out; 120 | } 121 | 122 | if (!write_full(fd, pw->buffer, size)) { 123 | swaylock_log_errno(LOG_ERROR, "Failed to write pw buffer"); 124 | goto out; 125 | } 126 | 127 | result = true; 128 | 129 | out: 130 | clear_password_buffer(pw); 131 | return result; 132 | } 133 | 134 | bool read_comm_reply(bool *auth_success) { 135 | if (read_full(comm[1][0], auth_success, sizeof(*auth_success)) <= 0) { 136 | swaylock_log(LOG_ERROR, "Failed to read pw result"); 137 | return false; 138 | } 139 | return true; 140 | } 141 | 142 | int get_comm_reply_fd(void) { 143 | return comm[1][0]; 144 | } 145 | -------------------------------------------------------------------------------- /completions/bash/swaylock: -------------------------------------------------------------------------------- 1 | # swaylock(1) completion 2 | 3 | _swaylock() 4 | { 5 | local cur prev short long scaling 6 | _get_comp_words_by_ref -n : cur prev 7 | 8 | short=( 9 | -C 10 | -c 11 | -d 12 | -e 13 | -f 14 | -F 15 | -h 16 | -i 17 | -k 18 | -K 19 | -L 20 | -l 21 | -n 22 | -r 23 | -s 24 | -t 25 | -u 26 | -v 27 | ) 28 | 29 | long=( 30 | --bs-hl-color 31 | --caps-lock-bs-hl-color 32 | --caps-lock-key-hl-color 33 | --color 34 | --config 35 | --daemonize 36 | --debug 37 | --disable-caps-lock-text 38 | --font 39 | --font-size 40 | --help 41 | --hide-keyboard-layout 42 | --ignore-empty-password 43 | --image 44 | --indicator-caps-lock 45 | --indicator-idle-visible 46 | --indicator-radius 47 | --indicator-thickness 48 | --indicator-x-position 49 | --indicator-y-position 50 | --inside-caps-lock-color 51 | --inside-clear-color 52 | --inside-color 53 | --inside-ver-color 54 | --inside-wrong-color 55 | --key-hl-color 56 | --layout-bg-color 57 | --layout-border-color 58 | --layout-text-color 59 | --line-caps-lock-color 60 | --line-clear-color 61 | --line-color 62 | --line-uses-inside 63 | --line-uses-ring 64 | --line-ver-color 65 | --line-wrong-color 66 | --no-unlock-indicator 67 | --ring-caps-lock-color 68 | --ring-clear-color 69 | --ring-color 70 | --ring-ver-color 71 | --ring-wrong-color 72 | --scaling 73 | --separator-color 74 | --show-failed-attempts 75 | --show-keyboard-layout 76 | --text-caps-lock-color 77 | --text-clear-color 78 | --text-color 79 | --text-ver-color 80 | --text-wrong-color 81 | --tiling 82 | --version 83 | ) 84 | 85 | scaling=( 86 | 'stretch' 87 | 'fill' 88 | 'fit' 89 | 'center' 90 | 'tile' 91 | 'solid_color' 92 | ) 93 | 94 | case $prev in 95 | -c|--color) 96 | return 97 | ;; 98 | --scaling) 99 | COMPREPLY=($(compgen -W "${scaling[*]}" -- "$cur")) 100 | return 101 | ;; 102 | -i|--image) 103 | if grep -q : <<< "$cur"; then 104 | output="${cur%%:*}:" 105 | cur="${cur#*:}" 106 | else 107 | output= 108 | fi 109 | COMPREPLY=($(compgen -f -- "$cur")) 110 | return 111 | ;; 112 | esac 113 | 114 | if [[ $cur == --* ]]; then 115 | COMPREPLY=($(compgen -W "${long[*]}" -- "$cur")) 116 | else 117 | COMPREPLY=($(compgen -W "${short[*]}" -- "$cur")) 118 | COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur")) 119 | fi 120 | 121 | } && 122 | complete -F _swaylock swaylock 123 | -------------------------------------------------------------------------------- /completions/fish/swaylock.fish: -------------------------------------------------------------------------------- 1 | # swaylock(1) completion 2 | 3 | complete -c swaylock -l bs-hl-color --description "Sets the color of backspace highlight segments." 4 | complete -c swaylock -l caps-lock-bs-hl-color --description "Sets the color of backspace highlight segments when Caps Lock is active." 5 | complete -c swaylock -l caps-lock-key-hl-color --description "Sets the color of the key press highlight segments when Caps Lock is active." 6 | complete -c swaylock -l color -s c --description "Turn the screen into the given color instead of white." 7 | complete -c swaylock -l config -s C --description "Path to the config file." 8 | complete -c swaylock -l daemonize -s f --description "Detach from the controlling terminal after locking." 9 | complete -c swaylock -l debug -s d --description "Enable debugging output." 10 | complete -c swaylock -l disable-caps-lock-text -s L --description "Disable the Caps Lock text." 11 | complete -c swaylock -l font --description "Sets the font of the text." 12 | complete -c swaylock -l font-size --description "Sets a fixed font size for the indicator text." 13 | complete -c swaylock -l help -s h --description "Show help message and quit." 14 | complete -c swaylock -l hide-keyboard-layout -s K --description "Hide the current xkb layout while typing." 15 | complete -c swaylock -l ignore-empty-password -s e --description "When an empty password is provided, do not validate it." 16 | complete -c swaylock -l image -s i --description "Display the given image, optionally only on the given output." 17 | complete -c swaylock -l indicator-caps-lock -s l --description "Show the current Caps Lock state also on the indicator." 18 | complete -c swaylock -l indicator-idle-visible --description "Sets the indicator to show even if idle." 19 | complete -c swaylock -l indicator-radius --description "Sets the indicator radius." 20 | complete -c swaylock -l indicator-thickness --description "Sets the indicator thickness." 21 | complete -c swaylock -l indicator-x-position --description "Sets the horizontal position of the indicator." 22 | complete -c swaylock -l indicator-y-position --description "Sets the vertical position of the indicator." 23 | complete -c swaylock -l inside-caps-lock-color --description "Sets the color of the inside of the indicator when Caps Lock is active." 24 | complete -c swaylock -l inside-clear-color --description "Sets the color of the inside of the indicator when cleared." 25 | complete -c swaylock -l inside-color --description "Sets the color of the inside of the indicator." 26 | complete -c swaylock -l inside-ver-color --description "Sets the color of the inside of the indicator when verifying." 27 | complete -c swaylock -l inside-wrong-color --description "Sets the color of the inside of the indicator when invalid." 28 | complete -c swaylock -l key-hl-color --description "Sets the color of the key press highlight segments." 29 | complete -c swaylock -l layout-bg-color --description "Sets the background color of the box containing the layout text." 30 | complete -c swaylock -l layout-border-color --description "Sets the color of the border of the box containing the layout text." 31 | complete -c swaylock -l layout-text-color --description "Sets the color of the layout text." 32 | complete -c swaylock -l line-caps-lock-color --description "Sets the color of the line between the inside and ring when Caps Lock is active." 33 | complete -c swaylock -l line-clear-color --description "Sets the color of the line between the inside and ring when cleared." 34 | complete -c swaylock -l line-color --description "Sets the color of the line between the inside and ring." 35 | complete -c swaylock -l line-uses-inside -s n --description "Use the inside color for the line between the inside and ring." 36 | complete -c swaylock -l line-uses-ring -s r --description "Use the ring color for the line between the inside and ring." 37 | complete -c swaylock -l line-ver-color --description "Sets the color of the line between the inside and ring when verifying." 38 | complete -c swaylock -l line-wrong-color --description "Sets the color of the line between the inside and ring when invalid." 39 | complete -c swaylock -l no-unlock-indicator -s u --description "Disable the unlock indicator." 40 | complete -c swaylock -l ring-caps-lock-color --description "Sets the color of the ring of the indicator when Caps Lock is active." 41 | complete -c swaylock -l ring-clear-color --description "Sets the color of the ring of the indicator when cleared." 42 | complete -c swaylock -l ring-color --description "Sets the color of the ring of the indicator." 43 | complete -c swaylock -l ring-ver-color --description "Sets the color of the ring of the indicator when verifying." 44 | complete -c swaylock -l ring-wrong-color --description "Sets the color of the ring of the indicator when invalid." 45 | complete -c swaylock -l scaling -s s --description "Image scaling mode: stretch, fill, fit, center, tile, solid_color." 46 | complete -c swaylock -l separator-color --description "Sets the color of the lines that separate highlight segments." 47 | complete -c swaylock -l show-failed-attempts -s F --description "Show current count of failed authentication attempts." 48 | complete -c swaylock -l show-keyboard-layout -s k --description "Display the current xkb layout while typing." 49 | complete -c swaylock -l text-caps-lock-color --description "Sets the color of the text when Caps Lock is active." 50 | complete -c swaylock -l text-clear-color --description "Sets the color of the text when cleared." 51 | complete -c swaylock -l text-color --description "Sets the color of the text." 52 | complete -c swaylock -l text-ver-color --description "Sets the color of the text when verifying." 53 | complete -c swaylock -l text-wrong-color --description "Sets the color of the text when invalid." 54 | complete -c swaylock -l tiling -s t --description "Same as --scaling=tile." 55 | complete -c swaylock -l version -s v --description "Show the version number and quit." 56 | -------------------------------------------------------------------------------- /completions/meson.build: -------------------------------------------------------------------------------- 1 | bash_comp = dependency('bash-completion', required: false) 2 | fish_comp = dependency('fish', required: false) 3 | 4 | datadir = get_option('datadir') 5 | 6 | if get_option('zsh-completions') 7 | zsh_files = files( 8 | 'zsh/_swaylock', 9 | ) 10 | zsh_install_dir = datadir + '/zsh/site-functions' 11 | 12 | install_data(zsh_files, install_dir: zsh_install_dir) 13 | endif 14 | 15 | if get_option('bash-completions') 16 | bash_files = files( 17 | 'bash/swaylock', 18 | ) 19 | if bash_comp.found() 20 | bash_install_dir = bash_comp.get_variable('completionsdir') 21 | else 22 | bash_install_dir = datadir + '/bash-completion/completions' 23 | endif 24 | 25 | install_data(bash_files, install_dir: bash_install_dir) 26 | endif 27 | 28 | if get_option('fish-completions') 29 | fish_files = files( 30 | 'fish/swaylock.fish', 31 | ) 32 | if fish_comp.found() 33 | fish_install_dir = fish_comp.get_variable('completionsdir') 34 | else 35 | fish_install_dir = datadir + '/fish/vendor_completions.d' 36 | endif 37 | 38 | install_data(fish_files, install_dir: fish_install_dir) 39 | endif 40 | -------------------------------------------------------------------------------- /completions/zsh/_swaylock: -------------------------------------------------------------------------------- 1 | #compdef swaylock 2 | # 3 | # Completion script for swaylock 4 | # 5 | 6 | _arguments -s \ 7 | '(--bs-hl-color)'--bs-hl-color'[Sets the color of backspace highlight segments]:color:' \ 8 | '(--caps-lock-bs-hl-color)'--caps-lock-bs-hl-color'[Sets the color of backspace highlight segments when Caps Lock is active]:color:' \ 9 | '(--caps-lock-key-hl-color)'--caps-lock-key-hl-color'[Sets the color of the key press highlight segments when Caps Lock is active]:color:' \ 10 | '(--color -c)'{--color,-c}'[Turn the screen into the given color instead of white]:color:' \ 11 | '(--config -C)'{--config,-C}'[Path to the config file]:filename:_files' \ 12 | '(--daemonize -f)'{--daemonize,-f}'[Detach from the controlling terminal after locking]' \ 13 | '(--debug -d)'{--debug,-d}'[Enable debugging output]' \ 14 | '(--disable-caps-lock-text -L)'{--disable-caps-lock-text,-L}'[Disable the Caps Lock text]' \ 15 | '(--font)'--font'[Sets the font of the text]:font:' \ 16 | '(--font-size)'--font-size'[Sets a fixed font size for the indicator text]' \ 17 | '(--help -h)'{--help,-h}'[Show help message and quit]' \ 18 | '(--hide-keyboard-layout -K)'{--hide-keyboard-layout,-K}'[Hide the current xkb layout while typing]' \ 19 | '(--ignore-empty-password -e)'{--ignore-empty-password,-e}'[When an empty password is provided, do not validate it]' \ 20 | '(--image -i)'{--image,-i}'[Display the given image, optionally only on the given output]:filename:_files' \ 21 | '(--indicator-caps-lock -l)'{--indicator-caps-lock,-l}'[Show the current Caps Lock state also on the indicator]' \ 22 | '(--indicator-idle-visible)'--indicator-idle-visible'[Sets the indicator to show even if idle]' \ 23 | '(--indicator-radius)'--indicator-radius'[Sets the indicator radius]:radius:' \ 24 | '(--indicator-thickness)'--indicator-thickness'[Sets the indicator thickness]:thickness:' \ 25 | '(--indicator-x-position)'--indicator-x-position'[Sets the horizontal position of the indicator]' \ 26 | '(--indicator-y-position)'--indicator-y-position'[Sets the vertical position of the indicator]' \ 27 | '(--inside-caps-lock-color)'--inside-caps-lock-color'[Sets the color of the inside of the indicator when Caps Lock is active]:color:' \ 28 | '(--inside-clear-color)'--inside-clear-color'[Sets the color of the inside of the indicator when cleared]:color:' \ 29 | '(--inside-color)'--inside-color'[Sets the color of the inside of the indicator]:color:' \ 30 | '(--inside-ver-color)'--inside-ver-color'[Sets the color of the inside of the indicator when verifying]:color:' \ 31 | '(--inside-wrong-color)'--inside-wrong-color'[Sets the color of the inside of the indicator when invalid]:color:' \ 32 | '(--key-hl-color)'--key-hl-color'[Sets the color of the key press highlight segments]:color:' \ 33 | '(--layout-bg-color)'--layout-bg-color'[Sets the background color of the box containing the layout text]:color:' \ 34 | '(--layout-border-color)'--layout-border-color'[Sets the color of the border of the box containing the layout text]:color:' \ 35 | '(--layout-text-color)'--layout-text-color'[Sets the color of the layout text]:color:' \ 36 | '(--line-caps-lock-color)'--line-caps-lock-color'[Sets the color of the line between the inside and ring when Caps Lock is active]:color:' \ 37 | '(--line-clear-color)'--line-clear-color'[Sets the color of the line between the inside and ring when cleared]:color:' \ 38 | '(--line-color)'--line-color'[Sets the color of the line between the inside and ring]:color:' \ 39 | '(--line-uses-inside -n)'{--line-uses-inside,-n}'[Use the inside color for the line between the inside and ring]' \ 40 | '(--line-uses-ring -r)'{--line-uses-ring,-r}'[Use the ring color for the line between the inside and ring]' \ 41 | '(--line-ver-color)'--line-ver-color'[Sets the color of the line between the inside and ring when verifying]:color:' \ 42 | '(--line-wrong-color)'--line-wrong-color'[Sets the color of the line between the inside and ring when invalid]:color:' \ 43 | '(--no-unlock-indicator -u)'{--no-unlock-indicator,-u}'[Disable the unlock indicator]' \ 44 | '(--ring-caps-lock-color)'--ring-caps-lock-color'[Sets the color of the ring of the indicator when Caps Lock is active]:color:' \ 45 | '(--ring-clear-color)'--ring-clear-color'[Sets the color of the ring of the indicator when cleared]:color:' \ 46 | '(--ring-color)'--ring-color'[Sets the color of the ring of the indicator]:color:' \ 47 | '(--ring-ver-color)'--ring-ver-color'[Sets the color of the ring of the indicator when verifying]:color:' \ 48 | '(--ring-wrong-color)'--ring-wrong-color'[Sets the color of the ring of the indicator when invalid]:color:' \ 49 | '(--scaling -s)'{--scaling,-s}'[Image scaling mode: stretch, fill, fit, center, tile, solid_color]:mode:(stretch fill fit center tile solid_color)' \ 50 | '(--separator-color)'--separator-color'[Sets the color of the lines that separate highlight segments]:color:' \ 51 | '(--show-failed-attempts -F)'{--show-failed-attempts,-F}'[Show current count of failed authentication attempts]' \ 52 | '(--show-keyboard-layout -k)'{--show-keyboard-layout,-k}'[Display the current xkb layout while typing]' \ 53 | '(--text-caps-lock-color)'--text-caps-lock-color'[Sets the color of the text when Caps Lock is active]:color:' \ 54 | '(--text-clear-color)'--text-clear-color'[Sets the color of the text when cleared]:color:' \ 55 | '(--text-color)'--text-color'[Sets the color of the text]:color:' \ 56 | '(--text-ver-color)'--text-ver-color'[Sets the color of the text when verifying]:color:' \ 57 | '(--text-wrong-color)'--text-wrong-color'[Sets the color of the text when invalid]:color:' \ 58 | '(--tiling -t)'{--tiling,-t}'[Same as --scaling=tile]' \ 59 | '(--version -v)'{--version,-v}'[Show the version number and quit]' 60 | -------------------------------------------------------------------------------- /include/background-image.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAY_BACKGROUND_IMAGE_H 2 | #define _SWAY_BACKGROUND_IMAGE_H 3 | #include "cairo.h" 4 | 5 | enum background_mode { 6 | BACKGROUND_MODE_STRETCH, 7 | BACKGROUND_MODE_FILL, 8 | BACKGROUND_MODE_FIT, 9 | BACKGROUND_MODE_CENTER, 10 | BACKGROUND_MODE_TILE, 11 | BACKGROUND_MODE_SOLID_COLOR, 12 | BACKGROUND_MODE_INVALID, 13 | }; 14 | 15 | enum background_mode parse_background_mode(const char *mode); 16 | cairo_surface_t *load_background_image(const char *path); 17 | void render_background_image(cairo_t *cairo, cairo_surface_t *image, 18 | enum background_mode mode, int buffer_width, int buffer_height); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /include/cairo.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAY_CAIRO_H 2 | #define _SWAY_CAIRO_H 3 | 4 | #include "config.h" 5 | #include 6 | #include 7 | #include 8 | #if HAVE_GDK_PIXBUF 9 | #include 10 | #endif 11 | 12 | void cairo_set_source_u32(cairo_t *cairo, uint32_t color); 13 | cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel); 14 | 15 | #if HAVE_GDK_PIXBUF 16 | 17 | cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf( 18 | const GdkPixbuf *gdkbuf); 19 | 20 | #endif // HAVE_GDK_PIXBUF 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /include/comm.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAYLOCK_COMM_H 2 | #define _SWAYLOCK_COMM_H 3 | 4 | #include 5 | 6 | struct swaylock_password; 7 | 8 | bool spawn_comm_child(void); 9 | ssize_t read_comm_request(char **buf_ptr); 10 | bool write_comm_reply(bool success); 11 | // Requests the provided password to be checked. The password is always cleared 12 | // when the function returns. 13 | bool write_comm_request(struct swaylock_password *pw); 14 | bool read_comm_reply(bool *auth_success); 15 | // FD to poll for password authentication replies. 16 | int get_comm_reply_fd(void); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /include/log.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAYLOCK_LOG_H 2 | #define _SWAYLOCK_LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | enum log_importance { 9 | LOG_SILENT = 0, 10 | LOG_ERROR = 1, 11 | LOG_INFO = 2, 12 | LOG_DEBUG = 3, 13 | LOG_IMPORTANCE_LAST, 14 | }; 15 | 16 | void swaylock_log_init(enum log_importance verbosity); 17 | 18 | #ifdef __GNUC__ 19 | #define _ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) 20 | #else 21 | #define _ATTRIB_PRINTF(start, end) 22 | #endif 23 | 24 | void _swaylock_log(enum log_importance verbosity, const char *format, ...) 25 | _ATTRIB_PRINTF(2, 3); 26 | 27 | const char *_swaylock_strip_path(const char *filepath); 28 | 29 | #define swaylock_log(verb, fmt, ...) \ 30 | _swaylock_log(verb, "[%s:%d] " fmt, _swaylock_strip_path(__FILE__), \ 31 | __LINE__, ##__VA_ARGS__) 32 | 33 | #define swaylock_log_errno(verb, fmt, ...) \ 34 | swaylock_log(verb, fmt ": %s", ##__VA_ARGS__, strerror(errno)) 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /include/loop.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAY_LOOP_H 2 | #define _SWAY_LOOP_H 3 | #include 4 | 5 | /** 6 | * This is an event loop system designed for sway clients, not sway itself. 7 | * 8 | * The loop consists of file descriptors and timers. Typically the Wayland 9 | * display's file descriptor will be one of the fds in the loop. 10 | */ 11 | 12 | struct loop; 13 | struct loop_timer; 14 | 15 | /** 16 | * Create an event loop. 17 | */ 18 | struct loop *loop_create(void); 19 | 20 | /** 21 | * Destroy the event loop (eg. on program termination). 22 | */ 23 | void loop_destroy(struct loop *loop); 24 | 25 | /** 26 | * Poll the event loop. This will block until one of the fds has data. 27 | */ 28 | void loop_poll(struct loop *loop); 29 | 30 | /** 31 | * Add a file descriptor to the loop. 32 | */ 33 | void loop_add_fd(struct loop *loop, int fd, short mask, 34 | void (*func)(int fd, short mask, void *data), void *data); 35 | 36 | /** 37 | * Add a timer to the loop. 38 | * 39 | * When the timer expires, the timer will be removed from the loop and freed. 40 | */ 41 | struct loop_timer *loop_add_timer(struct loop *loop, int ms, 42 | void (*callback)(void *data), void *data); 43 | 44 | /** 45 | * Remove a file descriptor from the loop. 46 | */ 47 | bool loop_remove_fd(struct loop *loop, int fd); 48 | 49 | /** 50 | * Remove a timer from the loop. 51 | */ 52 | bool loop_remove_timer(struct loop *loop, struct loop_timer *timer); 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /include/meson.build: -------------------------------------------------------------------------------- 1 | configure_file(output: 'config.h', configuration: conf_data) 2 | -------------------------------------------------------------------------------- /include/password-buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAY_PASSWORD_BUFFER_H 2 | #define _SWAY_PASSWORD_BUFFER_H 3 | 4 | #include 5 | 6 | char *password_buffer_create(size_t size); 7 | void password_buffer_destroy(char *buffer, size_t size); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /include/pool-buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAY_BUFFERS_H 2 | #define _SWAY_BUFFERS_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct pool_buffer { 9 | struct wl_buffer *buffer; 10 | cairo_surface_t *surface; 11 | cairo_t *cairo; 12 | uint32_t width, height; 13 | void *data; 14 | size_t size; 15 | bool busy; 16 | }; 17 | 18 | struct pool_buffer *create_buffer(struct wl_shm *shm, struct pool_buffer *buf, 19 | int32_t width, int32_t height, uint32_t format); 20 | struct pool_buffer *get_next_buffer(struct wl_shm *shm, 21 | struct pool_buffer pool[static 2], uint32_t width, uint32_t height); 22 | void destroy_buffer(struct pool_buffer *buffer); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/seat.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAYLOCK_SEAT_H 2 | #define _SWAYLOCK_SEAT_H 3 | #include 4 | #include 5 | #include 6 | 7 | struct loop; 8 | struct loop_timer; 9 | 10 | struct swaylock_xkb { 11 | bool caps_lock; 12 | bool control; 13 | struct xkb_state *state; 14 | struct xkb_context *context; 15 | struct xkb_keymap *keymap; 16 | }; 17 | 18 | struct swaylock_seat { 19 | struct swaylock_state *state; 20 | struct wl_pointer *pointer; 21 | struct wl_keyboard *keyboard; 22 | int32_t repeat_period_ms; 23 | int32_t repeat_delay_ms; 24 | uint32_t repeat_sym; 25 | uint32_t repeat_codepoint; 26 | struct loop_timer *repeat_timer; 27 | }; 28 | 29 | extern const struct wl_seat_listener seat_listener; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /include/swaylock.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAYLOCK_H 2 | #define _SWAYLOCK_H 3 | #include 4 | #include 5 | #include 6 | #include "background-image.h" 7 | #include "cairo.h" 8 | #include "pool-buffer.h" 9 | #include "seat.h" 10 | 11 | // Indicator state: status of authentication attempt 12 | enum auth_state { 13 | AUTH_STATE_IDLE, // nothing happening 14 | AUTH_STATE_VALIDATING, // currently validating password 15 | AUTH_STATE_INVALID, // displaying message: password was wrong 16 | }; 17 | 18 | // Indicator state: status of password buffer / typing letters 19 | enum input_state { 20 | INPUT_STATE_IDLE, // nothing happening; other states decay to this after time 21 | INPUT_STATE_CLEAR, // displaying message: password buffer was cleared 22 | INPUT_STATE_LETTER, // pressed a key that input a letter 23 | INPUT_STATE_BACKSPACE, // pressed backspace and removed a letter 24 | INPUT_STATE_NEUTRAL, // pressed a key (like Ctrl) that did nothing 25 | }; 26 | 27 | struct swaylock_colorset { 28 | uint32_t input; 29 | uint32_t cleared; 30 | uint32_t caps_lock; 31 | uint32_t verifying; 32 | uint32_t wrong; 33 | }; 34 | 35 | struct swaylock_colors { 36 | uint32_t background; 37 | uint32_t bs_highlight; 38 | uint32_t key_highlight; 39 | uint32_t caps_lock_bs_highlight; 40 | uint32_t caps_lock_key_highlight; 41 | uint32_t separator; 42 | uint32_t layout_background; 43 | uint32_t layout_border; 44 | uint32_t layout_text; 45 | struct swaylock_colorset inside; 46 | struct swaylock_colorset line; 47 | struct swaylock_colorset ring; 48 | struct swaylock_colorset text; 49 | }; 50 | 51 | struct swaylock_args { 52 | struct swaylock_colors colors; 53 | enum background_mode mode; 54 | char *font; 55 | uint32_t font_size; 56 | uint32_t radius; 57 | uint32_t thickness; 58 | uint32_t indicator_x_position; 59 | uint32_t indicator_y_position; 60 | bool override_indicator_x_position; 61 | bool override_indicator_y_position; 62 | bool ignore_empty; 63 | bool show_indicator; 64 | bool show_caps_lock_text; 65 | bool show_caps_lock_indicator; 66 | bool show_keyboard_layout; 67 | bool hide_keyboard_layout; 68 | bool show_failed_attempts; 69 | bool daemonize; 70 | int ready_fd; 71 | bool indicator_idle_visible; 72 | }; 73 | 74 | struct swaylock_password { 75 | size_t len; 76 | size_t buffer_len; 77 | char *buffer; 78 | }; 79 | 80 | struct swaylock_state { 81 | struct loop *eventloop; 82 | struct loop_timer *input_idle_timer; // timer to reset input state to IDLE 83 | struct loop_timer *auth_idle_timer; // timer to stop displaying AUTH_STATE_INVALID 84 | struct loop_timer *clear_password_timer; // clears the password buffer 85 | struct wl_display *display; 86 | struct wl_compositor *compositor; 87 | struct wl_subcompositor *subcompositor; 88 | struct wl_shm *shm; 89 | struct wl_list surfaces; 90 | struct wl_list images; 91 | struct swaylock_args args; 92 | struct swaylock_password password; 93 | struct swaylock_xkb xkb; 94 | cairo_surface_t *test_surface; 95 | cairo_t *test_cairo; // used to estimate font/text sizes 96 | enum auth_state auth_state; // state of the authentication attempt 97 | enum input_state input_state; // state of the password buffer and key inputs 98 | uint32_t highlight_start; // position of highlight; 2048 = 1 full turn 99 | int failed_attempts; 100 | bool run_display, locked; 101 | struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1; 102 | struct ext_session_lock_v1 *ext_session_lock_v1; 103 | }; 104 | 105 | struct swaylock_surface { 106 | cairo_surface_t *image; 107 | struct swaylock_state *state; 108 | struct wl_output *output; 109 | uint32_t output_global_name; 110 | struct wl_surface *surface; // surface for background 111 | struct wl_surface *child; // indicator surface made into subsurface 112 | struct wl_subsurface *subsurface; 113 | struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1; 114 | struct pool_buffer indicator_buffers[2]; 115 | bool created; 116 | bool dirty; 117 | uint32_t width, height; 118 | int32_t scale; 119 | enum wl_output_subpixel subpixel; 120 | char *output_name; 121 | struct wl_list link; 122 | struct wl_callback *frame; 123 | // Dimensions of last wl_buffer committed to background surface 124 | int last_buffer_width, last_buffer_height; 125 | }; 126 | 127 | // There is exactly one swaylock_image for each -i argument 128 | struct swaylock_image { 129 | char *path; 130 | char *output_name; 131 | cairo_surface_t *cairo_surface; 132 | struct wl_list link; 133 | }; 134 | 135 | void swaylock_handle_key(struct swaylock_state *state, 136 | xkb_keysym_t keysym, uint32_t codepoint); 137 | 138 | void render(struct swaylock_surface *surface); 139 | void damage_state(struct swaylock_state *state); 140 | void clear_password_buffer(struct swaylock_password *pw); 141 | void schedule_auth_idle(struct swaylock_state *state); 142 | 143 | void initialize_pw_backend(int argc, char **argv); 144 | void run_pw_backend_child(void); 145 | void clear_buffer(char *buf, size_t size); 146 | 147 | #endif 148 | -------------------------------------------------------------------------------- /include/unicode.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAY_UNICODE_H 2 | #define _SWAY_UNICODE_H 3 | #include 4 | #include 5 | 6 | // Technically UTF-8 supports up to 6 byte codepoints, but Unicode itself 7 | // doesn't really bother with more than 4. 8 | #define UTF8_MAX_SIZE 4 9 | 10 | #define UTF8_INVALID 0x80 11 | 12 | /** 13 | * Gets the size in bytes of the last utf8 character in a NULL terminated string 14 | * This function does not validate that the buffer contains correct utf8 data; 15 | * it merely looks for the first byte that correctly denotes the beginning of a 16 | * utf8 character. 17 | */ 18 | int utf8_last_size(const char *str); 19 | 20 | /** 21 | * Grabs the next UTF-8 character and advances the string pointer 22 | */ 23 | uint32_t utf8_decode(const char **str); 24 | 25 | /** 26 | * Encodes a character as UTF-8 and returns the length of that character. 27 | */ 28 | size_t utf8_encode(char *str, uint32_t ch); 29 | 30 | /** 31 | * Returns the size of the next UTF-8 character 32 | */ 33 | int utf8_size(const char *str); 34 | 35 | /** 36 | * Returns the size of a UTF-8 character 37 | */ 38 | size_t utf8_chsize(uint32_t ch); 39 | 40 | #endif 41 | 42 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "log.h" 9 | 10 | static enum log_importance log_importance = LOG_ERROR; 11 | 12 | static const char *verbosity_colors[] = { 13 | [LOG_SILENT] = "", 14 | [LOG_ERROR ] = "\x1B[1;31m", 15 | [LOG_INFO ] = "\x1B[1;34m", 16 | [LOG_DEBUG ] = "\x1B[1;30m", 17 | }; 18 | 19 | void swaylock_log_init(enum log_importance verbosity) { 20 | if (verbosity < LOG_IMPORTANCE_LAST) { 21 | log_importance = verbosity; 22 | } 23 | } 24 | 25 | void _swaylock_log(enum log_importance verbosity, const char *fmt, ...) { 26 | if (verbosity > log_importance) { 27 | return; 28 | } 29 | 30 | va_list args; 31 | va_start(args, fmt); 32 | 33 | // prefix the time to the log message 34 | struct tm result; 35 | time_t t = time(NULL); 36 | struct tm *tm_info = localtime_r(&t, &result); 37 | char buffer[26]; 38 | 39 | // generate time prefix 40 | strftime(buffer, sizeof(buffer), "%F %T - ", tm_info); 41 | fprintf(stderr, "%s", buffer); 42 | 43 | unsigned c = (verbosity < LOG_IMPORTANCE_LAST) 44 | ? verbosity : LOG_IMPORTANCE_LAST - 1; 45 | 46 | if (isatty(STDERR_FILENO)) { 47 | fprintf(stderr, "%s", verbosity_colors[c]); 48 | } 49 | 50 | vfprintf(stderr, fmt, args); 51 | 52 | if (isatty(STDERR_FILENO)) { 53 | fprintf(stderr, "\x1B[0m"); 54 | } 55 | fprintf(stderr, "\n"); 56 | 57 | va_end(args); 58 | } 59 | 60 | const char *_swaylock_strip_path(const char *filepath) { 61 | if (*filepath == '.') { 62 | while (*filepath == '.' || *filepath == '/') { 63 | ++filepath; 64 | } 65 | } 66 | return filepath; 67 | } 68 | -------------------------------------------------------------------------------- /loop.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "log.h" 11 | #include "loop.h" 12 | 13 | struct loop_fd_event { 14 | void (*callback)(int fd, short mask, void *data); 15 | void *data; 16 | struct wl_list link; // struct loop_fd_event::link 17 | }; 18 | 19 | struct loop_timer { 20 | void (*callback)(void *data); 21 | void *data; 22 | struct timespec expiry; 23 | bool removed; 24 | struct wl_list link; // struct loop_timer::link 25 | }; 26 | 27 | struct loop { 28 | struct pollfd *fds; 29 | int fd_length; 30 | int fd_capacity; 31 | 32 | struct wl_list fd_events; // struct loop_fd_event::link 33 | struct wl_list timers; // struct loop_timer::link 34 | }; 35 | 36 | struct loop *loop_create(void) { 37 | struct loop *loop = calloc(1, sizeof(struct loop)); 38 | if (!loop) { 39 | swaylock_log(LOG_ERROR, "Unable to allocate memory for loop"); 40 | return NULL; 41 | } 42 | loop->fd_capacity = 10; 43 | loop->fds = malloc(sizeof(struct pollfd) * loop->fd_capacity); 44 | wl_list_init(&loop->fd_events); 45 | wl_list_init(&loop->timers); 46 | return loop; 47 | } 48 | 49 | void loop_destroy(struct loop *loop) { 50 | struct loop_fd_event *event = NULL, *tmp_event = NULL; 51 | wl_list_for_each_safe(event, tmp_event, &loop->fd_events, link) { 52 | wl_list_remove(&event->link); 53 | free(event); 54 | } 55 | struct loop_timer *timer = NULL, *tmp_timer = NULL; 56 | wl_list_for_each_safe(timer, tmp_timer, &loop->timers, link) { 57 | wl_list_remove(&timer->link); 58 | free(timer); 59 | } 60 | free(loop->fds); 61 | free(loop); 62 | } 63 | 64 | void loop_poll(struct loop *loop) { 65 | // Calculate next timer in ms 66 | int ms = INT_MAX; 67 | if (!wl_list_empty(&loop->timers)) { 68 | struct timespec now; 69 | clock_gettime(CLOCK_MONOTONIC, &now); 70 | struct loop_timer *timer = NULL; 71 | wl_list_for_each(timer, &loop->timers, link) { 72 | int timer_ms = (timer->expiry.tv_sec - now.tv_sec) * 1000; 73 | timer_ms += (timer->expiry.tv_nsec - now.tv_nsec) / 1000000; 74 | if (timer_ms < ms) { 75 | ms = timer_ms; 76 | } 77 | } 78 | } 79 | if (ms < 0) { 80 | ms = 0; 81 | } 82 | 83 | int ret = poll(loop->fds, loop->fd_length, ms); 84 | if (ret < 0 && errno != EINTR) { 85 | swaylock_log_errno(LOG_ERROR, "poll failed"); 86 | exit(1); 87 | } 88 | 89 | // Dispatch fds 90 | size_t fd_index = 0; 91 | struct loop_fd_event *event = NULL; 92 | wl_list_for_each(event, &loop->fd_events, link) { 93 | struct pollfd pfd = loop->fds[fd_index]; 94 | 95 | // Always send these events 96 | unsigned events = pfd.events | POLLHUP | POLLERR; 97 | 98 | if (pfd.revents & events) { 99 | event->callback(pfd.fd, pfd.revents, event->data); 100 | } 101 | 102 | ++fd_index; 103 | } 104 | 105 | // Dispatch timers 106 | if (!wl_list_empty(&loop->timers)) { 107 | struct timespec now; 108 | clock_gettime(CLOCK_MONOTONIC, &now); 109 | struct loop_timer *timer = NULL, *tmp_timer = NULL; 110 | wl_list_for_each_safe(timer, tmp_timer, &loop->timers, link) { 111 | if (timer->removed) { 112 | wl_list_remove(&timer->link); 113 | free(timer); 114 | continue; 115 | } 116 | 117 | bool expired = timer->expiry.tv_sec < now.tv_sec || 118 | (timer->expiry.tv_sec == now.tv_sec && 119 | timer->expiry.tv_nsec < now.tv_nsec); 120 | if (expired) { 121 | timer->callback(timer->data); 122 | wl_list_remove(&timer->link); 123 | free(timer); 124 | } 125 | } 126 | } 127 | } 128 | 129 | void loop_add_fd(struct loop *loop, int fd, short mask, 130 | void (*callback)(int fd, short mask, void *data), void *data) { 131 | struct loop_fd_event *event = calloc(1, sizeof(struct loop_fd_event)); 132 | if (!event) { 133 | swaylock_log(LOG_ERROR, "Unable to allocate memory for event"); 134 | return; 135 | } 136 | event->callback = callback; 137 | event->data = data; 138 | wl_list_insert(loop->fd_events.prev, &event->link); 139 | 140 | struct pollfd pfd = {fd, mask, 0}; 141 | 142 | if (loop->fd_length == loop->fd_capacity) { 143 | loop->fd_capacity += 10; 144 | loop->fds = realloc(loop->fds, 145 | sizeof(struct pollfd) * loop->fd_capacity); 146 | } 147 | 148 | loop->fds[loop->fd_length++] = pfd; 149 | } 150 | 151 | struct loop_timer *loop_add_timer(struct loop *loop, int ms, 152 | void (*callback)(void *data), void *data) { 153 | struct loop_timer *timer = calloc(1, sizeof(struct loop_timer)); 154 | if (!timer) { 155 | swaylock_log(LOG_ERROR, "Unable to allocate memory for timer"); 156 | return NULL; 157 | } 158 | timer->callback = callback; 159 | timer->data = data; 160 | 161 | clock_gettime(CLOCK_MONOTONIC, &timer->expiry); 162 | timer->expiry.tv_sec += ms / 1000; 163 | 164 | long int nsec = (ms % 1000) * 1000000; 165 | if (timer->expiry.tv_nsec + nsec >= 1000000000) { 166 | timer->expiry.tv_sec++; 167 | nsec -= 1000000000; 168 | } 169 | timer->expiry.tv_nsec += nsec; 170 | 171 | wl_list_insert(&loop->timers, &timer->link); 172 | 173 | return timer; 174 | } 175 | 176 | bool loop_remove_fd(struct loop *loop, int fd) { 177 | size_t fd_index = 0; 178 | struct loop_fd_event *event = NULL, *tmp_event = NULL; 179 | wl_list_for_each_safe(event, tmp_event, &loop->fd_events, link) { 180 | if (loop->fds[fd_index].fd == fd) { 181 | wl_list_remove(&event->link); 182 | free(event); 183 | 184 | loop->fd_length--; 185 | memmove(&loop->fds[fd_index], &loop->fds[fd_index + 1], 186 | sizeof(struct pollfd) * (loop->fd_length - fd_index)); 187 | return true; 188 | } 189 | ++fd_index; 190 | } 191 | return false; 192 | } 193 | 194 | bool loop_remove_timer(struct loop *loop, struct loop_timer *remove) { 195 | struct loop_timer *timer = NULL, *tmp_timer = NULL; 196 | wl_list_for_each_safe(timer, tmp_timer, &loop->timers, link) { 197 | if (timer == remove) { 198 | timer->removed = true; 199 | return true; 200 | } 201 | } 202 | return false; 203 | } 204 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "background-image.h" 19 | #include "cairo.h" 20 | #include "comm.h" 21 | #include "log.h" 22 | #include "loop.h" 23 | #include "password-buffer.h" 24 | #include "pool-buffer.h" 25 | #include "seat.h" 26 | #include "swaylock.h" 27 | #include "ext-session-lock-v1-client-protocol.h" 28 | 29 | static uint32_t parse_color(const char *color) { 30 | if (color[0] == '#') { 31 | ++color; 32 | } 33 | 34 | int len = strlen(color); 35 | if (len != 6 && len != 8) { 36 | swaylock_log(LOG_DEBUG, "Invalid color %s, defaulting to 0xFFFFFFFF", 37 | color); 38 | return 0xFFFFFFFF; 39 | } 40 | uint32_t res = (uint32_t)strtoul(color, NULL, 16); 41 | if (strlen(color) == 6) { 42 | res = (res << 8) | 0xFF; 43 | } 44 | return res; 45 | } 46 | 47 | int lenient_strcmp(char *a, char *b) { 48 | if (a == b) { 49 | return 0; 50 | } else if (!a) { 51 | return -1; 52 | } else if (!b) { 53 | return 1; 54 | } else { 55 | return strcmp(a, b); 56 | } 57 | } 58 | 59 | static void daemonize(void) { 60 | int fds[2]; 61 | if (pipe(fds) != 0) { 62 | swaylock_log(LOG_ERROR, "Failed to pipe"); 63 | exit(1); 64 | } 65 | if (fork() == 0) { 66 | setsid(); 67 | close(fds[0]); 68 | int devnull = open("/dev/null", O_RDWR); 69 | dup2(STDOUT_FILENO, devnull); 70 | dup2(STDERR_FILENO, devnull); 71 | close(devnull); 72 | uint8_t success = 0; 73 | if (chdir("/") != 0) { 74 | write(fds[1], &success, 1); 75 | exit(1); 76 | } 77 | success = 1; 78 | if (write(fds[1], &success, 1) != 1) { 79 | exit(1); 80 | } 81 | close(fds[1]); 82 | } else { 83 | close(fds[1]); 84 | uint8_t success; 85 | if (read(fds[0], &success, 1) != 1 || !success) { 86 | swaylock_log(LOG_ERROR, "Failed to daemonize"); 87 | exit(1); 88 | } 89 | close(fds[0]); 90 | exit(0); 91 | } 92 | } 93 | 94 | static void destroy_surface(struct swaylock_surface *surface) { 95 | wl_list_remove(&surface->link); 96 | if (surface->ext_session_lock_surface_v1 != NULL) { 97 | ext_session_lock_surface_v1_destroy(surface->ext_session_lock_surface_v1); 98 | } 99 | if (surface->subsurface) { 100 | wl_subsurface_destroy(surface->subsurface); 101 | } 102 | if (surface->child) { 103 | wl_surface_destroy(surface->child); 104 | } 105 | if (surface->surface != NULL) { 106 | wl_surface_destroy(surface->surface); 107 | } 108 | destroy_buffer(&surface->indicator_buffers[0]); 109 | destroy_buffer(&surface->indicator_buffers[1]); 110 | wl_output_release(surface->output); 111 | free(surface); 112 | } 113 | 114 | static const struct ext_session_lock_surface_v1_listener ext_session_lock_surface_v1_listener; 115 | 116 | static cairo_surface_t *select_image(struct swaylock_state *state, 117 | struct swaylock_surface *surface); 118 | 119 | static bool surface_is_opaque(struct swaylock_surface *surface) { 120 | if (surface->image) { 121 | return cairo_surface_get_content(surface->image) == CAIRO_CONTENT_COLOR; 122 | } 123 | return (surface->state->args.colors.background & 0xff) == 0xff; 124 | } 125 | 126 | static void create_surface(struct swaylock_surface *surface) { 127 | struct swaylock_state *state = surface->state; 128 | 129 | surface->image = select_image(state, surface); 130 | 131 | surface->surface = wl_compositor_create_surface(state->compositor); 132 | assert(surface->surface); 133 | 134 | surface->child = wl_compositor_create_surface(state->compositor); 135 | assert(surface->child); 136 | surface->subsurface = wl_subcompositor_get_subsurface(state->subcompositor, surface->child, surface->surface); 137 | assert(surface->subsurface); 138 | wl_subsurface_set_sync(surface->subsurface); 139 | 140 | surface->ext_session_lock_surface_v1 = ext_session_lock_v1_get_lock_surface( 141 | state->ext_session_lock_v1, surface->surface, surface->output); 142 | ext_session_lock_surface_v1_add_listener(surface->ext_session_lock_surface_v1, 143 | &ext_session_lock_surface_v1_listener, surface); 144 | 145 | if (surface_is_opaque(surface) && 146 | surface->state->args.mode != BACKGROUND_MODE_CENTER && 147 | surface->state->args.mode != BACKGROUND_MODE_FIT) { 148 | struct wl_region *region = 149 | wl_compositor_create_region(surface->state->compositor); 150 | wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); 151 | wl_surface_set_opaque_region(surface->surface, region); 152 | wl_region_destroy(region); 153 | } 154 | 155 | surface->created = true; 156 | } 157 | 158 | static void ext_session_lock_surface_v1_handle_configure(void *data, 159 | struct ext_session_lock_surface_v1 *lock_surface, uint32_t serial, 160 | uint32_t width, uint32_t height) { 161 | struct swaylock_surface *surface = data; 162 | surface->width = width; 163 | surface->height = height; 164 | ext_session_lock_surface_v1_ack_configure(lock_surface, serial); 165 | surface->dirty = true; 166 | render(surface); 167 | } 168 | 169 | static const struct ext_session_lock_surface_v1_listener ext_session_lock_surface_v1_listener = { 170 | .configure = ext_session_lock_surface_v1_handle_configure, 171 | }; 172 | 173 | void damage_state(struct swaylock_state *state) { 174 | struct swaylock_surface *surface; 175 | wl_list_for_each(surface, &state->surfaces, link) { 176 | surface->dirty = true; 177 | render(surface); 178 | } 179 | } 180 | 181 | static void handle_wl_output_geometry(void *data, struct wl_output *wl_output, 182 | int32_t x, int32_t y, int32_t width_mm, int32_t height_mm, 183 | int32_t subpixel, const char *make, const char *model, 184 | int32_t transform) { 185 | struct swaylock_surface *surface = data; 186 | surface->subpixel = subpixel; 187 | if (surface->state->run_display) { 188 | surface->dirty = true; 189 | render(surface); 190 | } 191 | } 192 | 193 | static void handle_wl_output_mode(void *data, struct wl_output *output, 194 | uint32_t flags, int32_t width, int32_t height, int32_t refresh) { 195 | // Who cares 196 | } 197 | 198 | static void handle_wl_output_done(void *data, struct wl_output *output) { 199 | struct swaylock_surface *surface = data; 200 | if (!surface->created && surface->state->run_display) { 201 | create_surface(surface); 202 | } 203 | } 204 | 205 | static void handle_wl_output_scale(void *data, struct wl_output *output, 206 | int32_t factor) { 207 | struct swaylock_surface *surface = data; 208 | surface->scale = factor; 209 | if (surface->state->run_display) { 210 | surface->dirty = true; 211 | render(surface); 212 | } 213 | } 214 | 215 | static void handle_wl_output_name(void *data, struct wl_output *output, 216 | const char *name) { 217 | struct swaylock_surface *surface = data; 218 | surface->output_name = strdup(name); 219 | } 220 | 221 | static void handle_wl_output_description(void *data, struct wl_output *output, 222 | const char *description) { 223 | // Who cares 224 | } 225 | 226 | struct wl_output_listener _wl_output_listener = { 227 | .geometry = handle_wl_output_geometry, 228 | .mode = handle_wl_output_mode, 229 | .done = handle_wl_output_done, 230 | .scale = handle_wl_output_scale, 231 | .name = handle_wl_output_name, 232 | .description = handle_wl_output_description, 233 | }; 234 | 235 | static void ext_session_lock_v1_handle_locked(void *data, struct ext_session_lock_v1 *lock) { 236 | struct swaylock_state *state = data; 237 | state->locked = true; 238 | } 239 | 240 | static void ext_session_lock_v1_handle_finished(void *data, struct ext_session_lock_v1 *lock) { 241 | swaylock_log(LOG_ERROR, "Failed to lock session -- " 242 | "is another lockscreen running?"); 243 | exit(2); 244 | } 245 | 246 | static const struct ext_session_lock_v1_listener ext_session_lock_v1_listener = { 247 | .locked = ext_session_lock_v1_handle_locked, 248 | .finished = ext_session_lock_v1_handle_finished, 249 | }; 250 | 251 | static void handle_global(void *data, struct wl_registry *registry, 252 | uint32_t name, const char *interface, uint32_t version) { 253 | struct swaylock_state *state = data; 254 | if (strcmp(interface, wl_compositor_interface.name) == 0) { 255 | state->compositor = wl_registry_bind(registry, name, 256 | &wl_compositor_interface, 4); 257 | } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { 258 | state->subcompositor = wl_registry_bind(registry, name, 259 | &wl_subcompositor_interface, 1); 260 | } else if (strcmp(interface, wl_shm_interface.name) == 0) { 261 | state->shm = wl_registry_bind(registry, name, 262 | &wl_shm_interface, 1); 263 | } else if (strcmp(interface, wl_seat_interface.name) == 0) { 264 | struct wl_seat *seat = wl_registry_bind( 265 | registry, name, &wl_seat_interface, 4); 266 | struct swaylock_seat *swaylock_seat = 267 | calloc(1, sizeof(struct swaylock_seat)); 268 | swaylock_seat->state = state; 269 | wl_seat_add_listener(seat, &seat_listener, swaylock_seat); 270 | } else if (strcmp(interface, wl_output_interface.name) == 0) { 271 | struct swaylock_surface *surface = 272 | calloc(1, sizeof(struct swaylock_surface)); 273 | surface->state = state; 274 | surface->output = wl_registry_bind(registry, name, 275 | &wl_output_interface, 4); 276 | surface->output_global_name = name; 277 | wl_output_add_listener(surface->output, &_wl_output_listener, surface); 278 | wl_list_insert(&state->surfaces, &surface->link); 279 | } else if (strcmp(interface, ext_session_lock_manager_v1_interface.name) == 0) { 280 | state->ext_session_lock_manager_v1 = wl_registry_bind(registry, name, 281 | &ext_session_lock_manager_v1_interface, 1); 282 | } 283 | } 284 | 285 | static void handle_global_remove(void *data, struct wl_registry *registry, 286 | uint32_t name) { 287 | struct swaylock_state *state = data; 288 | struct swaylock_surface *surface; 289 | wl_list_for_each(surface, &state->surfaces, link) { 290 | if (surface->output_global_name == name) { 291 | destroy_surface(surface); 292 | break; 293 | } 294 | } 295 | } 296 | 297 | static const struct wl_registry_listener registry_listener = { 298 | .global = handle_global, 299 | .global_remove = handle_global_remove, 300 | }; 301 | 302 | static int sigusr_fds[2] = {-1, -1}; 303 | 304 | void do_sigusr(int sig) { 305 | (void)write(sigusr_fds[1], "1", 1); 306 | } 307 | 308 | static cairo_surface_t *select_image(struct swaylock_state *state, 309 | struct swaylock_surface *surface) { 310 | struct swaylock_image *image; 311 | cairo_surface_t *default_image = NULL; 312 | wl_list_for_each(image, &state->images, link) { 313 | if (lenient_strcmp(image->output_name, surface->output_name) == 0) { 314 | return image->cairo_surface; 315 | } else if (!image->output_name) { 316 | default_image = image->cairo_surface; 317 | } 318 | } 319 | return default_image; 320 | } 321 | 322 | static char *join_args(char **argv, int argc) { 323 | assert(argc > 0); 324 | int len = 0, i; 325 | for (i = 0; i < argc; ++i) { 326 | len += strlen(argv[i]) + 1; 327 | } 328 | char *res = malloc(len); 329 | len = 0; 330 | for (i = 0; i < argc; ++i) { 331 | strcpy(res + len, argv[i]); 332 | len += strlen(argv[i]); 333 | res[len++] = ' '; 334 | } 335 | res[len - 1] = '\0'; 336 | return res; 337 | } 338 | 339 | static void load_image(char *arg, struct swaylock_state *state) { 340 | // [[]:] 341 | struct swaylock_image *image = calloc(1, sizeof(struct swaylock_image)); 342 | char *separator = strchr(arg, ':'); 343 | if (separator) { 344 | *separator = '\0'; 345 | image->output_name = separator == arg ? NULL : strdup(arg); 346 | image->path = strdup(separator + 1); 347 | } else { 348 | image->output_name = NULL; 349 | image->path = strdup(arg); 350 | } 351 | 352 | struct swaylock_image *iter_image, *temp; 353 | wl_list_for_each_safe(iter_image, temp, &state->images, link) { 354 | if (lenient_strcmp(iter_image->output_name, image->output_name) == 0) { 355 | if (image->output_name) { 356 | swaylock_log(LOG_DEBUG, 357 | "Replacing image defined for output %s with %s", 358 | image->output_name, image->path); 359 | } else { 360 | swaylock_log(LOG_DEBUG, "Replacing default image with %s", 361 | image->path); 362 | } 363 | wl_list_remove(&iter_image->link); 364 | free(iter_image->cairo_surface); 365 | free(iter_image->output_name); 366 | free(iter_image->path); 367 | free(iter_image); 368 | break; 369 | } 370 | } 371 | 372 | // The shell will not expand ~ to the value of $HOME when an output name is 373 | // given. Also, any image paths given in the config file need to have shell 374 | // expansions performed 375 | wordexp_t p; 376 | while (strstr(image->path, " ")) { 377 | image->path = realloc(image->path, strlen(image->path) + 2); 378 | char *ptr = strstr(image->path, " ") + 1; 379 | memmove(ptr + 1, ptr, strlen(ptr) + 1); 380 | *ptr = '\\'; 381 | } 382 | if (wordexp(image->path, &p, 0) == 0) { 383 | free(image->path); 384 | image->path = join_args(p.we_wordv, p.we_wordc); 385 | wordfree(&p); 386 | } 387 | 388 | // Load the actual image 389 | image->cairo_surface = load_background_image(image->path); 390 | if (!image->cairo_surface) { 391 | free(image); 392 | return; 393 | } 394 | wl_list_insert(&state->images, &image->link); 395 | swaylock_log(LOG_DEBUG, "Loaded image %s for output %s", image->path, 396 | image->output_name ? image->output_name : "*"); 397 | } 398 | 399 | static void set_default_colors(struct swaylock_colors *colors) { 400 | colors->background = 0xFFFFFFFF; 401 | colors->bs_highlight = 0xDB3300FF; 402 | colors->key_highlight = 0x33DB00FF; 403 | colors->caps_lock_bs_highlight = 0xDB3300FF; 404 | colors->caps_lock_key_highlight = 0x33DB00FF; 405 | colors->separator = 0x000000FF; 406 | colors->layout_background = 0x000000C0; 407 | colors->layout_border = 0x00000000; 408 | colors->layout_text = 0xFFFFFFFF; 409 | colors->inside = (struct swaylock_colorset){ 410 | .input = 0x000000C0, 411 | .cleared = 0xE5A445C0, 412 | .caps_lock = 0x000000C0, 413 | .verifying = 0x0072FFC0, 414 | .wrong = 0xFA0000C0, 415 | }; 416 | colors->line = (struct swaylock_colorset){ 417 | .input = 0x000000FF, 418 | .cleared = 0x000000FF, 419 | .caps_lock = 0x000000FF, 420 | .verifying = 0x000000FF, 421 | .wrong = 0x000000FF, 422 | }; 423 | colors->ring = (struct swaylock_colorset){ 424 | .input = 0x337D00FF, 425 | .cleared = 0xE5A445FF, 426 | .caps_lock = 0xE5A445FF, 427 | .verifying = 0x3300FFFF, 428 | .wrong = 0x7D3300FF, 429 | }; 430 | colors->text = (struct swaylock_colorset){ 431 | .input = 0xE5A445FF, 432 | .cleared = 0x000000FF, 433 | .caps_lock = 0xE5A445FF, 434 | .verifying = 0x000000FF, 435 | .wrong = 0x000000FF, 436 | }; 437 | } 438 | 439 | enum line_mode { 440 | LM_LINE, 441 | LM_INSIDE, 442 | LM_RING, 443 | }; 444 | 445 | static int parse_options(int argc, char **argv, struct swaylock_state *state, 446 | enum line_mode *line_mode, char **config_path) { 447 | enum long_option_codes { 448 | LO_BS_HL_COLOR = 256, 449 | LO_CAPS_LOCK_BS_HL_COLOR, 450 | LO_CAPS_LOCK_KEY_HL_COLOR, 451 | LO_FONT, 452 | LO_FONT_SIZE, 453 | LO_IND_IDLE_VISIBLE, 454 | LO_IND_RADIUS, 455 | LO_IND_X_POSITION, 456 | LO_IND_Y_POSITION, 457 | LO_IND_THICKNESS, 458 | LO_INSIDE_COLOR, 459 | LO_INSIDE_CLEAR_COLOR, 460 | LO_INSIDE_CAPS_LOCK_COLOR, 461 | LO_INSIDE_VER_COLOR, 462 | LO_INSIDE_WRONG_COLOR, 463 | LO_KEY_HL_COLOR, 464 | LO_LAYOUT_TXT_COLOR, 465 | LO_LAYOUT_BG_COLOR, 466 | LO_LAYOUT_BORDER_COLOR, 467 | LO_LINE_COLOR, 468 | LO_LINE_CLEAR_COLOR, 469 | LO_LINE_CAPS_LOCK_COLOR, 470 | LO_LINE_VER_COLOR, 471 | LO_LINE_WRONG_COLOR, 472 | LO_RING_COLOR, 473 | LO_RING_CLEAR_COLOR, 474 | LO_RING_CAPS_LOCK_COLOR, 475 | LO_RING_VER_COLOR, 476 | LO_RING_WRONG_COLOR, 477 | LO_SEP_COLOR, 478 | LO_TEXT_COLOR, 479 | LO_TEXT_CLEAR_COLOR, 480 | LO_TEXT_CAPS_LOCK_COLOR, 481 | LO_TEXT_VER_COLOR, 482 | LO_TEXT_WRONG_COLOR, 483 | }; 484 | 485 | static struct option long_options[] = { 486 | {"config", required_argument, NULL, 'C'}, 487 | {"color", required_argument, NULL, 'c'}, 488 | {"debug", no_argument, NULL, 'd'}, 489 | {"ignore-empty-password", no_argument, NULL, 'e'}, 490 | {"daemonize", no_argument, NULL, 'f'}, 491 | {"ready-fd", required_argument, NULL, 'R'}, 492 | {"help", no_argument, NULL, 'h'}, 493 | {"image", required_argument, NULL, 'i'}, 494 | {"disable-caps-lock-text", no_argument, NULL, 'L'}, 495 | {"indicator-caps-lock", no_argument, NULL, 'l'}, 496 | {"line-uses-inside", no_argument, NULL, 'n'}, 497 | {"line-uses-ring", no_argument, NULL, 'r'}, 498 | {"scaling", required_argument, NULL, 's'}, 499 | {"tiling", no_argument, NULL, 't'}, 500 | {"no-unlock-indicator", no_argument, NULL, 'u'}, 501 | {"show-keyboard-layout", no_argument, NULL, 'k'}, 502 | {"hide-keyboard-layout", no_argument, NULL, 'K'}, 503 | {"show-failed-attempts", no_argument, NULL, 'F'}, 504 | {"version", no_argument, NULL, 'v'}, 505 | {"bs-hl-color", required_argument, NULL, LO_BS_HL_COLOR}, 506 | {"caps-lock-bs-hl-color", required_argument, NULL, LO_CAPS_LOCK_BS_HL_COLOR}, 507 | {"caps-lock-key-hl-color", required_argument, NULL, LO_CAPS_LOCK_KEY_HL_COLOR}, 508 | {"font", required_argument, NULL, LO_FONT}, 509 | {"font-size", required_argument, NULL, LO_FONT_SIZE}, 510 | {"indicator-idle-visible", no_argument, NULL, LO_IND_IDLE_VISIBLE}, 511 | {"indicator-radius", required_argument, NULL, LO_IND_RADIUS}, 512 | {"indicator-thickness", required_argument, NULL, LO_IND_THICKNESS}, 513 | {"indicator-x-position", required_argument, NULL, LO_IND_X_POSITION}, 514 | {"indicator-y-position", required_argument, NULL, LO_IND_Y_POSITION}, 515 | {"inside-color", required_argument, NULL, LO_INSIDE_COLOR}, 516 | {"inside-clear-color", required_argument, NULL, LO_INSIDE_CLEAR_COLOR}, 517 | {"inside-caps-lock-color", required_argument, NULL, LO_INSIDE_CAPS_LOCK_COLOR}, 518 | {"inside-ver-color", required_argument, NULL, LO_INSIDE_VER_COLOR}, 519 | {"inside-wrong-color", required_argument, NULL, LO_INSIDE_WRONG_COLOR}, 520 | {"key-hl-color", required_argument, NULL, LO_KEY_HL_COLOR}, 521 | {"layout-bg-color", required_argument, NULL, LO_LAYOUT_BG_COLOR}, 522 | {"layout-border-color", required_argument, NULL, LO_LAYOUT_BORDER_COLOR}, 523 | {"layout-text-color", required_argument, NULL, LO_LAYOUT_TXT_COLOR}, 524 | {"line-color", required_argument, NULL, LO_LINE_COLOR}, 525 | {"line-clear-color", required_argument, NULL, LO_LINE_CLEAR_COLOR}, 526 | {"line-caps-lock-color", required_argument, NULL, LO_LINE_CAPS_LOCK_COLOR}, 527 | {"line-ver-color", required_argument, NULL, LO_LINE_VER_COLOR}, 528 | {"line-wrong-color", required_argument, NULL, LO_LINE_WRONG_COLOR}, 529 | {"ring-color", required_argument, NULL, LO_RING_COLOR}, 530 | {"ring-clear-color", required_argument, NULL, LO_RING_CLEAR_COLOR}, 531 | {"ring-caps-lock-color", required_argument, NULL, LO_RING_CAPS_LOCK_COLOR}, 532 | {"ring-ver-color", required_argument, NULL, LO_RING_VER_COLOR}, 533 | {"ring-wrong-color", required_argument, NULL, LO_RING_WRONG_COLOR}, 534 | {"separator-color", required_argument, NULL, LO_SEP_COLOR}, 535 | {"text-color", required_argument, NULL, LO_TEXT_COLOR}, 536 | {"text-clear-color", required_argument, NULL, LO_TEXT_CLEAR_COLOR}, 537 | {"text-caps-lock-color", required_argument, NULL, LO_TEXT_CAPS_LOCK_COLOR}, 538 | {"text-ver-color", required_argument, NULL, LO_TEXT_VER_COLOR}, 539 | {"text-wrong-color", required_argument, NULL, LO_TEXT_WRONG_COLOR}, 540 | {0, 0, 0, 0} 541 | }; 542 | 543 | const char usage[] = 544 | "Usage: swaylock [options...]\n" 545 | "\n" 546 | " -C, --config " 547 | "Path to the config file.\n" 548 | " -c, --color " 549 | "Turn the screen into the given color instead of white.\n" 550 | " -d, --debug " 551 | "Enable debugging output.\n" 552 | " -e, --ignore-empty-password " 553 | "When an empty password is provided, do not validate it.\n" 554 | " -F, --show-failed-attempts " 555 | "Show current count of failed authentication attempts.\n" 556 | " -f, --daemonize " 557 | "Detach from the controlling terminal after locking.\n" 558 | " -R, --ready-fd " 559 | "File descriptor to send readiness notifications to.\n" 560 | " -h, --help " 561 | "Show help message and quit.\n" 562 | " -i, --image [[]:] " 563 | "Display the given image, optionally only on the given output.\n" 564 | " -k, --show-keyboard-layout " 565 | "Display the current xkb layout while typing.\n" 566 | " -K, --hide-keyboard-layout " 567 | "Hide the current xkb layout while typing.\n" 568 | " -L, --disable-caps-lock-text " 569 | "Disable the Caps Lock text.\n" 570 | " -l, --indicator-caps-lock " 571 | "Show the current Caps Lock state also on the indicator.\n" 572 | " -s, --scaling " 573 | "Image scaling mode: stretch, fill, fit, center, tile, solid_color.\n" 574 | " -t, --tiling " 575 | "Same as --scaling=tile.\n" 576 | " -u, --no-unlock-indicator " 577 | "Disable the unlock indicator.\n" 578 | " -v, --version " 579 | "Show the version number and quit.\n" 580 | " --bs-hl-color " 581 | "Sets the color of backspace highlight segments.\n" 582 | " --caps-lock-bs-hl-color " 583 | "Sets the color of backspace highlight segments when Caps Lock " 584 | "is active.\n" 585 | " --caps-lock-key-hl-color " 586 | "Sets the color of the key press highlight segments when " 587 | "Caps Lock is active.\n" 588 | " --font " 589 | "Sets the font of the text.\n" 590 | " --font-size " 591 | "Sets a fixed font size for the indicator text.\n" 592 | " --indicator-idle-visible " 593 | "Sets the indicator to show even if idle.\n" 594 | " --indicator-radius " 595 | "Sets the indicator radius.\n" 596 | " --indicator-thickness " 597 | "Sets the indicator thickness.\n" 598 | " --indicator-x-position " 599 | "Sets the horizontal position of the indicator.\n" 600 | " --indicator-y-position " 601 | "Sets the vertical position of the indicator.\n" 602 | " --inside-color " 603 | "Sets the color of the inside of the indicator.\n" 604 | " --inside-clear-color " 605 | "Sets the color of the inside of the indicator when cleared.\n" 606 | " --inside-caps-lock-color " 607 | "Sets the color of the inside of the indicator when Caps Lock " 608 | "is active.\n" 609 | " --inside-ver-color " 610 | "Sets the color of the inside of the indicator when verifying.\n" 611 | " --inside-wrong-color " 612 | "Sets the color of the inside of the indicator when invalid.\n" 613 | " --key-hl-color " 614 | "Sets the color of the key press highlight segments.\n" 615 | " --layout-bg-color " 616 | "Sets the background color of the box containing the layout text.\n" 617 | " --layout-border-color " 618 | "Sets the color of the border of the box containing the layout text.\n" 619 | " --layout-text-color " 620 | "Sets the color of the layout text.\n" 621 | " --line-color " 622 | "Sets the color of the line between the inside and ring.\n" 623 | " --line-clear-color " 624 | "Sets the color of the line between the inside and ring when " 625 | "cleared.\n" 626 | " --line-caps-lock-color " 627 | "Sets the color of the line between the inside and ring when " 628 | "Caps Lock is active.\n" 629 | " --line-ver-color " 630 | "Sets the color of the line between the inside and ring when " 631 | "verifying.\n" 632 | " --line-wrong-color " 633 | "Sets the color of the line between the inside and ring when " 634 | "invalid.\n" 635 | " -n, --line-uses-inside " 636 | "Use the inside color for the line between the inside and ring.\n" 637 | " -r, --line-uses-ring " 638 | "Use the ring color for the line between the inside and ring.\n" 639 | " --ring-color " 640 | "Sets the color of the ring of the indicator.\n" 641 | " --ring-clear-color " 642 | "Sets the color of the ring of the indicator when cleared.\n" 643 | " --ring-caps-lock-color " 644 | "Sets the color of the ring of the indicator when Caps Lock " 645 | "is active.\n" 646 | " --ring-ver-color " 647 | "Sets the color of the ring of the indicator when verifying.\n" 648 | " --ring-wrong-color " 649 | "Sets the color of the ring of the indicator when invalid.\n" 650 | " --separator-color " 651 | "Sets the color of the lines that separate highlight segments.\n" 652 | " --text-color " 653 | "Sets the color of the text.\n" 654 | " --text-clear-color " 655 | "Sets the color of the text when cleared.\n" 656 | " --text-caps-lock-color " 657 | "Sets the color of the text when Caps Lock is active.\n" 658 | " --text-ver-color " 659 | "Sets the color of the text when verifying.\n" 660 | " --text-wrong-color " 661 | "Sets the color of the text when invalid.\n" 662 | "\n" 663 | "All options are of the form .\n"; 664 | 665 | int c; 666 | optind = 1; 667 | while (1) { 668 | int opt_idx = 0; 669 | c = getopt_long(argc, argv, "c:deFfhi:kKLlnrs:tuvC:R:", long_options, 670 | &opt_idx); 671 | if (c == -1) { 672 | break; 673 | } 674 | switch (c) { 675 | case 'C': 676 | if (config_path) { 677 | *config_path = strdup(optarg); 678 | } 679 | break; 680 | case 'c': 681 | if (state) { 682 | state->args.colors.background = parse_color(optarg); 683 | } 684 | break; 685 | case 'd': 686 | swaylock_log_init(LOG_DEBUG); 687 | break; 688 | case 'e': 689 | if (state) { 690 | state->args.ignore_empty = true; 691 | } 692 | break; 693 | case 'F': 694 | if (state) { 695 | state->args.show_failed_attempts = true; 696 | } 697 | break; 698 | case 'f': 699 | if (state) { 700 | state->args.daemonize = true; 701 | } 702 | break; 703 | case 'R': 704 | if (state) { 705 | state->args.ready_fd = strtol(optarg, NULL, 10); 706 | } 707 | break; 708 | case 'i': 709 | if (state) { 710 | load_image(optarg, state); 711 | } 712 | break; 713 | case 'k': 714 | if (state) { 715 | state->args.show_keyboard_layout = true; 716 | } 717 | break; 718 | case 'K': 719 | if (state) { 720 | state->args.hide_keyboard_layout = true; 721 | } 722 | break; 723 | case 'L': 724 | if (state) { 725 | state->args.show_caps_lock_text = false; 726 | } 727 | break; 728 | case 'l': 729 | if (state) { 730 | state->args.show_caps_lock_indicator = true; 731 | } 732 | break; 733 | case 'n': 734 | if (line_mode) { 735 | *line_mode = LM_INSIDE; 736 | } 737 | break; 738 | case 'r': 739 | if (line_mode) { 740 | *line_mode = LM_RING; 741 | } 742 | break; 743 | case 's': 744 | if (state) { 745 | state->args.mode = parse_background_mode(optarg); 746 | if (state->args.mode == BACKGROUND_MODE_INVALID) { 747 | return 1; 748 | } 749 | } 750 | break; 751 | case 't': 752 | if (state) { 753 | state->args.mode = BACKGROUND_MODE_TILE; 754 | } 755 | break; 756 | case 'u': 757 | if (state) { 758 | state->args.show_indicator = false; 759 | } 760 | break; 761 | case 'v': 762 | fprintf(stdout, "swaylock version " SWAYLOCK_VERSION "\n"); 763 | exit(EXIT_SUCCESS); 764 | break; 765 | case LO_BS_HL_COLOR: 766 | if (state) { 767 | state->args.colors.bs_highlight = parse_color(optarg); 768 | } 769 | break; 770 | case LO_CAPS_LOCK_BS_HL_COLOR: 771 | if (state) { 772 | state->args.colors.caps_lock_bs_highlight = parse_color(optarg); 773 | } 774 | break; 775 | case LO_CAPS_LOCK_KEY_HL_COLOR: 776 | if (state) { 777 | state->args.colors.caps_lock_key_highlight = parse_color(optarg); 778 | } 779 | break; 780 | case LO_FONT: 781 | if (state) { 782 | free(state->args.font); 783 | state->args.font = strdup(optarg); 784 | } 785 | break; 786 | case LO_FONT_SIZE: 787 | if (state) { 788 | state->args.font_size = atoi(optarg); 789 | } 790 | break; 791 | case LO_IND_IDLE_VISIBLE: 792 | if (state) { 793 | state->args.indicator_idle_visible = true; 794 | } 795 | break; 796 | case LO_IND_RADIUS: 797 | if (state) { 798 | state->args.radius = strtol(optarg, NULL, 0); 799 | } 800 | break; 801 | case LO_IND_THICKNESS: 802 | if (state) { 803 | state->args.thickness = strtol(optarg, NULL, 0); 804 | } 805 | break; 806 | case LO_IND_X_POSITION: 807 | if (state) { 808 | state->args.override_indicator_x_position = true; 809 | state->args.indicator_x_position = atoi(optarg); 810 | } 811 | break; 812 | case LO_IND_Y_POSITION: 813 | if (state) { 814 | state->args.override_indicator_y_position = true; 815 | state->args.indicator_y_position = atoi(optarg); 816 | } 817 | break; 818 | case LO_INSIDE_COLOR: 819 | if (state) { 820 | state->args.colors.inside.input = parse_color(optarg); 821 | } 822 | break; 823 | case LO_INSIDE_CLEAR_COLOR: 824 | if (state) { 825 | state->args.colors.inside.cleared = parse_color(optarg); 826 | } 827 | break; 828 | case LO_INSIDE_CAPS_LOCK_COLOR: 829 | if (state) { 830 | state->args.colors.inside.caps_lock = parse_color(optarg); 831 | } 832 | break; 833 | case LO_INSIDE_VER_COLOR: 834 | if (state) { 835 | state->args.colors.inside.verifying = parse_color(optarg); 836 | } 837 | break; 838 | case LO_INSIDE_WRONG_COLOR: 839 | if (state) { 840 | state->args.colors.inside.wrong = parse_color(optarg); 841 | } 842 | break; 843 | case LO_KEY_HL_COLOR: 844 | if (state) { 845 | state->args.colors.key_highlight = parse_color(optarg); 846 | } 847 | break; 848 | case LO_LAYOUT_BG_COLOR: 849 | if (state) { 850 | state->args.colors.layout_background = parse_color(optarg); 851 | } 852 | break; 853 | case LO_LAYOUT_BORDER_COLOR: 854 | if (state) { 855 | state->args.colors.layout_border = parse_color(optarg); 856 | } 857 | break; 858 | case LO_LAYOUT_TXT_COLOR: 859 | if (state) { 860 | state->args.colors.layout_text = parse_color(optarg); 861 | } 862 | break; 863 | case LO_LINE_COLOR: 864 | if (state) { 865 | state->args.colors.line.input = parse_color(optarg); 866 | } 867 | break; 868 | case LO_LINE_CLEAR_COLOR: 869 | if (state) { 870 | state->args.colors.line.cleared = parse_color(optarg); 871 | } 872 | break; 873 | case LO_LINE_CAPS_LOCK_COLOR: 874 | if (state) { 875 | state->args.colors.line.caps_lock = parse_color(optarg); 876 | } 877 | break; 878 | case LO_LINE_VER_COLOR: 879 | if (state) { 880 | state->args.colors.line.verifying = parse_color(optarg); 881 | } 882 | break; 883 | case LO_LINE_WRONG_COLOR: 884 | if (state) { 885 | state->args.colors.line.wrong = parse_color(optarg); 886 | } 887 | break; 888 | case LO_RING_COLOR: 889 | if (state) { 890 | state->args.colors.ring.input = parse_color(optarg); 891 | } 892 | break; 893 | case LO_RING_CLEAR_COLOR: 894 | if (state) { 895 | state->args.colors.ring.cleared = parse_color(optarg); 896 | } 897 | break; 898 | case LO_RING_CAPS_LOCK_COLOR: 899 | if (state) { 900 | state->args.colors.ring.caps_lock = parse_color(optarg); 901 | } 902 | break; 903 | case LO_RING_VER_COLOR: 904 | if (state) { 905 | state->args.colors.ring.verifying = parse_color(optarg); 906 | } 907 | break; 908 | case LO_RING_WRONG_COLOR: 909 | if (state) { 910 | state->args.colors.ring.wrong = parse_color(optarg); 911 | } 912 | break; 913 | case LO_SEP_COLOR: 914 | if (state) { 915 | state->args.colors.separator = parse_color(optarg); 916 | } 917 | break; 918 | case LO_TEXT_COLOR: 919 | if (state) { 920 | state->args.colors.text.input = parse_color(optarg); 921 | } 922 | break; 923 | case LO_TEXT_CLEAR_COLOR: 924 | if (state) { 925 | state->args.colors.text.cleared = parse_color(optarg); 926 | } 927 | break; 928 | case LO_TEXT_CAPS_LOCK_COLOR: 929 | if (state) { 930 | state->args.colors.text.caps_lock = parse_color(optarg); 931 | } 932 | break; 933 | case LO_TEXT_VER_COLOR: 934 | if (state) { 935 | state->args.colors.text.verifying = parse_color(optarg); 936 | } 937 | break; 938 | case LO_TEXT_WRONG_COLOR: 939 | if (state) { 940 | state->args.colors.text.wrong = parse_color(optarg); 941 | } 942 | break; 943 | default: 944 | fprintf(stderr, "%s", usage); 945 | return 1; 946 | } 947 | } 948 | 949 | return 0; 950 | } 951 | 952 | static bool file_exists(const char *path) { 953 | return path && access(path, R_OK) != -1; 954 | } 955 | 956 | static char *get_config_path(void) { 957 | static const char *config_paths[] = { 958 | "$HOME/.swaylock/config", 959 | "$XDG_CONFIG_HOME/swaylock/config", 960 | SYSCONFDIR "/swaylock/config", 961 | }; 962 | 963 | char *config_home = getenv("XDG_CONFIG_HOME"); 964 | if (!config_home || config_home[0] == '\0') { 965 | config_paths[1] = "$HOME/.config/swaylock/config"; 966 | } 967 | 968 | wordexp_t p; 969 | char *path; 970 | for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) { 971 | if (wordexp(config_paths[i], &p, 0) == 0) { 972 | path = strdup(p.we_wordv[0]); 973 | wordfree(&p); 974 | if (file_exists(path)) { 975 | return path; 976 | } 977 | free(path); 978 | } 979 | } 980 | 981 | return NULL; 982 | } 983 | 984 | static int load_config(char *path, struct swaylock_state *state, 985 | enum line_mode *line_mode) { 986 | FILE *config = fopen(path, "r"); 987 | if (!config) { 988 | swaylock_log(LOG_ERROR, "Failed to read config. Running without it."); 989 | return 0; 990 | } 991 | char *line = NULL; 992 | size_t line_size = 0; 993 | ssize_t nread; 994 | int line_number = 0; 995 | int result = 0; 996 | while ((nread = getline(&line, &line_size, config)) != -1) { 997 | line_number++; 998 | 999 | if (line[nread - 1] == '\n') { 1000 | line[--nread] = '\0'; 1001 | } 1002 | 1003 | if (!*line || line[0] == '#') { 1004 | continue; 1005 | } 1006 | 1007 | swaylock_log(LOG_DEBUG, "Config Line #%d: %s", line_number, line); 1008 | char *flag = malloc(nread + 3); 1009 | if (flag == NULL) { 1010 | free(line); 1011 | fclose(config); 1012 | swaylock_log(LOG_ERROR, "Failed to allocate memory"); 1013 | return 0; 1014 | } 1015 | sprintf(flag, "--%s", line); 1016 | char *argv[] = {"swaylock", flag}; 1017 | result = parse_options(2, argv, state, line_mode, NULL); 1018 | free(flag); 1019 | if (result != 0) { 1020 | break; 1021 | } 1022 | } 1023 | free(line); 1024 | fclose(config); 1025 | return 0; 1026 | } 1027 | 1028 | static struct swaylock_state state; 1029 | 1030 | static void display_in(int fd, short mask, void *data) { 1031 | if (wl_display_dispatch(state.display) == -1) { 1032 | state.run_display = false; 1033 | } 1034 | } 1035 | 1036 | static void comm_in(int fd, short mask, void *data) { 1037 | if (mask & POLLIN) { 1038 | bool auth_success = false; 1039 | if (!read_comm_reply(&auth_success)) { 1040 | exit(EXIT_FAILURE); 1041 | } 1042 | if (auth_success) { 1043 | // Authentication succeeded 1044 | state.run_display = false; 1045 | } else { 1046 | state.auth_state = AUTH_STATE_INVALID; 1047 | schedule_auth_idle(&state); 1048 | ++state.failed_attempts; 1049 | damage_state(&state); 1050 | } 1051 | } else if (mask & (POLLHUP | POLLERR)) { 1052 | swaylock_log(LOG_ERROR, "Password checking subprocess crashed; exiting."); 1053 | exit(EXIT_FAILURE); 1054 | } 1055 | } 1056 | 1057 | static void term_in(int fd, short mask, void *data) { 1058 | state.run_display = false; 1059 | } 1060 | 1061 | // Check for --debug 'early' we also apply the correct loglevel 1062 | // to the forked child, without having to first proces all of the 1063 | // configuration (including from file) before forking and (in the 1064 | // case of the shadow backend) dropping privileges 1065 | void log_init(int argc, char **argv) { 1066 | static struct option long_options[] = { 1067 | {"debug", no_argument, NULL, 'd'}, 1068 | {0, 0, 0, 0} 1069 | }; 1070 | int c; 1071 | optind = 1; 1072 | while (1) { 1073 | int opt_idx = 0; 1074 | c = getopt_long(argc, argv, "-:d", long_options, &opt_idx); 1075 | if (c == -1) { 1076 | break; 1077 | } 1078 | switch (c) { 1079 | case 'd': 1080 | swaylock_log_init(LOG_DEBUG); 1081 | return; 1082 | } 1083 | } 1084 | swaylock_log_init(LOG_ERROR); 1085 | } 1086 | 1087 | int main(int argc, char **argv) { 1088 | log_init(argc, argv); 1089 | initialize_pw_backend(argc, argv); 1090 | srand(time(NULL)); 1091 | 1092 | enum line_mode line_mode = LM_LINE; 1093 | state.failed_attempts = 0; 1094 | state.args = (struct swaylock_args){ 1095 | .mode = BACKGROUND_MODE_FILL, 1096 | .font = strdup("sans-serif"), 1097 | .font_size = 0, 1098 | .radius = 50, 1099 | .thickness = 10, 1100 | .indicator_x_position = 0, 1101 | .indicator_y_position = 0, 1102 | .override_indicator_x_position = false, 1103 | .override_indicator_y_position = false, 1104 | .ignore_empty = false, 1105 | .show_indicator = true, 1106 | .show_caps_lock_indicator = false, 1107 | .show_caps_lock_text = true, 1108 | .show_keyboard_layout = false, 1109 | .hide_keyboard_layout = false, 1110 | .show_failed_attempts = false, 1111 | .indicator_idle_visible = false, 1112 | .ready_fd = -1, 1113 | }; 1114 | wl_list_init(&state.images); 1115 | set_default_colors(&state.args.colors); 1116 | 1117 | char *config_path = NULL; 1118 | int result = parse_options(argc, argv, NULL, NULL, &config_path); 1119 | if (result != 0) { 1120 | free(config_path); 1121 | return result; 1122 | } 1123 | if (!config_path) { 1124 | config_path = get_config_path(); 1125 | } 1126 | 1127 | if (config_path) { 1128 | swaylock_log(LOG_DEBUG, "Found config at %s", config_path); 1129 | int config_status = load_config(config_path, &state, &line_mode); 1130 | free(config_path); 1131 | if (config_status != 0) { 1132 | free(state.args.font); 1133 | return config_status; 1134 | } 1135 | } 1136 | 1137 | if (argc > 1) { 1138 | swaylock_log(LOG_DEBUG, "Parsing CLI Args"); 1139 | int result = parse_options(argc, argv, &state, &line_mode, NULL); 1140 | if (result != 0) { 1141 | free(state.args.font); 1142 | return result; 1143 | } 1144 | } 1145 | 1146 | if (line_mode == LM_INSIDE) { 1147 | state.args.colors.line = state.args.colors.inside; 1148 | } else if (line_mode == LM_RING) { 1149 | state.args.colors.line = state.args.colors.ring; 1150 | } 1151 | 1152 | state.password.len = 0; 1153 | state.password.buffer_len = 1024; 1154 | state.password.buffer = password_buffer_create(state.password.buffer_len); 1155 | if (!state.password.buffer) { 1156 | return EXIT_FAILURE; 1157 | } 1158 | state.password.buffer[0] = 0; 1159 | 1160 | if (pipe(sigusr_fds) != 0) { 1161 | swaylock_log(LOG_ERROR, "Failed to pipe"); 1162 | return EXIT_FAILURE; 1163 | } 1164 | if (fcntl(sigusr_fds[1], F_SETFL, O_NONBLOCK) == -1) { 1165 | swaylock_log(LOG_ERROR, "Failed to make pipe end nonblocking"); 1166 | return EXIT_FAILURE; 1167 | } 1168 | 1169 | wl_list_init(&state.surfaces); 1170 | state.xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 1171 | state.display = wl_display_connect(NULL); 1172 | if (!state.display) { 1173 | free(state.args.font); 1174 | swaylock_log(LOG_ERROR, "Unable to connect to the compositor. " 1175 | "If your compositor is running, check or set the " 1176 | "WAYLAND_DISPLAY environment variable."); 1177 | return EXIT_FAILURE; 1178 | } 1179 | state.eventloop = loop_create(); 1180 | 1181 | struct wl_registry *registry = wl_display_get_registry(state.display); 1182 | wl_registry_add_listener(registry, ®istry_listener, &state); 1183 | if (wl_display_roundtrip(state.display) == -1) { 1184 | swaylock_log(LOG_ERROR, "wl_display_roundtrip() failed"); 1185 | return EXIT_FAILURE; 1186 | } 1187 | 1188 | if (!state.compositor) { 1189 | swaylock_log(LOG_ERROR, "Missing wl_compositor"); 1190 | return 1; 1191 | } 1192 | 1193 | if (!state.subcompositor) { 1194 | swaylock_log(LOG_ERROR, "Missing wl_subcompositor"); 1195 | return 1; 1196 | } 1197 | 1198 | if (!state.shm) { 1199 | swaylock_log(LOG_ERROR, "Missing wl_shm"); 1200 | return 1; 1201 | } 1202 | 1203 | if (!state.ext_session_lock_manager_v1) { 1204 | swaylock_log(LOG_ERROR, "Missing ext-session-lock-v1"); 1205 | return 1; 1206 | } 1207 | 1208 | state.ext_session_lock_v1 = ext_session_lock_manager_v1_lock(state.ext_session_lock_manager_v1); 1209 | ext_session_lock_v1_add_listener(state.ext_session_lock_v1, 1210 | &ext_session_lock_v1_listener, &state); 1211 | 1212 | if (wl_display_roundtrip(state.display) == -1) { 1213 | free(state.args.font); 1214 | return 1; 1215 | } 1216 | 1217 | state.test_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 1, 1); 1218 | state.test_cairo = cairo_create(state.test_surface); 1219 | 1220 | struct swaylock_surface *surface; 1221 | wl_list_for_each(surface, &state.surfaces, link) { 1222 | create_surface(surface); 1223 | } 1224 | 1225 | while (!state.locked) { 1226 | if (wl_display_dispatch(state.display) < 0) { 1227 | swaylock_log(LOG_ERROR, "wl_display_dispatch() failed"); 1228 | return 2; 1229 | } 1230 | } 1231 | 1232 | if (state.args.ready_fd >= 0) { 1233 | if (write(state.args.ready_fd, "\n", 1) != 1) { 1234 | swaylock_log(LOG_ERROR, "Failed to send readiness notification"); 1235 | return 2; 1236 | } 1237 | close(state.args.ready_fd); 1238 | state.args.ready_fd = -1; 1239 | } 1240 | if (state.args.daemonize) { 1241 | daemonize(); 1242 | } 1243 | 1244 | loop_add_fd(state.eventloop, wl_display_get_fd(state.display), POLLIN, 1245 | display_in, NULL); 1246 | 1247 | loop_add_fd(state.eventloop, get_comm_reply_fd(), POLLIN, comm_in, NULL); 1248 | 1249 | loop_add_fd(state.eventloop, sigusr_fds[0], POLLIN, term_in, NULL); 1250 | 1251 | struct sigaction sa; 1252 | sa.sa_handler = do_sigusr; 1253 | sigemptyset(&sa.sa_mask); 1254 | sa.sa_flags = SA_RESTART; 1255 | sigaction(SIGUSR1, &sa, NULL); 1256 | 1257 | state.run_display = true; 1258 | while (state.run_display) { 1259 | errno = 0; 1260 | if (wl_display_flush(state.display) == -1 && errno != EAGAIN) { 1261 | break; 1262 | } 1263 | loop_poll(state.eventloop); 1264 | } 1265 | 1266 | ext_session_lock_v1_unlock_and_destroy(state.ext_session_lock_v1); 1267 | wl_display_roundtrip(state.display); 1268 | 1269 | free(state.args.font); 1270 | cairo_destroy(state.test_cairo); 1271 | cairo_surface_destroy(state.test_surface); 1272 | return 0; 1273 | } 1274 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'swaylock', 3 | 'c', 4 | version: '1.8.2', 5 | license: 'MIT', 6 | meson_version: '>=0.59.0', 7 | default_options: [ 8 | 'c_std=c11', 9 | 'warning_level=2', 10 | 'werror=true', 11 | ], 12 | ) 13 | 14 | cc = meson.get_compiler('c') 15 | 16 | add_project_arguments('-D_POSIX_C_SOURCE=200809L', language: 'c') 17 | 18 | add_project_arguments(cc.get_supported_arguments([ 19 | '-Wno-unused-parameter', 20 | '-Wno-unused-result', 21 | '-Wundef', 22 | '-Wvla', 23 | ]), language: 'c') 24 | 25 | is_freebsd = host_machine.system().startswith('freebsd') 26 | if is_freebsd 27 | add_project_arguments('-D_C11_SOURCE', language: 'c') 28 | endif 29 | 30 | wayland_client = dependency('wayland-client', version: '>=1.20.0') 31 | wayland_protos = dependency('wayland-protocols', version: '>=1.25', fallback: 'wayland-protocols') 32 | wayland_scanner = dependency('wayland-scanner', version: '>=1.15.0', native: true) 33 | xkbcommon = dependency('xkbcommon') 34 | cairo = dependency('cairo') 35 | gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf')) 36 | libpam = cc.find_library('pam', required: get_option('pam')) 37 | crypt = cc.find_library('crypt', required: not libpam.found()) 38 | math = cc.find_library('m') 39 | rt = cc.find_library('rt') 40 | 41 | git = find_program('git', required: false) 42 | scdoc = find_program('scdoc', required: get_option('man-pages')) 43 | wayland_scanner_prog = find_program(wayland_scanner.get_variable('wayland_scanner'), native: true) 44 | 45 | version = meson.project_version() 46 | if git.found() 47 | git_commit_hash = run_command([git, 'describe', '--always', '--tags'], check: false) 48 | git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false) 49 | if git_commit_hash.returncode() == 0 and git_branch.returncode() == 0 50 | version = '@0@ (" __DATE__ ", branch \'@1@\')'.format(git_commit_hash.stdout().strip(), git_branch.stdout().strip()) 51 | endif 52 | endif 53 | 54 | wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') 55 | 56 | wayland_scanner_code = generator( 57 | wayland_scanner_prog, 58 | output: '@BASENAME@-protocol.c', 59 | arguments: ['private-code', '@INPUT@', '@OUTPUT@'], 60 | ) 61 | 62 | wayland_scanner_client = generator( 63 | wayland_scanner_prog, 64 | output: '@BASENAME@-client-protocol.h', 65 | arguments: ['client-header', '@INPUT@', '@OUTPUT@'], 66 | ) 67 | 68 | client_protocols = [ 69 | wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', 70 | ] 71 | 72 | protos_src = [] 73 | foreach xml : client_protocols 74 | protos_src += wayland_scanner_code.process(xml) 75 | protos_src += wayland_scanner_client.process(xml) 76 | endforeach 77 | 78 | conf_data = configuration_data() 79 | conf_data.set_quoted('SYSCONFDIR', get_option('prefix') / get_option('sysconfdir')) 80 | conf_data.set_quoted('SWAYLOCK_VERSION', version) 81 | conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) 82 | 83 | subdir('include') 84 | 85 | dependencies = [ 86 | cairo, 87 | gdk_pixbuf, 88 | math, 89 | rt, 90 | xkbcommon, 91 | wayland_client, 92 | ] 93 | 94 | sources = [ 95 | 'background-image.c', 96 | 'cairo.c', 97 | 'comm.c', 98 | 'log.c', 99 | 'loop.c', 100 | 'main.c', 101 | 'password.c', 102 | 'password-buffer.c', 103 | 'pool-buffer.c', 104 | 'render.c', 105 | 'seat.c', 106 | 'unicode.c', 107 | ] 108 | 109 | if libpam.found() 110 | sources += ['pam.c'] 111 | dependencies += [libpam] 112 | else 113 | warning('The swaylock binary must be setuid when compiled without libpam') 114 | warning('You must do this manually post-install: chmod a+s /path/to/swaylock') 115 | sources += ['shadow.c'] 116 | dependencies += [crypt] 117 | endif 118 | 119 | swaylock_inc = include_directories('include') 120 | 121 | executable('swaylock', 122 | sources + protos_src, 123 | include_directories: [swaylock_inc], 124 | dependencies: dependencies, 125 | install: true 126 | ) 127 | 128 | if libpam.found() 129 | install_data( 130 | 'pam/swaylock', 131 | install_dir: get_option('sysconfdir') / 'pam.d' 132 | ) 133 | endif 134 | 135 | if scdoc.found() 136 | mandir = get_option('mandir') 137 | man_files = [ 138 | 'swaylock.1.scd', 139 | ] 140 | foreach filename : man_files 141 | topic = filename.split('.')[-3].split('/')[-1] 142 | section = filename.split('.')[-2] 143 | output = '@0@.@1@'.format(topic, section) 144 | 145 | custom_target( 146 | output, 147 | input: filename, 148 | output: output, 149 | command: scdoc, 150 | feed: true, 151 | capture: true, 152 | install: true, 153 | install_dir: '@0@/man@1@'.format(mandir, section) 154 | ) 155 | endforeach 156 | endif 157 | 158 | subdir('completions') 159 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('pam', type: 'feature', value: 'auto', description: 'Use PAM instead of shadow') 2 | option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats') 3 | option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') 4 | option('zsh-completions', type: 'boolean', value: true, description: 'Install zsh shell completions') 5 | option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions') 6 | option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions') 7 | -------------------------------------------------------------------------------- /pam.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "comm.h" 9 | #include "log.h" 10 | #include "password-buffer.h" 11 | #include "swaylock.h" 12 | 13 | static char *pw_buf = NULL; 14 | 15 | void initialize_pw_backend(int argc, char **argv) { 16 | if (getuid() != geteuid() || getgid() != getegid()) { 17 | swaylock_log(LOG_ERROR, 18 | "swaylock is setuid, but was compiled with the PAM" 19 | " backend. Run 'chmod a-s %s' to fix. Aborting.", argv[0]); 20 | exit(EXIT_FAILURE); 21 | } 22 | if (!spawn_comm_child()) { 23 | exit(EXIT_FAILURE); 24 | } 25 | } 26 | 27 | static int handle_conversation(int num_msg, const struct pam_message **msg, 28 | struct pam_response **resp, void *data) { 29 | /* PAM expects an array of responses, one for each message */ 30 | struct pam_response *pam_reply = 31 | calloc(num_msg, sizeof(struct pam_response)); 32 | if (pam_reply == NULL) { 33 | swaylock_log(LOG_ERROR, "Allocation failed"); 34 | return PAM_ABORT; 35 | } 36 | *resp = pam_reply; 37 | for (int i = 0; i < num_msg; ++i) { 38 | switch (msg[i]->msg_style) { 39 | case PAM_PROMPT_ECHO_OFF: 40 | case PAM_PROMPT_ECHO_ON: 41 | pam_reply[i].resp = strdup(pw_buf); // PAM clears and frees this 42 | if (pam_reply[i].resp == NULL) { 43 | swaylock_log(LOG_ERROR, "Allocation failed"); 44 | return PAM_ABORT; 45 | } 46 | break; 47 | case PAM_ERROR_MSG: 48 | case PAM_TEXT_INFO: 49 | break; 50 | } 51 | } 52 | return PAM_SUCCESS; 53 | } 54 | 55 | static const char *get_pam_auth_error(int pam_status) { 56 | switch (pam_status) { 57 | case PAM_AUTH_ERR: 58 | return "invalid credentials"; 59 | case PAM_CRED_INSUFFICIENT: 60 | return "swaylock cannot authenticate users; check /etc/pam.d/swaylock " 61 | "has been installed properly"; 62 | case PAM_AUTHINFO_UNAVAIL: 63 | return "authentication information unavailable"; 64 | case PAM_MAXTRIES: 65 | return "maximum number of authentication tries exceeded"; 66 | default:; 67 | static char msg[64]; 68 | snprintf(msg, sizeof(msg), "unknown error (%d)", pam_status); 69 | return msg; 70 | } 71 | } 72 | 73 | void run_pw_backend_child(void) { 74 | struct passwd *passwd = getpwuid(getuid()); 75 | if (!passwd) { 76 | swaylock_log_errno(LOG_ERROR, "getpwuid failed"); 77 | exit(EXIT_FAILURE); 78 | } 79 | 80 | char *username = passwd->pw_name; 81 | 82 | const struct pam_conv conv = { 83 | .conv = handle_conversation, 84 | .appdata_ptr = NULL, 85 | }; 86 | pam_handle_t *auth_handle = NULL; 87 | if (pam_start("swaylock", username, &conv, &auth_handle) != PAM_SUCCESS) { 88 | swaylock_log(LOG_ERROR, "pam_start failed"); 89 | exit(EXIT_FAILURE); 90 | } 91 | 92 | /* This code does not run as root */ 93 | swaylock_log(LOG_DEBUG, "Prepared to authorize user %s", username); 94 | 95 | int pam_status = PAM_SUCCESS; 96 | while (1) { 97 | ssize_t size = read_comm_request(&pw_buf); 98 | if (size < 0) { 99 | exit(EXIT_FAILURE); 100 | } else if (size == 0) { 101 | break; 102 | } 103 | 104 | int pam_status = pam_authenticate(auth_handle, 0); 105 | password_buffer_destroy(pw_buf, size); 106 | pw_buf = NULL; 107 | 108 | bool success = pam_status == PAM_SUCCESS; 109 | if (!success) { 110 | swaylock_log(LOG_ERROR, "pam_authenticate failed: %s", 111 | get_pam_auth_error(pam_status)); 112 | } 113 | 114 | if (!write_comm_reply(success)) { 115 | exit(EXIT_FAILURE); 116 | } 117 | 118 | if (success) { 119 | /* Unsuccessful requests may be queued after a successful one; 120 | * do not process them. */ 121 | break; 122 | } 123 | } 124 | 125 | pam_setcred(auth_handle, PAM_REFRESH_CRED); 126 | 127 | if (pam_end(auth_handle, pam_status) != PAM_SUCCESS) { 128 | swaylock_log(LOG_ERROR, "pam_end failed"); 129 | exit(EXIT_FAILURE); 130 | } 131 | 132 | exit((pam_status == PAM_SUCCESS) ? EXIT_SUCCESS : EXIT_FAILURE); 133 | } 134 | -------------------------------------------------------------------------------- /pam/swaylock: -------------------------------------------------------------------------------- 1 | # 2 | # PAM configuration file for the swaylock screen locker. By default, it includes 3 | # the 'login' configuration file (see /etc/pam.d/login) 4 | # 5 | 6 | auth include login 7 | -------------------------------------------------------------------------------- /password-buffer.c: -------------------------------------------------------------------------------- 1 | #include "password-buffer.h" 2 | #include "log.h" 3 | #include "swaylock.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static bool mlock_supported = true; 11 | static long int page_size = 0; 12 | 13 | static long int get_page_size() { 14 | if (!page_size) { 15 | page_size = sysconf(_SC_PAGESIZE); 16 | } 17 | return page_size; 18 | } 19 | 20 | // password_buffer_lock expects addr to be page alligned 21 | static bool password_buffer_lock(char *addr, size_t size) { 22 | int retries = 5; 23 | while (mlock(addr, size) != 0 && retries > 0) { 24 | switch (errno) { 25 | case EAGAIN: 26 | retries--; 27 | if (retries == 0) { 28 | swaylock_log(LOG_ERROR, "mlock() supported but failed too often."); 29 | return false; 30 | } 31 | break; 32 | case EPERM: 33 | swaylock_log_errno(LOG_ERROR, "Unable to mlock() password memory: Unsupported!"); 34 | mlock_supported = false; 35 | return true; 36 | default: 37 | swaylock_log_errno(LOG_ERROR, "Unable to mlock() password memory."); 38 | return false; 39 | } 40 | } 41 | 42 | return true; 43 | } 44 | 45 | // password_buffer_unlock expects addr to be page alligned 46 | static bool password_buffer_unlock(char *addr, size_t size) { 47 | if (mlock_supported) { 48 | if (munlock(addr, size) != 0) { 49 | swaylock_log_errno(LOG_ERROR, "Unable to munlock() password memory."); 50 | return false; 51 | } 52 | } 53 | 54 | return true; 55 | } 56 | 57 | char *password_buffer_create(size_t size) { 58 | void *buffer; 59 | int result = posix_memalign(&buffer, get_page_size(), size); 60 | if (result) { 61 | //posix_memalign doesn't set errno according to the man page 62 | errno = result; 63 | swaylock_log_errno(LOG_ERROR, "failed to alloc password buffer"); 64 | return NULL; 65 | } 66 | 67 | if (!password_buffer_lock(buffer, size)) { 68 | free(buffer); 69 | return NULL; 70 | } 71 | 72 | return buffer; 73 | } 74 | 75 | void password_buffer_destroy(char *buffer, size_t size) { 76 | clear_buffer(buffer, size); 77 | password_buffer_unlock(buffer, size); 78 | free(buffer); 79 | } 80 | -------------------------------------------------------------------------------- /password.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "comm.h" 9 | #include "log.h" 10 | #include "loop.h" 11 | #include "seat.h" 12 | #include "swaylock.h" 13 | #include "unicode.h" 14 | 15 | void clear_buffer(char *buf, size_t size) { 16 | // Use volatile keyword so so compiler can't optimize this out. 17 | volatile char *buffer = buf; 18 | volatile char zero = '\0'; 19 | for (size_t i = 0; i < size; ++i) { 20 | buffer[i] = zero; 21 | } 22 | } 23 | 24 | void clear_password_buffer(struct swaylock_password *pw) { 25 | clear_buffer(pw->buffer, pw->buffer_len); 26 | pw->len = 0; 27 | } 28 | 29 | static bool backspace(struct swaylock_password *pw) { 30 | if (pw->len != 0) { 31 | pw->len -= utf8_last_size(pw->buffer); 32 | pw->buffer[pw->len] = 0; 33 | return true; 34 | } 35 | return false; 36 | } 37 | 38 | static void append_ch(struct swaylock_password *pw, uint32_t codepoint) { 39 | size_t utf8_size = utf8_chsize(codepoint); 40 | if (pw->len + utf8_size + 1 >= pw->buffer_len) { 41 | // TODO: Display error 42 | return; 43 | } 44 | utf8_encode(&pw->buffer[pw->len], codepoint); 45 | pw->buffer[pw->len + utf8_size] = 0; 46 | pw->len += utf8_size; 47 | } 48 | 49 | static void set_input_idle(void *data) { 50 | struct swaylock_state *state = data; 51 | state->input_idle_timer = NULL; 52 | state->input_state = INPUT_STATE_IDLE; 53 | damage_state(state); 54 | } 55 | 56 | static void set_auth_idle(void *data) { 57 | struct swaylock_state *state = data; 58 | state->auth_idle_timer = NULL; 59 | state->auth_state = AUTH_STATE_IDLE; 60 | damage_state(state); 61 | } 62 | 63 | static void schedule_input_idle(struct swaylock_state *state) { 64 | if (state->input_idle_timer) { 65 | loop_remove_timer(state->eventloop, state->input_idle_timer); 66 | } 67 | state->input_idle_timer = loop_add_timer( 68 | state->eventloop, 1500, set_input_idle, state); 69 | } 70 | 71 | static void cancel_input_idle(struct swaylock_state *state) { 72 | if (state->input_idle_timer) { 73 | loop_remove_timer(state->eventloop, state->input_idle_timer); 74 | state->input_idle_timer = NULL; 75 | } 76 | } 77 | 78 | void schedule_auth_idle(struct swaylock_state *state) { 79 | if (state->auth_idle_timer) { 80 | loop_remove_timer(state->eventloop, state->auth_idle_timer); 81 | } 82 | state->auth_idle_timer = loop_add_timer( 83 | state->eventloop, 3000, set_auth_idle, state); 84 | } 85 | 86 | static void clear_password(void *data) { 87 | struct swaylock_state *state = data; 88 | state->clear_password_timer = NULL; 89 | state->input_state = INPUT_STATE_CLEAR; 90 | schedule_input_idle(state); 91 | clear_password_buffer(&state->password); 92 | damage_state(state); 93 | } 94 | 95 | static void schedule_password_clear(struct swaylock_state *state) { 96 | if (state->clear_password_timer) { 97 | loop_remove_timer(state->eventloop, state->clear_password_timer); 98 | } 99 | state->clear_password_timer = loop_add_timer( 100 | state->eventloop, 10000, clear_password, state); 101 | } 102 | 103 | static void cancel_password_clear(struct swaylock_state *state) { 104 | if (state->clear_password_timer) { 105 | loop_remove_timer(state->eventloop, state->clear_password_timer); 106 | state->clear_password_timer = NULL; 107 | } 108 | } 109 | 110 | static void submit_password(struct swaylock_state *state) { 111 | if (state->args.ignore_empty && state->password.len == 0) { 112 | return; 113 | } 114 | if (state->auth_state == AUTH_STATE_VALIDATING) { 115 | return; 116 | } 117 | 118 | state->input_state = INPUT_STATE_IDLE; 119 | state->auth_state = AUTH_STATE_VALIDATING; 120 | cancel_password_clear(state); 121 | cancel_input_idle(state); 122 | 123 | if (!write_comm_request(&state->password)) { 124 | state->auth_state = AUTH_STATE_INVALID; 125 | schedule_auth_idle(state); 126 | } 127 | 128 | damage_state(state); 129 | } 130 | 131 | static void update_highlight(struct swaylock_state *state) { 132 | // Advance a random amount between 1/4 and 3/4 of a full turn 133 | state->highlight_start = 134 | (state->highlight_start + (rand() % 1024) + 512) % 2048; 135 | } 136 | 137 | void swaylock_handle_key(struct swaylock_state *state, 138 | xkb_keysym_t keysym, uint32_t codepoint) { 139 | 140 | switch (keysym) { 141 | case XKB_KEY_KP_Enter: /* fallthrough */ 142 | case XKB_KEY_Return: 143 | submit_password(state); 144 | break; 145 | case XKB_KEY_Delete: 146 | case XKB_KEY_BackSpace: 147 | if (state->xkb.control) { 148 | clear_password_buffer(&state->password); 149 | state->input_state = INPUT_STATE_CLEAR; 150 | cancel_password_clear(state); 151 | } else { 152 | if (backspace(&state->password) && state->password.len != 0) { 153 | state->input_state = INPUT_STATE_BACKSPACE; 154 | schedule_password_clear(state); 155 | update_highlight(state); 156 | } else { 157 | state->input_state = INPUT_STATE_CLEAR; 158 | cancel_password_clear(state); 159 | } 160 | } 161 | schedule_input_idle(state); 162 | damage_state(state); 163 | break; 164 | case XKB_KEY_Escape: 165 | clear_password_buffer(&state->password); 166 | state->input_state = INPUT_STATE_CLEAR; 167 | cancel_password_clear(state); 168 | schedule_input_idle(state); 169 | damage_state(state); 170 | break; 171 | case XKB_KEY_Caps_Lock: 172 | case XKB_KEY_Shift_L: 173 | case XKB_KEY_Shift_R: 174 | case XKB_KEY_Control_L: 175 | case XKB_KEY_Control_R: 176 | case XKB_KEY_Meta_L: 177 | case XKB_KEY_Meta_R: 178 | case XKB_KEY_Alt_L: 179 | case XKB_KEY_Alt_R: 180 | case XKB_KEY_Super_L: 181 | case XKB_KEY_Super_R: 182 | state->input_state = INPUT_STATE_NEUTRAL; 183 | schedule_password_clear(state); 184 | schedule_input_idle(state); 185 | damage_state(state); 186 | break; 187 | case XKB_KEY_m: /* fallthrough */ 188 | case XKB_KEY_d: 189 | case XKB_KEY_j: 190 | if (state->xkb.control) { 191 | submit_password(state); 192 | break; 193 | } 194 | // fallthrough 195 | case XKB_KEY_c: /* fallthrough */ 196 | case XKB_KEY_u: 197 | if (state->xkb.control) { 198 | clear_password_buffer(&state->password); 199 | state->input_state = INPUT_STATE_CLEAR; 200 | cancel_password_clear(state); 201 | schedule_input_idle(state); 202 | damage_state(state); 203 | break; 204 | } 205 | // fallthrough 206 | default: 207 | if (codepoint) { 208 | append_ch(&state->password, codepoint); 209 | state->input_state = INPUT_STATE_LETTER; 210 | schedule_password_clear(state); 211 | schedule_input_idle(state); 212 | update_highlight(state); 213 | damage_state(state); 214 | } 215 | break; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /pool-buffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "pool-buffer.h" 13 | 14 | static int anonymous_shm_open(void) { 15 | int retries = 100; 16 | 17 | do { 18 | // try a probably-unique name 19 | struct timespec ts; 20 | clock_gettime(CLOCK_MONOTONIC, &ts); 21 | pid_t pid = getpid(); 22 | char name[50]; 23 | snprintf(name, sizeof(name), "/swaylock-%x-%x", 24 | (unsigned int)pid, (unsigned int)ts.tv_nsec); 25 | 26 | // shm_open guarantees that O_CLOEXEC is set 27 | int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); 28 | if (fd >= 0) { 29 | shm_unlink(name); 30 | return fd; 31 | } 32 | 33 | --retries; 34 | } while (retries > 0 && errno == EEXIST); 35 | 36 | return -1; 37 | } 38 | 39 | static void buffer_release(void *data, struct wl_buffer *wl_buffer) { 40 | struct pool_buffer *buffer = data; 41 | buffer->busy = false; 42 | } 43 | 44 | static const struct wl_buffer_listener buffer_listener = { 45 | .release = buffer_release 46 | }; 47 | 48 | struct pool_buffer *create_buffer(struct wl_shm *shm, 49 | struct pool_buffer *buf, int32_t width, int32_t height, 50 | uint32_t format) { 51 | uint32_t stride = width * 4; 52 | size_t size = stride * height; 53 | 54 | void *data = NULL; 55 | if (size > 0) { 56 | int fd = anonymous_shm_open(); 57 | if (fd == -1) { 58 | return NULL; 59 | } 60 | if (ftruncate(fd, size) < 0) { 61 | close(fd); 62 | return NULL; 63 | } 64 | data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 65 | struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); 66 | buf->buffer = wl_shm_pool_create_buffer(pool, 0, 67 | width, height, stride, format); 68 | wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); 69 | wl_shm_pool_destroy(pool); 70 | close(fd); 71 | } 72 | 73 | buf->size = size; 74 | buf->width = width; 75 | buf->height = height; 76 | buf->data = data; 77 | buf->surface = cairo_image_surface_create_for_data(data, 78 | CAIRO_FORMAT_ARGB32, width, height, stride); 79 | buf->cairo = cairo_create(buf->surface); 80 | return buf; 81 | } 82 | 83 | void destroy_buffer(struct pool_buffer *buffer) { 84 | if (buffer->buffer) { 85 | wl_buffer_destroy(buffer->buffer); 86 | } 87 | if (buffer->cairo) { 88 | cairo_destroy(buffer->cairo); 89 | } 90 | if (buffer->surface) { 91 | cairo_surface_destroy(buffer->surface); 92 | } 93 | if (buffer->data) { 94 | munmap(buffer->data, buffer->size); 95 | } 96 | memset(buffer, 0, sizeof(struct pool_buffer)); 97 | } 98 | 99 | struct pool_buffer *get_next_buffer(struct wl_shm *shm, 100 | struct pool_buffer pool[static 2], uint32_t width, uint32_t height) { 101 | struct pool_buffer *buffer = NULL; 102 | 103 | for (size_t i = 0; i < 2; ++i) { 104 | if (pool[i].busy) { 105 | continue; 106 | } 107 | buffer = &pool[i]; 108 | } 109 | 110 | if (!buffer) { 111 | return NULL; 112 | } 113 | 114 | if (buffer->width != width || buffer->height != height) { 115 | destroy_buffer(buffer); 116 | } 117 | 118 | if (!buffer->buffer) { 119 | if (!create_buffer(shm, buffer, width, height, 120 | WL_SHM_FORMAT_ARGB8888)) { 121 | return NULL; 122 | } 123 | } 124 | buffer->busy = true; 125 | return buffer; 126 | } 127 | -------------------------------------------------------------------------------- /render.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "cairo.h" 6 | #include "background-image.h" 7 | #include "swaylock.h" 8 | #include "log.h" 9 | 10 | #define M_PI 3.14159265358979323846 11 | const float TYPE_INDICATOR_RANGE = M_PI / 3.0f; 12 | 13 | static void set_color_for_state(cairo_t *cairo, struct swaylock_state *state, 14 | struct swaylock_colorset *colorset) { 15 | if (state->input_state == INPUT_STATE_CLEAR) { 16 | cairo_set_source_u32(cairo, colorset->cleared); 17 | } else if (state->auth_state == AUTH_STATE_VALIDATING) { 18 | cairo_set_source_u32(cairo, colorset->verifying); 19 | } else if (state->auth_state == AUTH_STATE_INVALID) { 20 | cairo_set_source_u32(cairo, colorset->wrong); 21 | } else { 22 | if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { 23 | cairo_set_source_u32(cairo, colorset->caps_lock); 24 | } else if (state->xkb.caps_lock && !state->args.show_caps_lock_indicator && 25 | state->args.show_caps_lock_text) { 26 | uint32_t inputtextcolor = state->args.colors.text.input; 27 | state->args.colors.text.input = state->args.colors.text.caps_lock; 28 | cairo_set_source_u32(cairo, colorset->input); 29 | state->args.colors.text.input = inputtextcolor; 30 | } else { 31 | cairo_set_source_u32(cairo, colorset->input); 32 | } 33 | } 34 | } 35 | 36 | static void surface_frame_handle_done(void *data, struct wl_callback *callback, 37 | uint32_t time) { 38 | struct swaylock_surface *surface = data; 39 | 40 | wl_callback_destroy(callback); 41 | surface->frame = NULL; 42 | 43 | render(surface); 44 | } 45 | 46 | static const struct wl_callback_listener surface_frame_listener = { 47 | .done = surface_frame_handle_done, 48 | }; 49 | 50 | static bool render_frame(struct swaylock_surface *surface); 51 | 52 | void render(struct swaylock_surface *surface) { 53 | struct swaylock_state *state = surface->state; 54 | 55 | int buffer_width = surface->width * surface->scale; 56 | int buffer_height = surface->height * surface->scale; 57 | if (buffer_width == 0 || buffer_height == 0) { 58 | return; // not yet configured 59 | } 60 | 61 | if (!surface->dirty || surface->frame) { 62 | // Nothing to do or frame already pending 63 | return; 64 | } 65 | 66 | bool need_destroy = false; 67 | struct pool_buffer buffer; 68 | 69 | if (buffer_width != surface->last_buffer_width || 70 | buffer_height != surface->last_buffer_height) { 71 | need_destroy = true; 72 | if (!create_buffer(state->shm, &buffer, buffer_width, buffer_height, 73 | WL_SHM_FORMAT_ARGB8888)) { 74 | swaylock_log(LOG_ERROR, 75 | "Failed to create new buffer for frame background."); 76 | return; 77 | } 78 | 79 | cairo_t *cairo = buffer.cairo; 80 | cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); 81 | 82 | cairo_save(cairo); 83 | cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); 84 | cairo_set_source_u32(cairo, state->args.colors.background); 85 | cairo_paint(cairo); 86 | if (surface->image && state->args.mode != BACKGROUND_MODE_SOLID_COLOR) { 87 | cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); 88 | render_background_image(cairo, surface->image, 89 | state->args.mode, buffer_width, buffer_height); 90 | } 91 | cairo_restore(cairo); 92 | cairo_identity_matrix(cairo); 93 | 94 | wl_surface_set_buffer_scale(surface->surface, surface->scale); 95 | wl_surface_attach(surface->surface, buffer.buffer, 0, 0); 96 | wl_surface_damage_buffer(surface->surface, 0, 0, INT32_MAX, INT32_MAX); 97 | need_destroy = true; 98 | 99 | surface->last_buffer_width = buffer_width; 100 | surface->last_buffer_height = buffer_height; 101 | } 102 | 103 | render_frame(surface); 104 | surface->dirty = false; 105 | surface->frame = wl_surface_frame(surface->surface); 106 | wl_callback_add_listener(surface->frame, &surface_frame_listener, surface); 107 | wl_surface_commit(surface->surface); 108 | 109 | if (need_destroy) { 110 | destroy_buffer(&buffer); 111 | } 112 | } 113 | 114 | static void configure_font_drawing(cairo_t *cairo, struct swaylock_state *state, 115 | enum wl_output_subpixel subpixel, int arc_radius) { 116 | cairo_font_options_t *fo = cairo_font_options_create(); 117 | cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); 118 | cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); 119 | cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(subpixel)); 120 | 121 | cairo_set_font_options(cairo, fo); 122 | cairo_select_font_face(cairo, state->args.font, 123 | CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 124 | if (state->args.font_size > 0) { 125 | cairo_set_font_size(cairo, state->args.font_size); 126 | } else { 127 | cairo_set_font_size(cairo, arc_radius / 3.0f); 128 | } 129 | cairo_font_options_destroy(fo); 130 | } 131 | 132 | static bool render_frame(struct swaylock_surface *surface) { 133 | struct swaylock_state *state = surface->state; 134 | 135 | // First, compute the text that will be drawn, if any, since this 136 | // determines the size/positioning of the surface 137 | 138 | char attempts[4]; // like i3lock: count no more than 999 139 | char *text = NULL; 140 | const char *layout_text = NULL; 141 | 142 | bool draw_indicator = state->args.show_indicator && 143 | (state->auth_state != AUTH_STATE_IDLE || 144 | state->input_state != INPUT_STATE_IDLE || 145 | state->args.indicator_idle_visible); 146 | 147 | if (draw_indicator) { 148 | if (state->input_state == INPUT_STATE_CLEAR) { 149 | // This message has highest priority 150 | text = "Cleared"; 151 | } else if (state->auth_state == AUTH_STATE_VALIDATING) { 152 | text = "Verifying"; 153 | } else if (state->auth_state == AUTH_STATE_INVALID) { 154 | text = "Wrong"; 155 | } else { 156 | // Caps Lock has higher priority 157 | if (state->xkb.caps_lock && state->args.show_caps_lock_text) { 158 | text = "Caps Lock"; 159 | } else if (state->args.show_failed_attempts && 160 | state->failed_attempts > 0) { 161 | if (state->failed_attempts > 999) { 162 | text = "999+"; 163 | } else { 164 | snprintf(attempts, sizeof(attempts), "%d", state->failed_attempts); 165 | text = attempts; 166 | } 167 | } 168 | 169 | if (state->xkb.keymap) { 170 | xkb_layout_index_t num_layout = xkb_keymap_num_layouts(state->xkb.keymap); 171 | if (!state->args.hide_keyboard_layout && 172 | (state->args.show_keyboard_layout || num_layout > 1)) { 173 | xkb_layout_index_t curr_layout = 0; 174 | 175 | // advance to the first active layout (if any) 176 | while (curr_layout < num_layout && 177 | xkb_state_layout_index_is_active(state->xkb.state, 178 | curr_layout, XKB_STATE_LAYOUT_EFFECTIVE) != 1) { 179 | ++curr_layout; 180 | } 181 | // will handle invalid index if none are active 182 | layout_text = xkb_keymap_layout_get_name(state->xkb.keymap, curr_layout); 183 | } 184 | } 185 | } 186 | } 187 | 188 | // Compute the size of the buffer needed 189 | int arc_radius = state->args.radius * surface->scale; 190 | int arc_thickness = state->args.thickness * surface->scale; 191 | int buffer_diameter = (arc_radius + arc_thickness) * 2; 192 | int buffer_width = buffer_diameter; 193 | int buffer_height = buffer_diameter; 194 | 195 | if (text || layout_text) { 196 | cairo_set_antialias(state->test_cairo, CAIRO_ANTIALIAS_BEST); 197 | configure_font_drawing(state->test_cairo, state, surface->subpixel, arc_radius); 198 | 199 | if (text) { 200 | cairo_text_extents_t extents; 201 | cairo_text_extents(state->test_cairo, text, &extents); 202 | if (buffer_width < extents.width) { 203 | buffer_width = extents.width; 204 | } 205 | } 206 | if (layout_text) { 207 | cairo_text_extents_t extents; 208 | cairo_font_extents_t fe; 209 | double box_padding = 4.0 * surface->scale; 210 | cairo_text_extents(state->test_cairo, layout_text, &extents); 211 | cairo_font_extents(state->test_cairo, &fe); 212 | buffer_height += fe.height + 2 * box_padding; 213 | if (buffer_width < extents.width + 2 * box_padding) { 214 | buffer_width = extents.width + 2 * box_padding; 215 | } 216 | } 217 | } 218 | // Ensure buffer size is multiple of buffer scale - required by protocol 219 | buffer_height += surface->scale - (buffer_height % surface->scale); 220 | buffer_width += surface->scale - (buffer_width % surface->scale); 221 | 222 | int subsurf_xpos; 223 | int subsurf_ypos; 224 | 225 | // Center the indicator unless overridden by the user 226 | if (state->args.override_indicator_x_position) { 227 | subsurf_xpos = state->args.indicator_x_position - 228 | buffer_width / (2 * surface->scale) + 2 / surface->scale; 229 | } else { 230 | subsurf_xpos = surface->width / 2 - 231 | buffer_width / (2 * surface->scale) + 2 / surface->scale; 232 | } 233 | 234 | if (state->args.override_indicator_y_position) { 235 | subsurf_ypos = state->args.indicator_y_position - 236 | (state->args.radius + state->args.thickness); 237 | } else { 238 | subsurf_ypos = surface->height / 2 - 239 | (state->args.radius + state->args.thickness); 240 | } 241 | 242 | struct pool_buffer *buffer = get_next_buffer(state->shm, 243 | surface->indicator_buffers, buffer_width, buffer_height); 244 | if (buffer == NULL) { 245 | swaylock_log(LOG_ERROR, "No buffer"); 246 | return false; 247 | } 248 | 249 | // Render the buffer 250 | cairo_t *cairo = buffer->cairo; 251 | cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); 252 | 253 | cairo_identity_matrix(cairo); 254 | 255 | // Clear 256 | cairo_save(cairo); 257 | cairo_set_source_rgba(cairo, 0, 0, 0, 0); 258 | cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); 259 | cairo_paint(cairo); 260 | cairo_restore(cairo); 261 | 262 | if (draw_indicator) { 263 | // Fill inner circle 264 | cairo_set_line_width(cairo, 0); 265 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, 266 | arc_radius - arc_thickness / 2, 0, 2 * M_PI); 267 | set_color_for_state(cairo, state, &state->args.colors.inside); 268 | cairo_fill_preserve(cairo); 269 | cairo_stroke(cairo); 270 | 271 | // Draw ring 272 | cairo_set_line_width(cairo, arc_thickness); 273 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, arc_radius, 274 | 0, 2 * M_PI); 275 | set_color_for_state(cairo, state, &state->args.colors.ring); 276 | cairo_stroke(cairo); 277 | 278 | // Draw a message 279 | configure_font_drawing(cairo, state, surface->subpixel, arc_radius); 280 | set_color_for_state(cairo, state, &state->args.colors.text); 281 | 282 | if (text) { 283 | cairo_text_extents_t extents; 284 | cairo_font_extents_t fe; 285 | double x, y; 286 | cairo_text_extents(cairo, text, &extents); 287 | cairo_font_extents(cairo, &fe); 288 | x = (buffer_width / 2) - 289 | (extents.width / 2 + extents.x_bearing); 290 | y = (buffer_diameter / 2) + 291 | (fe.height / 2 - fe.descent); 292 | 293 | cairo_move_to(cairo, x, y); 294 | cairo_show_text(cairo, text); 295 | cairo_close_path(cairo); 296 | cairo_new_sub_path(cairo); 297 | } 298 | 299 | // Typing indicator: Highlight random part on keypress 300 | if (state->input_state == INPUT_STATE_LETTER || 301 | state->input_state == INPUT_STATE_BACKSPACE) { 302 | double highlight_start = state->highlight_start * (M_PI / 1024.0); 303 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, 304 | arc_radius, highlight_start, 305 | highlight_start + TYPE_INDICATOR_RANGE); 306 | if (state->input_state == INPUT_STATE_LETTER) { 307 | if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { 308 | cairo_set_source_u32(cairo, state->args.colors.caps_lock_key_highlight); 309 | } else { 310 | cairo_set_source_u32(cairo, state->args.colors.key_highlight); 311 | } 312 | } else { 313 | if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { 314 | cairo_set_source_u32(cairo, state->args.colors.caps_lock_bs_highlight); 315 | } else { 316 | cairo_set_source_u32(cairo, state->args.colors.bs_highlight); 317 | } 318 | } 319 | cairo_stroke(cairo); 320 | 321 | // Draw borders 322 | double inner_radius = buffer_diameter / 2.0 - arc_thickness * 1.5; 323 | double outer_radius = buffer_diameter / 2.0 - arc_thickness / 2.0; 324 | 325 | cairo_set_line_width(cairo, 2.0 * surface->scale); 326 | cairo_set_source_u32(cairo, state->args.colors.separator); 327 | cairo_move_to(cairo, 328 | buffer_width / 2.0 + cos(highlight_start) * inner_radius, 329 | buffer_diameter / 2.0 + sin(highlight_start) * inner_radius 330 | ); 331 | cairo_line_to(cairo, 332 | buffer_width / 2.0 + cos(highlight_start) * outer_radius, 333 | buffer_diameter / 2.0 + sin(highlight_start) * outer_radius 334 | ); 335 | cairo_stroke(cairo); 336 | 337 | cairo_move_to(cairo, 338 | buffer_width / 2.0 + cos(highlight_start + TYPE_INDICATOR_RANGE) * inner_radius, 339 | buffer_diameter / 2.0 + sin(highlight_start + TYPE_INDICATOR_RANGE) * inner_radius 340 | ); 341 | cairo_line_to(cairo, 342 | buffer_width / 2.0 + cos(highlight_start + TYPE_INDICATOR_RANGE) * outer_radius, 343 | buffer_diameter / 2.0 + sin(highlight_start + TYPE_INDICATOR_RANGE) * outer_radius 344 | ); 345 | cairo_stroke(cairo); 346 | } 347 | 348 | // Draw inner + outer border of the circle 349 | set_color_for_state(cairo, state, &state->args.colors.line); 350 | cairo_set_line_width(cairo, 2.0 * surface->scale); 351 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, 352 | arc_radius - arc_thickness / 2, 0, 2 * M_PI); 353 | cairo_stroke(cairo); 354 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, 355 | arc_radius + arc_thickness / 2, 0, 2 * M_PI); 356 | cairo_stroke(cairo); 357 | 358 | // display layout text separately 359 | if (layout_text) { 360 | cairo_text_extents_t extents; 361 | cairo_font_extents_t fe; 362 | double x, y; 363 | double box_padding = 4.0 * surface->scale; 364 | cairo_text_extents(cairo, layout_text, &extents); 365 | cairo_font_extents(cairo, &fe); 366 | // upper left coordinates for box 367 | x = (buffer_width / 2) - (extents.width / 2) - box_padding; 368 | y = buffer_diameter; 369 | 370 | // background box 371 | cairo_rectangle(cairo, x, y, 372 | extents.width + 2.0 * box_padding, 373 | fe.height + 2.0 * box_padding); 374 | cairo_set_source_u32(cairo, state->args.colors.layout_background); 375 | cairo_fill_preserve(cairo); 376 | // border 377 | cairo_set_source_u32(cairo, state->args.colors.layout_border); 378 | cairo_stroke(cairo); 379 | 380 | // take font extents and padding into account 381 | cairo_move_to(cairo, 382 | x - extents.x_bearing + box_padding, 383 | y + (fe.height - fe.descent) + box_padding); 384 | cairo_set_source_u32(cairo, state->args.colors.layout_text); 385 | cairo_show_text(cairo, layout_text); 386 | cairo_new_sub_path(cairo); 387 | } 388 | } 389 | 390 | // Send Wayland requests 391 | wl_subsurface_set_position(surface->subsurface, subsurf_xpos, subsurf_ypos); 392 | 393 | wl_surface_set_buffer_scale(surface->child, surface->scale); 394 | wl_surface_attach(surface->child, buffer->buffer, 0, 0); 395 | wl_surface_damage_buffer(surface->child, 0, 0, INT32_MAX, INT32_MAX); 396 | wl_surface_commit(surface->child); 397 | 398 | return true; 399 | } 400 | -------------------------------------------------------------------------------- /seat.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "log.h" 7 | #include "swaylock.h" 8 | #include "seat.h" 9 | #include "loop.h" 10 | 11 | static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, 12 | uint32_t format, int32_t fd, uint32_t size) { 13 | struct swaylock_seat *seat = data; 14 | struct swaylock_state *state = seat->state; 15 | if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { 16 | close(fd); 17 | swaylock_log(LOG_ERROR, "Unknown keymap format %d, aborting", format); 18 | exit(1); 19 | } 20 | char *map_shm = mmap(NULL, size - 1, PROT_READ, MAP_PRIVATE, fd, 0); 21 | if (map_shm == MAP_FAILED) { 22 | close(fd); 23 | swaylock_log(LOG_ERROR, "Unable to initialize keymap shm, aborting"); 24 | exit(1); 25 | } 26 | struct xkb_keymap *keymap = xkb_keymap_new_from_buffer( 27 | state->xkb.context, map_shm, size - 1, XKB_KEYMAP_FORMAT_TEXT_V1, 28 | XKB_KEYMAP_COMPILE_NO_FLAGS); 29 | munmap(map_shm, size - 1); 30 | close(fd); 31 | assert(keymap); 32 | struct xkb_state *xkb_state = xkb_state_new(keymap); 33 | assert(xkb_state); 34 | xkb_keymap_unref(state->xkb.keymap); 35 | xkb_state_unref(state->xkb.state); 36 | state->xkb.keymap = keymap; 37 | state->xkb.state = xkb_state; 38 | } 39 | 40 | static void keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, 41 | uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { 42 | // Who cares 43 | } 44 | 45 | static void keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, 46 | uint32_t serial, struct wl_surface *surface) { 47 | // Who cares 48 | } 49 | 50 | static void keyboard_repeat(void *data) { 51 | struct swaylock_seat *seat = data; 52 | struct swaylock_state *state = seat->state; 53 | seat->repeat_timer = loop_add_timer( 54 | state->eventloop, seat->repeat_period_ms, keyboard_repeat, seat); 55 | swaylock_handle_key(state, seat->repeat_sym, seat->repeat_codepoint); 56 | } 57 | 58 | static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, 59 | uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) { 60 | struct swaylock_seat *seat = data; 61 | struct swaylock_state *state = seat->state; 62 | enum wl_keyboard_key_state key_state = _key_state; 63 | xkb_keysym_t sym = xkb_state_key_get_one_sym(state->xkb.state, key + 8); 64 | uint32_t keycode = key_state == WL_KEYBOARD_KEY_STATE_PRESSED ? 65 | key + 8 : 0; 66 | uint32_t codepoint = xkb_state_key_get_utf32(state->xkb.state, keycode); 67 | if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED) { 68 | swaylock_handle_key(state, sym, codepoint); 69 | } 70 | 71 | if (seat->repeat_timer) { 72 | loop_remove_timer(seat->state->eventloop, seat->repeat_timer); 73 | seat->repeat_timer = NULL; 74 | } 75 | 76 | if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && seat->repeat_period_ms > 0) { 77 | seat->repeat_sym = sym; 78 | seat->repeat_codepoint = codepoint; 79 | seat->repeat_timer = loop_add_timer( 80 | seat->state->eventloop, seat->repeat_delay_ms, keyboard_repeat, seat); 81 | } 82 | } 83 | 84 | static void keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, 85 | uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, 86 | uint32_t mods_locked, uint32_t group) { 87 | struct swaylock_seat *seat = data; 88 | struct swaylock_state *state = seat->state; 89 | if (state->xkb.state == NULL) { 90 | return; 91 | } 92 | 93 | int layout_same = xkb_state_layout_index_is_active(state->xkb.state, 94 | group, XKB_STATE_LAYOUT_EFFECTIVE); 95 | if (!layout_same) { 96 | damage_state(state); 97 | } 98 | xkb_state_update_mask(state->xkb.state, 99 | mods_depressed, mods_latched, mods_locked, 0, 0, group); 100 | int caps_lock = xkb_state_mod_name_is_active(state->xkb.state, 101 | XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED); 102 | if (caps_lock != state->xkb.caps_lock) { 103 | state->xkb.caps_lock = caps_lock; 104 | damage_state(state); 105 | } 106 | state->xkb.control = xkb_state_mod_name_is_active(state->xkb.state, 107 | XKB_MOD_NAME_CTRL, 108 | XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); 109 | } 110 | 111 | static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, 112 | int32_t rate, int32_t delay) { 113 | struct swaylock_seat *seat = data; 114 | if (rate <= 0) { 115 | seat->repeat_period_ms = -1; 116 | } else { 117 | // Keys per second -> milliseconds between keys 118 | seat->repeat_period_ms = 1000 / rate; 119 | } 120 | seat->repeat_delay_ms = delay; 121 | } 122 | 123 | static const struct wl_keyboard_listener keyboard_listener = { 124 | .keymap = keyboard_keymap, 125 | .enter = keyboard_enter, 126 | .leave = keyboard_leave, 127 | .key = keyboard_key, 128 | .modifiers = keyboard_modifiers, 129 | .repeat_info = keyboard_repeat_info, 130 | }; 131 | 132 | static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, 133 | uint32_t serial, struct wl_surface *surface, 134 | wl_fixed_t surface_x, wl_fixed_t surface_y) { 135 | wl_pointer_set_cursor(wl_pointer, serial, NULL, 0, 0); 136 | } 137 | 138 | static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, 139 | uint32_t serial, struct wl_surface *surface) { 140 | // Who cares 141 | } 142 | 143 | static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, 144 | uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { 145 | // Who cares 146 | } 147 | 148 | static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, 149 | uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { 150 | // Who cares 151 | } 152 | 153 | static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, 154 | uint32_t time, uint32_t axis, wl_fixed_t value) { 155 | // Who cares 156 | } 157 | 158 | static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { 159 | // Who cares 160 | } 161 | 162 | static void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, 163 | uint32_t axis_source) { 164 | // Who cares 165 | } 166 | 167 | static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, 168 | uint32_t time, uint32_t axis) { 169 | // Who cares 170 | } 171 | 172 | static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, 173 | uint32_t axis, int32_t discrete) { 174 | // Who cares 175 | } 176 | 177 | static const struct wl_pointer_listener pointer_listener = { 178 | .enter = wl_pointer_enter, 179 | .leave = wl_pointer_leave, 180 | .motion = wl_pointer_motion, 181 | .button = wl_pointer_button, 182 | .axis = wl_pointer_axis, 183 | .frame = wl_pointer_frame, 184 | .axis_source = wl_pointer_axis_source, 185 | .axis_stop = wl_pointer_axis_stop, 186 | .axis_discrete = wl_pointer_axis_discrete, 187 | }; 188 | 189 | static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, 190 | enum wl_seat_capability caps) { 191 | struct swaylock_seat *seat = data; 192 | if (seat->pointer) { 193 | wl_pointer_release(seat->pointer); 194 | seat->pointer = NULL; 195 | } 196 | if (seat->keyboard) { 197 | wl_keyboard_release(seat->keyboard); 198 | seat->keyboard = NULL; 199 | } 200 | if ((caps & WL_SEAT_CAPABILITY_POINTER)) { 201 | seat->pointer = wl_seat_get_pointer(wl_seat); 202 | wl_pointer_add_listener(seat->pointer, &pointer_listener, NULL); 203 | } 204 | if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) { 205 | seat->keyboard = wl_seat_get_keyboard(wl_seat); 206 | wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, seat); 207 | } 208 | } 209 | 210 | static void seat_handle_name(void *data, struct wl_seat *wl_seat, 211 | const char *name) { 212 | // Who cares 213 | } 214 | 215 | const struct wl_seat_listener seat_listener = { 216 | .capabilities = seat_handle_capabilities, 217 | .name = seat_handle_name, 218 | }; 219 | -------------------------------------------------------------------------------- /shadow.c: -------------------------------------------------------------------------------- 1 | #undef _POSIX_C_SOURCE 2 | #define _XOPEN_SOURCE // for crypt 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #ifdef __GLIBC__ 11 | // GNU, you damn slimy bastard 12 | #include 13 | #endif 14 | #include "comm.h" 15 | #include "log.h" 16 | #include "password-buffer.h" 17 | #include "swaylock.h" 18 | 19 | char *encpw = NULL; 20 | 21 | void initialize_pw_backend(int argc, char **argv) { 22 | /* This code runs as root */ 23 | struct passwd *pwent = getpwuid(getuid()); 24 | if (!pwent) { 25 | swaylock_log_errno(LOG_ERROR, "failed to getpwuid"); 26 | exit(EXIT_FAILURE); 27 | } 28 | encpw = pwent->pw_passwd; 29 | if (strcmp(encpw, "x") == 0) { 30 | struct spwd *swent = getspnam(pwent->pw_name); 31 | if (!swent) { 32 | swaylock_log_errno(LOG_ERROR, "failed to getspnam"); 33 | exit(EXIT_FAILURE); 34 | } 35 | encpw = swent->sp_pwdp; 36 | } 37 | 38 | if (setgid(getgid()) != 0) { 39 | swaylock_log_errno(LOG_ERROR, "Unable to drop root"); 40 | exit(EXIT_FAILURE); 41 | } 42 | if (setuid(getuid()) != 0) { 43 | swaylock_log_errno(LOG_ERROR, "Unable to drop root"); 44 | exit(EXIT_FAILURE); 45 | } 46 | if (setuid(0) != -1 || setgid(0) != -1) { 47 | swaylock_log_errno(LOG_ERROR, "Unable to drop root (we shouldn't be " 48 | "able to restore it after setuid/setgid)"); 49 | exit(EXIT_FAILURE); 50 | } 51 | 52 | /* This code does not run as root */ 53 | swaylock_log(LOG_DEBUG, "Prepared to authorize user %s", pwent->pw_name); 54 | 55 | if (!spawn_comm_child()) { 56 | exit(EXIT_FAILURE); 57 | } 58 | 59 | /* Buffer is only used by the child */ 60 | clear_buffer(encpw, strlen(encpw)); 61 | encpw = NULL; 62 | } 63 | 64 | void run_pw_backend_child(void) { 65 | assert(encpw != NULL); 66 | while (1) { 67 | char *buf; 68 | ssize_t size = read_comm_request(&buf); 69 | if (size < 0) { 70 | exit(EXIT_FAILURE); 71 | } else if (size == 0) { 72 | break; 73 | } 74 | 75 | const char *c = crypt(buf, encpw); 76 | password_buffer_destroy(buf, size); 77 | buf = NULL; 78 | 79 | if (c == NULL) { 80 | swaylock_log_errno(LOG_ERROR, "crypt failed"); 81 | exit(EXIT_FAILURE); 82 | } 83 | bool success = strcmp(c, encpw) == 0; 84 | 85 | if (!write_comm_reply(success)) { 86 | exit(EXIT_FAILURE); 87 | } 88 | 89 | sleep(2); 90 | } 91 | 92 | clear_buffer(encpw, strlen(encpw)); 93 | exit(EXIT_SUCCESS); 94 | } 95 | -------------------------------------------------------------------------------- /swaylock.1.scd: -------------------------------------------------------------------------------- 1 | swaylock(1) 2 | 3 | # NAME 4 | 5 | swaylock - Screen locker for Wayland 6 | 7 | # SYNOPSIS 8 | 9 | _swaylock_ [options...] 10 | 11 | Locks your Wayland session. 12 | 13 | # OPTIONS 14 | 15 | *-C, --config* 16 | The config file to use. By default, the following paths are checked: 17 | _$HOME/.swaylock/config_, _$XDG\_CONFIG\_HOME/swaylock/config_, and 18 | _SYSCONFDIR/swaylock/config_. All flags aside from this one are valid 19 | options in the configuration file using the format _long-option=value_. 20 | For options such as _ignore-empty-password_, just supply the _long-option_. 21 | All leading dashes should be omitted and the equals sign is required for 22 | flags that take an argument. 23 | 24 | *-d, --debug* 25 | Enable debugging output. 26 | 27 | *-e, --ignore-empty-password* 28 | When an empty password is provided, do not validate it. 29 | 30 | *-F, --show-failed-attempts* 31 | Show current count of failed authentication attempts. 32 | 33 | *-f, --daemonize* 34 | Detach from the controlling terminal after locking. 35 | 36 | Note: this is the default behavior of i3lock. 37 | 38 | *-R, --ready-fd* 39 | File descriptor to send readiness notifications to. 40 | 41 | When the session has been locked, a single newline is written to the FD. 42 | At this point, the compositor guarantees that no security sensitive content 43 | is visible on-screen. 44 | 45 | *-h, --help* 46 | Show help message and quit. 47 | 48 | *-v, --version* 49 | Show the version number and quit. 50 | 51 | # APPEARANCE 52 | 53 | *-u, --no-unlock-indicator* 54 | Disable the unlock indicator. 55 | 56 | *-i, --image* [[]:] 57 | Display the given image, optionally only on the given output. Use -c to set 58 | a background color. If the path potentially contains a ':', prefix it with another 59 | ':' to prevent interpreting part of it as . 60 | 61 | *-k, --show-keyboard-layout* 62 | Display the current xkb layout while typing. 63 | 64 | *-K, --hide-keyboard-layout* 65 | Force hiding the current xkb layout while typing, even if more than one layout 66 | is configured or the show-keyboard-layout option is set. 67 | 68 | *-L, --disable-caps-lock-text* 69 | Disable the Caps Lock text. 70 | 71 | *-l, --indicator-caps-lock* 72 | Show the current Caps Lock state also on the indicator. 73 | 74 | *-s, --scaling* 75 | Image scaling mode: _stretch_, _fill_, _fit_, _center_, _tile_, 76 | _solid\_color_. Use _solid\_color_ to display only the background color, even 77 | if a background image is specified. 78 | 79 | *-t, --tiling* 80 | Same as --scaling=tile. 81 | 82 | *-c, --color* 83 | Turn the screen into the given color instead of white. If -i is used, this 84 | sets the background of the image to the given color. Defaults to white 85 | (FFFFFF). 86 | 87 | *--bs-hl-color* 88 | Sets the color of backspace highlight segments. 89 | 90 | *--caps-lock-bs-hl-color* 91 | Sets the color of backspace highlight segments when Caps Lock is active. 92 | 93 | *--caps-lock-key-hl-color* 94 | Sets the color of the key press highlight segments when Caps Lock is active. 95 | 96 | *--font* 97 | Sets the font of the text. 98 | 99 | *--font-size* 100 | Sets a fixed font size for the indicator text. 101 | 102 | *--indicator-idle-visible* 103 | Sets the indicator to show even if idle. 104 | 105 | *--indicator-radius* 106 | Sets the indicator radius. The default value is 50. 107 | 108 | *--indicator-thickness* 109 | Sets the indicator thickness. The default value is 10. 110 | 111 | *--indicator-x-position* 112 | Sets the horizontal position of the indicator. 113 | 114 | *--indicator-y-position* 115 | Sets the vertical position of the indicator. 116 | 117 | *--inside-color* 118 | Sets the color of the inside of the indicator. 119 | 120 | *--inside-clear-color* 121 | Sets the color of the inside of the indicator when cleared. 122 | 123 | *--inside-caps-lock-color* 124 | Sets the color of the inside of the indicator when Caps Lock is active. 125 | 126 | *--inside-ver-color* 127 | Sets the color of the inside of the indicator when verifying. 128 | 129 | *--inside-wrong-color* 130 | Sets the color of the inside of the indicator when invalid. 131 | 132 | *--key-hl-color* 133 | Sets the color of the key press highlight segments. 134 | 135 | *--layout-bg-color* 136 | Sets the background color of the box containing the layout text. 137 | 138 | *--layout-border-color* 139 | Sets the color of the border of the box containing the layout text. 140 | 141 | *--layout-text-color* 142 | Sets the color of the layout text. 143 | 144 | *--line-color* 145 | Sets the color of the line between the inside and ring. 146 | 147 | *--line-clear-color* 148 | Sets the color of the line between the inside and ring when cleared. 149 | 150 | *--line-caps-lock-color* 151 | Sets the color of the line between the inside and ring when Caps Lock is 152 | active. 153 | 154 | *--line-ver-color* 155 | Sets the color of the line between the inside and ring when verifying. 156 | 157 | *--line-wrong-color* 158 | Sets the color of the line between the inside and ring when invalid. 159 | 160 | *-n, --line-uses-inside* 161 | Use the inside color for the line between the inside and ring. 162 | 163 | *-r, --line-uses-ring* 164 | Use the ring color for the line between the inside and ring. 165 | 166 | *--ring-color* 167 | Sets the color of the ring of the indicator when typing or idle. 168 | 169 | *--ring-clear-color* 170 | Sets the color of the ring of the indicator when cleared. 171 | 172 | *--ring-caps-lock-color* 173 | Sets the color of the ring of the indicator when Caps Lock is active. 174 | 175 | *--ring-ver-color* 176 | Sets the color of the ring of the indicator when verifying. 177 | 178 | *--ring-wrong-color* 179 | Sets the color of the ring of the indicator when invalid. 180 | 181 | *--separator-color* 182 | Sets the color of the lines that separate highlight segments. 183 | 184 | *--text-color* 185 | Sets the color of the text. 186 | 187 | *--text-clear-color* 188 | Sets the color of the text when cleared. 189 | 190 | *--text-caps-lock-color* 191 | Sets the color of the text when Caps Lock is active. 192 | 193 | *--text-ver-color* 194 | Sets the color of the text when verifying. 195 | 196 | *--text-wrong-color* 197 | Sets the color of the text when invalid. 198 | 199 | # SIGNALS 200 | 201 | *SIGUSR1* 202 | Unlock the screen and exit. 203 | 204 | # AUTHORS 205 | 206 | Maintained by Drew DeVault , who is assisted by other open 207 | source contributors. For more information about swaylock development, see 208 | https://github.com/swaywm/swaylock. 209 | -------------------------------------------------------------------------------- /unicode.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "unicode.h" 5 | 6 | int utf8_last_size(const char *str) { 7 | int len = 0; 8 | char *pos = strchr(str, '\0'); 9 | while (pos > str) { 10 | --pos; ++len; 11 | if ((*pos & 0xc0) != 0x80) { 12 | return len; 13 | } 14 | } 15 | return 0; 16 | } 17 | 18 | size_t utf8_chsize(uint32_t ch) { 19 | if (ch < 0x80) { 20 | return 1; 21 | } else if (ch < 0x800) { 22 | return 2; 23 | } else if (ch < 0x10000) { 24 | return 3; 25 | } 26 | return 4; 27 | } 28 | 29 | size_t utf8_encode(char *str, uint32_t ch) { 30 | size_t len = 0; 31 | uint8_t first; 32 | 33 | if (ch < 0x80) { 34 | first = 0; 35 | len = 1; 36 | } else if (ch < 0x800) { 37 | first = 0xc0; 38 | len = 2; 39 | } else if (ch < 0x10000) { 40 | first = 0xe0; 41 | len = 3; 42 | } else { 43 | first = 0xf0; 44 | len = 4; 45 | } 46 | 47 | for (size_t i = len - 1; i > 0; --i) { 48 | str[i] = (ch & 0x3f) | 0x80; 49 | ch >>= 6; 50 | } 51 | 52 | str[0] = ch | first; 53 | return len; 54 | } 55 | 56 | 57 | static const struct { 58 | uint8_t mask; 59 | uint8_t result; 60 | int octets; 61 | } sizes[] = { 62 | { 0x80, 0x00, 1 }, 63 | { 0xE0, 0xC0, 2 }, 64 | { 0xF0, 0xE0, 3 }, 65 | { 0xF8, 0xF0, 4 }, 66 | { 0xFC, 0xF8, 5 }, 67 | { 0xFE, 0xF8, 6 }, 68 | { 0x80, 0x80, -1 }, 69 | }; 70 | 71 | int utf8_size(const char *s) { 72 | uint8_t c = (uint8_t)*s; 73 | for (size_t i = 0; i < sizeof(sizes) / sizeof(*sizes); ++i) { 74 | if ((c & sizes[i].mask) == sizes[i].result) { 75 | return sizes[i].octets; 76 | } 77 | } 78 | return -1; 79 | } 80 | --------------------------------------------------------------------------------