diff mbox

[1/1] Improve Cocoa modifier key handling

Message ID 20170522180217.18234-1-ianloic@google.com
State New
Headers show

Commit Message

Cameron Esfahani via May 22, 2017, 6:02 p.m. UTC
I had two problems with QEMU on macOS:
 1) Sometimes when alt-tabbing to QEMU it would act as if the 'a' key
    was pressed so I'd get 'aaaaaaaaa....'.
 2) Using Sikuli to programatically send keys to the QEMU window text
    like "foo_bar" would come out as "fooa-bar".

They looked similar and after much digging the problem turned out to be
the same. When QEMU's ui/cocoa.m received an NSFlagsChanged NSEvent it
looked at the keyCode to determine what modifier key changed. This
usually works fine but sometimes the keyCode is 0 and the app should
instead be looking at the modifierFlags bitmask. Key code 0 is the 'a'
key.

I added code that handles keyCode == 0 differently. It checks the
modifierFlags and if they differ from QEMU's idea of which modifier
keys are currently pressed it toggles those changed keys.

This fixes my problems and seems work fine.

Signed-off-by: Ian McKellar <ianloic@google.com>
---
 ui/cocoa.m | 48 ++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 40 insertions(+), 8 deletions(-)

Comments

Gerd Hoffmann May 23, 2017, 10:03 a.m. UTC | #1
Hi,

> looked at the keyCode to determine what modifier key changed. This
> usually works fine but sometimes the keyCode is 0 and the app should
> instead be looking at the modifierFlags bitmask. Key code 0 is the
> 'a'
> key.
> 
> I added code that handles keyCode == 0 differently. It checks the
> modifierFlags and if they differ from QEMU's idea of which modifier
> keys are currently pressed it toggles those changed keys.

Sounds like this happens in case there is a modifier state change
without linked key event, such as state change while qemu did not have
the keyboard focus.  Nice that macos sends notifications in that case.

I'm wondering whenever we should just use modifierFlags all the time.

cheers,
  Gerd
Cameron Esfahani via May 23, 2017, 3:52 p.m. UTC | #2
On Tue, May 23, 2017 at 3:03 AM Gerd Hoffmann <kraxel@redhat.com> wrote:

> Sounds like this happens in case there is a modifier state change
> without linked key event, such as state change while qemu did not have
> the keyboard focus.  Nice that macos sends notifications in that case.
>

Yeah, I guess it makes sense to send an event to update modifier state when
keyboard focus returns.

>
> I'm wondering whenever we should just use modifierFlags all the time.
>

Probably. My initial patch tried to be minimally intrusive but I can try
reworking the NSEventTypeFlagsChanged handling to compare the new
modifierFlags to the last we've seen and synthesize the appropriate key
down/up events.

Ian
Cameron Esfahani via May 23, 2017, 5:39 p.m. UTC | #3
On Tue, May 23, 2017 at 8:52 AM Ian McKellar <ianloic@google.com> wrote:

> On Tue, May 23, 2017 at 3:03 AM Gerd Hoffmann <kraxel@redhat.com> wrote:
>
>>
>> I'm wondering whenever we should just use modifierFlags all the time.
>>
>
> Probably. My initial patch tried to be minimally intrusive but I can try
> reworking the NSEventTypeFlagsChanged handling to compare the new
> modifierFlags to the last we've seen and synthesize the appropriate key
> down/up events.
>
> After a little more experimentation I think that the approach in this
patch is the right one. modifierFlags doesn't[1] indicate which instance of
a modifier (ie: left or right) is being held. Except for
the NSEventTypeFlagsChanged that's synthesized when a window takes focus
the event's keyCode indicates which physical key is affected. That first
event after focus has keyCode==0 which we can detect because 0 is the
keyCode of the 'A' key which isn't a modifier.

Ian

[1] actually there are undocumented flags outside the
NSEventModifierFlagDeviceIndependentFlagsMask mask that indicate left/right
keys but I don't think we should use them.
Gerd Hoffmann May 24, 2017, 6:17 a.m. UTC | #4
Hi,

> After a little more experimentation I think that the approach in this
> patch is the right one. modifierFlags doesn't[1] indicate which
> instance of a modifier (ie: left or right) is being held.

Ok, makes sense.  One more thing:  I think capslock must be handled
differently as keydown + keyup toggle state.

cheers,
  Gerd
Cameron Esfahani via May 26, 2017, 11:39 p.m. UTC | #5
Sent another patch that does a better job of toggling caps-lock. I couldn't
make it fail with the old patch but I think the new patch is somewhat
better.

Ian

On Tue, May 23, 2017 at 11:17 PM Gerd Hoffmann <kraxel@redhat.com> wrote:

>   Hi,
>
> > After a little more experimentation I think that the approach in this
> > patch is the right one. modifierFlags doesn't[1] indicate which
> > instance of a modifier (ie: left or right) is being held.
>
> Ok, makes sense.  One more thing:  I think capslock must be handled
> differently as keydown + keyup toggle state.
>
> cheers,
>   Gerd
>
>
diff mbox

Patch

diff --git a/ui/cocoa.m b/ui/cocoa.m
index 207555edf7..e645befa13 100644
--- a/ui/cocoa.m
+++ b/ui/cocoa.m
@@ -52,6 +52,8 @@ 
 /* macOS 10.12 deprecated many constants, #define the new names for older SDKs */
 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
 #define NSEventMaskAny                  NSAnyEventMask
+#define NSEventModifierFlagCapsLock     NSAlphaShiftKeyMask
+#define NSEventModifierFlagShift        NSShiftKeyMask
 #define NSEventModifierFlagCommand      NSCommandKeyMask
 #define NSEventModifierFlagControl      NSControlKeyMask
 #define NSEventModifierFlagOption       NSAlternateKeyMask
@@ -536,6 +538,16 @@  QemuCocoaView *cocoaView;
     }
 }
 
+- (void) toggleModifier: (int)keycode {
+    if (modifiers_state[keycode] == 0) { // keydown
+        qemu_input_event_send_key_qcode(dcl->con, keycode, true);
+        modifiers_state[keycode] = 1;
+    } else { // keyup
+        qemu_input_event_send_key_qcode(dcl->con, keycode, false);
+        modifiers_state[keycode] = 0;
+    }
+}
+
 - (void) handleEvent:(NSEvent *)event
 {
     COCOA_DEBUG("QemuCocoaView: handleEvent\n");
@@ -547,7 +559,33 @@  QemuCocoaView *cocoaView;
 
     switch ([event type]) {
         case NSEventTypeFlagsChanged:
-            keycode = cocoa_keycode_to_qemu([event keyCode]);
+            if ([event keyCode] == 0) {
+                // When the Cocoa keyCode is zero that means keys should be
+                // synthesized based on the values in in the eventModifiers
+                // bitmask.
+
+                if (qemu_console_is_graphic(NULL)) {
+                    NSEventModifierFlags modifiers = [event modifierFlags];
+
+                    if (!!(modifiers & NSEventModifierFlagCapsLock) != !!modifiers_state[Q_KEY_CODE_CAPS_LOCK]) {
+                        [self toggleModifier:Q_KEY_CODE_CAPS_LOCK];
+                    }
+                    if (!!(modifiers & NSEventModifierFlagShift) != !!modifiers_state[Q_KEY_CODE_SHIFT]) {
+                        [self toggleModifier:Q_KEY_CODE_SHIFT];
+                    }
+                    if (!!(modifiers & NSEventModifierFlagControl) != !!modifiers_state[Q_KEY_CODE_CTRL]) {
+                        [self toggleModifier:Q_KEY_CODE_CTRL];
+                    }
+                    if (!!(modifiers & NSEventModifierFlagOption) != !!modifiers_state[Q_KEY_CODE_ALT]) {
+                        [self toggleModifier:Q_KEY_CODE_ALT];
+                    }
+                    if (!!(modifiers & NSEventModifierFlagCommand) != !!modifiers_state[Q_KEY_CODE_META_L]) {
+                        [self toggleModifier:Q_KEY_CODE_META_L];
+                    }
+                }
+            } else {
+                keycode = cocoa_keycode_to_qemu([event keyCode]);
+            }
 
             if ((keycode == Q_KEY_CODE_META_L || keycode == Q_KEY_CODE_META_R)
                && !isMouseGrabbed) {
@@ -562,13 +600,7 @@  QemuCocoaView *cocoaView;
                     qemu_input_event_send_key_qcode(dcl->con, keycode, true);
                     qemu_input_event_send_key_qcode(dcl->con, keycode, false);
                 } else if (qemu_console_is_graphic(NULL)) {
-                    if (modifiers_state[keycode] == 0) { // keydown
-                        qemu_input_event_send_key_qcode(dcl->con, keycode, true);
-                        modifiers_state[keycode] = 1;
-                    } else { // keyup
-                        qemu_input_event_send_key_qcode(dcl->con, keycode, false);
-                        modifiers_state[keycode] = 0;
-                    }
+                  [self toggleModifier:keycode];
                 }
             }