Merge lp:~andreas-pokorny/mir/0.20-fix-1550050 into lp:mir/0.20
- 0.20-fix-1550050
- Merge into 0.20
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 | ||||
Related bugs: |
|
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/
Andreas Pokorny (andreas-pokorny) wrote : | # |
Kevin DuBois (kdub) wrote : | # |
src/server/
- [this]()
+ [this]() -> std::shared_
- the_event_
- enable_repeat, key_repeat_timeout, key_repeat_delay);
+ the_event_
+ enable_repeat, key_repeat_timeout, key_repeat_delay);
unneeded changes?
+ void set_input_
two step initialization.
Andreas Pokorny (andreas-pokorny) wrote : | # |
> src/server/
>
> - [this]()
> + [this]() -> std::shared_
>
> - the_event_
> the_cookie_
> - enable_repeat, key_repeat_timeout, key_repeat_delay);
> + the_event_
> the_cookie_
> + enable_repeat, key_repeat_timeout, key_repeat_delay);
>
> unneeded changes?
yes reverting that..
>
> + void set_input_
> 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.
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.
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
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3334
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3334
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Andreas Pokorny (andreas-pokorny) wrote : | # |
ok does not work that way..
Preview Diff
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 | +} |
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.