fltk_webview/
lib.rs

1#![allow(clippy::needless_doctest_main)]
2#![doc = include_str!("../README.md")]
3
4use fltk::{
5    app, enums,
6    prelude::{GroupExt, WidgetBase, WidgetExt, WindowExt},
7    window,
8};
9use fltk_webview_sys as wv;
10use std::os::raw;
11use std::sync::Arc;
12pub use wv::SizeHint;
13pub use wv::Webview;
14
15// Linux path is unified under X11; Wayland embedding is not supported.
16
17pub trait FromFltkWindow {
18    fn create(debug: bool, win: &mut window::Window) -> Webview;
19}
20
21impl FromFltkWindow for Webview {
22    /// Create a Webview from an embedded fltk window. Requires that the window is already shown
23    fn create(debug: bool, win: &mut window::Window) -> Webview {
24        assert!(win.shown());
25        win.end();
26        win.set_color(enums::Color::White);
27        let inner;
28        unsafe {
29            #[cfg(target_os = "windows")]
30            {
31                extern "C" {
32                    pub fn move_focus(wv: *mut wv::webview_t);
33                }
34                extern "system" {
35                    pub fn SetFocus(child: *mut ()) -> *mut ();
36                    pub fn CoInitializeEx(pvReserved: *mut (), dwCoInit: u32) -> i32;
37                    // pub fn SendMessageW(
38                    //     hwnd: *mut (),
39                    //     msg: u32,
40                    //     wparam: usize,
41                    //     lparam: isize,
42                    // ) -> isize;
43                    pub fn SetParent(child: *mut (), parent: *mut ()) -> *mut ();
44                    pub fn SetWindowPos(
45                        hwnd: *mut (),
46                        hwnd_insert_after: *mut (),
47                        x: i32,
48                        y: i32,
49                        cx: i32,
50                        cy: i32,
51                        flags: u32,
52                    ) -> i32;
53                    // pub fn GetWindowLongW(hwnd: *mut (), index: i32) -> i32;
54                    pub fn SetWindowLongW(hwnd: *mut (), index: i32, new_long: i32) -> i32;
55                }
56                const COINIT_APARTMENTTHREADED: u32 = 0x2;
57                // const WM_SIZE: u32 = 0x0005;
58                CoInitializeEx(std::ptr::null_mut(), COINIT_APARTMENTTHREADED);
59                inner = wv::webview_create(debug as i32, std::ptr::null_mut());
60                wv::webview_set_size(inner, win.w(), win.h(), 3);
61                let wv_hwnd = wv::webview_get_window(inner);
62                // Manually set the webview window as a child and position it
63                const GWL_STYLE: i32 = -16;
64                const WS_CHILD: i32 = 0x40000000;
65                const WS_VISIBLE: i32 = 0x10000000;
66                const SWP_NOZORDER: u32 = 0x0004;
67                const SWP_NOACTIVATE: u32 = 0x0010;
68                const SWP_FRAMECHANGED: u32 = 0x0020;
69                // Change window style to WS_CHILD to remove decorations
70                SetWindowLongW(wv_hwnd as _, GWL_STYLE, WS_CHILD | WS_VISIBLE);
71                SetParent(wv_hwnd as _, win.raw_handle() as _);
72                SetWindowPos(
73                    wv_hwnd as _,
74                    std::ptr::null_mut(),
75                    0,
76                    0,
77                    win.w(),
78                    win.h(),
79                    SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED,
80                );
81                move_focus(inner as *mut _);
82                win.resize_callback(move |w, _, _, _, _| {
83                    wv::webview_set_size(inner, w.w(), w.h(), 3);
84                });
85                win.resize(win.x(), win.y(), win.w(), win.h());
86                win.handle(move |_w, ev| {
87                    if ev == enums::Event::Focus {
88                        move_focus(inner as *mut _);
89                        true
90                    } else {
91                        false
92                    }
93                });
94                let mut topwin =
95                    window::Window::from_widget_ptr(win.top_window().unwrap().as_widget_ptr());
96                topwin.set_callback(|t| {
97                    if app::event() == enums::Event::Close {
98                        t.hide();
99                    }
100                });
101                topwin.assume_derived();
102                topwin.handle(move |w, ev| match ev {
103                    fltk::enums::Event::Push => {
104                        SetFocus(w.raw_handle() as _);
105                        true
106                    }
107                    _ => false,
108                });
109            }
110            #[cfg(target_os = "macos")]
111            {
112                pub enum NSWindow {}
113                extern "C" {
114                    pub fn make_delegate(
115                        child: *mut NSWindow,
116                        parent: *mut NSWindow,
117                        add_menu: i32,
118                    );
119                    pub fn my_close_win(win: *mut NSWindow);
120                }
121                let handle = win.raw_handle();
122                inner = wv::webview_create(debug as i32, handle as _);
123                make_delegate(wv::webview_get_window(inner) as _, handle as _, 1);
124                win.resize_callback(move |w, _, _, _, _| {
125                    wv::webview_set_size(inner, w.w(), w.h(), 0);
126                });
127                win.resize(win.x(), win.y(), win.w(), win.h());
128                let mut topwin =
129                    window::Window::from_widget_ptr(win.top_window().unwrap().as_widget_ptr());
130                let inner = inner.clone();
131                topwin.set_callback(move |t| {
132                    if app::event() == enums::Event::Close {
133                        my_close_win(wv::webview_get_window(inner) as _);
134                        t.hide();
135                    }
136                });
137            }
138            #[cfg(not(any(target_os = "macos", target_os = "windows")))]
139            {
140                pub enum GdkWindow {}
141                pub enum GtkWindow {}
142                pub enum Display {}
143                extern "C" {
144                    pub fn gtk_init(argc: *mut i32, argv: *mut *mut raw::c_char);
145                    pub fn my_gtk_events_pending() -> i32;
146                    pub fn my_get_win(wid: *mut GtkWindow) -> *mut GdkWindow;
147                    pub fn my_get_xid(w: *mut GdkWindow) -> u64;
148                    pub fn x_init(disp: *mut Display, child: u64, parent: u64);
149                    pub fn x_focus(disp: *mut Display, child: u64);
150                    pub fn gtk_main_iteration_do(blocking: bool);
151                }
152                std::env::set_var("GDK_BACKEND", "x11");
153                gtk_init(&mut 0, std::ptr::null_mut());
154                inner = wv::webview_create(debug as i32, std::ptr::null_mut() as _);
155                assert!(!inner.is_null());
156                let temp_win = wv::webview_get_window(inner);
157                assert!(!temp_win.is_null());
158                let temp = my_get_win(temp_win as _);
159                assert!(!temp.is_null());
160                let xid = my_get_xid(temp as _);
161                let flxid = win.raw_handle();
162
163                // Unified X11 path: make child unmanaged and reparent into FLTK window
164                x_init(app::display() as _, xid, flxid);
165                // Ensure input focus goes to the embedded child when shown
166                x_focus(app::display() as _, xid);
167
168                win.resize_callback(move |w, _, _, _, _| {
169                    wv::webview_set_size(inner, w.w(), w.h(), 0);
170                });
171                win.resize(win.x(), win.y(), win.w(), win.h());
172                // Set focus to child on mouse press to ensure keystrokes reach WebKit
173                let xid_for_focus = xid;
174                win.handle(move |_, ev| {
175                    if ev == enums::Event::Push {
176                        x_focus(app::display() as _, xid_for_focus);
177                        true
178                    } else {
179                        false
180                    }
181                });
182
183                app::add_timeout3(0.016, |handle| {
184                    let mut spins = 0;
185                    while my_gtk_events_pending() != 0 && spins < 4 {
186                        gtk_main_iteration_do(false);
187                        spins += 1;
188                    }
189                    app::repeat_timeout3(0.016, handle);
190                });
191            }
192        }
193        assert!(!inner.is_null());
194        #[allow(clippy::arc_with_non_send_sync)]
195        let inner = Arc::new(inner);
196        Webview::from_raw(inner)
197    }
198}