├── .build.yml ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── background-image.c ├── cairo.c ├── comm.c ├── completions ├── bash │ └── swaylock ├── fish │ └── swaylock.fish ├── meson.build └── zsh │ └── _swaylock ├── example_label_outputs.py ├── example_rotate.sh ├── forward.c ├── 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-plugin ├── password-buffer.c ├── password.c ├── pool-buffer.c ├── render.c ├── seat.c ├── setsid.c ├── shadow.c ├── sleep-watcher.c ├── swaylock.1.scd ├── unicode.c ├── wayland-drm.xml └── wlr-layer-shell-unstable-v1.xml /.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-plugin 2 | 3 | This is a fork of [`swaylock`](https://github.com/swaywm/swaylock), a screen 4 | locking utility for Wayland compositors. With `swaylock-plugin`, you can for 5 | your lockscreen background display the animated output from any wallpaper program 6 | that implements the `wlr-layer-shell-unstable-v1` protocol. All you have to do 7 | is run `swaylock-plugin --command 'my-wallpaper ...'`, where `my-wallpaper ...` 8 | is replaced by your desired program. Examples: 9 | 10 | * [`swaybg`](https://github.com/swaywm/swaybg), which displays regular background images 11 | * [`mpvpaper`](https://github.com/GhostNaN/mpvpaper), which lets you play videos 12 | * [`shaderbg`](https://git.sr.ht/~mstoeckl/shaderbg), renders OpenGL shaders 13 | * [`rwalkbg`](https://git.sr.ht/~mstoeckl/rwalkbg), a very slow animation 14 | * [`wscreensaver`](https://git.sr.ht/~mstoeckl/wscreensaver), an experiment in porting 15 | a few xscreensaver hacks to Wayland. Best with the `--command-each` flag. 16 | * [`windowtolayer`](https://gitlab.freedesktop.org/mstoeckl/windowtolayer), a tool that 17 | can be used to run normally windowed applications, like terminals, as wallpapers. 18 | Requires `--command-each` flag. For example: 19 | ``` 20 | swaylock-plugin --command-each 'windowtolayer -- termite -e neo-matrix' 21 | swaylock-plugin --command-each 'windowtolayer -- alacritty -e asciiquarium' 22 | ``` 23 | * You can rotate between wallpapers in a folder by setting the following script 24 | as the command; e.g.: `swaylock-plugin --command './example_rotate.sh /path/to/folder'`. (This works by periodically killing the wallpaper program, after which `swaylock-plugin` automatically restarts it.) 25 | ``` 26 | #!/bin/sh 27 | file=`ls $1 | shuf -n 1` 28 | delay=60. 29 | echo "Runnning swaybg for $delay secs on: $1/$file" 30 | timeout $delay swaybg -i $1/$file 31 | ``` 32 | 33 | ` swaylock-plugin` requires that the Wayland compositor implement the `ext-session-lock-v1` protocol. 34 | 35 | This is experimental software, so if something fails to work it's probably a bug 36 | in this program -- report it at https://github.com/mstoeckl/swaylock-plugin . 37 | 38 | As this fork is not nearly as well tested as the original swaylock, before using this 39 | program, ensure that you can recover from both an unresponsive lockscreen and one 40 | that has crashed. (For example, in Sway, by creating a `--locked` bindsym to kill and 41 | restart swaylock-plugin; or by switching to a different virtual terminal, running 42 | `killall swaylock-plugin` and running swaylock-plugin, and restarting with e.g. `WAYLAND_DISPLAY=wayland-1 swaylock-plugin` .) 43 | 44 | See the man page, [`swaylock-plugin(1)`](swaylock.1.scd), for instructions on using swaylock-plugin. 45 | 46 | ## Grace period 47 | 48 | `swaylock-plugin` adds a grace period feature; unlike the original `swaylock`, it 49 | is not practical to emulate one using a separate program (like `chayang`) because 50 | any animated backgrounds would be interrupted. With the `--grace` flag, it is 51 | possible to unlock the screen without a password for the first few seconds after 52 | the screen locker starts with either a key press or significant mouse motion. 53 | 54 | This feature requires logind (systemd or elogind) support to automatically end the 55 | grace period just before the computer goes to sleep. The grace period also ends on 56 | receipt of the signal SIGUSR2. 57 | 58 | ### Example 59 | 60 | Sway can be made to lock the screen with a grace period and the custom wallpaper 61 | program specified in the script `lock-bg-command.sh` with the following configuration: 62 | 63 | ``` 64 | exec swayidle \ 65 | timeout 300 'swaylock-plugin --grace 30sec --pointer-hysteresis 25.0 --command-each lock-bg-command.sh' \ 66 | timeout 600 'swaymsg "output * dpms off"' \ 67 | resume 'swaymsg "output * dpms on"' \ 68 | before-sleep 'swaylock-plugin --command-each lock-bg-command.sh' 69 | bindsym --locked Ctrl+Alt+L exec \ 70 | 'killall -SIGUSR2 swaylock-plugin; \ 71 | swaylock-plugin --command-each lock-bg-command.sh' 72 | ``` 73 | 74 | This will, after 5 minutes of inactivity, start `swaylock-plugin`; for the next 75 | 30 seconds, one can easily unlock the screen by pressing any key or moving the 76 | mouse more than 25 pixels in a one second period; afterwards, authentication 77 | will be required. When the computer goes to sleep, the screen will lock for 78 | certain. (If `swaylock-plugin` was running and in the grace period, the grace 79 | period will end; in case `swaylock-plugin` was not running, a new instance will 80 | be started without a grace period, that locks the screen if it was not already 81 | locked.) One can also immediately lock the screen with a keybinding (or use the 82 | keybinding to restart the lock screen, if it crashed.) Any screens will be turned 83 | off after 10 minutes of inactivity. 84 | 85 | ## Installation 86 | 87 | Install dependencies: 88 | 89 | * meson \* 90 | * wayland 91 | * wayland-protocols \* 92 | * libxkbcommon 93 | * cairo 94 | * gdk-pixbuf2 95 | * pam (optional) 96 | * systemd or elogind (optional) 97 | * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional: man pages) \* 98 | * git \* 99 | * swaybg 100 | 101 | _\* Compile-time dep_ 102 | 103 | Run these commands: 104 | 105 | meson build 106 | ninja -C build 107 | sudo ninja -C build install 108 | 109 | On systems without PAM, you need to suid the swaylock-plugin binary: 110 | 111 | sudo chmod a+s /usr/local/bin/swaylock-plugin 112 | 113 | Swaylock will drop root permissions shortly after startup. 114 | -------------------------------------------------------------------------------- /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 | image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); 35 | g_object_unref(pixbuf); 36 | #else 37 | image = cairo_image_surface_create_from_png(path); 38 | #endif // HAVE_GDK_PIXBUF 39 | if (!image) { 40 | swaylock_log(LOG_ERROR, "Failed to read background image."); 41 | return NULL; 42 | } 43 | if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) { 44 | swaylock_log(LOG_ERROR, "Failed to read background image: %s." 45 | #if !HAVE_GDK_PIXBUF 46 | "\nSway was compiled without gdk_pixbuf support, so only" 47 | "\nPNG images can be loaded. This is the likely cause." 48 | #endif // !HAVE_GDK_PIXBUF 49 | , cairo_status_to_string(cairo_surface_status(image))); 50 | return NULL; 51 | } 52 | return image; 53 | } 54 | 55 | void render_background_image(cairo_t *cairo, cairo_surface_t *image, 56 | enum background_mode mode, int buffer_width, int buffer_height) { 57 | double width = cairo_image_surface_get_width(image); 58 | double height = cairo_image_surface_get_height(image); 59 | 60 | cairo_save(cairo); 61 | switch (mode) { 62 | case BACKGROUND_MODE_STRETCH: 63 | cairo_scale(cairo, 64 | (double)buffer_width / width, 65 | (double)buffer_height / height); 66 | cairo_set_source_surface(cairo, image, 0, 0); 67 | break; 68 | case BACKGROUND_MODE_FILL: { 69 | double window_ratio = (double)buffer_width / buffer_height; 70 | double bg_ratio = width / height; 71 | 72 | if (window_ratio > bg_ratio) { 73 | double scale = (double)buffer_width / width; 74 | cairo_scale(cairo, scale, scale); 75 | cairo_set_source_surface(cairo, image, 76 | 0, (double)buffer_height / 2 / scale - height / 2); 77 | } else { 78 | double scale = (double)buffer_height / height; 79 | cairo_scale(cairo, scale, scale); 80 | cairo_set_source_surface(cairo, image, 81 | (double)buffer_width / 2 / scale - width / 2, 0); 82 | } 83 | break; 84 | } 85 | case BACKGROUND_MODE_FIT: { 86 | double window_ratio = (double)buffer_width / buffer_height; 87 | double bg_ratio = width / height; 88 | 89 | if (window_ratio > bg_ratio) { 90 | double scale = (double)buffer_height / height; 91 | cairo_scale(cairo, scale, scale); 92 | cairo_set_source_surface(cairo, image, 93 | (double)buffer_width / 2 / scale - width / 2, 0); 94 | } else { 95 | double scale = (double)buffer_width / width; 96 | cairo_scale(cairo, scale, scale); 97 | cairo_set_source_surface(cairo, image, 98 | 0, (double)buffer_height / 2 / scale - height / 2); 99 | } 100 | break; 101 | } 102 | case BACKGROUND_MODE_CENTER: 103 | /* 104 | * Align the unscaled image to integer pixel boundaries 105 | * in order to prevent loss of clarity (this only matters 106 | * for odd-sized images). 107 | */ 108 | cairo_set_source_surface(cairo, image, 109 | (int)((double)buffer_width / 2 - width / 2), 110 | (int)((double)buffer_height / 2 - height / 2)); 111 | break; 112 | case BACKGROUND_MODE_TILE: { 113 | cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image); 114 | cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); 115 | cairo_set_source(cairo, pattern); 116 | break; 117 | } 118 | case BACKGROUND_MODE_SOLID_COLOR: 119 | case BACKGROUND_MODE_INVALID: 120 | assert(0); 121 | break; 122 | } 123 | cairo_paint(cairo); 124 | cairo_restore(cairo); 125 | } 126 | -------------------------------------------------------------------------------- /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 | close(comm[0][0]); 93 | close(comm[0][1]); 94 | return false; 95 | } 96 | if (!set_cloexec(comm[0][0]) || !set_cloexec(comm[0][1]) || 97 | !set_cloexec(comm[1][0]) || !set_cloexec(comm[1][1])) { 98 | swaylock_log_errno(LOG_ERROR, "failed to set cloexec"); 99 | close(comm[0][0]); 100 | close(comm[0][1]); 101 | close(comm[1][0]); 102 | close(comm[1][1]); 103 | return false; 104 | } 105 | pid_t child = fork(); 106 | if (child < 0) { 107 | swaylock_log_errno(LOG_ERROR, "failed to fork"); 108 | return false; 109 | } else if (child == 0) { 110 | close(comm[0][1]); 111 | close(comm[1][0]); 112 | run_pw_backend_child(); 113 | } 114 | close(comm[0][0]); 115 | close(comm[1][1]); 116 | return true; 117 | } 118 | 119 | bool write_comm_request(struct swaylock_password *pw) { 120 | bool result = false; 121 | int fd = comm[0][1]; 122 | 123 | size_t size = pw->len + 1; 124 | if (!write_full(fd, &size, sizeof(size))) { 125 | swaylock_log_errno(LOG_ERROR, "Failed to write pw size"); 126 | goto out; 127 | } 128 | 129 | if (!write_full(fd, pw->buffer, size)) { 130 | swaylock_log_errno(LOG_ERROR, "Failed to write pw buffer"); 131 | goto out; 132 | } 133 | 134 | result = true; 135 | 136 | out: 137 | clear_password_buffer(pw); 138 | return result; 139 | } 140 | 141 | bool read_comm_reply(bool *auth_success) { 142 | if (read_full(comm[1][0], auth_success, sizeof(*auth_success)) <= 0) { 143 | swaylock_log(LOG_ERROR, "Failed to read pw result"); 144 | return false; 145 | } 146 | return true; 147 | } 148 | 149 | int get_comm_reply_fd(void) { 150 | return comm[1][0]; 151 | } 152 | -------------------------------------------------------------------------------- /completions/bash/swaylock: -------------------------------------------------------------------------------- 1 | # swaylock-plugin(1) completion 2 | 3 | _swaylock-plugin() 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-plugin swaylock-plugin 123 | -------------------------------------------------------------------------------- /completions/fish/swaylock.fish: -------------------------------------------------------------------------------- 1 | # swaylock-plugin(1) completion 2 | 3 | complete -c swaylock-plugin -l bs-hl-color --description "Sets the color of backspace highlight segments." 4 | complete -c swaylock-plugin -l caps-lock-bs-hl-color --description "Sets the color of backspace highlight segments when Caps Lock is active." 5 | complete -c swaylock-plugin -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-plugin -l color -s c --description "Turn the screen into the given color instead of white." 7 | complete -c swaylock-plugin -l config -s C --description "Path to the config file." 8 | complete -c swaylock-plugin -l daemonize -s f --description "Detach from the controlling terminal after locking." 9 | complete -c swaylock-plugin -l debug -s d --description "Enable debugging output." 10 | complete -c swaylock-plugin -l disable-caps-lock-text -s L --description "Disable the Caps Lock text." 11 | complete -c swaylock-plugin -l font --description "Sets the font of the text." 12 | complete -c swaylock-plugin -l font-size --description "Sets a fixed font size for the indicator text." 13 | complete -c swaylock-plugin -l help -s h --description "Show help message and quit." 14 | complete -c swaylock-plugin -l hide-keyboard-layout -s K --description "Hide the current xkb layout while typing." 15 | complete -c swaylock-plugin -l ignore-empty-password -s e --description "When an empty password is provided, do not validate it." 16 | complete -c swaylock-plugin -l image -s i --description "Display the given image, optionally only on the given output." 17 | complete -c swaylock-plugin -l indicator-caps-lock -s l --description "Show the current Caps Lock state also on the indicator." 18 | complete -c swaylock-plugin -l indicator-idle-visible --description "Sets the indicator to show even if idle." 19 | complete -c swaylock-plugin -l indicator-radius --description "Sets the indicator radius." 20 | complete -c swaylock-plugin -l indicator-thickness --description "Sets the indicator thickness." 21 | complete -c swaylock-plugin -l indicator-x-position --description "Sets the horizontal position of the indicator." 22 | complete -c swaylock-plugin -l indicator-y-position --description "Sets the vertical position of the indicator." 23 | complete -c swaylock-plugin -l inside-caps-lock-color --description "Sets the color of the inside of the indicator when Caps Lock is active." 24 | complete -c swaylock-plugin -l inside-clear-color --description "Sets the color of the inside of the indicator when cleared." 25 | complete -c swaylock-plugin -l inside-color --description "Sets the color of the inside of the indicator." 26 | complete -c swaylock-plugin -l inside-ver-color --description "Sets the color of the inside of the indicator when verifying." 27 | complete -c swaylock-plugin -l inside-wrong-color --description "Sets the color of the inside of the indicator when invalid." 28 | complete -c swaylock-plugin -l key-hl-color --description "Sets the color of the key press highlight segments." 29 | complete -c swaylock-plugin -l layout-bg-color --description "Sets the background color of the box containing the layout text." 30 | complete -c swaylock-plugin -l layout-border-color --description "Sets the color of the border of the box containing the layout text." 31 | complete -c swaylock-plugin -l layout-text-color --description "Sets the color of the layout text." 32 | complete -c swaylock-plugin -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-plugin -l line-clear-color --description "Sets the color of the line between the inside and ring when cleared." 34 | complete -c swaylock-plugin -l line-color --description "Sets the color of the line between the inside and ring." 35 | complete -c swaylock-plugin -l line-uses-inside -s n --description "Use the inside color for the line between the inside and ring." 36 | complete -c swaylock-plugin -l line-uses-ring -s r --description "Use the ring color for the line between the inside and ring." 37 | complete -c swaylock-plugin -l line-ver-color --description "Sets the color of the line between the inside and ring when verifying." 38 | complete -c swaylock-plugin -l line-wrong-color --description "Sets the color of the line between the inside and ring when invalid." 39 | complete -c swaylock-plugin -l no-unlock-indicator -s u --description "Disable the unlock indicator." 40 | complete -c swaylock-plugin -l ring-caps-lock-color --description "Sets the color of the ring of the indicator when Caps Lock is active." 41 | complete -c swaylock-plugin -l ring-clear-color --description "Sets the color of the ring of the indicator when cleared." 42 | complete -c swaylock-plugin -l ring-color --description "Sets the color of the ring of the indicator." 43 | complete -c swaylock-plugin -l ring-ver-color --description "Sets the color of the ring of the indicator when verifying." 44 | complete -c swaylock-plugin -l ring-wrong-color --description "Sets the color of the ring of the indicator when invalid." 45 | complete -c swaylock-plugin -l scaling -s s --description "Image scaling mode: stretch, fill, fit, center, tile, solid_color." 46 | complete -c swaylock-plugin -l separator-color --description "Sets the color of the lines that separate highlight segments." 47 | complete -c swaylock-plugin -l show-failed-attempts -s F --description "Show current count of failed authentication attempts." 48 | complete -c swaylock-plugin -l show-keyboard-layout -s k --description "Display the current xkb layout while typing." 49 | complete -c swaylock-plugin -l text-caps-lock-color --description "Sets the color of the text when Caps Lock is active." 50 | complete -c swaylock-plugin -l text-clear-color --description "Sets the color of the text when cleared." 51 | complete -c swaylock-plugin -l text-color --description "Sets the color of the text." 52 | complete -c swaylock-plugin -l text-ver-color --description "Sets the color of the text when verifying." 53 | complete -c swaylock-plugin -l text-wrong-color --description "Sets the color of the text when invalid." 54 | complete -c swaylock-plugin -l tiling -s t --description "Same as --scaling=tile." 55 | complete -c swaylock-plugin -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_install_dir = datadir + '/zsh/site-functions' 8 | 9 | install_data(['zsh/_swaylock'], rename: ['_swaylock-plugin'], install_dir: zsh_install_dir) 10 | endif 11 | 12 | if get_option('bash-completions') 13 | if bash_comp.found() 14 | bash_install_dir = bash_comp.get_variable('completionsdir') 15 | else 16 | bash_install_dir = datadir + '/bash-completion/completions' 17 | endif 18 | 19 | install_data(['bash/swaylock'], rename: ['swaylock-plugin'], install_dir: bash_install_dir) 20 | endif 21 | 22 | if get_option('fish-completions') 23 | if fish_comp.found() 24 | fish_install_dir = fish_comp.get_variable('completionsdir') 25 | else 26 | fish_install_dir = datadir + '/fish/vendor_completions.d' 27 | endif 28 | 29 | install_data(['fish/swaylock.fish'], rename: ['swaylock-plugin.fish'], install_dir: fish_install_dir) 30 | endif 31 | -------------------------------------------------------------------------------- /completions/zsh/_swaylock: -------------------------------------------------------------------------------- 1 | #compdef swaylock-plugin 2 | # 3 | # Completion script for swaylock-plugin 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 | -------------------------------------------------------------------------------- /example_label_outputs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script, when run under swaylock-plugin --command-each, 4 | # will display the name of each output in the background 5 | 6 | import subprocess 7 | import os 8 | import tempfile 9 | 10 | text = os.getenv("SWAYLOCK_PLUGIN_OUTPUT_NAME", default="Use 'each'") 11 | text = text.replace("&", "&").replace("<", "<").replace(">", ">") 12 | template = """ 13 | 15 | {} 16 | 17 | """ 18 | with tempfile.NamedTemporaryFile(suffix=".svg") as f: 19 | f.write(template.format(text).encode()) 20 | f.flush() 21 | 22 | command = ["swaybg", "-m", "fit", "-i", f.name] 23 | subprocess.run(command, close_fds=False) 24 | -------------------------------------------------------------------------------- /example_rotate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | file=`ls $1 | shuf -n 1` 3 | delay=60. 4 | echo "Runnning swaybg for $delay secs on: $1/$file" 5 | timeout $delay swaybg -i $1/$file 6 | -------------------------------------------------------------------------------- /forward.c: -------------------------------------------------------------------------------- 1 | #include "swaylock.h" 2 | 3 | #include "log.h" 4 | #include "loop.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "linux-dmabuf-unstable-v1-server-protocol.h" 13 | #include "wayland-drm-server-protocol.h" 14 | #include "wlr-layer-shell-unstable-v1-server-protocol.h" 15 | #include "ext-session-lock-v1-client-protocol.h" 16 | #include "fractional-scale-v1-server-protocol.h" 17 | #include "viewporter-server-protocol.h" 18 | 19 | static const struct wl_surface_interface surface_impl; 20 | static const struct wl_buffer_interface buffer_impl; 21 | static const struct wl_shm_pool_interface shm_pool_impl; 22 | static const struct wl_compositor_interface compositor_impl; 23 | static const struct zwp_linux_buffer_params_v1_interface linux_dmabuf_params_impl; 24 | static const struct zwp_linux_dmabuf_feedback_v1_interface linux_dmabuf_feedback_v1_impl; 25 | static const struct wp_viewport_interface viewport_impl; 26 | static const struct wp_fractional_scale_v1_interface fractional_scale_impl; 27 | 28 | struct forward_params { 29 | struct zwp_linux_buffer_params_v1* params; 30 | struct wl_resource *resource; 31 | int32_t width; 32 | int32_t height; 33 | }; 34 | 35 | static bool does_transform_transpose_size(int32_t transform) { 36 | switch (transform) { 37 | default: 38 | case WL_OUTPUT_TRANSFORM_NORMAL: 39 | case WL_OUTPUT_TRANSFORM_180: 40 | case WL_OUTPUT_TRANSFORM_FLIPPED: 41 | case WL_OUTPUT_TRANSFORM_FLIPPED_180: 42 | return false; 43 | case WL_OUTPUT_TRANSFORM_90: 44 | case WL_OUTPUT_TRANSFORM_270: 45 | case WL_OUTPUT_TRANSFORM_FLIPPED_90: 46 | case WL_OUTPUT_TRANSFORM_FLIPPED_270: 47 | return false; 48 | } 49 | } 50 | 51 | static void nested_surface_destroy(struct wl_client *client, 52 | struct wl_resource *resource) { 53 | // this will also destroy the user_data. 54 | wl_resource_destroy(resource); 55 | } 56 | static void nested_surface_attach(struct wl_client *client, 57 | struct wl_resource *resource, struct wl_resource *buffer, int32_t x, int32_t y) { 58 | assert(wl_resource_instance_of(resource, &wl_surface_interface, &surface_impl)); 59 | struct forward_surface *surface = wl_resource_get_user_data(resource); 60 | struct forward_buffer *f_buffer = NULL; 61 | if (buffer) { 62 | assert(wl_resource_instance_of(buffer, &wl_buffer_interface, &buffer_impl)); 63 | f_buffer = wl_resource_get_user_data(buffer); 64 | } 65 | 66 | if (surface->pending.attachment == f_buffer) { 67 | /* no change */ 68 | return; 69 | } 70 | if (surface->pending.attachment != NULL && surface->pending.attachment != BUFFER_COMMITTED) { 71 | /* Dereference pending buffer */ 72 | struct forward_buffer *old_buf = surface->pending.attachment; 73 | wl_list_remove(&surface->pending.attachment_link); 74 | /* Remove old buffer if no links to it left */ 75 | if (old_buf->resource == NULL && wl_list_empty(&old_buf->pending_surfaces)) { 76 | assert(wl_list_empty(&old_buf->committed_surfaces)); 77 | wl_buffer_destroy(old_buf->buffer); 78 | free(old_buf); 79 | } 80 | } 81 | 82 | wl_list_insert(&f_buffer->pending_surfaces, &surface->pending.attachment_link); 83 | surface->pending.attachment = f_buffer; 84 | } 85 | static void nested_surface_damage(struct wl_client *client, 86 | struct wl_resource *resource, int32_t x, int32_t y, 87 | int32_t width, int32_t height) { 88 | assert(wl_resource_instance_of(resource, &wl_surface_interface, &surface_impl)); 89 | struct forward_surface *surface = wl_resource_get_user_data(resource); 90 | 91 | struct damage_record *new_damage = realloc(surface->old_damage, sizeof(struct damage_record) * (surface->old_damage_len + 1)); 92 | if (new_damage) { 93 | surface->old_damage = new_damage; 94 | surface->old_damage[surface->old_damage_len].x = x; 95 | surface->old_damage[surface->old_damage_len].y = y; 96 | surface->old_damage[surface->old_damage_len].w = width; 97 | surface->old_damage[surface->old_damage_len].h = height; 98 | surface->old_damage_len++; 99 | } 100 | } 101 | 102 | static void frame_callback_handle_resource_destroy(struct wl_resource *resource) { 103 | assert(wl_resource_instance_of(resource, &wl_callback_interface, NULL)); 104 | 105 | wl_list_remove(wl_resource_get_link(resource)); 106 | } 107 | static void nested_surface_frame(struct wl_client *client, 108 | struct wl_resource *resource, uint32_t callback) { 109 | assert(wl_resource_instance_of(resource, &wl_surface_interface, &surface_impl)); 110 | 111 | struct wl_resource *callback_resource = wl_resource_create(client, &wl_callback_interface, 112 | wl_resource_get_version(resource), callback); 113 | if (callback_resource == NULL) { 114 | wl_client_post_no_memory(client); 115 | return; 116 | } 117 | wl_resource_set_implementation(callback_resource, NULL, 118 | NULL, frame_callback_handle_resource_destroy); 119 | 120 | struct forward_surface *surface = wl_resource_get_user_data(resource); 121 | struct wl_list *link = wl_resource_get_link(callback_resource); 122 | wl_list_insert(&surface->frame_callbacks, link); 123 | } 124 | static void nested_surface_set_opaque_region(struct wl_client *client, 125 | struct wl_resource *resource, struct wl_resource *region) { 126 | // do nothing, swaylock doesn't need to know about regions 127 | } 128 | static void nested_surface_set_input_region(struct wl_client *client, 129 | struct wl_resource *resource, struct wl_resource *region) { 130 | // do nothing, swaylock doesn't need to know about regions 131 | } 132 | 133 | void add_serial_pair(struct forward_surface *surf, uint32_t upstream_serial, 134 | uint32_t downstream_serial, uint32_t width, uint32_t height, bool local_only) { 135 | surf->serial_table = realloc(surf->serial_table, sizeof(struct serial_pair) * (surf->serial_table_len + 1)); 136 | assert(surf->serial_table); 137 | 138 | surf->serial_table[surf->serial_table_len] = (struct serial_pair) { 139 | .plugin_serial = downstream_serial, 140 | .upstream_serial = upstream_serial, 141 | .config_width = width, 142 | .config_height = height, 143 | .local_only = local_only, 144 | }; 145 | surf->serial_table_len++; 146 | } 147 | 148 | static void bg_frame_handle_done(void *data, struct wl_callback *callback, 149 | uint32_t time) { 150 | (void)time; 151 | struct forward_surface *surface = data; 152 | 153 | // Trigger all frame callbacks for the background 154 | struct wl_resource *plugin_cb, *tmp; 155 | wl_resource_for_each_safe(plugin_cb, tmp, &surface->frame_callbacks) { 156 | wl_callback_send_done(plugin_cb, 0); 157 | wl_resource_destroy(plugin_cb); 158 | } 159 | wl_callback_destroy(callback); 160 | } 161 | 162 | static const struct wl_callback_listener bg_frame_listener = { 163 | .done = bg_frame_handle_done, 164 | }; 165 | 166 | static void nested_surface_commit(struct wl_client *client, 167 | struct wl_resource *resource) { 168 | assert(wl_resource_instance_of(resource, &wl_surface_interface, &surface_impl)); 169 | struct forward_surface *surface = wl_resource_get_user_data(resource); 170 | if (surface->inert) { 171 | return; 172 | } 173 | 174 | if (!surface->sway_surface) { 175 | /* Clients can create and commit to any number of wl_surfaces; however, 176 | * these have no impact until the surface is given a role. Ignore these 177 | * commits. */ 178 | return; 179 | } 180 | 181 | if (!surface->has_been_configured) { 182 | /* send initial configure */ 183 | struct swaylock_bg_client *bg_client = surface->sway_surface->client ? 184 | surface->sway_surface->client : surface->sway_surface->state->server.main_client; 185 | uint32_t plugin_serial = bg_client->serial++; 186 | 187 | uint32_t config_width = surface->sway_surface->width, config_height = surface->sway_surface->height; 188 | if (config_width == 0 || config_height == 0) { 189 | swaylock_log(LOG_ERROR, "committing nested surface before main surface dimensions known"); 190 | } 191 | 192 | // When committing a plugin surface for the first time, 193 | // if the upstream surface is also new, then forward the configure; 194 | // but if the upsteam surface was configured long ago, then 195 | // keep the configure local 196 | if (!surface->sway_surface->used_first_configure) { 197 | add_serial_pair(surface, surface->sway_surface->first_configure_serial, plugin_serial, 198 | config_width, config_height, false); 199 | surface->sway_surface->used_first_configure = true; 200 | } else if (surface->sway_surface->has_newer_serial) { 201 | /* In this case, the swaylock surface has received 202 | * unacknowledged configures that the previous client 203 | * for the surface did not acknowledge. Since we are 204 | * giving this client an up to date size, acknowledge 205 | * the corresponding configure when the client finally 206 | * responds. */ 207 | add_serial_pair(surface, surface->sway_surface->newest_serial, plugin_serial, 208 | config_width, config_height, false); 209 | } else { 210 | /* Swallow plugin's configure event -- all upstream configures 211 | * were acknowledged by past clients */ 212 | add_serial_pair(surface, 0, plugin_serial, config_width, config_height, true); 213 | } 214 | zwlr_layer_surface_v1_send_configure(surface->layer_surface, plugin_serial, 215 | config_width, config_height); 216 | 217 | surface->has_been_configured = true; 218 | 219 | // todo: handle unmap/remap logic -- is it an error to attach a buffer 220 | // after unmapping? 221 | assert(!surface->pending.attachment); 222 | /* The first commit should not be forwarded, because the main swaylock 223 | * process already made such a commit in order to receive its 224 | * own configure event. Thus, return here. */ 225 | return; 226 | } 227 | 228 | if (surface->committed.attachment == NULL && surface->pending.attachment == NULL) { 229 | /* In this scenario, no buffer has been attached yet; there is no point in making 230 | * a second or further commit without a buffer, so don't bother committing anything. 231 | * (Note: other than the buffer, the surface state has nothing that risks dangling 232 | * if it neglects to commit, and there is no attached buffer.) */ 233 | return; 234 | } 235 | if (surface->committed.attachment != NULL && surface->pending.attachment == NULL) { 236 | /* Good wallpaper clients should never unmap their surfaces. Kill it. */ 237 | wl_resource_post_error(resource, 1000, "The wallpaper program should not unmap any layer shell surface"); 238 | return; 239 | } 240 | 241 | // todo: every buffer needs surface backreferences for auto-cleanup 242 | // issue: figuring out details of this auto-cleanup 243 | // (one approach: use a forward_buffer object holding the upstream, 244 | // with a linked list of downstream users -- the plugin's wl_buffer 245 | // itself, but also all surfaces linked via commits. Only delete upstream 246 | // wl_buffer when all references are dead.) 247 | 248 | // integrate details, and commit/send updated data only, here 249 | 250 | struct swaylock_surface *sw_surf = surface->sway_surface; 251 | struct wl_surface *background = sw_surf->surface; 252 | 253 | /* Apply changes */ 254 | if (surface->committed.buffer_scale != surface->pending.buffer_scale) { 255 | wl_surface_set_buffer_scale(background, surface->pending.buffer_scale); 256 | surface->committed.buffer_scale = surface->pending.buffer_scale; 257 | } 258 | if (surface->committed.buffer_transform != surface->pending.buffer_transform) { 259 | wl_surface_set_buffer_transform(background, surface->pending.buffer_transform); 260 | surface->committed.buffer_transform = surface->pending.buffer_transform; 261 | } 262 | if (surface->committed.viewport_dest_width != surface->pending.viewport_dest_width || 263 | surface->committed.viewport_dest_height != surface->pending.viewport_dest_height) { 264 | assert(sw_surf->viewport); 265 | wp_viewport_set_destination(sw_surf->viewport, surface->pending.viewport_dest_width, 266 | surface->pending.viewport_dest_height); 267 | surface->committed.viewport_dest_width = surface->pending.viewport_dest_width; 268 | surface->committed.viewport_dest_height = surface->pending.viewport_dest_height; 269 | } 270 | if (surface->committed.viewport_source_x != surface->pending.viewport_source_x || 271 | surface->committed.viewport_source_y != surface->pending.viewport_source_y || 272 | surface->committed.viewport_source_w != surface->pending.viewport_source_w || 273 | surface->committed.viewport_source_h != surface->pending.viewport_source_h) { 274 | assert(sw_surf->viewport); 275 | wp_viewport_set_source(sw_surf->viewport, 276 | surface->committed.viewport_source_x, surface->committed.viewport_source_y, 277 | surface->committed.viewport_source_w, surface->committed.viewport_source_h); 278 | surface->committed.viewport_source_x = surface->pending.viewport_source_x; 279 | surface->committed.viewport_source_y = surface->pending.viewport_source_y; 280 | surface->committed.viewport_source_w = surface->pending.viewport_source_w; 281 | surface->committed.viewport_source_h = surface->pending.viewport_source_h; 282 | } 283 | // The protocol does not make this fully explicit, but the buffer should 284 | // be attached _each time_ that any damage is sent alongside it, even if 285 | // the buffer is the same. This is also necessary to ensure that the 286 | // appropriate release events are sent 287 | if (surface->pending.attachment != BUFFER_COMMITTED) { 288 | /* unlink the committed attachment */ 289 | if (surface->committed.attachment != NULL && surface->committed.attachment != BUFFER_UNREACHABLE) { 290 | assert(surface->committed.attachment->resource != NULL); 291 | wl_list_remove(&surface->committed.attachment_link); 292 | } 293 | 294 | /* See above: null attachments are either bad wallpaper program behavior or need no commit */ 295 | assert(surface->pending.attachment != NULL); 296 | 297 | struct forward_buffer *upstream_buffer = surface->pending.attachment; 298 | int32_t offset_x = wl_resource_get_version(resource) >= 5 ? 0 : surface->pending.offset_x; 299 | int32_t offset_y = wl_resource_get_version(resource) >= 5 ? 0 : surface->pending.offset_y; 300 | wl_surface_attach(background, 301 | upstream_buffer ? upstream_buffer->buffer : NULL, 302 | offset_x, offset_y); 303 | if (wl_resource_get_version(resource) < 5) { 304 | surface->committed.offset_x = surface->pending.offset_x; 305 | surface->committed.offset_y = surface->pending.offset_y; 306 | } 307 | surface->committed.attachment = surface->pending.attachment; 308 | 309 | surface->committed_buffer_width = upstream_buffer->width; 310 | surface->committed_buffer_height = upstream_buffer->height; 311 | wl_list_insert(&upstream_buffer->committed_surfaces, &surface->committed.attachment_link); 312 | } 313 | 314 | wl_fixed_t n = wl_fixed_from_int(-1); 315 | bool viewport_dst_on = surface->committed.viewport_dest_width != -1; 316 | bool viewport_src_on = surface->committed.viewport_source_w != n; 317 | uint32_t output_width, output_height; 318 | if (viewport_dst_on) { 319 | output_width = surface->committed.viewport_dest_width; 320 | output_height = surface->committed.viewport_dest_height; 321 | } else if (viewport_src_on) { 322 | output_width = wl_fixed_to_int(surface->committed.viewport_source_w); 323 | output_height = wl_fixed_to_int(surface->committed.viewport_source_h); 324 | if (wl_fixed_from_int(output_width) != surface->committed.viewport_source_w || 325 | wl_fixed_from_int(output_height) != surface->committed.viewport_source_h) { 326 | wl_resource_post_error(surface->viewport, WP_VIEWPORT_ERROR_BAD_SIZE, "width/height not integral"); 327 | return; 328 | } 329 | // TODO: technically, should also validate that the viewport dimensions fall inside the 330 | // (transformed) buffer bounding box 331 | } else { 332 | if (surface->committed_buffer_width % surface->committed.buffer_scale != 0 || 333 | surface->committed_buffer_height % surface->committed.buffer_scale != 0) { 334 | wl_resource_post_error(resource, WL_SURFACE_ERROR_INVALID_SIZE, "buffer dimensions not divisible by scale"); 335 | return; 336 | } 337 | output_width = surface->committed_buffer_width / surface->committed.buffer_scale; 338 | output_height = surface->committed_buffer_height / surface->committed.buffer_scale; 339 | if (does_transform_transpose_size(surface->committed.buffer_transform)) { 340 | uint32_t tmp = output_width; 341 | output_width = output_height; 342 | output_height = tmp; 343 | } 344 | } 345 | if (output_width != surface->last_acked_width || output_height != surface->last_acked_height) { 346 | swaylock_log(LOG_ERROR, "Wallpaper program committed surface at size %d x %d, which does not exactly match last acknowledged W x H = %d x %d", 347 | output_width, output_height, surface->last_acked_width, surface->last_acked_height); 348 | wl_resource_post_error(resource, 1000, "The wallpaper program should exactly match the configure width/height"); 349 | return; 350 | } 351 | 352 | // TODO: verify that on scale or attachment change, the resulting size exactly matches the output 353 | 354 | /* If there was an offset change, but no buffer value change */ 355 | if ((surface->committed.offset_x != surface->pending.offset_x || 356 | surface->committed.offset_y != surface->pending.offset_y) && wl_resource_get_version(resource) >= 5) { 357 | wl_surface_offset(background, surface->pending.offset_x, surface->pending.offset_y); 358 | surface->committed.offset_x = surface->pending.offset_x; 359 | surface->committed.offset_y = surface->pending.offset_y; 360 | } 361 | 362 | /* apply and clear damage */ 363 | for (size_t i = 0; i < surface->buffer_damage_len; i++) { 364 | wl_surface_damage_buffer(background, surface->buffer_damage[i].x, 365 | surface->buffer_damage[i].y, 366 | surface->buffer_damage[i].w, 367 | surface->buffer_damage[i].h); 368 | } 369 | for (size_t i = 0; i < surface->old_damage_len; i++) { 370 | wl_surface_damage(background, surface->old_damage[i].x, 371 | surface->old_damage[i].y, 372 | surface->old_damage[i].w, 373 | surface->old_damage[i].h); 374 | } 375 | 376 | free(surface->buffer_damage); 377 | surface->buffer_damage = NULL; 378 | surface->buffer_damage_len = 0; 379 | 380 | free(surface->old_damage); 381 | surface->old_damage = NULL; 382 | surface->old_damage_len = 0; 383 | 384 | /* Finally, commit updates to corresponding upstream background surface */ 385 | if (surface->committed.attachment) { 386 | // permit subsurface drawing 387 | surface->sway_surface->has_buffer = true; 388 | } 389 | 390 | if (!wl_list_empty(&surface->frame_callbacks)) { 391 | /* plugin has requested frame callbacks, so make a request now */ 392 | struct wl_callback *callback = wl_surface_frame(background); 393 | wl_callback_add_listener(callback, &bg_frame_listener, surface); 394 | } 395 | 396 | if (sw_surf->has_pending_ack_conf) { 397 | /* Submit this right before the commit, to avoid race conditions 398 | * between injected commits from the swaylock rendering and 399 | * the gap between ack and commit from the plugin */ 400 | ext_session_lock_surface_v1_ack_configure(sw_surf->ext_session_lock_surface_v1, sw_surf->pending_upstream_serial); 401 | sw_surf->has_pending_ack_conf = false; 402 | if (sw_surf->pending_upstream_serial == sw_surf->newest_serial) { 403 | sw_surf->has_newer_serial = false; 404 | } 405 | } 406 | 407 | if (sw_surf->client_submission_timer) { 408 | /* Disarm timer, indicating that plugin have responded on time 409 | * for this output. */ 410 | loop_remove_timer(sw_surf->state->eventloop, sw_surf->client_submission_timer); 411 | sw_surf->client_submission_timer = NULL; 412 | } 413 | 414 | wl_surface_commit(background); 415 | } 416 | 417 | static void nested_surface_set_buffer_transform(struct wl_client *client, 418 | struct wl_resource *resource, int32_t transform) { 419 | assert(wl_resource_instance_of(resource, &wl_surface_interface, &surface_impl)); 420 | struct forward_surface *surface = wl_resource_get_user_data(resource); 421 | surface->pending.buffer_transform = transform; 422 | // TODO: validate that the transform is valid; 423 | } 424 | static void nested_surface_set_buffer_scale(struct wl_client *client, 425 | struct wl_resource *resource, int32_t scale) { 426 | assert(wl_resource_instance_of(resource, &wl_surface_interface, &surface_impl)); 427 | struct forward_surface *surface = wl_resource_get_user_data(resource); 428 | surface->pending.buffer_scale = scale; 429 | } 430 | static void nested_surface_damage_buffer(struct wl_client *client, 431 | struct wl_resource *resource, int32_t x, int32_t y, 432 | int32_t width, int32_t height) { 433 | assert(wl_resource_instance_of(resource, &wl_surface_interface, &surface_impl)); 434 | struct forward_surface *surface = wl_resource_get_user_data(resource); 435 | 436 | struct damage_record *new_damage = realloc(surface->buffer_damage, sizeof(struct damage_record) * (surface->buffer_damage_len + 1)); 437 | if (new_damage) { 438 | surface->buffer_damage = new_damage; 439 | surface->buffer_damage[surface->buffer_damage_len].x = x; 440 | surface->buffer_damage[surface->buffer_damage_len].y = y; 441 | surface->buffer_damage[surface->buffer_damage_len].w = width; 442 | surface->buffer_damage[surface->buffer_damage_len].h = height; 443 | surface->buffer_damage_len++; 444 | } 445 | } 446 | 447 | static const struct wl_surface_interface surface_impl = { 448 | .destroy = nested_surface_destroy, 449 | .attach = nested_surface_attach, 450 | .damage = nested_surface_damage, 451 | .frame = nested_surface_frame, 452 | .set_opaque_region = nested_surface_set_opaque_region, 453 | .set_input_region = nested_surface_set_input_region, 454 | .commit = nested_surface_commit, 455 | .set_buffer_transform = nested_surface_set_buffer_transform, 456 | .set_buffer_scale = nested_surface_set_buffer_scale, 457 | .damage_buffer = nested_surface_damage_buffer, 458 | }; 459 | 460 | static void surface_handle_resource_destroy(struct wl_resource *resource) { 461 | assert(wl_resource_instance_of(resource, &wl_surface_interface, &surface_impl)); 462 | struct forward_surface *fwd_surface = wl_resource_get_user_data(resource); 463 | if (fwd_surface->sway_surface) { 464 | fwd_surface->sway_surface->plugin_surface = NULL; 465 | } 466 | 467 | struct wl_resource *cb_resource, *tmp; 468 | wl_resource_for_each_safe(cb_resource, tmp, &fwd_surface->frame_callbacks) { 469 | // the callback resource, on destruction, will try to remove itself, 470 | // so set it up with an empty list (on which _remove() is safe) 471 | wl_list_remove(wl_resource_get_link(cb_resource)); 472 | wl_list_init(wl_resource_get_link(cb_resource)); 473 | } 474 | if (fwd_surface->pending.attachment 475 | && fwd_surface->pending.attachment != BUFFER_UNREACHABLE 476 | && fwd_surface->pending.attachment != BUFFER_COMMITTED) { 477 | assert(fwd_surface->pending.attachment->resource != NULL); 478 | wl_list_remove(&fwd_surface->pending.attachment_link); 479 | } 480 | if (fwd_surface->committed.attachment 481 | && fwd_surface->committed.attachment != BUFFER_UNREACHABLE 482 | && fwd_surface->committed.attachment != BUFFER_COMMITTED) { 483 | assert(fwd_surface->committed.attachment->resource != NULL); 484 | wl_list_remove(&fwd_surface->committed.attachment_link); 485 | } 486 | 487 | free(fwd_surface->buffer_damage); 488 | free(fwd_surface->old_damage); 489 | free(fwd_surface->serial_table); 490 | 491 | if (fwd_surface->viewport) { 492 | wl_resource_set_user_data(fwd_surface->viewport, NULL); 493 | } 494 | if (fwd_surface->fractional_scale) { 495 | wl_resource_set_user_data(fwd_surface->fractional_scale, NULL); 496 | } 497 | 498 | free(fwd_surface); 499 | } 500 | 501 | static void default_surface_state(struct surface_state *state) { 502 | wl_fixed_t n = wl_fixed_from_int(-1); 503 | state->viewport_dest_height = -1; 504 | state->viewport_dest_width = -1; 505 | state->viewport_source_x = n; 506 | state->viewport_source_y = n; 507 | state->viewport_source_w = n; 508 | state->viewport_source_h = n; 509 | state->buffer_scale = 1; 510 | state->buffer_transform = WL_OUTPUT_TRANSFORM_NORMAL; 511 | state->offset_x = 0; 512 | state->offset_y = 0; 513 | state->attachment = NULL; 514 | // state->attachment_link is only used when attachment is not NULL 515 | } 516 | 517 | static void compositor_create_surface(struct wl_client *client, 518 | struct wl_resource *resource, uint32_t id) { 519 | assert(wl_resource_instance_of(resource, &wl_compositor_interface, &compositor_impl)); 520 | 521 | struct wl_resource *surf_resource = wl_resource_create(client, &wl_surface_interface, 522 | wl_resource_get_version(resource), id); 523 | if (surf_resource == NULL) { 524 | wl_client_post_no_memory(client); 525 | return; 526 | } 527 | 528 | struct forward_surface *fwd_surface = calloc(1, sizeof(struct forward_surface)); 529 | if (!fwd_surface) { 530 | wl_client_post_no_memory(client); 531 | return; 532 | } 533 | wl_list_init(&fwd_surface->frame_callbacks); 534 | default_surface_state(&fwd_surface->pending); 535 | default_surface_state(&fwd_surface->committed); 536 | 537 | wl_resource_set_implementation(surf_resource, &surface_impl, 538 | fwd_surface, surface_handle_resource_destroy); 539 | 540 | // do not listen for events, because the plugin has no input anyway 541 | } 542 | 543 | static void region_add(struct wl_client *client, struct wl_resource *resource, 544 | int32_t x, int32_t y, int32_t width, int32_t height) { 545 | // do nothing, swaylock doesn't need to know about regions 546 | } 547 | 548 | static void region_subtract(struct wl_client *client, struct wl_resource *resource, 549 | int32_t x, int32_t y, int32_t width, int32_t height) { 550 | // do nothing, swaylock doesn't need to know about regions 551 | } 552 | 553 | static void region_destroy(struct wl_client *client, struct wl_resource *resource) { 554 | wl_resource_destroy(resource); 555 | } 556 | static const struct wl_region_interface region_impl = { 557 | .destroy = region_destroy, 558 | .add = region_add, 559 | .subtract = region_subtract, 560 | }; 561 | 562 | static void compositor_create_region(struct wl_client *client, 563 | struct wl_resource *resource, uint32_t id) { 564 | /* for nested clients, regions are ignored entirely */ 565 | struct wl_resource *region_resource = wl_resource_create(client, 566 | &wl_region_interface, wl_resource_get_version(resource), id); 567 | if (region_resource == NULL) { 568 | wl_client_post_no_memory(client); 569 | return; 570 | } 571 | wl_resource_set_implementation(region_resource, ®ion_impl, NULL, NULL); 572 | } 573 | 574 | static const struct wl_compositor_interface compositor_impl = { 575 | .create_surface = compositor_create_surface, 576 | .create_region = compositor_create_region, 577 | }; 578 | void bind_wl_compositor(struct wl_client *client, void *data, 579 | uint32_t version, uint32_t id) { 580 | struct wl_resource *resource = 581 | wl_resource_create(client, &wl_compositor_interface, version, id); 582 | if (resource == NULL) { 583 | wl_client_post_no_memory(client); 584 | return; 585 | } 586 | wl_resource_set_implementation(resource, &compositor_impl, data, NULL); 587 | } 588 | 589 | 590 | static void nested_buffer_destroy(struct wl_client *client, struct wl_resource *resource) { 591 | wl_resource_destroy(resource); 592 | } 593 | static void handle_buffer_release(void *data, struct wl_buffer *wl_buffer) { 594 | struct forward_buffer *buffer = data; 595 | if (buffer->resource) { 596 | wl_buffer_send_release(buffer->resource); 597 | } 598 | } 599 | static const struct wl_buffer_interface buffer_impl = { 600 | .destroy = nested_buffer_destroy, 601 | }; 602 | static const struct wl_buffer_listener buffer_listener = { 603 | .release = handle_buffer_release 604 | }; 605 | static void buffer_handle_resource_destroy(struct wl_resource *resource) { 606 | assert(wl_resource_instance_of(resource, &wl_buffer_interface, &buffer_impl)); 607 | struct forward_buffer* buffer = wl_resource_get_user_data(resource); 608 | /* The plugin can not longer attach the buffer, so clean up all 609 | * places where it is committed. */ 610 | struct forward_surface *surface, *tmp; 611 | wl_list_for_each_safe(surface, tmp, &buffer->committed_surfaces, committed.attachment_link) { 612 | if (surface->pending.attachment == surface->committed.attachment) { 613 | surface->pending.attachment = BUFFER_COMMITTED; 614 | wl_list_remove(&surface->pending.attachment_link); 615 | } 616 | surface->committed.attachment = BUFFER_UNREACHABLE; 617 | wl_list_remove(&surface->committed.attachment_link); 618 | } 619 | 620 | if (wl_list_empty(&buffer->pending_surfaces)) { 621 | wl_buffer_destroy(buffer->buffer); 622 | free(buffer); 623 | } else { 624 | buffer->resource = NULL; 625 | } 626 | } 627 | static void nested_shm_pool_create_buffer(struct wl_client *client, 628 | struct wl_resource *resource, uint32_t id, 629 | int32_t offset, int32_t width, int32_t height, 630 | int32_t stride, uint32_t format) { 631 | assert(wl_resource_instance_of(resource, &wl_shm_pool_interface, &shm_pool_impl)); 632 | struct wl_shm_pool *shm_pool = wl_resource_get_user_data(resource); 633 | 634 | struct wl_resource *buf_resource = wl_resource_create(client, &wl_buffer_interface, 635 | wl_resource_get_version(resource), id); 636 | if (buf_resource == NULL) { 637 | wl_client_post_no_memory(client); 638 | return; 639 | } 640 | 641 | struct forward_buffer *buffer = calloc(1, sizeof(struct forward_buffer)); 642 | if (!buffer) { 643 | wl_client_post_no_memory(client); 644 | return; 645 | } 646 | buffer->resource = buf_resource; 647 | wl_list_init(&buffer->pending_surfaces); 648 | wl_list_init(&buffer->committed_surfaces); 649 | buffer->width = width; 650 | buffer->height = height; 651 | 652 | buffer->buffer = wl_shm_pool_create_buffer(shm_pool, 653 | offset, width, height, stride, format); 654 | if (!buffer->buffer) { 655 | wl_client_post_no_memory(client); 656 | return; 657 | } 658 | wl_resource_set_implementation(buf_resource, &buffer_impl, 659 | buffer, buffer_handle_resource_destroy); 660 | 661 | wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); 662 | } 663 | static void nested_shm_pool_destroy(struct wl_client *client, 664 | struct wl_resource *resource) { 665 | wl_resource_destroy(resource); 666 | } 667 | static void nested_shm_pool_resize(struct wl_client *client, 668 | struct wl_resource *resource, int32_t size) { 669 | assert(wl_resource_instance_of(resource, &wl_shm_pool_interface, &shm_pool_impl)); 670 | struct wl_shm_pool* shm_pool = wl_resource_get_user_data(resource); 671 | wl_shm_pool_resize(shm_pool, size); 672 | } 673 | 674 | static const struct wl_shm_pool_interface shm_pool_impl = { 675 | .create_buffer = nested_shm_pool_create_buffer, 676 | .destroy = nested_shm_pool_destroy, 677 | .resize = nested_shm_pool_resize, 678 | }; 679 | 680 | static void shm_pool_handle_resource_destroy(struct wl_resource *resource) { 681 | assert(wl_resource_instance_of(resource, &wl_shm_pool_interface, &shm_pool_impl)); 682 | struct wl_shm_pool* shm_pool = wl_resource_get_user_data(resource); 683 | wl_shm_pool_destroy(shm_pool); 684 | } 685 | static void shm_create_pool(struct wl_client *client, struct wl_resource *resource, 686 | uint32_t id, int32_t fd, int32_t size) { 687 | struct wl_resource *pool_resource = wl_resource_create(client, &wl_shm_pool_interface, 688 | wl_resource_get_version(resource), id); 689 | if (pool_resource == NULL) { 690 | close(fd); 691 | wl_client_post_no_memory(client); 692 | return; 693 | } 694 | 695 | struct forward_state *server = wl_resource_get_user_data(resource); 696 | struct wl_shm *shm = server->shm; 697 | struct wl_shm_pool *shm_pool = wl_shm_create_pool(shm, fd, size); 698 | close(fd); 699 | 700 | wl_resource_set_implementation(pool_resource, &shm_pool_impl, 701 | shm_pool, shm_pool_handle_resource_destroy); 702 | } 703 | 704 | static const struct wl_shm_interface shm_impl = { 705 | .create_pool = shm_create_pool 706 | }; 707 | 708 | void bind_wl_shm(struct wl_client *client, void *data, 709 | uint32_t version, uint32_t id) { 710 | struct wl_resource *resource = 711 | wl_resource_create(client, &wl_shm_interface, version, id); 712 | if (resource == NULL) { 713 | wl_client_post_no_memory(client); 714 | return; 715 | } 716 | struct forward_state *forward = data; 717 | for (size_t i = 0; i < forward->shm_formats_len; i++) { 718 | wl_shm_send_format(resource, forward->shm_formats[i]); 719 | } 720 | wl_resource_set_implementation(resource, &shm_impl, forward, NULL); 721 | } 722 | 723 | 724 | static void nested_dmabuf_params_destroy(struct wl_client *client, 725 | struct wl_resource *resource) { 726 | wl_resource_destroy(resource); 727 | } 728 | static void nested_dmabuf_params_add(struct wl_client *client, 729 | struct wl_resource *resource, int32_t fd, uint32_t plane_idx, 730 | uint32_t offset, uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo) { 731 | assert(wl_resource_instance_of(resource, &zwp_linux_buffer_params_v1_interface, &linux_dmabuf_params_impl)); 732 | struct forward_params* params = wl_resource_get_user_data(resource); 733 | zwp_linux_buffer_params_v1_add(params->params, fd, plane_idx, offset, stride, modifier_hi, modifier_lo); 734 | close(fd); 735 | } 736 | static void nested_dmabuf_params_create(struct wl_client *client, 737 | struct wl_resource *resource, int32_t width, int32_t height, 738 | uint32_t format, uint32_t flags) { 739 | assert(wl_resource_instance_of(resource, &zwp_linux_buffer_params_v1_interface, &linux_dmabuf_params_impl)); 740 | struct forward_params *params = wl_resource_get_user_data(resource); 741 | params->width = width; 742 | params->height = height; 743 | zwp_linux_buffer_params_v1_create(params->params, width, height, format, flags); 744 | } 745 | static struct forward_buffer *make_buffer(int width, int height) { 746 | struct forward_buffer *buffer = calloc(1, sizeof(struct forward_buffer)); 747 | if (!buffer) { 748 | return NULL; 749 | } 750 | wl_list_init(&buffer->pending_surfaces); 751 | wl_list_init(&buffer->committed_surfaces); 752 | buffer->width = width; 753 | buffer->height = height; 754 | return buffer; 755 | } 756 | static void nested_dmabuf_params_create_immed(struct wl_client *client, 757 | struct wl_resource *resource, uint32_t buffer_id, int32_t width, 758 | int32_t height, uint32_t format, uint32_t flags) { 759 | assert(wl_resource_instance_of(resource, &zwp_linux_buffer_params_v1_interface, &linux_dmabuf_params_impl)); 760 | struct wl_resource *buffer_resource = wl_resource_create(client, &wl_buffer_interface, 761 | wl_resource_get_version(resource), buffer_id); 762 | if (buffer_resource == NULL) { 763 | wl_client_post_no_memory(client); 764 | return; 765 | } 766 | 767 | struct forward_params *params = wl_resource_get_user_data(resource); 768 | 769 | struct forward_buffer *buffer = make_buffer(width, height); 770 | if (!buffer) { 771 | wl_client_post_no_memory(client); 772 | return; 773 | } 774 | buffer->resource = buffer_resource; 775 | buffer->buffer = zwp_linux_buffer_params_v1_create_immed(params->params, width, height, format, flags); 776 | if (!buffer->buffer) { 777 | wl_client_post_no_memory(client); 778 | return; 779 | } 780 | 781 | wl_resource_set_implementation(buffer_resource, &buffer_impl, 782 | buffer, buffer_handle_resource_destroy); 783 | 784 | wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); 785 | } 786 | 787 | 788 | static const struct zwp_linux_buffer_params_v1_interface linux_dmabuf_params_impl = { 789 | .destroy = nested_dmabuf_params_destroy, 790 | .add = nested_dmabuf_params_add, 791 | .create = nested_dmabuf_params_create, 792 | .create_immed = nested_dmabuf_params_create_immed, 793 | }; 794 | 795 | static void linux_dmabuf_params_handle_resource_destroy(struct wl_resource *resource) { 796 | assert(wl_resource_instance_of(resource, &zwp_linux_buffer_params_v1_interface, &linux_dmabuf_params_impl)); 797 | struct forward_params* params = wl_resource_get_user_data(resource); 798 | zwp_linux_buffer_params_v1_destroy(params->params); 799 | free(params); 800 | } 801 | 802 | static void nested_linux_dmabuf_destroy(struct wl_client *client, 803 | struct wl_resource *resource){ 804 | wl_resource_destroy(resource); 805 | } 806 | 807 | void handle_dmabuf_params_created(void *data, 808 | struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1, 809 | struct wl_buffer *wl_buffer) { 810 | struct forward_params *params = data; 811 | 812 | struct wl_client *client = wl_resource_get_client(params->resource); 813 | struct wl_resource *buffer_resource = wl_resource_create(client, &wl_buffer_interface, 814 | wl_resource_get_version(params->resource), 0); 815 | if (buffer_resource == NULL) { 816 | wl_client_post_no_memory(client); 817 | return; 818 | } 819 | 820 | struct forward_buffer *buffer = make_buffer(params->width, params->height); 821 | if (!buffer) { 822 | wl_client_post_no_memory(client); 823 | return; 824 | } 825 | buffer->resource = buffer_resource; 826 | buffer->buffer = wl_buffer; 827 | wl_resource_set_implementation(buffer_resource, &buffer_impl, 828 | buffer, buffer_handle_resource_destroy); 829 | wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); 830 | zwp_linux_buffer_params_v1_send_created(params->resource, buffer_resource); 831 | } 832 | 833 | static void handle_dmabuf_params_failed(void *data, 834 | struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1) { 835 | struct forward_params *params = data; 836 | zwp_linux_buffer_params_v1_send_failed(params->resource); 837 | } 838 | 839 | static const struct zwp_linux_buffer_params_v1_listener params_listener = { 840 | .created = handle_dmabuf_params_created, 841 | .failed = handle_dmabuf_params_failed, 842 | }; 843 | 844 | static void nested_linux_dmabuf_create_params(struct wl_client *client, 845 | struct wl_resource *resource, uint32_t params_id) { 846 | struct forward_params *params = calloc(1, sizeof(*params)); 847 | if (!params) { 848 | wl_client_post_no_memory(client); 849 | return; 850 | } 851 | 852 | struct wl_resource *params_resource = wl_resource_create(client, 853 | &zwp_linux_buffer_params_v1_interface, 854 | wl_resource_get_version(resource), params_id); 855 | if (params_resource == NULL) { 856 | free(params); 857 | wl_client_post_no_memory(client); 858 | return; 859 | } 860 | 861 | struct forward_state *forward = wl_resource_get_user_data(resource); 862 | params->resource = params_resource; 863 | params->params = zwp_linux_dmabuf_v1_create_params(forward->linux_dmabuf); 864 | params->width = 0; 865 | params->height = 0; 866 | zwp_linux_buffer_params_v1_add_listener(params->params, ¶ms_listener, 867 | params); 868 | wl_resource_set_implementation(params_resource, &linux_dmabuf_params_impl, 869 | params, linux_dmabuf_params_handle_resource_destroy); 870 | } 871 | 872 | static void nested_dmabuf_feedback_destroy(struct wl_client *client, 873 | struct wl_resource *resource) { 874 | wl_resource_destroy(resource); 875 | } 876 | static const struct zwp_linux_dmabuf_feedback_v1_interface linux_dmabuf_feedback_v1_impl = { 877 | .destroy = nested_dmabuf_feedback_destroy, 878 | }; 879 | 880 | static void linux_dmabuf_feedback_handle_resource_destroy(struct wl_resource *resource) { 881 | wl_list_remove(wl_resource_get_link(resource)); 882 | } 883 | 884 | void send_dmabuf_feedback_data(struct wl_resource *feedback, const struct dmabuf_feedback_state *state) { 885 | assert(wl_resource_instance_of(feedback, &zwp_linux_dmabuf_feedback_v1_interface, &linux_dmabuf_feedback_v1_impl)); 886 | 887 | struct wl_array main_device; 888 | main_device.data = (void*)&state->main_device; 889 | main_device.alloc = 0; 890 | main_device.size = sizeof(dev_t); 891 | zwp_linux_dmabuf_feedback_v1_send_main_device(feedback, &main_device); 892 | if (state->table_fd == -1) { 893 | swaylock_log(LOG_ERROR, "table fd was -1"); 894 | } 895 | zwp_linux_dmabuf_feedback_v1_send_format_table(feedback, state->table_fd, state->table_fd_size); 896 | for (size_t i = 0; i < state->tranches_len; i++) { 897 | struct wl_array tranche_device; 898 | tranche_device.data = (void*)&state->tranches[i].tranche_device; 899 | tranche_device.alloc = 0; 900 | tranche_device.size = sizeof(dev_t); 901 | zwp_linux_dmabuf_feedback_v1_send_tranche_target_device(feedback, &tranche_device); 902 | zwp_linux_dmabuf_feedback_v1_send_tranche_flags(feedback, state->tranches[i].flags); 903 | zwp_linux_dmabuf_feedback_v1_send_tranche_formats(feedback, &state->tranches[i].indices); 904 | zwp_linux_dmabuf_feedback_v1_send_tranche_done(feedback); 905 | } 906 | zwp_linux_dmabuf_feedback_v1_send_done(feedback); 907 | } 908 | 909 | void nested_linux_dmabuf_get_default_feedback(struct wl_client *client, 910 | struct wl_resource *resource, uint32_t id) { 911 | struct wl_resource *feedback_resource = wl_resource_create(client, &zwp_linux_dmabuf_feedback_v1_interface, 912 | wl_resource_get_version(resource), id); 913 | if (feedback_resource == NULL) { 914 | wl_client_post_no_memory(client); 915 | return; 916 | } 917 | 918 | /* The linux-dmabuf protocol docs guarantee that the initial set of 919 | * parameters will be provided _before_ the next roundtrip/wl_display_sync 920 | * returns. This is hard to implement for a nested compositor. 921 | * 922 | * We have two ways to handle this: 923 | * => Override wl_display.sync, to ensure it only returns after we run 924 | * a sync on the upstream. This is fairly awkward to do, because 925 | * wl_display has no 'get_implementation', and we thus can't override 926 | * just wl_display.sync without redoing the other display and 927 | * registry code. (This may be unavoidable if we ever would like for a 928 | * useful 'get_surface_feedback', that we can't easily emulate.) 929 | * => Store all the `get_default_feedback` data received by upstream, 930 | * and replay its values downstream immediately. (This lets us treat 931 | * get_surface_feedback and get_default_feedback identically, and 932 | * gives lower latencies. We can optionally replace the update source 933 | * using get_surface_feedback.) 934 | * 935 | * Currently, the second option is used. 936 | */ 937 | 938 | struct forward_state *forward = wl_resource_get_user_data(resource); 939 | 940 | wl_resource_set_implementation(feedback_resource, &linux_dmabuf_feedback_v1_impl, 941 | NULL, linux_dmabuf_feedback_handle_resource_destroy); 942 | 943 | send_dmabuf_feedback_data(feedback_resource, &forward->current); 944 | 945 | /* register to listen to future changes */ 946 | wl_list_insert(&forward->feedback_instances, wl_resource_get_link(feedback_resource)); 947 | } 948 | 949 | void nested_linux_dmabuf_get_surface_feedback(struct wl_client *client, 950 | struct wl_resource *resource, 951 | uint32_t id, 952 | struct wl_resource *surface) { 953 | struct wl_resource *feedback_resource = wl_resource_create(client, &zwp_linux_dmabuf_feedback_v1_interface, 954 | wl_resource_get_version(resource), id); 955 | if (feedback_resource == NULL) { 956 | wl_client_post_no_memory(client); 957 | return; 958 | } 959 | 960 | struct forward_state *forward = wl_resource_get_user_data(resource); 961 | 962 | wl_resource_set_implementation(feedback_resource, &linux_dmabuf_feedback_v1_impl, 963 | NULL, linux_dmabuf_feedback_handle_resource_destroy); 964 | 965 | send_dmabuf_feedback_data(feedback_resource, &forward->current); 966 | 967 | /* register to listen to future changes */ 968 | wl_list_insert(&forward->feedback_instances, wl_resource_get_link(feedback_resource)); 969 | 970 | /* alternative: instead of subscribing to general changes, ask for feedback from 971 | * upstream. 972 | * 973 | struct forward_surface *fwd_surface = wl_resource_get_user_data(surface); 974 | struct zwp_linux_dmabuf_feedback_v1 *feedback = 975 | zwp_linux_dmabuf_v1_get_surface_feedback(forward->linux_dmabuf, fwd_surface->upstream); 976 | */ 977 | 978 | } 979 | 980 | static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_impl = { 981 | .destroy = nested_linux_dmabuf_destroy, 982 | .create_params = nested_linux_dmabuf_create_params, 983 | .get_default_feedback = nested_linux_dmabuf_get_default_feedback, 984 | .get_surface_feedback = nested_linux_dmabuf_get_surface_feedback, 985 | }; 986 | 987 | void bind_linux_dmabuf(struct wl_client *client, void *data, 988 | uint32_t version, uint32_t id) { 989 | struct wl_resource *resource = 990 | wl_resource_create(client, &zwp_linux_dmabuf_v1_interface, version, id); 991 | if (resource == NULL) { 992 | wl_client_post_no_memory(client); 993 | return; 994 | } 995 | 996 | struct forward_state *forward = data; 997 | if (version <= 3) { 998 | /* warning; this weakly relies on formats being in sorted order */ 999 | uint32_t last_fmt = (uint32_t)-1; 1000 | for (size_t i = 0; i < forward->dmabuf_formats_len; i++) { 1001 | if (forward->dmabuf_formats[i].format != last_fmt) { 1002 | zwp_linux_dmabuf_v1_send_format(resource, forward->dmabuf_formats[i].format); 1003 | } 1004 | 1005 | last_fmt = forward->dmabuf_formats[i].format; 1006 | } 1007 | } 1008 | if (version == 3) { 1009 | for (size_t i = 0; i < forward->dmabuf_formats_len; i++) { 1010 | zwp_linux_dmabuf_v1_send_modifier(resource, forward->dmabuf_formats[i].format, forward->dmabuf_formats[i].modifier_lo, forward->dmabuf_formats[i].modifier_hi); 1011 | } 1012 | } 1013 | 1014 | wl_resource_set_implementation(resource, &linux_dmabuf_impl, data, NULL); 1015 | } 1016 | 1017 | 1018 | static void nested_drm_authenticate(struct wl_client *client, 1019 | struct wl_resource *resource, uint32_t id) { 1020 | wl_client_post_implementation_error(client, "wl_drm.authenticate not supported"); 1021 | } 1022 | static void nested_drm_create_buffer(struct wl_client *client, 1023 | struct wl_resource *resource, uint32_t id, 1024 | uint32_t name, int32_t width, 1025 | int32_t height, uint32_t stride, uint32_t format) { 1026 | wl_client_post_implementation_error(client, "wl_drm.create_buffer not supported"); 1027 | } 1028 | static void nested_drm_create_planar_buffer(struct wl_client *client, 1029 | struct wl_resource *resource, uint32_t id, 1030 | uint32_t name, int32_t width, int32_t height, 1031 | uint32_t format, int32_t offset0, int32_t stride0, 1032 | int32_t offset1, int32_t stride1, int32_t offset2, int32_t stride2) { 1033 | wl_client_post_implementation_error(client, "wl_drm.create_planar_buffer not supported"); 1034 | } 1035 | static void nested_drm_create_prime_buffer(struct wl_client *client, 1036 | struct wl_resource *resource, uint32_t id, int32_t name, 1037 | int32_t width, int32_t height, uint32_t format, int32_t offset0, 1038 | int32_t stride0, int32_t offset1,int32_t stride1, int32_t offset2, 1039 | int32_t stride2) { 1040 | wl_client_post_implementation_error(client, "wl_drm.create_prime_buffer not supported"); 1041 | 1042 | } 1043 | static const struct wl_drm_interface wl_drm_impl = { 1044 | .authenticate = nested_drm_authenticate, 1045 | .create_buffer = nested_drm_create_buffer, 1046 | .create_planar_buffer = nested_drm_create_planar_buffer, 1047 | .create_prime_buffer = nested_drm_create_prime_buffer, 1048 | }; 1049 | void bind_drm(struct wl_client *client, void *data, 1050 | uint32_t version, uint32_t id) { 1051 | struct wl_resource *resource = 1052 | wl_resource_create(client, &wl_drm_interface, version, id); 1053 | if (resource == NULL) { 1054 | wl_client_post_no_memory(client); 1055 | return; 1056 | } 1057 | // TODO: look this up from the upstream copy 1058 | wl_drm_send_device(resource, "/dev/dri/renderD128"); 1059 | wl_drm_send_capabilities(resource, 1); 1060 | 1061 | wl_resource_set_implementation(resource, &wl_drm_impl, data, NULL); 1062 | } 1063 | 1064 | static void viewport_handle_resource_destroy(struct wl_resource *resource) { 1065 | assert(wl_resource_instance_of(resource, &wp_viewport_interface, &viewport_impl)); 1066 | struct forward_surface *fwd_surface = wl_resource_get_user_data(resource); 1067 | if (fwd_surface) { 1068 | fwd_surface->viewport = NULL; 1069 | } 1070 | } 1071 | 1072 | static void nested_viewport_destroy(struct wl_client *client, 1073 | struct wl_resource *resource) { 1074 | /* `viewport_handle_resource_destroy` will be invoked */ 1075 | wl_resource_destroy(resource); 1076 | } 1077 | 1078 | static void nested_viewport_set_source(struct wl_client *client, struct wl_resource *resource, 1079 | wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) { 1080 | assert(wl_resource_instance_of(resource, &wp_viewport_interface, &viewport_impl)); 1081 | struct forward_surface *fwd_surface = wl_resource_get_user_data(resource); 1082 | wl_fixed_t n = wl_fixed_from_int(-1); 1083 | bool no_source = x == n && y == n && width == n && height == n; 1084 | if ((x < 0 || y < 0 || width <= 0 || height <= 0) && !no_source) { 1085 | wl_resource_post_error(resource, WP_VIEWPORT_ERROR_BAD_VALUE, "invalid x/y/width/height for set_source"); 1086 | } else { 1087 | fwd_surface->pending.viewport_source_x = x; 1088 | fwd_surface->pending.viewport_source_y = y; 1089 | fwd_surface->pending.viewport_source_w = width; 1090 | fwd_surface->pending.viewport_source_h = height; 1091 | } 1092 | } 1093 | 1094 | static void nested_viewport_set_destination(struct wl_client *client, struct wl_resource *resource, 1095 | int32_t width, int32_t height) { 1096 | assert(wl_resource_instance_of(resource, &wp_viewport_interface, &viewport_impl)); 1097 | struct forward_surface *fwd_surface = wl_resource_get_user_data(resource); 1098 | if ((width <= 0 || height <= 0) && !(width == -1 && height == -1)) { 1099 | wl_resource_post_error(resource, WP_VIEWPORT_ERROR_BAD_VALUE, "invalid width/height pair for set_destination"); 1100 | } else { 1101 | fwd_surface->pending.viewport_dest_width = width; 1102 | fwd_surface->pending.viewport_dest_height = height; 1103 | } 1104 | } 1105 | 1106 | static const struct wp_viewport_interface viewport_impl = { 1107 | .destroy = nested_viewport_destroy, 1108 | .set_source = nested_viewport_set_source, 1109 | .set_destination = nested_viewport_set_destination, 1110 | }; 1111 | 1112 | static void nested_viewporter_destroy(struct wl_client *client, struct wl_resource *resource) { 1113 | wl_resource_destroy(resource); 1114 | } 1115 | 1116 | static void nested_viewporter_get_viewport(struct wl_client *client, struct wl_resource *resource, 1117 | uint32_t id, struct wl_resource *surface) { 1118 | struct forward_surface *forward_surf = wl_resource_get_user_data(surface); 1119 | /* Each surface has at most one wp_viewport associated */ 1120 | if (forward_surf->viewport) { 1121 | wl_resource_post_error(resource, WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS, "viewport already exists"); 1122 | } 1123 | 1124 | struct wl_resource *viewport_resource = wl_resource_create(client, &wp_viewport_interface, 1125 | wl_resource_get_version(resource), id); 1126 | if (viewport_resource == NULL) { 1127 | wl_client_post_no_memory(client); 1128 | return; 1129 | } 1130 | forward_surf->viewport = viewport_resource; 1131 | wl_resource_set_implementation(viewport_resource, &viewport_impl, 1132 | forward_surf, viewport_handle_resource_destroy); 1133 | } 1134 | 1135 | static const struct wp_viewporter_interface viewporter_impl = { 1136 | .destroy = nested_viewporter_destroy, 1137 | .get_viewport = nested_viewporter_get_viewport, 1138 | }; 1139 | 1140 | void bind_viewporter(struct wl_client *client, void *data, 1141 | uint32_t version, uint32_t id) { 1142 | struct wl_resource *resource = 1143 | wl_resource_create(client, &wp_viewporter_interface, version, id); 1144 | if (resource == NULL) { 1145 | wl_client_post_no_memory(client); 1146 | return; 1147 | } 1148 | struct forward_state *forward = data; 1149 | wl_resource_set_implementation(resource, &viewporter_impl, forward, NULL); 1150 | } 1151 | 1152 | static void fractional_scale_handle_resource_destroy(struct wl_resource *resource) { 1153 | assert(wl_resource_instance_of(resource, &wp_fractional_scale_v1_interface, &fractional_scale_impl)); 1154 | struct forward_surface *fwd_surface = wl_resource_get_user_data(resource); 1155 | if (fwd_surface) { 1156 | fwd_surface->fractional_scale = NULL; 1157 | } 1158 | } 1159 | static void nested_fractional_scale_destroy(struct wl_client *client, 1160 | struct wl_resource *resource) { 1161 | /* `fractional_scale_handle_resource_destroy` will be invoked */ 1162 | wl_resource_destroy(resource); 1163 | } 1164 | static const struct wp_fractional_scale_v1_interface fractional_scale_impl = { 1165 | .destroy = nested_fractional_scale_destroy, 1166 | }; 1167 | 1168 | static void nested_fractional_scale_manager_destroy(struct wl_client *client, struct wl_resource *resource) { 1169 | /* nothing to do */ 1170 | } 1171 | static void nested_fractional_scale_manager_get_fractional_scale(struct wl_client *client, 1172 | struct wl_resource *resource, uint32_t id, struct wl_resource *surface) { 1173 | struct forward_surface *forward_surf = wl_resource_get_user_data(surface); 1174 | /* Each surface has at most one wp_fractional_scale associated */ 1175 | if (forward_surf->fractional_scale) { 1176 | wl_resource_post_error(resource, WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_FRACTIONAL_SCALE_EXISTS, "fractional scale already exists"); 1177 | } 1178 | 1179 | struct wl_resource *scale_resource = wl_resource_create(client, &wp_fractional_scale_v1_interface, 1180 | wl_resource_get_version(resource), id); 1181 | if (scale_resource == NULL) { 1182 | wl_client_post_no_memory(client); 1183 | return; 1184 | } 1185 | forward_surf->fractional_scale = scale_resource; 1186 | if (forward_surf->sway_surface && forward_surf->sway_surface->last_fractional_scale > 0) { 1187 | wp_fractional_scale_v1_send_preferred_scale(scale_resource, 1188 | forward_surf->sway_surface->last_fractional_scale); 1189 | } 1190 | wl_resource_set_implementation(scale_resource, &fractional_scale_impl, 1191 | forward_surf, fractional_scale_handle_resource_destroy); 1192 | } 1193 | 1194 | static const struct wp_fractional_scale_manager_v1_interface fractional_scale_manager_impl = { 1195 | .destroy = nested_fractional_scale_manager_destroy, 1196 | .get_fractional_scale = nested_fractional_scale_manager_get_fractional_scale, 1197 | }; 1198 | 1199 | void bind_fractional_scale(struct wl_client *client, void *data, 1200 | uint32_t version, uint32_t id) { 1201 | struct wl_resource *resource = 1202 | wl_resource_create(client, &wp_fractional_scale_manager_v1_interface, version, id); 1203 | if (resource == NULL) { 1204 | wl_client_post_no_memory(client); 1205 | return; 1206 | } 1207 | struct forward_state *forward = data; 1208 | wl_resource_set_implementation(resource, &fractional_scale_manager_impl, forward, NULL); 1209 | } 1210 | 1211 | 1212 | static void nested_data_source_offer(struct wl_client *client, struct wl_resource *resource, 1213 | const char *mime_type) { 1214 | } 1215 | static void nested_data_source_destroy(struct wl_client *client, struct wl_resource *resource) { 1216 | wl_resource_destroy(resource); 1217 | } 1218 | static void nested_data_source_set_actions(struct wl_client *client, struct wl_resource *resource, 1219 | uint32_t dnd_actions) { 1220 | } 1221 | static const struct wl_data_source_interface data_source_impl = { 1222 | .destroy = nested_data_source_destroy, 1223 | .offer = nested_data_source_offer, 1224 | .set_actions = nested_data_source_set_actions, 1225 | }; 1226 | static void data_source_handle_resource_destroy(struct wl_resource *resource) { 1227 | assert(wl_resource_instance_of(resource, &wl_data_source_interface, &data_source_impl)); 1228 | } 1229 | static void nested_ddm_create_data_source(struct wl_client *client, struct wl_resource *resource, 1230 | uint32_t id) { 1231 | struct wl_resource *source_resource = wl_resource_create(client, &wl_data_source_interface, 1232 | wl_resource_get_version(resource), id); 1233 | if (source_resource == NULL) { 1234 | wl_client_post_no_memory(client); 1235 | return; 1236 | } 1237 | wl_resource_set_implementation(source_resource, &data_source_impl, 1238 | NULL, data_source_handle_resource_destroy); 1239 | } 1240 | 1241 | static void nested_data_device_start_drag(struct wl_client *client, struct wl_resource *resource, 1242 | struct wl_resource *source, struct wl_resource *origin,struct wl_resource *icon, uint32_t serial) { 1243 | } 1244 | static void nested_data_device_set_selection(struct wl_client *client, struct wl_resource *resource, 1245 | struct wl_resource *source, uint32_t serial) { 1246 | } 1247 | static void nested_data_device_release(struct wl_client *client, 1248 | struct wl_resource *resource) { 1249 | wl_resource_destroy(resource); 1250 | } 1251 | static const struct wl_data_device_interface data_device_impl = { 1252 | .release = nested_data_device_release, 1253 | .set_selection = nested_data_device_set_selection, 1254 | .start_drag = nested_data_device_start_drag, 1255 | }; 1256 | static void data_device_handle_resource_destroy(struct wl_resource *resource) { 1257 | assert(wl_resource_instance_of(resource, &wl_data_device_interface, &data_device_impl)); 1258 | } 1259 | static void nested_ddm_get_data_device(struct wl_client *client, struct wl_resource *resource, 1260 | uint32_t id, struct wl_resource *seat) { 1261 | struct wl_resource *device_resource = wl_resource_create(client, &wl_data_device_interface, 1262 | wl_resource_get_version(resource), id); 1263 | if (device_resource == NULL) { 1264 | wl_client_post_no_memory(client); 1265 | return; 1266 | } 1267 | wl_resource_set_implementation(device_resource, &data_device_impl, 1268 | NULL, data_device_handle_resource_destroy); 1269 | } 1270 | 1271 | static const struct wl_data_device_manager_interface data_device_manager_impl = { 1272 | .get_data_device = nested_ddm_get_data_device, 1273 | .create_data_source = nested_ddm_create_data_source, 1274 | }; 1275 | 1276 | void bind_wl_data_device_manager(struct wl_client *client, void *data, 1277 | uint32_t version, uint32_t id) { 1278 | struct wl_resource *resource = 1279 | wl_resource_create(client, &wl_data_device_manager_interface, version, id); 1280 | if (resource == NULL) { 1281 | wl_client_post_no_memory(client); 1282 | return; 1283 | } 1284 | wl_resource_set_implementation(resource, &data_device_manager_impl, NULL, NULL); 1285 | } 1286 | -------------------------------------------------------------------------------- /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 | #include 7 | #include 8 | 9 | struct loop; 10 | struct loop_timer; 11 | 12 | struct swaylock_xkb { 13 | bool caps_lock; 14 | bool control; 15 | struct xkb_state *state; 16 | struct xkb_context *context; 17 | struct xkb_keymap *keymap; 18 | }; 19 | 20 | struct swaylock_seat { 21 | struct swaylock_state *state; 22 | struct wl_pointer *pointer; 23 | struct wl_keyboard *keyboard; 24 | int32_t repeat_period_ms; 25 | int32_t repeat_delay_ms; 26 | uint32_t repeat_sym; 27 | uint32_t repeat_codepoint; 28 | struct loop_timer *repeat_timer; 29 | /* mouse tracking to check for deliberate mouse motion */ 30 | int64_t last_interval; 31 | wl_fixed_t interval_start_x, interval_start_y; 32 | wl_fixed_t last_mouse_x, last_mouse_y; 33 | }; 34 | 35 | extern const struct wl_seat_listener seat_listener; 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /include/swaylock.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAYLOCK_H 2 | #define _SWAYLOCK_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "background-image.h" 8 | #include "cairo.h" 9 | #include "pool-buffer.h" 10 | #include "seat.h" 11 | #include "linux-dmabuf-unstable-v1-client-protocol.h" 12 | #include "wayland-drm-client-protocol.h" 13 | #include "fractional-scale-v1-client-protocol.h" 14 | #include "viewporter-client-protocol.h" 15 | 16 | // Indicator state: status of authentication attempt 17 | enum auth_state { 18 | AUTH_STATE_IDLE, // nothing happening 19 | AUTH_STATE_VALIDATING, // currently validating password 20 | AUTH_STATE_INVALID, // displaying message: password was wrong 21 | }; 22 | 23 | // Indicator state: status of password buffer / typing letters 24 | enum input_state { 25 | INPUT_STATE_IDLE, // nothing happening; other states decay to this after time 26 | INPUT_STATE_CLEAR, // displaying message: password buffer was cleared 27 | INPUT_STATE_LETTER, // pressed a key that input a letter 28 | INPUT_STATE_BACKSPACE, // pressed backspace and removed a letter 29 | INPUT_STATE_NEUTRAL, // pressed a key (like Ctrl) that did nothing 30 | }; 31 | 32 | struct swaylock_colorset { 33 | uint32_t input; 34 | uint32_t cleared; 35 | uint32_t caps_lock; 36 | uint32_t verifying; 37 | uint32_t wrong; 38 | }; 39 | 40 | struct swaylock_colors { 41 | uint32_t background; 42 | uint32_t bs_highlight; 43 | uint32_t key_highlight; 44 | uint32_t caps_lock_bs_highlight; 45 | uint32_t caps_lock_key_highlight; 46 | uint32_t separator; 47 | uint32_t layout_background; 48 | uint32_t layout_border; 49 | uint32_t layout_text; 50 | struct swaylock_colorset inside; 51 | struct swaylock_colorset line; 52 | struct swaylock_colorset ring; 53 | struct swaylock_colorset text; 54 | }; 55 | 56 | struct swaylock_args { 57 | struct swaylock_colors colors; 58 | enum background_mode mode; 59 | char *font; 60 | uint32_t font_size; 61 | uint32_t radius; 62 | uint32_t thickness; 63 | uint32_t indicator_x_position; 64 | uint32_t indicator_y_position; 65 | bool override_indicator_x_position; 66 | bool override_indicator_y_position; 67 | bool ignore_empty; 68 | bool show_indicator; 69 | bool show_caps_lock_text; 70 | bool show_caps_lock_indicator; 71 | bool show_keyboard_layout; 72 | bool hide_keyboard_layout; 73 | bool show_failed_attempts; 74 | bool daemonize; 75 | int ready_fd; 76 | bool indicator_idle_visible; 77 | char *plugin_command; 78 | bool plugin_per_output; 79 | /* negative values = no grace; unit: seconds */ 80 | float grace_time; 81 | /* max number of pixels/sec mouse motion which will be ignored */ 82 | float grace_pointer_hysteresis; 83 | }; 84 | 85 | struct swaylock_password { 86 | size_t len; 87 | size_t buffer_len; 88 | char *buffer; 89 | }; 90 | 91 | struct swaylock_bg_client { 92 | struct swaylock_state *state; 93 | 94 | /* Provide per-client serials, as serials get remapped anyway */ 95 | uint32_t serial; 96 | struct wl_client *client; 97 | 98 | /* If NULL, this client applies to all outputs; otherwise, to the 99 | * specific output indicated. */ 100 | struct swaylock_surface *unique_output; 101 | 102 | bool made_a_registry; // did client even create the wl_registry resource? 103 | /* Timer after which to give up on a non-connecting client. It is 104 | * important to verify this, as there may not be any outputs */ 105 | struct loop_timer *client_connect_timer; 106 | struct wl_listener client_resource_create_listener; 107 | struct wl_listener client_destroy_listener; 108 | 109 | /* For swaylock_bg_server::clients */ 110 | struct wl_list link; 111 | }; 112 | 113 | // for the plugin-based surface drawing 114 | struct swaylock_bg_server { 115 | struct wl_display *display; 116 | struct wl_event_loop *loop; 117 | struct wl_global *wlr_layer_shell; 118 | struct wl_global *compositor; 119 | struct wl_global *shm; 120 | struct wl_global *xdg_output_manager; 121 | struct wl_global *zwp_linux_dmabuf; 122 | struct wl_global *drm; 123 | struct wl_global *wp_fractional_scale; 124 | struct wl_global *wp_viewporter; 125 | struct wl_global *data_device_manager; 126 | 127 | struct wl_list clients; 128 | /* If not NULL, this client provides buffers for all surfaces */ 129 | struct swaylock_bg_client *main_client; 130 | }; 131 | 132 | struct dmabuf_modifier_pair { 133 | uint32_t format; 134 | uint32_t modifier_hi; 135 | uint32_t modifier_lo; 136 | }; 137 | 138 | struct feedback_pair { 139 | uint32_t format; 140 | uint32_t unused_padding; 141 | uint32_t modifier_hi; 142 | uint32_t modifier_lo; 143 | }; 144 | 145 | struct feedback_tranche { 146 | dev_t tranche_device; 147 | struct wl_array indices; 148 | uint32_t flags; 149 | }; 150 | struct dmabuf_feedback_state { 151 | dev_t main_device; 152 | int table_fd; 153 | int table_fd_size; 154 | struct feedback_tranche *tranches; 155 | size_t tranches_len; 156 | }; 157 | 158 | // todo: merge with swaylock_bg_server ? 159 | struct forward_state { 160 | /* these pointers are copies of those in swaylock_state */ 161 | struct wl_display *upstream_display; 162 | struct wl_registry *upstream_registry; 163 | 164 | struct wl_drm *drm; 165 | struct wl_shm *shm; 166 | /* this instance is used just for forwarding */ 167 | struct zwp_linux_dmabuf_v1 *linux_dmabuf; 168 | /* list of wl_resources corresponding to (default/surface) feedback instances 169 | * that should get updated when the upstream feedback is updated */ 170 | struct wl_list feedback_instances; 171 | /* We only let the background generator create surfaces, but not 172 | * subsurfaces, because those are much trickier to implement correctly, 173 | * and a well designed background shouldn't need them anyway. */ 174 | struct wl_compositor *compositor; 175 | 176 | struct wp_viewporter *viewporter; 177 | struct wp_fractional_scale_manager_v1 *fractional_scale; 178 | 179 | uint32_t *shm_formats; 180 | uint32_t shm_formats_len; 181 | 182 | struct dmabuf_modifier_pair *dmabuf_formats; 183 | uint32_t dmabuf_formats_len; 184 | 185 | struct dmabuf_feedback_state current, pending; 186 | struct feedback_tranche pending_tranche; 187 | }; 188 | 189 | struct damage_record { 190 | int32_t x,y,w,h; 191 | }; 192 | 193 | struct forward_buffer { 194 | /* may be null if plugin program deleted it */ 195 | struct wl_resource *resource; 196 | /* upstream buffer */ 197 | struct wl_buffer *buffer; 198 | /* list of surfaces where buffer is pending */ 199 | struct wl_list pending_surfaces; 200 | /* list of surfaces where buffer is committed */ 201 | struct wl_list committed_surfaces; 202 | /* dimensions of the buffer */ 203 | uint32_t width, height; 204 | }; 205 | /* BUFFER_UNREACHABLE is used for the committed buffer it it was been deleted 206 | * downstream 207 | * 208 | * BUFFER_COMMITTED is used for the pending buffer if it was deleted downstream 209 | * and matches whatever was already committed. 210 | */ 211 | #define BUFFER_UNREACHABLE (struct forward_buffer *)(-1) 212 | #define BUFFER_COMMITTED (struct forward_buffer *)(-2) 213 | 214 | struct surface_state { 215 | /* wl_buffer, invoke get_resource for upstream */ 216 | struct forward_buffer *attachment; 217 | struct wl_list attachment_link; 218 | int32_t offset_x, offset_y; 219 | int32_t buffer_scale; 220 | int32_t buffer_transform; 221 | 222 | /* Viewport state */ 223 | wl_fixed_t viewport_source_x; 224 | wl_fixed_t viewport_source_y; 225 | wl_fixed_t viewport_source_w; 226 | wl_fixed_t viewport_source_h; 227 | int32_t viewport_dest_width; 228 | int32_t viewport_dest_height; 229 | }; 230 | 231 | struct serial_pair { 232 | uint32_t plugin_serial; 233 | uint32_t upstream_serial; 234 | /* The width and height corresponding to the configure matching `plugin_serial`. 235 | * Used to verify the client submits buffers with dimensions actually matching its 236 | * configures. */ 237 | uint32_t config_width; 238 | uint32_t config_height; 239 | /* if true, plugin serial was not generated in response to an 240 | * upstream configure event; so do not forward acknowledgements. */ 241 | bool local_only; 242 | }; 243 | 244 | /* this is a resource associated to a downstream wl_surface */ 245 | struct forward_surface { 246 | bool has_been_configured; 247 | struct wl_resource *layer_surface; // downstream only 248 | 249 | /* is null until get_layer_surface is called and initializes this */ 250 | struct swaylock_surface *sway_surface; 251 | // set after layer surface is destroyed 252 | bool inert; 253 | 254 | /* list of callbacks for wl_surface::frame */ 255 | struct wl_list frame_callbacks; 256 | 257 | // double-buffered state 258 | struct surface_state pending; 259 | struct surface_state committed; 260 | // copy of buffer size, to retain even in case attached buffer is destroyed after commit 261 | uint32_t committed_buffer_width; 262 | uint32_t committed_buffer_height; 263 | 264 | /* damage is not, strictly speaking, double buffered */ 265 | struct damage_record *buffer_damage; 266 | size_t buffer_damage_len; 267 | struct damage_record *old_damage; 268 | size_t old_damage_len; 269 | 270 | uint32_t last_used_plugin_serial; 271 | uint32_t last_acked_width, last_acked_height; 272 | struct serial_pair *serial_table; 273 | size_t serial_table_len; 274 | 275 | /* The unique viewport resource attached to the surface, if any */ 276 | struct wl_resource *viewport; 277 | 278 | /* The unique fractional_scale resource attached to the surface, if any */ 279 | struct wl_resource *fractional_scale; 280 | }; 281 | 282 | struct swaylock_state { 283 | struct loop *eventloop; 284 | struct loop_timer *input_idle_timer; // timer to reset input state to IDLE 285 | struct loop_timer *auth_idle_timer; // timer to stop displaying AUTH_STATE_INVALID 286 | struct loop_timer *clear_password_timer; // clears the password buffer 287 | struct wl_display *display; 288 | struct wl_compositor *compositor; 289 | struct wl_subcompositor *subcompositor; 290 | struct zwlr_input_inhibit_manager_v1 *input_inhibit_manager; 291 | struct wl_shm *shm; 292 | struct zwp_linux_dmabuf_feedback_v1 *dmabuf_default_feedback; 293 | struct wl_list surfaces; 294 | struct wl_list images; 295 | struct swaylock_args args; 296 | struct swaylock_password password; 297 | struct swaylock_xkb xkb; 298 | cairo_surface_t *test_surface; 299 | cairo_t *test_cairo; // used to estimate font/text sizes 300 | enum auth_state auth_state; // state of the authentication attempt 301 | enum input_state input_state; // state of the password buffer and key inputs 302 | uint32_t highlight_start; // position of highlight; 2048 = 1 full turn 303 | int failed_attempts; 304 | bool run_display, locked; 305 | struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1; 306 | struct ext_session_lock_v1 *ext_session_lock_v1; 307 | struct zxdg_output_manager_v1 *zxdg_output_manager; 308 | struct forward_state forward; 309 | struct swaylock_bg_server server; 310 | bool start_clientless_mode; 311 | struct loop_timer *grace_timer; // timer for grace period to end 312 | int sleep_comm_r, sleep_comm_w; 313 | 314 | // for nested server, output was destroyed 315 | struct wl_list stale_wl_output_resources; 316 | struct wl_list stale_xdg_output_resources; 317 | }; 318 | 319 | struct swaylock_surface { 320 | cairo_surface_t *image; 321 | struct swaylock_state *state; 322 | struct wl_output *output; 323 | uint32_t output_global_name; 324 | struct wl_surface *surface; // surface for background 325 | struct wl_surface *child; // indicator surface made into subsurface 326 | struct wl_subsurface *subsurface; 327 | 328 | struct forward_surface *plugin_surface; 329 | 330 | struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1; 331 | struct wp_viewport *viewport; 332 | struct wp_fractional_scale_v1* fractional_scale; 333 | uint32_t last_fractional_scale; /* is zero if nothing received yet */ 334 | struct pool_buffer indicator_buffers[2]; 335 | bool created; 336 | bool dirty; 337 | uint32_t width, height; 338 | int32_t scale; 339 | enum wl_output_subpixel subpixel; 340 | char *output_name; 341 | char *output_description; 342 | int32_t physical_width, physical_height, output_transform; 343 | int32_t mode_width, mode_height; 344 | struct wl_list link; 345 | struct wl_callback *frame; 346 | 347 | struct wl_global *nested_server_output; 348 | // lists of associated resources 349 | struct wl_list nested_server_wl_output_resources; 350 | struct wl_list nested_server_xdg_output_resources; 351 | 352 | /* the serial of the configure which first established the size of the 353 | * surface; will be needed when plugin surface is set up and needs to link 354 | * its first configure to the first configure of the swaylock_surface */ 355 | uint32_t first_configure_serial; 356 | bool used_first_configure; 357 | 358 | /* needed to delay ack configures from plugin until just before matching commit */ 359 | bool has_pending_ack_conf; 360 | uint32_t pending_upstream_serial; 361 | 362 | /* Does this surface have a newer configure that it did not yet acknowledge? 363 | * Tracking this is useful when the client is replaced. */ 364 | bool has_newer_serial; 365 | uint32_t newest_serial; 366 | 367 | /* has a buffer been attached and committed */ 368 | bool has_buffer; 369 | 370 | /* If not NULL, the client which provides surfaces for this surface. 371 | * If NULL, server.main_client will do so */ 372 | struct swaylock_bg_client *client; 373 | 374 | /* Timer to verify if the client submits surfaces promptly. 375 | * (To be fully accurate, it would be better to launch a unique timer 376 | * every time the compositor resizes this surface, and then have the 377 | * client defuse all timers preceding the serial of its last 378 | * acknowledgement with associated valid surface submission. 379 | */ 380 | struct loop_timer *client_submission_timer; 381 | }; 382 | 383 | /* Forwarding interface. These create various resources which maintain an 384 | * exactly corresponding resource on the server side. (With exceptions: 385 | * wl_regions do not need to be forwarded, so such wl_region-type wl_resources 386 | * lack user data. 387 | * 388 | * This solution is only good for a prototype, because blind forwarding lets 389 | * a bad plugin process directly overload the compositor, instead of overloading 390 | * swaylock. The correct thing to do is to fully maintain local buffer/surface 391 | * state, and only upload buffer data or send damage as needed; it is better 392 | * to crash swaylock than to crash the compositor. 393 | */ 394 | void bind_wl_compositor(struct wl_client *client, void *data, uint32_t version, uint32_t id); 395 | void bind_wl_shm(struct wl_client *client, void *data, uint32_t version, uint32_t id); 396 | void bind_linux_dmabuf(struct wl_client *client, void *data, uint32_t version, uint32_t id); 397 | void bind_drm(struct wl_client *client, void *data, uint32_t version, uint32_t id); 398 | void bind_viewporter(struct wl_client *client, void *data, uint32_t version, uint32_t id); 399 | void bind_fractional_scale(struct wl_client *client, void *data, uint32_t version, uint32_t id); 400 | void send_dmabuf_feedback_data(struct wl_resource *feedback, const struct dmabuf_feedback_state *state); 401 | /* No-op interfaces; do the minimum required to implement the interface but have no effect; 402 | * used when clients unnecessarily require specific interfaces to run. */ 403 | void bind_wl_data_device_manager(struct wl_client *client, void *data, uint32_t version, uint32_t id); 404 | 405 | /* use this to record that in response to the configure event with upstream_serial, 406 | * a configure event with downstream_serial was sent to the plugin surface. 407 | * If local_only=true, mark that the downstream serial does _not_ need forwarding. */ 408 | void add_serial_pair(struct forward_surface *surf, uint32_t upstream_serial, 409 | uint32_t downstream_serial, uint32_t width, uint32_t height, bool local_only); 410 | 411 | // There is exactly one swaylock_image for each -i argument 412 | struct swaylock_image { 413 | char *path; 414 | char *output_name; 415 | cairo_surface_t *cairo_surface; 416 | struct wl_list link; 417 | }; 418 | 419 | void swaylock_handle_key(struct swaylock_state *state, 420 | xkb_keysym_t keysym, uint32_t codepoint); 421 | 422 | void render(struct swaylock_surface *surface); 423 | void damage_state(struct swaylock_state *state); 424 | void clear_password_buffer(struct swaylock_password *pw); 425 | void schedule_auth_idle(struct swaylock_state *state); 426 | 427 | void initialize_pw_backend(int argc, char **argv); 428 | void run_pw_backend_child(void); 429 | void clear_buffer(char *buf, size_t size); 430 | 431 | /* Returns false if it fails to set the close-on-exec flag for `fd` */ 432 | bool set_cloexec(int fd); 433 | 434 | #endif 435 | -------------------------------------------------------------------------------- /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 | bool removed; 17 | struct wl_list link; // struct loop_fd_event::link 18 | }; 19 | 20 | struct loop_timer { 21 | void (*callback)(void *data); 22 | void *data; 23 | struct timespec expiry; 24 | bool removed; 25 | struct wl_list link; // struct loop_timer::link 26 | }; 27 | 28 | struct loop { 29 | struct pollfd *fds; 30 | int fd_length; 31 | int fd_capacity; 32 | 33 | struct wl_list fd_events; // struct loop_fd_event::link 34 | struct wl_list timers; // struct loop_timer::link 35 | }; 36 | 37 | struct loop *loop_create(void) { 38 | struct loop *loop = calloc(1, sizeof(struct loop)); 39 | if (!loop) { 40 | swaylock_log(LOG_ERROR, "Unable to allocate memory for loop"); 41 | return NULL; 42 | } 43 | loop->fd_capacity = 10; 44 | loop->fds = malloc(sizeof(struct pollfd) * loop->fd_capacity); 45 | wl_list_init(&loop->fd_events); 46 | wl_list_init(&loop->timers); 47 | return loop; 48 | } 49 | 50 | void loop_destroy(struct loop *loop) { 51 | struct loop_fd_event *event = NULL, *tmp_event = NULL; 52 | wl_list_for_each_safe(event, tmp_event, &loop->fd_events, link) { 53 | wl_list_remove(&event->link); 54 | free(event); 55 | } 56 | struct loop_timer *timer = NULL, *tmp_timer = NULL; 57 | wl_list_for_each_safe(timer, tmp_timer, &loop->timers, link) { 58 | wl_list_remove(&timer->link); 59 | free(timer); 60 | } 61 | free(loop->fds); 62 | free(loop); 63 | } 64 | 65 | void loop_poll(struct loop *loop) { 66 | // Calculate next timer in ms 67 | int ms = INT_MAX; 68 | if (!wl_list_empty(&loop->timers)) { 69 | struct timespec now; 70 | clock_gettime(CLOCK_MONOTONIC, &now); 71 | struct loop_timer *timer = NULL; 72 | wl_list_for_each(timer, &loop->timers, link) { 73 | int timer_ms = (timer->expiry.tv_sec - now.tv_sec) * 1000; 74 | timer_ms += (timer->expiry.tv_nsec - now.tv_nsec) / 1000000; 75 | if (timer_ms < ms) { 76 | ms = timer_ms; 77 | } 78 | } 79 | } 80 | if (ms < 0) { 81 | ms = 0; 82 | } 83 | 84 | int ret = poll(loop->fds, loop->fd_length, ms); 85 | if (ret < 0 && errno != EINTR) { 86 | swaylock_log_errno(LOG_ERROR, "poll failed"); 87 | exit(1); 88 | } 89 | 90 | // Dispatch fds 91 | size_t fd_index = 0; 92 | struct loop_fd_event *event = NULL, *tmp_event = NULL; 93 | wl_list_for_each_safe(event, tmp_event, &loop->fd_events, link) { 94 | struct pollfd pfd = loop->fds[fd_index]; 95 | ++fd_index; 96 | 97 | if (event->removed) { 98 | continue; 99 | } 100 | 101 | // Always send these events 102 | unsigned events = pfd.events | POLLHUP | POLLERR; 103 | 104 | if (pfd.revents & events) { 105 | event->callback(pfd.fd, pfd.revents, event->data); 106 | } 107 | } 108 | 109 | // Free removed fd events 110 | fd_index = 0; 111 | wl_list_for_each_safe(event, tmp_event, &loop->fd_events, link) { 112 | if (event->removed) { 113 | wl_list_remove(&event->link); 114 | free(event); 115 | loop->fd_length--; 116 | memmove(&loop->fds[fd_index], &loop->fds[fd_index + 1], 117 | sizeof(struct pollfd) * (loop->fd_length - fd_index)); 118 | } else { 119 | fd_index++; 120 | } 121 | } 122 | 123 | // Dispatch timers 124 | if (!wl_list_empty(&loop->timers)) { 125 | struct timespec now; 126 | clock_gettime(CLOCK_MONOTONIC, &now); 127 | struct loop_timer *timer = NULL, *tmp_timer = NULL; 128 | wl_list_for_each_safe(timer, tmp_timer, &loop->timers, link) { 129 | if (timer->removed) { 130 | wl_list_remove(&timer->link); 131 | free(timer); 132 | continue; 133 | } 134 | 135 | bool expired = timer->expiry.tv_sec < now.tv_sec || 136 | (timer->expiry.tv_sec == now.tv_sec && 137 | timer->expiry.tv_nsec < now.tv_nsec); 138 | if (expired) { 139 | timer->callback(timer->data); 140 | wl_list_remove(&timer->link); 141 | free(timer); 142 | } 143 | } 144 | } 145 | } 146 | 147 | void loop_add_fd(struct loop *loop, int fd, short mask, 148 | void (*callback)(int fd, short mask, void *data), void *data) { 149 | struct loop_fd_event *event = calloc(1, sizeof(struct loop_fd_event)); 150 | if (!event) { 151 | swaylock_log(LOG_ERROR, "Unable to allocate memory for event"); 152 | return; 153 | } 154 | event->callback = callback; 155 | event->data = data; 156 | wl_list_insert(loop->fd_events.prev, &event->link); 157 | 158 | struct pollfd pfd = {fd, mask, 0}; 159 | 160 | if (loop->fd_length == loop->fd_capacity) { 161 | loop->fd_capacity += 10; 162 | loop->fds = realloc(loop->fds, 163 | sizeof(struct pollfd) * loop->fd_capacity); 164 | } 165 | 166 | loop->fds[loop->fd_length++] = pfd; 167 | } 168 | 169 | struct loop_timer *loop_add_timer(struct loop *loop, int ms, 170 | void (*callback)(void *data), void *data) { 171 | struct loop_timer *timer = calloc(1, sizeof(struct loop_timer)); 172 | if (!timer) { 173 | swaylock_log(LOG_ERROR, "Unable to allocate memory for timer"); 174 | return NULL; 175 | } 176 | timer->callback = callback; 177 | timer->data = data; 178 | 179 | clock_gettime(CLOCK_MONOTONIC, &timer->expiry); 180 | timer->expiry.tv_sec += ms / 1000; 181 | 182 | long int nsec = (ms % 1000) * 1000000; 183 | if (timer->expiry.tv_nsec + nsec >= 1000000000) { 184 | timer->expiry.tv_sec++; 185 | nsec -= 1000000000; 186 | } 187 | timer->expiry.tv_nsec += nsec; 188 | 189 | wl_list_insert(&loop->timers, &timer->link); 190 | 191 | return timer; 192 | } 193 | 194 | bool loop_remove_fd(struct loop *loop, int fd) { 195 | size_t fd_index = 0; 196 | struct loop_fd_event *event = NULL, *tmp_event = NULL; 197 | wl_list_for_each_safe(event, tmp_event, &loop->fd_events, link) { 198 | if (loop->fds[fd_index].fd == fd) { 199 | event->removed = true; 200 | return true; 201 | } 202 | ++fd_index; 203 | } 204 | return false; 205 | } 206 | 207 | bool loop_remove_timer(struct loop *loop, struct loop_timer *remove) { 208 | struct loop_timer *timer = NULL, *tmp_timer = NULL; 209 | wl_list_for_each_safe(timer, tmp_timer, &loop->timers, link) { 210 | if (timer == remove) { 211 | timer->removed = true; 212 | return true; 213 | } 214 | } 215 | return false; 216 | } 217 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'swaylock-plugin', 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_server = dependency('wayland-server', version: '>=1.20.0') 32 | wayland_protos = dependency('wayland-protocols', version: '>=1.25', fallback: 'wayland-protocols') 33 | wayland_scanner = dependency('wayland-scanner', version: '>=1.15.0', native: true) 34 | xkbcommon = dependency('xkbcommon') 35 | cairo = dependency('cairo') 36 | gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf')) 37 | libpam = cc.find_library('pam', required: get_option('pam')) 38 | crypt = cc.find_library('crypt', required: not libpam.found()) 39 | math = cc.find_library('m') 40 | rt = cc.find_library('rt') 41 | logind = dependency('lib' + get_option('logind-provider'), required: get_option('logind')) 42 | 43 | git = find_program('git', required: false) 44 | scdoc = find_program('scdoc', required: get_option('man-pages')) 45 | wayland_scanner_prog = find_program(wayland_scanner.get_variable('wayland_scanner'), native: true) 46 | 47 | version = meson.project_version() 48 | if git.found() 49 | git_commit_hash = run_command([git, 'describe', '--always', '--tags'], check: false) 50 | git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false) 51 | if git_commit_hash.returncode() == 0 and git_branch.returncode() == 0 52 | version = '@0@ (" __DATE__ ", branch \'@1@\')'.format(git_commit_hash.stdout().strip(), git_branch.stdout().strip()) 53 | endif 54 | endif 55 | 56 | wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') 57 | 58 | wayland_scanner_code = generator( 59 | wayland_scanner_prog, 60 | output: '@BASENAME@-protocol.c', 61 | arguments: ['private-code', '@INPUT@', '@OUTPUT@'], 62 | ) 63 | 64 | wayland_scanner_client = generator( 65 | wayland_scanner_prog, 66 | output: '@BASENAME@-client-protocol.h', 67 | arguments: ['client-header', '@INPUT@', '@OUTPUT@'], 68 | ) 69 | 70 | wayland_scanner_server = generator( 71 | wayland_scanner_prog, 72 | output: '@BASENAME@-server-protocol.h', 73 | arguments: ['server-header', '@INPUT@', '@OUTPUT@'], 74 | ) 75 | 76 | client_protocols = [ 77 | wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', 78 | wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', 79 | wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', 80 | wl_protocol_dir / 'unstable/fullscreen-shell/fullscreen-shell-unstable-v1.xml', 81 | wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', 82 | wl_protocol_dir / 'stable/viewporter/viewporter.xml', 83 | wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', 84 | 'wayland-drm.xml', 85 | ] 86 | server_protocols = [ 87 | wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', 88 | wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', 89 | wl_protocol_dir / 'stable/viewporter/viewporter.xml', 90 | wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', 91 | 'wlr-layer-shell-unstable-v1.xml', 92 | 'wayland-drm.xml', 93 | ] 94 | 95 | protos_src = [] 96 | foreach xml : client_protocols 97 | protos_src += wayland_scanner_code.process(xml) 98 | protos_src += wayland_scanner_client.process(xml) 99 | endforeach 100 | 101 | foreach xml : server_protocols 102 | if not client_protocols.contains(xml) 103 | protos_src += wayland_scanner_code.process(xml) 104 | endif 105 | protos_src += wayland_scanner_server.process(xml) 106 | endforeach 107 | 108 | conf_data = configuration_data() 109 | conf_data.set_quoted('SYSCONFDIR', get_option('prefix') / get_option('sysconfdir')) 110 | conf_data.set_quoted('SWAYLOCK_VERSION', version) 111 | conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) 112 | conf_data.set10('HAVE_SYSTEMD', false) 113 | conf_data.set10('HAVE_ELOGIND', false) 114 | if logind.found() 115 | conf_data.set10('HAVE_' + get_option('logind-provider').to_upper(), true) 116 | endif 117 | 118 | subdir('include') 119 | 120 | dependencies = [ 121 | cairo, 122 | gdk_pixbuf, 123 | math, 124 | rt, 125 | xkbcommon, 126 | wayland_client, 127 | wayland_server, 128 | ] 129 | 130 | sources = [ 131 | 'background-image.c', 132 | 'cairo.c', 133 | 'comm.c', 134 | 'forward.c', 135 | 'log.c', 136 | 'loop.c', 137 | 'main.c', 138 | 'password.c', 139 | 'password-buffer.c', 140 | 'pool-buffer.c', 141 | 'render.c', 142 | 'seat.c', 143 | 'setsid.c', 144 | 'unicode.c', 145 | ] 146 | 147 | if libpam.found() 148 | sources += ['pam.c'] 149 | dependencies += [libpam] 150 | else 151 | warning('The swaylock binary must be setuid when compiled without libpam') 152 | warning('You must do this manually post-install: chmod a+s /path/to/swaylock') 153 | sources += ['shadow.c'] 154 | dependencies += [crypt] 155 | endif 156 | 157 | swaylock_inc = include_directories('include') 158 | 159 | executable('swaylock-plugin', 160 | sources + protos_src, 161 | include_directories: [swaylock_inc], 162 | dependencies: dependencies, 163 | install: true 164 | ) 165 | 166 | executable('swaylock-sleep-watcher', 167 | ['sleep-watcher.c', 'log.c'], 168 | include_directories: [swaylock_inc], 169 | dependencies: [logind], 170 | install: true 171 | ) 172 | 173 | if libpam.found() 174 | install_data( 175 | 'pam/swaylock-plugin', 176 | install_dir: get_option('sysconfdir') / 'pam.d' 177 | ) 178 | endif 179 | 180 | if scdoc.found() 181 | mandir = get_option('mandir') 182 | man_files = [ 183 | 'swaylock.1.scd', 184 | ] 185 | foreach filename : man_files 186 | topic = filename.split('.')[-3].split('/')[-1] 187 | section = filename.split('.')[-2] 188 | output = '@0@-plugin.@1@'.format(topic, section) 189 | 190 | custom_target( 191 | output, 192 | input: filename, 193 | output: output, 194 | command: scdoc, 195 | feed: true, 196 | capture: true, 197 | install: true, 198 | install_dir: '@0@/man@1@'.format(mandir, section) 199 | ) 200 | endforeach 201 | endif 202 | 203 | subdir('completions') 204 | -------------------------------------------------------------------------------- /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 | option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind (to automatically end grace period on sleep)') 8 | option('logind-provider', type: 'combo', choices: ['systemd', 'elogind'], value: 'systemd', description: 'Provider of logind support library') 9 | -------------------------------------------------------------------------------- /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-plugin 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-plugin cannot authenticate users; check /etc/pam.d/swaylock-plugin " 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-plugin", 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-plugin: -------------------------------------------------------------------------------- 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 "cairo.h" 5 | #include "background-image.h" 6 | #include "swaylock.h" 7 | #include "log.h" 8 | 9 | #define M_PI 3.14159265358979323846 10 | const float TYPE_INDICATOR_RANGE = M_PI / 3.0f; 11 | const float TYPE_INDICATOR_BORDER_THICKNESS = M_PI / 128.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 | int buffer_width = surface->width * surface->scale; 54 | int buffer_height = surface->height * surface->scale; 55 | if (buffer_width == 0 || buffer_height == 0) { 56 | return; // not yet configured 57 | } 58 | 59 | if (!surface->has_buffer) { 60 | return; // background not yet ready 61 | } 62 | 63 | if (!surface->dirty || surface->frame) { 64 | // Nothing to do or frame already pending 65 | return; 66 | } 67 | 68 | render_frame(surface); 69 | surface->dirty = false; 70 | surface->frame = wl_surface_frame(surface->surface); 71 | wl_callback_add_listener(surface->frame, &surface_frame_listener, surface); 72 | wl_surface_commit(surface->surface); 73 | } 74 | 75 | static void configure_font_drawing(cairo_t *cairo, struct swaylock_state *state, 76 | enum wl_output_subpixel subpixel, int arc_radius) { 77 | cairo_font_options_t *fo = cairo_font_options_create(); 78 | cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); 79 | cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); 80 | cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(subpixel)); 81 | 82 | cairo_set_font_options(cairo, fo); 83 | cairo_select_font_face(cairo, state->args.font, 84 | CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 85 | if (state->args.font_size > 0) { 86 | cairo_set_font_size(cairo, state->args.font_size); 87 | } else { 88 | cairo_set_font_size(cairo, arc_radius / 3.0f); 89 | } 90 | cairo_font_options_destroy(fo); 91 | } 92 | 93 | static bool render_frame(struct swaylock_surface *surface) { 94 | struct swaylock_state *state = surface->state; 95 | 96 | // First, compute the text that will be drawn, if any, since this 97 | // determines the size/positioning of the surface 98 | 99 | char attempts[4]; // like i3lock: count no more than 999 100 | char *text = NULL; 101 | const char *layout_text = NULL; 102 | 103 | bool draw_indicator = state->args.show_indicator && 104 | (state->auth_state != AUTH_STATE_IDLE || 105 | state->input_state != INPUT_STATE_IDLE || 106 | state->args.indicator_idle_visible); 107 | 108 | if (draw_indicator) { 109 | if (state->input_state == INPUT_STATE_CLEAR) { 110 | // This message has highest priority 111 | text = "Cleared"; 112 | } else if (state->auth_state == AUTH_STATE_VALIDATING) { 113 | text = "Verifying"; 114 | } else if (state->auth_state == AUTH_STATE_INVALID) { 115 | text = "Wrong"; 116 | } else { 117 | // Caps Lock has higher priority 118 | if (state->xkb.caps_lock && state->args.show_caps_lock_text) { 119 | text = "Caps Lock"; 120 | } else if (state->args.show_failed_attempts && 121 | state->failed_attempts > 0) { 122 | if (state->failed_attempts > 999) { 123 | text = "999+"; 124 | } else { 125 | snprintf(attempts, sizeof(attempts), "%d", state->failed_attempts); 126 | text = attempts; 127 | } 128 | } 129 | 130 | if (state->xkb.keymap) { 131 | xkb_layout_index_t num_layout = xkb_keymap_num_layouts(state->xkb.keymap); 132 | if (!state->args.hide_keyboard_layout && 133 | (state->args.show_keyboard_layout || num_layout > 1)) { 134 | xkb_layout_index_t curr_layout = 0; 135 | 136 | // advance to the first active layout (if any) 137 | while (curr_layout < num_layout && 138 | xkb_state_layout_index_is_active(state->xkb.state, 139 | curr_layout, XKB_STATE_LAYOUT_EFFECTIVE) != 1) { 140 | ++curr_layout; 141 | } 142 | // will handle invalid index if none are active 143 | layout_text = xkb_keymap_layout_get_name(state->xkb.keymap, curr_layout); 144 | } 145 | } 146 | } 147 | } 148 | 149 | // Compute the size of the buffer needed 150 | int arc_radius = state->args.radius * surface->scale; 151 | int arc_thickness = state->args.thickness * surface->scale; 152 | int buffer_diameter = (arc_radius + arc_thickness) * 2; 153 | int buffer_width = buffer_diameter; 154 | int buffer_height = buffer_diameter; 155 | 156 | if (text || layout_text) { 157 | cairo_set_antialias(state->test_cairo, CAIRO_ANTIALIAS_BEST); 158 | configure_font_drawing(state->test_cairo, state, surface->subpixel, arc_radius); 159 | 160 | if (text) { 161 | cairo_text_extents_t extents; 162 | cairo_text_extents(state->test_cairo, text, &extents); 163 | if (buffer_width < extents.width) { 164 | buffer_width = extents.width; 165 | } 166 | } 167 | if (layout_text) { 168 | cairo_text_extents_t extents; 169 | cairo_font_extents_t fe; 170 | double box_padding = 4.0 * surface->scale; 171 | cairo_text_extents(state->test_cairo, layout_text, &extents); 172 | cairo_font_extents(state->test_cairo, &fe); 173 | buffer_height += fe.height + 2 * box_padding; 174 | if (buffer_width < extents.width + 2 * box_padding) { 175 | buffer_width = extents.width + 2 * box_padding; 176 | } 177 | } 178 | } 179 | // Ensure buffer size is multiple of buffer scale - required by protocol 180 | buffer_height += surface->scale - (buffer_height % surface->scale); 181 | buffer_width += surface->scale - (buffer_width % surface->scale); 182 | 183 | int subsurf_xpos; 184 | int subsurf_ypos; 185 | 186 | // Center the indicator unless overridden by the user 187 | if (state->args.override_indicator_x_position) { 188 | subsurf_xpos = state->args.indicator_x_position - 189 | buffer_width / (2 * surface->scale) + 2 / surface->scale; 190 | } else { 191 | subsurf_xpos = surface->width / 2 - 192 | buffer_width / (2 * surface->scale) + 2 / surface->scale; 193 | } 194 | 195 | if (state->args.override_indicator_y_position) { 196 | subsurf_ypos = state->args.indicator_y_position - 197 | (state->args.radius + state->args.thickness); 198 | } else { 199 | subsurf_ypos = surface->height / 2 - 200 | (state->args.radius + state->args.thickness); 201 | } 202 | 203 | struct pool_buffer *buffer = get_next_buffer(state->shm, 204 | surface->indicator_buffers, buffer_width, buffer_height); 205 | if (buffer == NULL) { 206 | swaylock_log(LOG_ERROR, "No buffer"); 207 | return false; 208 | } 209 | 210 | // Render the buffer 211 | cairo_t *cairo = buffer->cairo; 212 | cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); 213 | 214 | cairo_identity_matrix(cairo); 215 | 216 | // Clear 217 | cairo_save(cairo); 218 | cairo_set_source_rgba(cairo, 0, 0, 0, 0); 219 | cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); 220 | cairo_paint(cairo); 221 | cairo_restore(cairo); 222 | 223 | float type_indicator_border_thickness = 224 | TYPE_INDICATOR_BORDER_THICKNESS * surface->scale; 225 | 226 | if (draw_indicator) { 227 | // Fill inner circle 228 | cairo_set_line_width(cairo, 0); 229 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, 230 | arc_radius - arc_thickness / 2, 0, 2 * M_PI); 231 | set_color_for_state(cairo, state, &state->args.colors.inside); 232 | cairo_fill_preserve(cairo); 233 | cairo_stroke(cairo); 234 | 235 | // Draw ring 236 | cairo_set_line_width(cairo, arc_thickness); 237 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, arc_radius, 238 | 0, 2 * M_PI); 239 | set_color_for_state(cairo, state, &state->args.colors.ring); 240 | cairo_stroke(cairo); 241 | 242 | // Draw a message 243 | configure_font_drawing(cairo, state, surface->subpixel, arc_radius); 244 | set_color_for_state(cairo, state, &state->args.colors.text); 245 | 246 | if (text) { 247 | cairo_text_extents_t extents; 248 | cairo_font_extents_t fe; 249 | double x, y; 250 | cairo_text_extents(cairo, text, &extents); 251 | cairo_font_extents(cairo, &fe); 252 | x = (buffer_width / 2) - 253 | (extents.width / 2 + extents.x_bearing); 254 | y = (buffer_diameter / 2) + 255 | (fe.height / 2 - fe.descent); 256 | 257 | cairo_move_to(cairo, x, y); 258 | cairo_show_text(cairo, text); 259 | cairo_close_path(cairo); 260 | cairo_new_sub_path(cairo); 261 | } 262 | 263 | // Typing indicator: Highlight random part on keypress 264 | if (state->input_state == INPUT_STATE_LETTER || 265 | state->input_state == INPUT_STATE_BACKSPACE) { 266 | double highlight_start = state->highlight_start * (M_PI / 1024.0); 267 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, 268 | arc_radius, highlight_start, 269 | highlight_start + TYPE_INDICATOR_RANGE); 270 | if (state->input_state == INPUT_STATE_LETTER) { 271 | if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { 272 | cairo_set_source_u32(cairo, state->args.colors.caps_lock_key_highlight); 273 | } else { 274 | cairo_set_source_u32(cairo, state->args.colors.key_highlight); 275 | } 276 | } else { 277 | if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) { 278 | cairo_set_source_u32(cairo, state->args.colors.caps_lock_bs_highlight); 279 | } else { 280 | cairo_set_source_u32(cairo, state->args.colors.bs_highlight); 281 | } 282 | } 283 | cairo_stroke(cairo); 284 | 285 | // Draw borders 286 | cairo_set_source_u32(cairo, state->args.colors.separator); 287 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, 288 | arc_radius, highlight_start, 289 | highlight_start + type_indicator_border_thickness); 290 | cairo_stroke(cairo); 291 | 292 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, 293 | arc_radius, highlight_start + TYPE_INDICATOR_RANGE, 294 | highlight_start + TYPE_INDICATOR_RANGE + 295 | type_indicator_border_thickness); 296 | cairo_stroke(cairo); 297 | } 298 | 299 | // Draw inner + outer border of the circle 300 | set_color_for_state(cairo, state, &state->args.colors.line); 301 | cairo_set_line_width(cairo, 2.0 * surface->scale); 302 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, 303 | arc_radius - arc_thickness / 2, 0, 2 * M_PI); 304 | cairo_stroke(cairo); 305 | cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, 306 | arc_radius + arc_thickness / 2, 0, 2 * M_PI); 307 | cairo_stroke(cairo); 308 | 309 | // display layout text separately 310 | if (layout_text) { 311 | cairo_text_extents_t extents; 312 | cairo_font_extents_t fe; 313 | double x, y; 314 | double box_padding = 4.0 * surface->scale; 315 | cairo_text_extents(cairo, layout_text, &extents); 316 | cairo_font_extents(cairo, &fe); 317 | // upper left coordinates for box 318 | x = (buffer_width / 2) - (extents.width / 2) - box_padding; 319 | y = buffer_diameter; 320 | 321 | // background box 322 | cairo_rectangle(cairo, x, y, 323 | extents.width + 2.0 * box_padding, 324 | fe.height + 2.0 * box_padding); 325 | cairo_set_source_u32(cairo, state->args.colors.layout_background); 326 | cairo_fill_preserve(cairo); 327 | // border 328 | cairo_set_source_u32(cairo, state->args.colors.layout_border); 329 | cairo_stroke(cairo); 330 | 331 | // take font extents and padding into account 332 | cairo_move_to(cairo, 333 | x - extents.x_bearing + box_padding, 334 | y + (fe.height - fe.descent) + box_padding); 335 | cairo_set_source_u32(cairo, state->args.colors.layout_text); 336 | cairo_show_text(cairo, layout_text); 337 | cairo_new_sub_path(cairo); 338 | } 339 | } 340 | 341 | // Send Wayland requests 342 | wl_subsurface_set_position(surface->subsurface, subsurf_xpos, subsurf_ypos); 343 | 344 | wl_surface_set_buffer_scale(surface->child, surface->scale); 345 | wl_surface_attach(surface->child, buffer->buffer, 0, 0); 346 | wl_surface_damage_buffer(surface->child, 0, 0, INT32_MAX, INT32_MAX); 347 | wl_surface_commit(surface->child); 348 | 349 | return true; 350 | } 351 | -------------------------------------------------------------------------------- /seat.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "log.h" 9 | #include "swaylock.h" 10 | #include "seat.h" 11 | #include "loop.h" 12 | 13 | static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, 14 | uint32_t format, int32_t fd, uint32_t size) { 15 | struct swaylock_seat *seat = data; 16 | struct swaylock_state *state = seat->state; 17 | if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { 18 | close(fd); 19 | swaylock_log(LOG_ERROR, "Unknown keymap format %d, aborting", format); 20 | exit(1); 21 | } 22 | char *map_shm = mmap(NULL, size - 1, PROT_READ, MAP_PRIVATE, fd, 0); 23 | if (map_shm == MAP_FAILED) { 24 | close(fd); 25 | swaylock_log(LOG_ERROR, "Unable to initialize keymap shm, aborting"); 26 | exit(1); 27 | } 28 | struct xkb_keymap *keymap = xkb_keymap_new_from_buffer( 29 | state->xkb.context, map_shm, size - 1, XKB_KEYMAP_FORMAT_TEXT_V1, 30 | XKB_KEYMAP_COMPILE_NO_FLAGS); 31 | munmap(map_shm, size - 1); 32 | close(fd); 33 | assert(keymap); 34 | struct xkb_state *xkb_state = xkb_state_new(keymap); 35 | assert(xkb_state); 36 | xkb_keymap_unref(state->xkb.keymap); 37 | xkb_state_unref(state->xkb.state); 38 | state->xkb.keymap = keymap; 39 | state->xkb.state = xkb_state; 40 | } 41 | 42 | static void keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, 43 | uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { 44 | // Who cares 45 | } 46 | 47 | static void keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, 48 | uint32_t serial, struct wl_surface *surface) { 49 | // Who cares 50 | } 51 | 52 | static void keyboard_repeat(void *data) { 53 | struct swaylock_seat *seat = data; 54 | struct swaylock_state *state = seat->state; 55 | seat->repeat_timer = loop_add_timer( 56 | state->eventloop, seat->repeat_period_ms, keyboard_repeat, seat); 57 | swaylock_handle_key(state, seat->repeat_sym, seat->repeat_codepoint); 58 | } 59 | 60 | static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, 61 | uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) { 62 | struct swaylock_seat *seat = data; 63 | struct swaylock_state *state = seat->state; 64 | enum wl_keyboard_key_state key_state = _key_state; 65 | xkb_keysym_t sym = xkb_state_key_get_one_sym(state->xkb.state, key + 8); 66 | uint32_t keycode = key_state == WL_KEYBOARD_KEY_STATE_PRESSED ? 67 | key + 8 : 0; 68 | uint32_t codepoint = xkb_state_key_get_utf32(state->xkb.state, keycode); 69 | if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED) { 70 | if (state->grace_timer) { 71 | /* still in grace period, immediately unlock */ 72 | swaylock_log(LOG_DEBUG, "Key press during grace period, unlocking"); 73 | state->run_display = false; 74 | return; 75 | } 76 | swaylock_handle_key(state, sym, codepoint); 77 | } 78 | 79 | if (seat->repeat_timer) { 80 | loop_remove_timer(seat->state->eventloop, seat->repeat_timer); 81 | seat->repeat_timer = NULL; 82 | } 83 | 84 | if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && seat->repeat_period_ms > 0) { 85 | seat->repeat_sym = sym; 86 | seat->repeat_codepoint = codepoint; 87 | seat->repeat_timer = loop_add_timer( 88 | seat->state->eventloop, seat->repeat_delay_ms, keyboard_repeat, seat); 89 | } 90 | } 91 | 92 | static void keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, 93 | uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, 94 | uint32_t mods_locked, uint32_t group) { 95 | struct swaylock_seat *seat = data; 96 | struct swaylock_state *state = seat->state; 97 | if (state->xkb.state == NULL) { 98 | return; 99 | } 100 | 101 | int layout_same = xkb_state_layout_index_is_active(state->xkb.state, 102 | group, XKB_STATE_LAYOUT_EFFECTIVE); 103 | if (!layout_same) { 104 | damage_state(state); 105 | } 106 | xkb_state_update_mask(state->xkb.state, 107 | mods_depressed, mods_latched, mods_locked, 0, 0, group); 108 | int caps_lock = xkb_state_mod_name_is_active(state->xkb.state, 109 | XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED); 110 | if (caps_lock != state->xkb.caps_lock) { 111 | state->xkb.caps_lock = caps_lock; 112 | damage_state(state); 113 | } 114 | state->xkb.control = xkb_state_mod_name_is_active(state->xkb.state, 115 | XKB_MOD_NAME_CTRL, 116 | XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); 117 | } 118 | 119 | static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, 120 | int32_t rate, int32_t delay) { 121 | struct swaylock_seat *seat = data; 122 | if (rate <= 0) { 123 | seat->repeat_period_ms = -1; 124 | } else { 125 | // Keys per second -> milliseconds between keys 126 | seat->repeat_period_ms = 1000 / rate; 127 | } 128 | seat->repeat_delay_ms = delay; 129 | } 130 | 131 | static const struct wl_keyboard_listener keyboard_listener = { 132 | .keymap = keyboard_keymap, 133 | .enter = keyboard_enter, 134 | .leave = keyboard_leave, 135 | .key = keyboard_key, 136 | .modifiers = keyboard_modifiers, 137 | .repeat_info = keyboard_repeat_info, 138 | }; 139 | 140 | static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, 141 | uint32_t serial, struct wl_surface *surface, 142 | wl_fixed_t surface_x, wl_fixed_t surface_y) { 143 | struct swaylock_seat *seat = data; 144 | wl_pointer_set_cursor(wl_pointer, serial, NULL, 0, 0); 145 | if (!seat->state->grace_timer) { 146 | return; 147 | } 148 | struct timespec current_time; 149 | clock_gettime(CLOCK_MONOTONIC, ¤t_time); 150 | seat->last_interval = current_time.tv_sec; 151 | seat->interval_start_x = surface_x; 152 | seat->interval_start_y = surface_y; 153 | seat->last_mouse_x = surface_x; 154 | seat->last_mouse_y = surface_y; 155 | } 156 | 157 | static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, 158 | uint32_t serial, struct wl_surface *surface) { 159 | // Who cares 160 | } 161 | 162 | static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, 163 | uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { 164 | struct swaylock_seat *seat = data; 165 | if (!seat->state->grace_timer) { 166 | return; 167 | } 168 | struct timespec current_time; 169 | clock_gettime(CLOCK_MONOTONIC, ¤t_time); 170 | int64_t intv = current_time.tv_sec; 171 | if (intv != seat->last_interval) { 172 | seat->last_interval = intv; 173 | seat->interval_start_x = seat->last_mouse_x; 174 | seat->interval_start_y = seat->last_mouse_y; 175 | } 176 | float dx = wl_fixed_to_double(surface_x - seat->interval_start_x); 177 | float dy = wl_fixed_to_double(surface_y - seat->interval_start_y); 178 | float thresh = seat->state->args.grace_pointer_hysteresis; 179 | if (dx * dx + dy * dy >= thresh * thresh) { 180 | /* Mouse moved more than hysteresis value in a one-second time period; 181 | * this is probably a deliberate action and not noise or a minor tremor */ 182 | swaylock_log(LOG_DEBUG, "Significant mouse motion (%f logical px) during grace period, unlocking", 183 | sqrtf(dx * dx + dy * dy)); 184 | seat->state->run_display = false; 185 | } 186 | seat->last_mouse_x = surface_x; 187 | seat->last_mouse_y = surface_y; 188 | } 189 | 190 | static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, 191 | uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { 192 | struct swaylock_seat *seat = data; 193 | if (seat->state->grace_timer) { 194 | /* unlock during grace period on click */ 195 | swaylock_log(LOG_DEBUG, "Mouse button action during grace period, unlocking"); 196 | seat->state->run_display = false; 197 | } 198 | } 199 | 200 | static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, 201 | uint32_t time, uint32_t axis, wl_fixed_t value) { 202 | // Who cares 203 | } 204 | 205 | static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { 206 | // Who cares 207 | } 208 | 209 | static void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, 210 | uint32_t axis_source) { 211 | // Who cares 212 | } 213 | 214 | static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, 215 | uint32_t time, uint32_t axis) { 216 | // Who cares 217 | } 218 | 219 | static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, 220 | uint32_t axis, int32_t discrete) { 221 | // Who cares 222 | } 223 | 224 | static void wl_pointer_axis_value120(void *data, struct wl_pointer *wl_pointer, 225 | uint32_t axis, int32_t value120) { 226 | // Who cares 227 | } 228 | 229 | static void wl_pointer_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, 230 | uint32_t axis, uint32_t direction) { 231 | // Who cares 232 | } 233 | 234 | static const struct wl_pointer_listener pointer_listener = { 235 | .enter = wl_pointer_enter, 236 | .leave = wl_pointer_leave, 237 | .motion = wl_pointer_motion, 238 | .button = wl_pointer_button, 239 | .axis = wl_pointer_axis, 240 | .frame = wl_pointer_frame, 241 | .axis_source = wl_pointer_axis_source, 242 | .axis_stop = wl_pointer_axis_stop, 243 | .axis_discrete = wl_pointer_axis_discrete, 244 | .axis_value120 = wl_pointer_axis_value120, 245 | .axis_relative_direction = wl_pointer_axis_relative_direction, 246 | }; 247 | 248 | static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, 249 | enum wl_seat_capability caps) { 250 | struct swaylock_seat *seat = data; 251 | if (seat->pointer) { 252 | wl_pointer_release(seat->pointer); 253 | seat->pointer = NULL; 254 | } 255 | if (seat->keyboard) { 256 | wl_keyboard_release(seat->keyboard); 257 | seat->keyboard = NULL; 258 | } 259 | if ((caps & WL_SEAT_CAPABILITY_POINTER)) { 260 | seat->pointer = wl_seat_get_pointer(wl_seat); 261 | wl_pointer_add_listener(seat->pointer, &pointer_listener, seat); 262 | } 263 | if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) { 264 | seat->keyboard = wl_seat_get_keyboard(wl_seat); 265 | wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, seat); 266 | } 267 | } 268 | 269 | static void seat_handle_name(void *data, struct wl_seat *wl_seat, 270 | const char *name) { 271 | // Who cares 272 | } 273 | 274 | const struct wl_seat_listener seat_listener = { 275 | .capabilities = seat_handle_capabilities, 276 | .name = seat_handle_name, 277 | }; 278 | -------------------------------------------------------------------------------- /setsid.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | 5 | // The flag POSIX_SPAWN_SETSID is on track, but not officially standardized yet 6 | // glibc and musl should both support it 7 | uint32_t posix_spawn_setsid_flag() { 8 | #ifdef POSIX_SPAWN_SETSID 9 | return POSIX_SPAWN_SETSID; 10 | #else 11 | return 0; 12 | #endif 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sleep-watcher.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "config.h" 12 | #include "log.h" 13 | #if HAVE_SYSTEMD 14 | #include 15 | #include 16 | #elif HAVE_ELOGIND 17 | #include 18 | #include 19 | #endif 20 | 21 | #if HAVE_SYSTEMD || HAVE_ELOGIND 22 | #define DBUS_LOGIND_SERVICE "org.freedesktop.login1" 23 | #define DBUS_LOGIND_PATH "/org/freedesktop/login1" 24 | #define DBUS_LOGIND_MANAGER_INTERFACE "org.freedesktop.login1.Manager" 25 | #define DBUS_LOGIND_SESSION_INTERFACE "org.freedesktop.login1.Session" 26 | 27 | struct state { 28 | char *session_name; 29 | int sleep_lock_fd; 30 | struct sd_bus *bus; 31 | int comm_r; 32 | int comm_w; 33 | }; 34 | 35 | static void acquire_inhibitor_lock(struct state *state, 36 | const char *type, const char *mode) { 37 | sd_bus_message *msg = NULL; 38 | sd_bus_error error = SD_BUS_ERROR_NULL; 39 | int ret = sd_bus_call_method(state->bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, 40 | DBUS_LOGIND_MANAGER_INTERFACE, "Inhibit", &error, &msg, 41 | "ssss", type, "swaylock-sleep-helper", 42 | "Waiting to ensure screen lock grace period is ended before sleep", mode); 43 | if (ret < 0) { 44 | swaylock_log(LOG_ERROR, 45 | "Failed to send %s inhibit signal: %s", type, error.message); 46 | goto cleanup; 47 | } 48 | 49 | ret = sd_bus_message_read(msg, "h", &state->sleep_lock_fd); 50 | if (ret < 0) { 51 | errno = -ret; 52 | swaylock_log_errno(LOG_ERROR, 53 | "Failed to parse D-Bus response for %s inhibit", type); 54 | goto cleanup; 55 | } 56 | 57 | state->sleep_lock_fd = fcntl(state->sleep_lock_fd, F_DUPFD_CLOEXEC, 3); 58 | if (state->sleep_lock_fd >= 0) { 59 | swaylock_log(LOG_DEBUG, "Got %s lock: %d", type, state->sleep_lock_fd); 60 | } else { 61 | swaylock_log_errno(LOG_ERROR, "Failed to copy %s lock fd", type); 62 | } 63 | 64 | cleanup: 65 | sd_bus_error_free(&error); 66 | sd_bus_message_unref(msg); 67 | } 68 | 69 | static void release_inhibitor_lock(struct state *state) { 70 | if (state->sleep_lock_fd >= 0) { 71 | swaylock_log(LOG_DEBUG, "Releasing inhibitor lock %d", state->sleep_lock_fd); 72 | close(state->sleep_lock_fd); 73 | } 74 | state->sleep_lock_fd = -1; 75 | } 76 | 77 | static int prepare_for_sleep(sd_bus_message *msg, void *userdata, 78 | sd_bus_error *ret_error) { 79 | struct state *state = userdata; 80 | /* "b" apparently reads into an int, not a bool */ 81 | int going_down = 1; 82 | int ret = sd_bus_message_read(msg, "b", &going_down); 83 | if (ret < 0) { 84 | errno = -ret; 85 | swaylock_log_errno(LOG_ERROR, 86 | "Failed to parse D-Bus response for Inhibit"); 87 | } 88 | swaylock_log(LOG_DEBUG, "PrepareForSleep signal received %d", going_down); 89 | if (!going_down) { 90 | acquire_inhibitor_lock(state, "sleep", "delay"); 91 | return 0; 92 | } else { 93 | assert(state->comm_w != -1); 94 | close(state->comm_w); 95 | state->comm_w = -1; 96 | 97 | char tmp; 98 | int ret = read(state->comm_r, &tmp, 1); 99 | if (ret == -1) { 100 | /* Error */ 101 | swaylock_log_errno(LOG_ERROR, 102 | "Failed to read from comm pipe"); 103 | } else if (ret == 0) { 104 | /* swaylock-plugin has confirmed receipt of message by closing 105 | * the write end of comm_r */ 106 | swaylock_log(LOG_DEBUG, 107 | "swaylock-plugin acknowledged start of sleep"); 108 | } else { 109 | swaylock_log(LOG_ERROR, 110 | "Unexpected data on comm pipe"); 111 | } 112 | } 113 | swaylock_log(LOG_DEBUG, "Prepare for sleep done"); 114 | 115 | release_inhibitor_lock(state); 116 | return 0; 117 | } 118 | 119 | static void set_session(struct state *state) { 120 | sd_bus_message *msg = NULL; 121 | sd_bus_error error = SD_BUS_ERROR_NULL; 122 | const char *session_name_tmp; 123 | 124 | int ret = sd_bus_call_method(state->bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, 125 | DBUS_LOGIND_MANAGER_INTERFACE, "GetSession", &error, &msg, "s", "auto"); 126 | if (ret < 0) { 127 | swaylock_log(LOG_DEBUG, 128 | "GetSession failed: %s", error.message); 129 | sd_bus_error_free(&error); 130 | sd_bus_message_unref(msg); 131 | 132 | ret = sd_bus_call_method(state->bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, 133 | DBUS_LOGIND_MANAGER_INTERFACE, "GetSessionByPID", 134 | &error, &msg, "u", getpid()); 135 | if (ret < 0) { 136 | swaylock_log(LOG_DEBUG, "GetSessionByPID failed: %s", error.message); 137 | swaylock_log(LOG_ERROR, "Failed to find session"); 138 | goto cleanup; 139 | } 140 | } 141 | 142 | ret = sd_bus_message_read(msg, "o", &session_name_tmp); 143 | if (ret < 0) { 144 | swaylock_log(LOG_ERROR, 145 | "Failed to read session name"); 146 | goto cleanup; 147 | } 148 | state->session_name = strdup(session_name_tmp); 149 | swaylock_log(LOG_DEBUG, "Using session: %s", state->session_name); 150 | 151 | cleanup: 152 | sd_bus_error_free(&error); 153 | sd_bus_message_unref(msg); 154 | } 155 | 156 | int run(int comm_r, int comm_w) { 157 | struct state state = {0}; 158 | state.sleep_lock_fd = -1; 159 | state.comm_r = comm_r; 160 | state.comm_w = comm_w; 161 | 162 | int ret = sd_bus_default_system(&state.bus); 163 | if (ret < 0) { 164 | errno = -ret; 165 | swaylock_log_errno(LOG_ERROR, "Failed to open D-Bus connection"); 166 | return EXIT_FAILURE; 167 | } 168 | 169 | set_session(&state); 170 | 171 | ret = sd_bus_match_signal(state.bus, NULL, DBUS_LOGIND_SERVICE, 172 | DBUS_LOGIND_PATH, DBUS_LOGIND_MANAGER_INTERFACE, 173 | "PrepareForSleep", prepare_for_sleep, NULL); 174 | if (ret < 0) { 175 | errno = -ret; 176 | swaylock_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : sleep"); 177 | return EXIT_FAILURE; 178 | } 179 | acquire_inhibitor_lock(&state, "sleep", "delay"); 180 | 181 | struct pollfd pfds[2]; 182 | pfds[0].fd = state.comm_r; 183 | pfds[0].events = POLLIN; 184 | pfds[1].fd = sd_bus_get_fd(state.bus); 185 | pfds[1].events = POLLIN; 186 | 187 | while (true) { 188 | int ret = poll(pfds, 2, -1); 189 | if (ret == -1) { 190 | if (errno == EINTR) { 191 | continue; 192 | } 193 | swaylock_log_errno(LOG_ERROR, "poll failed, exiting"); 194 | break; 195 | } 196 | if (pfds[0].revents & (POLLIN | POLLERR | POLLHUP)) { 197 | char tmp; 198 | int ret = read(state.comm_r, &tmp, 1); 199 | if (ret == -1) { 200 | /* Error */ 201 | swaylock_log_errno(LOG_ERROR, 202 | "Failed to read from comm pipe"); 203 | } else if (ret == 0) { 204 | /* grace period ended, this program may exit */ 205 | swaylock_log(LOG_DEBUG, 206 | "swaylock-plugin grace period ended"); 207 | } else { 208 | swaylock_log(LOG_ERROR, 209 | "Unexpected data on comm pipe"); 210 | } 211 | break; 212 | } 213 | 214 | if (pfds[1].revents & (POLLIN | POLLERR | POLLHUP)) { 215 | int count = sd_bus_process(state.bus, NULL); 216 | sd_bus_flush(state.bus); 217 | if (count < 0) { 218 | swaylock_log_errno(LOG_ERROR, "sd_bus_process failed, exiting"); 219 | break; 220 | } 221 | } 222 | } 223 | sd_bus_close(state.bus); 224 | swaylock_log(LOG_DEBUG, "Exiting"); 225 | return EXIT_SUCCESS; 226 | } 227 | #endif 228 | 229 | 230 | int main(int argc, char **argv) { 231 | if (argc != 3) { 232 | fprintf(stderr, 233 | "This is a helper program for swaylock-plugin to ensure that if a grace\n" 234 | "period is used, the screen will still lock when the system goes to sleep\n" 235 | "or hibernation. It is automatically run by swaylock-plugin when necessary.\n"); 236 | return EXIT_FAILURE; 237 | } 238 | 239 | swaylock_log_init(LOG_DEBUG); 240 | 241 | char *end1 = NULL; 242 | int comm_w = strtol(argv[1], &end1, 10); 243 | char *end2 = NULL; 244 | int comm_r = strtol(argv[2], &end2, 10); 245 | if (*end1 != 0 || *end2 != 0) { 246 | swaylock_log(LOG_ERROR, "Failed to get communication pipes"); 247 | return EXIT_FAILURE; 248 | } 249 | 250 | #if !HAVE_SYSTEMD && !HAVE_ELOGIND 251 | (void)comm_r; 252 | (void)comm_w; 253 | return EXIT_FAILURE; 254 | #else 255 | return run(comm_r, comm_w); 256 | #endif 257 | } 258 | -------------------------------------------------------------------------------- /swaylock.1.scd: -------------------------------------------------------------------------------- 1 | swaylock-plugin(1) 2 | 3 | # NAME 4 | 5 | swaylock-plugin - Screen locker for Wayland 6 | 7 | # SYNOPSIS 8 | 9 | _swaylock-plugin_ [options...] 10 | 11 | Locks your Wayland session. 12 | 13 | # OPTIONS 14 | 15 | *--command* 16 | Specify the program used to draw the background. It must promptly 17 | provide a full-size background layer surface for each Wayland output. 18 | It will be provided a Wayland connection using the _WAYLAND_SOCKET_ 19 | environment variable, and will be restarted if it closes that 20 | connection. 21 | 22 | *--command-each* 23 | Like *--command*, except that the program is executed once for each output. 24 | Each instance of the program will only see a single output for which it 25 | should provide a surface. Each will also receive, through the environment 26 | variables _SWAYLOCK_PLUGIN_OUTPUT_NAME_ and _SWAYLOCK_PLUGIN_OUTPUT_DESC_, 27 | the values of the compositor's _wl_output::name_ and _wl_output::description_ 28 | for the instance's output. 29 | 30 | *-C, --config* 31 | The config file to use. By default, the following paths are checked: 32 | _$HOME/.swaylock/config_, _$XDG\_CONFIG\_HOME/swaylock/config_, and 33 | _SYSCONFDIR/swaylock/config_. All flags aside from this one are valid 34 | options in the configuration file using the format _long-option=value_. 35 | For options such as _ignore-empty-password_, just supply the _long-option_. 36 | All leading dashes should be omitted and the equals sign is required for 37 | flags that take an argument. 38 | 39 | *-d, --debug* 40 | Enable debugging output. 41 | 42 | *-e, --ignore-empty-password* 43 | When an empty password is provided, do not validate it. 44 | 45 | *-F, --show-failed-attempts* 46 | Show current count of failed authentication attempts. 47 | 48 | *-f, --daemonize* 49 | Detach from the controlling terminal after locking. 50 | 51 | Note: this is the default behavior of i3lock. 52 | 53 | *--grace*