Posts: 1,154
Threads: 268
Joined: Jul 2022
I want to create global hotkeys for a tray application, for example, as shown in the code below.
When I press the hotkey F12, the subroutine sub.hk_F12 will be executed.
When I press the hotkey Ctrl+F12, the subroutine sub.hk_Ctrl_F12 will be executed.
I haven't found any useful information on the forums.
Thanks in advance for any suggestions and help.
Function
TcpServer2
out
if(getopt(nthreads)>1) ret ;;allow single instance
AddTrayIcon "cut.ico" "test_TcpSocket_server[]Ctrl+click to end."
#compile "__TcpSocket"
TcpSocket x.ServerStart(5032 &sub.OnClientConnected)
#sub OnClientConnected
function TcpSocket&client $clientIp param !*reserved
client.Receive(_s 1000)
out F"SERVER: client request: {_s}"
client.Send("response 1")
#sub hk_F12
mes "hk_F12"
#sub hk_Ctrl_F12
mes "hk_Ctrl_F12"
Posts: 2
Threads: 0
Joined: Jul 2025
/*******************************************************************************
*
* Global Hotkey TCP Server Application
*
* Description:
* This script demonstrates the correct method for creating a tray application
* with global hotkeys in Quick Macros. It combines a TCP server with a
* message-handling loop to process system-wide hotkeys (F12 and Ctrl+F12)
* and tray icon interactions.
*
* Core Concepts:
* 1. Hidden Window: A hidden window (dialog) is created to act as the
* message receiver for the application.
* 2. Windows API: Uses RegisterHotKey() to tell Windows to notify our
* application when the specific key combinations are pressed.
* 3. Dialog Procedure: A function (sub.DialogProc) is created to process
* messages sent to the hidden window, such as WM_HOTKEY and tray icon clicks.
* 4. Message Loop: The `wait` command keeps the script alive, listening for
* and dispatching messages to the Dialog Procedure.
* 5. Cleanup: UnregisterHotKey() is called when the application exits to
* release the hotkeys for other applications to use.
*
*******************************************************************************/
function TcpServerWithHotkeys
// Allow only a single instance of this application to run.
if(getopt(nthreads)>1) ret
// --- Setup for Hidden Window and Message Handling ---
// Define a unique class name for our application window.
str className="GlobalHotkeyServerApp"
// Specify the function that will handle messages for this window.
str dialogProc="sub.DialogProc"
// Register the window class with Windows if it hasn't been registered yet.
WNDCLASSW w
if(!GetClassLong(0 +&dialogProc &className))
w.cbClsExtra=4 // Reserve 4 bytes for a pointer.
w.lpfnWndProc=&dialogProc
w.lpszClassName=className
if(!RegisterClassW(&w)) ret ;; Exit if class registration fails.
// Create the hidden window. It won't be visible to the user.
int hdlg=CreateWindowEx(0 className "TrayAppHiddenWindow" 0 0 0 0 0 0 0 _hinst 0)
if(!hdlg) ret ;; Exit if window creation fails.
// --- Add Tray Icon ---
// The 4th parameter associates the tray icon with our hidden window (hdlg).
// The 5th parameter is the icon's ID.
AddTrayIcon "cut.ico" "TCP Server Running[]Ctrl+Left-click to end." "" hdlg 101
// --- Register Global Hotkeys ---
// We associate the hotkeys with our hidden window (hdlg).
// When a hotkey is pressed, Windows will send a WM_HOTKEY message to this window.
// The second parameter is a unique ID for each hotkey.
if(!RegisterHotKey(hdlg 1 0 VK_F12)) out "Error: Failed to register hotkey F12."
if(!RegisterHotKey(hdlg 2 MOD_CONTROL VK_F12)) out "Error: Failed to register hotkey Ctrl+F12."
// --- Start TCP Server ---
// The #compile directive should be at the top level of the function.
#compile "__TcpSocket"
TcpSocket x
x.ServerStart(5032 &sub.OnClientConnected)
// Store the TcpSocket object's address in the window's extra memory.
// This allows us to access it later from the dialog procedure.
SetWindowLong hdlg 0 x
// --- Main Message Loop ---
// The `wait` command pauses the script and processes the window message queue.
// The script will stay here, responsive to events, until the window is destroyed.
wait 0 H hdlg
// --- Cleanup on Exit ---
// This code runs after the message loop ends (i.e., when the window is destroyed).
out "Exiting application, cleaning up..."
UnregisterHotKey hdlg 1
UnregisterHotKey hdlg 2
// The tray icon is removed automatically when its parent window is destroyed.
ret
//================================================================================
// CALLBACK FUNCTIONS
//================================================================================
#sub DialogProc
function# hWnd message wParam lParam
// This function is the heart of the event-driven part of the app.
// It receives all messages for our hidden window.
sel message
case WM_DESTROY
// This message is sent when DestroyWindow(hWnd) is called.
// It's the standard way to gracefully end a message loop.
PostQuitMessage 0
ret
case WM_HOTKEY
// A registered hotkey was pressed!
// wParam contains the ID of the hotkey that was pressed.
sel wParam
case 1 ;; This is the ID for F12
sub.hk_F12
case 2 ;; This is the ID for Ctrl+F12
sub.hk_Ctrl_F12
ret
case WM_APP+1 // Tray icon message (AddTrayIcon uses WM_APP+1 by default)
// lParam contains the specific mouse event that occurred on the tray icon.
// Check if Ctrl key was down during a left-click to exit.
if(lParam=WM_LBUTTONDOWN and GetMod & 4) ;; 4 is the flag for the Ctrl key
out "Ctrl+Click detected on tray icon. Shutting down."
DestroyWindow hWnd // This will end the message loop and exit the app.
ret
// For all other messages, use the default window procedure.
ret DefWindowProc(hWnd message wParam lParam)
#sub OnClientConnected
function TcpSocket&client $clientIp param !*reserved
// This is your original TCP connection handler.
// It can be called at any time, independent of the UI messages.
client.Receive(_s 1000)
out F"SERVER: Client request: {_s}"
client.Send("response from server")
#sub hk_F12
// This subroutine is now correctly called by the WM_HOTKEY handler.
mes "F12 Hotkey Pressed!" "Global Hotkey"
#sub hk_Ctrl_F12
// This subroutine is now correctly called by the WM_HOTKEY handler.
mes "Ctrl+F12 Hotkey Pressed!" "Global Hotkey"
Posts: 1,154
Threads: 268
Joined: Jul 2022
Thank you for your help.
The code seems to be AI-generated and contains some errors, but I can still learn from some of its ideas. I still haven't managed to solve it.
Posts: 2
Threads: 0
Joined: Jul 2025
Yes I was using that to help solve it. I'll try a few other ways and I'll try to post again soon.