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) if ctrl => {
249 self.write(&[0x08])?;
250 Ok(true)
251 }
252 Key::Named(NamedKey::Backspace) if alt => {
253 self.write(&[0x1b, 0x7f])?;
254 Ok(true)
255 }
256 Key::Named(NamedKey::Backspace) => {
257 self.write(&[0x7f])?;
258 Ok(true)
259 }
260 Key::Named(NamedKey::Delete) if alt || ctrl || shift => {
261 let m = 1 + shift as u8 + (alt as u8) * 2 + (ctrl as u8) * 4;
262 let seq = format!("\x1b[3;{m}~");
263 self.write(seq.as_bytes())?;
264 Ok(true)
265 }
266 Key::Named(NamedKey::Delete) => {
267 self.write(b"\x1b[3~")?;
268 Ok(true)
269 }
270 Key::Named(NamedKey::Shift) => {
271 self.shift_pressed(true);
272 Ok(true)
273 }
274 Key::Named(NamedKey::Tab) => {
275 self.write(b"\t")?;
276 Ok(true)
277 }
278 Key::Named(NamedKey::Escape) => {
279 self.write(&[0x1b])?;
280 Ok(true)
281 }
282 Key::Named(
283 dir @ (NamedKey::ArrowUp
284 | NamedKey::ArrowDown
285 | NamedKey::ArrowLeft
286 | NamedKey::ArrowRight),
287 ) => {
288 let ch = match dir {
289 NamedKey::ArrowUp => 'A',
290 NamedKey::ArrowDown => 'B',
291 NamedKey::ArrowRight => 'C',
292 NamedKey::ArrowLeft => 'D',
293 _ => unreachable!(),
294 };
295 if shift || ctrl {
296 let m = 1 + shift as u8 + (alt as u8) * 2 + (ctrl as u8) * 4;
297 let seq = format!("\x1b[1;{m}{ch}");
298 self.write(seq.as_bytes())?;
299 } else {
300 self.write(&[0x1b, b'[', ch as u8])?;
301 }
302 Ok(true)
303 }
304 Key::Character(ch) => {
305 self.write(ch.as_bytes())?;
306 Ok(true)
307 }
308 _ => Ok(false),
309 }
310 }
311
312 pub fn paste(&self, text: &str) -> Result<(), TerminalError> {
316 let bracketed = self.parser.borrow().screen().bracketed_paste();
317
318 let mut data = Vec::with_capacity(text.len() + 12);
319 if bracketed {
320 data.extend_from_slice(b"\x1b[200~");
321 }
322 data.extend_from_slice(text.as_bytes());
323 if bracketed {
324 data.extend_from_slice(b"\x1b[201~");
325 }
326
327 self.write(&data)
328 }
329
330 fn write_raw(&self, data: &[u8]) -> Result<(), TerminalError> {
332 match &mut *self.writer.borrow_mut() {
333 Some(w) => {
334 w.write_all(data)
335 .map_err(|e| TerminalError::WriteError(e.to_string()))?;
336 w.flush()
337 .map_err(|e| TerminalError::WriteError(e.to_string()))?;
338 Ok(())
339 }
340 None => Err(TerminalError::NotInitialized),
341 }
342 }
343
344 pub fn resize(&self, rows: u16, cols: u16) {
354 self.parser.borrow_mut().screen_mut().set_size(rows, cols);
355 self.refresh_buffer();
356 let _ = self.master.borrow().resize(PtySize {
357 rows,
358 cols,
359 pixel_width: 0,
360 pixel_height: 0,
361 });
362 }
363
364 pub fn scroll(&self, delta: i32) {
375 if self.parser.borrow().screen().alternate_screen() {
376 return;
377 }
378
379 {
380 let mut buffer = self.buffer.borrow_mut();
381 let new_offset = (buffer.scroll_offset as i64 + delta as i64).max(0) as usize;
382 buffer.scroll_offset = new_offset.min(buffer.total_scrollback);
383 }
384
385 self.refresh_buffer();
386 Platform::get().send(UserEvent::RequestRedraw);
387 }
388
389 pub fn scroll_to_bottom(&self) {
399 if self.parser.borrow().screen().alternate_screen() {
400 return;
401 }
402
403 self.buffer.borrow_mut().scroll_offset = 0;
404 self.refresh_buffer();
405 Platform::get().send(UserEvent::RequestRedraw);
406 }
407
408 pub fn scrollback_position(&self) -> usize {
418 self.buffer.borrow().scroll_offset
419 }
420
421 pub fn cwd(&self) -> Option<PathBuf> {
425 self.cwd.borrow().clone()
426 }
427
428 pub fn title(&self) -> Option<String> {
432 self.title.borrow().clone()
433 }
434
435 pub fn send_wheel_to_pty(&self, row: usize, col: usize, delta_y: f64) {
441 let encoding = self.parser.borrow().screen().mouse_protocol_encoding();
442 let seq = encode_wheel_event(row, col, delta_y, encoding);
443 let _ = self.write_raw(seq.as_bytes());
444 }
445
446 pub fn mouse_move(&self, row: usize, col: usize) {
457 let is_dragging = self.pressed_button.borrow().is_some();
458
459 if self.modifiers.borrow().contains(Modifiers::SHIFT) && is_dragging {
460 self.update_selection(row, col);
462 return;
463 }
464
465 let parser = self.parser.borrow();
466 let mouse_mode = parser.screen().mouse_protocol_mode();
467 let encoding = parser.screen().mouse_protocol_encoding();
468
469 let held = *self.pressed_button.borrow();
470
471 match mouse_mode {
472 vt100::MouseProtocolMode::AnyMotion => {
473 let seq = encode_mouse_move(row, col, held, encoding);
474 let _ = self.write_raw(seq.as_bytes());
475 }
476 vt100::MouseProtocolMode::ButtonMotion => {
477 if let Some(button) = held {
478 let seq = encode_mouse_move(row, col, Some(button), encoding);
479 let _ = self.write_raw(seq.as_bytes());
480 }
481 }
482 vt100::MouseProtocolMode::None => {
483 if is_dragging {
485 self.update_selection(row, col);
486 }
487 }
488 _ => {}
489 }
490 }
491
492 fn is_mouse_tracking_enabled(&self) -> bool {
494 let parser = self.parser.borrow();
495 parser.screen().mouse_protocol_mode() != vt100::MouseProtocolMode::None
496 }
497
498 pub fn mouse_down(&self, row: usize, col: usize, button: TerminalMouseButton) {
507 *self.pressed_button.borrow_mut() = Some(button);
508
509 if self.modifiers.borrow().contains(Modifiers::SHIFT) {
510 self.start_selection(row, col);
512 } else if self.is_mouse_tracking_enabled() {
513 let encoding = self.parser.borrow().screen().mouse_protocol_encoding();
514 let seq = encode_mouse_press(row, col, button, encoding);
515 let _ = self.write_raw(seq.as_bytes());
516 } else {
517 self.start_selection(row, col);
518 }
519 }
520
521 pub fn mouse_up(&self, row: usize, col: usize, button: TerminalMouseButton) {
531 *self.pressed_button.borrow_mut() = None;
532
533 if self.modifiers.borrow().contains(Modifiers::SHIFT) {
534 self.end_selection();
536 return;
537 }
538
539 let parser = self.parser.borrow();
540 let mouse_mode = parser.screen().mouse_protocol_mode();
541 let encoding = parser.screen().mouse_protocol_encoding();
542
543 match mouse_mode {
544 vt100::MouseProtocolMode::PressRelease
545 | vt100::MouseProtocolMode::ButtonMotion
546 | vt100::MouseProtocolMode::AnyMotion => {
547 let seq = encode_mouse_release(row, col, button, encoding);
548 let _ = self.write_raw(seq.as_bytes());
549 }
550 vt100::MouseProtocolMode::Press => {
551 }
553 vt100::MouseProtocolMode::None => {
554 self.end_selection();
555 }
556 }
557 }
558
559 const ALTERNATE_SCROLL_LINES: usize = 3;
561
562 pub fn release(&self) {
567 *self.pressed_button.borrow_mut() = None;
568 self.end_selection();
569 }
570
571 pub fn wheel(&self, delta_y: f64, row: usize, col: usize) {
582 let scroll_delta = if delta_y > 0.0 { 3 } else { -3 };
583 let scroll_offset = self.buffer.borrow().scroll_offset;
584 let (mouse_mode, alt_screen, app_cursor) = {
585 let parser = self.parser.borrow();
586 let screen = parser.screen();
587 (
588 screen.mouse_protocol_mode(),
589 screen.alternate_screen(),
590 screen.application_cursor(),
591 )
592 };
593
594 if scroll_offset > 0 {
595 let delta = scroll_delta;
597 self.scroll(delta);
598 } else if mouse_mode != vt100::MouseProtocolMode::None {
599 self.send_wheel_to_pty(row, col, delta_y);
601 } else if alt_screen {
602 let key = match (delta_y > 0.0, app_cursor) {
605 (true, true) => "\x1bOA",
606 (true, false) => "\x1b[A",
607 (false, true) => "\x1bOB",
608 (false, false) => "\x1b[B",
609 };
610 for _ in 0..Self::ALTERNATE_SCROLL_LINES {
611 let _ = self.write_raw(key.as_bytes());
612 }
613 } else {
614 let delta = scroll_delta;
616 self.scroll(delta);
617 }
618 }
619
620 pub fn read_buffer(&'_ self) -> Ref<'_, TerminalBuffer> {
622 self.buffer.borrow()
623 }
624
625 pub fn output_received(&self) -> impl std::future::Future<Output = ()> + '_ {
629 self.output_notifier.notified()
630 }
631
632 pub fn title_changed(&self) -> impl std::future::Future<Output = ()> + '_ {
636 self.title_notifier.notified()
637 }
638
639 pub fn last_write_elapsed(&self) -> std::time::Duration {
640 self.last_write_time.borrow().elapsed()
641 }
642
643 pub fn closed(&self) -> impl std::future::Future<Output = ()> + '_ {
656 self.closer_notifier.notified()
657 }
658
659 pub fn id(&self) -> TerminalId {
661 self.id
662 }
663
664 pub fn shift_pressed(&self, pressed: bool) {
669 let mut mods = self.modifiers.borrow_mut();
670 if pressed {
671 mods.insert(Modifiers::SHIFT);
672 } else {
673 mods.remove(Modifiers::SHIFT);
674 }
675 }
676
677 pub fn get_selection(&self) -> Option<TerminalSelection> {
679 self.buffer.borrow().selection.clone()
680 }
681
682 pub fn set_selection(&self, selection: Option<TerminalSelection>) {
684 self.buffer.borrow_mut().selection = selection;
685 }
686
687 pub fn start_selection(&self, row: usize, col: usize) {
688 let mut buffer = self.buffer.borrow_mut();
689 let scroll = buffer.scroll_offset;
690 buffer.selection = Some(TerminalSelection {
691 dragging: true,
692 start_row: row,
693 start_col: col,
694 start_scroll: scroll,
695 end_row: row,
696 end_col: col,
697 end_scroll: scroll,
698 });
699 Platform::get().send(UserEvent::RequestRedraw);
700 }
701
702 pub fn update_selection(&self, row: usize, col: usize) {
703 let mut buffer = self.buffer.borrow_mut();
704 let scroll = buffer.scroll_offset;
705 if let Some(selection) = &mut buffer.selection
706 && selection.dragging
707 {
708 selection.end_row = row;
709 selection.end_col = col;
710 selection.end_scroll = scroll;
711 Platform::get().send(UserEvent::RequestRedraw);
712 }
713 }
714
715 pub fn end_selection(&self) {
716 if let Some(selection) = &mut self.buffer.borrow_mut().selection {
717 selection.dragging = false;
718 Platform::get().send(UserEvent::RequestRedraw);
719 }
720 }
721
722 pub fn clear_selection(&self) {
724 self.buffer.borrow_mut().selection = None;
725 Platform::get().send(UserEvent::RequestRedraw);
726 }
727
728 pub fn get_selected_text(&self) -> Option<String> {
729 let buffer = self.buffer.borrow();
730 let selection = buffer.selection.clone()?;
731 if selection.is_empty() {
732 return None;
733 }
734
735 let scroll = buffer.scroll_offset;
736 let (display_start, start_col, display_end, end_col) = selection.display_positions(scroll);
737
738 let mut parser = self.parser.borrow_mut();
739 let saved_scrollback = parser.screen().scrollback();
740 let (_rows, cols) = parser.screen().size();
741
742 let mut lines = Vec::new();
743
744 for d in display_start..=display_end {
745 let cp = d - scroll as i64;
746 let needed_scrollback = (-cp).max(0) as usize;
747 let viewport_row = cp.max(0) as u16;
748
749 parser.screen_mut().set_scrollback(needed_scrollback);
750
751 let row_cells: Vec<_> = (0..cols)
752 .filter_map(|c| parser.screen().cell(viewport_row, c).cloned())
753 .collect();
754
755 let is_single = display_start == display_end;
756 let is_first = d == display_start;
757 let is_last = d == display_end;
758
759 let cells = if is_single {
760 let s = start_col.min(row_cells.len());
761 let e = end_col.min(row_cells.len());
762 &row_cells[s..e]
763 } else if is_first {
764 let s = start_col.min(row_cells.len());
765 &row_cells[s..]
766 } else if is_last {
767 &row_cells[..end_col.min(row_cells.len())]
768 } else {
769 &row_cells
770 };
771
772 let line: String = cells
773 .iter()
774 .map(|cell| {
775 if cell.has_contents() {
776 cell.contents()
777 } else {
778 " "
779 }
780 })
781 .collect::<String>();
782
783 lines.push(line);
784 }
785
786 parser.screen_mut().set_scrollback(saved_scrollback);
787
788 Some(lines.join("\n"))
789 }
790}