ไลบรารีการจัดการหน้าต่างที่รวดเร็วสำหรับ macOS
ในช่วงไม่กี่ปีที่ผ่านมานักพัฒนาหลายคนก่อนหน้านี้ใน Linux และ Windows ได้ย้ายไปที่ Mac สำหรับฮาร์ดแวร์ที่ยอดเยี่ยมและระบบปฏิบัติการที่ใช้ UNIX ที่ "ใช้งานได้"
แต่ระหว่างทางที่เรายอมแพ้สิ่งที่เรารัก: ควบคุมสภาพแวดล้อมเดสก์ท็อปของเรา
เป้าหมายของ Swindler คือการช่วยให้เรากลับมาควบคุมนั้น และให้สิ่งที่ดีที่สุดแก่เราทั้งสองโลก
การเขียนผู้จัดการหน้าต่างสำหรับ macOS นั้นยาก มีความท้าทายที่เป็นระบบมากมายรวมถึง API ที่ จำกัด และมีเอกสารไม่ดี ผู้จัดการหน้าต่างทั้งหมดใน MacOS จะต้องใช้ APIs การเข้าถึง C-based ซึ่งใช้งานยากและเป็นรถที่น่าประหลาดใจ
เป็นผลให้การเลือกผู้จัดการหน้าต่างมี จำกัด ค่อนข้างมากและหลาย ๆ อย่างที่นั่นมีข้อบกพร่องที่น่ารำคาญเช่นการค้างสภาพการแข่งขัน "Phantom Windows" และไม่ใช่ "มองเห็น" หน้าต่างที่มีอยู่จริง ยิ่งผู้จัดการหน้าต่างมีความซับซ้อนมากเท่าไหร่ก็ยิ่งต้องอาศัย API เหล่านี้มากขึ้นและข้อบกพร่องเหล่านี้ก็เริ่มปรากฏขึ้น
งานของ Swindler คือทำให้ง่ายต่อการเขียนผู้จัดการหน้าต่างที่ทรงพลังโดยใช้ Swift API และเลเยอร์ที่เป็นนามธรรม มันแก้ไขปัญหาของ API การเข้าถึงด้วยคุณสมบัติเหล่านี้:
API ของ Swindler ได้รับการบันทึกไว้อย่างเต็มที่และปลอดภัยด้วย Swift มันง่ายกว่าและปลอดภัยกว่าที่จะใช้มากกว่า APIs การเข้าถึง C-based (ดูตัวอย่างด้านล่าง)
ผู้จัดการหน้าต่างบน MacOS พึ่งพา IPC: คุณ ขอ ตำแหน่งของหน้าต่าง รอ ให้มันตอบกลับ ขอ ให้ย้ายหรือมุ่งเน้นจากนั้น รอ ให้แอปพลิเคชันปฏิบัติตาม (หรือไม่) เวลาส่วนใหญ่ใช้งานได้ดี แต่ก็ใช้งานได้ตามความเมตตาของการวนรอบเหตุการณ์ของแอปพลิเคชันระยะไกลซึ่งอาจนำไปสู่ความล่าช้าหลายวินาที
Swindler รักษารูปแบบของแอปพลิเคชันและสถานะหน้าต่างทั้งหมดดังนั้นรหัสของคุณจะรู้ทุกอย่างเกี่ยวกับหน้าต่างบนหน้าจอ การอ่านเป็นแบบทันที เนื่องจากรัฐทั้งหมดถูกแคชอยู่ในกระบวนการของแอปพลิเคชันของคุณและยังคงอยู่เสมอ Swindler ได้รับการทดสอบอย่างกว้างขวางเพื่อให้แน่ใจว่ามันยังคงสอดคล้องกับระบบในทุกสถานการณ์
หากคุณต้องการปรับขนาดหน้าต่างจำนวนมากพร้อมกันคุณสามารถทำได้โดยไม่ต้องกลัวแอปพลิเคชันที่ไม่ตอบสนองที่ถือทุกอย่างอื่น การร้องขอการเขียนจะถูกส่งแบบอะซิงโครนัสและพร้อมกันและ API ตามคำสัญญาของ Swindler ทำให้ง่ายต่อการติดตามสถานะของการดำเนินงาน
ผู้จัดการหน้าต่างที่มีความซับซ้อนมากขึ้นต้องสังเกตเหตุการณ์บน Windows แต่ผู้สังเกตการณ์ API นั้นไม่ได้มีการบันทึกไว้อย่างดีและมักจะออกจากเหตุการณ์ที่คุณคาดหวังหรือส่งพวกเขาในลำดับที่ไม่ถูกต้อง ตัวอย่างเช่นสถานการณ์ต่อไปนี้เป็นเรื่องปกติเมื่อหน้าต่างใหม่ปรากฏขึ้น:
1. MainWindowChanged on com.google.chrome to <window1>
2. WindowCreated on com.google.chrome: <window1>
เห็นปัญหา? ด้วย Swindler เหตุการณ์ทั้งหมดจะถูกปล่อยออกมาตามลำดับที่คาดหวังและเหตุการณ์ที่หายไปจะถูกกรอกข้อมูลสถานะในหน่วยความจำของ Swindler จะสอดคล้องกับตัวเองและกับเหตุการณ์ที่คุณได้รับเสมอหลีกเลี่ยงข้อบกพร่องมากมายที่ยากต่อการวินิจฉัย
เป็นโบนัส เหตุการณ์ที่เกิดจากรหัสของคุณถูกทำเครื่องหมาย เช่นนี้ดังนั้นคุณจะไม่ตอบสนองต่อการกระทำของผู้ใช้ คุณลักษณะนี้เพียงอย่างเดียวทำให้ระดับความซับซ้อนใหม่เป็นไปได้
รหัสต่อไปนี้กำหนด windows ทั้งหมดบนหน้าจอให้กับกริด สังเกตความเรียบง่ายและพลังของ API ตามสัญญา คำขอถูกส่งพร้อมกันและในพื้นหลังไม่ใช่แบบอนุกรม
Swindler . initialize ( ) . then { state -> Void in
let screen = state . screens . first!
let allPlacedOnGrid = screen . knownWindows . enumerate ( ) . map { index , window in
let rect = gridRect ( screen , index )
return window . frame . set ( rect )
}
when ( allPlacedOnGrid ) { _ in
print ( " all done! " )
}
} . catch { error in
// ...
}
func gridRect ( screen : Swindler . Screen , index : Int ) -> CGRect {
let gridSteps = 3
let position = CGSize ( width : screen . width / gridSteps ,
height : screen . height / gridSteps )
let size = CGPoint ( x : gridSize . width * ( index % gridSteps ) ,
y : gridSize . height * ( index / gridSteps ) )
return CGRect ( origin : position , size : size )
}การดูเหตุการณ์เป็นเรื่องง่าย นี่คือวิธีที่คุณจะใช้ snap-to-grid:
swindlerState . on { ( event : WindowMovedEvent ) in
guard event . external == true else {
// Ignore events that were caused by us.
return
}
let snapped = closestGridPosition ( event . window . frame . value )
event . window . frame . value = snapped
}แอปพลิเคชันของคุณจะต้องขอการเข้าถึง AX API ที่เชื่อถือได้ ในการทำเช่นนี้เพียงใช้รหัสนี้ใน AppDelegate ของคุณ:
func applicationDidFinishLaunching ( _ aNotification : Notification ) {
guard AXSwift . checkIsProcessTrusted ( prompt : true ) else {
print ( " Not trusted as an AX process; please authorize and re-launch " )
NSApp . terminate ( self )
return
}
// your code here
}ส่วนประกอบแอพ "พิเศษ" จำนวนมากไม่ตอบสนองต่อคำขอ AX หรือตอบกลับด้วยข้อผิดพลาด เป็นผลให้คาดว่าจะเห็นข้อความจำนวนหนึ่งเช่นนี้:
<Debug>: Window <AXUnknown "<AXUIElement 0x610000054eb0> {pid=464}" (pid=464)> has subrole AXUnknown, unwatching
<Debug>: Application invalidated: com.apple.dock
<Debug>: Couldn't initialize window for element <AXUnknown "<AXUIElement 0x610000054eb0> {pid=464}" (pid=464)> () of com.google.Chrome: windowIgnored(<AXUnknown "<AXUIElement 0x610000054eb0> {pid=464}" (pid=464)>)
<Notice>: Could not watch application com.apple.dock (pid=308): invalidObject(AXError.NotificationUnsupported)
<Debug>: Couldn't initialize window for element <AXScrollArea "<AXUIElement 0x61800004ed90> {pid=312}" (pid=312)> (desktop) of com.apple.finder: AXError.NotificationUnsupported
ขณะนี้สิ่งเหล่านี้ถูกบันทึกไว้เพราะมันยากที่จะตรวจสอบว่าแอพ "ควร" ล้มเหลว (โดยเฉพาะเมื่อหมดเวลา) ตราบใดที่สิ่งต่าง ๆ ดูเหมือนจะใช้งานได้คุณสามารถเพิกเฉยได้
Swindler อยู่ในระหว่างการพัฒนาและอยู่ใน อัลฟ่า นี่คือสถานะของคุณสมบัติที่สำคัญ:
คุณสามารถดู API ที่วางแผนไว้ทั้งหมดได้ที่นี่
เอกสาร API (รุ่นล่าสุด)
เอกสาร API (หลัก)
Swindler ใช้ Swift Package Manager
โคลนโครงการจากนั้นในการรันเชลล์ของคุณ:
$ cd Swindler
$ git submodule init
$ git submodule update
ณ จุดนี้คุณควรจะสามารถสร้าง Swindler ใน Xcode และเริ่มต้นในทางของคุณ!
คุณสามารถเรียกใช้โครงการตัวอย่างจากบรรทัดคำสั่ง
swift run
คุณสามารถแชทกับเราได้ที่ Gitter
ติดตามฉันบน Twitter: @tmandry
Swindler สร้างขึ้นบน Axswift