Uma biblioteca de gerenciamento de janelas rápida para macOS
Nos últimos anos, muitos desenvolvedores anteriormente no Linux e no Windows migraram para o MAC para seu excelente hardware e sistema operacional baseado em UNIX que "apenas funciona".
Mas, ao longo do caminho, desistimos de algo caro para nós: controle sobre o nosso ambiente de desktop.
O objetivo do Swindler é nos ajudar a recuperar esse controle e nos dar o melhor dos dois mundos.
Escrever gerentes de janelas para macOS é difícil. Existem muitos desafios sistêmicos, incluindo APIs limitadas e mal documentadas. Todos os gerentes de janelas do MacOS devem usar as APIs de acessibilidade baseadas em C, que são difíceis de usar e são surpreendentemente buggy.
Como resultado, a seleção de gerentes de janelas é bastante limitada, e muitos dos que estão por aí têm bugs irritantes, como congelamentos, condições de corrida, "Windows Phantom" e não "vendo" janelas que estão realmente lá. Quanto mais sofisticada o gerenciador de janelas é, mais ele se baseia nessas APIs e mais esses bugs começam a aparecer.
O trabalho de Swindler é facilitar a redação de poderosos gerentes de janelas usando uma API SWIFT bem documentada e uma camada de abstração. Ele aborda os problemas da API de acessibilidade com esses recursos:
A API de Swindler está totalmente documentada e segura por tipo graças ao Swift. É muito mais fácil e seguro de usar do que as APIs de acessibilidade baseada em C. (Veja o exemplo abaixo.)
Os gerentes de janelas no MacOS confiam no IPC: você solicita a posição de uma janela, aguarde que ele responda, solicite que ele seja movido ou focado e aguarde a conformidade do aplicativo (ou não). Na maioria das vezes, isso funciona bem, mas funciona à mercê do loop de eventos do aplicativo remoto, o que pode levar a atrasos longos e com vários segundos.
Swindler mantém um modelo de todos os aplicativos e estados de janela, para que seu código saiba tudo sobre as janelas na tela. As leituras são instantâneas , porque todo o estado é armazenado em cache no processo do seu aplicativo e permanece atualizado. O Swindler é extensivamente testado para garantir que permaneça consistente com o sistema em qualquer situação.
Se você precisar redimensionar muitas janelas simultaneamente, por exemplo, você pode fazê -lo sem medo de um aplicativo que não responde, segurando todo o resto. As solicitações de gravação são despachadas de forma assíncrona e simultaneamente, e a API baseada em promessa da Swindler facilita o acompanhamento do estado das operações.
Os gerentes de janelas mais sofisticados precisam observar eventos no Windows, mas a API do Observer não está bem documentada e geralmente deixa os eventos que você pode esperar, ou os entrega na ordem errada. Por exemplo, a situação a seguir é comum quando uma nova janela aparece:
1. MainWindowChanged on com.google.chrome to <window1>
2. WindowCreated on com.google.chrome: <window1>
Veja o problema? Com o Swindler, todos os eventos são emitidos na ordem esperada, e os ausentes são preenchidos. O estado de memória de Swindler sempre será consistente consigo mesmo e com os eventos que você recebe, evitando muitos bugs difíceis de diagnosticar.
Como bônus, os eventos causados pelo seu código são marcados como tal, para que você não responda a eles como ações do usuário. Somente esse recurso torna possível um nível totalmente novo de sofisticação.
O código a seguir atribui todas as janelas na tela a uma grade. Observe a simplicidade e o poder da API baseada em promessa. As solicitações são despachadas simultaneamente e em segundo plano, não em 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 )
}Observar os eventos é simples. Veja como você implementaria o 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
}Seu aplicativo deve solicitar acesso à API AXIFICADA CONFIANÇA. Para fazer isso, basta usar este código em seu 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
}Muitos componentes de aplicativos auxiliares ou "especiais" não respondem às solicitações do AX ou respondem com um erro. Como resultado, espera -se que ele veja uma série de mensagens como esta:
<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
Atualmente, eles são registrados porque é difícil determinar se um aplicativo "deve" falhar (especialmente nos tempos limite). Enquanto as coisas parecerem estar funcionando, você pode ignorá -las.
Swindler está em desenvolvimento e está em alfa . Aqui está o estado de seus principais recursos:
Você pode ver toda a API planejada aqui.
Documentação da API (versão mais recente)
Documentação da API (principal)
Swindler usa o Swift Package Manager.
Clone o projeto, em seguida, em sua concha, execute:
$ cd Swindler
$ git submodule init
$ git submodule update
Neste ponto, você poderá construir Swindler no Xcode e começar no seu caminho!
Você pode executar o projeto de exemplo na linha de comando.
swift run
Você pode conversar conosco com gitter.
Siga -me no Twitter: @tmandry
Swindler é construído no AxSwift.