Как получить окно / процесс переднего плана в Java (с использованием JNA) в MacOS?
В настоящее время я работаю над тем, чтобы получить окно / процесс переднего плана (верхнее) в MS Windows. Мне нужно сделать что-то подобное в macOS, используя JNA.
Каков эквивалентный код в macOS?
byte[] windowText = new byte[512];
PointerType hwnd = User32.INSTANCE.GetForegroundWindow();
User32.INSTANCE.GetWindowTextA(hwnd, windowText, 512);
System.out.println(Native.toString(windowText));
Ответы
На самом деле здесь есть два вопроса: окно переднего плана и процесс переднего плана. Я постараюсь ответить на оба вопроса.
Для процесса переднего плана простой способ использования JNA - сопоставить API служб приложений. Обратите внимание, что эти функции были введены в 10.9 и теперь устарели, но по-прежнему работают с 10.15. Более новая версия находится в AppKitLibrary
, см. Ниже.
Создайте этот класс, сопоставив две функции, которые вам понадобятся:
public interface ApplicationServices extends Library {
ApplicationServices INSTANCE = Native.load("ApplicationServices", ApplicationServices.class);
int GetFrontProcess(LongByReference processSerialNumber);
int GetProcessPID(LongByReference processSerialNumber, IntByReference pid);
}
Процесс "переднего плана" можно получить с помощью GetFrontProcess(). Это возвращает нечто, называемое ProcessSerialNumber
уникальным 64-битным значением, используемым во всем API служб приложений. Чтобы перевести его для использования в пользовательском пространстве, вам, вероятно, понадобится идентификатор процесса, и он GetProcessPID()сделает этот перевод за вас.
LongByReference psn = new LongByReference();
IntByReference pid = new IntByReference();
ApplicationServices.INSTANCE.GetFrontProcess(psn);
ApplicationServices.INSTANCE.GetProcessPID(psn, pid);
System.out.println("Front process pid: " + pid.getValue());
Хотя вышеперечисленное работает, оно устарело. Новое приложение должно использовать AppKitLibrary
:
public interface AppKitLibrary extends Library {
AppKitLibrary INSTANCE = Native.load("AppKitLibrary", AppKitLibrary.class);
}
Есть еще несколько вопросов к StackOverflow, касающихся самого верхнего приложения, использующего эту библиотеку, например этого . Сопоставление всего необходимого импорта и объектов - это гораздо больше работы, чем у меня есть время, чтобы ответить здесь, но вы можете найти это полезным. Вероятно, легче понять, как использовать фреймворк Rococoa (который использует JNA под капотом, но уже сопоставил весь AppKit через JNAerator) для доступа к этому API. Некоторые javadocs здесь .
Существуют также решения, с помощью AppleScript
которых вы можете запускать из Java через командную строку, используя Runtime.exec()
и захватывая выходные данные.
Что касается окна переднего плана на экране, тут все немного сложнее. В своем ответе на ваш предыдущий вопрос об итерации всех окон в macOS я ответил, как получить список всех окон с CoreGraphics
помощью JNA, включая CFDictionary
содержащий дополнительную информацию.
Один из этих ключей словаря kCGWindowLayerвозвращает a, CFNumber
представляющий номер слоя окна. В документации указано, что это 32-разрядная версия, поэтому intValue()
это уместно. Число - это «порядок рисования», поэтому большее число заменяет меньшее. Таким образом, вы можете перебрать все полученные окна и найти максимальное количество. Это будет слой «переднего плана».
Есть некоторые предостережения:
- На самом деле доступно всего 20 слоев. Многие вещи имеют общий слой.
- Layer 1000 - это заставка. Вы можете игнорировать уровни 1000 и выше.
- Слой 24 - это док, обычно наверху, а слой 25 (значки на доке) - на более высоком уровне.
- Слой 0 выглядит как остальная часть рабочего стола.
- Какое окно находится «сверху», зависит от того, в какую часть экрана вы смотрите. Над док-станцией док-станция будет на переднем плане (или значок приложения). На остальной части экрана вам нужно сравнить пиксель, который вы оцениваете, с прямоугольником экрана, полученным из окна CoreGraphics. (Используйте
kCGWindowBounds
ключ, который возвращаетCGRect
(структуру с четырьмя двойными, X, Y, шириной, высотой).
Вам нужно будет применить фильтр к экранным окнам. Если вы уже получили список, вы можете использовать kCGWindowIsOnscreenклавишу, чтобы определить, видно ли окно. Он возвращает CFBoolean
. Поскольку этот ключ является необязательным, вам нужно будет его проверить null
. Однако, если вы начинаете с нуля, было бы лучше использовать kCGWindowListOptionOnScreenOnly
константу параметров окна при первоначальном вызове CGWindowListCopyWindowInfo().
Помимо перебора всех окон, CGWindowListCopyWindowInfo()функция принимает CGWindowIDпараметр, relativeToWindow
и вы можете добавлять (с помощью побитового или) kCGWindowListOptionOnScreenAboveWindow
к параметрам.
Наконец, вы можете обнаружить, что ограничение окнами, связанными с текущим сеансом, может быть полезным, и вам следует сопоставить вариант CGWindowListCreate()с использованием синтаксиса, аналогичного синтаксису CopyInfo()
. Он возвращает массив номеров окон, которым вы можете ограничить поиск по словарю, или передать этот массив в качестве аргумента CGWindowListCreateDescriptionFromArray().
Как упоминалось в моем предыдущем ответе, вы «владеете» каждым объектом, который создаете с помощью функций Create
или Copy
, и несете ответственность за их освобождение, когда вы закончите с ними, чтобы избежать утечки памяти.
AppleScriptEngine appleEngine = new apple.applescript.AppleScriptEngine();
ArrayList<String> processNames = null;
try {
String processName = null;
processNames = (ArrayList<String>) appleEngine
.eval("tell application \"System Events\" to get name of application processes whose frontmost is true and visible is true");
if (processNames.size() > 0) {
processName = processNames.get(0);// the front most process name
}
return processName;
} catch (ScriptException e) {
log.debug("no app running");
}