macOS: Enable Latin key combinations when Command (⌘) modifier is pressed

When the Command key is pressed AppKit seems to do key equivalent
matching using a Latin/Roman interpretation of the current keyboard
layout.

For example, for a Greek layout, pressing Option+Command+C produces a
key event with chars="ç" and unmodchars="ψ", but AppKit still treats
this as a match for a key equivalent of Option+Command+C.

We can't do the same by just applying the modifiers to our key map,
as that too contains "ψ" for the Option+Command combination. What we
can do instead is take advantage of the fact that the Command modifier
layer in all/most keyboard layouts contains a Latin layer. We then
combine that with the modifiers of the event to produce the resulting
"Latin" key combination.

If the unmodified key is outside of Latin1, we also treat that as a
valid key combination, even if AppKit natively does not. For example,
for a Greek layout, we still want to support Option+Command+ψ as a key
combination, as it's unlikely to clash with the Latin key combination
we added above.

However, if the unmodified key is within Latin1, we skip it, to avoid
these types of conflicts. For example, in the same Greek layout, pressing
the key next to Tab will produce a Latin ';' symbol, but we've already
treated that as 'q', thanks to the Command modifier, so we skip the
potential Command+; key combination. This is also in line with what
AppKit natively does.

Fixes: QTBUG-96371
Fixes: QTBUG-79493
Task-number: QTBUG-112736
Change-Id: I30d678c1c7860642d3eed29c7757133ff74c6521
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Tor Arne Vestbø 2023-10-05 17:28:40 +02:00
parent 98889c2ffc
commit 422880c9db

View File

@ -522,13 +522,48 @@ QList<QKeyCombination> QAppleKeyMapper::possibleKeyCombinations(const QKeyEvent
auto eventModifiers = event->modifiers();
// The complete set of event modifiers, along with the
// unmodified key, is always a valid key combination,
// and the first priority.
ret << QKeyCombination::fromCombined(int(eventModifiers) + int(unmodifiedKey));
int startingModifierLayer = 0;
if (toCocoaModifiers(eventModifiers) & NSEventModifierFlagCommand) {
// When the Command key is pressed AppKit seems to do key equivalent
// matching using a Latin/Roman interpretation of the current keyboard
// layout. For example, for a Greek layout, pressing Option+Command+C
// produces a key event with chars="ç" and unmodchars="ψ", but AppKit
// still treats this as a match for a key equivalent of Option+Command+C.
// We can't do the same by just applying the modifiers to our key map,
// as that too contains "ψ" for the Option+Command combination. What we
// can do instead is take advantage of the fact that the Command
// modifier layer in all/most keyboard layouts contains a Latin
// layer. We then combine that with the modifiers of the event
// to produce the resulting "Latin" key combination.
static constexpr int kCommandLayer = 2;
ret << QKeyCombination::fromCombined(
int(eventModifiers) + int(keyMap[kCommandLayer]));
// If the unmodified key is outside of Latin1, we also treat
// that as a valid key combination, even if AppKit natively
// does not. For example, for a Greek layout, we still want
// to support Option+Command+ψ as a key combination, as it's
// unlikely to clash with the Latin key combination we added
// above.
// However, if the unmodified key is within Latin1, we skip
// it, to avoid these types of conflicts. For example, in
// the same Greek layout, pressing the key next to Tab will
// produce a Latin ';' symbol, but we've already treated that
// as 'q' above, thanks to the Command modifier, so we skip
// the potential Command+; key combination. This is also in
// line with what AppKit natively does.
// Skipping Latin1 unmodified keys also handles the case of
// a Latin layout, where the unmodified and modified keys
// are the same.
if (unmodifiedKey <= 0xff)
startingModifierLayer = 1;
}
// FIXME: We only compute the first 8 combinations. Why?
for (int i = 1; i < 8; ++i) {
for (int i = startingModifierLayer; i < 15; ++i) {
auto keyAfterApplyingModifiers = keyMap[i];
if (!keyAfterApplyingModifiers)
continue;