วิธีรับหน้าต่าง / กระบวนการเบื้องหน้าใน Java (โดยใช้ JNA) บน MacOS

Aug 18 2020

ขณะนี้ฉันกำลังดำเนินการเพื่อรับหน้าต่าง / กระบวนการเบื้องหน้า (บนสุด) ใน MS Windows ฉันต้องการทำสิ่งที่คล้ายกันใน macOS โดยใช้ JNA

รหัสเทียบเท่าใน 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

มีคำถามสองข้อที่นี่หน้าต่างเบื้องหน้าและกระบวนการเบื้องหน้า ฉันจะพยายามตอบทั้งสองอย่าง


สำหรับกระบวนการเบื้องหน้าวิธีง่ายๆโดยใช้ JNA คือการแมป Application Services API โปรดทราบว่าฟังก์ชันเหล่านี้ถูกนำมาใช้ใน 10.9 และตอนนี้เลิกใช้งานแล้ว แต่ยังคงใช้งานได้ตั้งแต่ 10.15 เวอร์ชันที่ใหม่กว่าอยู่ในAppKitLibraryโปรดดูด้านล่าง

สร้างคลาสนี้โดยทำแผนที่สองฟังก์ชันที่คุณต้องการ:

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

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

กระบวนการ "เบื้องหน้า" สามารถรับได้ด้วยGetFrontProcess(). ซึ่งส่งคืนสิ่งที่เรียกว่า a ProcessSerialNumberซึ่งเป็นค่า 64 บิตเฉพาะที่ใช้ตลอด Application Services API ในการแปลสำหรับการใช้งานพื้นที่ผู้ใช้ของคุณคุณอาจต้องการรหัสกระบวนการและ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 framework (ซึ่งใช้ JNA ภายใต้ประทุน แต่ได้แมป AppKit ทั้งหมดผ่าน JNAerator แล้ว) เพื่อเข้าถึง API นี้ บางjavadocs อยู่ที่นี่

นอกจากนี้ยังมีโซลูชันที่ใช้AppleScriptซึ่งคุณสามารถดำเนินการจาก Java ผ่านทางบรรทัดคำสั่งโดยใช้Runtime.exec()และจับเอาต์พุต


เกี่ยวกับหน้าต่างเบื้องหน้าบนหน้าจอนั้นค่อนข้างซับซ้อนกว่าเล็กน้อย ในคำตอบของฉันสำหรับคำถามก่อนหน้านี้ของคุณเกี่ยวกับการทำซ้ำ windows ทั้งหมดบน macOS ฉันได้ตอบวิธีรับรายการหน้าต่างทั้งหมดที่ใช้CoreGraphicsผ่าน JNA รวมถึงCFDictionaryข้อมูลเพิ่มเติม

หนึ่งในคีย์พจนานุกรมคือkCGWindowLayerซึ่งจะส่งคืนการCFNumberแสดงหมายเลขเลเยอร์หน้าต่าง เอกสารระบุว่านี่เป็นแบบ 32 บิตดังนั้นจึงintValue()เหมาะสม ตัวเลขนี้คือ "ลำดับการวาด" ดังนั้นตัวเลขที่สูงกว่าจะเขียนทับตัวเลขที่ต่ำกว่า คุณจึงสามารถทำซ้ำบนหน้าต่างที่ดึงข้อมูลทั้งหมดและค้นหาจำนวนสูงสุดได้ นี่จะเป็นเลเยอร์ "พื้นหน้า"

มีข้อแม้บางประการ:

  • จริงๆมีเพียง 20 ชั้นเท่านั้น หลายสิ่งใช้เลเยอร์ร่วมกัน
  • Layer 1000 คือสกรีนเซฟเวอร์ คุณสามารถละเว้นเลเยอร์ตั้งแต่ 1,000 ขึ้นไปได้
  • Layer 24 คือ Dock ซึ่งมักจะอยู่ด้านบนโดยมี Layer 25 (ไอคอนบน Dock) อยู่ในระดับที่สูงกว่า
  • เลเยอร์ 0 ดูเหมือนจะเป็นส่วนที่เหลือของเดสก์ท็อป
  • หน้าต่างใด "อยู่ด้านบน" ขึ้นอยู่กับตำแหน่งบนหน้าจอที่คุณมอง บนท่าเรือท่าเรือจะอยู่เบื้องหน้า (หรือไอคอนแอปพลิเคชัน) ในส่วนที่เหลือของหน้าจอคุณต้องตรวจสอบพิกเซลที่คุณกำลังประเมินเทียบกับสี่เหลี่ยมหน้าจอที่ได้รับจากหน้าต่าง CoreGraphics (ใช้kCGWindowBoundsคีย์ที่ส่งคืน a CGRect(โครงสร้างที่มี 4 คู่ X, Y, กว้าง, สูง)

คุณจะต้องกรองไปที่หน้าต่างบนหน้าจอ หากคุณดึงรายการมาแล้วคุณสามารถใช้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");
    }