I have a fairly simple question. How would I programmatically add/remove the workspaces found in mission control. I have seen this post here about changing to another space programmatically, and I think that it could be something similar to the answer, using CGSPrivate.h
. I don't need to worry about private frameworks, as it's not going on the app store.
EDIT: I also saw a post about modifying the com.apple.spaces.plist
and adding workspaces, but I have no Idea how I would add that, as the dict has UUID and other things.
While in Mission Control this is the Accessibility Hierarchy of Dock (on my Mac, OS X 10.10):
Role Position Title Value Description
AXList 632.000000, 1136.000000 (null) (null) (null)
AXDockItem 636.300049, 1138.000000 Finder (null) (null)
AXDockItem 688.300049, 1138.000000 Firefox (null) (null)
…
AXDockItem 1231.699951, 1138.000000 Trash (null) (null)
AXGroup 0.000000, 0.000000 (null) (null) (null)
AXGroup 20.000000, 227.000000 (null) (null) exposéd windows
AXList 0.000000, -2.000000 (null) (null) (null)
AXButton 592.000000, 20.000000 Desktop 1 (null) select Desktop 1
AXButton 864.000000, 20.000000 Desktop 2 (null) select Desktop 2
AXButton 1136.000000, 20.000000 Desktop 3 (null) select Desktop 3
AXButton 1824.000000, 20.000000 (null) (null) add desktop
The location of the workspace buttons is the middle of the remove button.
My test app:
- (AXUIElementRef)copyAXUIElementFrom:(AXUIElementRef)theContainer role:(CFStringRef)theRole atIndex:(NSInteger)theIndex {
AXUIElementRef aResultElement = NULL;
CFTypeRef aChildren;
AXError anAXError = AXUIElementCopyAttributeValue(theContainer, kAXChildrenAttribute, &aChildren);
if (anAXError == kAXErrorSuccess) {
NSUInteger anIndex = -1;
for (id anElement in (__bridge NSArray *)aChildren) {
if (theRole) {
CFTypeRef aRole;
anAXError = AXUIElementCopyAttributeValue((__bridge AXUIElementRef)anElement, kAXRoleAttribute, &aRole);
if (anAXError == kAXErrorSuccess) {
if (CFStringCompare(aRole, theRole, 0) == kCFCompareEqualTo)
anIndex++;
CFRelease(aRole);
}
}
else
anIndex++;
if (anIndex == theIndex) {
aResultElement = (AXUIElementRef)CFRetain((__bridge CFTypeRef)(anElement));
break;
}
}
CFRelease(aChildren);
}
return aResultElement;
}
- (IBAction)addWorkspace:(id)sender {
if (!AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)@{(__bridge NSString *)kAXTrustedCheckOptionPrompt:@YES}))
return;
// type control-arrow-up
CGEventRef anEvent = CGEventCreateKeyboardEvent(NULL, 0x7E, true);
CGEventSetFlags(anEvent, kCGEventFlagMaskControl);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
[NSThread sleepForTimeInterval:0.05];
anEvent = CGEventCreateKeyboardEvent(NULL, 0x7E, false);
CGEventSetFlags(anEvent, kCGEventFlagMaskControl);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
[NSThread sleepForTimeInterval:0.05];
// option down
anEvent = CGEventCreateKeyboardEvent(NULL, 0x3A, true);
CGEventPost(kCGHIDEventTap, anEvent);
[NSThread sleepForTimeInterval:0.05];
// click on the + button
NSArray *anArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"];
AXUIElementRef anAXDockApp = AXUIElementCreateApplication([[anArray objectAtIndex:0] processIdentifier]);
CFTypeRef aGroup = [self copyAXUIElementFrom:anAXDockApp role:kAXGroupRole atIndex:0];
CFTypeRef aButton = [self copyAXUIElementFrom:aGroup role:kAXButtonRole atIndex:0];
CFRelease(aGroup);
if (aButton) {
AXError anAXError = AXUIElementPerformAction(aButton, kAXPressAction);
CFRelease(aButton);
}
// option up
anEvent = CGEventCreateKeyboardEvent(NULL, 0x3A, false);
CGEventPost(kCGHIDEventTap, anEvent);
[NSThread sleepForTimeInterval:0.05];
// type escape
anEvent = CGEventCreateKeyboardEvent(NULL, 0x35, true);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
[NSThread sleepForTimeInterval:0.05];
anEvent = CGEventCreateKeyboardEvent(NULL, 0x35, false);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
}
- (IBAction)removeWorkspace:(id)sender {
if (!AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)@{(__bridge NSString *)kAXTrustedCheckOptionPrompt:@YES}))
return;
// type control-arrow-up
CGEventRef anEvent = CGEventCreateKeyboardEvent(NULL, 0x7E, true);
CGEventSetFlags(anEvent, kCGEventFlagMaskControl);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
[NSThread sleepForTimeInterval:0.05];
anEvent = CGEventCreateKeyboardEvent(NULL, 0x7E, false);
CGEventSetFlags(anEvent, kCGEventFlagMaskControl);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
[NSThread sleepForTimeInterval:0.05];
// move mouse to the top of the screen
CGPoint aPoint;
aPoint.x = 10.0;
aPoint.y = 10.0;
anEvent = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, aPoint, 0);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
[NSThread sleepForTimeInterval:0.05];
// option down
anEvent = CGEventCreateKeyboardEvent(NULL, 0x3A, true);
CGEventPost(kCGHIDEventTap, anEvent);
[NSThread sleepForTimeInterval:0.05];
// option down
anEvent = CGEventCreateKeyboardEvent(NULL, 0x3A, true);
CGEventPost(kCGHIDEventTap, anEvent);
[NSThread sleepForTimeInterval:0.05];
// click at the location of the workspace
NSArray *anArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"];
AXUIElementRef anAXDockApp = AXUIElementCreateApplication([[anArray objectAtIndex:0] processIdentifier]);
CFTypeRef aGroup = [self copyAXUIElementFrom:anAXDockApp role:kAXGroupRole atIndex:0];
CFTypeRef aList = [self copyAXUIElementFrom:aGroup role:kAXListRole atIndex:0];
CFRelease(aGroup);
CFTypeRef aButton = [self copyAXUIElementFrom:aList role:kAXButtonRole atIndex:1]; // index of the workspace
CFRelease(aList);
if (aButton) {
CFTypeRef aPosition;
AXError anAXError = AXUIElementCopyAttributeValue(aButton, kAXPositionAttribute, &aPosition);
if (anAXError == kAXErrorSuccess) {
AXValueGetValue(aPosition, kAXValueCGPointType, &aPoint);
CFRelease(aPosition);
// click
anEvent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, aPoint, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
[NSThread sleepForTimeInterval:0.05];
anEvent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp, aPoint, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
[NSThread sleepForTimeInterval:0.05];
CFRelease(aButton);
}
}
// option up
anEvent = CGEventCreateKeyboardEvent(NULL, 0x3A, false);
CGEventPost(kCGHIDEventTap, anEvent);
[NSThread sleepForTimeInterval:0.05];
// type escape
anEvent = CGEventCreateKeyboardEvent(NULL, 0x35, true);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
[NSThread sleepForTimeInterval:0.05];
anEvent = CGEventCreateKeyboardEvent(NULL, 0x35, false);
CGEventPost(kCGHIDEventTap, anEvent);
CFRelease(anEvent);
}