如何在MacOS上使用Java(使用JNA)获取前台窗口/进程?

如何在MacOS上使用Java(使用JNA)获取前台窗口/进程?,java,macos,jna,Java,Macos,Jna,目前,我正在MS Windows中获取前景(顶部)窗口/进程。我需要在macOS中使用JNA执行类似的操作 macOS中的等效代码是什么 byte[] windowText = new byte[512]; PointerType hwnd = User32.INSTANCE.GetForegroundWindow(); User32.INSTANCE.GetWindowTextA(hwnd, windowText, 512); System.out.println(Nati

目前,我正在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);
}
“前景”过程可通过以下步骤获得:。它返回一个名为
ProcessSerialNumber
的值,这是整个应用程序服务API中使用的唯一64位值。要将其转换为用户空间使用,您可能需要进程ID,并为您进行转换

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问题,例如。映射所有需要的导入和对象比我在这里的答案中花费的时间要多得多,但您可能会发现它很有用。可能更容易弄清楚如何使用(在引擎盖下使用JNA,但已经通过JNA映射了所有AppKit)来访问此API。一些

还可以使用
AppleScript
,通过命令行使用
Runtime.exec()
从Java执行,并捕获输出


关于屏幕上的前景窗口,它有点复杂。在您先前关于在macOS上迭代所有窗口的问题中,我回答了如何通过JNA使用
CoreGraphics
获取所有窗口的列表,包括包含更多信息的
CFDictionary

其中一个字典键是,它将返回表示窗口层编号的
CFNumber
。文档说明这是32位的,因此
intValue()
是合适的。该数字是“绘图顺序”,因此较高的数字将覆盖较低的数字。因此,您可以迭代所有检索到的窗口,并找到最大数目。这将是“前景”层

有一些警告:

  • 实际上只有20层可用。许多事物共享一层
  • 第1000层是屏幕保护程序。可以忽略1000层或更高的层
  • 第24层是基座,通常位于顶部,第25层(基座上的图标)位于更高的级别
  • 图层0似乎是桌面的其余部分
  • 哪个窗口是“在顶部”取决于您在屏幕上看的位置。在dock上,dock将位于前台(或应用程序图标)。在屏幕的其余部分,您需要检查正在评估的像素与从CoreGraphics窗口获得的屏幕矩形。(使用
    kCGWindowBounds
    键返回一个
    CGRect
    (一个具有4个双精度X、Y、宽度和高度的结构)
您需要筛选到屏幕上的窗口。如果您已经获取了列表,您可以使用该键确定窗口是否可见。它返回一个
CFBoolean
。由于该键是可选的,您需要测试
null
。但是,如果您从零开始,最好使用
kCGWindowLiStoption仅当您最初调用时才进行筛选

除了迭代所有窗口外,该函数还接受一个参数
relativeToWindow
,您可以将(按位或)
KCGWindowListoptionsScreeneUpersWindow
添加到选项中

最后,您可能会发现限制与当前会话关联的窗口可能很有用,您应该使用类似的语法映射到
CopyInfo()
变量。它返回一个窗口编号数组,您可以将字典搜索限制到该数组,或将该数组作为参数传递给它

正如我在前面的回答中提到的,您“拥有”使用
create
Copy
函数创建的每个对象,并负责在使用完这些对象后释放它们,以避免内存泄漏。

AppleScriptEngine appleEngine=new apple.applescript.AppleScriptEngine();
   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");
    }
ArrayList processNames=null; 试一试{ 字符串processName=null; processNames=(ArrayList)appleEngine .eval(“告诉应用程序\“系统事件\”以获取最前端为真且可见为真的应用程序进程的名称”); if(processNames.size()>0){ processName=processNames.get(0);//最前面的进程名 } 返回进程名; }捕获(脚本异常){ log.debug(“没有应用程序运行”); }
但请注意:我会先停止在这里思考java。试着弄清楚你在MacOs中通常是如何做这些事情的……然后看看你找到的元素是如何向java表示的。我已经找到了最大数量。但它不是前景窗口。为什么是这样?有什么想法吗?它总是返回相同的层号。即使我已经更改了前景,但仍然是er编号保持不变。当我切换到已经打开的程序/进程时会出现此问题。例如,文本编辑器和Ms word都打开。当前,Ms word处于活动状态,那么我的程序将显示Ms word,但当我切换到文本编辑器时。程序显示Ms word而不是文本编辑器。有什么问题吗?好的,还有另一个API,
ApplicationServices
ca给你更多信息。编辑我的答案。好的,我已经更新了ApplicationServices API,从10.15开始运行,但不能保证将来会运行。正确的解决方案是AppKitLibrary API,但这对我来说是太多的工作了,我无法在一个SO答案中完成,你必须自己去做。你好,很好,你花时间回答了这个问题。谢谢se添加更多文本以帮助其他人解释您所拥有的内容