systemctl_tui/
terminal.rs1use std::{
2 ops::{Deref, DerefMut},
3 sync::Arc,
4};
5
6use anyhow::{anyhow, Context, Result};
7use crossterm::{
8 cursor,
9 event::{DisableMouseCapture, EnableMouseCapture},
10 terminal::{EnterAlternateScreen, LeaveAlternateScreen},
11};
12use ratatui::backend::CrosstermBackend as Backend;
13use signal_hook::{iterator::Signals, low_level};
14use tokio::{
15 sync::{mpsc, Mutex},
16 task::JoinHandle,
17};
18
19use crate::components::{home::Home, Component};
20
21pub struct Tui {
23 pub terminal: ratatui::Terminal<Backend<std::io::Stderr>>,
24}
25
26impl Tui {
27 pub fn new() -> Result<Self> {
28 let terminal = ratatui::Terminal::new(Backend::new(std::io::stderr()))?;
29
30 let _ = std::thread::spawn(move || {
32 const SIGNALS: &[libc::c_int] = &[signal_hook::consts::signal::SIGTERM];
33 let mut sigs = Signals::new(SIGNALS).unwrap();
34 let signal = sigs.into_iter().next().unwrap();
35 let _ = exit();
36 low_level::emulate_default_handler(signal).unwrap();
37 });
38
39 Ok(Self { terminal })
40 }
41
42 pub fn enter(&self) -> Result<()> {
43 crossterm::terminal::enable_raw_mode()?;
44 crossterm::execute!(std::io::stderr(), EnterAlternateScreen, EnableMouseCapture, cursor::Hide)?;
45 Ok(())
46 }
47
48 pub fn suspend(&self) -> Result<()> {
49 self.exit()?;
50 #[cfg(not(windows))]
51 signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?;
52 Ok(())
53 }
54
55 pub fn resume(&self) -> Result<()> {
56 self.enter()?;
57 Ok(())
58 }
59
60 pub fn exit(&self) -> Result<()> {
61 exit()
62 }
63}
64
65pub fn exit() -> Result<()> {
67 crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, DisableMouseCapture, cursor::Show)?;
68 crossterm::terminal::disable_raw_mode()?;
69 Ok(())
70}
71
72impl Deref for Tui {
73 type Target = ratatui::Terminal<Backend<std::io::Stderr>>;
74
75 fn deref(&self) -> &Self::Target {
76 &self.terminal
77 }
78}
79
80impl DerefMut for Tui {
81 fn deref_mut(&mut self) -> &mut Self::Target {
82 &mut self.terminal
83 }
84}
85
86impl Drop for Tui {
87 fn drop(&mut self) {
88 exit().unwrap();
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93enum Message {
94 Render,
95 Stop,
96 Suspend,
97}
98
99pub struct TerminalHandler {
100 pub task: JoinHandle<()>,
101 tx: mpsc::UnboundedSender<Message>,
102 home: Arc<Mutex<Home>>,
103 pub tui: Arc<Mutex<Tui>>,
104}
105
106impl TerminalHandler {
107 pub fn new(home: Arc<Mutex<Home>>) -> Self {
108 let (tx, mut rx) = mpsc::unbounded_channel::<Message>();
109 let cloned_home = home.clone();
110 let tui = Tui::new().context(anyhow!("Unable to create terminal")).unwrap();
111 tui.enter().unwrap();
112 let tui = Arc::new(Mutex::new(tui));
113 let cloned_tui = tui.clone();
114 let task = tokio::spawn(async move {
115 loop {
116 match rx.recv().await {
117 Some(Message::Stop) => {
118 exit().unwrap_or_default();
119 break;
120 },
121 Some(Message::Suspend) => {
122 let t = tui.lock().await;
123 t.suspend().unwrap_or_default();
124 break;
125 },
126 Some(Message::Render) => {
127 let mut t = tui.lock().await;
128 let mut home = home.lock().await;
129 render(&mut t, &mut home);
130 },
131 None => {},
132 }
133 }
134 });
135 Self { task, tx, home: cloned_home, tui: cloned_tui }
136 }
137
138 pub fn suspend(&self) -> Result<()> {
139 self.tx.send(Message::Suspend)?;
140 Ok(())
141 }
142
143 pub fn stop(&self) -> Result<()> {
144 self.tx.send(Message::Stop)?;
145 Ok(())
146 }
147
148 pub async fn render(&self) {
149 let mut home = self.home.lock().await;
150 let mut tui = self.tui.lock().await;
151 render(&mut tui, &mut home);
152 }
153
154 pub fn enqueue_render(&self) -> Result<()> {
156 self.tx.send(Message::Render)?;
157 Ok(())
158 }
159}
160
161fn render(tui: &mut Tui, home: &mut Home) {
162 tui
163 .draw(|f| {
164 home.render(f, f.area());
165 })
166 .expect("Unable to draw");
167}