Окно перемещения и изменения размера API в OS X - PullRequest
27 голосов
/ 05 марта 2009

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

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

Ответы [ 2 ]

47 голосов
/ 05 марта 2009

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

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

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

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

2 голосов
/ 11 марта 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...