Merge lp:~andreas-pokorny/mir/compose-sequences-in-xkb-keymapper into lp:mir
- compose-sequences-in-xkb-keymapper
- Merge into development-branch
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Daniel van Vugt | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 3799 | ||||
Proposed branch: | lp:~andreas-pokorny/mir/compose-sequences-in-xkb-keymapper | ||||
Merge into: | lp:mir | ||||
Diff against target: |
544 lines (+284/-36) 7 files modified
debian/control (+1/-1) include/common/mir/input/keymap.h (+6/-0) src/client/input/xkb_mapper.cpp (+151/-28) src/include/common/mir/input/key_mapper.h (+0/-1) src/include/common/mir/input/xkb_mapper.h (+21/-5) tests/acceptance-tests/test_client_input.cpp (+1/-1) tests/unit-tests/client/input/test_xkb_mapper.cpp (+104/-0) |
||||
To merge this branch: | bzr merge lp:~andreas-pokorny/mir/compose-sequences-in-xkb-keymapper | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alan Griffiths | Abstain | ||
Kevin DuBois (community) | Approve | ||
Mir CI Bot | continuous-integration | Approve | |
Chris Halse Rogers | Approve | ||
Review via email: mp+308799@code.launchpad.net |
Commit message
Dead key key compose sequences in mirserver and mirclient
This implementation of key compose sequences keeps the stream of scan codes intact. So clients still get the full stream - only the scan codes are removed. Hence a client with a different surface keymap could still map key presses unaffected.
This is intended to be used inside qtmir to avoid qts default behavior of converting the compose sequence result into input method events, and thereby filtering out key events.
Description of the change
Add a neutral compose key handling to XKBMapper
After finding out that qts inputcontext plugin in charge of compose handling destroys the key sequence, the decision to not handle dead keys was reversed and this change was started. Because this implementation treats compose key results still as key events and additionally keeps the scan code in place the key sequence is not destroyed. Thus allowing clients to use a different keymap/compose setup then the server...
Mir CI Bot (mir-ci-bot) wrote : | # |
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3774
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3775
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Andreas Pokorny (andreas-pokorny) wrote : | # |
This does not build on vivid because v+o is still on libxkbcommon-0.4.2 while the rest is on libxkbcommmon-0.5. I am trying to get v+o upgraded this week.
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3776
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Chris Halse Rogers (raof) wrote : | # |
This seems to be sensible behaviour.
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3777
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3778
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Andreas Pokorny (andreas-pokorny) wrote : | # |
oh xkbcommon landed..
Mir CI Bot (mir-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:3778
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Kevin DuBois (kdub) wrote : | # |
+ xkb_keysym_t update_
in light of
+ update_
I think mir::optional_
not too bothered by that though, lgtm
Alan Griffiths (alan-griffiths) wrote : | # |
I don't feel strongly enough to block, but...
+#include <ostream>
...
+inline std::ostream& operator<
+{
+ return out << rhs.model << "-" << rhs.layout << "-"<< rhs.variant << "-" << rhs.options;
+}
This is a public header, couldn't we use <iosfwd> and a non-inline function?
~~~~
+ std::stringstream error;
+ error << "Illegal keymap configuration evdev-" << map;
Why do we construct the error message on both the good and bad codepaths?
~~~~
-xkb_keysym_t mircv::
+xkb_keysym_t mircv::
+ if (compose_state)
+ key_sym = compose_
It really is nicer to use the NullObject pattern or function overloading than to use nullptr as a valid input.
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2016-10-18 13:38:19 +0000 |
3 | +++ debian/control 2016-10-20 14:45:33 +0000 |
4 | @@ -28,7 +28,7 @@ |
5 | libandroid-properties-dev [i386 amd64 armhf arm64], |
6 | libgoogle-glog-dev, |
7 | liblttng-ust-dev, |
8 | - libxkbcommon-dev, |
9 | + libxkbcommon-dev (>= 0.5), |
10 | libumockdev-dev (>= 0.6), |
11 | umockdev (>= 0.8.7), |
12 | libudev-dev, |
13 | |
14 | === modified file 'include/common/mir/input/keymap.h' |
15 | --- include/common/mir/input/keymap.h 2016-06-09 18:16:12 +0000 |
16 | +++ include/common/mir/input/keymap.h 2016-10-20 14:45:33 +0000 |
17 | @@ -21,6 +21,7 @@ |
18 | #define MIR_INPUT_KEYMAP_H_ |
19 | |
20 | #include <string> |
21 | +#include <ostream> |
22 | |
23 | namespace mir |
24 | { |
25 | @@ -66,6 +67,11 @@ |
26 | return !(lhs == rhs); |
27 | } |
28 | |
29 | +inline std::ostream& operator<<(std::ostream &out, Keymap const& rhs) |
30 | +{ |
31 | + return out << rhs.model << "-" << rhs.layout << "-"<< rhs.variant << "-" << rhs.options; |
32 | +} |
33 | + |
34 | } |
35 | } |
36 | |
37 | |
38 | === modified file 'src/client/input/xkb_mapper.cpp' |
39 | --- src/client/input/xkb_mapper.cpp 2016-10-12 06:03:15 +0000 |
40 | +++ src/client/input/xkb_mapper.cpp 2016-10-20 14:45:33 +0000 |
41 | @@ -23,6 +23,7 @@ |
42 | #include "mir/events/event_private.h" |
43 | #include "mir/events/event_builders.h" |
44 | |
45 | +#include <sstream> |
46 | #include <boost/throw_exception.hpp> |
47 | |
48 | namespace mi = mir::input; |
49 | @@ -32,6 +33,18 @@ |
50 | namespace |
51 | { |
52 | |
53 | +char const* get_locale_from_environment() |
54 | +{ |
55 | + char const* loc = getenv("LC_ALL"); |
56 | + if (!loc) |
57 | + loc = getenv("LC_CTYPE"); |
58 | + if (!loc) |
59 | + loc = getenv("LANG"); |
60 | + if (!loc) |
61 | + loc = "C"; |
62 | + return loc; |
63 | +} |
64 | + |
65 | MirInputEventModifiers xkb_key_code_to_modifier(xkb_keysym_t key) |
66 | { |
67 | switch(key) |
68 | @@ -76,6 +89,26 @@ |
69 | // xkb scancodes are offset by 8 from evdev scancodes for compatibility with X protocol. |
70 | return evdev_scan_code + 8; |
71 | } |
72 | + |
73 | + |
74 | +mi::XKBComposeStatePtr make_unique_compose_state(mi::XKBComposeTablePtr const& table) |
75 | +{ |
76 | + return {xkb_compose_state_new(table.get(), XKB_COMPOSE_STATE_NO_FLAGS), &xkb_compose_state_unref}; |
77 | +} |
78 | + |
79 | +mi::XKBComposeTablePtr make_unique_compose_table_from_locale(mi::XKBContextPtr const& context, std::string const& locale) |
80 | +{ |
81 | + return {xkb_compose_table_new_from_locale( |
82 | + context.get(), |
83 | + locale.c_str(), |
84 | + XKB_COMPOSE_COMPILE_NO_FLAGS), |
85 | + &xkb_compose_table_unref}; |
86 | +} |
87 | + |
88 | +mi::XKBStatePtr make_unique_state(xkb_keymap* keymap) |
89 | +{ |
90 | + return {xkb_state_new(keymap), xkb_state_unref}; |
91 | +} |
92 | } |
93 | |
94 | mi::XKBContextPtr mi::make_unique_context() |
95 | @@ -85,6 +118,8 @@ |
96 | |
97 | mi::XKBKeymapPtr mi::make_unique_keymap(xkb_context* context, mi::Keymap const& map) |
98 | { |
99 | + std::stringstream error; |
100 | + error << "Illegal keymap configuration evdev-" << map; |
101 | xkb_rule_names keymap_names |
102 | { |
103 | "evdev", |
104 | @@ -93,23 +128,25 @@ |
105 | map.variant.c_str(), |
106 | map.options.c_str() |
107 | }; |
108 | - return {xkb_keymap_new_from_names(context, &keymap_names, xkb_keymap_compile_flags(0)), &xkb_keymap_unref}; |
109 | + auto keymap_ptr = xkb_keymap_new_from_names(context, &keymap_names, xkb_keymap_compile_flags(0)); |
110 | + |
111 | + if (!keymap_ptr) |
112 | + BOOST_THROW_EXCEPTION(std::invalid_argument(error.str().c_str())); |
113 | + return {keymap_ptr, &xkb_keymap_unref}; |
114 | } |
115 | |
116 | mi::XKBKeymapPtr mi::make_unique_keymap(xkb_context* context, char const* buffer, size_t size) |
117 | { |
118 | - return {xkb_keymap_new_from_buffer(context, buffer, size, XKB_KEYMAP_FORMAT_TEXT_V1, xkb_keymap_compile_flags(0)), |
119 | - &xkb_keymap_unref}; |
120 | -} |
121 | - |
122 | -using XKBStatePtr = std::unique_ptr<xkb_state, void(*)(xkb_state*)>; |
123 | -mi::XKBStatePtr mi::make_unique_state(xkb_keymap* keymap) |
124 | -{ |
125 | - return {xkb_state_new(keymap), xkb_state_unref}; |
126 | -} |
127 | - |
128 | -mircv::XKBMapper::XKBMapper() |
129 | - : context{make_unique_context()} |
130 | + auto keymap_ptr = xkb_keymap_new_from_buffer(context, buffer, size, XKB_KEYMAP_FORMAT_TEXT_V1, xkb_keymap_compile_flags(0)); |
131 | + if (!keymap_ptr) |
132 | + BOOST_THROW_EXCEPTION(std::runtime_error("failed to create keymap from buffer.")); |
133 | + |
134 | + return {keymap_ptr, &xkb_keymap_unref}; |
135 | +} |
136 | + |
137 | +mircv::XKBMapper::XKBMapper() : |
138 | + context{make_unique_context()}, |
139 | + compose_table{make_unique_compose_table_from_locale(context, get_locale_from_environment())} |
140 | { |
141 | } |
142 | |
143 | @@ -130,7 +167,7 @@ |
144 | MirInputEventModifiers new_modifier = 0; |
145 | for (auto const& mapping_state : device_mapping) |
146 | { |
147 | - new_modifier |= mapping_state.second.modifier_state; |
148 | + new_modifier |= mapping_state.second->modifier_state; |
149 | } |
150 | |
151 | modifier_state = new_modifier; |
152 | @@ -152,8 +189,12 @@ |
153 | if (input_type == mir_input_event_type_key) |
154 | { |
155 | auto mapping_state = get_keymapping_state(device_id); |
156 | - if (mapping_state && mapping_state->update_and_map(ev)) |
157 | - update_modifier(); |
158 | + if (mapping_state) |
159 | + { |
160 | + auto compose_state = get_compose_state(device_id); |
161 | + if (mapping_state->update_and_map(ev, compose_state)) |
162 | + update_modifier(); |
163 | + } |
164 | } |
165 | else if (modifier_state.is_set()) |
166 | { |
167 | @@ -168,14 +209,17 @@ |
168 | |
169 | if (dev_keymap != end(device_mapping)) |
170 | { |
171 | - return &dev_keymap->second; |
172 | + return dev_keymap->second.get(); |
173 | } |
174 | if (default_keymap) |
175 | { |
176 | - return |
177 | - &device_mapping.emplace(std::piecewise_construct, |
178 | - std::forward_as_tuple(id), |
179 | - std::forward_as_tuple(default_keymap)).first->second; |
180 | + decltype(device_mapping.begin()) insertion_pos; |
181 | + std::tie(insertion_pos, std::ignore) = |
182 | + device_mapping.emplace(std::piecewise_construct, |
183 | + std::forward_as_tuple(id), |
184 | + std::forward_as_tuple(std::make_unique<XkbMappingState>(default_keymap))); |
185 | + |
186 | + return insertion_pos->second.get(); |
187 | } |
188 | return nullptr; |
189 | } |
190 | @@ -214,7 +258,7 @@ |
191 | device_mapping.erase(id); |
192 | device_mapping.emplace(std::piecewise_construct, |
193 | std::forward_as_tuple(id), |
194 | - std::forward_as_tuple(std::move(new_keymap))); |
195 | + std::forward_as_tuple(std::make_unique<XkbMappingState>(std::move(new_keymap)))); |
196 | } |
197 | |
198 | void mircv::XKBMapper::clear_all_keymaps() |
199 | @@ -250,18 +294,16 @@ |
200 | state = make_unique_state(keymap.get()); |
201 | modifier_state = mir_input_event_modifier_none; |
202 | for (uint32_t scan_code : key_state) |
203 | - update_state(to_xkb_scan_code(scan_code), mir_keyboard_action_down); |
204 | + update_state(to_xkb_scan_code(scan_code), mir_keyboard_action_down, nullptr); |
205 | } |
206 | |
207 | -bool mircv::XKBMapper::XkbMappingState::update_and_map(MirEvent& event) |
208 | +bool mircv::XKBMapper::XkbMappingState::update_and_map(MirEvent& event, mircv::XKBMapper::ComposeState* compose_state) |
209 | { |
210 | auto& key_ev = *event.to_input()->to_keyboard(); |
211 | - // TODO test if key entry is start of a compose key sequence.. |
212 | - // then use compose key API to map key.. |
213 | uint32_t xkb_scan_code = to_xkb_scan_code(key_ev.scan_code()); |
214 | auto old_state = modifier_state; |
215 | |
216 | - key_ev.set_key_code(update_state(xkb_scan_code, key_ev.action())); |
217 | + key_ev.set_key_code(update_state(xkb_scan_code, key_ev.action(), compose_state)); |
218 | // TODO we should also indicate effective/consumed modifier state to properly |
219 | // implement short cuts with keys that are only reachable via modifier keys |
220 | key_ev.set_modifiers(expand_modifiers(modifier_state)); |
221 | @@ -269,11 +311,14 @@ |
222 | return old_state != modifier_state; |
223 | } |
224 | |
225 | -xkb_keysym_t mircv::XKBMapper::XkbMappingState::update_state(uint32_t scan_code, MirKeyboardAction action) |
226 | +xkb_keysym_t mircv::XKBMapper::XkbMappingState::update_state(uint32_t scan_code, MirKeyboardAction action, mircv::XKBMapper::ComposeState* compose_state) |
227 | { |
228 | auto key_sym = xkb_state_key_get_one_sym(state.get(), scan_code); |
229 | auto mod_change = xkb_key_code_to_modifier(key_sym); |
230 | |
231 | + if (compose_state) |
232 | + key_sym = compose_state->update_state(key_sym, action); |
233 | + |
234 | if (action == mir_keyboard_action_up) |
235 | { |
236 | xkb_state_update_key(state.get(), scan_code, XKB_KEY_UP); |
237 | @@ -287,3 +332,81 @@ |
238 | |
239 | return key_sym; |
240 | } |
241 | + |
242 | +mircv::XKBMapper::ComposeState* mircv::XKBMapper::get_compose_state(MirInputDeviceId id) |
243 | +{ |
244 | + auto dev_compose_state = device_composing.find(id); |
245 | + |
246 | + if (dev_compose_state != end(device_composing)) |
247 | + { |
248 | + return dev_compose_state->second.get(); |
249 | + } |
250 | + if (compose_table) |
251 | + { |
252 | + decltype(device_composing.begin()) insertion_pos; |
253 | + std::tie(insertion_pos, std::ignore) = |
254 | + device_composing.emplace(std::piecewise_construct, |
255 | + std::forward_as_tuple(id), |
256 | + std::forward_as_tuple(std::make_unique<ComposeState>(compose_table))); |
257 | + |
258 | + return insertion_pos->second.get(); |
259 | + } |
260 | + return nullptr; |
261 | +} |
262 | + |
263 | +mircv::XKBMapper::ComposeState::ComposeState(XKBComposeTablePtr const& table) : |
264 | + state{make_unique_compose_state(table)} |
265 | +{ |
266 | +} |
267 | + |
268 | +void mircv::XKBMapper::ComposeState::update_and_map(MirEvent& event) |
269 | +{ |
270 | + auto& key_ev = *event.to_input()->to_keyboard(); |
271 | + |
272 | + auto const key_sym = key_ev.key_code(); |
273 | + auto const action = key_ev.action(); |
274 | + key_ev.set_key_code(update_state(key_sym, action)); |
275 | +} |
276 | + |
277 | +xkb_keysym_t mircv::XKBMapper::ComposeState::update_state(xkb_keysym_t mapped_key, MirKeyboardAction action) |
278 | +{ |
279 | + // the state machine only cares about downs |
280 | + if (action == mir_keyboard_action_down) |
281 | + { |
282 | + if (xkb_compose_state_feed(state.get(), mapped_key) == XKB_COMPOSE_FEED_ACCEPTED) |
283 | + { |
284 | + auto result = xkb_compose_state_get_status(state.get()); |
285 | + if (result == XKB_COMPOSE_COMPOSED) |
286 | + { |
287 | + auto composed_key_sym = xkb_compose_state_get_one_sym(state.get()); |
288 | + last_composed_key = std::make_tuple(mapped_key, composed_key_sym); |
289 | + return composed_key_sym; |
290 | + } |
291 | + else if (result == XKB_COMPOSE_COMPOSING) |
292 | + { |
293 | + consumed_keys.insert(mapped_key); |
294 | + return XKB_KEY_NoSymbol; |
295 | + } |
296 | + else if (result == XKB_COMPOSE_CANCELLED) |
297 | + { |
298 | + consumed_keys.insert(mapped_key); |
299 | + return XKB_KEY_NoSymbol; |
300 | + } |
301 | + } |
302 | + } |
303 | + else |
304 | + { |
305 | + if (last_composed_key.is_set() && |
306 | + mapped_key == std::get<0>(last_composed_key.value())) |
307 | + { |
308 | + if (action == mir_keyboard_action_up) |
309 | + return std::get<1>(last_composed_key.consume()); |
310 | + else |
311 | + return std::get<1>(last_composed_key.value()); |
312 | + } |
313 | + if (consumed_keys.erase(mapped_key)) |
314 | + return XKB_KEY_NoSymbol; |
315 | + } |
316 | + |
317 | + return mapped_key; |
318 | +} |
319 | |
320 | === modified file 'src/include/common/mir/input/key_mapper.h' |
321 | --- src/include/common/mir/input/key_mapper.h 2016-06-24 07:41:32 +0000 |
322 | +++ src/include/common/mir/input/key_mapper.h 2016-10-20 14:45:33 +0000 |
323 | @@ -23,7 +23,6 @@ |
324 | #include "mir_toolkit/client_types.h" |
325 | #include "mir_toolkit/event.h" |
326 | |
327 | -#include <xkbcommon/xkbcommon.h> |
328 | #include <vector> |
329 | #include <memory> |
330 | |
331 | |
332 | === modified file 'src/include/common/mir/input/xkb_mapper.h' |
333 | --- src/include/common/mir/input/xkb_mapper.h 2016-07-07 09:59:19 +0000 |
334 | +++ src/include/common/mir/input/xkb_mapper.h 2016-10-20 14:45:33 +0000 |
335 | @@ -24,8 +24,11 @@ |
336 | #include "mir/input/key_mapper.h" |
337 | #include "mir/optional_value.h" |
338 | |
339 | +#include <xkbcommon/xkbcommon.h> |
340 | +#include <xkbcommon/xkbcommon-compose.h> |
341 | #include <mutex> |
342 | #include <unordered_map> |
343 | +#include <unordered_set> |
344 | |
345 | namespace mir |
346 | { |
347 | @@ -40,7 +43,8 @@ |
348 | XKBKeymapPtr make_unique_keymap(xkb_context* context, char const* buffer, size_t size); |
349 | |
350 | using XKBStatePtr = std::unique_ptr<xkb_state, void(*)(xkb_state*)>; |
351 | -XKBStatePtr make_unique_state(xkb_keymap* keymap); |
352 | +using XKBComposeTablePtr = std::unique_ptr<xkb_compose_table, void(*)(xkb_compose_table*)>; |
353 | +using XKBComposeStatePtr = std::unique_ptr<xkb_compose_state, void(*)(xkb_compose_state*)>; |
354 | |
355 | namespace receiver |
356 | { |
357 | @@ -71,25 +75,37 @@ |
358 | |
359 | std::mutex mutable guard; |
360 | |
361 | + struct ComposeState |
362 | + { |
363 | + ComposeState(XKBComposeTablePtr const& table); |
364 | + void update_and_map(MirEvent& event); |
365 | + xkb_keysym_t update_state(xkb_keysym_t mapped_key, MirKeyboardAction action); |
366 | + XKBComposeStatePtr state; |
367 | + std::unordered_set<xkb_keysym_t> consumed_keys; |
368 | + mir::optional_value<std::tuple<xkb_keysym_t,xkb_keysym_t>> last_composed_key; |
369 | + }; |
370 | + |
371 | struct XkbMappingState |
372 | { |
373 | explicit XkbMappingState(std::shared_ptr<xkb_keymap> const& keymap); |
374 | void set_key_state(std::vector<uint32_t> const& key_state); |
375 | - bool update_and_map(MirEvent& event); |
376 | - xkb_keysym_t update_state(uint32_t scan_code, MirKeyboardAction direction); |
377 | + bool update_and_map(MirEvent& event, ComposeState* compose_state); |
378 | + xkb_keysym_t update_state(uint32_t scan_code, MirKeyboardAction direction, ComposeState* compose_state); |
379 | std::shared_ptr<xkb_keymap> const keymap; |
380 | XKBStatePtr state; |
381 | MirInputEventModifiers modifier_state{0}; |
382 | - // TODO optional compose key state.. |
383 | }; |
384 | |
385 | XkbMappingState* get_keymapping_state(MirInputDeviceId id); |
386 | + ComposeState* get_compose_state(MirInputDeviceId id); |
387 | |
388 | XKBContextPtr context; |
389 | std::shared_ptr<xkb_keymap> default_keymap; |
390 | + XKBComposeTablePtr compose_table; |
391 | |
392 | mir::optional_value<MirInputEventModifiers> modifier_state; |
393 | - std::unordered_map<MirInputDeviceId, XkbMappingState> device_mapping; |
394 | + std::unordered_map<MirInputDeviceId, std::unique_ptr<XkbMappingState>> device_mapping; |
395 | + std::unordered_map<MirInputDeviceId, std::unique_ptr<ComposeState>> device_composing; |
396 | }; |
397 | } |
398 | } |
399 | |
400 | === modified file 'tests/acceptance-tests/test_client_input.cpp' |
401 | --- tests/acceptance-tests/test_client_input.cpp 2016-10-12 06:03:15 +0000 |
402 | +++ tests/acceptance-tests/test_client_input.cpp 2016-10-20 14:45:33 +0000 |
403 | @@ -744,7 +744,7 @@ |
404 | |
405 | EXPECT_THROW( |
406 | {server.the_shell()->focused_surface()->set_keymap(id, model, layout, "", "");}, |
407 | - std::runtime_error); |
408 | + std::invalid_argument); |
409 | } |
410 | |
411 | TEST_F(TestClientInput, event_filter_may_consume_events) |
412 | |
413 | === modified file 'tests/unit-tests/client/input/test_xkb_mapper.cpp' |
414 | --- tests/unit-tests/client/input/test_xkb_mapper.cpp 2016-10-12 06:03:15 +0000 |
415 | +++ tests/unit-tests/client/input/test_xkb_mapper.cpp 2016-10-20 14:45:33 +0000 |
416 | @@ -23,6 +23,7 @@ |
417 | #include "mir/events/event_private.h" |
418 | #include "mir/events/event_builders.h" |
419 | |
420 | +#include "mir_test_framework/temporary_environment_value.h" |
421 | #include "mir/test/event_matchers.h" |
422 | #include <xkbcommon/xkbcommon-keysyms.h> |
423 | #include <xkbcommon/xkbcommon.h> |
424 | @@ -36,13 +37,28 @@ |
425 | namespace mt = mir::test; |
426 | namespace mircv = mi::receiver; |
427 | namespace mev = mir::events; |
428 | +namespace mtf = mir_test_framework; |
429 | |
430 | using namespace ::testing; |
431 | |
432 | struct XKBMapper : Test |
433 | { |
434 | + // en_US.UTF-8 is the fallback used by libxkbcommon - setting this avoids |
435 | + // tests failing on systems with a locale that points to a working but |
436 | + // different set of compose sequences |
437 | + mir_test_framework::TemporaryEnvironmentValue lc_all{"LC_ALL", "en_US.UTF-8"}; |
438 | + int const xkb_new_unicode_symbols_offset = 0x1000000; |
439 | mircv::XKBMapper mapper; |
440 | |
441 | + int map_key(mircv::XKBMapper &keymapper, MirInputDeviceId dev, MirKeyboardAction action, int scan_code) |
442 | + { |
443 | + auto ev = mev::make_event(dev, std::chrono::nanoseconds(0), std::vector<uint8_t>{}, action, |
444 | + 0, scan_code, mir_input_event_modifier_none); |
445 | + |
446 | + keymapper.map_event(*ev); |
447 | + return ev->to_input()->to_keyboard()->key_code(); |
448 | + } |
449 | + |
450 | int map_key(MirKeyboardAction action, int scan_code) |
451 | { |
452 | auto ev = mev::make_event(MirInputDeviceId(0), std::chrono::nanoseconds(0), std::vector<uint8_t>{}, action, |
453 | @@ -334,3 +350,91 @@ |
454 | mapper.set_keymap_for_device(keyboard, mi::Keymap{"pc105", "de", "",""}); |
455 | EXPECT_EQ(XKB_KEY_y, map_key(keyboard, mir_keyboard_action_down, KEY_Z)); |
456 | } |
457 | + |
458 | +TEST_F(XKBMapper, composes_keys_on_deadkey_keymap) |
459 | +{ |
460 | + auto keyboard = MirInputDeviceId{3}; |
461 | + mapper.set_keymap_for_device(keyboard, mi::Keymap{"pc105", "us", "intl",""}); |
462 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(keyboard, mir_keyboard_action_down, KEY_RIGHTSHIFT)); |
463 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_down, KEY_APOSTROPHE)); |
464 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_up, KEY_APOSTROPHE)); |
465 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(keyboard, mir_keyboard_action_up, KEY_RIGHTSHIFT)); |
466 | + EXPECT_EQ(XKB_KEY_udiaeresis, map_key(keyboard, mir_keyboard_action_down, KEY_U)); |
467 | + EXPECT_EQ(XKB_KEY_udiaeresis, map_key(keyboard, mir_keyboard_action_up, KEY_U)); |
468 | +} |
469 | + |
470 | +TEST_F(XKBMapper, release_of_compose_sequence_keys_after_completion_still_yields_dead_keys) |
471 | +{ |
472 | + auto keyboard = MirInputDeviceId{3}; |
473 | + mapper.set_keymap_for_device(keyboard, mi::Keymap{"pc105", "us", "intl",""}); |
474 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(keyboard, mir_keyboard_action_down, KEY_RIGHTSHIFT)); |
475 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_down, KEY_APOSTROPHE)); |
476 | + EXPECT_EQ(XKB_KEY_Udiaeresis, map_key(keyboard, mir_keyboard_action_down, KEY_U)); |
477 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_up, KEY_APOSTROPHE)); |
478 | + EXPECT_EQ(XKB_KEY_Udiaeresis, map_key(keyboard, mir_keyboard_action_up, KEY_U)); |
479 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(keyboard, mir_keyboard_action_up, KEY_RIGHTSHIFT)); |
480 | +} |
481 | + |
482 | +TEST_F(XKBMapper, repeated_key_that_completed_the_sequence_yields_a_repeated_completion_key) |
483 | +{ |
484 | + auto keyboard = MirInputDeviceId{3}; |
485 | + mapper.set_keymap_for_device(keyboard, mi::Keymap{"pc105", "us", "intl",""}); |
486 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(keyboard, mir_keyboard_action_down, KEY_RIGHTSHIFT)); |
487 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_down, KEY_APOSTROPHE)); |
488 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_up, KEY_APOSTROPHE)); |
489 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(keyboard, mir_keyboard_action_up, KEY_RIGHTSHIFT)); |
490 | + EXPECT_EQ(XKB_KEY_udiaeresis, map_key(keyboard, mir_keyboard_action_down, KEY_U)); |
491 | + EXPECT_EQ(XKB_KEY_udiaeresis, map_key(keyboard, mir_keyboard_action_repeat, KEY_U)); |
492 | + EXPECT_EQ(XKB_KEY_udiaeresis, map_key(keyboard, mir_keyboard_action_repeat, KEY_U)); |
493 | + EXPECT_EQ(XKB_KEY_udiaeresis, map_key(keyboard, mir_keyboard_action_repeat, KEY_U)); |
494 | + EXPECT_EQ(XKB_KEY_udiaeresis, map_key(keyboard, mir_keyboard_action_repeat, KEY_U)); |
495 | + EXPECT_EQ(XKB_KEY_udiaeresis, map_key(keyboard, mir_keyboard_action_repeat, KEY_U)); |
496 | + EXPECT_EQ(XKB_KEY_udiaeresis, map_key(keyboard, mir_keyboard_action_repeat, KEY_U)); |
497 | +} |
498 | + |
499 | +TEST_F(XKBMapper, compose_key_option_activates_multi_key_based_sequences) |
500 | +{ |
501 | + auto keyboard = MirInputDeviceId{3}; |
502 | + mapper.set_keymap_for_device(keyboard, mi::Keymap{"pc105", "us", "intl","compose:ralt"}); |
503 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_down, KEY_RIGHTALT)); |
504 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_up, KEY_RIGHTALT)); |
505 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(keyboard, mir_keyboard_action_down, KEY_RIGHTSHIFT)); |
506 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_down, KEY_COMMA)); |
507 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_up, KEY_COMMA)); |
508 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(keyboard, mir_keyboard_action_up, KEY_RIGHTSHIFT)); |
509 | + auto const black_heart = xkb_new_unicode_symbols_offset + 0x2665; |
510 | + EXPECT_EQ(black_heart, map_key(keyboard, mir_keyboard_action_down, KEY_3)); |
511 | +} |
512 | + |
513 | +TEST_F(XKBMapper, breaking_a_compose_sequence_yields_no_keysym) |
514 | +{ |
515 | + auto keyboard = MirInputDeviceId{3}; |
516 | + mapper.set_keymap_for_device(keyboard, mi::Keymap{"pc105", "us", "intl",""}); |
517 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(keyboard, mir_keyboard_action_down, KEY_RIGHTSHIFT)); |
518 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_down, KEY_APOSTROPHE)); |
519 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_up, KEY_APOSTROPHE)); |
520 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(keyboard, mir_keyboard_action_up, KEY_RIGHTSHIFT)); |
521 | + |
522 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_down, KEY_N)); |
523 | + EXPECT_EQ(XKB_KEY_NoSymbol, map_key(keyboard, mir_keyboard_action_up, KEY_N)); |
524 | + |
525 | + EXPECT_EQ(XKB_KEY_u, map_key(keyboard, mir_keyboard_action_down, KEY_U)); |
526 | + EXPECT_EQ(XKB_KEY_u, map_key(keyboard, mir_keyboard_action_up, KEY_U)); |
527 | +} |
528 | + |
529 | +TEST_F(XKBMapper, no_key_composition_when_no_compose_file_is_found) |
530 | +{ |
531 | + mtf::TemporaryEnvironmentValue compose_file_path{"XCOMPOSEFILE", "/foo/bogus/path/Compose"}; |
532 | + mtf::TemporaryEnvironmentValue home_path{"HOME", "/not/your/home"}; |
533 | + mtf::TemporaryEnvironmentValue localedir{"XLOCALEDIR", "/not/your/home"}; |
534 | + mircv::XKBMapper local_mapper; |
535 | + |
536 | + auto keyboard = MirInputDeviceId{3}; |
537 | + local_mapper.set_keymap_for_device(keyboard, mi::Keymap{"pc105", "us", "intl",""}); |
538 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(local_mapper, keyboard, mir_keyboard_action_down, KEY_RIGHTSHIFT)); |
539 | + EXPECT_EQ(XKB_KEY_dead_diaeresis, map_key(local_mapper, keyboard, mir_keyboard_action_down, KEY_APOSTROPHE)); |
540 | + EXPECT_EQ(XKB_KEY_dead_diaeresis, map_key(local_mapper, keyboard, mir_keyboard_action_up, KEY_APOSTROPHE)); |
541 | + EXPECT_EQ(XKB_KEY_Shift_R, map_key(local_mapper, keyboard, mir_keyboard_action_up, KEY_RIGHTSHIFT)); |
542 | + EXPECT_EQ(XKB_KEY_u, map_key(local_mapper, keyboard, mir_keyboard_action_down, KEY_U)); |
543 | + EXPECT_EQ(XKB_KEY_u, map_key(local_mapper, keyboard, mir_keyboard_action_up, KEY_U)); |
544 | +} |
FAILED: Continuous integration, rev:3644 /mir-jenkins. ubuntu. com/job/ mir-ci/ 2006/ /mir-jenkins. ubuntu. com/job/ build-mir/ 2570/console /mir-jenkins. ubuntu. com/job/ build-0- fetch/2633/ console /mir-jenkins. ubuntu. com/job/ build-1- sourcepkg/ release= vivid+overlay/ 2625/console /mir-jenkins. ubuntu. com/job/ build-1- sourcepkg/ release= xenial+ overlay/ 2625/console /mir-jenkins. ubuntu. com/job/ build-1- sourcepkg/ release= yakkety/ 2625/console /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= amd64,compiler= clang,platform= mesa,release= yakkety/ 2599/console /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= amd64,compiler= gcc,platform= mesa,release= xenial+ overlay/ 2599/console /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= amd64,compiler= gcc,platform= mesa,release= yakkety/ 2599/console /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= cross-armhf, compiler= gcc,platform= android, release= vivid+overlay/ 2599/console /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= i386,compiler= gcc,platform= android, release= vivid+overlay/ 2599/console /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= i386,compiler= gcc,platform= mesa,release= xenial+ overlay/ 2599/console
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild: /mir-jenkins. ubuntu. com/job/ mir-ci/ 2006/rebuild
https:/