Skip to content

Commit

Permalink
feat: add set_dock_visibility method
Browse files Browse the repository at this point in the history
Signed-off-by: The1111mp <[email protected]>
  • Loading branch information
1111mp committed Feb 14, 2025
1 parent 5cc9298 commit c4d0f05
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 2 deletions.
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.
27 changes: 26 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,14 @@ impl<T> EventLoopExtMacOS for EventLoop<T> {
}
}

#[inline]
fn set_dock_visibility(&mut self, visible: bool) {
eprintln!("call set_dock_visibilityset_dock_visibility");
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 +397,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 +441,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

0 comments on commit c4d0f05

Please sign in to comment.