speechd_module_rs/lib.rs
1#![cfg_attr(doc, feature(doc_cfg))]
2#![cfg_attr(doc, doc(auto_cfg))]
3
4use core::ffi;
5
6use std::io;
7use std::io::Write as _;
8use std::os::fd::AsFd as _;
9use std::path;
10
11use speechd_module_sys as modapi;
12
13#[cfg(any(feature = "audio", doc))]
14pub mod audio;
15mod context;
16#[cfg(any(feature = "logger", doc))]
17mod logging;
18mod types;
19pub use types::*;
20
21
22/// Notify the speak-dispatcher server that the received utterance will be
23/// processed
24pub fn notify_speak_ok() {
25 modapi::module_speak_ok();
26}
27
28
29/// Read a single line from [`io::stdin`] with library buffering
30///
31/// In a speech-dispatch module using the `libspeechd_module` library the
32/// standard input is managed by library which uses its own input buffering.
33/// You must therefor use this function instead of the functions of
34/// [`io::stdin`] when directly reading server commands to avoid protocol
35/// errors due to partial reads ending up in the wrong buffer.
36///
37/// If `blocking` is true, this will wait for new input otherwise it will
38/// return `None` if no input is available.
39///
40/// The response will include the trailing newline.
41#[must_use]
42pub fn read_line(blocking: bool) -> Option<mbox::MArray<u8>> {
43 let line_ptr = modapi::module_readline(io::stdin().as_fd(), ffi::c_int::from(blocking));
44 (!line_ptr.is_null()).then(|| {
45 // Safety: We assume the library will either return a valid C string or `NULL`
46 unsafe { mbox::MArray::from_raw(line_ptr.cast::<u8>()) }
47 })
48}
49
50
51
52// Reimplements `module_main.c` from the speech-dispatcher distribution
53// so that we don’t have to deal with the fact that Rust doesn’t like having
54// its main be provided by another library, also improves returned errors
55// somewhat
56//
57/// Module main function, call this from your binary crate `main()`
58///
59/// Example
60/// =======
61///
62/// ```
63/// use std::env;
64///
65/// use speechd_module_rs as runtime;
66///
67///
68/// struct MyModule {
69/// // Runtime data
70/// }
71///
72/// impl runtime::Module for MyModule {
73/// // Module implementation
74/// }
75///
76/// fn main() -> runtime::Result<()> {
77/// let mut module = MyModule {};
78/// runtime::main(&mut module, &mut env::args())
79/// }
80/// ```
81pub fn main<M: Module>(module: M, args: &mut dyn Iterator<Item=String>) -> Result<()> {
82 main_inner(&mut ModuleWrapper(module), args) // Strips polymorphism over Error and Module type
83}
84
85fn main_inner(module: context::WrappedModule<'_>, args: &mut dyn Iterator<Item=String>) -> Result<()> {
86 // Set up `flexi_logger` if automatic logger management is enabled
87 #[cfg(feature = "logger")]
88 logging::init_logger()?;
89
90 // Skip over process name
91 drop(args.next());
92
93 // Handle arguments
94 let mut config_path: Option<path::PathBuf> = None;
95 if let Some(config_path_str) = args.next() {
96 config_path = Some(config_path_str.into());
97 }
98
99 // Invoke config read callback
100 module.config(
101 config_path.as_ref().map(AsRef::as_ref),
102 ).map_err(|e| Error::ModuleConfigFailed(Box::new(e)))?;
103
104 // Wait for server INIT
105 //
106 // Using the C function here, since it is also used by [`module_loop`] to
107 // and may internally buffer data.
108 if read_line(true).is_none_or(|line| line.as_ref() != b"INIT\n") {
109 return Err(Error::ProtoNoInit);
110 }
111
112 // Initialize module
113 match module.init() {
114 Ok(msg) => {
115 writeln!(io::stdout(), "299-{msg}")?;
116 writeln!(io::stdout(), "299 OK LOADED SUCCESSFULLY")?;
117 },
118
119 Err(err) => {
120 writeln!(io::stdout(), "399-{err}")?;
121 writeln!(io::stdout(), "399 ERR CANT INIT MODULE")?;
122 return Err(Error::ModuleInitFailed(Box::new(err)));
123 }
124 }
125
126 {
127 // This ensures that `module` is added to a thread-local variables
128 // where it will then be accessible using [`with_module`] from
129 // C callbacks
130 let _context = unsafe { context::Context::new(module) };
131
132 // Run module
133 if modapi::module_process(io::stdin().as_fd(), 1) != 0 {
134 log::info!("Standard input is end-of-stream");
135
136 writeln!(io::stdout(), "399 ERR MODULE CLOSED")?;
137
138 // `module_loop` will generally only return an error if it failed
139 // to read further commands
140 return Err(io::ErrorKind::BrokenPipe.into());
141 }
142 }
143
144 Ok(())
145}