2007 年 1 月 6 日 23 時 52 分

KeyEvent メッセージ


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


今日は KeyEvent メッセージだ。
これは種類 4 番のクライアントメッセージで、
クライアント上でキー入力が発生した際に送られる。

RFB が取り扱うキー入力は低水準と高水準の混合である。
低水準というのは、「A の刻印のあるキー」を「押す」とか、
「離す」とかいった情報のことを示す。
高水準というのは、「A」という文字とか、
「Tab」という文字といった情報のことを示す。

低水準は、キーボードの特定の「キーの動作」という意味で、
高水準では、入力の結果生じた「文字」の入力という意味だ。

例えば、Ctrl + C を 1 回入力する場合、
低水準では以下のような 4 個の入力イベントが発生する。

    1. 「Ctrl キーを押す」
    2. 「C キーを押す」
    3. 「C キーを離す」
    4. 「Ctrl キーを離す」

これは高水準では「Ctrl + C」という特殊文字 1 つとなる。

もし、C キーを押し続けた場合は、
低水準ではキーリピートの設定に従って、
3 が発生する前に 2 のイベントが複数回発生する。
高水準では、単に「Ctrl + C」が複数回発生するだけだ。

RFB では、基本的には印字可能文字は高水準で扱われる。
つまり、a が入力されれば a の ASCII コード、
A が入力されれば A の ASCII コードが渡される。

しかし、矢印や Ctrl, Shift などの特殊キーは、
押(離)されたキーの種類がコードとして低水準で渡される。

さて、このメッセージは 8 バイト固定長で、
以下のような構造を持つ。

    U8 messageType; // 常に 4
    U8 down; // 0 ならキーを離した、0 以外なら押した
    U8 reserved[2]; // 2 バイトの詰め物
    U32 code; // 仮想キーコード

down は、キーの押下状態がどう変化したかどうかだ。
0 以外なら、キーが押されたか、
キーリピートが発生したかのどちらかであり、
結果的にキーが押された状態になっている。
逆に 0 なら、キーが離されたということだ。
この値は、低水準の入力でないと意味がない。

code は低水準の場合は仮想キーコード、
高水準の場合は文字コードである。
定義されている仮想キーコードは
0xffnn の範囲の値になっているので、
それによって水準を区別する事ができる。

キーボードにはいくつもの種類があり、
物によってキー配列が異なっている。
そのため、どのキーボードでも同じ役割を持つキーには、
同じ「コード」を割り当てて識別可能としている。
そのコードのことを、仮想キーコードという。

例えば RFB では BackSpace キーは 0xff08 というコードだ。
これは、X Window System を基準としているため、
Windows では、Windows の仮想キーコードから、
RFB への仮想キーコードへとマッピングする必要がある。

では、最初に仮想キーを表す定数を定義しよう。
これも本来は Enum が理想だが、int 型の定数のみを持つ
インスタンス化できないクラスで定義することにしよう。

=========== KeyCode.java ===========

package jp.loafer.rfb;

/**
* 仮想キーコード定数。
* @author kes
*/
public final class KeyCode {

    /**
     * Backspace/Bksp キー。
     */
    public static final int BACK_SPACE = 0xff08;
    /**
     * Tab キー。
     */
    public static final int TAB = 0xff09;
    /**
     * Return/Enter キー。
     */
    public static final int RETURN = 0xff0d;
    /**
     * Esc キー。
     */
    public static final int ESCAPE = 0xff1b;
   
    /**
     * HOME キー。
     */
    public static final int HOME = 0xff50;
    /**
     * 左矢印キー。
     */
    public static final int LEFT = 0xff51;
    /**
     * 上矢印キー。
     */
    public static final int UP = 0xff52;
    /**
     * 右矢印キー。
     */
    public static final int RIGHT = 0xff53;
    /**
     * 下矢印キー。
     */
    public static final int DOWN = 0xff54;
    /**
     * Page Down キー。
     */
    public static final int PAGE_DOWN = 0xff56;
    /**
     * Page Up キー。
     */
    public static final int PAGE_UP = 0xff55;
    /**
     * End キー。
     */
    public static final int END = 0xff57;

    /**
     * Insert/Ins キー。
     */
    public static final int INSERT = 0xff63;
   
    /**
     * F1 キー。
     */
    public static final int F1 = 0xffbe;
    /**
     * F2 キー。
     */
    public static final int F2 = 0xffbf;
    /**
     * F3 キー。
     */
    public static final int F3 = 0xffc0;
    /**
     * F4 キー。
     */
    public static final int F4 = 0xffc1;
    /**
     * F5 キー。
     */
    public static final int F5 = 0xffc2;
    /**
     * F6 キー。
     */
    public static final int F6 = 0xffc3;
    /**
     * F7 キー。
     */
    public static final int F7 = 0xffc4;
    /**
     * F8 キー。
     */
    public static final int F8 = 0xffc5;
    /**
     * F9 キー。
     */
    public static final int F9 = 0xffc6;
    /**
     * F10 キー。
     */
    public static final int F10 = 0xffc7;
    /**
     * F11 キー。
     */
    public static final int F11 = 0xffc8;
    /**
     * F12 キー。
     */
    public static final int F12 = 0xffc9;

    /**
     * 左側の Shift キー。
     */
    public static final int LEFT_SHIFT = 0xffe1;
    /**
     * 右側の Shift キー。
     */
    public static final int RIGHT_SHIFT = 0xffe2;
    /**
     * 左側の Ctrl キー。
     */
    public static final int LEFT_CONTROL = 0xffe3;
    /**
     * 右側の Ctrl キー。
     */
    public static final int RIGHT_CONTROL = 0xffe4;

    /**
     * 左側の Alt キー。
     */
    public static final int LEFT_ALT = 0xffe9;
    /**
     * 右側の Alt キー。
     */
    public static final int RIGHT_ALT = 0xffea;
   
    /**
     * Delete/Del キー
     */
    public static final int DELETE = 0xffff;

   
    private KeyCode() {
        // インスタンスは不要
    }

}

=========== end of KeyCode.java ===========

いや~長くなった。

では、これを元に KeyEvent メッセージのクラスを作ろう。

========== KeyEventMessage.java ==========

package jp.loafer.rfb.message.client;

import java.io.IOException;

import jp.loafer.rfb.KeyCode;
import jp.loafer.rfb.RFBContext;
import jp.loafer.rfb.io.RFBInputStream;
import jp.loafer.rfb.io.RFBOutputStream;

/**
* KeyEvent クライアントメッセージ。
* @author kes
*/
public class KeyEventMessage extends BaseClientMessage {

    /**
     * 永続化用既定コンストラクタ。
     */
    public KeyEventMessage() {
        //
    }

    /**
     * KeyEvent メッセージを作成。
     * @param down キーが押されたかどうか。
     * @param code 仮想キーコード。
     */
    public KeyEventMessage(boolean down, int code) {
        this.down = down;
        this.code = code;
    }

    /**
     * down を取得。
     * @return down。
     */
    public boolean isDown() {
        return down;
    }

    /**
     * キーが押されたかどうかを取得。
    * @return キーが押されたなら true。
    */
    public int getCode() {
        return code;
    }

    /**
     * @see ClientMessage#getType()
     */
    public int getType() {
        return ClientMessage.KEY_EVENT;
    }

    /**
     * @see ClientMessage#read(RFBContext, RFBInputStream)
     */
    @Override
    public void read(RFBContext context, RFBInputStream in) throws IOException {
        super.read(context, in);
        down = in.readU8() != 0;
        in.readU8();
        in.readU8();
        code = in.readS32();
    }

    /**
     * @see ClientMessage#write(RFBContext, RFBOutputStream)
     */
    @Override
    public void write(RFBContext context, RFBOutputStream out) throws IOException {
        super.write(context, out);
        out.writeU8(down ? 1 : 0);
        out.writeU16(0);
        out.writeU32(code);
    }

    private boolean down;
    private int code;
   
}

========== end of KeyEventMessage.java ==========

サーバが低水準のメッセージを受け取った場合、
現在のキー(ボード)の押下状態を個別に管理し、
このメッセージを受け取るとそれを更新し、
状態を常に記憶する必要があるというわけだ。

サーバが高水準のメッセージを受け取った場合、
内部的に低水準の動作をエミュレートし、
キーボードの管理状態に反映する必要がある。

本来、特に Num Lock や Caps Lock、Scroll Lock 等は、
その状態によって他のキー入力に影響を与えるので、
ちゃんと管理しておかなければならないのだが、
RFB では文字入力は高水準で送られるため、
サーバ側の Num Lock や Caps Lock の状態と、
実際に送られてくる文字コードが一致しない事がある。

まあ、この点は非常にややこしいポイントである。
実際に文字入力を処理する機会があれば、
そのときにテストしてみよう。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.