题
我试图在 OS X 上找到记录的(或者未记录的,如果这是我唯一的选择)API,以从窗口服务器查询窗口列表,然后使窗口移动和调整大小。有人能指出我正确的方向吗?我想我会从 Win32 下的 FindWindowEx 和 MoveWindow 之类的东西开始。
请注意,我想从外部进程执行此操作 - 我不是问如何控制我自己的应用程序的窗口大小和位置。
解决方案
使用辅助功能 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 在评论中询问如何获取所有窗口的列表,具体方法如下:
您可以使用“kAXWindowsAttribute”代替“kAXFocusedWindowAttribute”作为 AXUIElementCopyAttributeValue 函数。结果不是 AXUIElementRef,而是一个 AXUIElementRef 元素的 CFArray,该应用程序的每个窗口都有一个。
其他提示
我同意无障碍是前进的最佳方式。但是如果你想要快速和肮脏,AppleScript也会起作用。