Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Hammerspoon config to send escape on short ctrl press
ctrl_table = {
sends_escape = true,
last_mods = {}
}
control_key_timer = hs.timer.delayed.new(0.15, function()
ctrl_table["send_escape"] = false
-- log.i("timer fired")
-- control_key_timer:stop()
end
)
last_mods = {}
control_handler = function(evt)
local new_mods = evt:getFlags()
if last_mods["ctrl"] == new_mods["ctrl"] then
return false
end
if not last_mods["ctrl"] then
-- log.i("control pressed")
last_mods = new_mods
ctrl_table["send_escape"] = true
-- log.i("starting timer")
control_key_timer:start()
else
-- log.i("contrtol released")
-- log.i(ctrl_table["send_escape"])
if ctrl_table["send_escape"] then
-- log.i("send escape key...")
hs.eventtap.keyStroke({}, "ESCAPE")
end
last_mods = new_mods
control_key_timer:stop()
end
return false
end
control_tap = hs.eventtap.new({12}, control_handler)
control_tap:start()
@alexlafroscia

This comment has been minimized.

Copy link

@alexlafroscia alexlafroscia commented Oct 27, 2016

Thanks so much for this! Works like a charm to get CAPSLOCK mapped to ESC and CTRL on Sierra!

@rjhilgefort

This comment has been minimized.

Copy link

@rjhilgefort rjhilgefort commented Oct 28, 2016

Thanks for this, now to get my hyper back...

@rjhilgefort

This comment has been minimized.

Copy link

@rjhilgefort rjhilgefort commented Oct 28, 2016

@arbelt I think it would be more clear if you change line 39 to be more explicit about the event flag you're targeting (it was the first thing I went digging for when reading through the code).

control_tap = hs.eventtap.new({ hs.eventtap.event.types.flagsChanged }, control_handler)

Without logging out hs.eventtap.event.types, it's unclear what event code 12 is.

@rjhilgefort

This comment has been minimized.

Copy link

@rjhilgefort rjhilgefort commented Oct 28, 2016

Also, it doesn't appear to be breaking the logic since the priming on line 2 isn't necessary, but it's not named the same as the rest of the file. Line 2's sends_escape = true, has an "s" that the other references don't. It should be:

send_escape = true,
@rjhilgefort

This comment has been minimized.

Copy link

@rjhilgefort rjhilgefort commented Oct 28, 2016

I'm finding this solution doesn't work with my tmux prefix chord unfortunately. I'll hack on it a bit and report back. If anyone else is in the same boat, let me know, and let me know what you've tried.

To get ready to play with it, I did some cleanup and simplification refactoring (original logic is still in tact, but with fewer variables). Feel free to have a look if you're using it as a jumping off point as well: https://gist.github.com/rjhilgefort/07ce5cdd3832083d7e94113d54372b1c

@jasoncodes

This comment has been minimized.

Copy link

@jasoncodes jasoncodes commented Oct 29, 2016

I have expanded on this script to avoid sending Escape when other keys are tapped while Control is held down. I’ve been running this for a few weeks now. Works great.

https://github.com/jasoncodes/dotfiles/blob/master/hammerspoon/control_escape.lua

@joaalto

This comment has been minimized.

Copy link

@joaalto joaalto commented Nov 22, 2016

Thank you arbelt and jasoncodes! This saved me from a lot of pain!

@wezzynl

This comment has been minimized.

Copy link

@wezzynl wezzynl commented Jan 25, 2017

Instead of using hs.eventtap.keyStroke to emit an event, returning from the control_handler function with a table of events to post (KeyDown then KeyUp) makes the escape perform instantly on KeyUp. hs.eventtap.keyStroke feels slow for some reason, taking a fraction of a second before the actual stroke gets sent, drove me nuts.

control_handler = function(evt)
  local new_mods = evt:getFlags()
  if last_mods["ctrl"] == new_mods["ctrl"] then
    return false
  end
  if not last_mods["ctrl"] then
    last_mods = new_mods
    send_escape = true
    control_key_timer:start()
  else
    last_mods = new_mods
    control_key_timer:stop()
    if send_escape then
      return true, {
        hs.eventtap.event.newKeyEvent({}, 'escape', true),
        hs.eventtap.event.newKeyEvent({}, 'escape', false),
      }
    end
  end
  return false
end

Seems much faster!

@CestDiego

This comment has been minimized.

Copy link

@CestDiego CestDiego commented Mar 11, 2017

@wezzynl Thank you for that comment. I also felt it a little unbearable sometimes to wait a fraction of a second for it to work. Also for some reason the escape key event would not be sent to emacs until I tried your solution.

@zcmarine

This comment has been minimized.

Copy link

@zcmarine zcmarine commented Mar 28, 2017

In case anyone else comes upon this and finds the above code confusing or, like me, keeps getting a bug where you end up having to hit escape multiple times, I rewrote the above and added comments, clearer variable names, and fixed that bug. Gist located here: https://gist.github.com/zcmarine/f65182fe26b029900792fa0b59f09d7f

Definitely wouldn't have gotten that done without the above as an initial starting point though, so thanks a ton to arbelt and jasoncodes!

@safecancel

This comment has been minimized.

Copy link

@safecancel safecancel commented Jun 19, 2017

Hi everyone,
Is there a way to not have this run when a specific app is in focus? (for example: remote desktop, vmware, teamviewer etc.)
Many thanks!

@dguo

This comment has been minimized.

Copy link

@dguo dguo commented Feb 24, 2020

@safecancel you can grab the current active application and use that to determine if the logic should run.

Thanks everyone. I've been using Karabiner-Elements only to remap caps lock to escape on tap and control on hold. With your solutions, I can use Hammerspoon instead (after changing caps lock to control in System Preferences) and drop Karabiner as a dependency.

@chrisspiegl

This comment has been minimized.

Copy link

@chrisspiegl chrisspiegl commented Mar 19, 2021

This is a great and very helpful gist. I used it to get my application to more reliably trigger the ESCAPE key. Since sending it with the hs.eventtap.sendKey does not seem to reliably do so (especially in applications like Alfred, 1Password, and such.

But the tip by @wezzynl in their comment above got me thinking and it actually does work much faster and more reliable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment