Hammerspoon: bind hotkeys to an application

Created on 1 Dec 2015  Â·  18Comments  Â·  Source: Hammerspoon/hammerspoon

I'd like for a hotkey to only work in a particular application. Is there a way to do this?

thanks.

Most helpful comment

Since this thread comes up in google when searching for per-app keybinding in Hammerspoon, and provides very little help, I'll just add an example script that makes it so that if you press ⌘+R in RubyMine, Firefox is focused and the page is refreshed:

reloadFxFromRubyMine = hs.hotkey.new('⌘', 'r', function()
      hs.application.launchOrFocus("Firefox.app")
      reloadFxFromRubyMine:disable() -- does not work without this, even though it should
      hs.eventtap.keyStroke({"⌘"}, "r")
  end)

hs.window.filter.new('RubyMine')
    :subscribe(hs.window.filter.windowFocused,function() reloadFxFromRubyMine:enable() end)
    :subscribe(hs.window.filter.windowUnfocused,function() reloadFxFromRubyMine:disable() end)

This can easily be made modular, but I only have one such use for it.

All 18 comments

@kimaldis if you use hs.application.watcher to get events when applications gain/lose focus (or in terms of the API, are "activated" or "deactivated") you can then activate specific hotkeys.

I'd suggest having a table for each application you want hotkeys for, then just loop over that app's table in the activated/deactivated events and enable/disable each hotkey.

I thought maybe so. Thanks for the input.

Just a quick pointer, something I found out recently — if you are bindings keys to app remember to unbind on both deactivated and terminated. @cmsj maybe terminated should send deactivated as well?

fwiw, this has been a planned feature for a while; as soon as I can spare some time I'll implement it; any thoughts/suggestions for what the api should look like are welcome - off the top of my head I'd say something like the obvious hs.hotkey.bindForApplication(appname,mods,key,message,fns,...), but maybe I'm overlooking some use cases.
fwiw#2, for a diy solution you can use hs.window.filter, so that you needn't worry about gotchas like the one mentioned by @szymonkaliski.

It would be nice, but the workaround seems simple enough.

I don't know if I've spotted something here but I set up alerts on activate and deactivate of a particular application. Using Tab to swap between applications, if the application being brought to the front causes a switch to another desktop, the alert doesn't appear. The disable/enables that I have in the watcher work fine, it looks like the code is executed, just the alert doesn't appear. No biggie, just thought you'd like to know.

Yeah, the alert shows up in the Space being switched away from.

@szymonkaliski I don't think terminated should send deactivated too. For this particular use of hs.application.watcher it would be helpful, but it may not for others. I like the higher level solution that @lowne mentioned, where we just provide the wrappers for doing per-app hotkeys. It's something that people are always going to want to be doing, so it makes a ton of sense for us to have it as a feature :)

@lowne i was wondering if you could expand on your 'fwiw #2' comment. Where would hs.window.filter be used?

BTW - this feature would be pretty killer. Certainly much better than using the system preferences to define app specific menu shortcuts.

Subscribed

Since this thread comes up in google when searching for per-app keybinding in Hammerspoon, and provides very little help, I'll just add an example script that makes it so that if you press ⌘+R in RubyMine, Firefox is focused and the page is refreshed:

reloadFxFromRubyMine = hs.hotkey.new('⌘', 'r', function()
      hs.application.launchOrFocus("Firefox.app")
      reloadFxFromRubyMine:disable() -- does not work without this, even though it should
      hs.eventtap.keyStroke({"⌘"}, "r")
  end)

hs.window.filter.new('RubyMine')
    :subscribe(hs.window.filter.windowFocused,function() reloadFxFromRubyMine:enable() end)
    :subscribe(hs.window.filter.windowUnfocused,function() reloadFxFromRubyMine:disable() end)

This can easily be made modular, but I only have one such use for it.

That's very helpful. Thank you.

That's awesome, thanks @raveren!

If you're going to be looking at it, Mark, ... I've just had a situation where I could do with a hotkey working in all applications except one.

Just a thought.

I too would like to be able to enable some hotkeys in all applications except for one (or two). In this case in all applications except for Emacs or iTerm. The solution from @raveren should be enough to get me going, but it would be great to know if any other solution has been created.

Incidentally, I'm catching the hotkey events and then creating a new different hotkey event (as a simple way of handling ctrl+f, ctrl+b, etc until Karabiner-Elements supports that on Sierra). I tried just recreating the original hotkey event if the foreground application was Emacs, but it turns out that Hammerspoon will catch that same event and go into an infinite loop of catching and creating the same key event. This doesn't seem ideal.

While still a work in progress, a project I'm working on implements app specific bindings. Probably overkill for the question here, but it is an example of what one could do to solve the problem.
https://github.com/quickdraw6906/hammerspoon-pc/tree/master

Using the notes in previous comments, I came up with this to "swap" Command and Option only in Terminal. I only cared about CMD+f/b/w/d/delete (basic emacs movements and deletions for bash), if you want more you'll have to add them in termBinds.

````lua

function termMetaCombo (letter)
return hs.hotkey.new({"cmd"}, letter, nil, function()
hs.eventtap.keyStroke({"alt"}, letter)
end)
end

termBinds = {
termMetaCombo('f'),
termMetaCombo('b'),
termMetaCombo('w'),
termMetaCombo('d'),
termMetaCombo('delete')
}

function enableBinds()
-- hs.console.printStyledtext("term focused")
for k,v in pairs(termBinds) do
v:enable()
end
end

function disableBinds()
-- hs.console.printStyledtext("term unfocused")
for k,v in pairs(termBinds) do
v:disable()
end
end

local wf=hs.window.filter

wf_terminal = wf.new{'Terminal'}
wf_terminal:subscribe(wf.windowFocused, enableBinds)
wf_terminal:subscribe(wf.windowUnfocused, disableBinds)
````

This is pretty close to the "Change Command_L to Option_L" that Karabiner/keyremap4macbook used to offer, though this method only works on keyup, not keydown which does make it feel a bit "laggy" comparatively (it doesn't seem possible to send keystrokes on keydown in Hammerspoon).

I'm going to close this because I'm gardening the bugs and this has been open for ages with no further discussion, feel free to re-open it if there are specific things you want to do :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aaronjensen picture aaronjensen  Â·  3Comments

tomrbowden picture tomrbowden  Â·  3Comments

dasmurphy picture dasmurphy  Â·  4Comments

jiahut picture jiahut  Â·  3Comments

jasonrudolph picture jasonrudolph  Â·  4Comments