Skip to content

Commit 6ddbd37

Browse files
committed
Fix App AUMID conflict between H.NotifyIcon and ToastCOM
+ Also make ToastCOM's callback to work (for real this time).
1 parent 075dc7d commit 6ddbd37

File tree

8 files changed

+320
-267
lines changed

8 files changed

+320
-267
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
using CollapseLauncher.Helper;
2+
using CollapseLauncher.Helper.Update;
3+
using Hi3Helper;
4+
using Hi3Helper.Data;
5+
using Hi3Helper.SentryHelper;
6+
using Hi3Helper.Shared.Region;
7+
using Hi3Helper.Win32.ShellLinkCOM;
8+
using Microsoft.Extensions.Logging;
9+
using NuGet.Versioning;
10+
using System;
11+
using System.IO;
12+
using System.Linq;
13+
using System.Runtime.CompilerServices;
14+
using System.Threading.Tasks;
15+
using Velopack;
16+
using Velopack.Locators;
17+
// ReSharper disable IdentifierTypo
18+
// ReSharper disable StringLiteralTypo
19+
// ReSharper disable CommentTypo
20+
21+
#nullable enable
22+
namespace CollapseLauncher.Classes.Extension
23+
{
24+
internal static class VelopackLocatorExtension
25+
{
26+
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "<AppId>k__BackingField")]
27+
internal static extern ref string? GetLocatorAumidField(this WindowsVelopackLocator locator);
28+
29+
internal static void StartUpdaterHook(string aumid)
30+
{
31+
#if !USEVELOPACK
32+
// Add Squirrel Hooks
33+
SquirrelAwareApp.HandleEvents(
34+
// Add shortcut and uninstaller entry on first start-up
35+
// ReSharper disable UnusedParameter.Local
36+
(_, sqr) =>
37+
{
38+
Console
39+
.WriteLine("Please do not close this console window while Collapse is preparing the installation via Squirrel...");
40+
},
41+
(_, sqr) =>
42+
{
43+
Console
44+
.WriteLine("Please do not close this console window while Collapse is updating via Squirrel...");
45+
},
46+
onAppUninstall: (_, sqr) =>
47+
{
48+
Console
49+
.WriteLine("Uninstalling Collapse via Squirrel...\r\n" +
50+
"Please do not close this console window while action is being performed!");
51+
},
52+
// ReSharper restore UnusedParameter.Local
53+
onEveryRun: (_, _, _) => { }
54+
);
55+
#else
56+
// Allocate the Velopack locator manually to avoid Velopack from re-assigning
57+
// its custom AUMID
58+
ILogger logger = ILoggerHelper.GetILogger("Velopack");
59+
WindowsVelopackLocator locator = new WindowsVelopackLocator(logger);
60+
// HACK: Always ensure to set the AUMID field null so it won't
61+
// set the AUMID to its own.
62+
locator.GetLocatorAumidField() = null;
63+
64+
VelopackApp builder = VelopackApp.Build()
65+
.WithRestarted(TryCleanupFallbackUpdate)
66+
.WithAfterUpdateFastCallback(TryCleanupFallbackUpdate)
67+
.WithFirstRun(TryCleanupFallbackUpdate)
68+
.SetLocator(locator);
69+
70+
builder.Run(logger);
71+
72+
_ = Task.Run(DeleteVelopackLock);
73+
74+
GenerateVelopackMetadata(aumid);
75+
76+
void DeleteVelopackLock()
77+
{
78+
// Get the current application directory
79+
string currentAppDir = AppDomain.CurrentDomain.BaseDirectory;
80+
81+
// Construct the path to the .velopack_lock file
82+
string velopackLockPath = Path.Combine(currentAppDir, "..", "packages", ".velopack_lock");
83+
84+
// Normalize the path
85+
velopackLockPath = Path.GetFullPath(velopackLockPath);
86+
87+
// Check if the file exists
88+
if (!File.Exists(velopackLockPath))
89+
{
90+
return;
91+
}
92+
93+
// Delete the file
94+
File.Delete(velopackLockPath);
95+
Logger.LogWriteLine(".velopack_lock file deleted successfully.");
96+
}
97+
#endif
98+
}
99+
100+
#if USEVELOPACK
101+
public static void TryCleanupFallbackUpdate(SemanticVersion newVersion)
102+
{
103+
string currentExecutedAppFolder = LauncherConfig.AppExecutableDir.TrimEnd('\\');
104+
string currentExecutedPath = LauncherConfig.AppExecutablePath;
105+
106+
// If the path is not actually running under "current" velopack folder, then return
107+
#if !DEBUG
108+
if (!currentExecutedAppFolder.EndsWith("current", StringComparison.OrdinalIgnoreCase)) // Expecting "current"
109+
{
110+
Logger.LogWriteLine("[TryCleanupFallbackUpdate] The launcher does not run from \"current\" folder");
111+
return;
112+
}
113+
#endif
114+
115+
try
116+
{
117+
// Otherwise, start cleaning-up process
118+
string? currentExecutedParentFolder = Path.GetDirectoryName(currentExecutedAppFolder);
119+
if (currentExecutedParentFolder != null)
120+
{
121+
DirectoryInfo directoryInfo = new DirectoryInfo(currentExecutedParentFolder);
122+
foreach (DirectoryInfo childLegacyAppSemVerFolder in
123+
directoryInfo.EnumerateDirectories("app-*", SearchOption.TopDirectoryOnly))
124+
{
125+
// Removing the "app-*" folder
126+
childLegacyAppSemVerFolder.Delete(true);
127+
Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Removed {childLegacyAppSemVerFolder.FullName} folder!",
128+
LogType.Default, true);
129+
}
130+
131+
// Try to remove squirrel temp clowd folder
132+
string squirrelTempPackagesFolder = Path.Combine(currentExecutedParentFolder, "SquirrelClowdTemp");
133+
DirectoryInfo squirrelTempPackagesFolderInfo = new DirectoryInfo(squirrelTempPackagesFolder);
134+
if (squirrelTempPackagesFolderInfo.Exists)
135+
{
136+
squirrelTempPackagesFolderInfo.Delete(true);
137+
Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Removed package temp folder: {squirrelTempPackagesFolder}!",
138+
LogType.Default, true);
139+
}
140+
141+
// Try to remove stub executable
142+
string squirrelLegacyStubPath = Path.Combine(currentExecutedParentFolder, "CollapseLauncher.exe");
143+
RemoveSquirrelFilePath(squirrelLegacyStubPath);
144+
145+
// Try to remove createdump executable
146+
string squirrelLegacyDumpPath = Path.Combine(currentExecutedParentFolder, "createdump.exe");
147+
RemoveSquirrelFilePath(squirrelLegacyDumpPath);
148+
149+
// Try to remove RestartAgent executable
150+
string squirrelLegacyRestartAgentPath =
151+
Path.Combine(currentExecutedParentFolder, "RestartAgent.exe");
152+
RemoveSquirrelFilePath(squirrelLegacyRestartAgentPath);
153+
}
154+
155+
// Try to remove legacy shortcuts
156+
string? currentWindowsPathDrive = Path.GetPathRoot(Environment.SystemDirectory);
157+
if (!string.IsNullOrEmpty(currentWindowsPathDrive))
158+
{
159+
string squirrelLegacyStartMenuGlobal =
160+
Path.Combine(currentWindowsPathDrive,
161+
@"ProgramData\Microsoft\Windows\Start Menu\Programs\Collapse\Collapse Launcher");
162+
string? squirrelLegacyStartMenuGlobalParent = Path.GetDirectoryName(squirrelLegacyStartMenuGlobal);
163+
if (Directory.Exists(squirrelLegacyStartMenuGlobalParent) &&
164+
Directory.Exists(squirrelLegacyStartMenuGlobal))
165+
{
166+
Directory.Delete(squirrelLegacyStartMenuGlobalParent, true);
167+
}
168+
}
169+
170+
// Try to delete all possible shortcuts on any users (since the shortcut used will be the global one)
171+
// Only do this if shortcut path is not same as current path tho... It pain to re-pin the shortcut again...
172+
string currentUsersDirPath = Path.Combine(currentWindowsPathDrive!, "Users");
173+
foreach (string userDirInfoPath in Directory
174+
.EnumerateDirectories(currentUsersDirPath, "*",
175+
SearchOption.TopDirectoryOnly)
176+
.Where(ConverterTool.IsUserHasPermission))
177+
{
178+
// Get the shortcut file
179+
string thisUserStartMenuShortcut = Path.Combine(userDirInfoPath,
180+
@"AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Collapse.lnk");
181+
if (!File.Exists(thisUserStartMenuShortcut))
182+
{
183+
continue;
184+
}
185+
186+
// Try open the shortcut and check whether this shortcut is actually pointing to
187+
// CollapseLauncher.exe file
188+
using ShellLink shellLink = new ShellLink(thisUserStartMenuShortcut);
189+
// Try to get the target path and its filename
190+
string shortcutTargetPath = shellLink.Target;
191+
if (!shortcutTargetPath.Equals(currentExecutedPath, StringComparison.OrdinalIgnoreCase))
192+
{
193+
continue;
194+
}
195+
196+
// Compare if the filename is equal, then delete it.
197+
File.Delete(thisUserStartMenuShortcut);
198+
Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Deleted old shortcut located at: " +
199+
$"{thisUserStartMenuShortcut} -> {shortcutTargetPath}",
200+
LogType.Default, true);
201+
}
202+
203+
// Try to recreate shortcuts
204+
TaskSchedulerHelper.RecreateIconShortcuts();
205+
}
206+
catch (Exception ex)
207+
{
208+
SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther);
209+
Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Failed while operating clean-up routines...\r\n{ex}");
210+
}
211+
212+
return;
213+
214+
void RemoveSquirrelFilePath(string filePath)
215+
{
216+
if (!File.Exists(filePath))
217+
{
218+
return;
219+
}
220+
221+
File.Delete(filePath);
222+
Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Removed old squirrel executables: {filePath}!",
223+
LogType.Default, true);
224+
}
225+
}
226+
#endif
227+
228+
public static string FindCollapseStubPath()
229+
{
230+
var collapseMainPath = LauncherConfig.AppExecutablePath;
231+
232+
#if USEVELOPACK
233+
const string collapseExecName = "CollapseLauncher.exe";
234+
var collapseStubPath = Path.Combine(Directory.GetParent(Path.GetDirectoryName(collapseMainPath)!)!.FullName,
235+
collapseExecName);
236+
if (File.Exists(collapseStubPath))
237+
{
238+
Logger.LogWriteLine($"Found stub at {collapseStubPath}", LogType.Default, true);
239+
return collapseStubPath;
240+
}
241+
#endif
242+
243+
Logger.LogWriteLine($"Collapse stub is not used anymore, returning current executable path!\r\n\t{collapseMainPath}",
244+
LogType.Default, true);
245+
return collapseMainPath;
246+
}
247+
248+
internal static void GenerateVelopackMetadata(string aumid)
249+
{
250+
const string xmlTemplate = """
251+
<?xml version="1.0" encoding="utf-8"?>
252+
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
253+
<metadata>
254+
<id>CollapseLauncher</id>
255+
<title>Collapse</title>
256+
<description>Collapse</description>
257+
<authors>Collapse Project Team</authors>
258+
<version>{0}</version>
259+
<channel>{1}</channel>
260+
<mainExe>CollapseLauncher.exe</mainExe>
261+
<os>win</os>
262+
<rid>win</rid>
263+
<shortcutLocations>Desktop,StartMenuRoot</shortcutLocations>
264+
<shortcutAmuid>{2}</shortcutAmuid>
265+
<shortcutAumid>{2}</shortcutAumid>
266+
</metadata>
267+
</package>
268+
"""; // Adding shortcutAumid for future use, since they typo-ed the XML tag LMAO
269+
string currentVersion = LauncherUpdateHelper.LauncherCurrentVersionString;
270+
string xmlPath = Path.Combine(LauncherConfig.AppExecutableDir, "sq.version");
271+
string xmlContent = string.Format(xmlTemplate, currentVersion, LauncherConfig.IsPreview ? "preview" : "stable", aumid);
272+
File.WriteAllText(xmlPath, xmlContent.ReplaceLineEndings("\n"));
273+
Logger.LogWriteLine($"Velopack metadata has been successfully written!\r\n{xmlContent}", LogType.Default, true);
274+
}
275+
}
276+
}

CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Hi3Helper;
1+
using CollapseLauncher.Classes.Extension;
2+
using Hi3Helper;
23
using Hi3Helper.SentryHelper;
34
using Hi3Helper.Shared.Region;
45
using Hi3Helper.Win32.ShellLinkCOM;
@@ -132,7 +133,7 @@ private static async Task InvokeToggleCommand()
132133
private static void AppendTaskNameAndPathArgument(StringBuilder argumentBuilder)
133134
{
134135
// Get current stub or main executable path
135-
string currentExecPath = MainEntryPoint.FindCollapseStubPath();
136+
string currentExecPath = VelopackLocatorExtension.FindCollapseStubPath();
136137

137138
// Build argument to the task name
138139
argumentBuilder.Append(" \"");

CollapseLauncher/Classes/Helper/WindowUtility.cs

+31-20
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Hi3Helper.Win32.TaskbarListCOM;
1414
using Hi3Helper.Win32.WinRT.ToastCOM;
1515
using Hi3Helper.Win32.WinRT.ToastCOM.Notification;
16+
using Microsoft.Extensions.Logging;
1617
using Microsoft.Graphics.Display;
1718
using Microsoft.UI;
1819
using Microsoft.UI.Composition.SystemBackdrops;
@@ -23,6 +24,8 @@
2324
using Microsoft.UI.Xaml.Media;
2425
using System;
2526
using System.Collections.Generic;
27+
using System.IO;
28+
using System.Runtime.CompilerServices;
2629
using System.Runtime.InteropServices;
2730
using Windows.Foundation;
2831
using Windows.Graphics;
@@ -285,7 +288,7 @@ internal static IconShowOptions CurrentWindowTitlebarIconShowOption
285288
internal static Guid? CurrentAumidInGuid
286289
{
287290
get => field ??= Extensions.GetGuidFromString(CurrentAumid ?? "");
288-
set;
291+
private set;
289292
}
290293

291294
internal static string? CurrentAumid
@@ -314,6 +317,7 @@ internal static string? CurrentAumid
314317

315318
internal static NotificationService? CurrentToastNotificationService
316319
{
320+
[method: MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
317321
get
318322
{
319323
// If toast notification service field is null, then initialize
@@ -326,31 +330,31 @@ internal static NotificationService? CurrentToastNotificationService
326330
{
327331
// Get Icon location paths
328332
(string iconLocationStartMenu, _)
329-
= TaskSchedulerHelper.GetIconLocationPaths(
330-
out string? appAumIdNameAlternative,
333+
= TaskSchedulerHelper.GetIconLocationPaths(out string? appAumidFromShortcut,
331334
out _,
332335
out string? executablePath,
333336
out _);
334337

335338
// Register notification service
336-
field = new NotificationService(ILoggerHelper.GetILogger("ToastCOM"));
339+
ILogger logger = ILoggerHelper.GetILogger("ToastCOM");
340+
field = new NotificationService(logger);
337341

338-
// Get AumId to use
339-
string? currentAumId = CurrentAumid ??= appAumIdNameAlternative;
342+
// Borrow existing guid from TrayIcon shell
343+
Guid existingAumidGuidFromShell = TrayIcon.Current.CollapseTaskbar.Id;
340344

341-
// Get string for AumId registration
342-
if (!string.IsNullOrEmpty(currentAumId))
343-
{
344-
// Initialize Toast Notification service
345-
CurrentAumidInGuid = field.Initialize(currentAumId,
346-
executablePath ?? "",
347-
iconLocationStartMenu,
348-
asElevatedUser: true
349-
);
350-
351-
// Subscribe ToastCallback
352-
field.ToastCallback += Service_ToastNotificationCallback;
353-
}
345+
// Use custom defined path for the Toast icon
346+
string iconPath = Path.Combine(LauncherConfig.AppAssetsFolder, "CollapseLauncherLogo.png");
347+
348+
// Initialize Toast Notification service
349+
CurrentAumidInGuid = field.Initialize(appAumidFromShortcut ?? MainEntryPoint.AppAumid,
350+
executablePath ?? "",
351+
iconLocationStartMenu,
352+
applicationId: existingAumidGuidFromShell,
353+
toastIconPngPath: iconPath
354+
);
355+
356+
// Subscribe ToastCallback
357+
field.ToastCallback += Service_ToastNotificationCallback;
354358
}
355359
catch (Exception ex)
356360
{
@@ -871,9 +875,16 @@ private static void Service_ToastNotificationCallback(string app, string arg, Di
871875

872876
// If the MainWindow is currently active, then set the window
873877
// to foreground.
874-
window._TrayIcon?.ToggleAllVisibility();
878+
window._TrayIcon?.ToggleMainVisibility(true);
879+
880+
IntPtr consoleWindowHandle = PInvoke.GetConsoleWindow();
881+
if (LauncherConfig.GetAppConfigValue("EnableConsole") && PInvoke.IsWindowVisible(consoleWindowHandle))
882+
{
883+
window._TrayIcon?.ToggleConsoleVisibility(true);
884+
}
875885

876886
// TODO: Make the callback actually usable on elevated app
887+
// 2025-03-09 11:08 PM+7 by neon-nyan: I FINALLY DID IT YO HOOOOOOOOOOOOOOO
877888
}
878889
#endregion
879890

0 commit comments

Comments
 (0)