From ccb60fb6a96c883c5b42611fea129b1eab271d51 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 11 Jul 2024 09:51:05 -0400 Subject: [PATCH] (#620) Enabling keyboard LEDs when the modifier state changes --- include/common/mir/input/keyboard_leds.h | 40 ++ .../mir/input/led_observer_registrar.h | 51 ++ src/common/CMakeLists.txt | 1 + .../server/mir/default_server_configuration.h | 9 +- .../server/mir/input/xkb_mapper_registrar.h | 147 +++++ src/platforms/evdev/libinput_device.cpp | 13 + src/platforms/evdev/libinput_device.h | 6 +- src/server/input/CMakeLists.txt | 1 + src/server/input/default_configuration.cpp | 21 +- src/server/input/default_input_device_hub.cpp | 16 +- src/server/input/default_input_device_hub.h | 5 +- src/server/input/xkb_mapper_registrar.cpp | 569 ++++++++++++++++++ src/server/symbols.map | 33 + .../doubles/mock_led_observer_registrar.h | 40 ++ .../include/mir/test/doubles/mock_libinput.h | 1 + .../input/test_single_seat_setup.cpp | 9 +- tests/mir_test_doubles/mock_libinput.cpp | 5 + .../evdev/test_evdev_device_detection.cpp | 5 + .../input/test_default_input_device_hub.cpp | 5 +- .../input/test_seat_input_device_tracker.cpp | 4 +- 20 files changed, 966 insertions(+), 15 deletions(-) create mode 100644 include/common/mir/input/keyboard_leds.h create mode 100644 include/platform/mir/input/led_observer_registrar.h create mode 100644 src/include/server/mir/input/xkb_mapper_registrar.h create mode 100644 src/server/input/xkb_mapper_registrar.cpp create mode 100644 tests/include/mir/test/doubles/mock_led_observer_registrar.h diff --git a/include/common/mir/input/keyboard_leds.h b/include/common/mir/input/keyboard_leds.h new file mode 100644 index 00000000000..6e62d580e21 --- /dev/null +++ b/include/common/mir/input/keyboard_leds.h @@ -0,0 +1,40 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_INPUT_KEYBOARD_LEDS_H +#define MIR_INPUT_KEYBOARD_LEDS_H + +#include "mir/flags.h" +#include + +namespace mir +{ +namespace input +{ +enum class KeyboardLed : uint32_t +{ + caps_lock = (1 << 0), + num_lock = (1 << 1), + scroll_lock = (1 << 2) +}; + +KeyboardLed mir_enable_enum_bit_operators(KeyboardLed); +using KeyboardLeds = mir::Flags; + +} +} + +#endif //MIR_INPUT_KEYBOARD_LEDS_H diff --git a/include/platform/mir/input/led_observer_registrar.h b/include/platform/mir/input/led_observer_registrar.h new file mode 100644 index 00000000000..d05b0a2e909 --- /dev/null +++ b/include/platform/mir/input/led_observer_registrar.h @@ -0,0 +1,51 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 or 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_INPUT_LED_OBSERVER_REGISTER_H +#define MIR_INPUT_LED_OBSERVER_REGISTER_H + +#include "mir/input/keyboard_leds.h" +#include "mir_toolkit/mir_input_device_types.h" +#include + +namespace mir +{ +namespace input +{ +class LedObserver +{ +public: + virtual ~LedObserver() = default; + virtual void leds_set(KeyboardLeds leds) = 0; +}; + +class LedObserverRegistrar +{ +public: + LedObserverRegistrar() = default; + virtual ~LedObserverRegistrar() = default; + + virtual void register_interest(std::weak_ptr const& observer, MirInputDeviceId id) = 0; + virtual void unregister_interest(LedObserver const& observer, MirInputDeviceId id) = 0; + +protected: + LedObserverRegistrar(LedObserverRegistrar const&) = delete; + LedObserverRegistrar& operator=(LedObserverRegistrar const&) = delete; +}; +} +} + +#endif //MIR_INPUT_LED_OBSERVER_REGISTER_H diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 45390f52706..5bf5e15e28a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -62,6 +62,7 @@ add_library(mircommon SHARED ${PROJECT_SOURCE_DIR}/include/common/mir/input/mir_touchpad_config.h ${PROJECT_SOURCE_DIR}/include/common/mir/input/mir_touchscreen_config.h ${PROJECT_SOURCE_DIR}/include/common/mir/input/mir_keyboard_config.h + ${PROJECT_SOURCE_DIR}/include/common/mir/input/keyboard_leds.h ${MIR_COMMON_SOURCES} ) diff --git a/src/include/server/mir/default_server_configuration.h b/src/include/server/mir/default_server_configuration.h index fefcaf19cad..f1d05bb7d5b 100644 --- a/src/include/server/mir/default_server_configuration.h +++ b/src/include/server/mir/default_server_configuration.h @@ -121,8 +121,14 @@ class TouchVisualizer; class CursorImages; class Seat; class KeyMapper; +class LedObserverRegistrar; +namespace receiver +{ +class XKBMapperRegistrar; +} } + namespace logging { class Logger; @@ -315,6 +321,7 @@ class DefaultServerConfiguration : public virtual ServerConfiguration virtual std::shared_ptr the_touch_visualizer(); virtual std::shared_ptr the_seat(); virtual std::shared_ptr the_key_mapper(); + virtual std::shared_ptr the_led_observer_registrar(); // new input reading related parts: virtual std::shared_ptr the_input_reading_multiplexer(); @@ -422,7 +429,7 @@ class DefaultServerConfiguration : public virtual ServerConfiguration CachedPtr decoration_manager; CachedPtr application_not_responding_detector; CachedPtr cookie_authority; - CachedPtr key_mapper; + CachedPtr xkb_mapper_registrar; std::shared_ptr console_services; std::shared_ptr decoration_strategy; diff --git a/src/include/server/mir/input/xkb_mapper_registrar.h b/src/include/server/mir/input/xkb_mapper_registrar.h new file mode 100644 index 00000000000..ff1e4e9f87f --- /dev/null +++ b/src/include/server/mir/input/xkb_mapper_registrar.h @@ -0,0 +1,147 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 or 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_INPUT_RECEIVER_XKB_MAPPER_H_ +#define MIR_INPUT_RECEIVER_XKB_MAPPER_H_ + +#include "mir/input/led_observer_registrar.h" +#include "mir/input/key_mapper.h" +#include "mir/input/keymap.h" +#include "mir/input/keyboard_leds.h" +#include "mir/optional_value.h" +#include "mir/events/xkb_modifiers.h" +#include "mir/observer_multiplexer.h" + +#include +#include +#include +#include +#include +#include + +namespace mir +{ +namespace input +{ + +using XKBContextPtr = std::unique_ptr; +XKBContextPtr make_unique_context(); + +using XKBStatePtr = std::unique_ptr; +using XKBComposeTablePtr = std::unique_ptr; +using XKBComposeStatePtr = std::unique_ptr; + +namespace receiver +{ + +class XKBMapperRegistrar : public KeyMapper, public LedObserverRegistrar +{ +public: + explicit XKBMapperRegistrar(Executor&); + + void set_key_state(MirInputDeviceId id, std::vector const& key_state) override; + void set_keymap_for_device(MirInputDeviceId id, std::shared_ptr map) override; + void set_keymap_for_all_devices(std::shared_ptr map) override; + void clear_keymap_for_device(MirInputDeviceId id) override; + void clear_all_keymaps() override; + void map_event(MirEvent& event) override; + MirInputEventModifiers modifiers() const override; + MirInputEventModifiers device_modifiers(MirInputDeviceId di) const override; + auto xkb_modifiers() const -> MirXkbModifiers override; + void register_interest(std::weak_ptr const& observer, MirInputDeviceId id) override; + void unregister_interest(LedObserver const& observer, MirInputDeviceId id) override; + +protected: + XKBMapperRegistrar(XKBMapperRegistrar const&) = delete; + XKBMapperRegistrar& operator=(XKBMapperRegistrar const&) = delete; + +private: + void set_keymap(MirInputDeviceId id, std::shared_ptr new_keymap); + void set_keymap(std::shared_ptr new_keymap); + void update_modifier(); + + std::mutex mutable guard; + + struct ComposeState + { + ComposeState(XKBComposeTablePtr const& table); + xkb_keysym_t update_state(xkb_keysym_t mapped_key, MirKeyboardAction action, std::string& text); + private: + XKBComposeStatePtr state; + std::unordered_set consumed_keys; + mir::optional_value> last_composed_key; + }; + + struct XkbMappingState + { + class XkbMappingStateLedRegistrar : public ObserverMultiplexer + { + public: + explicit XkbMappingStateLedRegistrar(Executor&); + void leds_set(KeyboardLeds leds) override; + }; + + explicit XkbMappingState( + std::shared_ptr keymap, + std::shared_ptr compiled_keymap, + Executor& executor); + void set_key_state(std::vector const& key_state); + + bool update_and_map(MirEvent& event, ComposeState* compose_state); + MirInputEventModifiers modifiers() const; + auto xkb_modifiers() const -> MirXkbModifiers; + void notify_leds_changed(); + XkbMappingStateLedRegistrar& get_registrar(); + private: + /// Returns a pair containing the keysym for the given scancode and if any XKB modifiers have been changed + auto update_state( + uint32_t scan_code, + MirKeyboardAction direction, + ComposeState* compose_state, + std::string& text) -> std::pair; + void press_modifier(MirInputEventModifiers mod); + void release_modifier(MirInputEventModifiers mod); + + std::shared_ptr const keymap; + std::shared_ptr const compiled_keymap; + XKBStatePtr state; + MirInputEventModifiers modifier_state{0}; + xkb_led_index_t num_led; + xkb_led_index_t caps_led; + xkb_led_index_t scroll_led; + XkbMappingStateLedRegistrar registrar; + }; + + XkbMappingState* get_keymapping_state(MirInputDeviceId id); + ComposeState* get_compose_state(MirInputDeviceId id); + + Executor& executor; + XKBContextPtr context; + std::shared_ptr default_keymap; + std::shared_ptr default_compiled_keymap; + XKBComposeTablePtr compose_table; + MirXkbModifiers xkb_modifiers_; + std::optional last_device_id; + + mir::optional_value modifier_state; + std::unordered_map> device_mapping; + std::unordered_map> device_composing; +}; +} +} +} + +#endif // MIR_INPUT_RECEIVER_XKB_MAPPER_H_ diff --git a/src/platforms/evdev/libinput_device.cpp b/src/platforms/evdev/libinput_device.cpp index c98ff74b11b..17ed5fc3b9b 100644 --- a/src/platforms/evdev/libinput_device.cpp +++ b/src/platforms/evdev/libinput_device.cpp @@ -106,6 +106,19 @@ auto get_axis_source(libinput_event_pointer* pointer) -> MirPointerAxisSource } } +void mie::LibInputDevice::leds_set(KeyboardLeds leds) +{ + int led = 0; + if (contains(leds, mir::input::KeyboardLed::caps_lock)) + led |= LIBINPUT_LED_CAPS_LOCK; + if (contains(leds, mir::input::KeyboardLed::num_lock)) + led |= LIBINPUT_LED_NUM_LOCK; + if (contains(leds, mir::input::KeyboardLed::scroll_lock)) + led |= LIBINPUT_LED_SCROLL_LOCK; + + libinput_device_led_update(device(), static_cast(led)); +} + mie::LibInputDevice::LibInputDevice(std::shared_ptr const& report, LibInputDevicePtr dev) : report{report}, device_{std::move(dev)}, pointer_pos{0, 0}, button_state{0} { diff --git a/src/platforms/evdev/libinput_device.h b/src/platforms/evdev/libinput_device.h index 46f53be78b2..205c3faa203 100644 --- a/src/platforms/evdev/libinput_device.h +++ b/src/platforms/evdev/libinput_device.h @@ -23,6 +23,8 @@ #include "mir/input/event_builder.h" #include "mir/input/input_device.h" #include "mir/input/input_device_info.h" +#include "mir/input/keyboard_leds.h" +#include "mir/input/led_observer_registrar.h" #include "mir/input/touchscreen_settings.h" #include "mir/geometry/point.h" @@ -43,7 +45,7 @@ class OutputInfo; class InputReport; namespace evdev { -class LibInputDevice : public input::InputDevice +class LibInputDevice : public input::InputDevice, public mir::input::LedObserver { public: LibInputDevice(std::shared_ptr const& report, LibInputDevicePtr dev); @@ -58,6 +60,8 @@ class LibInputDevice : public input::InputDevice optional_value get_touchscreen_settings() const override; void apply_settings(TouchscreenSettings const&) override; + void leds_set(KeyboardLeds leds) override; + void process_event(libinput_event* event); ::libinput_device* device() const; ::libinput_device_group* group(); diff --git a/src/server/input/CMakeLists.txt b/src/server/input/CMakeLists.txt index 784b9d0c99f..b6c28e32b67 100644 --- a/src/server/input/CMakeLists.txt +++ b/src/server/input/CMakeLists.txt @@ -27,6 +27,7 @@ set( seat_observer_multiplexer.h idle_poking_dispatcher.cpp virtual_input_device.cpp + xkb_mapper_registrar.cpp ${PROJECT_SOURCE_DIR}/src/include/server/mir/input/seat_observer.h ${PROJECT_SOURCE_DIR}/src/include/server/mir/input/input_dispatcher.h ${PROJECT_SOURCE_DIR}/src/include/server/mir/input/seat.h diff --git a/src/server/input/default_configuration.cpp b/src/server/input/default_configuration.cpp index 5475a24abc4..b6260aa8c06 100644 --- a/src/server/input/default_configuration.cpp +++ b/src/server/input/default_configuration.cpp @@ -36,8 +36,9 @@ #include "mir/input/touch_visualizer.h" #include "mir/input/input_probe.h" #include "mir/input/platform.h" -#include "mir/input/xkb_mapper.h" +#include "mir/input/xkb_mapper_registrar.h" #include "mir/input/vt_filter.h" +#include "mir/input/device.h" #include "mir/options/configuration.h" #include "mir/options/option.h" #include "mir/dispatch/multiplexing_dispatchable.h" @@ -313,7 +314,8 @@ std::shared_ptr mir::DefaultServerConfiguration::the_ the_input_reading_multiplexer(), the_clock(), the_key_mapper(), - the_server_status_listener()); + the_server_status_listener(), + the_led_observer_registrar()); // lp:1675357: KeyRepeatDispatcher must be informed about removed input devices, otherwise // pressed keys get repeated indefinitely @@ -325,13 +327,22 @@ std::shared_ptr mir::DefaultServerConfiguration::the_ std::shared_ptr mir::DefaultServerConfiguration::the_key_mapper() { - return key_mapper( - []() + return xkb_mapper_registrar( + [default_executor=the_main_loop()]() { - return std::make_shared(); + return std::make_shared(*default_executor); }); } +std::shared_ptr mir::DefaultServerConfiguration::the_led_observer_registrar() +{ + return xkb_mapper_registrar( + [default_executor=the_main_loop()]() + { + return std::make_shared(*default_executor); + }); +} + std::shared_ptr mir::DefaultServerConfiguration::the_seat_observer() { return seat_observer_multiplexer( diff --git a/src/server/input/default_input_device_hub.cpp b/src/server/input/default_input_device_hub.cpp index 0cbc31887f8..6725f8edc7c 100644 --- a/src/server/input/default_input_device_hub.cpp +++ b/src/server/input/default_input_device_hub.cpp @@ -32,6 +32,8 @@ #include "mir/log.h" #include "boost/throw_exception.hpp" +#include "mir/input/led_observer_registrar.h" +#include "mir/input/key_mapper.h" #include #include @@ -199,13 +201,15 @@ mi::DefaultInputDeviceHub::DefaultInputDeviceHub( std::shared_ptr const& input_multiplexer, std::shared_ptr const& clock, std::shared_ptr const& key_mapper, - std::shared_ptr const& server_status_listener) + std::shared_ptr const& server_status_listener, + std::shared_ptr led_observer_registrar) : seat{seat}, input_dispatchable{input_multiplexer}, device_queue(std::make_shared()), clock(clock), key_mapper(key_mapper), server_status_listener(server_status_listener), + led_observer_registrar{std::move(led_observer_registrar)}, device_id_generator{0} { input_dispatchable->add_watch(device_queue); @@ -492,6 +496,11 @@ auto mi::DefaultInputDeviceHub::add_device(std::shared_ptr const& d seat->add_device(*handle); dev->start(seat, input_dispatchable); + if (auto const observer = std::dynamic_pointer_cast(device)) + { + led_observer_registrar->register_interest(observer, handle->id()); + } + return handle; } else @@ -534,6 +543,11 @@ void mi::DefaultInputDeviceHub::remove_device(std::shared_ptr const BOOST_THROW_EXCEPTION(std::logic_error("Input device not managed by server")); } + if (auto const observer = std::dynamic_pointer_cast(device)) + { + led_observer_registrar->unregister_interest(*observer, (*pos)->id()); + } + devices.erase(pos, end(devices)); } diff --git a/src/server/input/default_input_device_hub.h b/src/server/input/default_input_device_hub.h index 18d236d1334..cf362a92fd5 100644 --- a/src/server/input/default_input_device_hub.h +++ b/src/server/input/default_input_device_hub.h @@ -54,6 +54,7 @@ namespace input { class InputSink; class InputDeviceObserver; +class LedObserverRegistrar; class DefaultDevice; class Seat; class KeyMapper; @@ -86,7 +87,8 @@ class DefaultInputDeviceHub : std::shared_ptr const& input_multiplexer, std::shared_ptr const& clock, std::shared_ptr const& key_mapper, - std::shared_ptr const& server_status_listener); + std::shared_ptr const& server_status_listener, + std::shared_ptr led_observer_registrar); // InputDeviceRegistry - calls from mi::Platform auto add_device(std::shared_ptr const& device) -> std::weak_ptr override; @@ -118,6 +120,7 @@ class DefaultInputDeviceHub : std::shared_ptr const clock; std::shared_ptr const key_mapper; std::shared_ptr const server_status_listener; + std::shared_ptr const led_observer_registrar; ThreadSafeList> observers; /// Does not guarantee it's own threadsafety, non-const methods should not be called from multiple threads at once diff --git a/src/server/input/xkb_mapper_registrar.cpp b/src/server/input/xkb_mapper_registrar.cpp new file mode 100644 index 00000000000..21fd72cfe3e --- /dev/null +++ b/src/server/input/xkb_mapper_registrar.cpp @@ -0,0 +1,569 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 or 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "mir/input/xkb_mapper_registrar.h" +#include "mir/input/keymap.h" +#include "mir/input/keyboard_leds.h" +#include "mir/events/event_private.h" +#include "mir/events/event_builders.h" + +#include + +#include + +namespace mi = mir::input; +namespace mev = mir::events; +namespace mircv = mi::receiver; + +namespace +{ + +char const* get_locale_from_environment() +{ + char const* loc = getenv("LC_ALL"); + if (!loc) + loc = getenv("LC_CTYPE"); + if (!loc) + loc = getenv("LANG"); + if (!loc) + loc = "C"; + return loc; +} + +uint32_t constexpr to_xkb_scan_code(uint32_t evdev_scan_code) +{ + // xkb scancodes are offset by 8 from evdev scancodes for compatibility with X protocol. + return evdev_scan_code + 8; +} + +MirInputEventModifiers modifier_from_xkb_scan_code(uint32_t key) +{ + switch(key) + { + case to_xkb_scan_code(KEY_RIGHTSHIFT): return mir_input_event_modifier_shift_right; + case to_xkb_scan_code(KEY_LEFTSHIFT): return mir_input_event_modifier_shift_left; + case to_xkb_scan_code(KEY_RIGHTALT): return mir_input_event_modifier_alt_right; + case to_xkb_scan_code(KEY_LEFTALT): return mir_input_event_modifier_alt_left; + case to_xkb_scan_code(KEY_RIGHTCTRL): return mir_input_event_modifier_ctrl_right; + case to_xkb_scan_code(KEY_LEFTCTRL): return mir_input_event_modifier_ctrl_left; + case to_xkb_scan_code(KEY_LEFTMETA): return mir_input_event_modifier_meta_left; + case to_xkb_scan_code(KEY_RIGHTMETA): return mir_input_event_modifier_meta_right; + case to_xkb_scan_code(KEY_CAPSLOCK): return mir_input_event_modifier_caps_lock; + case to_xkb_scan_code(KEY_SCREENLOCK): return mir_input_event_modifier_scroll_lock; + case to_xkb_scan_code(KEY_NUMLOCK): return mir_input_event_modifier_num_lock; + default: return MirInputEventModifiers{0}; + } +} + +bool is_toggle_modifier(MirInputEventModifiers key) +{ + return key == mir_input_event_modifier_caps_lock || + key == mir_input_event_modifier_scroll_lock || + key == mir_input_event_modifier_num_lock; +} + +MirInputEventModifiers expand_modifiers(MirInputEventModifiers modifiers) +{ + if (modifiers == 0) + return mir_input_event_modifier_none; + + if ((modifiers & mir_input_event_modifier_alt_left) || (modifiers & mir_input_event_modifier_alt_right)) + modifiers |= mir_input_event_modifier_alt; + + if ((modifiers & mir_input_event_modifier_ctrl_left) || (modifiers & mir_input_event_modifier_ctrl_right)) + modifiers |= mir_input_event_modifier_ctrl; + + if ((modifiers & mir_input_event_modifier_shift_left) || (modifiers & mir_input_event_modifier_shift_right)) + modifiers |= mir_input_event_modifier_shift; + + if ((modifiers & mir_input_event_modifier_meta_left) || (modifiers & mir_input_event_modifier_meta_right)) + modifiers |= mir_input_event_modifier_meta; + + return modifiers; +} + +mi::XKBComposeStatePtr make_unique_compose_state(mi::XKBComposeTablePtr const& table) +{ + return {xkb_compose_state_new(table.get(), XKB_COMPOSE_STATE_NO_FLAGS), &xkb_compose_state_unref}; +} + +mi::XKBComposeTablePtr make_unique_compose_table_from_locale(mi::XKBContextPtr const& context, std::string const& locale) +{ + return {xkb_compose_table_new_from_locale( + context.get(), + locale.c_str(), + XKB_COMPOSE_COMPILE_NO_FLAGS), + &xkb_compose_table_unref}; +} + +mi::XKBStatePtr make_unique_state(xkb_keymap* keymap) +{ + return {xkb_state_new(keymap), xkb_state_unref}; +} +} + +mi::XKBContextPtr mi::make_unique_context() +{ + auto context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) + { + fatal_error("Failed to create XKB context"); + } + return {context, &xkb_context_unref}; +} + +mircv::XKBMapperRegistrar::XkbMappingState::XkbMappingStateLedRegistrar::XkbMappingStateLedRegistrar(mir::Executor& executor) + : ObserverMultiplexer(executor) +{ +} + +void mircv::XKBMapperRegistrar::XkbMappingState::XkbMappingStateLedRegistrar::leds_set(mir::input::KeyboardLeds leds) +{ + for_each_observer(&LedObserver::leds_set, leds); +} + +mircv::XKBMapperRegistrar::XKBMapperRegistrar(Executor& executor) : + executor(executor), + context{make_unique_context()}, + compose_table{make_unique_compose_table_from_locale(context, get_locale_from_environment())} +{ +} + +void mircv::XKBMapperRegistrar::set_key_state(MirInputDeviceId id, std::vector const& key_state) +{ + std::lock_guard lg(guard); + + auto mapping_state = get_keymapping_state(id); + if (mapping_state) + mapping_state->set_key_state(key_state); +} + +void mircv::XKBMapperRegistrar::update_modifier() +{ + modifier_state = mir::optional_value{}; + xkb_modifiers_ = {}; + if (!device_mapping.empty()) + { + MirInputEventModifiers new_modifier = 0; + for (auto const& mapping_state : device_mapping) + { + new_modifier |= mapping_state.second->modifiers(); + auto const device_xkb_modifiers = mapping_state.second->xkb_modifiers(); + xkb_modifiers_.depressed |= device_xkb_modifiers.depressed; + xkb_modifiers_.latched |= device_xkb_modifiers.latched; + xkb_modifiers_.locked |= device_xkb_modifiers.locked; + if (last_device_id && mapping_state.first == last_device_id.value()) + { + xkb_modifiers_.effective_layout = device_xkb_modifiers.effective_layout; + } + } + + modifier_state = new_modifier; + } +} + +void mircv::XKBMapperRegistrar::map_event(MirEvent& ev) +{ + std::lock_guard lg(guard); + + auto type = mir_event_get_type(&ev); + + if (type == mir_event_type_input) + { + auto input_event = mir_event_get_input_event(&ev); + auto input_type = mir_input_event_get_type(input_event); + auto device_id = mir_input_event_get_device_id(input_event); + + if (input_type == mir_input_event_type_key) + { + auto mapping_state = get_keymapping_state(device_id); + if (mapping_state) + { + last_device_id = device_id; + auto compose_state = get_compose_state(device_id); + if (mapping_state->update_and_map(ev, compose_state)) + { + update_modifier(); + mapping_state->notify_leds_changed(); + } + } + + auto& key_event = *ev.to_input()->to_keyboard(); + if (!key_event.xkb_modifiers()) + { + key_event.set_xkb_modifiers(xkb_modifiers_); + } + } + else if (modifier_state.is_set()) + { + mev::set_modifier(ev, expand_modifiers(modifier_state.value())); + } + } +} + +mircv::XKBMapperRegistrar::XkbMappingState* mircv::XKBMapperRegistrar::get_keymapping_state(MirInputDeviceId id) +{ + auto dev_keymap = device_mapping.find(id); + + if (dev_keymap != end(device_mapping)) + { + return dev_keymap->second.get(); + } + if (default_keymap) + { + auto mapping_state = std::make_unique( + default_keymap, default_compiled_keymap, executor); + decltype(device_mapping.begin()) insertion_pos; + std::tie(insertion_pos, std::ignore) = + device_mapping.emplace( + std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple(std::move(mapping_state))); + + return insertion_pos->second.get(); + } + return nullptr; +} + +void mircv::XKBMapperRegistrar::set_keymap_for_all_devices(std::shared_ptr new_keymap) +{ + set_keymap(new_keymap); +} + +void mircv::XKBMapperRegistrar::set_keymap(std::shared_ptr new_keymap) +{ + std::lock_guard lg(guard); + default_keymap = std::move(new_keymap); + default_compiled_keymap = default_keymap->make_unique_xkb_keymap(context.get()); + device_mapping.clear(); +} + +void mircv::XKBMapperRegistrar::set_keymap_for_device(MirInputDeviceId id, std::shared_ptr new_keymap) +{ + set_keymap(id, std::move(new_keymap)); +} + +void mircv::XKBMapperRegistrar::set_keymap(MirInputDeviceId id, std::shared_ptr new_keymap) +{ + std::lock_guard lg(guard); + + auto compiled_keymap = new_keymap->make_unique_xkb_keymap(context.get()); + auto mapping_state = std::make_unique(std::move(new_keymap), std::move(compiled_keymap), executor); + + device_mapping.erase(id); + device_mapping.emplace( + std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple(std::move(mapping_state))); +} + +void mircv::XKBMapperRegistrar::clear_all_keymaps() +{ + std::lock_guard lg(guard); + default_keymap.reset(); + device_mapping.clear(); + update_modifier(); +} + +void mircv::XKBMapperRegistrar::clear_keymap_for_device(MirInputDeviceId id) +{ + std::lock_guard lg(guard); + device_mapping.erase(id); + update_modifier(); +} + +MirInputEventModifiers mircv::XKBMapperRegistrar::modifiers() const +{ + std::lock_guard lg(guard); + if (modifier_state.is_set()) + return expand_modifiers(modifier_state.value()); + return mir_input_event_modifier_none; +} + +MirInputEventModifiers mircv::XKBMapperRegistrar::device_modifiers(MirInputDeviceId id) const +{ + std::lock_guard lg(guard); + + auto it = device_mapping.find(id); + if (it == end(device_mapping)) + return mir_input_event_modifier_none; + return expand_modifiers(it->second->modifiers()); +} + +auto mircv::XKBMapperRegistrar::xkb_modifiers() const -> MirXkbModifiers +{ + std::lock_guard lg(guard); + return xkb_modifiers_; +} + +void mircv::XKBMapperRegistrar::register_interest(std::weak_ptr const& observer, MirInputDeviceId id) +{ + auto it = device_mapping.find(id); + if (it == end(device_mapping)) + return; + + auto& registrar = it->second->get_registrar(); + registrar.register_interest(observer); +} + +void mircv::XKBMapperRegistrar::unregister_interest(LedObserver const& observer, MirInputDeviceId id) +{ + auto it = device_mapping.find(id); + if (it == end(device_mapping)) + return; + + auto& registrar = it->second->get_registrar(); + registrar.unregister_interest(observer); +} + +mircv::XKBMapperRegistrar::XkbMappingState::XkbMappingState( + std::shared_ptr keymap, + std::shared_ptr compiled_keymap, + Executor& executor) + : keymap{std::move(keymap)}, + compiled_keymap{std::move(compiled_keymap)}, + state{make_unique_state(this->compiled_keymap.get())}, + num_led{xkb_keymap_led_get_index(this->compiled_keymap.get(), XKB_LED_NAME_NUM)}, + caps_led{xkb_keymap_led_get_index(this->compiled_keymap.get(), XKB_LED_NAME_CAPS)}, + scroll_led{xkb_keymap_led_get_index(this->compiled_keymap.get(), XKB_LED_NAME_SCROLL)}, + registrar(executor) +{ +} + +void mircv::XKBMapperRegistrar::XkbMappingState::set_key_state(std::vector const& key_state) +{ + modifier_state = mir_input_event_modifier_none; + std::unordered_set pressed_codes; + std::string t; + for (uint32_t scan_code : key_state) + { + bool already_pressed = pressed_codes.count(scan_code) > 0; + + update_state(to_xkb_scan_code(scan_code), + (already_pressed) ? mir_keyboard_action_up : mir_keyboard_action_down, + nullptr, + t); + + if (already_pressed) + pressed_codes.erase(scan_code); + else + pressed_codes.insert(scan_code); + } +} + +bool mircv::XKBMapperRegistrar::XkbMappingState::update_and_map(MirEvent& event, mircv::XKBMapperRegistrar::ComposeState* compose_state) +{ + auto& key_ev = *event.to_input()->to_keyboard(); + uint32_t xkb_scan_code = to_xkb_scan_code(key_ev.scan_code()); + auto old_state = modifier_state; + std::string key_text; + auto const update_result = update_state(xkb_scan_code, key_ev.action(), compose_state, key_text); + xkb_keysym_t const keysym = update_result.first; + bool const xkb_modifiers_updated = update_result.second; + + key_ev.set_keysym(keysym); + key_ev.set_text(key_text.c_str()); + // TODO we should also indicate effective/consumed modifier state to properly + // implement short cuts with keys that are only reachable via modifier keys + key_ev.set_modifiers(expand_modifiers(modifier_state)); + key_ev.set_keymap(keymap); + + return old_state != modifier_state || xkb_modifiers_updated; +} + +MirInputEventModifiers mircv::XKBMapperRegistrar::XkbMappingState::modifiers() const +{ + return modifier_state; +} + +auto mircv::XKBMapperRegistrar::XkbMappingState::xkb_modifiers() const -> MirXkbModifiers +{ + return MirXkbModifiers{ + xkb_state_serialize_mods(state.get(), XKB_STATE_MODS_DEPRESSED), + xkb_state_serialize_mods(state.get(), XKB_STATE_MODS_LATCHED), + xkb_state_serialize_mods(state.get(), XKB_STATE_MODS_LOCKED), + xkb_state_serialize_layout(state.get(), XKB_STATE_LAYOUT_EFFECTIVE)}; +} + +void mircv::XKBMapperRegistrar::XkbMappingState::notify_leds_changed() +{ + KeyboardLeds leds; + if (xkb_state_led_index_is_active(state.get(), caps_led)) + leds |= KeyboardLed::caps_lock; + if (xkb_state_led_index_is_active(state.get(), num_led)) + leds |= KeyboardLed::num_lock; + if (xkb_state_led_index_is_active(state.get(), scroll_led)) + leds |= KeyboardLed::scroll_lock; + + registrar.leds_set(leds); +} + +mircv::XKBMapperRegistrar::XkbMappingState::XkbMappingStateLedRegistrar& mircv::XKBMapperRegistrar::XkbMappingState::get_registrar() +{ + return registrar; +} + +auto mircv::XKBMapperRegistrar::XkbMappingState::update_state( + uint32_t scan_code, + MirKeyboardAction action, + mircv::XKBMapperRegistrar::ComposeState* compose_state, + std::string& text) +-> std::pair +{ + auto keysym = xkb_state_key_get_one_sym(state.get(), scan_code); + auto const mod_change = modifier_from_xkb_scan_code(scan_code); + + // Occasionally, we see XKB_KEY_Meta_L where XKB_KEY_Alt_L is correct + if (mod_change == mir_input_event_modifier_alt_left) + { + keysym = XKB_KEY_Alt_L; + } + + if(action == mir_keyboard_action_down || action == mir_keyboard_action_repeat) + { + char buffer[7]; + // scan code? really? not keysym? + xkb_state_key_get_utf8(state.get(), scan_code, buffer, sizeof(buffer)); + text = buffer; + } + + if (compose_state) + keysym = compose_state->update_state(keysym, action, text); + + uint32_t mask{0}; + if (action == mir_keyboard_action_up) + { + mask = xkb_state_update_key(state.get(), scan_code, XKB_KEY_UP); + // TODO get the modifier state from xkbcommon and apply it + // for all other modifiers manually track them here: + release_modifier(mod_change); + } + else if (action == mir_keyboard_action_down) + { + mask = xkb_state_update_key(state.get(), scan_code, XKB_KEY_DOWN); + // TODO get the modifier state from xkbcommon and apply it + // for all other modifiers manually track them here: + press_modifier(mod_change); + } + + bool const xkb_modifiers_changed = + (mask & XKB_STATE_MODS_DEPRESSED) || + (mask & XKB_STATE_MODS_LATCHED) || + (mask & XKB_STATE_MODS_LOCKED) || + (mask & XKB_STATE_LAYOUT_EFFECTIVE); + + return {keysym, xkb_modifiers_changed}; +} + +mircv::XKBMapperRegistrar::ComposeState* mircv::XKBMapperRegistrar::get_compose_state(MirInputDeviceId id) +{ + auto dev_compose_state = device_composing.find(id); + + if (dev_compose_state != end(device_composing)) + { + return dev_compose_state->second.get(); + } + if (compose_table) + { + decltype(device_composing.begin()) insertion_pos; + std::tie(insertion_pos, std::ignore) = + device_composing.emplace(std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple(std::make_unique(compose_table))); + + return insertion_pos->second.get(); + } + return nullptr; +} + +mircv::XKBMapperRegistrar::ComposeState::ComposeState(XKBComposeTablePtr const& table) : + state{make_unique_compose_state(table)} +{ +} + +xkb_keysym_t mircv::XKBMapperRegistrar::ComposeState::update_state(xkb_keysym_t mapped_key, MirKeyboardAction action, std::string& mapped_text) +{ + // the state machine only cares about downs + if (action == mir_keyboard_action_down) + { + if (xkb_compose_state_feed(state.get(), mapped_key) == XKB_COMPOSE_FEED_ACCEPTED) + { + auto result = xkb_compose_state_get_status(state.get()); + if (result == XKB_COMPOSE_COMPOSED) + { + char buffer[7]; + xkb_compose_state_get_utf8(state.get(), buffer, sizeof(buffer)); + mapped_text = buffer; + auto composed_key_sym = xkb_compose_state_get_one_sym(state.get()); + last_composed_key = std::make_tuple(mapped_key, composed_key_sym); + return composed_key_sym; + } + else if (result == XKB_COMPOSE_COMPOSING) + { + mapped_text = ""; + consumed_keys.insert(mapped_key); + return XKB_KEY_NoSymbol; + } + else if (result == XKB_COMPOSE_CANCELLED) + { + consumed_keys.insert(mapped_key); + return XKB_KEY_NoSymbol; + } + } + } + else + { + if (last_composed_key.is_set() && + mapped_key == std::get<0>(last_composed_key.value())) + { + if (action == mir_keyboard_action_up) + { + mapped_text = ""; + return std::get<1>(last_composed_key.consume()); + } + else + { + // on repeat get the text of the compose state + char buffer[7]; + xkb_compose_state_get_utf8(state.get(), buffer, sizeof(buffer)); + mapped_text = buffer; + return std::get<1>(last_composed_key.value()); + } + } + if (consumed_keys.erase(mapped_key)) + { + mapped_text = ""; + return XKB_KEY_NoSymbol; + } + } + + return mapped_key; +} + + +void mircv::XKBMapperRegistrar::XkbMappingState::press_modifier(MirInputEventModifiers mod) +{ + if (is_toggle_modifier(mod)) + modifier_state ^= mod; + else + modifier_state |= mod; +} + +void mircv::XKBMapperRegistrar::XkbMappingState::release_modifier(MirInputEventModifiers mod) +{ + if (!is_toggle_modifier(mod)) + modifier_state &= ~mod; +} diff --git a/src/server/symbols.map b/src/server/symbols.map index d3875c06e21..5b6e9aaf91a 100644 --- a/src/server/symbols.map +++ b/src/server/symbols.map @@ -1344,6 +1344,7 @@ local: *; MIR_SERVER_INTERNAL_2.18 { global: extern "C++" { + mir::DefaultServerConfiguration::the_led_observer_registrar*; mir::DecorationStrategy::?DecorationStrategy*; mir::DecorationStrategy::DecorationStrategy*; mir::DecorationStrategy::operator*; @@ -1352,8 +1353,39 @@ global: mir::Server::set_the_decoration_strategy*; mir::Server::the_decoration_strategy*; mir::Server::the_idle_handler*; + mir::input::make_unique_context*; + mir::input::receiver::XKBMapperRegistrar::XKBMapperRegistrar*; + mir::input::receiver::XKBMapperRegistrar::clear_all_keymaps*; + mir::input::receiver::XKBMapperRegistrar::clear_keymap_for_device*; + mir::input::receiver::XKBMapperRegistrar::device_modifiers*; + mir::input::receiver::XKBMapperRegistrar::map_event*; + mir::input::receiver::XKBMapperRegistrar::modifiers*; + mir::input::receiver::XKBMapperRegistrar::operator*; + mir::input::receiver::XKBMapperRegistrar::register_interest*; + mir::input::receiver::XKBMapperRegistrar::set_key_state*; + mir::input::receiver::XKBMapperRegistrar::set_keymap_for_all_devices*; + mir::input::receiver::XKBMapperRegistrar::set_keymap_for_device*; + mir::input::receiver::XKBMapperRegistrar::unregister_interest*; + mir::input::receiver::XKBMapperRegistrar::xkb_modifiers*; mir::shell::IdleHandlerObserver::?IdleHandlerObserver*; mir::shell::IdleHandlerObserver::IdleHandlerObserver*; + non-virtual?thunk?to?mir::DefaultServerConfiguration::the_led_observer_registrar*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::clear_all_keymaps*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::clear_keymap_for_device*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::device_modifiers*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::map_event*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::modifiers*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::register_interest*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::set_key_state*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::set_keymap_for_all_devices*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::set_keymap_for_device*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::unregister_interest*; + non-virtual?thunk?to?mir::input::receiver::XKBMapperRegistrar::xkb_modifiers*; + non-virtual?thunk?to?mir::shell::IdleHandlerObserver::?IdleHandlerObserver*; + typeinfo?for?mir::input::receiver::XKBMapperRegistrar; + typeinfo?for?mir::shell::IdleHandlerObserver; + vtable?for?mir::input::receiver::XKBMapperRegistrar; + vtable?for?mir::shell::IdleHandlerObserver; non-virtual?thunk?to?mir::DecorationStrategy::?DecorationStrategy*; non-virtual?thunk?to?mir::DefaultServerConfiguration::set_the_decoration_strategy*; non-virtual?thunk?to?mir::DefaultServerConfiguration::the_decoration_strategy*; @@ -1366,3 +1398,4 @@ global: vtable?for?mir::shell::IdleHandlerObserver; }; } MIR_SERVER_INTERNAL_2.17; + diff --git a/tests/include/mir/test/doubles/mock_led_observer_registrar.h b/tests/include/mir/test/doubles/mock_led_observer_registrar.h new file mode 100644 index 00000000000..1fdd45ef577 --- /dev/null +++ b/tests/include/mir/test/doubles/mock_led_observer_registrar.h @@ -0,0 +1,40 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_TEST_DOUBLES_LED_OBSERVER_REGISTRAR_H_ +#define MIR_TEST_DOUBLES_LED_OBSERVER_REGISTRAR_H_ + +#include "mir/input/led_observer_registrar.h" + +#include + +namespace mir +{ +namespace test +{ +namespace doubles +{ +struct MockLedObserverRegistrar : input::LedObserverRegistrar +{ + MOCK_METHOD2(register_interest, void(std::weak_ptr const&, MirInputDeviceId)); + MOCK_METHOD2(unregister_interest, void(input::LedObserver const&, MirInputDeviceId)); +}; + +} +} +} + +#endif diff --git a/tests/include/mir/test/doubles/mock_libinput.h b/tests/include/mir/test/doubles/mock_libinput.h index efa51257621..4487f8290d2 100644 --- a/tests/include/mir/test/doubles/mock_libinput.h +++ b/tests/include/mir/test/doubles/mock_libinput.h @@ -177,6 +177,7 @@ class MockLibInput MOCK_METHOD2(libinput_device_config_dwt_set_enabled, libinput_config_status(libinput_device*, libinput_config_dwt_state enable)); MOCK_METHOD1(libinput_device_config_dwt_get_enabled, libinput_config_dwt_state(libinput_device*)); MOCK_METHOD1(libinput_device_config_dwt_get_default_enabled, libinput_config_dwt_state(libinput_device*)); + MOCK_METHOD2(libinput_device_led_update, void(libinput_device*, libinput_led)); template PtrT get_next_fake_ptr() diff --git a/tests/integration-tests/input/test_single_seat_setup.cpp b/tests/integration-tests/input/test_single_seat_setup.cpp index ae320c5d094..a399eb2d372 100644 --- a/tests/integration-tests/input/test_single_seat_setup.cpp +++ b/tests/integration-tests/input/test_single_seat_setup.cpp @@ -26,6 +26,7 @@ #include "mir/test/doubles/mock_touch_visualizer.h" #include "mir/test/doubles/mock_cursor_listener.h" #include "mir/test/doubles/mock_input_manager.h" +#include "mir/test/doubles/mock_led_observer_registrar.h" #include "mir/test/doubles/mock_seat_report.h" #include "mir/test/doubles/mock_server_status_listener.h" #include "mir/test/doubles/mock_scene_session.h" @@ -41,7 +42,7 @@ #include "mir/scene/session_container.h" #include "mir/input/device.h" -#include "mir/input/xkb_mapper.h" +#include "mir/input/xkb_mapper_registrar.h" #include "mir/input/device_capability.h" #include "mir/input/mir_pointer_config.h" #include "mir/input/mir_touchpad_config.h" @@ -90,13 +91,14 @@ struct SingleSeatInputDeviceHubSetup : ::testing::Test NiceMock mock_visualizer; NiceMock mock_seat_observer; NiceMock mock_status_listener; - mi::receiver::XKBMapper key_mapper; + mi::receiver::XKBMapperRegistrar key_mapper{mir::immediate_executor}; mir::dispatch::MultiplexingDispatchable multiplexer; mtd::AdvanceableClock clock; mtd::MockInputManager mock_input_manager; ms::SessionContainer session_container; ms::BroadcastingSessionEventSink session_event_sink; mtd::FakeDisplayConfigurationObserverRegistrar display_config; + NiceMock led_observer_registrar; mi::BasicSeat seat{mt::fake_shared(mock_dispatcher), mt::fake_shared(mock_visualizer), mt::fake_shared(mock_cursor_listener), mt::fake_shared(display_config), mt::fake_shared(key_mapper), mt::fake_shared(clock), @@ -106,7 +108,8 @@ struct SingleSeatInputDeviceHubSetup : ::testing::Test mt::fake_shared(multiplexer), mt::fake_shared(clock), mt::fake_shared(key_mapper), - mt::fake_shared(mock_status_listener)}; + mt::fake_shared(mock_status_listener), + mt::fake_shared(led_observer_registrar)}; NiceMock mock_observer; mi::ConfigChanger changer{ mt::fake_shared(mock_input_manager), diff --git a/tests/mir_test_doubles/mock_libinput.cpp b/tests/mir_test_doubles/mock_libinput.cpp index c7970659f71..61e2bb9863e 100644 --- a/tests/mir_test_doubles/mock_libinput.cpp +++ b/tests/mir_test_doubles/mock_libinput.cpp @@ -654,6 +654,11 @@ libinput_config_dwt_state libinput_device_config_dwt_get_default_enabled(libinpu return global_libinput->libinput_device_config_dwt_get_default_enabled(device); } +void libinput_device_led_update(libinput_device *device, libinput_led leds) +{ + global_libinput->libinput_device_led_update(device, leds); +} + libinput_event* mtd::MockLibInput::setup_touch_event(libinput_device* dev, libinput_event_type type, uint64_t event_time, int slot, float x, float y, float major, float minor, float pressure, float orientation) { diff --git a/tests/unit-tests/input/evdev/test_evdev_device_detection.cpp b/tests/unit-tests/input/evdev/test_evdev_device_detection.cpp index 32e8214e27c..57f1c63f980 100644 --- a/tests/unit-tests/input/evdev/test_evdev_device_detection.cpp +++ b/tests/unit-tests/input/evdev/test_evdev_device_detection.cpp @@ -21,6 +21,8 @@ #include "src/server/report/null_report_factory.h" #include "mir_test_framework/libinput_environment.h" +#include "mir/test/doubles/mock_led_observer_registrar.h" +#include "mir/test/fake_shared.h" #include #include @@ -29,6 +31,8 @@ namespace mtf = mir_test_framework; namespace mi = mir::input; namespace mie = mi::evdev; +namespace mt = mir::test; +namespace mtd = mt::doubles; struct EvdevDeviceDetection : public ::testing::TestWithParam> { @@ -41,6 +45,7 @@ TEST_P(EvdevDeviceDetection, evaluates_expected_input_class) auto const& param = GetParam(); auto dev = env.setup_device(std::get<0>(param)); std::shared_ptr lib = mie::make_libinput(nullptr); + NiceMock led_observer_registrar; mie::LibInputDevice device(mir::report::null_input_report(), mie::make_libinput_device(lib, dev)); auto info = device.get_device_info(); EXPECT_THAT(info.capabilities, Eq(std::get<1>(param))); diff --git a/tests/unit-tests/input/test_default_input_device_hub.cpp b/tests/unit-tests/input/test_default_input_device_hub.cpp index 29cdef6116d..8a166efd8bb 100644 --- a/tests/unit-tests/input/test_default_input_device_hub.cpp +++ b/tests/unit-tests/input/test_default_input_device_hub.cpp @@ -21,6 +21,7 @@ #include "mir/test/doubles/mock_input_seat.h" #include "mir/test/doubles/mock_event_sink.h" #include "mir/test/doubles/mock_key_mapper.h" +#include "mir/test/doubles/mock_led_observer_registrar.h" #include "mir/test/doubles/mock_server_status_listener.h" #include "mir/test/doubles/advanceable_clock.h" #include "mir/test/fake_shared.h" @@ -67,6 +68,7 @@ MATCHER_P(WithName, name, struct InputDeviceHubTest : ::testing::Test { mir::dispatch::MultiplexingDispatchable multiplexer; + NiceMock led_observer_registrar; NiceMock mock_seat; NiceMock mock_key_mapper; NiceMock mock_server_status_listener; @@ -76,7 +78,8 @@ struct InputDeviceHubTest : ::testing::Test mt::fake_shared(multiplexer), mt::fake_shared(clock), mt::fake_shared(mock_key_mapper), - mt::fake_shared(mock_server_status_listener)}; + mt::fake_shared(mock_server_status_listener), + mt::fake_shared(led_observer_registrar)}; NiceMock mock_observer; NiceMock device{"device","dev-1", mi::DeviceCapability::unknown}; NiceMock another_device{"another_device","dev-2", mi::DeviceCapability::keyboard}; diff --git a/tests/unit-tests/input/test_seat_input_device_tracker.cpp b/tests/unit-tests/input/test_seat_input_device_tracker.cpp index c9418fb0a70..244ae11514b 100644 --- a/tests/unit-tests/input/test_seat_input_device_tracker.cpp +++ b/tests/unit-tests/input/test_seat_input_device_tracker.cpp @@ -17,7 +17,7 @@ #include "src/server/input/seat_input_device_tracker.h" #include "src/server/input/default_event_builder.h" -#include "mir/input/xkb_mapper.h" +#include "mir/input/xkb_mapper_registrar.h" #include "mir/test/doubles/mock_input_dispatcher.h" #include "mir/test/doubles/mock_cursor_listener.h" #include "mir/test/doubles/mock_touch_visualizer.h" @@ -65,7 +65,7 @@ struct SeatInputDeviceTracker : ::testing::Test mi::DefaultEventBuilder third_device_builder{ third_device, mt::fake_shared(clock)}; - mi::receiver::XKBMapper mapper; + mi::receiver::XKBMapperRegistrar mapper{mir::immediate_executor}; mi::SeatInputDeviceTracker tracker{ mt::fake_shared(mock_dispatcher), mt::fake_shared(mock_visualizer), mt::fake_shared(mock_cursor_listener), mt::fake_shared(mapper), mt::fake_shared(clock), mt::fake_shared(mock_seat_report)};