Browse Source

[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 <[email protected]>

---------

Co-authored-by: Max Katz <[email protected]>
Nikita Tsukanov 1 year ago
parent
commit
8adb67a3d5

+ 21 - 0
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);
+}

+ 38 - 0
src/Avalonia.Controls/Platform/X11Properties.cs

@@ -0,0 +1,38 @@
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+using Avalonia.Reactive;
+
+namespace Avalonia.Controls;
+
+/// <summary>
+/// Set of X11 specific properties and events that allow deeper customization of the application per platform.
+/// </summary>
+public class X11Properties
+{
+    public static readonly AttachedProperty<X11NetWmWindowType> NetWmWindowTypeProperty =
+        AvaloniaProperty.RegisterAttached<X11Properties, Window, X11NetWmWindowType>("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<string?> WmClassProperty =
+        AvaloniaProperty.RegisterAttached<X11Properties, Window, string?>("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<IX11OptionsToplevelImplFeature>();
+    
+    private static void OnWmClassChanged(AvaloniaPropertyChangedEventArgs<string?> e) => 
+        TryGetFeature(e)?.SetWmClass(e.NewValue.GetValueOrDefault(null));
+
+    private static void OnNetWmWindowTypeChanged(AvaloniaPropertyChangedEventArgs<X11NetWmWindowType> e) =>
+        TryGetFeature(e)?.SetNetWmWindowType(e.NewValue.GetValueOrDefault());
+}

+ 35 - 4
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);
+
+        }
     }
 }