在OS X中的窗口移动和调整大小API

29

我正在尝试查找OS X上记录的(或者未记录的,如果这是我的唯一选择)API,以从窗口服务器查询窗口列表,然后使窗口移动和调整大小。有人可以指导我吗?我想我需要从类似于Win32下的FindWindowEx和MoveWindow开始。

请注意,我希望从外部进程中执行此操作-我不仅仅是要控制自己应用程序的窗口大小和位置。

2个回答

50

使用无障碍 API。通过此 API,您可以连接到一个进程,获取窗口列表(实际上是一个数组),获取每个窗口的位置和大小,以及在需要时更改窗口属性。

但是,只有在用户在偏好设置中启用辅助设备访问权限(系统偏好设置->通用访问)时,应用程序才能使用此 API;在这种情况下,所有应用程序都可以使用此 API,或者如果您的应用程序是受信任的辅助应用程序,则可以使用 API,即使未选中此选项也是如此。无障碍 API 本身提供了必要的功能来使您的应用程序变得可信-基本上,您必须成为 root(使用安全服务请求用户的 root 权限),然后将您的进程标记为可信。标记为可信后,必须重新启动应用程序,因为可信状态仅在启动时进行检查,并且无法在应用程序运行时更改。信任状态是永久的,除非用户将应用程序移动到其他位置或应用程序二进制文件的哈希值发生更改(例如,在更新后)。如果用户在其 prefs 中启用了辅助设备,则所有应用程序都被视为已受信任。通常,您的应用程序会检查是否已启用此选项,如果已启用,则继续执行操作。如果没有,则会检查是否已经受信任,如果是,则只需执行您的操作。如果没有,请尝试使自己变得可信,然后重新启动应用程序,除非用户拒绝了 root 授权。API 提供了所有必要的功能来检查所有这些。

使用 Mac OS 窗口管理器存在私有函数可以实现相同的功能,但唯一的优点是您不需要成为受信任的辅助应用程序(在大多数情况下,这是一次性操作)。缺点是此 API 可能随时更改(它已经在过去发生过更改),它所有的未记录和功能只能通过反向工程获得。无障碍 API 是公开的、有文档的,并且自首个引入它的 OS X 版本以来几乎没有更改(10.4 和 10.5 中添加了一些新功能,但其他内容没有太大改变)。

以下是一个代码示例。它将等待 5 秒钟,以便您在进行其他操作之前切换到另一个窗口(否则它将始终使用终端窗口,对测试不太有趣)。然后它会获取最前面的进程,该进程中最前面的窗口,打印其位置和大小,最后将其向右移动 25 个像素。在命令行上编译它的方式如下(假设它命名为 test.c)

gcc -framework Carbon -o test test.c

请注意,为了简单起见,我没有在代码中执行任何错误检查(如果某些地方出现问题,程序可能会崩溃,并且某些事情可能会出错)。以下是代码:
/* Carbon includes everything necessary for Accessibilty API */
#include <Carbon/Carbon.h>

static bool amIAuthorized ()
{
    if (AXAPIEnabled() != 0) {
        /* Yehaa, all apps are authorized */
        return true;
    }
    /* Bummer, it's not activated, maybe we are trusted */
    if (AXIsProcessTrusted() != 0) {
        /* Good news, we are already trusted */
        return true;
    }
    /* Crap, we are not trusted...
     * correct behavior would now be to become a root process using
     * authorization services and then call AXMakeProcessTrusted() to make
     * ourselves trusted, then restart... I'll skip this here for
     * simplicity.
     */
    return false;
}


static AXUIElementRef getFrontMostApp ()
{
    pid_t pid;
    ProcessSerialNumber psn;
    
    GetFrontProcess(&psn);
    GetProcessPID(&psn, &pid);
    return AXUIElementCreateApplication(pid);
}
    

int main (
    int argc,
    char ** argv
) {
    int i;
    AXValueRef temp;
    CGSize windowSize;
    CGPoint windowPosition;
    CFStringRef windowTitle;
    AXUIElementRef frontMostApp;
    AXUIElementRef frontMostWindow;
        
    if (!amIAuthorized()) {
        printf("Can't use accessibility API!\n");
        return 1;
    }
    
    /* Give the user 5 seconds to switch to another window, otherwise
     * only the terminal window will be used
     */
    for (i = 0; i < 5; i++) {
        sleep(1);
        printf("%d", i + 1);
        if (i < 4) {
            printf("...");
            fflush(stdout);
        } else {
            printf("\n");
        }
    }
    
    /* Here we go. Find out which process is front-most */
    frontMostApp = getFrontMostApp();
    
    /* Get the front most window. We could also get an array of all windows
     * of this process and ask each window if it is front most, but that is
     * quite inefficient if we only need the front most window.
     */
    AXUIElementCopyAttributeValue(
        frontMostApp, kAXFocusedWindowAttribute, (CFTypeRef *)&frontMostWindow
    );
    
    /* Get the title of the window */
    AXUIElementCopyAttributeValue(
        frontMostWindow, kAXTitleAttribute, (CFTypeRef *)&windowTitle
    );
    
    /* Get the window size and position */
    AXUIElementCopyAttributeValue(
        frontMostWindow, kAXSizeAttribute, (CFTypeRef *)&temp
    );
    AXValueGetValue(temp, kAXValueCGSizeType, &windowSize);
    CFRelease(temp);
    
    AXUIElementCopyAttributeValue(
        frontMostWindow, kAXPositionAttribute, (CFTypeRef *)&temp
    );
    AXValueGetValue(temp, kAXValueCGPointType, &windowPosition);
    CFRelease(temp);

    /* Print everything */
    printf("\n");
    CFShow(windowTitle);
    printf(
        "Window is at (%f, %f) and has dimension of (%f, %f)\n",
        windowPosition.x,
        windowPosition.y,
        windowSize.width,
        windowSize.height
    );
    
    /* Move the window to the right by 25 pixels */
    windowPosition.x += 25;
    temp = AXValueCreate(kAXValueCGPointType, &windowPosition);
    AXUIElementSetAttributeValue(frontMostWindow, kAXPositionAttribute, temp);
    CFRelease(temp);
    
    /* Clean up */
    CFRelease(frontMostWindow);
    CFRelease(frontMostApp);
    return 0;
}

当Sine Ben在评论中询问如何获取所有窗口的列表时,以下是方法:

使用kAXWindowsAttribute替代kAXFocusedWindowAttribute作为AXUIElementCopyAttributeValue函数的参数。结果不再是一个AXUIElementRef,而是一个CFArray数组,其中包含该应用程序的每个窗口的AXUIElementRef元素。


1
@Ben:在AXUIElementCopyAttributeValue函数中,你可以使用"kAXWindowsAttribute"代替"kAXFocusedWindowAttribute"。结果将不再是一个AXUIElementRef,而是一个CFArray,其中包含此应用程序的每个窗口的AXUIElementRef元素。 - Mecki
不幸的是,这个方法已经无法在OSX 10.9上工作了,因为AXMakeProcessTrusted已经被弃用。请参见https://dev59.com/92Mm5IYBdhLWcg3wVdx5#24100593和https://dev59.com/-GjWa4cB1Zd3GeqPqnBx#24100470。 - fikovnik
感谢 @Mecki 的回答。使用辅助功能 API,是否可以获取并更改全屏应用程序/空间的顺序?谢谢! - sethfri
@sethfri 你最好在SO上提出这个问题,这样更容易回答并且能够吸引更多关注。一定要解释清楚你想要实现什么,因为有时候可能间接地实现,但不是你想要的方式。 - Mecki
@mecki 我确实发了,只是还没有得到任何回复,所以我去寻找其他问题 :( https://stackoverflow.com/questions/67330562/programmatically-change-order-of-full-screen-apps-on-macos-with-appkit - sethfri
显示剩余3条评论

2

我认为无障碍性是未来的趋势。但如果您需要快速且简单的解决方案,AppleScript也可以胜任。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接