use std::ffi::CString;
use std::os::raw;
use std::{mem, ptr};
use crate::{ButtonKind, Error, IconKind, Response};
const RTLD_LAZY: raw::c_int = 1;
const GTK_MESSAGE_INFO: i32 = 0;
const GTK_MESSAGE_WARNING: i32 = 1;
const GTK_MESSAGE_QUESTION: i32 = 2;
const GTK_MESSAGE_ERROR: i32 = 3;
const GTK_BUTTONS_OK: i32 = 1;
const GTK_BUTTONS_OK_CANCEL: i32 = 5;
const GTK_RESPONSE_OK: i32 = -5;
const GTK_RESPONSE_CANCEL: i32 = -6;
#[cfg_attr(any(target_os = "linux", target_os = "android"), link(name = "dl"))]
#[cfg_attr(any(target_os = "freebsd", target_os = "dragonfly"), link(name = "c"))]
unsafe extern "C" {
unsafe fn dlopen(filename: *const raw::c_char, flags: raw::c_int) -> *mut raw::c_void;
unsafe fn dlclose(handle: *mut raw::c_void) -> raw::c_int;
unsafe fn dlsym(handle: *mut raw::c_void, symbol: *const raw::c_char) -> *mut raw::c_void;
}
type GtkInit = unsafe extern "C" fn(argc: *mut raw::c_int, argv: *mut *mut *mut raw::c_char);
type GtkMessageDialogNew = unsafe extern "C" fn(
parent: *mut raw::c_void,
flags: raw::c_int,
message_type: raw::c_int,
buttons_type: raw::c_int,
msg: *const raw::c_char,
...
) -> *mut raw::c_void;
type GtkWindowSetTitle = unsafe extern "C" fn(window: *mut raw::c_void, title: *const raw::c_char);
type GtkWidgetShow = unsafe extern "C" fn(widget: *mut raw::c_void);
type GtkDialogRun = unsafe extern "C" fn(dialog: *mut raw::c_void) -> raw::c_int;
type GtkWidgetDestroy = unsafe extern "C" fn(widget: *mut raw::c_void);
fn to_gtk_message_type(icon: IconKind) -> i32 {
match icon {
IconKind::Info => GTK_MESSAGE_INFO,
IconKind::Warning => GTK_MESSAGE_WARNING,
IconKind::Error => GTK_MESSAGE_ERROR,
IconKind::Question => GTK_MESSAGE_QUESTION,
}
}
fn to_gtk_buttons_type(btn: ButtonKind) -> i32 {
match btn {
ButtonKind::Ok => GTK_BUTTONS_OK,
ButtonKind::OkCancel => GTK_BUTTONS_OK_CANCEL,
}
}
pub fn gtk3_show_dialog(
title: &str,
message: &str,
icon: IconKind,
btn: ButtonKind,
) -> Result<Response, Error> {
let lib_names = [
"libgtk-3.so.0", ];
let mut lib_handle = ptr::null_mut();
let mut found = false;
for lib_name in lib_names.iter() {
let cname = CString::new(*lib_name).map_err(|_| Error::InvalidString)?;
unsafe {
lib_handle = dlopen(cname.as_ptr(), RTLD_LAZY);
if !lib_handle.is_null() {
found = true;
break;
}
}
}
if !found {
return Err(Error::FailedToLoadLibrary);
}
unsafe {
let init_sym = dlsym(lib_handle, b"gtk_init\0".as_ptr() as *const _);
let dialog_new_sym = dlsym(lib_handle, b"gtk_message_dialog_new\0".as_ptr() as *const _);
let set_title_sym = dlsym(lib_handle, b"gtk_window_set_title\0".as_ptr() as *const _);
let widget_show_sym = dlsym(lib_handle, b"gtk_widget_show\0".as_ptr() as *const _);
let dialog_run_sym = dlsym(lib_handle, b"gtk_dialog_run\0".as_ptr() as *const _);
let widget_destroy_sym = dlsym(lib_handle, b"gtk_widget_destroy\0".as_ptr() as *const _);
if init_sym.is_null()
|| dialog_new_sym.is_null()
|| set_title_sym.is_null()
|| widget_show_sym.is_null()
|| dialog_run_sym.is_null()
|| widget_destroy_sym.is_null()
{
dlclose(lib_handle);
return Err(Error::FailedToFindSymbol);
}
let gtk_init: GtkInit = mem::transmute(init_sym);
let gtk_message_dialog_new: GtkMessageDialogNew = mem::transmute(dialog_new_sym);
let gtk_window_set_title: GtkWindowSetTitle = mem::transmute(set_title_sym);
let gtk_widget_show: GtkWidgetShow = mem::transmute(widget_show_sym);
let gtk_dialog_run: GtkDialogRun = mem::transmute(dialog_run_sym);
let gtk_widget_destroy: GtkWidgetDestroy = mem::transmute(widget_destroy_sym);
gtk_init(ptr::null_mut(), ptr::null_mut());
let c_title = CString::new(title).map_err(|_| Error::InvalidString)?;
let c_message = CString::new(message).map_err(|_| Error::InvalidString)?;
let icon = to_gtk_message_type(icon);
let buttons = to_gtk_buttons_type(btn);
let dialog = gtk_message_dialog_new(
ptr::null_mut(), 0, icon, buttons, b"%s\0".as_ptr() as *const raw::c_char, c_message.as_ptr(), );
gtk_window_set_title(dialog as *mut raw::c_void, c_title.as_ptr());
gtk_widget_show(dialog);
let ret = gtk_dialog_run(dialog as *mut raw::c_void) as i32;
gtk_widget_destroy(dialog);
dlclose(lib_handle);
match ret {
GTK_RESPONSE_OK => Ok(Response::Ok),
GTK_RESPONSE_CANCEL => Ok(Response::Cancel),
_ => Ok(Response::None),
}
}
}