1use std::fmt::Debug;
2use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
3use std::sync::{Arc, RwLock};
4use std::thread::{self, spawn};
5use std::time::Duration;
6
7use assert_matches::assert_matches;
8use crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
9use portable_pty::MasterPty;
10use portable_pty::{native_pty_system, ChildKiller, CommandBuilder, PtySize};
11use serde::{Deserialize, Serialize};
12use tokio::sync::mpsc::UnboundedSender;
13use tokio::task::spawn_blocking;
14use tui::layout::Rect;
15use vt100::MouseProtocolMode;
16
17use crate::config::{Config, ProcConfig};
18use crate::encode_term::{encode_key, encode_mouse_event, KeyCodeEncodeModes};
19use crate::error::ResultLogger;
20use crate::key::Key;
21
22pub struct Inst {
23 pub vt: VtWrap,
24
25 pub pid: u32,
26 pub master: Box<dyn MasterPty + Send>,
27 pub killer: Box<dyn ChildKiller + Send + Sync>,
28
29 pub running: Arc<AtomicBool>,
30}
31
32impl Debug for Inst {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 f.debug_struct("Inst")
35 .field("pid", &self.pid)
36 .field("running", &self.running)
37 .finish()
38 }
39}
40
41pub type VtWrap = Arc<RwLock<vt100::Parser>>;
42
43impl Inst {
44 fn spawn(
45 id: usize,
46 cmd: CommandBuilder,
47 tx: UnboundedSender<(usize, ProcUpdate)>,
48 size: &Size,
49 ) -> anyhow::Result<Self> {
50 let vt = vt100::Parser::new(size.height, size.width, 1000);
51 let vt = Arc::new(RwLock::new(vt));
52
53 let pty_system = native_pty_system();
54 let pair = pty_system.openpty(PtySize {
55 rows: size.height,
56 cols: size.width,
57 pixel_width: 0,
58 pixel_height: 0,
59 })?;
60
61 let running = Arc::new(AtomicBool::new(true));
62 let mut child = pair.slave.spawn_command(cmd)?;
63 let pid = child.process_id().unwrap_or(0);
64 let killer = child.clone_killer();
65
66 let mut reader = pair.master.try_clone_reader().unwrap();
67
68 {
69 let tx = tx.clone();
70 let vt = vt.clone();
71 let running = running.clone();
72 spawn_blocking(move || {
73 let mut buf = [0; 4 * 1024];
74 loop {
75 if !running.load(Ordering::Relaxed) {
76 break;
77 }
78
79 match reader.read(&mut buf[..]) {
80 Ok(count) => {
81 if count > 0 {
82 if let Ok(mut vt) = vt.write() {
83 vt.process(&buf[..count]);
84 match tx.send((id, ProcUpdate::Render)) {
85 Ok(_) => (),
86 Err(_) => break,
87 }
88 }
89 } else {
90 thread::sleep(Duration::from_millis(10));
91 }
92 }
93 _ => break,
94 }
95 }
96 });
97 }
98
99 {
100 let tx = tx.clone();
101 let running = running.clone();
102 spawn(move || {
103 let _status = child.wait();
105 running.store(false, Ordering::Relaxed);
106 let _result = tx.send((id, ProcUpdate::Stopped));
107 });
108 }
109
110 let inst = Inst {
111 vt,
112
113 pid,
114 master: pair.master,
115 killer,
116
117 running,
118 };
119 Ok(inst)
120 }
121
122 fn resize(&self, size: &Size) {
123 let rows = size.height;
124 let cols = size.width;
125
126 self
127 .master
128 .resize(PtySize {
129 rows,
130 cols,
131 pixel_width: 0,
132 pixel_height: 0,
133 })
134 .log_ignore();
135
136 if let Ok(mut vt) = self.vt.write() {
137 vt.set_size(rows, cols);
138 }
139 }
140}
141
142pub struct Proc {
143 pub id: usize,
144 pub name: String,
145 pub to_restart: bool,
146 pub changed: bool,
147 pub cmd: CommandBuilder,
148 size: Size,
149
150 stop_signal: StopSignal,
151
152 pub tx: UnboundedSender<(usize, ProcUpdate)>,
153
154 pub inst: ProcState,
155 pub copy_mode: CopyMode,
156}
157
158static NEXT_PROC_ID: AtomicUsize = AtomicUsize::new(1);
159
160#[derive(Debug)]
161pub enum ProcState {
162 None,
163 Some(Inst),
164 Error(String),
165}
166
167#[derive(Debug)]
168pub enum ProcUpdate {
169 Render,
170 Stopped,
171 Started,
172}
173
174#[derive(Clone, Debug, Deserialize)]
175#[serde(rename_all = "kebab-case")]
176pub enum StopSignal {
177 #[serde(rename = "SIGINT")]
178 SIGINT,
179 #[serde(rename = "SIGTERM")]
180 SIGTERM,
181 #[serde(rename = "SIGKILL")]
182 SIGKILL,
183 SendKeys(Vec<Key>),
184 HardKill,
185}
186
187impl Default for StopSignal {
188 fn default() -> Self {
189 StopSignal::SIGTERM
190 }
191}
192
193impl Proc {
194 pub fn new(
195 name: String,
196 cfg: &ProcConfig,
197 tx: UnboundedSender<(usize, ProcUpdate)>,
198 size: Rect,
199 ) -> Self {
200 let id = NEXT_PROC_ID.fetch_add(1, Ordering::Relaxed);
201 let size = Size::new(size);
202 let mut proc = Proc {
203 id,
204 name,
205 to_restart: false,
206 changed: false,
207 cmd: cfg.into(),
208 size,
209
210 stop_signal: cfg.stop.clone(),
211
212 tx,
213
214 inst: ProcState::None,
215 copy_mode: CopyMode::None(None),
216 };
217
218 if cfg.autostart {
219 proc.spawn_new_inst();
220 }
221
222 proc
223 }
224
225 fn spawn_new_inst(&mut self) {
226 assert_matches!(self.inst, ProcState::None);
227
228 let spawned =
229 Inst::spawn(self.id, self.cmd.clone(), self.tx.clone(), &self.size);
230 let inst = match spawned {
231 Ok(inst) => ProcState::Some(inst),
232 Err(err) => ProcState::Error(err.to_string()),
233 };
234 self.inst = inst;
235 }
236
237 pub fn start(&mut self) {
238 if !self.is_up() {
239 self.inst = ProcState::None;
240 self.spawn_new_inst();
241
242 let _res = self.tx.send((self.id, ProcUpdate::Started));
243 }
244 }
245
246 pub fn is_up(&self) -> bool {
247 if let ProcState::Some(inst) = &self.inst {
248 inst.running.load(Ordering::Relaxed)
249 } else {
250 false
251 }
252 }
253
254 pub fn lock_vt(
255 &self,
256 ) -> Option<std::sync::RwLockReadGuard<'_, vt100::Parser>> {
257 match &self.inst {
258 ProcState::None => None,
259 ProcState::Some(inst) => inst.vt.read().ok(),
260 ProcState::Error(_) => None,
261 }
262 }
263
264 pub fn lock_vt_mut(
265 &mut self,
266 ) -> Option<std::sync::RwLockWriteGuard<'_, vt100::Parser>> {
267 match &self.inst {
268 ProcState::None => None,
269 ProcState::Some(inst) => inst.vt.write().ok(),
270 ProcState::Error(_) => None,
271 }
272 }
273
274 pub fn kill(&mut self) {
275 if self.is_up() {
276 if let ProcState::Some(inst) = &mut self.inst {
277 let _result = inst.killer.kill();
278 }
279 }
280 }
281
282 #[cfg(not(windows))]
283 pub fn stop(&mut self) {
284 match self.stop_signal.clone() {
285 StopSignal::SIGINT => self.send_signal(libc::SIGINT),
286 StopSignal::SIGTERM => self.send_signal(libc::SIGTERM),
287 StopSignal::SIGKILL => self.send_signal(libc::SIGKILL),
288 StopSignal::SendKeys(keys) => {
289 for key in keys {
290 self.send_key(&key);
291 }
292 }
293 StopSignal::HardKill => self.kill(),
294 }
295 }
296
297 #[cfg(windows)]
298 pub fn stop(&mut self) {
299 match self.stop_signal.clone() {
300 StopSignal::SIGINT => log::warn!("SIGINT signal is ignored on Windows"),
301 StopSignal::SIGTERM => self.kill(),
302 StopSignal::SIGKILL => self.kill(),
303 StopSignal::SendKeys(keys) => {
304 for key in keys {
305 self.send_key(&key);
306 }
307 }
308 StopSignal::HardKill => self.kill(),
309 }
310 }
311
312 pub fn rename(&mut self, name: &str) {
313 self.name.replace_range(.., &name);
314 }
315
316 #[cfg(not(windows))]
317 fn send_signal(&mut self, sig: libc::c_int) {
318 if let ProcState::Some(inst) = &self.inst {
319 unsafe { libc::kill(inst.pid as i32, sig) };
320 }
321 }
322
323 pub fn resize(&mut self, size: Rect) {
324 let size = Size::new(size);
325 if let ProcState::Some(inst) = &self.inst {
326 inst.resize(&size);
327 }
328 self.size = size;
329 }
330
331 pub fn send_key(&mut self, key: &Key) {
332 if self.is_up() {
333 let application_cursor_keys = self
334 .lock_vt()
335 .map_or(false, |vt| vt.screen().application_cursor());
336 let encoder = encode_key(
337 key,
338 KeyCodeEncodeModes {
339 enable_csi_u_key_encoding: true,
340 application_cursor_keys,
341 newline_mode: false,
342 },
343 );
344 match encoder {
345 Ok(encoder) => {
346 self.write_all(encoder.as_bytes());
347 }
348 Err(_) => {
349 log::warn!("Failed to encode key: {}", key.to_string());
350 }
351 }
352 }
353 }
354
355 pub fn write_all(&mut self, bytes: &[u8]) {
356 if self.is_up() {
357 if let Some(mut vt) = self.lock_vt_mut() {
358 if vt.screen().scrollback() > 0 {
359 vt.set_scrollback(0);
360 }
361 }
362 if let ProcState::Some(inst) = &mut self.inst {
363 inst.master.write_all(bytes).log_ignore();
364 }
365 }
366 }
367
368 pub fn scroll_up_lines(&mut self, n: usize) {
369 match &mut self.copy_mode {
370 CopyMode::None(_) => {
371 if let Some(mut vt) = self.lock_vt_mut() {
372 Self::scroll_vt_up(&mut vt, n);
373 }
374 }
375 CopyMode::Start(screen, _) | CopyMode::Range(screen, _, _) => {
376 Self::scroll_screen_up(screen, n)
377 }
378 }
379 }
380
381 fn scroll_vt_up(vt: &mut vt100::Parser, n: usize) {
382 let pos = usize::saturating_add(vt.screen().scrollback(), n);
383 vt.set_scrollback(pos);
384 }
385
386 fn scroll_screen_up(screen: &mut vt100::Screen, n: usize) {
387 let pos = usize::saturating_add(screen.scrollback(), n);
388 screen.set_scrollback(pos);
389 }
390
391 pub fn scroll_down_lines(&mut self, n: usize) {
392 match &mut self.copy_mode {
393 CopyMode::None(_) => {
394 if let Some(mut vt) = self.lock_vt_mut() {
395 Self::scroll_vt_down(&mut vt, n);
396 }
397 }
398 CopyMode::Start(screen, _) | CopyMode::Range(screen, _, _) => {
399 Self::scroll_screen_down(screen, n)
400 }
401 }
402 }
403
404 fn scroll_vt_down(vt: &mut vt100::Parser, n: usize) {
405 let pos = usize::saturating_sub(vt.screen().scrollback(), n);
406 vt.set_scrollback(pos);
407 }
408
409 fn scroll_screen_down(screen: &mut vt100::Screen, n: usize) {
410 let pos = usize::saturating_sub(screen.scrollback(), n);
411 screen.set_scrollback(pos);
412 }
413
414 pub fn scroll_half_screen_up(&mut self) {
415 self.scroll_up_lines(self.size.height as usize / 2);
416 }
417
418 pub fn scroll_half_screen_down(&mut self) {
419 self.scroll_down_lines(self.size.height as usize / 2);
420 }
421
422 pub fn clear_buffer(&mut self) {
423 if let Some(mut vt) = self.lock_vt_mut() {
424 let screen = vt.screen();
426 let size = screen.size();
427 let scrollback_len = screen.scrollback_len();
428
429 *vt = vt100::Parser::new(size.0, size.1, scrollback_len);
431 }
432 }
433
434 pub fn handle_mouse(
435 &mut self,
436 event: MouseEvent,
437 term_area: Rect,
438 config: &Config,
439 ) {
440 let copy_mode = match self.copy_mode {
441 CopyMode::None(_) => false,
442 CopyMode::Start(_, _) | CopyMode::Range(_, _, _) => true,
443 };
444 let mouse_mode = self
445 .lock_vt()
446 .map(|vt| vt.screen().mouse_protocol_mode())
447 .unwrap_or_default();
448
449 if copy_mode {
450 match event.kind {
451 MouseEventKind::Down(btn) => match btn {
452 MouseButton::Left => {
453 let scrollback = match &self.copy_mode {
454 CopyMode::None(_) => unreachable!(),
455 CopyMode::Start(screen, _) | CopyMode::Range(screen, _, _) => {
456 screen.scrollback()
457 }
458 };
459 self.copy_mode = CopyMode::None(Some(translate_mouse_pos(
460 &event, &term_area, scrollback,
461 )));
462 }
463 MouseButton::Right => {
464 self.copy_mode = match std::mem::take(&mut self.copy_mode) {
465 CopyMode::None(_) => unreachable!(),
466 CopyMode::Start(screen, start)
467 | CopyMode::Range(screen, start, _) => {
468 let pos =
469 translate_mouse_pos(&event, &term_area, screen.scrollback());
470 CopyMode::Range(screen, start, pos)
471 }
472 };
473 }
474 MouseButton::Middle => (),
475 },
476 MouseEventKind::Up(_) => (),
477 MouseEventKind::Drag(MouseButton::Left) => {
478 self.copy_mode = match std::mem::take(&mut self.copy_mode) {
479 CopyMode::None(_) => unreachable!(),
480 CopyMode::Start(screen, start)
481 | CopyMode::Range(screen, start, _) => {
482 let pos =
483 translate_mouse_pos(&event, &term_area, screen.scrollback());
484 CopyMode::Range(screen, start, pos)
485 }
486 };
487 }
488 MouseEventKind::Drag(_) => (),
489 MouseEventKind::Moved => (),
490 MouseEventKind::ScrollDown => match &mut self.copy_mode {
491 CopyMode::None(_) => unreachable!(),
492 CopyMode::Start(screen, _) | CopyMode::Range(screen, _, _) => {
493 Self::scroll_screen_down(screen, config.mouse_scroll_speed);
494 }
495 },
496 MouseEventKind::ScrollUp => match &mut self.copy_mode {
497 CopyMode::None(_) => unreachable!(),
498 CopyMode::Start(screen, _) | CopyMode::Range(screen, _, _) => {
499 Self::scroll_screen_up(screen, config.mouse_scroll_speed);
500 }
501 },
502 }
503 } else {
504 if let ProcState::Some(inst) = &mut self.inst {
505 match mouse_mode {
506 MouseProtocolMode::None => match event.kind {
507 MouseEventKind::Down(btn) => match btn {
508 MouseButton::Left => {
509 if let Some(vt) = inst.vt.read().log_get() {
510 self.copy_mode = CopyMode::None(Some(translate_mouse_pos(
511 &event,
512 &term_area,
513 vt.screen().scrollback(),
514 )));
515 }
516 }
517 MouseButton::Right | MouseButton::Middle => (),
518 },
519 MouseEventKind::Up(_) => (),
520 MouseEventKind::Drag(MouseButton::Left) => {
521 if let Some(vt) = inst.vt.read().log_get() {
522 let pos = translate_mouse_pos(
523 &event,
524 &term_area,
525 vt.screen().scrollback(),
526 );
527 self.copy_mode = match std::mem::take(&mut self.copy_mode) {
528 CopyMode::None(pos_) => CopyMode::Range(
529 vt.screen().clone(),
530 pos_.unwrap_or_default(),
531 pos,
532 ),
533 CopyMode::Start(..) | CopyMode::Range(..) => {
534 unreachable!()
535 }
536 };
537 }
538 }
539 MouseEventKind::Drag(_) => (),
540 MouseEventKind::Moved => (),
541 MouseEventKind::ScrollDown => {
542 if let Some(mut vt) = inst.vt.write().log_get() {
543 Self::scroll_vt_down(&mut vt, config.mouse_scroll_speed);
544 }
545 }
546 MouseEventKind::ScrollUp => {
547 if let Some(mut vt) = inst.vt.write().log_get() {
548 Self::scroll_vt_up(&mut vt, config.mouse_scroll_speed);
549 }
550 }
551 },
552 MouseProtocolMode::Press
553 | MouseProtocolMode::PressRelease
554 | MouseProtocolMode::ButtonMotion
555 | MouseProtocolMode::AnyMotion => {
556 let ev = MouseEvent {
557 kind: event.kind,
558 column: event.column - term_area.x,
559 row: event.row - term_area.y,
560 modifiers: event.modifiers,
561 };
562 let seq = encode_mouse_event(ev);
563 let _r = inst.master.write_all(seq.as_bytes());
564 }
565 }
566 }
567 }
568 }
569}
570
571fn translate_mouse_pos(
572 event: &MouseEvent,
573 area: &Rect,
574 scrollback: usize,
575) -> Pos {
576 let x = (event.column - area.x) as i32;
577 let y = (event.row - area.y) as i32 - scrollback as i32;
578 Pos { y, x }
579}
580
581struct Size {
582 width: u16,
583 height: u16,
584}
585
586impl Size {
587 fn new(rect: Rect) -> Size {
588 Size {
589 width: rect.width.max(3),
590 height: rect.height.max(3),
591 }
592 }
593}
594
595pub enum CopyMode {
596 None(Option<Pos>),
597 Start(vt100::Screen, Pos),
598 Range(vt100::Screen, Pos, Pos),
599}
600
601impl Default for CopyMode {
602 fn default() -> Self {
603 CopyMode::None(None)
604 }
605}
606
607#[derive(
608 Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize,
609)]
610pub struct Pos {
611 pub y: i32,
612 pub x: i32,
613}
614
615impl Pos {
616 pub fn to_low_high<'a>(a: &'a Self, b: &'a Self) -> (&'a Self, &'a Self) {
617 if a.y > b.y {
618 return (b, a);
619 } else if a.y == b.y && a.x > b.x {
620 return (b, a);
621 }
622 (a, b)
623 }
624
625 pub fn within(start: &Self, end: &Self, target: &Self) -> bool {
626 let y = target.y;
627 let x = target.x;
628 let (low, high) = Pos::to_low_high(start, end);
629
630 if y > low.y {
631 if y < high.y {
632 true
633 } else if y == high.y && x <= high.x {
634 true
635 } else {
636 false
637 }
638 } else if y == low.y {
639 if y < high.y {
640 x >= low.x
641 } else if y == high.y {
642 x >= low.x && x <= high.x
643 } else {
644 false
645 }
646 } else {
647 false
648 }
649 }
650}