fix(windows): the launcher's Start menu shortcut is listed beside the game's again #88

Merged
HumanBean merged 1 commit from dyst/LongshotLauncher:fix/start-menu-shortcut into main 2026-11-15 17:27:49 +00:00
Member

On Windows, the launcher's own Start menu shortcut seems to disappear once the game's Start menu entry is made. Ticking "Start menu entry" on the Ready screen then makes only the game's entry, never the launcher's. A player reported both.

Why it happens

The Start menu lists one entry for each app it tells apart, and it tells shortcuts apart by their AppUserModelID. Both of the launcher's shortcuts start the same program:

  • Longshot Launcher.lnk, the launcher's own, made at install and again after each of its updates;
  • Longshot.lnk, the game's, which is the launcher with --play, made by the Ready screen.

Neither named an ID, so the Start menu took them for one app and showed only the first by name: Longshot, the game's. The launcher's file was still there, just not listed (Microsoft: AppUserModelIDs; the Start menu hiding shortcuts to the same exe).

What changed

  • Core/ShellLinks.cs (new) writes and reads shortcuts through the shell's own ShellLink object (IShellLinkW, IPersistFile, IPropertyStore), with the shortcut's AppUserModelID (PKEY_AppUserModel_ID).
    • How: source-generated COM (GeneratedComInterface, StrategyBasedComWrappers), since the release is built with BuiltInComInteropSupport=false.
    • What it replaces: WScript.Shell through a hidden PowerShell, which can't set the ID and reported nothing when it failed.
    • Failures: a refusal comes out as an IOException, which every caller already handles (the Ready screen shows its toast, and the update-time refresh tries again at the next start).
  • Two IDs (Core/Shortcuts.cs):
    • MagicBean.LongshotLauncher for the launcher's Start menu shortcut (Installer) and its own window (SetCurrentProcessExplicitAppUserModelID at the start, WindowsInstall.UseAppId), so a pinned launcher and its window stay one taskbar button.
    • MagicBean.Longshot for the game's shortcuts (WindowsShortcuts.Write).
    • IShortcutMaker.Make takes the ID.
  • Players who already have both shortcuts: the first start after the launcher updates to this version writes both again (Installer.Refresh and WindowsShortcuts.Refresh, unchanged), now with their IDs. The launcher's entry then shows again without the player doing anything.
  • App/WindowsShortcuts.cs: the PowerShell shortcut script (Line, Run) is gone. Quote stays for the uninstall's clean-up.
  • README: the shortcuts' note.

Testing

Tested (Windows 11, .NET SDK 10.0.103):

  • python -I tests/test_launcher_core.py --strict: 1097 checks pass. They include:
    • the installer's shortcut naming the launcher's ID;
    • on Windows, a new ShellLinkChecks that writes the launcher's and the game's shortcuts through the real shell in the test's scratch folder (never the Start menu) and reads them back: target, arguments and ID each. It also covers a shortcut written over itself, and a refused write coming out as an IOException.
  • python -I tests/test_release.py: OK.
  • dotnet build App -c Release: 0 warnings, 0 errors.

Not tested:

  • The Start menu itself: checking it needs a launcher installed from this branch (the Start menu, Apps & features, %LOCALAPPDATA%\Programs). I didn't install one on this PC, so there are no screenshots yet.

Must be tested (Windows, a launcher built from this branch):

  1. Install it and build a version.
  2. On Ready, keep "Start menu entry" ticked.
  3. Expect both Longshot Launcher and Longshot in the Start menu's All apps.
  4. Untick the entry: Longshot goes and Longshot Launcher stays.
  5. With the shortcuts from before this PR: update the launcher to this build and start it once. Expect Longshot Launcher back in the list.

Screenshots of the Start menu go here once someone has run it.

Risks

  • Taskbar grouping: the launcher's window now carries an explicit AppUserModelID. A taskbar pin made from the old shortcut (no ID) may show apart from the running window until it's pinned again from the new shortcut.
  • COM interop: a failure there is an IOException like any other shortcut failure, and nothing else depends on it.
  • Turning it off: revert the commit.

See #90 (the Start menu shortcut point).

🤖 Generated with Claude Code

On Windows, the launcher's own Start menu shortcut seems to disappear once the game's Start menu entry is made. Ticking "Start menu entry" on the Ready screen then makes only the game's entry, never the launcher's. A player reported both. ## Why it happens The Start menu lists one entry for each app it tells apart, and it tells shortcuts apart by their AppUserModelID. Both of the launcher's shortcuts start the same program: - `Longshot Launcher.lnk`, the launcher's own, made at install and again after each of its updates; - `Longshot.lnk`, the game's, which is the launcher with `--play`, made by the Ready screen. Neither named an ID, so the Start menu took them for one app and showed only the first by name: *Longshot*, the game's. The launcher's file was still there, just not listed ([Microsoft: AppUserModelIDs](https://learn.microsoft.com/en-us/windows/win32/shell/appids); [the Start menu hiding shortcuts to the same exe](https://gurupackager.blogspot.com/2013/12/disappearing-shortcuts-in-windows-8.html)). ## What changed - **`Core/ShellLinks.cs`** (new) writes and reads shortcuts through the shell's own ShellLink object (`IShellLinkW`, `IPersistFile`, `IPropertyStore`), with the shortcut's AppUserModelID (`PKEY_AppUserModel_ID`). - **How:** source-generated COM (`GeneratedComInterface`, `StrategyBasedComWrappers`), since the release is built with `BuiltInComInteropSupport=false`. - **What it replaces:** WScript.Shell through a hidden PowerShell, which can't set the ID and reported nothing when it failed. - **Failures:** a refusal comes out as an `IOException`, which every caller already handles (the Ready screen shows its toast, and the update-time refresh tries again at the next start). - **Two IDs** (`Core/Shortcuts.cs`): - `MagicBean.LongshotLauncher` for the launcher's Start menu shortcut (`Installer`) and its own window (`SetCurrentProcessExplicitAppUserModelID` at the start, `WindowsInstall.UseAppId`), so a pinned launcher and its window stay one taskbar button. - `MagicBean.Longshot` for the game's shortcuts (`WindowsShortcuts.Write`). - `IShortcutMaker.Make` takes the ID. - **Players who already have both shortcuts:** the first start after the launcher updates to this version writes both again (`Installer.Refresh` and `WindowsShortcuts.Refresh`, unchanged), now with their IDs. The launcher's entry then shows again without the player doing anything. - **`App/WindowsShortcuts.cs`:** the PowerShell shortcut script (`Line`, `Run`) is gone. `Quote` stays for the uninstall's clean-up. - **README:** the shortcuts' note. ## Testing **Tested** (Windows 11, .NET SDK 10.0.103): - `python -I tests/test_launcher_core.py --strict`: 1097 checks pass. They include: - the installer's shortcut naming the launcher's ID; - on Windows, a new `ShellLinkChecks` that writes the launcher's and the game's shortcuts through the real shell in the test's scratch folder (never the Start menu) and reads them back: target, arguments and ID each. It also covers a shortcut written over itself, and a refused write coming out as an `IOException`. - `python -I tests/test_release.py`: OK. - `dotnet build App -c Release`: 0 warnings, 0 errors. **Not tested:** - **The Start menu itself:** checking it needs a launcher installed from this branch (the Start menu, Apps & features, `%LOCALAPPDATA%\Programs`). I didn't install one on this PC, so there are no screenshots yet. **Must be tested** (Windows, a launcher built from this branch): 1. Install it and build a version. 2. On Ready, keep "Start menu entry" ticked. 3. Expect both *Longshot Launcher* and *Longshot* in the Start menu's All apps. 4. Untick the entry: *Longshot* goes and *Longshot Launcher* stays. 5. With the shortcuts from before this PR: update the launcher to this build and start it once. Expect *Longshot Launcher* back in the list. Screenshots of the Start menu go here once someone has run it. ## Risks - **Taskbar grouping:** the launcher's window now carries an explicit AppUserModelID. A taskbar pin made from the old shortcut (no ID) may show apart from the running window until it's pinned again from the new shortcut. - **COM interop:** a failure there is an `IOException` like any other shortcut failure, and nothing else depends on it. - **Turning it off:** revert the commit. See #90 (the Start menu shortcut point). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(windows): the launcher's Start menu shortcut is listed beside the game's again
All checks were successful
CI / Compile and test (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
11ffd52c90
The Start menu lists one entry for each app it tells apart, by the shortcuts' AppUserModelID. The
launcher's Longshot Launcher.lnk and the game's Longshot.lnk (the launcher with --play) start the
same program and named none, so it showed only the first by name, the game's: making the game's
Start menu entry seemed to delete the launcher's, and ticking it again made only the game's.

- Core/ShellLinks.cs: shortcuts written (and read) through the shell's ShellLink, with their
  AppUserModelID (PKEY_AppUserModel_ID), by source-generated COM, as the release has no built-in COM
  support; in place of WScript.Shell through PowerShell, which cannot set the id and said nothing when
  it failed. A refusal is an IOException, which every caller already takes
- Shortcuts.LauncherAppId (MagicBean.LongshotLauncher) for the launcher's shortcut and its window
  (SetCurrentProcessExplicitAppUserModelID at the start), Shortcuts.GameAppId (MagicBean.Longshot) for
  the game's; IShortcutMaker.Make takes the id
- a launcher update already writes both shortcuts again (Installer.Refresh, WindowsShortcuts.Refresh),
  so shortcuts made before take their ids at the first start of this version
- tests: the installer's shortcut names the launcher's id; on Windows, both shortcuts written and read
  back through the shell (target, arguments, id), and a refused one an IOException

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
HumanBean changed title from WIP: fix(windows): the launcher's Start menu shortcut is listed beside the game's again to fix(windows): the launcher's Start menu shortcut is listed beside the game's again 2026-11-15 17:27:46 +00:00
HumanBean deleted branch fix/start-menu-shortcut 2026-11-15 17:27:49 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/LongshotLauncher!88
No description provided.