Rust · 3116 bytes Raw Blame History
1 //! garcalcctl - Control utility for garcalc
2 //!
3 //! Sends commands to the running garcalc daemon via IPC.
4
5 use std::io::{BufRead, BufReader, Write};
6 use std::os::unix::net::UnixStream;
7
8 use anyhow::{Context, Result};
9 use clap::{Parser, Subcommand};
10 use garcalc_ipc::{Command, Mode, Response, socket_path};
11
12 #[derive(Parser)]
13 #[command(name = "garcalcctl")]
14 #[command(about = "Control utility for garcalc")]
15 #[command(version)]
16 struct Args {
17 #[command(subcommand)]
18 command: CtlCommand,
19 }
20
21 #[derive(Subcommand)]
22 enum CtlCommand {
23 /// Show the calculator window
24 Show,
25 /// Hide the calculator window
26 Hide,
27 /// Toggle window visibility
28 Toggle,
29 /// Evaluate an expression
30 Eval {
31 /// Expression to evaluate
32 expr: String,
33 },
34 /// Get or set calculator mode
35 Mode {
36 /// Mode to set (calculator, graph, geometry, spreadsheet, notes)
37 #[arg(value_name = "MODE")]
38 mode: Option<String>,
39 },
40 /// Get daemon status
41 Status,
42 /// Quit the daemon
43 Quit,
44 }
45
46 fn main() -> Result<()> {
47 let args = Args::parse();
48
49 let command = match args.command {
50 CtlCommand::Show => Command::Show,
51 CtlCommand::Hide => Command::Hide,
52 CtlCommand::Toggle => Command::Toggle,
53 CtlCommand::Eval { expr } => Command::Evaluate { expr },
54 CtlCommand::Mode { mode: None } => Command::GetMode,
55 CtlCommand::Mode { mode: Some(m) } => {
56 let mode = match m.to_lowercase().as_str() {
57 "calc" | "calculator" => Mode::Calculator,
58 "graph" | "graphing" => Mode::Graph,
59 "geo" | "geometry" => Mode::Geometry,
60 "sheet" | "spreadsheet" => Mode::Spreadsheet,
61 "notes" | "note" => Mode::Notes,
62 _ => {
63 eprintln!("Unknown mode: {m}");
64 eprintln!("Valid modes: calculator, graph, geometry, spreadsheet, notes");
65 std::process::exit(1);
66 }
67 };
68 Command::SetMode { mode }
69 }
70 CtlCommand::Status => Command::Status,
71 CtlCommand::Quit => Command::Quit,
72 };
73
74 let response = send_command(&command)?;
75
76 if response.success {
77 if let Some(data) = response.data {
78 println!("{}", serde_json::to_string_pretty(&data)?);
79 }
80 } else if let Some(error) = response.error {
81 eprintln!("Error: {error}");
82 std::process::exit(1);
83 }
84
85 Ok(())
86 }
87
88 fn send_command(command: &Command) -> Result<Response> {
89 let socket = socket_path();
90 let mut stream = UnixStream::connect(&socket)
91 .with_context(|| format!("Failed to connect to garcalc at {}", socket.display()))?;
92
93 let json = serde_json::to_string(command)?;
94 writeln!(stream, "{json}")?;
95 stream.flush()?;
96
97 let mut reader = BufReader::new(stream);
98 let mut response_line = String::new();
99 reader.read_line(&mut response_line)?;
100
101 let response: Response =
102 serde_json::from_str(&response_line).context("Failed to parse response from garcalc")?;
103
104 Ok(response)
105 }
106