use std::collections::VecDeque;
#[cfg(target_os = "macos")]
use std::ffi::c_int;
use std::ffi::{CStr, CString, c_char, c_void};
use std::sync::{Arc, LazyLock, Mutex};
use wxdragon_sys as ffi;
type CallbackQueue = Arc<Mutex<VecDeque<Box<dyn FnOnce() + Send + 'static>>>>;
static MAIN_THREAD_QUEUE: LazyLock<CallbackQueue> = LazyLock::new(|| Arc::new(Mutex::new(VecDeque::new())));
pub fn call_after<F>(callback: Box<F>)
where
F: FnOnce() + Send + 'static,
{
let mut queue = MAIN_THREAD_QUEUE.lock().unwrap();
queue.push_back(callback);
}
pub fn process_main_thread_queue() -> bool {
let mut callbacks = Vec::new();
{
let mut queue = MAIN_THREAD_QUEUE.lock().unwrap();
if queue.is_empty() {
return false;
}
for _ in 0..10 {
if let Some(callback) = queue.pop_front() {
callbacks.push(callback);
} else {
break;
}
}
}
for callback in callbacks {
callback();
}
true }
#[unsafe(no_mangle)]
pub extern "C" fn process_rust_callbacks() -> i32 {
if process_main_thread_queue() {
1 } else {
0 }
}
pub fn process_callbacks() {
unsafe {
ffi::wxd_App_ProcessCallbacks();
}
}
#[derive(Clone, Copy)]
pub struct App {
handle: *mut ffi::wxd_App_t,
}
impl App {
pub(crate) fn new() -> Option<Self> {
let handle = unsafe { ffi::wxd_GetApp() };
if handle.is_null() { None } else { Some(App { handle }) }
}
pub fn set_top_window<W>(&self, window: &W)
where
W: crate::window::WxWidget + ?Sized,
{
if !self.handle.is_null() {
unsafe {
ffi::wxd_App_SetTopWindow(self.handle, window.handle_ptr());
}
}
}
pub fn get_top_window(&self) -> Option<crate::window::Window> {
if self.handle.is_null() {
return None;
}
let top = unsafe { ffi::wxd_App_GetTopWindow(self.handle) };
if top.is_null() {
None
} else {
Some(unsafe { crate::window::Window::from_ptr(top) })
}
}
pub fn is_main_loop_running(&self) -> bool {
if self.handle.is_null() {
return false;
}
unsafe { ffi::wxd_App_IsMainLoopRunning(self.handle) }
}
pub fn exit_main_loop(&self) {
if !self.handle.is_null() {
unsafe { ffi::wxd_App_ExitMainLoop(self.handle) };
}
}
pub fn set_exit_on_frame_delete(&self, exit_on_frame_delete: bool) {
if !self.handle.is_null() {
unsafe { ffi::wxd_App_SetExitOnFrameDelete(self.handle, exit_on_frame_delete) };
}
}
pub fn get_exit_on_frame_delete(&self) -> bool {
if self.handle.is_null() {
return true;
}
unsafe { ffi::wxd_App_GetExitOnFrameDelete(self.handle) }
}
pub fn set_app_name(&self, name: &str) -> bool {
let c_name = match CString::new(name) {
Ok(v) => v,
Err(_) => return false,
};
if !self.handle.is_null() {
unsafe { ffi::wxd_App_SetAppName(self.handle, c_name.as_ptr()) };
}
true
}
pub fn get_app_name(&self) -> String {
if self.handle.is_null() {
return String::new();
}
get_app_string(self.handle, ffi::wxd_App_GetAppName).unwrap_or_default()
}
pub fn set_app_display_name(&self, name: &str) -> bool {
let c_name = match CString::new(name) {
Ok(v) => v,
Err(_) => return false,
};
if !self.handle.is_null() {
unsafe { ffi::wxd_App_SetAppDisplayName(self.handle, c_name.as_ptr()) };
}
true
}
pub fn get_app_display_name(&self) -> String {
if self.handle.is_null() {
return String::new();
}
get_app_string(self.handle, ffi::wxd_App_GetAppDisplayName).unwrap_or_default()
}
pub fn set_vendor_name(&self, name: &str) -> bool {
let c_name = match CString::new(name) {
Ok(v) => v,
Err(_) => return false,
};
if !self.handle.is_null() {
unsafe { ffi::wxd_App_SetVendorName(self.handle, c_name.as_ptr()) };
}
true
}
pub fn get_vendor_name(&self) -> String {
if self.handle.is_null() {
return String::new();
}
get_app_string(self.handle, ffi::wxd_App_GetVendorName).unwrap_or_default()
}
pub fn set_vendor_display_name(&self, name: &str) -> bool {
let c_name = match CString::new(name) {
Ok(v) => v,
Err(_) => return false,
};
if !self.handle.is_null() {
unsafe { ffi::wxd_App_SetVendorDisplayName(self.handle, c_name.as_ptr()) };
}
true
}
pub fn get_vendor_display_name(&self) -> String {
if self.handle.is_null() {
return String::new();
}
get_app_string(self.handle, ffi::wxd_App_GetVendorDisplayName).unwrap_or_default()
}
}
fn get_app_string(
app: *mut ffi::wxd_App_t,
getter: unsafe extern "C" fn(*const ffi::wxd_App_t, *mut c_char, usize) -> i32,
) -> Option<String> {
let len = unsafe { getter(app as *const ffi::wxd_App_t, std::ptr::null_mut(), 0) };
if len < 0 {
return None;
}
let mut buf = vec![0u8; len as usize + 1];
unsafe {
getter(app as *const ffi::wxd_App_t, buf.as_mut_ptr() as *mut c_char, buf.len());
}
Some(
unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }
.to_string_lossy()
.to_string(),
)
}
unsafe impl Send for App {}
unsafe impl Sync for App {}
pub fn set_top_window<W>(window: &W)
where
W: crate::window::WxWidget + ?Sized,
{
if let Some(app) = App::new() {
app.set_top_window(window);
}
}
pub fn get_app_instance() -> Option<App> {
App::new()
}
pub fn get_app() -> Option<crate::appearance::App> {
crate::appearance::get_app()
}
pub fn set_appearance(appearance: crate::appearance::Appearance) -> crate::appearance::AppearanceResult {
use crate::appearance::AppAppearance;
if let Some(app) = get_app() {
app.set_appearance(appearance)
} else {
crate::appearance::AppearanceResult::Failure
}
}
impl crate::event::AppEvents for App {
fn on_open_files<F>(&self, callback: F)
where
F: Fn(Vec<String>) + Send + 'static,
{
#[cfg(target_os = "macos")]
{
let callback = Box::new(callback);
let user_data = Box::into_raw(callback) as *mut c_void;
unsafe { ffi::wxd_App_AddMacOpenFilesHandler(self.handle, Some(mac_open_files_trampoline::<F>), user_data) };
}
#[cfg(not(target_os = "macos"))]
{
let _ = callback; }
}
fn on_open_url<F>(&self, callback: F)
where
F: Fn(String) + Send + 'static,
{
#[cfg(target_os = "macos")]
{
let callback = Box::new(callback);
let user_data = Box::into_raw(callback) as *mut c_void;
unsafe { ffi::wxd_App_AddMacOpenURLHandler(self.handle, Some(mac_open_url_trampoline::<F>), user_data) };
}
#[cfg(not(target_os = "macos"))]
{
let _ = callback;
}
}
fn on_new_file<F>(&self, callback: F)
where
F: Fn() + Send + 'static,
{
#[cfg(target_os = "macos")]
{
let callback = Box::new(callback);
let user_data = Box::into_raw(callback) as *mut c_void;
unsafe { ffi::wxd_App_AddMacNewFileHandler(self.handle, Some(mac_new_file_trampoline::<F>), user_data) };
}
#[cfg(not(target_os = "macos"))]
{
let _ = callback;
}
}
fn on_reopen_app<F>(&self, callback: F)
where
F: Fn() + Send + 'static,
{
#[cfg(target_os = "macos")]
{
let callback = Box::new(callback);
let user_data = Box::into_raw(callback) as *mut c_void;
unsafe { ffi::wxd_App_AddMacReopenAppHandler(self.handle, Some(mac_reopen_app_trampoline::<F>), user_data) };
}
#[cfg(not(target_os = "macos"))]
{
let _ = callback;
}
}
fn on_print_files<F>(&self, callback: F)
where
F: Fn(Vec<String>) + Send + 'static,
{
#[cfg(target_os = "macos")]
{
let callback = Box::new(callback);
let user_data = Box::into_raw(callback) as *mut c_void;
unsafe { ffi::wxd_App_AddMacPrintFilesHandler(self.handle, Some(mac_print_files_trampoline::<F>), user_data) };
}
#[cfg(not(target_os = "macos"))]
{
let _ = callback;
}
}
}
#[cfg(target_os = "macos")]
unsafe extern "C" fn mac_open_files_trampoline<F>(user_data: *mut c_void, files: *mut *const c_char, count: c_int)
where
F: Fn(Vec<String>) + Send + 'static,
{
if user_data.is_null() || files.is_null() {
return;
}
let callback = unsafe { &*(user_data as *const F) };
let mut file_list = Vec::new();
for i in 0..count as isize {
let file_ptr = unsafe { *files.offset(i) };
if !file_ptr.is_null()
&& let Ok(file_str) = unsafe { CStr::from_ptr(file_ptr) }.to_str()
{
file_list.push(file_str.to_string());
}
}
callback(file_list);
}
#[cfg(target_os = "macos")]
unsafe extern "C" fn mac_open_url_trampoline<F>(user_data: *mut c_void, url: *const c_char)
where
F: Fn(String) + Send + 'static,
{
if user_data.is_null() || url.is_null() {
return;
}
let callback = unsafe { &*(user_data as *const F) };
if let Ok(url_str) = unsafe { CStr::from_ptr(url) }.to_str() {
callback(url_str.to_string());
}
}
#[cfg(target_os = "macos")]
unsafe extern "C" fn mac_new_file_trampoline<F>(user_data: *mut c_void)
where
F: Fn() + Send + 'static,
{
if user_data.is_null() {
return;
}
let callback = unsafe { &*(user_data as *const F) };
callback();
}
#[cfg(target_os = "macos")]
unsafe extern "C" fn mac_reopen_app_trampoline<F>(user_data: *mut c_void)
where
F: Fn() + Send + 'static,
{
if user_data.is_null() {
return;
}
let callback = unsafe { &*(user_data as *const F) };
callback();
}
#[cfg(target_os = "macos")]
unsafe extern "C" fn mac_print_files_trampoline<F>(user_data: *mut c_void, files: *mut *const c_char, count: c_int)
where
F: Fn(Vec<String>) + Send + 'static,
{
if user_data.is_null() || files.is_null() {
return;
}
let callback = unsafe { &*(user_data as *const F) };
let mut file_list = Vec::new();
for i in 0..count as isize {
let file_ptr = unsafe { *files.offset(i) };
if !file_ptr.is_null()
&& let Ok(file_str) = unsafe { CStr::from_ptr(file_ptr) }.to_str()
{
file_list.push(file_str.to_string());
}
}
callback(file_list);
}
pub fn main<F>(on_init: F) -> Result<(), Box<dyn std::error::Error>>
where
F: FnOnce(App) + 'static,
{
let exit_code = unsafe {
let payload = Box::new(OnInitPayload {
cb: Some(Box::new(on_init)),
});
let user_data_ptr = Box::into_raw(payload) as *mut c_void;
let args: Vec<CString> = std::env::args_os()
.map(|os| {
let s = os.to_string_lossy();
CString::new(s.as_bytes()).unwrap_or_else(|_| CString::new("").unwrap())
})
.collect();
let _owned_prog: Option<CString>;
let mut raw_args: Vec<*mut c_char> = if args.is_empty() {
let pn = CString::new("wxRustApp").expect("CString for app name");
let ptr = pn.as_ptr() as *mut c_char;
_owned_prog = Some(pn);
vec![ptr]
} else {
args.iter().map(|c| c.as_ptr() as *mut c_char).collect()
};
raw_args.push(std::ptr::null_mut());
let argc: i32 = (raw_args.len() as i32) - 1; let argv_ptr = raw_args.as_mut_ptr();
let code = ffi::wxd_Main(argc, argv_ptr, Some(on_init_trampoline), user_data_ptr);
let _ = Box::from_raw(user_data_ptr as *mut OnInitPayload);
code
};
if exit_code != 0 {
return Err(format!("Application exited with code: {exit_code}").into());
}
Ok(())
}
struct OnInitPayload {
cb: Option<Box<dyn FnOnce(App)>>,
}
unsafe extern "C" fn on_init_trampoline(user_data: *mut c_void) -> bool {
if user_data.is_null() {
return false;
}
let payload = unsafe { &mut *(user_data as *mut OnInitPayload) };
let Some(cb) = payload.cb.take() else {
return false;
};
let app = match App::new() {
Some(app) => app,
None => {
log::error!("Failed to get app instance");
return false;
}
};
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cb(app)));
match result {
Ok(_) => true, Err(_) => {
log::error!("Panic caught in Rust AppOnInit callback!");
false }
}
}