kr580 1.1.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
mod delta;
mod parser;

use super::super::wide_null;
use super::{configuration_from_devmode, print_devmode};
use crate::devices::printer::{
    PrinterInfo, PrinterPropertyChange, PrinterPropertySheet, PrinterSettings,
};
use windows::Win32::Graphics::Gdi::DEVMODEA;
use windows::Win32::Graphics::Printing::PrintTicket::{
    HPTPROVIDER, PTCloseProvider, PTConvertDevModeToPrintTicket, PTConvertPrintTicketToDevMode,
    PTGetPrintCapabilities, PTMergeAndValidatePrintTicket, PTOpenProvider, PTReleaseMemory,
    kPTJobScope, kUserDefaultDevmode,
};
use windows::Win32::System::Com::StructuredStorage::CreateStreamOnHGlobal;
use windows::Win32::System::Com::{
    COINIT_MULTITHREADED, CoInitializeEx, CoUninitialize, IStream, STREAM_SEEK_END, STREAM_SEEK_SET,
};
use windows::core::{BSTR, PCWSTR};

pub(super) fn load(
    printer: &PrinterInfo,
    settings: &PrinterSettings,
) -> Result<PrinterPropertySheet, String> {
    let mut settings = settings.clone();
    settings.printer_name = printer.name.clone();
    let devmode = print_devmode(&settings)?.into_bytes();
    let configuration = configuration_from_devmode(printer, devmode.clone())?;
    match inspect(&printer.name, &devmode) {
        Ok((features, parameters)) => Ok(PrinterPropertySheet {
            configuration,
            features,
            parameters,
            provider_error: None,
        }),
        Err(error) => Ok(PrinterPropertySheet {
            configuration,
            features: Vec::new(),
            parameters: Vec::new(),
            provider_error: Some(error),
        }),
    }
}

pub(super) fn apply(
    printer: &PrinterInfo,
    settings: &PrinterSettings,
    change: &PrinterPropertyChange,
) -> Result<PrinterPropertySheet, String> {
    let _com = ComApartment::init()?;
    let provider = Provider::open(&printer.name)?;
    let mut settings = settings.clone();
    settings.printer_name = printer.name.clone();
    let base_devmode = print_devmode(&settings)?.into_bytes();
    let base_ticket = ticket_from_devmode(&provider, &base_devmode)?;
    let base_xml = stream_to_bytes(&base_ticket)?;
    let delta_xml = delta::build(&base_xml, change)?;
    let delta_ticket = stream_from_bytes(delta_xml.as_bytes())?;
    let result_ticket = empty_stream()?;
    let mut driver_error = BSTR::new();
    unsafe {
        PTMergeAndValidatePrintTicket(
            provider.0,
            &base_ticket,
            &delta_ticket,
            kPTJobScope,
            &result_ticket,
            Some(&mut driver_error),
        )
    }
    .map_err(|error| api_error("PTMergeAndValidatePrintTicket", error, &driver_error))?;
    let devmode = devmode_from_ticket(&provider, &result_ticket)?;
    let configuration = configuration_from_devmode(printer, devmode)?;
    let (features, parameters) = capabilities_from_ticket(&provider, &result_ticket)?;
    Ok(PrinterPropertySheet {
        configuration,
        features,
        parameters,
        provider_error: None,
    })
}

fn inspect(
    printer_name: &str,
    devmode: &[u8],
) -> Result<
    (
        Vec<crate::devices::printer::PrinterFeature>,
        Vec<crate::devices::printer::PrinterParameter>,
    ),
    String,
> {
    let _com = ComApartment::init()?;
    let provider = Provider::open(printer_name)?;
    let ticket = ticket_from_devmode(&provider, devmode)?;
    capabilities_from_ticket(&provider, &ticket)
}

fn ticket_from_devmode(provider: &Provider, devmode: &[u8]) -> Result<IStream, String> {
    let ticket = empty_stream()?;
    unsafe {
        PTConvertDevModeToPrintTicket(
            provider.0,
            devmode.len() as u32,
            devmode.as_ptr().cast::<DEVMODEA>(),
            kPTJobScope,
            &ticket,
        )
    }
    .map_err(|error| format!("PTConvertDevModeToPrintTicket failed: {error}"))?;
    rewind(&ticket)?;
    Ok(ticket)
}

fn capabilities_from_ticket(
    provider: &Provider,
    ticket: &IStream,
) -> Result<
    (
        Vec<crate::devices::printer::PrinterFeature>,
        Vec<crate::devices::printer::PrinterParameter>,
    ),
    String,
> {
    rewind(ticket)?;
    let capabilities = empty_stream()?;
    let mut driver_error = BSTR::new();
    unsafe { PTGetPrintCapabilities(provider.0, ticket, &capabilities, Some(&mut driver_error)) }
        .map_err(|error| api_error("PTGetPrintCapabilities", error, &driver_error))?;
    let ticket_xml = stream_to_bytes(ticket)?;
    let capabilities_xml = stream_to_bytes(&capabilities)?;
    parser::parse(&capabilities_xml, &ticket_xml)
}

fn devmode_from_ticket(provider: &Provider, ticket: &IStream) -> Result<Vec<u8>, String> {
    rewind(ticket)?;
    let mut size = 0;
    let mut output = std::ptr::null_mut::<DEVMODEA>();
    let mut driver_error = BSTR::new();
    unsafe {
        PTConvertPrintTicketToDevMode(
            provider.0,
            ticket,
            kUserDefaultDevmode,
            kPTJobScope,
            &mut size,
            &mut output,
            Some(&mut driver_error),
        )
    }
    .map_err(|error| api_error("PTConvertPrintTicketToDevMode", error, &driver_error))?;
    if output.is_null() || size == 0 {
        return Err("PTConvertPrintTicketToDevMode returned an empty DEVMODE".to_owned());
    }
    let bytes = unsafe { std::slice::from_raw_parts(output.cast::<u8>(), size as usize).to_vec() };
    unsafe { PTReleaseMemory(output.cast()) }
        .map_err(|error| format!("PTReleaseMemory failed: {error}"))?;
    Ok(bytes)
}

fn empty_stream() -> Result<IStream, String> {
    unsafe { CreateStreamOnHGlobal(None, true) }
        .map_err(|error| format!("CreateStreamOnHGlobal failed: {error}"))
}

fn stream_from_bytes(bytes: &[u8]) -> Result<IStream, String> {
    let stream = empty_stream()?;
    let mut written = 0;
    unsafe {
        stream.Write(
            bytes.as_ptr().cast(),
            bytes.len() as u32,
            Some(&mut written),
        )
    }
    .ok()
    .map_err(|error| format!("IStream::Write failed: {error}"))?;
    if written as usize != bytes.len() {
        return Err(format!(
            "IStream::Write wrote {written} of {} bytes",
            bytes.len()
        ));
    }
    rewind(&stream)?;
    Ok(stream)
}

fn stream_to_bytes(stream: &IStream) -> Result<Vec<u8>, String> {
    let mut size = 0;
    unsafe { stream.Seek(0, STREAM_SEEK_END, Some(&mut size)) }
        .map_err(|error| format!("IStream::Seek failed: {error}"))?;
    rewind(stream)?;
    let mut bytes = vec![0; size as usize];
    let mut read = 0;
    unsafe {
        stream.Read(
            bytes.as_mut_ptr().cast(),
            bytes.len() as u32,
            Some(&mut read),
        )
    }
    .ok()
    .map_err(|error| format!("IStream::Read failed: {error}"))?;
    bytes.truncate(read as usize);
    rewind(stream)?;
    Ok(bytes)
}

fn rewind(stream: &IStream) -> Result<(), String> {
    unsafe { stream.Seek(0, STREAM_SEEK_SET, None) }
        .map_err(|error| format!("IStream::Seek failed: {error}"))
}

fn api_error(api: &str, error: windows::core::Error, driver_error: &BSTR) -> String {
    let detail = driver_error.to_string();
    if detail.trim().is_empty() {
        format!("{api} failed: {error}")
    } else {
        format!("{api} failed: {error}: {detail}")
    }
}

struct Provider(HPTPROVIDER);

impl Provider {
    fn open(printer_name: &str) -> Result<Self, String> {
        let name = wide_null(printer_name);
        unsafe { PTOpenProvider(PCWSTR(name.as_ptr()), 1) }
            .map(Self)
            .map_err(|error| format!("PTOpenProvider failed: {error}"))
    }
}

impl Drop for Provider {
    fn drop(&mut self) {
        unsafe {
            let _ = PTCloseProvider(self.0);
        }
    }
}

struct ComApartment;

impl ComApartment {
    fn init() -> Result<Self, String> {
        unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }
            .ok()
            .map_err(|error| format!("CoInitializeEx failed: {error}"))?;
        Ok(Self)
    }
}

impl Drop for ComApartment {
    fn drop(&mut self) {
        unsafe {
            CoUninitialize();
        }
    }
}