├── .gitignore ├── CGSPrivate.h ├── Makefile ├── README.md ├── dmg-template.dmg ├── main.c └── make_dmg.sh /.gitignore: -------------------------------------------------------------------------------- 1 | spacefinder 2 | spaces-util-xcode/ 3 | spaces-util 4 | Spaces-Utility.dmg 5 | -------------------------------------------------------------------------------- /CGSPrivate.h: -------------------------------------------------------------------------------- 1 | // 2 | // CGSPrivate.h 3 | // Header file for undocumented CoreGraphics SPI 4 | // 5 | // Arranged by Nicholas Jitkoff 6 | // Based on CGSPrivate.h by Richard Wareham 7 | // 8 | // Contributors: 9 | // Austin Sarner: Shadows 10 | // Jason Harris: Filters, Shadows, Regions 11 | // Kevin Ballard: Warping 12 | // Steve Voida: Workspace notifications 13 | // Tony Arnold: Workspaces notifications enum filters 14 | // Ben Gertzfield: CGSRemoveConnectionNotifyProc 15 | // 16 | // Changes: 17 | // 2.3 - Added the CGSRemoveConnectionNotifyProc method with the help of Ben Gertzfield 18 | // 2.2 - Moved back to CGSPrivate, added more enums to the CGSConnectionNotifyEvent 19 | // 2.1 - Added spaces notifications 20 | // 2.0 - Original Release 21 | 22 | #include 23 | 24 | #define CGSConnectionID CGSConnection 25 | #define CGSWindowID CGSWindow 26 | #define CGSDefaultConnection _CGSDefaultConnection() 27 | 28 | #ifdef __cplusplus 29 | extern "C" { 30 | #endif 31 | 32 | typedef int CGSConnection; 33 | typedef int CGSWindow; 34 | typedef int CGSWorkspace; 35 | typedef int CGSValue; 36 | 37 | #pragma mark Listing Windows 38 | /* Get the default connection for the current process. */ 39 | extern CGSConnection _CGSDefaultConnection(void); 40 | 41 | // Disable/Enable Screen Updates 42 | extern CGError CGSDisableUpdate(CGSConnection cid); 43 | extern CGError CGSReenableUpdate(CGSConnection cid); 44 | 45 | #pragma mark Listing Windows 46 | 47 | // Get window counts and lists. 48 | extern CGError CGSGetWindowCount(const CGSConnection cid, CGSConnection targetCID, int* outCount); 49 | extern CGError CGSGetWindowList(const CGSConnection cid, CGSConnection targetCID, int count, int* list, int* outCount); 50 | 51 | // Get on-screen window counts and lists. 52 | extern CGError CGSGetOnScreenWindowCount(const CGSConnection cid, CGSConnection targetCID, int* outCount); 53 | extern CGError CGSGetOnScreenWindowList(const CGSConnection cid, CGSConnection targetCID, int count, int* list, int* outCount); 54 | 55 | // Per-workspace window counts and lists. 56 | extern CGError CGSGetWorkspaceWindowCount(const CGSConnection cid, CGSWorkspace workspaceNumber, int *outCount); 57 | extern CGError CGSGetWorkspaceWindowList(const CGSConnection cid, CGSWorkspace workspaceNumber, int count, int* list, int* outCount); 58 | 59 | #pragma mark Window Manipulation 60 | 61 | // Window Level 62 | extern CGError CGSGetWindowLevel(const CGSConnection cid, CGSWindow wid, CGWindowLevel *level); 63 | extern CGError CGSSetWindowLevel(const CGSConnection cid, CGSWindow wid, CGWindowLevel level); 64 | 65 | // Window ordering 66 | typedef enum _CGSWindowOrderingMode { 67 | kCGSOrderAbove = 1, // Window is ordered above target. 68 | kCGSOrderBelow = -1, // Window is ordered below target. 69 | kCGSOrderOut = 0 // Window is removed from the on-screen window list. 70 | } CGSWindowOrderingMode; 71 | 72 | extern CGError CGSOrderWindow(const CGSConnection cid, const CGSWindow wid, CGSWindowOrderingMode place, CGSWindow relativeToWindowID /* can be NULL */); 73 | extern CGError CGSWindowIsOrderedIn(const CGSConnection cid, const CGSWindow wid, Boolean *result); 74 | 75 | extern CGError CGSUncoverWindow(const CGSConnection cid, const CGSWindow wid); 76 | extern CGError CGSFlushWindow(const CGSConnection cid, const CGSWindow wid, int unknown /* 0 works */ ); 77 | 78 | // Position 79 | extern CGError CGSGetWindowBounds(CGSConnection cid, CGSWindowID wid, CGRect *outBounds); 80 | extern CGError CGSGetScreenRectForWindow(const CGSConnection cid, CGSWindow wid, CGRect *outRect); 81 | extern CGError CGSMoveWindow(const CGSConnection cid, const CGSWindow wid, CGPoint *point); 82 | extern CGError CGSSetWindowTransform(const CGSConnection cid, const CGSWindow wid, CGAffineTransform transform); 83 | extern CGError CGSGetWindowTransform(const CGSConnection cid, const CGSWindow wid, CGAffineTransform * outTransform); 84 | extern CGError CGSSetWindowTransforms(const CGSConnection cid, CGSWindow *wids, CGAffineTransform *transform, int n); 85 | 86 | // Alpha 87 | extern CGError CGSSetWindowAlpha(const CGSConnection cid, const CGSWindow wid, float alpha); 88 | extern CGError CGSSetWindowListAlpha(const CGSConnection cid, CGSWindow *wids, int count, float alpha); 89 | extern CGError CGSGetWindowAlpha(const CGSConnection cid, const CGSWindow wid, float* alpha); 90 | 91 | // Brightness 92 | extern CGError CGSSetWindowListBrightness(const CGSConnection cid, CGSWindow *wids, float *brightness, int count); 93 | 94 | // Workspace 95 | extern CGError CGSMoveWorkspaceWindows(const CGSConnection connection, CGSWorkspace toWorkspace, CGSWorkspace fromWorkspace); 96 | extern CGError CGSMoveWorkspaceWindowList(const CGSConnection connection, CGSWindow *wids, int count, CGSWorkspace toWorkspace); 97 | 98 | // Shadow 99 | extern CGError CGSSetWindowShadowAndRimParameters(const CGSConnection cid, CGSWindow wid, float standardDeviation, float density, int offsetX, int offsetY, unsigned int flags); 100 | extern CGError CGSGetWindowShadowAndRimParameters(const CGSConnection cid, CGSWindow wid, float* standardDeviation, float* density, int *offsetX, int *offsetY, unsigned int *flags); 101 | 102 | // Properties 103 | extern CGError CGSGetWindowProperty(const CGSConnection cid, CGSWindow wid, CGSValue key, CGSValue *outValue); 104 | extern CGError CGSSetWindowProperty(const CGSConnection cid, CGSWindow wid, CGSValue key, CGSValue *outValue); 105 | 106 | // Owner 107 | extern CGError CGSGetWindowOwner(const CGSConnection cid, const CGSWindow wid, CGSConnection *ownerCid); 108 | extern CGError CGSConnectionGetPID(const CGSConnection cid, pid_t *pid, const CGSConnection ownerCid); 109 | 110 | #pragma mark Window Tags 111 | 112 | typedef enum { 113 | CGSTagNone = 0, // No tags 114 | CGSTagExposeFade = 0x0002, // Fade out when Expose activates. 115 | CGSTagNoShadow = 0x0008, // No window shadow. 116 | CGSTagTransparent = 0x0200, // Transparent to mouse clicks. 117 | CGSTagSticky = 0x0800, // Appears on all workspaces. 118 | } CGSWindowTag; 119 | 120 | // thirtyTwo must = 32 for some reason. 121 | // tags is a pointer to an array of ints (size 2?). First entry holds window tags. 122 | extern CGError CGSGetWindowTags(const CGSConnection cid, const CGSWindow wid, CGSWindowTag *tags, int thirtyTwo); 123 | extern CGError CGSSetWindowTags(const CGSConnection cid, const CGSWindow wid, CGSWindowTag *tags, int thirtyTwo); 124 | extern CGError CGSClearWindowTags(const CGSConnection cid, const CGSWindow wid, CGSWindowTag *tags, int thirtyTwo); 125 | extern CGError CGSGetWindowEventMask(const CGSConnection cid, const CGSWindow wid, uint32_t *mask); 126 | extern CGError CGSSetWindowEventMask(const CGSConnection cid, const CGSWindow wid, uint32_t mask); 127 | 128 | # pragma mark Window Warping 129 | 130 | typedef struct { 131 | CGPoint local; 132 | CGPoint global; 133 | } CGPointWarp; 134 | 135 | extern CGError CGSSetWindowWarp(const CGSConnection cid, const CGSWindow wid, int w, int h, CGPointWarp mesh[h][w]); 136 | 137 | # pragma mark Window Core Image Filters 138 | 139 | typedef void *CGSWindowFilterRef; 140 | extern CGError CGSNewCIFilterByName(CGSConnection cid, CFStringRef filterName, CGSWindowFilterRef *outFilter); 141 | extern CGError CGSAddWindowFilter(CGSConnection cid, CGSWindowID wid, CGSWindowFilterRef filter, int flags); 142 | extern CGError CGSRemoveWindowFilter(CGSConnection cid, CGSWindowID wid, CGSWindowFilterRef filter); 143 | extern CGError CGSReleaseCIFilter(CGSConnection cid, CGSWindowFilterRef filter); 144 | extern CGError CGSSetCIFilterValuesFromDictionary(CGSConnection cid, CGSWindowFilterRef filter, CFDictionaryRef filterValues); 145 | 146 | #pragma mark Transitions 147 | 148 | typedef enum { 149 | CGSNone = 0, // No transition effect. 150 | CGSFade, // Cross-fade. 151 | CGSZoom, // Zoom/fade towards us. 152 | CGSReveal, // Reveal new desktop under old. 153 | CGSSlide, // Slide old out and new in. 154 | CGSWarpFade, // Warp old and fade out revealing new. 155 | CGSSwap, // Swap desktops over graphically. 156 | CGSCube, // The well-known cube effect. 157 | CGSWarpSwitch, // Warp old, switch and un-warp. 158 | CGSFlip, // Flip over 159 | CGSTransparentBackgroundMask = (1<<7) // OR this with any other type to get a transparent background 160 | } CGSTransitionType; 161 | 162 | typedef enum { 163 | CGSDown, // Old desktop moves down. 164 | CGSLeft, // Old desktop moves left. 165 | CGSRight, // Old desktop moves right. 166 | CGSInRight, // CGSSwap: Old desktop moves into screen, new comes from right. 167 | CGSBottomLeft = 5, // CGSSwap: Old desktop moves to bl, new comes from tr. 168 | CGSBottomRight, // CGSSwap: Old desktop to br, New from tl. 169 | CGSDownTopRight, // CGSSwap: Old desktop moves down, new from tr. 170 | CGSUp, // Old desktop moves up. 171 | CGSTopLeft, // Old desktop moves tl. 172 | CGSTopRight, // CGSSwap: old to tr. new from bl. 173 | CGSUpBottomRight, // CGSSwap: old desktop up, new from br. 174 | CGSInBottom, // CGSSwap: old in, new from bottom. 175 | CGSLeftBottomRight, // CGSSwap: old one moves left, new from br. 176 | CGSRightBottomLeft, // CGSSwap: old one moves right, new from bl. 177 | CGSInBottomRight, // CGSSwap: onl one in, new from br. 178 | CGSInOut // CGSSwap: old in, new out. 179 | } CGSTransitionOption; 180 | 181 | typedef struct { 182 | uint32_t unknown1; 183 | CGSTransitionType type; 184 | CGSTransitionOption option; 185 | CGSWindow wid; /* Can be 0 for full-screen */ 186 | float *backColour; /* Null for black otherwise pointer to 3 float array with RGB value */ 187 | } CGSTransitionSpec; 188 | 189 | extern CGError CGSNewTransition(const CGSConnection cid, const CGSTransitionSpec* spec, int *pTransitionHandle); 190 | extern CGError CGSInvokeTransition(const CGSConnection cid, int transitionHandle, float duration); 191 | extern CGError CGSReleaseTransition(const CGSConnection cid, int transitionHandle); 192 | 193 | #pragma mark Workspaces 194 | 195 | extern CGError CGSGetWorkspace(const CGSConnection cid, CGSWorkspace *workspace); 196 | extern CGError CGSGetWindowWorkspace(const CGSConnection cid, const CGSWindow wid, CGSWorkspace *workspace); 197 | extern CGError CGSSetWorkspace(const CGSConnection cid, CGSWorkspace workspace); 198 | extern CGError CGSSetWorkspaceWithTransition(const CGSConnection cid, CGSWorkspace workspace, CGSTransitionType transition, CGSTransitionOption subtype, float time); 199 | 200 | typedef enum { 201 | CGSScreenResolutionChangedEvent = 100, 202 | CGSConnectionNotifyEventUnknown2 = 101, 203 | CGSConnectionNotifyEventUnknown3 = 102, 204 | CGSConnectionNotifyEventUnknown4 = 103, 205 | CGSClientEnterFullscreen = 106, 206 | CGSClientExitFullscreen = 107, 207 | CGSConnectionNotifyEventUnknown7 = 750, 208 | CGSConnectionNotifyEventUnknown8 = 751, 209 | CGSWorkspaceConfigurationDisabledEvent = 761, // Seems to occur when objects are removed (rows/columns), or disabled 210 | CGSWorkspaceConfigurationEnabledEvent = 762, // Seems to occur when objects are added (rows/columns), or enabled 211 | CGSConnectionNotifyEventUnknown9 = 763, 212 | CGSConnectionNotifyEventUnknown10 = 764, 213 | CGSConnectionNotifyEventUnknown11 = 806, 214 | CGSConnectionNotifyEventUnknown12 = 807, 215 | CGSConnectionNotifyEventUnknown13 = 1201, // Seems to occur when applications are launched/quit. Is this a connection being created/destroyed by the application to the window server? 216 | CGSWorkspaceChangedEvent = 1401, 217 | CGSConnectionNotifyEventUnknown14 = 1409, 218 | CGSConnectionNotifyEventUnknown15 = 1410, 219 | CGSConnectionNotifyEventUnknown16 = 1411, 220 | CGSConnectionNotifyEventUnknown17 = 1412, 221 | CGSConnectionNotifyEventUnknown18 = 1500, 222 | CGSConnectionNotifyEventUnknown19 = 1501, 223 | CGSConnectionNotifyEventUnknown20 = 1700 224 | } CGSConnectionNotifyEvent; 225 | 226 | /* Prototype for the Spaces change notification callback. 227 | * 228 | * data1 -- returns whatever value is passed to data1 parameter in CGSRegisterConnectionNotifyProc 229 | * data2 -- indeterminate (always a large negative integer; seems to be limited to a small set of values) 230 | * data3 -- indeterminate (always returns the number '4' for me) 231 | * userParameter -- returns whatever value is passed to userParameter in CGSRegisterConnectionNotifyProc 232 | */ 233 | 234 | typedef void (*CGConnectionNotifyProc)(int data1, int data2, int data3, void* userParameter); 235 | 236 | /* Register a callback function to receive notifications about when 237 | the current Space is changing. 238 | * 239 | * cid -- Current connection 240 | * function -- A pointer to the intended callback function (must be in C; cannot be an Objective-C selector) 241 | * event -- indeterminate (this is hard-coded to 0x579 in Spaces.menu...perhpas some kind of event filter code?) -- use CGSWorkspaceChangedEvent in this for now 242 | * userParameter -- pointer to user-defined auxiliary information structure; passed directly to callback proc 243 | */ 244 | 245 | // For spaces notifications: CGSRegisterConnectionNotifyProc(_CGSDefaultConnection(), spacesCallback, 1401, (void*)userInfo); 246 | 247 | extern CGError CGSRegisterConnectionNotifyProc(const CGSConnection cid, CGConnectionNotifyProc function, CGSConnectionNotifyEvent event, void* userParameter); 248 | 249 | extern CGError CGSRemoveConnectionNotifyProc(const CGSConnection cid, CGConnectionNotifyProc function, CGSConnectionNotifyEvent event, void* userParameter); 250 | 251 | # pragma mark Miscellaneous 252 | 253 | // Regions 254 | typedef void *CGSRegionRef; 255 | extern CGError CGSNewRegionWithRect(CGRect const *inRect, CGSRegionRef *outRegion); 256 | extern CGError CGSNewEmptyRegion(CGSRegionRef *outRegion); 257 | extern CGError CGSReleaseRegion(CGSRegionRef region); 258 | 259 | // Creating Windows 260 | extern CGError CGSNewWindowWithOpaqueShape(CGSConnection cid, int always2, float x, float y, CGSRegionRef shape, CGSRegionRef opaqueShape, int unknown1, void *unknownPtr, int always32, CGSWindowID *outWID); 261 | extern CGError CGSReleaseWindow(CGSConnection cid, CGSWindowID wid); 262 | extern CGContextRef CGWindowContextCreate(CGSConnection cid, CGSWindowID wid, void *unknown); 263 | 264 | // Values 265 | extern int CGSIntegerValue(CGSValue intVal); 266 | extern void *CGSReleaseGenericObj(void*); 267 | 268 | // Deprecated in 10.5 269 | extern CGSValue CGSCreateCStringNoCopy(const char *str); //Normal CFStrings will work 270 | extern CGSValue CGSCreateCString(const char* str); 271 | extern char* CGSCStringValue(CGSValue string); 272 | 273 | #pragma mark Debugging 274 | 275 | // These all create files called /tmp/WindowServer + suffix 276 | #define kCGSDumpWindowInfo 0x80000001 // .winfo.out 277 | #define kCGSDumpConnectionInfo 0x80000002 // .cinfo.out 278 | #define kCGSDumpKeyInfo 0x8000000e // .keyinfo.out 279 | #define kCGSDumpSurfaceInfo 0x80000010 // .sinfo.out 280 | #define kCGSDumpGLInfo 0x80000013 // .glinfo.out 281 | #define kCGSDumpShadowInfo 0x80000014 //.shinfo.out 282 | #define kCGSDumpStoragesAndCachesInfo 0x80000015 // .scinfo.out 283 | #define kCGSDumpWindowPlistInfo 0x80000017 // .winfo.plist 284 | // Other flags: 285 | #define kCGSDebugOptionNormal 0 // Reset everything 286 | #define kCGSFlashScreenUpdates 4 // This is probably what the checkbox in Quartz Debug calls internally 287 | typedef unsigned long CGSDebugOptions; 288 | 289 | extern void CGSSetDebugOptions(CGSDebugOptions options); 290 | 291 | // Missing functions 292 | 293 | //CGSIntersectRegionWithRect 294 | //CGSSetWindowTransformsAtPlacement 295 | //CGSSetWindowListGlobalClipShape 296 | //extern CGError CGSWindowAddRectToDirtyShape(const CGSConnection cid, const CGSWindow wid, CGRect *rect); 297 | 298 | #ifdef __cplusplus 299 | } 300 | #endif 301 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | CFLAGS = -x objective-c -arch i386 -arch x86_64 \ 3 | -fmessage-length=0 -std=c99 \ 4 | -mmacosx-version-min=10.5 \ 5 | -fpascal-strings -fasm-blocks \ 6 | -framework Foundation \ 7 | -framework Carbon \ 8 | -Wall 9 | .PHONY: clean 10 | 11 | spaces-util: main.c Makefile 12 | gcc $(CFLAGS) -o spaces-util $< 13 | package: spaces-util 14 | ./make_dmg.sh 15 | clean: 16 | -rm spacefinder spaces-util 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Command-line OSX Spaces Manipulation Tool 2 | 3 | Original code is by [shabble](https://github.com/shabble), with some updates 4 | from [canadaduane](https://github.com/chicagoduane), animated switching code 5 | and dimension retrieval inspired by a blog post from 6 | [Denis Gryzlov](http://meeu.me/blog/dashboard-expose-spaces). 7 | 8 | For a variety of useful and useless purposes when scripting in OSX, it 9 | might be handy to know which of the Spaces (virtual desktops) 10 | you're currently on. You may also wish to programmatically switch spaces 11 | from the command line. 12 | 13 | [Mac OSX Hints](http://www.macosxhints.com/article.php?story=20080227075244778) 14 | has one possible solution, which uses the Assistive Devices support to rip the 15 | value from the menubar icon, but this is really horribly hacky, and pretty slow, 16 | at least for me. It takes at least a second, and more like 2 or 3 typically. 17 | 18 | An alternative idea is presented on 19 | [StackOverflow](http://stackoverflow.com/questions/554380/how-to-detect-which-space-the-user-is-on-in-mac-os-x-leopard) 20 | which uses some Carbon API calls to fetch a window handle of the currently 21 | focused app (which is presumably on your current space), and then poke around in 22 | its internal metadata dictionary to find its workspace ID. 23 | 24 | The latter is the method implemented here. 25 | 26 | ## Binary Install 27 | 28 | 1. Download the compiled binary 29 | 1. Double-click to mount the image file 30 | 1. Drag the `spaces-util` onto the `Binaries Folder` 31 | 1. Enter your password if prompted (The `Binaries Folder` link points to your 32 | `/usr/local/bin`, which you may require Administrator rights to write to. 33 | 1. After copying, eject the disk image, which can now be deleted. 34 | 1. If you do not want to install the program globally, copy it to somewhere 35 | in your local `$PATH`. 36 | 37 | ## Compilation 38 | 39 | Compile with: 40 | 41 | `$ make` 42 | 43 | and run with: 44 | 45 | `$ ./spaces-util [options]` 46 | 47 | or copy it to somewhere in your `$PATH`. There is currently no install target 48 | in the Makefile. 49 | 50 | ## Usage & Options 51 | 52 | If you have copied the program to a directory in your `$PATH`, you can invoke it 53 | with simply `$ spaces-util [options]`. Otherwise, you must navigate to the 54 | compile or copy directory, or invoke it with a full path. 55 | 56 | * `-a` animates space transition (only when `-s` is used) 57 | * `-q` quiet mode, prints a single number with the current space ID 58 | * `-qq` prints no output at all. Only useful with `-r` or `-s` 59 | * `-r` encodes the space ID in the process return value 60 | * `-n` returns the dimensions of the Spaces configuration 61 | * `-s ` sets the current space to ``. Any 'fetch' configuring 62 | arguments are ignored when used in conjunction with `-s` 63 | 64 | ### Fetching Spaces ID 65 | 66 | The program should terminate immediately, printing a string to `STDOUT` 67 | which matches the regex `/^Current Space ID: \d+$/`. If you pass the 68 | `-r` option, the return value of the program is set to the current Space number 69 | for ease of use in scripting. 70 | 71 | For example, in Bash: `$ ./spaces-util -r -qq; echo $?` will print the Space 72 | number apps. If the space cannot be determined for any reason, it will be 73 | returned as -1 (255). 74 | 75 | If you call spaces-util with the `-q` option, it will enter quiet mode and 76 | only print the space number (matching `/^\d+$/`). The silent version (`-qq`) 77 | will print nothing to `STDOUT`, and any error messages will be printed to 78 | `STDERR`. Silent is generally only useful if you're switching Spaces, or 79 | planning to use the return value. 80 | 81 | ### Fetching Spaces Dimensions 82 | 83 | The `-n` flag returns one of two strings, depending on whether quiet mode 84 | is enabled. If quiet is enabled, it will print `/^\dx\d$/`, where each 85 | number is a single digit in the range 1 .. 4. 86 | 87 | If quiet is not enabled, the output is more verbose: 88 | `/^Spaces dimensions: \d rows, \d columns$/` 89 | 90 | In silent mode, nothing is printed at all. 91 | 92 | The return value is the current Space number (if using `-r`), or 0 for success. 93 | It is set to -1 (255) for any failure. 94 | 95 | ### Setting Spaces ID 96 | 97 | spaces-util now supports switching to other Spaces via the `-s` option. 98 | Specifying `-s ` on its own will switch directly to that space, without 99 | any transitional animation. The `-a` (animate) flag enables the usual "slide" 100 | transition effect. 101 | 102 | Quiet and silent mode can be used with with `-s` option, otherwise, it will 103 | print a message `/^Switched to \d+/` to `STDOUT`, or an error message to 104 | `STDERR` if it fails. 105 | 106 | The return value is **NOT** set to the Space number when using the set feature, 107 | it is either 0 (for successful operation), or -1 (generally represented as 255) 108 | on error. 109 | 110 | Attempting to set the Space number beyond the number of spaces available will 111 | result in an error. Spaces are numbered from 1 (top-left), to _N_, where _N_ is 112 | _rows_ * _columns_ of your Spaces setup. 113 | 114 | ## Caveats 115 | 116 | I have no idea what happens if Spaces are disabled, or if you manage to invoke 117 | it from the Spaces Expose screen, or other weird edge cases. Submissions 118 | welcomed. It uses a couple of undocumented or private API calls, so there's 119 | no guarantee it'll work in future versions of OSX. 120 | 121 | It's survived fairly rigorous use on Leopard (10.5.8), and the fetch functions 122 | apparently work on Snow Leopard too. The setting functions there are as-yet 123 | untested. If you find it works/doesn't work, please leave me a message 124 | or add something to the 125 | [issues](https://github.com/shabble/osx-space-id/issues) 126 | page so I can update this statement with better data. 127 | 128 | Currently there's no binary available. If there's any interest, I can 129 | build a Universal Binary and add it here somewhere. Message me if you 130 | can't build it yourself. 131 | -------------------------------------------------------------------------------- /dmg-template.dmg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shabble/osx-space-id/734c44b1089045b933f8d689428934dce7db4000/dmg-template.dmg -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * main.c 3 | * spacefinder 4 | * 5 | * + Created by shabble on 04/03/2010. 6 | * + Added animated transition, thanks to Denis Gryzlov, 7 | * and refactored to use getopt, 20/6/2011. 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | */ 28 | 29 | #import 30 | #import 31 | 32 | #include 33 | #include 34 | #include "CGSPrivate.h" 35 | 36 | /* definitions for a couple of useful functions, from 37 | * http://meeu.me/blog/dashboard-expose-spaces 38 | */ 39 | 40 | void CoreDockSendNotification(NSString *notificationName); 41 | BOOL CoreDockGetWorkspacesEnabled(); 42 | void CoreDockGetWorkspacesCount(int *rows, int *columns); 43 | 44 | void set_space(long space, BOOL animate) { 45 | 46 | if (animate) { 47 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 48 | /* code for animated switching found here: 49 | http://meeu.me/blog/dashboard-expose-spaces 50 | */ 51 | NSString *noteName = @"com.apple.switchSpaces"; 52 | NSString *spaceID = [NSString stringWithFormat:@"%li", space - 1]; 53 | NSNotificationCenter *dCenter = [NSDistributedNotificationCenter defaultCenter]; 54 | [dCenter postNotificationName:noteName object:spaceID]; 55 | 56 | [pool drain]; 57 | } else { 58 | CGSConnection conn = _CGSDefaultConnection(); 59 | CGSSetWorkspace(conn, space); 60 | } 61 | 62 | } 63 | 64 | int get_space_via_keywin() { 65 | CFArrayRef winList = 66 | CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); 67 | 68 | int len = CFArrayGetCount(winList); 69 | int i, num = 0; 70 | for (i = 0; i < len; i++) { 71 | CFDictionaryRef winDict = CFArrayGetValueAtIndex(winList, i); 72 | if (CFDictionaryContainsKey(winDict, kCGWindowWorkspace)) { 73 | const void *thing = CFDictionaryGetValue(winDict, kCGWindowWorkspace); 74 | CFNumberRef numRef = (CFNumberRef)thing; 75 | CFNumberGetValue(numRef, kCFNumberIntType, &num); 76 | return num; 77 | } 78 | } 79 | return -1; 80 | } 81 | 82 | void show_help_and_exit() { 83 | fprintf(stderr, "%s\n", 84 | "spacefinder: [-a -q -r -s ]\n" 85 | " -a animates space transition (only when -s is used)\n" 86 | " -q quiet mode, prints a single number with the current space ID\n" 87 | " -qq prints no output at all. Only useful with -r or -s\n" 88 | " -r encodes the space ID in the process return value\n" 89 | " -n returns the dimensions of the Spaces configuration\n" 90 | " -s sets the current space to . Reading args are not used\n" 91 | " in conjunction with -s" 92 | 93 | ); 94 | exit(0); 95 | } 96 | 97 | int main(int argc, char* argv[]) { 98 | 99 | BOOL quiet_mode = NO; 100 | BOOL silent_mode = NO; 101 | BOOL animate_transition = NO; 102 | BOOL use_return_value = NO; 103 | BOOL should_set_space = NO; 104 | BOOL display_dimensions = NO; 105 | 106 | int c; 107 | 108 | int rows = 0; 109 | int cols = 0; 110 | int num_spaces = 0; 111 | long space = -1; 112 | 113 | if (!CoreDockGetWorkspacesEnabled()) { 114 | fprintf(stdout, "Spaces is not enabled\n"); 115 | return -1; 116 | } 117 | 118 | CoreDockGetWorkspacesCount(&rows, &cols); 119 | num_spaces = rows * cols; 120 | 121 | while ((c = getopt(argc, argv, "hnrqas:")) != -1) { 122 | switch(c) { 123 | case 'h': 124 | show_help_and_exit(); 125 | break; 126 | 127 | case 'n': 128 | display_dimensions = YES; 129 | break; 130 | case 'r': 131 | use_return_value = YES; 132 | break; 133 | case 'q': 134 | /* seen for the 2nd time */ 135 | if (quiet_mode == YES) { 136 | silent_mode = YES; 137 | } 138 | quiet_mode = YES; 139 | break; 140 | case 'a': 141 | animate_transition = YES; 142 | break; 143 | case 's': 144 | if (strlen(optarg)) { 145 | should_set_space = YES; 146 | space = strtol(optarg, NULL, 10); 147 | } else { 148 | fprintf (stderr, 149 | "Space number %s could not be parsed into a number", 150 | optarg); 151 | return -1; 152 | } 153 | break; 154 | case '?': 155 | if (!isprint(optopt)) { 156 | fprintf(stderr, 157 | "Unknown option character `\\x%x'.\n", 158 | optopt); 159 | } 160 | return -1; 161 | } 162 | } 163 | 164 | /* printf("Args: dims:%d set:%d quiet:%d silent:%d ret:%d\n", 165 | display_dimensions, should_set_space, quiet_mode, 166 | silent_mode, use_return_value); */ 167 | 168 | if (space > 0 && should_set_space) { /* set space */ 169 | if (space > num_spaces) { 170 | fprintf(stderr, "Cannot switch beyond maximum number of spaces\n"); 171 | return -1; 172 | } 173 | 174 | set_space(space, animate_transition); 175 | 176 | if (!quiet_mode) { 177 | fprintf(stdout, "Switched to %li\n", space); 178 | } 179 | 180 | return 0; 181 | 182 | } else { /* get space */ 183 | 184 | space = get_space_via_keywin(); 185 | 186 | if (display_dimensions) { 187 | if (rows > 0 && cols > 0) { 188 | 189 | if (quiet_mode) { 190 | if (!silent_mode) { 191 | fprintf(stdout, "%dx%d\n", rows, cols); 192 | } 193 | } else { 194 | fprintf(stdout, "Spaces dimensions: %d rows, %d columns\n", 195 | rows, cols); 196 | } 197 | } else { 198 | fprintf(stderr, "Could not determine the Spaces dimensions\n"); 199 | return -1; 200 | } 201 | } else { 202 | if (quiet_mode) { 203 | if (!silent_mode) { 204 | fprintf(stdout, "%li\n", space); 205 | } 206 | } else { 207 | fprintf(stdout, "Current Space ID: %li\n", space); 208 | } 209 | } 210 | 211 | if (use_return_value) { 212 | return space; 213 | } else { 214 | return 0; 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /make_dmg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -fex 4 | 5 | SRCROOT="${PWD}" 6 | 7 | dmg_template="${SRCROOT}/dmg-template.dmg" 8 | temp_dmg="${SRCROOT}/tmp.dmg" 9 | product_dmg="${SRCROOT}/Spaces-Utility.dmg" 10 | mnt_volume="/Volumes/Spaces-Utility" 11 | 12 | cp "${dmg_template}" "${temp_dmg}" 13 | chmod -R a+rwx "${temp_dmg}" 14 | 15 | device=$(hdiutil attach -readwrite -noverify -noautoopen "${temp_dmg}" | \ 16 | egrep '^/dev/' | sed 1q | awk '{print $1}') 17 | sleep 2 18 | cp "${SRCROOT}/spaces-util" "${mnt_volume}/" 19 | chmod -Rf a+r "${mnt_volume}" 20 | chmod -Rf go-w "${mnt_volume}" 21 | 22 | sync 23 | sync 24 | hdiutil detach ${device} 25 | 26 | if [[ -f "${product_dmg}" ]]; then 27 | rm "${product_dmg}" 28 | fi 29 | 30 | hdiutil convert "${temp_dmg}" -format UDZO \ 31 | -imagekey zlib-level=9 -o "${product_dmg}" 32 | 33 | rm "${temp_dmg}" 34 | echo "Complete!" 35 | 36 | #rm -rf "$dir" 37 | #mkdir "$dir" 38 | #cp -R "$BUILT_PRODUCTS_DIR/$PROJECT_NAME" "$dir" 39 | #cp -R "$SRCROOT/dmg-additional/" "$dir" 40 | #rm -f "$dmg" 41 | #hdiutil create -srcfolder "$dir" -volname "$PROJECT_NAME" "$dmg" 42 | #hdiutil internet-enable -yes "$dmg" 43 | #rm -rf "$dir" 44 | --------------------------------------------------------------------------------