1 //! WinApi related logic to cursor manipulation.
2 
3 use std::io;
4 use std::sync::Mutex;
5 
6 use crossterm_winapi::{is_true, Coord, Handle, HandleType, ScreenBuffer};
7 use winapi::{
8     shared::minwindef::{FALSE, TRUE},
9     um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD},
10 };
11 
12 use lazy_static::lazy_static;
13 
14 use crate::utils::Result;
15 
16 lazy_static! {
17     static ref SAVED_CURSOR_POS: Mutex<Option<(i16, i16)>> = Mutex::new(None);
18 }
19 
20 /// Returns the cursor position (column, row).
21 ///
22 /// The top left cell is represented `0,0`.
position() -> Result<(u16, u16)>23 pub fn position() -> Result<(u16, u16)> {
24     let cursor = ScreenBufferCursor::output()?;
25     Ok(cursor.position()?.into())
26 }
27 
show_cursor(show_cursor: bool) -> Result<()>28 pub(crate) fn show_cursor(show_cursor: bool) -> Result<()> {
29     ScreenBufferCursor::from(Handle::current_out_handle()?).set_visibility(show_cursor)
30 }
31 
move_to(column: u16, row: u16) -> Result<()>32 pub(crate) fn move_to(column: u16, row: u16) -> Result<()> {
33     let cursor = ScreenBufferCursor::output()?;
34     cursor.move_to(column as i16, row as i16)?;
35     Ok(())
36 }
37 
move_up(count: u16) -> Result<()>38 pub(crate) fn move_up(count: u16) -> Result<()> {
39     let (column, row) = position()?;
40     move_to(column, row - count)?;
41     Ok(())
42 }
43 
move_right(count: u16) -> Result<()>44 pub(crate) fn move_right(count: u16) -> Result<()> {
45     let (column, row) = position()?;
46     move_to(column + count, row)?;
47     Ok(())
48 }
49 
move_down(count: u16) -> Result<()>50 pub(crate) fn move_down(count: u16) -> Result<()> {
51     let (column, row) = position()?;
52     move_to(column, row + count)?;
53     Ok(())
54 }
55 
move_left(count: u16) -> Result<()>56 pub(crate) fn move_left(count: u16) -> Result<()> {
57     let (column, row) = position()?;
58     move_to(column - count, row)?;
59     Ok(())
60 }
61 
save_position() -> Result<()>62 pub(crate) fn save_position() -> Result<()> {
63     ScreenBufferCursor::output()?.save_position()?;
64     Ok(())
65 }
66 
restore_position() -> Result<()>67 pub(crate) fn restore_position() -> Result<()> {
68     ScreenBufferCursor::output()?.restore_position()?;
69     Ok(())
70 }
71 
72 /// WinApi wrapper over terminal cursor behaviour.
73 struct ScreenBufferCursor {
74     screen_buffer: ScreenBuffer,
75 }
76 
77 impl ScreenBufferCursor {
output() -> Result<ScreenBufferCursor>78     fn output() -> Result<ScreenBufferCursor> {
79         Ok(ScreenBufferCursor {
80             screen_buffer: ScreenBuffer::from(Handle::new(HandleType::CurrentOutputHandle)?),
81         })
82     }
83 
position(&self) -> Result<Coord>84     fn position(&self) -> Result<Coord> {
85         Ok(self.screen_buffer.info()?.cursor_pos())
86     }
87 
move_to(&self, x: i16, y: i16) -> Result<()>88     fn move_to(&self, x: i16, y: i16) -> Result<()> {
89         if x < 0 || x >= <i16>::max_value() {
90             Err(io::Error::new(
91                 io::ErrorKind::Other,
92                 format!(
93                     "Argument Out of Range Exception when setting cursor position to X: {}",
94                     x
95                 ),
96             ))?;
97         }
98 
99         if y < 0 || y >= <i16>::max_value() {
100             Err(io::Error::new(
101                 io::ErrorKind::Other,
102                 format!(
103                     "Argument Out of Range Exception when setting cursor position to Y: {}",
104                     y
105                 ),
106             ))?;
107         }
108 
109         let position = COORD { X: x, Y: y };
110 
111         unsafe {
112             if !is_true(SetConsoleCursorPosition(
113                 **self.screen_buffer.handle(),
114                 position,
115             )) {
116                 Err(io::Error::last_os_error())?;
117             }
118         }
119         Ok(())
120     }
121 
set_visibility(&self, visible: bool) -> Result<()>122     fn set_visibility(&self, visible: bool) -> Result<()> {
123         let cursor_info = CONSOLE_CURSOR_INFO {
124             dwSize: 100,
125             bVisible: if visible { TRUE } else { FALSE },
126         };
127 
128         unsafe {
129             if !is_true(SetConsoleCursorInfo(
130                 **self.screen_buffer.handle(),
131                 &cursor_info,
132             )) {
133                 Err(io::Error::last_os_error())?;
134             }
135         }
136         Ok(())
137     }
138 
restore_position(&self) -> Result<()>139     fn restore_position(&self) -> Result<()> {
140         if let Some((x, y)) = *SAVED_CURSOR_POS.lock().unwrap() {
141             self.move_to(x, y)?;
142         }
143 
144         Ok(())
145     }
146 
save_position(&self) -> Result<()>147     fn save_position(&self) -> Result<()> {
148         let position = self.position()?;
149 
150         let mut locked_pos = SAVED_CURSOR_POS.lock().unwrap();
151         *locked_pos = Some((position.x, position.y));
152 
153         Ok(())
154     }
155 }
156 
157 impl From<Handle> for ScreenBufferCursor {
from(handle: Handle) -> Self158     fn from(handle: Handle) -> Self {
159         ScreenBufferCursor {
160             screen_buffer: ScreenBuffer::from(handle),
161         }
162     }
163 }
164 
165 #[cfg(test)]
166 mod tests {
167     use super::{
168         move_down, move_left, move_right, move_to, move_up, position, restore_position,
169         save_position,
170     };
171 
172     #[test]
test_move_to_winapi()173     fn test_move_to_winapi() {
174         let (saved_x, saved_y) = position().unwrap();
175 
176         move_to(saved_x + 1, saved_y + 1).unwrap();
177         assert_eq!(position().unwrap(), (saved_x + 1, saved_y + 1));
178 
179         move_to(saved_x, saved_y).unwrap();
180         assert_eq!(position().unwrap(), (saved_x, saved_y));
181     }
182 
183     #[test]
test_move_right_winapi()184     fn test_move_right_winapi() {
185         let (saved_x, saved_y) = position().unwrap();
186         move_right(1).unwrap();
187         assert_eq!(position().unwrap(), (saved_x + 1, saved_y));
188     }
189 
190     #[test]
test_move_left_winapi()191     fn test_move_left_winapi() {
192         move_to(2, 0).unwrap();
193 
194         move_left(2).unwrap();
195 
196         assert_eq!(position().unwrap(), (0, 0));
197     }
198 
199     #[test]
test_move_up_winapi()200     fn test_move_up_winapi() {
201         move_to(0, 2).unwrap();
202 
203         move_up(2).unwrap();
204 
205         assert_eq!(position().unwrap(), (0, 0));
206     }
207 
208     #[test]
test_move_down_winapi()209     fn test_move_down_winapi() {
210         move_to(0, 0).unwrap();
211 
212         move_down(2).unwrap();
213 
214         assert_eq!(position().unwrap(), (0, 2));
215     }
216 
217     #[test]
test_save_restore_position_winapi()218     fn test_save_restore_position_winapi() {
219         let (saved_x, saved_y) = position().unwrap();
220 
221         save_position().unwrap();
222         move_to(saved_x + 1, saved_y + 1).unwrap();
223         restore_position().unwrap();
224 
225         let (x, y) = position().unwrap();
226 
227         assert_eq!(x, saved_x);
228         assert_eq!(y, saved_y);
229     }
230 }
231