mprocs/
proc.rs

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        // Block until program exits
104        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      // Get current parser dimensions and scrollback capacity
425      let screen = vt.screen();
426      let size = screen.size();
427      let scrollback_len = screen.scrollback_len();
428
429      // Reinitialize the parser to clear both visible area and scrollback
430      *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}