From 312fd592be8c84364abe42c803963f10054c083b Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 8 Jul 2026 15:23:53 -0700 Subject: [PATCH] Fix tray app display name branding Use OpenClaw Companion as the visible app identity for toasts, installer shortcuts, and approval/permission popup titles. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- installer.iss | 16 ++-- src/OpenClaw.Tray.WinUI/App.xaml.cs | 6 ++ src/OpenClaw.Tray.WinUI/AppIdentity.cs | 6 ++ .../Dialogs/PairingApprovalDialog.cs | 5 +- .../Dialogs/RecordingConsentDialog.cs | 5 +- .../OpenClaw.Tray.WinUI.csproj | 6 ++ .../Services/AppUserModelIdRegistrar.cs | 44 +++++++++ .../Services/ExecApprovalPromptService.cs | 9 +- .../Strings/en-us/Resources.resw | 2 +- .../Strings/fr-fr/Resources.resw | 2 +- .../Strings/nl-nl/Resources.resw | 2 +- .../Strings/zh-cn/Resources.resw | 2 +- .../Strings/zh-tw/Resources.resw | 2 +- src/OpenClaw.Tray.WinUI/app.manifest | 2 +- .../AppRefactorContractTests.cs | 2 + .../InstallerIssAssertionTests.cs | 21 ++++- .../Services/AppUserModelIdIdentityTests.cs | 91 +++++++++++++++++++ 17 files changed, 197 insertions(+), 26 deletions(-) create mode 100644 src/OpenClaw.Tray.WinUI/Services/AppUserModelIdRegistrar.cs create mode 100644 tests/OpenClaw.Tray.Tests/Services/AppUserModelIdIdentityTests.cs diff --git a/installer.iss b/installer.iss index e4a5ea4c5..74cab78fe 100644 --- a/installer.iss +++ b/installer.iss @@ -2,6 +2,7 @@ ; Pass /DDevBuild=1 to produce a side-by-side dev installer. #ifdef DevBuild #define MyAppName "OpenClaw Companion (Dev)" + #define MyAppAumid "OpenClaw.Companion.Dev" #define MyAppId "{{M0LTB0T-TRAY-4PP1-DEV}" #define MyInstallDir "OpenClawTray-Dev" #define MyMutex "OpenClawTray-Dev" @@ -12,6 +13,7 @@ #define MyOutputSuffix "-Dev" #else #define MyAppName "OpenClaw Companion" + #define MyAppAumid "OpenClaw.Companion" #define MyAppId "{{M0LTB0T-TRAY-4PP1-D3N7}" #define MyInstallDir "OpenClawTray" #define MyMutex "OpenClawTray" @@ -112,14 +114,14 @@ Root: HKCU; Subkey: "Software\Classes\{#MyProtocol}\DefaultIcon"; ValueType: str Root: HKCU; Subkey: "Software\Classes\{#MyProtocol}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1""" [Icons] -Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" -Name: "{group}\OpenClaw Gateway Setup"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://setup"; IconFilename: "{app}\{#MyAppExeName}" -Name: "{group}\OpenClaw Companion Settings"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://commandcenter"; IconFilename: "{app}\{#MyAppExeName}" -Name: "{group}\OpenClaw Chat"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://chat"; IconFilename: "{app}\{#MyAppExeName}" -Name: "{group}\Check for Updates"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://check-updates"; IconFilename: "{app}\{#MyAppExeName}" +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; AppUserModelID: "{#MyAppAumid}" +Name: "{group}\OpenClaw Gateway Setup"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://setup"; IconFilename: "{app}\{#MyAppExeName}"; AppUserModelID: "{#MyAppAumid}" +Name: "{group}\OpenClaw Companion Settings"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://commandcenter"; IconFilename: "{app}\{#MyAppExeName}"; AppUserModelID: "{#MyAppAumid}" +Name: "{group}\OpenClaw Chat"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://chat"; IconFilename: "{app}\{#MyAppExeName}"; AppUserModelID: "{#MyAppAumid}" +Name: "{group}\Check for Updates"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://check-updates"; IconFilename: "{app}\{#MyAppExeName}"; AppUserModelID: "{#MyAppAumid}" Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" -Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon -Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: startupicon +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; AppUserModelID: "{#MyAppAumid}" +Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: startupicon; AppUserModelID: "{#MyAppAumid}" [Run] Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent; Check: ShouldLaunchTray diff --git a/src/OpenClaw.Tray.WinUI/App.xaml.cs b/src/OpenClaw.Tray.WinUI/App.xaml.cs index abbc39cda..6ee2ed033 100644 --- a/src/OpenClaw.Tray.WinUI/App.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/App.xaml.cs @@ -475,6 +475,12 @@ private async Task OnLaunchedAsync(LaunchActivatedEventArgs args) // Store protocol URI for processing after setup _pendingProtocolUri = protocolUri; + var appUserModelIdRegistration = AppUserModelIdRegistrar.RegisterCurrentProcess(AppIdentity.AppUserModelId); + if (appUserModelIdRegistration.Attempted && appUserModelIdRegistration.HResult < 0) + { + Logger.Warn($"Failed to set AppUserModelID '{AppIdentity.AppUserModelId}' (HRESULT=0x{appUserModelIdRegistration.HResult:X8}); toast sender name may fall back to the executable name."); + } + // Initialize settings before update check so skip selections can be remembered. _settings = new SettingsManager(); _previousSettingsSnapshot = _settings.ToSettingsData().ToConnectionSnapshot(); diff --git a/src/OpenClaw.Tray.WinUI/AppIdentity.cs b/src/OpenClaw.Tray.WinUI/AppIdentity.cs index b698cfa7f..b60eeefbf 100644 --- a/src/OpenClaw.Tray.WinUI/AppIdentity.cs +++ b/src/OpenClaw.Tray.WinUI/AppIdentity.cs @@ -16,6 +16,9 @@ internal static class AppIdentity /// MSIX package identity name (must differ from release for side-by-side). public const string PackageIdentityName = "OpenClaw.Companion.Dev"; + /// Win32 AppUserModelID used for notifications and shell grouping. + public const string AppUserModelId = PackageIdentityName; + /// Windows Registry auto-start value name (must differ so both can auto-start). public const string AutoStartRegistryName = "OpenClawTray-Dev"; @@ -52,6 +55,9 @@ internal static class AppIdentity /// MSIX package identity name. public const string PackageIdentityName = "OpenClaw.Companion"; + /// Win32 AppUserModelID used for notifications and shell grouping. + public const string AppUserModelId = PackageIdentityName; + /// Windows Registry auto-start value name. public const string AutoStartRegistryName = "OpenClawTray"; diff --git a/src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs b/src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs index 9d4613276..1c0bdffe6 100644 --- a/src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs +++ b/src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs @@ -54,7 +54,8 @@ public PairingApprovalDialog(PairingApprovalCoordinator coordinator) { _coordinator = coordinator ?? throw new ArgumentNullException(nameof(coordinator)); - Title = LocalizationHelper.GetString("PairingApproval_WindowTitle"); + var windowTitle = $"{AppIdentity.DisplayName} - {LocalizationHelper.GetString("PairingApproval_WindowTitle")}"; + Title = windowTitle; this.SetWindowSize(460, 460); this.CenterOnScreen(); this.SetIcon("Assets\\openclaw.ico"); @@ -70,7 +71,7 @@ public PairingApprovalDialog(PairingApprovalCoordinator coordinator) titleBar.Children.Add(titleIcon); var titleText = new TextBlock { - Text = LocalizationHelper.GetString("PairingApproval_WindowTitle"), + Text = windowTitle, FontSize = 13, VerticalAlignment = VerticalAlignment.Center, Style = (Style)Application.Current.Resources["CaptionTextBlockStyle"], diff --git a/src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs b/src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs index db2bdc7e2..7c17ba254 100644 --- a/src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs +++ b/src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs @@ -38,7 +38,8 @@ public RecordingConsentDialog(RecordingType type) var descriptionKey = isScreen ? "RecordingConsent_ScreenDescription" : "RecordingConsent_CameraDescription"; var emoji = isScreen ? "🖥️" : "📷"; - Title = LocalizationHelper.GetString("RecordingConsent_WindowTitle"); + var windowTitle = $"{AppIdentity.DisplayName} - {LocalizationHelper.GetString("RecordingConsent_WindowTitle")}"; + Title = windowTitle; this.SetWindowSize(460, 340); this.CenterOnScreen(); this.SetIcon("Assets\\openclaw.ico"); @@ -67,7 +68,7 @@ public RecordingConsentDialog(RecordingType type) var titleText = new TextBlock { - Text = LocalizationHelper.GetString("RecordingConsent_WindowTitle"), + Text = windowTitle, FontSize = 13, VerticalAlignment = VerticalAlignment.Center, Style = (Style)Application.Current.Resources["CaptionTextBlockStyle"] diff --git a/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj b/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj index 7db7389ac..8838cba6e 100644 --- a/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj +++ b/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj @@ -8,6 +8,9 @@ enable true Assets\openclaw.ico + OpenClaw Companion + OpenClaw Companion + OpenClaw Companion OpenClawTray en-US x64;ARM64 @@ -19,6 +22,9 @@ $(DefineConstants);DEV_BUILD dev + OpenClaw Companion (Dev) + OpenClaw Companion (Dev) + OpenClaw Companion (Dev) release diff --git a/src/OpenClaw.Tray.WinUI/Services/AppUserModelIdRegistrar.cs b/src/OpenClaw.Tray.WinUI/Services/AppUserModelIdRegistrar.cs new file mode 100644 index 000000000..194f25f6d --- /dev/null +++ b/src/OpenClaw.Tray.WinUI/Services/AppUserModelIdRegistrar.cs @@ -0,0 +1,44 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace OpenClawTray.Services; + +internal readonly record struct AppUserModelIdRegistrationResult(bool Attempted, int HResult); + +internal static class AppUserModelIdRegistrar +{ + private const int AppModelErrorNoPackage = 15700; + private const int ErrorInsufficientBuffer = 122; + private const int ErrorSuccess = 0; + + public static AppUserModelIdRegistrationResult RegisterCurrentProcess(string appUserModelId) + { + ArgumentException.ThrowIfNullOrWhiteSpace(appUserModelId); + if (HasPackageIdentity()) + return new AppUserModelIdRegistrationResult(Attempted: false, HResult: ErrorSuccess); + + return new AppUserModelIdRegistrationResult( + Attempted: true, + HResult: SetCurrentProcessExplicitAppUserModelID(appUserModelId)); + } + + private static bool HasPackageIdentity() + { + var packageFullNameLength = 0; + var result = GetCurrentPackageFullName(ref packageFullNameLength, null); + if (result == AppModelErrorNoPackage) + return false; + + return result is ErrorSuccess or ErrorInsufficientBuffer; + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int GetCurrentPackageFullName( + ref int packageFullNameLength, + [MarshalAs(UnmanagedType.LPWStr)] StringBuilder? packageFullName); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int SetCurrentProcessExplicitAppUserModelID( + [MarshalAs(UnmanagedType.LPWStr)] string appID); +} diff --git a/src/OpenClaw.Tray.WinUI/Services/ExecApprovalPromptService.cs b/src/OpenClaw.Tray.WinUI/Services/ExecApprovalPromptService.cs index b37936609..19bf14f76 100644 --- a/src/OpenClaw.Tray.WinUI/Services/ExecApprovalPromptService.cs +++ b/src/OpenClaw.Tray.WinUI/Services/ExecApprovalPromptService.cs @@ -15,6 +15,7 @@ public sealed class ExecApprovalPromptService : IExecApprovalPromptHandler private readonly IOpenClawLogger _logger; private readonly Func? _chatProviderProvider; private readonly Func? _inlineApprovalAvailable; + private static string NativePromptTitle => $"{AppIdentity.DisplayName} - Approve local command?"; public ExecApprovalPromptService( DispatcherQueue dispatcherQueue, @@ -161,7 +162,7 @@ private void RaiseDecided( private static ExecApprovalPromptDecision ShowNativePrompt(ExecApprovalPromptRequest request) { var text = - "A remote agent wants to run a local command on this Windows machine." + + $"{AppIdentity.DisplayName} needs approval before a remote agent can run a local command on this Windows machine." + "\r\n\r\n" + request.Command + "\r\n\r\n" + @@ -190,7 +191,7 @@ private static ExecApprovalPromptDecision ShowMessageBoxFallback(string text) var result = MessageBoxW( IntPtr.Zero, fallbackText, - "Approve local command?", + NativePromptTitle, MessageBoxFlags.YesNoCancel | MessageBoxFlags.IconWarning | MessageBoxFlags.TopMost | @@ -263,7 +264,7 @@ public ExecApprovalPromptDecision Show() _hwnd = CreateWindowExW( WindowExStyleTopMost | WindowExStyleDialogModalFrame, WindowClassName, - "Approve local command?", + NativePromptTitle, WindowStyleCaption | WindowStyleSystemMenu | WindowStyleVisible, x, y, @@ -341,7 +342,7 @@ private static void EnsureClassRegistered() private void CreateControls(IntPtr hwnd) { var font = GetStockObject(DefaultGuiFont); - CreateChild(hwnd, "STATIC", "Approve local command?", 20, 18, 590, 24, StaticStyleLeft, 0, font); + CreateChild(hwnd, "STATIC", NativePromptTitle, 20, 18, 590, 24, StaticStyleLeft, 0, font); CreateChild( hwnd, "EDIT", diff --git a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw index a332f9b8b..0320ecfe4 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw @@ -832,7 +832,7 @@ Use one of these options: - OpenClaw · Permission Request + Permission Request Allow screen recording? diff --git a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw index daacb46bb..c0122184b 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw @@ -789,7 +789,7 @@ Utilisez l'une de ces options : - OpenClaw · Demande d'autorisation + Demande d'autorisation Autoriser l'enregistrement d'écran ? diff --git a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw index 027effb4e..a4b90a586 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw @@ -790,7 +790,7 @@ Gebruik een van deze opties: - OpenClaw · Toestemmingsverzoek + Toestemmingsverzoek Schermopname toestaan? diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw index cb67066e2..e6f218312 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw @@ -789,7 +789,7 @@ - OpenClaw · 权限请求 + 权限请求 允许屏幕录制? diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw index daf97d636..d9af27043 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw @@ -789,7 +789,7 @@ - OpenClaw · 權限請求 + 權限請求 允許螢幕錄製? diff --git a/src/OpenClaw.Tray.WinUI/app.manifest b/src/OpenClaw.Tray.WinUI/app.manifest index 7361ce5c9..54924b624 100644 --- a/src/OpenClaw.Tray.WinUI/app.manifest +++ b/src/OpenClaw.Tray.WinUI/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/tests/OpenClaw.Tray.Tests/AppRefactorContractTests.cs b/tests/OpenClaw.Tray.Tests/AppRefactorContractTests.cs index 22df8310f..7d9af313a 100644 --- a/tests/OpenClaw.Tray.Tests/AppRefactorContractTests.cs +++ b/tests/OpenClaw.Tray.Tests/AppRefactorContractTests.cs @@ -28,6 +28,8 @@ public void Startup_Order_PreservesInitializationInvariants() AssertInOrder( source, + "AppUserModelIdRegistrar.RegisterCurrentProcess(AppIdentity.AppUserModelId);", + "appUserModelIdRegistration.Attempted", "_settings = new SettingsManager();", "CheckForUpdatesAsync();", "ToastNotificationManagerCompat.OnActivated += OnToastActivated;", diff --git a/tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs b/tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs index 673be2ede..33da13085 100644 --- a/tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs +++ b/tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs @@ -46,14 +46,24 @@ public void Installer_CreatesStartMenuEntrypointsForTraySetupAndSupport() var iss = File.ReadAllText(Path.Combine(TestRepositoryPaths.GetRepositoryRoot(), "installer.iss")); Assert.Contains(@"#define MyAppName ""OpenClaw Companion""", iss); + Assert.Contains(@"#define MyAppAumid ""OpenClaw.Companion""", iss); Assert.Contains(@"#define MyCompression ""lzma""", iss); Assert.Contains(@"#define MySolidCompression ""yes""", iss); Assert.Contains("OutputBaseFilename=OpenClawCompanion{#MyOutputSuffix}-Setup-{#MyAppArch}", iss); - Assert.Contains(@"Name: ""{group}\{#MyAppName}""; Filename: ""{app}\{#MyAppExeName}""", iss); - Assert.Contains(@"Name: ""{group}\OpenClaw Gateway Setup""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://setup""", iss); - Assert.Contains(@"Name: ""{group}\OpenClaw Companion Settings""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://commandcenter""", iss); - Assert.Contains(@"Name: ""{group}\OpenClaw Chat""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://chat""", iss); - Assert.Contains(@"Name: ""{group}\Check for Updates""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://check-updates""", iss); + foreach (var iconEntry in new[] + { + @"Name: ""{group}\{#MyAppName}""; Filename: ""{app}\{#MyAppExeName}""; AppUserModelID: ""{#MyAppAumid}""", + @"Name: ""{group}\OpenClaw Gateway Setup""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://setup""; IconFilename: ""{app}\{#MyAppExeName}""; AppUserModelID: ""{#MyAppAumid}""", + @"Name: ""{group}\OpenClaw Companion Settings""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://commandcenter""; IconFilename: ""{app}\{#MyAppExeName}""; AppUserModelID: ""{#MyAppAumid}""", + @"Name: ""{group}\OpenClaw Chat""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://chat""; IconFilename: ""{app}\{#MyAppExeName}""; AppUserModelID: ""{#MyAppAumid}""", + @"Name: ""{group}\Check for Updates""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://check-updates""; IconFilename: ""{app}\{#MyAppExeName}""; AppUserModelID: ""{#MyAppAumid}""", + @"Name: ""{autodesktop}\{#MyAppName}""; Filename: ""{app}\{#MyAppExeName}""; Tasks: desktopicon; AppUserModelID: ""{#MyAppAumid}""", + @"Name: ""{userstartup}\{#MyAppName}""; Filename: ""{app}\{#MyAppExeName}""; Tasks: startupicon; AppUserModelID: ""{#MyAppAumid}""" + }) + { + Assert.Contains(iconEntry, iss); + } + Assert.DoesNotContain("AppUserModelID: \"OpenClaw.Tray.WinUI\"", iss); } [Fact] @@ -132,6 +142,7 @@ public void DevInstaller_UsesIndependentIdentityAndProtocol() var iss = File.ReadAllText(Path.Combine(TestRepositoryPaths.GetRepositoryRoot(), "installer.iss")); Assert.Contains(@"#define MyAppName ""OpenClaw Companion (Dev)""", iss); + Assert.Contains(@"#define MyAppAumid ""OpenClaw.Companion.Dev""", iss); Assert.Contains(@"#define MyInstallDir ""OpenClawTray-Dev""", iss); Assert.Contains(@"#define MyMutex ""OpenClawTray-Dev""", iss); Assert.Contains(@"#define MyProtocol ""openclaw-dev""", iss); diff --git a/tests/OpenClaw.Tray.Tests/Services/AppUserModelIdIdentityTests.cs b/tests/OpenClaw.Tray.Tests/Services/AppUserModelIdIdentityTests.cs new file mode 100644 index 000000000..76f3f6b7c --- /dev/null +++ b/tests/OpenClaw.Tray.Tests/Services/AppUserModelIdIdentityTests.cs @@ -0,0 +1,91 @@ +using System.IO; +using OpenClawTray; +using Xunit; + +namespace OpenClaw.Tray.Tests.Services; + +public sealed class AppUserModelIdIdentityTests +{ + [Fact] + public void WinUiProject_UsesHumanReadableMetadataForNotificationSenderFallback() + { + var project = File.ReadAllText(Path.Combine( + TestRepositoryPaths.GetRepositoryRoot(), + "src", + "OpenClaw.Tray.WinUI", + "OpenClaw.Tray.WinUI.csproj")); + + Assert.Contains("OpenClaw Companion", project); + Assert.Contains("OpenClaw Companion", project); + Assert.Contains("OpenClaw Companion", project); + Assert.Contains("OpenClaw Companion (Dev)", project); + Assert.Contains("OpenClaw Companion (Dev)", project); + Assert.Contains("OpenClaw Companion (Dev)", project); + Assert.DoesNotContain("OpenClaw.Tray.WinUI", project); + } + + [Fact] + public void Registrar_SkipsExplicitAumidWhenMsixPackageIdentityExists() + { + var source = File.ReadAllText(Path.Combine( + TestRepositoryPaths.GetRepositoryRoot(), + "src", + "OpenClaw.Tray.WinUI", + "Services", + "AppUserModelIdRegistrar.cs")); + + Assert.Contains("GetCurrentPackageFullName", source); + Assert.Contains("AppModelErrorNoPackage", source); + Assert.Contains("HResult", source); + Assert.Contains("SetCurrentProcessExplicitAppUserModelID", source); + } + + [Fact] + public void AppUserModelId_UsesCompanionIdentity() + { + Assert.Equal(AppIdentity.PackageIdentityName, AppIdentity.AppUserModelId); + Assert.DoesNotContain("OpenClaw.Tray.WinUI", AppIdentity.AppUserModelId); + } + + [Fact] + public void InstallerAumid_MatchesRuntimeAppUserModelId() + { + var iss = File.ReadAllText(Path.Combine( + TestRepositoryPaths.GetRepositoryRoot(), + "installer.iss")); + + Assert.Contains($@"#define MyAppAumid ""{AppIdentity.AppUserModelId}""", iss); + Assert.Contains(@"#define MyAppAumid ""OpenClaw.Companion.Dev""", iss); + } + + [Fact] + public void UnpackagedManifest_UsesCompanionIdentityForSystemPrompts() + { + var manifest = File.ReadAllText(Path.Combine( + TestRepositoryPaths.GetRepositoryRoot(), + "src", + "OpenClaw.Tray.WinUI", + "app.manifest")); + + Assert.Contains(@"", manifest); + Assert.DoesNotContain(@"name=""OpenClaw.Tray.WinUI""", manifest); + } + + [Fact] + public void ApprovalPopups_UseCompanionDisplayName() + { + var root = TestRepositoryPaths.GetRepositoryRoot(); + var pairingDialog = File.ReadAllText(Path.Combine(root, "src", "OpenClaw.Tray.WinUI", "Dialogs", "PairingApprovalDialog.cs")); + var recordingDialog = File.ReadAllText(Path.Combine(root, "src", "OpenClaw.Tray.WinUI", "Dialogs", "RecordingConsentDialog.cs")); + var execPrompt = File.ReadAllText(Path.Combine(root, "src", "OpenClaw.Tray.WinUI", "Services", "ExecApprovalPromptService.cs")); + + Assert.Contains("AppIdentity.DisplayName", pairingDialog); + Assert.Contains("AppIdentity.DisplayName", recordingDialog); + Assert.Contains("AppIdentity.DisplayName", execPrompt); + Assert.DoesNotContain("OpenClaw · Permission Request", File.ReadAllText(Path.Combine(root, "src", "OpenClaw.Tray.WinUI", "Strings", "en-us", "Resources.resw"))); + Assert.Contains("NativePromptTitle", execPrompt); + Assert.DoesNotContain("OpenClaw.Tray.WinUI", pairingDialog); + Assert.DoesNotContain("OpenClaw.Tray.WinUI", recordingDialog); + Assert.DoesNotContain("OpenClaw.Tray.WinUI", execPrompt); + } +}