Skip to content

Commit b5811ef

Browse files
authored
fix: auto deregister hotkeys when focused app is excluded (#26)
1 parent 03a1c9d commit b5811ef

2 files changed

Lines changed: 107 additions & 34 deletions

File tree

cmd/govim/main.go

Lines changed: 106 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -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
243307
func (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
280367
func (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")

configs/default-config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ hint_characters = "asdfghjkl"
1818
accessibility_check_on_start = true
1919

2020
# Applications to exclude from GoVim functionality
21-
# GoVim will do nothing when these applications are focused
21+
# GoVim will do nothing when these applications are focused and pass through everything back to the os
2222
# Use bundle IDs to identify applications. To find an app's bundle ID:
2323
# osascript -e 'id of app "AppName"'
2424
# Examples:

0 commit comments

Comments
 (0)