winprint 0.2.0

A crate for printing to a Windows printer device using Windows API
Documentation
use std::mem::ManuallyDrop;
use windows::core::w;
use windows::core::BSTR;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Foundation::E_INVALIDARG;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Foundation::{
    DISP_E_BADPARAMCOUNT, DISP_E_EXCEPTION, DISP_E_MEMBERNOTFOUND, DISP_E_TYPEMISMATCH,
    DISP_E_UNKNOWNNAME,
};
use windows::Win32::Storage::Xps::Printing::PrintDocumentPackageCompletion_Canceled;
use windows::Win32::Storage::Xps::Printing::PrintDocumentPackageCompletion_Completed;
use windows::Win32::Storage::Xps::Printing::PrintDocumentPackageCompletion_Failed;
use windows::Win32::System::Com::DISPATCH_METHOD;
use windows::Win32::System::Ole::DISPID_UNKNOWN;
use windows::Win32::System::Threading::CreateEventW;
use windows::Win32::System::Threading::SetEvent;
use windows::Win32::System::Threading::WaitForSingleObject;
use windows::Win32::System::Threading::INFINITE;
use windows::Win32::System::Variant::VT_BYREF;
use windows::{
    core::implement,
    Win32::{
        Foundation::E_NOTIMPL,
        Storage::Xps::Printing::{
            IPrintDocumentPackageStatusEvent, IPrintDocumentPackageStatusEvent_Impl,
            PrintDocumentPackageStatus,
        },
        System::Com::{
            IDispatch, IDispatch_Impl, ITypeInfo, DISPATCH_FLAGS, DISPPARAMS, EXCEPINFO,
        },
    },
};

#[implement(IPrintDocumentPackageStatusEvent, IDispatch)]
pub struct PrintCompletionSource {
    event: HANDLE,
}

impl PrintCompletionSource {
    pub fn new() -> windows::core::Result<Self> {
        let event = unsafe { CreateEventW(None, true, false, None)? };
        Ok(Self { event })
    }
    pub fn waiter(&self) -> PrintCompletionWaiter {
        PrintCompletionWaiter { event: self.event }
    }
}

pub struct PrintCompletionWaiter {
    event: HANDLE,
}

impl PrintCompletionWaiter {
    pub fn wait(&self) {
        unsafe {
            let _ = WaitForSingleObject(self.event, INFINITE);
        }
    }
}

impl Drop for PrintCompletionSource {
    fn drop(&mut self) {
        unsafe {
            let _ = CloseHandle(self.event);
        }
    }
}

impl IPrintDocumentPackageStatusEvent_Impl for PrintCompletionSource_Impl {
    fn PackageStatusUpdated(
        &self,
        packagestatus: *const PrintDocumentPackageStatus,
    ) -> windows::core::Result<()> {
        unsafe {
            if packagestatus.is_null() {
                return Err(windows::core::Error::from_hresult(E_INVALIDARG));
            }
            let status = &*packagestatus;
            match status.Completion {
                #[allow(non_upper_case_globals)]
                PrintDocumentPackageCompletion_Canceled
                | PrintDocumentPackageCompletion_Completed
                | PrintDocumentPackageCompletion_Failed => SetEvent(self.event)?,
                _ => {}
            }
        }
        Ok(())
    }
}

impl IDispatch_Impl for PrintCompletionSource_Impl {
    fn GetTypeInfoCount(&self) -> windows::core::Result<u32> {
        Ok(0)
    }
    fn GetTypeInfo(&self, _itinfo: u32, _lcid: u32) -> windows::core::Result<ITypeInfo> {
        Err(windows::core::Error::from_hresult(E_NOTIMPL))
    }
    fn GetIDsOfNames(
        &self,
        _riid: *const windows::core::GUID,
        rgsznames: *const windows::core::PCWSTR,
        cnames: u32,
        _lcid: u32,
        rgdispid: *mut i32,
    ) -> windows::core::Result<()> {
        let names = unsafe { std::slice::from_raw_parts(rgsznames, cnames as usize) };
        let dispid = unsafe { std::slice::from_raw_parts_mut(rgdispid, cnames as usize) };
        let mut unknown_found = false;
        for (name, dispid) in names.iter().zip(dispid.iter_mut()) {
            if *name == w!("PackageStatusUpdated") {
                *dispid = 1;
            } else {
                *dispid = DISPID_UNKNOWN;
                unknown_found = true;
            }
        }
        if unknown_found {
            Err(windows::core::Error::from_hresult(DISP_E_UNKNOWNNAME))
        } else {
            Ok(())
        }
    }
    fn Invoke(
        &self,
        dispidmember: i32,
        _riid: *const windows::core::GUID,
        _lcid: u32,
        wflags: DISPATCH_FLAGS,
        pdispparams: *const DISPPARAMS,
        pvarresult: *mut windows::core::VARIANT,
        pexcepinfo: *mut EXCEPINFO,
        puargerr: *mut u32,
    ) -> windows::core::Result<()> {
        if dispidmember == 1 && wflags == DISPATCH_METHOD {
            if !pvarresult.is_null() {
                unsafe {
                    (*pvarresult) = Default::default();
                }
            }
            let params = unsafe { &*pdispparams };
            if params.cArgs != 1 {
                return Err(windows::core::Error::from_hresult(DISP_E_BADPARAMCOUNT));
            }
            let arg = unsafe { &*params.rgvarg };
            let vt = unsafe { arg.as_raw().Anonymous.Anonymous.vt };
            if vt & VT_BYREF.0 == 0 {
                if !puargerr.is_null() {
                    unsafe {
                        *puargerr = 0;
                    }
                }
                return Err(windows::core::Error::from_hresult(DISP_E_TYPEMISMATCH));
            }
            let status = unsafe { arg.as_raw().Anonymous.Anonymous.Anonymous.byref }
                as *const PrintDocumentPackageStatus;
            if let Err(e) = self.PackageStatusUpdated(status) {
                if !pexcepinfo.is_null() {
                    unsafe {
                        (*pexcepinfo).wCode = 0;
                        (*pexcepinfo).bstrSource = ManuallyDrop::new(BSTR::default());
                        (*pexcepinfo).bstrDescription = ManuallyDrop::new(BSTR::default());
                        (*pexcepinfo).bstrHelpFile = ManuallyDrop::new(BSTR::default());
                        (*pexcepinfo).dwHelpContext = 0;
                        (*pexcepinfo).scode = e.code().0;
                    }
                }
                Err(windows::core::Error::from_hresult(DISP_E_EXCEPTION))
            } else {
                Ok(())
            }
        } else {
            Err(windows::core::Error::from_hresult(DISP_E_MEMBERNOTFOUND))
        }
    }
}