How the Trash Feature is Implemented Across Different Operating Systems in Superfile
Superfile uses a build-tag-selected abstraction layer in src/internal/trash to delegate deletion operations to native platform APIs—FreeDesktop.org on Linux, Foundation Framework on macOS, and COM IFileOperation on Windows—falling back to permanent deletion when trash is unavailable.
The open-source terminal file manager superfile (yorukot/superfile) implements cross-platform trash functionality through a modular Go architecture. Rather than simply deleting files permanently, the application detects the host operating system at compile time and routes trash operations to the appropriate native backend, ensuring users get expected OS behavior regardless of platform.
Build-Time Architecture and API Surface
Superfile abstracts the system trash behind a minimal public API defined in src/internal/trash/trash.go. The package exposes two primary functions that every platform must implement:
Available(path string) bool– Reports whether trash operations are supported for the given path.Move(path string) (Result, error)– Executes the actual trash operation and returns metadata about the action.
The appropriate implementation is selected at compile time using Go build tags. Each operating system provides a specific file that satisfies this interface:
- Linux:
trash_linux.go(FreeDesktop.org spec) - macOS:
trash_darwin.go(with CGO) ortrash_darwin_nocgo.go(fallback) - Windows:
trash_windows.go(COM interface) - Other platforms:
trash_unsupported.go(returnsErrUnsupported)
Linux Implementation: FreeDesktop.org Compliance
On Linux, superfile follows the FreeDesktop.org Trash specification. The implementation resides in src/internal/trash/trash_linux.go and handles cross-filesystem edge cases while maintaining metadata integrity.
Trash Directory Selection
The Linux backend selects the appropriate trash directory based on the file's location. For files on the same filesystem as the user's home directory, it uses ~/.local/share/Trash. For files on mounted drives, it creates a .Trash-<uid> directory at the filesystem root to avoid cross-device moves.
// Reserve a unique name and paths for .trashinfo and the file itself
trashName, infoPath, filesPath, err := reserveTrashInfo(trashDir, srcAbs)
if err != nil {
return Result{}, err
}
// Write the .trashinfo metadata file
if err := writeTrashInfo(infoPath, srcAbs, deletionTime); err != nil {
return Result{}, err
}
// Move the actual file into trash files directory
if err := movePath(srcAbs, filesPath); err != nil {
return Result{}, err
}
The writeTrashInfo function generates a .trashinfo file containing the original path and deletion timestamp, allowing restoration tools to return the file to its original location. The backend sets StrictlyRecycled: true and returns BackendFreeDesktop in the Result struct upon success.
macOS Implementation: Foundation Framework Integration
macOS support depends on CGO availability. When compiled with CGO enabled, trash_darwin.go imports the Foundation framework and calls FileManager.trashItem(at:) through C wrappers.
Native API Delegation
The implementation marshals the absolute path to a C string and invokes the native Objective-C trash API:
cPath := C.CString(absPath)
defer C.free(unsafe.Pointer(cPath))
result := C.spf_trash_item(cPath) // Calls Foundation FileManager
if result.errorMessage != nil {
return Result{
OriginalPath: absPath,
Backend: BackendMacOS,
}, errors.New(C.GoString(result.errorMessage))
}
trashedPath := ""
if result.trashedPath != nil {
trashedPath = C.GoString(result.trashedPath)
}
return Result{
OriginalPath: absPath,
TrashedPath: trashedPath,
Backend: BackendMacOS,
StrictlyRecycled: true,
}, nil
If CGO is disabled, trash_darwin_nocgo.go provides a stub implementation that returns ErrUnsupported, forcing superfile to fall back to permanent deletion.
Windows Implementation: COM IFileOperation
The Windows backend in trash_windows.go utilizes the Component Object Model (COM) to access the shell's recycle functionality. This ensures that trashed files appear in the Windows Recycle Bin with full undo support.
Secure Recycling via Shell APIs
The implementation uses IFileOperation with the FOF_ALLOWUNDO flag to enable restoration:
absPath, _ := filepath.Abs(path)
result := Result{
OriginalPath: absPath,
Backend: BackendWindows,
}
errCh := make(chan error, 1)
go func() {
runtime.LockOSThread() // COM requires threaded apartments
defer runtime.UnlockOSThread()
errCh <- recycleWithIFileOperation(absPath)
}()
if err := <-errCh; err != nil {
return result, err
}
result.StrictlyRecycled = true
return result, nil
The recycleWithIFileOperation function calls SHCreateItemFromParsingName to obtain an IShellItem, then invokes DeleteItem followed by PerformOperations to execute the recycle action atomically.
Integration and Fallback Handling
Superfile integrates the trash system within src/internal/handle_file_operations.go. The makeDeleteProcessor function constructs the deletion strategy based on user preferences and platform capability:
deleteFunc := os.RemoveAll // Default: permanent delete
if useTrash {
deleteFunc = func(item string) error {
_, err := trash.Move(item)
return err
}
}
for _, item := range items {
if err := deleteFunc(item); err != nil {
// Handle error: log and continue or abort based on config
}
}
Before attempting trash operations, the code checks trash.Available(path). If the platform reports ErrUnsupported or Available returns false—such as on non-CGO macOS builds or unsupported operating systems—superfile gracefully degrades to os.RemoveAll for permanent deletion.
Summary
- Superfile implements cross-platform trash support through the
src/internal/trashpackage using Go build tags for compile-time selection. - Linux follows the FreeDesktop.org specification, creating
.trashinfometadata and managing per-filesystem trash directories. - macOS delegates to the Foundation Framework via CGO when available, providing native Trash integration.
- Windows uses COM
IFileOperationwithFOF_ALLOWUNDOto ensure files appear in the Recycle Bin. - The system gracefully falls back to permanent deletion when
Available()returns false orErrUnsupportedis encountered.
Frequently Asked Questions
How does superfile handle trash on network drives or external mounts?
On Linux, superfile detects cross-filesystem paths and creates a .Trash-<uid> directory at the mount point's root, adhering to the FreeDesktop.org specification for external media. This prevents slow cross-device copying and ensures metadata is stored with the trashed files. macOS and Windows handle this automatically through their native APIs.
Can I restore files from trash using superfile?
The current implementation focuses on moving files to trash rather than restoration. While the Linux backend writes proper .trashinfo metadata that standard Linux file managers can use for restoration, superfile itself does not currently provide a built-in restore function. You must use your operating system's native trash/recycle interface to recover files.
What happens if I compile superfile without CGO on macOS?
If you build superfile with CGO_ENABLED=0 on macOS, the build system selects trash_darwin_nocgo.go, which returns ErrUnsupported for all trash operations. In this configuration, deletion operations fall back to permanent removal via os.RemoveAll, and files will not appear in the macOS Trash.
Why does Windows use a goroutine for trash operations?
The Windows implementation locks the OS thread (runtime.LockOSThread()) because COM operations require a consistent thread apartment state. Running the recycleWithIFileOperation function in a dedicated goroutine with thread locking prevents interference from Go's runtime thread scheduling, ensuring the COM initialize/uninitialize calls occur on the same thread and avoid undefined behavior.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →