Skip to main content

air_sys_types/
terminal.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Terminal (termios) — types ABI de la couche 0.
6//!
7//! [`Termios`] est la structure d'échange **exacte** avec le kernel pour les
8//! ioctls `TCGETS`/`TCSETS*` (cf. `<asm-generic/termbits.h>`). Sa disposition
9//! mémoire (`#[repr(C)]`) reproduit `struct termios` du kernel — **identique sur
10//! x86_64 et aarch64** (tous deux `asm-generic`). Les bits des quatre champs de
11//! drapeaux sont exposés en [`bitflags`] typés (lecture via accesseurs), et la
12//! vitesse encodée dans `CBAUD` via [`BaudRate`]. L'objet Rust **ergonomique**
13//! (manipulation haut niveau, `isatty`, `ttyname`) vit en couche 1 (`air-terminal`).
14
15use bitflags::bitflags;
16
17/// Nombre d'entrées du tableau de caractères de contrôle `c_cc` (Linux `NCCS`).
18pub const NCCS: usize = 19;
19
20/// Indices des caractères de contrôle dans [`Termios::control_chars`] (`c_cc`).
21///
22/// Valeurs `<asm-generic/termbits.h>` (identiques x86_64/aarch64).
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[repr(u8)]
25pub enum ControlChar {
26    /// `VINTR` — caractère d'interruption (SIGINT).
27    Intr = 0,
28    /// `VQUIT` — caractère de quit (SIGQUIT).
29    Quit = 1,
30    /// `VERASE` — effacement de caractère.
31    Erase = 2,
32    /// `VKILL` — effacement de ligne.
33    Kill = 3,
34    /// `VEOF` — fin de fichier (Ctrl-D).
35    Eof = 4,
36    /// `VTIME` — délai de lecture (dixièmes de seconde), mode non canonique.
37    Time = 5,
38    /// `VMIN` — nombre minimal d'octets, mode non canonique.
39    Min = 6,
40    /// `VSWTC` — caractère de bascule.
41    Swtc = 7,
42    /// `VSTART` — reprise du flux (XON).
43    Start = 8,
44    /// `VSTOP` — suspension du flux (XOFF).
45    Stop = 9,
46    /// `VSUSP` — suspension (SIGTSTP).
47    Susp = 10,
48    /// `VEOL` — fin de ligne additionnelle.
49    Eol = 11,
50    /// `VREPRINT` — réaffichage de la ligne.
51    Reprint = 12,
52    /// `VDISCARD` — abandon de la sortie.
53    Discard = 13,
54    /// `VWERASE` — effacement de mot.
55    Werase = 14,
56    /// `VLNEXT` — caractère littéral suivant.
57    Lnext = 15,
58    /// `VEOL2` — seconde fin de ligne.
59    Eol2 = 16,
60}
61
62bitflags! {
63    /// Drapeaux d'entrée (`c_iflag`).
64    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
65    pub struct InputFlags: u32 {
66        /// `IGNBRK` — ignore les conditions BREAK.
67        const IGNBRK = 0x0000_0001;
68        /// `BRKINT` — un BREAK provoque une interruption.
69        const BRKINT = 0x0000_0002;
70        /// `IGNPAR` — ignore les octets en erreur de parité.
71        const IGNPAR = 0x0000_0004;
72        /// `PARMRK` — marque les erreurs de parité.
73        const PARMRK = 0x0000_0008;
74        /// `INPCK` — active le contrôle de parité en entrée.
75        const INPCK = 0x0000_0010;
76        /// `ISTRIP` — masque le 8e bit.
77        const ISTRIP = 0x0000_0020;
78        /// `INLCR` — convertit NL en CR en entrée.
79        const INLCR = 0x0000_0040;
80        /// `IGNCR` — ignore les CR en entrée.
81        const IGNCR = 0x0000_0080;
82        /// `ICRNL` — convertit CR en NL en entrée.
83        const ICRNL = 0x0000_0100;
84        /// `IUCLC` — convertit les majuscules en minuscules.
85        const IUCLC = 0x0000_0200;
86        /// `IXON` — active le contrôle de flux XON/XOFF en sortie.
87        const IXON = 0x0000_0400;
88        /// `IXANY` — tout caractère relance la sortie suspendue.
89        const IXANY = 0x0000_0800;
90        /// `IXOFF` — active le contrôle de flux XON/XOFF en entrée.
91        const IXOFF = 0x0000_1000;
92        /// `IMAXBEL` — sonne quand la file d'entrée est pleine.
93        const IMAXBEL = 0x0000_2000;
94        /// `IUTF8` — l'entrée est en UTF-8 (effacement canonique correct).
95        const IUTF8 = 0x0000_4000;
96    }
97
98    /// Drapeaux de sortie (`c_oflag`).
99    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
100    pub struct OutputFlags: u32 {
101        /// `OPOST` — active le post-traitement de sortie.
102        const OPOST = 0x0000_0001;
103        /// `OLCUC` — convertit les minuscules en majuscules.
104        const OLCUC = 0x0000_0002;
105        /// `ONLCR` — convertit NL en CR-NL en sortie.
106        const ONLCR = 0x0000_0004;
107        /// `OCRNL` — convertit CR en NL en sortie.
108        const OCRNL = 0x0000_0008;
109        /// `ONOCR` — pas de CR en colonne 0.
110        const ONOCR = 0x0000_0010;
111        /// `ONLRET` — NL effectue aussi le retour chariot.
112        const ONLRET = 0x0000_0020;
113        /// `OFILL` — caractères de remplissage pour la temporisation.
114        const OFILL = 0x0000_0040;
115        /// `OFDEL` — le caractère de remplissage est DEL.
116        const OFDEL = 0x0000_0080;
117    }
118
119    /// Drapeaux de contrôle (`c_cflag`). La vitesse (`CBAUD`/`CBAUDEX`) se lit
120    /// via [`Termios::input_speed`]/[`Termios::output_speed`], pas ici.
121    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
122    pub struct ControlFlags: u32 {
123        /// `CSTOPB` — deux bits de stop (sinon un).
124        const CSTOPB = 0x0000_0040;
125        /// `CREAD` — active la réception.
126        const CREAD = 0x0000_0080;
127        /// `PARENB` — active la génération/contrôle de parité.
128        const PARENB = 0x0000_0100;
129        /// `PARODD` — parité impaire (sinon paire).
130        const PARODD = 0x0000_0200;
131        /// `HUPCL` — raccroche à la fermeture du dernier descripteur.
132        const HUPCL = 0x0000_0400;
133        /// `CLOCAL` — ignore les lignes de contrôle modem.
134        const CLOCAL = 0x0000_0800;
135        /// `CRTSCTS` — contrôle de flux matériel (RTS/CTS).
136        const CRTSCTS = 0x8000_0000;
137    }
138
139    /// Drapeaux locaux (`c_lflag`).
140    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
141    pub struct LocalFlags: u32 {
142        /// `ISIG` — génère des signaux pour INTR/QUIT/SUSP.
143        const ISIG = 0x0000_0001;
144        /// `ICANON` — mode canonique (ligne par ligne).
145        const ICANON = 0x0000_0002;
146        /// `ECHO` — écho des caractères saisis.
147        const ECHO = 0x0000_0008;
148        /// `ECHOE` — ERASE efface visuellement.
149        const ECHOE = 0x0000_0010;
150        /// `ECHOK` — KILL efface la ligne visuellement.
151        const ECHOK = 0x0000_0020;
152        /// `ECHONL` — écho de NL même sans ECHO.
153        const ECHONL = 0x0000_0040;
154        /// `NOFLSH` — ne vide pas les files sur signal.
155        const NOFLSH = 0x0000_0080;
156        /// `TOSTOP` — SIGTTOU pour l'écriture par un processus en arrière-plan.
157        const TOSTOP = 0x0000_0100;
158        /// `ECHOCTL` — affiche les caractères de contrôle en `^X`.
159        const ECHOCTL = 0x0000_0200;
160        /// `ECHOPRT` — affiche les caractères effacés (imprimante).
161        const ECHOPRT = 0x0000_0400;
162        /// `ECHOKE` — KILL efface chaque caractère visuellement.
163        const ECHOKE = 0x0000_0800;
164        /// `FLUSHO` — sortie vidée (toggle par DISCARD).
165        const FLUSHO = 0x0000_1000;
166        /// `PENDIN` — entrée en attente de réaffichage.
167        const PENDIN = 0x0000_4000;
168        /// `IEXTEN` — active le traitement étendu (LNEXT, WERASE…).
169        const IEXTEN = 0x0000_8000;
170    }
171}
172
173/// Masque de la taille de caractère dans `c_cflag` (`CSIZE`).
174const CSIZE_MASK: u32 = 0x0000_0030;
175/// Masque de la vitesse encodée dans `c_cflag` (`CBAUD | CBAUDEX`).
176const CBAUD_MASK: u32 = 0x0000_100f;
177
178/// Nombre de bits de données par caractère (`CSIZE` : CS5/CS6/CS7/CS8).
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180#[repr(u32)]
181pub enum CharSize {
182    /// `CS5` — 5 bits.
183    Bits5 = 0x0000_0000,
184    /// `CS6` — 6 bits.
185    Bits6 = 0x0000_0010,
186    /// `CS7` — 7 bits.
187    Bits7 = 0x0000_0020,
188    /// `CS8` — 8 bits.
189    Bits8 = 0x0000_0030,
190}
191
192impl CharSize {
193    /// Décode les bits `CSIZE` de `c_cflag`. Total (4 motifs) → jamais `None`.
194    #[must_use]
195    const fn from_cflag(cflag: u32) -> Self {
196        match cflag & CSIZE_MASK {
197            0x0000_0000 => Self::Bits5,
198            0x0000_0010 => Self::Bits6,
199            0x0000_0020 => Self::Bits7,
200            // Le masque 2 bits n'a que 4 valeurs ; 0x30 est la dernière.
201            _ => Self::Bits8,
202        }
203    }
204}
205
206/// Vitesse de transmission (valeur encodée `CBAUD`/`CBAUDEX` de `c_cflag`).
207///
208/// Couvre les vitesses POSIX standard. La conversion bidirectionnelle préserve
209/// l'octet de vitesse ; une valeur kernel inconnue est rendue telle quelle via
210/// [`BaudRate::from_code`]/[`BaudRate::code`] (pas de perte d'information —
211/// doctrine « kernel = bible »).
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub struct BaudRate(u32);
214
215impl BaudRate {
216    /// `B0` — débit nul (raccroche).
217    pub const B0: Self = Self(0x0000_0000);
218    /// `B50`.
219    pub const B50: Self = Self(0x0000_0001);
220    /// `B75`.
221    pub const B75: Self = Self(0x0000_0002);
222    /// `B110`.
223    pub const B110: Self = Self(0x0000_0003);
224    /// `B134`.
225    pub const B134: Self = Self(0x0000_0004);
226    /// `B150`.
227    pub const B150: Self = Self(0x0000_0005);
228    /// `B200`.
229    pub const B200: Self = Self(0x0000_0006);
230    /// `B300`.
231    pub const B300: Self = Self(0x0000_0007);
232    /// `B600`.
233    pub const B600: Self = Self(0x0000_0008);
234    /// `B1200`.
235    pub const B1200: Self = Self(0x0000_0009);
236    /// `B1800`.
237    pub const B1800: Self = Self(0x0000_000a);
238    /// `B2400`.
239    pub const B2400: Self = Self(0x0000_000b);
240    /// `B4800`.
241    pub const B4800: Self = Self(0x0000_000c);
242    /// `B9600`.
243    pub const B9600: Self = Self(0x0000_000d);
244    /// `B19200`.
245    pub const B19200: Self = Self(0x0000_000e);
246    /// `B38400`.
247    pub const B38400: Self = Self(0x0000_000f);
248    /// `B57600`.
249    pub const B57600: Self = Self(0x0000_1001);
250    /// `B115200`.
251    pub const B115200: Self = Self(0x0000_1002);
252    /// `B230400`.
253    pub const B230400: Self = Self(0x0000_1003);
254    /// `B460800`.
255    pub const B460800: Self = Self(0x0000_1004);
256    /// `B921600`.
257    pub const B921600: Self = Self(0x0000_1007);
258
259    /// Construit depuis le code de vitesse brut (octet `CBAUD|CBAUDEX`).
260    #[must_use]
261    pub const fn from_code(code: u32) -> Self {
262        Self(code & CBAUD_MASK)
263    }
264
265    /// Code de vitesse brut (octet `CBAUD|CBAUDEX`), pour réinjection dans `c_cflag`.
266    #[must_use]
267    pub const fn code(self) -> u32 {
268        self.0
269    }
270}
271
272/// Action de pose des attributs (`tcsetattr` : quand appliquer).
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274pub enum SetAction {
275    /// `TCSANOW` — applique immédiatement (`TCSETS`).
276    Now,
277    /// `TCSADRAIN` — applique après vidage de la sortie (`TCSETSW`).
278    Drain,
279    /// `TCSAFLUSH` — applique après vidage sortie + purge entrée (`TCSETSF`).
280    Flush,
281}
282
283/// File visée par `tcflush` (purge).
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub enum FlushQueue {
286    /// `TCIFLUSH` — purge les données reçues non lues.
287    Input,
288    /// `TCOFLUSH` — purge les données écrites non transmises.
289    Output,
290    /// `TCIOFLUSH` — purge les deux files.
291    Both,
292}
293
294/// Action de contrôle de flux pour `tcflow`.
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub enum FlowAction {
297    /// `TCOOFF` — suspend la sortie.
298    SuspendOutput,
299    /// `TCOON` — reprend la sortie.
300    ResumeOutput,
301    /// `TCIOFF` — émet un STOP (suspend l'entrée du terminal distant).
302    SuspendInput,
303    /// `TCION` — émet un START (reprend l'entrée du terminal distant).
304    ResumeInput,
305}
306
307/// Attributs d'un terminal — image ABI de `struct termios` (`<asm-generic>`).
308///
309/// `#[repr(C)]`, disposition identique x86_64/aarch64. Champs bruts (compatibles
310/// kernel) lus/écrits via accesseurs typés. À échanger avec le kernel par les
311/// ioctls de `air-sys-syscall::terminal`.
312#[derive(Debug, Clone, Copy, PartialEq, Eq)]
313#[repr(C)]
314pub struct Termios {
315    /// `c_iflag` — drapeaux d'entrée (brut).
316    pub c_iflag: u32,
317    /// `c_oflag` — drapeaux de sortie (brut).
318    pub c_oflag: u32,
319    /// `c_cflag` — drapeaux de contrôle (brut, inclut la vitesse `CBAUD`).
320    pub c_cflag: u32,
321    /// `c_lflag` — drapeaux locaux (brut).
322    pub c_lflag: u32,
323    /// `c_line` — discipline de ligne.
324    pub c_line: u8,
325    /// `c_cc` — caractères de contrôle (indexés par [`ControlChar`]).
326    pub c_cc: [u8; NCCS],
327}
328
329impl Termios {
330    /// Image toute-à-zéro (pour recevoir un `TCGETS`).
331    #[must_use]
332    pub const fn zeroed() -> Self {
333        Self {
334            c_iflag: 0,
335            c_oflag: 0,
336            c_cflag: 0,
337            c_lflag: 0,
338            c_line: 0,
339            c_cc: [0; NCCS],
340        }
341    }
342
343    /// Drapeaux d'entrée typés. Les bits inconnus sont conservés (`from_bits_retain`).
344    #[must_use]
345    pub const fn input_flags(&self) -> InputFlags {
346        InputFlags::from_bits_retain(self.c_iflag)
347    }
348
349    /// Drapeaux de sortie typés.
350    #[must_use]
351    pub const fn output_flags(&self) -> OutputFlags {
352        OutputFlags::from_bits_retain(self.c_oflag)
353    }
354
355    /// Drapeaux de contrôle typés (hors bits de taille/vitesse).
356    #[must_use]
357    pub const fn control_flags(&self) -> ControlFlags {
358        ControlFlags::from_bits_retain(self.c_cflag)
359    }
360
361    /// Drapeaux locaux typés.
362    #[must_use]
363    pub const fn local_flags(&self) -> LocalFlags {
364        LocalFlags::from_bits_retain(self.c_lflag)
365    }
366
367    /// Positionne les drapeaux d'entrée (remplace `c_iflag`).
368    pub const fn set_input_flags(&mut self, flags: InputFlags) {
369        self.c_iflag = flags.bits();
370    }
371
372    /// Positionne les drapeaux de sortie.
373    pub const fn set_output_flags(&mut self, flags: OutputFlags) {
374        self.c_oflag = flags.bits();
375    }
376
377    /// Positionne les drapeaux de contrôle **en préservant** taille et vitesse
378    /// (les bits `CSIZE`/`CBAUD` de `c_cflag` ne sont pas touchés).
379    pub const fn set_control_flags(&mut self, flags: ControlFlags) {
380        let preserved = self.c_cflag & (CSIZE_MASK | CBAUD_MASK);
381        // `ControlFlags` n'inclut ni CSIZE ni CBAUD → pas de chevauchement.
382        self.c_cflag = flags.bits() | preserved;
383    }
384
385    /// Positionne les drapeaux locaux.
386    pub const fn set_local_flags(&mut self, flags: LocalFlags) {
387        self.c_lflag = flags.bits();
388    }
389
390    /// Taille de caractère (`CSIZE`).
391    #[must_use]
392    pub const fn char_size(&self) -> CharSize {
393        CharSize::from_cflag(self.c_cflag)
394    }
395
396    /// Positionne la taille de caractère (`CSIZE`).
397    pub const fn set_char_size(&mut self, size: CharSize) {
398        self.c_cflag = (self.c_cflag & !CSIZE_MASK) | (size as u32);
399    }
400
401    /// Vitesse encodée dans `c_cflag` (entrée et sortie partagent `CBAUD` sur
402    /// Linux classique).
403    #[must_use]
404    pub const fn output_speed(&self) -> BaudRate {
405        BaudRate::from_code(self.c_cflag)
406    }
407
408    /// Vitesse d'entrée (identique à la sortie en termios classique Linux).
409    #[must_use]
410    pub const fn input_speed(&self) -> BaudRate {
411        BaudRate::from_code(self.c_cflag)
412    }
413
414    /// Positionne la vitesse (les bits `CBAUD|CBAUDEX` de `c_cflag`).
415    pub const fn set_speed(&mut self, baud: BaudRate) {
416        self.c_cflag = (self.c_cflag & !CBAUD_MASK) | baud.code();
417    }
418
419    /// Lit un caractère de contrôle (`c_cc[idx]`).
420    #[must_use]
421    pub const fn control_char(&self, which: ControlChar) -> u8 {
422        self.c_cc[which as usize]
423    }
424
425    /// Positionne un caractère de contrôle (`c_cc[idx]`).
426    pub const fn set_control_char(&mut self, which: ControlChar, value: u8) {
427        self.c_cc[which as usize] = value;
428    }
429}
430
431/// Taille d'une fenêtre de terminal — image ABI de `struct winsize`
432/// (`<asm-generic/termios.h>`).
433///
434/// `#[repr(C)]`, disposition **identique x86_64/aarch64** (`asm-generic`). À
435/// échanger avec le kernel par les ioctls `TIOCGWINSZ`/`TIOCSWINSZ` de
436/// `air-sys-syscall::terminal`. Le kernel signale les changements de taille par
437/// `SIGWINCH`. Les champs sont exposés bruts (`pub`) — comme [`Termios`] — pour
438/// l'échange direct avec le kernel ; des accesseurs const nommés existent pour la
439/// lecture ergonomique.
440#[derive(Debug, Clone, Copy, PartialEq, Eq)]
441#[repr(C)]
442pub struct Winsize {
443    /// `ws_row` — nombre de lignes (rangées de caractères).
444    pub ws_row: u16,
445    /// `ws_col` — nombre de colonnes (caractères par ligne).
446    pub ws_col: u16,
447    /// `ws_xpixel` — largeur en pixels (souvent 0 : nombre de terminaux ne le
448    /// renseignent pas).
449    pub ws_xpixel: u16,
450    /// `ws_ypixel` — hauteur en pixels (souvent 0, comme `ws_xpixel`).
451    pub ws_ypixel: u16,
452}
453
454impl Winsize {
455    /// Image toute-à-zéro (à passer à un `TIOCGWINSZ` qui la remplira).
456    #[must_use]
457    pub const fn zeroed() -> Self {
458        Self {
459            ws_row: 0,
460            ws_col: 0,
461            ws_xpixel: 0,
462            ws_ypixel: 0,
463        }
464    }
465
466    /// Nombre de lignes (`ws_row`).
467    #[must_use]
468    pub const fn rows(&self) -> u16 {
469        self.ws_row
470    }
471
472    /// Nombre de colonnes (`ws_col`).
473    #[must_use]
474    pub const fn cols(&self) -> u16 {
475        self.ws_col
476    }
477
478    /// Largeur en pixels (`ws_xpixel`), 0 si non renseigné.
479    #[must_use]
480    pub const fn x_pixels(&self) -> u16 {
481        self.ws_xpixel
482    }
483
484    /// Hauteur en pixels (`ws_ypixel`), 0 si non renseigné.
485    #[must_use]
486    pub const fn y_pixels(&self) -> u16 {
487        self.ws_ypixel
488    }
489}
490
491/// Numéro de l'esclave d'un pseudo-terminal (`/dev/pts/N`).
492///
493/// Newtype d'identifiant (ADR-021 : newtypes typés systématiques, comme
494/// [`Pid`](crate::Pid)/[`Tid`](crate::Tid)/[`PidFd`](crate::PidFd)) : jamais un
495/// `u32` brut pour désigner un esclave de PTY. La valeur est celle rendue par
496/// l'ioctl `TIOCGPTN` sur le maître `/dev/ptmx` — elle nomme le nœud
497/// `/dev/pts/N` correspondant à l'esclave.
498#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
499pub struct PtyNumber(u32);
500
501impl PtyNumber {
502    /// Construit un [`PtyNumber`] à partir du numéro brut rendu par `TIOCGPTN`.
503    #[must_use]
504    pub const fn new(value: u32) -> Self {
505        Self(value)
506    }
507
508    /// Numéro brut de l'esclave (l'`N` de `/dev/pts/N`).
509    #[must_use]
510    pub const fn value(self) -> u32 {
511        self.0
512    }
513}
514
515#[cfg(test)]
516mod tests;