Crate ltk_webkit

Source
Expand description

WPE WebKit integration for the LTK toolkit.

Exposes WebView, a wrapper around a WebKitWebView driven on the WPEPlatform path with the headless display backend. The view’s pixels are imported as an EGLImage per frame and re-targeted onto a GL texture inside LTK’s GLES context, so the page composites inline with the rest of the LTK widget tree — no hole-punching, no separate Wayland surface for WebKit.

§Quick start

use ltk::{ App, CursorShape, Element, Keysym };
use ltk_webkit::WebView;

#[ derive( Clone ) ]
enum Msg { Tick }

struct DemoApp { webview: WebView }

impl App for DemoApp
{
    type Message = Msg;

    fn view( &self ) -> Element<Msg>
    {
        ltk::column().push( self.webview.element::<Msg>() ).into()
    }
    fn update( &mut self, _: Msg ) {}

    fn poll_external( &mut self ) -> Vec<Msg>
    {
        self.webview.tick();
        vec![ Msg::Tick ]
    }
    fn poll_interval( &self ) -> Option<std::time::Duration>
    {
        Some( std::time::Duration::from_millis( 16 ) )
    }
    fn invalidate_after( &self, _: &Msg ) -> ltk::InvalidationScope
    {
        if self.webview.take_redraw_request() { ltk::InvalidationScope::All }
        else { ltk::InvalidationScope::Only( Vec::new() ) }
    }

    // Forward LTK input to WebKit.
    fn on_pointer_move( &mut self, x: f32, y: f32 )
    {
        self.webview.pointer_move( x as f64, y as f64 );
    }
    fn on_pointer_button( &mut self, x: f32, y: f32, pressed: bool )
    {
        if pressed { self.webview.pointer_press(   x as f64, y as f64 ); }
        else       { self.webview.pointer_release( x as f64, y as f64 ); }
    }
    fn on_pointer_axis( &mut self, x: f32, y: f32, dx: f32, dy: f32 )
    {
        let scale = -1.0_f32 / 3.0;
        self.webview.scroll( x as f64, y as f64, ( dx * scale ) as f64, ( dy * scale ) as f64 );
    }
    fn on_raw_key( &mut self, ks: Keysym, kc: u32, pressed: bool, ctrl: bool, shift: bool )
    {
        self.webview.send_key( ks.raw(), kc, pressed, ctrl, shift );
    }
    fn cursor_override( &self ) -> Option<CursorShape>
    {
        Some( self.webview.cursor_shape() )
    }

    fn window_config( &self ) -> Option<( &str, &str )>
    {
        Some( ( "ltk-webkit demo", "net.liberux.ltk-webkit.demo" ) )
    }
}

let webview = WebView::new( 800.0, 600.0, "https://example.com" )?;
ltk::run( DemoApp { webview } );

§What is wired up

Forwarded into WebKit via the methods on WebView:

LTK callbackWebView methodWPE event
App::on_pointer_moveWebView::pointer_movePOINTER_MOVE
App::on_pointer_button( pressed )WebView::pointer_press / WebView::pointer_releasePOINTER_DOWN/POINTER_UP
App::on_pointer_axisWebView::scrollSCROLL
App::on_raw_keyWebView::send_keyKEYBOARD_KEY_DOWN/UP
App::on_touch_down / move / upWebView::touch_down / WebView::touch_move / WebView::touch_upTOUCH_DOWN/TOUCH_MOVE/TOUCH_UP
layout rect change (per frame)(auto via WebView::element)WPEToplevel::resize

On a touchscreen the host must also return true from ltk’s App::claims_raw_touch — otherwise the primary finger is consumed by ltk’s widget gesture machine and never reaches these callbacks. WebKit’s own gesture recognition then turns the raw stream into taps, kinetic scrolls and pinch-zoom.

Forwarded out of WebKit (read by LTK):

WhatMethod
New rendered frame readyWebView::take_redraw_request
Cursor shape WebKit wantsWebView::cursor_shape

§Threading

Single-threaded usage only. WPE’s GMainLoop, the Wayland event loop and LTK’s render loop must all run on the same thread (the main thread, in practice). WebView exposes Send + Sync because the [ltk::ExternalSource::Texture] closure requires it, but the FFI pointers it holds are not actually safe to use across threads — calling WebView::tick or rendering off the main thread is undefined behaviour.

§Caveats

  • Press counts (single / double / triple click) come from wpe_view_compute_press_count, which uses a position + time window heuristic; quirky touchpads may need calibration.
  • Cursor changes incur a WebKit-IPC round trip; the host’s invalidate_after should return All whenever WebView::take_redraw_request is true to avoid stale-frame lag.
  • Cursor names WebKit emits but LTK doesn’t enumerate (col-resize, row-resize, zoom-in, none, …) fall back to Default.
  • Drop of an internal Inner does not destroy the bound EGLImage — minor leak, acceptable for the spike.

Structs§