Eine Swift -Fensterverwaltungsbibliothek für macOS
In den letzten Jahren sind viele Entwickler früher unter Linux und Windows auf Mac migriert, um ihre hervorragende Hardware und UNIX-basierte Betriebssysteme zu erhalten, die "nur funktioniert".
Aber auf dem Weg gaben wir uns etwas Liebes auf: Kontrolle über unsere Desktop -Umgebung.
Das Ziel von Swindler ist es, uns dabei zu helfen, diese Kontrolle zurückzunehmen und uns das Beste aus beiden Welten zu geben.
Das Schreiben von Fenstermanagern für macOS ist schwierig. Es gibt viele systemische Herausforderungen, einschließlich begrenzter und schlecht dokumentierter APIs. Alle Fenstermanager auf MacOS müssen die C-basierten APIs für Barrierefreiheit verwenden, die schwierig zu bedienen sind und selbst überraschend fehlerhaft sind.
Infolgedessen ist die Auswahl der Fenstermanager ziemlich begrenzt, und viele der da draußen haben nervige Fehler, wie Gefrierpunkte, Rassenbedingungen, "Phantomfenster" und nicht "sehen" Fenster, die tatsächlich da sind. Je ausgefeilter der Fenstermanager ist, desto mehr stützt er sich auf diese APIs und desto mehr werden diese Fehler angezeigt.
Swindlers Aufgabe ist es, es einfach zu machen, leistungsstarke Fenstermanager mit einer gut dokumentierten Swift-API und Abstraktionsschicht zu schreiben. Es befasst sich mit den Problemen der API der Barrierefreiheit mit diesen Funktionen:
Die API von Swindler ist dank Swift vollständig dokumentiert und typfrei. Es ist viel einfacher und sicherer zu bedienen als die C-basierten APIs auf Accessibility. (Siehe Beispiel unten.)
Fenstermanager auf macOS verlassen sich auf IPC: Sie bitten um eine Bewerbung um die Position eines Fensters, warten Sie , bis sie antwortet , um sie bewegt oder fokussiert zu werden, und warten Sie , bis die Anwendung einhält (oder nicht). Die meiste Zeit funktioniert dies in Ordnung, aber es funktioniert der Eventschleife der Fernanwendung, was zu langen, mehrfach Sekundensprungverzögerungen führen kann.
Swindler unterhält ein Modell aller Anwendungen und Fensterzustände, sodass Ihr Code alles über die Fenster auf dem Bildschirm weiß. Die Lesevorgänge sind augenblicklich , da der gesamte Staat innerhalb des Prozesses Ihrer Bewerbung zwischengespeichert wird und auf dem neuesten Stand ist. Swindler wird ausgiebig getestet, um sicherzustellen, dass es in jeder Situation mit dem System übereinstimmt.
Wenn Sie beispielsweise eine Menge Fenster gleichzeitig ändern müssen, können Sie dies ohne Angst vor einer nicht reagierenden Anwendung tun, die alles andere aufhält. Schreibanfragen werden asynchron und gleichzeitig entsandt, und die vielversprechende API von Swindler erleichtert es einfach, mit dem Betriebszustand Schritt zu halten.
Anspruchsvollere Fenstermanager müssen Ereignisse unter Windows beobachten, aber die API der Beobachter ist nicht gut dokumentiert und lässt häufig Ereignisse aus, die Sie möglicherweise erwarten, oder liefert sie in der falschen Reihenfolge. Beispielsweise ist die folgende Situation üblich, wenn ein neues Fenster auftaucht:
1. MainWindowChanged on com.google.chrome to <window1>
2. WindowCreated on com.google.chrome: <window1>
Das Problem sehen? Mit Swindler werden alle Ereignisse in der erwarteten Reihenfolge ausgestrahlt, und fehlende sind ausgefüllt. Swindlers In-Memory-Zustand wird immer mit sich selbst und den Ereignissen übereinstimmen, die Sie erhalten, um viele Fehler zu vermeiden, die schwer zu diagnostizieren sind.
Als Bonus werden Ereignisse, die durch Ihren Code verursacht werden, als solche markiert , sodass Sie sie nicht als Benutzeraktionen antworten. Allein diese Funktion ermöglicht eine ganz neue Auswahl an Raffinesse.
Der folgende Code weist einem Raster alle Fenster auf dem Bildschirm zu. Beachten Sie die Einfachheit und Kraft der versprochenbasierten API. Anfragen werden gleichzeitig und im Hintergrund nicht seriell versandt.
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 )
}Es ist einfach, nach Ereignissen zu achten. So würden Sie Snap-to-Grid implementieren:
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
}Ihre Bewerbung muss Zugriff auf die vertrauenswürdige AX -API anfordern. Verwenden Sie dazu diesen Code einfach in Ihrem 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
}Viele Helfer oder auf andere Weise "spezielle" App -Komponenten antworten nicht auf die AX -Anfragen oder antworten mit einem Fehler. Infolgedessen wird erwartet, dass eine Reihe von Nachrichten wie diese angezeigt werden:
<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
Derzeit sind diese protokolliert, da es schwierig ist festzustellen, ob eine App "fehlschlagen" sollte (insbesondere bei Zeitüberschreitungen). Solange die Dinge funktionieren, können Sie sie ignorieren.
Swindler ist in der Entwicklung und in Alpha . Hier ist der Zustand seiner Hauptmerkmale:
Sie können die gesamte geplante API hier sehen.
API -Dokumentation (neueste Version)
API -Dokumentation (Haupt)
Swindler verwendet Swift Paket Manager.
Klonen Sie das Projekt, dann in Ihrem Shell -Lauf:
$ cd Swindler
$ git submodule init
$ git submodule update
An diesem Punkt sollten Sie in Xcode Swindler bauen und auf dem Weg beginnen!
Sie können das Beispielprojekt aus der Befehlszeile ausführen.
swift run
Sie können mit uns auf dem Gitter chatten.
Folgen Sie mir auf Twitter: @tmandry
Swindler ist auf Axswift gebaut.