arci_speak_cmd/
lib.rs
1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use std::{io, process::Command};
5
6use arci::{Speaker, WaitFuture};
7
8#[derive(Debug, Default)]
20#[non_exhaustive]
21pub struct LocalCommand {}
22
23impl LocalCommand {
24 pub fn new() -> Self {
26 Self::default()
27 }
28}
29
30impl Speaker for LocalCommand {
31 fn speak(&self, message: &str) -> Result<WaitFuture, arci::Error> {
32 let (sender, receiver) = tokio::sync::oneshot::channel();
33 let message = message.to_string();
34
35 std::thread::spawn(move || {
36 let res = run_local_command(&message).map_err(|e| arci::Error::Other(e.into()));
37 let _ = sender.send(res);
38 });
39
40 Ok(WaitFuture::new(async move {
41 receiver.await.map_err(|e| arci::Error::Other(e.into()))?
42 }))
43 }
44}
45
46#[cfg(not(windows))]
47fn run_local_command(message: &str) -> io::Result<()> {
48 #[cfg(not(target_os = "macos"))]
49 const CMD_NAME: &str = "espeak";
50 #[cfg(target_os = "macos")]
51 const CMD_NAME: &str = "say";
52
53 let mut cmd = Command::new(CMD_NAME);
54 let status = cmd.arg(message).status()?;
55
56 if status.success() {
57 Ok(())
58 } else {
59 Err(io::Error::other(format!(
60 "failed to run `{CMD_NAME}` with message {message:?}"
61 )))
62 }
63}
64
65#[cfg(windows)]
66fn run_local_command(message: &str) -> io::Result<()> {
67 let cmd = format!("PowerShell -Command \"Add-Type –AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('{message}');\"");
70 let status = Command::new("powershell").arg(cmd).status()?;
71
72 if status.success() {
73 Ok(())
74 } else {
75 Err(io::Error::other(format!(
76 "failed to run `powershell` with message {message:?}"
77 )))
78 }
79}
80
81#[cfg(test)]
82mod test {
83 use super::*;
84
85 #[test]
86 fn test_local_command() {
87 let local_command = LocalCommand::new();
88
89 let wait = local_command.speak("message");
90
91 assert!(wait.is_ok());
92 }
93}