Support context menu modifications for third-party file managers (XYplorer, Total Commander, Double Commander and possibly more) - #797
Conversation
…info in third party file managers
…nd selected items
…ird-party file managers
|
You did a good job. I don’t understand anything about coding, but I would like Nilesoft Shell to work everywhere it’s possible. I use Everything and Directory Opus, and it’s a pity that this can’t be done in DOpus. If possible, could you please explain in simpler terms which lines of code need to be added to the .nss files to enable support in Everything? Thank you for your contribution! |
|
I was going to create a new thread asking about this but it sounds like the third-party file manager that I use, xplorer², can also be included here as well, because only changes to theme.nss are working and no modify.nss/remove.nss changes are working. |
|
Very nice contribution. Hopefully @moudey will add your PR in the next build. Happy to see Shell work with any other app that uses the OS built-in context menu. Thank you for your fix. |
|
NS source code here - in this GitHub Repo, is several builds behind. Some function are missing
I restored most of them, fixed a few others, and also added 3rd party app support (not the method described here, but without this idea I wouldn't have succeeded - many thanks for sharing the solution). I want to fix a few more things before sharing the finished dll file - when I'm ready I'll share it here too. |
|
https://github.com/RubicBG/ReShell-Releases/blob/main/shell.dll A few things have changed from the original:
All changes, fixes and new functions will be documented soon at: |
The DLL has always been registered as a context-menu handler under *, Directory, Drive, Folder, Directory\Background, DesktopBackground, LibraryFolder and LibraryFolder\Background, but DllGetClassObject returned E_NOTIMPL on every path: the registration existed only to make hosts load us, and the NtUserTrackPopupMenuEx hook did all the work. That costs every host that is not Explorer. Selections::QuerySelected reaches the selection through IShellBrowser, which it gets by sending WM_GETISHELLBROWSER to the window. A third-party file manager implements its own view and answers nothing, so QuerySelected returns false at its has_IShellBrowser gate and the menu is built with no selection - theming works, anything selection-aware does not. XYplorer, Total Commander, Double Commander, Everything and the rest were already calling IShellExtInit::Initialize and handing us the selection outright, and we were declining it. This is capability detection, not application detection: any host honouring ContextMenuHandlers is served, with no per-application list in the DLL or in anyone's config. Upstream PR moudey#797 approached the same goal by inserting a marker item and finding it again by title; identity here is the HMENU the host passes to QueryContextMenu, which is the same handle it later passes to TrackPopupMenu, so the hook recognises its own menu with a pointer compare. No placeholder item, nothing user-visible, nothing to mistranslate. The object inserts zero items, so Explorer's menu is unchanged by construction and the hook keeps doing everything there. DllCanUnloadNow had to be fixed first, and this is the part worth reviewing. It answered from _loader.explorer alone and returned S_OK - "nothing of mine is alive, unload me" - in every host that was not explorer.exe. That was true only while we handed out no objects. With a live IContextMenu outstanding it would let COM unload shell.dll out from under the host still holding it, so it now consults an object count. There was no reference counting anywhere in this DLL before: the only IUnknown implementation, ProcessReference, is never constructed. The count is what the new tests cover, since a mistake in it either unloads the DLL under a caller or pins it forever in every process that ever raised a menu, and neither shows up in a build. Verified to have teeth: dropping the decrement from ~ShellExtHandler produces four failures across two tests and fails the build. This is the plumbing only. The captured selection is not yet routed into Selections, so behaviour is unchanged everywhere for now - that is the next commit, and it is the part that needs a real host to verify. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The context-menu handler works, and the selection reaches Selections, but nothing was ever drawn outside Explorer: the shipped config's exclude.where was !process.is_explorer, and ContextMenu::Initialize bails at is_excluded() before it builds anything. Everything, XYplorer and every other host fell straight through to their own menu no matter what the handler did. This is the same condition that made upstream PR moudey#797 inert, and it was written down in this fork's own review of that PR before the handler was built. Building the plumbing and leaving the rule that says "not here" is a miss. The rule is now Explorer, or any host that invoked us as a registered context-menu handler. window.is_contextmenuhandler is true exactly when the host went through our IContextMenu, so it is the capability rather than an application name - no per-application list, which was the point of doing this ourselves instead of taking moudey#797. A process that loaded the DLL some other way and never went through the handler is still excluded, exactly as before. Note for anyone updating: install-local.ps1 preserves an existing shell.nss, so an install that already has the old rule keeps it. Use -ResetConfig, or edit the installed file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
I implemented an context menu shell extension in Nilesoft Shell to retrieve the selected file list, so that it works in most file managers where Shell Extensions are allowed to run in context menu. In my test, in XYplorer, Total Commander, Double Commander, Everything context menu modifications are working correctly, in Directory Opus and OneCommander, they are not working because they don't use native context menu.
In the beginning I discovered that Nilesoft Shell doesn't work properly with third-party file managers. It only changes the menu appearence, item addition/modification are not working, like so:

On the Internet I found out lots of discussions on this problem:
https://www.xyplorer.com/xyfc/viewtopic.php?p=226677#p226677
https://www.xyplorer.com/xyfc/viewtopic.php?t=26686
https://web.archive.org/web/20250715220407/https://www.xyplorer.com/xyfc/viewtopic.php?t=27737
https://resource.dopus.com/t/how-can-i-make-the-directory-opus-use-my-windows-context-menu/53508/4
https://www.reddit.com/r/OneCommander/comments/1hf7q6l/request_support_for_nilesoft_shell_integration/
Because I feel like Nilesoft Shell fits well with modern Windows 11 UI, and replacement solutions are not as easy to use, I decided to somehow implement third-party file manager support for Nilesoft Shell. I reseached into the code of Nilesoft Shell and come up with my solution.
The details of this implementation is that:
Selection::QuerySelected.g_ShellContext.ContextMenu::Initializeit checks for the dummy context menu item it generated to judge whether a menu is indeed a context menu.QuerySelectedreads selected file list stored ing_ShellContext.Instead of Windows File Explorer hook that only works in Explorer, this method can retrieve the selected file list wherever Shell Extension runs (in third-party file managers). The original implementation requires a
ShellBrowserto be available inSelection::QueryShellWindow, and used inSelection::QuerySelected, but third-party managers implement file browser by themselves, they don't have aShellBrowserexposed. My implementation doesn't completely change how it works, it only tries to use the Shell Extension retrieved file selection list when the original method fails.Below are some screenshots showing it work in different file managers. In
shell.nssthe excluded condition should be changed. like:XYplorer
exclude.where = !process.is_explorer && (process.name != "XYplorer") && (process.name != "notepad") && (process.name != "dopus") && (process.name != "dopusrt") && (process.name != "doublecmd") && (process.name != "OneCommander")Total Commander 64-bit

Double Commander

Directory Opus (not working)
Everything (working)

If it's not working, it can be added in "excluded" condition, to remove the "ShellExtSelectionRetriever Placeholder" item in context menu.