diff --git a/src/msw/evtloop.cpp b/src/msw/evtloop.cpp index a415f8d7e5..3fcbbc6cc9 100644 --- a/src/msw/evtloop.cpp +++ b/src/msw/evtloop.cpp @@ -37,8 +37,9 @@ using wxMsgList = std::list; #endif // wxUSE_THREADS -// This is defined in src/msw/window.cpp. +// These are defined in src/msw/window.cpp. extern WPARAM wxVKBlockedByKeyboardHook; +extern bool wxMSWHandleEscapeKey(WXMSG* msg); // ============================================================================ // GUI wxEventLoop implementation @@ -61,6 +62,12 @@ bool wxGUIEventLoop::IsChildOfCriticalWindow(wxWindowMSW *win) bool wxGUIEventLoop::PreProcessMessage(WXMSG *msg) { + // Escape is special as wxEVT_CHAR_HOOK is generated for it from here and + // not from the keyboard hook, see the comment before this function in + // src/msw/window.cpp. + if ( wxMSWHandleEscapeKey(msg) ) + return true; + HWND hwnd = msg->hwnd; wxWindow *wndThis = wxGetWindowFromHWND((WXHWND)hwnd); wxWindow *wnd; diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index 9a3ba0846c..d105d0e8a3 100644 --- a/src/msw/textentry.cpp +++ b/src/msw/textentry.cpp @@ -588,41 +588,22 @@ private: if ( m_win->HasFlag(wxTE_PROCESS_TAB) ) specialKey = true; break; - - case WXK_ESCAPE: - specialKey = true; - break; } if ( specialKey ) { - // Check if the drop down is currently open. + // Check if the drop down is currently open: if it is, it handles + // these keys itself. DWORD dwFlags = 0; - if ( SUCCEEDED(m_autoCompleteDropDown->GetDropDownStatus(&dwFlags, - nullptr)) - && dwFlags == ACDD_VISIBLE ) + if ( FAILED(m_autoCompleteDropDown->GetDropDownStatus(&dwFlags, + nullptr)) + || dwFlags != ACDD_VISIBLE ) { - if ( event.GetKeyCode() == WXK_ESCAPE ) - { - // We need to dismiss the drop-down manually as Escape - // could be eaten by something else (e.g. EVT_CHAR_HOOK in - // the dialog that this control is found in) otherwise. - ::SendMessage(GetHwndOf(m_win), WM_KEYDOWN, WXK_ESCAPE, 0); - - // Do not skip the event in this case, we've already handled it. - return; - } - } - else // Drop down is not open. - { - // In this case we need to handle Return and Tab as both of + // But if it isn't, we need to handle Return and Tab as both of // them are simply eaten by the auto completer and never reach // us at all otherwise. - if ( event.GetKeyCode() != WXK_ESCAPE ) - { - m_entry->MSWProcessSpecialKey(event); - return; - } + m_entry->MSWProcessSpecialKey(event); + return; } } diff --git a/src/msw/window.cpp b/src/msw/window.cpp index cf157f3d0f..f8da98c206 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -7769,22 +7769,26 @@ bool wxIsIMEOpen(const wxWindow* win) } // anonymous namespace -// Windows keyboard hook. Allows interception of e.g. F1, ESCAPE -// in active frames and dialogs, regardless of where the focus is. +// Windows keyboard hook. Allows interception of keys in active frames and +// dialogs, regardless of where the focus is. static HHOOK wxTheKeyboardHook = 0; +static bool +wxSendCharHookEvent(WXWPARAM wParam, + WXLPARAM lParam, + const wxWindow** win = nullptr); + LRESULT APIENTRY wxKeyboardHook(int nCode, WXWPARAM wParam, WXLPARAM lParam) { + // We specifically exclude Esc here to avoid handling it too early, see + // wxMSWHandleEscapeKey() below for details. DWORD hiWord = HIWORD(lParam); - if ( nCode != HC_NOREMOVE && ((hiWord & KF_UP) == 0) ) + if ( nCode != HC_NOREMOVE && ((hiWord & KF_UP) == 0) && wParam != VK_ESCAPE ) { - wchar_t uc = 0; - int id = wxMSWKeyboard::VKToWX(wParam, lParam, &uc); - - // Don't intercept keyboard entry (notably Escape) if a modal window - // (not managed by wx, e.g. IME one) is currently opened as more often - // than not it needs all the keys for itself. + // Don't intercept keyboard entry if a modal window (not managed by wx, + // e.g. IME one) is currently opened as more often than not it needs + // all the keys for itself. // // Also don't catch it if a window currently captures the mouse as // Escape is normally used to release the mouse capture and if you @@ -7793,51 +7797,23 @@ wxKeyboardHook(int nCode, WXWPARAM wParam, WXLPARAM lParam) // certain to have focus while it has the capture. if ( !gs_modalEntryWindowCount && !::GetCapture() ) { - if ( id != WXK_NONE - || static_cast(uc) != WXK_NONE - ) + const wxWindow* win = nullptr; + if ( wxSendCharHookEvent(wParam, lParam, &win) ) { - wxWindow const* win = wxWindow::DoFindFocus(); - if ( !win ) + // When IME is active, we must let it have the event as + // otherwise it could just hang, see #22473. + if ( !wxIsIMEOpen(win) ) { - // Even if the focus got lost somehow, still send the event - // to the top level parent to allow a wxDialog to always - // close on Escape. - win = wxGetActiveWindow(); + // Stop processing of this event. + return 1; } - wxKeyEvent event(wxEVT_CHAR_HOOK); - MSWInitAnyKeyEvent(event, wParam, lParam, win); - - event.m_keyCode = id; - event.m_uniChar = uc; - event.SetUnicodeChar(uc); - - wxEvtHandler * const handler = win ? win->GetEventHandler() - : wxTheApp; - - // Do not let exceptions propagate out of the hook, it's a - // module boundary. - if ( handler && handler->SafelyProcessEvent(event) ) - { - if ( !event.IsNextEventAllowed() ) - { - // When IME is active, we must let it have the event as - // otherwise it could just hang, see #22473. - if ( !wxIsIMEOpen(win) ) - { - // Stop processing of this event. - return 1; - } - - // Because we don't stop processing of the event at - // Windows level, we are going to get WM_KEYDOWN for - // this key, but we need to ignore it as it's not - // supposed to be generated if wxEVT_CHAR_HOOK handled - // the event. - wxVKBlockedByKeyboardHook = wParam; - } - } + // Because we don't stop processing of the event at + // Windows level, we are going to get WM_KEYDOWN for + // this key, but we need to ignore it as it's not + // supposed to be generated if wxEVT_CHAR_HOOK handled + // the event. + wxVKBlockedByKeyboardHook = wParam; } } } @@ -7845,6 +7821,90 @@ wxKeyboardHook(int nCode, WXWPARAM wParam, WXLPARAM lParam) return (int)CallNextHookEx(wxTheKeyboardHook, nCode, wParam, lParam); } +// Send wxEVT_CHAR_HOOK for the given key to the focused window and return true +// if it was handled and no further events should be generated for this key. +// +// The window the event was sent to, which may be null, is returned in the +// output parameter. +static bool +wxSendCharHookEvent(WXWPARAM wParam, WXLPARAM lParam, const wxWindow** out) +{ + wchar_t uc = 0; + int id = wxMSWKeyboard::VKToWX(wParam, lParam, &uc); + + if ( id == WXK_NONE && static_cast(uc) == WXK_NONE ) + return false; + + const wxWindow* win = wxWindow::DoFindFocus(); + if ( !win ) + { + // Even if the focus got lost somehow, still send the event + // to the top level parent to allow a wxDialog to always + // close on Escape. + win = wxGetActiveWindow(); + } + + wxKeyEvent event(wxEVT_CHAR_HOOK); + MSWInitAnyKeyEvent(event, wParam, lParam, win); + + event.m_keyCode = id; + event.m_uniChar = uc; + event.SetUnicodeChar(uc); + + wxEvtHandler * const handler = win ? win->GetEventHandler() + : wxTheApp; + + // Do not let exceptions propagate out of the hook, it's a + // module boundary. + if ( handler && handler->SafelyProcessEvent(event) ) + { + if ( !event.IsNextEventAllowed() ) + { + if ( out ) + *out = win; + + return true; + } + } + + return false; +} + +// This function is called by wxGUIEventLoop::PreProcessMessage() to generate +// wxEVT_CHAR_HOOK for Escape. Unlike for the other keys, we can't do it from +// wxKeyboardHook() above because it is called too early, before the IME or the +// focused control get a chance to handle this key, and they often need it, +// e.g. to cancel the IME composition or to close the auto-completion popup. +// +// Returns true if the message was handled and must not be processed further. +bool wxMSWHandleEscapeKey(WXMSG* msg) +{ + if ( msg->message != WM_KEYDOWN || msg->wParam != VK_ESCAPE ) + return false; + + // See the comments in wxKeyboardHook() explaining these checks. + if ( gs_modalEntryWindowCount || ::GetCapture() ) + return false; + + if ( const HWND hwndFocus = ::GetFocus() ) + { + // Multiline EDIT controls or wxWindow with wxWANTS_CHARS style, always + // ask for all keys, but this shouldn't prevent Escape from closing the + // dialog containing them, so only take these flags into account if + // they're returned specifically for this message, as it's done by the + // windows which need Escape only temporarily, e.g. the edit control + // using auto-completion while its drop down is shown. + const LRESULT codeAny = ::SendMessage(hwndFocus, WM_GETDLGCODE, 0, 0); + const LRESULT codeEsc = ::SendMessage(hwndFocus, WM_GETDLGCODE, + msg->wParam, (LPARAM)msg); + + if ( (codeEsc & ~codeAny) & (DLGC_WANTALLKEYS | DLGC_WANTMESSAGE) ) + return false; + } + + return wxSendCharHookEvent(msg->wParam, msg->lParam); +} + void wxSetKeyboardHook(bool doIt) { if ( doIt )