diff options
| author | Simeon Simeonov | 2020-03-12 12:30:51 +0100 |
|---|---|---|
| committer | Simeon Simeonov | 2020-03-12 12:30:51 +0100 |
| commit | 69a05d516bd0c7e369a0ba1fb78aac57a5ea26f2 (patch) | |
| tree | 87e37110ea0e328533a41c3b16b64036ff132641 /unlock_indicator.c | |
Initial commit for the last i3lock commit at this time f3b061233e28ad0cf920c9421adc2459b6920812
Diffstat (limited to 'unlock_indicator.c')
| -rw-r--r-- | unlock_indicator.c | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/unlock_indicator.c b/unlock_indicator.c new file mode 100644 index 0000000..d6237ed --- /dev/null +++ b/unlock_indicator.c | |||
| @@ -0,0 +1,361 @@ | |||
| 1 | /* | ||
| 2 | * vim:ts=4:sw=4:expandtab | ||
| 3 | * | ||
| 4 | * © 2010 Michael Stapelberg | ||
| 5 | * | ||
| 6 | * See LICENSE for licensing information | ||
| 7 | * | ||
| 8 | */ | ||
| 9 | #include <stdbool.h> | ||
| 10 | #include <stdlib.h> | ||
| 11 | #include <stdio.h> | ||
| 12 | #include <string.h> | ||
| 13 | #include <math.h> | ||
| 14 | #include <xcb/xcb.h> | ||
| 15 | #include <ev.h> | ||
| 16 | #include <cairo.h> | ||
| 17 | #include <cairo/cairo-xcb.h> | ||
| 18 | |||
| 19 | #include "i3lock.h" | ||
| 20 | #include "xcb.h" | ||
| 21 | #include "unlock_indicator.h" | ||
| 22 | #include "randr.h" | ||
| 23 | #include "dpi.h" | ||
| 24 | |||
| 25 | #define BUTTON_RADIUS 90 | ||
| 26 | #define BUTTON_SPACE (BUTTON_RADIUS + 5) | ||
| 27 | #define BUTTON_CENTER (BUTTON_RADIUS + 5) | ||
| 28 | #define BUTTON_DIAMETER (2 * BUTTON_SPACE) | ||
| 29 | |||
| 30 | /******************************************************************************* | ||
| 31 | * Variables defined in i3lock.c. | ||
| 32 | ******************************************************************************/ | ||
| 33 | |||
| 34 | extern bool debug_mode; | ||
| 35 | |||
| 36 | /* The current position in the input buffer. Useful to determine if any | ||
| 37 | * characters of the password have already been entered or not. */ | ||
| 38 | extern int input_position; | ||
| 39 | |||
| 40 | /* The lock window. */ | ||
| 41 | extern xcb_window_t win; | ||
| 42 | |||
| 43 | /* The current resolution of the X11 root window. */ | ||
| 44 | extern uint32_t last_resolution[2]; | ||
| 45 | |||
| 46 | /* Whether the unlock indicator is enabled (defaults to true). */ | ||
| 47 | extern bool unlock_indicator; | ||
| 48 | |||
| 49 | /* List of pressed modifiers, or NULL if none are pressed. */ | ||
| 50 | extern char *modifier_string; | ||
| 51 | |||
| 52 | /* A Cairo surface containing the specified image (-i), if any. */ | ||
| 53 | extern cairo_surface_t *img; | ||
| 54 | |||
| 55 | /* Whether the image should be tiled. */ | ||
| 56 | extern bool tile; | ||
| 57 | /* The background color to use (in hex). */ | ||
| 58 | extern char color[7]; | ||
| 59 | |||
| 60 | /* Whether the failed attempts should be displayed. */ | ||
| 61 | extern bool show_failed_attempts; | ||
| 62 | /* Number of failed unlock attempts. */ | ||
| 63 | extern int failed_attempts; | ||
| 64 | |||
| 65 | /******************************************************************************* | ||
| 66 | * Variables defined in xcb.c. | ||
| 67 | ******************************************************************************/ | ||
| 68 | |||
| 69 | /* The root screen, to determine the DPI. */ | ||
| 70 | extern xcb_screen_t *screen; | ||
| 71 | |||
| 72 | /******************************************************************************* | ||
| 73 | * Local variables. | ||
| 74 | ******************************************************************************/ | ||
| 75 | |||
| 76 | /* Cache the screen’s visual, necessary for creating a Cairo context. */ | ||
| 77 | static xcb_visualtype_t *vistype; | ||
| 78 | |||
| 79 | /* Maintain the current unlock/PAM state to draw the appropriate unlock | ||
| 80 | * indicator. */ | ||
| 81 | unlock_state_t unlock_state; | ||
| 82 | auth_state_t auth_state; | ||
| 83 | |||
| 84 | /* | ||
| 85 | * Draws global image with fill color onto a pixmap with the given | ||
| 86 | * resolution and returns it. | ||
| 87 | * | ||
| 88 | */ | ||
| 89 | xcb_pixmap_t draw_image(uint32_t *resolution) { | ||
| 90 | xcb_pixmap_t bg_pixmap = XCB_NONE; | ||
| 91 | const double scaling_factor = get_dpi_value() / 96.0; | ||
| 92 | int button_diameter_physical = ceil(scaling_factor * BUTTON_DIAMETER); | ||
| 93 | DEBUG("scaling_factor is %.f, physical diameter is %d px\n", | ||
| 94 | scaling_factor, button_diameter_physical); | ||
| 95 | |||
| 96 | if (!vistype) | ||
| 97 | vistype = get_root_visual_type(screen); | ||
| 98 | bg_pixmap = create_bg_pixmap(conn, screen, resolution, color); | ||
| 99 | /* Initialize cairo: Create one in-memory surface to render the unlock | ||
| 100 | * indicator on, create one XCB surface to actually draw (one or more, | ||
| 101 | * depending on the amount of screens) unlock indicators on. */ | ||
| 102 | cairo_surface_t *output = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, button_diameter_physical, button_diameter_physical); | ||
| 103 | cairo_t *ctx = cairo_create(output); | ||
| 104 | |||
| 105 | cairo_surface_t *xcb_output = cairo_xcb_surface_create(conn, bg_pixmap, vistype, resolution[0], resolution[1]); | ||
| 106 | cairo_t *xcb_ctx = cairo_create(xcb_output); | ||
| 107 | |||
| 108 | if (img) { | ||
| 109 | if (!tile) { | ||
| 110 | cairo_set_source_surface(xcb_ctx, img, 0, 0); | ||
| 111 | cairo_paint(xcb_ctx); | ||
| 112 | } else { | ||
| 113 | /* create a pattern and fill a rectangle as big as the screen */ | ||
| 114 | cairo_pattern_t *pattern; | ||
| 115 | pattern = cairo_pattern_create_for_surface(img); | ||
| 116 | cairo_set_source(xcb_ctx, pattern); | ||
| 117 | cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); | ||
| 118 | cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]); | ||
| 119 | cairo_fill(xcb_ctx); | ||
| 120 | cairo_pattern_destroy(pattern); | ||
| 121 | } | ||
| 122 | } else { | ||
| 123 | char strgroups[3][3] = {{color[0], color[1], '\0'}, | ||
| 124 | {color[2], color[3], '\0'}, | ||
| 125 | {color[4], color[5], '\0'}}; | ||
| 126 | uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)), | ||
| 127 | (strtol(strgroups[1], NULL, 16)), | ||
| 128 | (strtol(strgroups[2], NULL, 16))}; | ||
| 129 | cairo_set_source_rgb(xcb_ctx, rgb16[0] / 255.0, rgb16[1] / 255.0, rgb16[2] / 255.0); | ||
| 130 | cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]); | ||
| 131 | cairo_fill(xcb_ctx); | ||
| 132 | } | ||
| 133 | |||
| 134 | if (unlock_indicator && | ||
| 135 | (unlock_state >= STATE_KEY_PRESSED || auth_state > STATE_AUTH_IDLE)) { | ||
| 136 | cairo_scale(ctx, scaling_factor, scaling_factor); | ||
| 137 | /* Draw a (centered) circle with transparent background. */ | ||
| 138 | cairo_set_line_width(ctx, 10.0); | ||
| 139 | cairo_arc(ctx, | ||
| 140 | BUTTON_CENTER /* x */, | ||
| 141 | BUTTON_CENTER /* y */, | ||
| 142 | BUTTON_RADIUS /* radius */, | ||
| 143 | 0 /* start */, | ||
| 144 | 2 * M_PI /* end */); | ||
| 145 | |||
| 146 | /* Use the appropriate color for the different PAM states | ||
| 147 | * (currently verifying, wrong password, or default) */ | ||
| 148 | switch (auth_state) { | ||
| 149 | case STATE_AUTH_VERIFY: | ||
| 150 | case STATE_AUTH_LOCK: | ||
| 151 | cairo_set_source_rgba(ctx, 0, 114.0 / 255, 255.0 / 255, 0.75); | ||
| 152 | break; | ||
| 153 | case STATE_AUTH_WRONG: | ||
| 154 | case STATE_I3LOCK_LOCK_FAILED: | ||
| 155 | cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75); | ||
| 156 | break; | ||
| 157 | default: | ||
| 158 | if (unlock_state == STATE_NOTHING_TO_DELETE) { | ||
| 159 | cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75); | ||
| 160 | break; | ||
| 161 | } | ||
| 162 | cairo_set_source_rgba(ctx, 0, 0, 0, 0.75); | ||
| 163 | break; | ||
| 164 | } | ||
| 165 | cairo_fill_preserve(ctx); | ||
| 166 | |||
| 167 | switch (auth_state) { | ||
| 168 | case STATE_AUTH_VERIFY: | ||
| 169 | case STATE_AUTH_LOCK: | ||
| 170 | cairo_set_source_rgb(ctx, 51.0 / 255, 0, 250.0 / 255); | ||
| 171 | break; | ||
| 172 | case STATE_AUTH_WRONG: | ||
| 173 | case STATE_I3LOCK_LOCK_FAILED: | ||
| 174 | cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0); | ||
| 175 | break; | ||
| 176 | case STATE_AUTH_IDLE: | ||
| 177 | if (unlock_state == STATE_NOTHING_TO_DELETE) { | ||
| 178 | cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0); | ||
| 179 | break; | ||
| 180 | } | ||
| 181 | |||
| 182 | cairo_set_source_rgb(ctx, 51.0 / 255, 125.0 / 255, 0); | ||
| 183 | break; | ||
| 184 | } | ||
| 185 | cairo_stroke(ctx); | ||
| 186 | |||
| 187 | /* Draw an inner seperator line. */ | ||
| 188 | cairo_set_source_rgb(ctx, 0, 0, 0); | ||
| 189 | cairo_set_line_width(ctx, 2.0); | ||
| 190 | cairo_arc(ctx, | ||
| 191 | BUTTON_CENTER /* x */, | ||
| 192 | BUTTON_CENTER /* y */, | ||
| 193 | BUTTON_RADIUS - 5 /* radius */, | ||
| 194 | 0, | ||
| 195 | 2 * M_PI); | ||
| 196 | cairo_stroke(ctx); | ||
| 197 | |||
| 198 | cairo_set_line_width(ctx, 10.0); | ||
| 199 | |||
| 200 | /* Display a (centered) text of the current PAM state. */ | ||
| 201 | char *text = NULL; | ||
| 202 | /* We don't want to show more than a 3-digit number. */ | ||
| 203 | char buf[4]; | ||
| 204 | |||
| 205 | cairo_set_source_rgb(ctx, 0, 0, 0); | ||
| 206 | cairo_select_font_face(ctx, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); | ||
| 207 | cairo_set_font_size(ctx, 28.0); | ||
| 208 | switch (auth_state) { | ||
| 209 | case STATE_AUTH_VERIFY: | ||
| 210 | text = "Verifying…"; | ||
| 211 | break; | ||
| 212 | case STATE_AUTH_LOCK: | ||
| 213 | text = "Locking…"; | ||
| 214 | break; | ||
| 215 | case STATE_AUTH_WRONG: | ||
| 216 | text = "Wrong!"; | ||
| 217 | break; | ||
| 218 | case STATE_I3LOCK_LOCK_FAILED: | ||
| 219 | text = "Lock failed!"; | ||
| 220 | break; | ||
| 221 | default: | ||
| 222 | if (unlock_state == STATE_NOTHING_TO_DELETE) { | ||
| 223 | text = "No input"; | ||
| 224 | } | ||
| 225 | if (show_failed_attempts && failed_attempts > 0) { | ||
| 226 | if (failed_attempts > 999) { | ||
| 227 | text = "> 999"; | ||
| 228 | } else { | ||
| 229 | snprintf(buf, sizeof(buf), "%d", failed_attempts); | ||
| 230 | text = buf; | ||
| 231 | } | ||
| 232 | cairo_set_source_rgb(ctx, 1, 0, 0); | ||
| 233 | cairo_set_font_size(ctx, 32.0); | ||
| 234 | } | ||
| 235 | break; | ||
| 236 | } | ||
| 237 | |||
| 238 | if (text) { | ||
| 239 | cairo_text_extents_t extents; | ||
| 240 | double x, y; | ||
| 241 | |||
| 242 | cairo_text_extents(ctx, text, &extents); | ||
| 243 | x = BUTTON_CENTER - ((extents.width / 2) + extents.x_bearing); | ||
| 244 | y = BUTTON_CENTER - ((extents.height / 2) + extents.y_bearing); | ||
| 245 | |||
| 246 | cairo_move_to(ctx, x, y); | ||
| 247 | cairo_show_text(ctx, text); | ||
| 248 | cairo_close_path(ctx); | ||
| 249 | } | ||
| 250 | |||
| 251 | if (auth_state == STATE_AUTH_WRONG && (modifier_string != NULL)) { | ||
| 252 | cairo_text_extents_t extents; | ||
| 253 | double x, y; | ||
| 254 | |||
| 255 | cairo_set_font_size(ctx, 14.0); | ||
| 256 | |||
| 257 | cairo_text_extents(ctx, modifier_string, &extents); | ||
| 258 | x = BUTTON_CENTER - ((extents.width / 2) + extents.x_bearing); | ||
| 259 | y = BUTTON_CENTER - ((extents.height / 2) + extents.y_bearing) + 28.0; | ||
| 260 | |||
| 261 | cairo_move_to(ctx, x, y); | ||
| 262 | cairo_show_text(ctx, modifier_string); | ||
| 263 | cairo_close_path(ctx); | ||
| 264 | } | ||
| 265 | |||
| 266 | /* After the user pressed any valid key or the backspace key, we | ||
| 267 | * highlight a random part of the unlock indicator to confirm this | ||
| 268 | * keypress. */ | ||
| 269 | if (unlock_state == STATE_KEY_ACTIVE || | ||
| 270 | unlock_state == STATE_BACKSPACE_ACTIVE) { | ||
| 271 | cairo_new_sub_path(ctx); | ||
| 272 | double highlight_start = (rand() % (int)(2 * M_PI * 100)) / 100.0; | ||
| 273 | cairo_arc(ctx, | ||
| 274 | BUTTON_CENTER /* x */, | ||
| 275 | BUTTON_CENTER /* y */, | ||
| 276 | BUTTON_RADIUS /* radius */, | ||
| 277 | highlight_start, | ||
| 278 | highlight_start + (M_PI / 3.0)); | ||
| 279 | if (unlock_state == STATE_KEY_ACTIVE) { | ||
| 280 | /* For normal keys, we use a lighter green. */ | ||
| 281 | cairo_set_source_rgb(ctx, 51.0 / 255, 219.0 / 255, 0); | ||
| 282 | } else { | ||
| 283 | /* For backspace, we use red. */ | ||
| 284 | cairo_set_source_rgb(ctx, 219.0 / 255, 51.0 / 255, 0); | ||
| 285 | } | ||
| 286 | cairo_stroke(ctx); | ||
| 287 | |||
| 288 | /* Draw two little separators for the highlighted part of the | ||
| 289 | * unlock indicator. */ | ||
| 290 | cairo_set_source_rgb(ctx, 0, 0, 0); | ||
| 291 | cairo_arc(ctx, | ||
| 292 | BUTTON_CENTER /* x */, | ||
| 293 | BUTTON_CENTER /* y */, | ||
| 294 | BUTTON_RADIUS /* radius */, | ||
| 295 | highlight_start /* start */, | ||
| 296 | highlight_start + (M_PI / 128.0) /* end */); | ||
| 297 | cairo_stroke(ctx); | ||
| 298 | cairo_arc(ctx, | ||
| 299 | BUTTON_CENTER /* x */, | ||
| 300 | BUTTON_CENTER /* y */, | ||
| 301 | BUTTON_RADIUS /* radius */, | ||
| 302 | (highlight_start + (M_PI / 3.0)) - (M_PI / 128.0) /* start */, | ||
| 303 | highlight_start + (M_PI / 3.0) /* end */); | ||
| 304 | cairo_stroke(ctx); | ||
| 305 | } | ||
| 306 | } | ||
| 307 | |||
| 308 | if (xr_screens > 0) { | ||
| 309 | /* Composite the unlock indicator in the middle of each screen. */ | ||
| 310 | for (int screen = 0; screen < xr_screens; screen++) { | ||
| 311 | int x = (xr_resolutions[screen].x + ((xr_resolutions[screen].width / 2) - (button_diameter_physical / 2))); | ||
| 312 | int y = (xr_resolutions[screen].y + ((xr_resolutions[screen].height / 2) - (button_diameter_physical / 2))); | ||
| 313 | cairo_set_source_surface(xcb_ctx, output, x, y); | ||
| 314 | cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical); | ||
| 315 | cairo_fill(xcb_ctx); | ||
| 316 | } | ||
| 317 | } else { | ||
| 318 | /* We have no information about the screen sizes/positions, so we just | ||
| 319 | * place the unlock indicator in the middle of the X root window and | ||
| 320 | * hope for the best. */ | ||
| 321 | int x = (last_resolution[0] / 2) - (button_diameter_physical / 2); | ||
| 322 | int y = (last_resolution[1] / 2) - (button_diameter_physical / 2); | ||
| 323 | cairo_set_source_surface(xcb_ctx, output, x, y); | ||
| 324 | cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical); | ||
| 325 | cairo_fill(xcb_ctx); | ||
| 326 | } | ||
| 327 | |||
| 328 | cairo_surface_destroy(xcb_output); | ||
| 329 | cairo_surface_destroy(output); | ||
| 330 | cairo_destroy(ctx); | ||
| 331 | cairo_destroy(xcb_ctx); | ||
| 332 | return bg_pixmap; | ||
| 333 | } | ||
| 334 | |||
| 335 | /* | ||
| 336 | * Calls draw_image on a new pixmap and swaps that with the current pixmap | ||
| 337 | * | ||
| 338 | */ | ||
| 339 | void redraw_screen(void) { | ||
| 340 | DEBUG("redraw_screen(unlock_state = %d, auth_state = %d)\n", unlock_state, auth_state); | ||
| 341 | xcb_pixmap_t bg_pixmap = draw_image(last_resolution); | ||
| 342 | xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){bg_pixmap}); | ||
| 343 | /* XXX: Possible optimization: Only update the area in the middle of the | ||
| 344 | * screen instead of the whole screen. */ | ||
| 345 | xcb_clear_area(conn, 0, win, 0, 0, last_resolution[0], last_resolution[1]); | ||
| 346 | xcb_free_pixmap(conn, bg_pixmap); | ||
| 347 | xcb_flush(conn); | ||
| 348 | } | ||
| 349 | |||
| 350 | /* | ||
| 351 | * Hides the unlock indicator completely when there is no content in the | ||
| 352 | * password buffer. | ||
| 353 | * | ||
| 354 | */ | ||
| 355 | void clear_indicator(void) { | ||
| 356 | if (input_position == 0) { | ||
| 357 | unlock_state = STATE_STARTED; | ||
| 358 | } else | ||
| 359 | unlock_state = STATE_KEY_PRESSED; | ||
| 360 | redraw_screen(); | ||
| 361 | } | ||
