From b4262368e8072ab46964e5af35009e85c1ad824c Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Tue, 6 May 2025 17:58:25 +0200 Subject: [PATCH 1/5] WIP --- App/Controls/PInvoke.cs | 39 +++++++++++++++++++++++ App/Controls/TitleBarIcon.cs | 47 ++++++++++++++++++++++++++++ App/Views/FileSyncListWindow.xaml.cs | 10 ++++++ 3 files changed, 96 insertions(+) create mode 100644 App/Controls/PInvoke.cs create mode 100644 App/Controls/TitleBarIcon.cs diff --git a/App/Controls/PInvoke.cs b/App/Controls/PInvoke.cs new file mode 100644 index 0000000..40ebb9f --- /dev/null +++ b/App/Controls/PInvoke.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Coder.Desktop.App.Controls +{ + internal static class PInvoke + { + public const uint IMAGE_ICON = 1; + public const uint LR_LOADFROMFILE = 0x00000010; + public const uint WM_SETICON = 0x0080; + public const int ICON_SMALL = 0; + public const int ICON_BIG = 1; + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern IntPtr LoadImage( + IntPtr hInst, + string lpszName, + uint uType, + int cxDesired, + int cyDesired, + uint fuLoad); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SendMessage( + IntPtr hWnd, + uint Msg, + IntPtr wParam, + IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DestroyIcon(IntPtr hIcon); + + } +} diff --git a/App/Controls/TitleBarIcon.cs b/App/Controls/TitleBarIcon.cs new file mode 100644 index 0000000..1246dc7 --- /dev/null +++ b/App/Controls/TitleBarIcon.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.UI; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using WinRT.Interop; + +namespace Coder.Desktop.App.Controls +{ + public static class TitleBarIcon + { + public static void InjectIcon(Window window) + { + var hwnd = WindowNative.GetWindowHandle(window); + var windowId = Win32Interop.GetWindowIdFromWindow(hwnd); + AppWindow.GetFromWindowId(windowId).SetIcon("coder.ico"); + } + + public static void SetTitlebarIcon(Window window) + { + var hwnd = WindowNative.GetWindowHandle(window); + + string iconPathDark = "Assets/coder_icon_32_dark.ico"; + string iconPathLight = "Assets/coder_icon_32_light.ico"; + + var hIconDark = PInvoke.LoadImage( + IntPtr.Zero, + iconPathDark, + PInvoke.IMAGE_ICON, + 0, + 0, + PInvoke.LR_LOADFROMFILE + ); + + var hIconLight = PInvoke.LoadImage( + IntPtr.Zero, + iconPathLight, + PInvoke.IMAGE_ICON, + 0, + 0, + PInvoke.LR_LOADFROMFILE + ); + + PInvoke.SendMessage(hwnd, PInvoke.WM_SETICON, (IntPtr)PInvoke.ICON_SMALL, hIconDark); + PInvoke.SendMessage(hwnd, PInvoke.WM_SETICON, (IntPtr)PInvoke.ICON_BIG, hIconLight); + } + } +} diff --git a/App/Views/FileSyncListWindow.xaml.cs b/App/Views/FileSyncListWindow.xaml.cs index 428363b..0ac7da0 100644 --- a/App/Views/FileSyncListWindow.xaml.cs +++ b/App/Views/FileSyncListWindow.xaml.cs @@ -1,7 +1,14 @@ using Coder.Desktop.App.ViewModels; using Coder.Desktop.App.Views.Pages; +using Microsoft.UI.Xaml; +using Microsoft.UI; using Microsoft.UI.Xaml.Media; +using WinRT.Interop; using WinUIEx; +using Microsoft.UI.Windowing; +using System; +using System.IO; +using Coder.Desktop.App.Controls; namespace Coder.Desktop.App.Views; @@ -18,6 +25,9 @@ public FileSyncListWindow(FileSyncListViewModel viewModel) ViewModel.Initialize(this, DispatcherQueue); RootFrame.Content = new FileSyncListMainPage(ViewModel); + TitleBarIcon.SetTitlebarIcon(this); + this.CenterOnScreen(); } + } From 4d606b3b74fc1df00c8d6d22736b27670555b1fa Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Tue, 6 May 2025 22:01:21 +0200 Subject: [PATCH 2/5] implemented light/dark icon set with small/big icon differences --- App/App.xaml.cs | 2 + App/Controls/TitleBarIcon.cs | 54 +++++++++++++++++++------ App/Views/DirectoryPickerWindow.xaml.cs | 3 ++ App/Views/FileSyncListWindow.xaml.cs | 4 +- App/Views/SignInWindow.xaml.cs | 1 + 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/App/App.xaml.cs b/App/App.xaml.cs index 2cdee97..1d74e6e 100644 --- a/App/App.xaml.cs +++ b/App/App.xaml.cs @@ -22,6 +22,7 @@ using Serilog; using LaunchActivatedEventArgs = Microsoft.UI.Xaml.LaunchActivatedEventArgs; using Microsoft.Windows.AppNotifications; +using Coder.Desktop.App.Controls; namespace Coder.Desktop.App; @@ -106,6 +107,7 @@ public async Task ExitApplication() { _logger.LogDebug("exiting app"); _handleWindowClosed = false; + TitleBarIcon.DisposeIconsManager(); Exit(); var syncController = _services.GetRequiredService(); await syncController.DisposeAsync(); diff --git a/App/Controls/TitleBarIcon.cs b/App/Controls/TitleBarIcon.cs index 1246dc7..6aca66d 100644 --- a/App/Controls/TitleBarIcon.cs +++ b/App/Controls/TitleBarIcon.cs @@ -2,27 +2,40 @@ using Microsoft.UI; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls.Primitives; using WinRT.Interop; namespace Coder.Desktop.App.Controls { public static class TitleBarIcon { - public static void InjectIcon(Window window) - { - var hwnd = WindowNative.GetWindowHandle(window); - var windowId = Win32Interop.GetWindowIdFromWindow(hwnd); - AppWindow.GetFromWindowId(windowId).SetIcon("coder.ico"); - } + private static readonly Lazy _iconsManager = new(() => new IconsManager()); public static void SetTitlebarIcon(Window window) { var hwnd = WindowNative.GetWindowHandle(window); + var theme = window.Content is FrameworkElement fe + ? fe.ActualTheme + : ElementTheme.Default; + _iconsManager.Value.SetTitlebarIcon(hwnd, theme == ElementTheme.Dark); + } - string iconPathDark = "Assets/coder_icon_32_dark.ico"; - string iconPathLight = "Assets/coder_icon_32_light.ico"; + public static void DisposeIconsManager() + { + _iconsManager.Value.Dispose(); + } + } - var hIconDark = PInvoke.LoadImage( +#pragma warning disable CsWinRT1028 // Class does not need to be partial, it's an SDK bug: + public class IconsManager : IDisposable +#pragma warning restore CsWinRT1028 // Class is not marked partial + { + private nint hIconDark; + private nint hIconLight; + private const string iconPathDark = "Assets/coder_icon_32_dark.ico"; + private const string iconPathLight = "Assets/coder_icon_32_light.ico"; + public IconsManager() { + hIconDark = PInvoke.LoadImage( IntPtr.Zero, iconPathDark, PInvoke.IMAGE_ICON, @@ -30,8 +43,7 @@ public static void SetTitlebarIcon(Window window) 0, PInvoke.LR_LOADFROMFILE ); - - var hIconLight = PInvoke.LoadImage( + hIconLight = PInvoke.LoadImage( IntPtr.Zero, iconPathLight, PInvoke.IMAGE_ICON, @@ -39,9 +51,25 @@ public static void SetTitlebarIcon(Window window) 0, PInvoke.LR_LOADFROMFILE ); + } - PInvoke.SendMessage(hwnd, PInvoke.WM_SETICON, (IntPtr)PInvoke.ICON_SMALL, hIconDark); - PInvoke.SendMessage(hwnd, PInvoke.WM_SETICON, (IntPtr)PInvoke.ICON_BIG, hIconLight); + public void SetTitlebarIcon(nint windowHandle, bool isDarkTheme) + { + PInvoke.SendMessage(windowHandle, PInvoke.WM_SETICON, (IntPtr)PInvoke.ICON_SMALL, isDarkTheme ? hIconDark : hIconLight); + PInvoke.SendMessage(windowHandle, PInvoke.WM_SETICON, (IntPtr)PInvoke.ICON_BIG, isDarkTheme ? hIconLight : hIconDark); + } + + public void Dispose() + { + if (hIconDark != IntPtr.Zero) + { + PInvoke.DestroyIcon(hIconDark); + } + if (hIconLight != IntPtr.Zero) + { + PInvoke.DestroyIcon(hIconLight); + } + GC.SuppressFinalize(this); } } } diff --git a/App/Views/DirectoryPickerWindow.xaml.cs b/App/Views/DirectoryPickerWindow.xaml.cs index 6ed5f43..bbd82ec 100644 --- a/App/Views/DirectoryPickerWindow.xaml.cs +++ b/App/Views/DirectoryPickerWindow.xaml.cs @@ -8,6 +8,7 @@ using Microsoft.UI.Xaml.Media; using WinRT.Interop; using WinUIEx; +using Coder.Desktop.App.Controls; namespace Coder.Desktop.App.Views; @@ -16,6 +17,8 @@ public sealed partial class DirectoryPickerWindow : WindowEx public DirectoryPickerWindow(DirectoryPickerViewModel viewModel) { InitializeComponent(); + TitleBarIcon.SetTitlebarIcon(this); + SystemBackdrop = new DesktopAcrylicBackdrop(); viewModel.Initialize(this, DispatcherQueue); diff --git a/App/Views/FileSyncListWindow.xaml.cs b/App/Views/FileSyncListWindow.xaml.cs index 0ac7da0..c8a8b1f 100644 --- a/App/Views/FileSyncListWindow.xaml.cs +++ b/App/Views/FileSyncListWindow.xaml.cs @@ -20,13 +20,13 @@ public FileSyncListWindow(FileSyncListViewModel viewModel) { ViewModel = viewModel; InitializeComponent(); + TitleBarIcon.SetTitlebarIcon(this); + SystemBackdrop = new DesktopAcrylicBackdrop(); ViewModel.Initialize(this, DispatcherQueue); RootFrame.Content = new FileSyncListMainPage(ViewModel); - TitleBarIcon.SetTitlebarIcon(this); - this.CenterOnScreen(); } diff --git a/App/Views/SignInWindow.xaml.cs b/App/Views/SignInWindow.xaml.cs index 3fe4b5c..ba69704 100644 --- a/App/Views/SignInWindow.xaml.cs +++ b/App/Views/SignInWindow.xaml.cs @@ -22,6 +22,7 @@ public sealed partial class SignInWindow : Window public SignInWindow(SignInViewModel viewModel) { InitializeComponent(); + TitleBarIcon.SetTitlebarIcon(this); SystemBackdrop = new DesktopAcrylicBackdrop(); RootFrame.SizeChanged += RootFrame_SizeChanged; From 9c08ce51b08ad599a3d70127f56597930d090ed2 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Tue, 6 May 2025 22:09:14 +0200 Subject: [PATCH 3/5] simplified and moved back to regular icon --- App/App.xaml.cs | 1 - App/Controls/PInvoke.cs | 39 ----------------------- App/Controls/TitleBarIcon.cs | 60 ++---------------------------------- 3 files changed, 2 insertions(+), 98 deletions(-) delete mode 100644 App/Controls/PInvoke.cs diff --git a/App/App.xaml.cs b/App/App.xaml.cs index 1d74e6e..87519c6 100644 --- a/App/App.xaml.cs +++ b/App/App.xaml.cs @@ -107,7 +107,6 @@ public async Task ExitApplication() { _logger.LogDebug("exiting app"); _handleWindowClosed = false; - TitleBarIcon.DisposeIconsManager(); Exit(); var syncController = _services.GetRequiredService(); await syncController.DisposeAsync(); diff --git a/App/Controls/PInvoke.cs b/App/Controls/PInvoke.cs deleted file mode 100644 index 40ebb9f..0000000 --- a/App/Controls/PInvoke.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; - -namespace Coder.Desktop.App.Controls -{ - internal static class PInvoke - { - public const uint IMAGE_ICON = 1; - public const uint LR_LOADFROMFILE = 0x00000010; - public const uint WM_SETICON = 0x0080; - public const int ICON_SMALL = 0; - public const int ICON_BIG = 1; - - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] - public static extern IntPtr LoadImage( - IntPtr hInst, - string lpszName, - uint uType, - int cxDesired, - int cyDesired, - uint fuLoad); - - [DllImport("user32.dll", SetLastError = true)] - public static extern IntPtr SendMessage( - IntPtr hWnd, - uint Msg, - IntPtr wParam, - IntPtr lParam); - - [DllImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool DestroyIcon(IntPtr hIcon); - - } -} diff --git a/App/Controls/TitleBarIcon.cs b/App/Controls/TitleBarIcon.cs index 6aca66d..ddc3175 100644 --- a/App/Controls/TitleBarIcon.cs +++ b/App/Controls/TitleBarIcon.cs @@ -9,67 +9,11 @@ namespace Coder.Desktop.App.Controls { public static class TitleBarIcon { - private static readonly Lazy _iconsManager = new(() => new IconsManager()); - public static void SetTitlebarIcon(Window window) { var hwnd = WindowNative.GetWindowHandle(window); - var theme = window.Content is FrameworkElement fe - ? fe.ActualTheme - : ElementTheme.Default; - _iconsManager.Value.SetTitlebarIcon(hwnd, theme == ElementTheme.Dark); - } - - public static void DisposeIconsManager() - { - _iconsManager.Value.Dispose(); - } - } - -#pragma warning disable CsWinRT1028 // Class does not need to be partial, it's an SDK bug: - public class IconsManager : IDisposable -#pragma warning restore CsWinRT1028 // Class is not marked partial - { - private nint hIconDark; - private nint hIconLight; - private const string iconPathDark = "Assets/coder_icon_32_dark.ico"; - private const string iconPathLight = "Assets/coder_icon_32_light.ico"; - public IconsManager() { - hIconDark = PInvoke.LoadImage( - IntPtr.Zero, - iconPathDark, - PInvoke.IMAGE_ICON, - 0, - 0, - PInvoke.LR_LOADFROMFILE - ); - hIconLight = PInvoke.LoadImage( - IntPtr.Zero, - iconPathLight, - PInvoke.IMAGE_ICON, - 0, - 0, - PInvoke.LR_LOADFROMFILE - ); - } - - public void SetTitlebarIcon(nint windowHandle, bool isDarkTheme) - { - PInvoke.SendMessage(windowHandle, PInvoke.WM_SETICON, (IntPtr)PInvoke.ICON_SMALL, isDarkTheme ? hIconDark : hIconLight); - PInvoke.SendMessage(windowHandle, PInvoke.WM_SETICON, (IntPtr)PInvoke.ICON_BIG, isDarkTheme ? hIconLight : hIconDark); - } - - public void Dispose() - { - if (hIconDark != IntPtr.Zero) - { - PInvoke.DestroyIcon(hIconDark); - } - if (hIconLight != IntPtr.Zero) - { - PInvoke.DestroyIcon(hIconLight); - } - GC.SuppressFinalize(this); + var windowId = Win32Interop.GetWindowIdFromWindow(hwnd); + AppWindow.GetFromWindowId(windowId).SetIcon("coder.ico"); } } } From 680b716e5cac500c1ed742c13bb886e8b26491fd Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Tue, 6 May 2025 22:15:42 +0200 Subject: [PATCH 4/5] removed unecessary imports --- App/App.xaml.cs | 1 - App/Views/FileSyncListWindow.xaml.cs | 6 ------ 2 files changed, 7 deletions(-) diff --git a/App/App.xaml.cs b/App/App.xaml.cs index 87519c6..2cdee97 100644 --- a/App/App.xaml.cs +++ b/App/App.xaml.cs @@ -22,7 +22,6 @@ using Serilog; using LaunchActivatedEventArgs = Microsoft.UI.Xaml.LaunchActivatedEventArgs; using Microsoft.Windows.AppNotifications; -using Coder.Desktop.App.Controls; namespace Coder.Desktop.App; diff --git a/App/Views/FileSyncListWindow.xaml.cs b/App/Views/FileSyncListWindow.xaml.cs index c8a8b1f..01e6aa3 100644 --- a/App/Views/FileSyncListWindow.xaml.cs +++ b/App/Views/FileSyncListWindow.xaml.cs @@ -1,13 +1,7 @@ using Coder.Desktop.App.ViewModels; using Coder.Desktop.App.Views.Pages; -using Microsoft.UI.Xaml; -using Microsoft.UI; using Microsoft.UI.Xaml.Media; -using WinRT.Interop; using WinUIEx; -using Microsoft.UI.Windowing; -using System; -using System.IO; using Coder.Desktop.App.Controls; namespace Coder.Desktop.App.Views; From 621d3cd38622fbb6bca59dab9e1e32ac27eb2cb1 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Wed, 7 May 2025 12:45:26 +0200 Subject: [PATCH 5/5] moved TitleBarIcon from Controls to Utils --- App/{Controls => Utils}/TitleBarIcon.cs | 2 +- App/Views/DirectoryPickerWindow.xaml.cs | 2 +- App/Views/FileSyncListWindow.xaml.cs | 2 +- App/Views/SignInWindow.xaml.cs | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) rename App/{Controls => Utils}/TitleBarIcon.cs (92%) diff --git a/App/Controls/TitleBarIcon.cs b/App/Utils/TitleBarIcon.cs similarity index 92% rename from App/Controls/TitleBarIcon.cs rename to App/Utils/TitleBarIcon.cs index ddc3175..3efc81d 100644 --- a/App/Controls/TitleBarIcon.cs +++ b/App/Utils/TitleBarIcon.cs @@ -5,7 +5,7 @@ using Microsoft.UI.Xaml.Controls.Primitives; using WinRT.Interop; -namespace Coder.Desktop.App.Controls +namespace Coder.Desktop.App.Utils { public static class TitleBarIcon { diff --git a/App/Views/DirectoryPickerWindow.xaml.cs b/App/Views/DirectoryPickerWindow.xaml.cs index bbd82ec..2409d4b 100644 --- a/App/Views/DirectoryPickerWindow.xaml.cs +++ b/App/Views/DirectoryPickerWindow.xaml.cs @@ -8,7 +8,7 @@ using Microsoft.UI.Xaml.Media; using WinRT.Interop; using WinUIEx; -using Coder.Desktop.App.Controls; +using Coder.Desktop.App.Utils; namespace Coder.Desktop.App.Views; diff --git a/App/Views/FileSyncListWindow.xaml.cs b/App/Views/FileSyncListWindow.xaml.cs index 01e6aa3..fb899cc 100644 --- a/App/Views/FileSyncListWindow.xaml.cs +++ b/App/Views/FileSyncListWindow.xaml.cs @@ -2,7 +2,7 @@ using Coder.Desktop.App.Views.Pages; using Microsoft.UI.Xaml.Media; using WinUIEx; -using Coder.Desktop.App.Controls; +using Coder.Desktop.App.Utils; namespace Coder.Desktop.App.Views; diff --git a/App/Views/SignInWindow.xaml.cs b/App/Views/SignInWindow.xaml.cs index ba69704..fb933c7 100644 --- a/App/Views/SignInWindow.xaml.cs +++ b/App/Views/SignInWindow.xaml.cs @@ -6,6 +6,7 @@ using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; +using Coder.Desktop.App.Utils; namespace Coder.Desktop.App.Views;