use std::{cell::RefCell, rc::Rc};
use gtk::{
gdk::{EventButton, EventMask, ModifierType},
prelude::*,
};
use webkit2gtk::{WebView, WebViewExt};
pub fn setup(webview: &WebView) {
webview.add_events(EventMask::BUTTON1_MOTION_MASK | EventMask::BUTTON_PRESS_MASK);
let bf_state = BackForwardState(Rc::new(RefCell::new(0)));
let bf_state_c = bf_state.clone();
webview.connect_button_press_event(move |webview, event| {
let mut inhibit = false;
match event.button() {
8 => {
inhibit = true;
bf_state_c.set(BACK);
webview.run_javascript(
&create_js_mouse_event(event, true, &bf_state_c),
None::<>k::gio::Cancellable>,
|_| {},
);
}
9 => {
inhibit = true;
bf_state_c.set(FORWARD);
webview.run_javascript(
&create_js_mouse_event(event, true, &bf_state_c),
None::<>k::gio::Cancellable>,
|_| {},
);
}
_ => {}
}
if inhibit {
gtk::glib::Propagation::Stop
} else {
gtk::glib::Propagation::Proceed
}
});
let bf_state_c = bf_state.clone();
webview.connect_button_release_event(move |webview, event| {
let mut inhibit = false;
match event.button() {
8 => {
inhibit = true;
bf_state_c.remove(BACK);
webview.run_javascript(
&create_js_mouse_event(event, false, &bf_state_c),
None::<>k::gio::Cancellable>,
|_| {},
);
}
9 => {
inhibit = true;
bf_state_c.remove(FORWARD);
webview.run_javascript(
&create_js_mouse_event(event, false, &bf_state_c),
None::<>k::gio::Cancellable>,
|_| {},
);
}
_ => {}
}
if inhibit {
gtk::glib::Propagation::Stop
} else {
gtk::glib::Propagation::Proceed
}
});
}
fn create_js_mouse_event(event: &EventButton, pressed: bool, state: &BackForwardState) -> String {
let event_name = if pressed { "mousedown" } else { "mouseup" };
let button = if event.button() == 8 { 3 } else { 4 };
let (x, y) = event.position();
let (x, y) = (x as i32, y as i32);
let modifers_state = event.state();
let mut buttons = 0;
if modifers_state.contains(ModifierType::BUTTON1_MASK) {
buttons += 1;
}
if modifers_state.contains(ModifierType::BUTTON3_MASK) {
buttons += 2;
}
if modifers_state.contains(ModifierType::BUTTON2_MASK) {
buttons += 4;
}
if state.has(BACK) {
buttons += 8;
}
if state.has(FORWARD) {
buttons += 16;
}
format!(
r#"(() => {{
const el = document.elementFromPoint({x},{y});
const ev = new MouseEvent('{event_name}', {{
view: window,
button: {button},
buttons: {buttons},
x: {x},
y: {y},
bubbles: true,
detail: {detail},
cancelBubble: false,
cancelable: true,
clientX: {x},
clientY: {y},
composed: true,
layerX: {x},
layerY: {y},
pageX: {x},
pageY: {y},
screenX: window.screenX + {x},
screenY: window.screenY + {y},
ctrlKey: {ctrl_key},
metaKey: {meta_key},
shiftKey: {shift_key},
altKey: {alt_key},
}});
el.dispatchEvent(ev)
if (!ev.defaultPrevented && "{event_name}" === "mouseup") {{
if (ev.button === 3) {{
window.history.back();
}}
if (ev.button === 4) {{
window.history.forward();
}}
}}
}})()"#,
event_name = event_name,
x = x,
y = y,
detail = event.click_count().unwrap_or(1),
ctrl_key = modifers_state.contains(ModifierType::CONTROL_MASK),
alt_key = modifers_state.contains(ModifierType::MOD1_MASK),
shift_key = modifers_state.contains(ModifierType::SHIFT_MASK),
meta_key = modifers_state.contains(ModifierType::SUPER_MASK),
button = button,
buttons = buttons,
)
}
const BACK: u8 = 0b01;
const FORWARD: u8 = 0b10;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct BackForwardState(Rc<RefCell<u8>>);
impl BackForwardState {
fn set(&self, button: u8) {
*self.0.borrow_mut() |= button
}
fn remove(&self, button: u8) {
*self.0.borrow_mut() &= !button
}
fn has(&self, button: u8) -> bool {
let state = *self.0.borrow();
state & !button != state
}
}