tauri_runtime_verso/
runtime.rs

1#![allow(unused_variables)]
2
3use tao::{
4    event::{Event as TaoEvent, StartCause},
5    event_loop::{
6        ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy as TaoEventLoopProxy,
7        EventLoopWindowTarget as TaoEventLoopWindowTarget,
8    },
9    platform::run_return::EventLoopExtRunReturn,
10};
11use tauri_runtime::{
12    DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Result, RunEvent, Runtime,
13    RuntimeHandle, RuntimeInitArgs, UserEvent, WindowEventId,
14    dpi::PhysicalPosition,
15    monitor::Monitor,
16    webview::{DetachedWebview, PendingWebview},
17    window::{
18        DetachedWindow, DetachedWindowWebview, PendingWindow, RawWindow, WindowBuilder,
19        WindowEvent, WindowId,
20    },
21};
22use tauri_utils::Theme;
23use url::Url;
24use verso::CustomProtocolBuilder;
25
26use std::{
27    borrow::Cow,
28    collections::HashMap,
29    fmt::{self, Debug},
30    ops::Deref,
31    sync::{
32        Arc, Mutex,
33        atomic::{AtomicU32, Ordering},
34        mpsc::channel,
35    },
36    thread::{ThreadId, current as current_thread},
37};
38
39use crate::{
40    event_loop_ext::TaoEventLoopWindowTargetExt,
41    get_verso_path,
42    utils::{to_tao_theme, to_verso_theme},
43    webview::VersoWebviewDispatcher,
44    window::{VersoWindowDispatcher, Window},
45};
46
47type Task = Box<dyn FnOnce() + Send + 'static>;
48type TaskWithEventLoop<T> = Box<dyn FnOnce(&TaoEventLoopWindowTarget<Message<T>>) + Send + 'static>;
49
50pub enum Message<T: UserEvent> {
51    Task(Task),
52    /// Run task with the [`EventLoopWindowTarget`](TaoEventLoopWindowTarget)
53    TaskWithEventLoop(TaskWithEventLoop<T>),
54    CloseWindow(WindowId),
55    DestroyWindow(WindowId),
56    RequestExit(i32),
57    UserEvent(T),
58}
59
60impl<T: UserEvent> Clone for Message<T> {
61    fn clone(&self) -> Self {
62        match self {
63            Self::UserEvent(t) => Self::UserEvent(t.clone()),
64            _ => unimplemented!(),
65        }
66    }
67}
68
69#[derive(Clone)]
70pub struct DispatcherMainThreadContext<T: UserEvent> {
71    window_target: TaoEventLoopWindowTarget<Message<T>>,
72}
73
74// SAFETY: we ensure this type is only used on the main thread.
75#[allow(clippy::non_send_fields_in_send_ty)]
76unsafe impl<T: UserEvent> Send for DispatcherMainThreadContext<T> {}
77
78// SAFETY: we ensure this type is only used on the main thread.
79#[allow(clippy::non_send_fields_in_send_ty)]
80unsafe impl<T: UserEvent> Sync for DispatcherMainThreadContext<T> {}
81
82#[derive(Clone)]
83pub struct RuntimeContext<T: UserEvent> {
84    windows: Arc<Mutex<HashMap<WindowId, Window>>>,
85    prefered_theme: Arc<Mutex<Option<Theme>>>,
86    event_proxy: TaoEventLoopProxy<Message<T>>,
87    // This must only be used on main thread
88    main_thread: DispatcherMainThreadContext<T>,
89    main_thread_id: ThreadId,
90    next_window_id: Arc<AtomicU32>,
91    next_webview_id: Arc<AtomicU32>,
92    next_window_event_id: Arc<AtomicU32>,
93    next_webview_event_id: Arc<AtomicU32>,
94}
95
96impl<T: UserEvent> RuntimeContext<T> {
97    pub fn send_message(&self, message: Message<T>) -> Result<()> {
98        if current_thread().id() == self.main_thread_id {
99            match message {
100                Message::Task(task) => {
101                    task();
102                    return Ok(());
103                }
104                Message::TaskWithEventLoop(task) => {
105                    task(&self.main_thread.window_target);
106                    return Ok(());
107                }
108                _ => {}
109            }
110        }
111        self.event_proxy
112            .send_event(message)
113            .map_err(|_| Error::FailedToSendMessage)?;
114        Ok(())
115    }
116
117    /// Run a task on the main thread.
118    pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
119        self.send_message(Message::Task(Box::new(f)))
120    }
121
122    /// Run a task on the main thread.
123    pub fn run_on_main_thread_with_event_loop<
124        X: Send + Sync + 'static,
125        F: FnOnce(&TaoEventLoopWindowTarget<Message<T>>) -> X + Send + 'static,
126    >(
127        &self,
128        f: F,
129    ) -> Result<X> {
130        let (tx, rx) = channel();
131        self.send_message(Message::TaskWithEventLoop(Box::new(move |e| {
132            let _ = tx.send(f(e));
133        })))?;
134        rx.recv()
135            .map_err(|_| tauri_runtime::Error::FailedToReceiveMessage)
136    }
137
138    pub fn next_window_id(&self) -> WindowId {
139        self.next_window_id.fetch_add(1, Ordering::Relaxed).into()
140    }
141
142    pub fn next_webview_id(&self) -> u32 {
143        self.next_webview_id.fetch_add(1, Ordering::Relaxed)
144    }
145
146    pub fn next_window_event_id(&self) -> WindowEventId {
147        self.next_window_event_id.fetch_add(1, Ordering::Relaxed)
148    }
149
150    pub fn next_webview_event_id(&self) -> WindowEventId {
151        self.next_webview_event_id.fetch_add(1, Ordering::Relaxed)
152    }
153
154    /// `after_window_creation` not supported
155    ///
156    /// Only creating the window with a webview is supported,
157    /// will return [`tauri_runtime::Error::CreateWindow`] if there is no [`PendingWindow::webview`]
158    pub fn create_window<
159        R: Runtime<
160                T,
161                WindowDispatcher = VersoWindowDispatcher<T>,
162                WebviewDispatcher = VersoWebviewDispatcher<T>,
163            >,
164        F: Fn(RawWindow<'_>) + Send + 'static,
165    >(
166        &self,
167        pending: PendingWindow<T, R>,
168        _after_window_creation: Option<F>,
169    ) -> Result<DetachedWindow<T, R>> {
170        let label = pending.label;
171        let Some(pending_webview) = pending.webview else {
172            return Err(tauri_runtime::Error::CreateWindow);
173        };
174
175        let window_id = self.next_window_id();
176        let webview_id = self.next_webview_id();
177
178        let mut window_builder = pending.window_builder;
179
180        if window_builder.get_theme().is_none() {
181            window_builder = window_builder.theme(*self.prefered_theme.lock().unwrap());
182        }
183
184        let webview = window_builder
185            .verso_builder
186            .user_scripts(
187                pending_webview
188                    .webview_attributes
189                    .initialization_scripts
190                    .into_iter()
191                    .map(|script| script.script),
192            )
193            .custom_protocols(
194                pending_webview
195                    .uri_scheme_protocols
196                    .keys()
197                    .map(CustomProtocolBuilder::new),
198            )
199            .build(get_verso_path(), Url::parse(&pending_webview.url).unwrap());
200
201        let webview_label = label.clone();
202        let sender = self.event_proxy.clone();
203        let uri_scheme_protocols: HashMap<_, _> = pending_webview
204            .uri_scheme_protocols
205            .into_iter()
206            .map(|(key, value)| (key, Arc::new(value)))
207            .collect();
208        webview
209            .on_web_resource_requested(move |mut request, response_fn| {
210                // dbg!(&request);
211                // TODO: Servo's EmbedderMsg::WebResourceRequested message is sent too early
212                // that it doesn't include Origin header, so I hard coded this for now
213                if !request.headers().contains_key("Origin") {
214                    #[cfg(windows)]
215                    let uri = {
216                        let scheme = if pending_webview.webview_attributes.use_https_scheme {
217                            "https"
218                        } else {
219                            "http"
220                        };
221                        format!("{scheme}://tauri.localhost")
222                    };
223                    #[cfg(not(windows))]
224                    let uri = "tauri://localhost";
225                    request.headers_mut().insert("Origin", uri.parse().unwrap());
226                }
227                for (scheme, handler) in &uri_scheme_protocols {
228                    // Since servo doesn't support body in its EmbedderMsg::WebResourceRequested yet,
229                    // we use a header instead for now
230                    if scheme == "ipc" {
231                        if let Some(data) = request
232                            .headers_mut()
233                            .remove("Tauri-VersoRuntime-Invoke-Body")
234                        {
235                            if let Ok(body) =
236                                percent_encoding::percent_decode(data.as_bytes()).decode_utf8()
237                            {
238                                *request.body_mut() = body.as_bytes().to_vec();
239                            } else {
240                                log::error!("IPC invoke body header is not a valid UTF-8 string");
241                            }
242                        }
243                    }
244                    #[cfg(windows)]
245                    let (uri, http_or_https) = (
246                        request.uri().to_string(),
247                        if pending_webview.webview_attributes.use_https_scheme {
248                            "https"
249                        } else {
250                            "http"
251                        },
252                    );
253                    #[cfg(windows)]
254                    let is_custom_protocol_uri = is_work_around_uri(&uri, http_or_https, scheme);
255                    #[cfg(not(windows))]
256                    let is_custom_protocol_uri = request.uri().scheme_str() == Some(scheme);
257                    if is_custom_protocol_uri {
258                        #[cfg(windows)]
259                        {
260                            if let Ok(reverted) =
261                                revert_custom_protocol_work_around(&uri, http_or_https, scheme)
262                            {
263                                *request.uri_mut() = reverted
264                            } else {
265                                log::error!("Can't revert the URI work around on: {uri}")
266                            };
267                        }
268                        // Run the handler on main thread, this is needed because Tauri expects this
269                        let handler = handler.clone();
270                        let webview_label = webview_label.clone();
271                        let _ = sender.send_event(Message::Task(Box::new(move || {
272                            handler(
273                                &webview_label,
274                                request,
275                                Box::new(move |response| {
276                                    response_fn(Some(response.map(Cow::into_owned)));
277                                }),
278                            );
279                        })));
280                        return;
281                    }
282                }
283                response_fn(None);
284            })
285            .map_err(|_| tauri_runtime::Error::CreateWindow)?;
286
287        if let Some(navigation_handler) = pending_webview.navigation_handler {
288            if let Err(error) = webview.on_navigation_starting(move |url| navigation_handler(&url))
289            {
290                log::error!(
291                    "Register `on_navigation_starting` failed with {error}, `navigation_handler` will not get called for this window ({label})!"
292                );
293            }
294        }
295
296        let sender = self.event_proxy.clone();
297        webview
298            .on_close_requested(move || {
299                let _ = sender.send_event(Message::CloseWindow(window_id));
300            })
301            .map_err(|_| tauri_runtime::Error::CreateWindow)?;
302
303        let on_window_event_listeners = Arc::new(Mutex::new(HashMap::new()));
304
305        let webview = Arc::new(Mutex::new(webview));
306        let window = Window {
307            label: label.clone(),
308            webview: webview.clone(),
309            on_window_event_listeners: on_window_event_listeners.clone(),
310        };
311
312        self.windows.lock().unwrap().insert(window_id, window);
313
314        Ok(DetachedWindow {
315            id: window_id,
316            label: label.clone(),
317            dispatcher: VersoWindowDispatcher {
318                id: window_id,
319                context: self.clone(),
320                webview: webview.clone(),
321                on_window_event_listeners,
322            },
323            webview: Some(DetachedWindowWebview {
324                webview: DetachedWebview {
325                    label,
326                    dispatcher: VersoWebviewDispatcher {
327                        id: webview_id,
328                        context: self.clone(),
329                        webview,
330                    },
331                },
332                use_https_scheme: false,
333            }),
334        })
335    }
336
337    /// Handles the close window request by sending the [`WindowEvent::CloseRequested`] event
338    /// if the request doesn't request a forced close
339    /// and if not prevented, send [`WindowEvent::Destroyed`]
340    /// then checks if there're windows left, if not, send [`RunEvent::ExitRequested`]
341    /// returns if we should exit the event loop
342    pub fn handle_close_window_request<F: FnMut(RunEvent<T>) + 'static>(
343        &self,
344        callback: &mut F,
345        id: WindowId,
346        force: bool,
347    ) -> bool {
348        let mut windows = self.windows.lock().unwrap();
349        let Some(window) = windows.get(&id) else {
350            return false;
351        };
352        let label = window.label.clone();
353        let on_window_event_listeners = window.on_window_event_listeners.clone();
354
355        if !force {
356            let (tx, rx) = channel();
357            let window_event = WindowEvent::CloseRequested {
358                signal_tx: tx.clone(),
359            };
360            for handler in on_window_event_listeners.lock().unwrap().values() {
361                handler(&window_event);
362            }
363            callback(RunEvent::WindowEvent {
364                label: label.clone(),
365                event: WindowEvent::CloseRequested { signal_tx: tx },
366            });
367
368            let should_prevent = matches!(rx.try_recv(), Ok(true));
369            if should_prevent {
370                return false;
371            }
372        }
373
374        let webview_weak = std::sync::Arc::downgrade(&window.webview);
375
376        windows.remove(&id);
377        callback(RunEvent::WindowEvent {
378            label,
379            event: WindowEvent::Destroyed,
380        });
381
382        // This is required becuase tauri puts in a clone of the window in to WindowEventHandler closure,
383        // and we need to clear it for the window to drop or else it will stay there forever
384        on_window_event_listeners.lock().unwrap().clear();
385
386        if let Some(webview) = webview_weak.upgrade() {
387            log::warn!(
388                "The versoview controller reference count is not 0 on window close, \
389                there're leaks happening, shutting down this versoview instance regardless"
390            );
391            if let Err(error) = webview.lock().unwrap().exit() {
392                log::error!("Failed to exit the webview: {error}");
393            }
394        }
395
396        let is_empty = windows.is_empty();
397        if !is_empty {
398            return false;
399        }
400
401        let (tx, rx) = channel();
402        callback(RunEvent::ExitRequested { code: None, tx });
403
404        let recv = rx.try_recv();
405        let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));
406
407        !should_prevent
408    }
409}
410
411// Copied from wry
412/// WebView2 supports non-standard protocols only on Windows 10+, so we have to use a workaround,
413/// conveting `{protocol}://localhost/abc` to `{http_or_https}://{protocol}.localhost/abc`,
414/// and this function tests if the URI starts with `{http_or_https}://{protocol}.`
415///
416/// See https://github.com/MicrosoftEdge/WebView2Feedback/issues/73
417#[cfg(windows)]
418fn is_work_around_uri(uri: &str, http_or_https: &str, protocol: &str) -> bool {
419    uri.strip_prefix(http_or_https)
420        .and_then(|rest| rest.strip_prefix("://"))
421        .and_then(|rest| rest.strip_prefix(protocol))
422        .and_then(|rest| rest.strip_prefix("."))
423        .is_some()
424}
425
426// This is a work around wry did for old version of webview2, and tauri also expects it...
427// On Windows, the custom protocol looks like `http://<scheme_name>.<path>` while other platforms, it looks like `<scheme_name>://<path>`
428// And we need to revert this here to align with the wry behavior...
429#[cfg(windows)]
430fn revert_custom_protocol_work_around(
431    uri: &str,
432    http_or_https: &'static str,
433    protocol: &str,
434) -> std::result::Result<http::Uri, http::uri::InvalidUri> {
435    uri.replace(
436        &work_around_uri_prefix(http_or_https, protocol),
437        &format!("{protocol}://"),
438    )
439    .parse()
440}
441
442#[cfg(windows)]
443fn work_around_uri_prefix(http_or_https: &str, protocol: &str) -> String {
444    format!("{http_or_https}://{protocol}.")
445}
446
447impl<T: UserEvent> Debug for RuntimeContext<T> {
448    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449        f.debug_struct("RuntimeContext").finish()
450    }
451}
452
453/// A handle to the [`VersoRuntime`] runtime.
454#[derive(Debug, Clone)]
455pub struct VersoRuntimeHandle<T: UserEvent> {
456    context: RuntimeContext<T>,
457}
458
459impl<T: UserEvent> RuntimeHandle<T> for VersoRuntimeHandle<T> {
460    type Runtime = VersoRuntime<T>;
461
462    fn create_proxy(&self) -> EventProxy<T> {
463        EventProxy(self.context.event_proxy.clone())
464    }
465
466    /// Unsupported, has no effect
467    #[cfg(target_os = "macos")]
468    #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
469    fn set_activation_policy(
470        &self,
471        activation_policy: tauri_runtime::ActivationPolicy,
472    ) -> Result<()> {
473        Ok(())
474    }
475
476    /// Unsupported, has no effect
477    #[cfg(target_os = "macos")]
478    #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
479    fn set_dock_visibility(&self, visible: bool) -> Result<()> {
480        Ok(())
481    }
482
483    fn request_exit(&self, code: i32) -> Result<()> {
484        self.context.send_message(Message::RequestExit(code))
485    }
486
487    /// `after_window_creation` not supported
488    ///
489    /// Only creating the window with a webview is supported,
490    /// will return [`tauri_runtime::Error::CreateWindow`] if there is no [`PendingWindow::webview`]
491    fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
492        &self,
493        pending: PendingWindow<T, Self::Runtime>,
494        after_window_creation: Option<F>,
495    ) -> Result<DetachedWindow<T, Self::Runtime>> {
496        self.context.create_window(pending, after_window_creation)
497    }
498
499    /// Unsupported, always fail with [`tauri_runtime::Error::CreateWindow`]
500    fn create_webview(
501        &self,
502        window_id: WindowId,
503        pending: PendingWebview<T, Self::Runtime>,
504    ) -> Result<DetachedWebview<T, Self::Runtime>> {
505        Err(tauri_runtime::Error::CreateWindow)
506    }
507
508    /// Run a task on the main thread.
509    fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
510        self.context.run_on_main_thread(f)
511    }
512
513    fn primary_monitor(&self) -> Option<Monitor> {
514        self.context
515            .run_on_main_thread_with_event_loop(|e| e.tauri_primary_monitor())
516            .ok()
517            .flatten()
518    }
519
520    fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor> {
521        self.context
522            .run_on_main_thread_with_event_loop(move |e| e.tauri_monitor_from_point(x, y))
523            .ok()
524            .flatten()
525    }
526
527    fn available_monitors(&self) -> Vec<Monitor> {
528        self.context
529            .run_on_main_thread_with_event_loop(|e| e.tauri_available_monitors())
530            .unwrap()
531    }
532
533    fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {
534        self.context
535            .run_on_main_thread_with_event_loop(|e| e.tauri_cursor_position())?
536    }
537
538    fn set_theme(&self, theme: Option<Theme>) {
539        *self.context.prefered_theme.lock().unwrap() = theme;
540        for window in self.context.windows.lock().unwrap().values() {
541            if let Err(error) = window
542                .webview
543                .lock()
544                .unwrap()
545                .set_theme(theme.map(to_verso_theme))
546            {
547                log::error!("Failed to set the theme for webview: {error}");
548            }
549        }
550        let _ = self
551            .context
552            .run_on_main_thread_with_event_loop(move |e| e.set_theme(theme.map(to_tao_theme)));
553    }
554
555    /// Unsupported, has no effect
556    #[cfg(target_os = "macos")]
557    fn show(&self) -> Result<()> {
558        Ok(())
559    }
560
561    /// Unsupported, has no effect
562    #[cfg(target_os = "macos")]
563    fn hide(&self) -> Result<()> {
564        Ok(())
565    }
566
567    /// Unsupported, will always return an error
568    fn display_handle(
569        &self,
570    ) -> std::result::Result<raw_window_handle::DisplayHandle, raw_window_handle::HandleError> {
571        Err(raw_window_handle::HandleError::NotSupported)
572    }
573
574    /// Unsupported, has no effect, the callback will not be called
575    #[cfg(any(target_os = "macos", target_os = "ios"))]
576    #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
577    fn fetch_data_store_identifiers<F: FnOnce(Vec<[u8; 16]>) + Send + 'static>(
578        &self,
579        cb: F,
580    ) -> Result<()> {
581        Ok(())
582    }
583
584    /// Unsupported, has no effect, the callback will not be called
585    #[cfg(any(target_os = "macos", target_os = "ios"))]
586    #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
587    fn remove_data_store<F: FnOnce(Result<()>) + Send + 'static>(
588        &self,
589        uuid: [u8; 16],
590        cb: F,
591    ) -> Result<()> {
592        Ok(())
593    }
594}
595
596#[derive(Debug, Clone)]
597pub struct EventProxy<T: UserEvent>(TaoEventLoopProxy<Message<T>>);
598
599impl<T: UserEvent> EventLoopProxy<T> for EventProxy<T> {
600    fn send_event(&self, event: T) -> Result<()> {
601        self.0
602            .send_event(Message::UserEvent(event))
603            .map_err(|_| Error::FailedToSendMessage)
604    }
605}
606
607/// A Tauri Runtime wrapper around Verso.
608#[derive(Debug)]
609pub struct VersoRuntime<T: UserEvent = tauri::EventLoopMessage> {
610    pub context: RuntimeContext<T>,
611    event_loop: EventLoop<Message<T>>,
612}
613
614impl<T: UserEvent> VersoRuntime<T> {
615    fn init(event_loop: EventLoop<Message<T>>) -> Self {
616        let context = RuntimeContext {
617            windows: Default::default(),
618            prefered_theme: Arc::default(),
619            event_proxy: event_loop.create_proxy(),
620            main_thread: DispatcherMainThreadContext {
621                window_target: event_loop.deref().clone(),
622            },
623            main_thread_id: current_thread().id(),
624            next_window_id: Default::default(),
625            next_webview_id: Default::default(),
626            next_window_event_id: Default::default(),
627            next_webview_event_id: Default::default(),
628        };
629        Self {
630            context,
631            event_loop,
632        }
633    }
634
635    fn init_with_builder(
636        mut event_loop_builder: EventLoopBuilder<Message<T>>,
637        args: RuntimeInitArgs,
638    ) -> Self {
639        #[cfg(windows)]
640        if let Some(hook) = args.msg_hook {
641            use tao::platform::windows::EventLoopBuilderExtWindows;
642            event_loop_builder.with_msg_hook(hook);
643        }
644
645        #[cfg(any(
646            target_os = "linux",
647            target_os = "dragonfly",
648            target_os = "freebsd",
649            target_os = "netbsd",
650            target_os = "openbsd"
651        ))]
652        if let Some(app_id) = args.app_id {
653            use tao::platform::unix::EventLoopBuilderExtUnix;
654            event_loop_builder.with_app_id(app_id);
655        }
656        Self::init(event_loop_builder.build())
657    }
658}
659
660impl<T: UserEvent> Runtime<T> for VersoRuntime<T> {
661    type WindowDispatcher = VersoWindowDispatcher<T>;
662    type WebviewDispatcher = VersoWebviewDispatcher<T>;
663    type Handle = VersoRuntimeHandle<T>;
664    type EventLoopProxy = EventProxy<T>;
665
666    /// `args.msg_hook` hooks on the event loop of this process,
667    /// this doesn't work for the event loop of versoview instances
668    fn new(args: RuntimeInitArgs) -> Result<Self> {
669        let event_loop_builder = EventLoopBuilder::<Message<T>>::with_user_event();
670        Ok(Self::init_with_builder(event_loop_builder, args))
671    }
672
673    /// `args.msg_hook` hooks on the event loop of this process,
674    /// this doesn't work for the event loop of versoview instances
675    #[cfg(any(windows, target_os = "linux"))]
676    fn new_any_thread(args: RuntimeInitArgs) -> Result<Self> {
677        let mut event_loop_builder = EventLoopBuilder::<Message<T>>::with_user_event();
678        #[cfg(target_os = "linux")]
679        use tao::platform::unix::EventLoopBuilderExtUnix;
680        #[cfg(windows)]
681        use tao::platform::windows::EventLoopBuilderExtWindows;
682        event_loop_builder.with_any_thread(true);
683        Ok(Self::init_with_builder(event_loop_builder, args))
684    }
685
686    fn create_proxy(&self) -> EventProxy<T> {
687        EventProxy(self.event_loop.create_proxy())
688    }
689
690    fn handle(&self) -> Self::Handle {
691        VersoRuntimeHandle {
692            context: self.context.clone(),
693        }
694    }
695
696    /// `after_window_creation` not supported
697    ///
698    /// Only creating the window with a webview is supported,
699    /// will return [`tauri_runtime::Error::CreateWindow`] if there is no [`PendingWindow::webview`]
700    fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
701        &self,
702        pending: PendingWindow<T, Self>,
703        after_window_creation: Option<F>,
704    ) -> Result<DetachedWindow<T, Self>> {
705        self.context.create_window(pending, after_window_creation)
706    }
707
708    /// Unsupported, always fail with [`tauri_runtime::Error::CreateWindow`]
709    fn create_webview(
710        &self,
711        window_id: WindowId,
712        pending: PendingWebview<T, Self>,
713    ) -> Result<DetachedWebview<T, Self>> {
714        Err(tauri_runtime::Error::CreateWindow)
715    }
716
717    fn primary_monitor(&self) -> Option<Monitor> {
718        self.event_loop.tauri_primary_monitor()
719    }
720
721    fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor> {
722        self.event_loop.tauri_monitor_from_point(x, y)
723    }
724
725    fn available_monitors(&self) -> Vec<Monitor> {
726        self.event_loop.tauri_available_monitors()
727    }
728
729    fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {
730        self.event_loop.tauri_cursor_position()
731    }
732
733    fn set_theme(&self, theme: Option<Theme>) {
734        *self.context.prefered_theme.lock().unwrap() = theme;
735        for window in self.context.windows.lock().unwrap().values() {
736            if let Err(error) = window
737                .webview
738                .lock()
739                .unwrap()
740                .set_theme(theme.map(to_verso_theme))
741            {
742                log::error!("Failed to set the theme for webview: {error}");
743            }
744        }
745        self.event_loop.set_theme(theme.map(to_tao_theme));
746    }
747
748    /// Unsupported, has no effect when called
749    #[cfg(target_os = "macos")]
750    #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
751    fn set_activation_policy(&mut self, activation_policy: tauri_runtime::ActivationPolicy) {}
752
753    /// Unsupported, has no effect when called
754    #[cfg(target_os = "macos")]
755    #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
756    fn show(&self) {}
757
758    /// Unsupported, has no effect when called
759    #[cfg(target_os = "macos")]
760    #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
761    fn hide(&self) {}
762
763    /// Unsupported, has no effect
764    #[cfg(target_os = "macos")]
765    #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
766    fn set_dock_visibility(&mut self, visible: bool) {}
767
768    /// Unsupported, has no effect when called
769    fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {}
770
771    /// Unsupported, has no effect when called
772    fn run_iteration<F: FnMut(RunEvent<T>)>(&mut self, callback: F) {}
773
774    fn run<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) {
775        let exit_code = self.run_return(callback);
776        // std::process::exit(exit_code);
777    }
778
779    fn run_return<F: FnMut(RunEvent<T>) + 'static>(mut self, mut callback: F) -> i32 {
780        self.event_loop
781            .run_return(|event, event_loop, control_flow| {
782                if *control_flow != ControlFlow::Exit {
783                    *control_flow = ControlFlow::Wait;
784                }
785
786                match event {
787                    TaoEvent::NewEvents(StartCause::Init) => {
788                        callback(RunEvent::Ready);
789                    }
790                    TaoEvent::NewEvents(StartCause::Poll) => {
791                        callback(RunEvent::Resumed);
792                    }
793                    TaoEvent::MainEventsCleared => {
794                        callback(RunEvent::MainEventsCleared);
795                    }
796                    TaoEvent::LoopDestroyed => {
797                        callback(RunEvent::Exit);
798                    }
799                    TaoEvent::UserEvent(user_event) => match user_event {
800                        Message::Task(p) => p(),
801                        Message::TaskWithEventLoop(p) => p(event_loop),
802                        Message::CloseWindow(id) => {
803                            let should_exit =
804                                self.context
805                                    .handle_close_window_request(&mut callback, id, false);
806                            if should_exit {
807                                *control_flow = ControlFlow::Exit;
808                            }
809                        }
810                        Message::DestroyWindow(id) => {
811                            let should_exit =
812                                self.context
813                                    .handle_close_window_request(&mut callback, id, true);
814                            if should_exit {
815                                *control_flow = ControlFlow::Exit;
816                            }
817                        }
818                        Message::RequestExit(code) => {
819                            let (tx, rx) = channel();
820                            callback(RunEvent::ExitRequested {
821                                code: Some(code),
822                                tx,
823                            });
824
825                            let recv = rx.try_recv();
826                            let should_prevent =
827                                matches!(recv, Ok(ExitRequestedEventAction::Prevent));
828
829                            if !should_prevent {
830                                *control_flow = ControlFlow::Exit;
831                            }
832                        }
833                        Message::UserEvent(user_event) => callback(RunEvent::UserEvent(user_event)),
834                    },
835                    _ => {}
836                }
837            })
838    }
839}