Add Windows shell integration and tray mount manager#47
Merged
Conversation
3687b32 to
bd75bff
Compare
Switch from ofs_adf_image (requires FastFileSystem) to pfs3_image with explicit --driver flag. Add DEVNULL for stdout/stderr to prevent crash from invalid handles in DETACHED_PROCESS mode.
- Deferred-delete pattern: WinFSP calls unlink() before release(), so the Amiga handler reports ERROR_OBJECT_IN_USE (202). Defer deletion to release() when a file handle is active; also fixes a lock leak on error. - Tray exit deadlock: icon.stop() from a pystray callback deadlocks the Win32 message pump. Signal the poll thread to call it instead. - Launcher path resolution: find amifuse-launcher.exe via shutil.which and sysconfig for user-scheme and system installs, not just venv. - Doctor tray deps check: warn when pystray/Pillow are missing but shell registration is active.
File creation ("New Text Document") failed because the stat cache
TTL is 0 in write mode, making create()'s cache priming dead code.
WinFSP's immediate fgetattr after create() fell through to the Amiga
handler which hadn't committed yet, returning ENOENT.
Directory deletion failed because PFS3 and SFS maintain internal
cache state after directory modifications that causes
ACTION_DELETE_OBJECT to return OBJECT_IN_USE even with zero locks
in the volume chain. An ACTION_INHIBIT cycle clears this state.
Changes:
- Active-handle fallback in getattr for recently-created files
- FileInfoTimeout=1000 in Windows mount options
- opendir/releasedir to track directory handles
- Deferred unlink/rmdir for WinFSP delete-before-release pattern
- Inhibit cycle before directory deletion to clear handler state
- Atomic delete_dir bridge method to prevent lock races
- readdir/getattr filtering of pending-delete entries
- Fix command injection via cmd.exe metacharacters in launcher and tray inspect actions (launch python directly with CREATE_NEW_CONSOLE) - Replace CTRL_BREAK_EVENT with taskkill for detached process kill (CTRL_BREAK only works within same console group) - Add PID reuse guard: verify process is python/amifuse before kill - Fix race in _resolve_pending_dir_deletes (check dict under _fh_lock) - Wrap all _pending_deletes mutations in _fh_lock for consistency - Add null-handle check on CreateMutexW in tray single-instance - Move inhibit_cycle in rmdir to error-recovery path only - Guard releasedir deferred-delete against crashed/read-only handler - Fix VBS launcher backslash escaping for MSVCRT argument parsing - Eliminate TOCTOU in VBS launcher file verification - Fix test_tray mocks to target actual ctypes.get_last_error API
de536b5 to
993abc5
Compare
50d2496 to
9ab62dd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds native Windows Explorer integration to AmiFUSE. Users can right-click
.hdfand.adffiles to mount them directly from Explorer, with a system tray icon that manages active mounts. This transforms AmiFUSE from a CLI-only tool into an Explorer-integrated experience on Windows.The implementation includes a background launcher, system tray mount manager, working Windows unmount (via
CTRL_BREAK_EVENT), a migration from deprecatedwmicto CIM/PowerShell for mount discovery, and full Explorer file/directory manipulation support in write mode.What's New
For users:
.hdfand.adffiles: "Mount with AmiFUSE" (read-only) and "Mount Read-Write with AmiFUSE"amifuse register— adds file type associations underHKCU\Software\Classes(no admin required)amifuse unregister— cleanly removes all registry entries including empty key stubs.hdf(amber hard disk) and.adf(green floppy), pretty-file-icons style--backgroundmode now works on Windows — daemon mode guard removed, WinFSPuid=-1/gid=-1enables proper SID mapping for write mountsamifuse unmounton Windows — discovers mount PIDs and sendsCTRL_BREAK_EVENTfor clean terminationFileInfoTimeout=1000as defense-in-depthFor maintainability:
wmiccalls replaced withGet-CimInstancefallback (wmic is deprecated on Windows 11)fuse_fs.pytoplatform.pyfor reuseKnown Limitations
SHChangeNotify)How It Works
Flat context menu verbs, not cascading menus. Win11's modern context menu partially renders cascading submenus registered under HKCU — the parent item appears but the flyout is empty. Flat verbs work reliably in both classic and modern menus.
VBS wrapper for launcher. Explorer waits for directly-invoked executables to exit before releasing the context menu. A VBS wrapper launches
pythonw.exedetached and exits immediately, preventing Explorer from hanging.CREATE_BREAKAWAY_FROM_JOBprevents Job Object restrictions from blocking the subprocess.CTRL_BREAK_EVENT is the only clean unmount on Windows. Programmatic testing of 5 methods (
FSCTL_DISMOUNT_VOLUME,mountvol /D, WinFSPlaunchctl,CTRL_BREAK_EVENT,taskkill) showed onlyCTRL_BREAK_EVENTproduces a clean exit. WinFSP volumes don't support standard volume IOCTLs, aren't standard mount points, and don't respond toWM_CLOSE.Deferred-delete for WinFSP compatibility. WinFSP calls FUSE
unlink()during Cleanup (beforerelease()), while the Amiga handler still holds an internal file lock. AmiFUSE defers the deletion torelease(), which frees the handler lock first, then retries the delete.Active-handle getattr fallback for file creation. WinFSP calls
fgetattrimmediately aftercreate(), but the stat cache has TTL 0 in write mode and the Amiga handler hasn't committed yet -- producing ENOENT. When a file has an active open handle, getattr now returns synthetic attributes immediately instead of querying the handler.FileInfoTimeout=1000in WinFSP mount options provides defense-in-depth by caching file attributes for 1 second.Inhibit cycle for directory deletion. PFS3 and SFS maintain internal cache/reserved-block state after directory modifications. Even with zero volume locks,
ACTION_DELETE_OBJECTreturnsOBJECT_IN_USE(202). AnACTION_INHIBITcycle (inhibit then uninhibit) forces the handler to release all internal state -- the AmigaDOS-sanctioned way to clear handler caches. Directory deletion is deferred viaopendir/releasedirtracking, with atomic bridge-level delete holding the lock across locate-parent + delete to prevent concurrent readdir from creating stale locks. Pending-delete entries are filtered fromreaddirandgetattrresults.No .iso/.img registration. Windows handles these natively.
Files
amifuse/windows_shell.py(new)amifuse/launcher.py(new)amifuse/tray.py(new)amifuse/platform.py_pid_existsfix, driver size check,FileInfoTimeoutin Windows mount optionsamifuse/fuse_fs.pyamifuse/doctor.pypyproject.tomlpystray/Pillowoptional deps,amifuse-launcher/amifuse-traygui-scriptsREADME.mdTest Plan
Automated (all passing, 454 unit + 11 integration):
windows_shell.py— register, unregister, icon generation, edge casestray.py— menu building, mount discovery, unmount, single instance, auto-exit, quit signallauncher.py— subprocess spawning, logging, tray startupplatform.pyadditions —kill_pids,_pid_exists, dedup, CIM fallback, FileInfoTimeout assertiondoctor.py— tray dependency checktest_windows_shell_integration.py)test_detached_process.py)test_launcher_e2e.py)test_fuse_fs.py,test_platform.py,test_status.pyfor function extractionManual validation (2026-06-25):
amifuse register— context menu entries appear for .hdf/.adf in Exploreramifuse unregister— all registry entries removed cleanly (no empty stubs)amifuse unmount D:from CLI — clean termination via CTRL_BREAK_EVENT