Page 1 of 1
Dead keys with keyboard hook - how?
Posted: Wed Dec 24, 2025 3:33 am
by BarryG
Hi all. Hope you have a Merry Christmas.
I'm using a modified version of the keyboard hook code by Michael (
viewtopic.php?p=600684#p600684), and a user has emailed me saying that my app doesn't let him type in his native language anymore, because the "dead keys" of a Portuguese layout aren't being recognized. I'm using the keyboard layout shown in the screenshot below.
So here's a small test snippet where basically I'm using his keyboard language and trying to hold down
^ and then type
a to get
ä. It's not working, and quite frankly I'm not sure how to fix it. What makes it harder is that my app does auto-completion of typed text, so as you can see in the snippet I'm using the "text$" variable to track what's been typed, so this needs to be taken in consideration as well.
Note: The snippet doesn't take the Shift key into account, but it should (so Shift+A should show "A" and not "a" like it does now).
The end goal is for "text$" to be
Hi ä there when I type
Hi {shiftdown}^{shiftup}a there.
PS: The snippet works perfectly fine with an English keyboard layout.
Code: Select all
Structure KeyboardState
b.b[256]
EndStructure
Global kbs.KeyboardState, Hook, text$
Procedure.l myKeyboardHook(nCode, wParam, *p.KBDLLHOOKSTRUCT)
If nCode = #HC_ACTION
If wParam = #WM_KEYUP Or wParam = #WM_SYSKEYUP
key = *p\vkcode
k$=Space(9)
ToAscii_(key,MapVirtualKey_(key,#MAPVK_VK_TO_CHAR),@kbs,@k$,0)
text$ + Trim(k$)
SetGadgetText(1, text$)
EndIf
EndIf
ProcedureReturn CallNextHookEx_(0, nCode, wParam, *p)
EndProcedure
OpenWindow(0,0,0,500,180,"Hook Test",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
StringGadget(0,10,10,480,130,"")
TextGadget(1,10,150,480,20,"")
SetActiveGadget(0)
SetWindowsHookEx_(#WH_KEYBOARD_LL,@myKeyboardHook(),GetModuleHandle_(0),0)
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
quit=1
EndSelect
Until quit
Re: Dead keys with keyboard hook - how?
Posted: Wed Dec 24, 2025 11:11 am
by breeze4me
There is a way to track character input rather than individual key presses, as shown in the code below. If necessary, use it with a keyboard hook.
There's also a WM_DEADCHAR message, so check it out if needed.
https://learn.microsoft.com/en-us/windo ... m-deadchar
Code: Select all
Structure KeyboardState
b.b[256]
EndStructure
Global kbs.KeyboardState, Hook, text$
Procedure.l myGetMsgProc(nCode, wParam, *p.MSG)
If nCode = #HC_ACTION
If *p\message = #WM_CHAR
text$ + Chr(*p\wParam)
SetGadgetText(1, text$)
EndIf
EndIf
ProcedureReturn CallNextHookEx_(0, nCode, wParam, *p)
EndProcedure
OpenWindow(0,0,0,500,180,"Hook Test",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
StringGadget(0,10,10,480,130,"")
TextGadget(1,10,150,480,20,"")
SetActiveGadget(0)
Hook = SetWindowsHookEx_(#WH_GETMESSAGE,@myGetMsgProc(),GetModuleHandle_(0),GetCurrentThreadId_())
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
quit=1
EndSelect
Until quit
UnhookWindowsHookEx_(Hook)
Re: Dead keys with keyboard hook - how?
Posted: Wed Dec 24, 2025 11:01 pm
by BarryG
Thanks for the example, breeze4me.

Works perfectly like that, but I can't seem to make it work globally outside my app (for example with Notepad so that the typed chars are correct in Notepad and also correctly shown in the TextGadget). I will try again tomorrow, as it's Christmas morning here in Australia (8am). Thanks for the present!

Re: Dead keys with keyboard hook - how?
Posted: Thu Dec 25, 2025 8:58 am
by breeze4me
BarryG wrote: Wed Dec 24, 2025 11:01 pm
(for example with Notepad so that the typed chars are correct in Notepad and also correctly shown in the TextGadget).
Merry Christmas!
Tested on Windows 10.
If the handles for Notepad are not found, please fix it yourself.
Hook.dll code:
Code: Select all
EnableExplicit
#APP_HookInput = #WM_APP + 246
#GUID_FileMap$ = "MyAppFM_bd9b51f3-9905-4b7a-a63b-7ea7353e2c42"
Global g_hMod, g_hFileMap, *g_MapView
ProcedureDLL.l myGetMsgProc(nCode, wParam, *p.MSG)
Protected hWnd
If nCode = #HC_ACTION
If *p\message = #WM_CHAR
If *g_MapView
hWnd = PeekI(*g_MapView)
If hWnd
SendMessage_(hWnd, #APP_HookInput, *p\hwnd, *p\wParam)
EndIf
EndIf
EndIf
EndIf
ProcedureReturn CallNextHookEx_(0, nCode, wParam, *p)
EndProcedure
ProcedureDLL SetHook(hWndTarget)
Protected hHook, tid.l
If hWndTarget
tid = GetWindowThreadProcessId_(hWndTarget, 0)
If tid
hHook = SetWindowsHookEx_(#WH_GETMESSAGE, @myGetMsgProc(), g_hMod, tid)
EndIf
EndIf
ProcedureReturn hHook
EndProcedure
ProcedureDLL UnHook(hHook)
ProcedureReturn UnhookWindowsHookEx_(hHook)
EndProcedure
ProcedureDLL AttachProcess(Instance)
g_hMod = Instance
g_hFileMap = OpenFileMapping_(#FILE_MAP_READ, 0, #GUID_FileMap$)
If g_hFileMap
*g_MapView = MapViewOfFile_(g_hFileMap, #FILE_MAP_READ, 0, 0, 64)
EndIf
EndProcedure
ProcedureDLL DetachProcess(Instance)
If *g_MapView
UnmapViewOfFile_(*g_MapView)
EndIf
If g_hFileMap
CloseHandle_(g_hFileMap)
EndIf
EndProcedure
Main code:
Code: Select all
#APP_HookInput = #WM_APP + 246
#GUID_FileMap$ = "MyAppFM_bd9b51f3-9905-4b7a-a63b-7ea7353e2c42"
Prototype ptSetHook(hWndTarget)
Prototype ptUnHook(hHook)
Global SetHook.ptSetHook
Global UnHook.ptUnHook
Global NewList g_hHook()
Global NewMap g_Text$()
Global g_hFileMap, *g_MapView
If OpenLibrary(0, "Hook.dll")
SetHook = GetFunction(0, "SetHook")
UnHook = GetFunction(0, "UnHook")
If SetHook And UnHook
Debug "Open."
Else
Debug "Cannot open the hook Dll."
End
EndIf
EndIf
Procedure WndProc_Main(hWnd, uMsg, wParam, lParam)
If uMsg = #APP_HookInput
g_Text$(Str(wParam)) + Chr(lParam)
Debug "Target hWnd: " + wParam + " " + g_Text$()
ProcedureReturn 1
EndIf
ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure
;-----------------------------------------------------------
;- Test
;-----------------------------------------------------------
; Open two Notepad windows.
RunProgram("notepad.exe")
RunProgram("notepad.exe")
Sleep_(1500)
Global NewList g_HwndNotepad()
; Save the handles of two Notepad windows.
Procedure EnumWindowsProc(hWnd, lParam)
Protected s.s{64}
If GetClassName_(hWnd, @s, 60)
If s = "Notepad"
If AddElement(g_HwndNotepad())
g_HwndNotepad() = hWnd
EndIf
EndIf
EndIf
ProcedureReturn #True
EndProcedure
EnumWindows_(@EnumWindowsProc(), 0)
If ListSize(g_HwndNotepad()) <> 2
Debug "Notepad instances <> 2. Quit."
End
EndIf
If OpenWindow(0, 0, 0, 270, 80, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ButtonGadget(1, 10, 20, 120, 30, "Set Hook")
ButtonGadget(2, 140, 20, 120, 30, "UnHook")
SetWindowCallback(@WndProc_Main(), 0)
; After creating shared memory, store the window handle value that will receive messages.
g_hFileMap = CreateFileMapping_(#INVALID_HANDLE_VALUE, 0, #PAGE_READWRITE | #SEC_COMMIT, 0, 64, #GUID_FileMap$)
If g_hFileMap
*g_MapView = MapViewOfFile_(g_hFileMap, #FILE_MAP_ALL_ACCESS, 0, 0, 64)
If *g_MapView
PokeI(*g_MapView, WindowID(0))
EndIf
EndIf
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
Quit = 1
Case #PB_Event_Gadget
Select EventGadget()
Case 1
ForEach g_HwndNotepad()
hHook = SetHook(g_HwndNotepad())
If hHook
If AddElement(g_hHook())
g_hHook() = hHook
EndIf
Debug "hHook : " + hHook
EndIf
Next
Case 2
ForEach g_hHook()
If g_hHook()
Result = UnhookWindowsHookEx_(g_hHook())
Debug "UnHook: " + g_hHook() + " " + Result
g_hHook() = 0
EndIf
Next
ClearList(g_hHook())
EndSelect
EndSelect
Until Quit
EndIf
If *g_MapView
UnmapViewOfFile_(*g_MapView)
EndIf
If g_hFileMap
CloseHandle_(g_hFileMap)
EndIf
Re: Dead keys with keyboard hook - how?
Posted: Fri Dec 26, 2025 1:41 am
by BarryG
Thanks for trying, but this line doesn't output anything on Win 11:
Code: Select all
Debug "Target hWnd: " + wParam + " " + g_Text$()
Don't know if it's an easy fix? I don't know what to fix anyway.

Re: Dead keys with keyboard hook - how?
Posted: Fri Dec 26, 2025 3:52 am
by breeze4me
The issue seems to be that the hook is only installed on a single thread associated with a specific window. The code below installs a global hook without specifying a thread, so it should work.
Hook.dll code:
Code: Select all
EnableExplicit
#APP_HookInput = #WM_APP + 246
#GUID_FileMap$ = "MyAppFM_bd9b51f3-9905-4b7a-a63b-7ea7353e2c42"
Global g_hMod, g_hFileMap, *g_MapView
ProcedureDLL.l myGetMsgProc(nCode, wParam, *p.MSG)
Protected hWnd
If nCode = #HC_ACTION
If *p\message = #WM_CHAR
If *g_MapView
hWnd = PeekI(*g_MapView)
If hWnd
SendMessage_(hWnd, #APP_HookInput, *p\hwnd, *p\wParam)
EndIf
EndIf
EndIf
EndIf
ProcedureReturn CallNextHookEx_(0, nCode, wParam, *p)
EndProcedure
ProcedureDLL SetHook()
ProcedureReturn SetWindowsHookEx_(#WH_GETMESSAGE, @myGetMsgProc(), g_hMod, 0)
EndProcedure
ProcedureDLL UnHook(hHook)
ProcedureReturn UnhookWindowsHookEx_(hHook)
EndProcedure
ProcedureDLL AttachProcess(Instance)
g_hMod = Instance
g_hFileMap = OpenFileMapping_(#FILE_MAP_READ, 0, #GUID_FileMap$)
If g_hFileMap
*g_MapView = MapViewOfFile_(g_hFileMap, #FILE_MAP_READ, 0, 0, 64)
EndIf
EndProcedure
ProcedureDLL DetachProcess(Instance)
If *g_MapView
UnmapViewOfFile_(*g_MapView)
EndIf
If g_hFileMap
CloseHandle_(g_hFileMap)
EndIf
EndProcedure
Main code:
Code: Select all
#APP_HookInput = #WM_APP + 246
#GUID_FileMap$ = "MyAppFM_bd9b51f3-9905-4b7a-a63b-7ea7353e2c42"
Prototype ptSetHook()
Prototype ptUnHook(hHook)
Global SetHook.ptSetHook
Global UnHook.ptUnHook
Global NewMap g_Text$()
Global g_hFileMap, *g_MapView
If OpenLibrary(0, "Hook.dll")
SetHook = GetFunction(0, "SetHook")
UnHook = GetFunction(0, "UnHook")
EndIf
If SetHook And UnHook
Debug "Open."
Else
Debug "Cannot open the hook Dll."
End
EndIf
Procedure WndProc_Main(hWnd, uMsg, wParam, lParam)
If uMsg = #APP_HookInput
g_Text$(Str(wParam)) + Chr(lParam)
Debug "Target hWnd: " + wParam + " " + g_Text$()
ProcedureReturn 1
EndIf
ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure
RunProgram("notepad.exe")
RunProgram("notepad.exe")
Sleep_(1500)
If OpenWindow(0, 0, 0, 270, 80, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ButtonGadget(1, 10, 20, 120, 30, "Set Hook")
ButtonGadget(2, 140, 20, 120, 30, "UnHook")
SetWindowCallback(@WndProc_Main(), 0)
; After creating shared memory, store the window handle value that will receive messages.
g_hFileMap = CreateFileMapping_(#INVALID_HANDLE_VALUE, 0, #PAGE_READWRITE | #SEC_COMMIT, 0, 64, #GUID_FileMap$)
If g_hFileMap
*g_MapView = MapViewOfFile_(g_hFileMap, #FILE_MAP_ALL_ACCESS, 0, 0, 64)
If *g_MapView
PokeI(*g_MapView, WindowID(0))
EndIf
EndIf
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
Quit = 1
Case #PB_Event_Gadget
Select EventGadget()
Case 1
If hHook = 0
hHook = SetHook()
If hHook
Debug "hHook : " + hHook
EndIf
EndIf
Case 2
If hHook
;Result = UnhookWindowsHookEx_(hHook)
Result = UnHook(hHook)
Debug "UnHook: " + hHook + " " + Result
hHook = 0
EndIf
EndSelect
EndSelect
Until Quit
EndIf
If *g_MapView
UnmapViewOfFile_(*g_MapView)
EndIf
If g_hFileMap
CloseHandle_(g_hFileMap)
EndIf
Re: Dead keys with keyboard hook - how?
Posted: Fri Dec 26, 2025 8:15 am
by BarryG
Almost there... keys like Esc and Enter aren't detected on some apps that don't recognise them. Is there a way to make them always get detected, no matter which window or app has the focus? BTW, I appreciate your help.
Re: Dead keys with keyboard hook - how?
Posted: Fri Dec 26, 2025 10:05 am
by breeze4me
If necessary, use it with a keyboard hook.
As mentioned in the first answer, you can use a keyboard(low level) hook together.
Where to ignore and where to allow specific key inputs depends on the situation, so decide based on your needs.
Hook.dll code:
Code: Select all
EnableExplicit
#APP_HookInput = #WM_APP + 246
#GUID_FileMap$ = "MyAppFM_bd9b51f3-9905-4b7a-a63b-7ea7353e2c42"
Global g_hMod, g_hFileMap, *g_MapView
ProcedureDLL.l myGetMsgProc(nCode, wParam, *p.MSG)
Protected hWnd
If nCode = #HC_ACTION
; Whether to ignore the press of specific keys here is up to you.
;If *p\message = #WM_CHAR
If *p\message = #WM_CHAR And (*p\wParam <> #VK_ESCAPE And *p\wParam <> #VK_RETURN)
If *g_MapView
hWnd = PeekI(*g_MapView)
If hWnd
SendMessage_(hWnd, #APP_HookInput, *p\hwnd, *p\wParam)
EndIf
EndIf
EndIf
EndIf
ProcedureReturn CallNextHookEx_(0, nCode, wParam, *p)
EndProcedure
ProcedureDLL SetHook()
ProcedureReturn SetWindowsHookEx_(#WH_GETMESSAGE, @myGetMsgProc(), g_hMod, 0)
EndProcedure
ProcedureDLL UnHook(hHook)
ProcedureReturn UnhookWindowsHookEx_(hHook)
EndProcedure
ProcedureDLL AttachProcess(Instance)
g_hMod = Instance
g_hFileMap = OpenFileMapping_(#FILE_MAP_READ, 0, #GUID_FileMap$)
If g_hFileMap
*g_MapView = MapViewOfFile_(g_hFileMap, #FILE_MAP_READ, 0, 0, 64)
EndIf
EndProcedure
ProcedureDLL DetachProcess(Instance)
If *g_MapView
UnmapViewOfFile_(*g_MapView)
EndIf
If g_hFileMap
CloseHandle_(g_hFileMap)
EndIf
EndProcedure
Main code:
Code: Select all
#APP_HookInput = #WM_APP + 246
#APP_HookKBD = #WM_APP + 247
#GUID_FileMap$ = "MyAppFM_bd9b51f3-9905-4b7a-a63b-7ea7353e2c42"
Prototype ptSetHook()
Prototype ptUnHook(hHook)
Global SetHook.ptSetHook
Global UnHook.ptUnHook
Global NewMap g_Text$()
Global g_hFileMap, *g_MapView
Procedure.l myKeyboardHook(nCode, wParam, *p.KBDLLHOOKSTRUCT)
If nCode = #HC_ACTION
If (wParam = #WM_KEYUP Or wParam = #WM_SYSKEYUP) And (*p\vkcode = #VK_ESCAPE Or *p\vkcode = #VK_RETURN)
SendMessage_(WindowID(0), #APP_HookKBD, 0, *p\vkcode)
; Or you can send it using the same event code. Choose as needed.
;SendMessage_(WindowID(0), #APP_HookInput, 0, *p\vkcode)
EndIf
EndIf
ProcedureReturn CallNextHookEx_(0, nCode, wParam, *p)
EndProcedure
If OpenLibrary(0, "Hook.dll")
SetHook = GetFunction(0, "SetHook")
UnHook = GetFunction(0, "UnHook")
EndIf
If SetHook And UnHook
Debug "Open."
Else
Debug "Cannot open the hook Dll."
End
EndIf
Procedure WndProc_Main(hWnd, uMsg, wParam, lParam)
If uMsg = #APP_HookKBD
Debug "Key pressed: " + lParam
EndIf
If uMsg = #APP_HookInput
g_Text$(Str(wParam)) + Chr(lParam)
Debug "Target hWnd: " + wParam + " " + g_Text$()
ProcedureReturn 1
EndIf
ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure
RunProgram("notepad.exe")
RunProgram("notepad.exe")
Sleep_(1500)
If OpenWindow(0, 0, 0, 270, 80, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ButtonGadget(1, 10, 20, 120, 30, "Set Hook")
ButtonGadget(2, 140, 20, 120, 30, "UnHook")
SetWindowCallback(@WndProc_Main(), 0)
; After creating shared memory, store the window handle value that will receive messages.
g_hFileMap = CreateFileMapping_(#INVALID_HANDLE_VALUE, 0, #PAGE_READWRITE | #SEC_COMMIT, 0, 64, #GUID_FileMap$)
If g_hFileMap
*g_MapView = MapViewOfFile_(g_hFileMap, #FILE_MAP_ALL_ACCESS, 0, 0, 64)
If *g_MapView
PokeI(*g_MapView, WindowID(0))
EndIf
EndIf
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
Quit = 1
Case #PB_Event_Gadget
Select EventGadget()
Case 1
If hHook = 0
hHook = SetHook()
If hHook
Debug "hHook : " + hHook
EndIf
EndIf
If hHookKBD = 0
hHookKBD = SetWindowsHookEx_(#WH_KEYBOARD_LL, @myKeyboardHook(), GetModuleHandle_(0), 0)
If hHookKBD
Debug "hHookKBD : " + hHookKBD
EndIf
EndIf
Case 2
If hHook
Result = UnHook(hHook)
;Result = UnhookWindowsHookEx_(hHook)
Debug "UnHook(hHook): " + hHook + " " + Result
hHook = 0
EndIf
If hHookKBD
Result = UnhookWindowsHookEx_(hHookKBD)
Debug "UnHook(hHookKBD): " + hHookKBD + " " + Result
hHookKBD = 0
EndIf
EndSelect
EndSelect
Until Quit
EndIf
If *g_MapView
UnmapViewOfFile_(*g_MapView)
EndIf
If g_hFileMap
CloseHandle_(g_hFileMap)
EndIf
Re: Dead keys with keyboard hook - how?
Posted: Fri Dec 26, 2025 10:20 am
by BarryG
Thank you, kind sir. It now fully works as I need.

Can you PM me, please?
BTW, this GUID can be anything, right? Just something unique to my app?
Code: Select all
#GUID_FileMap$ = "MyAppFM_bd9b51f3-9905-4b7a-a63b-7ea7353e2c42"
Re: Dead keys with keyboard hook - how?
Posted: Fri Dec 26, 2025 10:32 am
by breeze4me
BarryG wrote: Fri Dec 26, 2025 10:20 am
BTW, this GUID can be anything, right? Just something unique to my app?
Code: Select all
#GUID_FileMap$ = "MyAppFM_bd9b51f3-9905-4b7a-a63b-7ea7353e2c42"
Yes, if the string is unique worldwide.

Re: Dead keys with keyboard hook - how?
Posted: Sat Dec 27, 2025 9:41 am
by Rinzwind
FYI: won't catch all keys... ie anywhere in MS word or in start menu. The application itself prevents passing messages on?
Re: Dead keys with keyboard hook - how?
Posted: Sat Dec 27, 2025 11:06 am
by breeze4me
Rinzwind wrote: Sat Dec 27, 2025 9:41 am
FYI: won't catch all keys... ie anywhere in MS word or in start menu. The application itself prevents passing messages on?
For applications that are not traditional Win32 applications(such as some modern apps like Universal Windows Platform (UWP) apps), the WM_CHAR message may not be hooked.
Depending on the frameworks used by apps, even low level keyboard hooks may or may not be possible.
Even so, for the Windows Start menu (at least in Windows 10), key input can be monitored using the WH_KEYBOARD_LL hook.
The AI's explanation is more detailed, so take a look.
Why standard WH_KEYBOARD_LL hooks are problematic for UWP
App Container Sandbox: UWP apps run within a secure, isolated app container that limits their access to system-wide functions, including cross-process hooking mechanisms.
DLL Injection Restrictions: Global hooks typically require a DLL to be injected into the address space of other processes. For UWP and Windows Store app processes, hook DLLs are not loaded in-process unless installed by UIAccess processes (like accessibility tools).
API Limitations: Access to certain traditional Win32 APIs may be restricted or behave differently within the UWP environment.