MacOS的Swift窗口管理庫
在過去的幾年中,許多以前在Linux上使用的開發人員已將其遷移到Mac,以獲得其出色的硬件和基於Unix的OS,“恰恰起作用”。
但是,一路上,我們放棄了對我們珍愛的東西:控制桌面環境。
Swindler的目的是幫助我們奪回該控制權,並為我們提供兩全其美。
為MacOS編寫窗口經理很難。有很多系統性的挑戰,包括有限的和有據可查的API。 MACOS上的所有窗口管理器都必須使用基於C的可訪問性API,這很難使用並且出奇地越野車本身。
結果,窗口管理器的選擇非常有限,其中許多窗口管理器都有煩人的錯誤,例如凍結,競賽條件,“幻影窗口”,而不是真正存在的窗口。窗口管理器越複雜,它對這些API的依賴越多,這些錯誤就越開始出現。
Swindler的工作是使使用有據可查的Swift API和抽象層撰寫強大的窗口管理器變得容易。它通過以下功能解決了可訪問性API的問題:
借助Swift,Swindler的API已充分記錄和類型的安全性。與基於C的可訪問性API相比,使用要容易得多,更安全。 (請參閱下面的示例。)
MACOS上的窗口管理器依賴於IPC:您向窗口的位置要求應用程序,等待它響應,要求將其移動或專注,然後等待應用程序遵守(或不遵守)。在大多數情況下,這還可以,但是它可以在遠程應用程序的事件循環的擺佈下起作用,這可能會導致長,多秒的延遲。
Swindler維護了所有應用程序和窗口狀態的模型,因此您的代碼知道屏幕上有關窗口的所有內容。讀取是瞬時的,因為所有狀態均在您的應用程序的過程中緩存並保持最新狀態。對Swindler進行了廣泛的測試,以確保其在任何情況下都與系統保持一致。
例如,如果您需要同時調整大小窗口大小,則可以這樣做,而不必擔心一個不反應的應用程序將其他所有內容都持續起來。寫作請求是異步和同時派遣的,而Swindler的基於承諾的API使得可以輕鬆跟上操作狀態。
更複雜的窗口管理人員必須觀察Windows上的事件,但是觀察者API的記錄不佳,並且經常遺漏您可能期望的事件,或者以錯誤的順序交付。例如,當新窗口彈出時,以下情況很常見:
1. MainWindowChanged on com.google.chrome to <window1>
2. WindowCreated on com.google.chrome: <window1>
看到問題了嗎?使用Swindler,所有事件都以預期的順序排放,並且填補了丟失的順序。 Swindler的內存狀態將始終與您自己以及您收到的事件一致,避免了許多難以診斷的錯誤。
作為獎勵,由您的代碼引起的事件標記為這樣,因此您不會以用戶操作對它們響應。僅此功能就可以使全新的複雜水平成為可能。
以下代碼將屏幕上的所有窗口分配給網格。請注意基於承諾的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 )
}觀看事件很簡單。您將如何實現快速網格:
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
}許多助手或其他“特殊”應用程序組件不會響應斧頭請求或錯誤響應。結果,預計會看到許多這樣的消息:
<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正在開發,並且在Alpha 。這是其主要特徵的狀態:
您可以在這裡看到整個計劃的API。
API文檔(最新版本)
API文檔(主要)
Swindler使用Swift Package Manager。
克隆項目,然後在您的外殼運行中:
$ cd Swindler
$ git submodule init
$ git submodule update
在這一點上,您應該能夠在Xcode中構建Swindler,然後從途中開始!
您可以從命令行運行示例項目。
swift run
您可以在Gitter上與我們聊天。
在Twitter上關注我:@tmandry
Swindler建在Axswift上。