Bagaimana cara mendapatkan jendela latar depan / proses di Java (Menggunakan JNA) di MacOS?

Aug 18 2020

Saat ini, saya sedang bekerja untuk mendapatkan jendela / proses latar depan (atas) di MS Windows. Saya perlu melakukan hal serupa di macOS menggunakan JNA.

Apa kode yang setara di macOS?

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

Jawaban

2 DanielWiddis Aug 18 2020 at 22:53

Sebenarnya ada dua pertanyaan di sini, jendela latar depan dan proses latar depan. Saya akan mencoba menjawab keduanya.


Untuk proses latar depan, cara mudah menggunakan JNA adalah dengan memetakan API Layanan Aplikasi . Perhatikan bahwa fungsi ini diperkenalkan pada 10.9 dan sekarang tidak digunakan lagi, tetapi masih berfungsi pada 10.15. Versi yang lebih baru ada di AppKitLibrary, lihat di bawah.

Buat kelas ini, petakan dua fungsi yang Anda perlukan:

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

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

Proses "latar depan" dapat diperoleh dengan GetFrontProcess(). Itu mengembalikan sesuatu yang disebut ProcessSerialNumber, nilai 64-bit unik yang digunakan di seluruh API Layanan Aplikasi. Untuk menerjemahkannya untuk penggunaan ruang pengguna Anda, Anda mungkin menginginkan ID Proses, dan GetProcessPID()melakukan terjemahan itu untuk Anda.

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

Meskipun cara di atas berfungsi, ini sudah usang. Aplikasi baru harus menggunakan AppKitLibrary:

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

Ada beberapa pertanyaan StackOverflow lainnya tentang aplikasi teratas yang menggunakan pustaka ini, seperti yang satu ini . Memetakan semua impor dan objek yang dibutuhkan jauh lebih banyak pekerjaan daripada yang saya punya waktu untuk melakukannya dalam jawaban di sini, tetapi Anda mungkin merasa berguna. Mungkin lebih mudah untuk mengetahui bagaimana menggunakan kerangka kerja Rococoa (yang menggunakan JNA di bawah tenda tetapi telah memetakan semua AppKit melalui JNAerator) untuk mengakses API ini. Beberapa javadoc ada di sini .

Ada juga solusi menggunakan AppleScriptyang Anda dapat mengeksekusi dari Jawa melalui baris perintah menggunakan Runtime.exec()dan menangkap output.


Berkenaan dengan jendela latar depan di layar, ini sedikit lebih rumit. Dalam jawaban saya untuk pertanyaan Anda sebelumnya tentang iterasi semua jendela di macOS, saya menjawab cara mendapatkan daftar semua jendela yang menggunakan CoreGraphicsmelalui JNA, termasuk yang CFDictionaryberisi informasi lebih lanjut.

Salah satu kunci kamus tersebut adalah kCGWindowLayeryang akan mengembalikan CFNumbermewakili nomor lapisan jendela. Dokumen menyatakan ini 32-bit, jadi intValue()sudah sesuai. Angka tersebut adalah "urutan gambar" sehingga angka yang lebih tinggi akan menimpa angka yang lebih rendah. Jadi, Anda dapat mengulang semua jendela yang diambil dan menemukan jumlah maksimum. Ini akan menjadi lapisan "latar depan".

Ada beberapa peringatan:

  • Sebenarnya hanya ada 20 lapisan yang tersedia. Banyak hal berbagi lapisan.
  • Layer 1000 adalah screensaver. Anda dapat mengabaikan lapisan 1000 dan yang lebih tinggi.
  • Layer 24 adalah Dock, biasanya di atas, dengan Layer 25 (ikon di dermaga) pada level yang lebih tinggi.
  • Layer 0 tampaknya merupakan bagian lain dari desktop.
  • Jendela mana yang "di atas" tergantung di mana Anda melihat layar. Di atas dok, dok akan berada di latar depan (atau ikon aplikasi). Di sisa layar, Anda perlu memeriksa piksel yang Anda evaluasi vs. persegi panjang layar yang diperoleh dari jendela CoreGraphics. (Gunakan kCGWindowBoundskunci yang mengembalikan a CGRect(struktur dengan 4 ganda, X, Y, lebar, tinggi).

Anda perlu memfilter ke jendela pada layar. Jika Anda sudah mengambil daftar, Anda dapat menggunakan kCGWindowIsOnscreenkunci untuk menentukan apakah jendela terlihat. Ini mengembalikan a CFBoolean. Karena kunci itu opsional, Anda perlu menguji null. Namun, jika Anda memulai dari nol, akan lebih baik untuk menggunakan kCGWindowListOptionOnScreenOnly Konstanta Opsi Jendela saat Anda pertama kali memanggil CGWindowListCopyWindowInfo().

Selain iterasi semua jendela, CGWindowListCopyWindowInfo()fungsi ini mengambil CGWindowIDparameter relativeToWindowdan Anda dapat menambahkan (dengan bitwise atau) kCGWindowListOptionOnScreenAboveWindowke opsi.

Terakhir, Anda mungkin menemukan bahwa membatasi ke jendela yang terkait dengan sesi saat ini mungkin berguna, dan Anda harus memetakan CGWindowListCreate()menggunakan sintaks yang mirip dengan CopyInfo()varian. Ini mengembalikan larik nomor jendela yang bisa Anda batasi untuk pencarian kamus Anda, atau meneruskan larik itu sebagai argumen CGWindowListCreateDescriptionFromArray().

Seperti yang disebutkan dalam jawaban saya sebelumnya, Anda "memiliki" setiap objek yang Anda buat menggunakan Createatau Copyfungsi, dan bertanggung jawab untuk melepaskannya setelah Anda selesai menggunakannya, untuk menghindari kebocoran memori.

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