Skip to content

Commit 1f72c24

Browse files
authored
feat: add set_badge_count for Linux, iOS; set_badge_label for Macos; set_overlay_icon for Windows (#1002)
1 parent aff33fb commit 1f72c24

File tree

20 files changed

+330
-8
lines changed

20 files changed

+330
-8
lines changed

.changes/badge_count.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tao": patch
3+
---
4+
5+
Add `WindowExtUnix::set_badge_count` for Linux, `WindowExtIos::set_badge_count` for iOS, `WindowExtMacos::set_badge_label` for Macos, `MacdowExtWindows::set_overlay_icon` for Windows

examples/icon.ico

4.19 KB
Binary file not shown.

examples/overlay.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2014-2021 The winit contributors
2+
// Copyright 2021-2023 Tauri Programme within The Commons Conservancy
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
use std::env::current_dir;
6+
use tao::{
7+
event::{ElementState, Event, KeyEvent, WindowEvent},
8+
event_loop::{ControlFlow, EventLoop},
9+
keyboard::{Key, ModifiersState},
10+
window::WindowBuilder,
11+
};
12+
13+
#[cfg(any(
14+
target_os = "linux",
15+
target_os = "dragonfly",
16+
target_os = "freebsd",
17+
target_os = "netbsd",
18+
target_os = "openbsd"
19+
))]
20+
use tao::platform::unix::WindowExtUnix;
21+
22+
#[cfg(target_os = "macos")]
23+
use tao::platform::macos::WindowExtMacOS;
24+
25+
#[cfg(target_os = "ios")]
26+
use tao::platform::ios::WindowExtIOS;
27+
28+
#[cfg(windows)]
29+
use tao::{
30+
dpi::PhysicalSize, platform::windows::IconExtWindows, platform::windows::WindowExtWindows,
31+
window::Icon,
32+
};
33+
34+
#[allow(clippy::single_match)]
35+
fn main() {
36+
env_logger::init();
37+
let event_loop = EventLoop::new();
38+
39+
let window = WindowBuilder::new().build(&event_loop).unwrap();
40+
41+
let mut modifiers = ModifiersState::default();
42+
43+
eprintln!("Key mappings:");
44+
#[cfg(windows)]
45+
eprintln!(" [any key]: Show the Overlay Icon");
46+
#[cfg(not(windows))]
47+
eprintln!(" [1-5]: Show a Badge count");
48+
eprintln!(" Ctrl+1: Clear");
49+
50+
event_loop.run(move |event, _, control_flow| {
51+
*control_flow = ControlFlow::Wait;
52+
53+
match event {
54+
Event::WindowEvent {
55+
event: WindowEvent::CloseRequested,
56+
..
57+
} => *control_flow = ControlFlow::Exit,
58+
Event::WindowEvent { event, .. } => match event {
59+
WindowEvent::ModifiersChanged(new_state) => {
60+
modifiers = new_state;
61+
}
62+
WindowEvent::KeyboardInput {
63+
event:
64+
KeyEvent {
65+
logical_key: Key::Character(key_str),
66+
state: ElementState::Released,
67+
..
68+
},
69+
..
70+
} => {
71+
let _count = match key_str {
72+
"1" => 1,
73+
"2" => 2,
74+
"3" => 3,
75+
"4" => 4,
76+
"5" => 5,
77+
_ => 20,
78+
};
79+
80+
if modifiers.is_empty() {
81+
#[cfg(windows)]
82+
{
83+
let mut path = current_dir().unwrap();
84+
path.push("./examples/icon.ico");
85+
let icon = Icon::from_path(path, Some(PhysicalSize::new(32, 32))).unwrap();
86+
87+
window.set_overlay_icon(Some(&icon));
88+
}
89+
90+
#[cfg(any(
91+
target_os = "linux",
92+
target_os = "dragonfly",
93+
target_os = "freebsd",
94+
target_os = "netbsd",
95+
target_os = "openbsd"
96+
))]
97+
window.set_badge_count(Some(_count), None);
98+
99+
#[cfg(target_os = "macos")]
100+
window.set_badge_label(_count.to_string().into());
101+
102+
#[cfg(target_os = "ios")]
103+
window.set_badge_count(_count);
104+
} else if modifiers.control_key() && key_str == "1" {
105+
#[cfg(windows)]
106+
window.set_overlay_icon(None);
107+
108+
#[cfg(any(
109+
target_os = "linux",
110+
target_os = "dragonfly",
111+
target_os = "freebsd",
112+
target_os = "netbsd",
113+
target_os = "openbsd"
114+
))]
115+
window.set_badge_count(None, None);
116+
117+
#[cfg(target_os = "macos")]
118+
window.set_badge_label(None);
119+
120+
#[cfg(target_os = "ios")]
121+
window.set_badge_count(0);
122+
}
123+
}
124+
_ => {}
125+
},
126+
_ => {}
127+
}
128+
});
129+
}

src/platform/ios.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
use std::os::raw::c_void;
88

99
use crate::{
10-
event_loop::EventLoop,
10+
event_loop::{EventLoop, EventLoopWindowTarget},
1111
monitor::{MonitorHandle, VideoMode},
12+
platform_impl::set_badge_count,
1213
window::{Window, WindowBuilder},
1314
};
1415

@@ -98,6 +99,9 @@ pub trait WindowExtIOS {
9899
/// and then calls
99100
/// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc).
100101
fn set_prefers_status_bar_hidden(&self, hidden: bool);
102+
103+
/// Sets the badge count on iOS launcher. 0 hides the count
104+
fn set_badge_count(&self, count: i32);
101105
}
102106

103107
impl WindowExtIOS for Window {
@@ -142,6 +146,22 @@ impl WindowExtIOS for Window {
142146
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
143147
self.window.set_prefers_status_bar_hidden(hidden)
144148
}
149+
150+
#[inline]
151+
fn set_badge_count(&self, count: i32) {
152+
self.window.set_badge_count(count)
153+
}
154+
}
155+
156+
pub trait EventLoopWindowTargetExtIOS {
157+
/// Sets the badge count on iOS launcher. 0 hides the count
158+
fn set_badge_count(&self, count: i32);
159+
}
160+
161+
impl<T> EventLoopWindowTargetExtIOS for EventLoopWindowTarget<T> {
162+
fn set_badge_count(&self, count: i32) {
163+
set_badge_count(count)
164+
}
145165
}
146166

147167
/// Additional methods on [`WindowBuilder`] that are specific to iOS.

src/platform/macos.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
dpi::{LogicalSize, Position},
1111
event_loop::{EventLoop, EventLoopWindowTarget},
1212
monitor::MonitorHandle,
13-
platform_impl::{get_aux_state_mut, Parent},
13+
platform_impl::{get_aux_state_mut, set_badge_label, Parent},
1414
window::{Window, WindowBuilder},
1515
};
1616

@@ -84,6 +84,9 @@ pub trait WindowExtMacOS {
8484
///
8585
/// <https://developer.apple.com/documentation/appkit/nswindow/1419167-titlebarappearstransparent>
8686
fn set_titlebar_transparent(&self, transparent: bool);
87+
88+
/// Sets the badge label on the taskbar
89+
fn set_badge_label(&self, label: Option<String>);
8790
}
8891

8992
impl WindowExtMacOS for Window {
@@ -161,6 +164,11 @@ impl WindowExtMacOS for Window {
161164
fn set_titlebar_transparent(&self, transparent: bool) {
162165
self.window.set_titlebar_transparent(transparent);
163166
}
167+
168+
#[inline]
169+
fn set_badge_label(&self, label: Option<String>) {
170+
self.window.set_badge_label(label);
171+
}
164172
}
165173

166174
/// Corresponds to `NSApplicationActivationPolicy`.
@@ -388,6 +396,9 @@ pub trait EventLoopWindowTargetExtMacOS {
388396
/// To set the activation policy before the app starts running, see
389397
/// [`EventLoopExtMacOS::set_activation_policy`](crate::platform::macos::EventLoopExtMacOS::set_activation_policy).
390398
fn set_activation_policy_at_runtime(&self, activation_policy: ActivationPolicy);
399+
400+
/// Sets the badge label on macos dock
401+
fn set_badge_label(&self, label: Option<String>);
391402
}
392403

393404
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
@@ -415,4 +426,8 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
415426
let ns_activation_policy: NSApplicationActivationPolicy = activation_policy.into();
416427
unsafe { msg_send![app, setActivationPolicy: ns_activation_policy] }
417428
}
429+
430+
fn set_badge_label(&self, label: Option<String>) {
431+
set_badge_label(label);
432+
}
418433
}

src/platform/unix.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ pub trait WindowExtUnix {
7878

7979
/// Whether to show the window icon in the taskbar or not.
8080
fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError>;
81+
82+
fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>);
8183
}
8284

8385
impl WindowExtUnix for Window {
@@ -100,6 +102,10 @@ impl WindowExtUnix for Window {
100102
let window = UnixWindow::new_from_gtk_window(&event_loop_window_target.p, window)?;
101103
Ok(Window { window: window })
102104
}
105+
106+
fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) {
107+
self.window.set_badge_count(count, desktop_filename);
108+
}
103109
}
104110

105111
pub trait WindowBuilderExtUnix {
@@ -208,6 +214,9 @@ pub trait EventLoopWindowTargetExtUnix {
208214

209215
/// Returns the gtk application for this event loop.
210216
fn gtk_app(&self) -> &gtk::Application;
217+
218+
/// Sets the badge count on the taskbar
219+
fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>);
211220
}
212221

213222
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
@@ -249,6 +258,11 @@ impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
249258
fn gtk_app(&self) -> &gtk::Application {
250259
&self.p.app
251260
}
261+
262+
#[inline]
263+
fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) {
264+
self.p.set_badge_count(count, desktop_filename);
265+
}
252266
}
253267

254268
unsafe extern "C" fn x_error_callback(

src/platform/windows.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ pub trait WindowExtWindows {
159159
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
160160
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
161161

162+
/// This sets the overlay icon
163+
fn set_overlay_icon(&self, icon: Option<&Icon>);
164+
162165
/// Returns the current window theme.
163166
fn theme(&self) -> Theme;
164167

@@ -238,6 +241,11 @@ impl WindowExtWindows for Window {
238241
fn set_rtl(&self, rtl: bool) {
239242
self.window.set_rtl(rtl)
240243
}
244+
245+
#[inline]
246+
fn set_overlay_icon(&self, icon: Option<&Icon>) {
247+
self.window.set_overlay_icon(icon);
248+
}
241249
}
242250

243251
/// Additional methods on `WindowBuilder` that are specific to Windows.

src/platform_impl/ios/badge.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use objc::runtime::{Class, Object};
2+
use objc::{msg_send, sel, sel_impl};
3+
4+
pub fn set_badge_count(count: i32) {
5+
unsafe {
6+
let ui_application = Class::get("UIApplication").expect("Failed to get UIApplication class");
7+
let app: *mut Object = msg_send![ui_application, sharedApplication];
8+
let _: () = msg_send![app, setApplicationIconBadgeNumber:count];
9+
}
10+
}

src/platform_impl/ios/event_loop.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::platform_impl::platform::{
3131
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
3232
NSStringRust, UIApplicationMain, UIUserInterfaceIdiom,
3333
},
34-
monitor, view, MonitorHandle,
34+
monitor, set_badge_count, view, MonitorHandle,
3535
};
3636

3737
#[non_exhaustive]
@@ -94,6 +94,11 @@ impl<T: 'static> EventLoopWindowTarget<T> {
9494
debug!("`EventLoopWindowTarget::cursor_position` is ignored on iOS");
9595
Ok((0, 0).into())
9696
}
97+
98+
/// Sets badge count on iOS launcher. 0 hides the count
99+
pub fn set_badge_count(&self, count: i32) {
100+
set_badge_count(count);
101+
}
97102
}
98103

99104
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]

src/platform_impl/ios/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ macro_rules! assert_main_thread {
7474
}
7575

7676
mod app_state;
77+
mod badge;
7778
mod event_loop;
7879
mod ffi;
7980
mod keycode;
@@ -90,8 +91,8 @@ pub use self::{
9091
monitor::{MonitorHandle, VideoMode},
9192
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
9293
};
93-
9494
pub(crate) use crate::icon::NoIcon as PlatformIcon;
95+
pub(crate) use badge::set_badge_count;
9596

9697
// todo: implement iOS keyboard event
9798
#[derive(Debug, Clone, Eq, PartialEq, Hash)]

src/platform_impl/ios/window.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::{
2323
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, UIRectEdge,
2424
UIScreenOverscanCompensation,
2525
},
26-
monitor, view, EventLoopWindowTarget, MonitorHandle,
26+
monitor, set_badge_count, view, EventLoopWindowTarget, MonitorHandle,
2727
},
2828
window::{
2929
CursorIcon, Fullscreen, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
@@ -440,6 +440,11 @@ impl Inner {
440440
pub fn theme(&self) -> Theme {
441441
Theme::Light
442442
}
443+
444+
/// Sets badge count on iOS launcher. 0 hides the count
445+
pub fn set_badge_count(&self, count: i32) {
446+
set_badge_count(count);
447+
}
443448
}
444449

445450
pub struct Window {

src/platform_impl/linux/event_loop.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ impl<T> EventLoopWindowTarget<T> {
160160
}
161161
}
162162

163+
#[inline]
164+
pub fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) {
165+
if let Err(e) = self.window_requests_tx.send((
166+
WindowId::dummy(),
167+
WindowRequest::BadgeCount(count, desktop_filename),
168+
)) {
169+
log::warn!("Fail to send update progress bar request: {e}");
170+
}
171+
}
172+
163173
#[inline]
164174
pub fn set_theme(&self, theme: Option<Theme>) {
165175
if let Err(e) = self
@@ -432,6 +442,7 @@ impl<T: 'static> EventLoop<T> {
432442
};
433443
}
434444
WindowRequest::ProgressBarState(_) => unreachable!(),
445+
WindowRequest::BadgeCount(_, _) => unreachable!(),
435446
WindowRequest::SetTheme(_) => unreachable!(),
436447
WindowRequest::WireUpEvents {
437448
transparent,
@@ -921,6 +932,9 @@ impl<T: 'static> EventLoop<T> {
921932
WindowRequest::ProgressBarState(state) => {
922933
taskbar.update(state);
923934
}
935+
WindowRequest::BadgeCount(count, desktop_filename) => {
936+
taskbar.update_count(count, desktop_filename);
937+
}
924938
WindowRequest::SetTheme(theme) => {
925939
if let Some(settings) = Settings::default() {
926940
match theme {

0 commit comments

Comments
 (0)