xpra icon
Bug tracker and wiki

Opened 4 months ago

Closed 7 weeks ago

#1609 closed defect (worksforme)

Command and Control keys on macOS host ends up as the same key on Linux

Reported by: Ray Donnelly Owned by: Ray Donnelly
Priority: major Milestone: 2.2
Component: android Version: trunk
Keywords: Cc:

Description

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!

Attachments (1)

apple-keyboard-layout.jpg (416.6 KB) - added by Antoine Martin 2 months ago.
apple keyboard layout

Download all attachments as: .zip

Change History (11)

comment:1 Changed 4 months ago by 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?

comment:2 Changed 4 months ago by Antoine Martin

Owner: changed from Antoine Martin to Ray Donnelly

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.

Last edited 4 months ago by Antoine Martin (previous) (diff)

comment:3 Changed 4 months ago by 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.

comment:4 Changed 4 months ago by 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

comment:5 Changed 4 months ago by Antoine Martin

Comments:

  • the first part that switches keycodes around, can we make this conditional on "swap-keys" being set? (you are running with it enabled, right?) and shouldn't it swap the other keys back too? (see updated patch below) - and maybe we should just enhance the KEY_TRANSLATIONS mechanism so it can be used to modify the keycode too (it would make the code more generic - no big deal, just a thought)
  • adding meta to gtk_util: makes sense, merged in r16564
  • the last part changes the meta modifier - so what happens to the modifier for Meta_L and Meta_R? does it never fire? Shouldn't this also be conditional on "swap-keys"? And rather than adding the meta modifier to "names", shouldn't this value be correct already from KeyboardBase.mask_to_names - in which case, maybe the modifier map is wrong...

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)
Last edited 4 months ago by Antoine Martin (previous) (diff)

comment:6 Changed 4 months ago by 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.

Last edited 4 months ago by Ray Donnelly (previous) (diff)

comment:7 Changed 4 months ago by 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.

Last edited 4 months ago by Ray Donnelly (previous) (diff)

comment:8 Changed 4 months ago by 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.

Changed 2 months ago by Antoine Martin

Attachment: apple-keyboard-layout.jpg added

apple keyboard layout

comment:9 Changed 2 months ago by 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:

  • ctrl_l:
    parse_key_event(<gtk.gdk.Event at 0x11e37bda0: GDK_KEY_PRESS keyval=Control_L>, True)=<GTKKeyEvent object, contents: \
        {'modifiers': ['mod2'], 'group': 0, 'string': '', 'keyname': 'Control_L', 'pressed': True, 'keyval': 65507, 'keycode': 59}>
    
  • alt_l (option_l):
    client 7: parse_key_event(<gtk.gdk.Event at 0x11e37be68: GDK_KEY_PRESS keyval=Alt_L>, True)=<GTKKeyEvent object, contents: \
        {'modifiers': ['mod2'], 'group': 1, 'string': '', 'keyname': 'Alt_L', 'pressed': True, 'keyval': 65513, 'keycode': 58}>
    
  • command_l:
    parse_key_event(<gtk.gdk.Event at 0x11e37bf08: GDK_KEY_PRESS keyval=Meta_L>, True)=<GTKKeyEvent object, contents: \
        {'modifiers': ['mod2'], 'group': 0, 'string': '', 'keyname': 'Meta_L', 'pressed': True, 'keyval': 65511, 'keycode': 55}>
    
  • command_r:
    parse_key_event(<gtk.gdk.Event at 0x11e37ba30: GDK_KEY_PRESS keyval=Meta_R>, True)=<GTKKeyEvent object, contents: \
       {'modifiers': ['mod2'], 'group': 0, 'string': '', 'keyname': 'Meta_R', 'pressed': True, 'keyval': 65512, 'keycode': 54}>
    
  • alt_r:
    parse_key_event(<gtk.gdk.Event at 0x11e37bda0: GDK_KEY_PRESS keyval=Alt_R>, True)=<GTKKeyEvent object, contents: \
        {'modifiers': ['mod2'], 'group': 1, 'string': '', 'keyname': 'Alt_R', 'pressed': True, 'keyval': 65514, 'keycode': 61}>
    
  • control_r:
    parse_key_event(<gtk.gdk.Event at 0x11e37bd78: GDK_KEY_PRESS keyval=Control_R>, True)=<GTKKeyEvent object, contents: \
        {'modifiers': ['mod2'], 'group': 0, 'string': '', 'keyname': 'Control_R', 'pressed': True, 'keyval': 65508, 'keycode': 62}>
    

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:

  • with swap keys: meta, super, control
  • without swap keys: control, super, alt

(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!

comment:10 Changed 7 weeks ago by Antoine Martin

Resolution: worksforme
Status: newclosed

Not heard back, closing.

Note: See TracTickets for help on using tickets.