sapi-lite 0.1.1

A simplified wrapper around Microsoft's Speech API (SAPI) library
Documentation
use windows as Windows;
use Windows::core::{implement, IUnknown};
use Windows::Win32::Foundation::PWSTR;
use Windows::Win32::Media::Speech::{
    ISpEventSource, ISpNotifySink, ISpObjectToken, ISpRecoResult, SPEI_END_INPUT_STREAM,
    SPEI_RECOGNITION, SPEI_RESERVED1, SPEI_RESERVED2, SPET_LPARAM_IS_OBJECT,
    SPET_LPARAM_IS_POINTER, SPET_LPARAM_IS_STRING, SPET_LPARAM_IS_TOKEN, SPET_LPARAM_IS_UNDEFINED,
    SPEVENT, SPEVENTENUM, SPEVENTLPARAMTYPE,
};

use crate::com_util::{next_elem, ComBox, MaybeWeak};
use crate::token::Token;
use crate::Result;

#[derive(Debug)]
pub(crate) enum Event {
    Recognition(ISpRecoResult),
    SpeechFinished(u32),
    OtherObject(IUnknown),
    OtherToken(Token),
    OtherString(ComBox<PWSTR>),
    OtherValue(ComBox<*const std::ffi::c_void>),
    Other,
}

impl Event {
    pub fn from_sapi(sapi_event: SPEVENT) -> Result<Self> {
        use Windows::core::{Abi, Interface};

        let id = SPEVENTENUM(sapi_event._bitfield & 0xffff);
        let lparam = sapi_event.lParam.0;
        match SPEVENTLPARAMTYPE(sapi_event._bitfield >> 16) {
            SPET_LPARAM_IS_OBJECT => {
                let intf = unsafe { IUnknown::from_abi(lparam as _) }?;
                match id {
                    SPEI_RECOGNITION => Ok(Self::Recognition(intf.cast()?)),
                    _ => Ok(Self::OtherObject(intf)),
                }
            }
            SPET_LPARAM_IS_POINTER => {
                Ok(Self::OtherValue(unsafe { ComBox::from_raw(lparam as _) }))
            }
            SPET_LPARAM_IS_STRING => Ok(Self::OtherString(unsafe {
                ComBox::from_raw(PWSTR(lparam as _))
            })),
            SPET_LPARAM_IS_TOKEN => Ok(Self::OtherToken(Token::from_sapi(unsafe {
                ISpObjectToken::from_abi(lparam as _)
            }?))),
            SPET_LPARAM_IS_UNDEFINED => match id {
                SPEI_END_INPUT_STREAM => Ok(Self::SpeechFinished(sapi_event.ulStreamNum)),
                _ => Ok(Self::Other),
            },
            _ => panic!("Unrecognized SPEVENTLPARAMTYPE value"),
        }
    }
}

pub(crate) struct EventSource {
    intf: MaybeWeak<ISpEventSource>,
}

impl EventSource {
    pub(crate) fn from_sapi(intf: ISpEventSource) -> Self {
        Self {
            intf: MaybeWeak::new(intf),
        }
    }

    pub(crate) fn next_event(&self) -> Result<Option<Event>> {
        Ok(
            match unsafe { next_elem(&*self.intf, ISpEventSource::GetEvents) }? {
                Some(sapi_event) => Some(Event::from_sapi(sapi_event)?),
                None => None,
            },
        )
    }

    fn downgrade(&mut self) {
        self.intf.set_weak(true);
    }
}

#[implement(Windows::Win32::Media::Speech::ISpNotifySink)]
pub(crate) struct EventSink {
    source: EventSource,
    handler: Box<dyn Fn(Event) -> Result<()>>,
}

#[allow(non_snake_case)]
impl EventSink {
    pub(crate) fn new<F: Fn(Event) -> Result<()> + 'static>(
        source: EventSource,
        handler: F,
    ) -> Self {
        Self {
            source,
            handler: Box::new(handler),
        }
    }

    pub(crate) fn install(self, interest: Option<&[SPEVENTENUM]>) -> Result<()> {
        use windows::core::ToImpl;

        let src_intf = self.source.intf.clone();
        let sink_intf: ISpNotifySink = self.into();
        unsafe { src_intf.SetNotifySink(&sink_intf) }?;
        unsafe { Self::to_impl(&sink_intf) }.source.downgrade();

        if let Some(flags) = interest {
            let mut flags_arg = (1u64 << SPEI_RESERVED1.0) | (1u64 << SPEI_RESERVED2.0);
            for flag in flags {
                flags_arg |= 1u64 << flag.0;
            }
            unsafe { src_intf.SetInterest(flags_arg, flags_arg) }?;
        }
        Ok(())
    }

    fn Notify(&self) -> Result<()> {
        while let Some(event) = self.source.next_event()? {
            (*self.handler)(event)?
        }
        Ok(())
    }
}