I would like to get the key pressed while I am holding ctrl.
Now I have a modifier handler, and I am using modifierListener = hs.eventtap.new({ hs.eventtap.event.types.flagsChanged }, modifierHandler) to create a eventtap. And start it right after.
Inside the modifier handler, I would like to get the key (such as 'h', 'j' ...) pressed while I am holding ctrl.
So I create another keyDown handler.
local keyListener = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function (event)
local keyArr = { 'h', 'j', 'k', 'l' }
local keyPressed = event:getCharacters()
if hs.fnutils.contains(keyArr, keyPressed) then
hs.eventtap.event.newKeyEvent({}, 'left', true):post()
hs.eventtap.event.newKeyEvent({}, 'left', false):post()
else
return false
end
end)
and call this keyListener:start() inside modifier handler, when ctrl is pressed.
Actually the left above inside newKeyEvent is just an example.
So, here is the thing. I noticed that when modifier key ctrl is pressed and hold, the eventtap.event.newKeyEvent (or say, the keyDown event handler) are executed more than once.
Also, I tried to add a return false right after that, which won't stop the keyListener.
How should I fix this? Or I am on the WRONG track?
I am trying to make it like, while I am holding ctrl key, hjkl will act like arrow keys. Also, while I am holding ctrl key, shift + hjkl will select text and alt + hl will jump by word.
Thanks in advance.
I don't know if this is the problem, but you probably want a return true after the two newKeyEvent lines... otherwise it's passing the ctrl-h to the underlying application as well. The hs.eventtap.event.newKeyEvent({}, 'left', true):post() will also generate a keyDown, which your watcher will receive, but since it won't match anything in your array, the return false in the "else clause" should make sure that it gets through to the underlying application as expected.
If adding the return true doesn't help, I'll try and set up a similar sample tomorrow and see if I can come up with any other ideas... post here if you do get it fixed, though!
@asmagill Thanks very much for your reply.
I guess the return true is, like a short circuit?
I added a return true right after those, now it works this way:
ctrl and press h, looks like it is sending ctrl + hctrl and press H, it is sending leftI think there might be somewhere else I am doing wrong :(
Please lemme know if you need more information.
Thanks again for your help
I think this will do what you want:
~~~lua
local module = {}
local keyHandler = function(e)
local watchFor = { h = "left", j = "down", k = "up", l = "right" }
local actualKey = e:getCharacters(true)
local replacement = watchFor[actualKey:lower()]
if replacement then
local isDown = e:getType() == hs.eventtap.event.types.keyDown
local flags = {}
for k, v in pairs(e:getFlags()) do
if v and k ~= "ctrl" then -- ctrl will be down because that's our "wrapper", so ignore it
table.insert(flags, k)
end
end
print(replacement, hs.inspect(flags), isDown)
local replacementEvent = hs.eventtap.event.newKeyEvent(flags, replacement, isDown)
if isDown then
-- allow for auto-repeat
replacementEvent:setProperty(hs.eventtap.event.properties.keyboardEventAutorepeat, e:getProperty(hs.eventtap.event.properties.keyboardEventAutorepeat))
end
return true, { replacementEvent }
else
return false -- do nothing to the event, just pass it along
end
end
local modifierHandler = function(e)
local flags = e:getFlags()
local onlyControlPressed = false
for k, v in pairs(flags) do
onlyControlPressed = v and k == "ctrl"
if not onlyControlPressed then break end
end
-- you must tap and hold ctrl by itself to turn this on
if onlyControlPressed and not module.keyListener then
print("keyhandler on")
module.keyListener = hs.eventtap.new({ hs.eventtap.event.types.keyDown, hs.eventtap.event.types.keyUp }, keyHandler):start()
-- however, adding additional modifiers afterwards is ok... its only when we have no flags that we switch back off
elseif not next(flags) and module.keyListener then
print("keyhandler off")
module.keyListener:stop()
module.keyListener = nil
end
return false
end
module.modifierListener = hs.eventtap.new({ hs.eventtap.event.types.flagsChanged }, modifierHandler):start()
return module
~~~
I've set it up so that you can save the above code into a file in your ~/.hammerspoon directory and then activate it by typing viKeys = require("filename") in the console (or putting it into your ~/.hammerspoon/init.lua file).
What this basically does is watch for ctrl... if ctrl is held by itself, then the keyListener begins. Even if you add additional modifiers (shift, opt, etc.) until you release all of the modifiers at the same time, keyListener stays active. While keyListener is active, it looks for the h, j, k, and l keys and if they are hit, creates a replacement event for both the keyUp and keyDown events, replicating the current modifiers, except for ctrl, and replicating the autoRepeat flag if it's set for a keyDown event.
edit: fixed debugging output so it would work without my shortcuts for hs.inspect
A slightly cleaner version is at https://github.com/asmagill/hammerspoon-config/blob/master/_scratch/viKeys.lua
@asmagill That works PERFECT!
Thanks very much for your help.
@S1ngS1ng mind if I close this?
@asmagill I'll do it. Thanks for your help
@asmagill is it possible to change "ctrl" to "fn" ??
Because this uses hs.eventtap and not hs.hotkey, yes, the modifier key can be changed to "fn" in the two places where the above code refers to "ctrl".
hs.hotkey uses older Carbon event functions which predates the fn key... there were some "hacks" to get limited use of the fn key with hs.hotkey but these stopped working with 10.12.
Officially Apple says we should be moving everything to eventtaps and not using the older functions, but this introduces new problems that I am working on before offering an eventtap based hotkey module as an alternative... however for this specific situation, "fn" works fine (in fact, if you look at my link above, I do exactly that on my machine).
@asmagill, I changed modifier key from "ctrl" to "fn", it works pretty good. many thanks for your detail explanation.
@asmagill Thank you very much!! I also changed modifier key from 'ctrl' to 'fn', and then with Karabiner-elements switched caps_lock and fn, so now caps_lock + hjkl will map to arrows... I'm so happy
Just a question, lets suppose i want to add a modifier in the middle, for example:
fn + s = shift
fn + s + h = shift + left
Is that possible?
Most helpful comment
I think this will do what you want:
~~~lua
local module = {}
local keyHandler = function(e)
local watchFor = { h = "left", j = "down", k = "up", l = "right" }
local actualKey = e:getCharacters(true)
local replacement = watchFor[actualKey:lower()]
if replacement then
local isDown = e:getType() == hs.eventtap.event.types.keyDown
local flags = {}
for k, v in pairs(e:getFlags()) do
if v and k ~= "ctrl" then -- ctrl will be down because that's our "wrapper", so ignore it
table.insert(flags, k)
end
end
print(replacement, hs.inspect(flags), isDown)
local replacementEvent = hs.eventtap.event.newKeyEvent(flags, replacement, isDown)
if isDown then
-- allow for auto-repeat
replacementEvent:setProperty(hs.eventtap.event.properties.keyboardEventAutorepeat, e:getProperty(hs.eventtap.event.properties.keyboardEventAutorepeat))
end
return true, { replacementEvent }
else
return false -- do nothing to the event, just pass it along
end
end
local modifierHandler = function(e)
local flags = e:getFlags()
local onlyControlPressed = false
for k, v in pairs(flags) do
onlyControlPressed = v and k == "ctrl"
if not onlyControlPressed then break end
end
-- you must tap and hold ctrl by itself to turn this on
if onlyControlPressed and not module.keyListener then
print("keyhandler on")
module.keyListener = hs.eventtap.new({ hs.eventtap.event.types.keyDown, hs.eventtap.event.types.keyUp }, keyHandler):start()
-- however, adding additional modifiers afterwards is ok... its only when we have no flags that we switch back off
elseif not next(flags) and module.keyListener then
print("keyhandler off")
module.keyListener:stop()
module.keyListener = nil
end
return false
end
module.modifierListener = hs.eventtap.new({ hs.eventtap.event.types.flagsChanged }, modifierHandler):start()
return module
~~~
I've set it up so that you can save the above code into a file in your
~/.hammerspoondirectory and then activate it by typingviKeys = require("filename")in the console (or putting it into your~/.hammerspoon/init.luafile).What this basically does is watch for
ctrl... ifctrlis held by itself, then thekeyListenerbegins. Even if you add additional modifiers (shift, opt, etc.) until you release all of the modifiers at the same time,keyListenerstays active. WhilekeyListeneris active, it looks for theh,j,k, andlkeys and if they are hit, creates a replacement event for both thekeyUpandkeyDownevents, replicating the current modifiers, except for ctrl, and replicating the autoRepeat flag if it's set for akeyDownevent.edit: fixed debugging output so it would work without my shortcuts for
hs.inspect