Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-061 — Architecture AirTerminal : modèle canonique + codecs (vision)

Statut : Accepté (2026-07-02, par délégation d’autonomie BDFL ; ADR de vision — gravé pour revue a posteriori). RFC de structure (ADR-015). Companion de ADR-060 (termios couche 0), ADR-046 (libc), ADR-001 (AirCom / IPC), ADR-003 (compositeur), ADR-007 (air-console), ADR-008 (air-tui), ADR-040 (Cap’n Proto).

Catégorie : Architecture (vision terminal, couche 1 → 2). Aucun engagement d’implémentation au-delà de la forme (modèle + trait) ; les codecs riches et air-codec-1 sont couche 2, différés.

Contexte — le terminal legacy n’est plus adapté

Le PTY est un seul canal d’octets non typé : sortie appli (texte + séquences d’échappement), entrée (frappes + souris encodée en fausses séquences + collage + focus), et négociation (requêtes/réponses) — tout multiplexé en-bande. Modèle « téléscripteur » (POSIX termios 1988, ECMA-48/VT102 1976-91), terminal-agnostique, incapable de décrire l’interaction riche (souris multi-boutons, molette, tactile, pression, gestes, biométrie — terminfo n’a pas de vocabulaire pour ça ; kitty & co. ont greffé des séquences faute d’autre canal). Air, qui possède tout le stack (libc, IPC, compositeur, apps), peut faire mieux — non en encodant mieux la souris en séquences, mais en n’ayant pas à le faire.

Résultat clé de la réflexion : sur un canal, « propre » et « legacy » ne cohabitent pas (le seul mécanisme est la greffe de séquences ignorables — la laideur même). ⇒ on sépare ; la cohabitation se fait au niveau système (objet), pas octet.

Décision — modèle canonique + codecs

  1. AirTerminal (objet couche 1) = un tag (Legacy | Air) + un codec + un transport. Il représente le terminal-pair avec lequel Air converse.

  2. Un modèle interne canonique Air (la lingua franca) : les codecs ne se parlent jamais ; ils traduisent tous ce modèle. Conçu une fois, riche et tourné vers l’avenir ; chaque type de terminal = un codec de plus, jamais une refonte. Le codec legacy est un adaptateur dégradant (il jette ce que xterm ne sait pas), pas une contrainte sur le modèle.

    #![allow(unused)]
    fn main() {
    #[non_exhaustive] enum TermIn  { Keyboard(KeyboardEvent), Mouse(MouseEvent),
                                     Screen(ScreenEvent), /* +Touch/Gesture/Device… */ Unknown(RawEvent) }
    #[non_exhaustive] enum MouseEvent { Button{..}, Wheel{..}, Motion{..},
                                     /* +Pressure/Tilt… */ Unknown{ subtype:u16, payload } }
    enum TermOut  { Text{run,attrs}, Cursor{..}, Clear{..}, Scroll{..}, SetMode{..},
                    AttachSurface{ rect, surface_id } }   // ⟵ la clé
    struct TermCaps { colors, mouse, keyboard_proto, surfaces:bool, .. }
    trait TerminalCodec { fn decode_in(&mut self, wire)->Vec<TermIn>;
                          fn encode_out(&mut self, &TermOut)->Bytes; fn caps(&self)->TermCaps; }
    }
  3. Discipline unique d’extensibilité — « Annoncer → Sélectionner → dégrader-sur-inconnu ». Tout ce qui est extensible (catégories/sous-types d’events, champs, types de surface, codecs) suit : (1) un registre à IDs stables ; (2) chaque bout annonce ses capacités ; (3) le consommateur choisit dans l’intersection ; (4) l’inconnu est catégorisé et ignoré, jamais fatal. Mécanique Rust/fil : #[non_exhaustive] (compat à la recompilation, quand on ajoute une variante) + variante Unknown (compat runtime, quand on reçoit plus récent que soi) + schéma évolutif Cap’n Proto (ADR-040 — ajouter un champ = compatible, inconnu skippé) pour l’axe « nouveau champ ». (Flux haute fréquence → batch par frame ; capnp donne la discipline d’évolution, pas forcément l’encodage par-événement.)

  4. AttachSurface = énumérer → sélectionner → lier. Une appli ne dessine pas une image en séquences : elle interroge les surfaces offertes (QuerySurfacesSurfaceOffer{kinds}), choisit celle qu’elle sait piloter (intersection offert ∧ compris), lie (AttachSurfaceSurfaceReady{surface_id}) et reçoit ses événements tagués. Le compositeur Air fournit une vraie surface (GPU, événements typés) — impossible sur Unix (pas d’autre canal), trivial sous Air. SurfaceKind est lui-même un registre extensible (même discipline).

  5. termios = affaire du codec legacy uniquement (discipline de ligne du PTY). Le chemin Air-natif est termios-free (events Key typés + éditeur-ligne userspace si besoin).

  6. La passerelle tombe toute seule : coller n’importe quel côté-appli à n’importe quel côté-display = composer deux codecs via le modèle (bash[ansi]↔modèle↔display[kitty] ; air-cli[air]↔modèle↔xterm[ansi, dégrade]). L’interop legacy = « un bout utilise le codec ansi ». Découverte du tag distant : un handshake « es-tu Air ? » ignorable par le legacy (la seule greffe résiduelle, bornée), puis bascule sur le canal propre.

Discipline de portée (ne pas bloquer le sceau)

  • Ce que la libc / le sceau couche 1 exige = termios (couche 0, ADR-060) + un mince air-terminal (couche 1) dont le premier (et seul, au sceau) codec est ansi/termios.
  • Le modèle canonique + le trait TerminalCodec + la règle d’extensibilité + AttachSurface se gravent maintenant (design ; c’est l’assurance anti-double-travail). Le trait peut entrer dans la couche 1 (interface stable) ; les codecs riches, air-codec-1, AttachSurface implémenté = couche 2, différés (conçus quand air-console/air-tui les réclament — pas avant, pour ne pas sur-designer).
  • termcap/terminfo = hors libc (c’est ncurses/libtinfo, couche 2) ; si Air le fournit un jour, base xterm-family only (98 % du marché 2026), pas la base terminfo legacy complète.

Conséquences

  • air-terminal est bâti comme le premier codec d’un cadre déjà juste, pas un wrapper jetable.
  • L’interaction riche (souris fine, tactile, gestes, biométrie, graphique) passe par le stack device/entrée d’Air (air-device, compositeur) via AttachSurface/events typés — pas par le terminal. Le terminal reste texte, borné, compatible.