/// 代码从 Avalonia 抄的 https://github.com/AvaloniaUI/Avalonia/blob/5e323b8fb1e2ca36550ca6fe678e487ff936d8bf/src/Avalonia.X11/X11Window.cs#L692
public X11Window(IntPtr windowHandle, IntPtr display, IntPtr rootWindow)
private readonly IntPtr _handle;
public IntPtr Display { get; }
public IntPtr RootWindow { get; }
private IntPtr _NET_WM_STATE => XInternAtom(Display, "_NET_WM_STATE", true);
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 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();
// XGetWindowProperty(Display, _handle, _NET_WM_STATE, IntPtr.Zero, new IntPtr(256),
// false, (IntPtr) Atom.XA_ATOM, out _, out _, out var nitems, out _,
// var ptr = (IntPtr*) prop.ToPointer();
// var newAtoms = new HashSet<IntPtr>();
// for (var c = 0; c < nitems.ToInt64(); c++)
// foreach (var atom in atoms)
// 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.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)
type = XEventName.ClientMessage,
message_type = message_type,
ptr2 = l1 ?? IntPtr.Zero,
ptr3 = l2 ?? IntPtr.Zero,
xev.ClientMessageEvent.ptr4 = l4 ?? IntPtr.Zero;
XSendEvent(Display, RootWindow, false,
new IntPtr((int) (EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask)), ref xev);