X11で左右のAltキーを区別する方法
この記事は個人ブログで公開した内容を再編集したものです。
この記事で分かること
- X11でAlt_LとAlt_Rを個別に扱う方法 (QueryKeymapを使った実装)
- X11におけるキーイベントの基礎 (ModMask/KeyCodeの仕組み)
- 実装例: Rust + x11rbでのコード例とビット演算の使い方
左右のAltキーを区別してショートカットを設定したい
X11でウィンドウマネージャーを開発する際、左右のAltキー (Alt_L、Alt_R) を区別してショートカットを設定する機能を実装したいケースがあると思います。例えば、「Alt_L+mでブラウザを起動して、「Alt_R+m」でターミナルを起動できるようにする、といったケースです。しかし、X11ではキーコンビネーション (Alt+m) のキーイベントだけでは、左右のAltを区別できないという問題があります。
そこで、この記事では、この問題を解決する方法としてQueryKeymapを使った解決方法を紹介します。
QueryKeymapを使ってキー状態を取得する
X11で左右のAltキーを区別する一つの方法として、QueryKeymapリクエスト(Rustのx11rbではquery_keymap()関数)を使うアプローチがあります。
これは、全キーのPress/Release状態を32バイトのビットベクトルで取得できるもので、各ビットが1つのKeyCodeに対応しています。
下記のように、Alt_L (通常KeyCode 64) と Alt_R (通常KeyCode 108) のビットを個別にチェックすることで区別が可能になります。
// Rustとx11rbを使った実装例
let reply = conn.query_keymap()?.reply()?;
let keys = reply.keys; // 32バイトのビットベクトル
// Alt_LのKeyCode (通常64) とAlt_RのKeyCode (通常108) のビットをチェック
let alt_l_pressed = is_key_pressed(&keys, 64);
let alt_r_pressed = is_key_pressed(&keys, 108);
X11のキーイベントを理解する
なぜQueryKeymapが必要なのかを理解するために、X11のキーイベントの仕組みを見ていきます。
X11では、キーボード入力を扱う際に2つの重要な概念があります。
-
ModMask (修飾キーの状態)**
Shift、Control、Altなどの修飾キーをビットマスクで表現します。例えば、AltはMod1Mask = 0x0008として表現されます。ここで重要なポイントは、Alt_LもAlt_Rもどちらも同じMod1Maskとして扱われる ということです。 -
KeyCode (物理キーの識別子)
物理的なキーを表す番号です。このレベルでは、Alt_L (64) とAlt_R (108) は異なる値で個別に扱われます。
例えば、キーコンビネーション (Alt+m) を入力した場合、X11のキーイベントには以下の情報が含まれます。
- Alt_LのKeyPressイベント → Alt_LのKeyCode (64) を含む
- m (+Alt) のKeyPressイベント → mのKeyCode (58) とModMask情報のみ
重要なのは、2番目のイベント (mのKeyPress) には、Alt_LのKeyCode (64) は含まれないということです。含まれるのは「Altが押されている」というModMaskの情報だけです。
この仕組みから、キーコンビネーションのキーイベント単体では左右のAltを個別に扱えないことがわかります。しかし、QueryKeymapを使えば全キーの状態を取得できるため、この制約を回避できます。
QueryKeymapの仕組みと使用例
QueryKeymapとは
X11 Protocolには、現在押されている全てのキーの状態を取得するためのQueryKeymapリクエストがあります (X11 Protocol: QueryKeymap)。
以下では、Rustのx11rbライブラリにあるquery_keymap()関数を使って、X11アプリケーション (WindowManager) 側で左右のAltキーを判定する方法について書きます。
WindowManagerでの実装例
以下のような流れで左右のAltキーを区別します。
- X11からKeyPressイベント (Alt+mキー) を受信
-
query_keymap()でその瞬間のキーボード全体の状態を取得 - Alt_L (KeyCode 64) とAlt_R (KeyCode 108) のビットをチェック
- どちらが押されているかに応じて異なるアプリケーションを起動
fn handle_key_press(
conn: &impl Connection,
event: KeyPressEvent,
alt_l_keycode: u8,
alt_r_keycode: u8,
) -> Result<(), Box<dyn std::error::Error>> {
// mキー (KeyCode 58) が押された場合
if event.detail == 58 {
// その瞬間のキーボード状態を取得
let reply = conn.query_keymap()?.reply()?;
// Alt_LまたはAlt_Rが押されているかチェック
if is_key_pressed(&reply.keys, alt_l_keycode) {
// 左Alt + m → Chromeを起動
std::process::Command::new("google-chrome").spawn()?;
} else if is_key_pressed(&reply.keys, alt_r_keycode) {
// 右Alt + m → Terminalを起動
std::process::Command::new("terminal").spawn()?;
}
}
Ok(())
}
// KeyCodeに対応するビットをチェック
fn is_key_pressed(keys: &[u8; 32], keycode: u8) -> bool {
let byte_index = (keycode / 8) as usize;
let bit_position = keycode % 8;
(keys[byte_index] & (1 << bit_position)) != 0
}
ビットチェックの仕組み
is_key_pressed関数では、以下の計算でKeyCodeに対応するビットをチェックしています。
let byte_index = (keycode / 8) as usize; // どのバイトか
let bit_position = keycode % 8; // バイト内の何ビット目か
(keys[byte_index] & (1 << bit_position)) != 0 // ビットが立っているか
例えば、Alt_L (KeyCode 64) の場合:
byte_index = 64 / 8 = 8bit_position = 64 % 8 = 0-
keys[8]の0ビット目をチェック
この方法で、任意のKeyCodeの押下状態を確認できます。
まとめ
X11で左右のAltキーを区別する方法として、QueryKeymapリクエストを使うアプローチを紹介しました。
キーイベントだけではModMaskレベルの情報しか得られませんが、query_keymap()を使えばKeyCodeレベルで全キーのPress/Release状態を取得できるため、左右の修飾キーを個別に扱うことができます。
今回紹介したQueryKeymap以外にも、以下のような方法が考えられます。
-
アプリケーション側での状態管理
Alt_LとAlt_RのKeyPressイベントを監視し、アプリケーション側で「どちらのAltが現在押されているか」を状態として保持する方法です。状態の不整合が起きないように、上手く管理する必要があります。 -
xmodmapで別Modifierにマッピング
xmodmapなどのツールを使って、Alt_LとAlt_Rを別々のModifierにマッピングする方法です。この場合、query_keymap()を使わずにModMaskだけで個別に扱えます。しかし、システム全体の設定を変更するため影響範囲が大きくなります。
QueryKeymapのアプローチは、アプリケーション単体で完結し、状態管理の複雑さもありません。特定キーイベントの度にリクエストするコストはかかりますが、実用上のパフォーマンス問題はなかったため、このアプローチを採用しました。
他のウィンドウマネージャーでは、左右の修飾キーの区別をどのように実装しているのか、気になるところです。
参考
この記事が役立ったら、LIKEやコメントで教えてください!
他の技術記事や開発記録は私のブログでも公開しています。
Discussion