From 4626e70f90b76a2e99b4f5a5e5f61edbe1afc3bf Mon Sep 17 00:00:00 2001 From: lindexi Date: Mon, 11 Nov 2024 10:19:21 +0800 Subject: [PATCH 1/7] Add `_NET_WM_PID` atom to Linux X11 window https://github.com/AvaloniaUI/Avalonia/issues/17444 --- src/Avalonia.X11/X11Window.cs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 0a254b6b2b3..2932d7742c6 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -151,15 +151,21 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl? popupParent, bool ov (int)CreateWindowArgs.InputOutput, visual, new UIntPtr((uint)valueMask), ref attr); + AppendPid(_handle); if (_useRenderWindow) + { _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, defaultWidth, defaultHeight, 0, depth, (int)CreateWindowArgs.InputOutput, visual, new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr); + AppendPid(_renderHandle); + } else + { _renderHandle = _handle; + } Handle = new PlatformHandle(_handle, "XID"); @@ -323,6 +329,25 @@ private void UpdateMotifHints() PropertyMode.Replace, ref hints, 5); } + /// + /// Append `_NET_WM_PID` atom to X11 window + /// + /// + private void AppendPid(IntPtr windowXId) + { + // See https://github.com/AvaloniaUI/Avalonia/issues/17444 +#if NET6_0_OR_GREATER + var pid = (uint) Environment.ProcessId; +#else + using var currentProcess = Process.GetCurrentProcess(); + var pid = (uint) currentProcess.Id; +#endif + // The type of `_NET_WM_PID` is `CARDINAL` which is 32-bit unsigned integer, see https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html + XChangeProperty(_x11.Display, windowXId, + _x11.Atoms._NET_WM_PID, _x11.Atoms.XA_CARDINAL, 32, + PropertyMode.Replace, ref pid, 1); + } + private void UpdateSizeHints(PixelSize? preResize, bool forceDisableResize = false) { if (_overrideRedirect) From 486f46e3a51574a4bb971600685ab8a3364d2897 Mon Sep 17 00:00:00 2001 From: lindexi Date: Mon, 11 Nov 2024 10:50:35 +0800 Subject: [PATCH 2/7] Also append WM_CLIENT_MACHINE --- src/Avalonia.X11/X11Window.cs | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2932d7742c6..e1638fa1dbf 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using Avalonia.Reactive; using System.Text; @@ -336,16 +337,37 @@ private void UpdateMotifHints() private void AppendPid(IntPtr windowXId) { // See https://github.com/AvaloniaUI/Avalonia/issues/17444 -#if NET6_0_OR_GREATER - var pid = (uint) Environment.ProcessId; -#else - using var currentProcess = Process.GetCurrentProcess(); - var pid = (uint) currentProcess.Id; -#endif + var pid = (uint)s_pid; // The type of `_NET_WM_PID` is `CARDINAL` which is 32-bit unsigned integer, see https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html XChangeProperty(_x11.Display, windowXId, _x11.Atoms._NET_WM_PID, _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref pid, 1); + + // If _NET_WM_PID is set, the ICCCM-specified property WM_CLIENT_MACHINE MUST also be set. + var hostNameFilePath = "cat /proc/sys/kernel/hostname"; + if (File.Exists(hostNameFilePath)) + { + var WM_CLIENT_MACHINE = XInternAtom(_x11.Display, "WM_CLIENT_MACHINE", false); + var hostName = File.ReadAllText(hostNameFilePath); + var stringToHGlobalAnsi = Marshal.StringToHGlobalAnsi(hostName); + + XChangeProperty(_x11.Display, windowXId, + WM_CLIENT_MACHINE, _x11.Atoms.XA_STRING, 8, + PropertyMode.Replace, ref stringToHGlobalAnsi, (int)hostName.Length); + } + } + + private static readonly int s_pid = GetProcessId(); + + private static int GetProcessId() + { +#if NET6_0_OR_GREATER + var pid = Environment.ProcessId; +#else + using var currentProcess = Process.GetCurrentProcess(); + var pid = currentProcess.Id; +#endif + return pid; } private void UpdateSizeHints(PixelSize? preResize, bool forceDisableResize = false) From 8438a6289e6b020629ef8545b31b02a12734095d Mon Sep 17 00:00:00 2001 From: lindexi Date: Mon, 11 Nov 2024 10:54:32 +0800 Subject: [PATCH 3/7] Get the host name from uname method to avoid read the file --- src/Avalonia.X11/UtsName.cs | 105 ++++++++++++++++++++++++++++++++++ src/Avalonia.X11/X11Window.cs | 20 ++++--- 2 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 src/Avalonia.X11/UtsName.cs diff --git a/src/Avalonia.X11/UtsName.cs b/src/Avalonia.X11/UtsName.cs new file mode 100644 index 00000000000..ae39232db51 --- /dev/null +++ b/src/Avalonia.X11/UtsName.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.X11; + +internal struct UtsName : IDisposable +{ + // https://github.com/torvalds/linux/blob/master/include/uapi/linux/utsname.h + /* + #define __NEW_UTS_LEN 64 + + struct new_utsname + { + char sysname[__NEW_UTS_LEN + 1]; + char nodename[__NEW_UTS_LEN + 1]; + char release[__NEW_UTS_LEN + 1]; + char version[__NEW_UTS_LEN + 1]; + char machine[__NEW_UTS_LEN + 1]; + char domainname[__NEW_UTS_LEN + 1]; + }; + */ + + private UtsName(IntPtr buffer) + { + _buffer = buffer; + } + + public static UtsName GetUtsName() + { + var ntsNameStructSize = (UtsLength + 1) * FieldCount; + + IntPtr buffer = Marshal.AllocHGlobal(ntsNameStructSize); + try + { + if (uname(buffer) != 0) + { + throw new InvalidOperationException("uname failed"); + } + + return new UtsName(buffer); + } + catch + { + Marshal.FreeHGlobal(buffer); + throw; + } + } + + private const int SystemNameFieldIndex = 0; + public Span SystemNameSpan => GetValue(SystemNameFieldIndex); + public string SystemName => Encoding.UTF8.GetString(SystemNameSpan); + + private const int NodeNameFieldIndex = 1; + public Span NodeNameSpan => GetValue(NodeNameFieldIndex); + public string NodeName => Encoding.UTF8.GetString(NodeNameSpan); + + private const int ReleaseFieldIndex = 2; + public Span ReleaseSpan => GetValue(ReleaseFieldIndex); + public string Release => Encoding.UTF8.GetString(ReleaseSpan); + + private const int VersionFieldIndex = 3; + public Span VersionSpan => GetValue(VersionFieldIndex); + public string Version => Encoding.UTF8.GetString(VersionSpan); + + private const int MachineFieldIndex = 4; + public Span MachineSpan => GetValue(MachineFieldIndex); + public string Machine => Encoding.UTF8.GetString(MachineSpan); + + private const int DomainNameFieldIndex = 5; + public Span DomainNameSpan => GetValue(DomainNameFieldIndex); + public string DomainName => Encoding.UTF8.GetString(DomainNameSpan); + + private const int UtsLength = 64; + + private const int FieldCount = 6; + + private Span GetValue(int fieldIndex) + { + var startOffset = (UtsLength + 1) * fieldIndex; + var length = 0; + while (Marshal.ReadByte(_buffer, startOffset + length) != 0) + { + length++; + } + + unsafe + { + return new Span((byte*)_buffer + startOffset, length); + } + } + + [DllImport("libc")] + private static extern int uname(IntPtr buf); + + private readonly IntPtr _buffer; + + public void Dispose() + { + Marshal.FreeHGlobal(_buffer); + } +} diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index e1638fa1dbf..868e4b59cb2 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using Avalonia.Reactive; using System.Text; @@ -343,17 +342,20 @@ private void AppendPid(IntPtr windowXId) _x11.Atoms._NET_WM_PID, _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref pid, 1); - // If _NET_WM_PID is set, the ICCCM-specified property WM_CLIENT_MACHINE MUST also be set. - var hostNameFilePath = "cat /proc/sys/kernel/hostname"; - if (File.Exists(hostNameFilePath)) - { - var WM_CLIENT_MACHINE = XInternAtom(_x11.Display, "WM_CLIENT_MACHINE", false); - var hostName = File.ReadAllText(hostNameFilePath); - var stringToHGlobalAnsi = Marshal.StringToHGlobalAnsi(hostName); + // If _NET_WM_PID is set, the ICCCM-specified property WM_CLIENT_MACHINE MUST also be set. + // the hostname can change, so we can't cache it + // gethostname(3) on Linux just calls uname(2), so do it ourselves + // and avoid a memcpy + using var utsName = UtsName.GetUtsName(); + + var WM_CLIENT_MACHINE = XInternAtom(_x11.Display, "WM_CLIENT_MACHINE", false); + var nodeNameSpan = utsName.NodeNameSpan; + fixed (byte* pNodeName = &nodeNameSpan.GetPinnableReference()) + { XChangeProperty(_x11.Display, windowXId, WM_CLIENT_MACHINE, _x11.Atoms.XA_STRING, 8, - PropertyMode.Replace, ref stringToHGlobalAnsi, (int)hostName.Length); + PropertyMode.Replace, pNodeName, nodeNameSpan.Length); } } From 13bd207b6d61bacb82d837d2063f27bf37a0ab74 Mon Sep 17 00:00:00 2001 From: lindexi Date: Mon, 11 Nov 2024 15:13:33 +0800 Subject: [PATCH 4/7] Fix build fail on .NET Standard 2.0 --- src/Avalonia.X11/UtsName.cs | 46 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.X11/UtsName.cs b/src/Avalonia.X11/UtsName.cs index ae39232db51..83b498ae3e0 100644 --- a/src/Avalonia.X11/UtsName.cs +++ b/src/Avalonia.X11/UtsName.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; namespace Avalonia.X11; @@ -51,34 +48,49 @@ public static UtsName GetUtsName() } private const int SystemNameFieldIndex = 0; - public Span SystemNameSpan => GetValue(SystemNameFieldIndex); - public string SystemName => Encoding.UTF8.GetString(SystemNameSpan); + public ReadOnlySpan SystemNameSpan => GetValue(SystemNameFieldIndex); + public string SystemName => GetAsciiString(SystemNameSpan); private const int NodeNameFieldIndex = 1; - public Span NodeNameSpan => GetValue(NodeNameFieldIndex); - public string NodeName => Encoding.UTF8.GetString(NodeNameSpan); + public ReadOnlySpan NodeNameSpan => GetValue(NodeNameFieldIndex); + public string NodeName => GetAsciiString(NodeNameSpan); private const int ReleaseFieldIndex = 2; - public Span ReleaseSpan => GetValue(ReleaseFieldIndex); - public string Release => Encoding.UTF8.GetString(ReleaseSpan); + public ReadOnlySpan ReleaseSpan => GetValue(ReleaseFieldIndex); + public string Release => GetAsciiString(ReleaseSpan); private const int VersionFieldIndex = 3; - public Span VersionSpan => GetValue(VersionFieldIndex); - public string Version => Encoding.UTF8.GetString(VersionSpan); + public ReadOnlySpan VersionSpan => GetValue(VersionFieldIndex); + public string Version => GetAsciiString(VersionSpan); private const int MachineFieldIndex = 4; - public Span MachineSpan => GetValue(MachineFieldIndex); - public string Machine => Encoding.UTF8.GetString(MachineSpan); + public ReadOnlySpan MachineSpan => GetValue(MachineFieldIndex); + public string Machine => GetAsciiString(MachineSpan); private const int DomainNameFieldIndex = 5; - public Span DomainNameSpan => GetValue(DomainNameFieldIndex); - public string DomainName => Encoding.UTF8.GetString(DomainNameSpan); + public ReadOnlySpan DomainNameSpan => GetValue(DomainNameFieldIndex); + public string DomainName => GetAsciiString(DomainNameSpan); private const int UtsLength = 64; private const int FieldCount = 6; - private Span GetValue(int fieldIndex) + private static string GetAsciiString(ReadOnlySpan value) + { +#if NET6_0_OR_GREATER + return Encoding.ASCII.GetString(value); +#else + unsafe + { + fixed (byte* ptr = value) + { + return Encoding.ASCII.GetString(ptr, value.Length); + } + } +#endif + } + + private ReadOnlySpan GetValue(int fieldIndex) { var startOffset = (UtsLength + 1) * fieldIndex; var length = 0; @@ -89,7 +101,7 @@ private Span GetValue(int fieldIndex) unsafe { - return new Span((byte*)_buffer + startOffset, length); + return new ReadOnlySpan((byte*)_buffer + startOffset, length); } } From 194f109832472283d9099e2d860ffa83e9422704 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 20 Nov 2024 19:34:40 +0800 Subject: [PATCH 5/7] The render window is the child window. And it should not append pid atom. --- src/Avalonia.X11/X11Window.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 868e4b59cb2..2903574d494 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -160,7 +160,6 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl? popupParent, bool ov visual, new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr); - AppendPid(_renderHandle); } else { From dc091b88ec0a9da66cce12049850d3f2b47dc49c Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 20 Nov 2024 19:50:06 +0800 Subject: [PATCH 6/7] Using the atom from atoms --- src/Avalonia.X11/X11Window.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2903574d494..9edd33cbe55 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -347,13 +347,11 @@ private void AppendPid(IntPtr windowXId) // and avoid a memcpy using var utsName = UtsName.GetUtsName(); - var WM_CLIENT_MACHINE = XInternAtom(_x11.Display, "WM_CLIENT_MACHINE", false); - var nodeNameSpan = utsName.NodeNameSpan; fixed (byte* pNodeName = &nodeNameSpan.GetPinnableReference()) { XChangeProperty(_x11.Display, windowXId, - WM_CLIENT_MACHINE, _x11.Atoms.XA_STRING, 8, + _x11.Atoms.XA_WM_CLIENT_MACHINE, _x11.Atoms.XA_STRING, 8, PropertyMode.Replace, pNodeName, nodeNameSpan.Length); } } From 3f98cdd99ea2875d1431f29fa4340352c6e7d129 Mon Sep 17 00:00:00 2001 From: lindexi Date: Fri, 29 Nov 2024 09:35:44 +0800 Subject: [PATCH 7/7] Replace UtsName with gethostname --- src/Avalonia.X11/UtsName.cs | 117 ---------------------------------- src/Avalonia.X11/X11Window.cs | 28 +++++--- 2 files changed, 18 insertions(+), 127 deletions(-) delete mode 100644 src/Avalonia.X11/UtsName.cs diff --git a/src/Avalonia.X11/UtsName.cs b/src/Avalonia.X11/UtsName.cs deleted file mode 100644 index 83b498ae3e0..00000000000 --- a/src/Avalonia.X11/UtsName.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Text; - -namespace Avalonia.X11; - -internal struct UtsName : IDisposable -{ - // https://github.com/torvalds/linux/blob/master/include/uapi/linux/utsname.h - /* - #define __NEW_UTS_LEN 64 - - struct new_utsname - { - char sysname[__NEW_UTS_LEN + 1]; - char nodename[__NEW_UTS_LEN + 1]; - char release[__NEW_UTS_LEN + 1]; - char version[__NEW_UTS_LEN + 1]; - char machine[__NEW_UTS_LEN + 1]; - char domainname[__NEW_UTS_LEN + 1]; - }; - */ - - private UtsName(IntPtr buffer) - { - _buffer = buffer; - } - - public static UtsName GetUtsName() - { - var ntsNameStructSize = (UtsLength + 1) * FieldCount; - - IntPtr buffer = Marshal.AllocHGlobal(ntsNameStructSize); - try - { - if (uname(buffer) != 0) - { - throw new InvalidOperationException("uname failed"); - } - - return new UtsName(buffer); - } - catch - { - Marshal.FreeHGlobal(buffer); - throw; - } - } - - private const int SystemNameFieldIndex = 0; - public ReadOnlySpan SystemNameSpan => GetValue(SystemNameFieldIndex); - public string SystemName => GetAsciiString(SystemNameSpan); - - private const int NodeNameFieldIndex = 1; - public ReadOnlySpan NodeNameSpan => GetValue(NodeNameFieldIndex); - public string NodeName => GetAsciiString(NodeNameSpan); - - private const int ReleaseFieldIndex = 2; - public ReadOnlySpan ReleaseSpan => GetValue(ReleaseFieldIndex); - public string Release => GetAsciiString(ReleaseSpan); - - private const int VersionFieldIndex = 3; - public ReadOnlySpan VersionSpan => GetValue(VersionFieldIndex); - public string Version => GetAsciiString(VersionSpan); - - private const int MachineFieldIndex = 4; - public ReadOnlySpan MachineSpan => GetValue(MachineFieldIndex); - public string Machine => GetAsciiString(MachineSpan); - - private const int DomainNameFieldIndex = 5; - public ReadOnlySpan DomainNameSpan => GetValue(DomainNameFieldIndex); - public string DomainName => GetAsciiString(DomainNameSpan); - - private const int UtsLength = 64; - - private const int FieldCount = 6; - - private static string GetAsciiString(ReadOnlySpan value) - { -#if NET6_0_OR_GREATER - return Encoding.ASCII.GetString(value); -#else - unsafe - { - fixed (byte* ptr = value) - { - return Encoding.ASCII.GetString(ptr, value.Length); - } - } -#endif - } - - private ReadOnlySpan GetValue(int fieldIndex) - { - var startOffset = (UtsLength + 1) * fieldIndex; - var length = 0; - while (Marshal.ReadByte(_buffer, startOffset + length) != 0) - { - length++; - } - - unsafe - { - return new ReadOnlySpan((byte*)_buffer + startOffset, length); - } - } - - [DllImport("libc")] - private static extern int uname(IntPtr buf); - - private readonly IntPtr _buffer; - - public void Dispose() - { - Marshal.FreeHGlobal(_buffer); - } -} diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 9edd33cbe55..4e95d69d082 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -341,21 +341,29 @@ private void AppendPid(IntPtr windowXId) _x11.Atoms._NET_WM_PID, _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref pid, 1); - // If _NET_WM_PID is set, the ICCCM-specified property WM_CLIENT_MACHINE MUST also be set. - // the hostname can change, so we can't cache it - // gethostname(3) on Linux just calls uname(2), so do it ourselves - // and avoid a memcpy - using var utsName = UtsName.GetUtsName(); + const int maxLength = 1024; + var name = stackalloc byte[maxLength]; + var result = gethostname(name, maxLength); + if (result != 0) + { + // Fail + return; + } - var nodeNameSpan = utsName.NodeNameSpan; - fixed (byte* pNodeName = &nodeNameSpan.GetPinnableReference()) + var length = 0; + while (length < maxLength && name[length] != 0) { - XChangeProperty(_x11.Display, windowXId, - _x11.Atoms.XA_WM_CLIENT_MACHINE, _x11.Atoms.XA_STRING, 8, - PropertyMode.Replace, pNodeName, nodeNameSpan.Length); + length++; } + + XChangeProperty(_x11.Display, windowXId, + _x11.Atoms.XA_WM_CLIENT_MACHINE, _x11.Atoms.XA_STRING, 8, + PropertyMode.Replace, name, length); } + [DllImport("libc")] + private static extern int gethostname(byte* name, int len); + private static readonly int s_pid = GetProcessId(); private static int GetProcessId()