如何在 MacOS 上的 Java(使用 JNA)中获得前景 window/process?
How to get foreground window/process in Java (Using JNA) on MacOS?
目前,我正在努力在 MS Windows 中获得前景(顶部)window/process。我需要在 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));
这里其实有两个问题,前台window和前台进程。我会尽量回答这两个问题。
对于前台进程,使用JNA的一个简单方法是映射Application ServicesAPI。请注意,这些函数是在 10.9 中引入的,现在已弃用,但从 10.15 开始仍然有效。较新的版本在 AppKit
库中,见下文。
创建此 class,映射您需要的两个函数:
public interface ApplicationServices extends Library {
ApplicationServices INSTANCE = Native.load("ApplicationServices", ApplicationServices.class);
int GetFrontProcess(LongByReference processSerialNumber);
int GetProcessPID(LongByReference processSerialNumber, IntByReference pid);
}
“前台”进程可以通过 GetFrontProcess()
. That returns something called a ProcessSerialNumber
, a unique 64-bit value used throughout the Application Services API. To translate it for your userspace use, you probably want the Process ID, and 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());
虽然上述方法有效,但已弃用。新应用程序应使用 AppKit
库:
public interface AppKit extends Library {
AppKit INSTANCE = Native.load("AppKit", AppKit.class);
}
关于使用此库的最顶层应用程序还有多个其他 Whosebug 问题,例如 this one. Mapping all the imports and objects needed is far more work than I have time to do in an answer here, but you might find it useful. It's probably easier to figure out how to use the Rococoa framework (which uses JNA under the hood but has already mapped all of AppKit via JNAerator) to access this API. Some javadocs are here。
还有 solutions 使用 AppleScript
,您可以使用 Runtime.exec()
通过命令行从 Java 执行并捕获输出。
关于屏幕上的前景window,有点复杂。在 之前关于在 macOS 上迭代所有 windows 的问题,我回答了如何通过 JNA 使用 CoreGraphics
获取所有 windows 的列表,包括 CFDictionary
包含更多信息。
其中一个字典键是 kCGWindowLayer
,它将 return 一个 CFNumber
表示 window 层数。文档声明这是 32 位的,因此 intValue()
是合适的。该数字是“绘图顺序”,因此较高的数字将覆盖较低的数字。因此,您可以遍历所有检索到的 windows 并找到最大数量。这将是“前景”层。
有一些注意事项:
- 实际上只有20层可用。许多东西共享一个图层。
- 第1000层是屏保。您可以忽略第 1000 层及更高层。
- 第 24 层是 Dock,通常在最上面,第 25 层(Dock 上的图标)在更高级别。
- 第 0 层似乎是桌面的其余部分。
- 哪个 window 位于“顶部”取决于您在屏幕上看到的位置。在停靠栏上方,停靠栏将位于前景(或应用程序图标)。在屏幕的其余部分,您需要检查您正在评估的像素与从 CoreGraphics window 获得的屏幕矩形。 (使用
kCGWindowBounds
键,其中 return 是 CGRect
(具有 4 个双打、X、Y、宽度、高度的结构)。
您将需要过滤到屏幕上 windows。如果您已经获取列表,您可以使用 kCGWindowIsOnscreen
key to determine whether the window is visible. It returns a CFBoolean
. Since that key is optional you will need to test for null
. However, if you are starting from nothing, it would be better to use the kCGWindowListOptionOnScreenOnly
Window Option Constant when you initially call CGWindowListCopyWindowInfo()
.
除了迭代allwindows,CGWindowListCopyWindowInfo()
function takes a CGWindowID
参数relativeToWindow
,你还可以添加(按位或)kCGWindowListOptionOnScreenAboveWindow
到选项。
最后,您可能会发现限制为与当前会话关联的 windows 可能很有用,您应该映射 CGWindowListCreate()
using similar syntax to the CopyInfo()
variant. It returns an array of window numbers that you could limit your dictionary search to, or pass that array as an argument to 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");
}
目前,我正在努力在 MS Windows 中获得前景(顶部)window/process。我需要在 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));
这里其实有两个问题,前台window和前台进程。我会尽量回答这两个问题。
对于前台进程,使用JNA的一个简单方法是映射Application ServicesAPI。请注意,这些函数是在 10.9 中引入的,现在已弃用,但从 10.15 开始仍然有效。较新的版本在 AppKit
库中,见下文。
创建此 class,映射您需要的两个函数:
public interface ApplicationServices extends Library {
ApplicationServices INSTANCE = Native.load("ApplicationServices", ApplicationServices.class);
int GetFrontProcess(LongByReference processSerialNumber);
int GetProcessPID(LongByReference processSerialNumber, IntByReference pid);
}
“前台”进程可以通过 GetFrontProcess()
. That returns something called a ProcessSerialNumber
, a unique 64-bit value used throughout the Application Services API. To translate it for your userspace use, you probably want the Process ID, and 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());
虽然上述方法有效,但已弃用。新应用程序应使用 AppKit
库:
public interface AppKit extends Library {
AppKit INSTANCE = Native.load("AppKit", AppKit.class);
}
关于使用此库的最顶层应用程序还有多个其他 Whosebug 问题,例如 this one. Mapping all the imports and objects needed is far more work than I have time to do in an answer here, but you might find it useful. It's probably easier to figure out how to use the Rococoa framework (which uses JNA under the hood but has already mapped all of AppKit via JNAerator) to access this API. Some javadocs are here。
还有 solutions 使用 AppleScript
,您可以使用 Runtime.exec()
通过命令行从 Java 执行并捕获输出。
关于屏幕上的前景window,有点复杂。在 CoreGraphics
获取所有 windows 的列表,包括 CFDictionary
包含更多信息。
其中一个字典键是 kCGWindowLayer
,它将 return 一个 CFNumber
表示 window 层数。文档声明这是 32 位的,因此 intValue()
是合适的。该数字是“绘图顺序”,因此较高的数字将覆盖较低的数字。因此,您可以遍历所有检索到的 windows 并找到最大数量。这将是“前景”层。
有一些注意事项:
- 实际上只有20层可用。许多东西共享一个图层。
- 第1000层是屏保。您可以忽略第 1000 层及更高层。
- 第 24 层是 Dock,通常在最上面,第 25 层(Dock 上的图标)在更高级别。
- 第 0 层似乎是桌面的其余部分。
- 哪个 window 位于“顶部”取决于您在屏幕上看到的位置。在停靠栏上方,停靠栏将位于前景(或应用程序图标)。在屏幕的其余部分,您需要检查您正在评估的像素与从 CoreGraphics window 获得的屏幕矩形。 (使用
kCGWindowBounds
键,其中 return 是CGRect
(具有 4 个双打、X、Y、宽度、高度的结构)。
您将需要过滤到屏幕上 windows。如果您已经获取列表,您可以使用 kCGWindowIsOnscreen
key to determine whether the window is visible. It returns a CFBoolean
. Since that key is optional you will need to test for null
. However, if you are starting from nothing, it would be better to use the kCGWindowListOptionOnScreenOnly
Window Option Constant when you initially call CGWindowListCopyWindowInfo()
.
除了迭代allwindows,CGWindowListCopyWindowInfo()
function takes a CGWindowID
参数relativeToWindow
,你还可以添加(按位或)kCGWindowListOptionOnScreenAboveWindow
到选项。
最后,您可能会发现限制为与当前会话关联的 windows 可能很有用,您应该映射 CGWindowListCreate()
using similar syntax to the CopyInfo()
variant. It returns an array of window numbers that you could limit your dictionary search to, or pass that array as an argument to 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");
}