Una biblioteca de gestión de ventanas rápidas para macOS
En los últimos años, muchos desarrolladores anteriormente en Linux y Windows han migrado a Mac por su excelente hardware y su sistema operativo basado en UNIX que "solo funciona".
Pero en el camino nos entregamos algo querido: controlar nuestro entorno de escritorio.
El objetivo de Swindler es ayudarnos a recuperar ese control y darnos lo mejor de ambos mundos.
Escribir administradores de ventanas para MacOS es difícil. Hay muchos desafíos sistémicos, que incluyen API limitadas y mal documentadas. Todos los administradores de ventanas en MacOS deben usar las API de accesibilidad basadas en C, que son difíciles de usar y son sorprendentemente errores.
Como resultado, la selección de administradores de ventanas es bastante limitada, y muchos de los que están por ahí tienen errores molestos, como congelaciones, condiciones de carrera, "ventanas fantasmas" y no "viendo" ventanas que realmente están allí. Cuanto más sofisticado sea el administrador de la ventana, más se basa en estas API y más comienzan a aparecer estos errores.
El trabajo de Swindler es facilitar la escritura de gerentes de ventanas potentes utilizando una API y abstracción bien documentada y una capa de abstracción. Aborda los problemas de la API de accesibilidad con estas características:
La API de Swindler está completamente documentada y segura de tipo gracias a Swift. Es mucho más fácil y más seguro de usar que las API de accesibilidad basadas en C. (Vea el ejemplo a continuación).
Los administradores de la ventana en MacOS confían en IPC: solicita una aplicación para la posición de una ventana, esperan a que responda, solicite que se mueva o se concentre, luego espere a que la aplicación cumpla (o no). La mayoría de las veces esto funciona bien, pero funciona a merced del bucle de eventos de la aplicación remota, lo que puede conducir a retrasos largos y multiséculos.
Swindler mantiene un modelo de todas las aplicaciones y estados de ventana, por lo que su código sabe todo sobre las ventanas en la pantalla. Las lecturas son instantáneas , porque todo el estado se almacena en caché dentro del proceso de su solicitud y se mantiene actualizado. Swindler se prueba ampliamente para garantizar que se mantenga consistente con el sistema en cualquier situación.
Si necesita cambiar el tamaño de muchas ventanas simultáneamente, por ejemplo, puede hacerlo sin temor a una aplicación que no responda que mantenga todo lo demás. Las solicitudes de escritura se envían de manera asincrónica y simultánea, y la API basada en la promesa de Swindler facilita el mantenimiento del estado de las operaciones.
Los administradores de ventanas más sofisticados tienen que observar eventos en Windows, pero la API del observador no está bien documentado y a menudo deja de lado los eventos que puede esperar o los entrega en el orden incorrecto. Por ejemplo, la siguiente situación es común cuando aparece una nueva ventana:
1. MainWindowChanged on com.google.chrome to <window1>
2. WindowCreated on com.google.chrome: <window1>
¿Ves el problema? Con Swindler, todos los eventos se emiten en el orden esperado, y los faltantes se completan. El estado en memoria de Swindler siempre será consistente consigo mismo y con los eventos que recibe, evitando muchos errores que son difíciles de diagnosticar.
Como beneficio adicional, los eventos causados por su código están marcados como tales, por lo que no responde a ellos como acciones del usuario. Esta característica por sí sola hace posible un nivel completamente nuevo de sofisticación.
El siguiente código asigna todas las ventanas en la pantalla a una cuadrícula. Tenga en cuenta la simplicidad y el poder de la API basada en la promesa. Las solicitudes se envían simultáneamente y en segundo plano, no en serie.
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 )
}Ver eventos es simple. Así es como implementaría 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
}Su aplicación debe solicitar acceso a la API de AX de confianza. Para hacer esto, simplemente use este código en su 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
}Muchos componentes de la aplicación "especiales" a ayuda o de otra manera no responden a las solicitudes de AX ni responden con un error. Como resultado, se espera ver una serie de mensajes como este:
<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
Actualmente se registran porque es difícil determinar si una aplicación "debería" fallar (especialmente en los tiempos de espera). Mientras las cosas parezcan funcionar, puedes ignorarlas.
Swindler está en desarrollo y está en alfa . Aquí está el estado de sus características principales:
Puedes ver toda la API planificada aquí.
Documentación de API (último lanzamiento)
Documentación de API (principal)
Swindler usa Swift Package Manager.
Clone el proyecto, luego en su shell run:
$ cd Swindler
$ git submodule init
$ git submodule update
¡En este punto, debería poder construir Swindler en Xcode y comenzar en su camino!
Puede ejecutar el proyecto de ejemplo desde la línea de comandos.
swift run
Puedes chatear con nosotros en Gitter.
Sígueme en Twitter: @tmandry
Swindler está construido en Axswift.