Clean up TTY termios definitions

This commit is contained in:
Ruihan Li
2025-06-05 21:29:18 +08:00
committed by Jianfeng Jiang
parent 8583eea62b
commit 71e08b3942
2 changed files with 169 additions and 144 deletions

View File

@ -3,7 +3,7 @@
use ostd::const_assert; use ostd::const_assert;
use super::{ use super::{
termio::{KernelTermios, WinSize, CC_C_CHAR}, termio::{CCtrlCharId, CTermios, CWinSize},
PushCharError, PushCharError,
}; };
use crate::{ use crate::{
@ -32,9 +32,9 @@ pub struct LineDiscipline {
/// Read buffer /// Read buffer
read_buffer: RingBuffer<u8>, read_buffer: RingBuffer<u8>,
/// Termios /// Termios
termios: KernelTermios, termios: CTermios,
/// Window size /// Window size
winsize: WinSize, winsize: CWinSize,
} }
struct CurrentLine { struct CurrentLine {
@ -89,8 +89,8 @@ impl LineDiscipline {
Self { Self {
current_line: CurrentLine::default(), current_line: CurrentLine::default(),
read_buffer: RingBuffer::new(BUFFER_CAPACITY), read_buffer: RingBuffer::new(BUFFER_CAPACITY),
termios: KernelTermios::default(), termios: CTermios::default(),
winsize: WinSize::default(), winsize: CWinSize::default(),
} }
} }
@ -134,12 +134,12 @@ impl LineDiscipline {
// Canonical mode // Canonical mode
if ch == *self.termios.get_special_char(CC_C_CHAR::VKILL) { if ch == self.termios.special_char(CCtrlCharId::VKILL) {
// Erase current line // Erase current line
self.current_line.drain(); self.current_line.drain();
} }
if ch == *self.termios.get_special_char(CC_C_CHAR::VERASE) { if ch == self.termios.special_char(CCtrlCharId::VERASE) {
// Type backspace // Type backspace
self.current_line.backspace(); self.current_line.backspace();
} }
@ -166,7 +166,7 @@ impl LineDiscipline {
match ch { match ch {
b'\n' => echo_callback(b"\n"), b'\n' => echo_callback(b"\n"),
b'\r' => echo_callback(b"\r\n"), b'\r' => echo_callback(b"\r\n"),
ch if ch == *self.termios.get_special_char(CC_C_CHAR::VERASE) => { ch if ch == self.termios.special_char(CCtrlCharId::VERASE) => {
// Write a space to overwrite the current character // Write a space to overwrite the current character
echo_callback(b"\x08 \x08"); echo_callback(b"\x08 \x08");
} }
@ -185,8 +185,8 @@ impl LineDiscipline {
/// If no bytes are available or the available bytes are fewer than `min(dst.len(), vmin)`, /// If no bytes are available or the available bytes are fewer than `min(dst.len(), vmin)`,
/// this method returns [`Errno::EAGAIN`]. /// this method returns [`Errno::EAGAIN`].
pub fn try_read(&mut self, dst: &mut [u8]) -> Result<usize> { pub fn try_read(&mut self, dst: &mut [u8]) -> Result<usize> {
let vmin = *self.termios.get_special_char(CC_C_CHAR::VMIN); let vmin = self.termios.special_char(CCtrlCharId::VMIN);
let vtime = *self.termios.get_special_char(CC_C_CHAR::VTIME); let vtime = self.termios.special_char(CCtrlCharId::VTIME);
if vtime != 0 { if vtime != 0 {
warn!("non-zero VTIME is not supported"); warn!("non-zero VTIME is not supported");
@ -236,40 +236,40 @@ impl LineDiscipline {
self.read_buffer.len() + self.current_line.len() >= self.read_buffer.capacity() self.read_buffer.len() + self.current_line.len() >= self.read_buffer.capacity()
} }
pub fn termios(&self) -> &KernelTermios { pub fn termios(&self) -> &CTermios {
&self.termios &self.termios
} }
pub fn set_termios(&mut self, termios: KernelTermios) { pub fn set_termios(&mut self, termios: CTermios) {
self.termios = termios; self.termios = termios;
} }
pub fn window_size(&self) -> WinSize { pub fn window_size(&self) -> CWinSize {
self.winsize self.winsize
} }
pub fn set_window_size(&mut self, winsize: WinSize) { pub fn set_window_size(&mut self, winsize: CWinSize) {
self.winsize = winsize; self.winsize = winsize;
} }
} }
fn is_line_terminator(ch: u8, termios: &KernelTermios) -> bool { fn is_line_terminator(ch: u8, termios: &CTermios) -> bool {
if ch == b'\n' if ch == b'\n'
|| ch == *termios.get_special_char(CC_C_CHAR::VEOF) || ch == termios.special_char(CCtrlCharId::VEOF)
|| ch == *termios.get_special_char(CC_C_CHAR::VEOL) || ch == termios.special_char(CCtrlCharId::VEOL)
{ {
return true; return true;
} }
if termios.contains_iexten() && ch == *termios.get_special_char(CC_C_CHAR::VEOL2) { if termios.contains_iexten() && ch == termios.special_char(CCtrlCharId::VEOL2) {
return true; return true;
} }
false false
} }
fn is_eof(ch: u8, termios: &KernelTermios) -> bool { fn is_eof(ch: u8, termios: &CTermios) -> bool {
ch == *termios.get_special_char(CC_C_CHAR::VEOF) ch == termios.special_char(CCtrlCharId::VEOF)
} }
fn is_printable_char(ch: u8) -> bool { fn is_printable_char(ch: u8) -> bool {
@ -284,14 +284,14 @@ fn is_ctrl_char(ch: u8) -> bool {
(0..0x20).contains(&ch) (0..0x20).contains(&ch)
} }
fn char_to_signal(ch: u8, termios: &KernelTermios) -> Option<SigNum> { fn char_to_signal(ch: u8, termios: &CTermios) -> Option<SigNum> {
if !termios.is_canonical_mode() || !termios.contains_isig() { if !termios.is_canonical_mode() || !termios.contains_isig() {
return None; return None;
} }
match ch { match ch {
ch if ch == *termios.get_special_char(CC_C_CHAR::VINTR) => Some(SIGINT), ch if ch == termios.special_char(CCtrlCharId::VINTR) => Some(SIGINT),
ch if ch == *termios.get_special_char(CC_C_CHAR::VQUIT) => Some(SIGQUIT), ch if ch == termios.special_char(CCtrlCharId::VQUIT) => Some(SIGQUIT),
_ => None, _ => None,
} }
} }

View File

@ -1,21 +1,17 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
#![expect(dead_code)]
#![expect(non_camel_case_types)]
use crate::prelude::*; use crate::prelude::*;
// This definition is from occlum /// A control character; the `cc_t` type in Linux.
const KERNEL_NCCS: usize = 19; ///
/// Reference: <https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits-common.h#L5>.
type TcflagT = u32; type CCtrlChar = u8;
type CcT = u8;
type SpeedT = u32;
bitflags! { bitflags! {
/// The input flags; `c_iflags` bits in Linux.
#[derive(Pod)] #[derive(Pod)]
#[repr(C)] #[repr(C)]
pub struct C_IFLAGS: u32 { pub(super) struct CInputFlags: u32 {
// https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits-common.h // https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits-common.h
const IGNBRK = 0x001; /* Ignore break condition */ const IGNBRK = 0x001; /* Ignore break condition */
const BRKINT = 0x002; /* Signal interrupt on break */ const BRKINT = 0x002; /* Signal interrupt on break */
@ -36,16 +32,18 @@ bitflags! {
} }
} }
impl Default for C_IFLAGS { impl Default for CInputFlags {
fn default() -> Self { fn default() -> Self {
C_IFLAGS::ICRNL | C_IFLAGS::IXON Self::ICRNL | Self::IXON
} }
} }
bitflags! { bitflags! {
/// The output flags; `c_oflags` bits in Linux.
#[repr(C)] #[repr(C)]
#[derive(Pod)] #[derive(Pod)]
pub struct C_OFLAGS: u32 { pub(super) struct COutputFlags: u32 {
// https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits-common.h#L21
const OPOST = 1 << 0; /* Perform output processing */ const OPOST = 1 << 0; /* Perform output processing */
const OLCUC = 1 << 1; const OLCUC = 1 << 1;
const ONLCR = 1 << 2; const ONLCR = 1 << 2;
@ -57,57 +55,65 @@ bitflags! {
} }
} }
impl Default for C_OFLAGS { impl Default for COutputFlags {
fn default() -> Self { fn default() -> Self {
C_OFLAGS::OPOST | C_OFLAGS::ONLCR Self::OPOST | Self::ONLCR
} }
} }
/// The control flags; `c_cflags` bits in Linux.
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy, Pod)] #[derive(Debug, Clone, Copy, Pod)]
pub struct C_CFLAGS(u32); pub(super) struct CCtrlFlags(u32);
impl Default for C_CFLAGS { impl Default for CCtrlFlags {
fn default() -> Self { fn default() -> Self {
let cbaud = C_CFLAGS_BAUD::B38400 as u32; let cbaud = CCtrlBaud::B38400 as u32;
let csize = C_CFLAGS_CSIZE::CS8 as u32; let csize = CCtrlSize::CS8 as u32;
let c_cflags = cbaud | csize | CREAD; let c_cflags = cbaud | csize | Self::READ_BIT;
Self(c_cflags) Self(c_cflags)
} }
} }
impl C_CFLAGS { impl CCtrlFlags {
pub fn cbaud(&self) -> Result<C_CFLAGS_BAUD> { const BAUD_MASK: u32 = 0x0000100f;
let cbaud = self.0 & CBAUD_MASK; const SIZE_MASK: u32 = 0x00000030;
Ok(C_CFLAGS_BAUD::try_from(cbaud)?) const READ_BIT: u32 = 0x00000080;
#[expect(dead_code)]
pub(super) fn baud(&self) -> Result<CCtrlBaud> {
let baud = self.0 & Self::BAUD_MASK;
Ok(CCtrlBaud::try_from(baud)?)
} }
pub fn csize(&self) -> Result<C_CFLAGS_CSIZE> { #[expect(dead_code)]
let csize = self.0 & CSIZE_MASK; pub(super) fn size(&self) -> Result<CCtrlSize> {
Ok(C_CFLAGS_CSIZE::try_from(csize)?) let size = self.0 & Self::SIZE_MASK;
Ok(CCtrlSize::try_from(size)?)
} }
pub fn cread(&self) -> bool { #[expect(dead_code)]
self.0 & CREAD != 0 pub(super) fn is_read(&self) -> bool {
self.0 & Self::READ_BIT != 0
} }
} }
const CREAD: u32 = 0x00000080; /// The size part of the control flags ([`CCtrlFlags`]).
const CBAUD_MASK: u32 = 0x0000100f;
const CSIZE_MASK: u32 = 0x00000030;
#[repr(u32)] #[repr(u32)]
#[derive(Clone, Copy, TryFromInt)] #[derive(Clone, Copy, TryFromInt)]
pub enum C_CFLAGS_CSIZE { pub(super) enum CCtrlSize {
// https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits.h#L97
CS5 = 0x00000000, CS5 = 0x00000000,
CS6 = 0x00000010, CS6 = 0x00000010,
CS7 = 0x00000020, CS7 = 0x00000020,
CS8 = 0x00000030, CS8 = 0x00000030,
} }
/// The baud part of the control flags ([`CCtrlFlags`]).
#[repr(u32)] #[repr(u32)]
#[derive(Debug, Clone, Copy, TryFromInt)] #[derive(Debug, Clone, Copy, TryFromInt)]
pub enum C_CFLAGS_BAUD { pub(super) enum CCtrlBaud {
// https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits-common.h#L30
B0 = 0x00000000, /* hang up */ B0 = 0x00000000, /* hang up */
B50 = 0x00000001, B50 = 0x00000001,
B75 = 0x00000002, B75 = 0x00000002,
@ -127,9 +133,11 @@ pub enum C_CFLAGS_BAUD {
} }
bitflags! { bitflags! {
/// The local flags; `c_lflags` bits in Linux.
#[repr(C)] #[repr(C)]
#[derive(Pod)] #[derive(Pod)]
pub struct C_LFLAGS: u32 { pub(super) struct CLocalFlags: u32 {
// https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits.h#L127
const ISIG = 0x00001; const ISIG = 0x00001;
const ICANON = 0x00002; const ICANON = 0x00002;
const XCASE = 0x00004; const XCASE = 0x00004;
@ -149,24 +157,25 @@ bitflags! {
} }
} }
impl Default for C_LFLAGS { impl Default for CLocalFlags {
fn default() -> Self { fn default() -> Self {
C_LFLAGS::ICANON Self::ICANON
| C_LFLAGS::ECHO | Self::ECHO
| C_LFLAGS::ISIG | Self::ISIG
| C_LFLAGS::ECHOE | Self::ECHOE
| C_LFLAGS::ECHOK | Self::ECHOK
| C_LFLAGS::ECHOCTL | Self::ECHOCTL
| C_LFLAGS::ECHOKE | Self::ECHOKE
| C_LFLAGS::IEXTEN | Self::IEXTEN
} }
} }
/* c_cc characters index*/ /// An index for a control character ([`CCtrlChar`]).
#[repr(u32)] #[repr(u32)]
#[derive(Debug, Clone, Copy, TryFromInt)] #[derive(Debug, Clone, Copy, TryFromInt)]
#[expect(clippy::upper_case_acronyms)] #[expect(clippy::upper_case_acronyms)]
pub enum CC_C_CHAR { pub(super) enum CCtrlCharId {
// https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits.h#L42
VINTR = 0, VINTR = 0,
VQUIT = 1, VQUIT = 1,
VERASE = 2, VERASE = 2,
@ -186,117 +195,133 @@ pub enum CC_C_CHAR {
VEOL2 = 16, VEOL2 = 16,
} }
impl CC_C_CHAR { impl CCtrlCharId {
// The special char is from gvisor // The special char is from gvisor
pub fn default_char(&self) -> u8 { pub(super) const fn default_char(&self) -> u8 {
const fn control_character(c: char) -> u8 {
debug_assert!(c as u8 >= b'A');
c as u8 - b'A' + 1u8
}
match self { match self {
CC_C_CHAR::VINTR => control_character('C'), Self::VINTR => control_character('C'),
CC_C_CHAR::VQUIT => control_character('\\'), Self::VQUIT => control_character('\\'),
CC_C_CHAR::VERASE => b'\x7f', Self::VERASE => b'\x7f',
CC_C_CHAR::VKILL => control_character('U'), Self::VKILL => control_character('U'),
CC_C_CHAR::VEOF => control_character('D'), Self::VEOF => control_character('D'),
CC_C_CHAR::VTIME => b'\0', Self::VTIME => b'\0',
CC_C_CHAR::VMIN => 1, Self::VMIN => 1,
CC_C_CHAR::VSWTC => b'\0', Self::VSWTC => b'\0',
CC_C_CHAR::VSTART => control_character('Q'), Self::VSTART => control_character('Q'),
CC_C_CHAR::VSTOP => control_character('S'), Self::VSTOP => control_character('S'),
CC_C_CHAR::VSUSP => control_character('Z'), Self::VSUSP => control_character('Z'),
CC_C_CHAR::VEOL => b'\0', Self::VEOL => b'\0',
CC_C_CHAR::VREPRINT => control_character('R'), Self::VREPRINT => control_character('R'),
CC_C_CHAR::VDISCARD => control_character('O'), Self::VDISCARD => control_character('O'),
CC_C_CHAR::VWERASE => control_character('W'), Self::VWERASE => control_character('W'),
CC_C_CHAR::VLNEXT => control_character('V'), Self::VLNEXT => control_character('V'),
CC_C_CHAR::VEOL2 => b'\0', Self::VEOL2 => b'\0',
} }
} }
} }
/// The termios; `struct termios` in Linux.
///
/// Reference: <https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits.h#L30>.
#[derive(Debug, Clone, Copy, Pod)] #[derive(Debug, Clone, Copy, Pod)]
#[repr(C)] #[repr(C)]
pub struct KernelTermios { pub(super) struct CTermios {
c_iflags: C_IFLAGS, c_iflags: CInputFlags,
c_oflags: C_OFLAGS, c_oflags: COutputFlags,
c_cflags: C_CFLAGS, c_cflags: CCtrlFlags,
c_lflags: C_LFLAGS, c_lflags: CLocalFlags,
c_line: CcT, c_line: CCtrlChar,
c_cc: [CcT; KERNEL_NCCS], c_cc: [CCtrlChar; Self::NUM_CTRL_CHARS],
} }
impl Default for KernelTermios { impl Default for CTermios {
fn default() -> Self { fn default() -> Self {
let mut termios = Self { let mut termios = Self {
c_iflags: C_IFLAGS::default(), c_iflags: CInputFlags::default(),
c_oflags: C_OFLAGS::default(), c_oflags: COutputFlags::default(),
c_cflags: C_CFLAGS::default(), c_cflags: CCtrlFlags::default(),
c_lflags: C_LFLAGS::default(), c_lflags: CLocalFlags::default(),
c_line: 0, c_line: 0,
c_cc: [CcT::default(); KERNEL_NCCS], c_cc: [CCtrlChar::default(); Self::NUM_CTRL_CHARS],
}; };
*termios.get_special_char_mut(CC_C_CHAR::VINTR) = CC_C_CHAR::VINTR.default_char(); *termios.special_char_mut(CCtrlCharId::VINTR) = CCtrlCharId::VINTR.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VQUIT) = CC_C_CHAR::VQUIT.default_char(); *termios.special_char_mut(CCtrlCharId::VQUIT) = CCtrlCharId::VQUIT.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VERASE) = CC_C_CHAR::VERASE.default_char(); *termios.special_char_mut(CCtrlCharId::VERASE) = CCtrlCharId::VERASE.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VKILL) = CC_C_CHAR::VKILL.default_char(); *termios.special_char_mut(CCtrlCharId::VKILL) = CCtrlCharId::VKILL.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VEOF) = CC_C_CHAR::VEOF.default_char(); *termios.special_char_mut(CCtrlCharId::VEOF) = CCtrlCharId::VEOF.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VTIME) = CC_C_CHAR::VTIME.default_char(); *termios.special_char_mut(CCtrlCharId::VTIME) = CCtrlCharId::VTIME.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VMIN) = CC_C_CHAR::VMIN.default_char(); *termios.special_char_mut(CCtrlCharId::VMIN) = CCtrlCharId::VMIN.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VSWTC) = CC_C_CHAR::VSWTC.default_char(); *termios.special_char_mut(CCtrlCharId::VSWTC) = CCtrlCharId::VSWTC.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VSTART) = CC_C_CHAR::VSTART.default_char(); *termios.special_char_mut(CCtrlCharId::VSTART) = CCtrlCharId::VSTART.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VSTOP) = CC_C_CHAR::VSTOP.default_char(); *termios.special_char_mut(CCtrlCharId::VSTOP) = CCtrlCharId::VSTOP.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VSUSP) = CC_C_CHAR::VSUSP.default_char(); *termios.special_char_mut(CCtrlCharId::VSUSP) = CCtrlCharId::VSUSP.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VEOL) = CC_C_CHAR::VEOL.default_char(); *termios.special_char_mut(CCtrlCharId::VEOL) = CCtrlCharId::VEOL.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VREPRINT) = CC_C_CHAR::VREPRINT.default_char(); *termios.special_char_mut(CCtrlCharId::VREPRINT) = CCtrlCharId::VREPRINT.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VDISCARD) = CC_C_CHAR::VDISCARD.default_char(); *termios.special_char_mut(CCtrlCharId::VDISCARD) = CCtrlCharId::VDISCARD.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VWERASE) = CC_C_CHAR::VWERASE.default_char(); *termios.special_char_mut(CCtrlCharId::VWERASE) = CCtrlCharId::VWERASE.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VLNEXT) = CC_C_CHAR::VLNEXT.default_char(); *termios.special_char_mut(CCtrlCharId::VLNEXT) = CCtrlCharId::VLNEXT.default_char();
*termios.get_special_char_mut(CC_C_CHAR::VEOL2) = CC_C_CHAR::VEOL2.default_char(); *termios.special_char_mut(CCtrlCharId::VEOL2) = CCtrlCharId::VEOL2.default_char();
termios termios
} }
} }
impl KernelTermios { impl CTermios {
pub fn get_special_char(&self, cc_c_char: CC_C_CHAR) -> &CcT { /// The number of the control characters.
&self.c_cc[cc_c_char as usize] ///
/// Reference: <https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits.h#L9>.
const NUM_CTRL_CHARS: usize = 19;
pub(super) fn special_char(&self, id: CCtrlCharId) -> CCtrlChar {
self.c_cc[id as usize]
} }
pub fn get_special_char_mut(&mut self, cc_c_char: CC_C_CHAR) -> &mut CcT { pub(super) fn special_char_mut(&mut self, id: CCtrlCharId) -> &mut CCtrlChar {
&mut self.c_cc[cc_c_char as usize] &mut self.c_cc[id as usize]
} }
/// Canonical mode means we will handle input by lines, not by single character /// Returns whether the terminal is in the canonical mode.
pub fn is_canonical_mode(&self) -> bool { ///
self.c_lflags.contains(C_LFLAGS::ICANON) /// The canonical mode means that the input characters will be handled by lines, not by single
/// characters.
pub(super) fn is_canonical_mode(&self) -> bool {
self.c_lflags.contains(CLocalFlags::ICANON)
} }
/// ICRNL means we should map \r to \n /// Returns whether the input flags contain `ICRNL`.
pub fn contains_icrnl(&self) -> bool { ///
self.c_iflags.contains(C_IFLAGS::ICRNL) /// The `ICRNL` flag means the `\r` characters in the input should be mapped to `\n`.
pub(super) fn contains_icrnl(&self) -> bool {
self.c_iflags.contains(CInputFlags::ICRNL)
} }
pub fn contains_isig(&self) -> bool { pub(super) fn contains_isig(&self) -> bool {
self.c_lflags.contains(C_LFLAGS::ISIG) self.c_lflags.contains(CLocalFlags::ISIG)
} }
pub fn contain_echo(&self) -> bool { pub(super) fn contain_echo(&self) -> bool {
self.c_lflags.contains(C_LFLAGS::ECHO) self.c_lflags.contains(CLocalFlags::ECHO)
} }
pub fn contains_echo_ctl(&self) -> bool { pub(super) fn contains_echo_ctl(&self) -> bool {
self.c_lflags.contains(C_LFLAGS::ECHOCTL) self.c_lflags.contains(CLocalFlags::ECHOCTL)
} }
pub fn contains_iexten(&self) -> bool { pub(super) fn contains_iexten(&self) -> bool {
self.c_lflags.contains(C_LFLAGS::IEXTEN) self.c_lflags.contains(CLocalFlags::IEXTEN)
} }
} }
const fn control_character(c: char) -> u8 { /// A window size; `winsize` in Linux.
debug_assert!(c as u8 >= b'A'); ///
c as u8 - b'A' + 1u8 /// Reference: <https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termios.h#L15>.
}
#[derive(Debug, Clone, Copy, Default, Pod)] #[derive(Debug, Clone, Copy, Default, Pod)]
#[repr(C)] #[repr(C)]
pub struct WinSize { pub(super) struct CWinSize {
ws_row: u16, ws_row: u16,
ws_col: u16, ws_col: u16,
ws_xpixel: u16, ws_xpixel: u16,