Skip to content
This repository was archived by the owner on Mar 12, 2022. It is now read-only.

Commit 0f23a4d

Browse files
committed
add example for transform
1 parent 8549da8 commit 0f23a4d

File tree

3 files changed

+214
-3
lines changed

3 files changed

+214
-3
lines changed

Diff for: RawAccess.md

+35-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,32 @@ In my experimentation, I was always able to return to a usable state by the foll
99
1. Change to another space by one of the "normal" methods (either the keyboard shortcuts, CTRL-#, or trackpad gestures.)
1010
2. `hs.execute('killall Dock')`
1111

12-
If that wasn't sufficient, logging out and/or rebooting the computer.
12+
You can also add the following to your `init.lua` file as a "safety" reset, which seems to work well for me if you can get to the console or a terminal and use the `hs` command... it basically picks an arbitrary space for each monitor, shows it, hides the others, resets all transforms, and restarts the Dock.
13+
14+
~~~lua
15+
resetSpaces = function()
16+
local s = require("hs._asm.undocumented.spaces")
17+
-- bypass check for raw function access
18+
local si = require("hs._asm.undocumented.spaces.internal")
19+
for k,v in pairs(s.spacesByScreenUUID()) do
20+
local first = true
21+
for a,b in ipairs(v) do
22+
if first and si.spaceType(b) == s.types.user then
23+
si.showSpaces(b)
24+
si._changeToSpace(b)
25+
first = false
26+
else
27+
si.hideSpaces(b)
28+
end
29+
si.spaceTransform(b, nil)
30+
end
31+
si.setScreenUUIDisAnimating(k, false)
32+
end
33+
hs.execute("killall Dock")
34+
end
35+
~~~
36+
37+
If that wasn't sufficient, logging out and/or rebooting the computer always work.
1338

1439
As freaky and odd as some of the results of these functions are, I decided to include them, rather than leave them solely in the Objective-C portion of the module or undefined at all because I believe in experimentation, and because they suggest some interesting possibilities, such as translating spaces in odd ways (rotation, inversion, etc.) and rendering multiple spaces on the screen at the same time (e.g. replicating *Mission Control*).
1540

@@ -124,7 +149,14 @@ spaces.raw.screenUUIDisAnimating(screenUUID) -> boolean
124149
~~~
125150
Returns whether the display specified by the given screenUUID is currently undergoing space-change animation. Because this will crash Hammerspoon if an incorrect UUID is provided, it does check to make sure that the UUID is valid first.
126151

127-
The API also provides a function for setting the animation flag to true or false, but it has not been implemented in this module at present. It would not be difficult to do so, but I've seen no reason at present.
152+
- - -
153+
154+
~~~lua
155+
spaces.raw.setScreenUUIDisAnimating(screenUUID, flag) -> boolean
156+
~~~
157+
Sets the flag indicating whether or not the specified screen is currently animating. No check is done on whether or not animation is really occurring, so this is primarily so you can "play nice" and "do the right thing" when writing your own animation sequences with `spaces.raw.spaceTransform` and the like.
158+
159+
Returns whether the display specified by the given screenUUID is currently undergoing space-change animation so you can verify (if desired) that the flag change has taken effect.
128160

129161
- - -
130162

@@ -216,7 +248,7 @@ To set the table's transform, provide the 6 elements require for CGAffineTransfo
216248
[ c d 0 ]
217249
[ tx ty 1 ]
218250

219-
You can resize, move, and rotate a space by using the appropriate Matrix transformation algorithms on these values. A discussion of this is outside the scope of this document.
251+
You can resize, move, and rotate a space by using the appropriate Matrix transformation algorithms on these values. A discussion of this is outside the scope of this document. Check out Google and Apple's documentation at https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CGAffineTransform/, especially the section about [the CGAffineTransform data type](https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CGAffineTransform/#//apple_ref/c/tdef/CGAffineTransform).
220252

221253
- - -
222254

Diff for: examples/transition.lua

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
--[[
2+
3+
DO NOT USE THIS BEFORE READING THE README.md and RawAccess.md FILES.
4+
5+
If you just want an example of things that can be done with this module,
6+
the `init.lua` file of this module is a better, safer file to examine first.
7+
This example can easily make your display very weird and hard to view if
8+
modified incorrectly.
9+
10+
This is an example of using the space transformation function for animating
11+
space changes. The transform function use matrix based math for handling
12+
the space animation. A full treatment of Matrix mathematics is beyond the
13+
scope of this example or documentation. A quick google search for "2D
14+
transformation matrix" will provide many examples, if you are new to the
15+
concept. Also check out Apple's CGAffineTransform Reference, at
16+
https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CGAffineTransform/
17+
especially the section about the CGAffineTransform data type.
18+
19+
This is quick and dirty... I suspect it could be made cleaner and/or
20+
smoother.
21+
22+
I do not know why some windows don't seem to fully behave until
23+
the space is at least a certain size -- perhaps its a limitation of windows
24+
with a minimum sized bounding box.
25+
26+
I don't know why windows which are not normal in some way (hs.drawing
27+
window behaviors of canJoinAllSpaces or moveToActiveSpace especially) behave
28+
oddly -- perhaps like the Dock and Notification center, they exist in a
29+
"different space".
30+
31+
That's what experimentation and the raw function access is for -- trying to
32+
figure out together that which Apple doesn't want to fully explain or
33+
document.
34+
35+
I have not (yet) tested this on a multi monitor setup yet, but I have tried to
36+
make it safe for multi-monitor setups. I *think* the only portion which
37+
*might* be wrong is the calculation of the translation portion of the matrix
38+
transform but you have been warned.
39+
40+
For this example, pass in up to two arguments: the space ID to transition
41+
to and an optional boolean argument indicating whether or not to reset the
42+
Dock after completing the transformation to make the change "stick". Defaults
43+
to true. See the documentation for hs._asm.undocumented.spaces for an
44+
explanation of the reason for this argument.
45+
46+
Usage -- Copy this file into your ~/.hammerspoon folder and then:
47+
> t = dofile("transition.lua")
48+
> t.transition(spaceID [, toggle]) -- change to the specified spaceID
49+
> t.reset() -- reset to a (presumably) safe state if something goes wrong
50+
51+
--]]
52+
53+
-- we get the internal, "raw" functions so we can bypass the need for the user
54+
-- to have previously enabled them. Check out the RawAccess.md file for more
55+
-- information
56+
local s = require("hs._asm.undocumented.spaces")
57+
local si = require("hs._asm.undocumented.spaces.internal")
58+
59+
60+
local fnutils = require("hs.fnutils")
61+
local timer = require("hs.timer")
62+
local screen = require("hs.screen")
63+
64+
local module = {}
65+
66+
module.transition = function(spaceID, reset)
67+
if type(reset) ~= "boolean" then reset = true end
68+
if spaceID == nil then
69+
error("You must specify a spaceID to transition to", 2)
70+
end
71+
72+
-- keep it at least a little safe...
73+
if not fnutils.find(s.query(), function(_) return _ == spaceID end) then
74+
error("You can only change to a user accessible space", 2)
75+
end
76+
77+
local targetScreenUUID = s.spaceScreenUUID(spaceID)
78+
local startingSpace = -1
79+
for i,v in ipairs(s.query(s.masks.currentSpaces)) do
80+
if s.spaceScreenUUID(v) == targetScreenUUID then
81+
startingSpace = v
82+
break
83+
end
84+
end
85+
if startingSpace == -1 then
86+
error("Unable to determine active space for the destination specified", 2)
87+
end
88+
if startingSpace == spaceID then
89+
error("Already on the destination space", 2)
90+
end
91+
92+
local targetScreen = nil
93+
for i,v in ipairs(screen.allScreens()) do
94+
if v:spacesUUID() == targetScreenUUID then
95+
targetScreen = v
96+
break
97+
end
98+
end
99+
if not targetScreen then
100+
error("Unable to get screen object for destination specified", 2)
101+
end
102+
103+
if si.screenUUIDisAnimating(targetScreenUUID) then
104+
error("Specified screen is already in an animation sequence", 2)
105+
end
106+
107+
-- Begin actual space animation stuff. We're attempting to "shrink" the active
108+
-- space into the upper left corner, and "grow" the new space from the lower
109+
-- right one... it seemed simpler than dealing with rotation and the matrix
110+
-- math that implies, but different enough from the "stock" animation to
111+
-- show what can be done.
112+
113+
local state = 1
114+
local screenFrame = targetScreen:fullFrame()
115+
-- print(spaceID, startingSpace)
116+
117+
si.showSpaces({spaceID, startingSpace})
118+
si.setScreenUUIDisAnimating(targetScreenUUID, true)
119+
module.activeTimer = timer.new(0.1, function()
120+
if state == 10 then
121+
module.activeTimer:stop()
122+
module.activeTimer = nil
123+
si.spaceTransform(startingSpace, nil)
124+
si.hideSpaces(startingSpace)
125+
si.spaceTransform(spaceID, nil)
126+
si._changeToSpace(spaceID) -- has an implicit show
127+
si.setScreenUUIDisAnimating(targetScreenUUID, false)
128+
if reset then hs.execute("killall Dock") end
129+
else
130+
local hDelta = -1 * screenFrame.h * (10 - state)
131+
local wDelta = -1 * screenFrame.w * (10 - state)
132+
-- print(screenFrame.h, screenFrame.w, hDelta,wDelta)
133+
si.spaceTransform(spaceID, { 11 - state, 0, 0, 11 - state, wDelta, hDelta })
134+
si.spaceTransform(startingSpace, { state + 1, 0, 0, state + 1, 0, 0 })
135+
end
136+
state = state + 1
137+
end):start()
138+
return module.activeTimer
139+
end
140+
141+
-- attempt to reset into a "stable" state if things go awry. We pick an
142+
-- arbitrary user space on each monitor, show them, hide all others, reset
143+
-- transforms to the identity matrix, toggle the animation flag to off
144+
-- and reset the dock.
145+
module.reset = function()
146+
for k,v in pairs(s.spacesByScreenUUID()) do
147+
local first = true
148+
for a,b in ipairs(v) do
149+
if first and si.spaceType(b) == s.types.user then
150+
si.showSpaces(b)
151+
si._changeToSpace(b)
152+
first = false
153+
else
154+
si.hideSpaces(b)
155+
end
156+
si.spaceTransform(b, nil)
157+
end
158+
si.setScreenUUIDisAnimating(k, false)
159+
end
160+
hs.execute("killall Dock")
161+
end
162+
163+
return module

Diff for: internal.m

+16
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,21 @@ static int screenUUIDisAnimating(lua_State *L) {
226226
return 1 ;
227227
}
228228

229+
static int setScreenUUIDisAnimating(lua_State *L) {
230+
[[LuaSkin shared] checkArgs:LS_TSTRING, LS_TBOOLEAN, LS_TBREAK] ;
231+
NSString *theDisplay = [[LuaSkin shared] toNSObjectAtIndex:1] ;
232+
BOOL isValid = isScreenUUIDValid(theDisplay) ;
233+
234+
if (isValid) {
235+
CGSManagedDisplaySetIsAnimating(CGSDefaultConnection, (__bridge CFStringRef)theDisplay, lua_toboolean(L, 2)) ;
236+
237+
lua_pushboolean(L, CGSManagedDisplayIsAnimating(CGSDefaultConnection, (__bridge CFStringRef)theDisplay)) ;
238+
} else {
239+
luaL_error(L, "setScreenUUIDisAnimating: invalid screen UUID") ;
240+
}
241+
return 1 ;
242+
}
243+
229244
static int mainScreenUUID(__unused lua_State *L) {
230245
[[LuaSkin shared] checkArgs:LS_TBREAK] ;
231246
[[LuaSkin shared] pushNSObject:(__bridge NSString *) kCGSPackagesMainDisplayIdentifier] ;
@@ -380,6 +395,7 @@ static int spaceManagedShape(lua_State *L) {
380395
{"mainScreenUUID", mainScreenUUID},
381396
{"UUIDforScreen", screenUUID},
382397
{"screenUUIDisAnimating", screenUUIDisAnimating},
398+
{"setScreenUUIDisAnimating", setScreenUUIDisAnimating},
383399

384400
{"showSpaces", showSpaces},
385401
{"hideSpaces", hideSpaces},

0 commit comments

Comments
 (0)