ソースを参照

feat(core): allow app run on any thread on Linux & Windows, closes #3172 (#3353)

Lucas Fernandes Nogueira 3 年 前
コミット
af44bf8168

+ 5 - 0
.changes/app-any-thread.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Added `any_thread()` to the `tauri::Builder` to run applications on any thread (only exposed on Linux and Windows).

+ 6 - 0
.changes/runtime-any-thread.md

@@ -0,0 +1,6 @@
+---
+"tauri-runtime": patch
+"tauri-runtime-wry": patch
+---
+
+Added `any_thread` constructor on the `Runtime` trait (only possible on Linux and Windows).

+ 26 - 10
core/tauri-runtime-wry/src/lib.rs

@@ -1569,16 +1569,8 @@ impl RuntimeHandle for WryHandle {
   }
 }
 
-impl Runtime for Wry {
-  type Dispatcher = WryDispatcher;
-  type Handle = WryHandle;
-  type GlobalShortcutManager = GlobalShortcutManagerHandle;
-  type ClipboardManager = ClipboardManagerWrapper;
-  #[cfg(feature = "system-tray")]
-  type TrayHandler = SystemTrayHandle;
-
-  fn new() -> Result<Self> {
-    let event_loop = EventLoop::<Message>::with_user_event();
+impl Wry {
+  fn init(event_loop: EventLoop<Message>) -> Result<Self> {
     let proxy = event_loop.create_proxy();
     let main_thread_id = current_thread().id();
     let web_context = WebContextStore::default();
@@ -1631,6 +1623,30 @@ impl Runtime for Wry {
       tray_context,
     })
   }
+}
+
+impl Runtime for Wry {
+  type Dispatcher = WryDispatcher;
+  type Handle = WryHandle;
+  type GlobalShortcutManager = GlobalShortcutManagerHandle;
+  type ClipboardManager = ClipboardManagerWrapper;
+  #[cfg(feature = "system-tray")]
+  type TrayHandler = SystemTrayHandle;
+
+  fn new() -> Result<Self> {
+    let event_loop = EventLoop::<Message>::with_user_event();
+    Self::init(event_loop)
+  }
+
+  #[cfg(any(windows, target_os = "linux"))]
+  fn new_any_thread() -> Result<Self> {
+    #[cfg(target_os = "linux")]
+    use wry::application::platform::unix::EventLoopExtUnix;
+    #[cfg(windows)]
+    use wry::application::platform::windows::EventLoopExtWindows;
+    let event_loop = EventLoop::<Message>::new_any_thread();
+    Self::init(event_loop)
+  }
 
   fn handle(&self) -> Self::Handle {
     WryHandle {

+ 6 - 1
core/tauri-runtime/src/lib.rs

@@ -310,9 +310,14 @@ pub trait Runtime: Sized + 'static {
   #[cfg(feature = "system-tray")]
   type TrayHandler: menu::TrayHandle + Clone + Send;
 
-  /// Creates a new webview runtime.
+  /// Creates a new webview runtime. Must be used on the main thread.
   fn new() -> crate::Result<Self>;
 
+  /// Creates a new webview runtime on any thread.
+  #[cfg(any(windows, target_os = "linux"))]
+  #[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "linux"))))]
+  fn new_any_thread() -> crate::Result<Self>;
+
   /// Gets a runtime handle.
   fn handle(&self) -> Self::Handle;
 

+ 27 - 0
core/tauri/src/app.rs

@@ -596,6 +596,11 @@ impl<R: Runtime> App<R> {
 /// ```
 #[allow(clippy::type_complexity)]
 pub struct Builder<R: Runtime> {
+  /// A flag indicating that the runtime must be started on an environment that supports the event loop not on the main thread.
+  #[cfg(any(windows, target_os = "linux"))]
+  #[cfg_attr(doc_cfg, doc(any(windows, target_os = "linux")))]
+  runtime_any_thread: bool,
+
   /// The JS message handler.
   invoke_handler: Box<InvokeHandler<R>>,
 
@@ -645,6 +650,8 @@ impl<R: Runtime> Builder<R> {
   /// Creates a new App builder.
   pub fn new() -> Self {
     Self {
+      #[cfg(any(windows, target_os = "linux"))]
+      runtime_any_thread: false,
       setup: Box::new(|_| Ok(())),
       invoke_handler: Box::new(|_| ()),
       invoke_responder: Arc::new(window_invoke_responder),
@@ -665,6 +672,18 @@ impl<R: Runtime> Builder<R> {
     }
   }
 
+  /// Builds a new Tauri application running on any thread, bypassing the main thread requirement.
+  ///
+  /// ## Platform-specific
+  ///
+  /// - **macOS**: on macOS the application *must* be executed on the main thread, so this function is not exposed.
+  #[cfg(any(windows, target_os = "linux"))]
+  #[cfg_attr(doc_cfg, doc(any(windows, target_os = "linux")))]
+  pub fn any_thread(mut self) -> Self {
+    self.runtime_any_thread = true;
+    self
+  }
+
   /// Defines the JS message handler callback.
   ///
   /// # Example
@@ -1098,7 +1117,15 @@ impl<R: Runtime> Builder<R> {
       ));
     }
 
+    #[cfg(any(windows, target_os = "linux"))]
+    let runtime = if self.runtime_any_thread {
+      R::new_any_thread()?
+    } else {
+      R::new()?
+    };
+    #[cfg(not(any(windows, target_os = "linux")))]
     let runtime = R::new()?;
+
     let runtime_handle = runtime.handle();
     let global_shortcut_manager = runtime.global_shortcut_manager();
     let clipboard_manager = runtime.clipboard_manager();

+ 22 - 11
core/tauri/src/test/mock_runtime.rs

@@ -492,20 +492,13 @@ pub struct MockRuntime {
   tray_handler: MockTrayHandler,
 }
 
-impl Runtime for MockRuntime {
-  type Dispatcher = MockDispatcher;
-  type Handle = MockRuntimeHandle;
-  type GlobalShortcutManager = MockGlobalShortcutManager;
-  type ClipboardManager = MockClipboardManager;
-  #[cfg(feature = "system-tray")]
-  type TrayHandler = MockTrayHandler;
-
-  fn new() -> Result<Self> {
+impl MockRuntime {
+  fn init() -> Self {
     let context = RuntimeContext {
       shortcuts: Default::default(),
       clipboard: Default::default(),
     };
-    Ok(Self {
+    Self {
       global_shortcut_manager: MockGlobalShortcutManager {
         context: context.clone(),
       },
@@ -517,7 +510,25 @@ impl Runtime for MockRuntime {
         context: context.clone(),
       },
       context,
-    })
+    }
+  }
+}
+
+impl Runtime for MockRuntime {
+  type Dispatcher = MockDispatcher;
+  type Handle = MockRuntimeHandle;
+  type GlobalShortcutManager = MockGlobalShortcutManager;
+  type ClipboardManager = MockClipboardManager;
+  #[cfg(feature = "system-tray")]
+  type TrayHandler = MockTrayHandler;
+
+  fn new() -> Result<Self> {
+    Ok(Self::init())
+  }
+
+  #[cfg(any(windows, target_os = "linux"))]
+  fn new_any_thread() -> Result<Self> {
+    Ok(Self::init())
   }
 
   fn handle(&self) -> Self::Handle {