マウスイベント Xにおけるマウスイベントは3種類ある。 * ButtonPress: ウィンドウ内でボタンが押された * ButtonRelease: ウィンドウ内でボタンが離された * MotionNotify: ウィンドウ内でマウスポインタが移動した Xには「ダブルクリック」イベントは存在しない。そのため、ダブルクリックを検知したければイベントのタイムスタンプを使って自前で判定する必要がある(GTKとかのツールキットを使うなら、ツールキットがこの辺をやってくれる)。 == 他所のウィンドウでのマウスイベントの処理 == 自分で作成したウィンドウについては上記の3イベントを監視すればよいが、そうでないウィンドウについては同じ方法を取ることはできない。というのも {{{ Only one client at a time can select a ButtonPress event, which is associated with the event mask ButtonPressMask. (Xlib - C Language Interface 11.1 Selecting Events より) }}} となっていて<>、基本的に自分が作成したのでないウィンドウについてButtonPressを検知することは不可能と考えられるからである(ウィンドウを作成したクライアント自身がselectする可能性が非常に高い)。 * 例外として、ルートウィンドウについてはWMが自由にできると考えて良さそう 大抵の操作はボタンを押す所から始まるので、これが検知できないとほとんど何もできない。 このままではウィンドウを直接ドラッグして移動やリサイズを行う操作は不可能になってしまう<>が、幸いXにはグラブ(Grab)という機能が用意されている。これは要するにイベントの横取りで、マウスをグラブすると、グラブしている間マウスイベントが自分の所にやってくるようになる。グラブし続けている限り他所にはイベントが届かなくなるので、必要でなくなったら直ちにグラブを終了しなければならない。 グラブすることができるのはキーボードかマウスで、さらに2種類に分かれる。 * アクティブなグラブ: 自分で明示的にグラブを開始する * `xcb_grab_keyboard()`とか`xcb_grab_pointer()`とか * 上記のリクエストが実行された時点からグラブが始まり、グラブを終了するまでイベントが届く * パッシブなグラブ: 特定のキー/ボタンの組み合わせを指定しておき、それが実際に押されたときにグラブを開始する * `xcb_grab_key()`とか`xcb_grab_button()`とか * あらかじめ上記の関数によってグラブするキー/ボタンを登録しておく * 指定した組み合わせが押されるとKeyPressとかButtonPressが届く ウィンドウを直接ドラッグして移動する操作は次のようにして実装できる。 1. 対象ウィンドウにパッシブグラブを仕掛けておく 1. 操作が行われるとButtonPressが届く 1. アクティブグラブを開始し、届いてくるMotionNotifyを用いて移動処理を行う 1. 移動が終了したら(ボタンを離す=ButtonReleaseイベント)、グラブを終了する == MotionNotifyの圧縮 == MotionNotifyイベントはマウスポインタがウィンドウ上で移動したときに届く。MotionNotifyは1ピクセルでもマウスポインタが動けば発行されるので、実際には大量のMotionNotifyが連続して届くことになる。<>これら一つ一つについてウィンドウの移動などを行うとCPUにかなりの負荷を掛けることになる(特にリサイズ時は毎回再描画が発生するので描画が追いつかなくなりがち)。 そこで、連続したMotionNotifyの大部分を捨てるなどして圧縮する方法がよく用いられている<>。Xlibだと割と簡単に書ける(`XCheckTypedEvent()`でイベントキュー内のMotionNotifyだけを拾って片っ端から捨てれば良い)が、XCBだとイベントはキューの先頭から取り出すことしかできない<>ので少し面倒になる。 awesomeはイベントループにlibevを使ってあれこれしている<>。 == MotionNotify Hint == 大量のMotionNotifyが要らない、という場合は監視対象のイベントマスクにPointerMotionMaskと共にPointerMotionHintMaskをORしておく<>とMotionNotifyの届き方が変わり、以下の条件を満たすまでの間に一つだけのMotionNotifyが届くようになる。 * キーやマウスボタンの状態が変わる * マウスポインタが対象ウィンドウから出る * クライアントがQueryPointerかGetMotionEventのいずれかのリクエストを送信する このとき届くMotionNotifyはあくまで「動いた」ことを表すだけなので、実際のマウスポインタ位置はクライアント側でリクエストを送って取得する必要がある。 * QueryPointer: 現在位置の取得 * GetMotionEvents: 指定したタイムスタンプ間の移動履歴を取得する ただし、ヒントでない真のMotionNotifyが届く可能性もある<>。どのようにするかはXサーバの自由、ということになっている。 ヒントが届いたタイミングでQueryPointerするとそれによって次のヒントが届く条件が満たされるので、結局ヒントを使わない場合とあまり変わらない気がする…のだが違いはあるのだろうか。むしろ、QueryPointerリクエストが行って帰ってくる分負荷が増える気もする。