Rust で Windows プログラミング - Shell_NotifyIcon編

##環境
toolchain: stable-i686-pc-windows-msvc
rustc 1.32.0 (9fda7c223 2019-01-16)
[winapi 0.3.6](https://github.com/retep998/winapi-rs)

##最小化でタスクトレイに収納
[前回ウィンドウを表示した](https://qiita.com/benki/items/c01f7da1e1a71cc91d11)ので、そのウィンドウを最小化するとタスクトレイにアイコンとして格納されるようにしてみます。タスクトレイのアイコンを左クリックすると、ウィンドウが再表示されます。

Cargo.toml の features に`"shellapi"`を追加します。

```toml:Cargo.toml
[dependencies.winapi]
version = "0.3"
features = ["winuser", "shellapi"]
```

main.rs に shellapi module を追加。

```rust:main.rs
use winapi::{
    um::{
        winuser::{RegisterClassW, WNDCLASSW, CS_HREDRAW, CS_VREDRAW,
                  LoadIconW, IDI_APPLICATION, LoadCursorW, IDC_ARROW,
                  CreateWindowExW, ShowWindow, SW_NORMAL, SW_HIDE, UpdateWindow,
                  GetMessageW, TranslateMessage, DispatchMessageW, MSG,
                  WM_DESTROY, PostQuitMessage, DefWindowProcW, WS_OVERLAPPEDWINDOW,
                  WM_USER, WM_SYSCOMMAND, SC_MINIMIZE, WM_LBUTTONDOWN},
        wingdi::{GetStockObject, WHITE_BRUSH},
        shellapi::{Shell_NotifyIconW, NOTIFYICONDATAW, NIM_ADD, NIM_DELETE,
                   NIF_ICON, NIF_MESSAGE, NIF_TIP},
    },
    shared::{
        windef::{HWND, HBRUSH},
        minwindef::{UINT, WPARAM, LPARAM, LRESULT},
    },
};
use std::ptr;
use std::mem;
```

タスクトレイアイコンのメッセージ識別子（？）と識別用のIDを定義し、`NOTIFYICONDATA`構造体へのポインタをグローバル変数として持っておきます。

```rust:main.rs
const MYMSG_TRAY: UINT = WM_USER + 1;
const ID_MYTRAY: UINT = 58091;

static mut P_NID: *mut NOTIFYICONDATAW = ptr::null_mut();
```

`main`関数に`NOTIFYICONDATA`構造体を初期化するコードを追加します。

```rust:main.rs
fn main() {
    unsafe {
        let class_name = encode("my_window_class_name");
        if !register_wndclass(&class_name) {
            return;
        }

        let hwnd = create_window(&class_name);
        if hwnd.is_null() {
            return;
        }

        let mut nid = create_nid(hwnd); //追加
        P_NID = &mut nid; //追加

        ShowWindow(hwnd, SW_NORMAL);
        UpdateWindow(hwnd);

        let mut msg = mem::uninitialized::<MSG>();
        loop {
            if GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
                return;
            }
            TranslateMessage(&mut msg);
            DispatchMessageW(&mut msg);
        }
    }
}
```

`create_nid`関数の中身です。例によって、`std::mem::zeroed()`で構造体を初期化して、必要なメンバだけに値を設定してきます。`szTip`は`[u16; 128]`の配列にNULL終端文字列を入れる必要がありますが、Rust ではどうやると良いのでしょう？

とりあえずサンプルでは、`[0u16; 128]`で 0 で初期化された配列を用意しておいて、`std::ptr::copy()`で UTF-16 にエンコードした文字列を配列にコピーするというやり方で対応しました。

```rust:main.rs
unsafe fn create_nid(hwnd: HWND) -> NOTIFYICONDATAW {
    let mut nid = mem::zeroed::<NOTIFYICONDATAW>();
    nid.cbSize = mem::size_of::<NOTIFYICONDATAW>() as u32;
    nid.uID = ID_MYTRAY;
    nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    nid.hWnd = hwnd;
    nid.uCallbackMessage = MYMSG_TRAY;
    nid.hIcon = LoadIconW(ptr::null_mut(), IDI_APPLICATION);
    let mut buf = [0u16; 128];
    let tip = "Hello, World!";
    ptr::copy(encode(tip).as_ptr(), &mut buf[0], tip.len());
    nid.szTip = buf;
    nid
}
```

最後にウィンドウプロシージャです。最小化したらアイコンをタスクトレイに表示してウィンドウを非表示に、タスクトレイのアイコンを左クリックしたらアイコンを削除してウィンドウを再表示するという処理です。終了時にタスクトレイにアイコンが残らないように、`WM_DESTROY`のところでアイコンを消すようにしています。

`NOTIFYICONDATA`構造体のポインタをグローバル変数として準備しておいたのは、下記のようにウィンドウプロシージャ内で`Shell_NotifyICon`関数に引数として渡す必要があったからです。

```rust:main.rs
unsafe extern "system" fn win_proc(hwnd: HWND, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
    match msg {
        WM_SYSCOMMAND => {
            match w_param {
                SC_MINIMIZE => {
                    Shell_NotifyIconW(NIM_ADD, P_NID);
                    ShowWindow(hwnd, SW_HIDE);
                },
                _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
            };
        },
        MYMSG_TRAY => {
            match (w_param as u32, l_param as u32) {
                (ID_MYTRAY, WM_LBUTTONDOWN) => {
                    Shell_NotifyIconW(NIM_DELETE, P_NID);
                    ShowWindow(hwnd, SW_NORMAL);
                },
                _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
            }
        },
        WM_DESTROY => {
            Shell_NotifyIconW(NIM_DELETE, P_NID);
            PostQuitMessage(0);
        },
        _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
    };
    0
}
```

main.rs をまとめてどうぞ。

```rust:main.rs
use winapi::{
    um::{
        winuser::{RegisterClassW, WNDCLASSW, CS_HREDRAW, CS_VREDRAW,
                  LoadIconW, IDI_APPLICATION, LoadCursorW, IDC_ARROW,
                  CreateWindowExW, ShowWindow, SW_NORMAL, SW_HIDE, UpdateWindow,
                  GetMessageW, TranslateMessage, DispatchMessageW, MSG,
                  WM_DESTROY, PostQuitMessage, DefWindowProcW, WS_OVERLAPPEDWINDOW,
                  WM_USER, WM_SYSCOMMAND, SC_MINIMIZE, WM_LBUTTONDOWN},
        wingdi::{GetStockObject, WHITE_BRUSH},
        shellapi::{Shell_NotifyIconW, NOTIFYICONDATAW, NIM_ADD, NIM_DELETE,
                   NIF_ICON, NIF_MESSAGE, NIF_TIP},
    },
    shared::{
        windef::{HWND, HBRUSH},
        minwindef::{UINT, WPARAM, LPARAM, LRESULT},
    },
};
use std::ptr;
use std::mem;

const MYMSG_TRAY: UINT = WM_USER + 1;
const ID_MYTRAY: UINT = 58091;

static mut P_NID: *mut NOTIFYICONDATAW = ptr::null_mut();

fn main() {
    unsafe {
        let class_name = encode("my_window_class_name");
        if !register_wndclass(&class_name) {
            return;
        }

        let hwnd = create_window(&class_name);
        if hwnd.is_null() {
            return;
        }

        let mut nid = create_nid(hwnd);
        P_NID = &mut nid;

        ShowWindow(hwnd, SW_NORMAL);
        UpdateWindow(hwnd);

        let mut msg = mem::uninitialized::<MSG>();
        loop {
            if GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
                return;
            }
            TranslateMessage(&mut msg);
            DispatchMessageW(&mut msg);
        }
    }
}

fn encode(source: &str) -> Vec<u16> {
    source.encode_utf16().chain(Some(0)).collect()
}

unsafe fn register_wndclass(class_name: &[u16]) -> bool {
    let mut winc = mem::zeroed::<WNDCLASSW>();
    winc.style = CS_HREDRAW | CS_VREDRAW;
    winc.lpfnWndProc = Some(win_proc);
    winc.hIcon = LoadIconW(ptr::null_mut(), IDI_APPLICATION);
    winc.hCursor = LoadCursorW(ptr::null_mut(), IDC_ARROW);
    winc.hbrBackground = GetStockObject(WHITE_BRUSH as i32) as HBRUSH;
    winc.lpszClassName = class_name.as_ptr();

    RegisterClassW(&winc) > 0
}

unsafe fn create_window(class_name: &[u16]) -> HWND {
    CreateWindowExW(
        0,
        class_name.as_ptr(),
        encode("Hello, World!").as_ptr(),
        WS_OVERLAPPEDWINDOW,
        0,0, 200, 200,
        ptr::null_mut(),
        ptr::null_mut(),
        ptr::null_mut(),
        ptr::null_mut(),
    )
}

unsafe fn create_nid(hwnd: HWND) -> NOTIFYICONDATAW {
    let mut nid = mem::zeroed::<NOTIFYICONDATAW>();
    nid.cbSize = mem::size_of::<NOTIFYICONDATAW>() as u32;
    nid.uID = ID_MYTRAY;
    nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    nid.hWnd = hwnd;
    nid.uCallbackMessage = MYMSG_TRAY;
    nid.hIcon = LoadIconW(ptr::null_mut(), IDI_APPLICATION);
    let mut buf = [0u16; 128];
    let tip = "Hello, World!";
    ptr::copy(encode(tip).as_ptr(), &mut buf[0], tip.len());
    nid.szTip = buf;
    nid
}

unsafe extern "system" fn win_proc(hwnd: HWND, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
    match msg {
        WM_SYSCOMMAND => {
            match w_param {
                SC_MINIMIZE => {
                    Shell_NotifyIconW(NIM_ADD, P_NID);
                    ShowWindow(hwnd, SW_HIDE);
                },
                _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
            };
        },
        MYMSG_TRAY => {
            match (w_param as u32, l_param as u32) {
                (ID_MYTRAY, WM_LBUTTONDOWN) => {
                    Shell_NotifyIconW(NIM_DELETE, P_NID);
                    ShowWindow(hwnd, SW_NORMAL);
                },
                _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
            }
        },
        WM_DESTROY => {
            Shell_NotifyIconW(NIM_DELETE, P_NID);
            PostQuitMessage(0);
        },
        _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
    };
    0
}
```

また何かネタを思いついたら投稿します。

今までのネタ
[Rust で Windows プログラミング - MessageBox編](https://qiita.com/benki/items/778ba2bd1266d28bce61)
[Rust で Windows プログラミング - CreateWindow編](https://qiita.com/benki/items/c01f7da1e1a71cc91d11)