diff mbox series

[2/2] ui/cocoa: add option to swap Option and Command

Message ID 20220306111114.18285-3-akihiko.odaki@gmail.com
State New
Headers show
Series cocoa: keyboard quality of life | expand

Commit Message

Akihiko Odaki March 6, 2022, 11:11 a.m. UTC
From: Gustavo Noronha Silva <gustavo@noronha.dev.br>

On Mac OS X the Option key maps to Alt and Command to Super/Meta. This change
swaps them around so that Alt is the key closer to the space bar and Meta/Super
is between Control and Alt, like on non-Mac keyboards.

It is a cocoa display option, disabled by default.

Acked-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Gustavo Noronha Silva <gustavo@noronha.dev.br>
Message-Id: <20210713213200.2547-3-gustavo@noronha.dev.br>
Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com>
---
 qapi/ui.json    |  8 ++++++-
 qemu-options.hx |  3 ++-
 ui/cocoa.m      | 64 ++++++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 65 insertions(+), 10 deletions(-)

Comments

BALATON Zoltan March 6, 2022, 11:46 a.m. UTC | #1
On Sun, 6 Mar 2022, Akihiko Odaki wrote:
> From: Gustavo Noronha Silva <gustavo@noronha.dev.br>
>
> On Mac OS X the Option key maps to Alt and Command to Super/Meta. This change
> swaps them around so that Alt is the key closer to the space bar and Meta/Super
> is between Control and Alt, like on non-Mac keyboards.
>
> It is a cocoa display option, disabled by default.
>
> Acked-by: Markus Armbruster <armbru@redhat.com>
> Signed-off-by: Gustavo Noronha Silva <gustavo@noronha.dev.br>
> Message-Id: <20210713213200.2547-3-gustavo@noronha.dev.br>
> Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com>
> ---
> qapi/ui.json    |  8 ++++++-
> qemu-options.hx |  3 ++-
> ui/cocoa.m      | 64 ++++++++++++++++++++++++++++++++++++++++++-------
> 3 files changed, 65 insertions(+), 10 deletions(-)
>
> diff --git a/qapi/ui.json b/qapi/ui.json
> index 1e9893419fe..b082e1a7dee 100644
> --- a/qapi/ui.json
> +++ b/qapi/ui.json
> @@ -1270,10 +1270,16 @@
> #             a global grab on key events. (default: off)
> #             See https://support.apple.com/en-in/guide/mac-help/mh32356/mac
> #
> +# @swap-option-command: Swap the Option and Command keys so that their key
> +#                       codes match their position on non-Mac keyboards and
> +#                       you can use Meta/Super and Alt where you expect them.
> +#                       (default: off)
> +#
> # Since: 6.1
> ##
> { 'struct'  : 'DisplayCocoa',
> -  'data'    : { '*full-grab'     : 'bool' } }
> +  'data'    : { '*full-grab'           : 'bool',
> +                '*swap-option-command' : 'bool' } }

This option name is too long to type. Could we abbreviate it somehow? Like 
swap-opt-cmd or swap-alt-meta?

Regards,
BALATON Zoltan

> ##
> # @DisplayType:
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 4df9ccc3446..8431445e9c0 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -1917,7 +1917,8 @@ DEF("display", HAS_ARG, QEMU_OPTION_display,
>     "-display curses[,charset=<encoding>]\n"
> #endif
> #if defined(CONFIG_COCOA)
> -    "-display cocoa[,full_grab=on|off]\n"
> +    "-display cocoa[,full-grab=on|off]\n"
> +    "              [,swap-option-command=on|off]\n"
> #endif
> #if defined(CONFIG_OPENGL)
>     "-display egl-headless[,rendernode=<file>]\n"
> diff --git a/ui/cocoa.m b/ui/cocoa.m
> index c41bc615d33..b152d3a1563 100644
> --- a/ui/cocoa.m
> +++ b/ui/cocoa.m
> @@ -73,6 +73,7 @@
> typedef struct {
>     int width;
>     int height;
> +    bool swap_option_command;
> } QEMUScreen;
>
> static void cocoa_update(DisplayChangeListener *dcl,
> @@ -329,6 +330,7 @@ - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
>  */
> - (BOOL) isMouseGrabbed;
> - (BOOL) isAbsoluteEnabled;
> +- (BOOL) isSwapOptionCommandEnabled;
> - (float) cdx;
> - (float) cdy;
> - (QEMUScreen) gscreen;
> @@ -704,6 +706,13 @@ - (void) setFullGrab:(id)sender
>     CFRelease(tapEventsSrc);
> }
>
> +- (void) setSwapOptionCommand:(id)sender
> +{
> +    COCOA_DEBUG("QemuCocoaView: setSwapOptionCommand\n");
> +
> +    screen.swap_option_command = true;
> +}
> +
> - (void) toggleKey: (int)keycode {
>     qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode));
> }
> @@ -853,12 +862,22 @@ - (bool) handleEventLocked:(NSEvent *)event
>         qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false);
>     }
>     if (!(modifiers & NSEventModifierFlagOption)) {
> -        qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
> -        qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
> +        if ([self isSwapOptionCommandEnabled]) {
> +            qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
> +            qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
> +        } else {
> +            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
> +            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
> +        }
>     }
>     if (!(modifiers & NSEventModifierFlagCommand)) {
> -        qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
> -        qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
> +        if ([self isSwapOptionCommandEnabled]) {
> +            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
> +            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
> +        } else {
> +            qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
> +            qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
> +        }
>     }
>
>     switch ([event type]) {
> @@ -890,13 +909,21 @@ - (bool) handleEventLocked:(NSEvent *)event
>
>                 case kVK_Option:
>                     if (!!(modifiers & NSEventModifierFlagOption)) {
> -                        [self toggleKey:Q_KEY_CODE_ALT];
> +                        if ([self isSwapOptionCommandEnabled]) {
> +                            [self toggleKey:Q_KEY_CODE_META_L];
> +                        } else {
> +                            [self toggleKey:Q_KEY_CODE_ALT];
> +                        }
>                     }
>                     break;
>
>                 case kVK_RightOption:
>                     if (!!(modifiers & NSEventModifierFlagOption)) {
> -                        [self toggleKey:Q_KEY_CODE_ALT_R];
> +                        if ([self isSwapOptionCommandEnabled]) {
> +                            [self toggleKey:Q_KEY_CODE_META_R];
> +                        } else {
> +                            [self toggleKey:Q_KEY_CODE_ALT_R];
> +                        }
>                     }
>                     break;
>
> @@ -904,14 +931,22 @@ - (bool) handleEventLocked:(NSEvent *)event
>                 case kVK_Command:
>                     if (isMouseGrabbed &&
>                         !!(modifiers & NSEventModifierFlagCommand)) {
> -                        [self toggleKey:Q_KEY_CODE_META_L];
> +                        if ([self isSwapOptionCommandEnabled]) {
> +                            [self toggleKey:Q_KEY_CODE_ALT];
> +                        } else {
> +                            [self toggleKey:Q_KEY_CODE_META_L];
> +                        }
>                     }
>                     break;
>
>                 case kVK_RightCommand:
>                     if (isMouseGrabbed &&
>                         !!(modifiers & NSEventModifierFlagCommand)) {
> -                        [self toggleKey:Q_KEY_CODE_META_R];
> +                        if ([self isSwapOptionCommandEnabled]) {
> +                            [self toggleKey:Q_KEY_CODE_ALT_R];
> +                        } else {
> +                            [self toggleKey:Q_KEY_CODE_META_R];
> +                        }
>                     }
>                     break;
>             }
> @@ -1146,6 +1181,7 @@ - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {
> }
> - (BOOL) isMouseGrabbed {return isMouseGrabbed;}
> - (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
> +- (BOOL) isSwapOptionCommandEnabled {return screen.swap_option_command;}
> - (float) cdx {return cdx;}
> - (float) cdy {return cdy;}
> - (QEMUScreen) gscreen {return screen;}
> @@ -1338,6 +1374,13 @@ - (void) setFullGrab:(id)sender
>     [cocoaView setFullGrab:sender];
> }
>
> +- (void) setSwapOptionCommand:(id)sender
> +{
> +    COCOA_DEBUG("QemuCocoaAppController: setSwapOptionCommand\n");
> +
> +    [cocoaView setSwapOptionCommand:sender];
> +}
> +
> /* Tries to find then open the specified filename */
> - (void) openDocumentation: (NSString *) filename
> {
> @@ -2127,6 +2170,11 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
>             [controller setFullGrab: nil];
>         });
>     }
> +    if (opts->u.cocoa.has_swap_option_command && opts->u.cocoa.swap_option_command) {
> +        dispatch_async(dispatch_get_main_queue(), ^{
> +            [controller setSwapOptionCommand: nil];
> +        });
> +    }
>     if (opts->has_show_cursor && opts->show_cursor) {
>         cursor_hide = 0;
>     }
>
Markus Armbruster March 7, 2022, 7:17 a.m. UTC | #2
BALATON Zoltan <balaton@eik.bme.hu> writes:

> On Sun, 6 Mar 2022, Akihiko Odaki wrote:
>> From: Gustavo Noronha Silva <gustavo@noronha.dev.br>
>>
>> On Mac OS X the Option key maps to Alt and Command to Super/Meta. This change
>> swaps them around so that Alt is the key closer to the space bar and Meta/Super
>> is between Control and Alt, like on non-Mac keyboards.
>>
>> It is a cocoa display option, disabled by default.
>>
>> Acked-by: Markus Armbruster <armbru@redhat.com>
>> Signed-off-by: Gustavo Noronha Silva <gustavo@noronha.dev.br>
>> Message-Id: <20210713213200.2547-3-gustavo@noronha.dev.br>
>> Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com>
>> ---
>> qapi/ui.json    |  8 ++++++-
>> qemu-options.hx |  3 ++-
>> ui/cocoa.m      | 64 ++++++++++++++++++++++++++++++++++++++++++-------
>> 3 files changed, 65 insertions(+), 10 deletions(-)
>>
>> diff --git a/qapi/ui.json b/qapi/ui.json
>> index 1e9893419fe..b082e1a7dee 100644
>> --- a/qapi/ui.json
>> +++ b/qapi/ui.json
>> @@ -1270,10 +1270,16 @@
>> #             a global grab on key events. (default: off)
>> #             See https://support.apple.com/en-in/guide/mac-help/mh32356/mac
>> #
>> +# @swap-option-command: Swap the Option and Command keys so that their key
>> +#                       codes match their position on non-Mac keyboards and
>> +#                       you can use Meta/Super and Alt where you expect them.
>> +#                       (default: off)
>> +#
>> # Since: 6.1
>> ##
>> { 'struct'  : 'DisplayCocoa',
>> -  'data'    : { '*full-grab'     : 'bool' } }
>> +  'data'    : { '*full-grab'           : 'bool',
>> +                '*swap-option-command' : 'bool' } }
>
> This option name is too long to type. Could we abbreviate it somehow?

We've largely avoided abbreviations in the QAPI schema, for better or
worse.

> Like swap-opt-cmd or swap-alt-meta?

We should stick to how the keys are generally called on this platform.
I can't say (I'm not using it).
Akihiko Odaki March 7, 2022, 7:50 a.m. UTC | #3
On 2022/03/07 16:17, Markus Armbruster wrote:
> BALATON Zoltan <balaton@eik.bme.hu> writes:
> 
>> On Sun, 6 Mar 2022, Akihiko Odaki wrote:
>>> From: Gustavo Noronha Silva <gustavo@noronha.dev.br>
>>>
>>> On Mac OS X the Option key maps to Alt and Command to Super/Meta. This change
>>> swaps them around so that Alt is the key closer to the space bar and Meta/Super
>>> is between Control and Alt, like on non-Mac keyboards.
>>>
>>> It is a cocoa display option, disabled by default.
>>>
>>> Acked-by: Markus Armbruster <armbru@redhat.com>
>>> Signed-off-by: Gustavo Noronha Silva <gustavo@noronha.dev.br>
>>> Message-Id: <20210713213200.2547-3-gustavo@noronha.dev.br>
>>> Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com>
>>> ---
>>> qapi/ui.json    |  8 ++++++-
>>> qemu-options.hx |  3 ++-
>>> ui/cocoa.m      | 64 ++++++++++++++++++++++++++++++++++++++++++-------
>>> 3 files changed, 65 insertions(+), 10 deletions(-)
>>>
>>> diff --git a/qapi/ui.json b/qapi/ui.json
>>> index 1e9893419fe..b082e1a7dee 100644
>>> --- a/qapi/ui.json
>>> +++ b/qapi/ui.json
>>> @@ -1270,10 +1270,16 @@
>>> #             a global grab on key events. (default: off)
>>> #             See https://support.apple.com/en-in/guide/mac-help/mh32356/mac
>>> #
>>> +# @swap-option-command: Swap the Option and Command keys so that their key
>>> +#                       codes match their position on non-Mac keyboards and
>>> +#                       you can use Meta/Super and Alt where you expect them.
>>> +#                       (default: off)
>>> +#
>>> # Since: 6.1
>>> ##
>>> { 'struct'  : 'DisplayCocoa',
>>> -  'data'    : { '*full-grab'     : 'bool' } }
>>> +  'data'    : { '*full-grab'           : 'bool',
>>> +                '*swap-option-command' : 'bool' } }
>>
>> This option name is too long to type. Could we abbreviate it somehow?
> 
> We've largely avoided abbreviations in the QAPI schema, for better or
> worse.
> 
>> Like swap-opt-cmd or swap-alt-meta?
> 
> We should stick to how the keys are generally called on this platform.
> I can't say (I'm not using it).
> 

The patch series is now in:
https://patchew.org/QEMU/20220306231753.50277-1-philippe.mathieu.daude@gmail.com/

It uses swap-opt-cmd for the name. Please reply to the series if it is 
problematic.

Regards,
Akihiko Odaki
BALATON Zoltan March 7, 2022, 12:25 p.m. UTC | #4
On Mon, 7 Mar 2022, Akihiko Odaki wrote:
> On 2022/03/07 16:17, Markus Armbruster wrote:
>> BALATON Zoltan <balaton@eik.bme.hu> writes:
>> 
>>> On Sun, 6 Mar 2022, Akihiko Odaki wrote:
>>>> From: Gustavo Noronha Silva <gustavo@noronha.dev.br>
>>>> 
>>>> On Mac OS X the Option key maps to Alt and Command to Super/Meta. This 
>>>> change
>>>> swaps them around so that Alt is the key closer to the space bar and 
>>>> Meta/Super
>>>> is between Control and Alt, like on non-Mac keyboards.
>>>> 
>>>> It is a cocoa display option, disabled by default.
>>>> 
>>>> Acked-by: Markus Armbruster <armbru@redhat.com>
>>>> Signed-off-by: Gustavo Noronha Silva <gustavo@noronha.dev.br>
>>>> Message-Id: <20210713213200.2547-3-gustavo@noronha.dev.br>
>>>> Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com>
>>>> ---
>>>> qapi/ui.json    |  8 ++++++-
>>>> qemu-options.hx |  3 ++-
>>>> ui/cocoa.m      | 64 ++++++++++++++++++++++++++++++++++++++++++-------
>>>> 3 files changed, 65 insertions(+), 10 deletions(-)
>>>> 
>>>> diff --git a/qapi/ui.json b/qapi/ui.json
>>>> index 1e9893419fe..b082e1a7dee 100644
>>>> --- a/qapi/ui.json
>>>> +++ b/qapi/ui.json
>>>> @@ -1270,10 +1270,16 @@
>>>> #             a global grab on key events. (default: off)
>>>> #             See 
>>>> https://support.apple.com/en-in/guide/mac-help/mh32356/mac
>>>> #
>>>> +# @swap-option-command: Swap the Option and Command keys so that their 
>>>> key
>>>> +#                       codes match their position on non-Mac keyboards 
>>>> and
>>>> +#                       you can use Meta/Super and Alt where you expect 
>>>> them.
>>>> +#                       (default: off)
>>>> +#
>>>> # Since: 6.1
>>>> ##
>>>> { 'struct'  : 'DisplayCocoa',
>>>> -  'data'    : { '*full-grab'     : 'bool' } }
>>>> +  'data'    : { '*full-grab'           : 'bool',
>>>> +                '*swap-option-command' : 'bool' } }
>>> 
>>> This option name is too long to type. Could we abbreviate it somehow?
>> 
>> We've largely avoided abbreviations in the QAPI schema, for better or
>> worse.

There are already some abreviated options in this file and since users 
will need to use it to switch this on it's better to have something that 
can be typed without too much hassle and result in a command that still 
fits in a window...

>>> Like swap-opt-cmd or swap-alt-meta?
>> 
>> We should stick to how the keys are generally called on this platform.
>> I can't say (I'm not using it).
>> 
>
> The patch series is now in:
> https://patchew.org/QEMU/20220306231753.50277-1-philippe.mathieu.daude@gmail.com/
>
> It uses swap-opt-cmd for the name. Please reply to the series if it is 
> problematic.

The more common name for these keys on macOS is option and command 
although the opt key sometimes also has alt written on it and the code 
seems to use meta instead of command but I think swap-opt-cmd is simple 
and clear so unless it's mandatory to have very long options that's not 
practical for users I'd go with swap-opt-cmd (which is what's in the last 
patch from Philippe so it should be OK).

Regards,
BALATON Zoltan
diff mbox series

Patch

diff --git a/qapi/ui.json b/qapi/ui.json
index 1e9893419fe..b082e1a7dee 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1270,10 +1270,16 @@ 
 #             a global grab on key events. (default: off)
 #             See https://support.apple.com/en-in/guide/mac-help/mh32356/mac
 #
+# @swap-option-command: Swap the Option and Command keys so that their key
+#                       codes match their position on non-Mac keyboards and
+#                       you can use Meta/Super and Alt where you expect them.
+#                       (default: off)
+#
 # Since: 6.1
 ##
 { 'struct'  : 'DisplayCocoa',
-  'data'    : { '*full-grab'     : 'bool' } }
+  'data'    : { '*full-grab'           : 'bool',
+                '*swap-option-command' : 'bool' } }
 
 ##
 # @DisplayType:
diff --git a/qemu-options.hx b/qemu-options.hx
index 4df9ccc3446..8431445e9c0 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1917,7 +1917,8 @@  DEF("display", HAS_ARG, QEMU_OPTION_display,
     "-display curses[,charset=<encoding>]\n"
 #endif
 #if defined(CONFIG_COCOA)
-    "-display cocoa[,full_grab=on|off]\n"
+    "-display cocoa[,full-grab=on|off]\n"
+    "              [,swap-option-command=on|off]\n"
 #endif
 #if defined(CONFIG_OPENGL)
     "-display egl-headless[,rendernode=<file>]\n"
diff --git a/ui/cocoa.m b/ui/cocoa.m
index c41bc615d33..b152d3a1563 100644
--- a/ui/cocoa.m
+++ b/ui/cocoa.m
@@ -73,6 +73,7 @@ 
 typedef struct {
     int width;
     int height;
+    bool swap_option_command;
 } QEMUScreen;
 
 static void cocoa_update(DisplayChangeListener *dcl,
@@ -329,6 +330,7 @@  - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
  */
 - (BOOL) isMouseGrabbed;
 - (BOOL) isAbsoluteEnabled;
+- (BOOL) isSwapOptionCommandEnabled;
 - (float) cdx;
 - (float) cdy;
 - (QEMUScreen) gscreen;
@@ -704,6 +706,13 @@  - (void) setFullGrab:(id)sender
     CFRelease(tapEventsSrc);
 }
 
+- (void) setSwapOptionCommand:(id)sender
+{
+    COCOA_DEBUG("QemuCocoaView: setSwapOptionCommand\n");
+
+    screen.swap_option_command = true;
+}
+
 - (void) toggleKey: (int)keycode {
     qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode));
 }
@@ -853,12 +862,22 @@  - (bool) handleEventLocked:(NSEvent *)event
         qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false);
     }
     if (!(modifiers & NSEventModifierFlagOption)) {
-        qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
-        qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
+        if ([self isSwapOptionCommandEnabled]) {
+            qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
+            qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
+        } else {
+            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
+            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
+        }
     }
     if (!(modifiers & NSEventModifierFlagCommand)) {
-        qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
-        qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
+        if ([self isSwapOptionCommandEnabled]) {
+            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
+            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
+        } else {
+            qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
+            qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
+        }
     }
 
     switch ([event type]) {
@@ -890,13 +909,21 @@  - (bool) handleEventLocked:(NSEvent *)event
 
                 case kVK_Option:
                     if (!!(modifiers & NSEventModifierFlagOption)) {
-                        [self toggleKey:Q_KEY_CODE_ALT];
+                        if ([self isSwapOptionCommandEnabled]) {
+                            [self toggleKey:Q_KEY_CODE_META_L];
+                        } else {
+                            [self toggleKey:Q_KEY_CODE_ALT];
+                        }
                     }
                     break;
 
                 case kVK_RightOption:
                     if (!!(modifiers & NSEventModifierFlagOption)) {
-                        [self toggleKey:Q_KEY_CODE_ALT_R];
+                        if ([self isSwapOptionCommandEnabled]) {
+                            [self toggleKey:Q_KEY_CODE_META_R];
+                        } else {
+                            [self toggleKey:Q_KEY_CODE_ALT_R];
+                        }
                     }
                     break;
 
@@ -904,14 +931,22 @@  - (bool) handleEventLocked:(NSEvent *)event
                 case kVK_Command:
                     if (isMouseGrabbed &&
                         !!(modifiers & NSEventModifierFlagCommand)) {
-                        [self toggleKey:Q_KEY_CODE_META_L];
+                        if ([self isSwapOptionCommandEnabled]) {
+                            [self toggleKey:Q_KEY_CODE_ALT];
+                        } else {
+                            [self toggleKey:Q_KEY_CODE_META_L];
+                        }
                     }
                     break;
 
                 case kVK_RightCommand:
                     if (isMouseGrabbed &&
                         !!(modifiers & NSEventModifierFlagCommand)) {
-                        [self toggleKey:Q_KEY_CODE_META_R];
+                        if ([self isSwapOptionCommandEnabled]) {
+                            [self toggleKey:Q_KEY_CODE_ALT_R];
+                        } else {
+                            [self toggleKey:Q_KEY_CODE_META_R];
+                        }
                     }
                     break;
             }
@@ -1146,6 +1181,7 @@  - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {
 }
 - (BOOL) isMouseGrabbed {return isMouseGrabbed;}
 - (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
+- (BOOL) isSwapOptionCommandEnabled {return screen.swap_option_command;}
 - (float) cdx {return cdx;}
 - (float) cdy {return cdy;}
 - (QEMUScreen) gscreen {return screen;}
@@ -1338,6 +1374,13 @@  - (void) setFullGrab:(id)sender
     [cocoaView setFullGrab:sender];
 }
 
+- (void) setSwapOptionCommand:(id)sender
+{
+    COCOA_DEBUG("QemuCocoaAppController: setSwapOptionCommand\n");
+
+    [cocoaView setSwapOptionCommand:sender];
+}
+
 /* Tries to find then open the specified filename */
 - (void) openDocumentation: (NSString *) filename
 {
@@ -2127,6 +2170,11 @@  static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
             [controller setFullGrab: nil];
         });
     }
+    if (opts->u.cocoa.has_swap_option_command && opts->u.cocoa.swap_option_command) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [controller setSwapOptionCommand: nil];
+        });
+    }
     if (opts->has_show_cursor && opts->show_cursor) {
         cursor_hide = 0;
     }