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

时间:2009-03-05 10:06:06

标签: windows macos layout

我正在尝试在OS X上找到文档(或者,未记录的,如果这是我唯一的选项)API,从窗口服务器查询窗口列表,然后使窗口移动和调整大小。谁能指出我正确的方向?我想我会从Win32下的FindWindowEx和MoveWindow开始。

请注意,我想从外部进程执行此操作 - 我不会问如何控制我自己的应用程序的窗口大小和位置。

2 个答案:

答案 0 :(得分:46)

使用辅助功能API。使用此API,您可以连接到进程,获取窗口列表(实际上是数组),获取每个窗口的位置和大小,还可以根据需要更改窗口属性。

但是,如果用户在其偏好设置(系统首选项 - >通用访问权限)中启用了辅助设备访问权限,则应用程序只能使用此API,在这种情况下,所有应用程序都可以使用此API,或者如果您的应用程序是一个受信任的配对应用程序(当它受信任时,它可以使用API​​,即使未选中此选项)。 Accessibility API本身提供了使您的应用程序可信的必要功能 - 基本上您必须成为root(使用安全服务来请求用户的root权限),然后将您的进程标记为受信任。一旦您的应用程序被标记为受信任,它必须重新启动,因为受信任状态仅在启动时检查,并且在应用程序运行时无法更改。信任状态是永久性的,除非用户将应用程序移动到其他地方或者应用程序二进制文件的散列改变(例如,在更新之后)。如果用户在其首选项中启用了辅助设备,则会将所有应用程序视为受信任。通常你的应用程序会检查是否启用了此选项,如果是,请继续并执行您的操作。如果没有,它会检查它是否已被信任,如果是,再次只是做你的东西。如果没有尝试使自己受信任,然后重新启动应用程序,除非用户拒绝root授权。 API提供了检查所有这些功能所需的所有功能。

使用Mac OS窗口管理器存在私有函数来执行相同的操作,但是唯一的优势就是您不需要成为受信任的辅助功能应用程序(这是首次启动时的一次性操作)大多数情况下)。缺点是这个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询问您如何获得评论中所有窗口的列表,具体如下:

而不是“kAXFocusedWindowAttribute”,而是使用“kAXWindowsAttribute”作为AXUIElementCopyAttributeValue函数。结果是没有AXUIElementRef,而是AXUIElementRef元素的CFArray,一个用于此应用程序的每个窗口。

答案 1 :(得分:2)

我同意无障碍是前进的最佳方式。但是如果你想要快速和肮脏,AppleScript也会起作用。