MacOSのJava(JNAを使用)でフォアグラウンドウィンドウ/プロセスを取得するにはどうすればよいですか?

Aug 18 2020

現在、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));  

回答

2 DanielWiddis Aug 18 2020 at 22:53

ここには、実際には、フォアグラウンドウィンドウとフォアグラウンドプロセスの2つの質問があります。私は両方に答えようとします。


フォアグラウンドプロセスの場合、JNAを使用する簡単な方法は、Application ServicesAPIをマップすることです。これらの関数は10.9で導入され、現在は非推奨ですが、10.15以降は引き続き機能することに注意してください。新しいバージョンはにありAppKitLibraryます。以下を参照してください。

このクラスを作成し、必要な2つの関数をマッピングします。

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

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

「フォアグラウンド」プロセスは、で取得できますGetFrontProcess()。これはProcessSerialNumber、Application ServicesAPI全体で使用される一意の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());

上記は機能しますが、非推奨です。新しいアプリケーションはAppKitLibrary:を使用する必要があります

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

このライブラリなど、このライブラリを使用する最上位のアプリケーションに関して、他にも複数のStackOverflowの質問があります。必要なすべてのインポートとオブジェクトをマッピングすることは、ここでの回答で行う時間よりもはるかに多くの作業ですが、役立つ場合があります。Rococoaフレームワーク(内部でJNAを使用しますが、すでにJNAeratorを介してすべてのAppKitをマップしています)を使用してこのAPIにアクセスする方法を理解する方がおそらく簡単です。いくつかのjavadocはここにあります。

出力を使用してキャプチャするコマンドラインを介してJavaから実行できるを使用するソリューションもあります。AppleScriptRuntime.exec()


画面の前景ウィンドウに関しては、もう少し複雑です。では私の答えのMacOS上のすべてのウィンドウを反復処理上の以前の質問に、私が使用しているすべてのウィンドウのリストを取得する方法を答えCoreGraphics含め、JNA経由CFDictionary含むより多くの情報。

それらの辞書キーの1つは、ウィンドウレイヤー番号を表すkCGWindowLayerを返すものCFNumberです。ドキュメントには、これは32ビットであると記載されているためintValue()、適切です。番号は「抽選順」であるため、番号が大きいほど小さい番号が上書きされます。したがって、取得したすべてのウィンドウを繰り返し処理して、最大数を見つけることができます。これが「フォアグラウンド」レイヤーになります。

いくつかの注意点があります:

  • 実際に利用できるレイヤーは20個だけです。多くのものがレイヤーを共有します。
  • レイヤー1000はスクリーンセーバーです。1000以上のレイヤーは無視してかまいません。
  • レイヤー24はドックで、通常は一番上にあり、レイヤー25(ドックのアイコン)が上位にあります。
  • レイヤー0はデスクトップの残りの部分のようです。
  • どのウィンドウが「一番上」にあるかは、画面のどこを見ているかによって異なります。ドックの上では、ドックはフォアグラウンド(またはアプリケーションアイコン)になります。画面の残りの部分で、評価しているピクセルとCoreGraphicsウィンドウから取得した画面の長方形を確認する必要があります。((4つのdouble、X、Y、width、heightを持つ構造体)kCGWindowBoundsを返すキーを使用しますCGRect

画面上のウィンドウにフィルターをかける必要があります。すでにリストを取得している場合は、kCGWindowIsOnscreenキーを使用してウィンドウが表示されているかどうかを判断できます。を返しますCFBoolean。そのキーはオプションなので、をテストする必要がありますnull。ただし、何もないところから始める場合は、最初にを呼び出すときにkCGWindowListOptionOnScreenOnly ウィンドウオプション定数を使用することをお勧めしますCGWindowListCopyWindowInfo()。

すべてのウィンドウを反復することに加えて、CGWindowListCopyWindowInfo()関数はCGWindowIDパラメーターrelativeToWindowを受け取りkCGWindowListOptionOnScreenAboveWindow、オプションに(ビット単位またはで)追加できます。

最後に、現在のセッションに関連付けられているウィンドウに制限すると便利な場合があるCGWindowListCreate()ため、CopyInfo()バリアントと同様の構文を使用してマップする必要があります。辞書検索を制限したり、その配列を引数としてに渡すことができるウィンドウ番号の配列を返しますCGWindowListCreateDescriptionFromArray()。

以前の回答で述べたように、CreateユーザーはCopy、または関数を使用して作成するすべてのオブジェクトを「所有」し、メモリリークを回避するために、オブジェクトが終了したらそれらを解放する責任があります。

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