環境
toolchain: stable-i686-pc-windows-msvc
rustc 1.32.0 (9fda7c223 2019-01-16)
winapi 0.3.6
ブラウザにキーボードイベントを送ってみる
前回 EnumWindows でウィンドウタイトルを列挙したので、任意のブラウザウィンドウをアクティブにしてキーボードイベントを送ってみます。うまく利用すると作業の自動化もできますね。
Cargo.toml は EnumWindows 編から変更はありません。
[dependencies.winapi]
version = "0.3"
features = ["winuser"]
まずは使用する module を書いていきます。
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
にキャストしています。
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)
という書き方になります。
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();
です。スマートではないですね。
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 をまとめてどうぞ。
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編