1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
#![cfg(feature = "win32_app")]
//! Helpers to simplify some tedious aspects of a Win32 application.
//!
//! They allow you to combine them with the regular Windows API, and so can be undermined, which is why they haven't been implemented hiding underlying complexity too much. E.g., a `Window` could be destoyed via its `HWND` while the struct continues to exist.
//!
//! The types automatically free resources they allocated on drop, but you have to pay attention to the correct drop order, e.g., in structs. Since fields are dropped from top to bottom, specify the higher-level resources last.
//!
//! Activate the feature `windows_<version>_win32_app` (available from `windows` v0.52 onwards).
pub mod error;
pub mod msg_loop;
pub mod tray_icon;
pub mod window;
mod app;
pub use app::*;
#[cfg(all(test, feature = "windows_latest_compatible_all"))]
mod tests {
use super::{
error::{try_or_quit_now, try_or_set_app_error, try_then_favor_app_error},
tray_icon::SimplifiedTrayIconMsg,
window::translate_timer_msg,
AppLike, InvisibleWindowAppHelper,
};
use crate::{
cell::ReentrantRefCell,
core::CheckNumberError,
win32_app::{
msg_loop,
tray_icon::{BalloonIcon, TrayIcon},
},
windows, Null, ResGuard,
};
use anyhow::anyhow;
use std::rc::Rc;
use windows::Win32::{
Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM},
UI::WindowsAndMessaging::{
DestroyWindow, KillTimer, LoadIconW, PostQuitMessage, SetTimer, HICON, IDI_APPLICATION,
WM_APP, WM_CREATE, WM_DESTROY, WM_TIMER,
},
};
#[ignore]
#[test]
fn tray_icon_app() -> anyhow::Result<()> {
try_then_favor_app_error(|| -> anyhow::Result<()> {
let (_app_helper, _app) = App::new()?;
msg_loop::run()?;
Ok(())
})
.map_err(|e| anyhow!(e))
}
struct App {
tray_icon: TrayIcon,
_tray_h_icon: ResGuard<HICON>,
}
impl App {
const TRAY_ICON_MSG: u32 = WM_APP;
const TIMER_ID: usize = 1;
}
impl<'a> AppLike<InvisibleWindowAppHelper<'a>> for App {
fn new() -> windows::core::Result<(
InvisibleWindowAppHelper<'a>,
Rc<ReentrantRefCell<Option<Self>>>,
)> {
Ok(unsafe { InvisibleWindowAppHelper::make_app()? })
}
fn startup_wnd_proc(
hwnd: HWND,
msg_id: u32,
_wparam: WPARAM,
_lparam: LPARAM,
) -> (Option<Self>, Option<LRESULT>) {
if msg_id == WM_CREATE {
let app = try_or_set_app_error(|| -> windows::core::Result<_> {
let mut tray_icon = TrayIcon::with_primary_id(hwnd, Some(Self::TRAY_ICON_MSG))?;
let tray_h_icon = ResGuard::with_acq_and_destroy_icon(|| unsafe {
LoadIconW(HINSTANCE::NULL, IDI_APPLICATION)
})?;
unsafe { tray_icon.set_icon(*tray_h_icon)? };
tray_icon.show(true)?;
unsafe {
SetTimer(hwnd, Self::TIMER_ID, 1500 /*ms*/, None)
}
.nonzero_or_win32_err()?;
Ok(Self {
tray_icon,
_tray_h_icon: tray_h_icon,
})
});
if let Some(app) = app {
(Some(app), Some(LRESULT(0)))
} else {
// Make window creation fail.
(None, Some(LRESULT(-1)))
}
} else {
(None, None)
}
}
fn wnd_proc(
&mut self,
hwnd: HWND,
msg_id: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> Option<LRESULT> {
match msg_id {
WM_TIMER => {
let msg = unsafe { translate_timer_msg(wparam, lparam) };
if msg.timer_id == Self::TIMER_ID {
try_or_quit_now(|| -> windows::core::Result<()> {
unsafe { KillTimer(hwnd, Self::TIMER_ID)? };
self.tray_icon.show_balloon(
BalloonIcon::User,
Some("Title"),
"This is the notification message.",
false,
true,
true,
)?;
self.tray_icon.set_tooltip(Some("Click the icon to exit"))?;
Ok(())
});
}
Some(LRESULT(0))
}
Self::TRAY_ICON_MSG => {
match self
.tray_icon
.simplifying_translate_window_msg(wparam, lparam)
{
SimplifiedTrayIconMsg::Activated => {
try_or_quit_now(|| -> windows::core::Result<_> {
self.tray_icon.delete()?;
self.reenter_wnd_proc(|_| unsafe { DestroyWindow(hwnd) })
});
Some(LRESULT(0))
}
_ => None,
}
}
WM_DESTROY => {
unsafe { PostQuitMessage(0) };
Some(LRESULT(0))
}
_ => None,
}
}
}
}