ltk_webkit/wpe/
file_chooser.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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! `<input type="file">`: WebKit's `run-file-chooser`, answered by the host with the files the user chose.

use std::ffi::{ c_char, CStr, CString };
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::ptr;
use std::sync::{ Mutex, Weak };

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

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

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

/// A page's request for files, which must be answered: dropped unanswered, it is cancelled.
pub struct FileRequest
{
	request:    *mut WebKitFileChooserRequest,
	multiple:   bool,
	mime_types: Vec<String>,
}

impl FileRequest
{
	/// Whether the page takes more than one file.
	pub fn multiple( &self ) -> bool { self.multiple }

	/// The MIME types the page accepts, such as `image/*`; empty when it takes any file.
	pub fn mime_types( &self ) -> &[ String ] { &self.mime_types }

	/// Hand the page these files, by their local paths.
	pub fn select( mut self, paths: &[ &Path ] )
	{
		let owned: Vec<CString> = paths.iter().filter_map( |p| CString::new( p.as_os_str().as_bytes() ).ok() ).collect();
		let mut list: Vec<*const c_char> = owned.iter().map( |s| s.as_ptr() ).collect();
		list.push( ptr::null() );
		let request = std::mem::replace( &mut self.request, ptr::null_mut() );
		// SAFETY: the request is alive while we hold its reference; WebKit copies the NULL-terminated list it is given.
		unsafe
		{
			webkit_file_chooser_request_select_files( request, list.as_ptr() );
			g_object_unref( request as *mut GObject );
		}
	}

	/// Tell the page no file was chosen.
	pub fn cancel( self ) {}
}

impl Drop for FileRequest
{
	fn drop( &mut self )
	{
		if self.request.is_null() { return; }
		// SAFETY: the request is alive while we hold its reference, which this releases.
		unsafe
		{
			webkit_file_chooser_request_cancel( self.request );
			g_object_unref( self.request as *mut GObject );
		}
	}
}

impl WebView
{
	/// The page's pending request for files, if it made one since the previous call.
	pub fn take_file_request( &self ) -> Option<FileRequest>
	{
		self.inner.lock().ok()?.file_request.take()
	}
}

pub( crate ) unsafe fn connect( webview: *mut WebKitWebView, inner: Weak<Mutex<Inner>> )
{
	// SAFETY: the handler matches the signal's signature; the closure owns its boxed `Weak`.
	unsafe
	{
		let handler = std::mem::transmute::<RunFileChooserFn, unsafe extern "C" fn()>( on_run_file_chooser );
		let data    = Box::into_raw( Box::new( inner ) ) as gpointer;
		g_signal_connect_data( webview as *mut GObject, b"run-file-chooser\0".as_ptr() as *const c_char, Some( handler ), data, Some( drop_weak ), 0 );
	}
}

unsafe extern "C" fn on_run_file_chooser( _webview: *mut WebKitWebView, request: *mut WebKitFileChooserRequest, user_data: gpointer ) -> gboolean
{
	// SAFETY: `user_data` is the boxed `Weak` owned by the closure.
	let Some( inner ) = unsafe { &*( user_data as *const Weak<Mutex<Inner>> ) }.upgrade() else { return GFALSE };
	// SAFETY: WebKit hands a live request; the reference taken here is released by `FileRequest`.
	let pending = unsafe
	{
		g_object_ref( request as *mut GObject );
		FileRequest
		{
			request,
			multiple:   webkit_file_chooser_request_get_select_multiple( request ) != 0,
			mime_types: strings( webkit_file_chooser_request_get_mime_types( request ) ),
		}
	};
	match inner.lock()
	{
		Ok( mut g ) => g.file_request = Some( pending ),
		Err( _ )    => return GFALSE,
	}
	GTRUE
}

/// A NULL-terminated array of C strings, which WebKit keeps.
unsafe fn strings( list: *const *const c_char ) -> Vec<String>
{
	let mut out = Vec::new();
	if list.is_null() { return out; }
	let mut i = 0;
	// SAFETY: the caller passes a NULL-terminated array of valid C strings.
	unsafe
	{
		while !( *list.add( i ) ).is_null()
		{
			out.push( CStr::from_ptr( *list.add( i ) ).to_string_lossy().into_owned() );
			i += 1;
		}
	}
	out
}