use super::Notification;
use crate::platform::PlatformError;
use std::ffi::c_void;
const SW_SHOWNORMAL: i32 = 1;
const COINIT_APARTMENTTHREADED: u32 = 0x2;
const COINIT_DISABLE_OLE1DDE: u32 = 0x4;
#[link(name = "shell32")]
unsafe extern "system" {
fn ShellExecuteW(
hwnd: *mut c_void,
operation: *const u16,
file: *const u16,
parameters: *const u16,
directory: *const u16,
show: i32,
) -> *mut c_void;
}
#[link(name = "ole32")]
unsafe extern "system" {
fn CoInitializeEx(reserved: *mut c_void, coinit: u32) -> i32;
}
fn wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
pub fn open_url(url: &str) -> Result<(), PlatformError> {
unsafe {
CoInitializeEx(
std::ptr::null_mut(),
COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE,
)
};
let target = match url.strip_prefix("file:///") {
Some(path) => percent_decode(path).replace('/', "\\"),
None => url.to_string(),
};
let op = wide("open");
let file = wide(&target);
let r = unsafe {
ShellExecuteW(
std::ptr::null_mut(),
op.as_ptr(),
file.as_ptr(),
std::ptr::null(),
std::ptr::null(),
SW_SHOWNORMAL,
)
} as isize;
if r > 32 {
Ok(())
} else {
Err(PlatformError::Os(format!(
"ShellExecuteW({target}) failed with {r}"
)))
}
}
pub fn notify(n: &Notification) -> Result<(), PlatformError> {
crate::platform::backend::windows::notify(&n.title, &n.body)
}
fn percent_decode(s: &str) -> String {
let b = s.as_bytes();
let mut out = Vec::with_capacity(b.len());
let mut i = 0;
while i < b.len() {
if b[i] == b'%'
&& i + 2 < b.len()
&& let Some(v) = std::str::from_utf8(&b[i + 1..i + 3])
.ok()
.and_then(|h| u8::from_str_radix(h, 16).ok())
{
out.push(v);
i += 3;
continue;
}
out.push(b[i]);
i += 1;
}
String::from_utf8_lossy(&out).into_owned()
}