Mir

Merge lp:~andreas-pokorny/mir/0.20-fix-1550050 into lp:mir/0.20

Proposed by Andreas Pokorny
Status: Merged
Merged at revision: 3331
Proposed branch: lp:~andreas-pokorny/mir/0.20-fix-1550050
Merge into: lp:mir/0.20
Diff against target: 423 lines (+219/-53)
6 files modified
include/test/mir/test/doubles/mock_input_device_hub.h (+43/-0)
src/server/input/default_configuration.cpp (+37/-24)
src/server/input/default_input_device_hub.cpp (+14/-8)
src/server/input/key_repeat_dispatcher.cpp (+41/-0)
src/server/input/key_repeat_dispatcher.h (+7/-4)
tests/unit-tests/input/test_key_repeat_dispatcher.cpp (+77/-17)
To merge this branch: bzr merge lp:~andreas-pokorny/mir/0.20-fix-1550050
Reviewer Review Type Date Requested Status
Mir CI Bot continuous-integration Needs Fixing
Kevin DuBois (community) Approve
Review via email: mp+287854@code.launchpad.net

Commit message

Fixes the potential endless repeat on unplugged devices

The key repeat dispatcher now reacts on the removal of input devices by resetting potentially still active repeat alarms. The integration here is a bit clumsy, since the input device hub needs the input dispatcher to forward device events. While the input dispatcher needs it to register at the input device hub as an observer.

Description of the change

This is the small version of the fix. It makes the KeyRepeatDispatcher observe the input devices attached and resets the repeat handling alarms on device removal.

The changes in src/server/input/default_configuration.cpp were necessary since there is already a dependency path from InputDeviceHub to InputDispatcher. I plan to make a different fix on lp:mir that moves the repeat handling directly to the device/platform itself.

To post a comment you must log in.
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

The effect of the branch is sometimes hidden by the fact that some bluetooth drivers keep track for input states and release keys when unplugging the device. The problem is easier to reproduce with USB devices.

Revision history for this message
Kevin DuBois (kdub) wrote :

src/server/input/default_configuration.cpp

- [this]()
+ [this]() -> std::shared_ptr<mi::InputDispatcher>

- the_event_filter_chain_dispatcher(), the_main_loop(), the_cookie_authority(),
- enable_repeat, key_repeat_timeout, key_repeat_delay);
+ the_event_filter_chain_dispatcher(), the_main_loop(), the_cookie_authority(),
+ enable_repeat, key_repeat_timeout, key_repeat_delay);

unneeded changes?

+ void set_input_device_hub(std::shared_ptr<InputDeviceHub> const& hub);
two step initialization.

review: Needs Fixing
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

> src/server/input/default_configuration.cpp
>
> - [this]()
> + [this]() -> std::shared_ptr<mi::InputDispatcher>
>
> - the_event_filter_chain_dispatcher(), the_main_loop(),
> the_cookie_authority(),
> - enable_repeat, key_repeat_timeout, key_repeat_delay);
> + the_event_filter_chain_dispatcher(), the_main_loop(),
> the_cookie_authority(),
> + enable_repeat, key_repeat_timeout, key_repeat_delay);
>
> unneeded changes?

yes reverting that..

>
> + void set_input_device_hub(std::shared_ptr<InputDeviceHub> const& hub);
> two step initialization.

This is one of the reasons why I make a better solution into lp:mir - it will turn into a bigger change and somewhat turn into something that no longer looks like simple fix.
Two step init is necessary to avoid a dependency cycle between 'the intput dispatchers' and InputDeviceHub.

Revision history for this message
Kevin DuBois (kdub) wrote :

I suppose I don't mind as much if the two step initialization will be improved before going to trunk.

It would be good (necessary?) to see a CI pass on the MP before merging as the release. I suppose all that adds up to a +1 from me then.

review: Approve
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

> I suppose I don't mind as much if the two step initialization will be improved
> before going to trunk.
>
> It would be good (necessary?) to see a CI pass on the MP before merging as the
> release. I suppose all that adds up to a +1 from me then.

Yeah our ci has yet no way of running against a release branch.. Maybe a manually triggered run could work

Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

ok does not work that way..

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'include/test/mir/test/doubles/mock_input_device_hub.h'
2--- include/test/mir/test/doubles/mock_input_device_hub.h 1970-01-01 00:00:00 +0000
3+++ include/test/mir/test/doubles/mock_input_device_hub.h 2016-03-03 16:17:57 +0000
4@@ -0,0 +1,43 @@
5+/*
6+ * Copyright © 2016 Canonical Ltd.
7+ *
8+ * This program is free software: you can redistribute it and/or modify it
9+ * under the terms of the GNU General Public License version 3,
10+ * as published by the Free Software Foundation.
11+ *
12+ * This program is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ * GNU General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU General Public License
18+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
19+ *
20+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
21+ */
22+
23+#ifndef MIR_TEST_DOUBLES_MOCK_INPUT_DEVICE_HUB_H_
24+#define MIR_TEST_DOUBLES_MOCK_INPUT_DEVICE_HUB_H_
25+
26+#include "mir/input/input_device_hub.h"
27+
28+namespace mir
29+{
30+namespace test
31+{
32+namespace doubles
33+{
34+
35+struct MockInputDeviceHub : input::InputDeviceHub
36+{
37+ MOCK_METHOD1(add_observer, void(std::shared_ptr<input::InputDeviceObserver> const&));
38+ MOCK_METHOD1(remove_observer, void(std::weak_ptr<input::InputDeviceObserver> const&));
39+ MOCK_METHOD1(for_each_input_device, void(std::function<void(std::shared_ptr<input::Device>const&)> const&));
40+
41+};
42+
43+}
44+}
45+}
46+
47+#endif
48
49=== modified file 'src/server/input/default_configuration.cpp'
50--- src/server/input/default_configuration.cpp 2016-02-12 10:51:29 +0000
51+++ src/server/input/default_configuration.cpp 2016-03-03 16:17:57 +0000
52@@ -270,33 +270,46 @@
53
54 std::shared_ptr<mi::InputDeviceRegistry> mir::DefaultServerConfiguration::the_input_device_registry()
55 {
56- return default_input_device_hub([this]()
57- {
58- return std::make_shared<mi::DefaultInputDeviceHub>(
59- std::make_shared<mi::BasicSeat>(
60- the_input_dispatcher(),
61- the_touch_visualizer(),
62- the_cursor_listener(),
63- the_input_region()),
64- the_input_reading_multiplexer(),
65- the_main_loop(),
66- the_cookie_authority());
67- });
68+ return default_input_device_hub(
69+ [this]()
70+ {
71+ auto input_dispatcher = the_input_dispatcher();
72+ auto key_repeater = std::dynamic_pointer_cast<mi::KeyRepeatDispatcher>(input_dispatcher);
73+ auto hub = std::make_shared<mi::DefaultInputDeviceHub>(
74+ std::make_shared<mi::BasicSeat>(
75+ input_dispatcher,
76+ the_touch_visualizer(),
77+ the_cursor_listener(),
78+ the_input_region()),
79+ the_input_reading_multiplexer(),
80+ the_main_loop(),
81+ the_cookie_authority());
82+
83+ if (key_repeater)
84+ key_repeater->set_input_device_hub(hub);
85+ return hub;
86+ });
87 }
88
89 std::shared_ptr<mi::InputDeviceHub> mir::DefaultServerConfiguration::the_input_device_hub()
90 {
91- return default_input_device_hub([this]()
92- {
93- return std::make_shared<mi::DefaultInputDeviceHub>(
94-std::make_shared<mi::BasicSeat>(
95- the_input_dispatcher(),
96- the_touch_visualizer(),
97- the_cursor_listener(),
98- the_input_region()),
99+ return default_input_device_hub(
100+ [this]()
101+ {
102+ auto input_dispatcher = the_input_dispatcher();
103+ auto key_repeater = std::dynamic_pointer_cast<mi::KeyRepeatDispatcher>(input_dispatcher);
104+ auto hub = std::make_shared<mi::DefaultInputDeviceHub>(
105+ std::make_shared<mi::BasicSeat>(
106+ input_dispatcher,
107+ the_touch_visualizer(),
108+ the_cursor_listener(),
109+ the_input_region()),
110+ the_input_reading_multiplexer(),
111+ the_main_loop(),
112+ the_cookie_authority());
113
114- the_input_reading_multiplexer(),
115- the_main_loop(),
116- the_cookie_authority());
117- });
118+ if (key_repeater)
119+ key_repeater->set_input_device_hub(hub);
120+ return hub;
121+ });
122 }
123
124=== modified file 'src/server/input/default_input_device_hub.cpp'
125--- src/server/input/default_input_device_hub.cpp 2016-01-29 15:55:17 +0000
126+++ src/server/input/default_input_device_hub.cpp 2016-03-03 16:17:57 +0000
127@@ -228,17 +228,23 @@
128
129 void mi::DefaultInputDeviceHub::remove_device_handle(MirInputDeviceId id)
130 {
131- auto handle_it = remove_if(begin(handles),
132- end(handles),
133- [&id](auto const& handle){return handle->id() == id;});
134+ auto handle_it = remove_if(
135+ begin(handles),
136+ end(handles),
137+ [this,&id](auto const& handle)
138+ {
139+ if (handle->id() != id)
140+ return false;
141+ for (auto const& observer : observers)
142+ {
143+ observer->device_removed(handle);
144+ observer->changes_complete();
145+ }
146+ return true;
147+ });
148
149 if (handle_it == end(handles))
150 return;
151- for (auto const& observer : observers)
152- {
153- observer->device_removed(*handle_it);
154- observer->changes_complete();
155- }
156
157 handles.erase(handle_it, end(handles));
158 }
159
160=== modified file 'src/server/input/key_repeat_dispatcher.cpp'
161--- src/server/input/key_repeat_dispatcher.cpp 2016-01-29 08:18:22 +0000
162+++ src/server/input/key_repeat_dispatcher.cpp 2016-03-03 16:17:57 +0000
163@@ -18,6 +18,8 @@
164
165 #include "key_repeat_dispatcher.h"
166
167+#include "mir/input/device.h"
168+#include "mir/input/input_device_hub.h"
169 #include "mir/time/alarm_factory.h"
170 #include "mir/time/alarm.h"
171 #include "mir/events/event_private.h"
172@@ -31,6 +33,34 @@
173
174 namespace mi = mir::input;
175
176+namespace
177+{
178+struct DeviceRemovalFilter : mi::InputDeviceObserver
179+{
180+ DeviceRemovalFilter(std::function<void(MirInputDeviceId)> const& on_removal)
181+ : on_removal(on_removal) {}
182+
183+ void device_added(std::shared_ptr<mi::Device> const&) override
184+ {
185+ }
186+
187+ void device_changed(std::shared_ptr<mi::Device> const&) override
188+ {
189+ }
190+
191+ void device_removed(std::shared_ptr<mi::Device> const& device) override
192+ {
193+ on_removal(device->id());
194+ }
195+
196+ void changes_complete() override
197+ {
198+ }
199+ std::function<void(MirInputDeviceId)> on_removal;
200+};
201+
202+}
203+
204 mi::KeyRepeatDispatcher::KeyRepeatDispatcher(
205 std::shared_ptr<mi::InputDispatcher> const& next_dispatcher,
206 std::shared_ptr<mir::time::AlarmFactory> const& factory,
207@@ -47,6 +77,17 @@
208 {
209 }
210
211+void mi::KeyRepeatDispatcher::set_input_device_hub(std::shared_ptr<InputDeviceHub> const& hub)
212+{
213+ hub->add_observer(std::make_shared<DeviceRemovalFilter>(
214+ [this](MirInputDeviceId id)
215+ {
216+ std::unique_lock<std::mutex> lock(repeat_state_mutex);
217+ repeat_state_by_device.erase(id); // destructor cancels alarms
218+ }
219+ ));
220+}
221+
222 mi::KeyRepeatDispatcher::KeyboardState& mi::KeyRepeatDispatcher::ensure_state_for_device_locked(std::lock_guard<std::mutex> const&, MirInputDeviceId id)
223 {
224 repeat_state_by_device.insert(std::make_pair(id, KeyboardState()));
225
226=== modified file 'src/server/input/key_repeat_dispatcher.h'
227--- src/server/input/key_repeat_dispatcher.h 2016-01-29 08:18:22 +0000
228+++ src/server/input/key_repeat_dispatcher.h 2016-03-03 16:17:57 +0000
229@@ -1,5 +1,5 @@
230 /*
231- * Copyright © 2015 Canonical Ltd.
232+ * Copyright © 2015-2016 Canonical Ltd.
233 *
234 * This program is free software: you can redistribute it and/or modify it
235 * under the terms of the GNU General Public License version 3,
236@@ -20,6 +20,7 @@
237 #define MIR_INPUT_KEY_REPEAT_DISPATCHER_H_
238
239 #include "mir/input/input_dispatcher.h"
240+#include "mir/input/input_device_observer.h"
241
242 #include <memory>
243 #include <chrono>
244@@ -39,8 +40,8 @@
245 }
246 namespace input
247 {
248-
249-class KeyRepeatDispatcher : public mir::input::InputDispatcher
250+class InputDeviceHub;
251+class KeyRepeatDispatcher : public InputDispatcher
252 {
253 public:
254 KeyRepeatDispatcher(std::shared_ptr<InputDispatcher> const& next_dispatcher,
255@@ -54,7 +55,9 @@
256 bool dispatch(MirEvent const& event) override;
257 void start() override;
258 void stop() override;
259-
260+
261+ void set_input_device_hub(std::shared_ptr<InputDeviceHub> const& hub);
262+
263 private:
264 std::mutex repeat_state_mutex;
265
266
267=== modified file 'tests/unit-tests/input/test_key_repeat_dispatcher.cpp'
268--- tests/unit-tests/input/test_key_repeat_dispatcher.cpp 2016-01-29 08:18:22 +0000
269+++ tests/unit-tests/input/test_key_repeat_dispatcher.cpp 2016-03-03 16:17:57 +0000
270@@ -23,9 +23,15 @@
271 #include "mir/time/alarm.h"
272 #include "mir/time/alarm_factory.h"
273 #include "mir/cookie/authority.h"
274+#include "mir/input/input_device_observer.h"
275+#include "mir/input/pointer_configuration.h"
276+#include "mir/input/touchpad_configuration.h"
277+#include "mir/input/device.h"
278
279+#include "mir/test/fake_shared.h"
280 #include "mir/test/event_matchers.h"
281 #include "mir/test/doubles/mock_input_dispatcher.h"
282+#include "mir/test/doubles/mock_input_device_hub.h"
283
284 #include <gtest/gtest.h>
285 #include <gmock/gmock.h>
286@@ -35,6 +41,8 @@
287 namespace mt = mir::test;
288 namespace mtd = mt::doubles;
289
290+using namespace ::testing;
291+
292 namespace
293 {
294 struct MockAlarm : public mir::time::Alarm
295@@ -43,6 +51,12 @@
296 MOCK_CONST_METHOD0(state, mir::time::Alarm::State());
297 MOCK_METHOD1(reschedule_in, bool(std::chrono::milliseconds));
298 MOCK_METHOD1(reschedule_for, bool(mir::time::Timestamp));
299+
300+ // destructor cancels the alarm
301+ ~MockAlarm()
302+ {
303+ cancel();
304+ }
305 };
306
307 struct MockAlarmFactory : public mir::time::AlarmFactory
308@@ -59,25 +73,60 @@
309 }
310 };
311
312+struct StubDevice : public mi::Device
313+{
314+ MirInputDeviceId device_id;
315+ StubDevice(MirInputDeviceId id) : device_id(id) {}
316+ MirInputDeviceId id() const { return device_id;}
317+ mi::DeviceCapabilities capabilities() const {return mi::DeviceCapability::keyboard;}
318+ std::string name() const {return {};}
319+ std::string unique_id() const {return {};}
320+
321+ mir::optional_value<mi::PointerConfiguration> pointer_configuration() const {return {};}
322+ void apply_pointer_configuration(mi::PointerConfiguration const&) {;}
323+ mir::optional_value<mi::TouchpadConfiguration> touchpad_configuration() const {return {};}
324+ void apply_touchpad_configuration(mi::TouchpadConfiguration const&) {}
325+};
326+
327 struct KeyRepeatDispatcher : public testing::Test
328 {
329 KeyRepeatDispatcher()
330 : dispatcher(mock_next_dispatcher, mock_alarm_factory, cookie_authority, true, repeat_time, repeat_delay)
331 {
332- }
333+ ON_CALL(hub,add_observer(_)).WillByDefault(SaveArg<0>(&observer));
334+ dispatcher.set_input_device_hub(mt::fake_shared(hub));
335+ }
336+ void simulate_device_removal()
337+ {
338+ StubDevice dev(test_device);
339+ observer->device_removed(mt::fake_shared(dev));
340+ observer->changes_complete();
341+ }
342+
343+ const MirInputDeviceId test_device = 123;
344 std::shared_ptr<mtd::MockInputDispatcher> mock_next_dispatcher = std::make_shared<mtd::MockInputDispatcher>();
345 std::shared_ptr<MockAlarmFactory> mock_alarm_factory = std::make_shared<MockAlarmFactory>();
346 std::shared_ptr<mir::cookie::Authority> cookie_authority = mir::cookie::Authority::create();
347 std::chrono::milliseconds const repeat_time{2};
348 std::chrono::milliseconds const repeat_delay{1};
349+ std::shared_ptr<mi::InputDeviceObserver> observer;
350+ NiceMock<mtd::MockInputDeviceHub> hub;
351 mi::KeyRepeatDispatcher dispatcher;
352+
353+ mir::EventUPtr a_key_down_event()
354+ {
355+ return mev::make_event(test_device, std::chrono::nanoseconds(0), std::vector<uint8_t>{}, mir_keyboard_action_down, 0, 0, mir_input_event_modifier_alt);
356+ }
357+
358+ mir::EventUPtr a_key_up_event()
359+ {
360+ return mev::make_event(test_device, std::chrono::nanoseconds(0), std::vector<uint8_t>{}, mir_keyboard_action_up, 0, 0, mir_input_event_modifier_alt);
361+ }
362 };
363 }
364
365 TEST_F(KeyRepeatDispatcher, forwards_start_stop)
366 {
367- using namespace ::testing;
368-
369 InSequence seq;
370 EXPECT_CALL(*mock_next_dispatcher, start()).Times(1);
371 EXPECT_CALL(*mock_next_dispatcher, stop()).Times(1);
372@@ -86,22 +135,8 @@
373 dispatcher.stop();
374 }
375
376-namespace
377-{
378-mir::EventUPtr a_key_down_event()
379-{
380- return mev::make_event(0, std::chrono::nanoseconds(0), std::vector<uint8_t>{}, mir_keyboard_action_down, 0, 0, mir_input_event_modifier_alt);
381-}
382-mir::EventUPtr a_key_up_event()
383-{
384- return mev::make_event(0, std::chrono::nanoseconds(0), std::vector<uint8_t>{}, mir_keyboard_action_up, 0, 0, mir_input_event_modifier_alt);
385-}
386-}
387-
388 TEST_F(KeyRepeatDispatcher, schedules_alarm_to_repeat_key_down)
389 {
390- using namespace ::testing;
391-
392 MockAlarm *mock_alarm = new MockAlarm; // deleted by AlarmFactory
393 std::function<void()> alarm_function;
394
395@@ -122,3 +157,28 @@
396 // Trigger the cancel
397 dispatcher.dispatch(*a_key_up_event());
398 }
399+
400+TEST_F(KeyRepeatDispatcher, stops_repeat_on_device_removal)
401+{
402+ MockAlarm *mock_alarm = new MockAlarm;
403+ std::function<void()> alarm_function;
404+ bool alarm_canceled = false;
405+
406+ InSequence seq;
407+ EXPECT_CALL(*mock_alarm_factory, create_alarm_adapter(_)).Times(1).
408+ WillOnce(DoAll(SaveArg<0>(&alarm_function), Return(mock_alarm)));
409+ // Once for initial down and again when invoked
410+ EXPECT_CALL(*mock_alarm, reschedule_in(repeat_time)).Times(1).WillOnce(Return(true));
411+ EXPECT_CALL(*mock_next_dispatcher, dispatch(mt::KeyDownEvent())).Times(1);
412+ EXPECT_CALL(*mock_next_dispatcher, dispatch(mt::KeyRepeatEvent())).Times(1);
413+ EXPECT_CALL(*mock_alarm, reschedule_in(repeat_delay)).Times(1).WillOnce(Return(true));
414+ ON_CALL(*mock_alarm, cancel()).WillByDefault(Invoke([&](){alarm_canceled = true; return true;}));
415+
416+ dispatcher.dispatch(*a_key_down_event());
417+
418+ alarm_function();
419+ Mock::VerifyAndClearExpectations(mock_alarm); // mock_alarm will be deleted after this
420+
421+ simulate_device_removal();
422+ EXPECT_THAT(alarm_canceled, Eq(true));
423+}

Subscribers

People subscribed via source and target branches