2 * vim:ts=4:sw=4:expandtab
4 * © 2010-2014 Michael Stapelberg
6 * See LICENSE for licensing information
16 #include <cairo/cairo-xcb.h>
20 #include "unlock_indicator.h"
23 #define BUTTON_RADIUS 90
24 #define BUTTON_SPACE (BUTTON_RADIUS + 5)
25 #define BUTTON_CENTER (BUTTON_RADIUS + 5)
26 #define BUTTON_DIAMETER (2 * BUTTON_SPACE)
28 /*******************************************************************************
29 * Variables defined in i3lock.c.
30 ******************************************************************************/
32 extern bool debug_mode;
34 /* The current position in the input buffer. Useful to determine if any
35 * characters of the password have already been entered or not. */
38 /* The lock window. */
39 extern xcb_window_t win;
41 /* The current resolution of the X11 root window. */
42 extern uint32_t last_resolution[2];
44 /* Whether the unlock indicator is enabled (defaults to true). */
45 extern bool unlock_indicator;
47 /* A Cairo surface containing the specified image (-i), if any. */
48 extern cairo_surface_t *img;
50 /* Whether the image should be tiled. */
52 /* The background color to use (in hex). */
55 /* Whether the failed attempts should be displayed. */
56 extern bool show_failed_attempts;
57 /* Number of failed unlock attempts. */
58 extern int failed_attempts;
60 /*******************************************************************************
61 * Variables defined in xcb.c.
62 ******************************************************************************/
64 /* The root screen, to determine the DPI. */
65 extern xcb_screen_t *screen;
67 /*******************************************************************************
69 ******************************************************************************/
71 /* Cache the screen’s visual, necessary for creating a Cairo context. */
72 static xcb_visualtype_t *vistype;
74 /* Maintain the current unlock/PAM state to draw the appropriate unlock
76 unlock_state_t unlock_state;
77 pam_state_t pam_state;
80 * Returns the scaling factor of the current screen. E.g., on a 227 DPI MacBook
81 * Pro 13" Retina screen, the scaling factor is 227/96 = 2.36.
84 static double scaling_factor(void) {
85 const int dpi = (double)screen->height_in_pixels * 25.4 /
86 (double)screen->height_in_millimeters;
91 * Draws global image with fill color onto a pixmap with the given
92 * resolution and returns it.
95 xcb_pixmap_t draw_image(uint32_t *resolution) {
96 xcb_pixmap_t bg_pixmap = XCB_NONE;
97 int button_diameter_physical = ceil(scaling_factor() * BUTTON_DIAMETER);
98 DEBUG("scaling_factor is %.f, physical diameter is %d px\n",
99 scaling_factor(), button_diameter_physical);
102 vistype = get_root_visual_type(screen);
103 bg_pixmap = create_bg_pixmap(conn, screen, resolution, color);
104 /* Initialize cairo: Create one in-memory surface to render the unlock
105 * indicator on, create one XCB surface to actually draw (one or more,
106 * depending on the amount of screens) unlock indicators on. */
107 cairo_surface_t *output = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, button_diameter_physical, button_diameter_physical);
108 cairo_t *ctx = cairo_create(output);
110 cairo_surface_t *xcb_output = cairo_xcb_surface_create(conn, bg_pixmap, vistype, resolution[0], resolution[1]);
111 cairo_t *xcb_ctx = cairo_create(xcb_output);
115 cairo_set_source_surface(xcb_ctx, img, 0, 0);
116 cairo_paint(xcb_ctx);
118 /* create a pattern and fill a rectangle as big as the screen */
119 cairo_pattern_t *pattern;
120 pattern = cairo_pattern_create_for_surface(img);
121 cairo_set_source(xcb_ctx, pattern);
122 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
123 cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
125 cairo_pattern_destroy(pattern);
128 char strgroups[3][3] = {{color[0], color[1], '\0'},
129 {color[2], color[3], '\0'},
130 {color[4], color[5], '\0'}};
131 uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
132 (strtol(strgroups[1], NULL, 16)),
133 (strtol(strgroups[2], NULL, 16))};
134 cairo_set_source_rgb(xcb_ctx, rgb16[0] / 255.0, rgb16[1] / 255.0, rgb16[2] / 255.0);
135 cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
139 if (unlock_state >= STATE_KEY_PRESSED && unlock_indicator) {
140 cairo_scale(ctx, scaling_factor(), scaling_factor());
141 /* Draw a (centered) circle with transparent background. */
142 cairo_set_line_width(ctx, 10.0);
144 BUTTON_CENTER /* x */,
145 BUTTON_CENTER /* y */,
146 BUTTON_RADIUS /* radius */,
150 /* Use the appropriate color for the different PAM states
151 * (currently verifying, wrong password, or default) */
153 case STATE_PAM_VERIFY:
154 cairo_set_source_rgba(ctx, 0, 114.0/255, 255.0/255, 0.75);
156 case STATE_PAM_WRONG:
157 cairo_set_source_rgba(ctx, 250.0/255, 0, 0, 0.75);
160 cairo_set_source_rgba(ctx, 0, 0, 0, 0.75);
163 cairo_fill_preserve(ctx);
166 case STATE_PAM_VERIFY:
167 cairo_set_source_rgb(ctx, 51.0/255, 0, 250.0/255);
169 case STATE_PAM_WRONG:
170 cairo_set_source_rgb(ctx, 125.0/255, 51.0/255, 0);
173 cairo_set_source_rgb(ctx, 51.0/255, 125.0/255, 0);
178 /* Draw an inner seperator line. */
179 cairo_set_source_rgb(ctx, 0, 0, 0);
180 cairo_set_line_width(ctx, 2.0);
182 BUTTON_CENTER /* x */,
183 BUTTON_CENTER /* y */,
184 BUTTON_RADIUS - 5 /* radius */,
189 cairo_set_line_width(ctx, 10.0);
191 /* Display a (centered) text of the current PAM state. */
193 /* We don't want to show more than a 3-digit number. */
196 cairo_set_source_rgb(ctx, 0, 0, 0);
197 cairo_set_font_size(ctx, 28.0);
199 case STATE_PAM_VERIFY:
202 case STATE_PAM_WRONG:
206 if (show_failed_attempts && failed_attempts > 0){
207 if (failed_attempts > 999) {
210 snprintf(buf, sizeof(buf), "%d", failed_attempts);
213 cairo_set_source_rgb(ctx, 1, 0, 0);
214 cairo_set_font_size(ctx, 32.0);
220 cairo_text_extents_t extents;
223 cairo_text_extents(ctx, text, &extents);
224 x = BUTTON_CENTER - ((extents.width / 2) + extents.x_bearing);
225 y = BUTTON_CENTER - ((extents.height / 2) + extents.y_bearing);
227 cairo_move_to(ctx, x, y);
228 cairo_show_text(ctx, text);
229 cairo_close_path(ctx);
232 /* After the user pressed any valid key or the backspace key, we
233 * highlight a random part of the unlock indicator to confirm this
235 if (unlock_state == STATE_KEY_ACTIVE ||
236 unlock_state == STATE_BACKSPACE_ACTIVE) {
237 cairo_new_sub_path(ctx);
238 double highlight_start = (rand() % (int)(2 * M_PI * 100)) / 100.0;
240 BUTTON_CENTER /* x */,
241 BUTTON_CENTER /* y */,
242 BUTTON_RADIUS /* radius */,
244 highlight_start + (M_PI / 3.0));
245 if (unlock_state == STATE_KEY_ACTIVE) {
246 /* For normal keys, we use a lighter green. */
247 cairo_set_source_rgb(ctx, 51.0/255, 219.0/255, 0);
249 /* For backspace, we use red. */
250 cairo_set_source_rgb(ctx, 219.0/255, 51.0/255, 0);
254 /* Draw two little separators for the highlighted part of the
255 * unlock indicator. */
256 cairo_set_source_rgb(ctx, 0, 0, 0);
258 BUTTON_CENTER /* x */,
259 BUTTON_CENTER /* y */,
260 BUTTON_RADIUS /* radius */,
261 highlight_start /* start */,
262 highlight_start + (M_PI / 128.0) /* end */);
265 BUTTON_CENTER /* x */,
266 BUTTON_CENTER /* y */,
267 BUTTON_RADIUS /* radius */,
268 highlight_start + (M_PI / 3.0) /* start */,
269 (highlight_start + (M_PI / 3.0)) + (M_PI / 128.0) /* end */);
274 if (xr_screens > 0) {
275 /* Composite the unlock indicator in the middle of each screen. */
276 for (int screen = 0; screen < xr_screens; screen++) {
277 int x = (xr_resolutions[screen].x + ((xr_resolutions[screen].width / 2) - (button_diameter_physical / 2)));
278 int y = (xr_resolutions[screen].y + ((xr_resolutions[screen].height / 2) - (button_diameter_physical / 2)));
279 cairo_set_source_surface(xcb_ctx, output, x, y);
280 cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical);
284 /* We have no information about the screen sizes/positions, so we just
285 * place the unlock indicator in the middle of the X root window and
286 * hope for the best. */
287 int x = (last_resolution[0] / 2) - (button_diameter_physical / 2);
288 int y = (last_resolution[1] / 2) - (button_diameter_physical / 2);
289 cairo_set_source_surface(xcb_ctx, output, x, y);
290 cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical);
294 cairo_surface_destroy(xcb_output);
295 cairo_surface_destroy(output);
297 cairo_destroy(xcb_ctx);
302 * Calls draw_image on a new pixmap and swaps that with the current pixmap
305 void redraw_screen(void) {
306 xcb_pixmap_t bg_pixmap = draw_image(last_resolution);
307 xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){ bg_pixmap });
308 /* XXX: Possible optimization: Only update the area in the middle of the
309 * screen instead of the whole screen. */
310 xcb_clear_area(conn, 0, win, 0, 0, last_resolution[0], last_resolution[1]);
311 xcb_free_pixmap(conn, bg_pixmap);
316 * Hides the unlock indicator completely when there is no content in the
320 void clear_indicator(void) {
321 if (input_position == 0) {
322 unlock_state = STATE_STARTED;
323 } else unlock_state = STATE_KEY_PRESSED;