From 86d496d424f5587756b856acf17a3d1bb6456ece Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 10 Oct 2023 20:17:35 +0100 Subject: [PATCH] WindowsHooks: Attempt to fix keyboard issues for plugin clients This patch should resolve an issue introduced in 0ab30555fcf335fea220bc50cc8938ec689a3800 where arrow keys and other directional keys (home, end, page up, page down) stopped working as expected. With this patch in place, - IME input in plugins should work correctly, including for languages with a selection palette (Japanese) and languages where multiple keypresses combine to a single character (Korean). - Keyboard shortcuts should work (cut, copy, paste) - Directional keys should work --- .../native/juce_Windowing_windows.cpp | 73 ++++++++++++------- .../native/juce_WindowsHooks_windows.cpp | 7 +- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index aee5fb98912f..6444b71c4dc3 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -2329,49 +2329,68 @@ class HWNDComponentPeer final : public ComponentPeer, JUCE_DECLARE_NON_COPYABLE (FileDropTarget) }; - static bool offerKeyMessageToJUCEWindow (MSG& m) + static bool offerKeyMessageToJUCEWindow (const MSG& msg) { - auto* peer = getOwnerOfWindow (m.hwnd); + // If this isn't a keyboard message, let the host deal with it. - if (peer == nullptr) + constexpr UINT messages[] { WM_KEYDOWN, WM_SYSKEYDOWN, WM_KEYUP, WM_SYSKEYUP, WM_CHAR, WM_SYSCHAR }; + + if (std::find (std::begin (messages), std::end (messages), msg.message) == std::end (messages)) return false; + auto* peer = getOwnerOfWindow (msg.hwnd); auto* focused = Component::getCurrentlyFocusedComponent(); - if (focused == nullptr || focused->getPeer() != peer) + if (focused == nullptr || peer == nullptr || focused->getPeer() != peer) return false; - if (TranslateMessage (&m)) - return true; + auto* hwnd = static_cast (peer->getNativeHandle()); - constexpr UINT keyMessages[] { WM_KEYDOWN, - WM_KEYUP, - WM_SYSKEYDOWN, - WM_SYSKEYUP, - WM_CHAR }; + if (hwnd == nullptr) + return false; - const auto messageTypeMatches = [&] (UINT msg) { return m.message == msg; }; + ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd }; - if (std::none_of (std::begin (keyMessages), std::end (keyMessages), messageTypeMatches)) - return false; + // If we've been sent a text character, process it as text. - ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { m.hwnd }; + if (msg.message == WM_CHAR || msg.message == WM_SYSCHAR) + return peer->doKeyChar ((int) msg.wParam, msg.lParam); - if (m.message == WM_CHAR) - return peer->doKeyChar ((int) m.wParam, m.lParam); + // The event was a keypress, rather than a text character - switch (m.message) + if (auto* target = peer->findCurrentTextInputTarget()) { - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - return peer->doKeyDown (m.wParam); + // If there's a focused text input target, we want to attempt "real" text input with an + // IME, and we want to prevent the host from eating keystrokes (spaces etc.). - case WM_KEYUP: - case WM_SYSKEYUP: - return peer->doKeyUp (m.wParam); + TranslateMessage (&msg); + + // TranslateMessage may post WM_CHAR back to the window, so we remove those messages + // from the queue before the host gets to see them. + // This will dispatch pending WM_CHAR messages, so we may end up reentering + // offerKeyMessageToJUCEWindow and hitting the WM_CHAR case above. + // We always return true if WM_CHAR is posted so that the keypress is not forwarded + // to the host. Otherwise, the host may call TranslateMessage again on this message, + // resulting in duplicate WM_CHAR messages being posted. + + MSG peeked{}; + if (PeekMessage (&peeked, hwnd, WM_CHAR, WM_DEADCHAR, PM_REMOVE) + || PeekMessage (&peeked, hwnd, WM_SYSCHAR, WM_SYSDEADCHAR, PM_REMOVE)) + { + return true; + } + + // If TranslateMessage didn't add a WM_CHAR to the queue, fall back to processing the + // event as a plain keypress } - return false; + // There's no text input target, or the key event wasn't translated, so we'll just see if we + // can use the plain keystroke event + + if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN) + return peer->doKeyDown (msg.wParam); + + return peer->doKeyUp (msg.wParam); } double getPlatformScaleFactor() const noexcept override @@ -4350,10 +4369,10 @@ class HWNDComponentPeer final : public ComponentPeer, case WM_IME_SETCONTEXT: imeHandler.handleSetContext (h, wParam == TRUE); lParam &= ~(LPARAM) ISC_SHOWUICOMPOSITIONWINDOW; - break; + return ImmIsUIMessage (h, message, wParam, lParam); case WM_IME_STARTCOMPOSITION: imeHandler.handleStartComposition (*this); return 0; - case WM_IME_ENDCOMPOSITION: imeHandler.handleEndComposition (*this, h); break; + case WM_IME_ENDCOMPOSITION: imeHandler.handleEndComposition (*this, h); return 0; case WM_IME_COMPOSITION: imeHandler.handleComposition (*this, h, lParam); return 0; case WM_GETDLGCODE: diff --git a/modules/juce_gui_basics/native/juce_WindowsHooks_windows.cpp b/modules/juce_gui_basics/native/juce_WindowsHooks_windows.cpp index 5a3fae24ae84..97afe888ee72 100644 --- a/modules/juce_gui_basics/native/juce_WindowsHooks_windows.cpp +++ b/modules/juce_gui_basics/native/juce_WindowsHooks_windows.cpp @@ -65,12 +65,11 @@ class WindowsHooks::Hooks static LRESULT CALLBACK keyboardHookCallback (int nCode, WPARAM wParam, LPARAM lParam) { - MSG& msg = *(MSG*) lParam; + auto& msg = *reinterpret_cast (lParam); - if (nCode == HC_ACTION && wParam == PM_REMOVE - && HWNDComponentPeer::offerKeyMessageToJUCEWindow (msg)) + if (nCode == HC_ACTION && wParam == PM_REMOVE && HWNDComponentPeer::offerKeyMessageToJUCEWindow (msg)) { - zerostruct (msg); + msg = {}; msg.message = WM_USER; return 0; }