]> git.sur5r.net Git - i3/i3lock/blob - unlock_indicator.c
Bugfix: Check for waiting X11 events before entering the event loop
[i3/i3lock] / unlock_indicator.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * © 2010-2012 Michael Stapelberg
5  *
6  * See LICENSE for licensing information
7  *
8  */
9 #include <stdbool.h>
10 #include <stdlib.h>
11 #include <math.h>
12 #include <xcb/xcb.h>
13 #include <xcb/xcb_keysyms.h>
14 #include <ev.h>
15
16 #ifndef NOLIBCAIRO
17 #include <cairo.h>
18 #include <cairo/cairo-xcb.h>
19 #endif
20
21 #include "xcb.h"
22 #include "unlock_indicator.h"
23 #include "xinerama.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 /* The current position in the input buffer. Useful to determine if any
35  * characters of the password have already been entered or not. */
36 int input_position;
37
38 /* The ev main loop. */
39 struct ev_loop *main_loop;
40
41 /* The lock window. */
42 extern xcb_window_t win;
43
44 /* The current resolution of the X11 root window. */
45 extern uint32_t last_resolution[2];
46
47 /* Whether the unlock indicator is enabled (defaults to true). */
48 extern bool unlock_indicator;
49
50 /* A Cairo surface containing the specified image (-i), if any. */
51 extern cairo_surface_t *img;
52 /* Whether the image should be tiled. */
53 extern bool tile;
54 /* The background color to use (in hex). */
55 extern char color[7];
56
57 /*******************************************************************************
58  * Local variables.
59  ******************************************************************************/
60
61 static struct ev_timer *clear_indicator_timeout;
62
63 /* Cache the screen’s visual, necessary for creating a Cairo context. */
64 static xcb_visualtype_t *vistype;
65
66 /* Maintain the current unlock/PAM state to draw the appropriate unlock
67  * indicator. */
68 unlock_state_t unlock_state;
69 pam_state_t pam_state;
70
71 /*
72  * Draws global image with fill color onto a pixmap with the given
73  * resolution and returns it.
74  *
75  */
76 xcb_pixmap_t draw_image(uint32_t *resolution) {
77     xcb_pixmap_t bg_pixmap = XCB_NONE;
78
79 #ifndef NOLIBCAIRO
80     if (!vistype)
81         vistype = get_root_visual_type(screen);
82     bg_pixmap = create_bg_pixmap(conn, screen, resolution, color);
83     /* Initialize cairo: Create one in-memory surface to render the unlock
84      * indicator on, create one XCB surface to actually draw (one or more,
85      * depending on the amount of screens) unlock indicators on. */
86     cairo_surface_t *output = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, BUTTON_DIAMETER, BUTTON_DIAMETER);
87     cairo_t *ctx = cairo_create(output);
88
89     cairo_surface_t *xcb_output = cairo_xcb_surface_create(conn, bg_pixmap, vistype, resolution[0], resolution[1]);
90     cairo_t *xcb_ctx = cairo_create(xcb_output);
91
92     if (img) {
93         if (!tile) {
94             cairo_set_source_surface(xcb_ctx, img, 0, 0);
95             cairo_paint(xcb_ctx);
96         } else {
97             /* create a pattern and fill a rectangle as big as the screen */
98             cairo_pattern_t *pattern;
99             pattern = cairo_pattern_create_for_surface(img);
100             cairo_set_source(xcb_ctx, pattern);
101             cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
102             cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
103             cairo_fill(xcb_ctx);
104             cairo_pattern_destroy(pattern);
105         }
106     } else {
107         char strgroups[3][3] = {{color[0], color[1], '\0'},
108                                 {color[2], color[3], '\0'},
109                                 {color[4], color[5], '\0'}};
110         uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
111                              (strtol(strgroups[1], NULL, 16)),
112                              (strtol(strgroups[2], NULL, 16))};
113         cairo_set_source_rgb(xcb_ctx, rgb16[0], rgb16[1], rgb16[2]);
114         cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
115         cairo_fill(xcb_ctx);
116     }
117
118     if (unlock_state >= STATE_KEY_PRESSED && unlock_indicator) {
119         /* Draw a (centered) circle with transparent background. */
120         cairo_set_line_width(ctx, 10.0);
121         cairo_arc(ctx,
122                   BUTTON_CENTER /* x */,
123                   BUTTON_CENTER /* y */,
124                   BUTTON_RADIUS /* radius */,
125                   0 /* start */,
126                   2 * M_PI /* end */);
127
128         /* Use the appropriate color for the different PAM states
129          * (currently verifying, wrong password, or default) */
130         switch (pam_state) {
131             case STATE_PAM_VERIFY:
132                 cairo_set_source_rgba(ctx, 0, 114.0/255, 255.0/255, 0.75);
133                 break;
134             case STATE_PAM_WRONG:
135                 cairo_set_source_rgba(ctx, 250.0/255, 0, 0, 0.75);
136                 break;
137             default:
138                 cairo_set_source_rgba(ctx, 0, 0, 0, 0.75);
139                 break;
140         }
141         cairo_fill_preserve(ctx);
142
143         switch (pam_state) {
144             case STATE_PAM_VERIFY:
145                 cairo_set_source_rgb(ctx, 51.0/255, 0, 250.0/255);
146                 break;
147             case STATE_PAM_WRONG:
148                 cairo_set_source_rgb(ctx, 125.0/255, 51.0/255, 0);
149                 break;
150             case STATE_PAM_IDLE:
151                 cairo_set_source_rgb(ctx, 51.0/255, 125.0/255, 0);
152                 break;
153         }
154         cairo_stroke(ctx);
155
156         /* Draw an inner seperator line. */
157         cairo_set_source_rgb(ctx, 0, 0, 0);
158         cairo_set_line_width(ctx, 2.0);
159         cairo_arc(ctx,
160                   BUTTON_CENTER /* x */,
161                   BUTTON_CENTER /* y */,
162                   BUTTON_RADIUS - 5 /* radius */,
163                   0,
164                   2 * M_PI);
165         cairo_stroke(ctx);
166
167         cairo_set_line_width(ctx, 10.0);
168
169         /* Display a (centered) text of the current PAM state. */
170         char *text = NULL;
171         switch (pam_state) {
172             case STATE_PAM_VERIFY:
173                 text = "verifying…";
174                 break;
175             case STATE_PAM_WRONG:
176                 text = "wrong!";
177                 break;
178             default:
179                 break;
180         }
181
182         if (text) {
183             cairo_text_extents_t extents;
184             double x, y;
185
186             cairo_set_source_rgb(ctx, 0, 0, 0);
187             cairo_set_font_size(ctx, 28.0);
188
189             cairo_text_extents(ctx, text, &extents);
190             x = BUTTON_CENTER - ((extents.width / 2) + extents.x_bearing);
191             y = BUTTON_CENTER - ((extents.height / 2) + extents.y_bearing);
192
193             cairo_move_to(ctx, x, y);
194             cairo_show_text(ctx, text);
195             cairo_close_path(ctx);
196         }
197
198         /* After the user pressed any valid key or the backspace key, we
199          * highlight a random part of the unlock indicator to confirm this
200          * keypress. */
201         if (unlock_state == STATE_KEY_ACTIVE ||
202             unlock_state == STATE_BACKSPACE_ACTIVE) {
203             cairo_new_sub_path(ctx);
204             double highlight_start = (rand() % (int)(2 * M_PI * 100)) / 100.0;
205             cairo_arc(ctx,
206                       BUTTON_CENTER /* x */,
207                       BUTTON_CENTER /* y */,
208                       BUTTON_RADIUS /* radius */,
209                       highlight_start,
210                       highlight_start + (M_PI / 3.0));
211             if (unlock_state == STATE_KEY_ACTIVE) {
212                 /* For normal keys, we use a lighter green. */
213                 cairo_set_source_rgb(ctx, 51.0/255, 219.0/255, 0);
214             } else {
215                 /* For backspace, we use red. */
216                 cairo_set_source_rgb(ctx, 219.0/255, 51.0/255, 0);
217             }
218             cairo_stroke(ctx);
219
220             /* Draw two little separators for the highlighted part of the
221              * unlock indicator. */
222             cairo_set_source_rgb(ctx, 0, 0, 0);
223             cairo_arc(ctx,
224                       BUTTON_CENTER /* x */,
225                       BUTTON_CENTER /* y */,
226                       BUTTON_RADIUS /* radius */,
227                       highlight_start /* start */,
228                       highlight_start + (M_PI / 128.0) /* end */);
229             cairo_stroke(ctx);
230             cairo_arc(ctx,
231                       BUTTON_CENTER /* x */,
232                       BUTTON_CENTER /* y */,
233                       BUTTON_RADIUS /* radius */,
234                       highlight_start + (M_PI / 3.0) /* start */,
235                       (highlight_start + (M_PI / 3.0)) + (M_PI / 128.0) /* end */);
236             cairo_stroke(ctx);
237         }
238     }
239
240     if (xr_screens > 0) {
241         /* Composite the unlock indicator in the middle of each screen. */
242         for (int screen = 0; screen < xr_screens; screen++) {
243             int x = (xr_resolutions[screen].x + ((xr_resolutions[screen].width / 2) - (BUTTON_DIAMETER / 2)));
244             int y = (xr_resolutions[screen].y + ((xr_resolutions[screen].height / 2) - (BUTTON_DIAMETER / 2)));
245             cairo_set_source_surface(xcb_ctx, output, x, y);
246             cairo_rectangle(xcb_ctx, x, y, BUTTON_DIAMETER, BUTTON_DIAMETER);
247             cairo_fill(xcb_ctx);
248         }
249     } else {
250         /* We have no information about the screen sizes/positions, so we just
251          * place the unlock indicator in the middle of the X root window and
252          * hope for the best. */
253         int x = (last_resolution[0] / 2);
254         int y = (last_resolution[1] / 2);
255         cairo_set_source_surface(xcb_ctx, output, x, y);
256         cairo_rectangle(xcb_ctx, x, y, BUTTON_DIAMETER, BUTTON_DIAMETER);
257         cairo_fill(xcb_ctx);
258     }
259
260     cairo_surface_destroy(xcb_output);
261     cairo_surface_destroy(output);
262     cairo_destroy(ctx);
263     cairo_destroy(xcb_ctx);
264 #endif
265     return bg_pixmap;
266 }
267
268 /*
269  * Calls draw_image on a new pixmap and swaps that with the current pixmap
270  *
271  */
272 void redraw_screen() {
273     xcb_pixmap_t bg_pixmap = draw_image(last_resolution);
274     xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){ bg_pixmap });
275     /* XXX: Possible optimization: Only update the area in the middle of the
276      * screen instead of the whole screen. */
277     xcb_clear_area(conn, 0, win, 0, 0, screen->width_in_pixels, screen->height_in_pixels);
278     xcb_free_pixmap(conn, bg_pixmap);
279     xcb_flush(conn);
280 }
281
282 /*
283  * Hides the unlock indicator completely when there is no content in the
284  * password buffer.
285  *
286  */
287 static void clear_indicator(EV_P_ ev_timer *w, int revents) {
288     if (input_position == 0) {
289         unlock_state = STATE_STARTED;
290     } else unlock_state = STATE_KEY_PRESSED;
291     redraw_screen();
292
293     ev_timer_stop(main_loop, clear_indicator_timeout);
294     free(clear_indicator_timeout);
295     clear_indicator_timeout = NULL;
296 }
297
298 /*
299  * (Re-)starts the clear_indicator timeout. Called after pressing backspace or
300  * after an unsuccessful authentication attempt.
301  *
302  */
303 void start_clear_indicator_timeout() {
304     if (clear_indicator_timeout) {
305         ev_timer_stop(main_loop, clear_indicator_timeout);
306         ev_timer_set(clear_indicator_timeout, 1.0, 0.);
307         ev_timer_start(main_loop, clear_indicator_timeout);
308     } else {
309         /* When there is no memory, we just don’t have a timeout. We cannot
310          * exit() here, since that would effectively unlock the screen. */
311         if (!(clear_indicator_timeout = calloc(sizeof(struct ev_timer), 1)))
312             return;
313         ev_timer_init(clear_indicator_timeout, clear_indicator, 1.0, 0.);
314         ev_timer_start(main_loop, clear_indicator_timeout);
315     }
316 }
317
318 /*
319  * Stops the clear_indicator timeout.
320  *
321  */
322 void stop_clear_indicator_timeout() {
323     if (clear_indicator_timeout) {
324         ev_timer_stop(main_loop, clear_indicator_timeout);
325         free(clear_indicator_timeout);
326         clear_indicator_timeout = NULL;
327     }
328 }