hammerspoon

Binding to multiple button clicks


To bind to the 1 key I use:

hs.hotkey.bind(hyper, '1'

How to bind to multiple presses of 1 key? Something like:

hs.hotkey.bind(hyper, '1+1'

Reading the documentation, this functionality is not mentioned.

By multiple presses I mean press 1 twice to run some code and press 1 three times to run a separate piece of code.


Solution

  • You're going to have to implement this yourself. Here's a basic summary of how to accomplish this:

    1. Start a timer from zero, and set a flag for the first press initially to false, which indicates the first press has not happened yet
    2. Observe and watch keypresses with hs.eventtap, specifically hs.eventtap.event.types.keyPress
    3. When the event (keyPress) happens, check if the key pressed was the correct key
    4. If it was the right key, check if it's the second press and if it was in time, if it wasn't in time or was not the second press then set the timer to the current time and first flag to true
    5. If it was the second press and was in time, then execute our handler and reset timer and first flag
    6. If it wasn't the right key then reset the timer and first flag

    Translated into code, this is what is could look like (I'm not a Lua expert). Note that the flags could be implemented as booleans here, or as an internal table holding keypresses so far which you could check:

    local timer = require("hs.timer")
    local eventtap = require("hs.eventtap") 
    local keycodes = require("hs.keycodes")
    local events = eventtap.event.types --all the event types
    
    timeFrame = 1 --this is the timeframe in which the second press should occur, in seconds
    key = 50 --the specific keycode we're detecting, in this case, 50
    
    --print(keycodes.map["`"]) you can look up the certain keycode by accessing the map
    
    function twoHandler()
        hs.alert("Pressed ` twice!") --the handler for the double press
    end
    
    function correctKeyChecker(event) --keypress validator, checks if the keycode matches the key we're trying to detect
        local keyCode = event:getKeyCode()
        return keyCode == key --return if keyCode is key
    end
    
    function inTime(time) --checks if the second press was in time
        return timer.secondsSinceEpoch() - time < timeFrame --if the time passed from the first press to the second was less than the timeframe, then it was in time
    end
    
    local pressTime, firstDown = 0, false --pressTime was the time the first press occurred which is set to 0, and firstDown indicates if the first press has occurred or not
    
    eventtap.new({ events.keyDown }, function(event) --watch the keyDown event, trigger the function every time there is a keydown
        if correctKeyChecker(event) then --if correct key
            if firstDown and inTime(pressTime) then --if first press already happened and the second was in time
                twoHandler() --execute the handler
            elseif not firstDown or inTime(pressTime) then --if the first press has not happened or the second wasn't in time
                pressTime, firstDown = timer.secondsSinceEpoch(), true --set first press time to now and first press to true
                return false --stop prematurely
            end
        end
        pressTime, firstDown = 0, false --if it reaches here that means the double tap was successful or the key was incorrect, thus reset timer and flag
        return false --keeps the event propogating
    end):start() --start our watcher
    

    I've commented the code line-by-line for a better understanding. If you want to detect 3 or 4 or some other arbitrary N number of presses, just set flags for N - 1 presses and add a few checks, but it's unusual to have key combinations that take more than 2 successive presses. It does seem a little verbose, but AFAIK this is how you do it. To avoid duplicate code and boilerplate, try putting this in a class-like structure or a module so that you can reuse code.

    As for executing a different handler for 2 successive presses, or 3 successive presses, that would be a little more hacky since you would have to wait the whole timeframe before knowing if the user will press again to know which handler to execute. That would cause a slight delay and a bad user experience, I would suggest against that, though you could probably implement that by refactoring the code and doing some more checks such as if it's the timeframe and the first flag was triggered, then execute the handler for one press.