Merge branch 'msw-esc-handling'

Change Esc handling in wxMSW to happen after IME and native controls.

See #27148.
This commit is contained in:
Vadim Zeitlin
2026-10-04 01:48:45 +02:00
3 changed files with 126 additions and 78 deletions
+8 -1
View File
@@ -37,8 +37,9 @@
using wxMsgList = std::list<MSG>;
#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;
+5 -24
View File
@@ -588,43 +588,24 @@ 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,
if ( FAILED(m_autoCompleteDropDown->GetDropDownStatus(&dwFlags,
nullptr))
&& dwFlags == ACDD_VISIBLE )
|| 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;
}
}
}
event.Skip();
}
+91 -31
View File
@@ -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,11 +7797,45 @@ 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<int>(uc) != WXK_NONE
)
const wxWindow* win = nullptr;
if ( wxSendCharHookEvent(wParam, lParam, &win) )
{
wxWindow const* win = wxWindow::DoFindFocus();
// 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;
}
}
}
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<int>(uc) == WXK_NONE )
return false;
const wxWindow* win = wxWindow::DoFindFocus();
if ( !win )
{
// Even if the focus got lost somehow, still send the event
@@ -7822,27 +7860,49 @@ wxKeyboardHook(int nCode, WXWPARAM wParam, WXLPARAM lParam)
{
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) )
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() )
{
// Stop processing of this event.
return 1;
// 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;
}
// 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;
}
}
}
}
}
return (int)CallNextHookEx(wxTheKeyboardHook, nCode, wParam, lParam);
return wxSendCharHookEvent(msg->wParam, msg->lParam);
}
void wxSetKeyboardHook(bool doIt)