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

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Kasper 2022-10-03 19:49:59 +02:00 committed by GitHub
parent 7c0fa1f3f9
commit 39bf895b73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 394 additions and 46 deletions

View File

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

View File

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

View File

@ -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
.changes/mac-app-hide.md Normal file
View File

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

View File

@ -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) {

View File

@ -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;

View File

@ -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
}

View File

@ -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" ]

View File

@ -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(

File diff suppressed because one or more lines are too long

View File

@ -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(())
}
}
};
}

View File

@ -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(())
}
}

View File

@ -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))]

View File

@ -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,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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,

View File

@ -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>

View File

@ -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 }

View File

@ -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",