1212namespace HotkeyHandler {
1313
1414 static HWND hwndHotkey = nullptr ;
15- static const UINT HOTKEY_ID_UNLOCK_ALL = 1 ;
15+ static const UINT HOTKEY_ID_UNLOCK_ALL = 1 ;
1616 static const UINT HOTKEY_ID_UNLOCK_LIST = 2 ;
1717 static std::atomic<bool > keepRunning{false };
1818 static std::thread messageThread;
1919
20- // Helper: trim whitespace
2120 static std::string trim (const std::string& s) {
2221 size_t start = s.find_first_not_of (" \t\r\n " );
2322 if (start == std::string::npos) return " " ;
2423 size_t end = s.find_last_not_of (" \t\r\n " );
2524 return s.substr (start, end - start + 1 );
2625 }
2726
28- // Unlock all achievements
2927 static void UnlockAllAchievements () {
3028 if (Overlay::achievements) {
3129 int count = 0 ;
3230 for (auto & ach : *Overlay::achievements) {
3331 if (ach.UnlockState == UnlockState::Locked) {
3432 Overlay::unlockAchievement (&ach);
3533 count++;
36- Logger::info (" [HOTKEY] Unlocking all : %s" , ach.AchievementId );
34+ Logger::info (" [HOTKEY] Unlocking: %s" , ach.AchievementId );
3735 }
3836 }
3937 Logger::info (" [HOTKEY] Unlocked %d achievements." , count);
@@ -42,7 +40,6 @@ namespace HotkeyHandler {
4240 }
4341 }
4442
45- // Unlock from file (unlock_list.txt in game folder)
4643 static void UnlockFromFile () {
4744 char path[MAX_PATH];
4845 GetModuleFileNameA (NULL , path, MAX_PATH);
@@ -75,7 +72,6 @@ namespace HotkeyHandler {
7572 Logger::info (" [HOTKEY] Unlocked %d achievements from %s" , count, listFile.c_str ());
7673 }
7774
78- // Window procedure for the hidden message window
7975 LRESULT CALLBACK HotkeyWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
8076 if (msg == WM_HOTKEY) {
8177 if (wParam == HOTKEY_ID_UNLOCK_ALL) {
@@ -89,75 +85,77 @@ namespace HotkeyHandler {
8985 return DefWindowProc (hwnd, msg, wParam, lParam);
9086 }
9187
92- // Message loop – runs in its own thread
88+ // FIX (Bug 3): The window, RegisterHotKey, and GetMessage must all live on the
89+ // same thread. We do all three here inside the message loop thread itself.
9390 static void MessageLoop () {
94- MSG msg;
95- while (keepRunning) {
96- // Use GetMessage (blocks) instead of PeekMessage to reduce CPU usage
97- if (GetMessage (&msg, NULL , 0 , 0 )) {
98- TranslateMessage (&msg);
99- DispatchMessage (&msg);
100- }
101- }
102- Logger::info (" Hotkey message loop exiting." );
103- }
104-
105- void Start () {
106- if (keepRunning) {
107- Logger::info (" Hotkey handler already running." );
108- return ;
109- }
110-
11191 // Register window class
112- WNDCLASSEX wc = {0 };
113- wc.cbSize = sizeof (WNDCLASSEX);
114- wc.lpfnWndProc = HotkeyWndProc;
115- wc.hInstance = GetModuleHandle (NULL );
92+ WNDCLASSEX wc = {};
93+ wc.cbSize = sizeof (WNDCLASSEX);
94+ wc.lpfnWndProc = HotkeyWndProc;
95+ wc.hInstance = GetModuleHandle (NULL );
11696 wc.lpszClassName = L" ScreamAPI_HotkeyWindow" ;
11797 if (!RegisterClassEx (&wc)) {
118- Logger::error (" Failed to register hotkey window class (error %d)" , GetLastError ());
119- return ;
98+ DWORD err = GetLastError ();
99+ // ERROR_CLASS_ALREADY_EXISTS (1410) is fine if Start() was somehow called twice
100+ if (err != ERROR_CLASS_ALREADY_EXISTS) {
101+ Logger::error (" [HOTKEY] Failed to register window class (error %d)" , err);
102+ keepRunning = false ;
103+ return ;
104+ }
120105 }
121106
122- // Create hidden message-only window
123- hwndHotkey = CreateWindowEx (0 , wc.lpszClassName , L" HotkeyWindow" , 0 , 0 , 0 , 0 , 0 ,
124- HWND_MESSAGE, NULL , wc.hInstance , NULL );
107+ hwndHotkey = CreateWindowEx (0 , wc.lpszClassName , L" HotkeyWindow" , 0 ,
108+ 0 , 0 , 0 , 0 , HWND_MESSAGE, NULL , wc.hInstance , NULL );
125109 if (!hwndHotkey) {
126- Logger::error (" Failed to create hotkey window (error %d)" , GetLastError ());
110+ Logger::error (" [HOTKEY] Failed to create message window (error %d)" , GetLastError ());
111+ keepRunning = false ;
127112 return ;
128113 }
129114
130- // Register hotkeys
131- if (!RegisterHotKey (hwndHotkey, HOTKEY_ID_UNLOCK_ALL, MOD_CONTROL | MOD_SHIFT, ' U' )) {
132- Logger::error (" Failed to register hotkey Ctrl+Shift+U (error %d)" , GetLastError ());
133- } else {
134- Logger::info (" Global hotkey Ctrl+Shift+U registered successfully." );
135- }
115+ // RegisterHotKey on THIS thread — WM_HOTKEY will arrive in this thread's queue
116+ if (!RegisterHotKey (hwndHotkey, HOTKEY_ID_UNLOCK_ALL, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, ' U' ))
117+ Logger::error (" [HOTKEY] Failed to register Ctrl+Shift+U (error %d)" , GetLastError ());
118+ else
119+ Logger::info (" [HOTKEY] Ctrl+Shift+U registered." );
136120
137- if (!RegisterHotKey (hwndHotkey, HOTKEY_ID_UNLOCK_LIST, MOD_CONTROL | MOD_SHIFT, ' L' )) {
138- Logger::error (" Failed to register hotkey Ctrl+Shift+L (error %d)" , GetLastError ());
139- } else {
140- Logger::info (" Global hotkey Ctrl+Shift+L registered successfully." );
121+ if (!RegisterHotKey (hwndHotkey, HOTKEY_ID_UNLOCK_LIST, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, ' L' ))
122+ Logger::error (" [HOTKEY] Failed to register Ctrl+Shift+L (error %d)" , GetLastError ());
123+ else
124+ Logger::info (" [HOTKEY] Ctrl+Shift+L registered." );
125+
126+ MSG msg;
127+ while (keepRunning) {
128+ // GetMessage blocks until a message arrives for THIS thread
129+ BOOL ret = GetMessage (&msg, NULL , 0 , 0 );
130+ if (ret == 0 || ret == -1 ) break ; // WM_QUIT or error
131+ TranslateMessage (&msg);
132+ DispatchMessage (&msg);
141133 }
142134
143- // Start message loop thread
144- keepRunning = true ;
135+ UnregisterHotKey (hwndHotkey, HOTKEY_ID_UNLOCK_ALL);
136+ UnregisterHotKey (hwndHotkey, HOTKEY_ID_UNLOCK_LIST);
137+ DestroyWindow (hwndHotkey);
138+ hwndHotkey = nullptr ;
139+ Logger::info (" [HOTKEY] Message loop exited." );
140+ }
141+
142+ void Start () {
143+ // FIX (Bug 2): guard with keepRunning so double-calls are silently ignored
144+ if (keepRunning.exchange (true )) {
145+ Logger::info (" [HOTKEY] Already running, ignoring duplicate Start()." );
146+ return ;
147+ }
145148 messageThread = std::thread (MessageLoop);
146149 messageThread.detach ();
147- Logger::info (" Hotkey message loop thread started." );
150+ Logger::info (" [HOTKEY] Message loop thread started." );
148151 }
149152
150153 void Stop () {
151154 keepRunning = false ;
152155 if (hwndHotkey) {
153- // Post a quit message to wake up GetMessage
154156 PostMessage (hwndHotkey, WM_QUIT, 0 , 0 );
155- UnregisterHotKey (hwndHotkey, HOTKEY_ID_UNLOCK_ALL);
156- UnregisterHotKey (hwndHotkey, HOTKEY_ID_UNLOCK_LIST);
157- DestroyWindow (hwndHotkey);
158- hwndHotkey = nullptr ;
159157 }
160- // The message thread will exit when keepRunning is false and GetMessage returns false
161- Logger::info (" Hotkey handler stopped." );
158+ Logger::info (" [HOTKEY] Stopped." );
162159 }
163- }
160+
161+ }
0 commit comments