1use alloc::boxed::Box;
2use alloc::string::String;
3use core::mem::swap;
4use core::ops::Range;
5use core::sync::atomic::Ordering;
6use core::time::Duration;
7use core::{cmp::min, fmt};
8
9use base64ct::{Base64, Encoding};
10use pc_keyboard::{DecodedKey, KeyCode};
11use vte::ansi::{Attr, NamedMode, Rgb};
12use vte::ansi::{CharsetIndex, StandardCharset, TabulationClearMode};
13use vte::ansi::{ClearMode, CursorShape, Processor, Timeout};
14use vte::ansi::{CursorStyle, Hyperlink, KeyboardModes};
15use vte::ansi::{Handler, LineClearMode, Mode, NamedPrivateMode, PrivateMode};
16
17use crate::buffer::TerminalBuffer;
18use crate::cell::{Cell, Flags};
19use crate::color::ColorScheme;
20use crate::config::{CONFIG, Clipboard, PtyWriter};
21use crate::font::FontManager;
22use crate::graphic::{DrawTarget, Graphic};
23use crate::keyboard::{KeyboardEvent, KeyboardManager};
24use crate::mouse::{MouseEvent, MouseInput, MouseManager};
25use crate::palette::Palette;
26
27#[derive(Default)]
28pub struct DummySyncHandler;
29
30#[rustfmt::skip]
31impl Timeout for DummySyncHandler {
32 fn set_timeout(&mut self, _: Duration) {}
33 fn clear_timeout(&mut self) {}
34 fn pending_timeout(&self) -> bool { false }
35}
36
37bitflags::bitflags! {
38 pub struct TerminalMode: u32 {
39 const SHOW_CURSOR = 1 << 0;
40 const APP_CURSOR = 1 << 1;
41 const APP_KEYPAD = 1 << 2;
42 const MOUSE_REPORT_CLICK = 1 << 3;
43 const BRACKETED_PASTE = 1 << 4;
44 const SGR_MOUSE = 1 << 5;
45 const MOUSE_MOTION = 1 << 6;
46 const LINE_WRAP = 1 << 7;
47 const LINE_FEED_NEW_LINE = 1 << 8;
48 const ORIGIN = 1 << 9;
49 const INSERT = 1 << 10;
50 const FOCUS_IN_OUT = 1 << 11;
51 const ALT_SCREEN = 1 << 12;
52 const MOUSE_DRAG = 1 << 13;
53 const MOUSE_MODE = 1 << 14;
54 const UTF8_MOUSE = 1 << 15;
55 const ALTERNATE_SCROLL = 1 << 16;
56 const VI = 1 << 17;
57 const URGENCY_HINTS = 1 << 18;
58 const ANY = u32::MAX;
59 }
60}
61
62impl Default for TerminalMode {
63 fn default() -> TerminalMode {
64 TerminalMode::SHOW_CURSOR | TerminalMode::LINE_WRAP
65 }
66}
67
68#[derive(Debug, Default, Clone, Copy)]
69struct Cursor {
70 row: usize,
71 column: usize,
72 shape: CursorShape,
73}
74
75pub struct Terminal<D: DrawTarget> {
76 performer: Processor<DummySyncHandler>,
77 inner: TerminalInner<D>,
78}
79
80pub struct TerminalInner<D: DrawTarget> {
81 cursor: Cursor,
82 saved_cursor: Cursor,
83 alt_cursor: Cursor,
84 mode: TerminalMode,
85 attribute_template: Cell,
86 buffer: TerminalBuffer<D>,
87 keyboard: KeyboardManager,
88 mouse: MouseManager,
89 scroll_region: Range<usize>,
90 charsets: [StandardCharset; 4],
91 active_charset: CharsetIndex,
92}
93
94impl<D: DrawTarget> Terminal<D> {
95 pub fn new(display: D) -> Self {
96 let mut graphic = Graphic::new(display);
97 graphic.clear(Cell::default());
98
99 Self {
100 performer: Processor::new(),
101 inner: TerminalInner {
102 cursor: Cursor::default(),
103 saved_cursor: Cursor::default(),
104 alt_cursor: Cursor::default(),
105 mode: TerminalMode::default(),
106 attribute_template: Cell::default(),
107 buffer: TerminalBuffer::new(graphic),
108 keyboard: KeyboardManager::default(),
109 mouse: MouseManager::default(),
110 scroll_region: Default::default(),
111 charsets: Default::default(),
112 active_charset: Default::default(),
113 },
114 }
115 }
116
117 pub fn rows(&self) -> usize {
118 self.inner.buffer.height()
119 }
120
121 pub fn columns(&self) -> usize {
122 self.inner.buffer.width()
123 }
124
125 pub fn flush(&mut self) {
126 self.inner.buffer.flush();
127 }
128
129 pub fn process(&mut self, bstr: &[u8]) {
130 self.inner.cursor_handler(false);
131 self.performer.advance(&mut self.inner, bstr);
132 if self.inner.mode.contains(TerminalMode::SHOW_CURSOR) {
133 self.inner.cursor_handler(true);
134 }
135 if CONFIG.auto_flush.load(Ordering::Relaxed) {
136 self.flush();
137 }
138 }
139}
140
141impl<D: DrawTarget> Terminal<D> {
142 pub fn handle_keyboard(&mut self, scancode: u8) {
143 match self.inner.keyboard.handle_keyboard(scancode) {
144 KeyboardEvent::SetColorScheme(index) => {
145 self.set_color_scheme(index);
146 }
147 KeyboardEvent::Scroll { up, page } => {
148 let lines = if page { self.rows() } else { 1 } as isize;
149 self.inner.scroll_history(if up { -lines } else { lines });
150 }
151 KeyboardEvent::AnsiString(s) => {
152 self.inner.buffer.ensure_latest();
153 CONFIG.pty_write(s)
154 }
155 KeyboardEvent::Paste => {
156 if let Some(clipboard) = CONFIG.clipboard.lock().as_mut() {
157 let Some(text) = clipboard.get_text() else {
158 return;
159 };
160
161 if self.inner.mode.contains(TerminalMode::BRACKETED_PASTE) {
162 CONFIG.pty_write(format!("\x1b[200~{text}\x1b[201~"));
163 } else {
164 CONFIG.pty_write(text);
165 }
166 }
167 }
168 _ => {}
169 }
170 }
171
172 pub fn handle_mouse(&mut self, input: MouseInput) {
173 if let MouseEvent::Scroll(lines) = self.inner.mouse.handle_mouse(input) {
174 if self.inner.mode.contains(TerminalMode::ALT_SCREEN) {
175 let key = DecodedKey::RawKey(if lines > 0 {
176 KeyCode::ArrowUp
177 } else {
178 KeyCode::ArrowDown
179 });
180
181 let e = self.inner.keyboard.key_to_event(key);
182 if let KeyboardEvent::AnsiString(s) = e {
183 (0..lines.unsigned_abs()).for_each(|_| CONFIG.pty_write(s.clone()));
184 }
185 } else {
186 self.inner.scroll_history(lines);
187 }
188 }
189 }
190}
191
192impl<D: DrawTarget> Terminal<D> {
193 pub fn set_auto_flush(&mut self, auto_flush: bool) {
194 CONFIG.auto_flush.store(auto_flush, Ordering::Relaxed);
195 }
196
197 pub fn set_logger(&mut self, logger: fn(fmt::Arguments)) {
198 *CONFIG.logger.lock() = Some(logger);
199 }
200
201 pub fn set_bell_handler(&mut self, handler: fn()) {
202 *CONFIG.bell_handler.lock() = Some(handler);
203 }
204
205 pub fn set_clipboard(&mut self, clipboard: Clipboard) {
206 *CONFIG.clipboard.lock() = Some(clipboard);
207 }
208
209 pub fn set_pty_writer(&mut self, writer: PtyWriter) {
210 *CONFIG.pty_writer.lock() = Some(writer);
211 }
212
213 pub fn set_history_size(&mut self, size: usize) {
214 self.inner.buffer.resize_history(size);
215 }
216
217 pub fn set_scroll_speed(&mut self, speed: usize) {
218 self.inner.mouse.set_scroll_speed(speed);
219 }
220
221 pub fn set_auto_crnl(&mut self, auto_crnl: bool) {
222 CONFIG.auto_crnl.store(auto_crnl, Ordering::Relaxed);
223 }
224
225 pub fn set_font_manager(&mut self, font_manager: Box<dyn FontManager>) {
226 let (font_width, font_height) = font_manager.size();
227 self.inner.buffer.update_size(font_width, font_height);
228 self.inner.scroll_region = 0..self.inner.buffer.height() - 1;
229 self.inner.reset_state();
230 *CONFIG.font_manager.lock() = Some(font_manager);
231 }
232
233 pub fn set_color_scheme(&mut self, palette_index: usize) {
234 *CONFIG.color_scheme.lock() = ColorScheme::new(palette_index);
235 self.inner.attribute_template = Cell::default();
236 self.inner.buffer.full_flush();
237 }
238
239 pub fn set_custom_color_scheme(&mut self, palette: &Palette) {
240 *CONFIG.color_scheme.lock() = ColorScheme::from(palette);
241 self.inner.attribute_template = Cell::default();
242 self.inner.buffer.full_flush();
243 }
244}
245
246impl<D: DrawTarget> fmt::Write for Terminal<D> {
247 fn write_str(&mut self, s: &str) -> fmt::Result {
248 self.process(s.as_bytes());
249 Ok(())
250 }
251}
252
253impl<D: DrawTarget> TerminalInner<D> {
254 fn cursor_handler(&mut self, enable: bool) {
255 let row = self.cursor.row % self.buffer.height();
256 let column = self.cursor.column % self.buffer.width();
257
258 let mut origin_cell = self.buffer.read(row, column);
259
260 let flag = match self.cursor.shape {
261 CursorShape::Block => Flags::CURSOR_BLOCK,
262 CursorShape::Underline => Flags::CURSOR_UNDERLINE,
263 CursorShape::Beam => Flags::CURSOR_BEAM,
264 CursorShape::HollowBlock => Flags::CURSOR_BLOCK,
265 CursorShape::Hidden => Flags::HIDDEN,
266 };
267
268 if enable {
269 origin_cell.flags.insert(flag);
270 } else {
271 origin_cell.flags.remove(flag);
272 }
273
274 self.buffer.write(row, column, origin_cell);
275 }
276
277 fn scroll_history(&mut self, count: isize) {
278 self.buffer.scroll_history(count);
279 if CONFIG.auto_flush.load(Ordering::Relaxed) {
280 self.buffer.flush();
281 }
282 }
283
284 fn swap_alt_screen(&mut self) {
285 self.mode ^= TerminalMode::ALT_SCREEN;
286 swap(&mut self.cursor, &mut self.alt_cursor);
287 self.buffer.swap_alt_screen(self.attribute_template);
288
289 if !self.mode.contains(TerminalMode::ALT_SCREEN) {
290 self.saved_cursor = self.cursor;
291 self.attribute_template = Cell::default();
292 }
293 }
294}
295
296impl<D: DrawTarget> Handler for TerminalInner<D> {
297 fn set_title(&mut self, title: Option<String>) {
298 log!("Unhandled set_title: {:?}", title);
299 }
300
301 fn set_cursor_style(&mut self, style: Option<CursorStyle>) {
302 log!("Set cursor style: {:?}", style);
303 if let Some(style) = style {
304 self.set_cursor_shape(style.shape);
305 }
306 }
307
308 fn set_cursor_shape(&mut self, shape: CursorShape) {
309 log!("Set cursor shape: {:?}", shape);
310 self.cursor.shape = shape;
311 }
312
313 fn input(&mut self, content: char) {
314 let index = self.active_charset as usize;
315 let template = self
316 .attribute_template
317 .set_content(self.charsets[index].map(content));
318
319 let width = if template.wide { 2 } else { 1 };
320 if self.cursor.column + width > self.buffer.width() {
321 if !self.mode.contains(TerminalMode::LINE_WRAP) {
322 return;
323 }
324 self.linefeed();
325 self.carriage_return();
326 }
327
328 self.buffer
329 .write(self.cursor.row, self.cursor.column, template);
330 self.cursor.column += 1;
331
332 if template.wide {
333 self.buffer.write(
334 self.cursor.row,
335 self.cursor.column,
336 template.set_placeholder(),
337 );
338 self.cursor.column += 1;
339 }
340 }
341
342 fn goto(&mut self, row: i32, col: usize) {
343 self.cursor.row = min(row as usize, self.buffer.height());
344 self.cursor.column = min(col, self.buffer.width());
345 }
346
347 fn goto_line(&mut self, row: i32) {
348 log!("Goto line: {}", row);
349 self.goto(row, self.cursor.column);
350 }
351
352 fn goto_col(&mut self, col: usize) {
353 log!("Goto column: {}", col);
354 self.goto(self.cursor.row as i32, col);
355 }
356
357 fn insert_blank(&mut self, count: usize) {
358 log!("Insert blank: {}", count);
359 let (row, columns) = (self.cursor.row, self.buffer.width());
360 let count = min(count, columns - self.cursor.column);
361
362 let template = self.attribute_template.clear();
363 for column in (self.cursor.column..columns - count).rev() {
364 self.buffer
365 .write(row, column + count, self.buffer.read(row, column));
366 self.buffer.write(row, column, template);
367 }
368 }
369
370 fn move_up(&mut self, rows: usize) {
371 log!("Move up: {}", rows);
372 self.goto(
373 self.cursor.row.saturating_sub(rows) as i32,
374 self.cursor.column,
375 );
376 }
377
378 fn move_down(&mut self, rows: usize) {
379 log!("Move down: {}", rows);
380 let goto_line = min(self.cursor.row + rows, self.buffer.height() - 1) as i32;
381 self.goto(goto_line, self.cursor.column);
382 }
383
384 fn identify_terminal(&mut self, intermediate: Option<char>) {
385 log!("Identify terminal: {:?}", intermediate);
386
387 let version_number = |version: &str| -> usize {
388 let mut result = 0;
389 let semver_versions = version.split('.');
390 for (i, part) in semver_versions.rev().enumerate() {
391 let semver_number = part.parse::<usize>().unwrap_or(0);
392 result += usize::pow(100, i as u32) * semver_number;
393 }
394 result
395 };
396
397 match intermediate {
398 None => CONFIG.pty_write(String::from("\x1b[?6c")),
399 Some('>') => {
400 let version = version_number(env!("CARGO_PKG_VERSION"));
401 CONFIG.pty_write(format!("\x1b[>0;{version};1c"));
402 }
403 _ => log!("Unsupported device attributes intermediate"),
404 }
405 }
406
407 fn device_status(&mut self, arg: usize) {
408 match arg {
409 5 => CONFIG.pty_write(String::from("\x1b[0n")),
410 6 => {
411 let (row, column) = (self.cursor.row, self.cursor.column);
412 CONFIG.pty_write(format!("\x1b[{};{}R", row + 1, column + 1));
413 }
414 _ => log!("Unknown device status query: {}", arg),
415 };
416 }
417
418 fn move_forward(&mut self, cols: usize) {
419 log!("Move forward: {}", cols);
420 self.cursor.column = min(self.cursor.column + cols, self.buffer.width() - 1);
421 }
422
423 fn move_backward(&mut self, cols: usize) {
424 log!("Move backward: {}", cols);
425 self.cursor.column = self.cursor.column.saturating_sub(cols);
426 }
427
428 fn move_up_and_cr(&mut self, rows: usize) {
429 log!("Move up and cr: {}", rows);
430 self.goto(self.cursor.row.saturating_sub(rows) as i32, 0);
431 }
432
433 fn move_down_and_cr(&mut self, rows: usize) {
434 log!("Move down and cr: {}", rows);
435 let goto_line = min(self.cursor.row + rows, self.buffer.height() - 1);
436 self.goto(goto_line as i32, 0);
437 }
438
439 fn put_tab(&mut self, count: u16) {
440 log!("Put tab: {}", count);
441 for _ in 0..count {
442 let tab_stop = self.cursor.column.div_ceil(8) * 8;
443 let end_column = tab_stop.min(self.buffer.width());
444 let template = self.attribute_template.clear();
445
446 while self.cursor.column < end_column {
447 self.buffer
448 .write(self.cursor.row, self.cursor.column, template);
449 self.cursor.column += 1;
450 }
451 }
452 }
453
454 fn backspace(&mut self) {
455 self.cursor.column = self.cursor.column.saturating_sub(1);
456 }
457
458 fn carriage_return(&mut self) {
459 self.cursor.column = 0;
460 }
461
462 fn linefeed(&mut self) {
463 if CONFIG.auto_crnl.load(Ordering::Relaxed) {
464 self.carriage_return();
465 }
466
467 if self.cursor.row == self.scroll_region.end {
468 self.scroll_up(1);
469 } else if self.cursor.row < self.buffer.height() - 1 {
470 self.cursor.row += 1;
471 }
472 }
473
474 fn bell(&mut self) {
475 log!("Bell triggered!");
476 CONFIG.bell_handler.lock().map(|handler| handler());
477 }
478
479 fn substitute(&mut self) {
480 log!("Unhandled substitute!");
481 }
482
483 fn newline(&mut self) {
484 self.linefeed();
485
486 if self.mode.contains(TerminalMode::LINE_FEED_NEW_LINE) {
487 self.carriage_return();
488 }
489 }
490
491 fn set_horizontal_tabstop(&mut self) {
492 log!("Unhandled set horizontal tabstop!");
493 }
494
495 fn scroll_up(&mut self, count: usize) {
496 self.buffer.scroll_region(
497 -(count as isize),
498 self.attribute_template,
499 self.scroll_region.clone(),
500 );
501 }
502
503 fn scroll_down(&mut self, count: usize) {
504 self.buffer.scroll_region(
505 count as isize,
506 self.attribute_template,
507 self.scroll_region.clone(),
508 );
509 }
510
511 fn insert_blank_lines(&mut self, count: usize) {
512 log!("Insert blank lines: {}", count);
513 self.scroll_down(count);
514 }
515
516 fn delete_lines(&mut self, count: usize) {
517 log!("Delete lines: {}", count);
518 self.scroll_up(count);
519 }
520
521 fn erase_chars(&mut self, count: usize) {
522 log!("Erase chars: {}", count);
523 let start = self.cursor.column;
524 let end = min(start + count, self.buffer.width());
525
526 let template = self.attribute_template.clear();
527 for column in start..end {
528 self.buffer.write(self.cursor.row, column, template);
529 }
530 }
531
532 fn delete_chars(&mut self, count: usize) {
533 log!("Delete chars: {}", count);
534 let (row, width) = (self.cursor.row, self.buffer.width());
535 let count = min(count, width - self.cursor.column - 1);
536
537 for i in self.cursor.column..width - count {
538 self.buffer.write(row, i, self.buffer.read(row, i + count));
539 }
540
541 for i in width - count..width {
542 self.buffer.write(row, i, self.attribute_template.clear());
543 }
544 }
545
546 fn move_backward_tabs(&mut self, count: u16) {
547 log!("Unhandled move backward tabs: {}", count);
548 }
549
550 fn move_forward_tabs(&mut self, count: u16) {
551 log!("Unhandled move forward tabs: {}", count);
552 }
553
554 fn save_cursor_position(&mut self) {
555 log!("Save cursor position");
556 self.saved_cursor = self.cursor;
557 }
558
559 fn restore_cursor_position(&mut self) {
560 log!("Restore cursor position");
561 self.cursor = self.saved_cursor;
562 }
563
564 fn clear_line(&mut self, mode: LineClearMode) {
565 log!("Clear line: {:?}", mode);
566 let template = self.attribute_template.clear();
567 match mode {
568 LineClearMode::Right => {
569 for column in self.cursor.column..self.buffer.width() {
570 self.buffer.write(self.cursor.row, column, template);
571 }
572 }
573 LineClearMode::Left => {
574 for column in 0..=self.cursor.column {
575 self.buffer.write(self.cursor.row, column, template);
576 }
577 }
578 LineClearMode::All => {
579 for column in 0..self.buffer.width() {
580 self.buffer.write(self.cursor.row, column, template);
581 }
582 }
583 }
584 }
585
586 fn clear_screen(&mut self, mode: ClearMode) {
587 log!("Clear screen: {:?}", mode);
588 let template = self.attribute_template.clear();
589
590 match mode {
591 ClearMode::All | ClearMode::Saved => {
592 self.buffer.clear(template);
593 self.cursor = Cursor::default();
594 if matches!(mode, ClearMode::Saved) {
595 self.buffer.clear_history();
596 }
597 }
598 ClearMode::Above => {
599 for row in 0..self.cursor.row {
600 for column in 0..self.buffer.width() {
601 self.buffer.write(row, column, template);
602 }
603 }
604 for column in 0..=self.cursor.column {
605 self.buffer.write(self.cursor.row, column, template);
606 }
607 }
608 ClearMode::Below => {
609 for column in self.cursor.column..self.buffer.width() {
610 self.buffer.write(self.cursor.row, column, template);
611 }
612 for row in self.cursor.row + 1..self.buffer.height() {
613 for column in 0..self.buffer.width() {
614 self.buffer.write(row, column, template);
615 }
616 }
617 }
618 }
619 }
620
621 fn clear_tabs(&mut self, mode: TabulationClearMode) {
622 log!("Unhandled clear tabs: {:?}", mode);
623 }
624
625 fn reset_state(&mut self) {
626 log!("Reset state");
627 if self.mode.contains(TerminalMode::ALT_SCREEN) {
628 self.swap_alt_screen();
629 }
630 self.buffer.clear(Cell::default());
631 self.cursor = Cursor::default();
632 self.saved_cursor = self.cursor;
633 self.buffer.clear_history();
634 self.mode = TerminalMode::default();
635 self.attribute_template = Cell::default();
636 }
637
638 fn reverse_index(&mut self) {
639 log!("Reverse index");
640 if self.cursor.row == self.scroll_region.start {
641 self.scroll_down(1);
642 } else {
643 self.cursor.row -= 1;
644 }
645 }
646
647 fn terminal_attribute(&mut self, attr: Attr) {
648 match attr {
649 Attr::Foreground(color) => self.attribute_template.foreground = color,
650 Attr::Background(color) => self.attribute_template.background = color,
651 Attr::Reset => self.attribute_template = Cell::default(),
652 Attr::Reverse => self.attribute_template.flags |= Flags::INVERSE,
653 Attr::CancelReverse => self.attribute_template.flags.remove(Flags::INVERSE),
654 Attr::Bold => self.attribute_template.flags.insert(Flags::BOLD),
655 Attr::CancelBold => self.attribute_template.flags.remove(Flags::BOLD),
656 Attr::CancelBoldDim => self.attribute_template.flags.remove(Flags::BOLD),
657 Attr::Italic => self.attribute_template.flags.insert(Flags::ITALIC),
658 Attr::CancelItalic => self.attribute_template.flags.remove(Flags::ITALIC),
659 Attr::Underline => self.attribute_template.flags.insert(Flags::UNDERLINE),
660 Attr::CancelUnderline => self.attribute_template.flags.remove(Flags::UNDERLINE),
661 Attr::Hidden => self.attribute_template.flags.insert(Flags::HIDDEN),
662 Attr::CancelHidden => self.attribute_template.flags.remove(Flags::HIDDEN),
663 _ => log!("Unhandled terminal attribute: {:?}", attr),
664 }
665 }
666
667 fn set_mode(&mut self, mode: Mode) {
668 let mode = match mode {
669 Mode::Named(mode) => mode,
670 Mode::Unknown(mode) => {
671 log!("Ignoring unknown mode {} in set_mode", mode);
672 return;
673 }
674 };
675
676 match mode {
677 NamedMode::Insert => self.mode.insert(TerminalMode::INSERT),
678 NamedMode::LineFeedNewLine => self.mode.insert(TerminalMode::LINE_FEED_NEW_LINE),
679 }
680 }
681
682 fn unset_mode(&mut self, mode: Mode) {
683 let mode = match mode {
684 Mode::Named(mode) => mode,
685 Mode::Unknown(mode) => {
686 log!("Ignoring unknown mode {} in unset_mode", mode);
687 return;
688 }
689 };
690
691 match mode {
692 NamedMode::Insert => self.mode.remove(TerminalMode::INSERT),
693 NamedMode::LineFeedNewLine => self.mode.remove(TerminalMode::LINE_FEED_NEW_LINE),
694 }
695 }
696
697 fn report_mode(&mut self, mode: Mode) {
698 log!("Unhandled report mode: {:?}", mode);
699 }
700
701 fn set_private_mode(&mut self, mode: PrivateMode) {
702 let mode = match mode {
703 PrivateMode::Named(mode) => mode,
704 PrivateMode::Unknown(mode) => {
705 log!("Ignoring unknown mode {} in set_private_mode", mode);
706 return;
707 }
708 };
709
710 match mode {
711 NamedPrivateMode::SwapScreenAndSetRestoreCursor => {
712 if !self.mode.contains(TerminalMode::ALT_SCREEN) {
713 self.swap_alt_screen();
714 }
715 }
716 NamedPrivateMode::ShowCursor => self.mode.insert(TerminalMode::SHOW_CURSOR),
717 NamedPrivateMode::CursorKeys => {
718 self.mode.insert(TerminalMode::APP_CURSOR);
719 self.keyboard.set_app_cursor(true);
720 }
721 NamedPrivateMode::LineWrap => self.mode.insert(TerminalMode::LINE_WRAP),
722 NamedPrivateMode::BracketedPaste => self.mode.insert(TerminalMode::BRACKETED_PASTE),
723 _ => log!("Unhandled set mode: {:?}", mode),
724 }
725 }
726
727 fn unset_private_mode(&mut self, mode: PrivateMode) {
728 let mode = match mode {
729 PrivateMode::Named(mode) => mode,
730 PrivateMode::Unknown(mode) => {
731 log!("Ignoring unknown mode {} in unset_private_mode", mode);
732 return;
733 }
734 };
735
736 match mode {
737 NamedPrivateMode::SwapScreenAndSetRestoreCursor => {
738 if self.mode.contains(TerminalMode::ALT_SCREEN) {
739 self.swap_alt_screen();
740 }
741 }
742 NamedPrivateMode::ShowCursor => self.mode.remove(TerminalMode::SHOW_CURSOR),
743 NamedPrivateMode::CursorKeys => {
744 self.mode.remove(TerminalMode::APP_CURSOR);
745 self.keyboard.set_app_cursor(false);
746 }
747 NamedPrivateMode::LineWrap => self.mode.remove(TerminalMode::LINE_WRAP),
748 NamedPrivateMode::BracketedPaste => self.mode.remove(TerminalMode::BRACKETED_PASTE),
749 _ => log!("Unhandled unset mode: {:?}", mode),
750 }
751 }
752
753 fn report_private_mode(&mut self, mode: PrivateMode) {
754 log!("Unhandled report private mode: {:?}", mode);
755 }
756
757 fn set_scrolling_region(&mut self, top: usize, bottom: Option<usize>) {
758 log!("Set scrolling region: top={}, bottom={:?}", top, bottom);
759 let bottom = bottom.unwrap_or(self.buffer.height());
760
761 if top >= bottom {
762 log!("Invalid scrolling region: ({};{})", top, bottom);
763 return;
764 }
765
766 self.scroll_region.start = min(top, self.buffer.height()) - 1;
767 self.scroll_region.end = min(bottom, self.buffer.height()) - 1;
768 self.goto(0, 0);
769 }
770
771 fn set_keypad_application_mode(&mut self) {
772 log!("Set keypad application mode");
773 self.mode.insert(TerminalMode::APP_KEYPAD);
774 }
775
776 fn unset_keypad_application_mode(&mut self) {
777 log!("Unset keypad application mode");
778 self.mode.remove(TerminalMode::APP_KEYPAD);
779 }
780
781 fn set_active_charset(&mut self, index: CharsetIndex) {
782 log!("Set active charset: {:?}", index);
783 self.active_charset = index;
784 }
785
786 fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) {
787 log!("Configure charset: {:?}, {:?}", index, charset);
788 self.charsets[index as usize] = charset;
789 }
790
791 fn set_color(&mut self, index: usize, color: Rgb) {
792 log!("Unhandled set color: {}, {:?}", index, color);
793 }
794
795 fn dynamic_color_sequence(&mut self, prefix: String, index: usize, terminator: &str) {
796 log!(
797 "Unhandled dynamic color sequence: {}, {}, {}",
798 prefix,
799 index,
800 terminator
801 );
802 }
803
804 fn reset_color(&mut self, index: usize) {
805 log!("Unhandled reset color: {}", index);
806 }
807
808 fn clipboard_store(&mut self, clipboard: u8, base64: &[u8]) {
809 log!("Clipboard store: {}, {:?}", clipboard, base64);
810
811 let text = str::from_utf8(base64)
812 .ok()
813 .and_then(|b64| Base64::decode_vec(b64).ok())
814 .and_then(|bytes| String::from_utf8(bytes).ok());
815
816 if let Some(text) = text {
817 if let Some(handler) = CONFIG.clipboard.lock().as_mut() {
818 handler.set_text(text);
819 }
820 }
821 }
822
823 fn clipboard_load(&mut self, clipboard: u8, terminator: &str) {
824 log!("Clipboard load: {}, {}", clipboard, terminator);
825
826 if let Some(handler) = CONFIG.clipboard.lock().as_mut() {
827 let Some(text) = handler.get_text() else {
828 return;
829 };
830
831 let base64 = Base64::encode_string(text.as_bytes());
832 let result = format!("\x1b]52;{};{base64}{terminator}", clipboard as char);
833 CONFIG.pty_write(result);
834 };
835 }
836
837 fn decaln(&mut self) {
838 log!("Unhandled decaln!");
839 }
840
841 fn push_title(&mut self) {
842 log!("Unhandled push title!");
843 }
844
845 fn pop_title(&mut self) {
846 log!("Unhandled pop title!");
847 }
848
849 fn text_area_size_pixels(&mut self) {
850 log!("Unhandled text area size pixels!");
851 }
852
853 fn text_area_size_chars(&mut self) {
854 log!("Unhandled text area size chars!");
855 }
856
857 fn set_hyperlink(&mut self, hyperlink: Option<Hyperlink>) {
858 log!("Unhandled set hyperlink: {:?}", hyperlink);
859 }
860
861 fn report_keyboard_mode(&mut self) {
862 log!("Report keyboard mode!");
863 let current_mode = KeyboardModes::NO_MODE.bits();
864 CONFIG.pty_write(format!("\x1b[?{current_mode}u"));
865 }
866
867 fn push_keyboard_mode(&mut self, mode: KeyboardModes) {
868 log!("Unhandled push keyboard mode: {:?}", mode);
869 }
870
871 fn pop_keyboard_modes(&mut self, to_pop: u16) {
872 log!("Unhandled pop keyboard modes: {}", to_pop);
873 }
874}