本文記錄在 dotnet 裡面如何設定 X11 視窗從最小化狀態還原為正常狀態
本文屬於學習 Avalonia 框架系列筆記,更多請參閱 部落格導航
核心設定方法如下
/// <summary>
/// 程式碼從 Avalonia 抄的 https://github.com/AvaloniaUI/Avalonia/blob/5e323b8fb1e2ca36550ca6fe678e487ff936d8bf/src/Avalonia.X11/X11Window.cs#L692
/// </summary>
unsafe class X11Window
{
public X11Window(IntPtr windowHandle, IntPtr display, IntPtr rootWindow)
{
Display = display;
RootWindow = rootWindow;
_handle = windowHandle;
}
private readonly IntPtr _handle;
public IntPtr Display { get; }
public IntPtr RootWindow { get; }
//private bool _mapped;
private IntPtr _NET_WM_STATE => XInternAtom(Display, "_NET_WM_STATE", true);
public void SetNormal()
{
ChangeWMAtoms(false, GetAtom("_NET_WM_STATE_HIDDEN"));
ChangeWMAtoms(false, GetAtom("_NET_WM_STATE_FULLSCREEN"));
ChangeWMAtoms(false, GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"),
GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"));
SendNetWMMessage(GetAtom("_NET_ACTIVE_WINDOW"), (IntPtr) 1, 0,
IntPtr.Zero);
IntPtr GetAtom(string name) => XInternAtom(Display, name, true);
}
private void ChangeWMAtoms(bool enable, params IntPtr[] atoms)
{
if (atoms.Length != 1 && atoms.Length != 2)
{
throw new ArgumentException();
}
//if (!_mapped)
//{
// XGetWindowProperty(Display, _handle, _NET_WM_STATE, IntPtr.Zero, new IntPtr(256),
// false, (IntPtr) Atom.XA_ATOM, out _, out _, out var nitems, out _,
// out var prop);
// var ptr = (IntPtr*) prop.ToPointer();
// var newAtoms = new HashSet<IntPtr>();
// for (var c = 0; c < nitems.ToInt64(); c++)
// {
// newAtoms.Add(*ptr);
// }
// XFree(prop);
// foreach (var atom in atoms)
// {
// if (enable)
// {
// newAtoms.Add(atom);
// }
// else
// {
// newAtoms.Remove(atom);
// }
// }
// XChangeProperty(Display, _handle, _NET_WM_STATE, (IntPtr) Atom.XA_ATOM, 32,
// PropertyMode.Replace, newAtoms.ToArray(), newAtoms.Count);
//}
SendNetWMMessage(_NET_WM_STATE,
(IntPtr) (enable ? 1 : 0),
atoms[0],
atoms.Length > 1 ? atoms[1] : IntPtr.Zero,
atoms.Length > 2 ? atoms[2] : IntPtr.Zero,
atoms.Length > 3 ? atoms[3] : IntPtr.Zero
);
}
private void SendNetWMMessage(IntPtr message_type, IntPtr l0,
IntPtr? l1 = null, IntPtr? l2 = null, IntPtr? l3 = null, IntPtr? l4 = null)
{
var xev = new XEvent
{
ClientMessageEvent =
{
type = XEventName.ClientMessage,
send_event = true,
window = _handle,
message_type = message_type,
format = 32,
ptr1 = l0,
ptr2 = l1 ?? IntPtr.Zero,
ptr3 = l2 ?? IntPtr.Zero,
ptr4 = l3 ?? IntPtr.Zero
}
};
xev.ClientMessageEvent.ptr4 = l4 ?? IntPtr.Zero;
XSendEvent(Display, RootWindow, false,
new IntPtr((int) (EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask)), ref xev);
}
}
以上程式碼沒有定義的函式和區域性變數等,可以在本文末尾找到完全的程式碼
為了測試其行為,我額外編寫了一些 C# 程式碼,不斷設定讓 X11 視窗最小化和還原到正常狀態
async Task InvokeAsync(Action action)
{
var taskCompletionSource = new TaskCompletionSource();
lock (invokeList)
{
invokeList.Add(() =>
{
action();
taskCompletionSource.SetResult();
});
}
// 在 Avalonia 裡面,是透過迴圈讀取的方式,透過 XPending 判斷是否有訊息
// 如果沒有訊息就進入自旋判斷是否有業務訊息和判斷是否有 XPending 訊息
// 核心使用 epoll_wait 進行等待
// 整個邏輯比較複雜
// 這裡簡單處理,只透過傳送 ClientMessage 的方式,告訴訊息迴圈需要處理業務邏輯
// 傳送 ClientMessage 是一個合理的方式,根據官方文件說明,可以看到這是沒有明確定義的
// https://www.x.org/releases/X11R7.5/doc/man/man3/XClientMessageEvent.3.html
// The X server places no interpretation on the values in the window, message_type, or data members.
// 在 cpf 裡面,和 Avalonia 實現差不多,也是在判斷 XPending 是否有訊息,沒訊息則判斷是否有業務邏輯
// 最後再進入等待邏輯。似乎 CPF 這樣的方式會導致 CPU 佔用略微提升
var @event = new XEvent
{
ClientMessageEvent =
{
type = XEventName.ClientMessage,
send_event = true,
window = handle,
message_type = 0,
format = 32,
ptr1 = invokeMessageId,
ptr2 = 0,
ptr3 = 0,
ptr4 = 0,
}
};
XSendEvent(display, handle, false, 0, ref @event);
XFlush(display);
await taskCompletionSource.Task;
}
_ = Task.Run(async () =>
{
var x11Window = new X11Window(handle, display, rootWindow);
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(3));
await InvokeAsync(() =>
{
var result = XIconifyWindow(display, handle, screen);
});
await Task.Delay(TimeSpan.FromSeconds(2));
await InvokeAsync(() =>
{
x11Window.SetNormal();
});
}
});
以上程式碼放在 github 和 gitee 上,可以使用如下命令列拉取程式碼
先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 0a1cb0ac238bd809c17059cfa57bcb9528b79c72
以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 0a1cb0ac238bd809c17059cfa57bcb9528b79c72
獲取程式碼之後,進入 DikalehebeekaJaqunicobo 資料夾,即可獲取到原始碼
設定 X11 視窗最小化的方法請看 dotnet 後臺執行緒設定 X11 視窗最小化