本文將告訴大家如何在 Gtk3 的 Gtk.Window 或 Gdk.Window 裡面獲取到對應的 X11 視窗 XID 號
記錄本文是因為我在這裡踩了很多坑,核心問題就是 GTK 有很多個版本,我開始找的全是使用 GTK 2 的 gdk_x11_drawable_get_xid
方法,而不是 GtkSharp 3.24 對應的 GTK 3 的方法
以上的 gdk_x11_drawable_get_xid 方法需要構建傳入 GdkDrawable 指標,讓我弄錯為使用 gtk_widget_get_window
方法去獲取其 gdk 視窗,於是錯誤就更加詭異
透過閱讀文件發現了以下的 gtk 架構圖,即 gtk 的視窗和 gdk 視窗是不相同的,可以透過 gtk_widget_get_window
方法獲取,在 C# dotnet 裡面可直接使用 Gtk.Window 的 Window 屬性,更多請參閱:https://en.wikipedia.org/wiki/GDK
從 Gtk 的 Window 視窗獲取 Gdk 的 Window 視窗,可使用以下簡單程式碼獲取
Gtk.Window window = xxx;
Gdk.Window gdkWindow = window.Window;
獲取 Gdk 視窗指標,可透過 Handle 屬性獲取,如以下程式碼
Gdk.Window gdkWindow = window.Window;
var handle = gdkWindow.Handle;
以上獲取的 handle 指標與 var windowHandle = gtk_widget_get_window(gtkWindow.Handle);
獲取的 windowHandle
是相同的,因為在 GtkSharp 的 Widget.cs 就是如此實現的
namespace Gtk;
public partial class Widget
{
... // 忽略其他程式碼
[GLib.Property ("window")]
public Gdk.Window Window
{
get
{
IntPtr raw_ret = gtk_widget_get_window(Handle);
Gdk.Window ret = GLib.Object.GetObject(raw_ret) as Gdk.Window;
return ret;
}
set
{
gtk_widget_set_window(Handle, value == null ? IntPtr.Zero : value.Handle);
}
}
[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
delegate IntPtr d_gtk_widget_get_window(IntPtr raw);
static d_gtk_widget_get_window gtk_widget_get_window = FuncLoader.LoadFunction<d_gtk_widget_get_window>(FuncLoader.GetProcAddress(GLibrary.Load(Library.Gtk), "gtk_widget_get_window"));
}
public partial class Container : Gtk.Widget
{
... // 忽略其他程式碼
}
public partial class Bin : Gtk.Container
{
... // 忽略其他程式碼
}
public partial class Window : Gtk.Bin
{
... // 忽略其他程式碼
}
使用 gdk_x11_window_get_xid 方法即可正確的從 gdk 視窗獲取到對應的 X11 視窗的 XID 值
為了方便使用 gdk_x11_window_get_xid 方法,以下照 GtkSharp 進行一些程式碼定義
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate IntPtr d_gdk_x11_window_get_xid(IntPtr gdkWindow);
private static d_gdk_x11_window_get_xid gdk_x11_window_get_xid =
LoadFunction<d_gdk_x11_window_get_xid>("libgdk-3.so.0", "gdk_x11_window_get_xid");
private static T LoadFunction<T>(string libName, string functionName)
{
if (!OperatingSystem.IsLinux())
{
// 防止炸除錯
return default(T)!;
}
// LoadLibrary
var libPtr = Linux.dlopen(libName, RTLD_GLOBAL | RTLD_LAZY);
Console.WriteLine($"Load {libName} ptr={libPtr}");
// GetProcAddress
var procAddress = Linux.dlsym(libPtr, functionName);
Console.WriteLine($"Load {functionName} ptr={procAddress}");
return Marshal.GetDelegateForFunctionPointer<T>(procAddress);
}
private const int RTLD_LAZY = 0x0001;
private const int RTLD_GLOBAL = 0x0100;
private class Linux
{
[DllImport("libdl.so.2")]
public static extern IntPtr dlopen(string path, int flags);
[DllImport("libdl.so.2")]
public static extern IntPtr dlsym(IntPtr handle, string symbol);
}
接著在視窗的 Show 方法之後,即可獲取到對應的 X11 視窗
protected override void OnShown()
{
base.OnShown(); // 在這句話呼叫之前 window.Window 是空
Window window = this;
Gdk.Window gdkWindow = window.Window;
if (gdkWindow is null)
{
// 確保 base.OnShown 呼叫
Console.WriteLine($"gdkWindow is null");
return;
}
var x11 = gdk_x11_window_get_xid(gdkWindow.Handle);
Console.WriteLine($"X11 視窗 0x{x11:x2}");
}
透過以上程式碼輸出的 X11 視窗的 XID 號,可以同步在命令列輸入進 xwininfo 命令裡面。比如我這裡輸出的是 X11 視窗 0x5600003
的值
開啟另一個命令列,輸入以下命令,將 XID 傳入 xwininfo 命令,即可看到顯示的視窗標題和當前執行的視窗是相同的
我核心踩坑就是搜到的是 GTK 2 的使用方法,以及將 gtk 的視窗當成 gdk 的視窗傳入方法
本文程式碼放在 github 和 gitee 上,可以使用如下命令列拉取程式碼
先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 4ae5a45eb65cab5a3b9f8991852be9602dee6533
以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 4ae5a45eb65cab5a3b9f8991852be9602dee6533
獲取程式碼之後,進入 LejarkeebemCowakiwhanar 資料夾,即可獲取到原始碼
更多 GTK 開發請參閱 部落格導航