use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use windows::core::PCWSTR;
use windows::Win32::Foundation::{HWND, LPARAM, WPARAM};
use windows::Win32::System::Com::*;
use windows::Win32::UI::WindowsAndMessaging::*;
use crate::com::cleanup::ComGuard;
use crate::com::dispatch::Dispatch;
use crate::com::variant::Variant;
use crate::error::{OaError, OaResult};
use crate::office::constants::XlCalculation;
pub fn init_com_sta() -> OaResult<ComGuard> {
unsafe {
let hr = CoInitializeEx(None, COINIT_APARTMENTTHREADED);
if hr.is_err() {
return Err(OaError::Com(hr.into()));
}
}
Ok(ComGuard::new())
}
pub fn create_instance(prog_id: &str) -> OaResult<Dispatch> {
unsafe {
let wide_prog_id: Vec<u16> = prog_id.encode_utf16().chain(std::iter::once(0)).collect();
let clsid = CLSIDFromProgID(PCWSTR(wide_prog_id.as_ptr()))?;
let dispatch: IDispatch =
CoCreateInstance(&clsid, None, CLSCTX_LOCAL_SERVER)?;
Ok(Dispatch::new(dispatch))
}
}
pub fn spawn_dialog_dismisser() -> (Arc<AtomicBool>, thread::JoinHandle<()>) {
let stop = Arc::new(AtomicBool::new(false));
let stop_clone = stop.clone();
let handle = thread::spawn(move || {
while !stop_clone.load(Ordering::Relaxed) {
dismiss_security_dialog();
thread::sleep(Duration::from_millis(100));
}
});
(stop, handle)
}
fn dismiss_security_dialog() {
let titles = [
"Microsoft PowerPoint Security Notice",
"Microsoft PowerPoint",
];
for title in &titles {
let wide_title: Vec<u16> = title.encode_utf16().chain(std::iter::once(0)).collect();
unsafe {
if let Ok(hwnd) = FindWindowW(None, PCWSTR(wide_title.as_ptr())) {
if hwnd != HWND::default() {
let _ = PostMessageW(Some(hwnd), WM_CLOSE, WPARAM(0), LPARAM(0));
}
}
}
}
}
pub fn stop_dialog_dismisser(
stop: Arc<AtomicBool>,
handle: thread::JoinHandle<()>,
) {
stop.store(true, Ordering::Relaxed);
let _ = handle.join();
}
pub struct ComSession {
stop: Arc<AtomicBool>,
handle: Option<thread::JoinHandle<()>>,
pub excel_app: Dispatch,
pub ppt_app: Dispatch,
_com: ComGuard,
}
impl ComSession {
pub fn new() -> OaResult<Self> {
let com = init_com_sta()?;
let (stop, handle) = spawn_dialog_dismisser();
let mut excel_app = create_instance("Excel.Application")?;
excel_app.put("Visible", Variant::from(false))?;
excel_app.put("DisplayAlerts", Variant::from(false))?;
excel_app.put("ScreenUpdating", Variant::from(false))?;
excel_app.put("EnableEvents", Variant::from(false))?;
let mut ppt_app = create_instance("PowerPoint.Application")?;
ppt_app.put("DisplayAlerts", Variant::from(0i32))?;
Ok(Self {
stop,
handle: Some(handle),
excel_app,
ppt_app,
_com: com,
})
}
pub fn close_all_workbooks(&mut self) {
let wbs_var = match self.excel_app.get("Workbooks") {
Ok(v) => v,
Err(_) => return,
};
let wbs_disp = match wbs_var.as_dispatch() {
Ok(d) => d,
Err(_) => return,
};
let mut wbs = Dispatch::new(wbs_disp);
let count = wbs.get("Count")
.and_then(|v| v.as_i32())
.unwrap_or(0);
for i in (1..=count).rev() {
if let Ok(v) = wbs.call("Item", &[Variant::from(i)])
&& let Ok(d) = v.as_dispatch()
{
let mut wb = Dispatch::new(d);
let _ = wb.call("Close", &[Variant::from(false)]);
}
}
}
}
impl Drop for ComSession {
fn drop(&mut self) {
let _ = self.excel_app.put("Calculation", Variant::from(XlCalculation::Automatic as i32));
let _ = self.excel_app.call0("Quit");
let _ = self.ppt_app.call0("Quit");
self.stop.store(true, Ordering::Relaxed);
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spawn_and_stop_dismisser() {
let (stop, handle) = spawn_dialog_dismisser();
thread::sleep(Duration::from_millis(100));
stop_dialog_dismisser(stop, handle);
}
#[test]
fn test_dismiss_security_dialog_no_crash() {
dismiss_security_dialog();
}
}