/*
* 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);
}