diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index d5f25718d9..4a1f24670e 100644 --- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -1579,6 +1579,14 @@ public: bool sendEventToInputContextOrComponent (NSEvent* ev) { + // In the case that an event was processed unsuccessfully in performKeyEquivalent and then + // posted back to keyDown by the system, this check will ensure that we don't attempt to + // process the same event a second time. + const auto newEvent = KeyEventAttributes::make (ev); + + if (std::exchange (lastSeenKeyEvent, newEvent) == newEvent) + return false; + // We assume that the event will be handled by the IME. // Occasionally, the inputContext may be sent key events like cmd+Q, which it will turn // into a noop: call and forward to doCommandBySelector:. @@ -1611,6 +1619,73 @@ public: } //============================================================================== + class KeyEventAttributes + { + auto tie() const + { + return std::tie (type, + modifierFlags, + timestamp, + windowNumber, + characters, + charactersIgnoringModifiers, + keyCode, + isRepeat); + } + + public: + static std::optional make (NSEvent* event) + { + const auto type = [event type]; + + if (type != NSEventTypeKeyDown && type != NSEventTypeKeyUp) + return {}; + + return KeyEventAttributes + { + type, + [event modifierFlags], + [event timestamp], + [event windowNumber], + nsStringToJuce ([event characters]), + nsStringToJuce ([event charactersIgnoringModifiers]), + [event keyCode], + static_cast ([event isARepeat]) + }; + } + + bool operator== (const KeyEventAttributes& other) const { return tie() == other.tie(); } + bool operator!= (const KeyEventAttributes& other) const { return tie() != other.tie(); } + + private: + KeyEventAttributes (NSEventType typeIn, + NSEventModifierFlags flagsIn, + NSTimeInterval timestampIn, + NSInteger windowNumberIn, + String charactersIn, + String charactersIgnoringModifiersIn, + unsigned short keyCodeIn, + bool isRepeatIn) + : type (typeIn), + modifierFlags (flagsIn), + timestamp (timestampIn), + windowNumber (windowNumberIn), + characters (charactersIn), + charactersIgnoringModifiers (charactersIgnoringModifiersIn), + keyCode (keyCodeIn), + isRepeat (isRepeatIn) + {} + + NSEventType type; + NSEventModifierFlags modifierFlags; + NSTimeInterval timestamp; + NSInteger windowNumber; + String characters; + String charactersIgnoringModifiers; + unsigned short keyCode; + bool isRepeat; + }; + NSWindow* window = nil; NSView* view = nil; WeakReference safeComponent; @@ -1632,6 +1707,8 @@ public: RectangleList deferredRepaints; uint32 lastRepaintTime; + std::optional lastSeenKeyEvent; + static ComponentPeer* currentlyFocusedPeer; static std::set keysCurrentlyDown; static int insideToFrontCall; @@ -2087,7 +2164,8 @@ struct JuceNSViewClass : public NSViewComponentPeerWrapper> addMethod (@selector (performKeyEquivalent:), [] (id self, SEL s, NSEvent* ev) -> BOOL { if (auto* owner = getOwner (self)) - return owner->sendEventToInputContextOrComponent (ev); + if (owner->sendEventToInputContextOrComponent (ev)) + return YES; return sendSuperclassMessage (self, s, ev); });