From 5ca0f8aa73ad0c2cebecfd2c2ae666dd401a6903 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 26 Sep 2026 22:20:52 +0200 Subject: [PATCH] Implement configurable accelerators support for wxOSX Use the new API, i.e. call ClaimsKeyBeforeAccelerator() and send wxEVT_ACCELERATOR_KEY, in wxOSX too, whenever possible, i.e. not for Cmd-Letter used by the menu items as we never get key events for them at all. This probably could be worked around by overriding performKeyEquivalent: but for now leave it like this because hopefully there should be no conflict between menu accelerators and standard text editing keys as Cmd+C etc act on the focused window anyhow by default, so the behaviour should be already correct anyhow. --- include/wx/osx/window.h | 12 +++++++++++ interface/wx/event.h | 8 ++++++++ src/osx/cocoa/window.mm | 28 ++++++++++++++++++++++---- src/osx/window_osx.cpp | 44 ++++++++++++++++++++++++++++++++--------- 4 files changed, 79 insertions(+), 13 deletions(-) diff --git a/include/wx/osx/window.h b/include/wx/osx/window.h index 384ff28c1d..76f0a56be2 100644 --- a/include/wx/osx/window.h +++ b/include/wx/osx/window.h @@ -319,6 +319,18 @@ public: virtual bool OSXHandleClicked( double timestampsec ); virtual bool OSXHandleKeyEvent( wxKeyEvent& event ); + +#if wxUSE_ACCEL + // Return false if the menu accelerator using the key corresponding to the + // given event shouldn't be used because this window needs this key for + // its own purposes. + // + // Note that this function only deals with the accelerators of the menu + // items, the ones defined by wxAcceleratorTable are checked later, from + // OSXHandleKeyEvent() itself. + bool OSXShouldUseMenuAcceleratorForKey( const wxKeyEvent& event ); +#endif // wxUSE_ACCEL + virtual void OSXSimulateFocusEvents(); bool IsNativeWindowWrapper() const { return m_isNativeWindowWrapper; } diff --git a/interface/wx/event.h b/interface/wx/event.h index c820aa9253..9c21fa43f8 100644 --- a/interface/wx/event.h +++ b/interface/wx/event.h @@ -1780,6 +1780,14 @@ public: the parents of this window, up to the top level one, which allows to handle it in the frame containing the control having the focus. + @note Under macOS this event is sent before `wxEVT_CHAR_HOOK` and not after + it. It is also not generated at all for the key combinations + involving the Command key used by the menu items because such keys are + handled by the system before the application has any chance to + intercept them. This is not usually a problem because the standard + macOS shortcuts such as `Cmd-C` are implemented by the menu items + acting on the currently focused control anyhow. + @beginEventTable{wxAcceleratorKeyEvent} @event{EVT_ACCELERATOR_KEY(func)} Process a `wxEVT_ACCELERATOR_KEY` event. diff --git a/src/osx/cocoa/window.mm b/src/osx/cocoa/window.mm index b27faaf903..c08339d637 100644 --- a/src/osx/cocoa/window.mm +++ b/src/osx/cocoa/window.mm @@ -1706,11 +1706,31 @@ void wxWidgetCocoaImpl::keyEvent(WX_NSEvent event, WXWidget slf, void *_cmd) { // there are key equivalents that are not command-combos and therefore not handled by cocoa automatically, // therefore we call the menubar directly here, exit if the menu is handling the shortcut - if ( [[[NSApplication sharedApplication] mainMenu] performKeyEquivalent:event] ) +#if wxUSE_ACCEL + // The focused window may want to handle this key itself instead of + // letting the menu use it as an accelerator, so ask it first. + bool useMenuAccel = true; + + if ( wxWindowMac* const peer = GetWXPeer() ) { - wxLogTrace(TRACE_KEYS, "%s processed as key equivalent by the menu", - wxDumpSelector((SEL)_cmd)); - return; + wxKeyEvent wxevent(wxEVT_KEY_DOWN); + SetupKeyEvent(wxevent, event); + + // SetupKeyEvent() may change the event type, so check that this + // is really a key press. + if ( wxevent.GetEventType() == wxEVT_KEY_DOWN ) + useMenuAccel = peer->OSXShouldUseMenuAcceleratorForKey(wxevent); + } + + if ( useMenuAccel ) +#endif // wxUSE_ACCEL + { + if ( [[[NSApplication sharedApplication] mainMenu] performKeyEquivalent:event] ) + { + wxLogTrace(TRACE_KEYS, "%s processed as key equivalent by the menu", + wxDumpSelector((SEL)_cmd)); + return; + } } BeginNativeKeyDownEvent(event); diff --git a/src/osx/window_osx.cpp b/src/osx/window_osx.cpp index d7d3c38666..f5d9ed1a86 100644 --- a/src/osx/window_osx.cpp +++ b/src/osx/window_osx.cpp @@ -2616,11 +2616,16 @@ bool wxWindowMac::OSXHandleKeyEvent( wxKeyEvent& event ) #if wxUSE_ACCEL if (event.GetEventType() == wxEVT_CHAR_HOOK) { - wxWindow *ancestor = this; - while (ancestor) + // Check if this key is used by one of the accelerators defined by + // wxAcceleratorTable: note that the accelerators of the menu items + // are not handled here, they are used by the menu bar itself, see + // OSXShouldUseMenuAcceleratorForKey(). + wxAcceleratorEntry entry; + wxWindow* ancestor = nullptr; + if ( FindAcceleratorForKey(event, entry, &ancestor) && !entry.GetMenuItem() ) { - int command = ancestor->GetAcceleratorTable()->GetCommand( event ); - if (command != -1) + const int command = entry.GetCommand(); + if ( ShouldUseAcceleratorForKey(event, command, nullptr) ) { wxEvtHandler * const handler = ancestor->GetEventHandler(); @@ -2636,11 +2641,6 @@ bool wxWindowMac::OSXHandleKeyEvent( wxKeyEvent& event ) // accelerator. return true; } - - if (ancestor->IsTopNavigationDomain(wxWindow::Navigation_Accel)) - break; - - ancestor = ancestor->GetParent(); } } #endif // wxUSE_ACCEL @@ -2648,6 +2648,32 @@ bool wxWindowMac::OSXHandleKeyEvent( wxKeyEvent& event ) return false; } +#if wxUSE_ACCEL + +bool wxWindowMac::OSXShouldUseMenuAcceleratorForKey( const wxKeyEvent& event ) +{ + wxAcceleratorEntry entry; + if ( !FindAcceleratorForKey(event, entry, nullptr /* don't need owner */) ) + { + // This key is not used by any accelerator at all. + return true; + } + + wxMenuItem* const item = entry.GetMenuItem(); + if ( !item ) + { + // This one comes from wxAcceleratorTable and will be dealt with in + // OSXHandleKeyEvent() when we get wxEVT_CHAR_HOOK for this key, don't + // do anything here (and, in particular, don't send the accelerator + // event twice for the same key). + return true; + } + + return ShouldUseAcceleratorForKey(event, entry.GetCommand(), item); +} + +#endif // wxUSE_ACCEL + wxSize wxWindowMac::GetDPI() const { return MakeDPIFromScaleFactor(GetDPIScaleFactor());