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;