nx_termios.nx source
↩ module page · 162 lines · 5521 B
1// nx_termios.nx -- raw / cooked terminal mode via ioctl(TCGETS/TCSETS).
2//
3// Switches the controlling TTY between cooked (line-buffered, echo on)
4// and raw (byte-by-byte, no echo, no signals) modes. Required for
5// interactive TUI applications.
6//
7// Pairs with nx_tty.nx (output) and nx_input.nx (input).
8//
9// Per POSIX termios. Raw mode disables:
10// - ICANON (canonical line buffering)
11// - ECHO (echo input to terminal)
12// - ISIG (Ctrl+C/Z signal generation)
13// - IEXTEN (extended input processing)
14// - IXON (XON/XOFF flow control)
15// - ICRNL (CR -> NL input translation)
16// - OPOST (output post-processing)
17// ...and sets c_cc[VMIN]=1, c_cc[VTIME]=0 (return on each byte).
18//
19// genealogy_id: posix_termios_1003.1 + bsd_v6_tty_driver
20// + stevens_advanced_programming_1992
21// lineage_id: tty_mode_toggle_via_ioctl
22// axioms: NX_AX_LOGIC_EXCLUDED_MIDDLE (TTY is in raw OR cooked
23// mode at any instant; no third state)
24
25// nx_safety_envelope:
26// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
27// sil_target: SIL1
28// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
29// verdict: NOT_YET_EVALUATED
30
31import "syscalls.nx"
32import "runtime.nx"
33import "nx_axioms.nx"
34
35// ===== ioctl request constants (Linux generic) =========================
36
37const NX_TIOC_GETS: i64 = 0x5401 // TCGETS
38const NX_TIOC_SETS: i64 = 0x5402 // TCSETS
39const NX_TIOC_GWINSZ: i64 = 0x5413 // TIOCGWINSZ
40
41// ===== termios flag bits (Linux generic) ===============================
42//
43// Subset used for raw-mode toggling.
44
45// c_iflag
46const NX_T_BRKINT: i64 = 0x0002
47const NX_T_ICRNL: i64 = 0x0100
48const NX_T_INPCK: i64 = 0x0010
49const NX_T_ISTRIP: i64 = 0x0020
50const NX_T_IXON: i64 = 0x0400
51
52// c_oflag
53const NX_T_OPOST: i64 = 0x0001
54
55// c_cflag (we OR these on for 8-bit chars + no parity)
56const NX_T_CS8: i64 = 0x0030
57
58// c_lflag
59const NX_T_ICANON: i64 = 0x0002
60const NX_T_ECHO: i64 = 0x0008
61const NX_T_ISIG: i64 = 0x0001
62const NX_T_IEXTEN: i64 = 0x8000
63
64// c_cc[] indices
65const NX_T_VMIN: i64 = 6
66const NX_T_VTIME: i64 = 5
67
68// ===== termios buffer access ============================================
69//
70// We don't expose a struct; the buffer is opaque to callers. The
71// only operations are: get current state, modify flags, set new state.
72// Size: 60 bytes on Linux RV64 (4*4 + 1 + 19 + padding).
73
74const NX_TERMIOS_BYTES: i64 = 60
75
76// Read a 32-bit field at byte offset.
77func nx_termios_get32(buf: *u8, off: i64) -> i64 {
78 let b0: i64 = buf[off]
79 let b1: i64 = buf[off + 1]
80 let b2: i64 = buf[off + 2]
81 let b3: i64 = buf[off + 3]
82 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
83}
84
85// Write a 32-bit field at byte offset.
86func nx_termios_set32(buf: *u8, off: i64, v: i64) -> i64 {
87 buf[off] = v & 0xff
88 buf[off + 1] = (v >> 8) & 0xff
89 buf[off + 2] = (v >> 16) & 0xff
90 buf[off + 3] = (v >> 24) & 0xff
91 return 0
92}
93
94// ===== get / set wrappers ==============================================
95
96func nx_termios_get(buf: *u8) -> i64 {
97 return sys_ioctl(0, NX_TIOC_GETS, buf as i64)
98}
99
100func nx_termios_set(buf: *u8) -> i64 {
101 return sys_ioctl(0, NX_TIOC_SETS, buf as i64)
102}
103
104// ===== high-level raw / cooked toggles =================================
105
106// Switch stdin into raw mode. Saves the current state into save_buf
107// so caller can restore it later. Returns 0 on success, negative
108// (-errno) on ioctl failure.
109
110func nx_termios_enter_raw(save_buf: *u8) -> i64 {
111 let r: i64 = nx_termios_get(save_buf)
112 if r != 0 { return r }
113 // Copy current state into a working buffer.
114 let work: *u8 = sys_mmap(NX_TERMIOS_BYTES)
115 var i: i64 = 0
116 while i < NX_TERMIOS_BYTES {
117 work[i] = save_buf[i]
118 i = i + 1
119 }
120 // Clear flags.
121 let iflag: i64 = nx_termios_get32(work, 0)
122 let cleared_i: i64 = iflag & (-1 - (NX_T_BRKINT | NX_T_ICRNL | NX_T_INPCK | NX_T_ISTRIP | NX_T_IXON))
123 nx_termios_set32(work, 0, cleared_i)
124
125 let oflag: i64 = nx_termios_get32(work, 4)
126 nx_termios_set32(work, 4, oflag & (-1 - NX_T_OPOST))
127
128 let cflag: i64 = nx_termios_get32(work, 8)
129 nx_termios_set32(work, 8, cflag | NX_T_CS8)
130
131 let lflag: i64 = nx_termios_get32(work, 12)
132 let cleared_l: i64 = lflag & (-1 - (NX_T_ICANON | NX_T_ECHO | NX_T_ISIG | NX_T_IEXTEN))
133 nx_termios_set32(work, 12, cleared_l)
134
135 // c_cc[VMIN] = 1, c_cc[VTIME] = 0. c_cc starts at offset 17
136 // (after iflag/oflag/cflag/lflag = 16 bytes + c_line byte = 17).
137 work[17 + NX_T_VMIN] = 1
138 work[17 + NX_T_VTIME] = 0
139
140 return nx_termios_set(work)
141}
142
143func nx_termios_restore(save_buf: *u8) -> i64 {
144 return nx_termios_set(save_buf)
145}
146
147// ===== terminal size ====================================================
148//
149// TIOCGWINSZ returns a struct winsize { unsigned short ws_row, ws_col,
150// ws_xpixel, ws_ypixel; }. 8 bytes total. We read it and return rows
151// + cols via out parameters.
152
153func nx_termios_winsize(out_rows: *i64, out_cols: *i64) -> i64 {
154 let buf: *u8 = sys_mmap(8)
155 let r: i64 = sys_ioctl(1, NX_TIOC_GWINSZ, buf as i64)
156 if r != 0 { return r }
157 let rows: i64 = (buf[0] as i64) | ((buf[1] as i64) << 8)
158 let cols: i64 = (buf[2] as i64) | ((buf[3] as i64) << 8)
159 out_rows[0] = rows
160 out_cols[0] = cols
161 return 0
162}