diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index debca7df..6b3227b7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -977,6 +977,20 @@ build-no-libwacom-nodeps@fedora:42: before_script: - dnf remove -y libwacom libwacom-devel +build-no-mtdev@fedora:42: + extends: + - .fedora-build@template + variables: + MESON_ARGS: "-Dmtdev=false" + +build-no-mtdev-nodeps@fedora:42: + extends: + - .fedora-build@template + variables: + MESON_ARGS: "-Dmtdev=false" + before_script: + - dnf remove -y mtdev mtdev-devel + build-docs@fedora:42: extends: - .fedora-build@template diff --git a/.gitlab-ci/ci.template b/.gitlab-ci/ci.template index e691235d..1a2c82d4 100644 --- a/.gitlab-ci/ci.template +++ b/.gitlab-ci/ci.template @@ -532,6 +532,20 @@ build-no-libwacom-nodeps@{{distro.name}}:{{version}}: before_script: - dnf remove -y libwacom libwacom-devel +build-no-mtdev@{{distro.name}}:{{version}}: + extends: + - .{{distro.name}}-build@template + variables: + MESON_ARGS: "-Dmtdev=false" + +build-no-mtdev-nodeps@{{distro.name}}:{{version}}: + extends: + - .{{distro.name}}-build@template + variables: + MESON_ARGS: "-Dmtdev=false" + before_script: + - dnf remove -y mtdev mtdev-devel + build-docs@{{distro.name}}:{{version}}: extends: - .{{distro.name}}-build@template diff --git a/meson.build b/meson.build index f8c0e8b1..33079a8a 100644 --- a/meson.build +++ b/meson.build @@ -162,7 +162,6 @@ config_h.set10('HAVE_INSTALLED_TESTS', get_option('install-tests')) # Dependencies pkgconfig = import('pkgconfig') dep_udev = dependency('libudev') -dep_mtdev = dependency('mtdev', version : '>= 1.1.0') dep_libevdev = dependency('libevdev', version: '>= 1.10.0') dep_lm = cc.find_library('m', required : false) @@ -172,6 +171,16 @@ dep_rt = cc.find_library('rt', required : false) includes_include = include_directories('include') includes_src = include_directories('src') +############ mtdev configuration ############ + +have_mtdev = get_option('mtdev') +config_h.set10('HAVE_MTDEV', have_mtdev) +if have_mtdev + dep_mtdev = dependency('mtdev', version : '>= 1.1.0') +else + dep_mtdev = declare_dependency() +endif + ############ libwacom configuration ############ have_libwacom = get_option('libwacom') @@ -399,6 +408,9 @@ src_libinput = src_libfilter + [ 'src/timer.c', 'src/util-libinput.c', ] +if have_mtdev + src_libinput += ['src/libinput-plugin-mtdev.c'] +endif deps_libinput = [ dep_mtdev, @@ -834,7 +846,6 @@ if get_option('tests') 'test/litest-device-mouse-wheel-click-count.c', 'test/litest-device-ms-nano-transceiver-mouse.c', 'test/litest-device-ms-surface-cover.c', - 'test/litest-device-protocol-a-touch-screen.c', 'test/litest-device-qemu-usb-tablet.c', 'test/litest-device-sony-vaio-keys.c', 'test/litest-device-synaptics-x220.c', @@ -892,6 +903,12 @@ if get_option('tests') 'test/litest-main.c', ] + if have_mtdev + litest_sources += [ + 'test/litest-device-protocol-a-touch-screen.c', + ] + endif + dep_dl = cc.find_library('dl') deps_litest = [ dep_libinput, diff --git a/meson_options.txt b/meson_options.txt index 047647f7..d872f331 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -10,6 +10,10 @@ option('libwacom', type: 'boolean', value: true, description: 'Use libwacom for tablet identification (default=true)') +option('mtdev', + type: 'boolean', + value: true, + description: 'Use mtdev for multitouch protocol A devices (default=true)') option('debug-gui', type: 'boolean', value: true, diff --git a/src/evdev-fallback.c b/src/evdev-fallback.c index 13dcd8a9..a01c6bdd 100644 --- a/src/evdev-fallback.c +++ b/src/evdev-fallback.c @@ -26,8 +26,6 @@ #include "config.h" -#include - #include "util-input-event.h" #include "evdev-fallback.h" @@ -1641,28 +1639,15 @@ fallback_dispatch_init_slots(struct fallback_dispatch *dispatch, /* We only handle the slotted Protocol B in libinput. Devices with ABS_MT_POSITION_* but not ABS_MT_SLOT require mtdev for conversion. */ - if (evdev_need_mtdev(device)) { - device->mtdev = mtdev_new_open(device->fd); - if (!device->mtdev) - return -1; - - /* pick 10 slots as default for type A - devices. */ - num_slots = 10; - active_slot = device->mtdev->caps.slot.value; - } else { - num_slots = libevdev_get_num_slots(device->evdev); - active_slot = libevdev_get_current_slot(evdev); - } + if (!libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT)) + return 0; + num_slots = libevdev_get_num_slots(device->evdev); + active_slot = libevdev_get_current_slot(evdev); slots = zalloc(num_slots * sizeof(struct mt_slot)); for (slot = 0; slot < num_slots; ++slot) { slots[slot].seat_slot = -1; - - if (evdev_need_mtdev(device)) - continue; - slots[slot].point.x = libevdev_get_slot_value(evdev, slot, ABS_MT_POSITION_X); slots[slot].point.y = diff --git a/src/evdev-plugin.c b/src/evdev-plugin.c index 0a5ea831..045d2771 100644 --- a/src/evdev-plugin.c +++ b/src/evdev-plugin.c @@ -23,8 +23,6 @@ #include "config.h" -#include - #include "util-macros.h" #include "util-mem.h" @@ -109,39 +107,7 @@ evdev_device_dispatch_frame(struct libinput_plugin *plugin, { struct evdev_device *device = evdev_device(libinput_device); uint64_t time = evdev_frame_get_time(frame); - - if (!device->mtdev) { - evdev_process_frame(device, frame, time); - } else { - size_t nevents; - struct evdev_event *events = evdev_frame_get_events(frame, &nevents); - for (size_t i = 0; i < nevents; i++) { - struct evdev_event *ev = &events[i]; - struct input_event e = evdev_event_to_input_event(ev, time); - mtdev_put_event(device->mtdev, &e); - } - - if (!mtdev_empty(device->mtdev)) { - _unref_(evdev_frame) *mtdev_frame = evdev_frame_new(256); - do { - struct input_event e; - - mtdev_get_event(device->mtdev, &e); - evdev_frame_append_input_event(mtdev_frame, &e); - if (e.type == EV_SYN && e.code == SYN_REPORT) { - evdev_frame_set_time(mtdev_frame, - input_event_time(&e)); - evdev_process_frame( - device, - mtdev_frame, - evdev_frame_get_time(mtdev_frame)); - /* mtdev can theoretically produce multiple - * frames */ - mtdev_frame = evdev_frame_unref(mtdev_frame); - } - } while (!mtdev_empty(device->mtdev)); - } - } + evdev_process_frame(device, frame, time); /* Discard event to make the plugin system aware we're done */ evdev_frame_reset(frame); diff --git a/src/evdev.c b/src/evdev.c index 36e79fb9..cc4dc40b 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -934,16 +933,6 @@ evdev_init_natural_scroll(struct evdev_device *device) device->base.config.natural_scroll = &device->scroll.config_natural; } -int -evdev_need_mtdev(struct evdev_device *device) -{ - struct libevdev *evdev = device->evdev; - - return (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) && - libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y) && - !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT)); -} - /* Fake MT devices have the ABS_MT_SLOT bit set because of the limited ABS_* range - they aren't MT devices, they just have too many ABS_ axes */ @@ -2271,7 +2260,7 @@ evdev_device_create(struct libinput_seat *seat, struct udev_device *udev_device) /* Use non-blocking mode so that we can loop on read on * evdev_device_data() until all events on the fd are - * read. mtdev_get() also expects this. */ + * read. */ fd = open_restricted(libinput, devnode, O_RDWR | O_NONBLOCK | O_CLOEXEC); if (fd < 0) { log_info(libinput, @@ -2304,7 +2293,6 @@ evdev_device_create(struct libinput_seat *seat, struct udev_device *udev_device) libinput); device->seat_caps = EVDEV_DEVICE_NO_CAPABILITIES; device->is_mt = 0; - device->mtdev = NULL; device->udev_device = udev_device_ref(udev_device); device->dispatch = NULL; device->fd = fd; @@ -2659,13 +2647,9 @@ evdev_device_get_touch_count(struct evdev_device *device) ntouches = libevdev_get_num_slots(device->evdev); if (ntouches == -1) { - /* mtdev devices have multitouch but we don't know - * how many. Otherwise, any touch device with num_slots of + /* any touch device with num_slots of * -1 is a single-touch device */ - if (device->mtdev) - ntouches = 0; - else - ntouches = 1; + ntouches = 1; } return ntouches; @@ -2875,11 +2859,6 @@ evdev_device_suspend(struct evdev_device *device) device->source = NULL; } - if (device->mtdev) { - mtdev_close_delete(device->mtdev); - device->mtdev = NULL; - } - if (device->fd != -1) { close_restricted(libinput, device->fd); device->fd = -1; @@ -2919,12 +2898,6 @@ evdev_device_resume(struct evdev_device *device) device->fd = fd; - if (evdev_need_mtdev(device)) { - device->mtdev = mtdev_new_open(device->fd); - if (!device->mtdev) - return -ENODEV; - } - libevdev_change_fd(device->evdev, fd); libevdev_set_clock_id(device->evdev, CLOCK_MONOTONIC); @@ -2938,10 +2911,8 @@ evdev_device_resume(struct evdev_device *device) } while (status == LIBEVDEV_READ_STATUS_SYNC); device->source = libinput_add_fd(libinput, fd, evdev_device_dispatch, device); - if (!device->source) { - mtdev_close_delete(device->mtdev); + if (!device->source) return -ENOMEM; - } evdev_notify_resumed_device(device); diff --git a/src/evdev.h b/src/evdev.h index d3aa7f0f..9be84df3 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -192,7 +192,6 @@ struct evdev_device { struct ratelimit nonpointer_rel_limit; /* ratelimit for REL_* events from non-pointer devices */ uint32_t model_flags; - struct mtdev *mtdev; struct { const struct input_absinfo *absinfo_x, *absinfo_y; @@ -454,9 +453,6 @@ evdev_totem_create(struct evdev_device *device); bool evdev_is_fake_mt_device(struct evdev_device *device); -int -evdev_need_mtdev(struct evdev_device *device); - void evdev_device_led_update(struct evdev_device *device, enum libinput_led leds); diff --git a/src/libinput-plugin-mtdev.c b/src/libinput-plugin-mtdev.c new file mode 100644 index 00000000..4796af76 --- /dev/null +++ b/src/libinput-plugin-mtdev.c @@ -0,0 +1,234 @@ +/* + * Copyright © 2010 Intel Corporation + * Copyright © 2013 Jonas Ådahl + * Copyright © 2013-2017 Red Hat, Inc. + * Copyright © 2017 James Ye + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "util-list.h" +#include "util-mem.h" +#include "util-strings.h" + +#include "evdev-frame.h" +#include "libinput-log.h" +#include "libinput-plugin-mtdev.h" +#include "libinput-plugin.h" +#include "libinput-util.h" + +struct plugin_device { + struct list link; + struct libinput_device *device; + + struct mtdev *mtdev; +}; + +struct plugin_data { + struct list devices; +}; + +static void +plugin_device_destroy(struct plugin_device *device) +{ + libinput_device_unref(device->device); + list_remove(&device->link); + mtdev_close_delete(device->mtdev); + free(device); +} + +DEFINE_DESTROY_CLEANUP_FUNC(plugin_device); + +static void +plugin_data_destroy(void *d) +{ + struct plugin_data *data = d; + + struct plugin_device *device; + list_for_each_safe(device, &data->devices, link) { + plugin_device_destroy(device); + } + + free(data); +} + +DEFINE_DESTROY_CLEANUP_FUNC(plugin_data); + +static void +plugin_destroy(struct libinput_plugin *libinput_plugin) +{ + struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin); + plugin_data_destroy(plugin); +} + +static void +mtdev_plugin_device_handle_frame(struct libinput_plugin *libinput_plugin, + struct plugin_device *device, + struct evdev_frame *frame) +{ + uint64_t time = evdev_frame_get_time(frame); + size_t nevents; + struct evdev_event *events = evdev_frame_get_events(frame, &nevents); + for (size_t i = 0; i < nevents; i++) { + struct evdev_event *ev = &events[i]; + struct input_event e = evdev_event_to_input_event(ev, time); + mtdev_put_event(device->mtdev, &e); + } + evdev_frame_reset(frame); + + while (!mtdev_empty(device->mtdev)) { + struct input_event e; + + mtdev_get_event(device->mtdev, &e); + evdev_frame_append_input_event(frame, &e); + if (e.type == EV_SYN && e.code == SYN_REPORT) { + evdev_frame_set_time(frame, input_event_time(&e)); + /* mtdev can theoretically produce multiple frames but I + * dont think it ever does */ + if (!mtdev_empty(device->mtdev)) { + plugin_log_bug(libinput_plugin, + "Didn't expect multiple frames"); + break; + } + } + } +} + +static void +mtdev_plugin_evdev_frame(struct libinput_plugin *libinput_plugin, + struct libinput_device *device, + struct evdev_frame *frame) +{ + struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin); + struct plugin_device *pd; + + list_for_each(pd, &plugin->devices, link) { + if (pd->device == device) { + mtdev_plugin_device_handle_frame(libinput_plugin, pd, frame); + break; + } + } +} + +static int +mtdev_needed(struct libevdev *evdev) +{ + return (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) && + libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y) && + !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT)); +} + +static void +mtdev_plugin_device_new(struct libinput_plugin *libinput_plugin, + struct libinput_device *device, + struct libevdev *evdev, + struct udev_device *udev) +{ + if (!mtdev_needed(evdev)) + return; + + libinput_plugin_enable_device_event_frame(libinput_plugin, device, true); + + struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin); + _destroy_(plugin_device) *pd = zalloc(sizeof(*pd)); + pd->device = libinput_device_ref(device); + pd->mtdev = mtdev_new(); + /* Shouldn't ever happen so no need to warn */ + if (!pd->mtdev) { + libevdev_disable_event_code(evdev, EV_ABS, ABS_MT_POSITION_X); + libevdev_disable_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y); + return; + } + mtdev_init(pd->mtdev); + + unsigned int codes[] = { + ABS_MT_POSITION_X, ABS_MT_POSITION_Y, ABS_MT_TOUCH_MAJOR, + ABS_MT_TOUCH_MINOR, ABS_MT_WIDTH_MAJOR, ABS_MT_WIDTH_MINOR, + ABS_MT_ORIENTATION, + }; + + ARRAY_FOR_EACH(codes, code) { + const struct input_absinfo *abs = libevdev_get_abs_info(evdev, *code); + if (!abs) + continue; + + mtdev_set_mt_event(pd->mtdev, *code, abs->value); + mtdev_set_abs_minimum(pd->mtdev, *code, abs->minimum); + mtdev_set_abs_maximum(pd->mtdev, *code, abs->maximum); + mtdev_set_abs_fuzz(pd->mtdev, *code, abs->fuzz); + mtdev_set_abs_resolution(pd->mtdev, *code, abs->resolution); + } + + /* Let's pretend we have slots */ + const struct input_absinfo slot = { + .minimum = 0, + .maximum = 9, + .value = 0, + }; + const struct input_absinfo tid = { + .minimum = 0, + .maximum = 65535, + .value = 0, + }; + libevdev_enable_event_code(evdev, EV_ABS, ABS_MT_SLOT, &slot); + libevdev_enable_event_code(evdev, EV_ABS, ABS_MT_TRACKING_ID, &tid); + + list_take_append(&plugin->devices, pd, link); +} + +static void +mtdev_plugin_device_removed(struct libinput_plugin *libinput_plugin, + struct libinput_device *device) +{ + struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin); + struct plugin_device *dev; + list_for_each_safe(dev, &plugin->devices, link) { + if (dev->device == device) { + plugin_device_destroy(dev); + return; + } + } +} + +static const struct libinput_plugin_interface interface = { + .run = NULL, + .destroy = plugin_destroy, + .device_new = mtdev_plugin_device_new, + .device_ignored = mtdev_plugin_device_removed, + .device_added = NULL, + .device_removed = mtdev_plugin_device_removed, + .evdev_frame = mtdev_plugin_evdev_frame, +}; + +void +libinput_mtdev_plugin(struct libinput *libinput) +{ + _destroy_(plugin_data) *plugin = zalloc(sizeof(*plugin)); + list_init(&plugin->devices); + + _unref_(libinput_plugin) *p = + libinput_plugin_new(libinput, "mtdev", &interface, steal(&plugin)); +} diff --git a/src/libinput-plugin-mtdev.h b/src/libinput-plugin-mtdev.h new file mode 100644 index 00000000..7c0e7825 --- /dev/null +++ b/src/libinput-plugin-mtdev.h @@ -0,0 +1,30 @@ +/* + * Copyright © 2025 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include "libinput-plugin.h" +#include "libinput.h" + +void +libinput_mtdev_plugin(struct libinput *libinput); diff --git a/src/libinput-plugin.c b/src/libinput-plugin.c index 7d83205f..c14ae337 100644 --- a/src/libinput-plugin.c +++ b/src/libinput-plugin.c @@ -31,8 +31,8 @@ #include "evdev-plugin.h" #include "libinput-plugin-button-debounce.h" #include "libinput-plugin-mouse-wheel.h" +#include "libinput-plugin-mtdev.h" #include "libinput-plugin-private.h" -#include "libinput-plugin-system.h" #include "libinput-plugin-tablet-double-tool.h" #include "libinput-plugin-tablet-eraser-button.h" #include "libinput-plugin-tablet-forced-tool.h" @@ -382,6 +382,9 @@ libinput_plugin_system_load_internal_plugins(struct libinput *libinput, /* FIXME: this should really be one of the first in the sequence * so plugins don't have to take care of this? */ +#if HAVE_MTDEV + libinput_mtdev_plugin(libinput); +#endif libinput_tablet_plugin_forced_tool(libinput); libinput_tablet_plugin_double_tool(libinput); libinput_tablet_plugin_proximity_timer(libinput); diff --git a/test/test-touch.c b/test/test-touch.c index caa9aa37..331ec93e 100644 --- a/test/test-touch.c +++ b/test/test-touch.c @@ -1094,12 +1094,13 @@ START_TEST(touch_count_mt) } END_TEST -START_TEST(touch_count_unknown) +START_TEST(touch_count_mtdev) { struct litest_device *dev = litest_current_device(); struct libinput_device *device = dev->libinput_device; - litest_assert_int_eq(libinput_device_touch_get_touch_count(device), 0); + /* We hardcode this to 10 */ + litest_assert_int_eq(libinput_device_touch_get_touch_count(device), 10); } END_TEST @@ -1352,9 +1353,11 @@ TEST_COLLECTION(touch) litest_add(fake_mt_exists, LITEST_FAKE_MT, LITEST_ANY); litest_add(fake_mt_no_touch_events, LITEST_FAKE_MT, LITEST_ANY); +#if HAVE_MTDEV litest_add(touch_protocol_a_init, LITEST_PROTOCOL_A, LITEST_ANY); litest_add(touch_protocol_a_touch, LITEST_PROTOCOL_A, LITEST_ANY); litest_add(touch_protocol_a_2fg_touch, LITEST_PROTOCOL_A, LITEST_ANY); +#endif litest_with_parameters(params, "axis", 'I', 2, litest_named_i32(ABS_X), litest_named_i32(ABS_Y)) { litest_add_parametrized(touch_initial_state, LITEST_TOUCH, LITEST_PROTOCOL_A, params); @@ -1372,8 +1375,10 @@ TEST_COLLECTION(touch) litest_add(touch_count_st, LITEST_SINGLE_TOUCH, LITEST_TOUCHPAD); litest_add(touch_count_mt, LITEST_TOUCH, LITEST_SINGLE_TOUCH|LITEST_PROTOCOL_A); - litest_add(touch_count_unknown, LITEST_PROTOCOL_A, LITEST_ANY); +#if HAVE_MTDEV + litest_add(touch_count_mtdev, LITEST_PROTOCOL_A, LITEST_ANY); litest_add(touch_count_invalid, LITEST_ANY, LITEST_TOUCH|LITEST_SINGLE_TOUCH|LITEST_PROTOCOL_A); +#endif litest_add(touch_palm_detect_tool_palm, LITEST_TOUCH, LITEST_ANY); litest_add(touch_palm_detect_tool_palm_on_off, LITEST_TOUCH, LITEST_ANY);