¿Cómo puede mi aplicación detectar un cambio en la ventana de otra aplicación?

En Cocoa en la Mac, me gustaría detectar cuándo se mueve, cambia el tamaño o vuelve a pintar una ventana perteneciente a otra aplicación. ¿Cómo puedo hacer esto?

Tendrá que usar las API de accesibilidad, que son simples C, ubicadas dentro del marco ApplicationServices. Por ejemplo:

Primero creas un objeto de aplicación:

AXUIElementRef app = AXUIElementCreateApplication( targetApplicationProcessID ); 

Entonces obtienes la ventana de esto. Puede solicitar la lista de ventanas y enumerar, o puede obtener la ventana más cercana (busque en AXAttributeConstants.h todos los nombres de atributos que usaría).

 AXUIElementRef frontWindow = NULL; AXError err = AXUIElementCopyAttributeValue( app, kAXMainWindowAttribute, &frontWindow ); if ( err != kAXErrorSuccess ) // it failed -- maybe no main window (yet) 

Ahora puede solicitar una notificación a través de una función de callback C cuando cambie una propiedad de esta ventana. Este es un proceso de cuatro pasos:

Primero necesita una función de callback para recibir las notificaciones:

 void MyAXObserverCallback( AXObserverRef observer, AXUIElementRef element, CFStringRef notificationName, void * contextData ) { // handle the notification appropriately // when using ObjC, your contextData might be an object, therefore you can do: SomeObject * obj = (SomeObject *) contextData; // now do something with obj } 

A continuación, necesita un AXObserverRef, que gestiona la rutina de callback. Esto requiere el mismo ID de proceso que usaste para crear el elemento ‘app’ anterior:

 AXObserverRef observer = NULL; AXError err = AXObserverCreate( applicationProcessID, MyObserverCallback, &observer ); if ( err != kAXErrorSuccess ) // handle the error 

Después de obtener su observador, el siguiente paso es solicitar la notificación de ciertas cosas. Consulte AXNotificationConstants.h para obtener la lista completa, pero para los cambios de ventana probablemente solo necesite estos dos:

 AXObserverAddNotification( observer, frontWindow, kAXMovedNotification, self ); AXObserverAddNotification( observer, frontWindow, kAXResizedNotification, self ); 

Tenga en cuenta que el último parámetro pasa un objeto supuesto ‘self’ como contextData. Esto no se conserva, por lo que es importante llamar a AXObserverRemoveNotification cuando este objeto desaparece.

Después de obtener su observador y las solicitudes de notificación agregadas, ahora desea adjuntar el observador a su runloop para que pueda enviar estas notificaciones de manera asincrónica (o de hecho en absoluto):

 CFRunLoopAddSource( [[NSRunLoop currentRunLoop] getCFRunLoop], AXObserverGetRunLoopSource(observer), kCFRunLoopDefaultMode ); 

AXUIElementRef s son objetos estilo CoreFoundation, por lo que debe usar CFRelease() para eliminarlos limpiamente. Para la limpieza aquí, por ejemplo, CFRelease(app) una vez que hayas obtenido el elemento frontWindow, ya que ya no necesitarás la aplicación.

Una nota sobre Garbage-Collection: para mantener un AXUIElementRef como una variable miembro, declararlo así:

 __strong AXUIElementRef frontWindow; 

Esto le indica al recolector de basura que realice un seguimiento de esta referencia. Cuando lo asigne, para compatibilidad con GC y sin GC, use esto:

 frontWindow = (AXUIElementRef) CFMakeCollectable( CFRetain(theElement) ); 

Investigaciones adicionales aparecieron en “Quartz Display Services”

La función más interesante para mis necesidades es CGRegisterScreenRefreshCallback.