【发布时间】:2010-10-11 11:32:31
【问题描述】:
我试图在 OS X 上找到已记录(或未记录,如果这是我唯一的选择)的 API,以从窗口服务器查询窗口列表,然后使窗口移动和调整大小。谁能指出我正确的方向?我想我会从 Win32 下的 FindWindowEx 和 MoveWindow 之类的东西开始。
请注意,我想从外部进程执行此操作 - 我不是在询问如何仅控制我自己的应用的窗口大小和位置。
【问题讨论】:
我试图在 OS X 上找到已记录(或未记录,如果这是我唯一的选择)的 API,以从窗口服务器查询窗口列表,然后使窗口移动和调整大小。谁能指出我正确的方向?我想我会从 Win32 下的 FindWindowEx 和 MoveWindow 之类的东西开始。
请注意,我想从外部进程执行此操作 - 我不是在询问如何仅控制我自己的应用的窗口大小和位置。
【问题讨论】:
使用辅助功能 API。使用此 API,您可以连接到进程,获取窗口列表(实际上是一个数组),获取每个窗口的位置和大小,还可以根据需要更改窗口属性。
但是,如果用户在其首选项(系统偏好设置 -> 通用访问)中启用了对辅助设备的访问权限,则应用程序只能使用此 API,在这种情况下,所有应用程序都可以使用此 API,或者如果您的应用程序是受信任的辅助应用程序(当它受信任时,即使未选中此选项,它也可以使用 API)。 Accessibility API 本身提供了使您的应用程序受信任的必要功能 - 基本上您必须成为 root(使用安全服务请求用户的 root 权限),然后将您的进程标记为受信任。一旦您的应用程序被标记为受信任,它必须重新启动,因为受信任状态仅在启动时检查,并且在应用程序运行时无法更改。信任状态是永久的,除非用户将应用程序移动到其他地方或应用程序二进制文件的哈希值发生变化(例如在更新之后)。如果用户在其首选项中启用了辅助设备,则所有应用程序都被视为受信任。通常,您的应用程序会检查此选项是否已启用,如果已启用,请继续执行您的操作。如果没有,它会检查它是否已经被信任,如果是,再次做你的事情。如果不尝试使自己受信任,然后重新启动应用程序,除非用户拒绝 root 授权。 API 提供了所有必要的功能来检查所有这些。
存在使用 Mac OS 窗口管理器执行相同操作的私有功能,但唯一可以为您带来的好处是您不需要成为受信任的辅助功能应用程序(这是在第一次启动时一次性操作大多数情况下)。缺点是此 API 可能随时更改(过去已经更改),所有这些都没有文档记录,功能只能通过逆向工程了解。然而,Accessibility 是公开的,它被记录在案并且自第一个引入它的 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 询问如何获得 cmets 中所有窗口的列表,方法如下:
您使用kAXWindowsAttribute 代替kAXFocusedWindowAttribute 来实现AXUIElementCopyAttributeValue 函数。结果不是AXUIElementRef,而是CFArray 的AXUIElementRef 元素,每个应用程序的窗口一个。
【讨论】:
AXMakeProcessTrusted 已被弃用。见stackoverflow.com/questions/17693408/… 和stackoverflow.com/questions/12419988/…
Apple Menu -> System Preferences -> Security & Privacy -> Privacy -> Accessibility。请注意您现在如何按应用单独控制它。过去,您只能启用/禁用该系统范围(所有应用程序或无应用程序),但现在应用程序可以请求此访问权限之王,用户可以授予或不授予它。将此授予应用程序与之前使进程 AX 受信任具有相同的效果,API 刚刚被用户可控设置所取代,恕我直言,这更有意义。
我同意辅助功能是最好的前进方式。但是,如果您想要快速而肮脏,AppleScript 也可以。
【讨论】: