1#include <math.h>
2#include <stdlib.h>
3#include <time.h>
4#include <locale.h>
5#include <wayland-client.h>
6#include "cairo.h"
7#include "background-image.h"
8#include "swaylock.h"
9
10// glib might or might not have already defined MIN,
11// depending on whether we have pixbuf or not...
12#ifndef MIN
13#define MIN(a, b) ((a) < (b) ? (a) : (b))
14#endif
15
16#define M_PI 3.14159265358979323846
17const float TYPE_INDICATOR_RANGE = M_PI / 3.0f;
18const float TYPE_INDICATOR_BORDER_THICKNESS = M_PI / 128.0f;
19
20static void set_color_for_state(cairo_t *cairo, struct swaylock_state *state,
21 struct swaylock_colorset *colorset) {
22 if (state->auth_state == AUTH_STATE_VALIDATING) {
23 cairo_set_source_u32(cairo, colorset->verifying);
24 } else if (state->auth_state == AUTH_STATE_INVALID) {
25 cairo_set_source_u32(cairo, colorset->wrong);
26 } else if (state->auth_state == AUTH_STATE_CLEAR) {
27 cairo_set_source_u32(cairo, colorset->cleared);
28 } else {
29 if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) {
30 cairo_set_source_u32(cairo, colorset->caps_lock);
31 } else if (state->xkb.caps_lock && !state->args.show_caps_lock_indicator &&
32 state->args.show_caps_lock_text) {
33 uint32_t inputtextcolor = state->args.colors.text.input;
34 state->args.colors.text.input = state->args.colors.text.caps_lock;
35 cairo_set_source_u32(cairo, colorset->input);
36 state->args.colors.text.input = inputtextcolor;
37 } else {
38 cairo_set_source_u32(cairo, colorset->input);
39 }
40 }
41}
42
43static void timetext(struct swaylock_surface *surface, char **tstr, char **dstr) {
44 static char dbuf[256];
45 static char tbuf[256];
46
47 // Use user's locale for strftime calls
48 char *prevloc = setlocale(LC_TIME, NULL);
49 setlocale(LC_TIME, "");
50
51 time_t t = time(NULL);
52 struct tm *tm = localtime(&t);
53
54 if (surface->state->args.timestr[0]) {
55 strftime(tbuf, sizeof(tbuf), surface->state->args.timestr, tm);
56 *tstr = tbuf;
57 } else {
58 *tstr = NULL;
59 }
60
61 if (surface->state->args.datestr[0]) {
62 strftime(dbuf, sizeof(dbuf), surface->state->args.datestr, tm);
63 *dstr = dbuf;
64 } else {
65 *dstr = NULL;
66 }
67
68 // Set it back, so we don't break stuff
69 setlocale(LC_TIME, prevloc);
70}
71
72void render_frame_background(struct swaylock_surface *surface, bool commit) {
73 struct swaylock_state *state = surface->state;
74
75 int buffer_width = surface->width * surface->scale;
76 int buffer_height = surface->height * surface->scale;
77 if (buffer_width == 0 || buffer_height == 0) {
78 return; // not yet configured
79 }
80
81 struct pool_buffer *buffer = get_next_buffer(state->shm,
82 surface->buffers, buffer_width, buffer_height);
83 if (buffer == NULL) {
84 return;
85 }
86
87 cairo_t *cairo = buffer->cairo;
88 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
89
90 cairo_save(cairo);
91 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
92 cairo_set_source_u32(cairo, state->args.colors.background);
93 cairo_pattern_set_filter(cairo_get_source(cairo), CAIRO_FILTER_BILINEAR);
94 cairo_paint(cairo);
95 if (surface->image && state->args.mode != BACKGROUND_MODE_SOLID_COLOR) {
96 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
97 if (fade_is_complete(&surface->fade)) {
98 if (!surface->scaled_image) {
99 surface->scaled_image =
100 scale_background_image(surface->image, state->args.mode,
101 buffer_width, buffer_height);
102 }
103 render_background_image(cairo, surface->scaled_image, 1);
104 } else {
105 if (!surface->screencopy.scaled_image) {
106 surface->screencopy.scaled_image =
107 scale_background_image(surface->screencopy.original_image,
108 state->args.mode, buffer_width, buffer_height);
109 }
110 render_background_image(cairo, surface->screencopy.scaled_image, 1);
111 if (!surface->scaled_image) {
112 surface->scaled_image =
113 scale_background_image(surface->image, state->args.mode,
114 buffer_width, buffer_height);
115 }
116 render_background_image(cairo, surface->scaled_image, surface->fade.alpha);
117 }
118 }
119 cairo_restore(cairo);
120 cairo_identity_matrix(cairo);
121
122 wl_surface_set_buffer_scale(surface->surface, surface->scale);
123 wl_surface_attach(surface->surface, buffer->buffer, 0, 0);
124 wl_surface_damage_buffer(surface->surface, 0, 0, INT32_MAX, INT32_MAX);
125 if (commit) {
126 wl_surface_commit(surface->surface);
127 }
128}
129
130void render_background_fade(struct swaylock_surface *surface, uint32_t time) {
131 if (fade_is_complete(&surface->fade)) {
132 return;
133 }
134
135 fade_update(&surface->fade, time);
136
137 render_frame_background(surface, true);
138 render_frame(surface);
139}
140
141void render_frame(struct swaylock_surface *surface) {
142 struct swaylock_state *state = surface->state;
143
144 int arc_radius = state->args.radius * surface->scale;
145 int arc_thickness = state->args.thickness * surface->scale;
146 int buffer_diameter = (arc_radius + arc_thickness) * 2;
147
148 int buffer_width = surface->indicator_width;
149 int buffer_height = surface->indicator_height;
150 int new_width = buffer_diameter;
151 int new_height = buffer_diameter;
152
153 int subsurf_xpos;
154 int subsurf_ypos;
155
156 // Center the indicator unless overridden by the user
157 if (state->args.override_indicator_x_position) {
158 subsurf_xpos = state->args.indicator_x_position -
159 buffer_width / (2 * surface->scale) + 2 / surface->scale;
160 } else {
161 subsurf_xpos = surface->width / 2 -
162 buffer_width / (2 * surface->scale) + 2 / surface->scale;
163 }
164
165 if (state->args.override_indicator_y_position) {
166 subsurf_ypos = state->args.indicator_y_position -
167 (state->args.radius + state->args.thickness);
168 } else {
169 subsurf_ypos = surface->height / 2 -
170 (state->args.radius + state->args.thickness);
171 }
172
173 wl_subsurface_set_position(surface->subsurface, subsurf_xpos, subsurf_ypos);
174
175 struct pool_buffer *buffer = get_next_buffer(state->shm,
176 surface->indicator_buffers, buffer_width, buffer_height);
177 if (buffer == NULL) {
178 return;
179 }
180
181 cairo_t *cairo = buffer->cairo;
182 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
183 cairo_font_options_t *fo = cairo_font_options_create();
184 cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
185 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
186 cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(surface->subpixel));
187 cairo_set_font_options(cairo, fo);
188 cairo_font_options_destroy(fo);
189 cairo_identity_matrix(cairo);
190
191 // Clear
192 cairo_save(cairo);
193 cairo_set_source_rgba(cairo, 0, 0, 0, 0);
194 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
195 cairo_paint(cairo);
196 cairo_restore(cairo);
197
198 float type_indicator_border_thickness =
199 TYPE_INDICATOR_BORDER_THICKNESS * surface->scale;
200
201 // This is a bit messy.
202 // After the fork, upstream added their own --indicator-idle-visible option,
203 // but it works slightly differently from swaylock-effects' --indicator
204 // option. To maintain compatibility with upstream swaylock scripts as well
205 // as with old swaylock-effects scripts, I will keep both flags.
206 bool upstream_show_indicator =
207 state->args.show_indicator && (state->auth_state != AUTH_STATE_IDLE ||
208 state->args.indicator_idle_visible);
209
210 if (state->args.indicator ||
211 (upstream_show_indicator && state->auth_state != AUTH_STATE_GRACE)) {
212 // Draw indicator image
213 cairo_surface_t *image = state->indicator_image;
214 if (image) {
215 int height = cairo_image_surface_get_height(image);
216 int width = cairo_image_surface_get_width(image);
217 int smallest = MIN(height, width);
218 double radius = arc_radius - arc_thickness * 0.5;
219 double scale = radius * 2 / smallest;
220 double offset = buffer_diameter * 0.5 / scale - smallest * 0.5;
221
222 // Create the arc that clips the image
223 cairo_arc(cairo,
224 buffer_diameter * 0.5,
225 buffer_diameter * 0.5,
226 radius,
227 0, 2 * M_PI);
228 // Scale cairo to make image fit the indicator
229 cairo_scale(cairo, scale, scale);
230 cairo_set_source_surface(cairo, image, offset, offset);
231 // Scale cairo back
232 cairo_scale(cairo, 1 / scale, 1 / scale);
233 cairo_fill(cairo);
234 }
235
236 // Fill inner circle
237 cairo_set_line_width(cairo, 0);
238 cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
239 arc_radius - arc_thickness / 2, 0, 2 * M_PI);
240 set_color_for_state(cairo, state, &state->args.colors.inside);
241 cairo_fill_preserve(cairo);
242 cairo_stroke(cairo);
243
244 // Draw ring
245 cairo_set_line_width(cairo, arc_thickness);
246 cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, arc_radius,
247 0, 2 * M_PI);
248 set_color_for_state(cairo, state, &state->args.colors.ring);
249 cairo_stroke(cairo);
250
251 // Draw a message
252 char *text = NULL;
253 char *text_l1 = NULL;
254 char *text_l2 = NULL;
255 const char *layout_text = NULL;
256 double font_size;
257 char attempts[4]; // like i3lock: count no more than 999
258 set_color_for_state(cairo, state, &state->args.colors.text);
259 cairo_select_font_face(cairo, state->args.font,
260 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
261 if (state->args.font_size > 0) {
262 font_size = state->args.font_size;
263 } else {
264 font_size = arc_radius / 3.0f;
265 }
266 cairo_set_font_size(cairo, font_size);
267 switch (state->auth_state) {
268 case AUTH_STATE_VALIDATING:
269 text = state->args.text_verifying;
270 break;
271 case AUTH_STATE_INVALID:
272 text = state->args.text_wrong;
273 break;
274 case AUTH_STATE_CLEAR:
275 text = state->args.text_cleared;
276 break;
277 case AUTH_STATE_INPUT:
278 case AUTH_STATE_INPUT_NOP:
279 case AUTH_STATE_BACKSPACE:
280 // Password text visible basic feat
281
282 char *hashText = "*";
283 if (state->password.len == 0 && state->args.ignore_empty) {
284 text = "";
285 } else {
286 size_t pass_len = state->password.len;
287 size_t max_stars = 10;
288 size_t stars_to_show = pass_len < max_stars ? pass_len : max_stars;
289 size_t hash_len = stars_to_show * strlen(hashText) + 1;
290 char *hash_buffer = malloc(hash_len);
291 if (!hash_buffer) {
292 return;
293 }
294 hash_buffer[0] = '\0';
295 for (size_t i = 0; i < stars_to_show; ++i) {
296 strcat(hash_buffer, hashText);
297 }
298 text = hash_buffer;
299 }
300
301 // Caps Lock has higher priority
302 if (state->xkb.caps_lock && state->args.show_caps_lock_text) {
303 text = state->args.text_caps_lock;
304 } else if (state->args.show_failed_attempts &&
305 state->failed_attempts > 0) {
306 if (state->failed_attempts > 999) {
307 text = "999+";
308 } else {
309 snprintf(attempts, sizeof(attempts), "%d", state->failed_attempts);
310 text = attempts;
311 }
312 } else if (state->args.clock) {
313 timetext(surface, &text_l1, &text_l2);
314 }
315
316 xkb_layout_index_t num_layout = xkb_keymap_num_layouts(state->xkb.keymap);
317 if (!state->args.hide_keyboard_layout &&
318 (state->args.show_keyboard_layout || num_layout > 1)) {
319 xkb_layout_index_t curr_layout = 0;
320
321 // advance to the first active layout (if any)
322 while (curr_layout < num_layout &&
323 xkb_state_layout_index_is_active(state->xkb.state,
324 curr_layout, XKB_STATE_LAYOUT_EFFECTIVE) != 1) {
325 ++curr_layout;
326 }
327 // will handle invalid index if none are active
328 layout_text = xkb_keymap_layout_get_name(state->xkb.keymap, curr_layout);
329 }
330 break;
331 default:
332 if (state->args.clock)
333 timetext(surface, &text_l1, &text_l2);
334 break;
335 }
336
337 if (text_l1 && !text_l2)
338 text = text_l1;
339 if (text_l2 && !text_l1)
340 text = text_l2;
341
342 if (text) {
343 cairo_text_extents_t extents;
344 cairo_font_extents_t fe;
345 double x, y;
346 cairo_text_extents(cairo, text, &extents);
347 cairo_font_extents(cairo, &fe);
348 x = (buffer_width / 2) -
349 (extents.width / 2 + extents.x_bearing);
350 y = (buffer_diameter / 2) +
351 (fe.height / 2 - fe.descent);
352
353 cairo_move_to(cairo, x, y);
354 cairo_show_text(cairo, text);
355 cairo_close_path(cairo);
356 cairo_new_sub_path(cairo);
357
358 if (new_width < extents.width) {
359 new_width = extents.width;
360 }
361 } else if (text_l1 && text_l2) {
362 cairo_text_extents_t extents_l1, extents_l2;
363 cairo_font_extents_t fe_l1, fe_l2;
364 double x_l1, y_l1, x_l2, y_l2;
365
366 /* Top */
367
368 cairo_text_extents(cairo, text_l1, &extents_l1);
369 cairo_font_extents(cairo, &fe_l1);
370 x_l1 = (buffer_width / 2) -
371 (extents_l1.width / 2 + extents_l1.x_bearing);
372 y_l1 = (buffer_diameter / 2) +
373 (fe_l1.height / 2 - fe_l1.descent) - arc_radius / 10.0f;
374
375 cairo_move_to(cairo, x_l1, y_l1);
376 cairo_show_text(cairo, text_l1);
377 cairo_close_path(cairo);
378 cairo_new_sub_path(cairo);
379
380 /* Bottom */
381
382 cairo_set_font_size(cairo, arc_radius / 6.0f);
383 cairo_text_extents(cairo, text_l2, &extents_l2);
384 cairo_font_extents(cairo, &fe_l2);
385 x_l2 = (buffer_width / 2) -
386 (extents_l2.width / 2 + extents_l2.x_bearing);
387 y_l2 = (buffer_diameter / 2) +
388 (fe_l2.height / 2 - fe_l2.descent) + arc_radius / 3.5f;
389
390 cairo_move_to(cairo, x_l2, y_l2);
391 cairo_show_text(cairo, text_l2);
392 cairo_close_path(cairo);
393 cairo_new_sub_path(cairo);
394
395 if (new_width < extents_l1.width)
396 new_width = extents_l1.width;
397 if (new_width < extents_l2.width)
398 new_width = extents_l2.width;
399
400
401 cairo_set_font_size(cairo, font_size);
402 }
403
404 // Typing indicator: Highlight random part on keypress
405 if (state->auth_state == AUTH_STATE_INPUT
406 || state->auth_state == AUTH_STATE_BACKSPACE) {
407
408 static double highlight_start = 0;
409 if (state->indicator_dirty) {
410 highlight_start +=
411 (rand() % (int)(M_PI * 100)) / 100.0 + M_PI * 0.5;
412 state->indicator_dirty = false;
413 }
414
415 cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
416 arc_radius, highlight_start,
417 highlight_start + TYPE_INDICATOR_RANGE);
418 if (state->auth_state == AUTH_STATE_INPUT) {
419 if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) {
420 cairo_set_source_u32(cairo, state->args.colors.caps_lock_key_highlight);
421 } else {
422 cairo_set_source_u32(cairo, state->args.colors.key_highlight);
423 }
424 } else {
425 if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) {
426 cairo_set_source_u32(cairo, state->args.colors.caps_lock_bs_highlight);
427 } else {
428 cairo_set_source_u32(cairo, state->args.colors.bs_highlight);
429 }
430 }
431 cairo_stroke(cairo);
432
433 // Draw borders
434 cairo_set_source_u32(cairo, state->args.colors.separator);
435 cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
436 arc_radius, highlight_start,
437 highlight_start + type_indicator_border_thickness);
438 cairo_stroke(cairo);
439
440 cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
441 arc_radius, highlight_start + TYPE_INDICATOR_RANGE,
442 highlight_start + TYPE_INDICATOR_RANGE +
443 type_indicator_border_thickness);
444 cairo_stroke(cairo);
445 }
446
447 // Draw inner + outer border of the circle
448 set_color_for_state(cairo, state, &state->args.colors.line);
449 cairo_set_line_width(cairo, 2.0 * surface->scale);
450 cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
451 arc_radius - arc_thickness / 2, 0, 2 * M_PI);
452 cairo_stroke(cairo);
453 cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
454 arc_radius + arc_thickness / 2, 0, 2 * M_PI);
455 cairo_stroke(cairo);
456
457 // display layout text separately
458 if (layout_text) {
459 cairo_text_extents_t extents;
460 cairo_font_extents_t fe;
461 double x, y;
462 double box_padding = 4.0 * surface->scale;
463 cairo_text_extents(cairo, layout_text, &extents);
464 cairo_font_extents(cairo, &fe);
465 // upper left coordinates for box
466 x = (buffer_width / 2) - (extents.width / 2) - box_padding;
467 y = buffer_diameter;
468
469 // background box
470 cairo_rectangle(cairo, x, y,
471 extents.width + 2.0 * box_padding,
472 fe.height + 2.0 * box_padding);
473 cairo_set_source_u32(cairo, state->args.colors.layout_background);
474 cairo_fill_preserve(cairo);
475 // border
476 cairo_set_source_u32(cairo, state->args.colors.layout_border);
477 cairo_stroke(cairo);
478
479 // take font extents and padding into account
480 cairo_move_to(cairo,
481 x - extents.x_bearing + box_padding,
482 y + (fe.height - fe.descent) + box_padding);
483 cairo_set_source_u32(cairo, state->args.colors.layout_text);
484 cairo_show_text(cairo, layout_text);
485 cairo_new_sub_path(cairo);
486
487 new_height += fe.height + 2 * box_padding;
488 if (new_width < extents.width + 2 * box_padding) {
489 new_width = extents.width + 2 * box_padding;
490 }
491 }
492 }
493
494 // Ensure buffer size is multiple of buffer scale - required by protocol
495 new_height += surface->scale - (new_height % surface->scale);
496 new_width += surface->scale - (new_width % surface->scale);
497
498 if (buffer_width != new_width || buffer_height != new_height) {
499 destroy_buffer(buffer);
500 surface->indicator_width = new_width;
501 surface->indicator_height = new_height;
502 render_frame(surface);
503 return;
504 }
505
506 wl_surface_set_buffer_scale(surface->child, surface->scale);
507 wl_surface_attach(surface->child, buffer->buffer, 0, 0);
508 wl_surface_damage_buffer(surface->child, 0, 0, INT32_MAX, INT32_MAX);
509 wl_surface_commit(surface->child);
510
511 wl_surface_commit(surface->surface);
512}