Une bibliothèque de gestion de fenêtres rapides pour macOS
Au cours des dernières années, de nombreux développeurs autrefois sur Linux et Windows ont migré vers Mac pour leur excellent matériel et leur système d'exploitation basé sur Unix qui "fonctionne juste".
Mais en cours de route, nous avons abandonné quelque chose de cher: contrôler notre environnement de bureau.
Le but de Swindler est de nous aider à reprendre ce contrôle et à nous donner le meilleur des deux mondes.
Il est difficile d'écrire des gestionnaires de fenêtres pour macOS. Il existe de nombreux défis systémiques, y compris des API limitées et mal documentées. Tous les gestionnaires de fenêtres sur macOS doivent utiliser les API d'accessibilité basés sur C, qui sont difficiles à utiliser et sont étonnamment buggy eux-mêmes.
En conséquence, la sélection des gestionnaires de fenêtres est assez limitée, et bon nombre de celles qui ont des bugs ennuyeux, comme les gel, les conditions de course, les "fenêtres fantômes" et ne pas "voir" des fenêtres qui sont réellement là. Plus le gestionnaire de fenêtres est sophistiqué, plus il s'appuie sur ces API et plus ces bogues commencent à apparaître.
Le travail de Swindler consiste à faciliter l'écriture de gestionnaires de fenêtres puissants à l'aide d'une API Swift bien documentée et d'une couche d'abstraction. Il aborde les problèmes de l'API d'accessibilité avec ces fonctionnalités:
L'API de Swindler est entièrement documentée et en sécurité grâce à Swift. C'est beaucoup plus facile et plus sûr à utiliser que les API d'accessibilité basée sur C. (Voir l'exemple ci-dessous.)
Les gestionnaires de fenêtres sur macOS s'appuient sur IPC: vous demandez une application pour une position d'une fenêtre, attendez qu'il réponde, demandez-vous qu'il soit déplacé ou concentré, puis attendez que l'application se conforme (ou non). La plupart du temps, cela fonctionne bien, mais cela fonctionne à la merci de la boucle d'événement de l'application distante, ce qui peut entraîner des retards longs et plusieurs secondes.
Swindler maintient un modèle de toutes les applications et états de fenêtres, donc votre code sait tout sur les fenêtres à l'écran. Les lectures sont instantanées , car tout l'état est mis en cache dans le processus de votre demande et reste à jour. Swindler est largement testé pour s'assurer qu'il reste cohérent avec le système dans n'importe quelle situation.
Si vous avez besoin de redimensionner de nombreuses fenêtres simultanément, par exemple, vous pouvez le faire sans crainte qu'une application qui ne répond pas, tenant tout le reste. Les demandes d'écriture sont envoyées de manière asynchrone et simultanément, et l'API basée sur les promesses de Swindler facilite la maintenance de l'état des opérations.
Les gestionnaires de fenêtres plus sophistiqués doivent observer des événements sous Windows, mais l'API de l'observateur n'est pas bien documenté et laisse souvent de côté les événements que vous pourriez vous attendre, ou les livre dans le mauvais ordre. Par exemple, la situation suivante est courante lorsqu'une nouvelle fenêtre apparaît:
1. MainWindowChanged on com.google.chrome to <window1>
2. WindowCreated on com.google.chrome: <window1>
Voir le problème? Avec Swindler, tous les événements sont émis dans l'ordre attendu, et ceux qui manquent sont remplis. L'état en mémoire de Swindler sera toujours cohérent avec lui-même et avec les événements que vous recevez, évitant de nombreux bogues difficiles à diagnostiquer.
En prime, les événements causés par votre code sont marqués comme tels, vous ne les répondez donc pas comme actions des utilisateurs. Cette fonctionnalité à elle seule rend possible un tout nouveau niveau de sophistication.
Le code suivant attribue toutes les fenêtres à l'écran à une grille. Notez la simplicité et le pouvoir de l'API basée sur les promesses. Les demandes sont envoyées simultanément et en arrière-plan, pas en série.
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 )
}Regarder des événements est simple. Voici comment vous implémenteriez 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
}Votre demande doit demander l'accès à l'API Axe de confiance. Pour ce faire, utilisez simplement ce code dans votre appdegate:
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
}De nombreux composants d'application auxiliaires ou autrement "spéciaux" ne répondent pas aux demandes de hache ou ne répondent pas avec une erreur. En conséquence, il devrait voir un certain nombre de messages comme celui-ci:
<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
Actuellement, ceux-ci sont enregistrés car il est difficile de déterminer si une application "devrait" échouer (en particulier sur les délais d'attente). Tant que les choses semblent fonctionner, vous pouvez les ignorer.
Swindler est en développement et est en alpha . Voici l'état de ses principales caractéristiques:
Vous pouvez voir toute l'API planifiée ici.
Documentation de l'API (dernière version)
Documentation de l'API (Main)
Swindler utilise Swift Package Manager.
Clone le projet, puis dans votre coquille:
$ cd Swindler
$ git submodule init
$ git submodule update
À ce stade, vous devriez pouvoir construire un escroc dans Xcode et commencer sur votre chemin!
Vous pouvez exécuter l'exemple de projet à partir de la ligne de commande.
swift run
Vous pouvez discuter avec nous sur Gitter.
Suivez-moi sur Twitter: @tmandry
Swindler est construit sur AxeSwift.