Janela Mova e redimensione APIs no OS X
Pergunta
Estou tentando encontrar as APIs documentadas (ou não documentadas, se essa for minha única opção) no OS X para consultar uma lista de Windows do servidor de janelas e, em seguida, fazer com que o Windows se mova e redimensione. Alguém pode me apontar na direção certa? Acho que eu começaria com algo como FindWindowex e MoveWindow sob Win32.
Observe que quero fazer isso em um processo externo - não estou perguntando como controlar apenas o tamanho e a posição da janela do meu aplicativo.
Solução
Use a API de acessibilidade. Usando esta API, você pode se conectar a um processo, obter uma lista de Windows (na verdade, uma matriz), obtenha as posições e tamanhos de cada janela e também altere as propriedades da janela, se quiser.
No entanto, um aplicativo só pode estar usando essa API se o usuário tiver permitido o acesso a dispositivos de assistência em suas preferências (pré -fs do sistema -> acesso universal), caso em que todos os aplicativos podem usar essa API ou se seu aplicativo for um aplicativo de assistência confiável (Quando é confiável, pode usar a API, mesmo que essa opção não seja verificada). A própria API de acessibilidade oferece as funções necessárias para tornar seu aplicativo confiável - basicamente você deve se tornar root (usando serviços de segurança para solicitar permissões raiz do usuário) e depois marcar seu processo como confiável. Depois que seu aplicativo é marcado, ele deve ser reiniciado, pois o estado confiável é verificado apenas na inicialização e não pode mudar enquanto o aplicativo estiver em execução. O estado de confiança é permanente, a menos que o usuário mova o aplicativo em outro lugar ou o hash das alterações binárias do aplicativo (por exemplo, após uma atualização). Se o usuário tiver dispositivos de assistência ativados em seus prefs, todos os aplicativos serão tratados como se fossem confiáveis. Normalmente, seu aplicativo verificaria se essa opção estiver ativada, se for, continue e faça suas coisas. Caso contrário, verificaria se já é confiável, se for, novamente, faça suas coisas. Se não for, tente se tornar confiável e reinicie o aplicativo, a menos que o usuário recusasse a autorização raiz. A API oferece todas as funções necessárias para verificar tudo isso.
Existem funções privadas para fazer o mesmo usando o Mac OS Window Manager, mas a única vantagem que o compraria é que você não precisa ser um aplicativo de acessibilidade confiável (que é uma operação única no primeiro lançamento na maioria dos casos) . As desvantagens são que essa API pode mudar a qualquer momento (ela já mudou no passado), tudo não é documentado e as funções são conhecidas apenas através da engenharia reversa. A acessibilidade, no entanto, é pública, está documentada e não muda muito desde a primeira versão do OS X que a introduziu (algumas novas funções foram adicionadas em 10.4 e novamente em 10.5, mas não muito mais mudou).
Aqui está um exemplo de código. Ele aguardará 5 segundos, para que você possa mudar para uma janela diferente antes de fazer qualquer outra coisa (caso contrário, ele sempre funcionará com a janela do terminal, bastante chato para testes). Em seguida, ele obtém o processo mais frontal, a maioria das janelas da frente desse processo, imprimirá sua posição e tamanho e, finalmente, moverá -o em 25 pixels para a direita. Você o compila na linha de comando como essa (assumindo que é nomeado test.c)
gcc -framework Carbon -o test test.c
Observe que eu não realizo nenhum erro de verificação do código por simplicidade (existem vários lugares que podem fazer com que o programa falhe se algo der errado e certas coisas podem/podem dar errado). Aqui está o 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 perguntou como você recebe uma lista de todas as janelas nos comentários, eis como:
Em vez de "kaxfocusedwindowattribute" você usa "kaxwindowsattribute" para a função AxuElementCopyAttributeValue. O resultado não é o AxuieLelementRef, mas um CFARRAY de elementos AxUieLEMELEmentRef, um para cada janela deste aplicativo.
Outras dicas
Concordo que a acessibilidade é o melhor caminho a seguir. Mas se você quiser, o AppleScript também funcionará.