1use std::{
2 cell::RefCell,
3 rc::Rc,
4 time::Instant,
5};
6
7use freya_core::{
8 notify::ArcNotify,
9 prelude::{
10 Platform,
11 UserEvent,
12 spawn_forever,
13 },
14};
15use futures_lite::{
16 AsyncReadExt,
17 StreamExt,
18};
19use futures_util::FutureExt;
20use keyboard_types::Modifiers;
21use portable_pty::{
22 CommandBuilder,
23 PtySize,
24 native_pty_system,
25};
26use vt100::Parser;
27
28use crate::{
29 buffer::TerminalBuffer,
30 handle::{
31 TerminalCleaner,
32 TerminalError,
33 TerminalHandle,
34 TerminalId,
35 },
36 parser::check_for_terminal_queries,
37};
38
39#[derive(Debug, Clone)]
41pub enum ScrollCommand {
42 Delta(i32),
44 ToBottom,
46}
47
48fn query_max_scrollback(parser: &mut Parser) -> usize {
51 let saved = parser.screen().scrollback();
52 parser.screen_mut().set_scrollback(usize::MAX);
53 let max = parser.screen().scrollback();
54 parser.screen_mut().set_scrollback(saved);
55 max
56}
57
58fn extract_buffer(
60 parser: &Parser,
61 scroll_offset: usize,
62 total_scrollback: usize,
63) -> TerminalBuffer {
64 let (rows, cols) = parser.screen().size();
65 let rows_vec: Vec<Vec<vt100::Cell>> = (0..rows)
66 .map(|r| {
67 (0..cols)
68 .filter_map(|c| parser.screen().cell(r, c).cloned())
69 .collect()
70 })
71 .collect();
72 let (cur_r, cur_c) = parser.screen().cursor_position();
73 TerminalBuffer {
74 rows: rows_vec,
75 cursor_row: cur_r as usize,
76 cursor_col: cur_c as usize,
77 cols: cols as usize,
78 rows_count: rows as usize,
79 selection: None,
80 scroll_offset,
81 total_scrollback,
82 }
83}
84
85pub(crate) fn spawn_pty(
87 id: TerminalId,
88 command: CommandBuilder,
89 scrollback_size: usize,
90) -> Result<TerminalHandle, TerminalError> {
91 let (update_tx, mut update_rx) = futures_channel::mpsc::unbounded::<()>();
92 let (resize_tx, mut resize_rx) = futures_channel::mpsc::unbounded::<(u16, u16)>();
93 let (scroll_tx, mut scroll_rx) = futures_channel::mpsc::unbounded::<ScrollCommand>();
94
95 let buffer = Rc::new(RefCell::new(TerminalBuffer::default()));
96 let parser = Rc::new(RefCell::new(Parser::new(24, 80, scrollback_size)));
97 let writer = Rc::new(RefCell::new(None::<Box<dyn std::io::Write + Send>>));
98 let closer_notifier = ArcNotify::new();
99 let output_notifier = ArcNotify::new();
100
101 let pty_system = native_pty_system();
102 let pair = pty_system
103 .openpty(PtySize::default())
104 .map_err(|_| TerminalError::NotInitialized)?;
105 let master_writer = pair
106 .master
107 .take_writer()
108 .map_err(|_| TerminalError::NotInitialized)?;
109 *writer.borrow_mut() = Some(master_writer);
110
111 pair.slave
112 .spawn_command(command)
113 .map_err(|_| TerminalError::NotInitialized)?;
114 let reader = pair
115 .master
116 .try_clone_reader()
117 .map_err(|_| TerminalError::NotInitialized)?;
118 let mut reader = blocking::Unblock::new(reader);
119 let platform = Platform::get();
120 let reader_task = spawn_forever({
121 let parser = parser.clone();
122 let buffer = buffer.clone();
123 let closer_notifier = closer_notifier.clone();
124 let writer = writer.clone();
125 async move {
126 loop {
127 futures_util::select! {
128 update = update_rx.next().fuse() => {
129 if update.is_none() {
130 *writer.borrow_mut() = None;
131 closer_notifier.notify();
132 platform.send(UserEvent::RequestRedraw);
133 break;
134 }
135 let mut parser = parser.borrow_mut();
136 let total_scrollback = query_max_scrollback(&mut parser);
137
138 let mut buffer = buffer.borrow_mut();
139 parser.screen_mut().set_scrollback(buffer.scroll_offset);
140 let mut new_buffer = extract_buffer(&parser, buffer.scroll_offset, total_scrollback);
141 parser.screen_mut().set_scrollback(0);
142
143 new_buffer.selection = buffer.selection.take();
144 *buffer = new_buffer;
145 platform.send(UserEvent::RequestRedraw);
146 }
147 resize = resize_rx.next().fuse() => {
148 if let Some((rows, cols)) = resize {
149 let mut parser = parser.borrow_mut();
150 parser.screen_mut().set_size(rows, cols);
151
152 let total_scrollback = query_max_scrollback(&mut parser);
153 let mut buffer = buffer.borrow_mut();
154 buffer.scroll_offset = buffer.scroll_offset.min(total_scrollback);
155
156 parser.screen_mut().set_scrollback(buffer.scroll_offset);
157 let mut new_buffer = extract_buffer(&parser, buffer.scroll_offset, total_scrollback);
158 parser.screen_mut().set_scrollback(0);
159
160 new_buffer.selection = buffer.selection.take();
161 *buffer = new_buffer;
162
163 let (rows, cols) = parser.screen().size();
164 let _ = pair.master.resize(PtySize {
165 rows,
166 cols,
167 pixel_width: 0,
168 pixel_height: 0,
169 });
170 }
171 }
172 scroll = scroll_rx.next().fuse() => {
173 if let Some(cmd) = scroll {
174 let mut parser = parser.borrow_mut();
175
176 if parser.screen().alternate_screen() {
177 continue;
178 }
179
180 let total_scrollback = query_max_scrollback(&mut parser);
181
182 let mut buffer = buffer.borrow_mut();
183 let offset = {
184 match cmd {
185 ScrollCommand::Delta(_) => {
186 buffer.scroll_offset = buffer.scroll_offset.min(total_scrollback);
187 }
188 ScrollCommand::ToBottom => {
189 buffer.scroll_offset = 0;
190 }
191 }
192 buffer.scroll_offset
193 };
194
195 parser.screen_mut().set_scrollback(offset);
196 let mut new_buffer = extract_buffer(&parser, offset, total_scrollback);
197 parser.screen_mut().set_scrollback(0);
198
199 new_buffer.selection = buffer.selection.take();
200 *buffer = new_buffer;
201 platform.send(UserEvent::RequestRedraw);
202 }
203 }
204 }
205 }
206 }
207 });
208
209 let pty_task = spawn_forever({
210 let writer = writer.clone();
211 let parser = parser.clone();
212 let output_notifier = output_notifier.clone();
213 async move {
214 loop {
215 let mut buf = [0u8; 4096];
216
217 match reader.read(&mut buf).await {
218 Ok(0) => break,
219 Ok(n) => {
220 let data = &buf[..n];
221
222 parser.borrow_mut().process(data);
223
224 let responses = check_for_terminal_queries(data, &parser.borrow());
225 if !responses.is_empty()
226 && let Some(writer) = &mut *writer.borrow_mut()
227 {
228 for response in responses {
229 let _ = writer.write_all(&response);
230 }
231 let _ = writer.flush();
232 }
233
234 let _ = update_tx.unbounded_send(());
235 output_notifier.notify();
236 }
237 Err(_) => break,
238 }
239 }
240 }
241 });
242
243 Ok(TerminalHandle {
244 closer_notifier: closer_notifier.clone(),
245 cleaner: Rc::new(TerminalCleaner {
246 writer: writer.clone(),
247 reader_task,
248 pty_task,
249 closer_notifier,
250 }),
251 id,
252 buffer,
253 parser,
254 writer,
255 resize_sender: Rc::new(resize_tx),
256 scroll_sender: Rc::new(scroll_tx),
257 output_notifier,
258 last_write_time: Rc::new(RefCell::new(Instant::now())),
259 pressed_button: Rc::new(RefCell::new(None)),
260 modifiers: Rc::new(RefCell::new(Modifiers::empty())),
261 })
262}