Подтвердить что ты не робот

Как мое приложение может обнаружить изменение в другое окно приложения?

В Cocoa на Mac я хотел бы обнаружить, когда окно, принадлежащее другому приложению, перемещается, изменяется или перерисовывается. Как я могу это сделать?

4b9b3361

Ответ 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.