kr580 2.0.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
use super::super::{last_os_error, read_wide_z};
use std::ptr::{null, null_mut};
use windows_sys::Win32::Foundation::{HGLOBAL, HWND};
use windows_sys::Win32::Graphics::Gdi::DEVMODEW;
use windows_sys::Win32::System::Com::{COINIT_APARTMENTTHREADED, CoInitializeEx, CoUninitialize};
use windows_sys::Win32::System::Memory::{GlobalLock, GlobalUnlock};
use windows_sys::Win32::UI::Controls::Dialogs::{
    CommDlgExtendedError, DEVNAMES, PD_PRINTSETUP, PD_USEDEVMODECOPIESANDCOLLATE, PRINTDLGW,
    PrintDlgW,
};

pub(super) fn configure() -> Result<Option<(String, Vec<u8>)>, String> {
    let _com = ComApartment::init()?;
    let mut dialog = print_dialog();
    let accepted = unsafe { PrintDlgW(&mut dialog) };
    if accepted == 0 {
        let code = unsafe { CommDlgExtendedError() };
        free_dialog_handles(&mut dialog);
        return if code == 0 {
            Ok(None)
        } else {
            Err(format!("printer setup failed: 0x{code:04X}"))
        };
    }
    let result = (|| {
        let printer_name = device_name(dialog.hDevNames)?;
        let devmode = devmode(dialog.hDevMode)?;
        Ok((printer_name, devmode))
    })();
    free_dialog_handles(&mut dialog);
    result.map(Some)
}

fn print_dialog() -> PRINTDLGW {
    PRINTDLGW {
        lStructSize: std::mem::size_of::<PRINTDLGW>() as u32,
        hwndOwner: null_mut::<std::ffi::c_void>() as HWND,
        hDevMode: null_mut(),
        hDevNames: null_mut(),
        hDC: null_mut(),
        Flags: PD_PRINTSETUP | PD_USEDEVMODECOPIESANDCOLLATE,
        nFromPage: 0,
        nToPage: 0,
        nMinPage: 0,
        nMaxPage: 0,
        nCopies: 1,
        hInstance: null_mut(),
        lCustData: 0,
        lpfnPrintHook: None,
        lpfnSetupHook: None,
        lpPrintTemplateName: null(),
        lpSetupTemplateName: null(),
        hPrintTemplate: null_mut(),
        hSetupTemplate: null_mut(),
    }
}

fn device_name(hdevnames: HGLOBAL) -> Result<String, String> {
    let ptr = unsafe { GlobalLock(hdevnames) as *const DEVNAMES };
    if ptr.is_null() {
        return Err(last_os_error("GlobalLock"));
    }
    let result = unsafe {
        let names = *ptr;
        let base = ptr.cast::<u16>();
        read_wide_z(base.add(names.wDeviceOffset as usize))
    };
    unsafe {
        GlobalUnlock(hdevnames);
    }
    result
}

fn devmode(hdevmode: HGLOBAL) -> Result<Vec<u8>, String> {
    let ptr = unsafe { GlobalLock(hdevmode) as *const DEVMODEW };
    if ptr.is_null() {
        return Err(last_os_error("GlobalLock"));
    }
    let mode = unsafe { ptr.read_unaligned() };
    let len = mode.dmSize as usize + mode.dmDriverExtra as usize;
    let bytes = unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), len).to_vec() };
    unsafe {
        GlobalUnlock(hdevmode);
    }
    Ok(bytes)
}

fn free_dialog_handles(dialog: &mut PRINTDLGW) {
    if !dialog.hDC.is_null() {
        unsafe {
            windows_sys::Win32::Graphics::Gdi::DeleteDC(dialog.hDC);
        }
    }
    unsafe {
        if !dialog.hDevMode.is_null() {
            windows_sys::Win32::Foundation::GlobalFree(dialog.hDevMode);
        }
        if !dialog.hDevNames.is_null() {
            windows_sys::Win32::Foundation::GlobalFree(dialog.hDevNames);
        }
    }
}

pub(super) struct ComApartment;

impl ComApartment {
    pub(super) fn init() -> Result<Self, String> {
        let result = unsafe { CoInitializeEx(null_mut(), COINIT_APARTMENTTHREADED as u32) };
        if result < 0 {
            return Err(format!(
                "printer setup COM initialization failed: 0x{:08X}",
                result as u32
            ));
        }
        Ok(Self)
    }
}

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