diff --git a/meson.build b/meson.build index c5987607f..73178d211 100644 --- a/meson.build +++ b/meson.build @@ -34,6 +34,7 @@ pixman = dependency('pixman-1') xkbcommon = dependency('xkbcommon') libdl = meson.get_compiler('cpp').find_library('dl') json = dependency('nlohmann_json', version: '>= 3.11.2') +udev = dependency('libudev') # We're not to use system wlroots: So we'll use the subproject if get_option('use_system_wlroots').disabled() diff --git a/metadata/input-device.xml b/metadata/input-device.xml index 63ed6769b..54a8f45f4 100644 --- a/metadata/input-device.xml +++ b/metadata/input-device.xml @@ -5,5 +5,8 @@ + diff --git a/src/api/wayfire/config-backend.hpp b/src/api/wayfire/config-backend.hpp index 8a47601e7..54db71bb6 100644 --- a/src/api/wayfire/config-backend.hpp +++ b/src/api/wayfire/config-backend.hpp @@ -49,7 +49,7 @@ class config_backend_t * described in input-device.xml */ virtual std::shared_ptr get_input_device_section( - wlr_input_device *device); + std::string const & prefix, wlr_input_device *device); virtual ~config_backend_t() = default; diff --git a/src/core/core.cpp b/src/core/core.cpp index 6c13c1ccc..1552f8b7a 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -242,7 +242,7 @@ void wf::compositor_core_impl_t::post_init() seat->focus_output(wo); // Refresh device mappings when we have all outputs and devices - input->refresh_device_mappings(); + input->configure_input_devices(); // Start processing cursor events seat->priv->cursor->setup_listeners(); diff --git a/src/core/plugin.cpp b/src/core/plugin.cpp index 6d7d8917d..c9ffd8bec 100644 --- a/src/core/plugin.cpp +++ b/src/core/plugin.cpp @@ -5,6 +5,7 @@ #include "wayfire/signal-definitions.hpp" #include #include +#include void wf::plugin_interface_t::fini() {} @@ -27,18 +28,76 @@ std::shared_ptr wf::config_backend_t::get_output_section( return config.get_section(name); } +static struct udev_property_and_desc +{ + char const *property_name; + char const *description; +} properties_and_descs[] = +{ + {"ID_PATH", "stable physical connection path"}, + {"ID_SERIAL", "stable vendor+pn+sn info"}, + {"LIBINPUT_DEVICE_GROUP", "stable libinput info"}, + // sometimes it contains info "by path", sometimes "by id" + {"DEVPATH", "unstable devpath"}, + // used for debugging, to find DEVPATH and match the right + // device in `udevadm info --tree` +}; + std::shared_ptr wf::config_backend_t::get_input_device_section( - wlr_input_device *device) + std::string const & prefix, wlr_input_device *device) { - std::string name = nonull(device->name); - name = "input-device:" + name; auto& config = wf::get_core().config; - if (!config.get_section(name)) + std::shared_ptr section; + auto print_devpath = getenv("WF_PRINT_UDEV_DEVPATH"); + + if (wlr_input_device_is_libinput(device)) { - config.merge_section( - config.get_section("input-device")->clone_with_name(name)); + auto libinput_dev = wlr_libinput_get_device_handle(device); + if (libinput_dev) + { + udev_device *udev_dev = libinput_device_get_udev_device(libinput_dev); + if (udev_dev) + { + for (struct udev_property_and_desc const & pd : properties_and_descs) + { + if (!print_devpath && !strncmp(pd.property_name, "DEVPATH", strlen("DEVPATH"))) + { + continue; + } + + const char *value = udev_device_get_property_value(udev_dev, pd.property_name); + if (value == nullptr) + { + continue; + } + + std::string name = prefix + ":" + nonull(value); + LOGD("Checking for config section [", name, "] ", + pd.property_name, " (", pd.description, ")"); + section = config.get_section(name); + if (section) + { + LOGD("Using config section [", name, "] for ", nonull(device->name)); + return section; + } + } + } + } + } + + std::string name = nonull(device->name); + name = prefix + ":" + name; + LOGD("Checking for config section [", name, "]"); + section = config.get_section(name); + if (section) + { + LOGD("Using config section [", name, "]"); + return section; } + config.merge_section( + config.get_section(prefix)->clone_with_name(name)); + return config.get_section(name); } diff --git a/src/core/seat/input-manager.cpp b/src/core/seat/input-manager.cpp index a91153259..2af3cd659 100644 --- a/src/core/seat/input-manager.cpp +++ b/src/core/seat/input-manager.cpp @@ -52,53 +52,97 @@ void wf::input_manager_t::handle_new_input(wlr_input_device *dev) data.device = nonstd::make_observer(input_devices.back().get()); wf::get_core().emit(&data); - refresh_device_mappings(); + configure_input_devices(); } -void wf::input_manager_t::refresh_device_mappings() +void wf::input_manager_t::calibrate_touch_device(wlr_input_device *dev, std::string const & cal) { - // Might trigger motion events which we want to avoid at other stages - auto state = wf::get_core().get_current_state(); - if (state != wf::compositor_state_t::RUNNING) + if (!wlr_input_device_is_libinput(dev) || (dev->type != WLR_INPUT_DEVICE_TOUCH)) { return; } - auto cursor = wf::get_core().get_wlr_cursor(); - for (auto& device : this->input_devices) + float m[6]; + auto libinput_dev = wlr_libinput_get_device_handle(dev); + if (sscanf(cal.c_str(), "%f %f %f %f %f %f", + &m[0], &m[1], &m[2], &m[3], &m[4], &m[5]) == 6) { - wlr_input_device *dev = device->get_wlr_handle(); - auto section = - wf::get_core().config_backend->get_input_device_section(dev); + enum libinput_config_status status; - auto mapped_output = section->get_option("output")->get_value_str(); - if (mapped_output.empty()) + status = libinput_device_config_calibration_set_matrix(libinput_dev, m); + if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) { - if (dev->type == WLR_INPUT_DEVICE_POINTER) - { - mapped_output = nonull(wlr_pointer_from_input_device( - dev)->output_name); - } else if (dev->type == WLR_INPUT_DEVICE_TOUCH) - { - mapped_output = - nonull(wlr_touch_from_input_device(dev)->output_name); - } else - { - mapped_output = nonull(dev->name); - } + LOGE("Failed to apply calibration for ", nonull(dev->name)); + LOGE(" ", m[0], " ", m[1], " ", m[2], " ", m[3], " ", m[4], " ", m[5]); + } else + { + LOGI("Calibrated input device successfully: ", nonull(dev->name)); + LOGI(" ", m[0], " ", m[1], " ", m[2], " ", m[3], " ", m[4], " ", m[5]); } + } else + { + LOGE("Incorrect calibration configuration for ", nonull(dev->name)); + LOGI("Setting default matrix calibration: "); + libinput_device_config_calibration_get_default_matrix(libinput_dev, m); + LOGI(" ", m[0], " ", m[1], " ", m[2], " ", m[3], " ", m[4], " ", m[5]); + libinput_device_config_calibration_set_matrix(libinput_dev, m); + } +} - auto wo = wf::get_core().output_layout->find_output(mapped_output); - if (wo) +void wf::input_manager_t::configure_input_device(wlr_input_device *dev) +{ + auto cursor = wf::get_core().get_wlr_cursor(); + auto section = + wf::get_core().config_backend->get_input_device_section("input-device", dev); + + auto mapped_output = section->get_option("output")->get_value_str(); + if (mapped_output.empty()) + { + if (dev->type == WLR_INPUT_DEVICE_POINTER) + { + mapped_output = nonull(wlr_pointer_from_input_device( + dev)->output_name); + } else if (dev->type == WLR_INPUT_DEVICE_TOUCH) { - LOGD("Mapping input ", dev->name, " to output ", wo->to_string(), "."); - wlr_cursor_map_input_to_output(cursor, dev, wo->handle); + mapped_output = + nonull(wlr_touch_from_input_device(dev)->output_name); } else { - LOGD("Mapping input ", dev->name, " to output null."); - wlr_cursor_map_input_to_output(cursor, dev, nullptr); + mapped_output = nonull(dev->name); } } + + auto cal = section->get_option("calibration")->get_value_str(); + if (!cal.empty()) + { + calibrate_touch_device(dev, cal); + } + + auto wo = wf::get_core().output_layout->find_output(mapped_output); + if (wo) + { + LOGD("Mapping input ", dev->name, " to output ", wo->to_string(), "."); + wlr_cursor_map_input_to_output(cursor, dev, wo->handle); + } else + { + LOGD("Mapping input ", dev->name, " to output null."); + wlr_cursor_map_input_to_output(cursor, dev, nullptr); + } +} + +void wf::input_manager_t::configure_input_devices() +{ + // Might trigger motion events which we want to avoid at other stages + auto state = wf::get_core().get_current_state(); + if (state != wf::compositor_state_t::RUNNING) + { + return; + } + + for (auto& device : this->input_devices) + { + configure_input_device(device->get_wlr_handle()); + } } void wf::input_manager_t::handle_input_destroyed(wlr_input_device *dev) @@ -170,7 +214,7 @@ wf::input_manager_t::input_manager_t() ev->output->set_inhibited(true); } - refresh_device_mappings(); + configure_input_devices(); }); wf::get_core().output_layout->connect(&output_added); } diff --git a/src/core/seat/input-manager.hpp b/src/core/seat/input-manager.hpp index 54392396e..b79ddde01 100644 --- a/src/core/seat/input-manager.hpp +++ b/src/core/seat/input-manager.hpp @@ -37,11 +37,23 @@ class input_manager_t */ uint32_t locked_mods = 0; + /** + * Map a single input device to output as specified in the + * config file or by hints in the wlroots backend. + */ + void configure_input_device(wlr_input_device *dev); + /** * Go through all input devices and map them to outputs as specified in the * config file or by hints in the wlroots backend. */ - void refresh_device_mappings(); + void configure_input_devices(); + + /** + * Calibrate a touch device with a matrix. This function does nothing + * if called with a device that is not a touch device. + */ + void calibrate_touch_device(wlr_input_device *dev, std::string const & cal); input_manager_t(); ~input_manager_t() = default; diff --git a/src/core/seat/keyboard.cpp b/src/core/seat/keyboard.cpp index ad3ab71b1..51a964dea 100644 --- a/src/core/seat/keyboard.cpp +++ b/src/core/seat/keyboard.cpp @@ -121,14 +121,18 @@ void wf::keyboard_t::setup_listeners() wf::keyboard_t::keyboard_t(wlr_input_device *dev) : handle(wlr_keyboard_from_input_device(dev)), device(dev) { - model.load_option("input/xkb_model"); - variant.load_option("input/xkb_variant"); - layout.load_option("input/xkb_layout"); - options.load_option("input/xkb_options"); - rules.load_option("input/xkb_rules"); - - repeat_rate.load_option("input/kb_repeat_rate"); - repeat_delay.load_option("input/kb_repeat_delay"); + auto section = + wf::get_core().config_backend->get_input_device_section("input", dev); + auto section_name = section->get_name(); + + model.load_option(section_name + "/xkb_model"); + variant.load_option(section_name + "/xkb_variant"); + layout.load_option(section_name + "/xkb_layout"); + options.load_option(section_name + "/xkb_options"); + rules.load_option(section_name + "/xkb_rules"); + + repeat_rate.load_option(section_name + "/kb_repeat_rate"); + repeat_delay.load_option(section_name + "/kb_repeat_delay"); // When the configuration options change, mark them as dirty. // They are applied at the config-reloaded signal. diff --git a/src/meson.build b/src/meson.build index 384533ae7..ebc7463e3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -64,7 +64,8 @@ wayfire_sources = ['geometry.cpp', wayfire_dependencies = [wayland_server, wlroots, xkbcommon, libinput, pixman, drm, egl, glesv2, glm, wf_protos, libdl, - wfconfig, libinotify, backtrace, wfutils, xcb, wftouch, json] + wfconfig, libinotify, backtrace, wfutils, xcb, + wftouch, json, udev] if conf_data.get('BUILD_WITH_IMAGEIO') wayfire_dependencies += [jpeg, png]