Подтвердить что ты не робот

Перемещение окон и изменение размера API в OS X

Я пытаюсь найти документированные (или недокументированные, если это единственный вариант) API в OS X, чтобы запросить список окон с оконного сервера, а затем заставить окна перемещаться и изменять размер. Может кто-то указать мне верное направление? Наверное, я начинаю с чего-то вроде FindWindowEx и MoveWindow под Win32.

Обратите внимание, что я хочу сделать это из внешнего процесса - я не спрашиваю, как управлять только моим размером и положением окна приложения.

4b9b3361

Ответ 1

Используйте API Accessibility. Используя этот API, вы можете подключиться к процессу, получить список окон (фактически массив), получить позиции и размеры каждого окна, а также изменить свойства окна, если хотите.

Однако приложение может использовать этот API только в том случае, если пользователь разрешил доступ к вспомогательным устройствам в своих предпочтениях (System Prefs → Universal Access), и в этом случае все приложения могут использовать этот API или если ваше приложение является (когда ему доверяют, он может использовать API, даже если этот параметр не проверен). Сам API Accessibility предлагает необходимые функции, чтобы сделать ваше приложение доверенным - в основном вы должны стать root (используя службы безопасности для запроса прав root для пользователя), а затем пометить ваш процесс как надежный. После того как ваше приложение было помечено как доверенное, его необходимо перезапустить, поскольку доверенное состояние проверяется только при запуске и не может быть изменено во время работы приложения. Состояние доверия является постоянным, если пользователь не перемещает приложение где-то еще или хэш двоичных изменений приложения (например, после обновления). Если у пользователя есть вспомогательные устройства, включенные в его привилегиях, все приложения обрабатываются так, как если бы им доверяли. Обычно ваше приложение проверяет, включен ли этот параметр, если он есть, продолжайте и делайте свои вещи. Если нет, он проверял бы, если он уже доверен, если это так, снова просто делайте свои вещи. Если вы не пытаетесь заставить себя доверять, а затем перезапустите приложение, если пользователь не отклонил авторизацию root. API предлагает все необходимые функции для проверки всего этого.

Существуют частные функции, которые делают то же самое с помощью диспетчера окон Mac OS, но единственным преимуществом, которое вы покупаете, является то, что вам не нужно быть надежным приложением Accessibility (которое является одноразовой операцией при первом запуске в большинство случаев). Недостатком является то, что этот 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 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;
}

Сине Бен спросил, как получить список всех окон в комментариях, вот как:

Вместо "kAXFocusedWindowAttribute" вы используете "kAXWindowsAttribute" для функции AXUIElementCopyAttributeValue. Результатом этого является отсутствие AXUIElementRef, но CFArray элементов AXUIElementRef, по одному для каждого окна этого приложения.

Ответ 2

Я согласен с тем, что доступность - лучший способ продвижения вперед. Но если вы хотите быстро и грязно, AppleScript также будет работать.