1use futures::StreamExt;
2use tokio::{
3 io::{AsyncBufReadExt, BufReader},
4 task::LocalSet,
5};
6
7use std::io::{self, Write};
8
9use self::command::{Command, CommandType};
10
11use lan_mouse_ipc::{
12 AsyncFrontendEventReader, AsyncFrontendRequestWriter, ClientConfig, ClientHandle, ClientState,
13 FrontendEvent, FrontendRequest, IpcError, DEFAULT_PORT,
14};
15
16mod command;
17
18pub fn run() -> Result<(), IpcError> {
19 let runtime = tokio::runtime::Builder::new_current_thread()
20 .enable_io()
21 .enable_time()
22 .build()?;
23 runtime.block_on(LocalSet::new().run_until(async move {
24 let (rx, tx) = lan_mouse_ipc::connect_async().await?;
25 let mut cli = Cli::new(rx, tx);
26 cli.run().await
27 }))?;
28 Ok(())
29}
30
31struct Cli {
32 clients: Vec<(ClientHandle, ClientConfig, ClientState)>,
33 changed: Option<ClientHandle>,
34 rx: AsyncFrontendEventReader,
35 tx: AsyncFrontendRequestWriter,
36}
37
38impl Cli {
39 fn new(rx: AsyncFrontendEventReader, tx: AsyncFrontendRequestWriter) -> Cli {
40 Self {
41 clients: vec![],
42 changed: None,
43 rx,
44 tx,
45 }
46 }
47
48 async fn run(&mut self) -> Result<(), IpcError> {
49 let stdin = tokio::io::stdin();
50 let stdin = BufReader::new(stdin);
51 let mut stdin = stdin.lines();
52
53 self.clients = loop {
55 match self.rx.next().await {
56 Some(Ok(e)) => {
57 if let FrontendEvent::Enumerate(clients) = e {
58 break clients;
59 }
60 }
61 Some(Err(e)) => return Err(e),
62 None => return Ok(()),
63 }
64 };
65
66 loop {
67 prompt()?;
68 tokio::select! {
69 line = stdin.next_line() => {
70 let Some(line) = line? else {
71 break Ok(());
72 };
73 let cmd: Command = match line.parse() {
74 Ok(cmd) => cmd,
75 Err(e) => {
76 eprintln!("{e}");
77 continue;
78 }
79 };
80 self.execute(cmd).await?;
81 }
82 event = self.rx.next() => {
83 if let Some(event) = event {
84 self.handle_event(event?);
85 } else {
86 break Ok(());
87 }
88 }
89 }
90 if let Some(handle) = self.changed.take() {
91 self.update_client(handle).await?;
92 }
93 }
94 }
95
96 async fn update_client(&mut self, handle: ClientHandle) -> Result<(), IpcError> {
97 self.tx.request(FrontendRequest::GetState(handle)).await?;
98 while let Some(Ok(event)) = self.rx.next().await {
99 self.handle_event(event.clone());
100 if let FrontendEvent::State(_, _, _) | FrontendEvent::NoSuchClient(_) = event {
101 break;
102 }
103 }
104 Ok(())
105 }
106
107 async fn execute(&mut self, cmd: Command) -> Result<(), IpcError> {
108 match cmd {
109 Command::None => {}
110 Command::Connect(pos, host, port) => {
111 let request = FrontendRequest::Create;
112 self.tx.request(request).await?;
113 let handle = loop {
114 if let Some(Ok(event)) = self.rx.next().await {
115 match event {
116 FrontendEvent::Created(h, c, s) => {
117 self.clients.push((h, c, s));
118 break h;
119 }
120 _ => {
121 self.handle_event(event);
122 continue;
123 }
124 }
125 }
126 };
127 for request in [
128 FrontendRequest::UpdateHostname(handle, Some(host.clone())),
129 FrontendRequest::UpdatePort(handle, port.unwrap_or(DEFAULT_PORT)),
130 FrontendRequest::UpdatePosition(handle, pos),
131 ] {
132 self.tx.request(request).await?;
133 }
134 self.update_client(handle).await?;
135 }
136 Command::Disconnect(id) => {
137 self.tx.request(FrontendRequest::Delete(id)).await?;
138 loop {
139 if let Some(Ok(event)) = self.rx.next().await {
140 self.handle_event(event.clone());
141 if let FrontendEvent::Deleted(_) = event {
142 self.handle_event(event);
143 break;
144 }
145 }
146 }
147 }
148 Command::Activate(id) => {
149 self.tx.request(FrontendRequest::Activate(id, true)).await?;
150 self.update_client(id).await?;
151 }
152 Command::Deactivate(id) => {
153 self.tx
154 .request(FrontendRequest::Activate(id, false))
155 .await?;
156 self.update_client(id).await?;
157 }
158 Command::List => {
159 self.tx.request(FrontendRequest::Enumerate()).await?;
160 while let Some(e) = self.rx.next().await {
161 let event = e?;
162 self.handle_event(event.clone());
163 if let FrontendEvent::Enumerate(_) = event {
164 break;
165 }
166 }
167 }
168 Command::SetHost(handle, host) => {
169 let request = FrontendRequest::UpdateHostname(handle, Some(host.clone()));
170 self.tx.request(request).await?;
171 self.update_client(handle).await?;
172 }
173 Command::SetPort(handle, port) => {
174 let request = FrontendRequest::UpdatePort(handle, port.unwrap_or(DEFAULT_PORT));
175 self.tx.request(request).await?;
176 self.update_client(handle).await?;
177 }
178 Command::Help => {
179 for cmd_type in [
180 CommandType::List,
181 CommandType::Connect,
182 CommandType::Disconnect,
183 CommandType::Activate,
184 CommandType::Deactivate,
185 CommandType::SetHost,
186 CommandType::SetPort,
187 ] {
188 eprintln!("{}", cmd_type.usage());
189 }
190 }
191 }
192 Ok(())
193 }
194
195 fn find_mut(
196 &mut self,
197 handle: ClientHandle,
198 ) -> Option<&mut (ClientHandle, ClientConfig, ClientState)> {
199 self.clients.iter_mut().find(|(h, _, _)| *h == handle)
200 }
201
202 fn remove(
203 &mut self,
204 handle: ClientHandle,
205 ) -> Option<(ClientHandle, ClientConfig, ClientState)> {
206 let idx = self.clients.iter().position(|(h, _, _)| *h == handle);
207 idx.map(|i| self.clients.swap_remove(i))
208 }
209
210 fn handle_event(&mut self, event: FrontendEvent) {
211 match event {
212 FrontendEvent::Changed(h) => self.changed = Some(h),
213 FrontendEvent::Created(h, c, s) => {
214 eprint!("client added ({h}): ");
215 print_config(&c);
216 eprint!(" ");
217 print_state(&s);
218 eprintln!();
219 self.clients.push((h, c, s));
220 }
221 FrontendEvent::NoSuchClient(h) => {
222 eprintln!("no such client: {h}");
223 }
224 FrontendEvent::State(h, c, s) => {
225 if let Some((_, config, state)) = self.find_mut(h) {
226 let old_host = config.hostname.clone().unwrap_or("\"\"".into());
227 let new_host = c.hostname.clone().unwrap_or("\"\"".into());
228 if old_host != new_host {
229 eprintln!(
230 "client {h}: hostname updated ({} -> {})",
231 old_host, new_host
232 );
233 }
234 if config.port != c.port {
235 eprintln!("client {h} changed port: {} -> {}", config.port, c.port);
236 }
237 if config.fix_ips != c.fix_ips {
238 eprintln!("client {h} ips updated: {:?}", c.fix_ips)
239 }
240 *config = c;
241 if state.active ^ s.active {
242 eprintln!(
243 "client {h} {}",
244 if s.active { "activated" } else { "deactivated" }
245 );
246 }
247 *state = s;
248 }
249 }
250 FrontendEvent::Deleted(h) => {
251 if let Some((h, c, _)) = self.remove(h) {
252 eprint!("client {h} removed (");
253 print_config(&c);
254 eprintln!(")");
255 }
256 }
257 FrontendEvent::PortChanged(p, e) => {
258 if let Some(e) = e {
259 eprintln!("failed to change port: {e}");
260 } else {
261 eprintln!("changed port to {p}");
262 }
263 }
264 FrontendEvent::Enumerate(clients) => {
265 self.clients = clients;
266 self.print_clients();
267 }
268 FrontendEvent::Error(e) => {
269 eprintln!("ERROR: {e}");
270 }
271 FrontendEvent::CaptureStatus(s) => {
272 eprintln!("capture status: {s:?}")
273 }
274 FrontendEvent::EmulationStatus(s) => {
275 eprintln!("emulation status: {s:?}")
276 }
277 }
278 }
279
280 fn print_clients(&mut self) {
281 for (h, c, s) in self.clients.iter() {
282 eprint!("client {h}: ");
283 print_config(c);
284 eprint!(" ");
285 print_state(s);
286 eprintln!();
287 }
288 }
289}
290
291fn prompt() -> io::Result<()> {
292 eprint!("lan-mouse > ");
293 std::io::stderr().flush()?;
294 Ok(())
295}
296
297fn print_config(c: &ClientConfig) {
298 eprint!(
299 "{}:{} ({}), ips: {:?}",
300 c.hostname.clone().unwrap_or("(no hostname)".into()),
301 c.port,
302 c.pos,
303 c.fix_ips
304 );
305}
306
307fn print_state(s: &ClientState) {
308 eprint!("active: {}, dns: {:?}", s.active, s.ips);
309}