8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-02-03

環境

toolchain: stable-i686-pc-windows-msvc
rustc 1.32.0 (9fda7c223 2019-01-16)
winapi 0.3.6

ブラウザにキーボードイベントを送ってみる

前回 EnumWindows でウィンドウタイトルを列挙したので、任意のブラウザウィンドウをアクティブにしてキーボードイベントを送ってみます。うまく利用すると作業の自動化もできますね。

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

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

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

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にキャストしています。

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)という書き方になります。

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構造体を調べてみるINPUT_u構造体にki_mut()という関数があるのが分かります。これを使うとKEYBDINPUT構造体へのミュータブルな参照が取得できるわけですね。これlet mut ki = input.u.ki_mut();です。スマートではないですね。

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 をまとめてどうぞ。

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編
Rust で Windows プログラミング - CreateWindow編
Rust で Windows プログラミング - Shell_NotifyIcon編
Rust で Windows プログラミング - EnumWindows編

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?