# How Superfile Handles Trash Across Different Operating Systems

> Discover how Superfile manages trash on Linux, macOS, and Windows using OS-specific backends. Learn about its fallback to permanent deletion when native recycling isn't available.

- Repository: [Yorukot/superfile](https://github.com/yorukot/superfile)
- Tags: internals
- Published: 2026-07-26

---

**Superfile delegates system-trash operations to a dedicated Go package in `src/internal/trash`, using build tags to compile OS-specific backends for Linux, macOS, and Windows while falling back to permanent deletion when native recycling is unavailable.**

The terminal file manager from `yorukot/superfile` manages cross-platform file deletion through a unified trash abstraction. How superfile handles trash across different operating systems depends on a compact internal package that leverages Go build tags, native APIs, and the FreeDesktop.org specification. The core integration point lives in [`src/internal/handle_file_operations.go`](https://github.com/yorukot/superfile/blob/main/src/internal/handle_file_operations.go), where the application decides whether to recycle or permanently delete an item.

## Cross-Platform Trash Architecture

The trash layer is intentionally small. A single public API in [`src/internal/trash/trash.go`](https://github.com/yorukot/superfile/blob/main/src/internal/trash/trash.go) declares the `Result` struct, the `Backend` type, and the sentinel `ErrUnsupported`. Every platform must implement two functions:

- **`Available(path string) bool`** – Reports whether the backend can trash the given path.
- **`Move(path string) (Result, error)`** – Moves the item into the system trash and returns metadata about the operation.

Go build tags select the correct implementation file at compile time. The package contains [`trash_linux.go`](https://github.com/yorukot/superfile/blob/main/trash_linux.go), [`trash_darwin.go`](https://github.com/yorukot/superfile/blob/main/trash_darwin.go), [`trash_windows.go`](https://github.com/yorukot/superfile/blob/main/trash_windows.go), [`trash_darwin_nocgo.go`](https://github.com/yorukot/superfile/blob/main/trash_darwin_nocgo.go), and [`trash_unsupported.go`](https://github.com/yorukot/superfile/blob/main/trash_unsupported.go), ensuring only one OS-specific variant is linked into the final binary.

## Linux Trash and the FreeDesktop.org Specification

On Linux, [`src/internal/trash/trash_linux.go`](https://github.com/yorukot/superfile/blob/main/src/internal/trash/trash_linux.go) implements the **FreeDesktop.org Trash** specification. It locates an appropriate trash directory—typically `~/.local/share/Trash` or a hidden `.Trash-<uid>` folder on the same filesystem as the file—and then performs three steps:

1. Generates a unique trash name.
2. Writes a `.trashinfo` file containing the original path and deletion timestamp.
3. Moves the file into the `files/` subdirectory.

This approach guarantees that standard Linux file managers can read and restore superfile-trashed items.

```go
// Move implementation for Linux
func Move(path string) (Result, error) {
    srcAbs, _ := filepath.Abs(path)
    td, err := selectTrashDir(srcAbs, true)      // find/create appropriate trash dir
    trashName, infoPath, filesPath, err := reserveTrashInfo(td, srcAbs)
    // write .trashinfo, then move the file
    if err := movePath(srcAbs, filesPath); err != nil { … }
    return Result{
        OriginalPath: srcAbs,
        TrashedPath:  filepath.Join(td.files, trashName),
        Backend:      BackendFreeDesktop,
        StrictlyRecycled: true,
    }, nil
}

```

## macOS Trash via the Foundation Framework

Superfile’s macOS support requires **C-go**. In [`src/internal/trash/trash_darwin.go`](https://github.com/yorukot/superfile/blob/main/src/internal/trash/trash_darwin.go), the `Move` function calls the native Foundation `FileManager.trashItem(at:)` API through a C wrapper named `spf_trash_item`. The wrapper returns the item’s new path inside the macOS Trash and any error emitted by the OS.

If superfile is compiled without CGO, [`src/internal/trash/trash_darwin_nocgo.go`](https://github.com/yorukot/superfile/blob/main/src/internal/trash/trash_darwin_nocgo.go) is selected instead. That stub returns `ErrUnsupported`, forcing the caller to fall back to permanent deletion.

```go
cPath := C.CString(absPath)
defer C.free(unsafe.Pointer(cPath))

result := C.spf_trash_item(cPath)               // Foundation FileManager call
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

```

## Windows Trash Using COM IFileOperation

On Windows, [`src/internal/trash/trash_windows.go`](https://github.com/yorukot/superfile/blob/main/src/internal/trash/trash_windows.go) uses the **COM** `IFileOperation` interface with the `FOF_ALLOWUNDO` flag to recycle files. The implementation converts the path to an `IShellItem` via `SHCreateItemFromParsingName`, invokes `DeleteItem`, and then runs `PerformOperations`.

Because COM requires consistent thread affinity, the operation spawns a goroutine and locks it to the underlying OS thread with `runtime.LockOSThread()`.

```go
absPath, _ := filepath.Abs(path)
result := Result{OriginalPath: absPath, Backend: BackendWindows}
errCh := make(chan error, 1)
go func() {
    runtime.LockOSThread()
    defer runtime.UnlockOSThread()
    errCh <- recycleWithIFileOperation(absPath) // performs the recycle
}()
if err := <-errCh; err != nil {
    return result, err
}
result.StrictlyRecycled = true
return result, nil

```

## Unsupported Platforms and Graceful Fallback

For any operating system not covered above, [`src/internal/trash/trash_unsupported.go`](https://github.com/yorukot/superfile/blob/main/src/internal/trash/trash_unsupported.go) provides a stub where `Available` always returns `false` and `Move` always returns `ErrUnsupported`. This ensures the codebase compiles on exotic platforms without trash semantics.

The integration logic in [`src/internal/handle_file_operations.go`](https://github.com/yorukot/superfile/blob/main/src/internal/handle_file_operations.go) decides at runtime whether to recycle or permanently delete. Inside `makeDeleteProcessor`, the code swaps the deletion strategy based on the `useTrash` setting:

```go
// inside makeDeleteProcessor (src/internal/handle_file_operations.go)
deleteFunc := os.RemoveAll                    // default: permanent delete
if useTrash {
    deleteFunc = func(item string) error {
        // Move the item to the system trash
        _, err := trash.Move(item)
        return err
    }
}
for _, item := range items {
    if err := deleteFunc(item); err != nil {
        // handle error …
    }
}

```

When the trash backend reports `ErrUnsupported` or `Available` is `false`, superfile falls back to `os.RemoveAll`, ensuring the deletion still completes.

## Summary

- Superfile centralizes trash logic in the `src/internal/trash` package and selects OS-specific code via Go build tags.
- **Linux** follows the FreeDesktop.org spec, writing `.trashinfo` files to `~/.local/share/Trash` or a per-filesystem `.Trash-<uid>` directory.
- **macOS** delegates to Foundation’s `FileManager.trashItem(at:)` through a C-go wrapper; a nocgo stub returns `ErrUnsupported`.
- **Windows** recycles items via COM `IFileOperation` with `FOF_ALLOWUNDO`, executing on a locked OS thread.
- If trash is unavailable or unsupported, [`src/internal/handle_file_operations.go`](https://github.com/yorukot/superfile/blob/main/src/internal/handle_file_operations.go) falls back to permanent deletion with `os.RemoveAll`.

## Frequently Asked Questions

### What Linux trash specification does Superfile follow?

Superfile follows the **FreeDesktop.org Trash** specification as implemented in [`src/internal/trash/trash_linux.go`](https://github.com/yorukot/superfile/blob/main/src/internal/trash/trash_linux.go). It generates unique trash names, writes `.trashinfo` metadata files, and stores the actual content under the `files/` subdirectory so standard Linux file managers can restore trashed items.

### Why does the Windows trash implementation lock the OS thread?

The Windows backend in [`src/internal/trash/trash_windows.go`](https://github.com/yorukot/superfile/blob/main/src/internal/trash/trash_windows.go) calls COM `IFileOperation`, which requires thread affinity. By spawning a goroutine and invoking `runtime.LockOSThread()`, superfile guarantees that the COM interface is created, invoked, and destroyed on the same OS thread, preventing runtime errors.

### What happens when Superfile is built on macOS without CGO?

When CGO is disabled, Go selects [`src/internal/trash/trash_darwin_nocgo.go`](https://github.com/yorukot/superfile/blob/main/src/internal/trash/trash_darwin_nocgo.go), whose `Move` returns `ErrUnsupported`. The deletion logic in [`src/internal/handle_file_operations.go`](https://github.com/yorukot/superfile/blob/main/src/internal/handle_file_operations.go) then falls back to `os.RemoveAll`, performing a permanent delete instead of moving the item to the macOS Trash.

### How does Superfile decide whether to trash or permanently delete a file?

Each platform file implements `Available(path string) bool`. On Linux, this returns `true` for any non-empty path; on macOS and Windows it validates the path string; on unsupported platforms it returns `false`. If `Available` is `false` or `trash.Move` returns an error, the core processor in [`src/internal/handle_file_operations.go`](https://github.com/yorukot/superfile/blob/main/src/internal/handle_file_operations.go) switches the delete function from `trash.Move` to `os.RemoveAll`.