12-25-2025, 10:02 AM
System Global Hotkeys Utility
Let me introduce another utility. This one focuses on system-wide keyboard shortcuts (Global Hotkeys) that the program registers upon startup and - crucially - unregisters when the program is closed properly.
Why is this useful?
Imagine a program running in the background, minimized (for example, to the system tray). In this state, it cannot respond to standard keyboard input because it lacks window focus. A perfect example is a Screen Recorder: you need to control it while it's minimized because you don't want the recorder's own interface to be visible in the recording, obstructing the view.
How Windows Handles Hotkeys:
The Windows operating system does not allow programs to simply "take" any shortcut they want. Once a shortcut is registered by an application, no other program can use it. This is why it is extremely important to ensure the program exits gracefully. In QB64PE, this is handled by the _EXIT function. This ensures that the hotkey is unregistered from the system before the process terminates, making it available again for other applications.
The Blacklist
This program includes a "blacklist" for combinations such as Ctrl + Alt + F1 through Ctrl + Alt + F12 Why? Because many of these are already reserved. For instance,
Ctrl + Alt + F8 is frequently used by Intel graphics drivers, and other system utilities use similar combinations. Remember: a shortcut can only be unregistered by the same application that registered it.
How to test this program:
Startup: When you run the following code, it will automatically find and select the first three available shortcuts and display their combinations to you.
Background Test: Minimize the program to the taskbar, switch to a completely different application (e.g., a web browser or text editor), and press one of the shortcuts shown earlier. You should hear a beep (Sound).
Verification: Maximize the program window again. You will see that it successfully detected and logged the key presses even while it was in the background.
Multi-instance Test: * Run the program; it will list certain shortcuts.
- Leave it running and launch a second instance of the same program.
- The second instance will display completely different shortcuts.
- This happens because the shortcuts from the first instance are still occupied and will remain so until that first instance is closed correctly and unregisters them.
- This approach is the ideal solution for building system-level applications and background utilities.
Program need hotkey.h file for run!
hotkey.h:
hotkey.bas:
Let me introduce another utility. This one focuses on system-wide keyboard shortcuts (Global Hotkeys) that the program registers upon startup and - crucially - unregisters when the program is closed properly.
Why is this useful?
Imagine a program running in the background, minimized (for example, to the system tray). In this state, it cannot respond to standard keyboard input because it lacks window focus. A perfect example is a Screen Recorder: you need to control it while it's minimized because you don't want the recorder's own interface to be visible in the recording, obstructing the view.
How Windows Handles Hotkeys:
The Windows operating system does not allow programs to simply "take" any shortcut they want. Once a shortcut is registered by an application, no other program can use it. This is why it is extremely important to ensure the program exits gracefully. In QB64PE, this is handled by the _EXIT function. This ensures that the hotkey is unregistered from the system before the process terminates, making it available again for other applications.
The Blacklist
This program includes a "blacklist" for combinations such as Ctrl + Alt + F1 through Ctrl + Alt + F12 Why? Because many of these are already reserved. For instance,
Ctrl + Alt + F8 is frequently used by Intel graphics drivers, and other system utilities use similar combinations. Remember: a shortcut can only be unregistered by the same application that registered it.
How to test this program:
Startup: When you run the following code, it will automatically find and select the first three available shortcuts and display their combinations to you.
Background Test: Minimize the program to the taskbar, switch to a completely different application (e.g., a web browser or text editor), and press one of the shortcuts shown earlier. You should hear a beep (Sound).
Verification: Maximize the program window again. You will see that it successfully detected and logged the key presses even while it was in the background.
Multi-instance Test: * Run the program; it will list certain shortcuts.
- Leave it running and launch a second instance of the same program.
- The second instance will display completely different shortcuts.
- This happens because the shortcuts from the first instance are still occupied and will remain so until that first instance is closed correctly and unregisters them.
- This approach is the ideal solution for building system-level applications and background utilities.
Program need hotkey.h file for run!
hotkey.h:
Code: (Select All)
#ifndef QB64_HOTKEY_H
#define QB64_HOTKEY_H
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifndef MOD_NOREPEAT
#define MOD_NOREPEAT 0x4000
#endif
static DWORD qb64_hk_last_error = 0;
static volatile LONG qb64_hk_q_head = 0;
static volatile LONG qb64_hk_q_tail = 0;
static int qb64_hk_queue[64];
typedef struct { int id; int used; } qb64_hk_reg_t;
static qb64_hk_reg_t qb64_hk_regs[64];
static void qb64_hk_push(int id) {
LONG head = qb64_hk_q_head;
LONG next = (head + 1) & 63;
if (next == qb64_hk_q_tail) return; /* full -> drop */
qb64_hk_queue[head] = id;
qb64_hk_q_head = next;
}
static int qb64_hk_add_reg(int id) {
for (int i = 0; i < 64; i++) {
if (!qb64_hk_regs[i].used) { qb64_hk_regs[i].used = 1; qb64_hk_regs[i].id = id; return 1; }
}
return 0;
}
static void qb64_hk_clear_regs(void) {
for (int i = 0; i < 64; i++) { qb64_hk_regs[i].used = 0; qb64_hk_regs[i].id = 0; }
}
#ifdef __cplusplus
extern "C" {
#endif
/* Zavolej 1x na startu – vytvoří message queue pro thread (PeekMessage to zařídí). */
int Hotkey_Enable(void) {
qb64_hk_last_error = 0;
qb64_hk_q_head = qb64_hk_q_tail = 0;
qb64_hk_clear_regs();
MSG msg;
PeekMessageA(&msg, NULL, 0, 0, PM_NOREMOVE); /* vytvoří queue */
return 1;
}
/* Globální hotkey pro tento thread: WM_HOTKEY bude v thread message queue. */
int Hotkey_Register(int id, unsigned int modifiers, unsigned int vk) {
qb64_hk_last_error = 0;
if (RegisterHotKey(NULL, id, modifiers, vk)) {
qb64_hk_add_reg(id);
return 1;
}
qb64_hk_last_error = GetLastError();
return 0;
}
void Hotkey_Unregister(int id) {
UnregisterHotKey(NULL, id);
}
void Hotkey_Disable(void) {
for (int i = 0; i < 64; i++) {
if (qb64_hk_regs[i].used) UnregisterHotKey(NULL, qb64_hk_regs[i].id);
}
qb64_hk_clear_regs();
qb64_hk_q_head = qb64_hk_q_tail = 0;
}
/* Poll: vysaje WM_HOTKEY z thread queue a vrátí další ID (0 = nic). */
int Hotkey_Pop(void) {
MSG msg;
while (PeekMessageA(&msg, NULL, WM_HOTKEY, WM_HOTKEY, PM_REMOVE)) {
qb64_hk_push((int)msg.wParam); /* wParam = hotkey ID */
}
LONG tail = qb64_hk_q_tail;
if (tail == qb64_hk_q_head) return 0;
int id = qb64_hk_queue[tail];
qb64_hk_q_tail = (tail + 1) & 63;
return id;
}
unsigned long Hotkey_GetLastError(void) {
return (unsigned long)qb64_hk_last_error;
}
#ifdef __cplusplus
}
#endif
#else
/* non-windows stubs */
#ifdef __cplusplus
extern "C" {
#endif
int Hotkey_Enable(void) { return 0; }
int Hotkey_Register(int, unsigned int, unsigned int) { return 0; }
void Hotkey_Unregister(int) {}
void Hotkey_Disable(void) {}
int Hotkey_Pop(void) { return 0; }
unsigned long Hotkey_GetLastError(void) { return 0; }
#ifdef __cplusplus
}
#endif
#endif
#endifCode: (Select All)
Option _Explicit
' ---- hotkey.h API ----
Declare Library "hotkey"
Function Hotkey_Enable% ()
Function Hotkey_Register% (ByVal id As Long, ByVal modifiers As Long, ByVal vk As Long)
Sub Hotkey_Unregister (ByVal id As Long)
Function Hotkey_Pop% ()
Function Hotkey_GetLastError~& ()
End Declare
' WinAPI modifikators
Const MOD_ALT = 1
Const MOD_CONTROL = 2
Const MOD_SHIFT = 4
Const MOD_WIN = 8
Const MOD_NOREPEAT = &H4000
' Vk codes (just what we use here)
Const VK_F1 = &H70
Const VK_F12 = &H7B
Const VK_NUMPAD0 = &H60
' for program output
Dim Shared GlobalMods(1 To 32) As Long
Dim Shared GlobalVK(1 To 32) As Long
Dim Shared GlobalKeyText$(1 To 32)
Screen _NewImage(900, 300, 32)
_Title "Auto hotkeys"
If Hotkey_Enable = 0 Then End
SetGlobalKeys 3
Print
Print "Press selected hotkeys. ESC end."
Do
Dim id As Long
id = Hotkey_Pop
If id Then
Select Case id
Case 1: Print "Function #1 (ID=1) run: "; GlobalKeyText$(1): Sound 150, .5
Case 2: Print "Function #2 (ID=2) run: "; GlobalKeyText$(2): Sound 300, .5
Case 3: Print "Function #3 (ID=3) run: "; GlobalKeyText$(3): Sound 450, .5
End Select
End If
_Limit 200
Loop Until InKey$ = Chr$(27) Or _Exit
'erase it and unregister it
Dim i As Long
For i = 1 To 3
Hotkey_Unregister i
Next i
System
Function ModName$ (m As Long)
Dim s As String
If (m And MOD_CONTROL) Then s = s + "Ctrl+"
If (m And MOD_ALT) Then s = s + "Alt+"
If (m And MOD_SHIFT) Then s = s + "Shift+"
If (m And MOD_WIN) Then s = s + "Win+"
If Len(s) Then s = Left$(s, Len(s) - 1)
ModName$ = s
End Function
Function VkName$ (vk As Long)
' F1-F12
If vk >= VK_F1 And vk <= VK_F12 Then
VkName$ = "F" + LTrim$(Str$(vk - VK_F1 + 1))
Exit Function
End If
' Numpad 0-9
If vk >= VK_NUMPAD0 And vk <= (VK_NUMPAD0 + 9) Then
VkName$ = "Num" + LTrim$(Str$(vk - VK_NUMPAD0))
Exit Function
End If
' A-Z
If vk >= &H41 And vk <= &H5A Then VkName$ = Chr$(vk): Exit Function
' 0-9
If vk >= &H30 And vk <= &H39 Then VkName$ = Chr$(vk): Exit Function
VkName$ = "VK_" + Hex$(vk)
End Function
Function KeyText$ (mods As Long, vk As Long)
KeyText$ = ModName$(mods) + "+" + VkName$(vk)
End Function
' return -1 if ok, 0 if fail
Function TryRegisterAndStore% (id As Long, mods As Long, vk As Long)
' To be on the safe side, if it is called multiple times: unregister the old ID
Hotkey_Unregister id
If Hotkey_Register(id, mods, vk) Then
GlobalMods(id) = mods
GlobalVK(id) = vk
GlobalKeyText$(id) = KeyText$(mods, vk)
TryRegisterAndStore% = -1
Else
TryRegisterAndStore% = 0
End If
End Function
' Main: finds the first n free ones from a predefined order and registers them under ID 1..n
Sub SetGlobalKeys (n As Long)
Dim modSet(1 To 6) As Long
' Order = preference (cleanest/least collision first)
modSet(1) = MOD_CONTROL Or MOD_ALT Or MOD_NOREPEAT
modSet(2) = MOD_CONTROL Or MOD_ALT Or MOD_SHIFT Or MOD_NOREPEAT
modSet(3) = MOD_CONTROL Or MOD_ALT Or MOD_WIN Or MOD_NOREPEAT
modSet(4) = MOD_CONTROL Or MOD_ALT Or MOD_SHIFT Or MOD_WIN Or MOD_NOREPEAT
modSet(5) = MOD_CONTROL Or MOD_SHIFT Or MOD_NOREPEAT ' a× pozdýji (bý×nýjÜÝ)
modSet(6) = MOD_ALT Or MOD_SHIFT Or MOD_NOREPEAT ' a× pozdýji
' Candidate keys (no F13+; no "killers" like Ctrl+C etc.)
' 1) F-keys (often best for recorders)
' 2) Numpad (if user has one)
' 3) A few letters that usually have no global meaning (R,P,M,K,J,...)
Dim vkList(1 To 12 + 10 + 8) As Long
Dim idx As Long: idx = 0
Dim vk As Long
For vk = VK_F12 To VK_F1 Step -1 ' start from F12 down so you can see what is occupied right away
idx = idx + 1: vkList(idx) = vk
Next vk
For vk = VK_NUMPAD0 To (VK_NUMPAD0 + 9)
idx = idx + 1: vkList(idx) = vk
Next vk
' "safer" letters (not C/V/X/A/S/Z etc.)
idx = idx + 1: vkList(idx) = Asc("R")
idx = idx + 1: vkList(idx) = Asc("P")
idx = idx + 1: vkList(idx) = Asc("M")
idx = idx + 1: vkList(idx) = Asc("K")
idx = idx + 1: vkList(idx) = Asc("J")
idx = idx + 1: vkList(idx) = Asc("U")
idx = idx + 1: vkList(idx) = Asc("I")
idx = idx + 1: vkList(idx) = Asc("O")
Dim need As Long: need = n
Dim id As Long, mi As Long, vi As Long
Dim found As Long
For id = 1 To need
found = 0
For mi = 1 To 6
For vi = 1 To idx
If IsBlacklisted%(modSet(mi), vkList(vi)) Then
' p°eskoŔ
Else
If TryRegisterAndStore%(id, modSet(mi), vkList(vi)) Then
found = -1
Exit For
End If
End If
Next vi
If found Then Exit For
Next mi
If Not found Then
GlobalKeyText$(id) = "NOT FOUND (all occupied)"
End If
Next id
' program list to program window
Cls
For id = 1 To need
Print "GlobalKey("; id; ") = "; GlobalKeyText$(id)
Next id
End Sub
Function IsBlacklisted% (mods As Long, vk As Long) 'disable combinations as CTRL + ALT + Fx (often used with graphic drivers and graphic system calls)
If (mods And (MOD_CONTROL Or MOD_ALT)) = (MOD_CONTROL Or MOD_ALT) Then
If vk >= VK_F1 And vk <= VK_F12 Then
IsBlacklisted% = -1
Exit Function
End If
End If
IsBlacklisted% = 0
End Function

