MacOS'ta Java'da (JNA kullanarak) ön plan penceresi / işlemi nasıl alınır?

Aug 18 2020

Şu anda, MS Windows'ta ön plan (üst) pencere / işlem almak için çalışıyorum. JNA kullanarak macOS'ta benzer bir şey yapmam gerekiyor.

MacOS'taki eşdeğer kod nedir?

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

Yanıtlar

2 DanielWiddis Aug 18 2020 at 22:53

Aslında burada iki soru var, ön plan penceresi ve ön plan süreci. İkisini de cevaplamaya çalışacağım.


Ön plan süreci için JNA'yı kullanmanın kolay bir yolu, Uygulama Hizmetleri API'sini eşlemektir. Bu işlevlerin 10.9'da tanıtıldığını ve artık kullanımdan kaldırıldığını, ancak yine de 10.15'ten itibaren çalıştığını unutmayın. Yeni sürüm içinde AppKitLibrary, aşağıya bakın.

İhtiyaç duyacağınız iki işlevi eşleyerek bu sınıfı oluşturun:

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

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

"Ön plan" işlemi ile elde edilebilir GetFrontProcess(). Bu ProcessSerialNumber, Uygulama Hizmetleri API'sinde kullanılan benzersiz 64 bitlik bir değer olan a adlı bir şeyi döndürür . Bunu kullanıcı alanı kullanımınız için çevirmek için, muhtemelen İşlem Kimliğini istiyorsunuz ve GetProcessPID()bu çeviriyi sizin için yapıyor.

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

Yukarıdakiler çalışırken kullanımdan kaldırılmıştır. Yeni bir uygulama AppKitLibraryşunları kullanmalıdır :

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

Gibi bu kütüphaneyi kullanan üstteki uygulama ile ilgili çoklu diğer StackOverflow soru vardır bu bir . İhtiyaç duyulan tüm içe aktarımları ve nesneleri haritalamak, burada bir cevap için ayıracak zamanımdan çok daha fazla iş, ancak bunu yararlı bulabilirsiniz. Bu API'ye erişmek için Rococoa çerçevesinin (kaputun altında JNA kullanan ancak JNAerator aracılığıyla tüm AppKit'i zaten haritalayan) nasıl kullanılacağını bulmak muhtemelen daha kolaydır . Bazı javadoc'lar burada .

De vardır çözümler kullanarak AppleScriptkomut satırı kullanarak aracılığıyla Java dan yürütebileceği olduğunu Runtime.exec()ve çıkışı yakalayan.


Ekrandaki ön plan penceresine gelince , biraz daha karmaşık. In my cevap MacOS üzerinde tüm pencereleri yineleme üzerinde önceki soruya, ben kullanan tüm pencerelerin listesini almak için nasıl cevap CoreGraphicsbir de dahil olmak üzere, JNA yoluyla CFDictionaryiçeren daha fazla bilgi.

Bu sözlük anahtarlarından biri , pencere katmanı numarasını temsil eden kCGWindowLayerbir döndürür CFNumber. Dokümanlar bunun 32 bit olduğunu ve bu intValue()nedenle uygun olduğunu belirtir. Sayı "çizim sırası" dır, bu nedenle daha yüksek bir sayı, daha düşük bir sayının üzerine yazacaktır. Böylece, alınan tüm pencereleri yineleyebilir ve maksimum sayıyı bulabilirsiniz. Bu, "ön plan" katmanı olacaktır.

Bazı uyarılar var:

  • Aslında sadece 20 katman mevcuttur. Çoğu şey bir katmanı paylaşır.
  • Katman 1000, ekran koruyucudur. 1000 ve üzeri katmanları göz ardı edebilirsiniz.
  • Katman 24, genellikle en üstte olan Dock'tur ve Katman 25 (yuvadaki simgeler) daha yüksek bir düzeydedir.
  • Katman 0, masaüstünün geri kalanı olarak görünür.
  • Hangi pencerenin "üstte" olduğu, ekranın neresine baktığınıza bağlıdır. Yuvanın üzerinde, yuva ön planda (veya uygulama simgesi) olacaktır. Ekranın geri kalanında, değerlendirmekte olduğunuz pikseli CoreGraphics penceresinden elde edilen ekran dikdörtgeniyle karşılaştırmanız gerekir. ( kCGWindowBoundsA CGRect(4 çift, X, Y, genişlik, yüksekliğe sahip bir yapı) döndüren anahtarı kullanın .

Ekrandaki pencereleri filtrelemeniz gerekecek. Listeyi zaten getirdiyseniz kCGWindowIsOnscreen, pencerenin görünür olup olmadığını belirlemek için anahtarı kullanabilirsiniz . Bir CFBoolean. Bu anahtar isteğe bağlı olduğundan test etmeniz gerekecek null. Bununla birlikte, sıfırdan başlıyorsanız, ilk aradığınızda kCGWindowListOptionOnScreenOnly Pencere Seçeneği Sabitini kullanmanız daha iyi olacaktır CGWindowListCopyWindowInfo().

Tüm pencereleri yinelemenin yanı sıra , CGWindowListCopyWindowInfo()işlev bir CGWindowIDparametre alır ve seçeneklere relativeToWindow(bit düzeyinde veya ile) ekleyebilirsiniz kCGWindowListOptionOnScreenAboveWindow.

Son olarak, mevcut oturumla ilişkili pencerelerle sınırlamanın yararlı olabileceğini fark edebilirsiniz CGWindowListCreate()ve CopyInfo()varyanta benzer sözdizimi kullanarak eşlemelisiniz. Sözlük aramanızı sınırlayabileceğiniz veya bu diziyi argüman olarak iletebileceğiniz bir dizi pencere numarası döndürür CGWindowListCreateDescriptionFromArray().

Önceki cevabımda belirtildiği gibi, kullanarak oluşturduğunuz her nesneye Createveya Copyişlevlere "sahip olursunuz" ve bellek sızıntılarını önlemek için işiniz bittiğinde bunları serbest bırakmaktan sorumlusunuz.

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