Skip to main content

indigo_tui/
terminal.rs

1#![allow(clippy::allow_attributes)]
2#![allow(clippy::uninlined_format_args)]
3#![allow(clippy::wildcard_imports)]
4
5use indigo_term::{KeyboardEnhancementFlags as KEF, *};
6use std::{
7    collections::VecDeque,
8    io::{self, Write},
9};
10
11pub struct Terminal {
12    tty: Tty,
13    reader: Reader,
14    events: VecDeque<Event>,
15}
16
17impl Terminal {
18    pub fn init() -> io::Result<Self> {
19        let mut this = Self {
20            tty: Tty::init()?,
21            reader: Reader::new(),
22            events: VecDeque::new(),
23        };
24
25        this.tty.enable_raw_mode()?;
26
27        if !this.enable_mode(IN_BAND_RESIZE_MODE)? {
28            drop(this);
29            panic!("terminal does not support in-band resize");
30        }
31        write!(this.tty, "{}", IN_BAND_RESIZE_SET)?;
32        this.tty.flush()?;
33
34        if !this.query_mode(SYNC_UPDATE_MODE)?.is_configurable() {
35            drop(this);
36            panic!("terminal does not support synchronized update");
37        }
38
39        if this.query_keyboard_flags()?.is_none() {
40            drop(this);
41            panic!("terminal does not support kitty keyboard protocol");
42        }
43        write!(
44            this.tty,
45            "{}",
46            // NOTE: All these flags must be enabled to get full keyboard functionality, since the
47            // raw/legacy keys are intentionally not supported.
48            KeyboardEnhancementFlagsPush(
49                KEF::DISAMBIGUATE | KEF::REPORT_EVENTS | KEF::REPORT_ALL_KEYS
50            )
51        )?;
52
53        Ok(this)
54    }
55
56    pub fn query_mode(&mut self, mode: u16) -> io::Result<ModeSetting> {
57        write!(self.tty, "{}{}", ModeQuery(mode), DA1_QUERY)?;
58        self.tty.flush()?;
59
60        let mut setting = ModeSetting::NotRecognized;
61
62        loop {
63            // We record the setting from the reply, but continue reading events until a DA1 event
64            // is consumed. Leaving it in the event stream would confuse future queries.
65            match self.reader.read_event(&mut self.tty)? {
66                Event::Decrpm {
67                    mode: event_mode,
68                    setting: event_setting,
69                } if mode == event_mode => setting = event_setting,
70                Event::Da1 => return Ok(setting),
71                event => self.events.push_back(event),
72            }
73        }
74    }
75
76    pub fn enable_mode(&mut self, mode: u16) -> io::Result<bool> {
77        #[allow(clippy::enum_glob_use)]
78        use ModeSetting::*;
79
80        match self.query_mode(mode)? {
81            NotRecognized | PermanentlyReset => Ok(false),
82            Set | PermanentlySet => Ok(true),
83            Reset => {
84                write!(self.tty, "{}", ModeSet(mode))?;
85                self.tty.flush()?;
86                Ok(true)
87            }
88        }
89    }
90
91    pub fn disable_mode(&mut self, mode: u16) -> io::Result<bool> {
92        #[allow(clippy::enum_glob_use)]
93        use ModeSetting::*;
94
95        match self.query_mode(mode)? {
96            NotRecognized | Reset | PermanentlyReset => Ok(true),
97            PermanentlySet => Ok(false),
98            Set => {
99                write!(self.tty, "{}", ModeReset(mode))?;
100                self.tty.flush()?;
101                Ok(true)
102            }
103        }
104    }
105
106    pub fn query_keyboard_flags(&mut self) -> io::Result<Option<KeyboardEnhancementFlags>> {
107        write!(
108            self.tty,
109            "{}{}",
110            KEYBOARD_ENHANCEMENT_FLAGS_QUERY, DA1_QUERY
111        )?;
112        self.tty.flush()?;
113
114        let mut flags = None;
115
116        loop {
117            // We record the setting from the reply, but continue reading events until a DA1 event
118            // is consumed. Leaving it in the event stream would confuse future queries.
119            match self.reader.read_event(&mut self.tty)? {
120                Event::KeyboardEnhancementFlags(event_flags) => flags = Some(event_flags),
121                Event::Da1 => return Ok(flags),
122                event => self.events.push_back(event),
123            }
124        }
125    }
126
127    pub fn push_keyboard_flags(&mut self, flags: KeyboardEnhancementFlags) -> io::Result<()> {
128        write!(self.tty, "{}", KeyboardEnhancementFlagsPush(flags))?;
129        self.tty.flush()?;
130        Ok(())
131    }
132
133    pub fn pop_keyboard_flags(&mut self) -> io::Result<()> {
134        write!(self.tty, "{}", KeyboardEnhancementFlagsPop)?;
135        self.tty.flush()?;
136        Ok(())
137    }
138
139    pub fn begin_sync_update(&mut self) -> io::Result<()> {
140        write!(self.tty, "{}", SYNC_UPDATE_SET)?;
141        self.tty.flush()?;
142        Ok(())
143    }
144
145    pub fn end_sync_update(&mut self) -> io::Result<()> {
146        write!(self.tty, "{}", SYNC_UPDATE_RESET)?;
147        self.tty.flush()?;
148        Ok(())
149    }
150
151    pub fn size(&self) -> io::Result<(u16, u16)> {
152        self.tty.size()
153    }
154
155    pub fn read_event(&mut self) -> io::Result<Event> {
156        if let Some(event) = self.events.pop_front() {
157            Ok(event)
158        } else {
159            self.reader.read_event(&mut self.tty)
160        }
161    }
162}
163
164impl Write for Terminal {
165    #[inline]
166    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
167        self.tty.write(buf)
168    }
169
170    #[inline]
171    fn flush(&mut self) -> io::Result<()> {
172        self.tty.flush()
173    }
174}
175
176impl Drop for Terminal {
177    fn drop(&mut self) {
178        let _ = write!(self.tty, "{}", SYNC_UPDATE_RESET);
179        let _ = write!(self.tty, "{}", IN_BAND_RESIZE_RESET);
180        let _ = write!(self.tty, "{}", KeyboardEnhancementFlagsPop);
181        let _ = self.tty.flush();
182    }
183}