From a67f955dc60cd9e679922cbedfc060175d62b47f Mon Sep 17 00:00:00 2001 From: ognjeeen Date: Fri, 7 Aug 2026 22:55:45 +0200 Subject: [PATCH 1/5] fix shutdown session handling --- src/CodexUsageWidget/App.xaml.cs | 9 ++++ src/CodexUsageWidget/Views/MainWindow.xaml.cs | 23 +++++++--- .../Views/MainWindowCloseState.cs | 21 +++++++++ .../Views/TaskbarLabelWindow.xaml.cs | 24 ++++++++-- .../MainWindowCloseStateTests.cs | 45 +++++++++++++++++++ 5 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 src/CodexUsageWidget/Views/MainWindowCloseState.cs create mode 100644 tests/CodexUsageWidget.Tests/MainWindowCloseStateTests.cs diff --git a/src/CodexUsageWidget/App.xaml.cs b/src/CodexUsageWidget/App.xaml.cs index 65c7219..7b81e34 100644 --- a/src/CodexUsageWidget/App.xaml.cs +++ b/src/CodexUsageWidget/App.xaml.cs @@ -86,6 +86,15 @@ protected override void OnExit(ExitEventArgs e) base.OnExit(e); } + protected override void OnSessionEnding(SessionEndingCancelEventArgs e) + { + base.OnSessionEnding(e); + if (!e.Cancel && MainWindow is MainWindow window) + { + window.NotifySessionEnding(); + } + } + public void Dispose() { if (_disposed) diff --git a/src/CodexUsageWidget/Views/MainWindow.xaml.cs b/src/CodexUsageWidget/Views/MainWindow.xaml.cs index 5d0b71e..39d73ac 100644 --- a/src/CodexUsageWidget/Views/MainWindow.xaml.cs +++ b/src/CodexUsageWidget/Views/MainWindow.xaml.cs @@ -26,12 +26,13 @@ public partial class MainWindow : Window private readonly TrayIconService _trayIcon; private readonly TaskbarLabelWindow _taskbarLabel = new(); private readonly WidgetVisibilityController _widgetVisibility; + private readonly MainWindowCloseState _closeState = new(); private WidgetDisplayMode _displayMode; private WidgetDensity _density; private UsageWidgetViewModel _viewModel = UsageWidgetViewModel.Loading(); private bool _isRealActivityActive; private bool _isActivityPreviewEnabled; - private bool _allowClose; + private bool _shutdownStarted; public MainWindow( UsageMonitor usageMonitor, @@ -323,24 +324,36 @@ private void MainWindowOnDeactivated(object? sender, EventArgs e) private void ExitApplication() { - _allowClose = true; + _closeState.RequestExplicitExit(); Close(); } + internal void NotifySessionEnding() => _closeState.NotifySessionEnding(); + private void MainWindowOnClosing(object? sender, CancelEventArgs e) { - if (!_allowClose) + var closeAction = _closeState.GetCloseAction(); + if (closeAction == MainWindowCloseAction.MinimizeToTaskbar) { e.Cancel = true; SetDisplayMode(WidgetDisplayMode.TaskbarIndicator); return; } + if (_shutdownStarted) + { + return; + } + + _shutdownStarted = true; _taskbarLabel.HideLabel(); - _taskbarLabel.Close(); + _taskbarLabel.CloseLabel(); _trayIcon.Dispose(); _activityMonitor.DisposeAsync().AsTask().GetAwaiter().GetResult(); _usageMonitor.DisposeAsync().AsTask().GetAwaiter().GetResult(); - System.Windows.Application.Current.Shutdown(); + if (closeAction == MainWindowCloseAction.CloseAndShutdownApplication) + { + System.Windows.Application.Current.Shutdown(); + } } } diff --git a/src/CodexUsageWidget/Views/MainWindowCloseState.cs b/src/CodexUsageWidget/Views/MainWindowCloseState.cs new file mode 100644 index 0000000..16dc16c --- /dev/null +++ b/src/CodexUsageWidget/Views/MainWindowCloseState.cs @@ -0,0 +1,21 @@ +namespace CodexUsageWidget.Views; + +public enum MainWindowCloseAction +{ + MinimizeToTaskbar, + CloseAndShutdownApplication, + CloseForSessionEnding +} + +public sealed class MainWindowCloseState +{ + private MainWindowCloseAction _action = MainWindowCloseAction.MinimizeToTaskbar; + + public MainWindowCloseAction GetCloseAction() => _action; + + public void RequestExplicitExit() => + _action = MainWindowCloseAction.CloseAndShutdownApplication; + + public void NotifySessionEnding() => + _action = MainWindowCloseAction.CloseForSessionEnding; +} diff --git a/src/CodexUsageWidget/Views/TaskbarLabelWindow.xaml.cs b/src/CodexUsageWidget/Views/TaskbarLabelWindow.xaml.cs index fee78fb..4ccf35e 100644 --- a/src/CodexUsageWidget/Views/TaskbarLabelWindow.xaml.cs +++ b/src/CodexUsageWidget/Views/TaskbarLabelWindow.xaml.cs @@ -16,6 +16,7 @@ public partial class TaskbarLabelWindow : Window private IntPtr _windowHandle; private bool _labelRequested; private bool _isTaskActive; + private bool _isClosed; private int _visibilityUpdateQueued; public TaskbarLabelWindow() @@ -40,7 +41,13 @@ public TaskbarLabelWindow() _positionTimer.Tick += (_, _) => UpdateVisibilityAndPosition(); _windowChangeWatcher = new WindowChangeWatcher(QueueVisibilityUpdate); - Closed += (_, _) => _windowChangeWatcher.Dispose(); + Closed += (_, _) => + { + _isClosed = true; + _labelRequested = false; + _positionTimer.Stop(); + _windowChangeWatcher.Dispose(); + }; } public event EventHandler? OpenRequested; @@ -75,7 +82,18 @@ public void HideLabel() { _labelRequested = false; _positionTimer.Stop(); - Hide(); + if (!_isClosed) + { + Hide(); + } + } + + public void CloseLabel() + { + if (!_isClosed) + { + Close(); + } } public void SetActivityState(bool isActive) @@ -115,7 +133,7 @@ private void Reposition() private void UpdateVisibilityAndPosition() { - if (!_labelRequested || _windowHandle == IntPtr.Zero) + if (_isClosed || !_labelRequested || _windowHandle == IntPtr.Zero) { return; } diff --git a/tests/CodexUsageWidget.Tests/MainWindowCloseStateTests.cs b/tests/CodexUsageWidget.Tests/MainWindowCloseStateTests.cs new file mode 100644 index 0000000..618bbd7 --- /dev/null +++ b/tests/CodexUsageWidget.Tests/MainWindowCloseStateTests.cs @@ -0,0 +1,45 @@ +using CodexUsageWidget.Views; + +namespace CodexUsageWidget.Tests; + +public sealed class MainWindowCloseStateTests +{ + [Fact] + public void OrdinaryWindowCloseMinimizesToTaskbar() + { + var state = new MainWindowCloseState(); + + Assert.Equal(MainWindowCloseAction.MinimizeToTaskbar, state.GetCloseAction()); + } + + [Fact] + public void ExplicitExitClosesAndShutsDownApplication() + { + var state = new MainWindowCloseState(); + + state.RequestExplicitExit(); + + Assert.Equal(MainWindowCloseAction.CloseAndShutdownApplication, state.GetCloseAction()); + } + + [Fact] + public void WindowsSessionEndingClosesWithoutNestedApplicationShutdown() + { + var state = new MainWindowCloseState(); + + state.NotifySessionEnding(); + + Assert.Equal(MainWindowCloseAction.CloseForSessionEnding, state.GetCloseAction()); + } + + [Fact] + public void WindowsSessionEndingTakesPrecedenceOverConcurrentExplicitExit() + { + var state = new MainWindowCloseState(); + state.RequestExplicitExit(); + + state.NotifySessionEnding(); + + Assert.Equal(MainWindowCloseAction.CloseForSessionEnding, state.GetCloseAction()); + } +} From daa6596d0a7323e297d82ae574bbf46ca85b2057 Mon Sep 17 00:00:00 2001 From: ognjeeen Date: Fri, 7 Aug 2026 23:42:06 +0200 Subject: [PATCH 2/5] Redesign tray and application icons --- .../Assets/CodexUsageWidget.ico | Bin 0 -> 30035 bytes .../Assets/CodexUsageWidget.svg | 12 ++++ src/CodexUsageWidget/CodexUsageWidget.csproj | 1 + .../Windows/UsageIconFactory.cs | 63 ++++++++++-------- 4 files changed, 49 insertions(+), 27 deletions(-) create mode 100644 src/CodexUsageWidget/Assets/CodexUsageWidget.ico create mode 100644 src/CodexUsageWidget/Assets/CodexUsageWidget.svg diff --git a/src/CodexUsageWidget/Assets/CodexUsageWidget.ico b/src/CodexUsageWidget/Assets/CodexUsageWidget.ico new file mode 100644 index 0000000000000000000000000000000000000000..69f27adb3952de1e491635cf5447f52f7100b6aa GIT binary patch literal 30035 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 literal 0 HcmV?d00001 diff --git a/src/CodexUsageWidget/Assets/CodexUsageWidget.svg b/src/CodexUsageWidget/Assets/CodexUsageWidget.svg new file mode 100644 index 0000000..daaf2b9 --- /dev/null +++ b/src/CodexUsageWidget/Assets/CodexUsageWidget.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/src/CodexUsageWidget/CodexUsageWidget.csproj b/src/CodexUsageWidget/CodexUsageWidget.csproj index 26767a7..917d03e 100644 --- a/src/CodexUsageWidget/CodexUsageWidget.csproj +++ b/src/CodexUsageWidget/CodexUsageWidget.csproj @@ -9,6 +9,7 @@ CodexUsageWidget CodexUsageWidget app.manifest + Assets\CodexUsageWidget.ico 1.2.2 Ognjen Marinković ognjeeen diff --git a/src/CodexUsageWidget/Infrastructure/Windows/UsageIconFactory.cs b/src/CodexUsageWidget/Infrastructure/Windows/UsageIconFactory.cs index eb6bb4f..3efbf1e 100644 --- a/src/CodexUsageWidget/Infrastructure/Windows/UsageIconFactory.cs +++ b/src/CodexUsageWidget/Infrastructure/Windows/UsageIconFactory.cs @@ -1,6 +1,4 @@ using System.Drawing.Drawing2D; -using System.Drawing.Text; -using System.Globalization; using System.Runtime.InteropServices; namespace CodexUsageWidget.Infrastructure.Windows; @@ -11,11 +9,13 @@ public static System.Drawing.Icon Create(double? remainingPercent) { using var bitmap = new System.Drawing.Bitmap(64, 64); using var graphics = System.Drawing.Graphics.FromImage(bitmap); - graphics.SmoothingMode = SmoothingMode.AntiAlias; - graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.SmoothingMode = SmoothingMode.HighQuality; var indicatorColor = remainingPercent switch { + null => System.Drawing.Color.FromArgb(151, 163, 182), <= 10 => System.Drawing.Color.FromArgb(240, 112, 112), <= 25 => System.Drawing.Color.FromArgb(240, 179, 94), _ => System.Drawing.Color.FromArgb(101, 216, 146) @@ -23,34 +23,43 @@ public static System.Drawing.Icon Create(double? remainingPercent) using var backgroundBrush = new System.Drawing.SolidBrush( System.Drawing.Color.FromArgb(22, 29, 39)); - using var borderPen = new System.Drawing.Pen(indicatorColor, 6f); - graphics.FillEllipse(backgroundBrush, 3, 3, 58, 58); - graphics.DrawEllipse(borderPen, 6, 6, 52, 52); + graphics.FillEllipse(backgroundBrush, 1, 1, 62, 62); - DrawPercentage(graphics, remainingPercent); + DrawTerminalPrompt(graphics); + DrawStatusIndicator(graphics, indicatorColor); return CloneIcon(bitmap); } - private static void DrawPercentage(System.Drawing.Graphics graphics, double? remainingPercent) + private static void DrawTerminalPrompt(System.Drawing.Graphics graphics) { - var text = remainingPercent is null - ? "?" - : Math.Round(Math.Clamp(remainingPercent.Value, 0d, 100d)) - .ToString("0", CultureInfo.InvariantCulture); - var fontSize = text.Length >= 3 ? 18f : 24f; - using var font = new System.Drawing.Font( - "Segoe UI", - fontSize, - System.Drawing.FontStyle.Bold, - System.Drawing.GraphicsUnit.Pixel); - using var textBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White); - var textSize = graphics.MeasureString(text, font); - graphics.DrawString( - text, - font, - textBrush, - (64f - textSize.Width) / 2f, - (64f - textSize.Height) / 2f - 1f); + using var promptPen = new System.Drawing.Pen( + System.Drawing.Color.FromArgb(246, 248, 252), + 7f) + { + StartCap = LineCap.Round, + EndCap = LineCap.Round, + LineJoin = LineJoin.Round + }; + + graphics.DrawLines( + promptPen, + [ + new System.Drawing.PointF(19f, 18f), + new System.Drawing.PointF(34f, 32f), + new System.Drawing.PointF(19f, 46f) + ]); + } + + private static void DrawStatusIndicator( + System.Drawing.Graphics graphics, + System.Drawing.Color indicatorColor) + { + using var outlineBrush = new System.Drawing.SolidBrush( + System.Drawing.Color.FromArgb(22, 29, 39)); + using var indicatorBrush = new System.Drawing.SolidBrush(indicatorColor); + + graphics.FillEllipse(outlineBrush, 37, 37, 26, 26); + graphics.FillEllipse(indicatorBrush, 41, 41, 18, 18); } private static System.Drawing.Icon CloneIcon(System.Drawing.Bitmap bitmap) From 8a4fe701fdc529685884be7b662f78acf9a06f3c Mon Sep 17 00:00:00 2001 From: ognjeeen Date: Fri, 7 Aug 2026 23:47:39 +0200 Subject: [PATCH 3/5] Add project icon for T3 Code --- assets/icon.svg | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 assets/icon.svg diff --git a/assets/icon.svg b/assets/icon.svg new file mode 100644 index 0000000..daaf2b9 --- /dev/null +++ b/assets/icon.svg @@ -0,0 +1,12 @@ + + + + + + From be664b8761605fd614b621743912eb37ae69f4dc Mon Sep 17 00:00:00 2001 From: ognjeeen Date: Sun, 9 Aug 2026 10:01:00 +0200 Subject: [PATCH 4/5] Redesign taskbar context menu --- .../Views/Resources/TaskbarMenuTheme.xaml | 148 ++++++++++++++++++ .../Views/TaskbarLabelWindow.xaml | 58 ++++++- 2 files changed, 199 insertions(+), 7 deletions(-) create mode 100644 src/CodexUsageWidget/Views/Resources/TaskbarMenuTheme.xaml diff --git a/src/CodexUsageWidget/Views/Resources/TaskbarMenuTheme.xaml b/src/CodexUsageWidget/Views/Resources/TaskbarMenuTheme.xaml new file mode 100644 index 0000000..4ce7228 --- /dev/null +++ b/src/CodexUsageWidget/Views/Resources/TaskbarMenuTheme.xaml @@ -0,0 +1,148 @@ + + + + + + + + + + + diff --git a/src/CodexUsageWidget/Views/TaskbarLabelWindow.xaml b/src/CodexUsageWidget/Views/TaskbarLabelWindow.xaml index 0011d96..8f3dd7d 100644 --- a/src/CodexUsageWidget/Views/TaskbarLabelWindow.xaml +++ b/src/CodexUsageWidget/Views/TaskbarLabelWindow.xaml @@ -14,6 +14,13 @@ Focusable="False" SnapsToDevicePixels="True" UseLayoutRounding="True"> + + + + + + + - - - - + + +