-
-
Notifications
You must be signed in to change notification settings - Fork 942
Add support for fn key and Escape to cancel on macOS #401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Add support for using the fn/Globe key as a shortcut trigger on macOS. The fn key is a modifier-only key that cannot be captured by standard shortcut libraries like tauri-plugin-global-shortcut. Implementation: - Add fn_monitor.rs using NSEvent.addGlobalMonitorForEventsMatchingMask to detect fn key press/release via FlagsChanged events - Add routing layer (register_binding/unregister_binding) to direct fn bindings to fn_monitor and regular bindings to global-shortcut - Add dispatch_binding_event to share action dispatch logic between regular shortcuts and fn key - Allow "fn" as valid binding in validate_shortcut_string (macOS only) - Remove unused rdev dependency, add objc2 dependencies for macOS The fn key requires Accessibility permission, which is already needed for the enigo pasting functionality. Limitations: - fn key events stop when Secure Input is active (password fields) - fn+key combinations conflict with system shortcuts; fn alone is safe
Cancel recording mid-transcription with Escape key. - Dynamic bindings: only registered while recording is active - Disabled on Linux due to global-shortcut plugin instability - Idempotent registration avoids callback deadlocks
Add UI elements for the fn key shortcut on macOS: - "Use fn" button to quickly set fn/Globe key as shortcut - "fn (Globe)" display when fn key is active - Tooltips explaining fn key usage
The fn key shortcut still works on macOS, but users must configure it manually by editing settings.json and setting current_binding to "fn". This commit can be reverted to restore the "Use fn" button in the UI.
|
@cjpais This is my attempt at #136 and #163 . Currently it's settings-only (no UI changes) but if you recent the last commit there's a UI too. Shout if you'd like me to change anything, and if you'd rather not merge this feel free to close - I need both |
|
So I haven't had a ton of time to review, but just my overall comment is I really don't want any special case handling for the function key in an ideal world. I really would like the keyboard handling on Mac OS to be all in one package. Even if it's not using global shortcut to do that, I'm fine with that. I just want there not to be a tiny weird branch in the code path. I'd rather that branch be a major branch at the top level: if you're on MacOS, use MacOS specific keyboard handling. Part of the reason for this is I don't really like the global shortcut plugin as it is, it seems to have some limitations which don't fit Handy's use case perfectly. In the early versions (not sure if they were published or not) the keyboard handling was my own version I wrote that supported input from any keys cross platform. I would like to eventually move back to something like that, the main blocker on MacOS for that particular code was that it needed additional permissions when built which I do not want to give. The code at this commit is what I am talking about https://github.com/cjpais/Handy/tree/83d845284d9f7dcfe5561524925cf59e9a5155e6. It went through some improvements after this specific commit, but generally it did work, though I think it probably could be reworked a fair amount, to be much better. But just giving an overall idea that we don't necessarily need to rely on the global shortcut plug-in and I eventually would like to deprecate that support entirely in favor of something better, something that works really, really perfectly as a cross-platform library that can be distributed independently of the Handy application as well as being used within Handy itself, if that makes sense. It definitely will start in handy and then move outwards as a library from there, kind of in the spirit of open sourcing the core of this application and making it even easier to develop things like this for everyone. And I think eventually I would like to extract a library that works better cross platform generally and maybe is not Tauri specific |
|
Gotcha - thanks!
Yeah I totally get this. When I've got some time I'll have a look at what a "minimum viable" macOS keyboard implementation could look like in Handy. (Notes to self: 1. make it modular for other OSs in the future. 2. Consider a similar API to
Yeah this makes tons of sense. I'm kinda amazed there isn't something out there already. |
|
Thank you for understand haha, I know you put time and effort into this
There might be! I just probably didn't look too deeply, but the underlying libraries are there (or close enough, enigo, obj-c bindings, etc) If there's a better dev experience to be had by diverging from the global-shortcut api structure that is okay too, but honestly from what I recall global shortcut api is decent |
|
Looks like rdev might support the fn key? Tho I'm sure there was a reason I used Will have a play. |
|
Maybe the same reason why we are using a fork of rdev? I forget exactly why I did this, but there was something broken in the main repo at the time I created handy hahah I'm almost certain it was because of this PR hadn't been merged at the time I brought rdev in, but can't say for certain: Narsil/rdev#147 more context: https://github.com/orgs/tauri-apps/discussions/7839 As far as I can tell they also use: objc2 lol |
|
So #224 allows the Escape key to be used to cancel recording. This PR includes a very similar a similar (though more generic) implemetation, written before 224 was merged. I've just extracted the only bit of 2915aea that's worth keeping into #408. When that's merged, this branch can be simplified to remove the Escape-related stuff (worth doing because I'm using it in my fork until we work out how to handle |
|
This would be a really highly desired feature if you could incorporate it because in the past I've used tools like Aqua Voice and it's really easy to use the globe slash FN key and escape. |
|
@meh256 Yeah that's my preferred setup too (the |
|
Notes:
|
|
Closing as I will have a PR coming in for this |
|
I'm on v0.7.0. Was able to set |
|
What do you mean? Can you check what it's set to in the debug menu? |
|
@jperezr21 i believe esc only works in non-ptt mode iirc, but we probably should fix this Debug is ctrl/cmd+shift+d |


Feature 1: fn Key Support (macOS)
How it works
The fn/Globe key is a modifier key that generates
NSEventType::FlagsChangedevents withNSEventModifierFlags::Function. Standard shortcut libraries (liketauri-plugin-global-shortcut) cannot capture modifier-only keys.Implementation
Parallel input system for fn key:
src-tauri/src/shortcut/fn_monitor.rs(macOS-only)NSEvent::addGlobalMonitorForEventsMatchingMask_handlerfrom objc2NSEventMask::FlagsChangedeventsNSEventModifierFlags::Functionto detect fn press/releasedispatch_binding_event()function as regular shortcutssrc-tauri/src/shortcut/mod.rsfn_monitorinstead oftauri-plugin-global-shortcutis_fn_binding()helper checks for fn-only bindingsvalidate_shortcut_string()allows "fn" as valid on macOSDependencies (macOS only in Cargo.toml):
Frontend (optional): "Use fn" button in
HandyShortcut.tsx- see Reviewer NotesPermissions
Requires Accessibility permission (same as already needed for
enigopasting). No additional permission prompts for users.Feature 2: Escape to Cancel Recording
How it works
The cancel shortcut is a dynamic binding - only registered while recording is active.
Implementation
CancelActioninactions.rs:cancel_current_operation()to discard recordingCancel binding in
settings.rs:dynamic: true- not registered at startupDynamic registration in
shortcut/mod.rs:register_dynamic_binding()- idempotent (unregisters first if already registered)unregister_dynamic_binding()- removes binding at runtimeinit_shortcuts()skips dynamic bindingsLifecycle:
TranscribeAction::start()registers cancel viarun_on_main_thread()TranscribeAction::stop()unregisters cancel viarun_on_main_thread()CancelAction::start()does NOT unregister (next registration handles cleanup)Key design decisions
Why idempotent registration?
Unregistering from inside the shortcut's own callback causes a deadlock (global_shortcut holds internal locks). Instead,
register_dynamic_binding()unregisters first, soCancelActiondoesn't need to unregister itself.Why release toggle lock before calling action?
dispatch_binding_event()releases the toggle state lock BEFORE callingaction.start()/stop(). This prevents deadlock whenCancelActioncallscancel_current_operation()which also needs the lock.Linux Notes
Dynamic shortcut registration (used for the cancel shortcut) is disabled on Linux due to
instability with the
tauri-plugin-global-shortcutplugin. See PR #392.This means the Escape-to-cancel feature is not available on Linux. The cancel shortcut will
silently be a no-op.
Potential future improvement: The
dynamicbinding architecture in this branch providesa cleaner foundation than the original async-spawn approach. If the underlying Linux shortcut
issues are resolved upstream, enabling dynamic bindings on Linux would only require removing
the
#[cfg(target_os = "linux")]guards inregister_dynamic_binding()andunregister_dynamic_binding()inshortcut/mod.rs.Reviewer Notes: fn Key UI
The fn key backend works regardless of UI changes. Users can always configure fn key manually
by editing
settings_store.json. Eg:UI Visibility Option
The commit "Remove fn key UI (manual config only)" removes the "Use fn" button from the
settings UI. This commit is structured to be easily included or excluded:
The UI additions that commit removes:
isFnBinding()helper functionReference PRs