i3lock-color

The world's most popular non-default computer lockscreen.
Index Commits Files Refs README LICENSE
commit 1e284b9488e46266b66d1c5031f7e815e889f7c0
parent fbf109178c1c098a8dbe3888e27ad46b436c5c8e
Author: SuperDJY <cmsxbc@users.noreply.github.com>
Date:   Tue, 11 Jan 2022 01:34:12 +0800

feat(control char): add basic control char support (#238)

* feat(control char): add basic control char support

* feat(control char): fix wrong x when \n after \b

* feat(control char): add `\t` support, same behavior as `\t` in c printf

* Gonna go with 4 spaces per tab to be safer

* fix(control chars): leading control chars run into 'out of bounds memory acessing', and render at wrong position

* doc(control chars): describe the control chars behavior and declare the influenced options and bump date to SEP 2021.

* update to NOV

* Bump years

* Redo manpage

Co-authored-by: Raymond Li <hi@raymond.li>
Diffstat:
Mi3lock.1 | 60+++++++++++++++++++++++++++++++++++++++++++++---------------
Munlock_indicator.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Munlock_indicator.h | 16++++++++++++++++
3 files changed, 158 insertions(+), 17 deletions(-)
diff --git a/i3lock.1 b/i3lock.1
@@ -8,7 +8,7 @@
 .fi
 ..
 
-.TH i3lock-color 1 "JUN 2021" Linux "User Manuals"
+.TH i3lock-color 1 "JAN 2022" Linux "User Manuals"
 
 .SH NAME
 i3lock-color \- improved screen locker
@@ -241,6 +241,23 @@ Sets the color of the status text while verifying and when password is wrong.
 Sets text colors.
 
 .TP
+.B \-\-keylayout mode
+Displays the keylayout. Positionable similar to date, time, and indicator.
+Modes are as follows:
+.RS
+.IP \[bu] 2
+0 - Displays the full string returned by the query, i.e. "English (US)"
+.IP \[bu]
+1 - Displays up until the first parenthesis, i.e. "English"
+.IP \[bu]
+2 - Displays just the contents of the parenthesis, i.e. "US"
+.RE
+
+.B For all following -str or -text options, some control characters
+.B (i.e. \\\\n, \\\\t) are supported. See \fBCONTROL CHARACTERS\fR
+.B for more details.
+
+.TP
 .B \-\-time\-str="%H:%M:%S"
 Sets the format used for generating the time string.
 See strftime(3) for a full list of format specifiers.
@@ -258,19 +275,6 @@ Sets the string to be shown while verifying the password/input/key/etc.
 Sets the string to be shown upon entering an incorrect password.
 
 .TP
-.B \-\-keylayout mode
-Displays the keylayout. Positionable similar to date, time, and indicator.
-Modes are as follows:
-.RS
-.IP \[bu] 2
-0 - Displays the full string returned by the query, i.e. "English (US)"
-.IP \[bu]
-1 - Displays up until the first parenthesis, i.e. "English"
-.IP \[bu]
-2 - Displays just the contents of the parenthesis, i.e. "US"
-.RE
-
-.TP
 .B \-\-noinput\-text="no input"
 Sets the string to be shown upon pressing backspace without anything to delete.
 
@@ -484,6 +488,25 @@ The interval to wait until switching to the next image.
 .B \-\-slideshow\-random\-selection
 Randomize the order of the images.
 
+.SH CONTROL CHARACTERS
+Control characters (\\r \\n \\b \\t) are supported in text OPTIONS. Their behavior
+are almost as same as anywhere else.
+.TP
+.B Carriage Return(\\\\r)
+Move to the start of line (left edge).
+Notes: The rendered characters would still live there.
+.TP
+.B Line Feed(\\\\n)
+Move to start of next line (left edge).
+.TP
+.B Backspace(\\\\b)
+Overwrite last one char if exists.
+Notes: The rendered character would still live there.
+.TP
+.B Tab(\\\\t)
+Move to next tab stop position.The width of one character for moving is as same as character 'a'.
+Note: The width may be strange if the font is not mono-spaced.
+
 .SH SEE ALSO
 .IR xautolock(1)
 \- use i3lock as your screen saver
@@ -491,7 +514,14 @@ Randomize the order of the images.
 .IR convert(1)
 \- feed a wide variety of image formats to i3lock
 
-.SH AUTHOR
+.SH HOMEPAGE
+https://github.com/Raymo111/i3lock-color
+
+Please report bugs and submit pull-requests as follows:
+For i3lock (upstream): https://github.com/i3/i3lock
+For i3lock-color (enhancements on top of i3lock): https://github.com/Raymo111/i3lock-color
+
+.SH AUTHORS
 Michael Stapelberg <michael+i3lock at stapelberg dot de>
 
 Jan-Erik Rediger <badboy at archlinux.us>
diff --git a/unlock_indicator.c b/unlock_indicator.c
@@ -256,6 +256,14 @@ static cairo_font_face_t *font_faces[6] = {
     NULL,
 };
 
+static control_char_config_t control_characters[] = {
+    {'\n', CC_POS_RESET, 0, CC_POS_CHANGE, 1},
+    {'\b', CC_POS_CHANGE, -1, CC_POS_KEEP, 0},
+    {'\r', CC_POS_RESET, 0, CC_POS_KEEP, 0},
+    {'\t', CC_POS_TAB, 4, CC_POS_KEEP, 0},
+};
+size_t control_char_count = sizeof control_characters / sizeof(control_char_config_t);
+
 static cairo_font_face_t *get_font_face(int which) {
     if (font_faces[which]) {
         return font_faces[which];
@@ -315,6 +323,94 @@ static cairo_font_face_t *get_font_face(int which) {
 }
 
 /*
+ * Splits the given text by "control chars",
+ * And then draws the given text onto the cairo context.
+ */
+static void draw_text_with_cc(cairo_t *ctx, text_t text, double start_x) {
+    // get scaled_font
+    cairo_scaled_font_t *sft;
+    cairo_matrix_t fm, ctm;
+    cairo_matrix_init_scale(&fm, text.size, text.size);
+    cairo_get_matrix(ctx, &ctm);
+    cairo_font_options_t *opts;
+    opts = cairo_font_options_create();
+    sft = cairo_scaled_font_create(text.font, &fm, &ctm, opts);
+    cairo_font_options_destroy(opts);
+    /* use `a` to represent common character width, using in `\t`  */
+    cairo_text_extents_t te;
+    cairo_text_extents(ctx, "a", &te);
+
+    // convert text to glyphs.
+    cairo_status_t status;
+    cairo_glyph_t* glyphs;
+    int nglyphs = 0,
+        len = 0,
+        start = 0,
+        lineno = 0,
+        x = start_x,
+        y = text.y;
+    size_t cur_cc;
+
+    while (text.str[start + len] != '\0') {
+        char is_cc = 0;
+        do {
+            for (cur_cc = 0; cur_cc < control_char_count; cur_cc++) {
+                if (text.str[start+len] == control_characters[cur_cc].character) {
+                    is_cc = 1;
+                    break;
+                }
+            }
+        } while (text.str[start+(len++)] != '\0' && !is_cc);
+        if (len > is_cc) {
+            status = cairo_scaled_font_text_to_glyphs(
+                sft, x, y, text.str + start, is_cc ? len - 1: len,
+                &glyphs, &nglyphs,
+                NULL, NULL, NULL
+            );
+            if (status == CAIRO_STATUS_SUCCESS) {
+                cairo_glyph_path(ctx, glyphs, nglyphs);
+            } else {
+                DEBUG("draw %c failed\n", text.str[start]);
+            }
+        }
+        if (is_cc && (cur_cc < control_char_count)) {
+            if (control_characters[cur_cc].x_behavior == CC_POS_CHANGE) {
+                char x_offset = control_characters[cur_cc].x_behavior_arg;
+                if (x_offset < 0 && x_offset > -nglyphs) {
+                    x = glyphs[nglyphs+x_offset].x;
+                } else if (x_offset > 0) {
+                    if (nglyphs >= 1) { // the case is some leading control chars.(although there is none now)
+                        x = glyphs[nglyphs - 1].x + x_offset * te.x_advance;
+                    } else { // deal the leading control chars.
+                        x += x_offset * te.x_advance;
+                    }
+                }
+            } else if (control_characters[cur_cc].x_behavior == CC_POS_RESET) {
+                x = start_x;
+            } else if (control_characters[cur_cc].x_behavior == CC_POS_TAB) {
+                if (nglyphs > 0) { // there may be leading tab, such as '\t\t' or '\n\t'
+                    int advance = control_characters[cur_cc].x_behavior_arg - ((nglyphs - 1) % control_characters[cur_cc].x_behavior_arg);
+                    x = glyphs[nglyphs - 1].x + advance * te.x_advance;
+                } else { // deal the leading tab.
+                    x += control_characters[cur_cc].x_behavior_arg * te.x_advance;
+                }
+            }
+            if (control_characters[cur_cc].y_behavior == CC_POS_CHANGE) {
+                lineno += control_characters[cur_cc].y_behavior_arg;
+            } // CC_POS_KEEP is default for y
+        }
+        y = text.y + text.size * lineno;
+        if (len > is_cc) {
+            cairo_glyph_free(glyphs);
+        }
+        nglyphs = 0;
+        start += len;
+        len = 0;
+    }
+    cairo_scaled_font_destroy(sft);
+}
+
+/*
  * Draws the given text onto the cairo context
  */
 static void draw_text(cairo_t *ctx, text_t text) {
@@ -342,9 +438,8 @@ static void draw_text(cairo_t *ctx, text_t text) {
     }
 
     cairo_set_source_rgba(ctx, text.color.red, text.color.green, text.color.blue, text.color.alpha);
-    cairo_move_to(ctx, x, text.y);
 
-    cairo_text_path(ctx, text.str);
+    draw_text_with_cc(ctx, text, x);
     cairo_fill_preserve(ctx);
 
     cairo_set_source_rgba(ctx, text.outline_color.red, text.outline_color.green, text.outline_color.blue, text.outline_color.alpha);
diff --git a/unlock_indicator.h b/unlock_indicator.h
@@ -47,6 +47,22 @@ typedef enum {
     MAX,
 } background_type_t;
 
+
+typedef enum {
+    CC_POS_RESET,
+    CC_POS_CHANGE,
+    CC_POS_KEEP,
+    CC_POS_TAB
+} control_char_pos_t;
+
+typedef struct {
+    char character;
+    control_char_pos_t x_behavior;
+    int x_behavior_arg;
+    control_char_pos_t y_behavior;
+    int y_behavior_arg;
+} control_char_config_t;
+
 void render_lock(uint32_t* resolution, xcb_drawable_t drawable);
 void draw_image(uint32_t* resolution, cairo_surface_t* img, cairo_t* xcb_ctx);
 void init_colors_once(void);