bp3d_os/shell/os/
windows.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;
31use windows_sys::Win32::System::Console::{
32    GetConsoleMode, GetConsoleScreenBufferInfo, GetStdHandle, SetConsoleMode, WriteConsoleW,
33    CONSOLE_MODE, CONSOLE_SCREEN_BUFFER_INFO, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
34    STD_OUTPUT_HANDLE,
35};
36
37/// Represents an interactive terminal.
38pub struct Terminal {
39    attrs: CONSOLE_MODE,
40}
41
42impl Terminal {
43    /// Creates a new instance of an interactive terminal.
44    ///
45    /// This function automatically sets-up the current OS terminal for interactive input and resets
46    /// it back on drop automatically.
47    pub fn new() -> Self {
48        let mut attrs = MaybeUninit::<CONSOLE_MODE>::uninit();
49        unsafe {
50            let handle = GetStdHandle(STD_OUTPUT_HANDLE);
51            if GetConsoleMode(handle, attrs.as_mut_ptr()) != 1 {
52                panic!("Failed to initialize a windows console");
53            }
54            let mut attrs2 = attrs.assume_init();
55            attrs2 |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
56            SetConsoleMode(handle, attrs2);
57            Terminal {
58                attrs: attrs.assume_init(),
59            }
60        }
61    }
62}
63
64impl Drop for Terminal {
65    fn drop(&mut self) {
66        unsafe {
67            let handle = GetStdHandle(STD_OUTPUT_HANDLE);
68            SetConsoleMode(handle, self.attrs);
69        }
70    }
71}
72
73/// Writes the given string immediately (unbuffered).
74pub fn write(str: &str) {
75    unsafe {
76        let handle = GetStdHandle(STD_OUTPUT_HANDLE);
77        let mut encoded = str.encode_utf16().collect::<Vec<_>>();
78        encoded.push(0);
79        WriteConsoleW(
80            handle,
81            encoded.as_ptr(),
82            (encoded.len() - 1) as _,
83            std::ptr::null_mut(),
84            std::ptr::null(),
85        );
86    }
87}
88
89/// Returns a tuple with respectively the maximum number of columns and rows available in the
90/// terminal.
91///
92/// This function issues a syscall each time it is invoked.
93pub fn get_window_size() -> (i32, i32) {
94    unsafe {
95        let handle = GetStdHandle(STD_OUTPUT_HANDLE);
96        let mut info = MaybeUninit::<CONSOLE_SCREEN_BUFFER_INFO>::uninit();
97        GetConsoleScreenBufferInfo(handle, info.as_mut_ptr());
98        let info = info.assume_init();
99        let columns = info.srWindow.Right - info.srWindow.Left + 1;
100        let rows = info.srWindow.Bottom - info.srWindow.Top + 1;
101        (columns as _, rows as _)
102    }
103}
104
105thread_local! {
106    static HEIGHT: Cell<i32> = Cell::new(-1);
107}
108
109/// Returns the maximum number of rows available in the terminal.
110///
111/// This function amortizes the cost of the syscall by only issuing it once for the current thread.
112pub fn get_window_height_amortized() -> i32 {
113    if HEIGHT.get() == -1 {
114        let (_, rows) = get_window_size();
115        HEIGHT.set(rows);
116    }
117    HEIGHT.get()
118}