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