/* * LavaLauncher - A simple launcher panel for Wayland * * Copyright (C) 2020 Leon Henrik Plickat * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include"lavalauncher.h" #include"item.h" #include"seat.h" #include"str.h" #include"bar.h" #include"output.h" #include"types/image_t.h" /******************* * * * Item commands * * * *******************/ /* We need to fork two times for UNIXy resons. */ static void item_command_exec_second_fork (struct Lava_bar_instance *instance, const char *cmd) { errno = 0; int ret = fork(); if ( ret == 0 ) { /* Prepare environment variables. */ setenvf("LAVALAUNCHER_OUTPUT_NAME", "%s", instance->output->name); setenvf("LAVALAUNCHER_OUTPUT_SCALE", "%d", instance->output->scale); /* execl() only returns on error; On success it replaces this process. */ execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); log_message(0, "ERROR: execl: %s\n", strerror(errno)); _exit(EXIT_FAILURE); } else if ( ret < 0 ) /* Yes, fork can fail. */ { log_message(0, "ERROR: fork: %s\n", strerror(errno)); _exit(EXIT_FAILURE); } } /* We need to fork two times for UNIXy resons. */ static void item_command_exec_first_fork (struct Lava_bar_instance *instance, const char *cmd) { errno = 0; int ret = fork(); if ( ret == 0 ) { setsid(); /* Restore signals. */ sigset_t mask; sigemptyset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); item_command_exec_second_fork(instance, cmd); _exit(EXIT_SUCCESS); } else if ( ret < 0 ) /* Yes, fork can fail. */ log_message(0, "ERROR: fork: %s\n", strerror(errno)); else waitpid(ret, NULL, 0); } static void execute_item_command (struct Lava_item_command *cmd, struct Lava_bar_instance *instance) { const char *command = cmd->command; log_message(1, "[item] Executing command: %s\n", command); /* Developer options meant for testing. Intentionally not documented. */ if (! strcmp(command, "exit")) { log_message(1, "[item] Triggering exit. " "This is a developer option not intended for actual usage.\n"); context.loop = false; context.reload = false; return; } else if (! strcmp(command, "reload")) { log_message(1, "[item] Triggering reload. " "This is a developer option not intended for actual usage.\n"); context.loop = false; context.reload = true; return; } item_command_exec_first_fork(instance, command); } static struct Lava_item_command *find_item_command (struct Lava_item *item, enum Interaction_type type, uint32_t modifiers, uint32_t special, bool allow_universal) { struct Lava_item_command *cmd; wl_list_for_each(cmd, &item->commands, link) if ( (cmd->type == type && cmd->modifiers == modifiers && cmd->special == special) || (allow_universal && cmd->type == INTERACTION_UNIVERSAL && type != INTERACTION_MOUSE_SCROLL) ) return cmd; return NULL; } static bool item_add_command (struct Lava_item *item, const char *command, enum Interaction_type type, uint32_t modifiers, uint32_t special) { TRY_NEW(struct Lava_item_command, cmd, false); cmd->type = type; cmd->modifiers = modifiers; cmd->special = special; set_string(&cmd->command, (char *)command); wl_list_insert(&item->commands, &cmd->link); return true; } static void destroy_item_command (struct Lava_item_command *cmd) { wl_list_remove(&cmd->link); free_if_set(cmd->command); free(cmd); } static void destroy_all_item_commands (struct Lava_item *item) { struct Lava_item_command *cmd, *tmp; wl_list_for_each_safe(cmd, tmp, &item->commands, link) destroy_item_command(cmd); } /************************** * * * Button configuration * * * **************************/ static bool button_set_image_path (struct Lava_item *button, const char *path) { DESTROY(button->img, image_t_destroy); if ( NULL == (button->img = image_t_create_from_file(path)) ) return false; return true; } static bool parse_bind_token_buffer (char *buffer, int *index,enum Interaction_type *type, uint32_t *modifiers, uint32_t *special, bool *type_defined) { buffer[*index] = '\0'; const struct { char *name; enum Interaction_type type; bool modifier; uint32_t value; } tokens[] = { /* Mouse buttons (basically everything from linux/input-event-codes.h that a mouse-like device can emit) */ { .name = "mouse-mouse", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_MOUSE }, { .name = "mouse-left", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_LEFT }, { .name = "mouse-right", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_RIGHT }, { .name = "mouse-middle", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_MIDDLE }, { .name = "mouse-side", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_SIDE }, { .name = "mouse-extra", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_EXTRA }, { .name = "mouse-forward", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_FORWARD }, { .name = "mouse-backward", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_BACK }, { .name = "mouse-task", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_TASK }, { .name = "mouse-misc", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_MISC }, { .name = "mouse-1", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_1 }, { .name = "mouse-2", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_2 }, { .name = "mouse-3", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_3 }, { .name = "mouse-4", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_4 }, { .name = "mouse-5", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_5 }, { .name = "mouse-6", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_6 }, { .name = "mouse-7", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_7 }, { .name = "mouse-8", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_8 }, { .name = "mouse-9", .type = INTERACTION_MOUSE_BUTTON, .modifier = false, .value = BTN_9 }, /* Scroll */ { .name = "scroll-up", .type = INTERACTION_MOUSE_SCROLL, .modifier = false, .value = 1 }, { .name = "scroll-down", .type = INTERACTION_MOUSE_SCROLL, .modifier = false, .value = 0 }, /* Touch */ { .name = "touch", .type = INTERACTION_TOUCH, .modifier = false, .value = 0 }, /* Modifiers */ { .name = "alt", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = ALT }, { .name = "capslock", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = CAPS }, { .name = "control", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = CONTROL }, { .name = "logo", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = LOGO }, { .name = "numlock", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = NUM }, { .name = "shift", .type = INTERACTION_UNIVERSAL, .modifier = true, .value = SHIFT } }; FOR_ARRAY(tokens, i) if (! strcmp(tokens[i].name, buffer)) { if (tokens[i].modifier) { *modifiers |= tokens[i].value; context.need_keyboard = true; } else { if (*type_defined) { log_message(0, "ERROR: A command can only have a single interaction type.\n"); return false; } *type_defined = true; *type = tokens[i].type; *special = tokens[i].value; switch (tokens[i].type) { case INTERACTION_MOUSE_BUTTON: case INTERACTION_MOUSE_SCROLL: context.need_pointer = true; break; case INTERACTION_TOUCH: context.need_touch = true; break; default: break; } } *index = 0; return true; } log_message(0, "ERROR: Unrecognized interaction type / modifier \"%s\".\n", buffer); return false; } static bool parse_token_buffer_add_char (char *buffer, int size, int *index, char ch) { if ( *index > size - 2 ) return false; buffer[*index] = ch; (*index)++; return true; } static bool button_item_command_from_string (struct Lava_item *button, const char *_bind, const char *command) { /* We can safely skip what we know is already there. */ char *bind = (char *)_bind + strlen("command"); const int buffer_size = 20; char buffer[buffer_size]; int buffer_index = 0; bool type_defined = false; enum Interaction_type type = INTERACTION_UNIVERSAL; uint32_t modifiers = 0, special = 0; bool start = false, stop = false; char *ch = (char *)bind; for (;;) { if ( *ch == '\0' ) { if ( !start || !stop ) goto error; /* If the type is still universal, no bind has been specified. */ if ( type == INTERACTION_UNIVERSAL ) { log_message(0, "ERROR: No interaction type defined.\n", bind); goto error; } /* Try to find a fitting command and overwrite it. * If none has been found, create a new one. */ struct Lava_item_command *cmd = find_item_command(button, type, modifiers, special, false); if ( cmd == NULL ) return item_add_command(button, command, type, modifiers, special); set_string(&cmd->command, (char *)command); return true; } else if ( *ch == '[' ) { if ( start || stop ) goto error; start = true; } else if ( *ch == ']' ) { if ( !start || stop ) goto error; if (! parse_bind_token_buffer(buffer, &buffer_index, &type, &modifiers, &special, &type_defined)) goto error; stop = true; } else if ( *ch == '+' ) { if (! parse_bind_token_buffer(buffer, &buffer_index, &type, &modifiers, &special, &type_defined)) goto error; } else { if ( !start || stop ) goto error; if (! parse_token_buffer_add_char(buffer, buffer_size, &buffer_index, *ch)) goto error; } ch++; } error: log_message(0, "ERROR: Unable to parse command bind string: %s\n", bind); return false; } static bool button_item_universal_command (struct Lava_item *button, const char *command) { /* Interaction type is universal, meaning the button can be activated * by both the pointer and touch. */ context.need_pointer = true; context.need_touch = true; /* Try to find a universal command and overwrite it. If none has been * found, create a new one. */ struct Lava_item_command *cmd = find_item_command(button, INTERACTION_UNIVERSAL, 0, 0, false); if ( cmd != NULL ) { set_string(&cmd->command, (char *)command); return true; } return item_add_command(button, command, INTERACTION_UNIVERSAL, 0, 0); } static bool button_set_variable (struct Lava_item *button, const char *variable, const char *value, int line) { if (! strcmp("image-path", variable)) TRY(button_set_image_path(button, value)) else if (! strcmp("command", variable)) /* Generic/universal command */ TRY(button_item_universal_command(button, value)) else if (string_starts_with(variable, "command")) /* Command with special bind */ TRY(button_item_command_from_string(button, variable, value)) log_message(0, "ERROR: Unrecognized button setting \"%s\".\n", variable); error: log_message(0, "INFO: The error is on line %d in \"%s\".\n", line, context.config_path); return false; } /************************** * * * Spacer configuration * * * **************************/ static bool spacer_set_length (struct Lava_item *spacer, const char *length) { int len = atoi(length); if ( len <= 0 ) { log_message(0, "ERROR: Spacer size must be greater than 0.\n"); return false; } spacer->length = (unsigned int)len; return true; } static bool spacer_set_variable (struct Lava_item *spacer, const char *variable, const char *value, int line) { if (! strcmp("length", variable)) TRY(spacer_set_length(spacer, value)) log_message(0, "ERROR: Unrecognized spacer setting \"%s\".\n", variable); error: log_message(0, "INFO: The error is on line %d in \"%s\".\n", line, context.config_path); return false; } bool item_set_variable (struct Lava_item *item, const char *variable, const char *value, int line) { switch (item->type) { case TYPE_BUTTON: return button_set_variable(item, variable, value, line); case TYPE_SPACER: return spacer_set_variable(item, variable, value, line); default: return false; } } /********** * * * Item * * * **********/ void item_interaction (struct Lava_item *item, struct Lava_bar_instance *instance, enum Interaction_type type, uint32_t modifiers, uint32_t special) { if ( item->type != TYPE_BUTTON ) return; log_message(1, "[item] Interaction: type=%d mod=%d spec=%d\n", type, modifiers, special); struct Lava_item_command *cmd; if ( NULL != (cmd = find_item_command(item, type, modifiers, special, true)) ) execute_item_command(cmd, instance); } bool create_item (struct Lava_bar *bar, enum Item_type type) { log_message(2, "[item] Creating item.\n"); TRY_NEW(struct Lava_item, item, false); item->index = 0; item->ordinate = 0; item->length = 0; item->img = NULL; item->type = type; bar->last_item = item; wl_list_init(&item->commands); wl_list_insert(&bar->items, &item->link); return true; } /* Return pointer to Lava_item struct from item list which includes the * given surface-local coordinates on the surface of the given output. */ struct Lava_item *item_from_coords (struct Lava_bar_instance *instance, uint32_t x, uint32_t y) { struct Lava_bar *bar = instance->bar; struct Lava_bar_configuration *config = instance->config; uint32_t ordinate; if ( config->orientation == ORIENTATION_HORIZONTAL ) ordinate = x - instance->item_area_dim.x; else ordinate = y - instance->item_area_dim.y; struct Lava_item *item, *temp; wl_list_for_each_reverse_safe(item, temp, &bar->items, link) { if ( ordinate >= item->ordinate && ordinate < item->ordinate + item->length ) return item; } return NULL; } unsigned int get_item_length_sum (struct Lava_bar *bar) { unsigned int sum = 0; struct Lava_item *it1, *it2; wl_list_for_each_reverse_safe (it1, it2, &bar->items, link) sum += it1->length; return sum; } /* When items are created when parsing the config file, the size is not yet * available, so items need to be finalized later. */ bool finalize_items (struct Lava_bar *bar) { bar->item_amount = wl_list_length(&bar->items); if ( bar->item_amount == 0 ) { log_message(0, "ERROR: Configuration defines a bar without items.\n"); return false; } unsigned int index = 0, ordinate = 0; struct Lava_item *it1, *it2; wl_list_for_each_reverse_safe(it1, it2, &bar->items, link) { // TODO XXX set size to -1, which should cause it to automatically be config->size if ( it1->type == TYPE_BUTTON ) it1->length = bar->default_config->size; it1->index = index; it1->ordinate = ordinate; index++; ordinate += it1->length; } return true; } static void destroy_item (struct Lava_item *item) { wl_list_remove(&item->link); destroy_all_item_commands(item); DESTROY(item->img, image_t_destroy); free(item); } void destroy_all_items (struct Lava_bar *bar) { log_message(1, "[items] Destroying all items.\n"); struct Lava_item *item, *temp; wl_list_for_each_safe(item, temp, &bar->items, link) destroy_item(item); }