以编程方式将工作区添加/删除到mac

时间:2016-01-25 23:47:01

标签: macos cocoa workspace mission-control

我有一个相当简单的问题。我将如何以编程方式添加/删除任务控制中找到的工作空间。我已经看过这篇文章here关于以编程方式更改到另一个空格,我认为它可能与答案类似,使用CGSPrivate.h。我不需要担心私有框架,因为它不在应用程序商店中。

编辑:我还看到一篇关于修改com.apple.spaces.plist和添加工作区的帖子,但我不知道如何添加它,因为dict有UUID和其他东西。

3 个答案:

答案 0 :(得分:2)

在Mission Control中,这是Dock的辅助功能层次结构(在我的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

工作区按钮的位置是删除按钮的中间位置。

我的测试应用:

- (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);
}

答案 1 :(得分:1)

Willeke类似,经过数小时的代码后我才能完成此任务。这是我的代码,然后我将解释它对未来遇到此问题的人的作用。

In .h

My code is in AppDelegate (it is a menubar app).

@interface AppDelegate : NSObject <NSApplicationDelegate>
{
    ...  
    // Workspace mutations vars

    NSInteger workspacesToRemove; // Used in removing workspaces (as 

loop)
    }

// Define constants for sizes

#define kWORKSPACE_WIDTH 145

#define kWORKSPACE_HEIGHT 90

#define kWORKSPACE_SPACING 30

In .m

- (void)removeAllWorkspaces
{
    NSDictionary *spacesPlist = [NSDictionary dictionaryWithContentsOfFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Preferences/com.apple.spaces.plist"]];

    NSDictionary *spacesDisplayConfig = [spacesPlist objectForKey:[[spacesPlist allKeys] objectAtIndex:0]];

    NSArray *spaceProperties = [spacesDisplayConfig objectForKey:@"Space Properties"];

    NSInteger numberOfWorkspaces = [spaceProperties count];

    NSLog(@"Number of workspaces: %ld", (long)numberOfWorkspaces);

    // Set counter


    workspacesToRemove = numberOfWorkspaces;

    [self openMissionControl];
}
#pragma mark Open/Close step methods

- (void)openMissionControl
{
    CGEventSourceRef src =
    CGEventSourceCreate(kCGEventSourceStateHIDSystemState);

    CGEventRef cntd = CGEventCreateKeyboardEvent(src, 0x3B, YES);
    CGEventRef cntu = CGEventCreateKeyboardEvent(src, 0x3B, NO);
    CGEventRef upd = CGEventCreateKeyboardEvent(src, 0x7E, YES);
    CGEventRef upu = CGEventCreateKeyboardEvent(src, 0x7E, NO);
    /*

    */
    CGEventSetFlags(upd, kCGEventFlagMaskControl);
    CGEventSetFlags(upu, kCGEventFlagMaskControl);

    CGEventTapLocation loc = kCGHIDEventTap; // kCGSessionEventTap also works
    CGEventPost(loc, cntd);
    CGEventPost(loc, upd);
    CGEventPost(loc, upu);
    CGEventPost(loc, cntu);

    CFRelease(cntd);
    CFRelease(cntu);

    CFRelease(upd);
    CFRelease(upu);


    [self performSelector:@selector(moveMouseToUpdateMissionControl) withObject:nil afterDelay:1];
}

- (void)moveMouseToUpdateMissionControl
{
    CGEventPost(kCGHIDEventTap, CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, CGPointMake([[NSScreen mainScreen] frame].size.width - 10, 10), kCGMouseButtonLeft));

    [self performSelector:@selector(moveMouseToCloseRightmostWorkspace) withObject:nil afterDelay:1];
}

- (void)moveMouseToCloseRightmostWorkspace
{
    NSRect workspaceRect = [self rectForWorkspaces];

    NSInteger closeX = (workspaceRect.origin.x + workspaceRect.size.width) - kWORKSPACE_WIDTH;

    CGPoint closePoint = CGPointMake(closeX, workspaceRect.origin.y);

    // Move mouse to point

    CGEventRef mouseMove = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, closePoint, kCGMouseButtonLeft);

    CGEventPost(kCGHIDEventTap, mouseMove);

    CFRelease(mouseMove);

    // Click

    [self performSelector:@selector(clickMouseAtPoint:) withObject:[NSValue valueWithPoint:closePoint] afterDelay:2]; // Must be equal or greater 1.5
}

- (void)clickMouseAtPoint:(NSValue *)pointValue
{
    CGPoint clickPoint = [pointValue pointValue];

    // Click

    CGEventPost(kCGHIDEventTap, CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, clickPoint, kCGMouseButtonLeft));

    CGEventPost(kCGHIDEventTap, CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp, clickPoint, kCGMouseButtonLeft));
    workspacesToRemove--;
    NSLog(@"%ld", (long)workspacesToRemove);
    if (workspacesToRemove > 1) {

        [self performSelector:@selector(moveMouseToCloseRightmostWorkspace) withObject:nil afterDelay:2];
    } else {

        [self performSelector:@selector(closeMissionControl) withObject:nil afterDelay:1];
    }

}

- (void)closeMissionControl
{
    CGEventSourceRef src =
    CGEventSourceCreate(kCGEventSourceStateHIDSystemState);

    CGEventRef cntd = CGEventCreateKeyboardEvent(src, 0x3B, YES);
    CGEventRef cntu = CGEventCreateKeyboardEvent(src, 0x3B, NO);
    CGEventRef upd = CGEventCreateKeyboardEvent(src, 0x7E, YES);
    CGEventRef upu = CGEventCreateKeyboardEvent(src, 0x7E, NO);

    CGEventSetFlags(upd, kCGEventFlagMaskControl);
    CGEventSetFlags(upu, kCGEventFlagMaskControl);

    CGEventTapLocation loc = kCGHIDEventTap; // kCGSessionEventTap also works
    CGEventPost(loc, cntd);
    CGEventPost(loc, upd);
    CGEventPost(loc, upu);
    CGEventPost(loc, cntu);

    CFRelease(cntd);
    CFRelease(cntu);

    CFRelease(upd);
    CFRelease(upu);
}

#pragma mark

#pragma mark Adding Workspaces

- (void)openWorkspaces:(NSInteger)numberToOpen
{
    // Open Mission control

    CGEventSourceRef src =
    CGEventSourceCreate(kCGEventSourceStateHIDSystemState);

    CGEventRef cntd = CGEventCreateKeyboardEvent(src, 0x3B, YES);
    CGEventRef cntu = CGEventCreateKeyboardEvent(src, 0x3B, NO);
    CGEventRef upd = CGEventCreateKeyboardEvent(src, 0x7E, YES);
    CGEventRef upu = CGEventCreateKeyboardEvent(src, 0x7E, NO);
    /*

     */
    CGEventSetFlags(upd, kCGEventFlagMaskControl);
    CGEventSetFlags(upu, kCGEventFlagMaskControl);

    CGEventTapLocation loc = kCGHIDEventTap; // kCGSessionEventTap also works
    CGEventPost(loc, cntd);
    CGEventPost(loc, upd);
    CGEventPost(loc, upu);
    CGEventPost(loc, cntu);

    [NSThread sleepForTimeInterval:2];

    // Move mouse to point

    CGEventRef mouseMove = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, CGPointMake([[NSScreen mainScreen] frame].size.width - 10, 10), kCGMouseButtonLeft);

    CGEventPost(kCGHIDEventTap, mouseMove);

    CFRelease(mouseMove);

    for (NSInteger i = 0; i < numberToOpen; i++) {

        // Add as many times as needed

        CGEventPost(kCGHIDEventTap, CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, CGPointMake([[NSScreen mainScreen] frame].size.width - 10, 10), kCGMouseButtonLeft));

        CGEventPost(kCGHIDEventTap, CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp, CGPointMake([[NSScreen mainScreen] frame].size.width - 10, 10), kCGMouseButtonLeft));

        [NSThread sleepForTimeInterval:1];

    }

    CGEventPost(loc, cntd);
    CGEventPost(loc, upd);
    CGEventPost(loc, upu);
    CGEventPost(loc, cntu);

    CFRelease(cntd);
    CFRelease(cntu);

    CFRelease(upd);
    CFRelease(upu);
}

- (NSRect)rectForWorkspaces
{
    NSDictionary *spacesPlist = [NSDictionary dictionaryWithContentsOfFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Preferences/com.apple.spaces.plist"]];

    NSDictionary *spacesDisplayConfig = [spacesPlist objectForKey:[[spacesPlist allKeys] objectAtIndex:0]];

    NSArray *spaceProperties = [spacesDisplayConfig objectForKey:@"Space Properties"];

    NSInteger numberOfWorkspaces = [spaceProperties count];

    NSInteger totalSpacing = (numberOfWorkspaces - 1) * kWORKSPACE_SPACING;

    NSInteger totalLengthOfWorkspaces = numberOfWorkspaces * kWORKSPACE_WIDTH;

    NSInteger totalRectWidth = totalSpacing + totalLengthOfWorkspaces;

    NSRect workspaceRect = NSMakeRect(0, 0, totalRectWidth, kWORKSPACE_HEIGHT);

    // Calculate center x or screen

    NSInteger screenCenter = [[NSScreen mainScreen] frame].size.width / 2;

    workspaceRect.origin.x = screenCenter - (workspaceRect.size.width / 2);

    workspaceRect.origin.y = kWORKSPACE_SPACING;

    return workspaceRect;
}

现在让我们逐步完成代码

对于删除工作空间,第一种方法removeAllWorkspaces是起点。

此代码从com.apple.spaces.plist文件中获取打开的工作空间数,然后设置变量workspacesToRemove。这个变量对于循环很重要,因为当有方法链(我称之为方法链)时很难做for-loop

接下来,我通过CGEvents调用一种方法来打开任务控制。然后我将鼠标移动到屏幕的顶角,以确保工作区图标正确居中。

接下来,代码使用rectForWorkspaces方法确定最右边工作区的关闭按钮的位置。

这是一个非常简单的方法,但它是发生的事情的主要部分。

它计算了工作空间在任务控制中所处位置的矩形。这是一个代表它计算的图像: Rect calculation

然后我拿这个矩形,减去145(工作区图标宽度),然后在弹出时单击关闭按钮。

此部分循环,直到关闭所有工作空间(1除外)。

FWI:它被分成许多方法的原因是我可以循环回到特定的方法并在延迟后执行方法而不会阻塞线程。

Yay复杂的结束!

添加工作区要容易得多。

它只有一种方法(openWorkspaces:(NSInteger)numberToOpen),它打开任务控制,将鼠标移动到位置,并点击次数,直到添加了所有工作空间。非常简单。

答案 2 :(得分:0)

最近我能找到一个解决方案就是通过苹果脚本实现这一点 - 请参阅ѺȐeallү的答案,链接如下:

How can I programmatically add a space to mission control?