Ironcoder 0x2 wraps up in a few hours, and this time the API was CoreGraphics. As soon as I heard this, I knew I wanted to do something with window mesh deformation. What’s that? Why, it’s the private CoreGraphics call you can use to do the effects such as the Genie minimize effect, though I believe it’s new in 10.3. It’s a function called CGSSetWindowWarp(), and it’s extremely poorly documented.

First, some history. CoreGraphics has a bunch of private calls that range from applying transitions to windows (such as the cube effect seen in Apple Remote Desktop and Quicksilver) to managing virtual desktops to setting alpha levels on groups of windows system-wide. As far as I am able to ascertain, Richard Wareham did the bulk of the work to expose these calls, as part of development on his Desktop Manager program, the most well-known virtual desktop solution for OS X. He released a file named CGSPrivate.h that contained these basic calls, so others could use it. Since then, more private calls have been discovered, and among them were CGSSetWindowWarp().

For a while, only the name of the function was known. As far as I know, I was the first person to determine what the arguments to the function were, though this information wasn’t widely disseminated. Instead, I simply told Nicholas Jitkoff (author of Quicksilver), as he was the one who asked me to help him figure it out. At that time, I didn’t know how the function was actually used, I just knew what arguments it took. And then I forgot about it.

Since then other people have played with the function (having figured out the arguments on their own). The most visible documentation of this I could find was on CocoaDev, where the author of the splendid Rotated Windows sample app said he figured out the arguments to the function but hadn’t yet figured out how it was actually used - basically, he did what I did.

At this point, what was known was the method signature:

typedef struct CGPointWarp { CGPoint local; CGPoint global; } CGPointWarp;

extern CGError CGSSetWindowWarp(CGSConnectionID, CGSWindowID, int w, int h, CGPointWarp mesh[w][h])

Most of this is pretty self-evident, but the tricky question is how the mesh works.

Well, some time ago I ran across a sample app called warp.c, which simply produced a simple animation of a window being bent backwards (using a bezier curve). It wasn’t very complex, but it did help show how the deformation mesh was constructed. What the mesh does is maps local window coordinates to global screen coordinates. It’s a 2-dimensional array that forms a grid of points. From what I can determine, the local points are expected to form a regular, even grid. The vertical and horizontal deltas can be different, but they must be consistent. Changing a single point causes visual issues, from simply incorrect drawing to the window disappearing.

Based on this, I was able to construct my entry for ironcoder that used CGSSetWindowWarp() as the main effect.

To summarize, the way CGSSetWindowWarp() is used is the mesh is a 2-dimensional grid of local points with associated global points. The local points are in the window’s coordinate system, whereas the global points are in the screen’s coordinate system.

One pitfall I discovered during this process is you need to be aware of the window being dragged. If you’re animating a window and someone starts to drag it, you can run into problems where the window keeps snapping back to its old location. Why is this? Because -[NSWindow frame] doesn’t update until you end the drag, so your global coordinates are going to be based on the window’s old position. Not to mention -[NSWindow frame] uses a flipped screen coordinate system from CGSSetWindowWarp(). There’s a good solution to this, however. That solution is to use another private method:

extern OSStatus CGSGetWindowBounds(CGSConnectionID cid, CGSWindowID wid, CGRect *bounds)

This will give you a CGRect (structurally and functionally equivalent to an NSRect) that contains the current frame of the window, taking a current drag into account, and using the same coordinate system that CGSSetWindowWarp expects. There’s also another similar function:

extern OSStatus CGSGetScreenRectForWindow(CGSConnectionID cid, CGSWindowID wid, CGRect *outRect)

There is one crucial difference, namely, that the CGRect given by this function is the rect that covers the entire rendered area of the window, meaning any window mesh deformations are taken into account. So this is not the right function to use when calculating the deformation mesh, because any pre-existing deformation will throw off the coordinates. So use CGSGetWindowBounds() always.

The last bit of information necessary to use CGSSetWindowWarp() is how to get the CGSConnectionID and CGSWindowID. This, luckily, is extremely simple. A CGSConnectionID can be retrieved with the private function:

extern CGSConnectionID _CGSDefaultConnection()

and the CGSWindowID is the result of calling -[NSWindow windowNumber] on your window. Incidentally both CGSConnectionID and CGSWindowID should be typedefs of int.

You should be aware that applying a deformation mesh to a window will cause the shadow to not draw. So if you want a normal shadow, you will need to draw it yourself. Additionally, the window warp can be removed (and shadow restored) by calling CGSSetWindowWarp() with 0, 0, and NULL.

Update: My ironcoder entry is now available, in case you want another example of CGSSetWindowWarp().

No Responses to “CGSSetWindowWarp explained”
  1. Great little demo! It clearly needs a second hand though…having some obvious motion happening with the distortion would show it off a lot better.

  2. Kevin Ballard says:

    I thought about adding a second hand, but only after I had already submitted it :/

    Maybe I’ll go back and edit it..

  3. CGSPrivate has been advanced further by people like Wade Tregaskis, and is really being used extensively now.
    More information on Core Graphics APIs on my website (see http://dev.lipidity.com/apple/the-ultimate-core-graphics-resource )

    I have a tutorial on there as well.