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}