diff options
Diffstat (limited to 'swaylock/main.c')
-rw-r--r-- | swaylock/main.c | 1057 |
1 files changed, 0 insertions, 1057 deletions
diff --git a/swaylock/main.c b/swaylock/main.c deleted file mode 100644 index 0b167da1..00000000 --- a/swaylock/main.c +++ /dev/null | |||
@@ -1,1057 +0,0 @@ | |||
1 | #define _POSIX_C_SOURCE 200809L | ||
2 | #include <assert.h> | ||
3 | #include <ctype.h> | ||
4 | #include <errno.h> | ||
5 | #include <fcntl.h> | ||
6 | #include <getopt.h> | ||
7 | #include <poll.h> | ||
8 | #include <stdbool.h> | ||
9 | #include <stdio.h> | ||
10 | #include <stdlib.h> | ||
11 | #include <string.h> | ||
12 | #include <sys/mman.h> | ||
13 | #include <sys/stat.h> | ||
14 | #include <time.h> | ||
15 | #include <unistd.h> | ||
16 | #include <wayland-client.h> | ||
17 | #include <wordexp.h> | ||
18 | #include <wlr/util/log.h> | ||
19 | #include "swaylock/seat.h" | ||
20 | #include "swaylock/swaylock.h" | ||
21 | #include "background-image.h" | ||
22 | #include "pool-buffer.h" | ||
23 | #include "cairo.h" | ||
24 | #include "log.h" | ||
25 | #include "loop.h" | ||
26 | #include "stringop.h" | ||
27 | #include "util.h" | ||
28 | #include "wlr-input-inhibitor-unstable-v1-client-protocol.h" | ||
29 | #include "wlr-layer-shell-unstable-v1-client-protocol.h" | ||
30 | #include "xdg-output-unstable-v1-client-protocol.h" | ||
31 | |||
32 | void sway_terminate(int exit_code) { | ||
33 | exit(exit_code); | ||
34 | } | ||
35 | |||
36 | static void daemonize(void) { | ||
37 | int fds[2]; | ||
38 | if (pipe(fds) != 0) { | ||
39 | wlr_log(WLR_ERROR, "Failed to pipe"); | ||
40 | exit(1); | ||
41 | } | ||
42 | if (fork() == 0) { | ||
43 | setsid(); | ||
44 | close(fds[0]); | ||
45 | int devnull = open("/dev/null", O_RDWR); | ||
46 | dup2(STDOUT_FILENO, devnull); | ||
47 | dup2(STDERR_FILENO, devnull); | ||
48 | close(devnull); | ||
49 | uint8_t success = 0; | ||
50 | if (chdir("/") != 0) { | ||
51 | write(fds[1], &success, 1); | ||
52 | exit(1); | ||
53 | } | ||
54 | success = 1; | ||
55 | if (write(fds[1], &success, 1) != 1) { | ||
56 | exit(1); | ||
57 | } | ||
58 | close(fds[1]); | ||
59 | } else { | ||
60 | close(fds[1]); | ||
61 | uint8_t success; | ||
62 | if (read(fds[0], &success, 1) != 1 || !success) { | ||
63 | wlr_log(WLR_ERROR, "Failed to daemonize"); | ||
64 | exit(1); | ||
65 | } | ||
66 | close(fds[0]); | ||
67 | exit(0); | ||
68 | } | ||
69 | } | ||
70 | |||
71 | static void destroy_surface(struct swaylock_surface *surface) { | ||
72 | wl_list_remove(&surface->link); | ||
73 | if (surface->layer_surface != NULL) { | ||
74 | zwlr_layer_surface_v1_destroy(surface->layer_surface); | ||
75 | } | ||
76 | if (surface->surface != NULL) { | ||
77 | wl_surface_destroy(surface->surface); | ||
78 | } | ||
79 | destroy_buffer(&surface->buffers[0]); | ||
80 | destroy_buffer(&surface->buffers[1]); | ||
81 | wl_output_destroy(surface->output); | ||
82 | free(surface); | ||
83 | } | ||
84 | |||
85 | static const struct zwlr_layer_surface_v1_listener layer_surface_listener; | ||
86 | |||
87 | static cairo_surface_t *select_image(struct swaylock_state *state, | ||
88 | struct swaylock_surface *surface); | ||
89 | |||
90 | static bool surface_is_opaque(struct swaylock_surface *surface) { | ||
91 | if (surface->image) { | ||
92 | return cairo_surface_get_content(surface->image) == CAIRO_CONTENT_COLOR; | ||
93 | } | ||
94 | return (surface->state->args.colors.background & 0xff) == 0xff; | ||
95 | } | ||
96 | |||
97 | static void create_layer_surface(struct swaylock_surface *surface) { | ||
98 | struct swaylock_state *state = surface->state; | ||
99 | |||
100 | surface->image = select_image(state, surface); | ||
101 | |||
102 | surface->surface = wl_compositor_create_surface(state->compositor); | ||
103 | assert(surface->surface); | ||
104 | |||
105 | surface->layer_surface = zwlr_layer_shell_v1_get_layer_surface( | ||
106 | state->layer_shell, surface->surface, surface->output, | ||
107 | ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "lockscreen"); | ||
108 | assert(surface->layer_surface); | ||
109 | |||
110 | zwlr_layer_surface_v1_set_size(surface->layer_surface, 0, 0); | ||
111 | zwlr_layer_surface_v1_set_anchor(surface->layer_surface, | ||
112 | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | | ||
113 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | | ||
114 | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | | ||
115 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); | ||
116 | zwlr_layer_surface_v1_set_exclusive_zone(surface->layer_surface, -1); | ||
117 | zwlr_layer_surface_v1_set_keyboard_interactivity( | ||
118 | surface->layer_surface, true); | ||
119 | zwlr_layer_surface_v1_add_listener(surface->layer_surface, | ||
120 | &layer_surface_listener, surface); | ||
121 | |||
122 | if (surface_is_opaque(surface) && | ||
123 | surface->state->args.mode != BACKGROUND_MODE_CENTER && | ||
124 | surface->state->args.mode != BACKGROUND_MODE_FIT) { | ||
125 | struct wl_region *region = | ||
126 | wl_compositor_create_region(surface->state->compositor); | ||
127 | wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); | ||
128 | wl_surface_set_opaque_region(surface->surface, region); | ||
129 | wl_region_destroy(region); | ||
130 | } | ||
131 | |||
132 | wl_surface_commit(surface->surface); | ||
133 | } | ||
134 | |||
135 | static void layer_surface_configure(void *data, | ||
136 | struct zwlr_layer_surface_v1 *layer_surface, | ||
137 | uint32_t serial, uint32_t width, uint32_t height) { | ||
138 | struct swaylock_surface *surface = data; | ||
139 | surface->width = width; | ||
140 | surface->height = height; | ||
141 | zwlr_layer_surface_v1_ack_configure(layer_surface, serial); | ||
142 | render_frame(surface); | ||
143 | } | ||
144 | |||
145 | static void layer_surface_closed(void *data, | ||
146 | struct zwlr_layer_surface_v1 *layer_surface) { | ||
147 | struct swaylock_surface *surface = data; | ||
148 | destroy_surface(surface); | ||
149 | } | ||
150 | |||
151 | static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { | ||
152 | .configure = layer_surface_configure, | ||
153 | .closed = layer_surface_closed, | ||
154 | }; | ||
155 | |||
156 | static const struct wl_callback_listener surface_frame_listener; | ||
157 | |||
158 | static void surface_frame_handle_done(void *data, struct wl_callback *callback, | ||
159 | uint32_t time) { | ||
160 | struct swaylock_surface *surface = data; | ||
161 | |||
162 | wl_callback_destroy(callback); | ||
163 | surface->frame_pending = false; | ||
164 | |||
165 | if (surface->dirty) { | ||
166 | // Schedule a frame in case the surface is damaged again | ||
167 | struct wl_callback *callback = wl_surface_frame(surface->surface); | ||
168 | wl_callback_add_listener(callback, &surface_frame_listener, surface); | ||
169 | surface->frame_pending = true; | ||
170 | |||
171 | render_frame(surface); | ||
172 | surface->dirty = false; | ||
173 | } | ||
174 | } | ||
175 | |||
176 | static const struct wl_callback_listener surface_frame_listener = { | ||
177 | .done = surface_frame_handle_done, | ||
178 | }; | ||
179 | |||
180 | void damage_surface(struct swaylock_surface *surface) { | ||
181 | surface->dirty = true; | ||
182 | if (surface->frame_pending) { | ||
183 | return; | ||
184 | } | ||
185 | |||
186 | struct wl_callback *callback = wl_surface_frame(surface->surface); | ||
187 | wl_callback_add_listener(callback, &surface_frame_listener, surface); | ||
188 | surface->frame_pending = true; | ||
189 | wl_surface_commit(surface->surface); | ||
190 | } | ||
191 | |||
192 | void damage_state(struct swaylock_state *state) { | ||
193 | struct swaylock_surface *surface; | ||
194 | wl_list_for_each(surface, &state->surfaces, link) { | ||
195 | damage_surface(surface); | ||
196 | } | ||
197 | } | ||
198 | |||
199 | static void handle_wl_output_geometry(void *data, struct wl_output *wl_output, | ||
200 | int32_t x, int32_t y, int32_t width_mm, int32_t height_mm, | ||
201 | int32_t subpixel, const char *make, const char *model, | ||
202 | int32_t transform) { | ||
203 | struct swaylock_surface *surface = data; | ||
204 | surface->subpixel = subpixel; | ||
205 | if (surface->state->run_display) { | ||
206 | damage_surface(surface); | ||
207 | } | ||
208 | } | ||
209 | |||
210 | static void handle_wl_output_mode(void *data, struct wl_output *output, | ||
211 | uint32_t flags, int32_t width, int32_t height, int32_t refresh) { | ||
212 | // Who cares | ||
213 | } | ||
214 | |||
215 | static void handle_wl_output_done(void *data, struct wl_output *output) { | ||
216 | // Who cares | ||
217 | } | ||
218 | |||
219 | static void handle_wl_output_scale(void *data, struct wl_output *output, | ||
220 | int32_t factor) { | ||
221 | struct swaylock_surface *surface = data; | ||
222 | surface->scale = factor; | ||
223 | if (surface->state->run_display) { | ||
224 | damage_surface(surface); | ||
225 | } | ||
226 | } | ||
227 | |||
228 | struct wl_output_listener _wl_output_listener = { | ||
229 | .geometry = handle_wl_output_geometry, | ||
230 | .mode = handle_wl_output_mode, | ||
231 | .done = handle_wl_output_done, | ||
232 | .scale = handle_wl_output_scale, | ||
233 | }; | ||
234 | |||
235 | static void handle_xdg_output_logical_size(void *data, struct zxdg_output_v1 *output, | ||
236 | int width, int height) { | ||
237 | // Who cares | ||
238 | } | ||
239 | |||
240 | static void handle_xdg_output_logical_position(void *data, | ||
241 | struct zxdg_output_v1 *output, int x, int y) { | ||
242 | // Who cares | ||
243 | } | ||
244 | |||
245 | static void handle_xdg_output_name(void *data, struct zxdg_output_v1 *output, | ||
246 | const char *name) { | ||
247 | wlr_log(WLR_DEBUG, "output name is %s", name); | ||
248 | struct swaylock_surface *surface = data; | ||
249 | surface->xdg_output = output; | ||
250 | surface->output_name = strdup(name); | ||
251 | } | ||
252 | |||
253 | static void handle_xdg_output_description(void *data, struct zxdg_output_v1 *output, | ||
254 | const char *description) { | ||
255 | // Who cares | ||
256 | } | ||
257 | |||
258 | static void handle_xdg_output_done(void *data, struct zxdg_output_v1 *output) { | ||
259 | // Who cares | ||
260 | } | ||
261 | |||
262 | struct zxdg_output_v1_listener _xdg_output_listener = { | ||
263 | .logical_position = handle_xdg_output_logical_position, | ||
264 | .logical_size = handle_xdg_output_logical_size, | ||
265 | .done = handle_xdg_output_done, | ||
266 | .name = handle_xdg_output_name, | ||
267 | .description = handle_xdg_output_description, | ||
268 | }; | ||
269 | |||
270 | static void handle_global(void *data, struct wl_registry *registry, | ||
271 | uint32_t name, const char *interface, uint32_t version) { | ||
272 | struct swaylock_state *state = data; | ||
273 | if (strcmp(interface, wl_compositor_interface.name) == 0) { | ||
274 | state->compositor = wl_registry_bind(registry, name, | ||
275 | &wl_compositor_interface, 3); | ||
276 | } else if (strcmp(interface, wl_shm_interface.name) == 0) { | ||
277 | state->shm = wl_registry_bind(registry, name, | ||
278 | &wl_shm_interface, 1); | ||
279 | } else if (strcmp(interface, wl_seat_interface.name) == 0) { | ||
280 | struct wl_seat *seat = wl_registry_bind( | ||
281 | registry, name, &wl_seat_interface, 3); | ||
282 | struct swaylock_seat *swaylock_seat = | ||
283 | calloc(1, sizeof(struct swaylock_seat)); | ||
284 | swaylock_seat->state = state; | ||
285 | wl_seat_add_listener(seat, &seat_listener, swaylock_seat); | ||
286 | } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { | ||
287 | state->layer_shell = wl_registry_bind( | ||
288 | registry, name, &zwlr_layer_shell_v1_interface, 1); | ||
289 | } else if (strcmp(interface, zwlr_input_inhibit_manager_v1_interface.name) == 0) { | ||
290 | state->input_inhibit_manager = wl_registry_bind( | ||
291 | registry, name, &zwlr_input_inhibit_manager_v1_interface, 1); | ||
292 | } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { | ||
293 | state->zxdg_output_manager = wl_registry_bind( | ||
294 | registry, name, &zxdg_output_manager_v1_interface, 2); | ||
295 | } else if (strcmp(interface, wl_output_interface.name) == 0) { | ||
296 | struct swaylock_surface *surface = | ||
297 | calloc(1, sizeof(struct swaylock_surface)); | ||
298 | surface->state = state; | ||
299 | surface->output = wl_registry_bind(registry, name, | ||
300 | &wl_output_interface, 3); | ||
301 | surface->output_global_name = name; | ||
302 | wl_output_add_listener(surface->output, &_wl_output_listener, surface); | ||
303 | wl_list_insert(&state->surfaces, &surface->link); | ||
304 | |||
305 | if (state->run_display) { | ||
306 | create_layer_surface(surface); | ||
307 | wl_display_roundtrip(state->display); | ||
308 | } | ||
309 | } | ||
310 | } | ||
311 | |||
312 | static void handle_global_remove(void *data, struct wl_registry *registry, | ||
313 | uint32_t name) { | ||
314 | struct swaylock_state *state = data; | ||
315 | struct swaylock_surface *surface; | ||
316 | wl_list_for_each(surface, &state->surfaces, link) { | ||
317 | if (surface->output_global_name == name) { | ||
318 | destroy_surface(surface); | ||
319 | break; | ||
320 | } | ||
321 | } | ||
322 | } | ||
323 | |||
324 | static const struct wl_registry_listener registry_listener = { | ||
325 | .global = handle_global, | ||
326 | .global_remove = handle_global_remove, | ||
327 | }; | ||
328 | |||
329 | static cairo_surface_t *select_image(struct swaylock_state *state, | ||
330 | struct swaylock_surface *surface) { | ||
331 | struct swaylock_image *image; | ||
332 | cairo_surface_t *default_image = NULL; | ||
333 | wl_list_for_each(image, &state->images, link) { | ||
334 | if (lenient_strcmp(image->output_name, surface->output_name) == 0) { | ||
335 | return image->cairo_surface; | ||
336 | } else if (!image->output_name) { | ||
337 | default_image = image->cairo_surface; | ||
338 | } | ||
339 | } | ||
340 | return default_image; | ||
341 | } | ||
342 | |||
343 | static void load_image(char *arg, struct swaylock_state *state) { | ||
344 | // [<output>:]<path> | ||
345 | struct swaylock_image *image = calloc(1, sizeof(struct swaylock_image)); | ||
346 | char *separator = strchr(arg, ':'); | ||
347 | if (separator) { | ||
348 | *separator = '\0'; | ||
349 | image->output_name = strdup(arg); | ||
350 | image->path = strdup(separator + 1); | ||
351 | } else { | ||
352 | image->output_name = NULL; | ||
353 | image->path = strdup(arg); | ||
354 | } | ||
355 | |||
356 | struct swaylock_image *iter_image, *temp; | ||
357 | wl_list_for_each_safe(iter_image, temp, &state->images, link) { | ||
358 | if (lenient_strcmp(iter_image->output_name, image->output_name) == 0) { | ||
359 | if (image->output_name) { | ||
360 | wlr_log(WLR_DEBUG, | ||
361 | "Replacing image defined for output %s with %s", | ||
362 | image->output_name, image->path); | ||
363 | } else { | ||
364 | wlr_log(WLR_DEBUG, "Replacing default image with %s", | ||
365 | image->path); | ||
366 | } | ||
367 | wl_list_remove(&iter_image->link); | ||
368 | free(iter_image->cairo_surface); | ||
369 | free(iter_image->output_name); | ||
370 | free(iter_image->path); | ||
371 | free(iter_image); | ||
372 | break; | ||
373 | } | ||
374 | } | ||
375 | |||
376 | // Bash doesn't replace the ~ with $HOME if the output name is supplied | ||
377 | wordexp_t p; | ||
378 | if (wordexp(image->path, &p, 0) == 0) { | ||
379 | free(image->path); | ||
380 | image->path = strdup(p.we_wordv[0]); | ||
381 | wordfree(&p); | ||
382 | } | ||
383 | |||
384 | // Load the actual image | ||
385 | image->cairo_surface = load_background_image(image->path); | ||
386 | if (!image->cairo_surface) { | ||
387 | free(image); | ||
388 | return; | ||
389 | } | ||
390 | wl_list_insert(&state->images, &image->link); | ||
391 | wlr_log(WLR_DEBUG, "Loaded image %s for output %s", | ||
392 | image->path, image->output_name ? image->output_name : "*"); | ||
393 | } | ||
394 | |||
395 | static void set_default_colors(struct swaylock_colors *colors) { | ||
396 | colors->background = 0xFFFFFFFF; | ||
397 | colors->bs_highlight = 0xDB3300FF; | ||
398 | colors->key_highlight = 0x33DB00FF; | ||
399 | colors->caps_lock_bs_highlight = 0xDB3300FF; | ||
400 | colors->caps_lock_key_highlight = 0x33DB00FF; | ||
401 | colors->separator = 0x000000FF; | ||
402 | colors->inside = (struct swaylock_colorset){ | ||
403 | .input = 0x000000C0, | ||
404 | .cleared = 0xE5A445C0, | ||
405 | .caps_lock = 0x000000C0, | ||
406 | .verifying = 0x0072FFC0, | ||
407 | .wrong = 0xFA0000C0, | ||
408 | }; | ||
409 | colors->line = (struct swaylock_colorset){ | ||
410 | .input = 0x000000FF, | ||
411 | .cleared = 0x000000FF, | ||
412 | .caps_lock = 0x000000FF, | ||
413 | .verifying = 0x000000FF, | ||
414 | .wrong = 0x000000FF, | ||
415 | }; | ||
416 | colors->ring = (struct swaylock_colorset){ | ||
417 | .input = 0x337D00FF, | ||
418 | .cleared = 0xE5A445FF, | ||
419 | .caps_lock = 0xE5A445FF, | ||
420 | .verifying = 0x3300FFFF, | ||
421 | .wrong = 0x7D3300FF, | ||
422 | }; | ||
423 | colors->text = (struct swaylock_colorset){ | ||
424 | .input = 0xE5A445FF, | ||
425 | .cleared = 0x000000FF, | ||
426 | .caps_lock = 0xE5A445FF, | ||
427 | .verifying = 0x000000FF, | ||
428 | .wrong = 0x000000FF, | ||
429 | }; | ||
430 | } | ||
431 | |||
432 | enum line_mode { | ||
433 | LM_LINE, | ||
434 | LM_INSIDE, | ||
435 | LM_RING, | ||
436 | }; | ||
437 | |||
438 | static int parse_options(int argc, char **argv, struct swaylock_state *state, | ||
439 | enum line_mode *line_mode, char **config_path) { | ||
440 | enum long_option_codes { | ||
441 | LO_BS_HL_COLOR = 256, | ||
442 | LO_CAPS_LOCK_BS_HL_COLOR, | ||
443 | LO_CAPS_LOCK_KEY_HL_COLOR, | ||
444 | LO_FONT, | ||
445 | LO_IND_RADIUS, | ||
446 | LO_IND_THICKNESS, | ||
447 | LO_INSIDE_COLOR, | ||
448 | LO_INSIDE_CLEAR_COLOR, | ||
449 | LO_INSIDE_CAPS_LOCK_COLOR, | ||
450 | LO_INSIDE_VER_COLOR, | ||
451 | LO_INSIDE_WRONG_COLOR, | ||
452 | LO_KEY_HL_COLOR, | ||
453 | LO_LINE_COLOR, | ||
454 | LO_LINE_CLEAR_COLOR, | ||
455 | LO_LINE_CAPS_LOCK_COLOR, | ||
456 | LO_LINE_VER_COLOR, | ||
457 | LO_LINE_WRONG_COLOR, | ||
458 | LO_RING_COLOR, | ||
459 | LO_RING_CLEAR_COLOR, | ||
460 | LO_RING_CAPS_LOCK_COLOR, | ||
461 | LO_RING_VER_COLOR, | ||
462 | LO_RING_WRONG_COLOR, | ||
463 | LO_SEP_COLOR, | ||
464 | LO_TEXT_COLOR, | ||
465 | LO_TEXT_CLEAR_COLOR, | ||
466 | LO_TEXT_CAPS_LOCK_COLOR, | ||
467 | LO_TEXT_VER_COLOR, | ||
468 | LO_TEXT_WRONG_COLOR, | ||
469 | }; | ||
470 | |||
471 | static struct option long_options[] = { | ||
472 | {"config", required_argument, NULL, 'C'}, | ||
473 | {"color", required_argument, NULL, 'c'}, | ||
474 | {"ignore-empty-password", no_argument, NULL, 'e'}, | ||
475 | {"daemonize", no_argument, NULL, 'f'}, | ||
476 | {"help", no_argument, NULL, 'h'}, | ||
477 | {"image", required_argument, NULL, 'i'}, | ||
478 | {"disable-caps-lock-text", no_argument, NULL, 'L'}, | ||
479 | {"indicator-caps-lock", no_argument, NULL, 'l'}, | ||
480 | {"line-uses-inside", no_argument, NULL, 'n'}, | ||
481 | {"socket", required_argument, NULL, 'p'}, | ||
482 | {"line-uses-ring", no_argument, NULL, 'r'}, | ||
483 | {"scaling", required_argument, NULL, 's'}, | ||
484 | {"tiling", no_argument, NULL, 't'}, | ||
485 | {"no-unlock-indicator", no_argument, NULL, 'u'}, | ||
486 | {"version", no_argument, NULL, 'v'}, | ||
487 | {"bs-hl-color", required_argument, NULL, LO_BS_HL_COLOR}, | ||
488 | {"caps-lock-bs-hl-color", required_argument, NULL, LO_CAPS_LOCK_BS_HL_COLOR}, | ||
489 | {"caps-lock-key-hl-color", required_argument, NULL, LO_CAPS_LOCK_KEY_HL_COLOR}, | ||
490 | {"font", required_argument, NULL, LO_FONT}, | ||
491 | {"indicator-radius", required_argument, NULL, LO_IND_RADIUS}, | ||
492 | {"indicator-thickness", required_argument, NULL, LO_IND_THICKNESS}, | ||
493 | {"inside-color", required_argument, NULL, LO_INSIDE_COLOR}, | ||
494 | {"inside-clear-color", required_argument, NULL, LO_INSIDE_CLEAR_COLOR}, | ||
495 | {"inside-caps-lock-color", required_argument, NULL, LO_INSIDE_CAPS_LOCK_COLOR}, | ||
496 | {"inside-ver-color", required_argument, NULL, LO_INSIDE_VER_COLOR}, | ||
497 | {"inside-wrong-color", required_argument, NULL, LO_INSIDE_WRONG_COLOR}, | ||
498 | {"key-hl-color", required_argument, NULL, LO_KEY_HL_COLOR}, | ||
499 | {"line-color", required_argument, NULL, LO_LINE_COLOR}, | ||
500 | {"line-clear-color", required_argument, NULL, LO_LINE_CLEAR_COLOR}, | ||
501 | {"line-caps-lock-color", required_argument, NULL, LO_LINE_CAPS_LOCK_COLOR}, | ||
502 | {"line-ver-color", required_argument, NULL, LO_LINE_VER_COLOR}, | ||
503 | {"line-wrong-color", required_argument, NULL, LO_LINE_WRONG_COLOR}, | ||
504 | {"ring-color", required_argument, NULL, LO_RING_COLOR}, | ||
505 | {"ring-clear-color", required_argument, NULL, LO_RING_CLEAR_COLOR}, | ||
506 | {"ring-caps-lock-color", required_argument, NULL, LO_RING_CAPS_LOCK_COLOR}, | ||
507 | {"ring-ver-color", required_argument, NULL, LO_RING_VER_COLOR}, | ||
508 | {"ring-wrong-color", required_argument, NULL, LO_RING_WRONG_COLOR}, | ||
509 | {"separator-color", required_argument, NULL, LO_SEP_COLOR}, | ||
510 | {"text-color", required_argument, NULL, LO_TEXT_COLOR}, | ||
511 | {"text-clear-color", required_argument, NULL, LO_TEXT_CLEAR_COLOR}, | ||
512 | {"text-caps-lock-color", required_argument, NULL, LO_TEXT_CAPS_LOCK_COLOR}, | ||
513 | {"text-ver-color", required_argument, NULL, LO_TEXT_VER_COLOR}, | ||
514 | {"text-wrong-color", required_argument, NULL, LO_TEXT_WRONG_COLOR}, | ||
515 | {0, 0, 0, 0} | ||
516 | }; | ||
517 | |||
518 | const char usage[] = | ||
519 | "Usage: swaylock [options...]\n" | ||
520 | "\n" | ||
521 | " -C, --config <config_file> " | ||
522 | "Path to the config file.\n" | ||
523 | " -c, --color <color> " | ||
524 | "Turn the screen into the given color instead of white.\n" | ||
525 | " -e, --ignore-empty-password " | ||
526 | "When an empty password is provided, do not validate it.\n" | ||
527 | " -f, --daemonize " | ||
528 | "Detach from the controlling terminal after locking.\n" | ||
529 | " -h, --help " | ||
530 | "Show help message and quit.\n" | ||
531 | " -i, --image [<output>:]<path> " | ||
532 | "Display the given image.\n" | ||
533 | " -L, --disable-caps-lock-text " | ||
534 | "Disable the Caps Lock text.\n" | ||
535 | " -l, --indicator-caps-lock " | ||
536 | "Show the current Caps Lock state also on the indicator.\n" | ||
537 | " -s, --scaling <mode> " | ||
538 | "Scaling mode: stretch, fill, fit, center, tile.\n" | ||
539 | " -t, --tiling " | ||
540 | "Same as --scaling=tile.\n" | ||
541 | " -u, --no-unlock-indicator " | ||
542 | "Disable the unlock indicator.\n" | ||
543 | " -v, --version " | ||
544 | "Show the version number and quit.\n" | ||
545 | " --bs-hl-color <color> " | ||
546 | "Sets the color of backspace highlight segments.\n" | ||
547 | " --caps-lock-bs-hl-color <color> " | ||
548 | "Sets the color of backspace highlight segments when Caps Lock " | ||
549 | "is active.\n" | ||
550 | " --caps-lock-key-hl-color <color> " | ||
551 | "Sets the color of the key press highlight segments when " | ||
552 | "Caps Lock is active.\n" | ||
553 | " --font <font> " | ||
554 | "Sets the font of the text.\n" | ||
555 | " --indicator-radius <radius> " | ||
556 | "Sets the indicator radius.\n" | ||
557 | " --indicator-thickness <thick> " | ||
558 | "Sets the indicator thickness.\n" | ||
559 | " --inside-color <color> " | ||
560 | "Sets the color of the inside of the indicator.\n" | ||
561 | " --inside-clear-color <color> " | ||
562 | "Sets the color of the inside of the indicator when cleared.\n" | ||
563 | " --inside-caps-lock-color <color> " | ||
564 | "Sets the color of the inside of the indicator when Caps Lock " | ||
565 | "is active.\n" | ||
566 | " --inside-ver-color <color> " | ||
567 | "Sets the color of the inside of the indicator when verifying.\n" | ||
568 | " --inside-wrong-color <color> " | ||
569 | "Sets the color of the inside of the indicator when invalid.\n" | ||
570 | " --key-hl-color <color> " | ||
571 | "Sets the color of the key press highlight segments.\n" | ||
572 | " --line-color <color> " | ||
573 | "Sets the color of the line between the inside and ring.\n" | ||
574 | " --line-clear-color <color> " | ||
575 | "Sets the color of the line between the inside and ring when " | ||
576 | "cleared.\n" | ||
577 | " --line-caps-lock-color <color> " | ||
578 | "Sets the color of the line between the inside and ring when " | ||
579 | "Caps Lock is active.\n" | ||
580 | " --line-ver-color <color> " | ||
581 | "Sets the color of the line between the inside and ring when " | ||
582 | "verifying.\n" | ||
583 | " --line-wrong-color <color> " | ||
584 | "Sets the color of the line between the inside and ring when " | ||
585 | "invalid.\n" | ||
586 | " -n, --line-uses-inside " | ||
587 | "Use the inside color for the line between the inside and ring.\n" | ||
588 | " -r, --line-uses-ring " | ||
589 | "Use the ring color for the line between the inside and ring.\n" | ||
590 | " --ring-color <color> " | ||
591 | "Sets the color of the ring of the indicator.\n" | ||
592 | " --ring-clear-color <color> " | ||
593 | "Sets the color of the ring of the indicator when cleared.\n" | ||
594 | " --ring-caps-lock-color <color> " | ||
595 | "Sets the color of the ring of the indicator when Caps Lock " | ||
596 | "is active.\n" | ||
597 | " --ring-ver-color <color> " | ||
598 | "Sets the color of the ring of the indicator when verifying.\n" | ||
599 | " --ring-wrong-color <color> " | ||
600 | "Sets the color of the ring of the indicator when invalid.\n" | ||
601 | " --separator-color <color> " | ||
602 | "Sets the color of the lines that separate highlight segments.\n" | ||
603 | " --text-color <color> " | ||
604 | "Sets the color of the text.\n" | ||
605 | " --text-clear-color <color> " | ||
606 | "Sets the color of the text when cleared.\n" | ||
607 | " --text-caps-lock-color <color> " | ||
608 | "Sets the color of the text when Caps Lock is active.\n" | ||
609 | " --text-ver-color <color> " | ||
610 | "Sets the color of the text when verifying.\n" | ||
611 | " --text-wrong-color <color> " | ||
612 | "Sets the color of the text when invalid.\n" | ||
613 | "\n" | ||
614 | "All <color> options are of the form <rrggbb[aa]>.\n"; | ||
615 | |||
616 | int c; | ||
617 | optind = 1; | ||
618 | while (1) { | ||
619 | int opt_idx = 0; | ||
620 | c = getopt_long(argc, argv, "c:efhi:Llnrs:tuvC:", long_options, &opt_idx); | ||
621 | if (c == -1) { | ||
622 | break; | ||
623 | } | ||
624 | switch (c) { | ||
625 | case 'C': | ||
626 | if (config_path) { | ||
627 | *config_path = strdup(optarg); | ||
628 | } | ||
629 | break; | ||
630 | case 'c': | ||
631 | if (state) { | ||
632 | state->args.colors.background = parse_color(optarg); | ||
633 | state->args.mode = BACKGROUND_MODE_SOLID_COLOR; | ||
634 | } | ||
635 | break; | ||
636 | case 'e': | ||
637 | if (state) { | ||
638 | state->args.ignore_empty = true; | ||
639 | } | ||
640 | break; | ||
641 | case 'f': | ||
642 | if (state) { | ||
643 | state->args.daemonize = true; | ||
644 | } | ||
645 | break; | ||
646 | case 'i': | ||
647 | if (state) { | ||
648 | load_image(optarg, state); | ||
649 | } | ||
650 | break; | ||
651 | case 'L': | ||
652 | if (state) { | ||
653 | state->args.show_caps_lock_text = false; | ||
654 | } | ||
655 | break; | ||
656 | case 'l': | ||
657 | if (state) { | ||
658 | state->args.show_caps_lock_indicator = true; | ||
659 | } | ||
660 | break; | ||
661 | case 'n': | ||
662 | if (line_mode) { | ||
663 | *line_mode = LM_INSIDE; | ||
664 | } | ||
665 | break; | ||
666 | case 'r': | ||
667 | if (line_mode) { | ||
668 | *line_mode = LM_RING; | ||
669 | } | ||
670 | break; | ||
671 | case 's': | ||
672 | if (state) { | ||
673 | state->args.mode = parse_background_mode(optarg); | ||
674 | if (state->args.mode == BACKGROUND_MODE_INVALID) { | ||
675 | return 1; | ||
676 | } | ||
677 | } | ||
678 | break; | ||
679 | case 't': | ||
680 | if (state) { | ||
681 | state->args.mode = BACKGROUND_MODE_TILE; | ||
682 | } | ||
683 | break; | ||
684 | case 'u': | ||
685 | if (state) { | ||
686 | state->args.show_indicator = false; | ||
687 | } | ||
688 | break; | ||
689 | case 'v': | ||
690 | fprintf(stdout, "swaylock version " SWAY_VERSION "\n"); | ||
691 | exit(EXIT_SUCCESS); | ||
692 | break; | ||
693 | case LO_BS_HL_COLOR: | ||
694 | if (state) { | ||
695 | state->args.colors.bs_highlight = parse_color(optarg); | ||
696 | } | ||
697 | break; | ||
698 | case LO_CAPS_LOCK_BS_HL_COLOR: | ||
699 | if (state) { | ||
700 | state->args.colors.caps_lock_bs_highlight = parse_color(optarg); | ||
701 | } | ||
702 | break; | ||
703 | case LO_CAPS_LOCK_KEY_HL_COLOR: | ||
704 | if (state) { | ||
705 | state->args.colors.caps_lock_key_highlight = parse_color(optarg); | ||
706 | } | ||
707 | break; | ||
708 | case LO_FONT: | ||
709 | if (state) { | ||
710 | free(state->args.font); | ||
711 | state->args.font = strdup(optarg); | ||
712 | } | ||
713 | break; | ||
714 | case LO_IND_RADIUS: | ||
715 | if (state) { | ||
716 | state->args.radius = strtol(optarg, NULL, 0); | ||
717 | } | ||
718 | break; | ||
719 | case LO_IND_THICKNESS: | ||
720 | if (state) { | ||
721 | state->args.thickness = strtol(optarg, NULL, 0); | ||
722 | } | ||
723 | break; | ||
724 | case LO_INSIDE_COLOR: | ||
725 | if (state) { | ||
726 | state->args.colors.inside.input = parse_color(optarg); | ||
727 | } | ||
728 | break; | ||
729 | case LO_INSIDE_CLEAR_COLOR: | ||
730 | if (state) { | ||
731 | state->args.colors.inside.cleared = parse_color(optarg); | ||
732 | } | ||
733 | break; | ||
734 | case LO_INSIDE_CAPS_LOCK_COLOR: | ||
735 | if (state) { | ||
736 | state->args.colors.inside.caps_lock = parse_color(optarg); | ||
737 | } | ||
738 | break; | ||
739 | case LO_INSIDE_VER_COLOR: | ||
740 | if (state) { | ||
741 | state->args.colors.inside.verifying = parse_color(optarg); | ||
742 | } | ||
743 | break; | ||
744 | case LO_INSIDE_WRONG_COLOR: | ||
745 | if (state) { | ||
746 | state->args.colors.inside.wrong = parse_color(optarg); | ||
747 | } | ||
748 | break; | ||
749 | case LO_KEY_HL_COLOR: | ||
750 | if (state) { | ||
751 | state->args.colors.key_highlight = parse_color(optarg); | ||
752 | } | ||
753 | break; | ||
754 | case LO_LINE_COLOR: | ||
755 | if (state) { | ||
756 | state->args.colors.line.input = parse_color(optarg); | ||
757 | } | ||
758 | break; | ||
759 | case LO_LINE_CLEAR_COLOR: | ||
760 | if (state) { | ||
761 | state->args.colors.line.cleared = parse_color(optarg); | ||
762 | } | ||
763 | break; | ||
764 | case LO_LINE_CAPS_LOCK_COLOR: | ||
765 | if (state) { | ||
766 | state->args.colors.line.caps_lock = parse_color(optarg); | ||
767 | } | ||
768 | break; | ||
769 | case LO_LINE_VER_COLOR: | ||
770 | if (state) { | ||
771 | state->args.colors.line.verifying = parse_color(optarg); | ||
772 | } | ||
773 | break; | ||
774 | case LO_LINE_WRONG_COLOR: | ||
775 | if (state) { | ||
776 | state->args.colors.line.wrong = parse_color(optarg); | ||
777 | } | ||
778 | break; | ||
779 | case LO_RING_COLOR: | ||
780 | if (state) { | ||
781 | state->args.colors.ring.input = parse_color(optarg); | ||
782 | } | ||
783 | break; | ||
784 | case LO_RING_CLEAR_COLOR: | ||
785 | if (state) { | ||
786 | state->args.colors.ring.cleared = parse_color(optarg); | ||
787 | } | ||
788 | break; | ||
789 | case LO_RING_CAPS_LOCK_COLOR: | ||
790 | if (state) { | ||
791 | state->args.colors.ring.caps_lock = parse_color(optarg); | ||
792 | } | ||
793 | break; | ||
794 | case LO_RING_VER_COLOR: | ||
795 | if (state) { | ||
796 | state->args.colors.ring.verifying = parse_color(optarg); | ||
797 | } | ||
798 | break; | ||
799 | case LO_RING_WRONG_COLOR: | ||
800 | if (state) { | ||
801 | state->args.colors.ring.wrong = parse_color(optarg); | ||
802 | } | ||
803 | break; | ||
804 | case LO_SEP_COLOR: | ||
805 | if (state) { | ||
806 | state->args.colors.separator = parse_color(optarg); | ||
807 | } | ||
808 | break; | ||
809 | case LO_TEXT_COLOR: | ||
810 | if (state) { | ||
811 | state->args.colors.text.input = parse_color(optarg); | ||
812 | } | ||
813 | break; | ||
814 | case LO_TEXT_CLEAR_COLOR: | ||
815 | if (state) { | ||
816 | state->args.colors.text.cleared = parse_color(optarg); | ||
817 | } | ||
818 | break; | ||
819 | case LO_TEXT_CAPS_LOCK_COLOR: | ||
820 | if (state) { | ||
821 | state->args.colors.text.caps_lock = parse_color(optarg); | ||
822 | } | ||
823 | break; | ||
824 | case LO_TEXT_VER_COLOR: | ||
825 | if (state) { | ||
826 | state->args.colors.text.verifying = parse_color(optarg); | ||
827 | } | ||
828 | break; | ||
829 | case LO_TEXT_WRONG_COLOR: | ||
830 | if (state) { | ||
831 | state->args.colors.text.wrong = parse_color(optarg); | ||
832 | } | ||
833 | break; | ||
834 | default: | ||
835 | fprintf(stderr, "%s", usage); | ||
836 | return 1; | ||
837 | } | ||
838 | } | ||
839 | |||
840 | return 0; | ||
841 | } | ||
842 | |||
843 | static bool file_exists(const char *path) { | ||
844 | return path && access(path, R_OK) != -1; | ||
845 | } | ||
846 | |||
847 | static char *get_config_path(void) { | ||
848 | static const char *config_paths[] = { | ||
849 | "$HOME/.swaylock/config", | ||
850 | "$XDG_CONFIG_HOME/swaylock/config", | ||
851 | SYSCONFDIR "/swaylock/config", | ||
852 | }; | ||
853 | |||
854 | if (!getenv("XDG_CONFIG_HOME")) { | ||
855 | char *home = getenv("HOME"); | ||
856 | char *config_home = malloc(strlen(home) + strlen("/.config") + 1); | ||
857 | if (!config_home) { | ||
858 | wlr_log(WLR_ERROR, "Unable to allocate $HOME/.config"); | ||
859 | } else { | ||
860 | strcpy(config_home, home); | ||
861 | strcat(config_home, "/.config"); | ||
862 | setenv("XDG_CONFIG_HOME", config_home, 1); | ||
863 | wlr_log(WLR_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home); | ||
864 | free(config_home); | ||
865 | } | ||
866 | } | ||
867 | |||
868 | wordexp_t p; | ||
869 | char *path; | ||
870 | for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) { | ||
871 | if (wordexp(config_paths[i], &p, 0) == 0) { | ||
872 | path = strdup(p.we_wordv[0]); | ||
873 | wordfree(&p); | ||
874 | if (file_exists(path)) { | ||
875 | return path; | ||
876 | } | ||
877 | free(path); | ||
878 | } | ||
879 | } | ||
880 | |||
881 | return NULL; | ||
882 | } | ||
883 | |||
884 | static int load_config(char *path, struct swaylock_state *state, | ||
885 | enum line_mode *line_mode) { | ||
886 | FILE *config = fopen(path, "r"); | ||
887 | if (!config) { | ||
888 | wlr_log(WLR_ERROR, "Failed to read config. Running without it."); | ||
889 | return 0; | ||
890 | } | ||
891 | char *line = NULL; | ||
892 | size_t line_size = 0; | ||
893 | ssize_t nread; | ||
894 | int line_number = 0; | ||
895 | int result = 0; | ||
896 | while ((nread = getline(&line, &line_size, config)) != -1) { | ||
897 | line_number++; | ||
898 | |||
899 | if (line[nread - 1] == '\n') { | ||
900 | line[--nread] = '\0'; | ||
901 | } | ||
902 | |||
903 | if (!*line || line[0] == '#') { | ||
904 | continue; | ||
905 | } | ||
906 | |||
907 | wlr_log(WLR_DEBUG, "Config Line #%d: %s", line_number, line); | ||
908 | char flag[nread + 3]; | ||
909 | sprintf(flag, "--%s", line); | ||
910 | char *argv[] = {"swaylock", flag}; | ||
911 | result = parse_options(2, argv, state, line_mode, NULL); | ||
912 | if (result != 0) { | ||
913 | break; | ||
914 | } | ||
915 | } | ||
916 | free(line); | ||
917 | fclose(config); | ||
918 | return 0; | ||
919 | } | ||
920 | |||
921 | static struct swaylock_state state; | ||
922 | |||
923 | static void display_in(int fd, short mask, void *data) { | ||
924 | if (wl_display_dispatch(state.display) == -1) { | ||
925 | state.run_display = false; | ||
926 | } | ||
927 | } | ||
928 | |||
929 | int main(int argc, char **argv) { | ||
930 | wlr_log_init(WLR_DEBUG, NULL); | ||
931 | initialize_pw_backend(); | ||
932 | |||
933 | enum line_mode line_mode = LM_LINE; | ||
934 | state.args = (struct swaylock_args){ | ||
935 | .mode = BACKGROUND_MODE_FILL, | ||
936 | .font = strdup("sans-serif"), | ||
937 | .radius = 50, | ||
938 | .thickness = 10, | ||
939 | .ignore_empty = false, | ||
940 | .show_indicator = true, | ||
941 | .show_caps_lock_indicator = false, | ||
942 | .show_caps_lock_text = true | ||
943 | }; | ||
944 | wl_list_init(&state.images); | ||
945 | set_default_colors(&state.args.colors); | ||
946 | |||
947 | char *config_path = NULL; | ||
948 | int result = parse_options(argc, argv, NULL, NULL, &config_path); | ||
949 | if (result != 0) { | ||
950 | free(config_path); | ||
951 | return result; | ||
952 | } | ||
953 | if (!config_path) { | ||
954 | config_path = get_config_path(); | ||
955 | } | ||
956 | |||
957 | if (config_path) { | ||
958 | wlr_log(WLR_DEBUG, "Found config at %s", config_path); | ||
959 | int config_status = load_config(config_path, &state, &line_mode); | ||
960 | free(config_path); | ||
961 | if (config_status != 0) { | ||
962 | return config_status; | ||
963 | } | ||
964 | } | ||
965 | |||
966 | if (argc > 1) { | ||
967 | wlr_log(WLR_DEBUG, "Parsing CLI Args"); | ||
968 | int result = parse_options(argc, argv, &state, &line_mode, NULL); | ||
969 | if (result != 0) { | ||
970 | return result; | ||
971 | } | ||
972 | } | ||
973 | |||
974 | if (line_mode == LM_INSIDE) { | ||
975 | state.args.colors.line = state.args.colors.inside; | ||
976 | } else if (line_mode == LM_RING) { | ||
977 | state.args.colors.line = state.args.colors.ring; | ||
978 | } | ||
979 | |||
980 | #ifdef __linux__ | ||
981 | // Most non-linux platforms require root to mlock() | ||
982 | if (mlock(state.password.buffer, sizeof(state.password.buffer)) != 0) { | ||
983 | sway_abort("Unable to mlock() password memory."); | ||
984 | } | ||
985 | #endif | ||
986 | |||
987 | wl_list_init(&state.surfaces); | ||
988 | state.xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); | ||
989 | state.display = wl_display_connect(NULL); | ||
990 | if (!state.display) { | ||
991 | sway_abort("Unable to connect to the compositor. " | ||
992 | "If your compositor is running, check or set the " | ||
993 | "WAYLAND_DISPLAY environment variable."); | ||
994 | } | ||
995 | |||
996 | struct wl_registry *registry = wl_display_get_registry(state.display); | ||
997 | wl_registry_add_listener(registry, ®istry_listener, &state); | ||
998 | wl_display_roundtrip(state.display); | ||
999 | assert(state.compositor && state.layer_shell && state.shm); | ||
1000 | if (!state.input_inhibit_manager) { | ||
1001 | wlr_log(WLR_ERROR, "Compositor does not support the input inhibitor " | ||
1002 | "protocol, refusing to run insecurely"); | ||
1003 | return 1; | ||
1004 | } | ||
1005 | |||
1006 | if (wl_list_empty(&state.surfaces)) { | ||
1007 | wlr_log(WLR_DEBUG, "Exiting - no outputs to show on."); | ||
1008 | return 0; | ||
1009 | } | ||
1010 | |||
1011 | zwlr_input_inhibit_manager_v1_get_inhibitor(state.input_inhibit_manager); | ||
1012 | if (wl_display_roundtrip(state.display) == -1) { | ||
1013 | wlr_log(WLR_ERROR, "Exiting - failed to inhibit input:" | ||
1014 | " is another lockscreen already running?"); | ||
1015 | return 2; | ||
1016 | } | ||
1017 | |||
1018 | if (state.zxdg_output_manager) { | ||
1019 | struct swaylock_surface *surface; | ||
1020 | wl_list_for_each(surface, &state.surfaces, link) { | ||
1021 | surface->xdg_output = zxdg_output_manager_v1_get_xdg_output( | ||
1022 | state.zxdg_output_manager, surface->output); | ||
1023 | zxdg_output_v1_add_listener( | ||
1024 | surface->xdg_output, &_xdg_output_listener, surface); | ||
1025 | } | ||
1026 | wl_display_roundtrip(state.display); | ||
1027 | } else { | ||
1028 | wlr_log(WLR_INFO, "Compositor does not support zxdg output manager, " | ||
1029 | "images assigned to named outputs will not work"); | ||
1030 | } | ||
1031 | |||
1032 | struct swaylock_surface *surface; | ||
1033 | wl_list_for_each(surface, &state.surfaces, link) { | ||
1034 | create_layer_surface(surface); | ||
1035 | } | ||
1036 | |||
1037 | if (state.args.daemonize) { | ||
1038 | wl_display_roundtrip(state.display); | ||
1039 | daemonize(); | ||
1040 | } | ||
1041 | |||
1042 | state.eventloop = loop_create(); | ||
1043 | loop_add_fd(state.eventloop, wl_display_get_fd(state.display), POLLIN, | ||
1044 | display_in, NULL); | ||
1045 | |||
1046 | state.run_display = true; | ||
1047 | while (state.run_display) { | ||
1048 | errno = 0; | ||
1049 | if (wl_display_flush(state.display) == -1 && errno != EAGAIN) { | ||
1050 | break; | ||
1051 | } | ||
1052 | loop_poll(state.eventloop); | ||
1053 | } | ||
1054 | |||
1055 | free(state.args.font); | ||
1056 | return 0; | ||
1057 | } | ||