浏览代码

feat(macOS): Add application `show` and `hide` methods (#3689)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Kasper 2 年之前
父节点
当前提交
39bf895b73

+ 5 - 0
.changes/mac-app-hide-allowlist.md

@@ -0,0 +1,5 @@
+---
+"tauri-utils": minor
+---
+
+Added the `app` allowlist module.

+ 5 - 0
.changes/mac-app-hide-api.md

@@ -0,0 +1,5 @@
+---
+"api": minor
+---
+
+Added `show` and `hide` methods on the `app` module.

+ 6 - 0
.changes/mac-app-hide-runtime.md

@@ -0,0 +1,6 @@
+---
+"tauri-runtime-wry": minor
+"tauri-runtime": minor
+---
+
+Added `Runtime::show()`, `RuntimeHandle::show()`, `Runtime::hide()`, `RuntimeHandle::hide()` for hiding/showing the entire application on macOS.

+ 5 - 0
.changes/mac-app-hide.md

@@ -0,0 +1,5 @@
+---
+"tauri": minor
+---
+
+Add `App::show()`, `AppHandle::show()`, `App::hide()` and `AppHandle::hide()` for hiding/showing the entire application on macOS.

+ 46 - 0
core/tauri-runtime-wry/src/lib.rs

@@ -29,6 +29,8 @@ use webview2_com::FocusChangedEventHandler;
 #[cfg(windows)]
 use windows::Win32::{Foundation::HWND, System::WinRT::EventRegistrationToken};
 #[cfg(target_os = "macos")]
+use wry::application::platform::macos::EventLoopWindowTargetExtMacOS;
+#[cfg(target_os = "macos")]
 use wry::application::platform::macos::WindowBuilderExtMacOS;
 #[cfg(target_os = "linux")]
 use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
@@ -1021,6 +1023,13 @@ unsafe impl Send for GtkWindow {}
 pub struct RawWindowHandle(pub raw_window_handle::RawWindowHandle);
 unsafe impl Send for RawWindowHandle {}
 
+#[cfg(target_os = "macos")]
+#[derive(Debug, Clone)]
+pub enum ApplicationMessage {
+  Show,
+  Hide,
+}
+
 pub enum WindowMessage {
   #[cfg(desktop)]
   WithWebview(Box<dyn FnOnce(Webview) + Send>),
@@ -1126,6 +1135,8 @@ pub type CreateWebviewClosure<T> = Box<
 
 pub enum Message<T: 'static> {
   Task(Box<dyn FnOnce() + Send>),
+  #[cfg(target_os = "macos")]
+  Application(ApplicationMessage),
   Window(WebviewId, WindowMessage),
   Webview(WebviewId, WebviewMessage),
   #[cfg(all(desktop, feature = "system-tray"))]
@@ -1786,6 +1797,22 @@ impl<T: UserEvent> RuntimeHandle<T> for WryHandle<T> {
   fn raw_display_handle(&self) -> RawDisplayHandle {
     self.context.main_thread.window_target.raw_display_handle()
   }
+
+  #[cfg(target_os = "macos")]
+  fn show(&self) -> tauri_runtime::Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Application(ApplicationMessage::Show),
+    )
+  }
+
+  #[cfg(target_os = "macos")]
+  fn hide(&self) -> tauri_runtime::Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Application(ApplicationMessage::Hide),
+    )
+  }
 }
 
 impl<T: UserEvent> Wry<T> {
@@ -1995,6 +2022,16 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
       });
   }
 
+  #[cfg(target_os = "macos")]
+  fn show(&self) {
+    self.event_loop.show_application();
+  }
+
+  #[cfg(target_os = "macos")]
+  fn hide(&self) {
+    self.event_loop.hide_application();
+  }
+
   #[cfg(desktop)]
   fn run_iteration<F: FnMut(RunEvent<T>) + 'static>(&mut self, mut callback: F) -> RunIteration {
     use wry::application::platform::run_return::EventLoopExtRunReturn;
@@ -2185,6 +2222,15 @@ fn handle_user_message<T: UserEvent>(
   } = context;
   match message {
     Message::Task(task) => task(),
+    #[cfg(target_os = "macos")]
+    Message::Application(application_message) => match application_message {
+      ApplicationMessage::Show => {
+        event_loop.show_application();
+      }
+      ApplicationMessage::Hide => {
+        event_loop.hide_application();
+      }
+    },
     Message::Window(id, window_message) => {
       if let WindowMessage::UpdateMenuItem(item_id, update) = window_message {
         if let Some(menu_items) = windows.borrow_mut().get_mut(&id).map(|w| &mut w.menu_items) {

+ 20 - 0
core/tauri-runtime/src/lib.rs

@@ -352,6 +352,16 @@ pub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'st
   ) -> Result<<Self::Runtime as Runtime<T>>::TrayHandler>;
 
   fn raw_display_handle(&self) -> RawDisplayHandle;
+
+  /// Shows the application, but does not automatically focus it.
+  #[cfg(target_os = "macos")]
+  #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
+  fn show(&self) -> Result<()>;
+
+  /// Hides the application.
+  #[cfg(target_os = "macos")]
+  #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
+  fn hide(&self) -> Result<()>;
 }
 
 /// A global shortcut manager.
@@ -441,6 +451,16 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
   #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
   fn set_activation_policy(&mut self, activation_policy: ActivationPolicy);
 
+  /// Shows the application, but does not automatically focus it.
+  #[cfg(target_os = "macos")]
+  #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
+  fn show(&self);
+
+  /// Hides the application.
+  #[cfg(target_os = "macos")]
+  #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
+  fn hide(&self);
+
   /// Runs the one step of the webview runtime event loop and returns control flow to the caller.
   #[cfg(desktop)]
   fn run_iteration<F: Fn(RunEvent<T>) + 'static>(&mut self, callback: F) -> RunIteration;

+ 44 - 0
core/tauri-utils/src/config.rs

@@ -2009,6 +2009,46 @@ impl Allowlist for ClipboardAllowlistConfig {
   }
 }
 
+/// Allowlist for the app APIs.
+#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
+#[cfg_attr(feature = "schema", derive(JsonSchema))]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+pub struct AppAllowlistConfig {
+  /// Use this flag to enable all app APIs.
+  #[serde(default)]
+  pub all: bool,
+  /// Enables the app's `show` API.
+  #[serde(default)]
+  pub show: bool,
+  /// Enables the app's `hide` API.
+  #[serde(default)]
+  pub hide: bool,
+}
+
+impl Allowlist for AppAllowlistConfig {
+  fn all_features() -> Vec<&'static str> {
+    let allowlist = Self {
+      all: false,
+      show: true,
+      hide: true,
+    };
+    let mut features = allowlist.to_features();
+    features.push("app-all");
+    features
+  }
+
+  fn to_features(&self) -> Vec<&'static str> {
+    if self.all {
+      vec!["app-all"]
+    } else {
+      let mut features = Vec::new();
+      check_feature!(self, features, show, "app-show");
+      check_feature!(self, features, hide, "app-hide");
+      features
+    }
+  }
+}
+
 /// Allowlist configuration.
 #[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
 #[cfg_attr(feature = "schema", derive(JsonSchema))]
@@ -2053,6 +2093,9 @@ pub struct AllowlistConfig {
   /// Clipboard APIs allowlist.
   #[serde(default)]
   pub clipboard: ClipboardAllowlistConfig,
+  /// App APIs allowlist.
+  #[serde(default)]
+  pub app: AppAllowlistConfig,
 }
 
 impl Allowlist for AllowlistConfig {
@@ -2070,6 +2113,7 @@ impl Allowlist for AllowlistConfig {
     features.extend(ProtocolAllowlistConfig::all_features());
     features.extend(ProcessAllowlistConfig::all_features());
     features.extend(ClipboardAllowlistConfig::all_features());
+    features.extend(AppAllowlistConfig::all_features());
     features
   }
 

+ 5 - 1
core/tauri/Cargo.toml

@@ -176,7 +176,8 @@ api-all = [
   "process-all",
   "protocol-all",
   "shell-all",
-  "window-all"
+  "window-all",
+  "app-all"
 ]
 clipboard-all = [ "clipboard-write-text", "clipboard-read-text" ]
 clipboard-read-text = [ "clipboard" ]
@@ -283,6 +284,9 @@ window-set-cursor-position = [ ]
 window-set-ignore-cursor-events = [ ]
 window-start-dragging = [ ]
 window-print = [ ]
+app-all = [ "app-show", "app-hide" ]
+app-show = [ ]
+app-hide = [ ]
 config-json5 = [ "tauri-macros/config-json5" ]
 config-toml = [ "tauri-macros/config-toml" ]
 icon-ico = [ "infer", "ico" ]

+ 2 - 0
core/tauri/build.rs

@@ -131,6 +131,8 @@ fn main() {
 
   alias_module("clipboard", &["write-text", "read-text"], api_all);
 
+  alias_module("app", &["show", "hide"], api_all);
+
   let checked_features_out_path =
     Path::new(&std::env::var("OUT_DIR").unwrap()).join("checked_features");
   std::fs::write(

文件差异内容过多而无法显示
+ 0 - 0
core/tauri/scripts/bundle.js


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

@@ -734,6 +734,28 @@ macro_rules! shared_app_impl {
           manager: self.manager.clone(),
         }
       }
+
+      /// Shows the application, but does not automatically focus it.
+      #[cfg(target_os = "macos")]
+      pub fn show(&self) -> crate::Result<()> {
+        match self.runtime() {
+          RuntimeOrDispatch::Runtime(r) => r.show(),
+          RuntimeOrDispatch::RuntimeHandle(h) => h.show()?,
+          _ => unreachable!(),
+        }
+        Ok(())
+      }
+
+      /// Hides the application.
+      #[cfg(target_os = "macos")]
+      pub fn hide(&self) -> crate::Result<()> {
+        match self.runtime() {
+          RuntimeOrDispatch::Runtime(r) => r.hide(),
+          RuntimeOrDispatch::RuntimeHandle(h) => h.hide()?,
+          _ => unreachable!(),
+        }
+        Ok(())
+      }
     }
   };
 }

+ 23 - 1
core/tauri/src/endpoints/app.rs

@@ -5,7 +5,7 @@
 use super::InvokeContext;
 use crate::Runtime;
 use serde::Deserialize;
-use tauri_macros::{command_enum, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 /// The API descriptor.
 #[command_enum]
@@ -19,6 +19,12 @@ pub enum Cmd {
   GetAppName,
   /// Get Tauri Version
   GetTauriVersion,
+  /// Shows the application on macOS.
+  #[cmd(app_show, "app > show")]
+  Show,
+  /// Hides the application on macOS.
+  #[cmd(app_hide, "app > hide")]
+  Hide,
 }
 
 impl Cmd {
@@ -33,4 +39,20 @@ impl Cmd {
   fn get_tauri_version<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
     Ok(env!("CARGO_PKG_VERSION"))
   }
+
+  #[module_command_handler(app_show)]
+  #[allow(unused_variables)]
+  fn show<R: Runtime>(context: InvokeContext<R>) -> super::Result<()> {
+    #[cfg(target_os = "macos")]
+    context.window.app_handle.show()?;
+    Ok(())
+  }
+
+  #[module_command_handler(app_hide)]
+  #[allow(unused_variables)]
+  fn hide<R: Runtime>(context: InvokeContext<R>) -> super::Result<()> {
+    #[cfg(target_os = "macos")]
+    context.window.app_handle.hide()?;
+    Ok(())
+  }
 }

+ 6 - 0
core/tauri/src/lib.rs

@@ -148,6 +148,12 @@
 //! - **window-set-ignore-cursor-events**: Enables the [`setIgnoreCursorEvents` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setignorecursorevents).
 //! - **window-start-dragging**: Enables the [`startDragging` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#startdragging).
 //! - **window-print**: Enables the [`print` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#print).
+//!
+//! ### App allowlist
+//!
+//! - **app-all**: Enables all [App APIs](https://tauri.app/en/docs/api/js/modules/app).
+//! - **app-show**: Enables the [`show` API](https://tauri.app/en/docs/api/js/modules/app#show).
+//! - **app-hide**: Enables the [`hide` API](https://tauri.app/en/docs/api/js/modules/app#hide).
 
 #![warn(missing_docs, rust_2018_idioms)]
 #![cfg_attr(doc_cfg, feature(doc_cfg))]

+ 20 - 0
core/tauri/src/test/mock_runtime.rs

@@ -94,6 +94,18 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
   fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
     unimplemented!()
   }
+
+  /// Shows the application, but does not automatically focus it.
+  #[cfg(target_os = "macos")]
+  fn show(&self) -> Result<()> {
+    Ok(())
+  }
+
+  /// Hides the application.
+  #[cfg(target_os = "macos")]
+  fn hide(&self) -> Result<()> {
+    Ok(())
+  }
 }
 
 #[derive(Debug, Clone)]
@@ -670,6 +682,14 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
   #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
   fn set_activation_policy(&mut self, activation_policy: tauri_runtime::ActivationPolicy) {}
 
+  #[cfg(target_os = "macos")]
+  #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
+  fn show(&self) {}
+
+  #[cfg(target_os = "macos")]
+  #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
+  fn hide(&self) {}
+
   #[cfg(any(
     target_os = "macos",
     windows,

文件差异内容过多而无法显示
+ 0 - 0
examples/api/dist/assets/index.css


文件差异内容过多而无法显示
+ 0 - 0
examples/api/dist/assets/index.js


+ 6 - 0
examples/api/src/App.svelte

@@ -17,6 +17,7 @@
   import Updater from './views/Updater.svelte'
   import Clipboard from './views/Clipboard.svelte'
   import WebRTC from './views/WebRTC.svelte'
+  import App from './views/App.svelte'
 
   import { onMount } from 'svelte'
   import { listen } from '@tauri-apps/api/event'
@@ -75,6 +76,11 @@
       component: Notifications,
       icon: 'i-codicon-bell-dot'
     },
+    !isMobile && {
+      label: 'App',
+      component: App,
+      icon: 'i-codicon-hubot'
+    },
     !isMobile && {
       label: 'Window',
       component: Window,

+ 33 - 0
examples/api/src/views/App.svelte

@@ -0,0 +1,33 @@
+<script>
+  import { show, hide } from '@tauri-apps/api/app'
+
+  export let onMessage
+
+  function showApp() {
+    hideApp()
+      .then(() => {
+        setTimeout(() => {
+          show()
+            .then(() => onMessage('Shown app'))
+            .catch(onMessage)
+        }, 2000)
+      })
+      .catch(onMessage)
+  }
+
+  function hideApp() {
+    return hide()
+      .then(() => onMessage('Hide app'))
+      .catch(onMessage)
+  }
+</script>
+
+<div>
+  <button
+    class="btn"
+    id="show"
+    title="Hides and shows the app after 2 seconds"
+    on:click={showApp}>Show</button
+  >
+  <button class="btn" id="hide" on:click={hideApp}>Hide</button>
+</div>

+ 61 - 4
tooling/api/src/app.ts

@@ -6,6 +6,23 @@
  * Get application metadata.
  *
  * This package is also accessible with `window.__TAURI__.app` when [`build.withGlobalTauri`](https://tauri.app/v1/api/config/#buildconfig.withglobaltauri) in `tauri.conf.json` is set to `true`.
+ *
+ * The APIs must be added to [`tauri.allowlist.app`](https://tauri.app/v1/api/config/#allowlistconfig.app) in `tauri.conf.json`:
+ * ```json
+ * {
+ *   "tauri": {
+ *     "allowlist": {
+ *       "app": {
+ *         "all": true, // enable all app APIs
+ *         "show": true,
+ *         "hide": true
+ *       }
+ *     }
+ *   }
+ * }
+ * ```
+ * It is recommended to allowlist only the APIs you use for optimal bundle size and security.
+ *
  * @module
  */
 
@@ -22,7 +39,7 @@ import { invokeTauriCommand } from './helpers/tauri'
  * @since 1.0.0
  */
 async function getVersion(): Promise<string> {
-  return invokeTauriCommand<string>({
+  return invokeTauriCommand({
     __tauriModule: 'App',
     message: {
       cmd: 'getAppVersion'
@@ -41,7 +58,7 @@ async function getVersion(): Promise<string> {
  * @since 1.0.0
  */
 async function getName(): Promise<string> {
-  return invokeTauriCommand<string>({
+  return invokeTauriCommand({
     __tauriModule: 'App',
     message: {
       cmd: 'getAppName'
@@ -61,7 +78,7 @@ async function getName(): Promise<string> {
  * @since 1.0.0
  */
 async function getTauriVersion(): Promise<string> {
-  return invokeTauriCommand<string>({
+  return invokeTauriCommand({
     __tauriModule: 'App',
     message: {
       cmd: 'getTauriVersion'
@@ -69,4 +86,44 @@ async function getTauriVersion(): Promise<string> {
   })
 }
 
-export { getName, getVersion, getTauriVersion }
+/**
+ * Shows the application on macOS. This function does not automatically focuses any app window.
+ *
+ * @example
+ * ```typescript
+ * import { show } from '@tauri-apps/api/app';
+ * await show();
+ * ```
+ *
+ * @since 1.2.0
+ */
+async function show(): Promise<void> {
+  return invokeTauriCommand({
+    __tauriModule: 'App',
+    message: {
+      cmd: 'show'
+    }
+  })
+}
+
+/**
+ * Hides the application on macOS.
+ *
+ * @example
+ * ```typescript
+ * import { hide } from '@tauri-apps/api/app';
+ * await hide();
+ * ```
+ *
+ * @since 1.2.0
+ */
+async function hide(): Promise<void> {
+  return invokeTauriCommand({
+    __tauriModule: 'App',
+    message: {
+      cmd: 'hide'
+    }
+  })
+}
+
+export { getName, getVersion, getTauriVersion, show, hide }

+ 46 - 1
tooling/cli/schema.json

@@ -28,6 +28,11 @@
       "default": {
         "allowlist": {
           "all": false,
+          "app": {
+            "all": false,
+            "hide": false,
+            "show": false
+          },
           "clipboard": {
             "all": false,
             "readText": false,
@@ -296,6 +301,11 @@
           "description": "The allowlist configuration.",
           "default": {
             "all": false,
+            "app": {
+              "all": false,
+              "hide": false,
+              "show": false
+            },
             "clipboard": {
               "all": false,
               "readText": false,
@@ -657,7 +667,7 @@
           ]
         },
         "hiddenTitle": {
-          "description": "Sets the window title to be hidden on macOS.",
+          "description": "If `true`, sets the window title to be hidden on macOS.",
           "default": false,
           "type": "boolean"
         }
@@ -1730,6 +1740,19 @@
               "$ref": "#/definitions/ClipboardAllowlistConfig"
             }
           ]
+        },
+        "app": {
+          "description": "App APIs allowlist.",
+          "default": {
+            "all": false,
+            "hide": false,
+            "show": false
+          },
+          "allOf": [
+            {
+              "$ref": "#/definitions/AppAllowlistConfig"
+            }
+          ]
         }
       },
       "additionalProperties": false
@@ -2316,6 +2339,28 @@
       },
       "additionalProperties": false
     },
+    "AppAllowlistConfig": {
+      "description": "Allowlist for the app APIs.",
+      "type": "object",
+      "properties": {
+        "all": {
+          "description": "Use this flag to enable all app APIs.",
+          "default": false,
+          "type": "boolean"
+        },
+        "show": {
+          "description": "Enables the app's `show` API.",
+          "default": false,
+          "type": "boolean"
+        },
+        "hide": {
+          "description": "Enables the app's `hide` API.",
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false
+    },
     "SecurityConfig": {
       "description": "Security configuration.",
       "type": "object",

部分文件因为文件数量过多而无法显示