Получение номера окна через 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 через мост!