From 8adb67a3d5aea3535c52c19600213db40bc60553 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 1 Mar 2024 00:09:32 +0300 Subject: [PATCH] [X11] Allow setting WM_CLASS and _NET_WM_WINDOW_TYPE (#14619) * [X11] Allow setting WM_CLASS and _NET_WM_WINDOW_TYPE * Renamed to X11Properties and moved to Avalonia.Controls * [PrivateApi] * Rename * Suggested doc Co-authored-by: Max Katz --------- Co-authored-by: Max Katz --- .../IX11OptionsToplevelImplFeature.cs | 21 ++++++++++ .../Platform/X11Properties.cs | 38 ++++++++++++++++++ src/Avalonia.X11/X11Window.cs | 39 +++++++++++++++++-- 3 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/Avalonia.Controls/Platform/IX11OptionsToplevelImplFeature.cs create mode 100644 src/Avalonia.Controls/Platform/X11Properties.cs diff --git a/src/Avalonia.Controls/Platform/IX11OptionsToplevelImplFeature.cs b/src/Avalonia.Controls/Platform/IX11OptionsToplevelImplFeature.cs new file mode 100644 index 0000000000..b1ff618fbf --- /dev/null +++ b/src/Avalonia.Controls/Platform/IX11OptionsToplevelImplFeature.cs @@ -0,0 +1,21 @@ +using Avalonia.Metadata; +namespace Avalonia.Controls.Platform; + +public enum X11NetWmWindowType +{ + Normal, + Dialog, + Utility, + Menu, + Toolbar, + Splash, + Dock, + Desktop +} + +[PrivateApi] +public interface IX11OptionsToplevelImplFeature +{ + void SetNetWmWindowType(X11NetWmWindowType type); + void SetWmClass(string? className); +} diff --git a/src/Avalonia.Controls/Platform/X11Properties.cs b/src/Avalonia.Controls/Platform/X11Properties.cs new file mode 100644 index 0000000000..fec88a427c --- /dev/null +++ b/src/Avalonia.Controls/Platform/X11Properties.cs @@ -0,0 +1,38 @@ +using Avalonia.Controls.Platform; +using Avalonia.Platform; +using Avalonia.Reactive; + +namespace Avalonia.Controls; + +/// +/// Set of X11 specific properties and events that allow deeper customization of the application per platform. +/// +public class X11Properties +{ + public static readonly AttachedProperty NetWmWindowTypeProperty = + AvaloniaProperty.RegisterAttached("NetWmWindowType"); + + public static void SetNetWmWindowType(Window obj, X11NetWmWindowType value) => obj.SetValue(NetWmWindowTypeProperty, value); + public static X11NetWmWindowType GetNetWmWindowType(Window obj) => obj.GetValue(NetWmWindowTypeProperty); + + public static readonly AttachedProperty WmClassProperty = + AvaloniaProperty.RegisterAttached("WmClass"); + + public static void SetWmClass(Window obj, string? value) => obj.SetValue(WmClassProperty, value); + public static string? GetWmClass(Window obj) => obj.GetValue(WmClassProperty); + + static X11Properties() + { + NetWmWindowTypeProperty.Changed.Subscribe(OnNetWmWindowTypeChanged); + WmClassProperty.Changed.Subscribe(OnWmClassChanged); + } + + private static IX11OptionsToplevelImplFeature? TryGetFeature(AvaloniaPropertyChangedEventArgs e) + => (e.Sender as TopLevel)?.PlatformImpl?.TryGetFeature(); + + private static void OnWmClassChanged(AvaloniaPropertyChangedEventArgs e) => + TryGetFeature(e)?.SetWmClass(e.NewValue.GetValueOrDefault(null)); + + private static void OnNetWmWindowTypeChanged(AvaloniaPropertyChangedEventArgs e) => + TryGetFeature(e)?.SetNetWmWindowType(e.NewValue.GetValueOrDefault()); +} \ No newline at end of file diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 669ecd585f..368a5a8e0f 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -33,7 +33,8 @@ using Avalonia.Platform.Storage.FileIO; namespace Avalonia.X11 { - internal unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client + internal unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client, + IX11OptionsToplevelImplFeature { private readonly AvaloniaX11Platform _platform; private readonly bool _popup; @@ -183,9 +184,8 @@ namespace Avalonia.X11 _x11.Atoms.WM_DELETE_WINDOW }; XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length); - XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM, - 32, PropertyMode.Replace, new[] { _x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL }, 1); - + SetNetWmWindowType(X11NetWmWindowType.Normal); + SetWmClass(_handle, _platform.Options.WmClass); } @@ -913,6 +913,9 @@ namespace Avalonia.X11 return new BclLauncher(); } + if (featureType == typeof(IX11OptionsToplevelImplFeature)) + return this; + return null; } @@ -1251,6 +1254,13 @@ namespace Avalonia.X11 XFree(hint); } + + public void SetWmClass(string? className) + { + if (_handle == IntPtr.Zero) + return; + SetWmClass(_handle, className ?? _platform.Options.WmClass); + } public void SetMinMaxSize(Size minSize, Size maxSize) { @@ -1412,5 +1422,26 @@ namespace Avalonia.X11 public IntPtr Handle => _owner._renderHandle; public string HandleDescriptor => "XID"; } + + public void SetNetWmWindowType(X11NetWmWindowType type) + { + if(_handle == IntPtr.Zero) + return; + + var atom = type switch + { + X11NetWmWindowType.Dialog => _x11.Atoms._NET_WM_WINDOW_TYPE_DIALOG, + X11NetWmWindowType.Utility => _x11.Atoms._NET_WM_WINDOW_TYPE_UTILITY, + X11NetWmWindowType.Toolbar => _x11.Atoms._NET_WM_WINDOW_TYPE_TOOLBAR, + X11NetWmWindowType.Splash => _x11.Atoms._NET_WM_WINDOW_TYPE_SPLASH, + X11NetWmWindowType.Dock => _x11.Atoms._NET_WM_WINDOW_TYPE_DOCK, + X11NetWmWindowType.Desktop => _x11.Atoms._NET_WM_WINDOW_TYPE_DESKTOP, + _ => _x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL + }; + + XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM, + 32, PropertyMode.Replace, new[] { atom }, 1); + + } } }