Playing with Hammerspoon


I started using Hammerspoon recently. My keyboard remapping of CAPS LOCK to Ctrl when pressing and Esc when tapping had stopped working after upgrading to macOS Sierra. Turns out the current version of Karabiner doesn't work with Sierra (Oct-2016).

After finding out I could emulate the same behavior with Hammerspoon thanks to this gist, I gave it a try to see what else it could do. That's how I ended up writing the following Lua utility.

The idea is to provide an easy way of showing keystrokes on the screen. It's inspired by other tools like Keycastr which for some reason I couldn't get to work.

function showKeyPress(tap_event)
  local duration = 1.5  -- popup duration
  local modifiers = ""  -- key modifiers string representation
  local flags = tap_event:getFlags()
  local character = hs.keycodes.map[tap_event:getKeyCode()]
  -- we only want to read special characters via getKeyCode, so we
  -- use this subset of hs.keycodes.map
  local special_chars = {
    ["f1"] = true, ["f2"] = true, ["f3"] = true, ["f4"] = true,
    ["f5"] = true, ["f6"] = true, ["f7"] = true, ["f8"] = true,
    ["f9"] = true, ["f10"] = true, ["f11"] = true, ["f12"] = true,
    ["f13"] = true, ["f14"] = true, ["f15"] = true, ["f16"] = true,
    ["f17"] = true, ["f18"] = true, ["f19"] = true, ["f20"] = true,
    ["pad"] = true, ["pad*"] = true, ["pad+"] = true, ["pad/"] = true,
    ["pad-"] = true, ["pad="] = true, ["pad0"] = true, ["pad1"] = true,
    ["pad2"] = true, ["pad3"] = true, ["pad4"] = true, ["pad5"] = true,
    ["pad6"] = true, ["pad7"] = true, ["pad8"] = true, ["pad9"] = true,
    ["padclear"] = true, ["padenter"] = true, ["return"] = true,
    ["tab"] = true, ["space"] = true, ["delete"] = true, ["escape"] = true,
    ["help"] = true, ["home"] = true, ["pageup"] = true,
    ["forwarddelete"] = true, ["end"] = true, ["pagedown"] = true,
    ["left"] = true, ["right"] = true, ["down"] = true, ["up"] = true
  }

  -- if we have a simple character (no modifiers), we want a shorter
  -- popup duration.
  if (not flags.shift and not flags.cmd and
        not flags.alt and not flags.ctrl) then
    duration = 0.3
  end

  -- we want to get regular characters via getCharacters as it
  -- "cleans" the key for us (e.g. for a "⇧-5" keypress we want
  -- to show "⇧-%").
  if special_chars[character] == nil then
    character = tap_event:getCharacters(true)
    if flags.shift then
      character = string.lower(character)
    end
  end

  -- make some known special characters look good
  if character == "return" then
    character = "⏎"
  elseif character == "delete" then
    character = "⌫"
  elseif character == "escape" then
    character = "⎋"
  elseif character == "space" then
    character = "SPC"
  elseif character == "up" then
    character = "↑"
  elseif character == "down" then
    character = "↓"
  elseif character == "left" then
    character = "←"
  elseif character == "right" then
    character = "→"
  end

  -- get modifiers' string representation
  if flags.ctrl then
    modifiers = modifiers .. "C-"
  end
  if flags.cmd then
    modifiers = modifiers .. "⌘-"
  end
  if flags.shift then
    modifiers = modifiers .. "⇧-"
  end
  if flags.alt then
    modifiers = modifiers .. "⌥-"
  end

  -- actually show the popup
  hs.alert.show(modifiers .. character, duration)

end


local key_tap = hs.eventtap.new(
  {hs.eventtap.event.types.keyDown},
  showKeyPress
)

-- Enable/Disable Keypress Show Mode with "C-⌘-⇧-p"
k = hs.hotkey.modal.new({"cmd", "shift", "ctrl"}, 'P')
function k:entered()
  hs.alert.show("Enabling Keypress Show Mode", 1.5)
  key_tap:start()
end
function k:exited()
  hs.alert.show("Disabling Keypress Show Mode", 1.5)
end
k:bind({"cmd", "shift", "ctrl"}, 'P', function()
    key_tap:stop()
    k:exit()
end)

Currently it looks like this:

how the keystroke utility looks

As far as I know, there's currently no way of setting the popup position on the screen, so it always appears on the middle. Even so, I'm pretty happy I gave Hammerspoon a try and hope I can come up with other useful hacks.