1 //! WinAPI related logic to cursor manipulation.
2 
3 use std::convert::TryFrom;
4 use std::io;
5 use std::sync::atomic::{AtomicU64, Ordering};
6 
7 use crossterm_winapi::{result, Coord, Handle, HandleType, ScreenBuffer};
8 use winapi::{
9     shared::minwindef::{FALSE, TRUE},
10     um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD},
11 };
12 
13 use crate::Result;
14 
15 /// The position of the cursor, written when you save the cursor's position.
16 ///
17 /// This is `u64::MAX` initially. Otherwise, it stores the cursor's x position bit-shifted left 16
18 /// times or-ed with the cursor's y position, where both are `i16`s.
19 static SAVED_CURSOR_POS: AtomicU64 = AtomicU64::new(u64::MAX);
20 
21 // The 'y' position of the cursor is not relative to the window but absolute to screen buffer.
22 // We can calculate the relative cursor position by subtracting the top position of the terminal window from the y position.
23 // This results in an 1-based coord zo subtract 1 to make cursor position 0-based.
parse_relative_y(y: i16) -> Result<i16>24 pub fn parse_relative_y(y: i16) -> Result<i16> {
25     let window = ScreenBuffer::current()?.info()?;
26 
27     let window_size = window.terminal_window();
28     let screen_size = window.terminal_size();
29 
30     if y <= screen_size.height {
31         Ok(y)
32     } else {
33         Ok(y - window_size.top)
34     }
35 }
36 
37 /// Returns the cursor position (column, row).
38 ///
39 /// The top left cell is represented `0,0`.
position() -> Result<(u16, u16)>40 pub fn position() -> Result<(u16, u16)> {
41     let cursor = ScreenBufferCursor::output()?;
42     let mut position = cursor.position()?;
43     //    if position.y != 0 {
44     position.y = parse_relative_y(position.y)?;
45     //    }
46     Ok(position.into())
47 }
48 
show_cursor(show_cursor: bool) -> Result<()>49 pub(crate) fn show_cursor(show_cursor: bool) -> Result<()> {
50     ScreenBufferCursor::from(Handle::current_out_handle()?).set_visibility(show_cursor)
51 }
52 
move_to(column: u16, row: u16) -> Result<()>53 pub(crate) fn move_to(column: u16, row: u16) -> Result<()> {
54     let cursor = ScreenBufferCursor::output()?;
55     cursor.move_to(column as i16, row as i16)?;
56     Ok(())
57 }
58 
move_up(count: u16) -> Result<()>59 pub(crate) fn move_up(count: u16) -> Result<()> {
60     let (column, row) = position()?;
61     move_to(column, row - count)?;
62     Ok(())
63 }
64 
move_right(count: u16) -> Result<()>65 pub(crate) fn move_right(count: u16) -> Result<()> {
66     let (column, row) = position()?;
67     move_to(column + count, row)?;
68     Ok(())
69 }
70 
move_down(count: u16) -> Result<()>71 pub(crate) fn move_down(count: u16) -> Result<()> {
72     let (column, row) = position()?;
73     move_to(column, row + count)?;
74     Ok(())
75 }
76 
move_left(count: u16) -> Result<()>77 pub(crate) fn move_left(count: u16) -> Result<()> {
78     let (column, row) = position()?;
79     move_to(column - count, row)?;
80     Ok(())
81 }
82 
move_to_column(new_column: u16) -> Result<()>83 pub(crate) fn move_to_column(new_column: u16) -> Result<()> {
84     let (_, row) = position()?;
85     move_to(new_column, row)?;
86     Ok(())
87 }
88 
move_to_row(new_row: u16) -> Result<()>89 pub(crate) fn move_to_row(new_row: u16) -> Result<()> {
90     let (col, _) = position()?;
91     move_to(col, new_row)?;
92     Ok(())
93 }
94 
move_to_next_line(count: u16) -> Result<()>95 pub(crate) fn move_to_next_line(count: u16) -> Result<()> {
96     let (_, row) = position()?;
97     move_to(0, row + count)?;
98     Ok(())
99 }
100 
move_to_previous_line(count: u16) -> Result<()>101 pub(crate) fn move_to_previous_line(count: u16) -> Result<()> {
102     let (_, row) = position()?;
103     move_to(0, row - count)?;
104     Ok(())
105 }
106 
save_position() -> Result<()>107 pub(crate) fn save_position() -> Result<()> {
108     ScreenBufferCursor::output()?.save_position()?;
109     Ok(())
110 }
111 
restore_position() -> Result<()>112 pub(crate) fn restore_position() -> Result<()> {
113     ScreenBufferCursor::output()?.restore_position()?;
114     Ok(())
115 }
116 
117 /// WinAPI wrapper over terminal cursor behaviour.
118 struct ScreenBufferCursor {
119     screen_buffer: ScreenBuffer,
120 }
121 
122 impl ScreenBufferCursor {
output() -> Result<ScreenBufferCursor>123     fn output() -> Result<ScreenBufferCursor> {
124         Ok(ScreenBufferCursor {
125             screen_buffer: ScreenBuffer::from(Handle::new(HandleType::CurrentOutputHandle)?),
126         })
127     }
128 
position(&self) -> Result<Coord>129     fn position(&self) -> Result<Coord> {
130         Ok(self.screen_buffer.info()?.cursor_pos())
131     }
132 
move_to(&self, x: i16, y: i16) -> Result<()>133     fn move_to(&self, x: i16, y: i16) -> Result<()> {
134         if x < 0 {
135             return Err(io::Error::new(
136                 io::ErrorKind::Other,
137                 format!(
138                     "Argument Out of Range Exception when setting cursor position to X: {}",
139                     x
140                 ),
141             ));
142         }
143 
144         if y < 0 {
145             return Err(io::Error::new(
146                 io::ErrorKind::Other,
147                 format!(
148                     "Argument Out of Range Exception when setting cursor position to Y: {}",
149                     y
150                 ),
151             ));
152         }
153 
154         let position = COORD { X: x, Y: y };
155 
156         unsafe {
157             if result(SetConsoleCursorPosition(
158                 **self.screen_buffer.handle(),
159                 position,
160             ))
161             .is_err()
162             {
163                 return Err(io::Error::last_os_error());
164             }
165         }
166         Ok(())
167     }
168 
set_visibility(&self, visible: bool) -> Result<()>169     fn set_visibility(&self, visible: bool) -> Result<()> {
170         let cursor_info = CONSOLE_CURSOR_INFO {
171             dwSize: 100,
172             bVisible: if visible { TRUE } else { FALSE },
173         };
174 
175         unsafe {
176             if result(SetConsoleCursorInfo(
177                 **self.screen_buffer.handle(),
178                 &cursor_info,
179             ))
180             .is_err()
181             {
182                 return Err(io::Error::last_os_error());
183             }
184         }
185         Ok(())
186     }
187 
restore_position(&self) -> Result<()>188     fn restore_position(&self) -> Result<()> {
189         if let Ok(val) = u32::try_from(SAVED_CURSOR_POS.load(Ordering::Relaxed)) {
190             let x = (val >> 16) as i16;
191             let y = val as i16;
192             self.move_to(x, y)?;
193         }
194 
195         Ok(())
196     }
197 
save_position(&self) -> Result<()>198     fn save_position(&self) -> Result<()> {
199         let position = self.position()?;
200 
201         let bits = u64::from(u32::from(position.x as u16) << 16 | u32::from(position.y as u16));
202         SAVED_CURSOR_POS.store(bits, Ordering::Relaxed);
203 
204         Ok(())
205     }
206 }
207 
208 impl From<Handle> for ScreenBufferCursor {
from(handle: Handle) -> Self209     fn from(handle: Handle) -> Self {
210         ScreenBufferCursor {
211             screen_buffer: ScreenBuffer::from(handle),
212         }
213     }
214 }
215 
216 #[cfg(test)]
217 mod tests {
218     use super::{
219         move_down, move_left, move_right, move_to, move_to_column, move_to_next_line,
220         move_to_previous_line, move_to_row, move_up, position, restore_position, save_position,
221     };
222 
223     #[test]
test_move_to_winapi()224     fn test_move_to_winapi() {
225         let (saved_x, saved_y) = position().unwrap();
226 
227         move_to(saved_x + 1, saved_y + 1).unwrap();
228         assert_eq!(position().unwrap(), (saved_x + 1, saved_y + 1));
229 
230         move_to(saved_x, saved_y).unwrap();
231         assert_eq!(position().unwrap(), (saved_x, saved_y));
232     }
233 
234     #[test]
test_move_right_winapi()235     fn test_move_right_winapi() {
236         let (saved_x, saved_y) = position().unwrap();
237         move_right(1).unwrap();
238         assert_eq!(position().unwrap(), (saved_x + 1, saved_y));
239     }
240 
241     #[test]
test_move_left_winapi()242     fn test_move_left_winapi() {
243         move_to(2, 0).unwrap();
244 
245         move_left(2).unwrap();
246 
247         assert_eq!(position().unwrap(), (0, 0));
248     }
249 
250     #[test]
test_move_up_winapi()251     fn test_move_up_winapi() {
252         move_to(0, 2).unwrap();
253 
254         move_up(2).unwrap();
255 
256         assert_eq!(position().unwrap(), (0, 0));
257     }
258 
259     #[test]
test_move_to_next_line_winapi()260     fn test_move_to_next_line_winapi() {
261         move_to(0, 2).unwrap();
262 
263         move_to_next_line(2).unwrap();
264 
265         assert_eq!(position().unwrap(), (0, 4));
266     }
267 
268     #[test]
test_move_to_previous_line_winapi()269     fn test_move_to_previous_line_winapi() {
270         move_to(0, 2).unwrap();
271 
272         move_to_previous_line(2).unwrap();
273 
274         assert_eq!(position().unwrap(), (0, 0));
275     }
276 
277     #[test]
test_move_to_column_winapi()278     fn test_move_to_column_winapi() {
279         move_to(0, 2).unwrap();
280 
281         move_to_column(12).unwrap();
282 
283         assert_eq!(position().unwrap(), (12, 2));
284     }
285 
286     #[test]
test_move_to_row_winapi()287     fn test_move_to_row_winapi() {
288         move_to(0, 2).unwrap();
289 
290         move_to_row(5).unwrap();
291 
292         assert_eq!(position().unwrap(), (0, 5));
293     }
294 
295     #[test]
test_move_down_winapi()296     fn test_move_down_winapi() {
297         move_to(0, 0).unwrap();
298 
299         move_down(2).unwrap();
300 
301         assert_eq!(position().unwrap(), (0, 2));
302     }
303 
304     #[test]
test_save_restore_position_winapi()305     fn test_save_restore_position_winapi() {
306         let (saved_x, saved_y) = position().unwrap();
307 
308         save_position().unwrap();
309         move_to(saved_x + 1, saved_y + 1).unwrap();
310         restore_position().unwrap();
311 
312         let (x, y) = position().unwrap();
313 
314         assert_eq!(x, saved_x);
315         assert_eq!(y, saved_y);
316     }
317 }
318