dotnet 如何從 Gtk 3 的窗口到對應的 X11 窗口

本文將告訴大家如何在 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 的窗口傳入方法

本文代碼放在 githubgitee 上,可以使用如下命令行拉取代碼

先創建一個空文件夾,接着使用命令行 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 開發請參閱 博客導航

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章