Enabling/Disabling Keybindings Per-App

242 views
Skip to first unread message

Oliver Taylor

unread,
Jan 10, 2021, 6:31:55 PM1/10/21
to hamme...@googlegroups.com
I'm attempting to remap 'ctrl-w' to 'alt-delete' and 'alt-d' to
'alt-forwarddelete' globally, but disable that binding in Emacs and Terminal.

(I'll caveat the below by saying that I'm not a programmer, just a simple
dotfile copypasta hack.)

The below code works well, but with 2 problems:

1. When switching from Emacs to Terminal (or Terminal to Emacs) the bindings
are disabled, then immediately re-enabled. Is this a bug or a problem with
my code? (probably code)

2. I would love to be able to group both key bindings and enable them as a set
(in reality I have 9 individual bindings that I'm using this technique
for), is that possible?

3. It seems like I should be able to create a single IF Emacs OR Terminal
statement but similar problems to the below approach persist.

Any help would be appreciated!

-----

function deleteWordBack()
hs.eventtap.event.newKeyEvent(hs.keycodes.map.alt, true):post()
hs.eventtap.event.newKeyEvent('delete', true):post()
hs.eventtap.event.newKeyEvent('delete', false):post()
hs.eventtap.event.newKeyEvent(hs.keycodes.map.alt, false):post()
end

function deleteWordForward()
hs.eventtap.event.newKeyEvent(hs.keycodes.map.alt, true):post()
hs.eventtap.event.newKeyEvent('forwarddelete', true):post()
hs.eventtap.event.newKeyEvent('forwarddelete', false):post()
hs.eventtap.event.newKeyEvent(hs.keycodes.map.alt, false):post()
end

local bindDeleteWordBack = hs.hotkey.bind({'ctrl'}, 'w', deleteWordBack)
local bindDeleteWordForward = hs.hotkey.bind({'alt'}, 'd', deleteWordForward)

bindDeleteWordBack:enable()
bindDeleteWordForward:enable()

appKeyBinder = hs.application.watcher.new(function(appName, eventType, appObject)
if appName == "Terminal" then
if eventType == hs.application.watcher.activated then
bindDeleteWordBack:disable()
bindDeleteWordForward:disable()
elseif eventType == hs.application.watcher.deactivated or eventType == hs.application.watcher.terminated then
bindDeleteWordBack:enable()
bindDeleteWordForward:enable()
end
elseif appName == "Emacs" then
if eventType == hs.application.watcher.activated then
bindDeleteWordBack:disable()
bindDeleteWordForward:disable()
elseif eventType == hs.application.watcher.deactivated or eventType == hs.application.watcher.terminated then
bindDeleteWordBack:enable()
bindDeleteWordForward:enable()
end
end
end):start()

Dan Mattera

unread,
Feb 17, 2021, 10:22:57 AM2/17/21
to Hammerspoon
Hey Oliver, 

I do some similar things to what you are looking for here and so i've worked through some of the same issues you're facing.

The cause of your issue in #1 is that when you switch between apps, 'application.watcher' fires twice: first it fires your appKeyBinder callback sending it an "activated" event for the app you just switched to, then it fires it again for the deactivated event for app you just switched away from. This means that when you switch from Terminal to Emacs (or vice versa), it fires the activated event for Emacs which correctly disables those hotkeys but then it fires the deactivated event immediately after that for Terminal which enables them again. One solution for fixing this would be to keep track of the most recent activated/deactivated apps and then add an additional check at the end of appKeyBinder for instances of switching between these two apps.

As for #2, this can be accomplished by using hs.hotkey.modal whose purpose is to create a collection of hotkeys that you want to be able to enable/disable all together given certain conditions (i.e. when certain apps are active).

For #3, I switched around/combined some of your original logic to make things a little cleaner/simpler:


-- create a new hotkey modal with no key binding to activated/deactivate it. we will activate/deactivate it based on the current app being used
noEmacsTerminalModal = hs.hotkey.modal.new({""}, "", nil)

-- bind your hotkeys to noEmacsTerminalModal like so:
noEmacsTerminalModal:bind({'ctrl'}, 'w', deleteWordBack)
noEmacsTerminalModal:bind({'alt'}, 'd', deleteWordForward)

-- enable all the hotkeys bound to noEmacsTerminalModal
noEmacsTerminalModal:enter()

-- set up "global" variables so that deactivated appKeyBinder events can reference the previously activated app and vice versa
local activatedApp = ""
local deactivatedApp = ""

appKeyBinder = hs.application.watcher.new(
function(appName, eventType, appObject)
if eventType == hs.application.watcher.activated then
activatedApp = appName
if appName == "Terminal" or appName == "Emacs" then
-- disable all hotkeys bound to noEmacsTerminalModal
noEmacsTerminalModal:exit()
end
elseif eventType == hs.application.watcher.deactivated or eventType == hs.application.watcher.terminated then
deactivatedApp = appName
if appName == "Terminal" or appName == "Emacs" then
-- enable all hotkeys bound to noEmacsTerminalModal
noEmacsTerminalModal:enter()
end
end
-- check to see if we just switched from Terminal to Emacs or vice versa
if (activatedApp == "Emacs" or activatedApp = "Terminal") and (deactivatedApp == "Emacs" or deactivatedApp = "Terminal") then
-- disable all hotkeys bound to noEmacsTerminalModal
noEmacsTerminalModal:exit()
end
end):start()

Hope this helps!

Dan
Reply all
Reply to author
Forward
0 new messages