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