11#include < algorithm>
2+ #include < atomic>
23#include < cctype>
4+ #include < mutex>
35#include < string>
6+ #include < thread>
47#include < unordered_map>
58#include < vector>
69
@@ -138,12 +141,26 @@ bool ParseAcceleratorMac(const std::string& accelerator, UInt32& modifiers, UInt
138141 }
139142 }
140143
141- if (key_token.rfind (" f" , 0 ) == 0 ) {
142- int fnum = std::stoi (key_token.substr (1 ));
143- if (fnum >= 1 && fnum <= 24 ) {
144- keycode = kVK_F1 + (fnum - 1 );
145- return true ;
144+ if (key_token.rfind (" f" , 0 ) == 0 && key_token.size () > 1 ) {
145+ // Carbon virtual key codes for function keys are non-contiguous; use explicit table.
146+ static const UInt32 kFKeyCodes [] = {
147+ kVK_F1 , kVK_F2 , kVK_F3 , kVK_F4 , kVK_F5 ,
148+ kVK_F6 , kVK_F7 , kVK_F8 , kVK_F9 , kVK_F10 ,
149+ kVK_F11 , kVK_F12 , kVK_F13 , kVK_F14 , kVK_F15 ,
150+ kVK_F16 , kVK_F17 , kVK_F18 , kVK_F19 , kVK_F20 ,
151+ };
152+ try {
153+ int fnum = std::stoi (key_token.substr (1 ));
154+ if (fnum >= 1 && fnum <= static_cast <int >(std::size (kFKeyCodes ))) {
155+ keycode = kFKeyCodes [fnum - 1 ];
156+ return true ;
157+ }
158+ } catch (const std::invalid_argument&) {
159+ return false ;
160+ } catch (const std::out_of_range&) {
161+ return false ;
146162 }
163+ return false ;
147164 }
148165
149166 if (key_token == " space" ) {
@@ -189,11 +206,17 @@ bool ParseAcceleratorMac(const std::string& accelerator, UInt32& modifiers, UInt
189206
190207} // namespace
191208
209+ // Polling interval for the background event thread (seconds).
210+ static constexpr EventTimeout kEventPollTimeout = kEventDurationSecond * 0.1 ;
211+
192212class ShortcutManagerImpl final : public ShortcutManager::Impl {
193213 public:
194214 explicit ShortcutManagerImpl (ShortcutManager* manager) : manager_(manager) {}
195215
196216 ~ShortcutManagerImpl () override {
217+ StopThread ();
218+ JoinThread ();
219+
197220 for (const auto & [id , hotkey] : hotkeys_) {
198221 UnregisterEventHotKey (hotkey);
199222 }
@@ -208,7 +231,7 @@ explicit ShortcutManagerImpl(ShortcutManager* manager) : manager_(manager) {}
208231 bool IsSupported () override { return true ; }
209232
210233 bool RegisterShortcut (const std::shared_ptr<Shortcut>& shortcut) override {
211- EnsureHandler ();
234+ EnsureThread ();
212235
213236 UInt32 modifiers = 0 ;
214237 UInt32 keycode = 0 ;
@@ -242,10 +265,10 @@ bool UnregisterShortcut(const std::shared_ptr<Shortcut>& shortcut) override {
242265 return true ;
243266 }
244267
245- void SetupEventMonitoring () override { EnsureHandler (); }
268+ void SetupEventMonitoring () override { EnsureThread (); }
246269
247270 void CleanupEventMonitoring () override {
248- // Keep handler installed as long as shortcuts exist .
271+ // Keep thread running while shortcuts may still be registered .
249272 }
250273
251274 private:
@@ -279,6 +302,59 @@ void EnsureHandler() {
279302 &handler_);
280303 }
281304
305+ void EnsureThread () {
306+ std::lock_guard<std::mutex> lock (thread_mutex_);
307+ if (running_.load ()) {
308+ return ;
309+ }
310+
311+ EnsureHandler ();
312+
313+ running_.store (true );
314+ thread_ = std::thread ([this]() { ThreadMain (); });
315+ }
316+
317+ void StopThread () {
318+ std::lock_guard<std::mutex> lock (thread_mutex_);
319+ if (!running_.load ()) {
320+ return ;
321+ }
322+
323+ running_.store (false );
324+ // Unlock before joining so the thread can complete any in-progress work.
325+ // Note: the thread only touches running_ and Carbon APIs, so this is safe.
326+ }
327+
328+ // Must be called without thread_mutex_ held (to allow the thread to finish).
329+ void JoinThread () {
330+ if (thread_.joinable ()) {
331+ thread_.join ();
332+ }
333+ }
334+
335+ void ThreadMain () {
336+ const EventTypeSpec hotkey_spec = {kEventClassKeyboard , kEventHotKeyPressed };
337+
338+ while (running_.load ()) {
339+ EventRef event = nullptr ;
340+ // Use a short timeout so the loop can check the running_ flag periodically.
341+ OSStatus status =
342+ ReceiveNextEvent (1 , &hotkey_spec, kEventPollTimeout , true , &event);
343+
344+ if (!running_.load ()) {
345+ if (status == noErr && event) {
346+ ReleaseEvent (event);
347+ }
348+ break ;
349+ }
350+
351+ if (status == noErr && event) {
352+ SendEventToEventTarget (event, GetApplicationEventTarget ());
353+ ReleaseEvent (event);
354+ }
355+ }
356+ }
357+
282358 void HandleHotKey (ShortcutId shortcut_id) {
283359 auto shortcut = manager_->Get (shortcut_id);
284360 if (!shortcut) {
@@ -296,6 +372,10 @@ void HandleHotKey(ShortcutId shortcut_id) {
296372 ShortcutManager* manager_;
297373 std::unordered_map<ShortcutId, EventHotKeyRef> hotkeys_;
298374 EventHandlerRef handler_ = nullptr ;
375+
376+ std::atomic<bool > running_{false };
377+ std::thread thread_;
378+ std::mutex thread_mutex_;
299379};
300380
301381ShortcutManager::ShortcutManager ()
0 commit comments