¿Cómo obtener una ventana/proceso en primer plano en Java (Usando JNA) en MacOS?

Aug 18 2020

Actualmente, estoy trabajando para obtener una ventana/proceso de primer plano (superior) en MS Windows. Necesito hacer algo similar en macOS usando JNA.

¿Cuál es el código equivalente en macOS?

  byte[] windowText = new byte[512];
  PointerType hwnd = User32.INSTANCE.GetForegroundWindow();  
  User32.INSTANCE.GetWindowTextA(hwnd, windowText, 512);
  System.out.println(Native.toString(windowText));  

Respuestas

2 DanielWiddis Aug 18 2020 at 22:53

En realidad, hay dos preguntas aquí, la ventana en primer plano y el proceso en primer plano. Intentaré responder a las dos.


Para el proceso de primer plano , una manera fácil de usar JNA es mapear la API de servicios de aplicaciones . Tenga en cuenta que estas funciones se introdujeron en 10.9 y ahora están en desuso, pero aún funcionan a partir de 10.15. La versión más reciente está en AppKitLibrary, ver más abajo.

Cree esta clase, mapeando las dos funciones que necesitará:

public interface ApplicationServices extends Library {
    ApplicationServices INSTANCE = Native.load("ApplicationServices", ApplicationServices.class);

    int GetFrontProcess(LongByReference processSerialNumber);
    int GetProcessPID(LongByReference processSerialNumber, IntByReference pid);
}

El proceso de "primer plano" se puede obtener con GetFrontProcess(). Eso devuelve algo llamado ProcessSerialNumber, un valor único de 64 bits utilizado en toda la API de servicios de aplicaciones. Para traducirlo para su uso en el espacio de usuario, probablemente desee la ID del proceso y GetProcessPID()haga esa traducción por usted.

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());

Si bien lo anterior funciona, está en desuso. Una nueva aplicación debe usar AppKitLibrary:

public interface AppKitLibrary extends Library {
    AppKitLibrary INSTANCE = Native.load("AppKitLibrary", AppKitLibrary.class);
}

Hay muchas otras preguntas de StackOverflow con respecto a la aplicación superior que usa esta biblioteca, como esta . Mapear todas las importaciones y objetos necesarios es mucho más trabajo del que tengo tiempo para hacer en una respuesta aquí, pero puede que le resulte útil. Probablemente sea más fácil descubrir cómo usar el marco Rococoa (que usa JNA bajo el capó, pero ya mapeó todo AppKit a través de JNAerator) para acceder a esta API. Algunos javadocs están aquí .

También hay solucionesAppleScript que puede ejecutar desde Java a través de la línea de comandos usando y Runtime.exec()capturando la salida.


Con respecto a la ventana de primer plano en la pantalla, es un poco más complicado. En mi respuesta a su pregunta anterior sobre la iteración de todas las ventanas en macOS, respondí cómo obtener una lista de todas las ventanas usando CoreGraphicsJNA, incluida una CFDictionaryque contiene más información.

Una de esas claves del diccionario es kCGWindowLayerla que devolverá un CFNumbernúmero que representa la capa de la ventana. Los documentos indican que esto es de 32 bits, por lo que intValue()es apropiado. El número es el "orden de dibujo", por lo que un número más alto sobrescribirá un número más bajo. Entonces puede iterar sobre todas las ventanas recuperadas y encontrar el número máximo. Esta será la capa de "primer plano".

Hay algunas advertencias:

  • En realidad, solo hay 20 capas disponibles. Muchas cosas comparten una capa.
  • La capa 1000 es el salvapantallas. Puede ignorar las capas 1000 y superiores.
  • La capa 24 es el Dock, generalmente en la parte superior, con la Capa 25 (los íconos en el Dock) en un nivel superior.
  • La capa 0 parece ser el resto del escritorio.
  • Qué ventana está "en la parte superior" depende de dónde mire en la pantalla. Sobre el muelle, el muelle estará en primer plano (o el icono de la aplicación). En el resto de la pantalla, debe comparar el píxel que está evaluando con el rectángulo de pantalla obtenido de la ventana de CoreGraphics. (Utilice la kCGWindowBoundsclave que devuelve a CGRect(una estructura con 4 dobles, X, Y, ancho, alto).

Deberá filtrar a las ventanas en pantalla. Si ya buscó la lista, puede usar la kCGWindowIsOnscreenclave para determinar si la ventana está visible. Devuelve un CFBoolean. Dado que esa clave es opcional, deberá realizar una prueba de null. Sin embargo, si está comenzando desde cero, sería mejor usar la kCGWindowListOptionOnScreenOnly Constante de opción de ventana cuando llame inicialmente a CGWindowListCopyWindowInfo().

Además de iterar todas las ventanas, la CGWindowListCopyWindowInfo()función toma un CGWindowIDparámetro relativeToWindowy puede agregar (con o bit a bit) kCGWindowListOptionOnScreenAboveWindowa las opciones.

Finalmente, puede encontrar que la limitación a las ventanas asociadas con la sesión actual puede ser útil, y debe mapear CGWindowListCreate()usando una sintaxis similar a la CopyInfo()variante. Devuelve una matriz de números de ventana a los que podría limitar su búsqueda en el diccionario, o pasar esa matriz como argumento a CGWindowListCreateDescriptionFromArray().

Como mencioné en mi respuesta anterior, usted "posee" todos los objetos que crea usando Createo Copyfunciones, y es responsable de liberarlos cuando haya terminado con ellos, para evitar pérdidas de memoria.

BinLiu Oct 18 2020 at 18:31
   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");
    }