Pregunta

Estoy tratando de encontrar API documentadas (o indocumentadas, si esa es mi única opción) en OS X para consultar una lista de ventanas desde el servidor de ventanas y luego hacer que las ventanas se muevan y cambien de tamaño. ¿Alguien puede señalarme en la dirección correcta? Supongo que empezaría con algo como FindWindowEx y MoveWindow en Win32.

Tenga en cuenta que quiero hacer esto desde un proceso externo: no estoy preguntando cómo controlar solo la posición y el tamaño de la ventana de mi propia aplicación.

¿Fue útil?

Solución

Usa la API de accesibilidad. Usando esta API, puede conectarse a un proceso, obtener una lista de ventanas (en realidad una matriz), obtener las posiciones y tamaños de cada ventana y también cambiar las propiedades de la ventana si lo desea.

Sin embargo, una aplicación solo puede usar esta API si el usuario ha habilitado el acceso para dispositivos de asistencia en sus preferencias (Preferencias del sistema - > Acceso universal), en cuyo caso todas las aplicaciones pueden usar esta API, o si su aplicación es una aplicación asistiva confiable (cuando es confiable, puede usar la API, incluso si esta opción no está marcada). La API de accesibilidad en sí misma ofrece las funciones necesarias para que su aplicación sea confiable. Básicamente, debe convertirse en root (utilizando los servicios de seguridad para solicitar permisos de root) y luego marcar su proceso como confiable. Una vez que su aplicación se ha marcado como confiable, debe reiniciarse ya que el estado confiable solo se verifica al inicio y no puede cambiar mientras la aplicación se está ejecutando. El estado de confianza es permanente, a menos que el usuario mueva la aplicación a otro lugar o el hash de los cambios binarios de la aplicación (por ejemplo, después de una actualización). Si el usuario tiene dispositivos de asistencia habilitados en sus preferencias, todas las aplicaciones se tratan como si fueran de confianza. Por lo general, su aplicación verificará si esta opción está habilitada, si es así, continúe y haga sus cosas. Si no, verificaría si ya es confiable, si es así, nuevamente haga sus cosas. Si no, trate de hacerse confiable y luego reinicie la aplicación a menos que el usuario rechace la autorización de root. La API ofrece todas las funciones necesarias para comprobar todo esto.

Existen funciones privadas para hacer lo mismo con el administrador de ventanas de Mac OS, pero la única ventaja que podría comprarle es que no necesita ser una aplicación confiable de Accesibilidad (que es una operación de una sola vez en el primer lanzamiento en mayoria de los casos). Las desventajas son que esta API puede cambiar en cualquier momento (ya ha cambiado en el pasado), todo está indocumentado y las funciones solo se conocen a través de la ingeniería inversa. Sin embargo, la accesibilidad es pública, está documentada y no ha cambiado mucho desde la primera versión de OS X que la introdujo (algunas funciones nuevas se agregaron en 10.4 y nuevamente en 10.5, pero no mucho más ha cambiado).

Aquí hay un ejemplo de código. Esperará 5 segundos, por lo que puede cambiar a una ventana diferente antes de hacer cualquier otra cosa (de lo contrario, siempre funcionará con la ventana del terminal, algo aburrido para las pruebas). Luego obtendrá el proceso más frontal, la ventana más frontal de este proceso, imprimirá su posición y tamaño y finalmente lo moverá 25 píxeles hacia la derecha. Lo compila en la línea de comando así (suponiendo que se llame test.c)

gcc -framework Carbon -o test test.c

Tenga en cuenta que no realizo ninguna comprobación de errores en el código para simplificarlo (hay varios lugares que podrían hacer que el programa se bloquee si algo sale mal y ciertas cosas pueden / pueden salir mal). Aquí está el código:

/* 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 preguntó cómo obtener una lista de todas las ventanas en los comentarios, así es como:

En lugar de " kAXFocusedWindowAttribute " utiliza " kAXWindowsAttribute " para la función AXUIElementCopyAttributeValue. El resultado es entonces que no hay AXUIElementRef, sino un elemento CFArray de AXUIElementRef, uno para cada ventana de esta aplicación.

Otros consejos

Estoy de acuerdo en que la accesibilidad es la mejor manera de avanzar. Pero si quieres algo rápido y sucio, AppleScript también funcionará.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top