You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
5.5 KiB
Lua
195 lines
5.5 KiB
Lua
4 years ago
|
-- Save to ~/.hammerspoon
|
||
|
-- In ~/.hammerspoon/init.lua:
|
||
|
-- local vimouse = require('vimouse')
|
||
|
-- vimouse('cmd', 'm')
|
||
|
--
|
||
|
-- This sets cmd-m as the key that toggles Vi Mouse.
|
||
|
--
|
||
|
-- h/j/k/l moves the mouse cursor by 20 pixels. Holding shift moves by 100
|
||
|
-- pixels, and holding alt moves by 5 pixels.
|
||
|
--
|
||
|
-- Pressing <space> sends left mouse down. Releasing <space> sends left mouse
|
||
|
-- up. Holding <space> and pressing h/j/k/l is mouse dragging. Tapping
|
||
|
-- <space> quickly sends double and triple clicks. Holding ctrl sends right
|
||
|
-- mouse events.
|
||
|
--
|
||
|
-- <c-y> and <c-e> sends the scroll wheel event. Holding the keys will speed
|
||
|
-- up the scrolling.
|
||
|
--
|
||
|
-- Press <esc> or the configured toggle key to end Vi Mouse mode.
|
||
|
|
||
|
return function(tmod, tkey)
|
||
|
-- local overlay = nil
|
||
|
local log = hs.logger.new('vimouse', 'debug')
|
||
|
local tap = nil
|
||
|
local orig_coords = nil
|
||
|
local dragging = false
|
||
|
local scrolling = 0
|
||
|
local mousedown_time = 0
|
||
|
local mousepress_time = 0
|
||
|
local mousepress = 0
|
||
|
local tapmods = {['cmd']=false, ['ctrl']=false, ['alt']=false, ['shift']=false}
|
||
|
|
||
|
if type(tmod) == 'string' then
|
||
|
tapmods[tmod] = true
|
||
|
else
|
||
|
for _, name in ipairs(tmod) do
|
||
|
tapmods[name] = true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local eventTypes = hs.eventtap.event.types
|
||
|
local eventPropTypes = hs.eventtap.event.properties
|
||
|
local keycodes = hs.keycodes.map
|
||
|
|
||
|
function postEvent(et, coords, modkeys, clicks)
|
||
|
local e = hs.eventtap.event.newMouseEvent(et, coords, modkeys)
|
||
|
if clicks > 3 then
|
||
|
clicks = 3
|
||
|
end
|
||
|
e:setProperty(eventPropTypes.mouseEventClickState, clicks)
|
||
|
e:post()
|
||
|
end
|
||
|
|
||
|
tap = hs.eventtap.new({eventTypes.keyDown, eventTypes.keyUp}, function(event)
|
||
|
local code = event:getKeyCode()
|
||
|
local flags = event:getFlags()
|
||
|
local repeating = event:getProperty(eventPropTypes.keyboardEventAutorepeat)
|
||
|
local coords = hs.mouse.getAbsolutePosition()
|
||
|
|
||
|
if (code == keycodes.tab or code == keycodes['`']) and flags.cmd then
|
||
|
-- Window cycling
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
if code == keycodes.space then
|
||
|
-- Mouse clicking
|
||
|
if repeating ~= 0 then
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
local btn = 'left'
|
||
|
if flags.ctrl then
|
||
|
btn = 'right'
|
||
|
end
|
||
|
|
||
|
local now = hs.timer.secondsSinceEpoch()
|
||
|
if now - mousepress_time > hs.eventtap.doubleClickInterval() then
|
||
|
mousepress = 1
|
||
|
end
|
||
|
|
||
|
if event:getType() == eventTypes.keyUp then
|
||
|
dragging = false
|
||
|
postEvent(eventTypes[btn..'MouseUp'], coords, flags, mousepress)
|
||
|
elseif event:getType() == eventTypes.keyDown then
|
||
|
dragging = true
|
||
|
if now - mousedown_time <= 0.3 then
|
||
|
mousepress = mousepress + 1
|
||
|
mousepress_time = now
|
||
|
end
|
||
|
|
||
|
mousedown_time = hs.timer.secondsSinceEpoch()
|
||
|
postEvent(eventTypes[btn..'MouseDown'], coords, flags, mousepress)
|
||
|
end
|
||
|
|
||
|
orig_coords = coords
|
||
|
elseif event:getType() == eventTypes.keyDown then
|
||
|
local mul = 0
|
||
|
local step = 20
|
||
|
local x_delta = 0
|
||
|
local y_delta = 0
|
||
|
local scroll_y_delta = 0
|
||
|
local is_tapkey = code == keycodes[tkey]
|
||
|
|
||
|
if is_tapkey == true then
|
||
|
for name, _ in pairs(tapmods) do
|
||
|
if flags[name] == nil then
|
||
|
flags[name] = false
|
||
|
end
|
||
|
|
||
|
if tapmods[name] ~= flags[name] then
|
||
|
is_tapkey = false
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if flags.alt then
|
||
|
step = 5
|
||
|
end
|
||
|
|
||
|
if flags.shift then
|
||
|
mul = 5
|
||
|
else
|
||
|
mul = 1
|
||
|
end
|
||
|
|
||
|
if is_tapkey or code == keycodes['escape'] then
|
||
|
if dragging then
|
||
|
postEvent(eventTypes.leftMouseUp, coords, flags, 0)
|
||
|
end
|
||
|
dragging = false
|
||
|
-- overlay:delete()
|
||
|
-- overlay = nil
|
||
|
hs.alert('Vi Mouse Off')
|
||
|
tap:stop()
|
||
|
hs.mouse.setAbsolutePosition(orig_coords)
|
||
|
return true
|
||
|
elseif (code == keycodes['y'] or code == keycodes['e']) and flags.ctrl then
|
||
|
if repeating ~= 0 then
|
||
|
scrolling = scrolling + 1
|
||
|
else
|
||
|
scrolling = 1
|
||
|
end
|
||
|
|
||
|
local scroll_mul = 1 + math.log(scrolling)
|
||
|
if code == keycodes['y'] then
|
||
|
scroll_y_delta = math.ceil(-1 * scroll_mul)
|
||
|
else
|
||
|
scroll_y_delta = math.floor(1 * scroll_mul)
|
||
|
end
|
||
|
log.d("Scrolling", scrolling, '-', scroll_y_delta)
|
||
|
elseif code == keycodes['h'] then
|
||
|
x_delta = step * mul * -1
|
||
|
elseif code == keycodes['l'] then
|
||
|
x_delta = step * mul
|
||
|
elseif code == keycodes['j'] then
|
||
|
y_delta = step * mul
|
||
|
elseif code == keycodes['k'] then
|
||
|
y_delta = step * mul * -1
|
||
|
end
|
||
|
|
||
|
if scroll_y_delta ~= 0 then
|
||
|
hs.eventtap.event.newScrollEvent({0, scroll_y_delta}, flags, 'line'):post()
|
||
|
end
|
||
|
|
||
|
if x_delta or y_delta then
|
||
|
coords.x = coords.x + x_delta
|
||
|
coords.y = coords.y + y_delta
|
||
|
|
||
|
if dragging then
|
||
|
postEvent(eventTypes.leftMouseDragged, coords, flags, 0)
|
||
|
else
|
||
|
hs.mouse.setAbsolutePosition(coords)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
return true
|
||
|
end)
|
||
|
|
||
|
hs.hotkey.bind(tmod, tkey, nil, function(event)
|
||
|
local screen = hs.mouse.getCurrentScreen()
|
||
|
local frame = screen:fullFrame()
|
||
|
|
||
|
-- overlay = hs.drawing.rectangle(frame)
|
||
|
-- overlay:setFillColor({['red']=0, ['blue']=0, ['green']=0, ['alpha']=0.2})
|
||
|
-- overlay:setFill(true)
|
||
|
-- overlay:setLevel(hs.drawing.windowLevels['assistiveTechHigh'])
|
||
|
-- overlay:show()
|
||
|
|
||
|
hs.alert('Vi Mouse On')
|
||
|
orig_coords = hs.mouse.getAbsolutePosition()
|
||
|
tap:start()
|
||
|
end)
|
||
|
end
|