繁体   English   中英

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

[英]How to get foreground window/process in Java (Using JNA) on MacOS?

目前,我正在努力在 MS Windows 中获取前景(顶部)窗口/进程。我需要使用 JNA 在 macOS 中做类似的事情。

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 的一种简单方法是 map 应用程序服务API。请注意,这些功能是在 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()获得。 这会返回一个称为ProcessSerialNumber的东西,这是一个在整个应用程序服务 API 中使用的唯一 64 位值。要将其转换为您的用户空间使用,您可能需要进程 ID,而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);
}

关于使用此库的最顶层应用程序,还有多个其他 StackOverflow 问题,例如这个 映射所有需要的导入和对象比我在这里的答案中有时间做的工作要多得多,但您可能会发现它很有用。 弄清楚如何使用Rococoa 框架(它在后台使用 JNA 但已经通过 JNAerator 映射了所有 AppKit)来访问这个 API 可能更容易。 这里有一些 javadoc

还有使用AppleScript解决方案,您可以使用Runtime.exec()通过命令行从 Java 执行并捕获 output。


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

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

有一些注意事项:

  • 实际上只有 20 层可用。 许多东西共享一个层。
  • 第 1000 层是屏幕保护程序。 您可以忽略第 1000 层及更高层。
  • 第 24 层是 Dock,通常在最上面,第 25 层(dock 上的图标)在更高级别。
  • 第 0 层似乎是桌面的 rest。
  • 哪个 window 在“顶部”取决于您在屏幕上看到的位置。 在停靠栏上方,停靠栏将位于前景(或应用程序图标)。 在屏幕的 rest 上,您需要检查您正在评估的像素与从 CoreGraphics window 获得的屏幕矩形。(使用返回CGRectkCGWindowBounds键(具有 4 个双精度、X、Y、宽度的结构,高度)。

您需要过滤到屏幕上的 windows。如果您已经获取列表,您可以使用kCGWindowIsOnscreen键来确定 window 是否可见。 它返回一个CFBoolean 由于该密钥是可选的,因此您需要测试null 但是,如果您是从零开始,最好在最初调用CGWindowListCopyWindowInfo()时使用kCGWindowListOptionOnScreenOnly Window 选项常量

除了迭代所有windows 之外, CGWindowListCopyWindowInfo() function 还采用CGWindowID参数relativeToWindow并且您可以将(按位或) kCGWindowListOptionOnScreenAboveWindow添加到选项中。

最后,您可能会发现限制为与当前 session 关联的 windows 可能很有用,您应该使用与CGWindowListCreate() CopyInfo() 它返回一个包含 window 个数字的数组,您可以将字典搜索限制为该数组,或者将该数组作为参数传递给CGWindowListCreateDescriptionFromArray()

正如我在之前的回答中提到的,您“拥有”使用CreateCopy函数创建的每个 object,并负责在使用完成后释放它们,以避免 memory 泄漏。

   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");
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM