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