// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; }