| 1 | module term |
| 2 | |
| 3 | import os |
| 4 | import time |
| 5 | |
| 6 | #include <conio.h> |
| 7 | |
| 8 | @[typedef] |
| 9 | pub struct C.COORD { |
| 10 | mut: |
| 11 | X i16 |
| 12 | Y i16 |
| 13 | } |
| 14 | |
| 15 | @[typedef] |
| 16 | pub struct C.SMALL_RECT { |
| 17 | mut: |
| 18 | Left u16 |
| 19 | Top u16 |
| 20 | Right u16 |
| 21 | Bottom u16 |
| 22 | } |
| 23 | |
| 24 | // win: CONSOLE_SCREEN_BUFFER_INFO |
| 25 | // https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str |
| 26 | @[typedef] |
| 27 | pub struct C.CONSOLE_SCREEN_BUFFER_INFO { |
| 28 | mut: |
| 29 | dwSize C.COORD |
| 30 | dwCursorPosition C.COORD |
| 31 | wAttributes u16 |
| 32 | srWindow C.SMALL_RECT |
| 33 | dwMaximumWindowSize C.COORD |
| 34 | } |
| 35 | |
| 36 | pub union C.uChar { |
| 37 | mut: |
| 38 | UnicodeChar rune |
| 39 | AsciiChar u8 |
| 40 | } |
| 41 | |
| 42 | @[typedef] |
| 43 | pub struct C.CHAR_INFO { |
| 44 | mut: |
| 45 | Char C.uChar |
| 46 | Attributes u16 |
| 47 | } |
| 48 | |
| 49 | // ref - https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo |
| 50 | fn C.GetConsoleScreenBufferInfo(handle C.HANDLE, info &C.CONSOLE_SCREEN_BUFFER_INFO) bool |
| 51 | |
| 52 | // ref - https://docs.microsoft.com/en-us/windows/console/setconsoletitle |
| 53 | fn C.SetConsoleTitle(title &u16) bool |
| 54 | |
| 55 | // ref - https://docs.microsoft.com/en-us/windows/console/setconsolecursorposition |
| 56 | fn C.SetConsoleCursorPosition(handle C.HANDLE, coord C.COORD) bool |
| 57 | |
| 58 | // ref - https://docs.microsoft.com/en-us/windows/console/scrollconsolescreenbuffer |
| 59 | fn C.ScrollConsoleScreenBuffer(output C.HANDLE, scroll_rect &C.SMALL_RECT, clip_rect &C.SMALL_RECT, des C.COORD, |
| 60 | fill &C.CHAR_INFO) bool |
| 61 | |
| 62 | // get_terminal_size returns a number of columns and rows of terminal window. |
| 63 | pub fn get_terminal_size() (int, int) { |
| 64 | if os.is_atty(1) > 0 && os.getenv('TERM') != 'dumb' { |
| 65 | info := C.CONSOLE_SCREEN_BUFFER_INFO{} |
| 66 | if C.GetConsoleScreenBufferInfo(C.GetStdHandle(C.STD_OUTPUT_HANDLE), &info) { |
| 67 | columns := int(info.srWindow.Right - info.srWindow.Left + 1) |
| 68 | rows := int(info.srWindow.Bottom - info.srWindow.Top + 1) |
| 69 | return columns, rows |
| 70 | } |
| 71 | } |
| 72 | return default_columns_size, default_rows_size |
| 73 | } |
| 74 | |
| 75 | // get_cursor_position returns a Coord containing the current cursor position. |
| 76 | pub fn get_cursor_position() !Coord { |
| 77 | mut res := Coord{} |
| 78 | if os.is_atty(1) > 0 && os.getenv('TERM') != 'dumb' { |
| 79 | info := C.CONSOLE_SCREEN_BUFFER_INFO{} |
| 80 | if C.GetConsoleScreenBufferInfo(C.GetStdHandle(C.STD_OUTPUT_HANDLE), &info) { |
| 81 | res.x = info.dwCursorPosition.X |
| 82 | res.y = info.dwCursorPosition.Y |
| 83 | } else { |
| 84 | return os.last_error() |
| 85 | } |
| 86 | } |
| 87 | return res |
| 88 | } |
| 89 | |
| 90 | // set_terminal_title changes the terminal title. |
| 91 | pub fn set_terminal_title(title string) bool { |
| 92 | wide_title := title.to_wide() |
| 93 | return C.SetConsoleTitle(wide_title) |
| 94 | } |
| 95 | |
| 96 | // set_tab_title changes the terminal *tab title*, for terminal emulators that do support several tabs. |
| 97 | pub fn set_tab_title(title string) bool { |
| 98 | // TODO: investigate, whether there is an API for changing just the tab title on windows yet. |
| 99 | return set_terminal_title(title) |
| 100 | } |
| 101 | |
| 102 | // clear clears current terminal screen. |
| 103 | // Implementation taken from https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-2. |
| 104 | pub fn clear() bool { |
| 105 | hconsole := C.GetStdHandle(C.STD_OUTPUT_HANDLE) |
| 106 | mut csbi := C.CONSOLE_SCREEN_BUFFER_INFO{} |
| 107 | mut scrollrect := C.SMALL_RECT{} |
| 108 | mut scrolltarget := C.COORD{} |
| 109 | mut fill := C.CHAR_INFO{} |
| 110 | |
| 111 | // Get the number of character cells in the current buffer. |
| 112 | if !C.GetConsoleScreenBufferInfo(hconsole, &csbi) { |
| 113 | return false |
| 114 | } |
| 115 | // Scroll the rectangle of the entire buffer. |
| 116 | scrollrect.Left = 0 |
| 117 | scrollrect.Top = 0 |
| 118 | scrollrect.Right = u16(csbi.dwSize.X) |
| 119 | scrollrect.Bottom = u16(csbi.dwSize.Y) |
| 120 | |
| 121 | // Scroll it upwards off the top of the buffer with a magnitude of the entire height. |
| 122 | scrolltarget.X = 0 |
| 123 | scrolltarget.Y = (0 - csbi.dwSize.Y) |
| 124 | |
| 125 | // Fill with empty spaces with the buffer's default text attribute. |
| 126 | fill.Char.UnicodeChar = rune(` `) |
| 127 | fill.Attributes = csbi.wAttributes |
| 128 | |
| 129 | // Do the scroll |
| 130 | C.ScrollConsoleScreenBuffer(hconsole, &scrollrect, C.NULL, scrolltarget, &fill) |
| 131 | |
| 132 | // Move the cursor to the top left corner too. |
| 133 | csbi.dwCursorPosition.X = 0 |
| 134 | csbi.dwCursorPosition.Y = 0 |
| 135 | |
| 136 | C.SetConsoleCursorPosition(hconsole, csbi.dwCursorPosition) |
| 137 | return true |
| 138 | } |
| 139 | |
| 140 | // supports_sixel returns `true` if the terminal supports Sixel graphics |
| 141 | // |
| 142 | // For more info on the sixel format: |
| 143 | // See https://en.wikipedia.org/wiki/Sixel |
| 144 | // See https://www.digiater.nl/openvms/decus/vax90b1/krypton-nasa/all-about-sixels.text |
| 145 | // For more info on terminal support: |
| 146 | // See https://www.arewesixelyet.com |
| 147 | pub fn supports_sixel() bool { |
| 148 | // According to (2024) https://www.arewesixelyet.com/#windows-console there's no support |
| 149 | return false |
| 150 | } |
| 151 | |
| 152 | // graphics_num_colors returns the number of color registers the terminal |
| 153 | // graphic attribute is set to use. This can be useful to know if the terminal |
| 154 | // is configured to support Sixel graphics. |
| 155 | // |
| 156 | // See "CSI ? Pi ; Pa ; Pv S" from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html |
| 157 | pub fn graphics_num_colors() u16 { |
| 158 | // Since this call is related to sixel terminal graphics and Windows Console and Terminal |
| 159 | // does not have support for querying the graphics setup this call returns 0 |
| 160 | return 0 |
| 161 | } |
| 162 | |
| 163 | // enable_echo enable/disable echo input characters. |
| 164 | pub fn enable_echo(enable bool) { |
| 165 | // no need under windows, use key_pressed func's echo |
| 166 | } |
| 167 | |
| 168 | fn C.kbhit() bool |
| 169 | fn C._getch() i32 |
| 170 | fn C._getche() i32 |
| 171 | |
| 172 | // KeyPressedParams contains the optional parameters that you can pass to key_pressed. |
| 173 | @[params] |
| 174 | pub struct KeyPressedParams { |
| 175 | pub mut: |
| 176 | blocking bool // whether to wait for a pressed key |
| 177 | echo bool // whether to output the pressed key to stdout |
| 178 | } |
| 179 | |
| 180 | // key_pressed gives back a single character, read from the standard input. |
| 181 | // It returns -1 on error or no character in non-blocking mode |
| 182 | pub fn key_pressed(params KeyPressedParams) i64 { |
| 183 | for { |
| 184 | if C.kbhit() { |
| 185 | res := if params.echo { |
| 186 | C._getche() |
| 187 | } else { |
| 188 | C._getch() |
| 189 | } |
| 190 | // see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getche-getwche?view=msvc-170 |
| 191 | // > When _getche or _getwche reads a function key or an arrow key, the function must be called twice; |
| 192 | // > the first call returns 0 or 0xE0, and the second call returns the actual key code. |
| 193 | if res in [0, 0xe0] { |
| 194 | if C.kbhit() { |
| 195 | res2 := if params.echo { |
| 196 | C._getche() |
| 197 | } else { |
| 198 | C._getch() |
| 199 | } |
| 200 | return i64(u32(0xe0) << 16 | u32(res2)) |
| 201 | } |
| 202 | } |
| 203 | return i64(res) |
| 204 | } |
| 205 | if !params.blocking { |
| 206 | // in non-blocking mode, we need to return immediately |
| 207 | return -1 |
| 208 | } |
| 209 | time.sleep(1 * time.millisecond) |
| 210 | } |
| 211 | return 0 |
| 212 | } |
| 213 | |