@@ -52,6 +52,8 @@ type App struct {
5252 lastScrollKey string
5353 selectedHint * hints.Hint
5454 enabled bool
55+ // Track whether global hotkeys are currently registered
56+ hotkeysRegistered bool
5557}
5658
5759// NewApp creates a new application instance
@@ -105,15 +107,16 @@ func NewApp(cfg *config.Config) (*App, error) {
105107 scrollCtrl := scroll .NewController (cfg .Scroll , log )
106108
107109 app := & App {
108- config : cfg ,
109- logger : log ,
110- hotkeyManager : hotkeyMgr ,
111- hintGenerator : hintGen ,
112- hintOverlay : hintOverlay ,
113- scrollController : scrollCtrl ,
114- currentMode : ModeIdle ,
115- enabled : true ,
116- hintInput : "" ,
110+ config : cfg ,
111+ logger : log ,
112+ hotkeyManager : hotkeyMgr ,
113+ hintGenerator : hintGen ,
114+ hintOverlay : hintOverlay ,
115+ scrollController : scrollCtrl ,
116+ currentMode : ModeIdle ,
117+ enabled : true ,
118+ hintInput : "" ,
119+ hotkeysRegistered : false ,
117120 }
118121
119122 // Create event tap for capturing keys in modes
@@ -149,10 +152,8 @@ func (a *App) Run() error {
149152 a .ipcServer .Start ()
150153 a .logger .Info ("IPC server started" )
151154
152- // Register hotkeys
153- if err := a .registerHotkeys (); err != nil {
154- return fmt .Errorf ("failed to register hotkeys: %w" , err )
155- }
155+ // Initialize hotkeys based on current focused app and exclusion
156+ a .refreshHotkeysForCurrentApp ()
156157
157158 a .logger .Info ("GoVim is running" )
158159 fmt .Println ("✓ GoVim is running" )
@@ -168,6 +169,9 @@ func (a *App) Run() error {
168169 fmt .Printf (" Scroll mode: %s\n " , key )
169170 }
170171
172+ // Start a background watcher to update hotkey registration when focus or enabled state changes
173+ go a .watchFocusedAppForHotkeys ()
174+
171175 // Wait for interrupt signal with force-quit support
172176 sigChan := make (chan os.Signal , 1 )
173177 signal .Notify (sigChan , os .Interrupt , syscall .SIGTERM )
@@ -239,6 +243,66 @@ func (a *App) registerHotkeys() error {
239243 return nil
240244}
241245
246+ // refreshHotkeysForCurrentApp registers or unregisters global hotkeys based on
247+ // whether GoVim is enabled and whether the currently focused app is excluded.
248+ func (a * App ) refreshHotkeysForCurrentApp () {
249+ // If disabled, ensure no hotkeys are registered
250+ if ! a .enabled {
251+ if a .hotkeysRegistered {
252+ a .logger .Debug ("GoVim disabled; unregistering hotkeys" )
253+ a .hotkeyManager .UnregisterAll ()
254+ a .hotkeysRegistered = false
255+ }
256+ return
257+ }
258+
259+ bundleID := a .getFocusedBundleID ()
260+
261+ // If app is excluded, unregister; otherwise ensure registered
262+ if a .config .IsAppExcluded (bundleID ) {
263+ if a .hotkeysRegistered {
264+ a .logger .Info ("Focused app excluded; unregistering global hotkeys" ,
265+ zap .String ("bundle_id" , bundleID ))
266+ a .hotkeyManager .UnregisterAll ()
267+ a .hotkeysRegistered = false
268+ }
269+ return
270+ }
271+
272+ if ! a .hotkeysRegistered {
273+ if err := a .registerHotkeys (); err != nil {
274+ a .logger .Error ("Failed to register hotkeys" , zap .Error (err ))
275+ return
276+ }
277+ a .hotkeysRegistered = true
278+ }
279+ }
280+
281+ // watchFocusedAppForHotkeys periodically checks the focused application and
282+ // updates global hotkey registration so excluded apps receive the keybindings.
283+ func (a * App ) watchFocusedAppForHotkeys () {
284+ var lastBundleID string
285+ lastEnabled := a .enabled
286+ for {
287+ // Check current focused bundle
288+ focused := accessibility .GetFocusedApplication ()
289+ var bundleID string
290+ if focused != nil {
291+ bundleID = focused .GetBundleIdentifier ()
292+ focused .Release ()
293+ }
294+
295+ // If focus or enabled state changed, refresh hotkeys
296+ if bundleID != lastBundleID || lastEnabled != a .enabled {
297+ a .refreshHotkeysForCurrentApp ()
298+ lastBundleID = bundleID
299+ lastEnabled = a .enabled
300+ }
301+
302+ time .Sleep (500 * time .Millisecond )
303+ }
304+ }
305+
242306// updateRolesForCurrentApp updates clickable and scrollable roles based on the current focused app
243307func (a * App ) updateRolesForCurrentApp () {
244308 // Get the focused application
@@ -276,6 +340,29 @@ func (a *App) updateRolesForCurrentApp() {
276340 accessibility .SetScrollableRoles (scrollableRoles )
277341}
278342
343+ // getFocusedBundleID returns the bundle identifier of the currently focused
344+ // application, or an empty string if it cannot be determined.
345+ func (a * App ) getFocusedBundleID () string {
346+ app := accessibility .GetFocusedApplication ()
347+ if app == nil {
348+ return ""
349+ }
350+ defer app .Release ()
351+ return app .GetBundleIdentifier ()
352+ }
353+
354+ // isFocusedAppExcluded returns true if the currently focused application's bundle
355+ // ID is in the excluded apps list. Logs context for debugging.
356+ func (a * App ) isFocusedAppExcluded () bool {
357+ bundleID := a .getFocusedBundleID ()
358+ if bundleID != "" && a .config .IsAppExcluded (bundleID ) {
359+ a .logger .Debug ("Current app is excluded; ignoring mode activation" ,
360+ zap .String ("bundle_id" , bundleID ))
361+ return true
362+ }
363+ return false
364+ }
365+
279366// activateHintMode activates hint mode
280367func (a * App ) activateHintMode (withActions bool ) {
281368 if ! a .enabled {
@@ -287,16 +374,9 @@ func (a *App) activateHintMode(withActions bool) {
287374 return
288375 }
289376
290- // Check if current app is excluded
291- focusedApp := accessibility .GetFocusedApplication ()
292- if focusedApp != nil {
293- defer focusedApp .Release ()
294- bundleID := focusedApp .GetBundleIdentifier ()
295- if a .config .IsAppExcluded (bundleID ) {
296- a .logger .Debug ("Current app is excluded, ignoring hint mode activation" ,
297- zap .String ("bundle_id" , bundleID ))
298- return
299- }
377+ // Centralized exclusion guard
378+ if a .isFocusedAppExcluded () {
379+ return
300380 }
301381
302382 a .logger .Info ("Activating hint mode" )
@@ -796,16 +876,9 @@ func (a *App) activateScrollMode() {
796876 return
797877 }
798878
799- // Check if current app is excluded
800- focusedApp := accessibility .GetFocusedApplication ()
801- if focusedApp != nil {
802- defer focusedApp .Release ()
803- bundleID := focusedApp .GetBundleIdentifier ()
804- if a .config .IsAppExcluded (bundleID ) {
805- a .logger .Debug ("Current app is excluded, ignoring scroll mode activation" ,
806- zap .String ("bundle_id" , bundleID ))
807- return
808- }
879+ // Centralized exclusion guard
880+ if a .isFocusedAppExcluded () {
881+ return
809882 }
810883
811884 a .logger .Info ("Activating scroll mode" )
0 commit comments