2026-02-27 07:24:23 -06:00
// Copyright (c) Microsoft Corporation
2025-06-05 22:29:18 +02:00
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis ;
using System.Runtime.InteropServices ;
using CommunityToolkit.Mvvm.Messaging ;
2025-08-13 13:42:52 -05:00
using Microsoft.CmdPal.UI.Messages ;
2025-06-05 22:29:18 +02:00
using Microsoft.CmdPal.UI.ViewModels ;
2025-08-13 13:42:52 -05:00
using Microsoft.CmdPal.UI.ViewModels.Messages ;
CmdPal: Extract persistence services from SettingsModel and AppStateModel (#46312)
## Summary of the Pull Request
Extracts persistence (load/save) logic from `SettingsModel` and
`AppStateModel` into dedicated service classes, following the
single-responsibility principle. Consumers now interact with
`ISettingsService` and `IAppStateService` instead of receiving raw model
objects through DI.
**New services introduced:**
- `IPersistenceService` / `PersistenceService` — generic `Load<T>` /
`Save<T>` with AOT-compatible `JsonTypeInfo<T>`, ensures target
directory exists before writing
- `ISettingsService` / `SettingsService` — loads settings on
construction, runs migrations, exposes `Settings` property and
`SettingsChanged` event
- `IAppStateService` / `AppStateService` — loads state on construction,
exposes `State` property and `StateChanged` event
**Key changes:**
- `SettingsModel` and `AppStateModel` are now pure data models — all
file I/O, migration, and directory management removed
- Raw `SettingsModel` and `AppStateModel` removed from DI container;
consumers receive the appropriate service
- `IApplicationInfoService.ConfigDirectory` injected into services for
config path resolution (no more hardcoded `Utilities.BaseSettingsPath`)
- ~30 consumer files updated across `Microsoft.CmdPal.UI.ViewModels` and
`Microsoft.CmdPal.UI` projects
- All `#pragma warning disable SA1300` suppressions removed —
convenience accessors replaced with direct `_settingsService.Settings` /
`_appStateService.State` access
- Namespace prefixes (`Services.ISettingsService`) replaced with proper
`using` directives
## PR Checklist
- [ ] **Communication:** I've discussed this with core contributors
already.
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** N/A — no end-user-facing strings changed
- [ ] **Dev docs:** N/A — internal refactor, no public API changes
- [ ] **New binaries:** N/A — no new binaries introduced
## Detailed Description of the Pull Request / Additional comments
### Architecture
Services are registered as singletons in `App.xaml.cs`:
```csharp
services.AddSingleton<IPersistenceService, PersistenceService>();
services.AddSingleton<ISettingsService, SettingsService>();
services.AddSingleton<IAppStateService, AppStateService>();
```
`PersistenceService.Save<T>` writes the serialized model directly to
disk, creating the target directory if it doesn't exist. It also does
not attempt to merge existing and new settings/state. `SettingsService`
runs hotkey migrations on load and raises `SettingsChanged` after saves.
`AppStateService` always raises `StateChanged` after saves.
### Files changed (41 files, +1169/−660)
| Area | Files | What changed |
|------|-------|-------------|
| New services | `Services/IPersistenceService.cs`,
`PersistenceService.cs`, `ISettingsService.cs`, `SettingsService.cs`,
`IAppStateService.cs`, `AppStateService.cs` | New service interfaces and
implementations |
| Models | `SettingsModel.cs`, `AppStateModel.cs` | Stripped to pure
data bags |
| DI | `App.xaml.cs` | Service registration, removed raw model DI |
| ViewModels | 12 files | Constructor injection of services |
| UI | 10 files | Service injection replacing model access |
| Settings | `DockSettings.cs` | `Colors.Transparent` replaced with
struct literal to avoid WinUI3 COM dependency |
| Tests | `PersistenceServiceTests.cs`, `SettingsServiceTests.cs`,
`AppStateServiceTests.cs` | 38 unit tests covering all three services |
| Config | `.gitignore` | Added `.squad/`, `.github/agents/` exclusions
|
## Validation Steps Performed
- Built `Microsoft.CmdPal.UI` with MSBuild (x64/Debug) — exit code 0,
clean build
- Ran 38 unit tests via `vstest.console.exe` — all passing
- Verified no remaining `#pragma warning disable SA1300` blocks
- Verified no remaining `Services.` namespace prefixes
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-20 18:58:27 -05:00
using Microsoft.CmdPal.UI.ViewModels.Services ;
2025-06-05 22:29:18 +02:00
using Microsoft.UI.Xaml ;
using Windows.Win32 ;
using Windows.Win32.Foundation ;
using Windows.Win32.UI.Shell ;
using Windows.Win32.UI.WindowsAndMessaging ;
using WinRT.Interop ;
using RS_ = Microsoft . CmdPal . UI . Helpers . ResourceLoaderInstance ;
namespace Microsoft.CmdPal.UI.Helpers ;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_*")]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_*")]
internal sealed partial class TrayIconService
{
private const uint MY_NOTIFY_ID = 1000 ;
private const uint WM_TRAY_ICON = PInvoke . WM_USER + 1 ;
CmdPal: Extract persistence services from SettingsModel and AppStateModel (#46312)
## Summary of the Pull Request
Extracts persistence (load/save) logic from `SettingsModel` and
`AppStateModel` into dedicated service classes, following the
single-responsibility principle. Consumers now interact with
`ISettingsService` and `IAppStateService` instead of receiving raw model
objects through DI.
**New services introduced:**
- `IPersistenceService` / `PersistenceService` — generic `Load<T>` /
`Save<T>` with AOT-compatible `JsonTypeInfo<T>`, ensures target
directory exists before writing
- `ISettingsService` / `SettingsService` — loads settings on
construction, runs migrations, exposes `Settings` property and
`SettingsChanged` event
- `IAppStateService` / `AppStateService` — loads state on construction,
exposes `State` property and `StateChanged` event
**Key changes:**
- `SettingsModel` and `AppStateModel` are now pure data models — all
file I/O, migration, and directory management removed
- Raw `SettingsModel` and `AppStateModel` removed from DI container;
consumers receive the appropriate service
- `IApplicationInfoService.ConfigDirectory` injected into services for
config path resolution (no more hardcoded `Utilities.BaseSettingsPath`)
- ~30 consumer files updated across `Microsoft.CmdPal.UI.ViewModels` and
`Microsoft.CmdPal.UI` projects
- All `#pragma warning disable SA1300` suppressions removed —
convenience accessors replaced with direct `_settingsService.Settings` /
`_appStateService.State` access
- Namespace prefixes (`Services.ISettingsService`) replaced with proper
`using` directives
## PR Checklist
- [ ] **Communication:** I've discussed this with core contributors
already.
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** N/A — no end-user-facing strings changed
- [ ] **Dev docs:** N/A — internal refactor, no public API changes
- [ ] **New binaries:** N/A — no new binaries introduced
## Detailed Description of the Pull Request / Additional comments
### Architecture
Services are registered as singletons in `App.xaml.cs`:
```csharp
services.AddSingleton<IPersistenceService, PersistenceService>();
services.AddSingleton<ISettingsService, SettingsService>();
services.AddSingleton<IAppStateService, AppStateService>();
```
`PersistenceService.Save<T>` writes the serialized model directly to
disk, creating the target directory if it doesn't exist. It also does
not attempt to merge existing and new settings/state. `SettingsService`
runs hotkey migrations on load and raises `SettingsChanged` after saves.
`AppStateService` always raises `StateChanged` after saves.
### Files changed (41 files, +1169/−660)
| Area | Files | What changed |
|------|-------|-------------|
| New services | `Services/IPersistenceService.cs`,
`PersistenceService.cs`, `ISettingsService.cs`, `SettingsService.cs`,
`IAppStateService.cs`, `AppStateService.cs` | New service interfaces and
implementations |
| Models | `SettingsModel.cs`, `AppStateModel.cs` | Stripped to pure
data bags |
| DI | `App.xaml.cs` | Service registration, removed raw model DI |
| ViewModels | 12 files | Constructor injection of services |
| UI | 10 files | Service injection replacing model access |
| Settings | `DockSettings.cs` | `Colors.Transparent` replaced with
struct literal to avoid WinUI3 COM dependency |
| Tests | `PersistenceServiceTests.cs`, `SettingsServiceTests.cs`,
`AppStateServiceTests.cs` | 38 unit tests covering all three services |
| Config | `.gitignore` | Added `.squad/`, `.github/agents/` exclusions
|
## Validation Steps Performed
- Built `Microsoft.CmdPal.UI` with MSBuild (x64/Debug) — exit code 0,
clean build
- Ran 38 unit tests via `vstest.console.exe` — all passing
- Verified no remaining `#pragma warning disable SA1300` blocks
- Verified no remaining `Services.` namespace prefixes
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-20 18:58:27 -05:00
private readonly ISettingsService _settingsService ;
2025-06-05 22:29:18 +02:00
private readonly uint WM_TASKBAR_RESTART ;
private Window ? _window ;
private HWND _hwnd ;
private WNDPROC ? _originalWndProc ;
private WNDPROC ? _trayWndProc ;
private NOTIFYICONDATAW ? _trayIconData ;
private DestroyIconSafeHandle ? _largeIcon ;
private DestroyMenuSafeHandle ? _popupMenu ;
CmdPal: Extract persistence services from SettingsModel and AppStateModel (#46312)
## Summary of the Pull Request
Extracts persistence (load/save) logic from `SettingsModel` and
`AppStateModel` into dedicated service classes, following the
single-responsibility principle. Consumers now interact with
`ISettingsService` and `IAppStateService` instead of receiving raw model
objects through DI.
**New services introduced:**
- `IPersistenceService` / `PersistenceService` — generic `Load<T>` /
`Save<T>` with AOT-compatible `JsonTypeInfo<T>`, ensures target
directory exists before writing
- `ISettingsService` / `SettingsService` — loads settings on
construction, runs migrations, exposes `Settings` property and
`SettingsChanged` event
- `IAppStateService` / `AppStateService` — loads state on construction,
exposes `State` property and `StateChanged` event
**Key changes:**
- `SettingsModel` and `AppStateModel` are now pure data models — all
file I/O, migration, and directory management removed
- Raw `SettingsModel` and `AppStateModel` removed from DI container;
consumers receive the appropriate service
- `IApplicationInfoService.ConfigDirectory` injected into services for
config path resolution (no more hardcoded `Utilities.BaseSettingsPath`)
- ~30 consumer files updated across `Microsoft.CmdPal.UI.ViewModels` and
`Microsoft.CmdPal.UI` projects
- All `#pragma warning disable SA1300` suppressions removed —
convenience accessors replaced with direct `_settingsService.Settings` /
`_appStateService.State` access
- Namespace prefixes (`Services.ISettingsService`) replaced with proper
`using` directives
## PR Checklist
- [ ] **Communication:** I've discussed this with core contributors
already.
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** N/A — no end-user-facing strings changed
- [ ] **Dev docs:** N/A — internal refactor, no public API changes
- [ ] **New binaries:** N/A — no new binaries introduced
## Detailed Description of the Pull Request / Additional comments
### Architecture
Services are registered as singletons in `App.xaml.cs`:
```csharp
services.AddSingleton<IPersistenceService, PersistenceService>();
services.AddSingleton<ISettingsService, SettingsService>();
services.AddSingleton<IAppStateService, AppStateService>();
```
`PersistenceService.Save<T>` writes the serialized model directly to
disk, creating the target directory if it doesn't exist. It also does
not attempt to merge existing and new settings/state. `SettingsService`
runs hotkey migrations on load and raises `SettingsChanged` after saves.
`AppStateService` always raises `StateChanged` after saves.
### Files changed (41 files, +1169/−660)
| Area | Files | What changed |
|------|-------|-------------|
| New services | `Services/IPersistenceService.cs`,
`PersistenceService.cs`, `ISettingsService.cs`, `SettingsService.cs`,
`IAppStateService.cs`, `AppStateService.cs` | New service interfaces and
implementations |
| Models | `SettingsModel.cs`, `AppStateModel.cs` | Stripped to pure
data bags |
| DI | `App.xaml.cs` | Service registration, removed raw model DI |
| ViewModels | 12 files | Constructor injection of services |
| UI | 10 files | Service injection replacing model access |
| Settings | `DockSettings.cs` | `Colors.Transparent` replaced with
struct literal to avoid WinUI3 COM dependency |
| Tests | `PersistenceServiceTests.cs`, `SettingsServiceTests.cs`,
`AppStateServiceTests.cs` | 38 unit tests covering all three services |
| Config | `.gitignore` | Added `.squad/`, `.github/agents/` exclusions
|
## Validation Steps Performed
- Built `Microsoft.CmdPal.UI` with MSBuild (x64/Debug) — exit code 0,
clean build
- Ran 38 unit tests via `vstest.console.exe` — all passing
- Verified no remaining `#pragma warning disable SA1300` blocks
- Verified no remaining `Services.` namespace prefixes
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-20 18:58:27 -05:00
public TrayIconService ( ISettingsService settingsService )
2025-06-05 22:29:18 +02:00
{
CmdPal: Extract persistence services from SettingsModel and AppStateModel (#46312)
## Summary of the Pull Request
Extracts persistence (load/save) logic from `SettingsModel` and
`AppStateModel` into dedicated service classes, following the
single-responsibility principle. Consumers now interact with
`ISettingsService` and `IAppStateService` instead of receiving raw model
objects through DI.
**New services introduced:**
- `IPersistenceService` / `PersistenceService` — generic `Load<T>` /
`Save<T>` with AOT-compatible `JsonTypeInfo<T>`, ensures target
directory exists before writing
- `ISettingsService` / `SettingsService` — loads settings on
construction, runs migrations, exposes `Settings` property and
`SettingsChanged` event
- `IAppStateService` / `AppStateService` — loads state on construction,
exposes `State` property and `StateChanged` event
**Key changes:**
- `SettingsModel` and `AppStateModel` are now pure data models — all
file I/O, migration, and directory management removed
- Raw `SettingsModel` and `AppStateModel` removed from DI container;
consumers receive the appropriate service
- `IApplicationInfoService.ConfigDirectory` injected into services for
config path resolution (no more hardcoded `Utilities.BaseSettingsPath`)
- ~30 consumer files updated across `Microsoft.CmdPal.UI.ViewModels` and
`Microsoft.CmdPal.UI` projects
- All `#pragma warning disable SA1300` suppressions removed —
convenience accessors replaced with direct `_settingsService.Settings` /
`_appStateService.State` access
- Namespace prefixes (`Services.ISettingsService`) replaced with proper
`using` directives
## PR Checklist
- [ ] **Communication:** I've discussed this with core contributors
already.
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** N/A — no end-user-facing strings changed
- [ ] **Dev docs:** N/A — internal refactor, no public API changes
- [ ] **New binaries:** N/A — no new binaries introduced
## Detailed Description of the Pull Request / Additional comments
### Architecture
Services are registered as singletons in `App.xaml.cs`:
```csharp
services.AddSingleton<IPersistenceService, PersistenceService>();
services.AddSingleton<ISettingsService, SettingsService>();
services.AddSingleton<IAppStateService, AppStateService>();
```
`PersistenceService.Save<T>` writes the serialized model directly to
disk, creating the target directory if it doesn't exist. It also does
not attempt to merge existing and new settings/state. `SettingsService`
runs hotkey migrations on load and raises `SettingsChanged` after saves.
`AppStateService` always raises `StateChanged` after saves.
### Files changed (41 files, +1169/−660)
| Area | Files | What changed |
|------|-------|-------------|
| New services | `Services/IPersistenceService.cs`,
`PersistenceService.cs`, `ISettingsService.cs`, `SettingsService.cs`,
`IAppStateService.cs`, `AppStateService.cs` | New service interfaces and
implementations |
| Models | `SettingsModel.cs`, `AppStateModel.cs` | Stripped to pure
data bags |
| DI | `App.xaml.cs` | Service registration, removed raw model DI |
| ViewModels | 12 files | Constructor injection of services |
| UI | 10 files | Service injection replacing model access |
| Settings | `DockSettings.cs` | `Colors.Transparent` replaced with
struct literal to avoid WinUI3 COM dependency |
| Tests | `PersistenceServiceTests.cs`, `SettingsServiceTests.cs`,
`AppStateServiceTests.cs` | 38 unit tests covering all three services |
| Config | `.gitignore` | Added `.squad/`, `.github/agents/` exclusions
|
## Validation Steps Performed
- Built `Microsoft.CmdPal.UI` with MSBuild (x64/Debug) — exit code 0,
clean build
- Ran 38 unit tests via `vstest.console.exe` — all passing
- Verified no remaining `#pragma warning disable SA1300` blocks
- Verified no remaining `Services.` namespace prefixes
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-20 18:58:27 -05:00
_settingsService = settingsService ;
2025-06-05 22:29:18 +02:00
// TaskbarCreated is the message that's broadcast when explorer.exe
// restarts. We need to know when that happens to be able to bring our
// notification area icon back
WM_TASKBAR_RESTART = PInvoke . RegisterWindowMessage ( "TaskbarCreated" ) ;
}
public void SetupTrayIcon ( bool? showSystemTrayIcon = null )
{
CmdPal: Extract persistence services from SettingsModel and AppStateModel (#46312)
## Summary of the Pull Request
Extracts persistence (load/save) logic from `SettingsModel` and
`AppStateModel` into dedicated service classes, following the
single-responsibility principle. Consumers now interact with
`ISettingsService` and `IAppStateService` instead of receiving raw model
objects through DI.
**New services introduced:**
- `IPersistenceService` / `PersistenceService` — generic `Load<T>` /
`Save<T>` with AOT-compatible `JsonTypeInfo<T>`, ensures target
directory exists before writing
- `ISettingsService` / `SettingsService` — loads settings on
construction, runs migrations, exposes `Settings` property and
`SettingsChanged` event
- `IAppStateService` / `AppStateService` — loads state on construction,
exposes `State` property and `StateChanged` event
**Key changes:**
- `SettingsModel` and `AppStateModel` are now pure data models — all
file I/O, migration, and directory management removed
- Raw `SettingsModel` and `AppStateModel` removed from DI container;
consumers receive the appropriate service
- `IApplicationInfoService.ConfigDirectory` injected into services for
config path resolution (no more hardcoded `Utilities.BaseSettingsPath`)
- ~30 consumer files updated across `Microsoft.CmdPal.UI.ViewModels` and
`Microsoft.CmdPal.UI` projects
- All `#pragma warning disable SA1300` suppressions removed —
convenience accessors replaced with direct `_settingsService.Settings` /
`_appStateService.State` access
- Namespace prefixes (`Services.ISettingsService`) replaced with proper
`using` directives
## PR Checklist
- [ ] **Communication:** I've discussed this with core contributors
already.
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** N/A — no end-user-facing strings changed
- [ ] **Dev docs:** N/A — internal refactor, no public API changes
- [ ] **New binaries:** N/A — no new binaries introduced
## Detailed Description of the Pull Request / Additional comments
### Architecture
Services are registered as singletons in `App.xaml.cs`:
```csharp
services.AddSingleton<IPersistenceService, PersistenceService>();
services.AddSingleton<ISettingsService, SettingsService>();
services.AddSingleton<IAppStateService, AppStateService>();
```
`PersistenceService.Save<T>` writes the serialized model directly to
disk, creating the target directory if it doesn't exist. It also does
not attempt to merge existing and new settings/state. `SettingsService`
runs hotkey migrations on load and raises `SettingsChanged` after saves.
`AppStateService` always raises `StateChanged` after saves.
### Files changed (41 files, +1169/−660)
| Area | Files | What changed |
|------|-------|-------------|
| New services | `Services/IPersistenceService.cs`,
`PersistenceService.cs`, `ISettingsService.cs`, `SettingsService.cs`,
`IAppStateService.cs`, `AppStateService.cs` | New service interfaces and
implementations |
| Models | `SettingsModel.cs`, `AppStateModel.cs` | Stripped to pure
data bags |
| DI | `App.xaml.cs` | Service registration, removed raw model DI |
| ViewModels | 12 files | Constructor injection of services |
| UI | 10 files | Service injection replacing model access |
| Settings | `DockSettings.cs` | `Colors.Transparent` replaced with
struct literal to avoid WinUI3 COM dependency |
| Tests | `PersistenceServiceTests.cs`, `SettingsServiceTests.cs`,
`AppStateServiceTests.cs` | 38 unit tests covering all three services |
| Config | `.gitignore` | Added `.squad/`, `.github/agents/` exclusions
|
## Validation Steps Performed
- Built `Microsoft.CmdPal.UI` with MSBuild (x64/Debug) — exit code 0,
clean build
- Ran 38 unit tests via `vstest.console.exe` — all passing
- Verified no remaining `#pragma warning disable SA1300` blocks
- Verified no remaining `Services.` namespace prefixes
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-20 18:58:27 -05:00
if ( showSystemTrayIcon ? ? _settingsService . Settings . ShowSystemTrayIcon )
2025-06-05 22:29:18 +02:00
{
2025-08-18 06:07:28 -05:00
if ( _window is null )
2025-06-05 22:29:18 +02:00
{
_window = new Window ( ) ;
_hwnd = new HWND ( WindowNative . GetWindowHandle ( _window ) ) ;
// LOAD BEARING: If you don't stick the pointer to HotKeyPrc into a
// member (and instead like, use a local), then the pointer we marshal
// into the WindowLongPtr will be useless after we leave this function,
// and our **WindProc will explode**.
_trayWndProc = WindowProc ;
var hotKeyPrcPointer = Marshal . GetFunctionPointerForDelegate ( _trayWndProc ) ;
_originalWndProc = Marshal . GetDelegateForFunctionPointer < WNDPROC > ( PInvoke . SetWindowLongPtr ( _hwnd , WINDOW_LONG_PTR_INDEX . GWL_WNDPROC , hotKeyPrcPointer ) ) ;
}
2025-08-18 06:07:28 -05:00
if ( _trayIconData is null )
2025-06-05 22:29:18 +02:00
{
// We need to stash this handle, so it doesn't clean itself up. If
// explorer restarts, we'll come back through here, and we don't
// really need to re-load the icon in that case. We can just use
// the handle from the first time.
_largeIcon = GetAppIconHandle ( ) ;
_trayIconData = new NOTIFYICONDATAW ( )
{
2025-07-01 10:36:59 +08:00
cbSize = ( uint ) Marshal . SizeOf < NOTIFYICONDATAW > ( ) ,
2025-06-05 22:29:18 +02:00
hWnd = _hwnd ,
uID = MY_NOTIFY_ID ,
uFlags = NOTIFY_ICON_DATA_FLAGS . NIF_MESSAGE | NOTIFY_ICON_DATA_FLAGS . NIF_ICON | NOTIFY_ICON_DATA_FLAGS . NIF_TIP ,
uCallbackMessage = WM_TRAY_ICON ,
hIcon = ( HICON ) _largeIcon . DangerousGetHandle ( ) ,
szTip = RS_ . GetString ( "AppStoreName" ) ,
} ;
}
var d = ( NOTIFYICONDATAW ) _trayIconData ;
// Add the notification icon
PInvoke . Shell_NotifyIcon ( NOTIFY_ICON_MESSAGE . NIM_ADD , in d ) ;
2025-08-18 06:07:28 -05:00
if ( _popupMenu is null )
2025-06-05 22:29:18 +02:00
{
_popupMenu = PInvoke . CreatePopupMenu_SafeHandle ( ) ;
PInvoke . InsertMenu ( _popupMenu , 0 , MENU_ITEM_FLAGS . MF_BYPOSITION | MENU_ITEM_FLAGS . MF_STRING , PInvoke . WM_USER + 1 , RS_ . GetString ( "TrayMenu_Settings" ) ) ;
2025-08-21 10:40:37 +02:00
PInvoke . InsertMenu ( _popupMenu , 1 , MENU_ITEM_FLAGS . MF_BYPOSITION | MENU_ITEM_FLAGS . MF_STRING , PInvoke . WM_USER + 2 , RS_ . GetString ( "TrayMenu_Close" ) ) ;
2025-06-05 22:29:18 +02:00
}
}
else
{
Destroy ( ) ;
}
}
public void Destroy ( )
{
2025-08-18 06:07:28 -05:00
if ( _trayIconData is not null )
2025-06-05 22:29:18 +02:00
{
var d = ( NOTIFYICONDATAW ) _trayIconData ;
if ( PInvoke . Shell_NotifyIcon ( NOTIFY_ICON_MESSAGE . NIM_DELETE , in d ) )
{
_trayIconData = null ;
}
}
2025-08-18 06:07:28 -05:00
if ( _popupMenu is not null )
2025-06-05 22:29:18 +02:00
{
_popupMenu . Close ( ) ;
_popupMenu = null ;
}
2025-08-18 06:07:28 -05:00
if ( _largeIcon is not null )
2025-06-05 22:29:18 +02:00
{
_largeIcon . Close ( ) ;
_largeIcon = null ;
}
2025-08-18 06:07:28 -05:00
if ( _window is not null )
2025-06-05 22:29:18 +02:00
{
_window . Close ( ) ;
_window = null ;
_hwnd = HWND . Null ;
}
}
private DestroyIconSafeHandle GetAppIconHandle ( )
{
2025-07-01 10:36:59 +08:00
var exePath = Path . Combine ( AppContext . BaseDirectory , "Microsoft.CmdPal.UI.exe" ) ;
2025-06-05 22:29:18 +02:00
DestroyIconSafeHandle largeIcon ;
PInvoke . ExtractIconEx ( exePath , 0 , out largeIcon , out _ , 1 ) ;
return largeIcon ;
}
private LRESULT WindowProc (
HWND hwnd ,
uint uMsg ,
WPARAM wParam ,
LPARAM lParam )
{
switch ( uMsg )
{
case PInvoke . WM_COMMAND :
{
if ( wParam = = PInvoke . WM_USER + 1 )
{
CmdPal: GEH per partes; part 1: error report builder, sanitizer and internals tools setting page (#44140)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This PR adds three parts of the original big bad global error handler
(error report builder, sanitization and internal tools UI).
### Error Report Generation
- `ErrorReportBuilder`: Produces a detailed, technical report with
system context.
- Comprehensive data: OS version, architecture, culture, app version,
elevation status, etc.
- Exception analysis: Coalesces nested exception messages and HRESULT
details for clearer diagnostics.
<details><summary>Example</summary>
<pre>
This is an error report generated by Windows Command Palette.
If you are seeing this, it means something went a little sideways in the
app.
You can help us fix it by filing a report at
https://aka.ms/powerToysReportBug.
(While you’re at it, give the details below a quick skim — just to make
sure there’s nothing personal you’d prefer not to share. It’s rare, but
sometimes little surprises sneak in.)
============================================================
Summary:
Message: Test exception; thrown from the UI thread
Type: System.NotImplementedException
Source: Microsoft.CmdPal.UI
Time: 2025-08-25 18:54:44.3854569
HRESULT: 0x80004001 (-2147467263)
Context: MainThreadException
Application:
App version: 0.0.1.0
Is elevated: no
Environment:
OS version: Microsoft Windows 10.0.26120
OS architecture: X64
Runtime identifier: win-x64
Framework: .NET 9.0.8
Process architecture: X64
Culture: cs-CZ
UI culture: en-US
Stack Trace:
at
Microsoft.CmdPal.UI.Settings.InternalPage.ThrowPlainMainThreadException_Click(Object
sender, RoutedEventArgs e)
at
WinRT._EventSource_global__Microsoft_UI_Xaml_RoutedEventHandler.EventState.<GetEventInvoke>b__1_0(Object
sender, RoutedEventArgs e)
at ABI.Microsoft.UI.Xaml.RoutedEventHandler.Do_Abi_Invoke(IntPtr
thisPtr, IntPtr sender, IntPtr e)
------------------ Full Exception Details ------------------
System.NotImplementedException: Test exception; thrown from the UI
thread
at
Microsoft.CmdPal.UI.Settings.InternalPage.ThrowPlainMainThreadException_Click(Object
sender, RoutedEventArgs e)
at
WinRT._EventSource_global__Microsoft_UI_Xaml_RoutedEventHandler.EventState.<GetEventInvoke>b__1_0(Object
sender, RoutedEventArgs e)
at ABI.Microsoft.UI.Xaml.RoutedEventHandler.Do_Abi_Invoke(IntPtr
thisPtr, IntPtr sender, IntPtr e)
============================================================
</pre>
</details>
Real-world example: #41362
### PII Sanitization Framework
- `ErrorReportSanitizer`: Multi-layer sanitization pipeline for
sensitive data.
- Nine specialized rule providers:
- `PiiRuleProvider`: Personally identifiable information (emails, phone
numbers, SSNs).
- `ProfilePathAndUsernameRuleProvider`: Windows user profiles and
usernames.
- `NetworkRuleProvider`: IP addresses, MAC addresses, network
identifiers.
- `SecretKeyValueRulesProvider`: API keys, tokens, passwords in
key/value formats.
- `FilenameMaskRuleProvider`: Sensitive file paths and extensions.
- `UrlRuleProvider`: URLs and web addresses.
- `TokenRuleProvider`: JWT and other auth tokens.
- `ConnectionStringRuleProvider`: Database connection strings.
- `EnvironmentPropertiesRuleProvider`: Environment variables and system
properties.
### Internals Tools Page
A page in settings available in non-CI-builds:
<img width="1305" height="745" alt="image"
src="https://github.com/user-attachments/assets/3145ecfd-997f-491d-8c8a-6096634b6045"
/>
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-29 04:09:37 +01:00
WeakReferenceMessenger . Default . Send ( new OpenSettingsMessage ( ) ) ;
2025-06-05 22:29:18 +02:00
}
else if ( wParam = = PInvoke . WM_USER + 2 )
{
WeakReferenceMessenger . Default . Send < QuitMessage > ( ) ;
}
}
break ;
// Shell_NotifyIcon can fail when we invoke it during the time explorer.exe isn't present/ready to handle it.
// We'll also never receive WM_TASKBAR_RESTART message if the first call to Shell_NotifyIcon failed, so we use
// WM_WINDOWPOSCHANGING which is always received on explorer startup sequence.
case PInvoke . WM_WINDOWPOSCHANGING :
{
2025-08-18 06:07:28 -05:00
if ( _trayIconData is null )
2025-06-05 22:29:18 +02:00
{
SetupTrayIcon ( ) ;
}
}
break ;
default :
// WM_TASKBAR_RESTART isn't a compile-time constant, so we can't
// use it in a case label
if ( uMsg = = WM_TASKBAR_RESTART )
{
// Handle the case where explorer.exe restarts.
// Even if we created it before, do it again
SetupTrayIcon ( ) ;
}
else if ( uMsg = = WM_TRAY_ICON )
{
switch ( ( uint ) lParam . Value )
{
case PInvoke . WM_RBUTTONUP :
{
2025-08-18 06:07:28 -05:00
if ( _popupMenu is not null )
2025-06-05 22:29:18 +02:00
{
PInvoke . GetCursorPos ( out var cursorPos ) ;
PInvoke . SetForegroundWindow ( _hwnd ) ;
PInvoke . TrackPopupMenuEx ( _popupMenu , ( uint ) TRACK_POPUP_MENU_FLAGS . TPM_LEFTALIGN | ( uint ) TRACK_POPUP_MENU_FLAGS . TPM_BOTTOMALIGN , cursorPos . X , cursorPos . Y , _hwnd , null ) ;
}
}
break ;
case PInvoke . WM_LBUTTONUP :
case PInvoke . WM_LBUTTONDBLCLK :
WeakReferenceMessenger . Default . Send < HotkeySummonMessage > ( new ( string . Empty , HWND . Null ) ) ;
break ;
}
}
break ;
}
return PInvoke . CallWindowProc ( _originalWndProc , hwnd , uMsg , wParam , lParam ) ;
}
}