1 /*
2 * LavaLauncher - A simple launcher panel for Wayland
3 *
4 * Copyright (C) 2020 Leon Henrik Plickat
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #define _POSIX_C_SOURCE 200809L
21
22 #include<stdarg.h>
23 #include<stdio.h>
24 #include<stdlib.h>
25 #include<stdbool.h>
26 #include<unistd.h>
27 #include<string.h>
28 #include<errno.h>
29 #include<signal.h>
30 #include<sys/wait.h>
31 #include<linux/input-event-codes.h>
32
33 #include"lavalauncher.h"
34 #include"item.h"
35 #include"seat.h"
36 #include"str.h"
37 #include"bar.h"
38 #include"output.h"
39 #include"types/image_t.h"
40
41 /*******************
42 * *
43 * Item commands *
44 * *
45 *******************/
46 /* We need to fork two times for UNIXy resons. */
item_command_exec_second_fork(struct Lava_bar_instance * instance,const char * cmd)47 static void item_command_exec_second_fork (struct Lava_bar_instance *instance, const char *cmd)
48 {
49 errno = 0;
50 int ret = fork();
51 if ( ret == 0 )
52 {
53 /* Prepare environment variables. */
54 setenvf("LAVALAUNCHER_OUTPUT_NAME", "%s", instance->output->name);
55 setenvf("LAVALAUNCHER_OUTPUT_SCALE", "%d", instance->output->scale);
56
57 /* execl() only returns on error; On success it replaces this process. */
58 execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
59 log_message(0, "ERROR: execl: %s\n", strerror(errno));
60 _exit(EXIT_FAILURE);
61 }
62 else if ( ret < 0 ) /* Yes, fork can fail. */
63 {
64 log_message(0, "ERROR: fork: %s\n", strerror(errno));
65 _exit(EXIT_FAILURE);
66 }
67 }
68
69 /* We need to fork two times for UNIXy resons. */
item_command_exec_first_fork(struct Lava_bar_instance * instance,const char * cmd)70 static void item_command_exec_first_fork (struct Lava_bar_instance *instance, const char *cmd)
71 {
72 errno = 0;
73 int ret = fork();
74 if ( ret == 0 )
75 {
76 setsid();
77
78 /* Restore signals. */
79 sigset_t mask;
80 sigemptyset(&mask);
81 sigprocmask(SIG_SETMASK, &mask, NULL);
82
83 item_command_exec_second_fork(instance, cmd);
84 _exit(EXIT_SUCCESS);
85 }
86 else if ( ret < 0 ) /* Yes, fork can fail. */
87 log_message(0, "ERROR: fork: %s\n", strerror(errno));
88 else
89 waitpid(ret, NULL, 0);
90 }
91
execute_item_command(struct Lava_item_command * cmd,struct Lava_bar_instance * instance)92 static void execute_item_command (struct Lava_item_command *cmd, struct Lava_bar_instance *instance)
93 {
94 const char *command = cmd->command;
95
96 log_message(1, "[item] Executing command: %s\n", command);
97
98 /* Developer options meant for testing. Intentionally not documented. */
99 if (! strcmp(command, "exit"))
100 {
101 log_message(1, "[item] Triggering exit. "
102 "This is a developer option not intended for actual usage.\n");
103 context.loop = false;
104 context.reload = false;
105 return;
106 }
107 else if (! strcmp(command, "reload"))
108 {
109 log_message(1, "[item] Triggering reload. "
110 "This is a developer option not intended for actual usage.\n");
111 context.loop = false;
112 context.reload = true;
113 return;
114 }
115
116 item_command_exec_first_fork(instance, command);
117 }
118
find_item_command(struct Lava_item * item,enum Interaction_type type,uint32_t modifiers,uint32_t special,bool allow_universal)119 static struct Lava_item_command *find_item_command (struct Lava_item *item,
120 enum Interaction_type type, uint32_t modifiers, uint32_t special,
121 bool allow_universal)
122 {
123 struct Lava_item_command *cmd;
124 wl_list_for_each(cmd, &item->commands, link)
125 if ( (cmd->type == type && cmd->modifiers == modifiers && cmd->special == special)
126 || (allow_universal && cmd->type == INTERACTION_UNIVERSAL && type != INTERACTION_MOUSE_SCROLL) )
127 return cmd;
128 return NULL;
129 }
130
item_add_command(struct Lava_item * item,const char * command,enum Interaction_type type,uint32_t modifiers,uint32_t special)131 static bool item_add_command (struct Lava_item *item, const char *command,
132 enum Interaction_type type, uint32_t modifiers, uint32_t special)
133 {
134 TRY_NEW(struct Lava_item_command, cmd, false);
135
136 cmd->type = type;
137 cmd->modifiers = modifiers;
138 cmd->special = special;
139
140 set_string(&cmd->command, (char *)command);
141
142 wl_list_insert(&item->commands, &cmd->link);
143 return true;
144 }
145
destroy_item_command(struct Lava_item_command * cmd)146 static void destroy_item_command (struct Lava_item_command *cmd)
147 {
148 wl_list_remove(&cmd->link);
149 free_if_set(cmd->command);
150 free(cmd);
151 }
152
destroy_all_item_commands(struct Lava_item * item)153 static void destroy_all_item_commands (struct Lava_item *item)
154 {
155 struct Lava_item_command *cmd, *tmp;
156 wl_list_for_each_safe(cmd, tmp, &item->commands, link)
157 destroy_item_command(cmd);
158 }
159
160 /**************************
161 * *
162 * Button configuration *
163 * *
164 **************************/
button_set_image_path(struct Lava_item * button,const char * path)165 static bool button_set_image_path (struct Lava_item *button, const char *path)
166 {
167 DESTROY(button->img, image_t_destroy);
168 if ( NULL == (button->img = image_t_create_from_file(path)) )
169 return false;
170 return true;
171 }
172
parse_bind_token_buffer(char * buffer,int * index,enum Interaction_type * type,uint32_t * modifiers,uint32_t * special,bool * type_defined)173 static bool parse_bind_token_buffer (char *buffer, int *index,enum Interaction_type *type,
174 uint32_t *modifiers, uint32_t *special, bool *type_defined)
175 {
176 buffer[*index] = '\0';
177
178 const struct
179 {
180 char *name;
181 enum Interaction_type type;
182 bool modifier;
183 uint32_t value;
184 } tokens[] = {
185 /* Mouse buttons (basically everything from linux/input-event-codes.h that a mouse-like device can emit) */
186 { .name = "mouse-mouse", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_MOUSE },
187 { .name = "mouse-left", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_LEFT },
188 { .name = "mouse-right", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_RIGHT },
189 { .name = "mouse-middle", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_MIDDLE },
190 { .name = "mouse-side", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_SIDE },
191 { .name = "mouse-extra", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_EXTRA },
192 { .name = "mouse-forward", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_FORWARD },
193 { .name = "mouse-backward", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_BACK },
194 { .name = "mouse-task", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_TASK },
195 { .name = "mouse-misc", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_MISC },
196 { .name = "mouse-1", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_1 },
197 { .name = "mouse-2", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_2 },
198 { .name = "mouse-3", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_3 },
199 { .name = "mouse-4", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_4 },
200 { .name = "mouse-5", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_5 },
201 { .name = "mouse-6", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_6 },
202 { .name = "mouse-7", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_7 },
203 { .name = "mouse-8", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_8 },
204 { .name = "mouse-9", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_9 },
205
206 /* Scroll */
207 { .name = "scroll-up", .type = INTERACTION_MOUSE_SCROLL, .modifier = false, .value = 1 },
208 { .name = "scroll-down", .type = INTERACTION_MOUSE_SCROLL, .modifier = false, .value = 0 },
209
210 /* Touch */
211 { .name = "touch", .type = INTERACTION_TOUCH, .modifier = false, .value = 0 },
212
213 /* Modifiers */
214 { .name = "alt", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = ALT },
215 { .name = "capslock", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = CAPS },
216 { .name = "control", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = CONTROL },
217 { .name = "logo", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = LOGO },
218 { .name = "numlock", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = NUM },
219 { .name = "shift", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = SHIFT }
220 };
221
222 FOR_ARRAY(tokens, i) if (! strcmp(tokens[i].name, buffer))
223 {
224 if (tokens[i].modifier)
225 {
226 *modifiers |= tokens[i].value;
227 context.need_keyboard = true;
228 }
229 else
230 {
231 if (*type_defined)
232 {
233 log_message(0, "ERROR: A command can only have a single interaction type.\n");
234 return false;
235 }
236 *type_defined = true;
237
238 *type = tokens[i].type;
239 *special = tokens[i].value;
240 switch (tokens[i].type)
241 {
242 case INTERACTION_MOUSE_BUTTON:
243 case INTERACTION_MOUSE_SCROLL:
244 context.need_pointer = true;
245 break;
246
247 case INTERACTION_TOUCH:
248 context.need_touch = true;
249 break;
250
251 default:
252 break;
253 }
254 }
255
256 *index = 0;
257 return true;
258 }
259
260 log_message(0, "ERROR: Unrecognized interaction type / modifier \"%s\".\n", buffer);
261 return false;
262 }
263
parse_token_buffer_add_char(char * buffer,int size,int * index,char ch)264 static bool parse_token_buffer_add_char (char *buffer, int size, int *index, char ch)
265 {
266 if ( *index > size - 2 )
267 return false;
268 buffer[*index] = ch;
269 (*index)++;
270 return true;
271 }
272
button_item_command_from_string(struct Lava_item * button,const char * _bind,const char * command)273 static bool button_item_command_from_string (struct Lava_item *button,
274 const char *_bind, const char *command)
275 {
276 /* We can safely skip what we know is already there. */
277 char *bind = (char *)_bind + strlen("command");
278
279 const int buffer_size = 20;
280 char buffer[buffer_size];
281 int buffer_index = 0;
282
283 bool type_defined = false;
284 enum Interaction_type type = INTERACTION_UNIVERSAL;
285 uint32_t modifiers = 0, special = 0;
286 bool start = false, stop = false;
287 char *ch = (char *)bind;
288 for (;;)
289 {
290 if ( *ch == '\0' )
291 {
292 if ( !start || !stop )
293 goto error;
294
295 /* If the type is still universal, no bind has been specified. */
296 if ( type == INTERACTION_UNIVERSAL )
297 {
298 log_message(0, "ERROR: No interaction type defined.\n", bind);
299 goto error;
300 }
301
302 /* Try to find a fitting command and overwrite it.
303 * If none has been found, create a new one.
304 */
305 struct Lava_item_command *cmd = find_item_command(button,
306 type, modifiers, special, false);
307 if ( cmd == NULL )
308 return item_add_command(button, command, type, modifiers, special);
309 set_string(&cmd->command, (char *)command);
310 return true;
311 }
312 else if ( *ch == '[' )
313 {
314 if ( start || stop )
315 goto error;
316 start = true;
317 }
318 else if ( *ch == ']' )
319 {
320 if ( !start || stop )
321 goto error;
322 if (! parse_bind_token_buffer(buffer, &buffer_index,
323 &type, &modifiers, &special, &type_defined))
324 goto error;
325 stop = true;
326 }
327 else if ( *ch == '+' )
328 {
329 if (! parse_bind_token_buffer(buffer, &buffer_index,
330 &type, &modifiers, &special, &type_defined))
331 goto error;
332 }
333 else
334 {
335 if ( !start || stop )
336 goto error;
337 if (! parse_token_buffer_add_char(buffer, buffer_size, &buffer_index, *ch))
338 goto error;
339 }
340
341 ch++;
342 }
343
344 error:
345 log_message(0, "ERROR: Unable to parse command bind string: %s\n", bind);
346 return false;
347 }
348
button_item_universal_command(struct Lava_item * button,const char * command)349 static bool button_item_universal_command (struct Lava_item *button, const char *command)
350 {
351 /* Interaction type is universal, meaning the button can be activated
352 * by both the pointer and touch.
353 */
354 context.need_pointer = true;
355 context.need_touch = true;
356
357 /* Try to find a universal command and overwrite it. If none has been
358 * found, create a new one.
359 */
360 struct Lava_item_command *cmd = find_item_command(button,
361 INTERACTION_UNIVERSAL, 0, 0, false);
362 if ( cmd != NULL )
363 {
364 set_string(&cmd->command, (char *)command);
365 return true;
366 }
367
368 return item_add_command(button, command, INTERACTION_UNIVERSAL, 0, 0);
369 }
370
button_set_variable(struct Lava_item * button,const char * variable,const char * value,int line)371 static bool button_set_variable (struct Lava_item *button, const char *variable,
372 const char *value, int line)
373 {
374 if (! strcmp("image-path", variable))
375 TRY(button_set_image_path(button, value))
376 else if (! strcmp("command", variable)) /* Generic/universal command */
377 TRY(button_item_universal_command(button, value))
378 else if (string_starts_with(variable, "command")) /* Command with special bind */
379 TRY(button_item_command_from_string(button, variable, value))
380
381 log_message(0, "ERROR: Unrecognized button setting \"%s\".\n", variable);
382 error:
383 log_message(0, "INFO: The error is on line %d in \"%s\".\n",
384 line, context.config_path);
385 return false;
386 }
387
388 /**************************
389 * *
390 * Spacer configuration *
391 * *
392 **************************/
spacer_set_length(struct Lava_item * spacer,const char * length)393 static bool spacer_set_length (struct Lava_item *spacer, const char *length)
394 {
395 int len = atoi(length);
396 if ( len <= 0 )
397 {
398 log_message(0, "ERROR: Spacer size must be greater than 0.\n");
399 return false;
400 }
401 spacer->length = (unsigned int)len;
402 return true;
403 }
404
spacer_set_variable(struct Lava_item * spacer,const char * variable,const char * value,int line)405 static bool spacer_set_variable (struct Lava_item *spacer,
406 const char *variable, const char *value, int line)
407 {
408 if (! strcmp("length", variable))
409 TRY(spacer_set_length(spacer, value))
410
411 log_message(0, "ERROR: Unrecognized spacer setting \"%s\".\n", variable);
412 error:
413 log_message(0, "INFO: The error is on line %d in \"%s\".\n",
414 line, context.config_path);
415 return false;
416 }
417
item_set_variable(struct Lava_item * item,const char * variable,const char * value,int line)418 bool item_set_variable (struct Lava_item *item, const char *variable,
419 const char *value, int line)
420 {
421 switch (item->type)
422 {
423 case TYPE_BUTTON:
424 return button_set_variable(item, variable, value, line);
425
426 case TYPE_SPACER:
427 return spacer_set_variable(item, variable, value, line);
428
429 default:
430 return false;
431 }
432 }
433
434 /**********
435 * *
436 * Item *
437 * *
438 **********/
item_interaction(struct Lava_item * item,struct Lava_bar_instance * instance,enum Interaction_type type,uint32_t modifiers,uint32_t special)439 void item_interaction (struct Lava_item *item, struct Lava_bar_instance *instance,
440 enum Interaction_type type, uint32_t modifiers, uint32_t special)
441 {
442 if ( item->type != TYPE_BUTTON )
443 return;
444
445 log_message(1, "[item] Interaction: type=%d mod=%d spec=%d\n",
446 type, modifiers, special);
447
448 struct Lava_item_command *cmd;
449 if ( NULL != (cmd = find_item_command(item, type, modifiers, special, true)) )
450 execute_item_command(cmd, instance);
451 }
452
create_item(struct Lava_bar * bar,enum Item_type type)453 bool create_item (struct Lava_bar *bar, enum Item_type type)
454 {
455 log_message(2, "[item] Creating item.\n");
456
457 TRY_NEW(struct Lava_item, item, false);
458
459 item->index = 0;
460 item->ordinate = 0;
461 item->length = 0;
462 item->img = NULL;
463 item->type = type;
464 bar->last_item = item;
465 wl_list_init(&item->commands);
466 wl_list_insert(&bar->items, &item->link);
467 return true;
468 }
469
470 /* Return pointer to Lava_item struct from item list which includes the
471 * given surface-local coordinates on the surface of the given output.
472 */
item_from_coords(struct Lava_bar_instance * instance,uint32_t x,uint32_t y)473 struct Lava_item *item_from_coords (struct Lava_bar_instance *instance, uint32_t x, uint32_t y)
474 {
475 struct Lava_bar *bar = instance->bar;
476 struct Lava_bar_configuration *config = instance->config;
477 uint32_t ordinate;
478 if ( config->orientation == ORIENTATION_HORIZONTAL )
479 ordinate = x - instance->item_area_dim.x;
480 else
481 ordinate = y - instance->item_area_dim.y;
482
483 struct Lava_item *item, *temp;
484 wl_list_for_each_reverse_safe(item, temp, &bar->items, link)
485 {
486 if ( ordinate >= item->ordinate
487 && ordinate < item->ordinate + item->length )
488 return item;
489 }
490 return NULL;
491 }
492
get_item_length_sum(struct Lava_bar * bar)493 unsigned int get_item_length_sum (struct Lava_bar *bar)
494 {
495 unsigned int sum = 0;
496 struct Lava_item *it1, *it2;
497 wl_list_for_each_reverse_safe (it1, it2, &bar->items, link)
498 sum += it1->length;
499 return sum;
500 }
501
502 /* When items are created when parsing the config file, the size is not yet
503 * available, so items need to be finalized later.
504 */
finalize_items(struct Lava_bar * bar)505 bool finalize_items (struct Lava_bar *bar)
506 {
507 bar->item_amount = wl_list_length(&bar->items);
508 if ( bar->item_amount == 0 )
509 {
510 log_message(0, "ERROR: Configuration defines a bar without items.\n");
511 return false;
512 }
513
514
515 unsigned int index = 0, ordinate = 0;
516 struct Lava_item *it1, *it2;
517 wl_list_for_each_reverse_safe(it1, it2, &bar->items, link)
518 {
519 // TODO XXX set size to -1, which should cause it to automatically be config->size
520 if ( it1->type == TYPE_BUTTON )
521 it1->length = bar->default_config->size;
522
523 it1->index = index;
524 it1->ordinate = ordinate;
525
526 index++;
527 ordinate += it1->length;
528 }
529
530 return true;
531 }
532
destroy_item(struct Lava_item * item)533 static void destroy_item (struct Lava_item *item)
534 {
535 wl_list_remove(&item->link);
536 destroy_all_item_commands(item);
537 DESTROY(item->img, image_t_destroy);
538 free(item);
539 }
540
destroy_all_items(struct Lava_bar * bar)541 void destroy_all_items (struct Lava_bar *bar)
542 {
543 log_message(1, "[items] Destroying all items.\n");
544 struct Lava_item *item, *temp;
545 wl_list_for_each_safe(item, temp, &bar->items, link)
546 destroy_item(item);
547 }
548
549