eventtap: setProperty choose CGEventField setter based on value type#3859
eventtap: setProperty choose CGEventField setter based on value type#3859mogenson wants to merge 2 commits intoHammerspoon:masterfrom
Conversation
eventtap event's setProperty() method uses CGEventSetDoubleValueField for a defined list of CGEventField properties, and CGEventSetIntegerValueField for all others. Keep the list of double type CGEventField properties, but make a change to check the type of the value parameter for the all others case. If the value is a Lua integer, use CGEventSetIntegerValueField. If the value is a Lua number (aka double), use the CGEventSetDoubleValueField. The motivation for this change is to use setProperty() to set private / hidden event fields. These event field constants are not defined in any public headers from Apple so it doesn't make sense to add a mapping from string to constant value in the hs.eventtap.event.types table. However, a spoon or Hammerspoon config can set these event fields with setProperty(property, value) by passing an integer for the property and integer or number for the value. Also add a doc note clarifying which CGEvent API is used depending on the Lua value type passed.
|
Some people have discovered a very cool way to switch spaces without the animation by posting a swipe gesture with a very fast velocity: https://github.com/joshuarli/iss It would be great if we could use this technique in Hammerspoon. However, it requires setting some undocumented / hidden CGEventFields. I'm not sure about the best way to go about this. The fast swipe gesture only works on spaces on the same screen, so it's not a suitable replacement for With this change we can create a fast swipe gesture to switch spaces without an animation by doing the following: local FLT_TRUE_MIN = 2 ^ -149
local kCGEventGestureHIDType = 110
local kCGEventGesturePhase = 132
local kCGEventGestureScrollY = 119
local kCGEventGestureSwipeMotion = 123
local kCGEventGestureSwipeProgress = 124
local kCGEventGestureSwipeVelocityX = 129
local kCGEventGestureSwipeVelocityY = 130
local kCGEventGestureZoomDeltaX = 139
local kCGEventScrollGestureFlagBits = 135
local kCGGestureMotionHorizontal = 1
local kCGSEventDockControl = 30
local kCGSEventGesture = 29
local kCGSEventTypeField = 55
local kCGSGesturePhaseBegan = 1
local kCGSGesturePhaseEnded = 4
local kIOHIDEventTypeDockSwipe = 23
function fast_switch_space(direction_right)
-- begin gesture
local ev = hs.eventtap.event.newEvent()
ev:setProperty(kCGSEventTypeField, kCGSEventDockControl)
ev:setProperty(kCGEventGestureHIDType, kIOHIDEventTypeDockSwipe)
ev:setProperty(kCGEventGesturePhase, kCGSGesturePhaseBegan)
ev:setProperty(kCGEventScrollGestureFlagBits, direction_right and 1 or 0)
ev:setProperty(kCGEventGestureSwipeMotion, kCGGestureMotionHorizontal)
ev:setProperty(kCGEventGestureScrollY, 0.0) -- must be a double value
ev:setProperty(kCGEventGestureZoomDeltaX, FLT_TRUE_MIN) -- must be a double value
ev:post()
hs.eventtap.event.newEvent():setProperty(kCGSEventTypeField, kCGSEventGesture):post()
-- end gesture
ev = hs.eventtap.event.newEvent()
ev:setProperty(kCGSEventTypeField, kCGSEventDockControl)
ev:setProperty(kCGEventGestureHIDType, kIOHIDEventTypeDockSwipe)
ev:setProperty(kCGEventGesturePhase, kCGSGesturePhaseEnded)
ev:setProperty(kCGEventGestureSwipeProgress, direction_right and 2.0 or -2.0) -- double value
ev:setProperty(kCGEventScrollGestureFlagBits, direction_right and 1 or 0)
ev:setProperty(kCGEventGestureSwipeMotion, kCGGestureMotionHorizontal)
ev:setProperty(kCGEventGestureScrollY, 0.0) -- must be a double value
ev:setProperty(kCGEventGestureSwipeVelocityX, direction_right and 400.0 or -400.0) -- must be a double value
ev:setProperty(kCGEventGestureSwipeVelocityY, 0.0) -- must be a double value
ev:setProperty(kCGEventGestureZoomDeltaX, FLT_TRUE_MIN) -- must be a double value
ev:post()
hs.eventtap.event.newEvent():setProperty(kCGSEventTypeField, kCGSEventGesture):post()
endAs far as I know, this change preserves previous behavior because all the existing "(N)" CGEventField properties still explicitly call However, let me know if all CGEventField types should be defined in |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3859 +/- ##
==========================================
+ Coverage 27.39% 27.42% +0.03%
==========================================
Files 191 191
Lines 51537 51552 +15
==========================================
+ Hits 14119 14140 +21
+ Misses 37418 37412 -6 🚀 New features to boost your workflow:
|
|
Generally yes, even private properties are added to the appropriate tables (in this case, in |
|
Related: #3850 (Although I do see that it cannot replace |
Create constants for some of the undocumented CGEventField types found in a WebKit test: https://github.com/WebKit/webkit/blob/main/Tools/TestRunnerShared/spi/CoreGraphicsTestSPI.h Add a mapping from string to value to the eventtap.event.properties table. Use CGEventSetDoubleValueField to set an event property for CGEventFields that use a double type.
38ec451 to
8ba08f8
Compare
Sure, I've added a second commit to first define the private properties as You can now amend the Lua example for fast space switching to use the properties table: local props = hs.eventtap.event.properties
function fast_switch_space(direction_right)
-- begin gesture
local ev = hs.eventtap.event.newEvent()
ev:setProperty(props.eventTypeField, kCGSEventDockControl)
ev:setProperty(props.eventGestureHIDType, kIOHIDEventTypeDockSwipe)
ev:setProperty(props.eventGesturePhase, kCGSGesturePhaseBegan)
ev:setProperty(props.eventScrollGestureFlagBits, direction_right and 1 or 0)
ev:setProperty(props.eventGestureSwipeMotion, kCGGestureMotionHorizontal)
ev:setProperty(props.eventGestureScrollY, 0.0) -- must be a double value
ev:setProperty(props.eventGestureZoomDeltaX, FLT_TRUE_MIN) -- must be a double value
ev:post()
hs.eventtap.event.newEvent():setProperty(props.eventTypeField, kCGSEventGesture):post()
-- end gesture
ev = hs.eventtap.event.newEvent()
ev:setProperty(props.eventTypeField, kCGSEventDockControl)
ev:setProperty(props.eventGestureHIDType, kIOHIDEventTypeDockSwipe)
ev:setProperty(props.eventGesturePhase, kCGSGesturePhaseEnded)
ev:setProperty(props.eventGestureSwipeProgress, direction_right and 2.0 or -2.0) -- double value
ev:setProperty(props.eventScrollGestureFlagBits, direction_right and 1 or 0)
ev:setProperty(props.eventGestureSwipeMotion, kCGGestureMotionHorizontal)
ev:setProperty(props.eventGestureScrollY, 0.0) -- must be a double value
ev:setProperty(props.eventGestureSwipeVelocityX, direction_right and 400.0 or -400.0) -- must be a double value
ev:setProperty(props.eventGestureSwipeVelocityY, 0.0) -- must be a double value
ev:setProperty(props.eventGestureZoomDeltaX, FLT_TRUE_MIN) -- must be a double value
ev:post()
hs.eventtap.event.newEvent():setProperty(props.eventTypeField, kCGSEventGesture):post()
endIf this approach is correct, I can squash these two commits before merging. |
| CGEventRef event = *(CGEventRef*)luaL_checkudata(L, 1, EVENT_USERDATA_TAG); | ||
| CGEventField field = (CGEventField)(luaL_checkinteger(L, 2)); | ||
| if ((field == kCGMouseEventPressure) || // These fields use a double (floating point number) | ||
| if ((field == kCGEventGestureScrollY) || // These fields use a double (floating point number) |
There was a problem hiding this comment.
This is really a very small list of properties - with just these added, and still no way to explicitly specify that you want to set (or get) the property as a double, eventually someone will have to come back to this list and add more. Let's not throw out the code to dynamically detect whether you want to set the value as an integer or double. In fact that code is always correct, since CG seems to internally cast values from int to double and vice versa when you get or set them with the wrong function.
There was a problem hiding this comment.
In fact that code is always correct, since CG seems to internally cast values from int to double and vice versa when you get or set them with the wrong function.
My bad - turns out when you get or set a float property using ints, it just gets or sets 0. I'm now convinced it's really best to add special getPropertyFloat and setPropertyFloat functions.
(Using floats to get or set an int property works fine, though.)
| } | ||
|
|
||
| // Undocumented event fields | ||
| static const CGEventField kCGEventGestureHIDType = (CGEventField)110; |
There was a problem hiding this comment.
We should grab the full list as it was published in WebKit open source: https://github.com/WebKit/WebKit/blob/4f1b310945dff0f6827a5e4329c2883698fbc47e/Tools/TestRunnerShared/spi/CoreGraphicsTestSPI.h#L76
| static const CGEventField kCGEventGestureSwipeVelocityY = (CGEventField)130; | ||
| static const CGEventField kCGEventGestureZoomDeltaX = (CGEventField)139; | ||
| static const CGEventField kCGEventScrollGestureFlagBits = (CGEventField)135; | ||
| static const CGEventField kCGSEventTypeField = (CGEventField)55; |
|
Today I did a little bit of experimentation with this technique (deleting things to see if it still works) and discovered
So the important properties are:
The gestureSwipePositionX/Y values don't seem to matter for synthetic dockcontrol events, only gestureSwipeProgress - and if we're sending a one-shot synthesized swipe and not trying to create a cool animation, there's no need to specify gesturePhase. So I wrote these functions to create a nice but fully powerful interface: |
|
Oh and check out this super fun thing you can do now all your trackpad swipes are amplified dramatically! never wait for an animation to finish again! |
eventtap event's setProperty() method uses CGEventSetDoubleValueField for a defined list of CGEventField properties, and CGEventSetIntegerValueField for all others.
Keep the list of double type CGEventField properties, but make a change to check the type of the value parameter for the all others case.
If the value is a Lua integer, use CGEventSetIntegerValueField. If the value is a Lua number (aka double), use the CGEventSetDoubleValueField.
The motivation for this change is to use setProperty() to set private / hidden event fields. These event field constants are not defined in any public headers from Apple so it doesn't make sense to add a mapping from string to constant value in the hs.eventtap.event.types table. However, a spoon or Hammerspoon config can set these event fields with setProperty(property, value) by passing an integer for the property and integer or number for the value.
Also add a doc note clarifying which CGEvent API is used depending on the Lua value type passed.