Register global hotkey with WIN+... fails

Windows specific forum
highend
Enthusiast
Enthusiast
Posts: 123
Joined: Tue Jun 17, 2014 4:49 pm

Register global hotkey with WIN+... fails

Post by highend »

Hi,

Somewhere from this forum:

Code: Select all

Procedure SetHotKey(hWnd, key, modifiers)
  Static id

  id + 1
  If RegisterHotKey_(hWnd, id, modifiers, key) = 0
    MessageRequester("Test", "FAILED: " + key)
    ProcedureReturn -1
  EndIf

  ProcedureReturn id
EndProcedure

Procedure HotKeyMsg()
  Protected.MSG msg

  GetMessage_(@msg, 0, 0, 0)
  If msg\message = #WM_HOTKEY
    ProcedureReturn msg\wParam
  EndIf

EndProcedure

If OpenWindow(0, 0, 0, 0, 0, "", #PB_Window_Invisible)
  Define.i event

  hk_ctrl_a = SetHotKey(WindowID(0), #VK_A, #MOD_CONTROL)
  hk_f7     = SetHotKey(WindowID(0), #VK_F7, 0)
  hk_esc    = SetHotKey(WindowID(0), #VK_ESCAPE, 0)
  hk_win_f  = SetHotKey(WindowID(0), #VK_F, #MOD_WIN)

  Repeat
    event = HotKeyMsg()
    If event
      Select event
        Case hk_ctrl_a
          MessageRequester("Test", "CTRL+A")

        Case hk_f7
          MessageRequester("Test", "F7")

        Case hk_esc
          MessageRequester("Test", "ESC")
          Break

        Case hk_win_f
          MessageRequester("Test", "WIN+F")
      EndSelect
    EndIf
    Delay(20)
  ForEver
EndIf
I can't register e.g. WIN+F (the other three work fine)...

I do know that the OS has registered it but in AutoHotkey I can re-register that hotkey just fine:

Code: Select all

#NoEnv
;#NoTrayIcon
#Persistent
#SingleInstance Force

#F::Msgbox, % "WIN+F"
Last edited by highend on Sun Dec 05, 2021 11:01 pm, edited 1 time in total.
Axolotl
Enthusiast
Enthusiast
Posts: 432
Joined: Wed Dec 31, 2008 3:36 pm

Re: Register global hotkey with WIN+... fails

Post by Axolotl »

As far as I know you need something like GlobalAddAtom() instead of local id's for System-wide hotkeys. Sorry cannot show an example, codes are on the other computer.
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
highend
Enthusiast
Enthusiast
Posts: 123
Joined: Tue Jun 17, 2014 4:49 pm

Re: Register global hotkey with WIN+... fails

Post by highend »

@Axolotl

I would be grateful if you can post an example if you have access again

@AZJIO

That looks very hacky :oops:
BarryG
Addict
Addict
Posts: 3268
Joined: Thu Apr 18, 2019 8:17 am

Re: Register global hotkey with WIN+... fails

Post by BarryG »

Axolotl wrote: Sun Dec 05, 2021 4:58 pmAs far as I know you need something like GlobalAddAtom() instead of local id's for System-wide hotkeys
I tried that, and it still fails (see below). Win+F is reserved for Win 10's "Feedback" tool, so it can't be registered.

I think AutoHotkey must be setting a keyboard hook to overcome this, ie. it's pretending to register a hotkey but actually doing it with a hook. I haven't checked the source to know for sure, but it seems very likely. RegisterHotKey_() with Win+F definitely isn't permitted, as shown below.

Code: Select all

OpenWindow(0,100,100,200,100,"Win+F Hotkey",#PB_Window_SystemMenu)

id=GlobalAddAtom_("Win+F") ; Any text here.
hk=RegisterHotKey_(WindowID(0),id,#MOD_WIN,#VK_F)

If hk=0

  Debug "Hotkey failed to register"
  
Else
  
  Repeat
    
    event=WaitWindowEvent()
    
    If event=#WM_HOTKEY
      n+1
      Debug n
    EndIf
    
  Until event=#PB_Event_CloseWindow
  
EndIf
[Edit] I was correct about AutoHotkey. It uses a keyboard hook when RegisterHotkey_() fails. From its "Hotkey.cpp" source (line 353):

Code: Select all

// If the hotkey is normal, try to register it.  If the register fails, use the hook to try
// to override any other script or program that might have it registered (as documented):
		if (hot.mType == HK_NORMAL)
		{
			if (!hot.Register()) // Can't register it, usually due to some other application or the OS using it.
				if (!is_win9x)   // If the OS supports the hook, it can be used to override the other application.
					hot.mType = HK_KEYBD_HOOK;
Axolotl
Enthusiast
Enthusiast
Posts: 432
Joined: Wed Dec 31, 2008 3:36 pm

Re: Register global hotkey with WIN+... fails

Post by Axolotl »

@highend
yepp. BarryG is right. I have implemented it exactly like this. I read some recommendations about low level keyboard hook stuff.
That is a different story I have no experiences with, but I hacked something together. :oops:
I hope this gives you a good starting point.

@BarryG
thank you, otherwise I would have send my 'not working code' and embarrassed myself.

Here comes the 'LowLevel' code...
Any kind of Feedback would be very nice

Code: Select all

; ---------------------------------------------------------------------------------------------------------------------
;:: FILE : LowLevelKeyboardHook.pb 
;::
;:: Target OS   : Windows
;:: License     : Free, unrestricted, no warranty whatsoever
;::               Use at your own risk
;::
;:: Copyright (c) 2021 by AHa 
; ---------------------------------------------------------------------------------------------------------------------

#WINDOW_Main = 1 

#GADGET_btnHook = 1 
#GADGET_edtOutput = 2 


Structure KEYBOARDHOOKSTRUCTURE
  vkCode.l
  scanCode.l
  flags.l
  time.l
  dwExtraInfo.l
EndStructure

#WM_KEY               = #WM_USER + 123 

Global Hotkey_hWnd 
Global Hotkey_hKeyboardHook 

Procedure Hotkey_KeyboardProc(nCode, wParam, *kb.KEYBOARDHOOKSTRUCTURE) 
  Static fMod = 0, vkCode = 0  

  If nCode = #HC_ACTION 
    If wParam = #WM_KEYDOWN 
      If *kb\vkCode = #VK_LWIN Or *kb\vkCode = #VK_RWIN 
        fMod = #MOD_WIN 
      ElseIf *kb\vkCode = #VK_F  
        vkCode = *kb\vkCode 
      EndIf 

    ElseIf wParam = #WM_KEYUP 
      If *kb\vkCode = #VK_LWIN Or *kb\vkCode = #VK_RWIN 
        fMod = 0 
      ElseIf *kb\vkCode = #VK_F  
        vkCode = 0 
      EndIf 
    EndIf 

    If fMod And vkCode 
      PostMessage_(Hotkey_hWnd, #WM_KEY, vkCode, fMod) 
      ProcedureReturn #True  ;:: do not start Feedback Hub 
    EndIf  

  EndIf 
  ProcedureReturn CallNextHookEx_(0, nCode, wParam, lParam) ;:: hhk is ignored 
EndProcedure ;() 


Procedure SetKeyboardHook(State)  ;:: 
  Static hKeyboardHook = 0 

  If State = #False  ;:: unhook 
    If hKeyboardHook 
      UnhookWindowsHookEx_(hKeyboardHook)  ;:: NONZERO 
      hKeyboardHook = 0 
    EndIf 
  ElseIf State = #True ;:: hook 
    If hKeyboardHook 
      UnhookWindowsHookEx_(hKeyboardHook) 
    EndIf 
    hKeyboardHook = SetWindowsHookEx_(#WH_KEYBOARD_LL, @Hotkey_KeyboardProc(), GetModuleHandle_(0), 0) 
  EndIf
EndProcedure ;()


Procedure Main() 
  Protected fHook 

  If OpenWindow(#WINDOW_Main, 0, 0, 600, 300, "Low Level Hook", #PB_Window_SystemMenu|#PB_Window_ScreenCentered) 
    Hotkey_hWnd = WindowID(#WINDOW_Main) 

    ButtonGadget(#GADGET_btnHook, 8, 4, 96, 24, "Hook ON") 
    EditorGadget(#GADGET_edtOutput, 8, 32, 584, 260) 
    AddGadgetItem(#GADGET_edtOutput, -1, "Press WIN+F with Hook On and Off should show different results. :) ") 

    Repeat 
      Select WaitWindowEvent() 
        Case #WM_KEY 
          AddGadgetItem(#GADGET_edtOutput, -1, "Hook Info: You pressed WIN+F :) ") 
         ;MessageRequester("Hook Info ", "You pressed WIN+F") 

        Case #PB_Event_Gadget  
          Select EventGadget() 
            Case #GADGET_btnHook 
              If fHook ; hook is active 
                fHook = #False 
                SetKeyboardHook(#False) 
                SetGadgetText(#GADGET_btnHook, "Hook ON") 

              Else ; hook not active 
                fHook = #True 
                SetKeyboardHook(#True) 
                SetGadgetText(#GADGET_btnHook, "Hook OFF") 
              EndIf 
          EndSelect 

        Case #PB_Event_CloseWindow : Break 
      EndSelect 
    ForEver 

    SetKeyboardHook(#False)  ;:: do not forget to unhook :)  
  EndIf 
  ProcedureReturn 0 
EndProcedure 
; 
End Main() 
;// BoF 
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
User avatar
ChrisR
Addict
Addict
Posts: 1124
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: Register global hotkey with WIN+... fails

Post by ChrisR »

If it can be of any help, I have this cpp program that works. It uses the WinIO library.:
The WinIo library was developed to allow 32-bit Windows applications to directly access I/O ports and physical memory. It bypasses Windows protection mechanisms by using a combination of a kernel-mode device driver and several low-level programming techniques.

Code: Select all

// WinF.cpp : Defines the entry point for the win32 application.
// Build here in Ms Visual Studio 2008 SP1: File > New > Project > Win32 project.  
// 
// The program uses WinIo library http://www.internals.com/ 
// WinIo - Version 3.0 supports 32 or 64 bit platforms and concurrent use from multiple applications. 
// The WinIo library allows 32 or 64 bit Windows applications to directly access I/O ports and physical memory access under Windows NT/2000/XP/2003/Vista/7/2008/8/2012/10.
// It bypasses Windows protection mechanisms by using a combination of a kernel-mode device driver and several low-level programming techniques.


#include "stdafx.h"
#include <windows.h>

typedef BOOL(WINAPI *INITIALIZEWINIO)(void);
typedef BOOL(WINAPI *SHUTDOWNWINIO)(void);
typedef BOOL(WINAPI *GETPORTVAL)(WORD wPortAddr, PDWORD pdwPortVal, BYTE bSize);
typedef BOOL(WINAPI *SETPORTVAL)(WORD wPortAddr, DWORD dwPortVal, BYTE bSize);

INITIALIZEWINIO   InitializeWinIo = NULL;
SHUTDOWNWINIO	ShutdownWinIo = NULL;
GETPORTVAL  GetPortVal = NULL;
SETPORTVAL  SetPortVal = NULL;

#define key_cmd 0x64 // Keyboard command port
#define key_dat 0x60 // Keyboard data port

BOOL InitFuncs(void)
{
	// Directives, controls compilation
	#if defined( _WIN64 )
		HMODULE hMod = LoadLibrary(L"WinIo64.dll");
	#else
		HMODULE hMod = LoadLibrary(L"WinIo32.dll");
	#endif

  InitializeWinIo= (INITIALIZEWINIO)GetProcAddress(hMod,"InitializeWinIo");
	ShutdownWinIo= (SHUTDOWNWINIO)GetProcAddress(hMod,"ShutdownWinIo");
  GetPortVal= (GETPORTVAL)GetProcAddress(hMod,"GetPortVal");
	SetPortVal= (SETPORTVAL)GetProcAddress(hMod,"SetPortVal");

	if(InitializeWinIo==NULL) return FALSE;
	if(ShutdownWinIo==NULL) return FALSE;
	if(GetPortVal==NULL) return FALSE;
	if(SetPortVal==NULL) return FALSE;
	
	return TRUE;
}

// Wait for the buffer is empty
void KBCwait4IBE() {
    DWORD ch=0; 
    do { GetPortVal(key_cmd,&ch,1); // Read keyboard command port, come ch
    	Sleep (4);
    } while(ch & 0x2); // Bit1 is 1, then the input buffer is full explanation, repeated testing! Until empty.
}

// Key is pressed
void KeyDown(DWORD VirtualKey) {
    DWORD K_Make_Code=MapVirtualKey(VirtualKey,0); // winuser.h defined function
    KBCwait4IBE();
    SetPortVal(key_cmd,0xd2,1); // Send keyboard write command 
	KBCwait4IBE();
	SetPortVal(key_dat,K_Make_Code,1); // Write key scan code	
} 

// Key release 
void KeyUp(DWORD VirtualKey) { 
	DWORD K_Make_Code=MapVirtualKey(VirtualKey,0); // Key scan code 
	DWORD K_Break_Code=K_Make_Code+0x80; // Key break code 
	KBCwait4IBE(); 
	SetPortVal(key_cmd,0xd2,1); // Send keyboard write command 
	KBCwait4IBE();
	SetPortVal(key_dat,K_Break_Code,1); // Write key break code 
} 

// Key is pressed. Extended
void KeyDownEx(DWORD VirtualKey) {
    DWORD K_Make_Code=MapVirtualKey(VirtualKey,0); // winuser.h defined function
	
    KBCwait4IBE();
    SetPortVal(key_cmd,0xd2,1); // Send keyboard write command 
	KBCwait4IBE();
	SetPortVal(key_dat,0xe0,1); // Write extended key flag 

    KBCwait4IBE();
    SetPortVal(key_cmd,0xd2,1); // Send keyboard write command 
	KBCwait4IBE();
	SetPortVal(key_dat,K_Make_Code,1); // Write key scan code
} 

// Key release. Extended
void KeyUpEx(DWORD VirtualKey) { 
	DWORD K_Make_Code=MapVirtualKey(VirtualKey,0); // Key scan code 
	DWORD K_Break_Code=K_Make_Code+0x80; // Key break code 

    KBCwait4IBE();
    SetPortVal(key_cmd,0xd2,1); // Send keyboard write command 
	KBCwait4IBE();
	SetPortVal(key_dat,0xe0,1); // Write extended key flag	
	
    KBCwait4IBE();
    SetPortVal(key_cmd,0xd2,1); // Send keyboard write command 
	KBCwait4IBE();
	SetPortVal(key_dat,K_Break_Code,1); // Write key break code 		
} 

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    BOOL init;
	
	if(InitFuncs()!=TRUE)
	{
		MessageBox(0, L"Load WinIo.dll failed!", L"WinF Error", MB_ICONEXCLAMATION | MB_OK);
		//printf("Load WinIo.dll failed!\n");
		return 1;
	}

    init = InitializeWinIo();
	if(init!=TRUE)
	{
		MessageBox(0, L"InitializeWinIo failed!", L"WinF Error", MB_ICONEXCLAMATION | MB_OK);
		//printf("InitializeWinIo failed!\n");
		return 2;
	}

	KeyDownEx(VK_LWIN);   // Left Win is pressed (extended key)
	KeyDown('F');         // F is pressed
	Sleep(100);           // Wait for fast CPU
	KeyUp('F');           // F is release
 	KeyUpEx(VK_LWIN);     // Left Win is release (extended key)

	ShutdownWinIo();

	return 0;
}
highend
Enthusiast
Enthusiast
Posts: 123
Joined: Tue Jun 17, 2014 4:49 pm

Re: Register global hotkey with WIN+... fails

Post by highend »

@BarryG
Thanks for the detective work! You're absolutely right, internally AHK uses a hook if it can't register a hotkey. Didn't know that...

@ChrisR
Thanks for the C++ code, I'll put it into my local repository for reference!

@Axolotl
Thanks! That's a very sophisticated way to show that / how it works (with a GUI) :D
Seems to work flawless from my tests!

I've trying to adapt it to my workflow and allow to register / unregister shortcut keys on the fly (which works so far) but to make it perfect it should be possible to call a procedure from the hook that is given via the structured map as well. Have to think a bit more if it can be done and how...

That's my current state:

Code: Select all

EnumerationBinary KeyModifierStates 1
  #KM_Shift                  ;  1
  #KM_Ctrl                   ;  2
  #KM_Alt                    ;  4
  #KM_Win                    ;  8
EndEnumeration

Structure KBD_STRUCT
  kbdState.i       ; The keyboard modifier state (from KeyModifierStates)
EndStructure

; Keyboard shortcut(s)
Structure SHORTCUT_STRUCT
  modifier.i       ; Modifier, #KM_Shift, #KM_Ctrl, #KM_Alt, #KM_Win
  functionID.i     ; The function ID to execute
EndStructure

; Fill this map first!
Global NewMap KeyboardShortcuts.SHORTCUT_STRUCT()

Global kbd.KBD_STRUCT
Global.i hKeyboardShortcutsHook


Procedure KeyboardShortcutsHook(nCode, wParam, *lParam.KBDLLHOOKSTRUCT)
  Static.i kbdState, shiftState, ctrlState, altState, winState

  If nCode < 0 Or nCode <> #HC_ACTION
    ProcedureReturn CallNextHookEx_(hKeyboardShortcutsHook, nCode, wParam, *lParam)
  EndIf

  Select wParam
    Case #WM_KEYDOWN, #WM_SYSKEYDOWN ; #WM_SYSKEYDOWN is a necessity for testing the ALT key!
      Select *lParam\vkCode
        Case #VK_LSHIFT,   #VK_RSHIFT   : shiftState = #KM_Shift
        Case #VK_LCONTROL, #VK_RCONTROL : ctrlState  = #KM_Ctrl
        Case #VK_LMENU,    #VK_RMENU    : altState   = #KM_Alt
        Case #VK_LWIN,     #VK_RWIN     : winState   = #KM_Win
      EndSelect

    Case #WM_KEYUP, #WM_SYSKEYUP
      Select *lParam\vkCode
        Case #VK_LSHIFT,   #VK_RSHIFT   : shiftState = #False
        Case #VK_LCONTROL, #VK_RCONTROL : ctrlState  = #False
        Case #VK_LMENU,    #VK_RMENU    : altState   = #False
        Case #VK_LWIN,     #VK_RWIN     : winState   = #False
      EndSelect

  EndSelect

  ; Put the state into the structure
  kbdState = shiftState + ctrlState + altState + winState
  If kbd\kbdState <> kbdState
    kbd\kbdState = kbdState
  EndIf

  If wParam = #WM_KEYDOWN Or wParam = #WM_SYSKEYDOWN
    If FindMapElement(KeyboardShortcuts(), Str(*lParam\vkCode))
      With KeyboardShortcuts()
        If kbdState = \modifier
          Debug "Registered shortcut pressed"

          ; Execute \functionID ...
        EndIf
      EndWith

      ; Swallow the modifier key
      ProcedureReturn #True
    EndIf
  EndIf

  ProcedureReturn CallNextHookEx_(hKeyboardShortcutsHook, nCode, wParam, *lParam)
EndProcedure


If OpenWindow(0, 0, 0, 300, 200, "Keyboard hook", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)

  ; Hooking
  hKeyboardShortcutsHook = SetWindowsHookEx_(#WH_KEYBOARD_LL, @KeyboardShortcutsHook(), #Null, 0)

  ClearMap(KeyboardShortcuts())
  ; Register "WIN+F"
  KeyboardShortcuts(Str(#VK_F))\modifier = #KM_Win

  ; Register "SHIFT+F10"
  KeyboardShortcuts(Str(#VK_F10))\modifier = #KM_Shift

  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow

  If hKeyboardShortcutsHook : UnhookWindowsHookEx_(hKeyboardShortcutsHook) : EndIf
EndIf
Axolotl
Enthusiast
Enthusiast
Posts: 432
Joined: Wed Dec 31, 2008 3:36 pm

Re: Register global hotkey with WIN+... fails

Post by Axolotl »

I am always happy to help.
This already looks quite useful. Please keep in mind that the low-level routine affects the execution of other applications. I think that the longer you stay in it, the slower the response of other applications will be.
That's why I had passed processing through PostMessage() and left the LL procedure asap.
BTW: the constants you are using were already defined in PureBasic:

Code: Select all

Debug #MOD_ALT            ;:: #MOD_ALT      = 0x01
Debug #MOD_CONTROL        ;:: #MOD_CONTROL  = 0x02 
Debug #MOD_SHIFT          ;:: #MOD_SHIFT    = 0x04
Debug #MOD_WIN            ;:: #MOD_WIN      = 0x08

Happy coding and stay healthy.
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
Post Reply