วิธีรับหน้าต่าง / กระบวนการเบื้องหน้าใน Java (โดยใช้ JNA) บน MacOS
ขณะนี้ฉันกำลังดำเนินการเพื่อรับหน้าต่าง / กระบวนการเบื้องหน้า (บนสุด) ใน 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));
คำตอบ
มีคำถามสองข้อที่นี่หน้าต่างเบื้องหน้าและกระบวนการเบื้องหน้า ฉันจะพยายามตอบทั้งสองอย่าง
สำหรับกระบวนการเบื้องหน้าวิธีง่ายๆโดยใช้ 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
คีย์ที่ส่งคืน aCGRect
(โครงสร้างที่มี 4 คู่ X, Y, กว้าง, สูง)
คุณจะต้องกรองไปที่หน้าต่างบนหน้าจอ หากคุณดึงรายการมาแล้วคุณสามารถใช้kCGWindowIsOnscreenคีย์เพื่อกำหนดว่าจะมองเห็นหน้าต่างได้หรือไม่ มันส่งคืนไฟล์CFBoolean
. null
ตั้งแต่ที่สำคัญที่จะเป็นตัวเลือกที่คุณจะต้องทดสอบ แต่ถ้าคุณจะเริ่มต้นจากอะไรมันจะดีกว่าที่จะใช้kCGWindowListOptionOnScreenOnly
คงหน้าต่างตัวเลือกCGWindowListCopyWindowInfo()เมื่อคุณเริ่มการโทร
นอกเหนือจากการวนซ้ำหน้าต่างทั้งหมดแล้วCGWindowListCopyWindowInfo()ฟังก์ชันยังรับCGWindowIDพารามิเตอร์relativeToWindow
และคุณสามารถเพิ่ม (ด้วยบิตหรือ) kCGWindowListOptionOnScreenAboveWindow
ให้กับตัวเลือกได้
สุดท้ายคุณอาจพบว่าการ จำกัด เฉพาะหน้าต่างที่เชื่อมโยงกับเซสชันปัจจุบันอาจเป็นประโยชน์และคุณควรแมปCGWindowListCreate()โดยใช้ไวยากรณ์ที่คล้ายกันกับCopyInfo()
ตัวแปร ก็จะส่งกลับอาร์เรย์ของตัวเลขหน้าต่างที่คุณสามารถ จำกัด CGWindowListCreateDescriptionFromArray()การค้นหาพจนานุกรมของคุณหรือผ่านอาร์เรย์ที่เป็นอาร์กิวเมนต์ไปยัง
ดังที่ได้กล่าวไว้ในคำตอบก่อนหน้าของฉันคุณ "เป็นเจ้าของ" ทุกวัตถุที่คุณสร้างโดยใช้Create
หรือCopy
ฟังก์ชันและมีหน้าที่ปล่อยวัตถุเหล่านั้นเมื่อคุณทำเสร็จแล้วเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำ
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");
}