動機
Caps Lock キーを物理的に無効化している人がいました。私はソフトウェア的に強制有効化してみようと思いました。
↓ Shift + Caps Lock キーで解除してもすぐに有効になる様子
環境
Windows 10 22H2
rustc 1.88.0
windows crate 0.61.3
実装
短いので全部載せます。
コピペ用サンプルコード
[package]
name = "caps_lock_lock"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0"
[dependencies.windows]
version = "0.61"
features = [
"Win32_Graphics_Gdi",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_WindowsAndMessaging",
]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use anyhow::Result;
use windows::{
Win32::{
Foundation::{HWND, LPARAM, LRESULT, WPARAM},
UI::{
Input::KeyboardAndMouse::{
GetKeyState, INPUT, INPUT_KEYBOARD, KEYEVENTF_KEYUP, SendInput, VK_CAPITAL,
VK_LSHIFT,
},
WindowsAndMessaging::{
CW_USEDEFAULT, CreateWindowExW, DefWindowProcW, DispatchMessageW, GetMessageW,
KillTimer, MSG, PostQuitMessage, RegisterClassW, SW_SHOW, SetTimer, ShowWindow,
TranslateMessage, WINDOW_EX_STYLE, WM_DESTROY, WM_TIMER, WNDCLASSW,
WS_OVERLAPPEDWINDOW,
},
},
},
core::{PCWSTR, w},
};
const CLASS_NAME: PCWSTR = w!("caps_lock_lock_win_class_name");
const ID_TIMER: usize = 42;
struct Timer(HWND);
impl Timer {
fn new(hwnd: HWND, elapse_millisec: u32) -> Self {
unsafe { SetTimer(Some(hwnd), ID_TIMER, elapse_millisec, None) };
Self(hwnd)
}
}
impl Drop for Timer {
fn drop(&mut self) {
_ = unsafe { KillTimer(Some(self.0), ID_TIMER) };
}
}
unsafe extern "system" fn wnd_proc(hwnd: HWND, msg: u32, wp: WPARAM, lp: LPARAM) -> LRESULT {
match msg {
WM_TIMER if wp.0 == ID_TIMER => {
if unsafe { GetKeyState(VK_CAPITAL.0 as _) } & 0x0001 == 0 {
let mut inputs = [INPUT::default(); 4];
inputs[0].r#type = INPUT_KEYBOARD;
inputs[0].Anonymous.ki.wVk = VK_LSHIFT;
inputs[1].r#type = INPUT_KEYBOARD;
inputs[1].Anonymous.ki.wVk = VK_CAPITAL;
inputs[2].r#type = INPUT_KEYBOARD;
inputs[2].Anonymous.ki.wVk = VK_CAPITAL;
inputs[2].Anonymous.ki.dwFlags = KEYEVENTF_KEYUP;
inputs[3].r#type = INPUT_KEYBOARD;
inputs[3].Anonymous.ki.wVk = VK_LSHIFT;
inputs[3].Anonymous.ki.dwFlags = KEYEVENTF_KEYUP;
unsafe { SendInput(&inputs, size_of::<INPUT>() as _) };
}
}
WM_DESTROY => unsafe {
PostQuitMessage(0);
},
_ => return unsafe { DefWindowProcW(hwnd, msg, wp, lp) },
}
LRESULT::default()
}
fn main() -> Result<()> {
let wc = WNDCLASSW {
lpfnWndProc: Some(wnd_proc),
lpszClassName: CLASS_NAME,
..Default::default()
};
unsafe { RegisterClassW(&wc) };
let hwnd = unsafe {
CreateWindowExW(
WINDOW_EX_STYLE::default(),
CLASS_NAME,
w!("caps_lock_lock"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
200,
200,
None,
None,
None,
None,
)?
};
#[cfg(debug_assertions)]
unsafe {
_ = ShowWindow(hwnd, SW_SHOW);
}
let _timer = Timer::new(hwnd, 1000);
let mut msg = MSG::default();
loop {
if !unsafe { GetMessageW(&mut msg, None, 0, 0).as_bool() } {
break;
}
unsafe {
_ = TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
Ok(())
}
実装の解説
コンソールウィンドウの抑止
リリースビルド時にコンソールウィンドウが表示されないようにします。
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
隠しウィンドウの生成
main
関数でウィンドウを生成します。ShowWindow
を呼ばないのでウィンドウは表示されず、タスクバーにも表示されません。プログラムを終了するにはタスクマネージャ等から強制終了する必要があります。
fn main() -> Result<()> {
let wc = WNDCLASSW {
lpfnWndProc: Some(wnd_proc),
lpszClassName: CLASS_NAME,
..Default::default()
};
unsafe { RegisterClassW(&wc) };
// ウィンドウの生成
let hwnd = unsafe {
CreateWindowExW(
WINDOW_EX_STYLE::default(),
CLASS_NAME,
w!("caps_lock_lock"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
200,
200,
None,
None,
None,
None,
)?
};
// リリースビルド時には ShowWindow は呼ばれない
#[cfg(debug_assertions)]
unsafe {
_ = ShowWindow(hwnd, SW_SHOW);
}
// Timer を設定する(後述)
let _timer = Timer::new(hwnd, 1000);
// メッセージループ
let mut msg = MSG::default();
loop {
if !unsafe { GetMessageW(&mut msg, None, 0, 0).as_bool() } {
break;
}
unsafe {
_ = TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
Ok(())
}
タイマーの設定
Caps Lock が有効になっているかどうかのチェックですが、今回は Windows のタイマーを使用して 1 秒ごとにキーの状態を確認します。SetTimer
関数でタイマーを設定してKillTimer
関数でタイマーを解除します。解除忘れがないようにTimer
構造体を作ってスコープを抜けるときに自動的にタイマーが解除されるようにしておきます。(今回のような小さなプログラムではあまり恩恵は感じられませんが、リソースの解放忘れを防ぐ目的で積極的に使使っていきたいテクニックだと思います)
struct Timer(HWND);
impl Timer {
fn new(hwnd: HWND, elapse_millisec: u32) -> Self {
unsafe { SetTimer(Some(hwnd), ID_TIMER, elapse_millisec, None) };
Self(hwnd)
}
}
impl Drop for Timer {
fn drop(&mut self) {
_ = unsafe { KillTimer(Some(self.0), ID_TIMER) };
}
}
fn main() -> Result<()> {
// 省略
let _timer = Timer::new(hwnd, 1000); // _timer 変数に代入する必要あり
// 省略
}
main
関数内で_timer
変数に代入していますがこのように記述しておく必要があります。下記のようにTimer::new
だけだとすぐにKillTimer
が呼ばれてタイマーが解除されてしまいます。
fn main() -> Result<()> {
// 省略
Timer::new(hwnd, 1000); // 変数に代入しないと、ここですぐに drop して KillTimer が呼ばれてしまう
// 省略
}
ウィンドウプロシージャ
最後にウィンドウプロシージャの説明です。1 秒ごとにGetKeyState
関数で Caps Lock の状態を確認します。 OFF だったらSetInput
関数で Shift + Caps Lock を送信して Caps Lock を ON にしています。SetInput
関数の第二引数が配列全体のサイズではなくて、INPUT
構造体のサイズであることに留意してください。(これに気付かなくて時間を溶かしました)
unsafe extern "system" fn wnd_proc(hwnd: HWND, msg: u32, wp: WPARAM, lp: LPARAM) -> LRESULT {
match msg {
WM_TIMER if wp.0 == ID_TIMER => {
// Caps Lock が OFF の場合
if unsafe { GetKeyState(VK_CAPITAL.0 as _) } & 0x0001 == 0 {
let mut inputs = [INPUT::default(); 4];
// Shift キー押下
inputs[0].r#type = INPUT_KEYBOARD;
inputs[0].Anonymous.ki.wVk = VK_LSHIFT;
// Caps Lock キー押下
inputs[1].r#type = INPUT_KEYBOARD;
inputs[1].Anonymous.ki.wVk = VK_CAPITAL;
// Caps Lock キー離上
inputs[2].r#type = INPUT_KEYBOARD;
inputs[2].Anonymous.ki.wVk = VK_CAPITAL;
inputs[2].Anonymous.ki.dwFlags = KEYEVENTF_KEYUP;
// Shift キー離上
inputs[3].r#type = INPUT_KEYBOARD;
inputs[3].Anonymous.ki.wVk = VK_LSHIFT;
inputs[3].Anonymous.ki.dwFlags = KEYEVENTF_KEYUP;
// 第二引数は INPUT 構造体のサイズ
unsafe { SendInput(&inputs, size_of::<INPUT>() as _) };
}
}
WM_DESTROY => unsafe {
PostQuitMessage(0);
},
_ => return unsafe { DefWindowProcW(hwnd, msg, wp, lp) },
}
LRESULT::default()
}
まとめ
これは私の思考実験です。同僚の PC にいたずらしないでね