#![cfg(all(target_family = "unix", not(target_os = "macos")))]
use gdk_sys::{gdk_threads_add_idle, GdkRGBA};
use gio_sys::GAsyncResult;
use glib_sys::*;
use gobject_sys::{g_signal_connect_data, GObject};
use gtk_sys::*;
use javascriptcore_sys::*;
use libc::{c_char, c_double, c_int, c_void};
use std::ffi::CStr;
use std::mem;
use std::ptr;
use webkit2gtk_sys::*;
type ExternalInvokeCallback = extern "C" fn(webview: *mut WebView, arg: *const c_char);
#[repr(C)]
struct WebView {
url: *const c_char,
title: *const c_char,
width: c_int,
height: c_int,
resizable: c_int,
debug: c_int,
frameless: c_int,
external_invoke_cb: ExternalInvokeCallback,
window: *mut GtkWidget,
scroller: *mut GtkWidget,
webview: *mut GtkWidget,
inspector_window: *mut GtkWidget,
queue: *mut GAsyncQueue,
ready: c_int,
js_busy: c_int,
should_exit: c_int,
userdata: *mut c_void,
}
#[no_mangle]
unsafe extern "C" fn webview_set_title(webview: *mut WebView, title: *const c_char) {
gtk_window_set_title(mem::transmute((*webview).window), title);
}
#[no_mangle]
unsafe extern "C" fn webview_set_fullscreen(webview: *mut WebView, fullscreen: c_int) {
if fullscreen > 0 {
gtk_window_fullscreen(mem::transmute((*webview).window));
} else {
gtk_window_unfullscreen(mem::transmute((*webview).window));
}
}
#[no_mangle]
unsafe extern "C" fn webview_new(
title: *const c_char,
url: *const c_char,
width: c_int,
height: c_int,
resizable: c_int,
debug: c_int,
frameless: c_int,
external_invoke_cb: ExternalInvokeCallback,
userdata: *mut c_void,
) -> *mut WebView {
let w = Box::new(WebView {
url,
title,
width,
height,
resizable,
debug,
frameless,
external_invoke_cb,
window: ptr::null_mut(),
scroller: ptr::null_mut(),
webview: ptr::null_mut(),
inspector_window: ptr::null_mut(),
queue: ptr::null_mut(),
ready: 0,
js_busy: 0,
should_exit: 0,
userdata,
});
let w = Box::into_raw(w);
if gtk_init_check(ptr::null_mut(), ptr::null_mut()) == GFALSE {
return ptr::null_mut();
}
(*w).queue = g_async_queue_new();
let window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(mem::transmute(window), title);
(*w).window = window;
if resizable > 0 {
gtk_window_set_default_size(mem::transmute(window), width, height);
} else {
gtk_widget_set_size_request(mem::transmute(window), width, height);
}
if frameless > 0 {
gtk_window_set_decorated(mem::transmute(window), 0);
}
gtk_window_set_resizable(mem::transmute(window), resizable);
gtk_window_set_position(mem::transmute(window), GTK_WIN_POS_CENTER);
let scroller = gtk_scrolled_window_new(ptr::null_mut(), ptr::null_mut());
gtk_container_add(mem::transmute(window), scroller);
(*w).scroller = scroller;
let m = webkit_user_content_manager_new();
webkit_user_content_manager_register_script_message_handler(
m,
CStr::from_bytes_with_nul_unchecked(b"external\0").as_ptr(),
);
g_signal_connect_data(
mem::transmute(m),
CStr::from_bytes_with_nul_unchecked(b"script-message-received::external\0").as_ptr(),
Some(mem::transmute(external_message_received_cb as *const ())),
mem::transmute(w),
None,
0,
);
let webview = webkit_web_view_new_with_user_content_manager(m);
(*w).webview = webview;
webkit_web_view_load_uri(
mem::transmute(webview),
if url.is_null() {
b"\0".as_ptr() as *const i8
} else {
url
},
);
g_signal_connect_data(
mem::transmute(webview),
CStr::from_bytes_with_nul_unchecked(b"load-changed\0").as_ptr(),
Some(mem::transmute(webview_load_changed_cb as *const ())),
mem::transmute(w),
None,
0,
);
gtk_container_add(mem::transmute(scroller), webview);
let settings = webkit_web_view_get_settings(mem::transmute(webview));
webkit_settings_set_enable_webgl(settings, 1);
webkit_settings_set_enable_accelerated_2d_canvas(settings, 1);
if debug > 0 {
webkit_settings_set_enable_write_console_messages_to_stdout(settings, 1);
webkit_settings_set_enable_developer_extras(settings, 1);
} else {
g_signal_connect_data(
mem::transmute(webview),
CStr::from_bytes_with_nul_unchecked(b"context-menu\0").as_ptr(),
Some(mem::transmute(webview_context_menu_cb as *const ())),
mem::transmute(w),
None,
0,
);
}
gtk_widget_show_all(window);
webkit_web_view_run_javascript(
mem::transmute(webview),
CStr::from_bytes_with_nul_unchecked(b"window.external={invoke:function(x){window.webkit.messageHandlers.external.postMessage(x);}}\0").as_ptr(),
ptr::null_mut(),
None,
ptr::null_mut(),
);
g_signal_connect_data(
mem::transmute(window),
CStr::from_bytes_with_nul_unchecked(b"destroy\0").as_ptr(),
Some(mem::transmute(webview_destroy_cb as *const ())),
mem::transmute(w),
None,
0,
);
w
}
extern "C" fn webview_context_menu_cb(
_webview: *mut WebKitWebView,
_default_menu: *mut GtkWidget,
_hit_test_result: *mut WebKitHitTestResult,
_triggered_with_keyboard: gboolean,
_userdata: gboolean,
) -> gboolean {
GTRUE
}
unsafe extern "C" fn external_message_received_cb(
_m: *mut WebKitUserContentManager,
r: *mut WebKitJavascriptResult,
arg: gpointer,
) {
let webview: *mut WebView = mem::transmute(arg);
let context = webkit_javascript_result_get_global_context(r);
let value = webkit_javascript_result_get_value(r);
let js = JSValueToStringCopy(context, value, ptr::null_mut());
let n = JSStringGetMaximumUTF8CStringSize(js);
let mut s = Vec::new();
s.reserve(n);
JSStringGetUTF8CString(js, s.as_mut_ptr(), n);
((*webview).external_invoke_cb)(webview, s.as_ptr());
}
#[no_mangle]
unsafe extern "C" fn webview_get_user_data(webview: *mut WebView) -> *mut c_void {
(*webview).userdata
}
#[no_mangle]
unsafe extern "C" fn webview_free(webview: *mut WebView) {
let _ = Box::from_raw(webview);
}
#[no_mangle]
unsafe extern "C" fn webview_loop(webview: *mut WebView, blocking: c_int) -> c_int {
gtk_main_iteration_do(blocking);
(*webview).should_exit
}
#[no_mangle]
unsafe extern "C" fn webview_set_color(webview: *mut WebView, r: u8, g: u8, b: u8, a: u8) {
let color = GdkRGBA {
red: r as c_double / 255.0,
green: g as c_double / 255.0,
blue: b as c_double / 255.0,
alpha: a as c_double / 255.0,
};
webkit_web_view_set_background_color(mem::transmute((*webview).webview), &color);
}
unsafe extern "C" fn webview_load_changed_cb(
_webview: *mut WebKitWebView,
event: WebKitLoadEvent,
arg: gpointer,
) {
let w: *mut WebView = mem::transmute(arg);
if event == WEBKIT_LOAD_FINISHED {
(*w).ready = 1;
}
}
unsafe extern "C" fn webview_eval_finished(
_object: *mut GObject,
_result: *mut GAsyncResult,
userdata: gpointer,
) {
let webview: *mut WebView = mem::transmute(userdata);
(*webview).js_busy = 0;
}
#[no_mangle]
unsafe extern "C" fn webview_eval(webview: *mut WebView, js: *const c_char) -> c_int {
while (*webview).ready == 0 {
g_main_context_iteration(ptr::null_mut(), GTRUE);
}
(*webview).js_busy = 1;
webkit_web_view_run_javascript(
mem::transmute((*webview).webview),
js,
ptr::null_mut(),
Some(webview_eval_finished),
mem::transmute(webview),
);
while (*webview).js_busy == 1 {
g_main_context_iteration(ptr::null_mut(), GTRUE);
}
0
}
type DispatchFn = extern "C" fn(webview: *mut WebView, arg: *mut c_void);
#[repr(C)]
struct DispatchArg {
func: DispatchFn,
webview: *mut WebView,
arg: *mut c_void,
}
unsafe extern "C" fn webview_dispatch_wrapper(userdata: gpointer) -> gboolean {
let webview: *mut WebView = mem::transmute(userdata);
loop {
let arg: *mut DispatchArg = mem::transmute(g_async_queue_try_pop((*webview).queue));
if arg.is_null() {
break;
}
((*arg).func)(webview, (*arg).arg);
let _ = Box::from_raw(arg);
}
GFALSE
}
#[no_mangle]
unsafe extern "C" fn webview_dispatch(webview: *mut WebView, func: DispatchFn, arg: *mut c_void) {
let arg = Box::new(DispatchArg { func, webview, arg });
let queue = (*webview).queue;
g_async_queue_lock(queue);
g_async_queue_push_unlocked(queue, mem::transmute(Box::into_raw(arg)));
if g_async_queue_length_unlocked(queue) == 1 {
gdk_threads_add_idle(Some(webview_dispatch_wrapper), mem::transmute(webview));
}
g_async_queue_unlock(queue);
}
#[no_mangle]
unsafe extern "C" fn webview_destroy_cb(_widget: *mut GtkWidget, arg: gpointer) {
webview_exit(mem::transmute(arg));
}
#[no_mangle]
unsafe extern "C" fn webview_exit(webview: *mut WebView) {
(*webview).should_exit = 1;
}
#[no_mangle]
unsafe extern "C" fn webview_print_log(s: *const c_char) {
let format = std::ffi::CString::new("%s\n").unwrap();
libc::printf(format.as_ptr(), s);
}