Получение номера окна через OSX Accessibility API

Я работаю над приложением, которое перемещает окна сторонних приложений на экране.

Чтобы получить обзор всех в настоящее время открытых окон, я использую

CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);

Это возвращает массив словарей, определяющих каждое открытое окно. Вот примерный словарь возвращается:

{
    kCGWindowAlpha = 1;
    kCGWindowBounds =         {
        Height = 442;
        Width = 475;
        X = 3123;
        Y = "-118";
    };
    kCGWindowIsOnscreen = 1;
    kCGWindowLayer = 0;
    kCGWindowMemoryUsage = 907184;
    kCGWindowName = Untitled;
    kCGWindowNumber = 7328;
    kCGWindowOwnerName = TextEdit;
    kCGWindowOwnerPID = 20706;
    kCGWindowSharingState = 1;
    kCGWindowStoreType = 2;
    kCGWindowWorkspace = 3;
},

Словарь полон полезной информации, используемой в другом месте, но в нем отсутствует объект доступности, который можно было бы использовать для изменения позиций окон. Окна четко обозначены номером окна.

Теперь я использую PID (kCGWindowOwnerPID) для создания объекта доступности для приложения окна:

AXUIElementRef app = AXUIElementCreateApplication(pid);

Затем следует получить список всех окон, которые приложение открыло с помощью AXUIElementCopyAttributeValues:

NSArray *result;

AXUIElementCopyAttributeValues(
                               (AXUIElementRef) app, 
                               kAXWindowsAttribute,
                               0,
                               99999,
                               (CFArrayRef *) &result
                               );

Это работает и возвращает массив AXUIElements. Вот где я застрял. Кажется, что нет вызова API для получения номера окна объекта доступности. Есть ли способ либо

а) Найти номер окна объекта доступности (чтобы в конечном итоге перебрать массив и найти правильное окно)

или же

б) В противном случае четко сопоставить окно, описанное в массиве, возвращенном CGWindowListCopyWindowInfo, объектам доступности, возвращаемым AXUIElementCopyAttributeValues?

3 ответа

Решение

Мы закончили тем, что наняли специального Разработчика Доступности для этой задачи.

Оказывается, нет способа сделать это без использования недокументированных API (в нашем случае это не так).

К счастью, есть практический обходной путь:

Обведите все открытые окна приложения. Получите их положение, размер и название:

AXUIElementCopyAttributeValue(target, kAXPositionAttribute, CFTypeRef*)&posValue);
AXUIElementCopyAttributeValue(target, kAXSizeAttribute, (CFTypeRef*)&sizeValue);
AXUIElementCopyAttributeValue(target, kAXTitleAttribute, (CFTypeRef*)&titleValue);

Далее конвертируем позицию и размер в реальный CGPoint а также CGSize ценности:

AXValueGetValue(posValue, kAXValueCGPointType, &point);
AXValueGetValue(sizeValue, kAXValueCGSizeType, &size);

Сравните размер, положение и заголовок со значениями, возвращаемыми объектом в CGWindowListCopyWindowInfo(), Если они совпадают, вы можете смело предположить, что это окно, которое вы искали, и использовать уже открытый AXUIElement (target в нашем случае) чтобы это работало.

Затраты на цикл по всем открытым окнам оказываются незначительными в OSX. Существует довольно низкий предел количества открытых окон одновременно.

Кроме того, хотя это не на 100% точно (возможно, что 2 окна имеют одинаковое положение, размер и заголовок), мы не сталкивались с реальной ситуацией, когда это происходит до сих пор.

Существует частная функция для получения номера окна CG для данного объекта AX для окна: _AXUIElementGetWindow, Больше деталей в обсуждении SO Уникальная идентификация активного окна в OS X Похоже, что нет никакого открытого API, чтобы выполнить задачу со 100% вероятностью. Идентификация окон по заголовку и рамке (как описано в ответе выше) будет работать в 99,9% случаев.

Уточнение других ответов, которые предполагают недокументированный вызов_AXUIElementGetWindow.

В Swift сделайте так:

1. Создать Bridged-Header.hсо следующим содержанием:

      #import <AppKit/AppKit.h>

AXError _AXUIElementGetWindow(AXUIElementRef element, uint32_t *identifier);

2. Ссылка на этот файл в настройках сборки: (ваш путь может отличаться, он относится к корню проекта)

3. Вызовите так:

      // variable 'window' is your AXUIElement window
var cgWindowId = CGWindowID()
if (_AXUIElementGetWindow(window, &gcWindowId) != .success) {. 
  print("cannot get CGWindow id (objc bridged call)")
}

Поздравляем, вы успешно вызвали целевые функции C через мост!

Другие вопросы по тегам