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 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/trash package using Go build tags for compile-time selection.
  • Linux follows the FreeDesktop.org specification, creating .trashinfo metadata and managing per-filesystem trash directories.
  • macOS delegates to the Foundation Framework via CGO when available, providing native Trash integration.
  • Windows uses COM IFileOperation with FOF_ALLOWUNDO to ensure files appear in the Recycle Bin.
  • The system gracefully falls back to permanent deletion when Available() returns false or ErrUnsupported is 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →