1use std::{
2 cell::RefCell,
3 path::PathBuf,
4 rc::Rc,
5 time::Instant,
6};
7
8use freya_core::{
9 notify::ArcNotify,
10 prelude::{
11 Platform,
12 UserEvent,
13 spawn_forever,
14 },
15};
16use futures_lite::AsyncReadExt;
17use keyboard_types::Modifiers;
18use portable_pty::{
19 CommandBuilder,
20 MasterPty,
21 PtySize,
22 native_pty_system,
23};
24use termwiz::escape::{
25 Action,
26 CSI,
27 OperatingSystemCommand,
28 csi::{
29 Cursor,
30 Device,
31 },
32 parser::Parser as TermwizParser,
33};
34use vt100::Parser;
35
36use crate::{
37 buffer::TerminalBuffer,
38 handle::{
39 TerminalCleaner,
40 TerminalError,
41 TerminalHandle,
42 TerminalId,
43 },
44};
45
46pub(crate) fn query_max_scrollback(parser: &mut Parser) -> usize {
49 let saved = parser.screen().scrollback();
50 parser.screen_mut().set_scrollback(usize::MAX);
51 let max = parser.screen().scrollback();
52 parser.screen_mut().set_scrollback(saved);
53 max
54}
55
56pub(crate) fn extract_buffer(
58 parser: &Parser,
59 scroll_offset: usize,
60 total_scrollback: usize,
61) -> TerminalBuffer {
62 let (rows, cols) = parser.screen().size();
63 let rows_vec: Vec<Vec<vt100::Cell>> = (0..rows)
64 .map(|r| {
65 (0..cols)
66 .filter_map(|c| parser.screen().cell(r, c).cloned())
67 .collect()
68 })
69 .collect();
70 let (cur_r, cur_c) = parser.screen().cursor_position();
71 TerminalBuffer {
72 rows: rows_vec,
73 cursor_row: cur_r as usize,
74 cursor_col: cur_c as usize,
75 cols: cols as usize,
76 rows_count: rows as usize,
77 selection: None,
78 scroll_offset,
79 total_scrollback,
80 }
81}
82
83pub(crate) fn spawn_pty(
85 id: TerminalId,
86 command: CommandBuilder,
87 scrollback_size: usize,
88) -> Result<TerminalHandle, TerminalError> {
89 let (update_tx, mut update_rx) = futures_channel::mpsc::unbounded::<()>();
90
91 let buffer = Rc::new(RefCell::new(TerminalBuffer::default()));
92 let parser = Rc::new(RefCell::new(Parser::new(24, 80, scrollback_size)));
93 let writer = Rc::new(RefCell::new(None::<Box<dyn std::io::Write + Send>>));
94 let closer_notifier = ArcNotify::new();
95 let output_notifier = ArcNotify::new();
96 let title_notifier = ArcNotify::new();
97 let cwd: Rc<RefCell<Option<PathBuf>>> = Rc::new(RefCell::new(None));
98 let title: Rc<RefCell<Option<String>>> = Rc::new(RefCell::new(None));
99
100 let pty_system = native_pty_system();
101 let pair = pty_system
102 .openpty(PtySize::default())
103 .map_err(|_| TerminalError::NotInitialized)?;
104 let master_writer = pair
105 .master
106 .take_writer()
107 .map_err(|_| TerminalError::NotInitialized)?;
108 *writer.borrow_mut() = Some(master_writer);
109
110 pair.slave
111 .spawn_command(command)
112 .map_err(|_| TerminalError::NotInitialized)?;
113 let reader = pair
114 .master
115 .try_clone_reader()
116 .map_err(|_| TerminalError::NotInitialized)?;
117 let mut reader = blocking::Unblock::new(reader);
118
119 let master: Rc<RefCell<Box<dyn MasterPty + Send>>> = Rc::new(RefCell::new(pair.master));
120
121 let platform = Platform::get();
122 let reader_task = spawn_forever({
123 let parser = parser.clone();
124 let buffer = buffer.clone();
125 let closer_notifier = closer_notifier.clone();
126 let writer = writer.clone();
127 async move {
128 use futures_lite::StreamExt;
129 while let Some(()) = update_rx.next().await {
130 let mut parser = parser.borrow_mut();
131 let total_scrollback = query_max_scrollback(&mut parser);
132
133 let mut buffer = buffer.borrow_mut();
134 let old_total_scrollback = buffer.total_scrollback;
135 let delta = total_scrollback.saturating_sub(old_total_scrollback);
136 parser.screen_mut().set_scrollback(buffer.scroll_offset);
137 let mut new_buffer =
138 extract_buffer(&parser, buffer.scroll_offset, total_scrollback);
139 parser.screen_mut().set_scrollback(0);
140
141 new_buffer.selection = buffer.selection.take().map(|mut selection| {
142 selection.start_scroll = selection.start_scroll.saturating_add(delta);
143 selection.end_scroll = selection.end_scroll.saturating_add(delta);
144 selection
145 });
146 *buffer = new_buffer;
147 platform.send(UserEvent::RequestRedraw);
148 }
149 *writer.borrow_mut() = None;
151 closer_notifier.notify();
152 platform.send(UserEvent::RequestRedraw);
153 }
154 });
155
156 let pty_task = spawn_forever({
157 let writer = writer.clone();
158 let parser = parser.clone();
159 let output_notifier = output_notifier.clone();
160 let cwd = cwd.clone();
161 let title = title.clone();
162 let title_notifier = title_notifier.clone();
163 async move {
164 let mut tw_parser = TermwizParser::new();
165 loop {
166 let mut buf = [0u8; 4096];
167
168 match reader.read(&mut buf).await {
169 Ok(0) => break,
170 Ok(n) => {
171 let data = &buf[..n];
172
173 parser.borrow_mut().process(data);
174
175 let actions = tw_parser.parse_as_vec(data);
177 let mut responses: Vec<Vec<u8>> = Vec::new();
178
179 for action in actions {
180 match action {
181 Action::CSI(CSI::Device(dev)) => match *dev {
182 Device::RequestPrimaryDeviceAttributes => {
183 responses.push(b"\x1b[?62;22c".to_vec());
184 }
185 Device::RequestSecondaryDeviceAttributes => {
186 responses.push(b"\x1b[>0;0;0c".to_vec());
187 }
188 Device::StatusReport => {
189 responses.push(b"\x1b[0n".to_vec());
190 }
191 _ => {}
192 },
193 Action::CSI(CSI::Cursor(Cursor::RequestActivePositionReport)) => {
194 let p = parser.borrow();
195 let (row, col) = p.screen().cursor_position();
196 let response = format!("\x1b[{};{}R", row + 1, col + 1);
197 responses.push(response.into_bytes());
198 }
199 Action::OperatingSystemCommand(osc) => match *osc {
200 OperatingSystemCommand::CurrentWorkingDirectory(url) => {
201 let path =
203 if let Some(stripped) = url.strip_prefix("file://") {
204 if let Some(rest) = stripped.strip_prefix('/') {
206 PathBuf::from(format!("/{rest}"))
207 } else if let Some((_host, path)) =
208 stripped.split_once('/')
209 {
210 PathBuf::from(format!("/{path}"))
211 } else {
212 PathBuf::from(stripped)
213 }
214 } else {
215 PathBuf::from(url)
216 };
217 *cwd.borrow_mut() = Some(path);
218 }
219 OperatingSystemCommand::SetWindowTitle(t)
220 | OperatingSystemCommand::SetIconNameAndWindowTitle(t) => {
221 *title.borrow_mut() = Some(t);
222 title_notifier.notify();
223 }
224 _ => {}
225 },
226 _ => {}
227 }
228 }
229
230 if !responses.is_empty()
231 && let Some(writer) = &mut *writer.borrow_mut()
232 {
233 for response in responses {
234 let _ = writer.write_all(&response);
235 }
236 let _ = writer.flush();
237 }
238
239 let _ = update_tx.unbounded_send(());
240 output_notifier.notify();
241 }
242 Err(_) => break,
243 }
244 }
245 }
246 });
247
248 Ok(TerminalHandle {
249 closer_notifier: closer_notifier.clone(),
250 cleaner: Rc::new(TerminalCleaner {
251 writer: writer.clone(),
252 reader_task,
253 pty_task,
254 closer_notifier,
255 }),
256 id,
257 buffer,
258 parser,
259 writer,
260 master,
261 cwd,
262 title,
263 title_notifier,
264 output_notifier,
265 last_write_time: Rc::new(RefCell::new(Instant::now())),
266 pressed_button: Rc::new(RefCell::new(None)),
267 modifiers: Rc::new(RefCell::new(Modifiers::empty())),
268 })
269}