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