[KBSWITCH] Make Alt+Shift working Part 1 (#8039)

Recently language switching (Alt+Shift)
was not working. This PR will fix
Alt+Shift (partially).
JIRA issue: CORE-18546
- Add WH_KEYBOARD_LL hook to
  detect Alt+Shift.
- Add delay to the action after language
  change.
- Increase g_SpecialIds not to be full.
- Delete useless ID_NEXTLAYOUT
  command.
This commit is contained in:
Katayama Hirofumi MZ
2025-05-27 20:09:03 +09:00
committed by GitHub
parent c03d7794b8
commit 3df71d678d
5 changed files with 162 additions and 164 deletions

View File

@@ -1,14 +1,17 @@
/*
* PROJECT: ReactOS Keyboard Layout Switcher
* FILE: base/applications/kbswitch/kbsdll/kbsdll.c
* PROGRAMMER: Dmitry Chapyshev <dmitry@reactos.org>
*
* PROJECT: ReactOS Keyboard Layout Switcher
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Switching Keyboard Layouts
* COPYRIGHT: Copyright Dmitry Chapyshev (dmitry@reactos.org)
* Copyright Colin Finck (mail@colinfinck.de)
* Copyright 2022-2025 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
*/
#include "../kbswitch.h"
HHOOK hWinHook = NULL;
HHOOK hShellHook = NULL;
HHOOK hKeyboardLLHook = NULL;
HINSTANCE hInstance = NULL;
HWND hKbSwitchWnd = NULL;
@@ -18,77 +21,108 @@ PostMessageToMainWnd(UINT Msg, WPARAM wParam, LPARAM lParam)
PostMessage(hKbSwitchWnd, Msg, wParam, lParam);
}
LRESULT CALLBACK
WinHookProc(int code, WPARAM wParam, LPARAM lParam)
static LRESULT CALLBACK
WinHookProc(INT code, WPARAM wParam, LPARAM lParam)
{
if (code < 0)
{
return CallNextHookEx(hWinHook, code, wParam, lParam);
}
switch (code)
{
case HCBT_ACTIVATE:
case HCBT_SETFOCUS:
{
HWND hwndFocus = (HWND)wParam;
if (hwndFocus && hwndFocus != hKbSwitchWnd)
{
PostMessageToMainWnd(WM_WINDOW_ACTIVATE, wParam, lParam);
}
PostMessageToMainWnd(WM_WINDOW_ACTIVATE, (WPARAM)hwndFocus, 0);
break;
}
break;
}
return CallNextHookEx(hWinHook, code, wParam, lParam);
}
LRESULT CALLBACK
ShellHookProc(int code, WPARAM wParam, LPARAM lParam)
static LRESULT CALLBACK
ShellHookProc(INT code, WPARAM wParam, LPARAM lParam)
{
if (code < 0)
{
return CallNextHookEx(hShellHook, code, wParam, lParam);
}
switch (code)
{
case HSHELL_WINDOWACTIVATED:
{
PostMessageToMainWnd(WM_WINDOW_ACTIVATE, wParam, 0);
break;
}
case HSHELL_LANGUAGE:
{
PostMessageToMainWnd(WM_LANG_CHANGED, wParam, lParam);
break;
}
break;
}
return CallNextHookEx(hShellHook, code, wParam, lParam);
}
BOOL WINAPI
KbSwitchSetHooks(VOID)
static LRESULT CALLBACK
KeyboardLLHook(INT code, WPARAM wParam, LPARAM lParam)
{
hWinHook = SetWindowsHookEx(WH_CBT, WinHookProc, hInstance, 0);
hShellHook = SetWindowsHookEx(WH_SHELL, ShellHookProc, hInstance, 0);
if (code < 0)
return CallNextHookEx(hKeyboardLLHook, code, wParam, lParam);
if (!hWinHook || !hShellHook)
if (code == HC_ACTION)
{
return FALSE;
KBDLLHOOKSTRUCT *pKbStruct = (KBDLLHOOKSTRUCT *)lParam;
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
{
BOOL bShiftPressed = GetAsyncKeyState(VK_SHIFT) < 0;
BOOL bAltPressed = GetAsyncKeyState(VK_MENU) < 0;
BOOL bCtrlPressed = GetAsyncKeyState(VK_CONTROL) < 0;
// Detect Alt+Shift and Ctrl+Shift
if ((pKbStruct->vkCode == VK_SHIFT && bAltPressed) ||
(pKbStruct->vkCode == VK_MENU && bShiftPressed) ||
(pKbStruct->vkCode == VK_SHIFT && bCtrlPressed) ||
(pKbStruct->vkCode == VK_CONTROL && bShiftPressed))
{
PostMessageToMainWnd(WM_LANG_CHANGED, 0, 0);
}
}
}
return TRUE;
return CallNextHookEx(hKeyboardLLHook, code, wParam, lParam);
}
VOID WINAPI
KbSwitchDeleteHooks(VOID)
BOOL APIENTRY
KbSwitchSetHooks(_In_ BOOL bDoHook)
{
if (hWinHook)
if (bDoHook)
{
UnhookWindowsHookEx(hWinHook);
hWinHook = NULL;
hWinHook = SetWindowsHookEx(WH_CBT, WinHookProc, hInstance, 0);
hShellHook = SetWindowsHookEx(WH_SHELL, ShellHookProc, hInstance, 0);
hKeyboardLLHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardLLHook, hInstance, 0);
if (hWinHook && hShellHook && hKeyboardLLHook)
return TRUE;
}
/* Unhook */
if (hKeyboardLLHook)
{
UnhookWindowsHookEx(hKeyboardLLHook);
hKeyboardLLHook = NULL;
}
if (hShellHook)
{
UnhookWindowsHookEx(hShellHook);
hShellHook = NULL;
}
if (hWinHook)
{
UnhookWindowsHookEx(hWinHook);
hWinHook = NULL;
}
return !bDoHook;
}
BOOL WINAPI
@@ -103,9 +137,7 @@ DllMain(IN HINSTANCE hinstDLL,
hInstance = hinstDLL;
hKbSwitchWnd = FindWindow(szKbSwitcherName, NULL);
if (!hKbSwitchWnd)
{
return FALSE;
}
}
break;
}

View File

@@ -1,2 +1 @@
1 stdcall KbSwitchSetHooks()
2 stdcall KbSwitchDeleteHooks()
1 stdcall KbSwitchSetHooks(long)

View File

@@ -1,12 +1,11 @@
/*
* PROJECT: Keyboard Layout Switcher
* FILE: base/applications/kbswitch/kbswitch.c
* PURPOSE: Switching Keyboard Layouts
* PROGRAMMERS: Dmitry Chapyshev (dmitry@reactos.org)
* Colin Finck (mail@colinfinck.de)
* Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
* PROJECT: ReactOS Keyboard Layout Switcher
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Switching Keyboard Layouts
* COPYRIGHT: Copyright Dmitry Chapyshev (dmitry@reactos.org)
* Copyright Colin Finck (mail@colinfinck.de)
* Copyright 2022-2025 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
*/
#include "kbswitch.h"
#include <shlobj.h>
#include <shlwapi_undoc.h>
@@ -32,12 +31,13 @@ WINE_DEFAULT_DEBUG_CHANNEL(internat);
#define WM_NOTIFYICONMSG (WM_USER + 248)
PKBSWITCHSETHOOKS KbSwitchSetHooks = NULL;
PKBSWITCHDELETEHOOKS KbSwitchDeleteHooks = NULL;
#define TIMER_ID_LANG_CHANGED_DELAYED 0x10000
#define TIMER_LANG_CHANGED_DELAY 200
FN_KbSwitchSetHooks KbSwitchSetHooks = NULL;
UINT ShellHookMessage = 0;
HINSTANCE hInst;
HANDLE hProcessHeap;
HINSTANCE g_hInst = NULL;
HMODULE g_hHookDLL = NULL;
INT g_nCurrentLayoutNum = 1;
HICON g_hTrayIcon = NULL;
@@ -45,8 +45,8 @@ HWND g_hwndLastActive = NULL;
INT g_cKLs = 0;
HKL g_ahKLs[64];
ULONG
NTAPI
/* Debug logging */
ULONG NTAPI
vDbgPrintExWithPrefix(IN PCCH Prefix,
IN ULONG ComponentId,
IN ULONG Level,
@@ -54,27 +54,24 @@ vDbgPrintExWithPrefix(IN PCCH Prefix,
IN va_list ap)
{
CHAR Buffer[512];
SIZE_T PrefixLength = strlen(Prefix);
strncpy(Buffer, Prefix, PrefixLength);
_vsnprintf(Buffer + PrefixLength,
sizeof(Buffer) - PrefixLength,
Format,
ap);
_vsnprintf(Buffer + PrefixLength, _countof(Buffer) - PrefixLength, Format, ap);
Buffer[_countof(Buffer) - 1] = ANSI_NULL; /* Avoid buffer overrun */
OutputDebugStringA(Buffer);
return 0;
}
typedef struct
typedef struct tagSPECIAL_ID
{
DWORD dwLayoutId;
HKL hKL;
TCHAR szKLID[CCH_LAYOUT_ID + 1];
} SPECIAL_ID, *PSPECIAL_ID;
SPECIAL_ID g_SpecialIds[80];
#define MAX_SPECIAL_IDS 256
SPECIAL_ID g_SpecialIds[MAX_SPECIAL_IDS];
INT g_cSpecialIds = 0;
static VOID LoadSpecialIds(VOID)
@@ -121,7 +118,7 @@ static VOID LoadSpecialIds(VOID)
if (g_cSpecialIds >= _countof(g_SpecialIds))
{
OutputDebugStringA("g_SpecialIds is full!");
ERR("g_SpecialIds is full!");
break;
}
}
@@ -158,16 +155,20 @@ GetKLIDFromHKL(HKL hKL, LPTSTR szKLID, SIZE_T KLIDLength)
}
}
static HKL GetActiveKL(VOID)
{
/* FIXME: Get correct console window's HKL when console window */
HWND hwndTarget = (g_hwndLastActive ? g_hwndLastActive : GetForegroundWindow());
DWORD dwTID = GetWindowThreadProcessId(hwndTarget, NULL);
return GetKeyboardLayout(dwTID);
}
static VOID UpdateLayoutList(HKL hKL OPTIONAL)
{
INT iKL;
if (!hKL)
{
HWND hwndTarget = (g_hwndLastActive ? g_hwndLastActive : GetForegroundWindow());
DWORD dwTID = GetWindowThreadProcessId(hwndTarget, NULL);
hKL = GetKeyboardLayout(dwTID);
}
hKL = GetActiveKL();
g_cKLs = GetKeyboardLayoutList(_countof(g_ahKLs), g_ahKLs);
@@ -191,15 +192,9 @@ static VOID UpdateLayoutList(HKL hKL OPTIONAL)
static HKL GetHKLFromLayoutNum(INT nLayoutNum)
{
if (0 <= (nLayoutNum - 1) && (nLayoutNum - 1) < g_cKLs)
{
return g_ahKLs[nLayoutNum - 1];
}
else
{
HWND hwndTarget = (g_hwndLastActive ? g_hwndLastActive : GetForegroundWindow());
DWORD dwTID = GetWindowThreadProcessId(hwndTarget, NULL);
return GetKeyboardLayout(dwTID);
}
return GetActiveKL();
}
static VOID
@@ -587,11 +582,9 @@ SetHooks(VOID)
}
#define IHOOK_SET 1
#define IHOOK_DELETE 2
KbSwitchSetHooks = (PKBSWITCHSETHOOKS) GetProcAddress(g_hHookDLL, MAKEINTRESOURCEA(IHOOK_SET));
KbSwitchDeleteHooks = (PKBSWITCHDELETEHOOKS) GetProcAddress(g_hHookDLL, MAKEINTRESOURCEA(IHOOK_DELETE));
KbSwitchSetHooks = (FN_KbSwitchSetHooks)GetProcAddress(g_hHookDLL, MAKEINTRESOURCEA(IHOOK_SET));
if (!KbSwitchSetHooks || !KbSwitchDeleteHooks || !KbSwitchSetHooks())
if (!KbSwitchSetHooks || !KbSwitchSetHooks(TRUE))
{
ERR("SetHooks failed\n");
return FALSE;
@@ -604,10 +597,10 @@ SetHooks(VOID)
VOID
DeleteHooks(VOID)
{
if (KbSwitchDeleteHooks)
if (KbSwitchSetHooks)
{
KbSwitchDeleteHooks();
KbSwitchDeleteHooks = NULL;
KbSwitchSetHooks(FALSE);
KbSwitchSetHooks = NULL;
}
if (g_hHookDLL)
@@ -654,20 +647,12 @@ UpdateLanguageDisplay(HWND hwnd, HKL hKL)
}
HWND
GetTargetWindow(HWND hwndFore)
GetTargetWindow(HWND hwndFore OPTIONAL)
{
TCHAR szClass[64];
HWND hwndIME;
HWND hwndTarget = hwndFore;
if (hwndTarget == NULL)
hwndTarget = GetForegroundWindow();
GetClassName(hwndTarget, szClass, _countof(szClass));
if (_tcsicmp(szClass, szKbSwitcherName) == 0)
HWND hwndTarget = (hwndFore ? hwndFore : GetForegroundWindow());
if (IsWndClassName(hwndTarget, szKbSwitcherName))
hwndTarget = g_hwndLastActive;
hwndIME = ImmGetDefaultIMEWnd(hwndTarget);
return (hwndIME ? hwndIME : hwndTarget);
return hwndTarget;
}
UINT
@@ -682,26 +667,17 @@ UpdateLanguageDisplayCurrent(HWND hwnd, HWND hwndFore)
static BOOL RememberLastActive(HWND hwnd, HWND hwndFore)
{
TCHAR szClass[64];
hwndFore = GetAncestor(hwndFore, GA_ROOT);
if (!IsWindowVisible(hwndFore) || !GetClassName(hwndFore, szClass, _countof(szClass)))
if (!IsWindowVisible(hwndFore))
return FALSE;
if (_tcsicmp(szClass, szKbSwitcherName) == 0 ||
_tcsicmp(szClass, TEXT("Shell_TrayWnd")) == 0)
if (IsWndClassName(hwndFore, szKbSwitcherName) ||
IsWndClassName(hwndFore, TEXT("Shell_TrayWnd")))
{
return FALSE; /* Special window */
}
/* FIXME: CONWND needs special handling */
if (_tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0)
{
HKL hKL = GetKeyboardLayout(0);
UpdateLanguageDisplay(hwnd, hKL);
}
g_hwndLastActive = hwndFore;
return TRUE;
}
@@ -734,19 +710,37 @@ WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
break;
}
case WM_LANG_CHANGED: /* Comes from kbsdll.dll and this module */
case WM_TIMER:
{
TRACE("WM_LANG_CHANGED: wParam:%p, lParam:%p\n", wParam, lParam);
UpdateLayoutList((HKL)lParam);
UpdateLanguageDisplay(hwnd, (HKL)lParam);
if (wParam == TIMER_ID_LANG_CHANGED_DELAYED)
{
KillTimer(hwnd, TIMER_ID_LANG_CHANGED_DELAYED);
HKL hKL = GetActiveKL();
UpdateLayoutList(hKL);
UpdateLanguageDisplay(hwnd, hKL);
}
break;
}
// WM_LANG_CHANGED message:
// wParam: HWND hwndTarget or zero
// lParam: HKL hKL or zero
case WM_LANG_CHANGED: /* Comes from kbsdll.dll and this module */
{
TRACE("WM_LANG_CHANGED: wParam:%p, lParam:%p\n", wParam, lParam);
/* Delayed action */
KillTimer(hwnd, TIMER_ID_LANG_CHANGED_DELAYED);
SetTimer(hwnd, TIMER_ID_LANG_CHANGED_DELAYED, TIMER_LANG_CHANGED_DELAY, NULL);
break;
}
// WM_WINDOW_ACTIVATE message:
// wParam: HWND hwndTarget or zero
// lParam: zero
case WM_WINDOW_ACTIVATE: /* Comes from kbsdll.dll and this module */
{
HWND hwndFore;
TRACE("WM_WINDOW_ACTIVATE: wParam:%p, lParam:%p\n", wParam, lParam);
hwndFore = GetForegroundWindow();
HWND hwndFore = wParam ? (HWND)wParam : GetForegroundWindow();
if (RememberLastActive(hwnd, hwndFore))
return UpdateLanguageDisplayCurrent(hwnd, hwndFore);
break;
@@ -764,24 +758,29 @@ WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
GetCursorPos(&pt);
SetForegroundWindow(hwnd);
INT nID;
if (lParam == WM_LBUTTONUP)
{
/* Rebuild the left popup menu on every click to take care of keyboard layout changes */
hLeftPopupMenu = BuildLeftPopupMenu();
TrackPopupMenu(hLeftPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
nID = TrackPopupMenu(hLeftPopupMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, NULL);
DestroyMenu(hLeftPopupMenu);
}
else
{
if (!s_hRightPopupMenu)
{
s_hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP));
s_hMenu = LoadMenu(g_hInst, MAKEINTRESOURCE(IDR_POPUP));
s_hRightPopupMenu = GetSubMenu(s_hMenu, 0);
}
TrackPopupMenu(s_hRightPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
nID = TrackPopupMenu(s_hRightPopupMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, NULL);
}
PostMessage(hwnd, WM_NULL, 0, 0);
if (nID)
PostMessage(hwnd, WM_COMMAND, nID, 0);
break;
}
}
@@ -807,46 +806,6 @@ WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
break;
}
case ID_NEXTLAYOUT:
{
HWND hwndTarget = (HWND)lParam, hwndTargetSave = NULL;
DWORD dwThreadID;
HKL hKL;
UINT uNum;
TCHAR szClass[64];
BOOL bCONWND = FALSE;
if (hwndTarget == NULL)
hwndTarget = g_hwndLastActive;
/* FIXME: CONWND needs special handling */
if (hwndTarget &&
GetClassName(hwndTarget, szClass, _countof(szClass)) &&
_tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0)
{
bCONWND = TRUE;
hwndTargetSave = hwndTarget;
hwndTarget = NULL;
}
if (hwndTarget)
{
dwThreadID = GetWindowThreadProcessId(hwndTarget, NULL);
hKL = GetKeyboardLayout(dwThreadID);
uNum = GetLayoutNum(hKL);
if (uNum != 0)
g_nCurrentLayoutNum = uNum;
}
ActivateLayout(hwnd, GetNextLayout(), hwndTarget, TRUE);
/* FIXME: CONWND needs special handling */
if (bCONWND)
ActivateLayout(hwnd, g_nCurrentLayoutNum, hwndTargetSave, TRUE);
break;
}
default:
{
if (1 <= LOWORD(wParam) && LOWORD(wParam) <= 1000)
@@ -866,7 +825,7 @@ WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
if (wParam == SPI_SETNONCLIENTMETRICS)
{
PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam);
PostMessage(hwnd, WM_WINDOW_ACTIVATE, 0, 0);
break;
}
}
@@ -874,6 +833,7 @@ WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
case WM_DESTROY:
{
KillTimer(hwnd, TIMER_ID_LANG_CHANGED_DELAYED);
DeleteHooks();
DestroyMenu(s_hMenu);
DeleteTrayIcon(hwnd);
@@ -893,9 +853,9 @@ WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
TRACE("ShellHookMessage: wParam:%p, lParam:%p\n", wParam, lParam);
if (wParam == HSHELL_LANGUAGE)
PostMessage(hwnd, WM_LANG_CHANGED, wParam, lParam);
else if (wParam == HSHELL_WINDOWACTIVATED)
PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam);
PostMessage(hwnd, WM_LANG_CHANGED, 0, 0);
else if (wParam == HSHELL_WINDOWACTIVATED || wParam == HSHELL_RUDEAPPACTIVATED)
PostMessage(hwnd, WM_WINDOW_ACTIVATE, 0, 0);
break;
}
@@ -938,8 +898,7 @@ _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdSh
return 1;
}
hInst = hInstance;
hProcessHeap = GetProcessHeap();
g_hInst = hInstance;
ZeroMemory(&WndClass, sizeof(WndClass));
WndClass.lpfnWndProc = WndProc;

View File

@@ -1,6 +1,6 @@
#pragma once
#include <stdarg.h>
#include <stdlib.h>
#include <windef.h>
#include <winbase.h>
#include <winuser.h>
@@ -14,17 +14,26 @@
#include "resource.h"
// Character Count of a layout ID like "00000409"
#define CCH_LAYOUT_ID 8
#define CCH_LAYOUT_ID 8 // Character Count of a layout ID like "00000409"
#define CCH_ULONG_DEC 10 // Maximum Character Count of a ULONG in decimal
// Maximum Character Count of a ULONG in decimal
#define CCH_ULONG_DEC 10
#define WM_KEY_PRESSED (WM_USER + 10100)
#define WM_LANG_CHANGED (WM_USER + 10200)
#define WM_WINDOW_ACTIVATE (WM_USER + 10300)
typedef BOOL (WINAPI *PKBSWITCHSETHOOKS) (VOID);
typedef VOID (WINAPI *PKBSWITCHDELETEHOOKS) (VOID);
typedef BOOL (APIENTRY *FN_KbSwitchSetHooks)(BOOL bDoHook);
const TCHAR szKbSwitcherName[] = INDICATOR_CLASS;
static inline BOOL
IsWndClassName(_In_opt_ HWND hwndTarget, PCTSTR pszName)
{
TCHAR szClass[32];
GetClassName(hwndTarget, szClass, _countof(szClass));
return lstrcmpi(szClass, pszName) == 0;
}
static inline BOOL
IsConsoleWnd(_In_opt_ HWND hwndTarget)
{
return IsWndClassName(hwndTarget, TEXT("ConsoleWindowClass"));
}

View File

@@ -9,4 +9,3 @@
/* Menu items */
#define ID_EXIT 10001
#define ID_PREFERENCES 10002
#define ID_NEXTLAYOUT 10003