Mosaico: Il Tiling Window Manager per macOS che Ho Scritto per Smettere di Riavviare Yabai

C’è un momento preciso in cui un side project nasce: è quando digiti lo stesso comando per la quindicesima volta in un giorno e una vocina dentro di te dice “sai che potresti anche scrivertelo da solo, ‘sto coso?”.
Il mio comando era yabai --restart-service.
Uso il tiling da anni: le finestre si dispongono da sole a mosaico sullo schermo, senza sovrapposizioni, e tu le governi da tastiera senza mai toccare il mouse. Su macOS lo standard de facto è yabai, accompagnato da skhd per le scorciatoie e, nel mio caso, da un plugin SwiftBar per avere i comandi in menubar. Tre strumenti, due file di configurazione, un piccolo ecosistema che negli anni mi ero cesellato con cura.
Poi ho installato la beta di macOS 27. E l’ecosistema ha iniziato a scricchiolare: yabai che perde le finestre, layout che si scombinano al risveglio dallo standby, e il rito quotidiano del restart per rimettere le cose a posto. Non è colpa di yabai, sia chiaro — inseguire le beta di Apple è un lavoro ingrato e i maintainer fanno miracoli. Ma io il tiling lo uso per lavorare, e un attrezzo di lavoro che devi riavviare di continuo smette di essere un attrezzo e diventa un giocattolino.
Detto, fatto (di nuovo). Benvenuti in Mosaico.
Cos’è Mosaico
Mosaico è un tiling window manager per macOS racchiuso in una singola app. Niente formule Homebrew, niente file di configurazione da editare, niente terminale: trascini l’app in Applicazioni, concedi un permesso (Accessibilità) e le finestre iniziano a organizzarsi da sole. Tutto il resto — spazi tra le finestre, scorciatoie, esclusioni — si configura da un’interfaccia grafica.
In pratica sostituisce l’intero trittico yabai + skhd + plugin di menubar con un’unica app che vive accanto all’orologio, con l’icona ⊞.
Per chi non ha mai usato un tiling window manager, l’analogia più semplice è questa: immagina che lo schermo sia un vassoio e le finestre siano tessere di un mosaico (da qui il nome, che fantasia eh). Ogni volta che apri una finestra nuova, il vassoio si ridivide da solo perché tutte le tessere abbiano il loro posto, senza buchi e senza sovrapposizioni. Tu non trascini più niente: al massimo dici “questa tessera scambiala con quella”.
Cosa Fa (La Lista della Spesa)
- Tiling BSP automatico — le finestre dividono lo schermo ad albero binario; una nuova finestra divide quella che ha il focus (layout a spirale), con gap e padding configurabili
- Integrazione nativa con gli Spaces — un albero di tiling indipendente per ogni spazio di Mission Control; il layout viene applicato solo allo spazio visibile
- Drop zone con overlay visuale — trascini una finestra sopra un’altra: al centro le scambia, sulla metà alta/bassa le impila in verticale, sulla metà sinistra/destra le affianca in orizzontale. La zona di destinazione si illumina mentre trascini
- Ridimensionamento manuale “edge-aware” — trascini un bordo qualsiasi e i vicini su quel lato assorbono lo spazio, esattamente come farebbe yabai
- Hotkey globali — preset vim-flavored (focus/swap/warp con H/J/K/L), completamente rimappabile dalla finestra impostazioni
- Supporto mouse — il puntatore segue il focus (opzionale); ⌥+trascina sposta, ⌥+tasto destro ridimensiona; sposti una finestra su un altro spazio con ⇧⌥1…7
- Regole intelligenti — dialogs, sheet, picture-in-picture e finestre non ridimensionabili flottano da sole e restano sopra il layout; puoi escludere intere app o singole finestre direttamente dal menu in menubar
- Impostazioni GUI — gap, recorder di scorciatoie, esclusioni; tutto persistito in JSON. L’avvio al login (novità della 0.2.0) è un toggle a un click direttamente in menubar, e lo stato viene letto dal sistema: riflette sempre la realtà, anche se lo cambi dalle Impostazioni di Sistema. Se macOS richiede l’approvazione del login item, le impostazioni te lo dicono e ti portano al pannello giusto
Le scorciatoie predefinite sono le stesse che usavo con skhd, quindi la mia memoria muscolare non ha dovuto ricominciare da capo:
| Shortcut | Azione |
|---|---|
⌥ H/J/K/L | Focus finestra ovest/sud/nord/est |
⇧⌥ H/J/K/L | Swap finestre |
⌃⌥ H/J/K/L | Warp (sposta + nuovo split) |
⇧⌥ R | Ruota il layout |
⇧⌥ T | Toggle float |
⇧⌥ M | Massimizza / ripristina |
⇧⌥ E | Ribilancia tutte le finestre |
⇧⌥ 1…7 | Sposta la finestra allo spazio N |
⌃⌥ Q | Pausa / riprendi il tiling |
⌃⌥ R | Re-tile di tutto |
Come Funziona Sotto il Cofano (E Perché Non Serve Toccare il SIP)
Qui la parte che mi interessava di più progettare bene. Mosaico è costruito sulla API pubblica di Accessibilità (AXUIElement) per leggere e spostare le finestre, più due SPI ben note nell’ambiente: _AXUIElementGetWindow per identificare le finestre in modo stabile e la famiglia CGSCopyManagedDisplaySpaces per la consapevolezza degli Spaces nativi. Sono le stesse interfacce usate da strumenti come yabai e AltTab.
La differenza importante: Mosaico non richiede di disabilitare il SIP e non inietta scripting addition nel sistema. Il System Integrity Protection è, semplificando molto, il buttafuori di macOS: impedisce a chiunque di mettere le mani nelle parti delicate del sistema. Yabai, per alcune delle sue funzioni più avanzate, chiede di ammorbidire il buttafuori. Mosaico no: resta un ospite educato che usa solo le porte che Apple lascia aperte.
Anche lo spostamento di una finestra su un altro spazio usa un trucco elegante (che è poi lo stesso che yabai adotta quando gira senza scripting addition): simula la presa della barra del titolo mentre scatta lo switch nativo ⌃N di Mission Control, e la finestra viene trasportata insieme al cambio di spazio. Per questo motivo le scorciatoie ⌃1…⌃7 di Mission Control devono essere abilitate nelle impostazioni tastiera di macOS.
Installazione
Servono macOS 14 o successivo (Apple Silicon e Intel). Poi:
- Scarica
Mosaico.zipdalla pagina releases e decomprimilo - Trascina Mosaico.app in Applicazioni
- Primo avvio: tasto destro → Apri → Apri (la build non è ancora notarizzata)
- Concedi il permesso Accessibilità quando richiesto
Fine. L’icona ⊞ compare in menubar e il tiling parte subito.
Se invece vuoi compilare dai sorgenti, bastano i Command Line Tools di Xcode — il progetto non ha dipendenze di terze parti, una cosa di cui vado moderatamente fiero:
swift build # build di debug
.build/debug/Mosaico --selftest # self test del layout engine
.build/debug/Mosaico --diag # diagnostica permessi e discovery
Scripts/make-app.sh # produce dist/Mosaico.app e dist/Mosaico.zip
Il Problema del Certificato (Ovvero: Perché macOS Continuava a Chiedermi il Permesso)
E qui arriviamo alla parte che mi ha fatto perdere più capelli di quanti ne avessi preventivati, e che merita una spiegazione per bene perché è una rogna che colpisce chiunque sviluppi app macOS che usano l’Accessibilità.
Il punto è questo: macOS lega il permesso di Accessibilità alla firma del codice dell’app. Non al nome dell’app, non al percorso: alla firma. È come se il buttafuori di prima non riconoscesse le persone dalla faccia, ma dal timbro sulla mano.
Quando compili senza un’identità di firma, macOS applica la cosiddetta firma ad-hoc: un timbro usa-e-getta, diverso a ogni build. Risultato: ricompili l’app, la firma cambia, macOS non la riconosce più e invalida silenziosamente il permesso di Accessibilità. Silenziosamente è la parola chiave: nessun avviso, nessun popup. L’app parte, sembra tutto ok, e le finestre semplicemente non si muovono. Durante lo sviluppo, quando ricompili decine di volte al giorno, significa fare il pellegrinaggio a Impostazioni di Sistema → Privacy e Sicurezza → Accessibilità a ogni singola build. Dopo la terza volta si ride, dopo la trentesima un po’ meno.
La soluzione: un certificato self-signed con un nome stabile. Se la firma non cambia mai, macOS continua a riconoscere l’app e il permesso sopravvive alle ricompilazioni. Lo script Scripts/make-app.sh è già predisposto: se nel portachiavi esiste un certificato chiamato “Mosaico Dev” firma con quello, altrimenti ripiega sulla firma ad-hoc.
Il certificato lo crei una volta sola, in tre passaggi:
# 1. Genera un certificato self-signed per code signing (valido 10 anni)
openssl req -x509 -newkey rsa:2048 -keyout mosaico-key.pem -out mosaico-cert.pem \
-days 3650 -nodes -subj "/CN=Mosaico Dev" \
-addext "keyUsage=digitalSignature" \
-addext "extendedKeyUsage=codeSigning" \
-addext "basicConstraints=CA:false"
# 2. Importa chiave e certificato nel portachiavi di login
security import mosaico-key.pem -k ~/Library/Keychains/login.keychain-db -T /usr/bin/codesign
security import mosaico-cert.pem -k ~/Library/Keychains/login.keychain-db -T /usr/bin/codesign
# 3. Dichiara il certificato affidabile per il code signing (macOS chiederà la password)
security add-trusted-cert -p codeSign -k ~/Library/Keychains/login.keychain-db mosaico-cert.pem
Vediamo cosa fa ogni passaggio, senza darlo per scontato:
openssl req -x509genera una coppia chiave privata + certificato. Il dettaglio importante è-subj "/CN=Mosaico Dev"— il nome con cui lo script lo cercherà nel portachiavi — e le estensioniextendedKeyUsage=codeSigning, che dichiarano che questo certificato serve a firmare codice e non, per dire, un sito web. Dieci anni di validità: quando scadrà, con ogni probabilità starò riscrivendo tutto in un linguaggio che oggi non esiste.security import ... -T /usr/bin/codesigninfila chiave e certificato nel portachiavi di login. Il flag-Tè la parte furba: autorizza esplicitamente il binariocodesigna usare quella chiave, così non ti ritrovi un popup del portachiavi a ogni build.security add-trusted-cert -p codeSigndice a macOS “fidati di questo certificato, ma solo per il code signing”. Senza questo passaggio il certificato esisterebbe ma il sistema lo tratterebbe come un perfetto sconosciuto.
Per verificare che tutto sia andato a buon fine:
security find-identity -p codesigning
Se nell’elenco compare "Mosaico Dev", sei a posto. Da quel momento ogni rebuild mantiene la stessa firma e il permesso di Accessibilità sopravvive. Alla prima build firmata macOS potrebbe mostrare un prompt del portachiavi per codesign: scegli “Consenti sempre” e non lo rivedrai più.
Due avvertenze doverose:
- I file
.pemgenerati non devono finire su Git (il.gitignoredel repo li esclude già, ma repetita iuvant: quella chiave privata è tua e di nessun altro) - Un certificato self-signed risolve il problema solo sulla tua macchina. Se distribuisci l’app ad altri, Gatekeeper continuerà a fare la faccia scura — per quello serve un Developer ID Apple con notarizzazione, che infatti è in cima alla roadmap
Lo Stato delle Cose
Mosaico è in early development — mentre scrivo l’ultima release pubblicata è la v0.2.0 — ma è la mia daily driver: il core — tiling, gestione degli Spaces nativi, hotkey, drop zone e impostazioni — è stabile. Aspettati qualche spigolo sui tipi di finestra meno comuni e sui setup multi-display.
E qui una confessione: il changelog delle 0.1.x è di fatto un diario di guerra contro i casi limite del window management su macOS. Lo standby che al risveglio scombinava il layout (l’API di Accessibilità dopo il wake riporta per qualche istante le finestre come invalide, e il modello le potava prima che arrivasse la notifica di risveglio). I terminali che si “ri-agganciano” alla griglia dei caratteri e innescavano un loop infinito applica→scatta→adotta. Le “tessere fantasma” lasciate nel layout da finestre nascoste con ⌘H che l’Accessibilità riportava ancora come valide. Ognuno di questi bug meriterebbe un post a sé; per ora vi risparmio e vi lascio il CHANGELOG, che è più onesto di qualsiasi pagina marketing.
Limitazioni note, per trasparenza:
- La build distribuita è self-signed: al primo avvio serve tasto destro → Apri, e se l’identità di firma cambia macOS richiede il permesso di Accessibilità
- Le finestre in fullscreen nativo restano fuori dal tiling finché non escono dal fullscreen
- L’API di Accessibilità non può enumerare le finestre sugli Spaces non attivi, quindi la lista in menubar si popola man mano che visiti ogni spazio
- L’interfaccia per ora è solo in italiano (la localizzazione è in roadmap — sì, per una volta gli anglofoni dovranno arrangiarsi loro)
Roadmap
- Firma con Developer ID + notarizzazione (via la frizione di Gatekeeper e i re-prompt dei permessi)
- Cask Homebrew
- Localizzazione inglese della UI
- Migliore supporto multi-display
- Regole di auto-float configurabili dalla GUI
- Bordi finestra / evidenziazione del focus opzionali
- Preset di layout (colonne, monocle) accanto al BSP
Il progetto è open source con licenza MIT su GitHub. Issue e PR sono benvenute — soprattutto da chi ha setup multi-display esotici, che è esattamente il territorio dove vivono i draghi.
P.S. Dopo PlayNotch avevo giurato che il capitolo Swift era chiuso. Poi macOS 27 beta ha rotto yabai e il giuramento è durato quanto un buon proposito di gennaio. A questo punto tanto vale ammetterlo: il notch, le finestre… sto lentamente riscrivendo macOS un pezzo alla volta. Inviate aiuti!