Skip to content

Commit

Permalink
WindowsHooks: Attempt to fix keyboard issues for plugin clients
Browse files Browse the repository at this point in the history
This patch should resolve an issue introduced in
0ab3055 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
  • Loading branch information
reuk committed Oct 11, 2023
1 parent 4fa2839 commit 86d496d
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 31 deletions.
73 changes: 46 additions & 27 deletions modules/juce_gui_basics/native/juce_Windowing_windows.cpp
Expand Up @@ -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<HWND> (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
Expand Down Expand Up @@ -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:
Expand Down
7 changes: 3 additions & 4 deletions modules/juce_gui_basics/native/juce_WindowsHooks_windows.cpp
Expand Up @@ -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<MSG*> (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;
}
Expand Down

0 comments on commit 86d496d

Please sign in to comment.