Skip to main content

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}