i3lock-color

The world's most popular non-default computer lockscreen.
Index Commits Files Refs README LICENSE
commit c0367e1449084f28ab874456c8e650f0724e050f
parent 684db97e83a2e7f57721854c8ce03421240c2075
Author: Cassandra Fox <cass@fox.mom>
Date:   Thu, 30 May 2019 17:39:18 -0700

Merge pull request #120 from mortie/i3lock-color

Merge in upstream changes to i3lock
Diffstat:
MCHANGELOG | 17+++++++++++++++++
MMakefile.am | 7++++++-
MREADME.md | 18++++++++++++++++++
Mi3lock.1 | 15+++++++++++++++
Mi3lock.c | 230++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mxcb.c | 9+++++++++
6 files changed, 281 insertions(+), 15 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
@@ -1,3 +1,20 @@
+2018-10-18 i3lock 2.11.1
+
+ • Fix dist tarball by including I3LOCK_VERSION
+
+2018-10-10 i3lock 2.11
+
+ • Switch to autotools
+ • Display an error when backspace is pressed without any input
+ • Print an error when a non-PNG file is opened
+   (i3lock only supports PNG files) (Thanks eplanet)
+ • Don’t unnecessarily check the xcb_connect return value,
+   it is known never to be NULL (Thanks SegFault42)
+ • Fix memory leak when grabbing fails (Thanks karulont)
+ • Respect Xft.dpi for determining the unlock indicator’s scale factor
+ • Discard pending password verification attempts
+   when a new password is entered (Thanks layus)
+
 2017-11-25 i3lock 2.10
 
  • Only use -lpam when not on OpenBSD (Thanks Kaashif)
diff --git a/Makefile.am b/Makefile.am
@@ -67,4 +67,9 @@ EXTRA_DIST = \
     $(pamd_files) \
     CHANGELOG \
     LICENSE \
-    README.md
+    README.md \
+    I3LOCK_VERSION
+
+# SUID the executable so it has permissions to lock TTY switching
+install-exec-hook:
+    chmod +s $(DESTDIR)$(bindir)/i3lock$(EXEEXT)
diff --git a/README.md b/README.md
@@ -101,6 +101,24 @@ A [sample script](https://github.com/PandorasFox/i3lock-color/blob/master/lock.s
 On OpenBSD the `i3lock` binary needs to be setgid `auth` to call the
 authentication helpers, e.g. `/usr/libexec/auth/login_passwd`.
 
+Building i3lock
+---------------
+First install the dependencies listed in requirements section, then run these
+commands (might need to be adapted to your OS):
+```
+autoreconf --force --install
+
+rm -rf build/
+mkdir -p build && cd build/
+
+../configure \
+  --prefix=/usr \
+  --sysconfdir=/etc \
+  --disable-sanitizers
+
+make
+```
+
 Upstream
 --------
 Please submit pull requests for i3lock things to https://github.com/i3/i3lock and pull requests for features to me here at https://github.com/PandorasFox/i3lock-color.
diff --git a/i3lock.1 b/i3lock.1
@@ -29,6 +29,7 @@ i3lock-color \- improved screen locker
 .RB [\|\-e\|]
 .RB [\|\-f\|]
 .RB [\|\-m\|]
+.RB [\|\-l\|]
 
 .SH DESCRIPTION
 .B i3lock-color
@@ -75,6 +76,16 @@ verified or whether it is wrong).
 Display the given PNG image instead of a blank screen.
 
 .TP
+.BI \fB\-\-raw= format
+Read the image given by \-\-image as a raw image instead of PNG. The argument is the image's format
+as <width>x<height>:<pixfmt>. The supported pixel formats are:
+\'native', 'rgb', 'xrgb', 'rgbx', 'bgr', 'xbgr', and 'bgrx'.
+The "native" pixel format expects a pixel as a 32-bit (4-byte) integer in
+the machine's native endianness, with the upper 8 bits unused. Red, green and blue are stored in
+the remaining bits, in that order.
+Example: \-\-raw=1920x1080:rgb
+
+.TP
 .BI \-c\  rrggbb \fR,\ \fB\-\-color= rrggbb
 Turn the screen into the given color instead of white. Color must be given in 3-byte
 format: rrggbb (i.e. ff0000 is red).
@@ -107,6 +118,10 @@ your computer with the enter key.
 Show the number of failed attempts, if any.
 
 .TP
+.B \-l, \-\-lock-console
+Lock the console to disable TTY switching (Linux only).
+
+.TP
 .B \-\-debug
 Enables debug logging.
 Note, that this will log the password used for authentication to stdout.
diff --git a/i3lock.c b/i3lock.c
@@ -49,6 +49,11 @@
 #endif
 #include <xcb/xcb_aux.h>
 #include <xcb/randr.h>
+#if defined(__linux__)
+#include <fcntl.h>
+#include <linux/vt.h>
+#include <sys/ioctl.h>
+#endif
 
 #include "i3lock.h"
 #include "xcb.h"
@@ -553,7 +558,7 @@ static void input_done(void) {
         else if (strcmp(mod_name, XKB_MOD_NAME_NUM) == 0)
             mod_name = "Num Lock";
         else if (strcmp(mod_name, XKB_MOD_NAME_LOGO) == 0)
-            mod_name = "Win";
+            mod_name = "Super";
 
         char *tmp;
         if (modifier_string == NULL) {
@@ -887,6 +892,152 @@ void handle_screen_resize(void) {
     redraw_screen();
 }
 
+static ssize_t read_raw_image_native(uint32_t *dest, FILE *src, size_t width, size_t height, int pixstride) {
+    ssize_t count = 0;
+    for (size_t y = 0; y < height; y++) {
+        size_t n = fread(&dest[y * pixstride], 1, width * 4, src);
+        count += n;
+        if (n < (size_t)(width * 4))
+            break;
+    }
+
+    return count;
+}
+
+struct raw_pixel_format {
+    int bpp;
+    int red;
+    int green;
+    int blue;
+};
+
+static ssize_t read_raw_image_fmt(uint32_t *dest, FILE *src, size_t width, size_t height, int pixstride,
+                                  struct raw_pixel_format fmt) {
+    unsigned char *buf = malloc(width * fmt.bpp);
+    if (buf == NULL)
+        return -1;
+
+    ssize_t count = 0;
+    for (size_t y = 0; y < height; y++) {
+        size_t n = fread(buf, 1, width * fmt.bpp, src);
+        count += n;
+        if (n < (size_t)(width * fmt.bpp))
+            break;
+
+        for (size_t x = 0; x < width; ++x) {
+            int idx = x * fmt.bpp;
+            dest[y * pixstride + x] = 0 |
+                                      (buf[idx + fmt.red]) << 16 |
+                                      (buf[idx + fmt.green]) << 8 |
+                                      (buf[idx + fmt.blue]);
+        }
+    }
+
+    free(buf);
+    return count;
+}
+
+// Pre-defind pixel formats (<bytes per pixel>, <red pixel>, <green pixel>, <blue pixel>)
+static const struct raw_pixel_format raw_fmt_rgb = {3, 0, 1, 2};
+static const struct raw_pixel_format raw_fmt_rgbx = {4, 0, 1, 2};
+static const struct raw_pixel_format raw_fmt_xrgb = {4, 1, 2, 3};
+static const struct raw_pixel_format raw_fmt_bgr = {3, 2, 1, 0};
+static const struct raw_pixel_format raw_fmt_bgrx = {4, 2, 1, 0};
+static const struct raw_pixel_format raw_fmt_xbgr = {4, 3, 2, 1};
+
+static cairo_surface_t *read_raw_image(const char *image_path, const char *image_raw_format) {
+    cairo_surface_t *img;
+
+#define RAW_PIXFMT_MAXLEN 6
+#define STRINGIFY1(x) #x
+#define STRINGIFY(x) STRINGIFY1(x)
+    /* Parse format as <width>x<height>:<pixfmt> */
+    char pixfmt[RAW_PIXFMT_MAXLEN + 1];
+    size_t w, h;
+    const char *fmt = "%zux%zu:%" STRINGIFY(RAW_PIXFMT_MAXLEN) "s";
+    if (sscanf(image_raw_format, fmt, &w, &h, pixfmt) != 3) {
+        fprintf(stderr, "Invalid image format: \"%s\"\n", image_raw_format);
+        return NULL;
+    }
+#undef RAW_PIXFMT_MAXLEN
+#undef STRINGIFY1
+#undef STRINGIFY
+
+    /* Create image surface */
+    img = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
+    if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
+        fprintf(stderr, "Could not create surface: %s\n",
+                cairo_status_to_string(cairo_surface_status(img)));
+        return NULL;
+    }
+    cairo_surface_flush(img);
+
+    /* Use uint32_t* because cairo uses native endianness */
+    uint32_t *data = (uint32_t *)cairo_image_surface_get_data(img);
+    const int pixstride = cairo_image_surface_get_stride(img) / 4;
+
+    FILE *f = fopen(image_path, "r");
+    if (f == NULL) {
+        fprintf(stderr, "Could not open image \"%s\": %s\n",
+                image_path, strerror(errno));
+        cairo_surface_destroy(img);
+        return NULL;
+    }
+
+    /* Read the image, respecting cairo's stride, according to the pixfmt */
+    ssize_t size, count;
+    if (strcmp(pixfmt, "native") == 0) {
+        /* If the pixfmt is 'native', just read each line directly into the buffer */
+        size = w * h * 4;
+        count = read_raw_image_native(data, f, w, h, pixstride);
+    } else {
+        const struct raw_pixel_format *fmt = NULL;
+
+        if (strcmp(pixfmt, "rgb") == 0)
+            fmt = &raw_fmt_rgb;
+        else if (strcmp(pixfmt, "rgbx") == 0)
+            fmt = &raw_fmt_rgbx;
+        else if (strcmp(pixfmt, "xrgb") == 0)
+            fmt = &raw_fmt_xrgb;
+        else if (strcmp(pixfmt, "bgr") == 0)
+            fmt = &raw_fmt_bgr;
+        else if (strcmp(pixfmt, "bgrx") == 0)
+            fmt = &raw_fmt_bgrx;
+        else if (strcmp(pixfmt, "xbgr") == 0)
+            fmt = &raw_fmt_xbgr;
+
+        if (fmt == NULL) {
+            fprintf(stderr, "Unknown raw pixel format: %s\n", pixfmt);
+            fclose(f);
+            cairo_surface_destroy(img);
+            return NULL;
+        }
+
+        size = w * h * fmt->bpp;
+        count = read_raw_image_fmt(data, f, w, h, pixstride, *fmt);
+    }
+
+    cairo_surface_mark_dirty(img);
+
+    if (count < size) {
+        if (count < 0 || ferror(f)) {
+            fprintf(stderr, "Failed to read image \"%s\": %s\n",
+                    image_path, strerror(errno));
+            fclose(f);
+            cairo_surface_destroy(img);
+            return NULL;
+        } else {
+            /* Print a warning if the file contains less data than expected,
+             * but don't abort. It's useful to see how the image looks even if it's wrong. */
+            fprintf(stderr, "Warning: expected to read %zi bytes from \"%s\", read %zi\n",
+                    size, image_path, count);
+        }
+    }
+
+    fclose(f);
+    return img;
+}
+
 static bool verify_png_image(const char *image_path) {
     if (!image_path) {
         return false;
@@ -992,7 +1143,7 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
     xcb_generic_event_t *event;
 
     if (xcb_connection_has_error(conn))
-        errx(EXIT_FAILURE, "X11 connection broke, did your server terminate?\n");
+        errx(EXIT_FAILURE, "X11 connection broke, did your server terminate?");
 
     while ((event = xcb_poll_for_event(conn)) != NULL) {
         if (event->response_type == 0) {
@@ -1062,7 +1213,7 @@ static void raise_loop(xcb_window_t window) {
     int screens;
 
     if (xcb_connection_has_error((conn = xcb_connect(NULL, &screens))) > 0)
-        errx(EXIT_FAILURE, "Cannot open display\n");
+        errx(EXIT_FAILURE, "Cannot open display");
 
     /* We need to know about the window being obscured or getting destroyed. */
     xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK,
@@ -1108,11 +1259,15 @@ static void raise_loop(xcb_window_t window) {
 /*
  * Loads an image from the given path. Handles JPEG and PNG. Returns NULL in case of error.
  */
-static cairo_surface_t* load_image(char* image_path) {
+static cairo_surface_t* load_image(char* image_path, char* image_raw_format) {
     cairo_surface_t *img = NULL;
     JPEG_INFO jpg_info;
 
-    if (verify_png_image(image_path)) {
+    if (image_raw_format != NULL && image_path != NULL) {
+        /* Read image. 'read_raw_image' returns NULL on error,
+         * so we don't have to handle errors here. */
+        img = read_raw_image(image_path, image_raw_format);
+    } else if (verify_png_image(image_path)) {
         /* Create a pixmap to render on, fill it with the background color */
         img = cairo_image_surface_create_from_png(image_path);
     } else if (file_is_jpg(image_path)) {
@@ -1139,7 +1294,7 @@ static cairo_surface_t* load_image(char* image_path) {
  * Loads the images from the provided directory and stores them in the pointer array
  * img_slideshow
  */
-static void load_slideshow_images(const char *path) {
+static void load_slideshow_images(const char *path, char *image_raw_format) {
     slideshow_enabled = true;
     DIR *d;
     struct dirent *dir;
@@ -1161,7 +1316,7 @@ static void load_slideshow_images(const char *path) {
         strcat(path_to_image, "/");
         strcat(path_to_image, dir->d_name);
 
-        img_slideshow[file_count] = load_image(path_to_image);
+        img_slideshow[file_count] = load_image(path_to_image, image_raw_format);
 
         if (img_slideshow[file_count] != NULL) {
             ++file_count;
@@ -1177,10 +1332,16 @@ int main(int argc, char *argv[]) {
     struct passwd *pw;
     char *username;
     char *image_path = NULL;
+    char *image_raw_format = NULL;
 #ifndef __OpenBSD__
     int ret;
     struct pam_conv conv = {conv_callback, NULL};
 #endif
+#if defined(__linux__)
+    bool lock_tty_switching = false;
+    int term = -1;
+#endif
+
     int curs_choice = CURS_NONE;
     int o;
     int longoptind = 0;
@@ -1195,6 +1356,7 @@ int main(int argc, char *argv[]) {
         {"help", no_argument, NULL, 'h'},
         {"no-unlock-indicator", no_argument, NULL, 'u'},
         {"image", required_argument, NULL, 'i'},
+        {"raw", required_argument, NULL, 998},
         {"tiling", no_argument, NULL, 't'},
         {"ignore-empty-password", no_argument, NULL, 'e'},
         {"inactivity-timeout", required_argument, NULL, 'I'},
@@ -1307,14 +1469,15 @@ int main(int argc, char *argv[]) {
         {"slideshow-interval", required_argument, NULL, 903},
         {"slideshow-random-selection", no_argument, NULL, 904},
 
+        {"lock-console", no_argument, NULL, 'l'},
         {NULL, no_argument, NULL, 0}};
 
     if ((pw = getpwuid(getuid())) == NULL)
         err(EXIT_FAILURE, "getpwuid() failed");
     if ((username = pw->pw_name) == NULL)
-        errx(EXIT_FAILURE, "pw->pw_name is NULL.\n");
+        errx(EXIT_FAILURE, "pw->pw_name is NULL.");
 
-    char *optstring = "hvnbdc:p:ui:teI:frsS:kB:m";
+    char *optstring = "hvnbdc:p:ui:teI:frsS:kB:ml";
     char *arg = NULL;
     int opt = 0;
     while ((o = getopt_long(argc, argv, optstring, longopts, &longoptind)) != -1) {
@@ -1342,7 +1505,7 @@ int main(int argc, char *argv[]) {
                     arg++;
 
                 if (strlen(arg) != 6 || sscanf(arg, "%06[0-9a-fA-F]", color) != 1)
-                    errx(EXIT_FAILURE, "color is invalid, it must be given in 3-byte hexadecimal format: rrggbb\n");
+                    errx(EXIT_FAILURE, "color is invalid, it must be given in 3-byte hexadecimal format: rrggbb");
 
                 break;
             }
@@ -1361,7 +1524,7 @@ int main(int argc, char *argv[]) {
                 } else if (!strcmp(optarg, "default")) {
                     curs_choice = CURS_DEFAULT;
                 } else {
-                    errx(EXIT_FAILURE, "i3lock: Invalid pointer type given. Expected one of \"win\" or \"default\".\n");
+                    errx(EXIT_FAILURE, "i3lock: Invalid pointer type given. Expected one of \"win\" or \"default\".");
                 }
                 break;
             case 'e':
@@ -1370,6 +1533,13 @@ int main(int argc, char *argv[]) {
             case 'f':
                 show_failed_attempts = true;
                 break;
+            case 'l':
+#if defined(__linux__)
+                lock_tty_switching = true;
+#else
+                errx(EXIT_FAILURE, "TTY switch locking is only supported on Linux.");
+#endif
+                break;
             case 'r':
                 if (internal_line_source != 0) {
                   errx(EXIT_FAILURE, "i3lock-color: Options line-uses-ring and line-uses-inside conflict.");
@@ -1829,9 +1999,12 @@ int main(int argc, char *argv[]) {
             case 999:
                 debug_mode = true;
                 break;
+            case 998:
+                image_raw_format = strdup(optarg);
+                break;
             default:
                 errx(EXIT_FAILURE, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-u] [-p win|default]"
-                                   " [-i image.png] [-t] [-e] [-f]\n"
+                                   " [-i image.png] [-t] [-e] [-f] [-l]\n"
                                    "Please see the manpage for a full list of arguments.");
         }
     }
@@ -1960,12 +2133,14 @@ int main(int argc, char *argv[]) {
             img = load_image(image_path);
         } else {
             /* Path to a directory is provided -> use slideshow mode */
-            load_slideshow_images(image_path);
+            load_slideshow_images(image_path, image_raw_format);
         }
 
         free(image_path);
     }
 
+    free(image_raw_format);
+
     xcb_pixmap_t* blur_pixmap = NULL;
     if (blur) {
         blur_pixmap = malloc(sizeof(xcb_pixmap_t));
@@ -2060,7 +2235,22 @@ int main(int argc, char *argv[]) {
     /* Initialize the libev event loop. */
     main_loop = EV_DEFAULT;
     if (main_loop == NULL)
-        errx(EXIT_FAILURE, "Could not initialize libev. Bad LIBEV_FLAGS?\n");
+        errx(EXIT_FAILURE, "Could not initialize libev. Bad LIBEV_FLAGS?");
+
+#if defined(__linux__)
+
+    /* Lock tty switching */
+    if (lock_tty_switching) {
+        if ((term = open("/dev/console", O_RDWR)) == -1) {
+            perror("error locking TTY switching: opening console failed");
+        }
+
+        if (term != -1 && (ioctl(term, VT_LOCKSWITCH)) == -1) {
+            perror("error locking TTY switching: locking console failed");
+        }
+    }
+
+#endif
 
     /* Explicitly call the screen redraw in case "locking…" message was displayed */
     auth_state = STATE_AUTH_IDLE;
@@ -2102,6 +2292,18 @@ int main(int argc, char *argv[]) {
         return 0;
     }
 
+#if defined(__linux__)
+    /* Restore tty switching */
+    if (lock_tty_switching) {
+        if (term != -1 && (ioctl(term, VT_UNLOCKSWITCH)) == -1) {
+            perror("error unlocking TTY switching: unlocking console failed");
+        }
+
+        close(term);
+    }
+
+#endif
+
     DEBUG("restoring focus to X11 window 0x%08x\n", stolen_focus);
     xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
     xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
diff --git a/xcb.c b/xcb.c
@@ -182,6 +182,15 @@ xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, c
                         strlen(name),
                         name);
 
+    xcb_change_property(conn,
+                        XCB_PROP_MODE_REPLACE,
+                        win,
+                        XCB_ATOM_WM_CLASS,
+                        XCB_ATOM_STRING,
+                        8,
+                        2 * (strlen("i3lock") + 1),
+                        "i3lock\0i3lock\0");
+
     /* Map the window (= make it visible) */
     xcb_map_window(conn, win);