git.lucas.co / cce-browser
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 }