bp3d_os/shell/os/unix.rs
1// Copyright (c) 2025, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13// * Neither the name of BlockProject 3D nor the names of its contributors
14// may be used to endorse or promote products derived from this software
15// without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29use std::cell::Cell;
30use std::mem::MaybeUninit;
31
32/// Represents an interactive terminal.
33pub struct Terminal {
34 attrs: libc::termios,
35}
36
37impl Terminal {
38 /// Creates a new instance of an interactive terminal.
39 ///
40 /// This function automatically sets-up the current OS terminal for interactive input and resets
41 /// it back on drop automatically.
42 pub fn new() -> Self {
43 let mut attrs = MaybeUninit::<libc::termios>::uninit();
44 unsafe {
45 libc::tcgetattr(0, attrs.as_mut_ptr());
46 let mut newattrs = attrs.assume_init();
47 newattrs.c_lflag &= !(libc::ICANON | libc::ECHO);
48 libc::tcsetattr(0, libc::TCSANOW, &newattrs);
49 Terminal {
50 attrs: attrs.assume_init(),
51 }
52 }
53 }
54}
55
56impl Drop for Terminal {
57 fn drop(&mut self) {
58 unsafe {
59 libc::tcsetattr(0, libc::TCSANOW, &self.attrs);
60 }
61 }
62}
63
64/// Writes the given string immediately (unbuffered).
65pub fn write(str: &str) {
66 unsafe {
67 libc::write(1, str.as_bytes().as_ptr() as _, str.len());
68 }
69}
70
71/// Returns a tuple with respectively the maximum number of columns and rows available in the
72/// terminal.
73///
74/// This function issues a syscall each time it is invoked.
75pub fn get_window_size() -> (i32, i32) {
76 let mut sz = std::mem::MaybeUninit::<libc::winsize>::uninit();
77 unsafe {
78 libc::ioctl(1, libc::TIOCGWINSZ, sz.as_mut_ptr());
79 (sz.assume_init().ws_col as _, sz.assume_init().ws_row as _)
80 }
81}
82
83thread_local! {
84 static HEIGHT: Cell<i32> = const { Cell::new(-1) };
85}
86
87/// Returns the maximum number of rows available in the terminal.
88///
89/// This function amortizes the cost of the syscall by only issuing it once for the current thread.
90pub fn get_window_height_amortized() -> i32 {
91 if HEIGHT.get() == -1 {
92 let mut sz = std::mem::MaybeUninit::<libc::winsize>::uninit();
93 unsafe {
94 libc::ioctl(1, libc::TIOCGWINSZ, sz.as_mut_ptr());
95 HEIGHT.set(sz.assume_init().ws_row as _);
96 }
97 }
98 HEIGHT.get()
99}