os_terminal/
terminal.rs

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}