2009-03-05 15 views
23

Sto cercando di trovare API documentate (o non documentate, se è la mia unica opzione) su OS X per richiedere un elenco di finestre dal server della finestra e quindi spostare le finestre e ridimensionare. Qualcuno può indicarmi la giusta direzione? Credo che inizierei con qualcosa come FindWindowEx e MoveWindow sotto Win32.API di spostamento e ridimensionamento della finestra in OS X

Nota che voglio farlo da un processo esterno - Non sto chiedendo come controllare solo la dimensione e la posizione della finestra della mia app.

risposta

42

Utilizzare l'API di accessibilità. Usando questa API è possibile connettersi a un processo, ottenere un elenco di finestre (in realtà un array), ottenere le posizioni e le dimensioni di ciascuna finestra e anche modificare le proprietà delle finestre, se lo si desidera.

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 fidata e affidabile (quando è affidabile, può utilizzare l'API, anche se questa opzione non è selezionata). La stessa API di accessibilità offre le funzioni necessarie per rendere l'applicazione attendibile - in pratica è necessario diventare root (utilizzando i servizi di sicurezza per richiedere i permessi di root dell'utente) e quindi contrassegnare il processo come affidabile. Una volta che la tua applicazione è stata contrassegnata come attendibile, deve essere riavviata poiché lo stato di fiducia viene verificato solo all'avvio e non può essere modificato mentre l'app è in esecuzione. Lo stato trust è permanente, a meno che l'utente non sposti l'applicazione da qualche altra parte o l'hash delle modifiche binarie dell'applicazione (ad esempio dopo un aggiornamento). Se l'utente ha dispositivi di assistenza abilitati nelle sue preferenze, tutte le applicazioni vengono trattate come se fossero attendibili. Di solito la tua app controllerebbe se questa opzione è abilitata, se lo è, vai avanti e fai le tue cose. In caso contrario, controllerebbe se è già affidabile, se lo è, di nuovo basta fare le tue cose. Se non si tenta di rendersi affidabile 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 utilizzando il gestore di finestre di Mac OS, ma l'unico vantaggio che si comprerebbe è che non è necessario essere un'applicazione di accessibilità attendibile (che è un'operazione una tantum al primo avvio nella 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 conosciute solo mediante 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 molto altro è cambiato).

Ecco un esempio di codice. Attenderà 5 secondi, quindi è possibile passare a una finestra diversa prima che faccia qualsiasi altra cosa (altrimenti funzionerà sempre con la finestra del terminale, piuttosto noiosa per il test). Quindi otterrà la parte anteriore del processo, la parte anteriore di questo processo, stamperà la sua posizione e dimensione e infine la sposterà di 25 pixel a destra. Si compila on line di comando del genere (ammesso che si chiama test.c)

gcc -framework Carbon -o test test.c 

Si prega di notare che non si esegue alcuna verifica degli errori nel codice per semplicità (ci sono vari luoghi che potrebbero far sì che il programma di crash se qualcosa va storto e certe cose possono/possono andare storte). 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 chiesto come si ottiene un elenco di tutte le finestre nei commenti, ecco come:

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

+0

"Potremmo anche ottenere una matrice di tutte le finestre di questo processo" ... Come dovrei farlo? –

+1

@Ben: anziché "kAXFocusedWindowAttribute" si utilizza "kAXWindowsAttribute" per la funzione AXUIElementCopyAttributeValue. Il risultato non è quindi AXUIElementRef, ma un CFArray di elementi AXUIElementRef, uno per ogni finestra di questa applicazione. – Mecki

+0

Grazie, proprio quello che stavo cercando. –

0

Sono d'accordo sul fatto che l'accessibilità è la migliore soluzione. Ma se vuoi un lavoro veloce e sporco, anche AppleScript funzionerà.

Problemi correlati