ltk_webkit/wpe/
fullscreen.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! A page asking for fullscreen, as a video player's button does: WebKit's `enter-fullscreen` and `leave-fullscreen` signals reach the host, which decides what fullscreen means for its window.

use std::ffi::c_char;
use std::ptr;
use std::sync::{ Mutex, Weak };

use glib_sys::{ gboolean, gpointer, GTRUE };
use gobject_sys::{ g_signal_connect_data, GObject };
use wpe_webkit_sys::*;

use super::{ popups, Inner, WebView };

type FullscreenFn = unsafe extern "C" fn( *mut WebKitWebView, gpointer ) -> gboolean;

const EXIT_SCRIPT: &[ u8 ] = b"if ( document.fullscreenElement ) { document.exitFullscreen(); }\0";

impl WebView
{
	/// The fullscreen state the page asked for since the previous call: `true` to enter, `false` to leave.
	pub fn take_fullscreen_request( &self ) -> Option<bool>
	{
		self.inner.lock().ok().and_then( |mut g| g.fullscreen_request.take() )
	}

	/// Tell the page that the host left fullscreen, so its element goes back to its place.
	pub fn exit_fullscreen( &self )
	{
		let Ok( g ) = self.inner.lock() else { return };
		let webview = g.webview;
		drop( g );
		if webview.is_null() { return; }
		// SAFETY: `webview` is owned by `Inner`, which `self` keeps alive; the script is NUL terminated.
		unsafe
		{
			webkit_web_view_evaluate_javascript( webview, EXIT_SCRIPT.as_ptr() as *const c_char, -1, ptr::null(), ptr::null(), ptr::null_mut(), None, ptr::null_mut() );
		}
	}
}

pub( crate ) unsafe fn connect( webview: *mut WebKitWebView, inner: Weak<Mutex<Inner>> )
{
	// SAFETY: the handlers match the signals' signature; each closure owns its boxed `Weak`.
	unsafe
	{
		let enter = std::mem::transmute::<FullscreenFn, unsafe extern "C" fn()>( on_enter );
		let leave = std::mem::transmute::<FullscreenFn, unsafe extern "C" fn()>( on_leave );
		let enter_data = Box::into_raw( Box::new( inner.clone() ) ) as gpointer;
		let leave_data = Box::into_raw( Box::new( inner ) ) as gpointer;
		g_signal_connect_data( webview as *mut GObject, b"enter-fullscreen\0".as_ptr() as *const c_char, Some( enter ), enter_data, Some( popups::drop_weak ), 0 );
		g_signal_connect_data( webview as *mut GObject, b"leave-fullscreen\0".as_ptr() as *const c_char, Some( leave ), leave_data, Some( popups::drop_weak ), 0 );
	}
}

unsafe fn request( user_data: gpointer, enter: bool ) -> gboolean
{
	// SAFETY: `user_data` is the boxed `Weak` owned by the closure.
	if let Some( inner ) = unsafe { &*( user_data as *const Weak<Mutex<Inner>> ) }.upgrade()
	{
		if let Ok( mut g ) = inner.lock() { g.fullscreen_request = Some( enter ); }
	}
	GTRUE
}

unsafe extern "C" fn on_enter( _webview: *mut WebKitWebView, user_data: gpointer ) -> gboolean
{
	// SAFETY: forwarded as received.
	unsafe { request( user_data, true ) }
}

unsafe extern "C" fn on_leave( _webview: *mut WebKitWebView, user_data: gpointer ) -> gboolean
{
	// SAFETY: forwarded as received.
	unsafe { request( user_data, false ) }
}