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上。