use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::path::PathBuf;
use crate::com::ComGuard;
use crate::context_menu::ContextMenu;
use crate::menu_items::MenuItem;
use crate::shell_item::ShellItems;
thread_local! {
static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
}
fn set_last_error(err: &crate::error::Error) {
let msg = CString::new(err.to_string()).unwrap_or_default();
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(msg);
});
}
pub struct FfiComGuard(ComGuard);
#[repr(C)]
pub struct FfiSelectedItem {
pub command_id: u32,
pub label: *mut c_char,
pub verb: *mut c_char,
}
#[no_mangle]
pub extern "C" fn wcm_last_error() -> *const c_char {
LAST_ERROR.with(|e| {
e.borrow()
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(std::ptr::null())
})
}
#[no_mangle]
pub extern "C" fn wcm_com_init() -> *mut FfiComGuard {
match ComGuard::new() {
Ok(guard) => Box::into_raw(Box::new(FfiComGuard(guard))),
Err(e) => {
set_last_error(&e);
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn wcm_com_uninit(guard: *mut FfiComGuard) {
if !guard.is_null() {
let _ = unsafe { Box::from_raw(guard) };
}
}
#[no_mangle]
pub unsafe extern "C" fn wcm_show_context_menu(
path: *const c_char,
x: i32,
y: i32,
) -> *mut FfiSelectedItem {
if path.is_null() {
return std::ptr::null_mut();
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let items = match ShellItems::from_path(PathBuf::from(path_str)) {
Ok(i) => i,
Err(e) => {
set_last_error(&e);
return std::ptr::null_mut();
}
};
let menu = match ContextMenu::new(items) {
Ok(m) => m,
Err(e) => {
set_last_error(&e);
return std::ptr::null_mut();
}
};
match menu.show_at(x, y) {
Ok(Some(selected)) => {
let label = CString::new(selected.menu_item().label.as_str())
.unwrap_or_default()
.into_raw();
let verb = selected
.menu_item()
.command_string
.as_deref()
.and_then(|v| CString::new(v).ok())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut());
let command_id = selected.command_id();
if let Err(e) = selected.execute() {
set_last_error(&e);
}
Box::into_raw(Box::new(FfiSelectedItem {
command_id,
label,
verb,
}))
}
Ok(None) => std::ptr::null_mut(),
Err(e) => {
set_last_error(&e);
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn wcm_free_selected(item: *mut FfiSelectedItem) {
if !item.is_null() {
let item = unsafe { Box::from_raw(item) };
if !item.label.is_null() {
let _ = unsafe { CString::from_raw(item.label) };
}
if !item.verb.is_null() {
let _ = unsafe { CString::from_raw(item.verb) };
}
}
}
#[no_mangle]
pub unsafe extern "C" fn wcm_enumerate_menu(
path: *const c_char,
extended: bool,
) -> *mut c_char {
if path.is_null() {
return std::ptr::null_mut();
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let items = match ShellItems::from_path(PathBuf::from(path_str)) {
Ok(i) => i,
Err(e) => {
set_last_error(&e);
return std::ptr::null_mut();
}
};
let menu = match ContextMenu::new(items) {
Ok(m) => m.extended(extended),
Err(e) => {
set_last_error(&e);
return std::ptr::null_mut();
}
};
match menu.enumerate() {
Ok(items) => {
let json = menu_items_to_json(&items);
match CString::new(json) {
Ok(c) => c.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
Err(e) => {
set_last_error(&e);
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn wcm_free_string(s: *mut c_char) {
if !s.is_null() {
let _ = unsafe { CString::from_raw(s) };
}
}
fn menu_items_to_json(items: &[MenuItem]) -> String {
#[derive(serde::Serialize)]
struct JsonMenuItem<'a> {
id: u32,
label: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
verb: Option<&'a str>,
separator: bool,
disabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
submenu: Option<Vec<JsonMenuItem<'a>>>,
}
fn to_json_items(items: &[MenuItem]) -> Vec<JsonMenuItem<'_>> {
items
.iter()
.map(|item| JsonMenuItem {
id: item.id,
label: &item.label,
verb: item.command_string.as_deref(),
separator: item.is_separator,
disabled: item.is_disabled,
submenu: item.submenu.as_ref().map(|s| to_json_items(s)),
})
.collect()
}
serde_json::to_string(&to_json_items(items)).unwrap_or_else(|_| "[]".to_string())
}