最近更新: 2012-10-26

多顯示器的場合,如何將視窗呈現在指定的顯示器處

本文說明在多顯示器併用的場合中,X Window 環境如何得知顯示範圍尺寸、實際啟用的顯示器數量、以及將視窗放置在指定的顯示器。

基本概念

在 X Window 系統中,對於顯示功能定義了幾個專有名詞,分別是 Display , Screen, Monitor, Window 。 它們之間的關係是 Display 包含 Screen ,Screen 包含 Monitor 與 Window 。 Monitor 代表實際的顯示器,Window 則是負責與使用者互動的部件。 在這些專有名詞中,只有 Monitor 和顯示器硬體是一對一關係,其他都是抽象的軟體概念。

在一般的使用場合中,一個 Display 只會有一個 Screen ,一個 Screen 包含全部使用者實際啟用的 Monitor (顯示器)。 一個雙顯示器的操作環境,在 X Window 眼中的畫面可能會是圖一的關係:

X Window 眼中的雙顯示器關係圖

圖一是一般雙顯示器常見的配置方式,兩台相同大小的顯示器,框體水平,左右平行放置 (當然還有其他擺法,如我的桌上電腦使用兩台螢幕,一直一橫)。 在這種情形下, X Window 的可顯示範圍像素大小是兩台顯示器併起來的像素大小。 Screen 會用 X, Y 座標註明顯示器映照的畫面範圍起始點。 以圖一為例,Screen 的長寬是 1920+1920 + 1200 = 3840x1200。 然後顯示器 0 號映照畫面 (0,0)-(1919,1200) 的區域;顯示器 1 號映照畫面 (1920,0)-(3840,1200) 的區域。 釐清 Screen 與 Monitor 的座標關係後,我們才能將 Window 呈現在指定的顯示器。

接著將以 GTK 函數撰寫一個範例程式,分別在每個顯示器上呈現一個 Window ,並標上顯示器編號。 如圖二所示:

雙顯示器上各呈現一個有編號的 Window

Vala 版 - screen-monitors.vala

// valac --pkg=gtk+-2.0 --pkg=gdk-2.0 screen-monitors.vala
using Gtk;
using Gdk;

int main()
{
    int i = 0;
    string[1] nothing = {""};
    Gtk.init(ref nothing);

    var scr = Gdk.Screen.get_default();
    stdout.printf("Screen height(%d), width(%d)\n", 
            Gdk.Screen.height(), Gdk.Screen.width());
    stdout.printf("Screen height(%d), width(%d)\n", 
            scr.get_height(), scr.get_width());
    var n_monitors = scr.get_n_monitors();
    stdout.printf("Monitors: %d\n", n_monitors);

    var rects = new Rectangle[n_monitors];
    Rectangle rect;
    for (i = 0; i < n_monitors; ++i) {
        scr.get_monitor_geometry(i, out rects[i]);
        stdout.printf("Rectangle %d : x[%d], y[%d], height[%d], width[%d]\n", 
                i, rects[i].x, rects[i].y, rects[i].width, rects[i].height);
    }

    var ws = new Gtk.Window[n_monitors];
    Gtk.Window w;
    Label label;
    for (i = 0; i < n_monitors; ++i) {
        w = ws[i] = new Gtk.Window();
        rect = rects[i];
        w.destroy.connect(Gtk.main_quit);
        w.title = @"Window at monitor $i";
        w.set_default_size(rect.width/2, rect.height/2);
        w.move(rect.x + rect.width/4, rect.y + rect.height/4);
        label = new Label(null);
        label.set_use_markup(true);
        label.set_markup(@"<span font='64'>$i</span>");
        w.add(label);
        w.show_all();
    }

    Gtk.main();
    return 0;
}

GJS/Gnome-Shell 版 - screen-monitors.js

// Use GDK to get size of current screen.
const Gdk = imports.gi.Gdk;
const Gtk = imports.gi.Gtk;

Gtk.init(0, null);

var screen = Gdk.Screen.get_default(); // w.get_screen();
var n_monitors = screen.get_n_monitors();
print("Current screen width: " + screen.get_width() + 
                  "; height: " + screen.get_height());
print("Number of Monitors: " + n_monitors);

var monitor_geometry = [new Gdk.Rectangle, new Gdk.Rectangle];
//http://www.pygtk.org/docs/pygtk/class-gdkrectangle.html

for (var i = 0; i < n_monitors; ++i) {
    screen.get_monitor_geometry(i, monitor_geometry[i])
    print("Geometry of Monitor ", i);
    print("\tWidth:  " + monitor_geometry[i].width);
    print("\tHeight: " + monitor_geometry[i].height);
    print("\tX:      " + monitor_geometry[i].x);
    print("\tY:      " + monitor_geometry[i].y);
}
//http://developer.gnome.org/gdk/stable/GdkScreen.html
//http://www.pygtk.org/docs/pygtk/gdk-functions.html
//http://www.gtk.org/api/2.6/gdk/multihead.html

var ws = [new Gtk.Window, new Gtk.Window];
var w, label, rect;
for (var i = 0; i < n_monitors; ++i) {
    w = ws[i];
    rect = monitor_geometry[i];
    w.connect("destroy", Gtk.main_quit);
    w.title = "Window at monitor " + i;
    w.set_default_size(rect.width / 2, rect.height / 2);
    w.move(rect.x + rect.width / 4, rect.y + rect.height / 4);
    label = new Gtk.Label({
        label: "<span font='64'>" + i + "</span>",
        use_markup: true
        }); // 跟 GIR 的宣告內容不符...
    w.add(label);
    w.show_all();
}

Gtk.main(); // gtk loop, quit by main_quit().

程式說明

在一般場合中,一個 Display 只會有一個 Screen ,所以直接使用 Gdk.Screen.get_default() 取得預設的 Screen 項目。 它提供方法查詢這個 Screen 的顯示範圍像素大小以及連接的顯示器數量。

使用 Screen 的個體方法 get_monitor_geometry() 查詢各個顯示器映照區域的起始座標與長寬。

GTK (或基礎的 X API) 並沒有提供方法直接指定 Window 放置的顯示器。 我們要先查詢顯示器映照的顯示區域,然後再自己將 Window 移到指定區域。 所以配置 Window 後,再使用 move() 方法,將 Window 移往指定的顯示器映照區域放置。 這樣使用者就會在每個顯示器上看到相對應的編號 Window 。

樂多舊網址: http://blog.roodo.com/rocksaying/archives/21137736.html