303 lines
14 KiB
Lua
303 lines
14 KiB
Lua
--- === hs.eventtap ===
|
|
---
|
|
--- Tap into input events (mouse, keyboard, trackpad) for observation and possibly overriding them
|
|
--- It also provides convenience wrappers for sending mouse and keyboard events. If you need to construct finely controlled mouse/keyboard events, see hs.eventtap.event
|
|
---
|
|
--- This module is based primarily on code from the previous incarnation of Mjolnir by [Steven Degutis](https://github.com/sdegutis/).
|
|
|
|
--- === hs.eventtap.event ===
|
|
---
|
|
--- Create, modify and inspect events for `hs.eventtap`
|
|
---
|
|
--- This module is based primarily on code from the previous incarnation of Mjolnir by [Steven Degutis](https://github.com/sdegutis/).
|
|
---
|
|
--- `hs.eventtap.event.newGesture` uses an external library by Calf Trail Software, LLC.
|
|
---
|
|
--- Touch
|
|
--- Copyright (C) 2010 Calf Trail Software, LLC
|
|
---
|
|
--- This program is free software; you can redistribute it and/or
|
|
--- modify it under the terms of the GNU General Public License
|
|
--- as published by the Free Software Foundation; either version 2
|
|
--- of the License, or (at your option) any later version.
|
|
---
|
|
--- This program is distributed in the hope that it will be useful,
|
|
--- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
--- GNU General Public License for more details.
|
|
---
|
|
--- You should have received a copy of the GNU General Public License
|
|
--- along with this program; if not, write to the Free Software
|
|
--- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
local module = require("hs.libeventtap")
|
|
module.event = require("hs.libeventtapevent")
|
|
local fnutils = require("hs.fnutils")
|
|
local keycodes = require("hs.keycodes")
|
|
local timer = require("hs.timer")
|
|
|
|
-- private variables and methods -----------------------------------------
|
|
|
|
local function getKeycode(s)
|
|
local n
|
|
if type(s)=='number' then n=s
|
|
elseif type(s)~='string' then error('key must be a string or a number',3)
|
|
elseif (s:sub(1, 1) == '#') then n=tonumber(s:sub(2))
|
|
else n=keycodes.map[string.lower(s)] end
|
|
if not n then error('Invalid key: '..s..' - this may mean that the key requested does not exist in your keymap (particularly if you switch keyboard layouts frequently)',3) end
|
|
return n
|
|
end
|
|
|
|
local function getMods(mods)
|
|
local r={}
|
|
if not mods then return r end
|
|
if type(mods)=='table' then mods=table.concat(mods,'-') end
|
|
if type(mods)~='string' then error('mods must be a string or a table of strings',3) end
|
|
-- super simple substring search for mod names in a string
|
|
mods=string.lower(mods)
|
|
local function find(ps)
|
|
for _,s in ipairs(ps) do
|
|
if string.find(mods,s,1,true) then r[#r+1]=ps[#ps] return end
|
|
end
|
|
end
|
|
find{'cmd','command','⌘'} find{'ctrl','control','⌃'}
|
|
find{'alt','option','⌥'} find{'shift','⇧'}
|
|
find{'fn'}
|
|
return r
|
|
end
|
|
|
|
module.event.types = ls.makeConstantsTable(module.event.types)
|
|
module.event.properties = ls.makeConstantsTable(module.event.properties)
|
|
module.event.rawFlagMasks = ls.makeConstantsTable(module.event.rawFlagMasks)
|
|
|
|
-- Public interface ------------------------------------------------------
|
|
|
|
local originalNewKeyEvent = module.event.newKeyEvent
|
|
module.event.newKeyEvent = function(mods, key, isDown)
|
|
if type(mods) == "nil" then mods = {} end
|
|
if (type(mods) == "number" or type(mods) == "string") and type(key) == "boolean" then
|
|
mods, key, isDown = nil, mods, key
|
|
end
|
|
local keycode = getKeycode(key)
|
|
local modifiers = mods and getMods(mods) or nil
|
|
-- print(finspect(table.pack(modifiers, keycode, isDown)))
|
|
return originalNewKeyEvent(modifiers, keycode, isDown)
|
|
end
|
|
|
|
--- hs.eventtap.event.newKeyEventSequence(modifiers, character) -> table
|
|
--- Function
|
|
--- Generates a table containing the keydown and keyup events to generate the keystroke with the specified modifiers.
|
|
---
|
|
--- Parameters:
|
|
--- * modifiers - A table containing the keyboard modifiers to apply ("cmd", "alt", "shift", "ctrl", "rightCmd", "rightAlt", "rightShift", "rightCtrl", or "fn")
|
|
--- * character - A string containing a character to be emitted
|
|
---
|
|
--- Returns:
|
|
--- * a table with events which contains the individual events that Apple recommends for building up a keystroke combination (see [hs.eventtap.event.newKeyEvent](#newKeyEvents)) in the order that they should be posted (i.e. the first half will contain keyDown events and the second half will contain keyUp events)
|
|
---
|
|
--- Notes:
|
|
--- * The `modifiers` table must contain the full name of the modifiers you wish used for the keystroke as defined in `hs.keycodes.map` -- the Unicode equivalents are not supported by this function.
|
|
--- * The returned table will always contain an even number of events -- the first half will be the keyDown events and the second half will be the keyUp events.
|
|
--- * The events have not been posted; the table can be used without change as the return value for a callback to a watcher defined with [hs.eventtap.new](#new).
|
|
function module.event.newKeyEventSequence(modifiers, character)
|
|
local codes = fnutils.map({table.unpack(modifiers), character}, getKeycode)
|
|
local n = #codes
|
|
local events = {}
|
|
for i, code in ipairs(codes) do
|
|
events[i] = module.event.newKeyEvent(code, true)
|
|
events[2*n+1-i] = module.event.newKeyEvent(code, false)
|
|
end
|
|
return events
|
|
end
|
|
|
|
--- hs.eventtap.event.newMouseEvent(eventtype, point[, modifiers) -> event
|
|
--- Constructor
|
|
--- Creates a new mouse event
|
|
---
|
|
--- Parameters:
|
|
--- * eventtype - One of the mouse related values from `hs.eventtap.event.types`
|
|
--- * point - An hs.geometry point table (i.e. of the form `{x=123, y=456}`) indicating the location where the mouse event should occur
|
|
--- * modifiers - An optional table (e.g. {"cmd", "alt"}) containing zero or more of the following keys:
|
|
--- * cmd
|
|
--- * alt
|
|
--- * shift
|
|
--- * ctrl
|
|
--- * fn
|
|
---
|
|
--- Returns:
|
|
--- * An `hs.eventtap` object
|
|
function module.event.newMouseEvent(eventtype, point, modifiers)
|
|
local types = module.event.types
|
|
local button
|
|
if eventtype == types["leftMouseDown"] or eventtype == types["leftMouseUp"] or eventtype == types["leftMouseDragged"] then
|
|
button = "left"
|
|
elseif eventtype == types["rightMouseDown"] or eventtype == types["rightMouseUp"] or eventtype == types["rightMouseDragged"] then
|
|
button = "right"
|
|
elseif eventtype == types["otherMouseDown"] or eventtype == types["otherMouseUp"] or eventtype == types["otherMouseDragged"] then
|
|
button = "other"
|
|
elseif eventtype == types["mouseMoved"] then
|
|
button = "none"
|
|
else
|
|
print("Error: unrecognised mouse button eventtype: " .. tostring(eventtype))
|
|
return nil
|
|
end
|
|
return module.event._newMouseEvent(eventtype, point, button, modifiers)
|
|
end
|
|
|
|
--- hs.eventtap.leftClick(point[, delay])
|
|
--- Function
|
|
--- Generates a left mouse click event at the specified point
|
|
---
|
|
--- Parameters:
|
|
--- * point - A table with keys `{x, y}` indicating the location where the mouse event should occur
|
|
--- * delay - An optional delay (in microseconds) between mouse down and up event. Defaults to 200000 (i.e. 200ms)
|
|
---
|
|
--- Returns:
|
|
--- * None
|
|
---
|
|
--- Notes:
|
|
--- * This is a wrapper around `hs.eventtap.event.newMouseEvent` that sends `leftmousedown` and `leftmouseup` events)
|
|
function module.leftClick(point, delay)
|
|
if delay==nil then
|
|
delay=200000
|
|
end
|
|
|
|
module.event.newMouseEvent(module.event.types["leftMouseDown"], point):post()
|
|
timer.usleep(delay)
|
|
module.event.newMouseEvent(module.event.types["leftMouseUp"], point):post()
|
|
end
|
|
|
|
--- hs.eventtap.rightClick(point[, delay])
|
|
--- Function
|
|
--- Generates a right mouse click event at the specified point
|
|
---
|
|
--- Parameters:
|
|
--- * point - A table with keys `{x, y}` indicating the location where the mouse event should occur
|
|
--- * delay - An optional delay (in microseconds) between mouse down and up event. Defaults to 200000 (i.e. 200ms)
|
|
---
|
|
--- Returns:
|
|
--- * None
|
|
---
|
|
--- Notes:
|
|
--- * This is a wrapper around `hs.eventtap.event.newMouseEvent` that sends `rightmousedown` and `rightmouseup` events)
|
|
function module.rightClick(point, delay)
|
|
if delay==nil then
|
|
delay=200000
|
|
end
|
|
|
|
module.event.newMouseEvent(module.event.types["rightMouseDown"], point):post()
|
|
timer.usleep(delay)
|
|
module.event.newMouseEvent(module.event.types["rightMouseUp"], point):post()
|
|
end
|
|
|
|
--- hs.eventtap.otherClick(point[, delay][, button])
|
|
--- Function
|
|
--- Generates an "other" mouse click event at the specified point
|
|
---
|
|
--- Parameters:
|
|
--- * point - A table with keys `{x, y}` indicating the location where the mouse event should occur
|
|
--- * delay - An optional delay (in microseconds) between mouse down and up event. Defaults to 200000 (i.e. 200ms)
|
|
--- * button - An optional integer, default 2, between 2 and 31 specifying the button number to be pressed. If this parameter is specified then `delay` must also be specified, though you may specify it as `nil` to use the default.
|
|
---
|
|
--- Returns:
|
|
--- * None
|
|
---
|
|
--- Notes:
|
|
--- * This is a wrapper around `hs.eventtap.event.newMouseEvent` that sends `otherMouseDown` and `otherMouseUp` events)
|
|
--- * macOS recognizes up to 32 distinct mouse buttons, though few mouse devices have more than 3. The left mouse button corresponds to button number 0 and the right mouse button corresponds to 1; distinct events are used for these mouse buttons, so you should use `hs.eventtap.leftClick` and `hs.eventtap.rightClick` respectively. All other mouse buttons are coalesced into the `otherMouse` events and are distinguished by specifying the specific button with the `mouseEventButtonNumber` property, which this function does for you.
|
|
--- * The specific purpose of mouse buttons greater than 2 varies by hardware and application (typically they are not present on a mouse and have no effect in an application)
|
|
function module.otherClick(point, delay, button)
|
|
if delay==nil then
|
|
delay=200000
|
|
end
|
|
if button==nil then
|
|
button = 2
|
|
end
|
|
if button < 2 or button > 31 then
|
|
error("button number must be between 2 and 31 inclusive", 2)
|
|
end
|
|
module.event.newMouseEvent(module.event.types["otherMouseDown"], point):setProperty(module.event.properties["mouseEventButtonNumber"], button):post()
|
|
hs.timer.usleep(delay)
|
|
module.event.newMouseEvent(module.event.types["otherMouseUp"], point):setProperty(module.event.properties["mouseEventButtonNumber"], button):post()
|
|
end
|
|
|
|
|
|
--- hs.eventtap.middleClick(point[, delay])
|
|
--- Function
|
|
--- Generates a middle mouse click event at the specified point
|
|
---
|
|
--- Parameters:
|
|
--- * point - A table with keys `{x, y}` indicating the location where the mouse event should occur
|
|
--- * delay - An optional delay (in microseconds) between mouse down and up event. Defaults to 200000 (i.e. 200ms)
|
|
---
|
|
--- Returns:
|
|
--- * None
|
|
---
|
|
--- Notes:
|
|
--- * This function is just a wrapper which calls `hs.eventtap.otherClick(point, delay, 2)` and is included solely for backwards compatibility.
|
|
module.middleClick = function(point, delay)
|
|
module.otherClick(point, delay, 2)
|
|
end
|
|
|
|
--- hs.eventtap.keyStroke(modifiers, character[, delay, application])
|
|
--- Function
|
|
--- Generates and emits a single keystroke event pair for the supplied keyboard modifiers and character
|
|
---
|
|
--- Parameters:
|
|
--- * modifiers - A table containing the keyboard modifiers to apply ("fn", "ctrl", "alt", "cmd", "shift", or their Unicode equivalents)
|
|
--- * character - A string containing a character to be emitted
|
|
--- * delay - An optional delay (in microseconds) between key down and up event. Defaults to 200000 (i.e. 200ms)
|
|
--- * application - An optional hs.application object to send the keystroke to
|
|
---
|
|
--- Returns:
|
|
--- * None
|
|
---
|
|
--- Notes:
|
|
--- * This function is ideal for sending single keystrokes with a modifier applied (e.g. sending ⌘-v to paste, with `hs.eventtap.keyStroke({"cmd"}, "v")`). If you want to emit multiple keystrokes for typing strings of text, see `hs.eventtap.keyStrokes()`
|
|
--- * Note that invoking this function with a table (empty or otherwise) for the `modifiers` argument will force the release of any modifier keys which have been explicitly created by [hs.eventtap.event.newKeyEvent](#newKeyEvent) and posted that are still in the "down" state. An explicit `nil` for this argument will not (i.e. the keystroke will inherit any currently "down" modifiers)
|
|
function module.keyStroke(modifiers, character, delay, application)
|
|
local targetApp = nil
|
|
local keyDelay = 200000
|
|
|
|
if type(delay) == "userdata" then
|
|
targetApp = delay
|
|
else
|
|
targetApp = application
|
|
end
|
|
|
|
if type(delay) == "number" then
|
|
keyDelay = delay
|
|
end
|
|
|
|
--print("targetApp: "..tostring(targetApp))
|
|
--print("keyDelay: "..tostring(keyDelay))
|
|
|
|
module.event.newKeyEvent(modifiers, character, true):post(targetApp)
|
|
timer.usleep(keyDelay)
|
|
module.event.newKeyEvent(modifiers, character, false):post(targetApp)
|
|
end
|
|
|
|
|
|
--- hs.eventtap.scrollWheel(offsets, modifiers, unit) -> event
|
|
--- Function
|
|
--- Generates and emits a scroll wheel event
|
|
---
|
|
--- Parameters:
|
|
--- * offsets - A table containing the {horizontal, vertical} amount to scroll. Positive values scroll up or left, negative values scroll down or right.
|
|
--- * mods - A table containing zero or more of the following:
|
|
--- * cmd
|
|
--- * alt
|
|
--- * shift
|
|
--- * ctrl
|
|
--- * fn
|
|
--- * unit - An optional string containing the name of the unit for scrolling. Either "line" (the default) or "pixel"
|
|
---
|
|
--- Returns:
|
|
--- * None
|
|
function module.scrollWheel(offsets, modifiers, unit)
|
|
module.event.newScrollEvent(offsets, modifiers, unit):post()
|
|
end
|
|
-- Return Module Object --------------------------------------------------
|
|
|
|
return module
|