hs.hotkey.modal feeding the keys on the focused window

402 views
Skip to first unread message

qwertyu...@gmail.com

unread,
May 6, 2016, 6:04:41 AM5/6/16
to Hammerspoon
Hi all,

I missed to see that I could ask here instead of in Github issues. Anyway, I posted this.

Anyone happen to know what I am missing?
 
k = hs.hotkey.modal.new('ctrl', 'd')
k:bind('', 'i', function() hs.application.launchOrFocus('iTerm') end) 
 
Hammerspoon is listening to it but at the same time ctrl + d is ran on the focused application e.g. if it's iTerm, it executes ^D. It would be nice if it was the same as how Slate does it.

Anyone happen to know how to make it work?

Thanks! 

asmagill

unread,
May 6, 2016, 2:27:07 PM5/6/16
to Hammerspoon, qwertyu...@gmail.com
I think the problem occurs because hs.hotkey.modal disables it's trigger (i.e. ctrl-d) before it completes the OS X hotkey handler, so the ctrl-d is still considered a "live" event to be passed through to the underlying application.  The following works for me (though you lose the ability to type ctrl-d to logout of a terminal window):

triggerK = hs.hotkey.bind('ctrl', 'd', function() k:enter() end)
k:bind('', 'i', function() hs.application.launchOrFocus('Terminal') end, function() k:exit() end)

Arnold French

unread,
May 6, 2016, 2:34:01 PM5/6/16
to asmagill, Hammerspoon

Yeah, sorry, I still need ctrl + d. It would be nice if it works only when needed. Definitely similar to how Slate does it.

asmagill

unread,
May 6, 2016, 2:44:36 PM5/6/16
to Hammerspoon, asma...@icloud.com, qwertyu...@gmail.com
Never used Slate, so that doesn't tell me much!

You could use an application watcher (hs.application.watcher) to enable and disable triggerK when you leave or enter iTerm, or if you're looking for a double-tap of ctrl-d to send an actual ctrl-d, something like this (I haven't stress tested it, but it seems to work):

triggerK = hs.hotkey.bind('ctrl', 'd', function() k:enter() end)
k:bind('', 'i', function() hs.application.launchOrFocus('Terminal') end, function() k:exit() end)

k:bind('ctrl', 'd', function()
    triggerK:disable()
end, function()
    hs.eventtap.keyStroke({"ctrl"}, "d")
    hs.timer.doAfter(.1, function() triggerK:enable() end)
    k:exit()
end)

The timer in the release function for ctrl-d is to make sure that the trigger is re-enabled *after* the keystroke has had a chance to actually be passed through to the terminal program.

Arnold French

unread,
May 6, 2016, 3:15:15 PM5/6/16
to asmagill, Hammerspoon, asma...@icloud.com

Never used Slate, so that doesn’t tell me much!

Ah, my bad.

The double tap works. The application watcher seem to be a good idea as well. I’ll try to experiment with it.

The way slate does it I think is that when I press a modifier e.g. CTRL + d. It waits if another key is pressed for a few seconds (maybe ~2 seconds). If no keys are pressed then it disables the modifier which allows me to use CTRL + d normally.

Aaron Magill

unread,
May 6, 2016, 3:25:33 PM5/6/16
to Arnold French, Hammerspoon
You could probably do something like this (haven't tested it myself yet, but it *should* work and may be more similar to what you're used to:

triggerK = hs.hotkey.bind('ctrl', 'd', function() k:enter() end)
function k:entered()
triggerK:disable()
hs.timer.doAfter(2, function() k:exit() end)
end
function k:exited()
triggerK:enable()
end

k:bind('', 'i', function() hs.application.launchOrFocus('Terminal') end)


The modal set doesn't get automatically exited when you tap `i`, so if you had other bindings as well, you'd have time to try them out if you mis-typed, but it will be exited after the 2 second delay started by the k:entered() function.  As long as you hit the second ctrl-d within the 2 second window, it should go through, though.

asmagill

unread,
May 6, 2016, 3:29:08 PM5/6/16
to Hammerspoon, qwertyu...@gmail.com
Oh, wait... do you mean that if no key follows, then the ctrl-d is sent, rather than intercepted?  I'll have to think about how that might be accomplished... it should be possible, but may involve some eventtap shenanigans, so i'm going to have to mull a bit...


On Friday, May 6, 2016 at 2:25:33 PM UTC-5, asmagill wrote:
You could probably do something like this (haven't tested it myself yet, but it *should* work and may be more similar to what you're used to:

triggerK = hs.hotkey.bind('ctrl', 'd', function() k:enter() end)
function k:entered()
triggerK:disable()
hs.timer.doAfter(2, function() k:exit() end)
end
function k:exited()
triggerK:enable()
end

k:bind('', 'i', function() hs.application.launchOrFocus('Terminal') end)


The modal set doesn't get automatically exited when you tap `i`, so if you had other bindings as well, you'd have time to try them out if you mis-typed, but it will be exited after the 2 second delay started by the k:entered() function.  As long as you hit the second ctrl-d within the 2 second window, it should go through, though.

Never used Slate, so that doesn’t tell me much!

Ah, my bad.

The double tap works. The application watcher seem to be a good idea as well. I’ll try to experiment with it.

The way slate does it I think is that when I press a modifier e.g. CTRL + d. It waits if another key is pressed for a few seconds (maybe ~2 seconds). If no keys are pressed then it disables the modifier which allows me to use CTRL + d normally.



asmagill

unread,
May 6, 2016, 3:36:20 PM5/6/16
to Hammerspoon, qwertyu...@gmail.com
Ok, not as bad as I feared:

triggerK = hs.hotkey.bind('ctrl', 'd', function() k:enter() end)
function k:entered()
k.didSomething = false  
triggerK:disable()
hs.timer.doAfter(2, function() k:exit() end)
end
function k:exited()
  if not k.didSomething then
      hs.eventtap.keyStroke({'ctrl'}, 'd')
  end
hs.timer.doAfter(.1, function() triggerK:enable() end)
end

k:bind('', 'i', function() k.didSomething = true ; hs.application.launchOrFocus('Terminal') end)

Between all of the examples here you should be able to find one that is close to what you want!

Enjoy!

Arnold French

unread,
May 6, 2016, 3:36:59 PM5/6/16
to asmagill, Hammerspoon

Oh, wait… do you mean that if no key follows, then the ctrl-d is sent, rather than intercepted? I’ll have to think about how that might be accomplished… it should be possible, but may involve some eventtap shenanigans, so i’m going to have to mull a bit…

Oh no, no, no. The one you sent is perfect!!! I removed the k:exited() though since I don’t really need the mistype bit. I’ll try to experiment and see if I get issues.

I’m new to both hammerspoon and lua so I’ll try to read through this. Thanks a lot!

Arnold French

unread,
May 6, 2016, 3:39:10 PM5/6/16
to asmagill, Hammerspoon
Sorry, I spoke too soon. It still fed CTRL+D to the terminal. It logged out the terminal but yeah it still allowed me to focus on another application. What happened is that when the focus is on the terminal and I pressed CTRL + D then g (I set it to switch to Chrome), it logged out the terminal and switched to Chrome.


On May 7, 2016 at 3:29:09 AM, asmagill (asma...@icloud.com) wrote:

asmagill

unread,
May 6, 2016, 4:21:37 PM5/6/16
to Hammerspoon, asma...@icloud.com, qwertyu...@gmail.com
You said you removed the k:exited part... do you mean this:

function k:exited()
triggerK:enable()
end

This code is necessary to re-enable the outer hotkey (ctrl-d); otherwise, Hammerspoon no longer listens for it.

To be clear:
k:exit() -- causes the modal state to be left immediately
k:exited() -- is executed when the modal state leaves (think of it as the cleanup we require when k:exit() is invoked)
k:enter() -- causes the modal state to be entered (which is why it's the target of the hotkey for ctrl-d)
k:entered() -- is executed when k:enter() is invoked.

So, what is (should be) happening:
You tap ctrl-d, which triggers the hotkey stored in triggerK
triggerK executes it's key-down function, which executes k:enter()
k:enter() causes the modal keys to become active (everything you defined with k:bind(...)) and k:entered() to be executed
k:entered() disables triggerK so that ctrl-d is no longer a hotkey and sets up a 2 second timer, after which k:exit() will be called

Because ctrl-d is no longer a hotkey, you can tap it a second time and it will be caught by iTerm.

if you hit a modal key (i or g have been mentioned) their keyDown function is executed.  When I said that this version allowed for mis-types, it was because I removed their keyUp functions (the second function a hotkey binding allows you to optionally add)... in the previous iteration, I had the keyUp functions defined as function() k:exit() end.  We don't need them anymore because the timer in k:entered() does this for us, though I don't think it would matter if you put them back in.

If you hit `i` to go to iTerm and hit ctrl-d again *before* the 2 second timer, ctrl-d is still not a hotkey at the moment, so iTerm receives it.

Now, 2 seconds go by and the timer fires, executing k:exit()
k:exit() disables the modal hot keys (i, g, others?) and executes k:exited()
k:exited() contains the code to re-enable ctrl-d, which we disabled earlier in k:entered().

And you're back to the hotkey state you were before pressing ctrl-d the fist time.

Does that help?
Reply all
Reply to author
Forward
0 new messages