native_windows_gui2/controls/
tray_notification.rs1use super::{ControlBase, ControlHandle};
2use crate::win32::base_helper::to_utf16;
3use crate::win32::window_helper as wh;
4use crate::{Icon, NwgError};
5use std::{mem, ptr};
6use winapi::um::shellapi::{
7 NIIF_ERROR, NIIF_INFO, NIIF_LARGE_ICON, NIIF_NONE, NIIF_NOSOUND, NIIF_RESPECT_QUIET_TIME,
8 NIIF_USER, NIIF_WARNING,
9};
10use winapi::um::shellapi::{NOTIFYICONDATAW, Shell_NotifyIconW};
11
12const NOT_BOUND: &'static str = "TrayNotification is not yet bound to a winapi object";
13const BAD_HANDLE: &'static str = "INTERNAL ERROR: TrayNotification handle is not HWND!";
14
15bitflags! {
16 pub struct TrayNotificationFlags: u32 {
17 const NO_ICON = NIIF_NONE;
18 const INFO_ICON = NIIF_INFO;
19 const WARNING_ICON = NIIF_WARNING;
20 const ERROR_ICON = NIIF_ERROR;
21 const USER_ICON = NIIF_USER;
22 const SILENT = NIIF_NOSOUND;
23 const LARGE_ICON = NIIF_LARGE_ICON;
24 const QUIET = NIIF_RESPECT_QUIET_TIME;
25 }
26}
27
28#[derive(Default, PartialEq, Eq)]
87pub struct TrayNotification {
88 pub handle: ControlHandle,
89}
90
91impl TrayNotification {
92 pub fn builder<'a>() -> TrayNotificationBuilder<'a> {
93 TrayNotificationBuilder {
94 parent: None,
95 icon: None,
96 balloon_icon: None,
97 tip: None,
98 info: None,
99 info_title: None,
100 flags: TrayNotificationFlags::NO_ICON,
101 realtime: false,
102 callback: true,
103 visible: true,
104 }
105 }
106
107 pub fn set_visibility(&self, v: bool) {
109 use winapi::um::shellapi::{NIF_STATE, NIM_MODIFY, NIS_HIDDEN};
110
111 if self.handle.blank() {
112 panic!("{}", NOT_BOUND);
113 }
114 self.handle.tray().expect(BAD_HANDLE);
115
116 unsafe {
117 let mut data = self.notify_default();
118 data.uFlags |= NIF_STATE;
119 data.dwState = if v { 0 } else { NIS_HIDDEN };
120 data.dwStateMask = NIS_HIDDEN;
121 Shell_NotifyIconW(NIM_MODIFY, &mut data);
122 }
123 }
124
125 pub fn set_tip<'a>(&self, tip: &'a str) {
128 use winapi::um::shellapi::{NIF_SHOWTIP, NIF_TIP, NIM_MODIFY};
129
130 if self.handle.blank() {
131 panic!("{}", NOT_BOUND);
132 }
133 self.handle.tray().expect(BAD_HANDLE);
134
135 unsafe {
136 let mut data = self.notify_default();
137
138 data.uFlags = NIF_TIP | NIF_SHOWTIP;
139
140 let tip_v = to_utf16(tip);
141 let length = if tip_v.len() >= 128 { 127 } else { tip_v.len() };
142 for i in 0..length {
143 data.szTip[i] = tip_v[i];
144 }
145
146 Shell_NotifyIconW(NIM_MODIFY, &mut data);
147 }
148 }
149
150 pub fn set_focus(&self) {
152 use winapi::um::shellapi::NIM_SETFOCUS;
153
154 if self.handle.blank() {
155 panic!("{}", NOT_BOUND);
156 }
157 self.handle.tray().expect(BAD_HANDLE);
158
159 unsafe {
160 let mut data = self.notify_default();
161 Shell_NotifyIconW(NIM_SETFOCUS, &mut data);
162 }
163 }
164
165 pub fn set_icon(&self, icon: &Icon) {
167 use winapi::shared::windef::HICON;
168 use winapi::um::shellapi::{NIF_ICON, NIM_MODIFY};
169
170 if self.handle.blank() {
171 panic!("{}", NOT_BOUND);
172 }
173 self.handle.tray().expect(BAD_HANDLE);
174
175 unsafe {
176 let mut data = self.notify_default();
177
178 data.uFlags = NIF_ICON;
179 data.hIcon = icon.handle as HICON;
180 Shell_NotifyIconW(NIM_MODIFY, &mut data);
181 }
182 }
183
184 pub fn show<'a>(
195 &self,
196 text: &'a str,
197 title: Option<&'a str>,
198 flags: Option<TrayNotificationFlags>,
199 icon: Option<&'a Icon>,
200 ) {
201 use winapi::shared::windef::HICON;
202 use winapi::um::shellapi::{NIF_INFO, NIM_MODIFY};
203
204 if self.handle.blank() {
205 panic!("{}", NOT_BOUND);
206 }
207 self.handle.tray().expect(BAD_HANDLE);
208
209 let default_flags = TrayNotificationFlags::NO_ICON | TrayNotificationFlags::SILENT;
210
211 unsafe {
212 let mut data = self.notify_default();
213 data.uFlags = NIF_INFO;
214 data.dwInfoFlags = flags.unwrap_or(default_flags).bits();
215 data.hBalloonIcon = icon.map(|i| i.handle as HICON).unwrap_or(ptr::null_mut());
216
217 let info_v = to_utf16(text);
218 let length = if info_v.len() >= 256 {
219 255
220 } else {
221 info_v.len()
222 };
223 for i in 0..length {
224 data.szInfo[i] = info_v[i];
225 }
226
227 let info_title_v = match title {
228 Some(t) => to_utf16(t),
229 None => vec![],
230 };
231
232 let length = if info_title_v.len() >= 256 {
233 255
234 } else {
235 info_title_v.len()
236 };
237 for i in 0..length {
238 data.szInfoTitle[i] = info_title_v[i];
239 }
240
241 Shell_NotifyIconW(NIM_MODIFY, &mut data);
242 }
243 }
244
245 fn notify_default(&self) -> NOTIFYICONDATAW {
246 unsafe {
247 let parent = self.handle.tray().unwrap();
248 NOTIFYICONDATAW {
249 cbSize: mem::size_of::<NOTIFYICONDATAW>() as u32,
250 hWnd: parent,
251 uID: 0,
252 uFlags: 0,
253 uCallbackMessage: 0,
254 hIcon: ptr::null_mut(),
255 szTip: mem::zeroed(),
256 dwState: 0,
257 dwStateMask: 0,
258 szInfo: mem::zeroed(),
259 u: mem::zeroed(),
260 szInfoTitle: mem::zeroed(),
261 dwInfoFlags: 0,
262 guidItem: mem::zeroed(),
263 hBalloonIcon: ptr::null_mut(),
264 }
265 }
266 }
267}
268
269impl Drop for TrayNotification {
270 fn drop(&mut self) {
271 use winapi::um::shellapi::NIM_DELETE;
272
273 if self.handle.tray().is_some() {
274 let mut data = self.notify_default();
275 unsafe {
276 Shell_NotifyIconW(NIM_DELETE, &mut data);
277 }
278 }
279
280 self.handle.destroy();
281 }
282}
283
284pub struct TrayNotificationBuilder<'a> {
285 parent: Option<ControlHandle>,
286 icon: Option<&'a Icon>,
287
288 tip: Option<&'a str>,
289
290 info: Option<&'a str>,
291 info_title: Option<&'a str>,
292 flags: TrayNotificationFlags,
293 balloon_icon: Option<&'a Icon>,
294
295 realtime: bool,
296 callback: bool,
297 visible: bool,
298}
299
300impl<'a> TrayNotificationBuilder<'a> {
301 pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> TrayNotificationBuilder<'a> {
302 self.parent = Some(p.into());
303 self
304 }
305
306 pub fn icon(mut self, ico: Option<&'a Icon>) -> TrayNotificationBuilder<'a> {
307 self.icon = ico;
308 self
309 }
310
311 pub fn realtime(mut self, r: bool) -> TrayNotificationBuilder<'a> {
312 self.realtime = r;
313 self
314 }
315
316 pub fn callback(mut self, cb: bool) -> TrayNotificationBuilder<'a> {
317 self.callback = cb;
318 self
319 }
320
321 pub fn visible(mut self, v: bool) -> TrayNotificationBuilder<'a> {
322 self.visible = v;
323 self
324 }
325
326 pub fn balloon_icon(mut self, ico: Option<&'a Icon>) -> TrayNotificationBuilder<'a> {
328 self.balloon_icon = ico;
329 self
330 }
331
332 pub fn flags(mut self, flags: TrayNotificationFlags) -> TrayNotificationBuilder<'a> {
334 self.flags = flags;
335 self
336 }
337
338 pub fn tip(mut self, tip: Option<&'a str>) -> TrayNotificationBuilder<'a> {
340 self.tip = tip;
341 self
342 }
343
344 pub fn info(mut self, info: Option<&'a str>) -> TrayNotificationBuilder<'a> {
346 self.info = info;
347 self
348 }
349
350 pub fn info_title(mut self, title: Option<&'a str>) -> TrayNotificationBuilder<'a> {
353 self.info_title = title;
354 self
355 }
356
357 pub fn build(self, out: &mut TrayNotification) -> Result<(), NwgError> {
358 use winapi::shared::windef::HICON;
359 use winapi::um::shellapi::{
360 NIF_ICON, NIF_INFO, NIF_MESSAGE, NIF_REALTIME, NIF_SHOWTIP, NIF_STATE, NIF_TIP,
361 NIM_ADD, NIS_HIDDEN, NOTIFYICON_VERSION_4, NOTIFYICONDATAW_u,
362 };
363 use winapi::um::winnt::WCHAR;
364
365 let version = NOTIFYICON_VERSION_4;
367 let mut flags = NIF_ICON;
368 let mut info_flags = 0;
369 let mut state = 0;
370
371 if self.info.is_some() {
372 flags |= NIF_INFO;
373 info_flags |= self.flags.bits();
374 }
375
376 if self.tip.is_some() {
377 flags |= NIF_TIP | NIF_SHOWTIP;
378 }
379
380 if self.realtime {
381 flags |= NIF_REALTIME;
382 }
383 if self.callback {
384 flags |= NIF_MESSAGE;
385 }
386 if !self.visible {
387 state |= NIS_HIDDEN;
388 flags |= NIF_STATE;
389 }
390
391 let parent = match self.parent {
394 Some(p) => match p.hwnd() {
395 Some(handle) => Ok(handle),
396 None => Err(NwgError::control_create(
397 "TrayNotification must be window-like control.",
398 )),
399 },
400 None => Err(NwgError::no_parent("Button")),
401 }?;
402
403 let icon = match self.icon {
404 Some(i) => i.handle as HICON,
405 None => panic!("Tray notification requires an Icon at creation"),
406 };
407
408 let balloon_icon = match (self.info.is_some(), self.balloon_icon) {
409 (false, _) | (true, None) => ptr::null_mut(),
410 (true, Some(i)) => i.handle as HICON,
411 };
412
413 let handle = ControlBase::build_tray_notification()
415 .parent(parent)
416 .build()?;
417
418 let mut tip: [WCHAR; 128] = [0; 128];
420 if self.tip.is_some() {
421 let tip_v = to_utf16(self.tip.unwrap());
422 let length = if tip_v.len() >= 128 { 127 } else { tip_v.len() };
423 for i in 0..length {
424 tip[i] = tip_v[i];
425 }
426 }
427
428 let mut info: [WCHAR; 256] = [0; 256];
429 if self.info.is_some() {
430 let info_v = to_utf16(self.info.unwrap());
431 let length = if info_v.len() >= 256 {
432 255
433 } else {
434 info_v.len()
435 };
436 for i in 0..length {
437 info[i] = info_v[i];
438 }
439 }
440
441 let mut title: [WCHAR; 64] = [0; 64];
442 if self.info.is_some() && self.info_title.is_some() {
443 let info_title_v = to_utf16(self.info_title.unwrap());
444 let length = if info_title_v.len() >= 256 {
445 255
446 } else {
447 info_title_v.len()
448 };
449 for i in 0..length {
450 title[i] = info_title_v[i];
451 }
452 }
453
454 unsafe {
456 let mut u: NOTIFYICONDATAW_u = mem::zeroed();
457 *u.uVersion_mut() = version;
458
459 let mut data = NOTIFYICONDATAW {
460 cbSize: mem::size_of::<NOTIFYICONDATAW>() as u32,
461 hWnd: parent,
462 uID: 0,
463 uFlags: flags,
464 uCallbackMessage: wh::NWG_TRAY,
465 hIcon: icon,
466 szTip: tip,
467 dwState: state,
468 dwStateMask: state,
469 szInfo: info,
470 u,
471 szInfoTitle: title,
472 dwInfoFlags: info_flags,
473 guidItem: mem::zeroed(),
474 hBalloonIcon: balloon_icon,
475 };
476
477 Shell_NotifyIconW(NIM_ADD, &mut data);
478 }
479
480 *out = Default::default();
482 out.handle = handle;
483
484 Ok(())
485 }
486}