web browser (Servo)
git clone https://git.lucas.co/cce-browser.git
examples/wpe_spike.rs (7.8K)
1 //! Rust port of `spike/wpe-spike.c` — the WPE embedding contract, from Rust.
2 //!
3 //! Run with: `cargo run --release -p cce-browser --features wpe --example wpe_spike`
4 //! Writes the second rendered frame to `/tmp/wpe-spike-rs.ppm`.
5 //!
6 //! The point of this example is the **subclassing**, which is the only part
7 //! of the port that is not ordinary FFI. WPE's instance structs are opaque
8 //! (`WPE_DECLARE_DERIVABLE_TYPE` typedefs `struct _WPEView` and never defines
9 //! it), so we cannot write `struct SpikeView { parent: WPEView }` the way the
10 //! C spike does. Instead `g_type_query` reports the parent's instance and
11 //! class sizes at runtime and we register against those — ABI-safe, and it
12 //! keeps working if WPE grows a field. The *class* structs are public, so
13 //! bindgen lays them out correctly and vfunc assignment is a plain field set.
14 //!
15 //! See WPE-PORT.md for the two traps this encodes: the toplevel owns format
16 //! negotiation, and the buffer handshake has two halves.
17
18 #![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)]
19
20 use std::ffi::{c_char, c_void, CString};
21 use std::io::Write;
22 use std::sync::atomic::{AtomicU32, Ordering};
23
24 mod ffi {
25 #![allow(non_upper_case_globals, non_camel_case_types, non_snake_case, dead_code)]
26 include!(concat!(env!("OUT_DIR"), "/wpe_bindings.rs"));
27 }
28 use ffi::*;
29
30 static FRAMES: AtomicU32 = AtomicU32::new(0);
31 static mut LOOP_PTR: *mut GMainLoop = std::ptr::null_mut();
32
33 /// Register a GObject subclass of `parent`, sizing it from the runtime type
34 /// query rather than from a struct layout we cannot see.
35 unsafe fn register_subclass(
36 parent: GType,
37 name: &str,
38 class_init: unsafe extern "C" fn(*mut c_void, *mut c_void),
39 ) -> GType {
40 let mut q: GTypeQuery = std::mem::zeroed();
41 g_type_query(parent, &mut q);
42 assert!(q.type_ != 0, "parent type not registered");
43 let cname = CString::new(name).unwrap();
44 g_type_register_static_simple(
45 parent,
46 cname.as_ptr(),
47 q.class_size,
48 std::mem::transmute::<_, GClassInitFunc>(class_init),
49 q.instance_size,
50 None,
51 0,
52 )
53 }
54
55 // ---- the view: receives rendered buffers ----
56
57 unsafe extern "C" fn view_render_buffer(
58 view: *mut WPEView,
59 buffer: *mut WPEBuffer,
60 _damage: *const WPERectangle,
61 n_damage: u32,
62 _error: *mut *mut GError,
63 ) -> gboolean {
64 let n = FRAMES.fetch_add(1, Ordering::SeqCst) + 1;
65 let (w, h) = (wpe_buffer_get_width(buffer), wpe_buffer_get_height(buffer));
66 let is_shm = g_type_check_instance_is_a(buffer as *mut GTypeInstance, wpe_buffer_shm_get_type()) != 0;
67 println!("render_buffer #{n}: {w}x{h} shm={is_shm} damage_rects={n_damage}");
68
69 if is_shm && n == 2 {
70 let shm = buffer as *mut WPEBufferSHM;
71 let bytes = wpe_buffer_shm_get_data(shm);
72 let mut len: u64 = 0;
73 let px = g_bytes_get_data(bytes, &mut len as *mut u64) as *const u8;
74 let stride = wpe_buffer_shm_get_stride(shm) as usize;
75 println!(" bytes={len} stride={stride} format={}", wpe_buffer_shm_get_format(shm));
76
77 // ARGB8888 is B,G,R,A in memory on little-endian.
78 let mut out = Vec::with_capacity(w as usize * h as usize * 3 + 32);
79 out.extend_from_slice(format!("P6\n{w} {h}\n255\n").as_bytes());
80 for y in 0..h as usize {
81 for x in 0..w as usize {
82 let p = px.add(y * stride + x * 4);
83 out.push(*p.add(2));
84 out.push(*p.add(1));
85 out.push(*p);
86 }
87 }
88 let _ = std::fs::File::create("/tmp/wpe-spike-rs.ppm").map(|mut f| f.write_all(&out));
89 println!(" WROTE /tmp/wpe-spike-rs.ppm");
90 g_main_loop_quit(LOOP_PTR);
91 }
92
93 // Both halves, or the engine produces one frame and stalls forever.
94 wpe_view_buffer_rendered(view, buffer);
95 wpe_view_buffer_released(view, buffer);
96 1
97 }
98
99 unsafe extern "C" fn view_class_init(class: *mut c_void, _data: *mut c_void) {
100 (*(class as *mut WPEViewClass)).render_buffer = Some(view_render_buffer);
101 }
102
103 // ---- the toplevel: WebKit asks IT for buffer formats ----
104
105 unsafe extern "C" fn toplevel_formats(_t: *mut WPEToplevel) -> *mut WPEBufferFormats {
106 let b = wpe_buffer_formats_builder_new(std::ptr::null_mut());
107 wpe_buffer_formats_builder_append_group(
108 b,
109 std::ptr::null_mut(),
110 WPEBufferFormatUsage::WPE_BUFFER_FORMAT_USAGE_MAPPING,
111 );
112 for cc in [fourcc(b'A', b'R', b'2', b'4'), fourcc(b'X', b'R', b'2', b'4')] {
113 wpe_buffer_formats_builder_append_format(b, cc, 0);
114 }
115 wpe_buffer_formats_builder_end(b)
116 }
117
118 unsafe extern "C" fn toplevel_resize(t: *mut WPEToplevel, w: i32, h: i32) -> gboolean {
119 println!("toplevel resize -> {w}x{h}");
120 wpe_toplevel_resized(t, w, h);
121 1
122 }
123
124 unsafe extern "C" fn toplevel_class_init(class: *mut c_void, _data: *mut c_void) {
125 let c = class as *mut WPEToplevelClass;
126 (*c).get_preferred_buffer_formats = Some(toplevel_formats);
127 (*c).resize = Some(toplevel_resize);
128 }
129
130 // ---- the display: vends the view and the toplevel ----
131
132 static mut VIEW_TYPE: GType = 0;
133 static mut TOPLEVEL_TYPE: GType = 0;
134
135 unsafe extern "C" fn display_connect(_d: *mut WPEDisplay, _e: *mut *mut GError) -> gboolean {
136 1
137 }
138
139 unsafe extern "C" fn display_create_view(d: *mut WPEDisplay) -> *mut WPEView {
140 let prop = CString::new("display").unwrap();
141 g_object_new(VIEW_TYPE, prop.as_ptr(), d, std::ptr::null::<c_char>()) as *mut WPEView
142 }
143
144 unsafe extern "C" fn display_create_toplevel(d: *mut WPEDisplay, max_views: u32) -> *mut WPEToplevel {
145 println!("create_toplevel(max_views={max_views})");
146 let (p1, p2) = (CString::new("display").unwrap(), CString::new("max-views").unwrap());
147 g_object_new(TOPLEVEL_TYPE, p1.as_ptr(), d, p2.as_ptr(), max_views, std::ptr::null::<c_char>())
148 as *mut WPEToplevel
149 }
150
151 unsafe extern "C" fn display_class_init(class: *mut c_void, _data: *mut c_void) {
152 let c = class as *mut WPEDisplayClass;
153 (*c).connect = Some(display_connect);
154 (*c).create_view = Some(display_create_view);
155 (*c).create_toplevel = Some(display_create_toplevel);
156 }
157
158 const fn fourcc(a: u8, b: u8, c: u8, d: u8) -> u32 {
159 (a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24)
160 }
161
162 fn main() {
163 let url = std::env::args().nth(1).unwrap_or_else(|| "https://example.com".into());
164 let curl = CString::new(url.clone()).unwrap();
165
166 unsafe {
167 VIEW_TYPE = register_subclass(wpe_view_get_type(), "CceSpikeView", view_class_init);
168 TOPLEVEL_TYPE = register_subclass(wpe_toplevel_get_type(), "CceSpikeToplevel", toplevel_class_init);
169 let display_type = register_subclass(wpe_display_get_type(), "CceSpikeDisplay", display_class_init);
170
171 let display = g_object_new(display_type, std::ptr::null::<c_char>()) as *mut WPEDisplay;
172 let mut err: *mut GError = std::ptr::null_mut();
173 if wpe_display_connect(display, &mut err) == 0 {
174 eprintln!("connect failed");
175 std::process::exit(1);
176 }
177
178 let prop = CString::new("display").unwrap();
179 let wv = g_object_new(webkit_web_view_get_type(), prop.as_ptr(), display, std::ptr::null::<c_char>())
180 as *mut WebKitWebView;
181 let view = webkit_web_view_get_wpe_view(wv);
182 println!("web view={wv:?} wpe_view={view:?}");
183
184 let top = wpe_display_create_toplevel(display, 1);
185 if !top.is_null() {
186 wpe_toplevel_resized(top, 1200, 800);
187 wpe_view_set_toplevel(view, top);
188 }
189 wpe_view_resized(view, 1200, 800);
190 wpe_view_set_visible(view, 1);
191 wpe_view_map(view);
192
193 webkit_web_view_load_uri(wv, curl.as_ptr());
194 LOOP_PTR = g_main_loop_new(std::ptr::null_mut(), 0);
195 g_timeout_add_seconds(30, Some(std::mem::transmute(g_main_loop_quit as *const ())), LOOP_PTR as *mut c_void);
196 g_main_loop_run(LOOP_PTR);
197 }
198 println!("done, {} frames", FRAMES.load(Ordering::SeqCst));
199 }