use fltk::{
app, enums,
prelude::{GroupExt, WidgetBase, WidgetExt, WindowExt},
window,
};
use fltk_webview_sys as wv;
use std::{
ffi::{CStr, CString},
mem,
os::raw,
sync::Arc,
};
pub(crate) trait FlString {
fn safe_new(s: &str) -> CString;
}
impl FlString for CString {
fn safe_new(s: &str) -> CString {
match CString::new(s) {
Ok(v) => v,
Err(r) => {
let i = r.nul_position();
CString::new(&r.into_vec()[0..i]).unwrap()
}
}
}
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum SizeHint {
None = 0,
Min = 1,
Max = 2,
Fixed = 3,
}
#[derive(Clone)]
pub struct Webview {
inner: Arc<wv::webview_t>,
}
unsafe impl Send for Webview {}
unsafe impl Sync for Webview {}
impl Drop for Webview {
fn drop(&mut self) {
if Arc::strong_count(&self.inner) == 0 {
unsafe {
wv::webview_terminate(*self.inner);
wv::webview_destroy(*self.inner);
}
}
}
}
impl Webview {
pub fn create(debug: bool, win: &mut window::Window) -> Webview {
assert!(win.shown());
win.end();
win.set_color(enums::Color::White);
let inner;
unsafe {
#[cfg(target_os = "windows")]
{
inner = wv::webview_create(
debug as i32,
&mut win.raw_handle() as *mut *mut raw::c_void as *mut raw::c_void,
);
win.draw(move |w| wv::webview_set_size(inner, w.w(), w.h(), 0));
let mut topwin =
window::Window::from_widget_ptr(win.top_window().unwrap().as_widget_ptr());
topwin.set_callback(|t| {
if app::event() == enums::Event::Close {
t.hide();
}
});
}
#[cfg(target_os = "macos")]
{
pub enum NSWindow {}
extern "C" {
pub fn make_delegate(child: *mut NSWindow, parent: *mut NSWindow);
pub fn my_close_win(win: *mut NSWindow);
}
let handle = win.raw_handle();
inner = wv::webview_create(debug as i32, handle as _);
make_delegate(wv::webview_get_window(inner) as _, handle as _);
win.draw(move |w| wv::webview_set_size(inner, w.w(), w.h(), 0));
let mut topwin =
window::Window::from_widget_ptr(win.top_window().unwrap().as_widget_ptr());
let inner = inner.clone();
topwin.set_callback(move |t| {
if app::event() == enums::Event::Close {
my_close_win(wv::webview_get_window(inner) as _);
t.hide();
}
});
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
pub enum GdkWindow {}
pub enum GtkWindow {}
pub enum Display {}
extern "C" {
pub fn gtk_init(argc: *mut i32, argv: *mut *mut raw::c_char);
pub fn my_get_win(wid: *mut GtkWindow) -> *mut GdkWindow;
pub fn my_get_xid(w: *mut GdkWindow) -> u64;
pub fn x_init(disp: *mut Display, child: u64, parent: u64);
pub fn g_idle_add(
cb: Option<extern "C" fn(*mut raw::c_void) -> bool>,
data: *mut raw::c_void,
);
pub fn gtk_main();
pub fn gtk_main_quit();
}
gtk_init(&mut 0, std::ptr::null_mut());
inner = wv::webview_create(debug as i32, std::ptr::null_mut() as _);
assert!(!inner.is_null());
let temp_win = wv::webview_get_window(inner);
assert!(!temp_win.is_null());
let temp = my_get_win(temp_win as _);
assert!(!temp.is_null());
let xid = my_get_xid(temp as _);
let flxid = win.raw_handle();
if win_manager("gnome-session") {
win.draw(move |w| {
x_init(app::display() as _, xid, flxid);
app::sleep(0.03);
wv::webview_set_size(inner, w.w(), w.h(), 0);
});
win.flush();
} else {
x_init(app::display() as _, xid, flxid);
win.draw(move |w| wv::webview_set_size(inner, w.w(), w.h(), 0));
}
extern "C" fn cb(_data: *mut raw::c_void) -> bool {
app::check()
}
g_idle_add(Some(cb), std::ptr::null_mut());
app::add_idle(|| gtk_main());
let mut topwin =
window::Window::from_widget_ptr(win.top_window().unwrap().as_widget_ptr());
topwin.set_callback(move |t| {
if app::event() == enums::Event::Close {
gtk_main_quit();
t.hide();
}
});
}
}
assert!(!inner.is_null());
let inner = Arc::new(inner);
Self { inner }
}
pub fn navigate(&self, url: &str) {
let url = std::ffi::CString::safe_new(url);
unsafe {
wv::webview_navigate(*self.inner, url.as_ptr() as _);
}
}
pub fn set_html(&self, html: &str) {
self.navigate(&(String::from("data:text/html;charset=utf-8,") + html));
}
pub fn init(&self, js: &str) {
let js = CString::safe_new(js);
unsafe {
wv::webview_init(*self.inner, js.as_ptr());
}
}
pub fn eval(&self, js: &str) {
let js = CString::safe_new(js);
unsafe {
wv::webview_eval(*self.inner, js.as_ptr());
}
}
pub fn dispatch<F>(&mut self, f: F)
where
F: FnOnce(Webview) + Send + 'static,
{
let closure = Box::into_raw(Box::new(f));
extern "C" fn callback<F>(webview: wv::webview_t, arg: *mut raw::c_void)
where
F: FnOnce(Webview) + Send + 'static,
{
let webview = Webview {
inner: Arc::new(webview),
};
let closure: Box<F> = unsafe { Box::from_raw(arg as *mut F) };
(*closure)(webview);
}
unsafe { wv::webview_dispatch(*self.inner, Some(callback::<F>), closure as *mut _) }
}
pub fn bind<F>(&self, name: &str, f: F)
where
F: FnMut(&str, &str),
{
let name = CString::safe_new(name);
let closure = Box::new(f);
extern "C" fn callback<F: FnMut(&str, &str)>(
seq: *const raw::c_char,
req: *const raw::c_char,
arg: *mut raw::c_void,
) {
let seq = unsafe {
CStr::from_ptr(seq)
.to_str()
.expect("No null bytes in parameter seq")
};
let req = unsafe {
CStr::from_ptr(req)
.to_str()
.expect("No null bytes in parameter req")
};
let mut f: Box<F> = unsafe { Box::from_raw(arg as *mut F) };
(*f)(seq, req);
mem::forget(f);
}
unsafe {
wv::webview_bind(
*self.inner,
name.as_ptr(),
Some(callback::<F>),
Box::into_raw(closure) as *mut _,
)
};
}
pub fn unbind(&self, name: &str) {
let name = CString::safe_new(name);
let _move = move || unsafe { wv::webview_unbind(*self.inner, name.as_ptr()) };
_move();
}
pub fn return_(&self, seq: &str, status: i32, result: &str) {
let seq = CString::safe_new(seq);
let result = CString::safe_new(result);
unsafe { wv::webview_return(*self.inner, seq.as_ptr(), status, result.as_ptr()) }
}
pub fn set_size(&self, width: i32, height: i32, hints: SizeHint) {
unsafe { wv::webview_set_size(*self.inner, width, height, hints as i32) }
}
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn win_manager(prog: &str) -> bool {
let sm = std::env::var("SESSION_MANAGER");
if let Ok(sm) = sm {
let pid = sm.split("/").last();
if let Some(pid) = pid {
match std::process::Command::new("ps")
.args(&["-p", pid, "-o", "comm="])
.output()
{
Ok(out) => {
if String::from_utf8_lossy(&out.stdout).contains(prog) {
true
} else {
false
}
}
_ => false,
}
} else {
false
}
} else {
false
}
}