Skip to content

Make the launches of one Explorer selection one donut - #1128

Open
KnifeLemon wants to merge 2 commits into
mainfrom
fix/explorer-multiselect-forward
Open

KnifeLemon wants to merge 2 commits into
mainfrom
fix/explorer-multiselect-forward

Conversation

@KnifeLemon

@KnifeLemon KnifeLemon commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

Refs #767 (the reporter still owes a log, so this doesn't close it)

Mechanism

Measured by building the shell's own default context menu (SHCreateDefaultContextMenu) over a private registry key, so Explorer's real menu wasn't touched, and invoking a classic verb on a multi-selection. Windows 11 26200, Explorer's classic-verb code; not run on Windows 10.

  • "%1" with MultiSelectModel=Player: one process per selected file, also with no MultiSelectModel and with Document. %* brings nothing extra: it comes out empty.
  • The processes start out of order (f004 f001 f003 f002 f005), so the first to arrive needn't be the clicked file.
  • Player shows the entry up to 100 files and hides it at 101. Document and no model hide it above 15.
  • The real Filee.exe started this way against a listener with the same pipe code, 100 files: all 100 delivered, median gap 159 ms, longest 723 ms, 15 gaps over the 350 ms debounce. With 30 files: 1 gap. A slower computer will have more.

The loss is in the app, not the pipe. When the debounce ran out, ShowForFiles called Load, which replaces the donut's files. So a slow burst leaves the donut with the last few files, or one, and a quick click converts only the first arrival. On Windows SingleInstance lost nothing: 200 parallel forwards all delivered, with no access-denied answers.

Fix

No wait can tell which launch is the last, and they can span many seconds, so:

  • SelectionLaunches: the first launch reads the file manager's selection (GetFileManagerSelection, as the selection shortcut does; Windows only). If its file is part of it, the donut gets all of it at once, and the launches still on their way add nothing. So a click at any moment converts everything, as one job.
  • RadialController.TryAddFiles: for when the selection can't be read (another file manager, Explorer not in front). Later launches join the donut open for earlier ones instead of replacing its files.
  • Once a conversion took the donut's files, later launches open a donut of their own. In that fallback case only, an early click can make two jobs; no file is lost.

%1 and Player stay: %* doesn't help, and Player is what lifts the 15-file limit.

A log line (A launch for one file found N selected file(s) in <window>) tells which path a report took.

Also fixed: launches lost on macOS and Linux

The new burst test found a separate, silent loss on Unix. All pipes of a name share one listening socket, which .NET closes and unlinks when the last of them is disposed. The listener kept one pipe at a time, so after every launch the socket closed and took the queued launches with it, though their sends had succeeded and they reported Delivered. StartListening now makes the next pipe before closing the connected one (two instances at most), so the socket stays open.

This doesn't change #767's Windows report. The Linux menu entries pass all files in one launch (%F), so it takes separate launches close together to hit it.

For a decision

  • Above 100 files the classic entry is hidden. Send To still works.
  • A true single activation is possible on Windows 10: a classic verb with ExplorerCommandHandler pointing at FileeExplorerMenu.dll. That needs no selection read, but it loads an in-process DLL into Explorer on Windows 10, and I can't test that here, so it isn't in this PR.

Tests

SelectionLaunchesTests is new. RadialControllerTests has 2 new tests: making TryAddFiles always decline fails A_later_launch_joins_the_donut_open_for_earlier_ones. SingleInstanceTests has a 200-launch burst test. It passes on Windows without the listener change and failed on all four macOS and Linux jobs (135-161 of 200 arrived). Those three classes: all passed. dotnet format --verify-no-changes clean on the changed files.

Not tested: a real right-click in Explorer on Windows 10, and the selection read in that exact moment (it needs Explorer in the foreground).

🤖 Generated with Claude Code

KnifeLemon and others added 2 commits November 4, 2026 23:20
Explorer's classic verb ("%1", MultiSelectModel Player, the entry on Windows 10) starts one Filee per selected file.
They arrive out of order and over many seconds: 100 files had 15 gaps above the 350 ms debounce on a fast computer,
and every time the debounce ran out the donut's files were replaced, so a conversion got a few files or just one.

The first launch now reads the file manager's selection, so the donut has all of it at once and the launches still on
their way add nothing. When the selection can't be read, later launches join the donut that is open for earlier ones
instead of replacing its files; once a conversion took its files, they get a donut of their own.

Refs #767

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Unix all NamedPipeServerStreams of a name share one listening socket, which .NET closes and unlinks when the last
of them is disposed. The listener kept one pipe at a time, so after every launch the socket closed, and the launches
already queued on it went with it, though their connect and send had succeeded and they reported Delivered. 200
launches at once: 135-161 arrived. Even two launches close together could lose one.

The next pipe is now made before the connected one is closed, so the socket stays open. Windows gets the same order,
which closes the moment there was no pipe at all between two launches.

Refs #767

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant