1
use std::{
2
    io::{self, Read as _},
3
    os::fd::AsFd,
4
};
5

            
6
use anyhow::Context;
7
use clap::{CommandFactory, Parser};
8
use clap_complete::{Shell, generate};
9

            
10
use nix::unistd;
11
use roowho2_lib::{
12
    server::varlink_api::{
13
        DEFAULT_CLIENT_SERVER_SOCKET_PATH, VarlinkWalldClientProxy, VarlinkWalldClientResponse,
14
    },
15
    version,
16
};
17

            
18
/// Send a message to another user
19
#[derive(Debug, Parser)]
20
#[command(
21
  author = "Programvareverkstedet <projects@pvv.ntnu.no>",
22
  version,
23
  long_version = version::LONG_VERSION
24
)]
25
pub struct Args {
26
    /// User to send the message to
27
    #[arg(value_name = "USER", required_unless_present = "completions")]
28
    user: Option<String>,
29

            
30
    /// The tty to send the message to
31
    ttyname: Option<String>,
32

            
33
    /// Generate shell completion scripts for the specified shell
34
    /// and print them to stdout.
35
    #[arg(long, value_enum, hide = true)]
36
    completions: Option<Shell>,
37
}
38

            
39
#[tokio::main(flavor = "current_thread")]
40
async fn main() -> anyhow::Result<()> {
41
    let args = Args::parse();
42

            
43
    if let Some(shell) = args.completions {
44
        generate(shell, &mut Args::command(), "write", &mut std::io::stdout());
45
        return Ok(());
46
    }
47

            
48
    let mut message = String::new();
49
    std::io::stdin()
50
        .read_to_string(&mut message)
51
        .context("failed to read message from stdin")?;
52

            
53
    let mut conn = zlink::tokio::unix::connect(DEFAULT_CLIENT_SERVER_SOCKET_PATH)
54
        .await
55
        .with_context(|| {
56
            format!("failed to connect to roowho2 at {DEFAULT_CLIENT_SERVER_SOCKET_PATH}")
57
        })?;
58

            
59
    let user = args
60
        .user
61
        .expect("required_unless_present=completions guarantees this is set");
62

            
63
    let stdin = io::stdin();
64
    let source_tty = if !unistd::isatty(stdin.as_fd()).unwrap_or(false) {
65
        None
66
    } else {
67
        // TODO: should we send this as a CStr maybe?
68
        unistd::ttyname(stdin.as_fd())
69
            .ok()
70
            .map(|p| p.to_string_lossy().to_string())
71
    };
72

            
73
    VarlinkWalldClientProxy::write(&mut conn, source_tty, user, args.ttyname, message)
74
        .await
75
        .context("varlink call to walld failed")?
76
        .map_err(|err| anyhow::format_err!("{err}"))
77
        .and_then(|res| {
78
            if let VarlinkWalldClientResponse::Write(r) = res {
79
                Ok(r)
80
            } else {
81
                Err(anyhow::format_err!(
82
                    "unexpected response from walld: {:?}",
83
                    res
84
                ))
85
            }
86
        })?;
87

            
88
    Ok(())
89
}