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 cursor_visible: !parser.screen().hide_cursor(),
81 }
82}
83
84pub(crate) fn spawn_pty(
86 id: TerminalId,
87 command: CommandBuilder,
88 scrollback_size: usize,
89) -> Result<TerminalHandle, TerminalError> {
90 let (update_tx, mut update_rx) = futures_channel::mpsc::unbounded::<()>();
91
92 let buffer = Rc::new(RefCell::new(TerminalBuffer::default()));
93 let parser = Rc::new(RefCell::new(Parser::new(24, 80, scrollback_size)));
94 let writer = Rc::new(RefCell::new(None::<Box<dyn std::io::Write + Send>>));
95 let closer_notifier = ArcNotify::new();
96 let output_notifier = ArcNotify::new();
97 let title_notifier = ArcNotify::new();
98 let cwd: Rc<RefCell<Option<PathBuf>>> = Rc::new(RefCell::new(None));
99 let title: Rc<RefCell<Option<String>>> = Rc::new(RefCell::new(None));
100 let clipboard_content: Rc<RefCell<Option<String>>> = Rc::new(RefCell::new(None));
101 let clipboard_notifier = ArcNotify::new();
102
103 let pty_system = native_pty_system();
104 let pair = pty_system
105 .openpty(PtySize::default())
106 .map_err(|_| TerminalError::NotInitialized)?;
107 let master_writer = pair
108 .master
109 .take_writer()
110 .map_err(|_| TerminalError::NotInitialized)?;
111 *writer.borrow_mut() = Some(master_writer);
112
113 pair.slave
114 .spawn_command(command)
115 .map_err(|_| TerminalError::NotInitialized)?;
116 let reader = pair
117 .master
118 .try_clone_reader()
119 .map_err(|_| TerminalError::NotInitialized)?;
120 let mut reader = blocking::Unblock::new(reader);
121
122 let master: Rc<RefCell<Box<dyn MasterPty + Send>>> = Rc::new(RefCell::new(pair.master));
123
124 let platform = Platform::get();
125 let reader_task = spawn_forever({
126 let parser = parser.clone();
127 let buffer = buffer.clone();
128 let closer_notifier = closer_notifier.clone();
129 let writer = writer.clone();
130 async move {
131 use futures_lite::StreamExt;
132 while let Some(()) = update_rx.next().await {
133 let mut parser = parser.borrow_mut();
134 let total_scrollback = query_max_scrollback(&mut parser);
135
136 let mut buffer = buffer.borrow_mut();
137 let old_total_scrollback = buffer.total_scrollback;
138 let delta = total_scrollback.saturating_sub(old_total_scrollback);
139 parser.screen_mut().set_scrollback(buffer.scroll_offset);
140 let mut new_buffer =
141 extract_buffer(&parser, buffer.scroll_offset, total_scrollback);
142 parser.screen_mut().set_scrollback(0);
143
144 new_buffer.selection = buffer.selection.take().map(|mut selection| {
145 selection.start_scroll = selection.start_scroll.saturating_add(delta);
146 selection.end_scroll = selection.end_scroll.saturating_add(delta);
147 selection
148 });
149 *buffer = new_buffer;
150 platform.send(UserEvent::RequestRedraw);
151 }
152 *writer.borrow_mut() = None;
154 closer_notifier.notify();
155 platform.send(UserEvent::RequestRedraw);
156 }
157 });
158
159 let pty_task = spawn_forever({
160 let writer = writer.clone();
161 let parser = parser.clone();
162 let output_notifier = output_notifier.clone();
163 let cwd = cwd.clone();
164 let title = title.clone();
165 let title_notifier = title_notifier.clone();
166 let clipboard_content = clipboard_content.clone();
167 let clipboard_notifier = clipboard_notifier.clone();
168 async move {
169 let mut tw_parser = TermwizParser::new();
170 loop {
171 let mut buf = [0u8; 4096];
172
173 match reader.read(&mut buf).await {
174 Ok(0) => break,
175 Ok(n) => {
176 let data = &buf[..n];
177
178 parser.borrow_mut().process(data);
179
180 let actions = tw_parser.parse_as_vec(data);
182 let mut responses: Vec<Vec<u8>> = Vec::new();
183
184 for action in actions {
185 match action {
186 Action::CSI(CSI::Device(dev)) => match *dev {
187 Device::RequestPrimaryDeviceAttributes => {
188 responses.push(b"\x1b[?62;22c".to_vec());
189 }
190 Device::RequestSecondaryDeviceAttributes => {
191 responses.push(b"\x1b[>0;0;0c".to_vec());
192 }
193 Device::StatusReport => {
194 responses.push(b"\x1b[0n".to_vec());
195 }
196 _ => {}
197 },
198 Action::CSI(CSI::Cursor(Cursor::RequestActivePositionReport)) => {
199 let p = parser.borrow();
200 let (row, col) = p.screen().cursor_position();
201 let response = format!("\x1b[{};{}R", row + 1, col + 1);
202 responses.push(response.into_bytes());
203 }
204 Action::OperatingSystemCommand(osc) => match *osc {
205 OperatingSystemCommand::CurrentWorkingDirectory(url) => {
206 let path =
208 if let Some(stripped) = url.strip_prefix("file://") {
209 if let Some(rest) = stripped.strip_prefix('/') {
211 PathBuf::from(format!("/{rest}"))
212 } else if let Some((_host, path)) =
213 stripped.split_once('/')
214 {
215 PathBuf::from(format!("/{path}"))
216 } else {
217 PathBuf::from(stripped)
218 }
219 } else {
220 PathBuf::from(url)
221 };
222 *cwd.borrow_mut() = Some(path);
223 }
224 OperatingSystemCommand::SetWindowTitle(t)
225 | OperatingSystemCommand::SetIconNameAndWindowTitle(t) => {
226 *title.borrow_mut() = Some(t);
227 title_notifier.notify();
228 }
229 OperatingSystemCommand::SetSelection(_sel, text) => {
230 *clipboard_content.borrow_mut() = Some(text);
231 clipboard_notifier.notify();
232 }
233 _ => {}
234 },
235 _ => {}
236 }
237 }
238
239 if !responses.is_empty()
240 && let Some(writer) = &mut *writer.borrow_mut()
241 {
242 for response in responses {
243 let _ = writer.write_all(&response);
244 }
245 let _ = writer.flush();
246 }
247
248 let _ = update_tx.unbounded_send(());
249 output_notifier.notify();
250 }
251 Err(_) => break,
252 }
253 }
254 }
255 });
256
257 Ok(TerminalHandle {
258 closer_notifier: closer_notifier.clone(),
259 cleaner: Rc::new(TerminalCleaner {
260 writer: writer.clone(),
261 reader_task,
262 pty_task,
263 closer_notifier,
264 }),
265 id,
266 buffer,
267 parser,
268 writer,
269 master,
270 cwd,
271 title,
272 title_notifier,
273 clipboard_content,
274 clipboard_notifier,
275 output_notifier,
276 last_write_time: Rc::new(RefCell::new(Instant::now())),
277 pressed_button: Rc::new(RefCell::new(None)),
278 modifiers: Rc::new(RefCell::new(Modifiers::empty())),
279 })
280}