rustydialogs 0.4.2

Provides a simple and cross-platform way to display native dialog boxes.
Documentation
use std::ffi::{CStr, CString, OsStr};
use std::os::raw::c_char;
use std::os::unix::ffi::OsStrExt;
use std::{ptr, sync};

use glib_sys::{g_free, g_slist_free, GSList};
use gobject_sys::g_object_unref;

use super::*;

mod color;
mod file;
mod folder;
mod input;
mod message;

pub fn message_box(p: &MessageBox<'_>) -> Option<MessageResult> {
	message::show(p)
}

pub fn pick_file(p: &FileDialog<'_>) -> Option<PathBuf> {
	file::pick_file(p)
}

pub fn pick_files(p: &FileDialog<'_>) -> Option<Vec<PathBuf>> {
	file::pick_files(p)
}

pub fn save_file(p: &FileDialog<'_>) -> Option<PathBuf> {
	file::save_file(p)
}

pub fn folder_dialog(p: &FolderDialog<'_>) -> Option<PathBuf> {
	folder::folder_dialog(p)
}

pub fn text_input(p: &TextInput<'_>) -> Option<String> {
	input::text_input(p)
}

pub fn color_picker(p: &ColorPicker<'_>) -> Option<ColorValue> {
	color::color_picker(p)
}

static GTK_INITIALIZED: sync::OnceLock<bool> = sync::OnceLock::new();

fn ensure_gtk_initialized() {
	let ok = *GTK_INITIALIZED.get_or_init(|| unsafe { gtk_sys::gtk_init_check(ptr::null_mut(), ptr::null_mut()) != 0 });
	if !ok {
		panic!("Failed to initialize GTK3 backend. Ensure a graphical session is available.");
	}
}

fn cstring(value: &str) -> CString {
	CString::new(value).unwrap_or_else(|_| CString::new(value.replace('\0', " ")).unwrap())
}

fn c_to_path_buf(ptr: *mut c_char) -> Option<PathBuf> {
	if ptr.is_null() {
		return None;
	}
	let bytes = unsafe { CStr::from_ptr(ptr).to_bytes().to_vec() };
	unsafe { g_free(ptr as *mut _) };
	Some(PathBuf::from(OsStr::from_bytes(&bytes)))
}

fn run_dialog(dialog: *mut gtk_sys::GtkWidget) -> i32 {
	let response = unsafe { gtk_sys::gtk_dialog_run(dialog as *mut gtk_sys::GtkDialog) };
	unsafe {
		gtk_sys::gtk_widget_destroy(dialog);
		while gtk_sys::gtk_events_pending() != 0 {
			gtk_sys::gtk_main_iteration();
		}
	}
	response
}

fn run_native_dialog(dialog: *mut gtk_sys::GtkNativeDialog) -> i32 {
	let response = unsafe { gtk_sys::gtk_native_dialog_run(dialog) };
	unsafe {
		gtk_sys::gtk_native_dialog_hide(dialog);
		while gtk_sys::gtk_events_pending() != 0 {
			gtk_sys::gtk_main_iteration();
		}
	}
	response
}

fn collect_file_list(list: *mut GSList) -> Vec<PathBuf> {
	let mut result = Vec::new();
	let mut node = list;
	while !node.is_null() {
		let filename_ptr = unsafe { (*node).data as *mut c_char };
		if !filename_ptr.is_null() {
			let bytes = unsafe { CStr::from_ptr(filename_ptr).to_bytes() };
			result.push(PathBuf::from(OsStr::from_bytes(bytes)));
			unsafe { g_free(filename_ptr as *mut _) };
		}
		node = unsafe { (*node).next };
	}
	unsafe { g_slist_free(list) };
	result
}