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