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

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

##ブラウザにキーボードイベントを送ってみる
前回 [EnumWindows でウィンドウタイトルを列挙した](https://qiita.com/benki/items/16e0bb1956358d63a06e)ので、任意のブラウザウィンドウをアクティブにしてキーボードイベントを送ってみます。うまく利用すると作業の自動化もできますね。

Cargo.toml は EnumWindows 編から変更はありません。

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

まずは使用する module を書いていきます。

```rust:main.rs
use winapi::{
    um::winuser::{EnumWindows, GetWindowTextW, SendInput, INPUT, INPUT_KEYBOARD,
                  KEYEVENTF_KEYUP, VK_CONTROL, SetForegroundWindow, SetFocus},
    shared::{
        windef::{HWND},
        minwindef::{LPARAM, BOOL, TRUE, FALSE}
    },
};
use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
use std::mem;
```

`HOGE`構造体の宣言と`main`関数です。`HOGE`構造体への参照を`EnumWindowsProc`コールバック関数に`LPARAM`として渡すということをやっています。`&HOGE as LPARAM`と書くとコンパイラに怒られるので、一度生ポインタにキャスト`(&hoge as *const HOGE)`してから`LPARAM`にキャストしています。

```rust:main.rs
struct HOGE {
    win_names: Vec<String>,
}

fn main() {
    unsafe {
        let win_names = vec!("Mozilla Firefox".into(), "Microsoft Edge".into(), "Google Chrome".into());
        let hoge = HOGE { win_names };
        EnumWindows(Some(enum_proc), (&hoge as *const HOGE) as LPARAM);
    }
}
```

`enum_proc`コールバック関数です。ここでの味噌は`LPARAM`を`&HOGE`にキャストするところです。`l_param as &HOGE`と書くとこれもコンパイラに怒られます。一度生ポインタを Dereference して参照を取る`&*(l_param as *const HOGE)`という書き方になります。

```rust:main.rs
unsafe extern "system" fn enum_proc(hwnd: HWND, l_param: LPARAM) -> BOOL {
    let mut buf = [0u16; 1024];
    if GetWindowTextW(hwnd, &mut buf[0], 1024) > 0 {
        let win_text = decode(&buf);
        let hoge = &*(l_param as *const HOGE) as &HOGE;

        if hoge.win_names.iter().any(|n| win_text.contains(n)) {
            SetForegroundWindow(hwnd);
            SetFocus(hwnd);

            key_down(VK_CONTROL as u16);
            key_enter(0x46); // f
            key_up(VK_CONTROL as u16);
            return FALSE;
        }
    }
    TRUE
}
```

続いて、キーイベントを発生させる処理です。`INPUT`構造体を`std::mem::zeroed()`で初期化します。私は union の`KEYBDINPUT`を Rust どうやって扱うのかでちょっと悩みました。

winapi crate の github の FAQ でも一番最初に出てくるので、みんな同じところで引っ掛かるのでしょうか。FAQ には「varient method で値を割り当てろ」と書いてますがよく分かりません。

winapi の doc.rs で[`INPUT`構造体を調べてみる](https://docs.rs/winapi/*/x86_64-pc-windows-msvc/winapi/um/winuser/struct.INPUT.html)と`INPUT_u`構造体に`ki_mut()`という関数があるのが分かります。これを使うと`KEYBDINPUT`構造体へのミュータブルな参照が取得できるわけですね。これ`let mut ki = input.u.ki_mut();`です。スマートではないですね。

```rust:main.rs
unsafe fn create_input(key_code: u16, flags: u32) -> INPUT {
    let mut input = mem::zeroed::<INPUT>();
    input.type_ = INPUT_KEYBOARD;
    let mut ki = input.u.ki_mut();
    ki.wVk = key_code;
    ki.dwFlags = flags;
    input
}

unsafe fn key_down(key_code: u16) {
    let mut input = create_input(key_code, 0);
    SendInput(1, &mut input, mem::size_of::<INPUT>() as i32);
}

unsafe fn key_up(key_code: u16) {
    let mut input = create_input(key_code, KEYEVENTF_KEYUP);
    SendInput(1, &mut input, mem::size_of::<INPUT>() as i32);
}

unsafe fn key_enter(key_code: u16) {
    key_down(key_code);
    key_up(key_code);
}
```

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

```rust:main.rs
use winapi::{
    um::winuser::{EnumWindows, GetWindowTextW, SendInput, INPUT, INPUT_KEYBOARD,
                  KEYEVENTF_KEYUP, VK_CONTROL, SetForegroundWindow, SetFocus},
    shared::{
        windef::{HWND},
        minwindef::{LPARAM, BOOL, TRUE, FALSE}
    },
};
use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
use std::mem;

struct HOGE {
    win_names: Vec<String>,
}

fn main() {
    unsafe {
        let win_names = vec!("Mozilla Firefox".into(), "Microsoft Edge".into(), "Google Chrome".into());
        let hoge = HOGE { win_names };
        EnumWindows(Some(enum_proc), (&hoge as *const HOGE) as LPARAM);
    }
}

unsafe extern "system" fn enum_proc(hwnd: HWND, l_param: LPARAM) -> BOOL {
    let mut buf = [0u16; 1024];
    if GetWindowTextW(hwnd, &mut buf[0], 1024) > 0 {
        let win_text = decode(&buf);
        let hoge = &*(l_param as *const HOGE) as &HOGE;

        if hoge.win_names.iter().any(|n| win_text.contains(n)) {
            SetForegroundWindow(hwnd);
            SetFocus(hwnd);

            key_down(VK_CONTROL as u16);
            key_enter(0x46);
            key_up(VK_CONTROL as u16);
            return FALSE;
        }
    }
    TRUE
}

fn decode(source: &[u16]) -> String {
    decode_utf16(source.iter().take_while(|&i| *i != 0).cloned())
        .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
        .collect()
}

unsafe fn create_input(key_code: u16, flags: u32) -> INPUT {
    let mut input = mem::zeroed::<INPUT>();
    input.type_ = INPUT_KEYBOARD;
    let mut ki = input.u.ki_mut();
    ki.wVk = key_code;
    ki.dwFlags = flags;
    input
}

unsafe fn key_down(key_code: u16) {
    let mut input = create_input(key_code, 0);
    SendInput(1, &mut input, mem::size_of::<INPUT>() as i32);
}

unsafe fn key_up(key_code: u16) {
    let mut input = create_input(key_code, KEYEVENTF_KEYUP);
    SendInput(1, &mut input, mem::size_of::<INPUT>() as i32);
}

unsafe fn key_enter(key_code: u16) {
    key_down(key_code);
    key_up(key_code);
}
```

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