В Cocoa на Mac я хотел бы обнаружить, когда окно, принадлежащее другому приложению, перемещается, изменяется или перерисовывается. Как я могу это сделать?
Как мое приложение может обнаружить изменение в другое окно приложения?
Ответ 1
Вам нужно будет использовать API Accessibility, которые являются plain-C, находящимися в структуре ApplicationServices. Например:
Сначала вы создаете объект приложения:
AXUIElementRef app = AXUIElementCreateApplication( targetApplicationProcessID );
Затем вы получите окно из этого. Вы можете запросить список окон и перечислить, или вы можете получить самое ближайшее окно (посмотрите в AXAttributeConstants.h для всех имен атрибутов, которые вы будете использовать).
AXUIElementRef frontWindow = NULL;
AXError err = AXUIElementCopyAttributeValue( app, kAXMainWindowAttribute, &frontWindow );
if ( err != kAXErrorSuccess )
// it failed -- maybe no main window (yet)
Теперь вы можете запросить уведомление через функцию обратного вызова C, когда изменяется свойство этого окна. Это четырехэтапный процесс:
Сначала вам нужна функция обратного вызова для получения уведомлений:
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
}
Далее вам нужен AXObserverRef, который управляет процедурой обратного вызова. Для этого требуется тот же идентификатор процесса, который вы использовали для создания элемента "app" выше:
AXObserverRef observer = NULL;
AXError err = AXObserverCreate( applicationProcessID, MyObserverCallback, &observer );
if ( err != kAXErrorSuccess )
// handle the error
Получив своего наблюдателя, следующий шаг - запросить уведомление о некоторых вещах. См. AXNotificationConstants.h для полного списка, но для изменений окна вам, вероятно, понадобятся только эти два:
AXObserverAddNotification( observer, frontWindow, kAXMovedNotification, self );
AXObserverAddNotification( observer, frontWindow, kAXResizedNotification, self );
Обратите внимание, что последний параметр пропускает предполагаемый "я" объект как contextData. Это не сохраняется, поэтому важно вызвать AXObserverRemoveNotification
, когда этот объект уйдет.
Получив ваш наблюдатель и добавив уведомления, теперь вы хотите привязать наблюдателя к своей runloop, чтобы вы могли отправлять эти уведомления асинхронным образом (или вообще вообще):
CFRunLoopAddSource( [[NSRunLoop currentRunLoop] getCFRunLoop],
AXObserverGetRunLoopSource(observer),
kCFRunLoopDefaultMode );
AXUIElementRef
- объекты типа CoreFoundation, поэтому вам нужно использовать CFRelease()
, чтобы избавиться от них. Для чистоты здесь, например, вы использовали бы CFRelease(app)
, как только вы получили элемент frontWindow, так как вам больше не понадобится приложение.
Заметка о коллекции мусора: Чтобы сохранить AXUIElementRef как переменную-член, объявите ее так:
__strong AXUIElementRef frontWindow;
Это дает указание сборщику мусора отслеживать эту ссылку. При назначении его для совместимости с GC и non-GC используйте это:
frontWindow = (AXUIElementRef) CFMakeCollectable( CFRetain(theElement) );
Ответ 2
Дальнейшие исследования поднялись на "Услуги по настройке кварца"
Интересной функцией для моих нужд является CGRegisterScreenRefreshCallback.