Mir

Merge lp:~andreas-pokorny/mir/compose-sequences-in-xkb-keymapper into lp:mir

Proposed by Andreas Pokorny
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
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...

To post a comment you must log in.
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3644
https://mir-jenkins.ubuntu.com/job/mir-ci/2006/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2570/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2633/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2625/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2625/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2625/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2599/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2599/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2599/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2599/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2599/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2599/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2006/rebuild

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

FAILED: Continuous integration, rev:3774
https://mir-jenkins.ubuntu.com/job/mir-ci/2009/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2575/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2638
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2630
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2630
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2630
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2604
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2604/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2604
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2604/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2604
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2604/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2604/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2604/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2604
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2604/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2009/rebuild

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

FAILED: Continuous integration, rev:3775
https://mir-jenkins.ubuntu.com/job/mir-ci/2011/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2577/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2640
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2632
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2632
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2632
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2606
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2606/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2606
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2606/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2606
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2606/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2606/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2606/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2606
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2606/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2011/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
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.

Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Chris Halse Rogers (raof) wrote :

This seems to be sensible behaviour.

review: Approve
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3777
https://mir-jenkins.ubuntu.com/job/mir-ci/2028/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2598/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2661
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2653
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2653
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2653
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2627
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2627/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2627
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2627/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2627
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2627/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2627/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2627/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2627
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2627/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2028/rebuild

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

FAILED: Continuous integration, rev:3778
https://mir-jenkins.ubuntu.com/job/mir-ci/2032/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2602/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2665
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2657
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2657
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2657
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2631
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2631/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2631
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2631/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2631
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2631/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2631/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2631/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2631
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2631/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2032/rebuild

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

oh xkbcommon landed..

Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:3778
https://mir-jenkins.ubuntu.com/job/mir-ci/2081/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/2668
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2731
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2723
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2723
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2723
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2697
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2697/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2697
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2697/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2697
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2697/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2697
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2697/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2697
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2697/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2697
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2697/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2081/rebuild

review: Approve (continuous-integration)
Revision history for this message
Kevin DuBois (kdub) wrote :

+ xkb_keysym_t update_state(uint32_t scan_code, MirKeyboardAction direction, ComposeState* compose_state);
in light of
+ update_state(to_xkb_scan_code(scan_code), mir_keyboard_action_down, nullptr);

I think mir::optional_value<ComposeState*> as the new parameter makes sense. (or having two functions with different parameters).

not too bothered by that though, lgtm

review: Approve
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

I don't feel strongly enough to block, but...

+#include <ostream>
...
+inline std::ostream& operator<<(std::ostream &out, Keymap const& rhs)
+{
+ 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::XKBMapper::XkbMappingState::update_state(uint32_t scan_code, MirKeyboardAction action)
+xkb_keysym_t mircv::XKBMapper::XkbMappingState::update_state(uint32_t scan_code, MirKeyboardAction ...
+ if (compose_state)
+ key_sym = compose_state->update_state(key_sym, action);

It really is nicer to use the NullObject pattern or function overloading than to use nullptr as a valid input.

review: Abstain

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+}

Subscribers

People subscribed via source and target branches