Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions installer.iss
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/OpenClaw.Tray.WinUI/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
6 changes: 6 additions & 0 deletions src/OpenClaw.Tray.WinUI/AppIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ internal static class AppIdentity
/// <summary>MSIX package identity name (must differ from release for side-by-side).</summary>
public const string PackageIdentityName = "OpenClaw.Companion.Dev";

/// <summary>Win32 AppUserModelID used for notifications and shell grouping.</summary>
public const string AppUserModelId = PackageIdentityName;

/// <summary>Windows Registry auto-start value name (must differ so both can auto-start).</summary>
public const string AutoStartRegistryName = "OpenClawTray-Dev";

Expand Down Expand Up @@ -52,6 +55,9 @@ internal static class AppIdentity
/// <summary>MSIX package identity name.</summary>
public const string PackageIdentityName = "OpenClaw.Companion";

/// <summary>Win32 AppUserModelID used for notifications and shell grouping.</summary>
public const string AppUserModelId = PackageIdentityName;

/// <summary>Windows Registry auto-start value name.</summary>
public const string AutoStartRegistryName = "OpenClawTray";

Expand Down
5 changes: 3 additions & 2 deletions src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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"],
Expand Down
5 changes: 3 additions & 2 deletions src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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"]
Expand Down
6 changes: 6 additions & 0 deletions src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<Nullable>enable</Nullable>
<EnableMsixTooling>true</EnableMsixTooling>
<ApplicationIcon>Assets\openclaw.ico</ApplicationIcon>
<AssemblyTitle>OpenClaw Companion</AssemblyTitle>
<FileDescription>OpenClaw Companion</FileDescription>
<Product>OpenClaw Companion</Product>
<RootNamespace>OpenClawTray</RootNamespace>
<DefaultLanguage>en-US</DefaultLanguage>
<Platforms>x64;ARM64</Platforms>
Expand All @@ -19,6 +22,9 @@
<PropertyGroup Condition="'$(DevBuild)' == 'true'">
<DefineConstants>$(DefineConstants);DEV_BUILD</DefineConstants>
<AppIdentityMarker>dev</AppIdentityMarker>
<AssemblyTitle>OpenClaw Companion (Dev)</AssemblyTitle>
<FileDescription>OpenClaw Companion (Dev)</FileDescription>
<Product>OpenClaw Companion (Dev)</Product>
</PropertyGroup>
<PropertyGroup Condition="'$(DevBuild)' != 'true'">
<AppIdentityMarker>release</AppIdentityMarker>
Expand Down
44 changes: 44 additions & 0 deletions src/OpenClaw.Tray.WinUI/Services/AppUserModelIdRegistrar.cs
Original file line number Diff line number Diff line change
@@ -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);
}
9 changes: 5 additions & 4 deletions src/OpenClaw.Tray.WinUI/Services/ExecApprovalPromptService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public sealed class ExecApprovalPromptService : IExecApprovalPromptHandler
private readonly IOpenClawLogger _logger;
private readonly Func<OpenClawChatDataProvider?>? _chatProviderProvider;
private readonly Func<string, bool>? _inlineApprovalAvailable;
private static string NativePromptTitle => $"{AppIdentity.DisplayName} - Approve local command?";

public ExecApprovalPromptService(
DispatcherQueue dispatcherQueue,
Expand Down Expand Up @@ -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" +
Expand Down Expand Up @@ -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 |
Expand Down Expand Up @@ -263,7 +264,7 @@ public ExecApprovalPromptDecision Show()
_hwnd = CreateWindowExW(
WindowExStyleTopMost | WindowExStyleDialogModalFrame,
WindowClassName,
"Approve local command?",
NativePromptTitle,
WindowStyleCaption | WindowStyleSystemMenu | WindowStyleVisible,
x,
y,
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,7 @@ Use one of these options:
</data>
<!-- ==================== Recording Consent Dialog ==================== -->
<data name="RecordingConsent_WindowTitle" xml:space="preserve">
<value>OpenClaw · Permission Request</value>
<value>Permission Request</value>
</data>
<data name="RecordingConsent_ScreenTitle" xml:space="preserve">
<value>Allow screen recording?</value>
Expand Down
2 changes: 1 addition & 1 deletion src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@ Utilisez l'une de ces options :
</data>
<!-- ==================== Recording Consent Dialog ==================== -->
<data name="RecordingConsent_WindowTitle" xml:space="preserve">
<value>OpenClaw · Demande d'autorisation</value>
<value>Demande d'autorisation</value>
</data>
<data name="RecordingConsent_ScreenTitle" xml:space="preserve">
<value>Autoriser l'enregistrement d'écran ?</value>
Expand Down
2 changes: 1 addition & 1 deletion src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ Gebruik een van deze opties:
</data>
<!-- ==================== Recording Consent Dialog ==================== -->
<data name="RecordingConsent_WindowTitle" xml:space="preserve">
<value>OpenClaw · Toestemmingsverzoek</value>
<value>Toestemmingsverzoek</value>
</data>
<data name="RecordingConsent_ScreenTitle" xml:space="preserve">
<value>Schermopname toestaan?</value>
Expand Down
2 changes: 1 addition & 1 deletion src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@
</data>
<!-- ==================== Recording Consent Dialog ==================== -->
<data name="RecordingConsent_WindowTitle" xml:space="preserve">
<value>OpenClaw · 权限请求</value>
<value>权限请求</value>
</data>
<data name="RecordingConsent_ScreenTitle" xml:space="preserve">
<value>允许屏幕录制?</value>
Expand Down
2 changes: 1 addition & 1 deletion src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@
</data>
<!-- ==================== Recording Consent Dialog ==================== -->
<data name="RecordingConsent_WindowTitle" xml:space="preserve">
<value>OpenClaw · 權限請求</value>
<value>權限請求</value>
</data>
<data name="RecordingConsent_ScreenTitle" xml:space="preserve">
<value>允許螢幕錄製?</value>
Expand Down
2 changes: 1 addition & 1 deletion src/OpenClaw.Tray.WinUI/app.manifest
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="OpenClaw.Tray.WinUI"/>
<assemblyIdentity version="1.0.0.0" name="OpenClaw.Companion"/>

<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
Expand Down
2 changes: 2 additions & 0 deletions tests/OpenClaw.Tray.Tests/AppRefactorContractTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public void Startup_Order_PreservesInitializationInvariants()

AssertInOrder(
source,
"AppUserModelIdRegistrar.RegisterCurrentProcess(AppIdentity.AppUserModelId);",
"appUserModelIdRegistration.Attempted",
"_settings = new SettingsManager();",
"CheckForUpdatesAsync();",
"ToastNotificationManagerCompat.OnActivated += OnToastActivated;",
Expand Down
21 changes: 16 additions & 5 deletions tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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);
Expand Down
Loading