多顯示器的場合,如何將視窗呈現在指定的顯示器處
本文說明在多顯示器併用的場合中,X Window 環境如何得知顯示範圍尺寸、實際啟用的顯示器數量、以及將視窗放置在指定的顯示器。
基本概念
在 X Window 系統中,對於顯示功能定義了幾個專有名詞,分別是 Display , Screen, Monitor, Window 。 它們之間的關係是 Display 包含 Screen ,Screen 包含 Monitor 與 Window 。 Monitor 代表實際的顯示器,Window 則是負責與使用者互動的部件。 在這些專有名詞中,只有 Monitor 和顯示器硬體是一對一關係,其他都是抽象的軟體概念。
在一般的使用場合中,一個 Display 只會有一個 Screen ,一個 Screen 包含全部使用者實際啟用的 Monitor (顯示器)。 一個雙顯示器的操作環境,在 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 ,並標上顯示器編號。 如圖二所示:

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 。