Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add set_dock_visibility method #1058

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/feat-dock-visibility-on-macos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'tao': minor
---

macOS: Add `set_dock_visibility` method to support setting the visibility of the application in the dock.
26 changes: 25 additions & 1 deletion src/platform/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
dpi::{LogicalSize, Position},
event_loop::{EventLoop, EventLoopWindowTarget},
monitor::MonitorHandle,
platform_impl::{get_aux_state_mut, set_badge_label, Parent},
platform_impl::{get_aux_state_mut, set_badge_label, set_dock_visibility, Parent},
window::{Window, WindowBuilder},
};

Expand Down Expand Up @@ -321,6 +321,13 @@ pub trait EventLoopExtMacOS {
/// [`EventLoopWindowTargetExtMacOS::set_activation_policy_at_runtime`](crate::platform::macos::EventLoopWindowTargetExtMacOS::set_activation_policy_at_runtime).
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy);

/// Sets the visibility of the application in the dock.
///
/// This function only takes effect if it's called before calling
/// [`run`](crate::event_loop::EventLoop::run) or
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return).
fn set_dock_visibility(&mut self, visible: bool);

/// Used to prevent the application from automatically activating when launched if
/// another application is already active
///
Expand All @@ -340,6 +347,13 @@ impl<T> EventLoopExtMacOS for EventLoop<T> {
}
}

#[inline]
fn set_dock_visibility(&mut self, visible: bool) {
unsafe {
get_aux_state_mut(&**self.event_loop.delegate).dock_visibility = visible;
}
}

#[inline]
fn set_activate_ignoring_other_apps(&mut self, ignore: bool) {
unsafe {
Expand Down Expand Up @@ -382,6 +396,12 @@ pub trait EventLoopWindowTargetExtMacOS {
/// [`EventLoopExtMacOS::set_activation_policy`](crate::platform::macos::EventLoopExtMacOS::set_activation_policy).
fn set_activation_policy_at_runtime(&self, activation_policy: ActivationPolicy);

/// Sets the visibility of the application in the dock.
///
/// To set the dock visibility before the app starts running, see
/// [`EventLoopExtMacOS::set_dock_visibility`](crate::platform::macos::EventLoopExtMacOS::set_dock_visibility).
fn set_dock_visibility_at_runtime(&self, visible: bool);

/// Sets the badge label on macos dock
fn set_badge_label(&self, label: Option<String>);
}
Expand Down Expand Up @@ -420,6 +440,10 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
unsafe { msg_send![app, setActivationPolicy: ns_activation_policy] }
}

fn set_dock_visibility_at_runtime(&self, visible: bool) {
set_dock_visibility(visible);
}

fn set_badge_label(&self, label: Option<String>) {
set_badge_label(label);
}
Expand Down
4 changes: 4 additions & 0 deletions src/platform_impl/macos/app_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub struct AuxDelegateState {
/// menubar is initially unresponsive on macOS 10.15 for example.
pub activation_policy: ActivationPolicy,

/// Whether the application is visible in the dock.
pub dock_visibility: bool,

pub activate_ignoring_other_apps: bool,
}

Expand Down Expand Up @@ -85,6 +88,7 @@ extern "C" fn new(class: &Class, _: Sel) -> id {
Box::into_raw(Box::new(RefCell::new(AuxDelegateState {
activation_policy: ActivationPolicy::Regular,
activate_ignoring_other_apps: true,
dock_visibility: true,
}))) as *mut c_void,
);
this
Expand Down
87 changes: 87 additions & 0 deletions src/platform_impl/macos/dock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#![allow(non_snake_case, non_upper_case_globals)]

use std::{
sync::Mutex,
time::{Duration, Instant},
};

use cocoa::base::{id, NO};

#[link(name = "ApplicationServices", kind = "framework")]
extern "C" {
fn TransformProcessType(psn: *const ProcessSerialNumber, transformState: i32) -> i32;
}

#[repr(C)]
struct ProcessSerialNumber {
highLongOfPSN: u32,
lowLongOfPSN: u32,
}

/// https://developer.apple.com/documentation/applicationservices/1501096-anonymous/kcurrentprocess?language=objc
pub const kCurrentProcess: u32 = 2;
/// https://developer.apple.com/documentation/applicationservices/1501117-anonymous/kprocesstransformtouielementapplication?language=objc
pub const kProcessTransformToUIElementApplication: i32 = 4;
/// https://developer.apple.com/documentation/applicationservices/1501117-anonymous/kprocesstransformtoforegroundapplication?language=objc
pub const kProcessTransformToForegroundApplication: i32 = 1;

lazy_static! {
static ref LAST_DOCK_SHOW: Mutex<Option<Instant>> = Mutex::new(None);
}

pub fn set_dock_visibility(visible: bool) {
if visible {
set_dock_show();
} else {
set_dock_hide();
}
}

fn set_dock_hide() {
// Transforming application state from UIElement to Foreground is an
// asynchronous operation, and unfortunately there is currently no way to know
// when it is finished.
// So if we call DockHide => DockShow => DockHide => DockShow in a very short
// time, we would trigger a bug of macOS that, there would be multiple dock
// icons of the app left in system.
// To work around this, we make sure DockHide does nothing if it is called
// immediately after DockShow. After some experiments, 1 second seems to be
// a proper interval.
let now = Instant::now();
let last_dock_show = LAST_DOCK_SHOW.lock().unwrap();
if let Some(last_dock_show_time) = *last_dock_show {
if now.duration_since(last_dock_show_time) < Duration::new(1, 0) {
return;
}
}

unsafe {
let app: id = msg_send![class!(NSApplication), sharedApplication];
let windows: id = msg_send![app, windows];

// Assuming windows is an NSArray, and calling setCanHide on each window
let selector = sel!(setCanHide:);
let _: () = msg_send![windows, makeObjectsPerformSelector:selector withObject:NO];

let psn = ProcessSerialNumber {
highLongOfPSN: 0,
lowLongOfPSN: kCurrentProcess,
};
TransformProcessType(&psn, kProcessTransformToUIElementApplication);
}
}

fn set_dock_show() {
let now = Instant::now();
let mut last_dock_show = LAST_DOCK_SHOW.lock().unwrap();
*last_dock_show = Some(now);

unsafe {
let psn = ProcessSerialNumber {
highLongOfPSN: 0,
lowLongOfPSN: kCurrentProcess,
};

TransformProcessType(&psn, kProcessTransformToForegroundApplication);
}
}
3 changes: 2 additions & 1 deletion src/platform_impl/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod app;
mod app_delegate;
mod app_state;
mod badge;
mod dock;
mod event;
mod event_loop;
mod ffi;
Expand Down Expand Up @@ -37,7 +38,7 @@ use crate::{
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
};
pub(crate) use badge::set_badge_label;

pub(crate) use dock::set_dock_visibility;
pub(crate) use icon::PlatformIcon;

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down
Loading