绑定到多个按钮点击

Binding to multiple button clicks

要绑定到 1 键,我使用:

hs.hotkey.bind(hyper, '1'

如何绑定多次1键?类似于:

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

正在阅读the documentation,未提及此功能。

多次按下是指按两次 1 以 运行 一些代码,然后按三次 1 以 运行 一段单独的代码。

您不能使用绑定绑定所有键或多个键。相反,您可以使用此函数:http://www.hammerspoon.org/docs/hs.eventtap.html#keyStroke

所以最直接的编程语言不可知方法如下:

  1. 为任意击键调用您的函数。
  2. 在函数中保留一个静态实例变量,该变量将保留以前的击键。
  3. 作为函数的第一个任务,将新出现的字符附加到该变量。
  4. 检查最后 3 个字符是否是所需的“11”字符串。

极端条件下的额外费用:

  1. 如果变量长度超过某个点,将其减少到长度 1,这样它就不会在内存中保留不必要的位置。

您将不得不自己实施。以下是如何完成此操作的基本摘要:

  1. 从零开始计时,将第一次按下的标志设置为false,表示第一次按下还没有发生
  2. 使用 hs.eventtap 观察和观察按键,特别是 hs.eventtap.event.types.keyPress
  3. 当事件(keyPress)发生时,检查按下的键是否是正确的键
  4. 如果是右键,检查是否是第二次按下,是否及时,如果不及时不是第二次按下则设置计时器到当前时间,第一个标志为 true
  5. 如果是第二次按下并且及时,则执行我们的处理程序并重置计时器和第一个标志
  6. 如果不是正确的键,则重置计时器和第一个标志

翻译成代码,这就是可能的样子(我不是 Lua 专家)。请注意,这些标志可以在此处作为布尔值实现,或者作为内部 table 保持到目前为止您可以检查的按键:

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

为了更好地理解,我逐行注释了代码。如果您想检测 3 次或 4 次或其他任意 N 次按下,只需为 N - 1 次按下设置标志并添加一些检查,但是连续按下 2 次以上的组合键是不常见的。它确实看起来有点冗长,但 AFAIK 这就是你如何做到的。为避免重复代码和样板,请尝试将其放入类似 class 的结构或模块中,以便您可以重用代码。

至于为 2 次连续按下或 3 次连续按下执行不同的处理程序,这会有点麻烦,因为您必须等待整个时间范围才能知道用户是否会再次按下以知道要使用哪个处理程序执行。这会导致轻微的延迟和糟糕的用户体验,我建议不要这样做,尽管您可以通过重构代码并进行更多检查来实现它,例如是否是时间范围和第一个标志被触发,然后执行处理程序按一次。

n 次按键的解决方案

派对已经很晚了,但几乎没有这方面的信息,所以我认为我真的应该把它放在这里,因为这是唯一的搜索结果之一。

我的解决方案比其他一些解决方案更优雅(在我看来);肯定有一些可以改进的地方,但我对 Lua 或 Hammerspoon 还不够熟悉,无法修复它们。

它应该可以为任意数量的连续按键分配一个快捷方式。

阅读代码注释以了解其工作原理。我试图尽可能详细,努力使它对那些不太了解编码的人和那些不熟悉 Lua 或 Hammerspoon 的人(像我)更友好.

require("hs.timer") -- Load timer module, used for timing

keyDownCount = 0 -- Keypress counter, used later in the program to store the number of times the key has been pressed
keyMultipressGapTime = 0.3 -- Max time between consecutive keypresses, used to determine when the user has stopped pressing the key
keyMaxPressCount = 3 -- Max number of key presses
testKeyCode = 18 -- Key code to bind shortcut to (in this case the 1 key)

-- READ CheckKeyDownCount FUNCTION CODE (BELOW) FIRST
-- Function to press a key with code
-- This isn't completely intuitive so I'm including it
-- Im sure there's a better way of doing this but this is what I figured out
function PressKey(keyCode) 
    keyDown = hs.eventtap.event.newKeyEvent(keyCode, true) -- Create new keydown event using the keycode passed in the keycode argument
    keyDown:setProperty(hs.eventtap.event.properties.eventSourceUserData, 1) -- Sets user data byte of keydown event to 1, used later to prevent keydown event handler from self triggering
    keyDown:post() -- Fire keydown event
    hs.eventtap.event.newKeyEvent(keyCode, false):post() -- Create and fire keyup event using the keycode passed in the keycode argument
end

-- READ EVENT HANDLER CODE (BELOW) FIRST
-- Function to check the number of times the key was pressed and act accordingly
-- Pretty self explanatory
function CheckKeyDownCount()
    CheckKeyDownCountTimer:stop() -- Stops keydown timer so it doesn't repeat
    -- There may be a better way of doing this but I can't find a way to creating and restarting a non repeating timer without creating a whole new timer object every time

    if keyDownCount == 1 then -- Perform action based on number of keypresses
        hs.alert("Pressed once")
        PressKey(testKeyCode)
    elseif keyDownCount == 2 then
        hs.alert("Pressed twice")
    elseif keyDownCount == 3 then
        hs.alert("Pressed thrice")
    end
    
    keyDownCount = 0 -- Reset keypress counter
end

CheckKeyDownCountTimer = hs.timer.new(keyMultipressGapTime, CheckKeyDownCount) -- Creates timer for determining when the user has stopped pressing the key
-- Time interval is set to the max time between consecutive keypresses
-- Runs the CheckKeyDownCount function at end of time interval
-- IMPORTANT: Time interval automatically resets when timer is stopped and started

-- Creates keydown event handler
-- FOR BEGINNERS: An event handler is a routine that runs when triggered by an event (kind of like an interrupt if you know what that is), normally they call a function, like below
-- FOR BEGINNERS CONTINUED: The timer above is also an event handler of sorts, with the event being the end of the time interval, event handlers are very useful because they allow asynchronous code execution
-- FOR BEGINNERS CONTINUED: In this case asynchronous code execution means that the program will continue executing until an event needs to be handled, the program will then stop where it is, handel the event, and then jump back to where it left off
multipressBtnShortcuts = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(event) 
    -- FOR BEGINNERS: "function(event)" creates anonymous function containing the below code and passes it the keydown event as an object called "event" (Just makes the code neater, you could use a separate function if you want)
    -- FOR BEGINNERS CONTINUED: An anonymous function is just a function without an identifier (name), instead they're objects and often behave kinda like variables (look this up, it's kinda hard to explain and not relevant here)
    -- RANDOM NOTE: Also turns out all functions in lua are anonymous which is pretty interesting, the interpreter just converts the structure "function foo(x) return 2*x end" into "foo = function (x) return 2*x end" 
    
    if event:getKeyCode() == testKeyCode and event:getProperty(hs.eventtap.event.properties.eventSourceUserData) == 0 then -- Check if keycode is the shortcut keycode and check if the user data byte is set to 0 (default)
        -- The user data byte check is to prevent the event handler from triggering itself (SEE PressKey FUNCTION ABOVE)
        -- I'm sure there's a better way to do this but I cant find it

        event:setType(hs.eventtap.event.types.nullEvent)  -- Null the keypress event
        -- Overrides the keypress, remove if you don't want the original keypresses to be overridden
        -- I'm sure there's a better way to do this but I cant find it
        
        keyDownCount = keyDownCount + 1 -- Add one to keypress counter

        if CheckKeyDownCountTimer:running() then -- If the max key press gap timer is running stop it (NOTE: Stopping and starting it also resets it)
            CheckKeyDownCountTimer:stop() 
        end

        if keyDownCount < keyMaxPressCount then -- If keypress counter is less then the max number of keypresses restart the max key press gap timer (NOTE: Stopping and starting it also resets it)
            CheckKeyDownCountTimer:start() 
        else -- Alternativly, if the keypress counter is greater than or equal to the max number of keypresses run the CheckKeyDownCount function 
            CheckKeyDownCount()
        end            
    end
    return false -- Ends the anonymous function by returning false, not sure if this is really necessary but it's what other people seem to do 
end)

multipressBtnShortcuts:start() -- Starts the keydown event handler