Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Dynamic Libraries (Windows)
#45
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:

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

#endif
hotkey.bas:

Code: (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


Reply


Messages In This Thread
Dynamic Libraries (Windows) - by Petr - 12-08-2025, 06:48 PM
RE: Dynamic Libraries - by Petr - 12-08-2025, 07:12 PM
RE: Dynamic Libraries - by Petr - 12-08-2025, 07:52 PM
RE: Dynamic Libraries - by Jack - 12-08-2025, 08:41 PM
RE: Dynamic Libraries - by Petr - 12-09-2025, 07:21 PM
RE: Dynamic Libraries - by Petr - 12-11-2025, 04:19 PM
RE: Dynamic Libraries - by Petr - 12-12-2025, 10:00 PM
RE: Dynamic Libraries - by Petr - 12-13-2025, 05:56 PM
RE: Dynamic Libraries - by 2112 - 12-13-2025, 07:06 PM
RE: Dynamic Libraries - by ahenry3068 - 12-13-2025, 07:47 PM
RE: Dynamic Libraries - by Petr - 12-13-2025, 07:41 PM
RE: Dynamic Libraries - by 2112 - 12-13-2025, 08:23 PM
RE: Dynamic Libraries - by Petr - 12-13-2025, 08:36 PM
RE: Dynamic Libraries - by Petr - 12-13-2025, 09:03 PM
RE: Dynamic Libraries - by ahenry3068 - 12-13-2025, 11:16 PM
RE: Dynamic Libraries - by Petr - 12-14-2025, 12:15 AM
RE: Dynamic Libraries - by Petr - 12-15-2025, 08:22 AM
RE: Dynamic Libraries (Windows) - by Petr - 12-15-2025, 06:30 PM
RE: Dynamic Libraries (Windows) - by Mad Axeman - 12-19-2025, 02:37 PM
RE: Dynamic Libraries (Windows) - by Pete - 12-18-2025, 07:45 PM
RE: Dynamic Libraries (Windows) - by Steffan-68 - 12-18-2025, 08:31 PM
RE: Dynamic Libraries (Windows) - by Pete - 12-18-2025, 08:55 PM
RE: Dynamic Libraries (Windows) - by Steffan-68 - 12-18-2025, 09:48 PM
RE: Dynamic Libraries (Windows) - by Petr - 12-18-2025, 09:32 PM
RE: Dynamic Libraries (Windows) - by Pete - 12-18-2025, 11:53 PM
RE: Dynamic Libraries (Windows) - by Pete - 12-19-2025, 06:40 AM
RE: Dynamic Libraries (Windows) - by Petr - 12-19-2025, 03:08 PM
RE: Dynamic Libraries (Windows) - by Petr - 12-19-2025, 10:35 PM
RE: Dynamic Libraries (Windows) - by Petr - 12-19-2025, 10:54 PM
RE: Dynamic Libraries (Windows) - by Petr - 12-19-2025, 11:20 PM
RE: Dynamic Libraries (Windows) - by Pete - 12-19-2025, 11:37 PM
RE: Dynamic Libraries (Windows) - by Petr - 12-20-2025, 10:22 PM
RE: Dynamic Libraries (Windows) - by SMcNeill - 12-20-2025, 11:03 PM
RE: Dynamic Libraries (Windows) - by Petr - 12-21-2025, 05:40 PM
RE: Dynamic Libraries (Windows) - by Petr - 12-22-2025, 08:23 PM
RE: Dynamic Libraries (Windows) - by Petr - 12-23-2025, 09:06 AM
RE: Dynamic Libraries (Windows) - by Petr - 12-24-2025, 09:54 PM
RE: Dynamic Libraries (Windows) - by Petr - 12-25-2025, 10:02 AM
RE: Dynamic Libraries (Windows) - by Petr - 12-26-2025, 11:14 PM
RE: Dynamic Libraries (Windows) - by Petr - 12-27-2025, 01:35 PM
RE: Dynamic Libraries (Windows) - by MasterGy - 12-27-2025, 07:23 PM
RE: Dynamic Libraries (Windows) - by Petr - 01-07-2026, 06:31 PM
RE: Dynamic Libraries (Windows) - by Petr - 01-07-2026, 09:13 PM
RE: Dynamic Libraries (Windows) - by Petr - 02-24-2026, 06:38 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  Dynamic Libraries (Linux) Petr 19 1,220 12-29-2025, 09:52 PM
Last Post: Petr

Forum Jump:


Users browsing this thread: 1 Guest(s)