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,
            }
        }
    }
}