Hammerspoon / hammerspoon

Staggeringly powerful macOS desktop automation with Lua
http://www.hammerspoon.org
MIT License
12.08k stars 582 forks source link

Add ability for hs.eventtap to use the global queue #2104

Closed latenitefilms closed 4 years ago

latenitefilms commented 5 years ago

If I trigger the below code in the console:

hs.eventtap.event.newKeyEvent(hs.keycodes.map.shift, true):post()

...I expect it to hold down the SHIFT key until I trigger:

hs.eventtap.event.newKeyEvent(hs.keycodes.map.shift, false):post()

...however it doesn't seem to do anything?

I've also tried hs.eventtap.event.newKeyEvent({}, hs.keycodes.map.shift, true):post().

Any ideas?

latenitefilms commented 5 years ago

Any ideas what I'm doing wrong @asmagill ?

asmagill commented 5 years ago

The event "queue" that hs.eventtap.event uses is not the global one, but rather one unique to Hammerspoon (I remember trying the global one at one point and it broke some things people were used to so didn't really pursue it further, though when I was working on an eventtap replacement (which might be worth reconsidering for 2.0) I did make this a choice you could set when creating the event).

Because of this, the "shift" key isn't "pressed" globally (i.e. events originating outside of Hammerspoon do not see this flag as having been set)

As an example, try the following in the console:

t = true ; d = hs.hotkey.bind({"cmd","alt"}, "S", nil, function() hs.eventtap.event.newKeyEvent(hs.keycodes.map.shift, t):post() ; t = not t ; end)

Now, press and release Cmd-Alt-S. Type something into the console (without the shift key pressed) and you'll see it in lower case, BUT try this in the console:

hs.eventtap.event.newKeyEvent(hs.keycodes.map.a, true):post() ; hs.eventtap.event.newKeyEvent(hs.keycodes.map.a, false):post()

And you'll see an upper case A appear in the console's input field.

To verify it's because of our hotkey, press and relase Cmd-Alt-S again, and then repeat the sample posts... this time, the input field receives a lower case a.

latenitefilms commented 5 years ago

Ah, I see - thanks for the detailed explanation! Is there any workaround for this?

Is your eventtap replacement on GitHub?

latenitefilms commented 4 years ago

@cmsj & @asmagill - This issue has cropped up again, so if you have any ideas let me know!

@asmagill - I haven't been able to find your hs.eventtap replacement anywhere - is it on GitHub?

Also, I just randomly thought, I wonder if I can make use of hs.eventtap.event:post([app]), and just specify to the frontmost application as a workaround - but alas, it doesn't work as expected.

latenitefilms commented 4 years ago

Whilst you're online @asmagill - I'm still pretty keen to somehow tap into the global event queue, so that I can use buttons on external devices (like the Streamdeck) as modifier keys - so if you have any tips or tricks, let me know. Thanks!

Hope you're keeping healthy, happy and sane in these crazy times!

asmagill commented 4 years ago

For detecting events (extensions/eventtap/internal.m), we're already as "low" as we can get without using root permissions in a helper app of some sort (see https://developer.apple.com/documentation/coregraphics/1454426-cgeventtapcreate)

For posting events (extensions/eventtap/event.m), what I refer to above concerns CGEventSourceCreate (see https://developer.apple.com/documentation/coregraphics/1408776-cgeventsourcecreate) and I was experimenting with using kCGEventSourceStateCombinedSessionState as the argument.

As to the code I was working on, I think I was referring to https://github.com/asmagill/hammerspoon_asm/tree/2b2818c86799c32695de35298b68cf58e3dd5c0c/event, which I moved to a local archive folder for things I've more or less abandoned, but this was the last version in my repos history. I think I'd already given up on kCGEventSourceStateCombinedSessionState by this time so feel free to try your own changes or browsing further back in the repo's history...

latenitefilms commented 4 years ago

Oh, interesting - thanks heaps @asmagill !

Looking at this code, it looks like you did previous experiment with using kCGEventSourceStateCombinedSessionState in the main Hammerspoon release?

I'll try uncommenting it out at some point, and see what explodes.

latenitefilms commented 4 years ago

Sadly, changing CGEventSourceCreate(kCGEventSourceStatePrivate); to CGEventSourceCreate(kCGEventSourceStateCombinedSessionState) didn't seem to have any impact.

I also tried changing CGEventPost(kCGSessionEventTap, event); to CGEventPost(kCGHIDEventTap, event);, but again, it had no impact.

I know it's possible, because the Loupedeck control surface (for example) allows you to use buttons on their panel as modifier keys, without any admin privileges or special helper apps.

I did just come across this code here, which allows you to toggle CAPS LOCK - I wonder if a similar technique can be used for other modifiers?

var ioConnect: io_connect_t = .init(0)
let ioService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass))
IOServiceOpen(ioService, mach_task_self_, UInt32(kIOHIDParamConnectType), &ioConnect)

var modifierLockState = false
IOHIDGetModifierLockState(ioConnect, Int32(kIOHIDCapsLockState), &modifierLockState) 

modifierLockState.toggle()
IOHIDSetModifierLockState(ioConnect, Int32(kIOHIDCapsLockState), modifierLockState)

IOServiceClose(ioConnect)

The other thing I was thinking was maybe I could use hs.eventtap to intercept all events and "inject" a modifier key if needed? For example, this seems to work:

addShiftToEverything = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(e)
    local flags = e:getFlags()  
    print(string.format("flags: %s", hs.inspect(flags)))
    flags.shift = true
    e:setFlags(flags)
    return false, e 
end):start()

...but it's probably a bit risky, as it has the potential to slow down keyboard input.

Any other thoughts or ideas @asmagill or @cmsj ?

latenitefilms commented 4 years ago

@asmagill - Until I come up with a better solution, I'm currently "injecting" the modifier keys using hs.eventtap, which actually seems to work pretty well.

Here's the code I'm using:

https://github.com/CommandPost/CommandPost/blob/3e269dfe59beb8d9c1aeff983dd01a79de8d8cd6/src/plugins/core/shortcuts/actions.lua#L308

However, injecting into gestures doesn't seem to work. Any hints or tricks?

asmagill commented 4 years ago

Sorry, no. Gestures seem to be a little bit of a black art, at least from the CoreGraphics side of things, which is what most of our eventtap module uses. I've yet to come across a good example for dissecting them or synthetically creating them. Of course that was a couple of years ago that I last looked...

Using eventtap and looking at the rawflags value, you can distinguish between left and right for cmd, option, and shift but that's the best I have at the moment for extending the number of modifier keys and combinations we can detect. (I forget the exact values you need to look for... check out the virtual toolbar example either in the module or in my personal config -- it specifically looks for a right option key for toggling the toolbar visibility and a left one for allowing you to move the toolbar rather than click on it)

latenitefilms commented 4 years ago

FYI: I also tried this, but no dice:

Screen Shot 2020-08-13 at 5 10 43 pm
latenitefilms commented 4 years ago

Funnily enough, I stumbled across this post, which suggested using AppleScript, and it actually works great!

hs.applescript([[tell application "System Events" to shift key down]])
latenitefilms commented 4 years ago

Although it does work... there is a slight delay in it being triggered. It's definitely not as responsive as holding down the SHIFT key for example. I think this would still be better if we could solve it in Obj-C land.

@cmsj - Do you have any ideas?

latenitefilms commented 4 years ago

Note to self - this is interesting and useful:

https://stackoverflow.com/questions/27141645/osx-runloop-options-when-creating-event-taps

latenitefilms commented 4 years ago

FYI - The BetterTouchTool creator came back to me with these helpful hints:

Screen Shot 2020-08-14 at 1 43 32 am Screen Shot 2020-08-14 at 1 43 41 am

CGEnableEventStateCombining looks really interesting:

Enables or disables the merging of actual key and mouse state with the application-specified state in a synthetic event.

latenitefilms commented 4 years ago

Using AppleScript seems to get around my specific issue, so closing this for now.