画面サイズを取得する話
ウィンドウ管理を行うには画面の大きさ(画面解像度)が分からないと色々と困る1ので、これを取得する必要がある。調べてみると面倒だったのでメモ。
用語
すごく混乱しやすい。
- display
screenの集合。プログラミング上は「Xサーバとの接続」的な意味もある。とにかく「ただの画面」ではない
- screen
- 物理的なモニタ(拡張により、必ずしも1つの物理モニタとは限らない。後述)
- ルートウィンドウ
- Xにおけるウィンドウは階層構造を持っており、頂点に位置するのがこれ。screenに対して1つ存在する
基本的にはdisplayとscreenは1対nの関係にある。
display - screen0 - 画面 \ screen1 - 画面 ルートウィンドウは2つ。この場合、screen間(つまりは画面間)でウィンドウを移動することはできない。
Xlibのドキュメントには、displayの説明として次のように書いてある。
A set of screens for a single user with one keyboard and one pointer (usually a mouse) is called a display. (1.1節より)
ただし、XineramaやXRandRなどの拡張は複数のディスプレイ(上の意味ではなく普通の意味での)を1つのscreenとして扱えるようにする。現在はこちらが一般的。
display - screen0 - 画面 \ 画面 ルートウィンドウは1つになり、画面をまたぐようなウィンドウの移動が可能になる。
拡張について
XineramaとXRandRについて簡単にまとめる。いずれも複数のディスプレイを1つのscreenとして扱えるようにする点では同じ。XRandRの方が後発。
Xinerama
xorg.confで設定する。(静的な設定)
- ラップトップのような、抜き差しが起こる状況では色々と問題が起こる
APIが非常に単純なので、manとヘッダを読めばすぐに使い方が分かる。逆に言えば細かいことはできない。
XRandR
こちらはxrandrコマンドを使って動的に設定を行える。
- RandRとはResize and Rotationの略で、初めから動的な設定変更を想定している
- 動的な設定変更が起こりうるので、設定変更をアプリケーション側に通知させることができるようになっている
- nvidiaのプロプライエタリドライバは割と最近XRandRに対応するようになったらしい?
アプリケーション側から画面設定を変更することも可能。色々できる分APIがややこしく、情報も少ない(仕様とヘッダファイルを読むのは必須)
画面サイズを取得する
基本
XineramaやXRandRを考慮しない、1つの物理モニタが1つのscreenに対応している状況であれば、screenのサイズを取得するだけで良い。
Display *dpy = XOpenDisplay(NULL); /* Xサーバに接続 */
int screen = DefaultScreen(dpy);
int width = DisplayWidth(dpy, screen);
int height = DisplayHeight(dpy, screen);
DisplayWidth/HeightはScreenWidth/Heightであるべきだが、何故かこの名前になっている。これについてはXlibのドキュメントに「混乱させてごめん」と書いてある(2.2節)。仕方ない。
ややこしい場合
XineramaやXRandRが有効になっている場合、複数の物理モニタが並んで一つのscreenを構成する。どのように並べるかは設定しだいだが、例えば同じサイズ(仮にXGAとする)のモニタを2台横に並べればこんな感じになる:
-------------------------------- | || | | 1024x768 || 1024x768 | | +0+0 || +1024+0 | -------------------------------- それぞれのモニタがXGA(1024x768)だとすると、screenは2048x768となる。 2つのモニタ間に論理的な境界はないので、モニタ間でウィンドウを移動することができる。 また、サイズと左上の点のxy座標を用いて 左のモニタを1024x768+0+0、右のモニタを1024x768+1024+0のように表す。(xrandrコマンドの出力形式)
大きさが不揃いなモニタを並べる場合はさらに面倒なことになる。たとえばWSVGA(1024x600)の右隣にXGAを並べるとこんな感じになる:
-------------------------------- | || | | || | ----------------| | ---------------- 1024x168ピクセル分の空白が左下に生まれるが、この場合のscreenは2048x768になる。(大きい方に合わせられる) もちろん空白地帯には物理的な画面が存在しないので何かを表示することはできない。
マルチモニタをどのように解釈するかによって、どのようにサイズを取れば良いかが変わる。大別して2つ方法が考えられる。
- screen全体をそのまま1つの大きなデスクトップとして扱う
- この場合、画面サイズは単にscreenのサイズを取ればよい(上の方法)
- サイズが不揃いなモニタを並べる場合問題が起こることもある
- 物理的なモニタそれぞれに1つづつデスクトップがあるものとして扱う
- この場合、screenのサイズを取っても各モニタのサイズがわからないので役に立たない
- 必要なのは各モニタの幅、高さとxy座標のオフセット
- モニタごとの画面解像度を取るにはXineramaやXRandRのAPIを叩く必要がある
- この場合、screenのサイズを取っても各モニタのサイズがわからないので役に立たない
画面解像度の取得(Xinerama)
XineramaQueryScreens()で各モニタの情報が入った構造体配列へのポインタが得られる。
1 #include <stdio.h>
2 #include <X11/Xlib.h>
3 #include <X11/extensions/Xinerama.h>
4
5 void query(void);
6
7 Display *dpy;
8 int screen;
9 Window root;
10
11 void query(void)
12 {
13 XineramaScreenInfo *info;
14 int n, i;
15 info = XineramaQueryScreens(dpy, &n);
16 for (i = 0; i < n; ++i) {
17 printf("screen %d: %dx%d+%d+%d\n",
18 info[i].screen_number, info[i].width, info[i].height,
19 info[i].x_org, info[i].y_org);
20 }
21 XFree(info);
22 }
23
24 int main(void)
25 {
26 int major, minor;
27 XEvent e;
28
29 dpy = XOpenDisplay(NULL);
30 if (!dpy) {
31 fputs("failed to open display\n", stderr);
32 return 1;
33 }
34
35 screen = DefaultScreen(dpy);
36 root = RootWindow(dpy, screen);
37 if (XineramaIsActive(dpy)) {
38 XineramaQueryVersion(dpy, &major, &minor);
39 printf("Xinerama is active, version %d.%d\n", major, minor);
40 query();
41 } else {
42 fputs("Xinerama is not active\n", stderr);
43 }
44 XCloseDisplay(dpy);
45 return 0;
46 }
画面解像度の取得(XRandR)
XRandRでの設定には以下の3つの概念が現れ、これらは個々に設定できる。
output: 実際の出力先。UTF-8でエンコードされた名前を持つ(xrandrコマンドで出てくるVGA1とかLVDS1とか)
crtc: リフレッシュレートやビューポート2の設定
<width>x<height>+<x>+<y>の形式でビューポートは表せる
- mode: screenの設定(画面解像度とか)
仕様の1.2節によれば、これらの関係は次のようになっているらしい。
- outputに対してcrtcが紐付けられる(1対n?)
- crtcの設定はmodeの設定を与えることによって定まる
- modeは(ハードウェアの制約の下)自由に定義できる
ということで、画面解像度取得は次のような流れになる。
- 有効になっている(接続されている)outputを列挙する
- outputに対応するcrtcを取得し、ビューポートを得る
outputとcrtcの対応を調べる方法を現在調査中。
- それぞれ列挙するのは簡単だった
XineramaとXRandR
ディスプレイの設定方法として現在主流なのはXRandRの方だが、互換性のためXRandRで施した設定の情報はXineramaのAPIによっても取得することができるようになっているらしい。込み入ったことをやりたいのでなければ、XineramaのAPI経由で情報を取得すれば両方に対応できて楽?
設定変更の通知
Xineramaでは設定変更をクライアント側に通知させることができない。通知させるにはXRandRを使う必要がある。XRandR固有の詳しい情報が不要なら、通知関連の処理をXRandRのAPIを使って行い、実際の情報はXineramaのAPIを使って取得する、ということも可能らしい。(Openboxはそういう実装だった)
- 設定変更が起こった場合、WMが然るべき措置を取らないとウィンドウが手の届かない所に置き去りにされる恐れがある
通知はXイベントの形で行われ、設定変更が起こると
RRScreenChangeNotify
ルートウィンドウに対するConfigureNotify
のいずれかがやってくる(前者を受け取る場合XRRSelectInput()を、後者の場合XSelectInput()を使う)。このタイミングで情報を取得しなおして然るべき対応をすればよい。