use std::thread::sleep;
use crate::i18n::t_args;
use std::time::{Duration, Instant};
use windows::core::PWSTR;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Storage::FileSystem::{
GetFileVersionInfoSizeW, GetFileVersionInfoW, VerQueryValueW,
};
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS,
};
use windows::Win32::System::Threading::{
OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_WIN32,
PROCESS_QUERY_LIMITED_INFORMATION,
};
pub const GOOGLE_DRIVE_DESCRIPTION: &str = "Google Drive";
#[allow(dead_code)]
pub fn is_process_running(process_name: &str) -> bool {
unsafe {
let snapshot = match CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) {
Ok(handle) => handle,
Err(_) => return false,
};
let mut entry = PROCESSENTRY32W {
dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
..Default::default()
};
let mut found = false;
if Process32FirstW(snapshot, &mut entry).is_ok() {
loop {
let len = entry
.szExeFile
.iter()
.position(|c| *c == 0)
.unwrap_or(entry.szExeFile.len());
let name = String::from_utf16_lossy(&entry.szExeFile[..len]);
if name.eq_ignore_ascii_case(process_name) {
found = true;
break;
}
if Process32NextW(snapshot, &mut entry).is_err() {
break;
}
}
}
let _ = CloseHandle(snapshot);
found
}
}
fn file_description(exe_path: &[u16]) -> Option<String> {
unsafe {
let path = windows::core::PCWSTR::from_raw(exe_path.as_ptr());
let mut dw_handle = 0u32;
let size = GetFileVersionInfoSizeW(path, Some(&mut dw_handle));
if size == 0 {
return None;
}
let mut data = vec![0u8; size as usize];
GetFileVersionInfoW(path, dw_handle, size, data.as_mut_ptr() as *mut _)
.ok()?;
let mut buf: *mut std::ffi::c_void = std::ptr::null_mut();
let mut len = 0u32;
let translations: Vec<(u16, u16)> = if VerQueryValueW(
data.as_ptr() as *const _,
windows::core::w!("\\VarFileInfo\\Translation"),
&mut buf,
&mut len,
)
.as_bool()
&& !buf.is_null()
&& len >= 4
{
let count = (len as usize) / 4;
let pairs = std::slice::from_raw_parts(buf as *const u32, count);
pairs
.iter()
.map(|v| ((v & 0xFFFF) as u16, (v >> 16) as u16))
.collect()
} else {
vec![(0x0409, 0x04B0), (0x0409, 0x04E4)]
};
for (lang, codepage) in translations {
let sub_block = format!("\\StringFileInfo\\{lang:04x}{codepage:04x}\\FileDescription");
let sub_block_wide: Vec<u16> = sub_block
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let mut value: *mut std::ffi::c_void = std::ptr::null_mut();
let mut value_len = 0u32;
if VerQueryValueW(
data.as_ptr() as *const _,
windows::core::PCWSTR::from_raw(sub_block_wide.as_ptr()),
&mut value,
&mut value_len,
)
.as_bool()
&& !value.is_null()
&& value_len > 0
{
let chars =
std::slice::from_raw_parts(value as *const u16, (value_len as usize).saturating_sub(1));
return Some(String::from_utf16_lossy(chars));
}
}
None
}
}
pub fn is_process_running_with_description(description: &str) -> bool {
unsafe {
let snapshot = match CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) {
Ok(handle) => handle,
Err(_) => return false,
};
let mut entry = PROCESSENTRY32W {
dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
..Default::default()
};
let mut found = false;
if Process32FirstW(snapshot, &mut entry).is_ok() {
loop {
if entry.th32ProcessID != 0 {
if let Ok(process) =
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, entry.th32ProcessID)
{
let mut buffer = [0u16; 1024];
let mut size = buffer.len() as u32;
if QueryFullProcessImageNameW(
process,
PROCESS_NAME_WIN32,
PWSTR::from_raw(buffer.as_mut_ptr()),
&mut size,
)
.is_ok()
{
let end = (size as usize).min(buffer.len() - 1);
buffer[end] = 0;
if let Some(desc) = file_description(&buffer[..=end]) {
if desc.trim().eq_ignore_ascii_case(description) {
found = true;
}
}
}
let _ = CloseHandle(process);
if found {
break;
}
}
}
if Process32NextW(snapshot, &mut entry).is_err() {
break;
}
}
}
let _ = CloseHandle(snapshot);
found
}
}
pub fn wait_for_process(
description: &str,
timeout: Option<Duration>,
settle: Duration,
) -> Result<Duration, String> {
let start = Instant::now();
let poll = Duration::from_secs(1);
loop {
if is_process_running_with_description(description) {
let waited = start.elapsed();
if !settle.is_zero() {
sleep(settle);
}
return Ok(waited);
}
if let Some(limit) = timeout {
if start.elapsed() >= limit {
return Err(t_args(
"process_watcher.timeout",
&[
("process", description),
("seconds", &limit.as_secs().to_string()),
],
));
}
}
sleep(poll);
}
}