Xpra: Ticket #1609: Command and Control keys on macOS host ends up as the same key on Linux

My host machine that I'm typing on is an MBP 2015 running macOS Sierra. My target machine is Fedora 26 Linux (on Apple hardware for those who care about that licensing detail) running VirtualBox running OSX 10.9 so my goal is to have the keyboard work as if I were working directly on OSX 10.9 on the MBP.

I managed to hack the code so that the Command and Control keys are 'correctly' detected on OSX 10.9, however the Command key does not work as a modifier so I cannot press it in combination with any other key. Advice on how to debug this would be appreciated.

My hack patch is simple enough (but clearly a hack!):

diff --git a/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py b/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py
--- a/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py	(revision 16558)
+++ b/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py	(working copy)
@@ -1358,6 +1358,13 @@
         keycode = event.hardware_keycode
         keyname = gdk.keyval_name(keyval)
         keyname = KEY_TRANSLATIONS.get((keyname, keyval, keycode), keyname)
+        # mac hacks
+        if keycode == 55:
+            keycode = 133
+            keyname = 'Super_L'
+        elif keycode == 54:
+            keycode = 133
+            keyname = 'Super_R'
         key_event = GTKKeyEvent()
         key_event.modifiers = self._client.mask_to_names(event.state)
         key_event.keyname = keyname or ""

The lack of Command-as-modifier *may* be related to https://bugzilla.gnome.org/show_bug.cgi?id=736125

If anyone wants to see how OSX interprets the key presses this post might help: https://mcmw.abilitynet.org.uk/apple-os-x-10-8-mountain-lion-using-the-on-screen-keyboard/

Is this likely something that can be fixed in the Python code or will it involve changes at the GTK+ level?

Suggestions very welcome. I'm so close to having a great OSX 10.9 development setup, just one pesky little modifier key away!



Sat, 29 Jul 2017 23:35:17 GMT - Ray Donnelly:

Small update, the right Command button change is ineffectual and shouldn't have been included in my patch. I'll save that for another day, hopefully after Xpra is added to the Anaconda Distribution (and MSYS2).

Here is the corrected hacky patch:

diff --git a/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py b/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py
--- a/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py	(revision 16558)
+++ b/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py	(working copy)
@@ -1358,6 +1358,10 @@
         keycode = event.hardware_keycode
         keyname = gdk.keyval_name(keyval)
         keyname = KEY_TRANSLATIONS.get((keyname, keyval, keycode), keyname)
+        # mac hacks
+        if keycode == 55:
+            keycode = 133
+            keyname = 'Super_L'
         key_event = GTKKeyEvent()
         key_event.modifiers = self._client.mask_to_names(event.state)
         key_event.keyname = keyname or ""

I know this isn't related to this ticket, but I was wondering how well Xpra is working on GTK+3 and Python 3?


Sun, 30 Jul 2017 07:00:14 GMT - Antoine Martin: owner changed

Are you using --swap-keys=no as per #1608? Could it just be that the swap-keys code is incomplete? It swaps the modifiers (control vs command) but not the actual key presses for those keys. Maybe we should be swapping those (similar to your changes) when swap-keys is enabled, that looks like a bug.

I've used xpra with virtualbox before, and I had found that virtualbox was messing up some key events.. so that could be interfering. Maybe we should try to get the keyboard to work reliably from macos to a test application first (xev, whatever), then add virtualbox into the mix? If we do need the GTK patch (looks easy to apply to GTK2), I would need a simple test case to verify what it does or doesn't do.


Sun, 30 Jul 2017 07:46:33 GMT - Ray Donnelly:

I don't think I was using ---swap-keys when I ran into the not-a-modifier issue (because #1608 prevented me passing that option).

I will use xev later to debug further.


Sun, 30 Jul 2017 13:29:02 GMT - Ray Donnelly:

Hi Antoine,

I managed to bodge and hack enough code to get the correct result. Here is my complete patch. If you have pointers about how to do this properly then I will try to do that.

svn diff --git
Index: src/xpra/client/gtk_base/gtk_client_window_base.py
===================================================================
diff --git a/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py b/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py
--- a/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py	(revision 16562)
+++ b/trunk/src/xpra/client/gtk_base/gtk_client_window_base.py	(working copy)
@@ -1358,6 +1358,13 @@
         keycode = event.hardware_keycode
         keyname = gdk.keyval_name(keyval)
         keyname = KEY_TRANSLATIONS.get((keyname, keyval, keycode), keyname)
+        # mac hacks
+        if keycode == 55:
+            keycode = 133
+            keyname = 'Super_L'
+        if keycode == 54:
+            keycode = 134
+            keyname = 'Super_R'
         key_event = GTKKeyEvent()
         key_event.modifiers = self._client.mask_to_names(event.state)
         key_event.keyname = keyname or ""
Index: src/xpra/gtk_common/gtk_util.py
===================================================================
diff --git a/trunk/src/xpra/gtk_common/gtk_util.py b/trunk/src/xpra/gtk_common/gtk_util.py
--- a/trunk/src/xpra/gtk_common/gtk_util.py	(revision 16562)
+++ b/trunk/src/xpra/gtk_common/gtk_util.py	(working copy)
@@ -758,6 +758,7 @@
                         gdk.SHIFT_MASK          : "SHIFT",
                         gdk.LOCK_MASK           : "LOCK",
                         gdk.CONTROL_MASK        : "CONTROL",
+                        gdk.META_MASK           : "META",
                         gdk.MOD1_MASK           : "MOD1",
                         gdk.MOD2_MASK           : "MOD2",
                         gdk.MOD3_MASK           : "MOD3",
Index: src/xpra/platform/darwin/keyboard.py
===================================================================
diff --git a/trunk/src/xpra/platform/darwin/keyboard.py b/trunk/src/xpra/platform/darwin/keyboard.py
--- a/trunk/src/xpra/platform/darwin/keyboard.py	(revision 16562)
+++ b/trunk/src/xpra/platform/darwin/keyboard.py	(working copy)
@@ -116,7 +116,7 @@
     def set_modifier_mappings(self, mappings):
         KeyboardBase.set_modifier_mappings(self, mappings)
-        self.meta_modifier = self.modifier_keys.get("Meta_L") or self.modifier_keys.get("Meta_R")
+        self.meta_modifier = self.modifier_keys.get("Super_L") or self.modifier_keys.get("Super_R")
         self.control_modifier = self.modifier_keys.get("Control_L") or self.modifier_keys.get("Control_R")
         self.num_lock_modifier = self.modifier_keys.get("Num_Lock")
         log("set_modifier_mappings(%s) meta=%s, control=%s, numlock=%s", mappings, self.meta_modifier, self.control_modifier, self.num_lock_modifier)
@@ -155,6 +155,8 @@
     def mask_to_names(self, mask):
         names = KeyboardBase.mask_to_names(self, mask)
+        if bool(mask & META_MASK):
+            names.append(self.meta_modifier)
         if self.swap_keys and self.meta_modifier is not None and self.control_modifier is not None:
             meta_on = bool(mask & META_MASK)
             meta_set = self.meta_modifier in names

Sun, 30 Jul 2017 13:48:29 GMT - Antoine Martin:

Comments:

I'll try to play with this on macos when I get a chance.

--- xpra/client/gtk_base/gtk_client_window_base.py	(revision 16553)
+++ xpra/client/gtk_base/gtk_client_window_base.py	(working copy)
@@ -1357,6 +1357,19 @@
         keyval = event.keyval
         keycode = event.hardware_keycode
         keyname = gdk.keyval_name(keyval)
+        if OSX and self._client.swap_keys:
+            if keycode==55:
+                keycode = 133
+                keyname = 'Super_L'
+            elif keycode==54:
+                keycode = 134
+                keyname = 'Super_R'
+            elif keycode==133:
+                keycode = 55
+                keyname = "Meta_L"
+            elif keycode==134:
+                keycode = 54
+                keyname = "Meta_R"
         keyname = KEY_TRANSLATIONS.get((keyname, keyval, keycode), keyname)
         key_event = GTKKeyEvent()
         key_event.modifiers = self._client.mask_to_names(event.state)

Sun, 30 Jul 2017 14:02:18 GMT - Ray Donnelly:

Previously, swap keys swapped Control_L with Meta_L and Control_R with Meta_R but that leads to mod2 being set and that's numlock and set always anyway so Meta_L and Meta_R seem best avoided.

I avoid Meta_L and Meta_R by using Super_L and Super_R instead (I got these keynames and keycodes from xev -event keyboard running CentOS6 via VirtualBox? directly on the macBook Pro).

I always run with --swap-keys=no, and my patch is probably not --swap-keys=yes friendly. I can try to make it work under that setting too though.


Sun, 30 Jul 2017 14:04:41 GMT - Ray Donnelly:

.. so I'm not really doing the same thing as swap-keys here. It's orthogonal to that (or should be anyway). I am remapping how the command button is interpreted in order to avoid numlock/mod2 confusion.


Sun, 30 Jul 2017 14:36:23 GMT - Antoine Martin:

Gotcha, that makes more sense now. I'll take a look. You're right, we shouldn't be sending mod2 in any case.


Sat, 09 Sep 2017 06:40:14 GMT - Antoine Martin: attachment set

apple keyboard layout


Sat, 09 Sep 2017 09:56:49 GMT - Antoine Martin:

First, there was a bug in the keyswap code which could end up generating spurious key events server side, fixed in r16809. Also, regarding your question about GTK3 and Python3, please see #1568.


Layout

Collecting some debugging data as per wiki/Keyboard.

With a mac mini and an Apple keyboard that looks like this one: apple keyboard layout


Num Lock

I don't see any problems with numlock (just a minor improvement in r16810), it gets toggled with this key event:

parse_key_event(<gtk.gdk.Event at 0x11e37b9e0: GDK_KEY_PRESS keyval=Escape>, True)=<GTKKeyEvent object, contents: \
   {'modifiers': ['mod2'], 'group': 0, 'string': '\x1b', 'keyname': 'Escape', 'pressed': True, 'keyval': 65307, 'keycode': 71}>
(..)
toggling numlock

And no other modifiers end up triggering mod2. Maybe you just got confused by the fact that it is always present?


Key Events

As for the actual modifiers, I see the following key events:

I did plug in a non-apple standard keyboard, and found that alt and cmd were reversed!

xmodmap

$ DISPLAY=:100 xmodmap -pm
xmodmap:  up to 4 keys per modifier, (keycodes in parentheses):
shift       Shift_L (0x32),  Shift_R (0x3e)
lock        Caps_Lock (0x42)
control     Control_L (0x25),  Control_R (0x6d)
mod1        Alt_L (0x40),  Alt_R (0x71),  Alt_L (0x7d),  Meta_L (0x9c)
mod2        Num_Lock (0x4d)
mod3        Super_L (0x73),  Super_R (0x74),  Super_L (0x7f)
mod4        Hyper_L (0x80),  Hyper_R (0x85)
mod5        Mode_switch (0x8),  ISO_Level3_Shift (0x7c)

So we have two keys mapped to the same modifier (mod1). And both will be swapped with control when "swap-keys" is enabled. So r16812 always swaps "Alt" for "Super" (hardcoded for now). r16811 also improves the consistency of the keymap data provided by the server side. (should be optional) Both changesets are bigger because of extra docstrings and we're trying harder to support legacy data formats.

With these changes in place, I can see:

(slight difference as one emits "Alt" and the other "Meta"... but since those are both mapped to the same modifier, this should be fine)

Part of the problem is that we're relying on a default keymap then translating the macos key events to make them fit. Maybe it would be better to define a better keymap? Who uses "Hyper" anyway? Then we could have "Alt" and "Meta" mapped to separate modifiers for example.

@Ray Donnelly: does that work for you? A new beta macos build here: http://xpra.org/beta/osx/ If not, please explain how I can reproduce the problem - I never use those keys for anything, so xev is as far as I got!


Sun, 01 Oct 2017 10:02:27 GMT - Antoine Martin: status changed; resolution set

Not heard back, closing.


Thu, 08 Feb 2018 19:18:14 GMT - emclain: status changed; resolution deleted

I am having a similar problem with xpra-2.2.4, so this fix did not help me. I do not use swap-keys.

Client: macOS High Sierra 10.13.2 (17C88), xpra-2.2.4 Server: CentOS Linux release 7.3.1611 (Core) Relevant Xpra settings: --swap-keys no --debug=keyboard,scroll

In X11 (XQuartz), I have the setting "Option keys send Alt_L and Alt_R" checked. I don't know if this affects Xpra, but I figured I'd mention it.

I think the fundamental problem is that the modifier flags (mod1, mod2, etc.) are sometimes being interpreted according to the mapping received from the server, rather than using the appropriate mapping for the client. In --swap-keys mode you are reading the META_MASK and CONTROL_MASK rather than interpreting the modifier flags directly, so --swap-keys avoids this problem.

Running xmodmap -pm on my Mac yields this, which looks quite different for me than it does for you in comment:9:

shift       Shift_L (0x40),  Shift_R (0x44)
lock        Caps_Lock (0x41)
control     Control_L (0x43),  Control_R (0x46)
mod1        Alt_L (0x42),  Alt_R (0x45)
mod2        Meta_L (0x3f),  Meta_R (0x47)
mod3
mod4
mod5

If I type Command-A, below is what appears in the log generated by Xpra. You can see that the Command key sets the mask as "flags GDK_MOD2_MASK | GDK_META_MASK". Meanwhile, Xpra thinks that "meta mod=mod1" and "num lock mod=mod2" (because this is the mapping set on the server).

2018-02-06 13:00:06,010 mask_to_names(<flags 0 of type GdkModifierType>)=['mod2']
2018-02-06 13:00:06,010 parse_key_event(<gtk.gdk.Event at 0x10b687f30: GDK_KEY_PRESS keyval=Meta_L>, True)=<GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': '', 'keyname': 'Meta_L', 'pressed': True, 'keyval': 65511, 'keycode': 55}>
2018-02-06 13:00:06,010 handle_key_action(GLClientWindow(6 : gtk2.GLWindowBacking(6, (178, 178), None)), <GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': '', 'keyname': 'Meta_L', 'pressed': True, 'keyval': 65511, 'keycode': 55}>) wid=6
2018-02-06 13:00:06,011 send_key_action(6, <GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': '', 'keyname': 'Meta_L', 'pressed': True, 'keyval': 65511, 'keycode': 55}>)
2018-02-06 13:00:06,305 mask_to_names names=['mod2'], meta mod=mod1, control mod=control, num lock mod=mod2, num lock state=True
2018-02-06 13:00:06,306 mask_to_names(<flags GDK_MOD2_MASK | GDK_META_MASK of type GdkModifierType>)=['mod2']
2018-02-06 13:00:06,306 parse_key_event(<gtk.gdk.Event at 0x10b687f30: GDK_KEY_PRESS keyval=a>, True)=<GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': 'a', 'keyname': 'a', 'pressed': True, 'keyval': 97, 'keycode': 0}>
2018-02-06 13:00:06,306 handle_key_action(GLClientWindow(6 : gtk2.GLWindowBacking(6, (178, 178), None)), <GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': 'a', 'keyname': 'a', 'pressed': True, 'keyval': 97, 'keycode': 0}>) wid=6
2018-02-06 13:00:06,306 send_key_action(6, <GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': 'a', 'keyname': 'a', 'pressed': True, 'keyval': 97, 'keycode': 0}>)
2018-02-06 13:00:06,408 mask_to_names names=['mod2'], meta mod=mod1, control mod=control, num lock mod=mod2, num lock state=True
2018-02-06 13:00:06,409 mask_to_names(<flags GDK_MOD2_MASK | GDK_META_MASK of type GdkModifierType>)=['mod2']
2018-02-06 13:00:06,409 parse_key_event(<gtk.gdk.Event at 0x10b687f30: GDK_KEY_RELEASE keyval=a>, False)=<GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': 'a', 'keyname': 'a', 'pressed': False, 'keyval': 97, 'keycode': 0}>
2018-02-06 13:00:06,409 handle_key_action(GLClientWindow(6 : gtk2.GLWindowBacking(6, (178, 178), None)), <GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': 'a', 'keyname': 'a', 'pressed': False, 'keyval': 97, 'keycode': 0}>) wid=6
2018-02-06 13:00:06,409 send_key_action(6, <GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': 'a', 'keyname': 'a', 'pressed': False, 'keyval': 97, 'keycode': 0}>)
2018-02-06 13:00:06,666 mask_to_names names=['mod2'], meta mod=mod1, control mod=control, num lock mod=mod2, num lock state=True
2018-02-06 13:00:06,666 mask_to_names(<flags GDK_MOD2_MASK | GDK_META_MASK of type GdkModifierType>)=['mod2']
2018-02-06 13:00:06,666 parse_key_event(<gtk.gdk.Event at 0x10b687f30: GDK_KEY_RELEASE keyval=Meta_L>, False)=<GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': '', 'keyname': 'Meta_L', 'pressed': False, 'keyval': 65511, 'keycode': 55}>
2018-02-06 13:00:06,666 handle_key_action(GLClientWindow(6 : gtk2.GLWindowBacking(6, (178, 178), None)), <GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': '', 'keyname': 'Meta_L', 'pressed': False, 'keyval': 65511, 'keycode': 55}>) wid=6
2018-02-06 13:00:06,666 send_key_action(6, <GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 0, 'string': '', 'keyname': 'Meta_L', 'pressed': False, 'keyval': 65511, 'keycode': 55}>)

Here's the output of xev through Xpra when I type Command-A. An Alt keypress is generated but the "state" field of the 'a' keypress remains as 0x10 (num lock/mod2 set, no other modifiers; no expected 'mod1' for 'Alt' modifier).

You can also see that the events are actually getting reordered: I typed Command-down, a-down, a-up, Command-up, but xev shows Alt-down, Alt-up, a-down, a-up. I think this might have something to do with the mod2 getting muddled up with num lock? At some point xpra interpreted the mod2 from Command as setting Num Lock, which was already set, so it had to generate a synthetic KeyRelease event?

KeyPress event, serial 36, synthetic NO, window 0x1000001,
    root 0x25d, subw 0x0, time 80082287, (160,161), root:(160,206),
    state 0x10, keycode 64 (keysym 0xffe9, Alt_L), same_screen YES,
    XLookupString gives 0 bytes:
    XmbLookupString gives 0 bytes:
    XFilterEvent returns: False
KeyRelease event, serial 36, synthetic NO, window 0x1000001,
    root 0x25d, subw 0x0, time 80082535, (160,161), root:(160,206),
    state 0x18, keycode 64 (keysym 0xffe9, Alt_L), same_screen YES,
    XLookupString gives 0 bytes:
    XFilterEvent returns: False
KeyPress event, serial 36, synthetic NO, window 0x1000001,
    root 0x25d, subw 0x0, time 80082536, (160,161), root:(160,206),
    state 0x10, keycode 38 (keysym 0x61, a), same_screen YES,
    XLookupString gives 1 bytes: (61) "a"
    XmbLookupString gives 1 bytes: (61) "a"
    XFilterEvent returns: False
KeyRelease event, serial 36, synthetic NO, window 0x1000001,
    root 0x25d, subw 0x0, time 80082536, (160,161), root:(160,206),
    state 0x10, keycode 38 (keysym 0x61, a), same_screen YES,
    XLookupString gives 1 bytes: (61) "a"
    XFilterEvent returns: False

Thu, 08 Feb 2018 19:36:15 GMT - emclain: owner, status changed

The other problem I'm having is with the Option (Alt) key. (If this is a separate issue I can file a separate ticket for it.)

On macOS Option is typically used to type accented characters. When I type Option keys in Xpra, it allows the Mac to turn the keystroke into an accented character rather than grabbing the raw keystroke.

When I type Option-A, here's what appears in the xpra log. You can see that xpra sends 'keyname':'aring' (Swedish 'a' with a ring over it: 'å') instead of just sending 'a' with the modifier.

2018-02-06 13:48:02,486 parse_key_event(<gtk.gdk.Event at 0x10b74daf8: GDK_KEY_PRESS keyval=Alt_L>, True)=<GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 1, 'string': '', 'keyname': 'Alt_L', 'pressed': True, 'keyval': 65513, 'keycode': 58}>
2018-02-06 13:48:02,486 handle_key_action(GLClientWindow(6 : gtk2.GLWindowBacking(6, (178, 178), None)), <GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 1, 'string': '', 'keyname': 'Alt_L', 'pressed': True, 'keyval': 65513, 'keycode': 58}>) wid=6
2018-02-06 13:48:02,487 swap keys: translating key '<GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 1, 'string': '', 'keyname': 'Alt_L', 'pressed': True, 'keyval': 65513, 'keycode': 58}>' to (115, 'Super_L')
2018-02-06 13:48:02,487 send_key_action(6, <GTKKeyEvent object, contents: {'modifiers': ['mod2'], 'group': 1, 'string': '', 'keyname': 'Super_L', 'pressed': True, 'keyval': 65513, 'keycode': 115}>)
2018-02-06 13:48:02,917 mask_to_names names=['mod1'], meta mod=mod1, control mod=control, num lock mod=mod2, num lock state=True
2018-02-06 13:48:02,918 mask_to_names(<flags GDK_MOD1_MASK of type GdkModifierType>)=['mod1', 'mod2']
2018-02-06 13:48:02,918 parse_key_event(<gtk.gdk.Event at 0x10b74daf8: GDK_KEY_PRESS keyval=aring>, True)=<GTKKeyEvent object, contents: {'modifiers': ['mod1', 'mod2'], 'group': 1, 'string': '\xc3\xa5', 'keyname': 'aring', 'pressed': True, 'keyval': 229, 'keycode': 0}>
2018-02-06 13:48:02,919 handle_key_action(GLClientWindow(6 : gtk2.GLWindowBacking(6, (178, 178), None)), <GTKKeyEvent object, contents: {'modifiers': ['mod1', 'mod2'], 'group': 1, 'string': '\xc3\xa5', 'keyname': 'aring', 'pressed': True, 'keyval': 229, 'keycode': 0}>) wid=6
2018-02-06 13:48:02,919 send_key_action(6, <GTKKeyEvent object, contents: {'modifiers': ['mod1', 'mod2'], 'group': 1, 'string': '\xc3\xa5', 'keyname': 'aring', 'pressed': True, 'keyval': 229, 'keycode': 0}>)
2018-02-06 13:48:03,117 mask_to_names names=['mod1'], meta mod=mod1, control mod=control, num lock mod=mod2, num lock state=True
2018-02-06 13:48:03,117 mask_to_names(<flags GDK_MOD1_MASK of type GdkModifierType>)=['mod1', 'mod2']
2018-02-06 13:48:03,117 parse_key_event(<gtk.gdk.Event at 0x10b74daf8: GDK_KEY_RELEASE keyval=aring>, False)=<GTKKeyEvent object, contents: {'modifiers': ['mod1', 'mod2'], 'group': 1, 'string': '\xc3\xa5', 'keyname': 'aring', 'pressed': False, 'keyval': 229, 'keycode': 0}>
2018-02-06 13:48:03,118 handle_key_action(GLClientWindow(6 : gtk2.GLWindowBacking(6, (178, 178), None)), <GTKKeyEvent object, contents: {'modifiers': ['mod1', 'mod2'], 'group': 1, 'string': '\xc3\xa5', 'keyname': 'aring', 'pressed': False, 'keyval': 229, 'keycode': 0}>) wid=6
2018-02-06 13:48:03,118 send_key_action(6, <GTKKeyEvent object, contents: {'modifiers': ['mod1', 'mod2'], 'group': 1, 'string': '\xc3\xa5', 'keyname': 'aring', 'pressed': False, 'keyval': 229, 'keycode': 0}>)
2018-02-06 13:48:03,541 mask_to_names names=['mod1'], meta mod=mod1, control mod=control, num lock mod=mod2, num lock state=True
2018-02-06 13:48:03,542 mask_to_names(<flags GDK_MOD1_MASK of type GdkModifierType>)=['mod1', 'mod2']
2018-02-06 13:48:03,542 parse_key_event(<gtk.gdk.Event at 0x10b74daf8: GDK_KEY_RELEASE keyval=Alt_L>, False)=<GTKKeyEvent object, contents: {'modifiers': ['mod1', 'mod2'], 'group': 0, 'string': '', 'keyname': 'Alt_L', 'pressed': False, 'keyval': 65513, 'keycode': 58}>
2018-02-06 13:48:03,542 handle_key_action(GLClientWindow(6 : gtk2.GLWindowBacking(6, (178, 178), None)), <GTKKeyEvent object, contents: {'modifiers': ['mod1', 'mod2'], 'group': 0, 'string': '', 'keyname': 'Alt_L', 'pressed': False, 'keyval': 65513, 'keycode': 58}>) wid=6
2018-02-06 13:48:03,542 swap keys: translating key '<GTKKeyEvent object, contents: {'modifiers': ['mod1', 'mod2'], 'group': 0, 'string': '', 'keyname': 'Alt_L', 'pressed': False, 'keyval': 65513, 'keycode': 58}>' to (115, 'Super_L')
2018-02-06 13:48:03,543 send_key_action(6, <GTKKeyEvent object, contents: {'modifiers': ['mod1', 'mod2'], 'group': 0, 'string': '', 'keyname': 'Super_L', 'pressed': False, 'keyval': 65513, 'keycode': 115}>)
2018-02-06 13:48:05,960 mask_to_names names=[], meta mod=mod1, control mod=control, num lock mod=mod2, num lock state=True
2018-02-06 13:48:05,960 mask_to_names(<flags GDK_BUTTON1_MASK of type GdkModifierType>)=['mod2']

And here's the output of xev through Xpra when I type Option-a. You can see there's some similar weirdness going on where Super_L gets pressed and released before the 'a'. And then, of course, 'aring' instead of 'a'.

KeyPress event, serial 55, synthetic NO, window 0x1000001,
    root 0x25d, subw 0x0, time 348575285, (167,167), root:(192,498),
    state 0x2010, keycode 127 (keysym 0xffeb, Super_L), same_screen YES,
    XKeysymToKeycode returns keycode: 115
    XLookupString gives 0 bytes:
    XmbLookupString gives 0 bytes:
    XFilterEvent returns: False
KeyRelease event, serial 55, synthetic NO, window 0x1000001,
    root 0x25d, subw 0x0, time 348575653, (167,167), root:(192,498),
    state 0x2030, keycode 127 (keysym 0xffeb, Super_L), same_screen YES,
    XKeysymToKeycode returns keycode: 115
    XLookupString gives 0 bytes:
    XFilterEvent returns: False
KeyPress event, serial 55, synthetic NO, window 0x1000001,
    root 0x25d, subw 0x0, time 348575653, (167,167), root:(192,498),
    state 0x2010, keycode 113 (keysym 0xffea, Alt_R), same_screen YES,
    XLookupString gives 0 bytes:    XmbLookupString gives 0 bytes:
    XFilterEvent returns: False
KeyPress event, serial 55, synthetic NO, window 0x1000001,
    root 0x25d, subw 0x0, time 348575653, (167,167), root:(192,498),
    state 0x2018, keycode 38 (keysym 0xe5, aring), same_screen YES,
    XLookupString gives 2 bytes: (c3 a5) "å"
    XmbLookupString gives 2 bytes: (c3 a5) "å"
    XFilterEvent returns: False
KeyRelease event, serial 55, synthetic NO, window 0x1000001,
    root 0x25d, subw 0x0, time 348575653, (167,167), root:(192,498),
    state 0x2018, keycode 38 (keysym 0xe5, aring), same_screen YES,
    XLookupString gives 2 bytes: (c3 a5) "å"
    XFilterEvent returns: False
KeyRelease event, serial 55, synthetic NO, window 0x1000001,
    root 0x25d, subw 0x0, time 348577206, (167,167), root:(192,498),
    state 0x18, keycode 113 (keysym 0xffea, Alt_R), same_screen YES,
    XLookupString gives 0 bytes:
    XFilterEvent returns: False





Thu, 08 Feb 2018 19:36:59 GMT - emclain: cc set


Mon, 12 Feb 2018 05:37:03 GMT - Antoine Martin:

In X11 (XQuartz), I have the setting "Option keys send Alt_L and Alt_R" checked. I don't know if this affects Xpra, but I figured I'd mention it.

It shouldn't. Xpra uses the native API only, not X11. But thanks for mentioning it.

I think the fundamental problem is that the modifier flags (mod1, mod2, etc.) are sometimes being interpreted according to the mapping received from the server, rather than using the appropriate mapping for the client.

Could be.

Running xmodmap -pm on my Mac yields this, which looks quite different for me than it does for you in comment:9

We care about the xmodmap in use by the server. This one you generated on the client using X11 is probably irrelevant. The modifier definitions seen by the client won't be going through X11 at all so they could be completely different, or not.

I think this might have something to do with the mod2 getting muddled up with num lock? At some point xpra interpreted the mod2 from Command as setting Num Lock, which was already set, so it had to generate a synthetic KeyRelease event?

That would make sense. Having the server's "-d keyboard" log would tell us for sure why it decided to do that. Can you attach it here?

The other problem I'm having is with the Option (Alt) key.

Is the key that gets printed or used the right one? Is the only problem the spurious Super_L press + release?


Tue, 13 Feb 2018 21:05:00 GMT - emclain: attachment set

xpra client log


Tue, 13 Feb 2018 21:05:26 GMT - emclain: attachment set

output of xev, corresponds to xpra.log


Tue, 13 Feb 2018 21:20:21 GMT - emclain:

Attached are the logs from the xpra client on my mac. Here's what I did:

  1. Started xev on server
  2. Started xpra client on Mac (logging starts here)
  3. Moused into xev
  4. Ctrl-A, Cmd-A, Option-A
  5. Ctrl-E, Cmd-E, Option-E
  6. Ctrl-M, Cmd-M, Option-M
  7. Moused out of xev

The other problem I'm having is with the Option (Alt) key.

Is the key that gets printed or used the right one? Is the only problem the spurious Super_L press + release?

The main problem with the Option key is that it's sending accented characters with an Alt modifier rather than the raw character with Alt modifier. Option-a produces Alt-å, Option-e produces Alt-´, and Option-m produces Alt-µ.

You can see in the example in the log that the Option-e (which on Mac is a combination key for the acute accent ´) actually affects the next character Ctrl-m, causing it to be sent as Ctrl-ḿ.

I would expect the Mac special keyboard handling of the Option key to be disabled in Xpra, and just send Alt-a, Alt-e, and Alt-m to be interpreted by the server.

(BTW, I think I just noticed that the first Ctrl-a in my xpra/xev logs produced the combination Ctrl-á. I think I must have had an acute accent ´ queued up before I started capturing.)


Wed, 14 Feb 2018 04:50:19 GMT - Antoine Martin:

I still don't see any server log.


Wed, 14 Feb 2018 19:52:54 GMT - emclain: attachment set

xpra server log


Wed, 14 Feb 2018 19:56:02 GMT - emclain:

Whoops, I missed that you wanted a server log. Attached.

The test procedure was a little different in that I hit a few un-modified keystrokes before and after the modified keystrokes.

  1. Moused into xev
  2. A, Ctrl-A, Cmd-A, Option-A, A
  3. E, Ctrl-E, Cmd-E, Option-E, E
  4. M, Ctrl-M, Cmd-M, Option-M, M
  5. Moused around
  6. Ctrl-A, Cmd-A, Option-A
  7. Ctrl-E, Cmd-E, Option-E
  8. Ctrl-M, Cmd-M, Option-M
  9. Moused out

Thanks!


Wed, 28 Mar 2018 05:02:24 GMT - Antoine Martin: status, component, description, milestone changed


Thu, 26 Apr 2018 17:31:13 GMT - Antoine Martin: owner, status changed

Fixes in r19088 + r19089 + r19090.

@emclain / Ray Donnelly: please try the latest 2.3 beta builds from https://xpra.org/beta/osx/. (ideally both the Python3 builds and the regular ones)


Fri, 01 Jun 2018 11:46:17 GMT - Antoine Martin: status changed; resolution set

Not heard back, closing.


Sat, 23 Jan 2021 05:29:01 GMT - migration script:

this ticket has been moved to: https://github.com/Xpra-org/xpra/issues/1609