main
44ceea7 ยท 1 month ago 7 commits
  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}