⌨️

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つの重要な概念があります。

  1. ModMask (修飾キーの状態)**
    Shift、Control、Altなどの修飾キーをビットマスクで表現します。例えば、Altは Mod1Mask = 0x0008 として表現されます。ここで重要なポイントは、Alt_LもAlt_Rもどちらも同じMod1Maskとして扱われる ということです。

  2. KeyCode (物理キーの識別子)
    物理的なキーを表す番号です。このレベルでは、Alt_L (64) とAlt_R (108) は異なる値で個別に扱われます。

例えば、キーコンビネーション (Alt+m) を入力した場合、X11のキーイベントには以下の情報が含まれます。

  1. Alt_LのKeyPressイベント → Alt_LのKeyCode (64) を含む
  2. 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キーを区別します。

  1. X11からKeyPressイベント (Alt+mキー) を受信
  2. query_keymap() でその瞬間のキーボード全体の状態を取得
  3. Alt_L (KeyCode 64) とAlt_R (KeyCode 108) のビットをチェック
  4. どちらが押されているかに応じて異なるアプリケーションを起動
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 = 8
  • bit_position = 64 % 8 = 0
  • keys[8]の0ビット目をチェック

この方法で、任意のKeyCodeの押下状態を確認できます。

まとめ

X11で左右のAltキーを区別する方法として、QueryKeymapリクエストを使うアプローチを紹介しました。

キーイベントだけではModMaskレベルの情報しか得られませんが、query_keymap()を使えばKeyCodeレベルで全キーのPress/Release状態を取得できるため、左右の修飾キーを個別に扱うことができます。

今回紹介したQueryKeymap以外にも、以下のような方法が考えられます。

  1. アプリケーション側での状態管理
    Alt_LとAlt_RのKeyPressイベントを監視し、アプリケーション側で「どちらのAltが現在押されているか」を状態として保持する方法です。状態の不整合が起きないように、上手く管理する必要があります。

  2. xmodmapで別Modifierにマッピング
    xmodmapなどのツールを使って、Alt_LとAlt_Rを別々のModifierにマッピングする方法です。この場合、query_keymap()を使わずにModMaskだけで個別に扱えます。しかし、システム全体の設定を変更するため影響範囲が大きくなります。

QueryKeymapのアプローチは、アプリケーション単体で完結し、状態管理の複雑さもありません。特定キーイベントの度にリクエストするコストはかかりますが、実用上のパフォーマンス問題はなかったため、このアプローチを採用しました。

他のウィンドウマネージャーでは、左右の修飾キーの区別をどのように実装しているのか、気になるところです。

参考


この記事が役立ったら、LIKEやコメントで教えてください!

他の技術記事や開発記録は私のブログでも公開しています。

Discussion