diff --git a/Interop/X11.cs b/Interop/X11.cs index a414acc..6ef11da 100644 --- a/Interop/X11.cs +++ b/Interop/X11.cs @@ -88,6 +88,9 @@ internal static partial class X11 [LibraryImport(LibX11, StringMarshalling = StringMarshalling.Utf8)] public static partial int XStoreName(IntPtr display, IntPtr window, string windowName); + [LibraryImport(LibX11)] + public static partial int XSetClassHint(IntPtr display, IntPtr window, ref XClassHint classHint); + [LibraryImport(LibX11)] public static partial int XRaiseWindow(IntPtr display, IntPtr window); diff --git a/Interop/XClassHint.cs b/Interop/XClassHint.cs new file mode 100644 index 0000000..0d03728 --- /dev/null +++ b/Interop/XClassHint.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace Microsoft.Maui.Platform.Linux.Interop; + +[StructLayout(LayoutKind.Sequential)] +public struct XClassHint +{ + public IntPtr res_name; + public IntPtr res_class; +} diff --git a/LinuxApplication.cs b/LinuxApplication.cs index 6c8b4c9..8b126f2 100644 --- a/LinuxApplication.cs +++ b/LinuxApplication.cs @@ -265,6 +265,18 @@ public class LinuxApplication : IDisposable Console.WriteLine("[LinuxApplication] GTK pre-initialized for WebView support"); } + // Set application name for desktop integration (taskbar, etc.) + // Try to get the name from environment or use executable name + string? appName = Environment.GetEnvironmentVariable("APPIMAGE_NAME"); + if (string.IsNullOrEmpty(appName)) + { + appName = Path.GetFileNameWithoutExtension(Environment.ProcessPath ?? "MauiApp"); + } + string prgName = appName.Replace(" ", ""); + GtkNative.g_set_prgname(prgName); + GtkNative.g_set_application_name(appName); + Console.WriteLine($"[LinuxApplication] Set application name: {appName} (prgname: {prgName})"); + // Initialize dispatcher LinuxDispatcher.Initialize(); DispatcherProvider.SetCurrent(LinuxDispatcherProvider.Instance); diff --git a/Native/GtkNative.cs b/Native/GtkNative.cs index 3e94d08..72672ce 100644 --- a/Native/GtkNative.cs +++ b/Native/GtkNative.cs @@ -42,6 +42,12 @@ internal static class GtkNative [DllImport("libgtk-3.so.0")] public static extern void gtk_window_set_title(IntPtr window, string title); + [DllImport("libglib-2.0.so.0")] + public static extern void g_set_prgname(string name); + + [DllImport("libglib-2.0.so.0")] + public static extern void g_set_application_name(string name); + [DllImport("libgtk-3.so.0")] public static extern void gtk_window_set_default_size(IntPtr window, int width, int height); diff --git a/Window/X11Window.cs b/Window/X11Window.cs index 5004e34..adf0367 100644 --- a/Window/X11Window.cs +++ b/Window/X11Window.cs @@ -150,6 +150,9 @@ public class X11Window : IDisposable // Set window title X11.XStoreName(_display, _window, title); + // Set WM_CLASS for desktop integration (taskbar icon matching) + SetWMClass(title.Replace(" ", ""), title.Replace(" ", "")); + // Select input events X11.XSelectInput(_display, _window, XEventMask.KeyPressMask | @@ -196,6 +199,37 @@ public class X11Window : IDisposable } } + /// + /// Sets the WM_CLASS property for desktop integration. + /// This allows the desktop to match the window to its .desktop file. + /// + public void SetWMClass(string resName, string resClass) + { + IntPtr namePtr = IntPtr.Zero; + IntPtr classPtr = IntPtr.Zero; + try + { + namePtr = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(resName); + classPtr = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(resClass); + + var classHint = new XClassHint + { + res_name = namePtr, + res_class = classPtr + }; + + X11.XSetClassHint(_display, _window, ref classHint); + Console.WriteLine($"[X11Window] Set WM_CLASS: {resName}, {resClass}"); + } + finally + { + if (namePtr != IntPtr.Zero) + System.Runtime.InteropServices.Marshal.FreeHGlobal(namePtr); + if (classPtr != IntPtr.Zero) + System.Runtime.InteropServices.Marshal.FreeHGlobal(classPtr); + } + } + /// /// Sets the window icon from a file. Supports both raster images and SVG. ///