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_crnl_mapping(&mut self, mapping: bool) {
222        CONFIG.crnl_mapping.store(mapping, 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.crnl_mapping.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 set_horizontal_tabstop(&mut self) {
484        log!("Unhandled set horizontal tabstop!");
485    }
486
487    fn scroll_up(&mut self, count: usize) {
488        self.buffer.scroll_region(
489            -(count as isize),
490            self.attribute_template,
491            self.scroll_region.clone(),
492        );
493    }
494
495    fn scroll_down(&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 insert_blank_lines(&mut self, count: usize) {
504        log!("Insert blank lines: {}", count);
505        self.scroll_down(count);
506    }
507
508    fn delete_lines(&mut self, count: usize) {
509        log!("Delete lines: {}", count);
510        self.scroll_up(count);
511    }
512
513    fn erase_chars(&mut self, count: usize) {
514        log!("Erase chars: {}", count);
515        let start = self.cursor.column;
516        let end = min(start + count, self.buffer.width());
517
518        let template = self.attribute_template.clear();
519        for column in start..end {
520            self.buffer.write(self.cursor.row, column, template);
521        }
522    }
523
524    fn delete_chars(&mut self, count: usize) {
525        log!("Delete chars: {}", count);
526        let (row, width) = (self.cursor.row, self.buffer.width());
527        let count = min(count, width - self.cursor.column - 1);
528
529        for i in self.cursor.column..width - count {
530            self.buffer.write(row, i, self.buffer.read(row, i + count));
531        }
532
533        for i in width - count..width {
534            self.buffer.write(row, i, self.attribute_template.clear());
535        }
536    }
537
538    fn move_backward_tabs(&mut self, count: u16) {
539        log!("Unhandled move backward tabs: {}", count);
540    }
541
542    fn move_forward_tabs(&mut self, count: u16) {
543        log!("Unhandled move forward tabs: {}", count);
544    }
545
546    fn save_cursor_position(&mut self) {
547        log!("Save cursor position");
548        self.saved_cursor = self.cursor;
549    }
550
551    fn restore_cursor_position(&mut self) {
552        log!("Restore cursor position");
553        self.cursor = self.saved_cursor;
554    }
555
556    fn clear_line(&mut self, mode: LineClearMode) {
557        log!("Clear line: {:?}", mode);
558        let template = self.attribute_template.clear();
559        match mode {
560            LineClearMode::Right => {
561                for column in self.cursor.column..self.buffer.width() {
562                    self.buffer.write(self.cursor.row, column, template);
563                }
564            }
565            LineClearMode::Left => {
566                for column in 0..=self.cursor.column {
567                    self.buffer.write(self.cursor.row, column, template);
568                }
569            }
570            LineClearMode::All => {
571                for column in 0..self.buffer.width() {
572                    self.buffer.write(self.cursor.row, column, template);
573                }
574            }
575        }
576    }
577
578    fn clear_screen(&mut self, mode: ClearMode) {
579        log!("Clear screen: {:?}", mode);
580        let template = self.attribute_template.clear();
581
582        match mode {
583            ClearMode::All | ClearMode::Saved => {
584                self.buffer.clear(template);
585                self.cursor = Cursor::default();
586                if matches!(mode, ClearMode::Saved) {
587                    self.buffer.clear_history();
588                }
589            }
590            ClearMode::Above => {
591                for row in 0..self.cursor.row {
592                    for column in 0..self.buffer.width() {
593                        self.buffer.write(row, column, template);
594                    }
595                }
596                for column in 0..=self.cursor.column {
597                    self.buffer.write(self.cursor.row, column, template);
598                }
599            }
600            ClearMode::Below => {
601                for column in self.cursor.column..self.buffer.width() {
602                    self.buffer.write(self.cursor.row, column, template);
603                }
604                for row in self.cursor.row + 1..self.buffer.height() {
605                    for column in 0..self.buffer.width() {
606                        self.buffer.write(row, column, template);
607                    }
608                }
609            }
610        }
611    }
612
613    fn clear_tabs(&mut self, mode: TabulationClearMode) {
614        log!("Unhandled clear tabs: {:?}", mode);
615    }
616
617    fn reset_state(&mut self) {
618        log!("Reset state");
619        if self.mode.contains(TerminalMode::ALT_SCREEN) {
620            self.swap_alt_screen();
621        }
622        self.buffer.clear(Cell::default());
623        self.cursor = Cursor::default();
624        self.saved_cursor = self.cursor;
625        self.buffer.clear_history();
626        self.mode = TerminalMode::default();
627        self.attribute_template = Cell::default();
628    }
629
630    fn reverse_index(&mut self) {
631        log!("Reverse index");
632        if self.cursor.row == self.scroll_region.start {
633            self.scroll_down(1);
634        } else {
635            self.cursor.row -= 1;
636        }
637    }
638
639    fn terminal_attribute(&mut self, attr: Attr) {
640        match attr {
641            Attr::Foreground(color) => self.attribute_template.foreground = color,
642            Attr::Background(color) => self.attribute_template.background = color,
643            Attr::Reset => self.attribute_template = Cell::default(),
644            Attr::Reverse => self.attribute_template.flags |= Flags::INVERSE,
645            Attr::CancelReverse => self.attribute_template.flags.remove(Flags::INVERSE),
646            Attr::Bold => self.attribute_template.flags.insert(Flags::BOLD),
647            Attr::CancelBold => self.attribute_template.flags.remove(Flags::BOLD),
648            Attr::CancelBoldDim => self.attribute_template.flags.remove(Flags::BOLD),
649            Attr::Italic => self.attribute_template.flags.insert(Flags::ITALIC),
650            Attr::CancelItalic => self.attribute_template.flags.remove(Flags::ITALIC),
651            Attr::Underline => self.attribute_template.flags.insert(Flags::UNDERLINE),
652            Attr::CancelUnderline => self.attribute_template.flags.remove(Flags::UNDERLINE),
653            Attr::Hidden => self.attribute_template.flags.insert(Flags::HIDDEN),
654            Attr::CancelHidden => self.attribute_template.flags.remove(Flags::HIDDEN),
655            _ => log!("Unhandled terminal attribute: {:?}", attr),
656        }
657    }
658
659    fn set_mode(&mut self, mode: Mode) {
660        let mode = match mode {
661            Mode::Named(mode) => mode,
662            Mode::Unknown(mode) => {
663                log!("Ignoring unknown mode {} in set_mode", mode);
664                return;
665            }
666        };
667
668        match mode {
669            NamedMode::Insert => self.mode.insert(TerminalMode::INSERT),
670            NamedMode::LineFeedNewLine => self.mode.insert(TerminalMode::LINE_FEED_NEW_LINE),
671        }
672    }
673
674    fn unset_mode(&mut self, mode: Mode) {
675        let mode = match mode {
676            Mode::Named(mode) => mode,
677            Mode::Unknown(mode) => {
678                log!("Ignoring unknown mode {} in unset_mode", mode);
679                return;
680            }
681        };
682
683        match mode {
684            NamedMode::Insert => self.mode.remove(TerminalMode::INSERT),
685            NamedMode::LineFeedNewLine => self.mode.remove(TerminalMode::LINE_FEED_NEW_LINE),
686        }
687    }
688
689    fn report_mode(&mut self, mode: Mode) {
690        log!("Unhandled report mode: {:?}", mode);
691    }
692
693    fn set_private_mode(&mut self, mode: PrivateMode) {
694        let mode = match mode {
695            PrivateMode::Named(mode) => mode,
696            PrivateMode::Unknown(mode) => {
697                log!("Ignoring unknown mode {} in set_private_mode", mode);
698                return;
699            }
700        };
701
702        match mode {
703            NamedPrivateMode::SwapScreenAndSetRestoreCursor => {
704                if !self.mode.contains(TerminalMode::ALT_SCREEN) {
705                    self.swap_alt_screen();
706                }
707            }
708            NamedPrivateMode::ShowCursor => self.mode.insert(TerminalMode::SHOW_CURSOR),
709            NamedPrivateMode::CursorKeys => {
710                self.mode.insert(TerminalMode::APP_CURSOR);
711                self.keyboard.set_app_cursor(true);
712            }
713            NamedPrivateMode::LineWrap => self.mode.insert(TerminalMode::LINE_WRAP),
714            NamedPrivateMode::BracketedPaste => self.mode.insert(TerminalMode::BRACKETED_PASTE),
715            _ => log!("Unhandled set mode: {:?}", mode),
716        }
717    }
718
719    fn unset_private_mode(&mut self, mode: PrivateMode) {
720        let mode = match mode {
721            PrivateMode::Named(mode) => mode,
722            PrivateMode::Unknown(mode) => {
723                log!("Ignoring unknown mode {} in unset_private_mode", mode);
724                return;
725            }
726        };
727
728        match mode {
729            NamedPrivateMode::SwapScreenAndSetRestoreCursor => {
730                if self.mode.contains(TerminalMode::ALT_SCREEN) {
731                    self.swap_alt_screen();
732                }
733            }
734            NamedPrivateMode::ShowCursor => self.mode.remove(TerminalMode::SHOW_CURSOR),
735            NamedPrivateMode::CursorKeys => {
736                self.mode.remove(TerminalMode::APP_CURSOR);
737                self.keyboard.set_app_cursor(false);
738            }
739            NamedPrivateMode::LineWrap => self.mode.remove(TerminalMode::LINE_WRAP),
740            NamedPrivateMode::BracketedPaste => self.mode.remove(TerminalMode::BRACKETED_PASTE),
741            _ => log!("Unhandled unset mode: {:?}", mode),
742        }
743    }
744
745    fn report_private_mode(&mut self, mode: PrivateMode) {
746        log!("Unhandled report private mode: {:?}", mode);
747    }
748
749    fn set_scrolling_region(&mut self, top: usize, bottom: Option<usize>) {
750        log!("Set scrolling region: top={}, bottom={:?}", top, bottom);
751        let bottom = bottom.unwrap_or(self.buffer.height());
752
753        if top >= bottom {
754            log!("Invalid scrolling region: ({};{})", top, bottom);
755            return;
756        }
757
758        self.scroll_region.start = min(top, self.buffer.height()) - 1;
759        self.scroll_region.end = min(bottom, self.buffer.height()) - 1;
760        self.goto(0, 0);
761    }
762
763    fn set_keypad_application_mode(&mut self) {
764        log!("Set keypad application mode");
765        self.mode.insert(TerminalMode::APP_KEYPAD);
766    }
767
768    fn unset_keypad_application_mode(&mut self) {
769        log!("Unset keypad application mode");
770        self.mode.remove(TerminalMode::APP_KEYPAD);
771    }
772
773    fn set_active_charset(&mut self, index: CharsetIndex) {
774        log!("Set active charset: {:?}", index);
775        self.active_charset = index;
776    }
777
778    fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) {
779        log!("Configure charset: {:?}, {:?}", index, charset);
780        self.charsets[index as usize] = charset;
781    }
782
783    fn set_color(&mut self, index: usize, color: Rgb) {
784        log!("Unhandled set color: {}, {:?}", index, color);
785    }
786
787    fn dynamic_color_sequence(&mut self, prefix: String, index: usize, terminator: &str) {
788        log!(
789            "Unhandled dynamic color sequence: {}, {}, {}",
790            prefix,
791            index,
792            terminator
793        );
794    }
795
796    fn reset_color(&mut self, index: usize) {
797        log!("Unhandled reset color: {}", index);
798    }
799
800    fn clipboard_store(&mut self, clipboard: u8, base64: &[u8]) {
801        log!("Clipboard store: {}, {:?}", clipboard, base64);
802
803        let text = core::str::from_utf8(base64)
804            .ok()
805            .and_then(|b64| Base64::decode_vec(b64).ok())
806            .and_then(|bytes| String::from_utf8(bytes).ok());
807
808        if let Some(text) = text {
809            if let Some(handler) = CONFIG.clipboard.lock().as_mut() {
810                handler.set_text(text);
811            }
812        }
813    }
814
815    fn clipboard_load(&mut self, clipboard: u8, terminator: &str) {
816        log!("Clipboard load: {}, {}", clipboard, terminator);
817
818        if let Some(handler) = CONFIG.clipboard.lock().as_mut() {
819            let Some(text) = handler.get_text() else {
820                return;
821            };
822
823            let base64 = Base64::encode_string(text.as_bytes());
824            let result = format!("\x1b]52;{};{base64}{terminator}", clipboard as char);
825            CONFIG.pty_write(result);
826        };
827    }
828
829    fn decaln(&mut self) {
830        log!("Unhandled decaln!");
831    }
832
833    fn push_title(&mut self) {
834        log!("Unhandled push title!");
835    }
836
837    fn pop_title(&mut self) {
838        log!("Unhandled pop title!");
839    }
840
841    fn text_area_size_pixels(&mut self) {
842        log!("Unhandled text area size pixels!");
843    }
844
845    fn text_area_size_chars(&mut self) {
846        log!("Unhandled text area size chars!");
847    }
848
849    fn set_hyperlink(&mut self, hyperlink: Option<Hyperlink>) {
850        log!("Unhandled set hyperlink: {:?}", hyperlink);
851    }
852
853    fn report_keyboard_mode(&mut self) {
854        log!("Report keyboard mode!");
855        let current_mode = KeyboardModes::NO_MODE.bits();
856        CONFIG.pty_write(format!("\x1b[?{current_mode}u"));
857    }
858
859    fn push_keyboard_mode(&mut self, mode: KeyboardModes) {
860        log!("Unhandled push keyboard mode: {:?}", mode);
861    }
862
863    fn pop_keyboard_modes(&mut self, to_pop: u16) {
864        log!("Unhandled pop keyboard modes: {}", to_pop);
865    }
866}