1use std::{
2 cell::{
3 Ref,
4 RefCell,
5 },
6 io::Write,
7 path::PathBuf,
8 rc::Rc,
9 time::Instant,
10};
11
12use freya_core::{
13 notify::ArcNotify,
14 prelude::{
15 Platform,
16 TaskHandle,
17 UseId,
18 UserEvent,
19 },
20};
21use keyboard_types::{
22 Key,
23 Modifiers,
24 NamedKey,
25};
26use portable_pty::{
27 MasterPty,
28 PtySize,
29};
30use vt100::Parser;
31
32use crate::{
33 buffer::{
34 TerminalBuffer,
35 TerminalSelection,
36 },
37 parser::{
38 TerminalMouseButton,
39 encode_mouse_move,
40 encode_mouse_press,
41 encode_mouse_release,
42 encode_wheel_event,
43 },
44 pty::{
45 extract_buffer,
46 query_max_scrollback,
47 spawn_pty,
48 },
49};
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53pub struct TerminalId(pub usize);
54
55impl TerminalId {
56 pub fn new() -> Self {
57 Self(UseId::<TerminalId>::get_in_hook())
58 }
59}
60
61impl Default for TerminalId {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67#[derive(Debug, thiserror::Error)]
69pub enum TerminalError {
70 #[error("PTY error: {0}")]
71 PtyError(String),
72
73 #[error("Write error: {0}")]
74 WriteError(String),
75
76 #[error("Terminal not initialized")]
77 NotInitialized,
78}
79
80pub(crate) struct TerminalCleaner {
82 pub(crate) writer: Rc<RefCell<Option<Box<dyn Write + Send>>>>,
84 pub(crate) reader_task: TaskHandle,
86 pub(crate) pty_task: TaskHandle,
87 pub(crate) closer_notifier: ArcNotify,
89}
90
91impl Drop for TerminalCleaner {
92 fn drop(&mut self) {
93 *self.writer.borrow_mut() = None;
94 self.reader_task.try_cancel();
95 self.pty_task.try_cancel();
96 self.closer_notifier.notify();
97 }
98}
99
100#[derive(Clone)]
107#[allow(dead_code)]
108pub struct TerminalHandle {
109 pub(crate) id: TerminalId,
111 pub(crate) buffer: Rc<RefCell<TerminalBuffer>>,
113 pub(crate) parser: Rc<RefCell<Parser>>,
115 pub(crate) writer: Rc<RefCell<Option<Box<dyn Write + Send>>>>,
117 pub(crate) master: Rc<RefCell<Box<dyn MasterPty + Send>>>,
119 pub(crate) cwd: Rc<RefCell<Option<PathBuf>>>,
121 pub(crate) title: Rc<RefCell<Option<String>>>,
123 pub(crate) closer_notifier: ArcNotify,
125 pub(crate) cleaner: Rc<TerminalCleaner>,
127 pub(crate) output_notifier: ArcNotify,
129 pub(crate) title_notifier: ArcNotify,
131 pub(crate) last_write_time: Rc<RefCell<Instant>>,
133 pub(crate) pressed_button: Rc<RefCell<Option<TerminalMouseButton>>>,
135 pub(crate) modifiers: Rc<RefCell<Modifiers>>,
137}
138
139impl PartialEq for TerminalHandle {
140 fn eq(&self, other: &Self) -> bool {
141 self.id == other.id
142 }
143}
144
145impl TerminalHandle {
146 pub fn new(
160 id: TerminalId,
161 command: portable_pty::CommandBuilder,
162 scrollback_length: Option<usize>,
163 ) -> Result<Self, TerminalError> {
164 spawn_pty(id, command, scrollback_length.unwrap_or(1000))
165 }
166
167 fn refresh_buffer(&self) {
169 let mut parser = self.parser.borrow_mut();
170 let total_scrollback = query_max_scrollback(&mut parser);
171
172 let mut buffer = self.buffer.borrow_mut();
173 buffer.scroll_offset = buffer.scroll_offset.min(total_scrollback);
174
175 parser.screen_mut().set_scrollback(buffer.scroll_offset);
176 let mut new_buffer = extract_buffer(&parser, buffer.scroll_offset, total_scrollback);
177 parser.screen_mut().set_scrollback(0);
178
179 new_buffer.selection = buffer.selection.take();
180 *buffer = new_buffer;
181 }
182
183 pub fn write(&self, data: &[u8]) -> Result<(), TerminalError> {
193 self.write_raw(data)?;
194 let mut buffer = self.buffer.borrow_mut();
195 buffer.selection = None;
196 buffer.scroll_offset = 0;
197 drop(buffer);
198 *self.last_write_time.borrow_mut() = Instant::now();
199 self.scroll_to_bottom();
200 Ok(())
201 }
202
203 pub fn write_key(&self, key: &Key, modifiers: Modifiers) -> Result<bool, TerminalError> {
225 let shift = modifiers.contains(Modifiers::SHIFT);
226 let ctrl = modifiers.contains(Modifiers::CONTROL);
227 let alt = modifiers.contains(Modifiers::ALT);
228
229 match key {
230 Key::Character(ch) if ctrl && ch.len() == 1 => {
231 self.write(&[ch.as_bytes()[0] & 0x1f])?;
232 Ok(true)
233 }
234 Key::Named(NamedKey::Enter) if shift || ctrl => {
235 if self.parser.borrow().screen().alternate_screen() {
236 let m = 1 + shift as u8 + (alt as u8) * 2 + (ctrl as u8) * 4;
237 let seq = format!("\x1b[13;{m}u");
238 self.write(seq.as_bytes())?;
239 } else {
240 self.write(b"\r")?;
241 }
242 Ok(true)
243 }
244 Key::Named(NamedKey::Enter) => {
245 self.write(b"\r")?;
246 Ok(true)
247 }
248 Key::Named(NamedKey::Backspace) => {
249 self.write(&[0x7f])?;
250 Ok(true)
251 }
252 Key::Named(NamedKey::Delete) if alt || ctrl || shift => {
253 let m = 1 + shift as u8 + (alt as u8) * 2 + (ctrl as u8) * 4;
254 let seq = format!("\x1b[3;{m}~");
255 self.write(seq.as_bytes())?;
256 Ok(true)
257 }
258 Key::Named(NamedKey::Delete) => {
259 self.write(b"\x1b[3~")?;
260 Ok(true)
261 }
262 Key::Named(NamedKey::Shift) => {
263 self.shift_pressed(true);
264 Ok(true)
265 }
266 Key::Named(NamedKey::Tab) => {
267 self.write(b"\t")?;
268 Ok(true)
269 }
270 Key::Named(NamedKey::Escape) => {
271 self.write(&[0x1b])?;
272 Ok(true)
273 }
274 Key::Named(
275 dir @ (NamedKey::ArrowUp
276 | NamedKey::ArrowDown
277 | NamedKey::ArrowLeft
278 | NamedKey::ArrowRight),
279 ) => {
280 let ch = match dir {
281 NamedKey::ArrowUp => 'A',
282 NamedKey::ArrowDown => 'B',
283 NamedKey::ArrowRight => 'C',
284 NamedKey::ArrowLeft => 'D',
285 _ => unreachable!(),
286 };
287 if shift || ctrl {
288 let m = 1 + shift as u8 + (alt as u8) * 2 + (ctrl as u8) * 4;
289 let seq = format!("\x1b[1;{m}{ch}");
290 self.write(seq.as_bytes())?;
291 } else {
292 self.write(&[0x1b, b'[', ch as u8])?;
293 }
294 Ok(true)
295 }
296 Key::Character(ch) => {
297 self.write(ch.as_bytes())?;
298 Ok(true)
299 }
300 _ => Ok(false),
301 }
302 }
303
304 pub fn paste(&self, text: &str) -> Result<(), TerminalError> {
308 let bracketed = self.parser.borrow().screen().bracketed_paste();
309
310 let mut data = Vec::with_capacity(text.len() + 12);
311 if bracketed {
312 data.extend_from_slice(b"\x1b[200~");
313 }
314 data.extend_from_slice(text.as_bytes());
315 if bracketed {
316 data.extend_from_slice(b"\x1b[201~");
317 }
318
319 self.write(&data)
320 }
321
322 fn write_raw(&self, data: &[u8]) -> Result<(), TerminalError> {
324 match &mut *self.writer.borrow_mut() {
325 Some(w) => {
326 w.write_all(data)
327 .map_err(|e| TerminalError::WriteError(e.to_string()))?;
328 w.flush()
329 .map_err(|e| TerminalError::WriteError(e.to_string()))?;
330 Ok(())
331 }
332 None => Err(TerminalError::NotInitialized),
333 }
334 }
335
336 pub fn resize(&self, rows: u16, cols: u16) {
346 self.parser.borrow_mut().screen_mut().set_size(rows, cols);
347 self.refresh_buffer();
348 let _ = self.master.borrow().resize(PtySize {
349 rows,
350 cols,
351 pixel_width: 0,
352 pixel_height: 0,
353 });
354 }
355
356 pub fn scroll(&self, delta: i32) {
367 if self.parser.borrow().screen().alternate_screen() {
368 return;
369 }
370
371 {
372 let mut buffer = self.buffer.borrow_mut();
373 let new_offset = (buffer.scroll_offset as i64 + delta as i64).max(0) as usize;
374 buffer.scroll_offset = new_offset.min(buffer.total_scrollback);
375 }
376
377 self.refresh_buffer();
378 Platform::get().send(UserEvent::RequestRedraw);
379 }
380
381 pub fn scroll_to_bottom(&self) {
391 if self.parser.borrow().screen().alternate_screen() {
392 return;
393 }
394
395 self.buffer.borrow_mut().scroll_offset = 0;
396 self.refresh_buffer();
397 Platform::get().send(UserEvent::RequestRedraw);
398 }
399
400 pub fn scrollback_position(&self) -> usize {
410 self.buffer.borrow().scroll_offset
411 }
412
413 pub fn cwd(&self) -> Option<PathBuf> {
417 self.cwd.borrow().clone()
418 }
419
420 pub fn title(&self) -> Option<String> {
424 self.title.borrow().clone()
425 }
426
427 pub fn send_wheel_to_pty(&self, row: usize, col: usize, delta_y: f64) {
433 let encoding = self.parser.borrow().screen().mouse_protocol_encoding();
434 let seq = encode_wheel_event(row, col, delta_y, encoding);
435 let _ = self.write_raw(seq.as_bytes());
436 }
437
438 pub fn mouse_move(&self, row: usize, col: usize) {
449 let is_dragging = self.pressed_button.borrow().is_some();
450
451 if self.modifiers.borrow().contains(Modifiers::SHIFT) && is_dragging {
452 self.update_selection(row, col);
454 return;
455 }
456
457 let parser = self.parser.borrow();
458 let mouse_mode = parser.screen().mouse_protocol_mode();
459 let encoding = parser.screen().mouse_protocol_encoding();
460
461 let held = *self.pressed_button.borrow();
462
463 match mouse_mode {
464 vt100::MouseProtocolMode::AnyMotion => {
465 let seq = encode_mouse_move(row, col, held, encoding);
466 let _ = self.write_raw(seq.as_bytes());
467 }
468 vt100::MouseProtocolMode::ButtonMotion => {
469 if let Some(button) = held {
470 let seq = encode_mouse_move(row, col, Some(button), encoding);
471 let _ = self.write_raw(seq.as_bytes());
472 }
473 }
474 vt100::MouseProtocolMode::None => {
475 if is_dragging {
477 self.update_selection(row, col);
478 }
479 }
480 _ => {}
481 }
482 }
483
484 fn is_mouse_tracking_enabled(&self) -> bool {
486 let parser = self.parser.borrow();
487 parser.screen().mouse_protocol_mode() != vt100::MouseProtocolMode::None
488 }
489
490 pub fn mouse_down(&self, row: usize, col: usize, button: TerminalMouseButton) {
499 *self.pressed_button.borrow_mut() = Some(button);
500
501 if self.modifiers.borrow().contains(Modifiers::SHIFT) {
502 self.start_selection(row, col);
504 } else if self.is_mouse_tracking_enabled() {
505 let encoding = self.parser.borrow().screen().mouse_protocol_encoding();
506 let seq = encode_mouse_press(row, col, button, encoding);
507 let _ = self.write_raw(seq.as_bytes());
508 } else {
509 self.start_selection(row, col);
510 }
511 }
512
513 pub fn mouse_up(&self, row: usize, col: usize, button: TerminalMouseButton) {
523 *self.pressed_button.borrow_mut() = None;
524
525 if self.modifiers.borrow().contains(Modifiers::SHIFT) {
526 self.end_selection();
528 return;
529 }
530
531 let parser = self.parser.borrow();
532 let mouse_mode = parser.screen().mouse_protocol_mode();
533 let encoding = parser.screen().mouse_protocol_encoding();
534
535 match mouse_mode {
536 vt100::MouseProtocolMode::PressRelease
537 | vt100::MouseProtocolMode::ButtonMotion
538 | vt100::MouseProtocolMode::AnyMotion => {
539 let seq = encode_mouse_release(row, col, button, encoding);
540 let _ = self.write_raw(seq.as_bytes());
541 }
542 vt100::MouseProtocolMode::Press => {
543 }
545 vt100::MouseProtocolMode::None => {
546 self.end_selection();
547 }
548 }
549 }
550
551 const ALTERNATE_SCROLL_LINES: usize = 3;
553
554 pub fn release(&self) {
559 *self.pressed_button.borrow_mut() = None;
560 self.end_selection();
561 }
562
563 pub fn wheel(&self, delta_y: f64, row: usize, col: usize) {
574 let scroll_delta = if delta_y > 0.0 { 3 } else { -3 };
575 let scroll_offset = self.buffer.borrow().scroll_offset;
576 let (mouse_mode, alt_screen, app_cursor) = {
577 let parser = self.parser.borrow();
578 let screen = parser.screen();
579 (
580 screen.mouse_protocol_mode(),
581 screen.alternate_screen(),
582 screen.application_cursor(),
583 )
584 };
585
586 if scroll_offset > 0 {
587 let delta = scroll_delta;
589 self.scroll(delta);
590 } else if mouse_mode != vt100::MouseProtocolMode::None {
591 self.send_wheel_to_pty(row, col, delta_y);
593 } else if alt_screen {
594 let key = match (delta_y > 0.0, app_cursor) {
597 (true, true) => "\x1bOA",
598 (true, false) => "\x1b[A",
599 (false, true) => "\x1bOB",
600 (false, false) => "\x1b[B",
601 };
602 for _ in 0..Self::ALTERNATE_SCROLL_LINES {
603 let _ = self.write_raw(key.as_bytes());
604 }
605 } else {
606 let delta = scroll_delta;
608 self.scroll(delta);
609 }
610 }
611
612 pub fn read_buffer(&'_ self) -> Ref<'_, TerminalBuffer> {
614 self.buffer.borrow()
615 }
616
617 pub fn output_received(&self) -> impl std::future::Future<Output = ()> + '_ {
621 self.output_notifier.notified()
622 }
623
624 pub fn title_changed(&self) -> impl std::future::Future<Output = ()> + '_ {
628 self.title_notifier.notified()
629 }
630
631 pub fn last_write_elapsed(&self) -> std::time::Duration {
632 self.last_write_time.borrow().elapsed()
633 }
634
635 pub fn closed(&self) -> impl std::future::Future<Output = ()> + '_ {
648 self.closer_notifier.notified()
649 }
650
651 pub fn id(&self) -> TerminalId {
653 self.id
654 }
655
656 pub fn shift_pressed(&self, pressed: bool) {
661 let mut mods = self.modifiers.borrow_mut();
662 if pressed {
663 mods.insert(Modifiers::SHIFT);
664 } else {
665 mods.remove(Modifiers::SHIFT);
666 }
667 }
668
669 pub fn get_selection(&self) -> Option<TerminalSelection> {
671 self.buffer.borrow().selection.clone()
672 }
673
674 pub fn set_selection(&self, selection: Option<TerminalSelection>) {
676 self.buffer.borrow_mut().selection = selection;
677 }
678
679 pub fn start_selection(&self, row: usize, col: usize) {
680 let mut buffer = self.buffer.borrow_mut();
681 let scroll = buffer.scroll_offset;
682 buffer.selection = Some(TerminalSelection {
683 dragging: true,
684 start_row: row,
685 start_col: col,
686 start_scroll: scroll,
687 end_row: row,
688 end_col: col,
689 end_scroll: scroll,
690 });
691 Platform::get().send(UserEvent::RequestRedraw);
692 }
693
694 pub fn update_selection(&self, row: usize, col: usize) {
695 let mut buffer = self.buffer.borrow_mut();
696 let scroll = buffer.scroll_offset;
697 if let Some(selection) = &mut buffer.selection
698 && selection.dragging
699 {
700 selection.end_row = row;
701 selection.end_col = col;
702 selection.end_scroll = scroll;
703 Platform::get().send(UserEvent::RequestRedraw);
704 }
705 }
706
707 pub fn end_selection(&self) {
708 if let Some(selection) = &mut self.buffer.borrow_mut().selection {
709 selection.dragging = false;
710 Platform::get().send(UserEvent::RequestRedraw);
711 }
712 }
713
714 pub fn clear_selection(&self) {
716 self.buffer.borrow_mut().selection = None;
717 Platform::get().send(UserEvent::RequestRedraw);
718 }
719
720 pub fn get_selected_text(&self) -> Option<String> {
721 let buffer = self.buffer.borrow();
722 let selection = buffer.selection.clone()?;
723 if selection.is_empty() {
724 return None;
725 }
726
727 let scroll = buffer.scroll_offset;
728 let (display_start, start_col, display_end, end_col) = selection.display_positions(scroll);
729
730 let mut parser = self.parser.borrow_mut();
731 let saved_scrollback = parser.screen().scrollback();
732 let (_rows, cols) = parser.screen().size();
733
734 let mut lines = Vec::new();
735
736 for d in display_start..=display_end {
737 let cp = d - scroll as i64;
738 let needed_scrollback = (-cp).max(0) as usize;
739 let viewport_row = cp.max(0) as u16;
740
741 parser.screen_mut().set_scrollback(needed_scrollback);
742
743 let row_cells: Vec<_> = (0..cols)
744 .filter_map(|c| parser.screen().cell(viewport_row, c).cloned())
745 .collect();
746
747 let is_single = display_start == display_end;
748 let is_first = d == display_start;
749 let is_last = d == display_end;
750
751 let cells = if is_single {
752 let s = start_col.min(row_cells.len());
753 let e = end_col.min(row_cells.len());
754 &row_cells[s..e]
755 } else if is_first {
756 let s = start_col.min(row_cells.len());
757 &row_cells[s..]
758 } else if is_last {
759 &row_cells[..end_col.min(row_cells.len())]
760 } else {
761 &row_cells
762 };
763
764 let line: String = cells
765 .iter()
766 .map(|cell| {
767 if cell.has_contents() {
768 cell.contents()
769 } else {
770 " "
771 }
772 })
773 .collect::<String>();
774
775 lines.push(line);
776 }
777
778 parser.screen_mut().set_scrollback(saved_scrollback);
779
780 Some(lines.join("\n"))
781 }
782}