ログイン
編集不可のページディスカッション情報添付ファイル
clear/wm_devel/2012-09-05

MMA

画面サイズを取得する話

ウィンドウ管理を行うには画面の大きさ(画面解像度)が分からないと色々と困る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コマンドを使って動的に設定を行える。

アプリケーション側から画面設定を変更することも可能。色々できる分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つ方法が考えられる。

画面解像度の取得(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つの概念が現れ、これらは個々に設定できる。

仕様の1.2節によれば、これらの関係は次のようになっているらしい。

ということで、画面解像度取得は次のような流れになる。

  1. 有効になっている(接続されている)outputを列挙する
  2. outputに対応するcrtcを取得し、ビューポートを得る

outputとcrtcの対応を調べる方法を現在調査中。

XineramaとXRandR

ディスプレイの設定方法として現在主流なのはXRandRの方だが、互換性のためXRandRで施した設定の情報はXineramaのAPIによっても取得することができるようになっているらしい。込み入ったことをやりたいのでなければ、XineramaのAPI経由で情報を取得すれば両方に対応できて楽?

設定変更の通知

Xineramaでは設定変更をクライアント側に通知させることができない。通知させるにはXRandRを使う必要がある。XRandR固有の詳しい情報が不要なら、通知関連の処理をXRandRのAPIを使って行い、実際の情報はXineramaのAPIを使って取得する、ということも可能らしい。(Openboxはそういう実装だった)

通知はXイベントの形で行われ、設定変更が起こると

のいずれかがやってくる(前者を受け取る場合XRRSelectInput()を、後者の場合XSelectInput()を使う)。このタイミングで情報を取得しなおして然るべき対応をすればよい。

  1. タイル型WM作るなら特に (1)

  2. (1画面に収まらないような)screen全体のうち、今見えている部分 (2)

clear/wm_devel/2012-09-05 (最終更新日時 2012-12-25 18:45:11 更新者 clear)