1use std::{
2 cell::RefCell,
3 path::PathBuf,
4 rc::Rc,
5 time::Instant,
6};
7
8use alacritty_terminal::{
9 event::{
10 Event as AlacrittyEvent,
11 EventListener,
12 },
13 grid::Dimensions,
14 term::{
15 Config as TermConfig,
16 Term,
17 },
18 vte::{
19 Parser as VteParser,
20 ansi::{
21 Processor,
22 StdSyncHandler,
23 },
24 },
25};
26use freya_core::{
27 notify::ArcNotify,
28 prelude::{
29 Platform,
30 UserEvent,
31 spawn_forever,
32 },
33};
34use futures_lite::AsyncReadExt;
35use keyboard_types::Modifiers;
36use portable_pty::{
37 CommandBuilder,
38 PtySize,
39 native_pty_system,
40};
41
42use crate::{
43 handle::{
44 TerminalCleaner,
45 TerminalError,
46 TerminalHandle,
47 TerminalId,
48 TerminalInner,
49 },
50 osc7::{
51 CwdSink,
52 parse_cwd_url,
53 },
54};
55
56#[derive(Clone, Copy)]
58pub(crate) struct TermSize {
59 pub screen_lines: usize,
60 pub columns: usize,
61}
62
63impl Dimensions for TermSize {
64 fn total_lines(&self) -> usize {
65 self.screen_lines
66 }
67
68 fn screen_lines(&self) -> usize {
69 self.screen_lines
70 }
71
72 fn columns(&self) -> usize {
73 self.columns
74 }
75}
76
77#[derive(Clone)]
80pub struct EventProxy {
81 pub(crate) writer: Rc<RefCell<Option<Box<dyn std::io::Write + Send>>>>,
82 pub(crate) title: Rc<RefCell<Option<String>>>,
83 pub(crate) title_notifier: ArcNotify,
84 pub(crate) clipboard_content: Rc<RefCell<Option<String>>>,
85 pub(crate) clipboard_notifier: ArcNotify,
86}
87
88impl EventListener for EventProxy {
89 fn send_event(&self, event: AlacrittyEvent) {
90 match event {
91 AlacrittyEvent::PtyWrite(text) => {
92 if let Some(writer) = &mut *self.writer.borrow_mut() {
93 let _ = writer.write_all(text.as_bytes());
94 let _ = writer.flush();
95 }
96 }
97 AlacrittyEvent::Title(t) => {
98 *self.title.borrow_mut() = Some(t);
99 self.title_notifier.notify();
100 }
101 AlacrittyEvent::ResetTitle => {
102 *self.title.borrow_mut() = None;
103 self.title_notifier.notify();
104 }
105 AlacrittyEvent::ClipboardStore(_, text) => {
106 *self.clipboard_content.borrow_mut() = Some(text);
107 self.clipboard_notifier.notify();
108 }
109 _ => {}
111 }
112 }
113}
114
115pub(crate) fn spawn_pty(
117 id: TerminalId,
118 command: CommandBuilder,
119 scrollback_size: usize,
120) -> Result<TerminalHandle, TerminalError> {
121 let writer = Rc::new(RefCell::new(None::<Box<dyn std::io::Write + Send>>));
122 let closer_notifier = ArcNotify::new();
123 let output_notifier = ArcNotify::new();
124 let title_notifier = ArcNotify::new();
125 let cwd: Rc<RefCell<Option<PathBuf>>> = Rc::new(RefCell::new(None));
126 let title: Rc<RefCell<Option<String>>> = Rc::new(RefCell::new(None));
127 let clipboard_content: Rc<RefCell<Option<String>>> = Rc::new(RefCell::new(None));
128 let clipboard_notifier = ArcNotify::new();
129
130 let event_proxy = EventProxy {
131 writer: writer.clone(),
132 title: title.clone(),
133 title_notifier: title_notifier.clone(),
134 clipboard_content: clipboard_content.clone(),
135 clipboard_notifier: clipboard_notifier.clone(),
136 };
137
138 let term_config = TermConfig {
139 scrolling_history: scrollback_size,
140 ..TermConfig::default()
141 };
142 let initial_size = TermSize {
143 screen_lines: 24,
144 columns: 80,
145 };
146 let term = Rc::new(RefCell::new(Term::new(
147 term_config,
148 &initial_size,
149 event_proxy,
150 )));
151
152 let pty_system = native_pty_system();
153 let pair = pty_system
154 .openpty(PtySize::default())
155 .map_err(|_| TerminalError::NotInitialized)?;
156 let master_writer = pair
157 .master
158 .take_writer()
159 .map_err(|_| TerminalError::NotInitialized)?;
160 *writer.borrow_mut() = Some(master_writer);
161
162 pair.slave
163 .spawn_command(command)
164 .map_err(|_| TerminalError::NotInitialized)?;
165 let reader = pair
166 .master
167 .try_clone_reader()
168 .map_err(|_| TerminalError::NotInitialized)?;
169 let mut reader = blocking::Unblock::new(reader);
170
171 let inner = Rc::new(RefCell::new(TerminalInner {
172 master: pair.master,
173 last_write_time: Instant::now(),
174 pressed_button: None,
175 modifiers: Modifiers::empty(),
176 }));
177
178 let platform = Platform::get();
179 let pty_task = spawn_forever({
180 let term = term.clone();
181 let writer = writer.clone();
182 let closer_notifier = closer_notifier.clone();
183 let output_notifier = output_notifier.clone();
184 let cwd = cwd.clone();
185 async move {
186 let mut processor = Processor::<StdSyncHandler>::new();
187 let mut cwd_parser = VteParser::new();
189 let mut cwd_sink = CwdSink::default();
190 loop {
191 let mut buf = [0u8; 4096];
192 match reader.read(&mut buf).await {
193 Ok(0) => break,
194 Ok(n) => {
195 let data = &buf[..n];
196 processor.advance(&mut *term.borrow_mut(), data);
197
198 cwd_parser.advance(&mut cwd_sink, data);
199 if let Some(url) = cwd_sink.take() {
200 *cwd.borrow_mut() = Some(parse_cwd_url(&url));
201 }
202
203 output_notifier.notify();
204 platform.send(UserEvent::RequestRedraw);
205 }
206 Err(_) => break,
207 }
208 }
209 *writer.borrow_mut() = None;
211 closer_notifier.notify();
212 platform.send(UserEvent::RequestRedraw);
213 }
214 });
215
216 Ok(TerminalHandle {
217 closer_notifier: closer_notifier.clone(),
218 cleaner: Rc::new(TerminalCleaner {
219 writer: writer.clone(),
220 pty_task,
221 closer_notifier,
222 }),
223 id,
224 term,
225 writer,
226 inner,
227 cwd,
228 title,
229 title_notifier,
230 clipboard_content,
231 clipboard_notifier,
232 output_notifier,
233 })
234}