1 //! WinApi related logic for terminal manipulation.
2 use crossterm_winapi::{Console, ConsoleMode, Coord, Handle, ScreenBuffer, Size};
3 use winapi::{
4 shared::minwindef::DWORD,
5 um::wincon::{SetConsoleTitleW, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT},
6 };
7
8 use crate::{cursor, terminal::ClearType, ErrorKind, Result};
9
10 const RAW_MODE_MASK: DWORD = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT;
11
enable_raw_mode() -> Result<()>12 pub(crate) fn enable_raw_mode() -> Result<()> {
13 let console_mode = ConsoleMode::from(Handle::current_in_handle()?);
14
15 let dw_mode = console_mode.mode()?;
16
17 let new_mode = dw_mode & !RAW_MODE_MASK;
18
19 console_mode.set_mode(new_mode)?;
20
21 Ok(())
22 }
23
disable_raw_mode() -> Result<()>24 pub(crate) fn disable_raw_mode() -> Result<()> {
25 let console_mode = ConsoleMode::from(Handle::current_in_handle()?);
26
27 let dw_mode = console_mode.mode()?;
28
29 let new_mode = dw_mode | RAW_MODE_MASK;
30
31 console_mode.set_mode(new_mode)?;
32
33 Ok(())
34 }
35
size() -> Result<(u16, u16)>36 pub(crate) fn size() -> Result<(u16, u16)> {
37 let terminal_size = ScreenBuffer::current()?.info()?.terminal_size();
38 // windows starts counting at 0, unix at 1, add one to replicated unix behaviour.
39 Ok((
40 (terminal_size.width + 1) as u16,
41 (terminal_size.height + 1) as u16,
42 ))
43 }
44
clear(clear_type: ClearType) -> Result<()>45 pub(crate) fn clear(clear_type: ClearType) -> Result<()> {
46 let screen_buffer = ScreenBuffer::current()?;
47 let csbi = screen_buffer.info()?;
48
49 let pos = csbi.cursor_pos();
50 let buffer_size = csbi.buffer_size();
51 let current_attribute = csbi.attributes();
52
53 match clear_type {
54 ClearType::All => {
55 clear_entire_screen(buffer_size, current_attribute)?;
56 }
57 ClearType::FromCursorDown => clear_after_cursor(pos, buffer_size, current_attribute)?,
58 ClearType::FromCursorUp => clear_before_cursor(pos, buffer_size, current_attribute)?,
59 ClearType::CurrentLine => clear_current_line(pos, buffer_size, current_attribute)?,
60 ClearType::UntilNewLine => clear_until_line(pos, buffer_size, current_attribute)?,
61 };
62 Ok(())
63 }
64
scroll_up(row_count: u16) -> Result<()>65 pub(crate) fn scroll_up(row_count: u16) -> Result<()> {
66 let csbi = ScreenBuffer::current()?;
67 let mut window = csbi.info()?.terminal_window();
68
69 // check whether the window is too close to the screen buffer top
70 let count = row_count as i16;
71 if window.top >= count {
72 window.top -= count; // move top down
73 window.bottom -= count; // move bottom down
74
75 Console::output()?.set_console_info(true, window)?;
76 }
77 Ok(())
78 }
79
scroll_down(row_count: u16) -> Result<()>80 pub(crate) fn scroll_down(row_count: u16) -> Result<()> {
81 let screen_buffer = ScreenBuffer::current()?;
82 let csbi = screen_buffer.info()?;
83 let mut window = csbi.terminal_window();
84 let buffer_size = csbi.buffer_size();
85
86 // check whether the window is too close to the screen buffer top
87 let count = row_count as i16;
88 if window.bottom < buffer_size.height - count {
89 window.top += count; // move top down
90 window.bottom += count; // move bottom down
91
92 Console::output()?.set_console_info(true, window)?;
93 }
94 Ok(())
95 }
96
set_size(width: u16, height: u16) -> Result<()>97 pub(crate) fn set_size(width: u16, height: u16) -> Result<()> {
98 if width <= 1 {
99 return Err(ErrorKind::ResizingTerminalFailure(String::from(
100 "Cannot set the terminal width lower than 1.",
101 )));
102 }
103
104 if height <= 1 {
105 return Err(ErrorKind::ResizingTerminalFailure(String::from(
106 "Cannot set the terminal height lower then 1.",
107 )));
108 }
109
110 // get the position of the current console window
111 let screen_buffer = ScreenBuffer::current()?;
112 let console = Console::from(screen_buffer.handle().clone());
113 let csbi = screen_buffer.info()?;
114
115 let current_size = csbi.buffer_size();
116 let window = csbi.terminal_window();
117
118 let mut new_size = Size::new(current_size.width, current_size.height);
119
120 // If the buffer is smaller than this new window size, resize the
121 // buffer to be large enough. Include window position.
122 let mut resize_buffer = false;
123
124 let width = width as i16;
125 if current_size.width < window.left + width {
126 if window.left >= i16::max_value() - width {
127 return Err(ErrorKind::ResizingTerminalFailure(String::from(
128 "Argument out of range when setting terminal width.",
129 )));
130 }
131
132 new_size.width = window.left + width;
133 resize_buffer = true;
134 }
135 let height = height as i16;
136 if current_size.height < window.top + height {
137 if window.top >= i16::max_value() - height {
138 return Err(ErrorKind::ResizingTerminalFailure(String::from(
139 "Argument out of range when setting terminal height.",
140 )));
141 }
142
143 new_size.height = window.top + height;
144 resize_buffer = true;
145 }
146
147 if resize_buffer
148 && screen_buffer
149 .set_size(new_size.width - 1, new_size.height - 1)
150 .is_err()
151 {
152 return Err(ErrorKind::ResizingTerminalFailure(String::from(
153 "Something went wrong when setting screen buffer size.",
154 )));
155 }
156
157 let mut window = window;
158
159 // preserve the position, but change the size.
160 window.bottom = window.top + height - 1;
161 window.right = window.left + width - 1;
162 console.set_console_info(true, window)?;
163
164 // if we resized the buffer, un-resize it.
165 if resize_buffer
166 && screen_buffer
167 .set_size(current_size.width - 1, current_size.height - 1)
168 .is_err()
169 {
170 return Err(ErrorKind::ResizingTerminalFailure(String::from(
171 "Something went wrong when setting screen buffer size.",
172 )));
173 }
174
175 let bounds = console.largest_window_size();
176
177 if width > bounds.x {
178 return Err(ErrorKind::ResizingTerminalFailure(format!(
179 "Argument width: {} out of range when setting terminal width.",
180 width
181 )));
182 }
183 if height > bounds.y {
184 return Err(ErrorKind::ResizingTerminalFailure(format!(
185 "Argument height: {} out of range when setting terminal height.",
186 width
187 )));
188 }
189
190 Ok(())
191 }
192
set_window_title(title: &str) -> Result<()>193 pub(crate) fn set_window_title(title: &str) -> Result<()> {
194 let mut title: Vec<_> = title.encode_utf16().collect();
195 title.push(0);
196 let result = unsafe { SetConsoleTitleW(title.as_ptr()) };
197 if result != 0 {
198 Ok(())
199 } else {
200 Err(ErrorKind::SettingTerminalTitleFailure)
201 }
202 }
203
clear_after_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()>204 fn clear_after_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> {
205 let (mut x, mut y) = (location.x, location.y);
206
207 // if cursor position is at the outer right position
208 if x as i16 > buffer_size.width {
209 y += 1;
210 x = 0;
211 }
212
213 // location where to start clearing
214 let start_location = Coord::new(x, y);
215
216 // get sum cells before cursor
217 let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32;
218
219 clear_winapi(start_location, cells_to_write, current_attribute)
220 }
221
clear_before_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()>222 fn clear_before_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> {
223 let (xpos, ypos) = (location.x, location.y);
224
225 // one cell after cursor position
226 let x = 0;
227 // one at row of cursor position
228 let y = 0;
229
230 // location where to start clearing
231 let start_location = Coord::new(x, y);
232
233 // get sum cells before cursor
234 let cells_to_write = (buffer_size.width as u32 * ypos as u32) + (xpos as u32 + 1);
235
236 // clear everything before cursor position
237 clear_winapi(start_location, cells_to_write, current_attribute)
238 }
239
clear_entire_screen(buffer_size: Size, current_attribute: u16) -> Result<()>240 fn clear_entire_screen(buffer_size: Size, current_attribute: u16) -> Result<()> {
241 // get sum cells before cursor
242 let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32;
243
244 // location where to start clearing
245 let start_location = Coord::new(0, 0);
246
247 // clear the entire screen
248 clear_winapi(start_location, cells_to_write, current_attribute)?;
249
250 // put the cursor back at cell 0,0
251 cursor::sys::move_to(0, 0)?;
252 Ok(())
253 }
254
clear_current_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()>255 fn clear_current_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> {
256 // location where to start clearing
257 let start_location = Coord::new(0, location.y);
258
259 // get sum cells before cursor
260 let cells_to_write = buffer_size.width as u32;
261
262 // clear the whole current line
263 clear_winapi(start_location, cells_to_write, current_attribute)?;
264
265 // put the cursor back at cell 1 on current row
266 cursor::sys::move_to(0, location.y as u16)?;
267 Ok(())
268 }
269
clear_until_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()>270 fn clear_until_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> {
271 let (x, y) = (location.x, location.y);
272
273 // location where to start clearing
274 let start_location = Coord::new(x, y);
275
276 // get sum cells before cursor
277 let cells_to_write = (buffer_size.width - x as i16) as u32;
278
279 // clear until the current line
280 clear_winapi(start_location, cells_to_write, current_attribute)?;
281
282 // put the cursor back at original cursor position before we did the clearing
283 cursor::sys::move_to(x as u16, y as u16)?;
284 Ok(())
285 }
286
clear_winapi(start_location: Coord, cells_to_write: u32, current_attribute: u16) -> Result<()>287 fn clear_winapi(start_location: Coord, cells_to_write: u32, current_attribute: u16) -> Result<()> {
288 let console = Console::from(Handle::current_out_handle()?);
289 console.fill_whit_character(start_location, cells_to_write, ' ')?;
290 console.fill_whit_attribute(start_location, cells_to_write, current_attribute)?;
291 Ok(())
292 }
293
294 #[cfg(test)]
295 mod tests {
296 use std::{ffi::OsString, os::windows::ffi::OsStringExt};
297
298 use winapi::um::wincon::GetConsoleTitleW;
299
300 use crossterm_winapi::ScreenBuffer;
301
302 use super::{scroll_down, scroll_up, set_size, set_window_title, size};
303
304 #[test]
test_resize_winapi()305 fn test_resize_winapi() {
306 let (width, height) = size().unwrap();
307
308 set_size(30, 30).unwrap();
309 assert_eq!((30, 30), size().unwrap());
310
311 // reset to previous size
312 set_size(width, height).unwrap();
313 assert_eq!((width, height), size().unwrap());
314 }
315
316 // Test is disabled, because it's failing on Travis CI
317 #[test]
318 #[ignore]
test_scroll_down_winapi()319 fn test_scroll_down_winapi() {
320 let current_window = ScreenBuffer::current()
321 .unwrap()
322 .info()
323 .unwrap()
324 .terminal_window();
325
326 scroll_down(2).unwrap();
327
328 let new_window = ScreenBuffer::current()
329 .unwrap()
330 .info()
331 .unwrap()
332 .terminal_window();
333
334 assert_eq!(new_window.top, current_window.top + 2);
335 assert_eq!(new_window.bottom, current_window.bottom + 2);
336 }
337
338 // Test is disabled, because it's failing on Travis CI
339 #[test]
340 #[ignore]
test_scroll_up_winapi()341 fn test_scroll_up_winapi() {
342 // move the terminal buffer down before moving it up
343 test_scroll_down_winapi();
344
345 let current_window = ScreenBuffer::current()
346 .unwrap()
347 .info()
348 .unwrap()
349 .terminal_window();
350
351 scroll_up(2).unwrap();
352
353 let new_window = ScreenBuffer::current()
354 .unwrap()
355 .info()
356 .unwrap()
357 .terminal_window();
358
359 assert_eq!(new_window.top, current_window.top - 2);
360 assert_eq!(new_window.bottom, current_window.bottom - 2);
361 }
362
363 #[test]
test_set_title_winapi()364 fn test_set_title_winapi() {
365 let test_title = "this is a crossterm test title";
366 set_window_title(test_title).unwrap();
367
368 let mut raw = [0 as u16; 128];
369 let length = unsafe { GetConsoleTitleW(raw.as_mut_ptr(), raw.len() as u32) } as usize;
370 assert_ne!(0, length);
371
372 let console_title = OsString::from_wide(&raw[..length]).into_string().unwrap();
373 assert_eq!(test_title, &console_title[..]);
374 }
375 }
376