Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input device updates #2510

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 3 additions & 0 deletions metadata/input-device.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
<option name="output" type="string">
<default></default>
</option>
<option name="calibration" type="string">
<default></default>
</option>
</object>
</wayfire>
2 changes: 1 addition & 1 deletion src/api/wayfire/config-backend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class config_backend_t
* described in input-device.xml
*/
virtual std::shared_ptr<config::section_t> get_input_device_section(
wlr_input_device *device);
std::string const & prefix, wlr_input_device *device);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs API/ABI version bump :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I forgot to bump! :)


virtual ~config_backend_t() = default;

Expand Down
2 changes: 1 addition & 1 deletion src/core/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
71 changes: 65 additions & 6 deletions src/core/plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "wayfire/signal-definitions.hpp"
#include <wayfire/util/log.hpp>
#include <wayfire/config-backend.hpp>
#include <libudev.h>

void wf::plugin_interface_t::fini()
{}
Expand All @@ -27,18 +28,76 @@ std::shared_ptr<config::section_t> 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<config::section_t> 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<wf::config::section_t> section;
auto print_devpath = getenv("WF_PRINT_UDEV_DEVPATH");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what you are looking for here are the debug categories, they are rather simple to add, maybe an input-device category?

Check these here: https://github.com/WayfireWM/wayfire/blob/master/src/api/wayfire/debug.hpp#L42

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, this makes more sense. This way we can use wayfire -d input-device to get the additional message(s).


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")))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, wouldn't it be more appropriate to send this info over via IPC and print it in the scripts we already have for listing input devices? I can imagine that this would be much easier to use.

Otherwise, I think we can/should just print every property we are querying here (in the corresponding debug category).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree that we should require ipc for this task. It should be as easy as wayfire -d input-device (or plural, input-devices).

{
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);
}

Expand Down
106 changes: 75 additions & 31 deletions src/core/seat/input-manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
Expand Down
14 changes: 13 additions & 1 deletion src/core/seat/input-manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem like it should be in input-manager ... Because this is device-type-specific (mapping inputs to outputs works for tablets, touch and pointer). Ideally speaking, we'd want to add an input-device class for touch devices and load their specific options there, just like we do for pointers: https://github.com/WayfireWM/wayfire/blob/master/src/core/seat/pointing-device.hpp

And then we can create this device-specific object here:

At the very least, it seems like this should be somewhere in the input device class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, ok..


input_manager_t();
~input_manager_t() = default;
Expand Down
20 changes: 12 additions & 8 deletions src/core/seat/keyboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we'd want the same for pointer?

left_handed_mode.load_option("input/left_handed_mode");
middle_emulation.load_option("input/middle_emulation");
mouse_scroll_speed.load_option("input/mouse_scroll_speed");
mouse_cursor_speed.load_option("input/mouse_cursor_speed");
touchpad_cursor_speed.load_option("input/touchpad_cursor_speed");
touchpad_scroll_speed.load_option("input/touchpad_scroll_speed");
mouse_natural_scroll_enabled.load_option("input/mouse_natural_scroll");
touchpad_tap_enabled.load_option("input/tap_to_click");
touchpad_dwt_enabled.load_option("input/disable_touchpad_while_typing");
touchpad_dwmouse_enabled.load_option("input/disable_touchpad_while_mouse");
touchpad_natural_scroll_enabled.load_option("input/natural_scroll");
touchpad_drag_lock_enabled.load_option("input/drag_lock");
mouse_accel_profile.load_option("input/mouse_accel_profile");
touchpad_accel_profile.load_option("input/touchpad_accel_profile");
touchpad_click_method.load_option("input/click_method");
touchpad_scroll_method.load_option("input/scroll_method");

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking what other bits we might want this for.. adding for pointer makes sense to me, will update.

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.
Expand Down
3 changes: 2 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Loading