NEDM/input_manager.c

486 lines
14 KiB
C

// Copyright 2020 - 2025, project-repo and the NEDM contributors
// SPDX-License-Identifier: MIT
#define _POSIX_C_SOURCE 200812L
#include "input_manager.h"
#include "config.h"
#include "input.h"
#include "seat.h"
#include "server.h"
#include <ctype.h>
#include <float.h>
#include <libevdev/libevdev.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/backend/libinput.h>
#include <wlr/config.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard_group.h>
#include <wlr/types/wlr_virtual_keyboard_v1.h>
#include <wlr/types/wlr_virtual_pointer_v1.h>
#include <wlr/util/log.h>
void
strip_whitespace(char *str) {
static const char whitespace[] = " \f\n\r\t\v";
size_t len = strlen(str);
size_t start = strspn(str, whitespace);
memmove(str, &str[start], len + 1 - start);
if(*str) {
for(len -= start + 1; isspace(str[len]); --len) {
}
str[len + 1] = '\0';
}
}
char *
input_device_get_identifier(struct wlr_input_device *device) {
int vendor = 0;
int product = 0;
#if WLR_HAS_LIBINPUT_BACKEND
if(wlr_input_device_is_libinput(device)) {
struct libinput_device *libinput_dev =
wlr_libinput_get_device_handle(device);
vendor = libinput_device_get_id_vendor(libinput_dev);
product = libinput_device_get_id_product(libinput_dev);
}
#endif
char *name = strdup(device->name ? device->name : "");
strip_whitespace(name);
char *p = name;
for(; *p; ++p) {
// There are in fact input devices with unprintable characters in its
// name
if(*p == ' ' || !isprint(*p)) {
*p = '_';
}
}
const char *fmt = "%d:%d:%s";
int len = snprintf(NULL, 0, fmt, vendor, product, name) + 1;
char *identifier = malloc(len);
if(!identifier) {
wlr_log(WLR_ERROR, "Unable to allocate unique input device name");
free(name);
return NULL;
}
snprintf(identifier, len, fmt, vendor, product, name);
free(name);
return identifier;
}
void
input_manager_handle_device_destroy(struct wl_listener *listener,
__attribute__((unused)) void *data) {
struct nedm_input_device *input_device =
wl_container_of(listener, input_device, device_destroy);
if(!input_device) {
wlr_log(WLR_ERROR, "Could not find cagebreak input device to destroy");
return;
}
seat_remove_device(input_device->server->seat, input_device);
wl_list_remove(&input_device->link);
wl_list_remove(&input_device->device_destroy.link);
free(input_device->identifier);
free(input_device);
}
struct nedm_input_config *
input_manager_create_empty_input_config(void) {
struct nedm_input_config *cfg = calloc(1, sizeof(struct nedm_input_config));
if(cfg == NULL) {
return NULL;
}
/* Libinput devices */
cfg->tap = INT_MIN;
cfg->tap_button_map = INT_MIN;
cfg->drag = INT_MIN;
cfg->drag_lock = INT_MIN;
cfg->dwt = INT_MIN;
cfg->send_events = INT_MIN;
cfg->click_method = INT_MIN;
cfg->middle_emulation = INT_MIN;
cfg->natural_scroll = INT_MIN;
cfg->accel_profile = INT_MIN;
cfg->pointer_accel = FLT_MIN;
cfg->scroll_factor = FLT_MIN;
cfg->scroll_button = INT_MIN;
cfg->scroll_method = INT_MIN;
cfg->left_handed = INT_MIN;
/*cfg->repeat_delay = INT_MIN;
cfg->repeat_rate = INT_MIN;
cfg->xkb_numlock = INT_MIN;
cfg->xkb_capslock = INT_MIN;
cfg->xkb_file_is_set = false;
wl_list_init(&cfg->tools);*/
/* Keyboards */
cfg->enable_keybindings = -1;
cfg->repeat_delay = -1;
cfg->repeat_rate = -1;
return cfg;
}
/* cfg1 has precedence */
struct nedm_input_config *
input_manager_merge_input_configs(struct nedm_input_config *cfg1,
struct nedm_input_config *cfg2) {
struct nedm_input_config *out_cfg = calloc(1, sizeof(struct nedm_input_config));
if(cfg1->identifier == NULL) {
if(cfg2->identifier != NULL) {
out_cfg->identifier = strdup(cfg2->identifier);
}
} else {
out_cfg->identifier = strdup(cfg1->identifier);
}
if(out_cfg == NULL) {
return NULL;
}
if(cfg1->tap == INT_MIN) {
out_cfg->tap = cfg2->tap;
} else {
out_cfg->tap = cfg1->tap;
}
if(cfg1->send_events == INT_MIN) {
out_cfg->send_events = cfg2->send_events;
} else {
out_cfg->send_events = cfg1->send_events;
}
if(cfg1->dwt == INT_MIN) {
out_cfg->dwt = cfg2->dwt;
} else {
out_cfg->dwt = cfg1->dwt;
}
if(cfg1->drag_lock == INT_MIN) {
out_cfg->drag_lock = cfg2->drag_lock;
} else {
out_cfg->drag_lock = cfg1->drag_lock;
}
if(cfg1->drag == INT_MIN) {
out_cfg->drag = cfg2->drag;
} else {
out_cfg->drag = cfg1->drag;
}
if(cfg1->tap_button_map == INT_MIN) {
out_cfg->tap_button_map = cfg2->tap_button_map;
} else {
out_cfg->tap_button_map = cfg1->tap_button_map;
}
if(cfg1->left_handed == INT_MIN) {
out_cfg->left_handed = cfg2->left_handed;
} else {
out_cfg->left_handed = cfg1->left_handed;
}
if(cfg1->scroll_method == INT_MIN) {
out_cfg->scroll_method = cfg2->scroll_method;
} else {
out_cfg->scroll_method = cfg1->scroll_method;
}
if(cfg1->scroll_button == INT_MIN) {
out_cfg->scroll_button = cfg2->scroll_button;
} else {
out_cfg->scroll_button = cfg1->scroll_button;
}
if(cfg1->scroll_factor == FLT_MIN) {
out_cfg->scroll_factor = cfg2->scroll_factor;
} else {
out_cfg->scroll_factor = cfg1->scroll_factor;
}
if(cfg1->pointer_accel == FLT_MIN) {
out_cfg->pointer_accel = cfg2->pointer_accel;
} else {
out_cfg->pointer_accel = cfg1->pointer_accel;
}
if(cfg1->accel_profile == INT_MIN) {
out_cfg->accel_profile = cfg2->accel_profile;
} else {
out_cfg->accel_profile = cfg1->accel_profile;
}
if(cfg1->natural_scroll == INT_MIN) {
out_cfg->natural_scroll = cfg2->natural_scroll;
} else {
out_cfg->natural_scroll = cfg1->natural_scroll;
}
if(cfg1->middle_emulation == INT_MIN) {
out_cfg->middle_emulation = cfg2->middle_emulation;
} else {
out_cfg->middle_emulation = cfg1->middle_emulation;
}
if(cfg1->click_method == INT_MIN) {
out_cfg->click_method = cfg2->click_method;
} else {
out_cfg->click_method = cfg1->click_method;
}
if(cfg1->enable_keybindings == -1) {
out_cfg->enable_keybindings = cfg2->enable_keybindings;
} else {
out_cfg->enable_keybindings = cfg1->enable_keybindings;
}
if(cfg1->repeat_delay == -1) {
out_cfg->repeat_delay = cfg2->repeat_delay;
} else {
out_cfg->repeat_delay = cfg1->repeat_delay;
}
if(cfg1->repeat_rate == -1) {
out_cfg->repeat_rate = cfg2->repeat_rate;
} else {
out_cfg->repeat_rate = cfg1->repeat_rate;
}
if(cfg1->calibration_matrix.configured == false) {
out_cfg->calibration_matrix = cfg2->calibration_matrix;
} else {
out_cfg->calibration_matrix = cfg1->calibration_matrix;
}
return out_cfg;
}
void
apply_keyboard_group_config(struct nedm_input_config *config,
struct nedm_keyboard_group *group) {
if(config->enable_keybindings != -1) {
group->enable_keybindings = config->enable_keybindings;
}
int repeat_delay = 600, repeat_rate = 25;
if(config->repeat_delay != -1) {
repeat_delay = config->repeat_delay;
}
if(config->repeat_rate != -1) {
repeat_rate = config->repeat_rate;
}
wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate,
repeat_delay);
}
void
nedm_input_manager_configure_keyboard_group(struct nedm_keyboard_group *group) {
struct nedm_server *server = group->seat->server;
struct nedm_input_config *tot_cfg = input_manager_create_empty_input_config();
if(tot_cfg == NULL) {
return;
}
struct nedm_input_config *tmp_cfg, *config = NULL;
wl_list_for_each(config, &server->input_config, link) {
if(strcmp(config->identifier, group->identifier) == 0 ||
strcmp(config->identifier, "*") == 0 ||
(strncmp(config->identifier, "type:", 5) == 0 &&
strcmp(config->identifier + 5, "keyboard") == 0)) {
tmp_cfg = tot_cfg;
if(tot_cfg->identifier == NULL ||
strcmp(tot_cfg->identifier, "*") == 0 ||
strcmp(config->identifier, group->identifier)) {
tot_cfg = input_manager_merge_input_configs(config, tot_cfg);
} else {
tot_cfg = input_manager_merge_input_configs(tot_cfg, config);
}
if(tmp_cfg->identifier != NULL) {
free(tmp_cfg->identifier);
}
free(tmp_cfg);
}
}
apply_keyboard_group_config(tot_cfg, group);
if(tot_cfg->identifier != NULL) {
free(tot_cfg->identifier);
}
free(tot_cfg);
}
void
nedm_input_manager_configure(struct nedm_server *server) {
struct nedm_input_device *device = NULL;
wl_list_for_each(device, &server->input->devices, link) {
nedm_input_configure_libinput_device(device);
}
struct nedm_keyboard_group *group = NULL;
wl_list_for_each(group, &server->seat->keyboard_groups, link) {
nedm_input_manager_configure_keyboard_group(group);
}
}
static void
new_input(struct nedm_input_manager *input, struct wlr_input_device *device,
bool virtual) {
struct nedm_input_device *input_device =
calloc(1, sizeof(struct nedm_input_device));
if(!input_device) {
wlr_log(WLR_ERROR, "Could not allocate input device");
return;
}
device->data = input_device;
input_device->is_virtual = virtual;
input_device->wlr_device = device;
input_device->identifier = input_device_get_identifier(device);
input_device->server = input->server;
input_device->pointer = NULL;
input_device->touch = NULL;
wl_list_insert(&input->devices, &input_device->link);
nedm_input_configure_libinput_device(input_device);
wl_signal_add(&device->events.destroy, &input_device->device_destroy);
input_device->device_destroy.notify = input_manager_handle_device_destroy;
struct nedm_seat *seat = input->server->seat;
seat_add_device(seat, input_device);
}
static void
handle_new_input(struct wl_listener *listener, void *data) {
struct nedm_input_manager *input =
wl_container_of(listener, input, new_input);
struct wlr_input_device *device = data;
new_input(input, device, false);
}
void
handle_virtual_keyboard(struct wl_listener *listener, void *data) {
struct nedm_input_manager *input =
wl_container_of(listener, input, virtual_keyboard_new);
struct wlr_virtual_keyboard_v1 *keyboard = data;
struct wlr_input_device *device = &keyboard->keyboard.base;
new_input(input, device, true);
}
void
handle_virtual_pointer(struct wl_listener *listener, void *data) {
struct nedm_input_manager *input_manager =
wl_container_of(listener, input_manager, virtual_pointer_new);
struct wlr_virtual_pointer_v1_new_pointer_event *event = data;
struct wlr_virtual_pointer_v1 *pointer = event->new_pointer;
struct wlr_input_device *device = &pointer->pointer.base;
new_input(input_manager, device, true);
if(event->suggested_output) {
wlr_cursor_map_input_to_output(input_manager->server->seat->cursor,
device, event->suggested_output);
}
}
struct nedm_input_manager *
input_manager_create(struct nedm_server *server) {
struct nedm_input_manager *input = calloc(1, sizeof(struct nedm_input_manager));
if(!input) {
return NULL;
}
wl_list_init(&input->devices);
input->server = server;
input->new_input.notify = handle_new_input;
wl_signal_add(&server->backend->events.new_input, &input->new_input);
input->virtual_keyboard =
wlr_virtual_keyboard_manager_v1_create(server->wl_display);
wl_signal_add(&input->virtual_keyboard->events.new_virtual_keyboard,
&input->virtual_keyboard_new);
input->virtual_keyboard_new.notify = handle_virtual_keyboard;
input->virtual_pointer =
wlr_virtual_pointer_manager_v1_create(server->wl_display);
wl_signal_add(&input->virtual_pointer->events.new_virtual_pointer,
&input->virtual_pointer_new);
input->virtual_pointer_new.notify = handle_virtual_pointer;
return input;
}
void
input_manager_destroy(struct nedm_input_manager *input) {
if (!input) {
return;
}
// Remove virtual keyboard listeners first
if (input->virtual_keyboard) {
wl_list_remove(&input->virtual_keyboard_new.link);
}
// Remove virtual pointer listeners
if (input->virtual_pointer) {
wl_list_remove(&input->virtual_pointer_new.link);
}
// Remove input device listeners
wl_list_remove(&input->new_input.link);
// Clean up device list - but don't free devices if they were already handled by seat
struct nedm_input_device *device, *tmp;
wl_list_for_each_safe(device, tmp, &input->devices, link) {
wl_list_remove(&device->link);
// Don't free device - it should have been freed by seat destruction
}
free(input);
}
uint32_t
get_mouse_bindsym(const char *name, char **error) {
// Get event code from name
int code = libevdev_event_code_from_name(EV_KEY, name);
if(code == -1) {
size_t len = snprintf(NULL, 0, "Unknown event %s", name) + 1;
*error = malloc(len);
if(*error) {
snprintf(*error, len, "Unknown event %s", name);
}
return 0;
}
return code;
}
uint32_t
get_mouse_bindcode(const char *name, char **error) {
// Validate event code
errno = 0;
char *endptr;
int code = strtol(name, &endptr, 10);
if(endptr == name && code <= 0) {
*error = strdup("Button event code must be a positive integer.");
return 0;
} else if(errno == ERANGE) {
*error = strdup("Button event code out of range.");
return 0;
}
const char *event = libevdev_event_code_get_name(EV_KEY, code);
if(!event || strncmp(event, "BTN_", strlen("BTN_")) != 0) {
size_t len = snprintf(NULL, 0, "Event code %d (%s) is not a button",
code, event ? event : "(null)") +
1;
*error = malloc(len);
if(*error) {
snprintf(*error, len, "Event code %d (%s) is not a button", code,
event ? event : "(null)");
}
return 0;
}
return code;
}
uint32_t
input_manager_get_mouse_button(const char *name, char **error) {
uint32_t button = get_mouse_bindsym(name, error);
if(!button && !*error) {
button = get_mouse_bindcode(name, error);
}
return button;
}