Domanda

Sto cercando di trovare API documentate (o non documentate, se questa è la mia unica opzione) su OS X per interrogare un elenco di finestre dal server di finestre e quindi farle spostare e ridimensionare. Qualcuno può indicarmi la giusta direzione? Immagino che inizierei con qualcosa come FindWindowEx e MoveWindow sotto Win32.

Nota che voglio farlo da un processo esterno: non sto chiedendo come controllare solo le dimensioni e la posizione della finestra della mia app.

È stato utile?

Soluzione

Utilizza l'API di accessibilità. Usando questa API puoi connetterti a un processo, ottenere un elenco di finestre (in realtà un array), ottenere posizioni e dimensioni di ciascuna finestra e modificare le proprietà della finestra, se lo desideri.

Tuttavia, un'applicazione può utilizzare questa API solo se l'utente ha abilitato l'accesso per i dispositivi di assistenza nelle sue preferenze (Preferenze di sistema - > Accesso universale), nel qual caso tutte le applicazioni possono utilizzare questa API o se l'applicazione è un'applicazione assitive attendibile (quando è attendibile, può utilizzare l'API, anche se questa opzione non è selezionata). L'API di accessibilità stessa offre le funzioni necessarie per rendere attendibile la tua applicazione - sostanzialmente devi diventare root (usando i servizi di sicurezza per richiedere le autorizzazioni di root dell'utente) e quindi contrassegnare il tuo processo come attendibile. Una volta che l'applicazione è stata contrassegnata come attendibile, deve essere riavviata poiché lo stato di fiducia viene verificato solo all'avvio e non può cambiare mentre l'app è in esecuzione. Lo stato di attendibilità è permanente, a meno che l'utente non sposta l'applicazione da qualche altra parte o che l'hash delle modifiche binarie dell'applicazione (ad es. Dopo un aggiornamento). Se l'utente ha dispositivi di supporto abilitati nelle sue preferenze, tutte le applicazioni vengono trattate come se fossero attendibili. Di solito la tua app controlla se questa opzione è abilitata, se lo è, vai avanti e fai le tue cose. In caso contrario, verificherebbe se è già attendibile, in caso affermativo, fai di nuovo le tue cose. In caso contrario, provare a fidarsi e quindi riavviare l'applicazione a meno che l'utente non abbia rifiutato l'autorizzazione di root. L'API offre tutte le funzioni necessarie per verificare tutto questo.

Esistono funzioni private per fare lo stesso usando il gestore delle finestre di Mac OS, ma l'unico vantaggio che ti acquisterebbe è che non è necessario essere un'applicazione di accessibilità affidabile (che è un'operazione unica al primo avvio in la maggior parte dei casi). Gli svantaggi sono che questa API può cambiare in qualsiasi momento (è già cambiata in passato), è completamente priva di documenti e le funzioni sono note solo attraverso il reverse engineering. L'accessibilità tuttavia è pubblica, è documentata e non è cambiata molto dalla prima versione di OS X che l'ha introdotta (alcune nuove funzioni sono state aggiunte in 10.4 e di nuovo in 10.5, ma non è cambiato molto altro).

Ecco un esempio di codice. Aspetterà 5 secondi, quindi puoi passare a una finestra diversa prima di fare qualsiasi altra cosa (altrimenti funzionerà sempre con la finestra del terminale, piuttosto noioso per i test). Quindi otterrà il processo più anteriore, la finestra più anteriore di questo processo, stampa la sua posizione e dimensione e infine lo sposta di 25 pixel a destra. Lo compili sulla riga di comando in questo modo (supponendo che sia chiamato test.c)

gcc -framework Carbon -o test test.c

Si noti che non eseguo alcun controllo degli errori nel codice per semplicità (ci sono vari posti che potrebbero causare l'arresto anomalo del programma se qualcosa va storto e alcune cose possono / possono andare storto). Ecco il codice:

/* 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 ha chiesto come ottenere un elenco di tutte le finestre nei commenti, ecco come:

Invece di " kAXFocusedWindowAttribute " utilizzi " kAXWindowsAttribute " per la funzione AXUIElementCopyAttributeValue. Il risultato non è quindi AXUIElementRef, ma un CFArray di elementi AXUIElementRef, uno per ogni finestra di questa applicazione.

Altri suggerimenti

Sono d'accordo che l'accessibilità sia la soluzione migliore. Ma se vuoi una soluzione rapida, anche AppleScript funzionerà.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top