Le Mac App Store interdit l'existence des gestionnaires de fenêtres. J'en ai publié un quand même.
Un ingénieur du Developer Technical Support d’Apple m’a dit que mon application était impossible : « Il n’est pas possible d’utiliser les API Accessibility depuis une application en sandbox… votre seule voie possible est de distribuer votre application directement. »1 L’application était un outil de mosaïque de fenêtres, l’API Accessibility est ce sur quoi repose tout gestionnaire de fenêtres, et toute application du Mac App Store s’exécute dans la sandbox qui l’interdit. La semaine dernière, 941 Tiles est malgré tout arrivé sur le Mac App Store : un clic dans la barre des menus et toutes les fenêtres éparpillées d’une application se rangent dans une grille régulière. La règle de la sandbox n’a jamais plié. L’astuce a été de remarquer que macOS contient déjà un déplaceur de fenêtres privilégié, avec la bénédiction complète d’Apple, et qu’il accepte du JSON.
TL;DR
- App Sandbox bloque catégoriquement les appels Accessibility entre applications, aucun entitlement ne le débloque, et toute application du Mac App Store doit être en sandbox. La catégorie des gestionnaires de fenêtres est de fait fermée aux nouveaux venus depuis juin 2012 ; les outils de mosaïque encore présents sur la boutique sont antérieurs à cette obligation et s’exécutent hors sandbox pour toujours.134
- La porte autorisée : une application en sandbox peut piloter le moteur Shortcuts d’Apple au moyen des Apple events, et Shortcuts fournit les actions Find Windows / Move Window / Resize Window, qui déplacent les fenêtres des autres applications avec les privilèges propres à Apple.25 Mon application calcule où chaque fenêtre doit aller ; un Shortcut compagnon effectue le déplacement. Délégation, pas permission.
- L’ingénierie qui a rendu la chose réelle : la génération programmatique du fichier
.shortcutcompagnon sous forme de plist binaire deWFWorkflowActions, un contrat de charge utile JSON décrivant les cadres et indexé sur l’ordre de profondeur des fenêtres, des garde-fous contre les fenêtres fantômes et les dépassements d’index, et une base de code à deux moteurs qui utilise Accessibility en direct dans les builds Developer ID et la délégation à Shortcuts dans les builds destinés à la boutique. - Premier essai réussi sur la machine qui avait fait échouer cinq builds destinés à la boutique : quatre fenêtres de terminal éparpillées placées aux cadres exacts des quadrants en 0,6 seconde.
- À qui de droit : le mécanisme est éprouvé sur le terrain par snApp (open source, 2023) et Align (2024). C’est la lecture du code source de snApp qui a transformé « impossible » en plan de développement.2
Le mur
La première version de 941 Tiles était un outil de mosaïque classique dans la barre des menus. macOS dispose d’un système Accessibility qui, avec l’autorisation de l’utilisateur, permet à une application d’énumérer et de déplacer les fenêtres d’une autre (AXUIElementSetAttributeValue sur les attributs de position et de taille des fenêtres). Construite là-dessus, l’application a fonctionné sans le moindre défaut pendant des semaines de développement. J’avais préparé toute la fiche App Store Connect : métadonnées en 17 langues, captures d’écran, tarification, 21 builds envoyés.
Puis un build TestFlight est arrivé sur un deuxième Mac. Autorisation Accessibility accordée, application signalant l’accès actif, et elle ne pouvait lire aucune fenêtre.
Ce n’était pas un bug. Toute application du Mac App Store s’exécute dans l’App Sandbox d’Apple, une règle de confinement selon laquelle une application ne peut toucher qu’à ses propres ressources, précisément pour qu’elle ne puisse ni espionner ni perturber le reste de votre Mac.4 Aller chercher les fenêtres d’une autre application est exactement ce que la sandbox existe pour empêcher. Les builds de développement s’exécutent hors sandbox, ce qui explique que tout ait marché pendant des semaines ; le build destiné à la boutique, lui, appliquait la règle que j’ignorais. Aucune boîte de dialogue d’autorisation, aucun entitlement, aucun réglage ne la débloque. Quand j’ai posé la question directement à Apple, la réponse du DTS a été celle qui ouvre cet article : distribuer hors de la boutique.1
Une catégorie fermée depuis 2012
Ce qui rend ce mur intéressant, c’est de savoir qui se tient de l’autre côté. Magnet, Moom, BetterSnapTool : des outils de fenêtres que vous pouvez acheter aujourd’hui même sur le Mac App Store. Leurs fiches sont antérieures à l’obligation de sandbox de juin 2012 ; ils s’exécutent donc hors sandbox au titre d’une clause d’antériorité qui ne se transmet à personne d’autre. Tout gestionnaire de fenêtres né après 2012 (Rectangle, Swish, Loop, BetterTouchTool, Amethyst) se distribue hors de la boutique. Apple a aussi fait respecter la ligne dans l’autre sens : PopClip, présent sur la boutique pendant près de 13 ans, l’a quittée en mars 2024 après que la revue eut indiqué à son développeur qu’une nouvelle fonctionnalité fondée sur Accessibility ne pouvait pas être publiée dans une mise à jour en sandbox.3
Je veux être clair sur ma lecture : la règle de la sandbox est raisonnable. Une application capable de lire et de déplacer silencieusement chaque fenêtre de votre écran est une application capable de vous surveiller, et « faites-moi confiance » n’est pas un modèle de sécurité. La promesse de la boutique, c’est que rien de ce que vous y installez ne peut faire cela. L’injustice est étroite et précise : les acteurs déjà en place conservent une capacité que les nouveaux venus ne peuvent obtenir par aucune quantité de consentement utilisateur. La catégorie n’a pas été interdite ; elle a été fossilisée.
Le retournement
Le verdict du DTS avait un défaut : la boutique contenait déjà des contre-exemples. Align (2024) et snApp (2023) organisent tous deux les fenêtres en mosaïque, vivent tous deux sur le Mac App Store et sont nés tous deux bien après 2012. snApp est open source : le mécanisme n’est donc pas un secret, c’est un dépôt.2
L’application en sandbox ne touche jamais aux fenêtres d’une autre application. Elle pilote Shortcuts, l’application d’automatisation d’Apple elle-même, au moyen de deux entitlements déclarés :
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.temporary-exception.apple-events</key>
<array>
<string>com.apple.shortcuts.events</string>
</array>
Depuis macOS 12, Shortcuts fournit des actions nommées Find Windows, Move Window et Resize Window.5 Shortcuts a le droit de déplacer des fenêtres parce qu’il appartient à Apple. L’application calcule donc où chaque fenêtre doit aller, remet ce plan à un Shortcut compagnon installé par l’utilisateur, et c’est le moteur d’Apple lui-même qui déplace. La sandbox n’est jamais violée, car le processus en sandbox n’accomplit jamais l’acte privilégié. Délégation, pas permission.
Il y a quelque chose de presque architectural dans cette forme : la frontière de sécurité reste exactement là où Apple l’a tracée, et la capacité franchit cette frontière en passant par le seul processus du système qui la détient légitimement.
La réalisation
Connaître le mécanisme et le publier sont deux semaines différentes. Les pièces qui l’ont rendu digne de la production :
Générer le Shortcut par programme. Les utilisateurs ne devraient pas assembler à la main un flux de 12 actions. Le fichier .shortcut compagnon est un plist binaire de WFWorkflowActions que l’application génère et signe au moment du build avec shortcuts sign -m anyone, si bien que l’installation tient en un clic dans l’application Shortcuts. Le contrat d’exécution est une charge utile JSON transmise par le shortcut.run(withInput:) de ScriptingBridge :
{"app": "Ghostty", "frames": [{"x": 0, "y": 38, "w": 1280, "h": 771}, ...]}
L’identité d’une fenêtre, c’est son ordre de profondeur. L’action Find Windows du Shortcut indexe les fenêtres de l’avant vers l’arrière, et CGWindowList les énumère dans le même ordre. L’emplacement i de la charge utile porte donc simplement le cadre de la fenêtre i, et en attribuant les cellules par rang de position, la grille reproduit le principe « les fenêtres restent près de là où elles étaient ». Aucun identifiant de fenêtre ne franchit la frontière, parce qu’il n’en existe aucun de l’autre côté.
Garde-fous. Une limitation Items in Range bornée par Count empêche une charge utile périmée d’indexer au-delà de la liste réelle des fenêtres. Un filtre de taille minimale (120×80) exclut les panneaux fantômes en stationnement que CGWindowList signale et que l’ancienne vérification de rôle Accessibility masquait. Et un fait qui m’a surpris : lister les fenêtres ne demande aucune autorisation ; CGWindowList est légal en sandbox. Seul le déplacement a jamais été interdit.
Une base de code, deux moteurs. L’application détecte la sandbox au lancement grâce à la variable d’environnement APP_SANDBOX_CONTAINER_ID. Les builds destinés à la boutique passent par Shortcuts ; les builds Developer ID et de développement empruntent la voie Accessibility directe. Même interface, mêmes calculs, déplaceur différent.
Le mode surveillance sans notifications. La fonctionnalité facultative de remise en mosaïque automatique s’abonnerait normalement aux notifications de fenêtres d’Accessibility, que la sandbox supprime également. Elle survit sous forme d’interrogation du serveur de fenêtres : des comparaisons CGWindowList peu coûteuses, et une remise en mosaïque au moindre changement.
Le coût, dit honnêtement
L’utilisateur paie une taxe de configuration unique : installer le Shortcut compagnon (un clic) et accorder une autorisation d’automatisation dans une boîte de dialogue (l’invite standard « 941 Tiles souhaite contrôler Shortcuts », localisée dans les 17 langues via InfoPlist.xcstrings). Ensuite, l’expérience est identique à celle d’un outil de mosaïque à API directe : clic sur l’icône de grille, clic sur une application, les fenêtres se rangent. L’exécution du Shortcut est invisible et prend bien moins d’une seconde.
Je ne prétendrai pas que cette taxe est nulle ; c’est un écran de plus que ce qu’affiche Magnet. C’est aussi le prix intégral de l’existence à l’intérieur de la sandbox, plutôt que de demander aux utilisateurs de contourner Gatekeeper pour un téléchargement notarisé auquel ils n’ont encore aucune raison de faire confiance.
Le résultat
Le même Mac Intel qui avait fait échouer cinq builds destinés à la boutique : installation, configuration du Shortcut, consentement, puis quatre fenêtres Ghostty éparpillées rangées aux cadres exacts des quadrants en 0,6 seconde dès le premier essai. Ensuite, la passe d’internationalisation (String Catalogs, règles de pluriel CLDR, y compris les six catégories de l’arabe), la fiche localisée et la soumission. Après un refus en revue et une nouvelle revue d’environ 30 minutes, 941 Tiles est en ligne : 4,99 $ en achat unique, macOS 15+, 17 langues, aucune collecte de données, aucun accès réseau.
Ce qui se généralise
Le motif dépasse la mosaïque de fenêtres. Quand une plateforme enferme une capacité derrière une frontière, la réponse consiste rarement à combattre la frontière ; elle consiste à recenser les processus natifs qui détiennent déjà cette capacité et qui en exposent une interface autorisée. Sur macOS, ce recensement pointe de plus en plus vers un seul endroit : Shortcuts, devenu discrètement le courtier d’automatisation privilégiée du système, joignable depuis l’intérieur de la sandbox par conception. Mon guide Shortcuts couvre cette surface en profondeur, et l’étude de cas 941 Tiles déroule la même histoire avec les schémas d’architecture.
La sandbox a fait son travail. L’application a été publiée quand même. Ces deux phrases sont vraies, et la porte entre les deux était celle d’Apple.
Questions fréquentes
Une application Mac en sandbox peut-elle utiliser les API Accessibility ?
Non. App Sandbox bloque catégoriquement les appels Accessibility entre applications, et aucun entitlement, aucune boîte de dialogue d’autorisation, aucun réglage ne le débloque. La réponse du DTS d’Apple qui m’a été faite était : « Il n’est pas possible d’utiliser les API Accessibility depuis une application en sandbox… votre seule voie possible est de distribuer votre application directement. » Les builds de développement s’exécutent hors sandbox, ce qui explique qu’un outil de mosaïque puisse fonctionner pendant des semaines, puis ne lire aucune fenêtre dans un build destiné à la boutique.14
Comment un gestionnaire de fenêtres peut-il être publié sur le Mac App Store ?
En déléguant l’acte privilégié. Une application en sandbox peut piloter le moteur Shortcuts d’Apple au moyen des Apple events, et Shortcuts propose les actions Find Windows, Move Window et Resize Window depuis macOS 12. Mon application calcule où chaque fenêtre doit aller et remet ce plan à un Shortcut compagnon installé par l’utilisateur, qui effectue le déplacement avec les privilèges propres à Apple. La sandbox n’est jamais violée, car le processus en sandbox n’accomplit jamais l’acte privilégié.25
Pourquoi Magnet et Moom sont-ils sur la boutique si les gestionnaires de fenêtres ne peuvent pas y être ?
Leurs fiches sont antérieures à l’obligation de sandbox de juin 2012 ; ils s’exécutent donc hors sandbox au titre d’une clause d’antériorité qui ne se transmet à personne d’autre. Tout gestionnaire de fenêtres né après 2012, dont Rectangle, Swish, Loop, BetterTouchTool et Amethyst, se distribue hors de la boutique. PopClip a quitté la boutique en mars 2024 après que la revue eut indiqué à son développeur qu’une nouvelle fonctionnalité Accessibility ne pouvait pas être publiée dans une mise à jour en sandbox. La catégorie n’a pas été interdite ; elle a été fossilisée.34
Que coûte la délégation à Shortcuts pour l’utilisateur ?
Une taxe de configuration unique : installer le Shortcut compagnon en un clic, puis accorder une autorisation d’automatisation dans une boîte de dialogue. Ensuite, l’expérience est identique à celle d’un outil de mosaïque à API directe : clic sur l’icône de grille, clic sur une application, les fenêtres se rangent, et l’exécution du Shortcut reste invisible, bien en dessous de la seconde. C’est un écran de plus que ce qu’affiche Magnet, et c’est le prix intégral de l’existence à l’intérieur de la sandbox.
Comment une seule base de code prend-elle en charge les builds en sandbox et les builds directs ?
L’application détecte la sandbox au lancement grâce à la variable d’environnement APP_SANDBOX_CONTAINER_ID. Les builds destinés à la boutique passent par Shortcuts ; les builds Developer ID et de développement empruntent la voie Accessibility directe. Même interface, mêmes calculs, déplaceur différent. Le mode surveillance s’adapte lui aussi : la sandbox supprime les notifications de fenêtres d’Accessibility, si bien que la remise en mosaïque automatique facultative survit sous forme d’interrogation CGWindowList peu coûteuse, qui compare et remet en mosaïque au moindre changement.
Références
-
Apple Developer Forums, fil 805556 (octobre 2025), réponse du DTS d’Apple : « Il n’est pas possible d’utiliser les API Accessibility depuis une application en sandbox… votre seule voie possible est de distribuer votre application directement en la signant avec un Developer ID. » ↩↩↩↩
-
Om Chachad, snApp sur GitHub (2023), l’outil de mosaïque de fenêtres open source du Mac App Store dont le code source documente le mécanisme de délégation à Shortcuts : les entitlements de cible de scripting
com.apple.security.automation.apple-eventset un Shortcut compagnon construit à partir des actions de fenêtre d’Apple. ↩↩↩↩ -
Michael Tsai, « PopClip Leaving the Mac App Store » (21 mars 2024), sur la revue d’Apple exigeant le retrait d’une nouvelle fonctionnalité fondée sur Accessibility d’une mise à jour en sandbox, après près de 13 ans de présence sur la boutique. ↩↩↩
-
Apple, documentation d’App Sandbox, le modèle de confinement que toutes les applications du Mac App Store sont tenues d’adopter depuis juin 2012. ↩↩↩↩
-
Apple, Guide d’utilisation de Shortcuts pour Mac, qui documente les actions Find Windows, Move Window et Resize Window disponibles depuis macOS 12. ↩↩↩