5

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.

4

3 回答 3

1

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

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

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

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这就是你的做法。为避免重复代码和样板,请尝试将其放入类结构或模块中,以便您可以重用代码。

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

于 2017-06-04T04:46:01.283 回答
1

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

因此,最直接的与编程语言无关的方法如下:

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

极端条件下的额外费用:

  1. 如果可变长度通过某个点,则将其减少到长度 1,这样它就不会在内存中保留不必要的位置。
于 2017-06-04T03:16:37.630 回答
0

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 
于 2021-09-15T13:29:39.760 回答