shell

package
v0.13.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: BSD-3-Clause Imports: 14 Imported by: 0

Documentation

Overview

Package shell holds the pure composition/model logic of the desktop shell: the launchable-app index, MIME "open with" resolution, the categorized application-menu model, the directory listing model and the thumbnail cache-key derivation. None of it touches a rendering surface, so every branch is unit-coverable against temp-dir fixtures; the render package turns these models into go-widgets widgets.

Index

Constants

View Source
const MimeDirectory = "inode/directory"

MimeDirectory is the MIME type the shell assigns to directory entries.

Variables

View Source
var (
	// ErrMoveIntoSelf is returned when src already lives directly inside
	// destDir, so the move would be a no-op.
	ErrMoveIntoSelf = errors.New("move: source is already in the destination")
	// ErrMoveExists is returned when an entry of the same name already exists in
	// destDir: the move is refused rather than silently overwriting it.
	ErrMoveExists = errors.New("move: an item with that name already exists")
	// ErrMoveNotDir is returned when destDir exists but is not a directory.
	ErrMoveNotDir = errors.New("move: destination is not a directory")
)

Move refusals: MoveFile returns one of these sentinel errors when it declines a move for a structural reason (as opposed to an OS error it passes through).

Functions

func HumanBytes added in v0.11.0

func HumanBytes(n int64) string

HumanBytes formats a byte count as a compact human-readable string using French unit abbreviations (o, Ko, Mo, Go, To) and decimal (1000) steps, the convention macOS/Finder uses.

func IsImageName added in v0.10.0

func IsImageName(name string) bool

IsImageName reports whether name has a known image file extension. It is the single source of truth for extension-based image detection, shared by the thumbnail-eligibility policy here and the render layer's picture-glyph fallback, so the two never drift.

func MimeByExt added in v0.11.0

func MimeByExt(name string) string

MimeByExt maps a file name's extension to a MIME type by a small built-in table, or "" when the extension is not recognised. It covers the common image/video/audio/text/document kinds the file manager needs to pick an icon and a Type label; it never touches the filesystem.

func MoveFile added in v0.12.0

func MoveFile(src, destDir string) (string, error)

MoveFile moves the file (or directory) at src into destDir, preserving src's base name, and returns the final destination path.

On the same volume it is a single os.Rename; when the rename reports a cross-device error (EXDEV) it falls back to copying src's bytes into destDir and removing the original. It refuses, without touching the filesystem:

  • a no-op — src is already directly inside destDir (ErrMoveIntoSelf);
  • a name collision — destDir already holds an entry of src's base name (ErrMoveExists), so an existing file is never silently overwritten;
  • a destDir that exists but is not a directory (ErrMoveNotDir).

Any other filesystem error (a missing destDir, a permission denial on the rename/copy/remove, a source that cannot be read) is returned unwrapped so the caller can surface it to the user — MoveFile never panics on one.

func Thumbnailable

func Thumbnailable(it FileItem) bool

Thumbnailable reports whether a file item should be given an image preview: a regular file that is an image, recognised either by its classified MIME type (image/*) or, when MIME classification is unavailable or weak, by a known image file extension — so a macOS .png classified as application/octet-stream is still thumbnailed. Directories and non-image files are never thumbnailed.

Types

type App

type App struct {
	ID          string
	Name        string
	GenericName string
	Comment     string
	Icon        string
	Exec        string
	Categories  []string
	Keywords    []string
	Terminal    bool
	// contains filtered or unexported fields
}

App is one launchable application, distilled from a desktop entry into the fields a dock / launcher needs. The originating entry is retained so the launcher can expand its Exec line at click time.

func (App) Entry

func (a App) Entry() *desktopentry.Entry

Entry returns the desktop entry this App was built from (nil for an App that was not derived from one). The launcher passes it to desktopentry.ExpandExec.

func (App) Label

func (a App) Label() string

Label is the user-visible name, falling back to the desktop-file id when the entry carries no Name.

type AppIndex

type AppIndex struct {
	// contains filtered or unexported fields
}

AppIndex is a sorted, de-duplicated, searchable set of launchable apps.

func NewAppIndex

func NewAppIndex(entries []*desktopentry.Entry) *AppIndex

NewAppIndex builds an index from scanned desktop entries. Non-launchable entries (wrong Type, no Exec) are dropped; the first entry seen for a given non-empty id wins (desktopentry.Scan already applies XDG directory precedence, so "first" means "highest priority"). Apps are ordered case-insensitively by their display label.

func (*AppIndex) All

func (x *AppIndex) All() []App

All returns a copy of the indexed apps in order.

func (*AppIndex) At

func (x *AppIndex) At(i int) App

At returns the app at index i.

func (*AppIndex) Len

func (x *AppIndex) Len() int

Len is the number of indexed apps.

func (*AppIndex) Search

func (x *AppIndex) Search(query string) []App

Search returns the apps matching query in index order. A blank (or whitespace-only) query returns every app.

type AppSource added in v0.4.0

type AppSource interface {
	// Apps is the sorted, de-duplicated, searchable launchable-app index that
	// feeds the dock and launcher.
	Apps() *AppIndex
	// Menu is the flattened, ordered application menu.
	Menu() *MenuModel
	// Dir is the current directory listing, already MIME-classified, that
	// feeds the file grid. It may be nil when no directory is available.
	Dir() *Dir
	// Resolve answers the "open with" query for a bare file name plus optional
	// leading content bytes: the classified MIME type, its default application
	// and the ordered candidate applications.
	Resolve(name string, content []byte) OpenWith
	// IconBytes returns the encoded image bytes (PNG/JPEG/GIF) for an icon
	// referenced by theme name (e.g. "web-browser") or by absolute path (e.g.
	// a thumbnail), and ok=false when it cannot be resolved. The render layer
	// decodes the bytes into a go-widgets Image (falling back to a placeholder
	// swatch on ok=false), so this is the single icon-pixel seam a source must
	// satisfy — no source touches the toolkit.
	IconBytes(name string) ([]byte, bool)
	// ThumbKey returns the IconBytes key of a file item's thumbnail — an
	// absolute cache path for the native source, a virtual asset name for the
	// embedded one — or "" when the item is not thumbnailed.
	ThumbKey(it FileItem) string
}

AppSource is the data source behind the desktop shell: everything the UI needs to render a populated desktop, abstracted away from where it comes from. It supplies the launchable-app index, the categorized application menu, the current directory listing (already MIME-classified), the "open with" resolution for a file, the encoded image bytes for an icon (by theme name or absolute path), and — for a file item — the icon-loader key of its thumbnail (or "" when it has none).

The two implementations live in github.com/go-widgets/desktop/source:

  • the native xdgSource scans a real XDG filesystem (desktopentry.Scan + icontheme + mime/mimeapps + menu.Load + go-thumbnail), exactly the behavior the shell has always had; and
  • the embeddedSource serves a curated set from an embed.FS, so the shell renders a real, populated desktop in the browser (js/wasm), where no real filesystem exists.

Both drive the identical shell/render composition logic — the whole point of the seam — so a Scene built from either is indistinguishable to the toolkit.

type Category

type Category struct {
	Name string
	Icon string
	Apps []App
}

Category is one branch of the application menu: a user-visible directory name (plus its icon) and the launchable apps allocated to it.

type Dir

type Dir struct {
	Path  string
	Items []FileItem
}

Dir is a listed directory: its path and its items, directories first then files, each group ordered case-insensitively by name.

func ListDir

func ListDir(path string) (*Dir, error)

ListDir lists path into a Dir. Hidden entries (dotfiles: names beginning with ".", e.g. .ssh, .cache, .Trash) are skipped, so the file grid shows a clean user-facing directory rather than a wall of dotfile clutter. It does not classify MIME types (call Classify for that); a read failure (missing / unreadable directory) is returned as an error.

func (*Dir) Classify

func (d *Dir) Classify(r *Resolver)

Classify fills each item's Mime using r: directories get MimeDirectory, regular files are classified by name+content. A file that cannot be resolved (e.g. it became unreadable) is left with an empty Mime rather than aborting the whole listing.

func (*Dir) ClassifyLite added in v0.11.0

func (d *Dir) ClassifyLite()

ClassifyLite fills each item's Mime from its name/extension only, without the heavy content-sniffing Resolver — the portable classifier the file manager uses while navigating (a Resolver needs an XDG MIME database that does not exist on macOS/Windows or in the browser). Directories get MimeDirectory; regular files get an extension-derived MIME (or "" when the extension is unknown, which the Type column renders from the extension itself).

type FileItem

type FileItem struct {
	Name    string
	Path    string
	IsDir   bool
	Mime    string
	Size    int64     // byte size of a regular file (0 for directories)
	ModTime time.Time // last-modification time (zero when unavailable)
}

FileItem is one entry of a listed directory.

func (FileItem) HumanSize added in v0.11.0

func (it FileItem) HumanSize() string

HumanSize renders the item's byte size for the list view: an em dash for a directory (Finder shows "--" there), otherwise a compact human-readable size.

func (FileItem) IsImage added in v0.11.0

func (it FileItem) IsImage() bool

IsImage reports whether the item is a raster image the file manager can thumbnail (by MIME class when classified, else by name extension).

func (FileItem) ModTimeString added in v0.11.0

func (it FileItem) ModTimeString() string

ModTimeString formats the item's modification time for the list view's Date column ("--" when the time is unavailable).

func (FileItem) TypeLabel added in v0.11.0

func (it FileItem) TypeLabel() string

TypeLabel is the human-readable kind shown in the list view's Type column: "Dossier" for a directory, otherwise a short label derived from the MIME type (when classified) or the file-name extension.

type MenuModel struct {
	Categories []Category
}

MenuModel is the desktop application menu flattened into an ordered list of non-empty categories, ready to render as a go-widgets Menu / MenuBar. Nested submenus are walked depth-first, so a category appears once per menu node that carries apps, in menu (layout) order.

func NewMenuModel

func NewMenuModel(tree *menu.Tree) *MenuModel

NewMenuModel flattens a resolved menu.Tree. A nil tree (or a tree with a nil Root) yields an empty model. Only menu nodes that actually contain apps become categories; a purely structural submenu with no direct apps is skipped but still descended into.

func (m *MenuModel) Len() int

Len is the number of non-empty categories.

type OpenWith

type OpenWith struct {
	MimeType   string
	Default    App
	HasDefault bool
	Candidates []App
}

OpenWith is the resolved "open with" answer for a path or MIME type: the classified MIME type, the default application (if any) and the ordered list of candidate applications.

type Place added in v0.11.0

type Place struct {
	Label string
	Path  string
	Kind  PlaceKind
}

Place is one sidebar row: a display label, the directory it navigates to (empty when the kind is not navigable, e.g. the network) and its kind.

func (Place) Navigable added in v0.11.0

func (p Place) Navigable() bool

Navigable reports whether clicking the place opens a real directory listing. The network location is not navigable — it shows the empty-shares state.

type PlaceKind added in v0.11.0

type PlaceKind int

PlaceKind classifies a sidebar entry so the render layer can pick an icon and so navigation can special-case the non-directory kinds (the network location has no path and shows an honest empty state).

const (
	// PlaceFolder is an ordinary navigable directory (a Favoris entry).
	PlaceFolder PlaceKind = iota
	// PlaceApplications is the applications folder (a Favoris entry).
	PlaceApplications
	// PlacePictures / PlaceMovies / PlaceMusic / PlaceDownloads are the media
	// Favoris entries, split out so each gets a distinct sidebar glyph.
	PlacePictures
	PlaceMovies
	PlaceMusic
	PlaceDownloads
	PlaceDesktop
	PlaceDocuments
	// PlaceHome is the user's home directory (an Emplacements entry).
	PlaceHome
	// PlaceVolume is the startup volume / root drive (an Emplacements entry).
	PlaceVolume
	// PlaceNetwork is the network location: there is no real network browsing
	// in a pure-Go shell, so it navigates to an honest empty "Aucun partage"
	// state rather than a fake listing. Its Path is "".
	PlaceNetwork
	// PlaceTrash is the trash / recycle bin (an Emplacements entry).
	PlaceTrash
)

type Places added in v0.11.0

type Places struct {
	Favorites []Place
	Locations []Place
}

Places is the file manager's sidebar model: the two labelled sections a macOS Finder shows — Favoris (user folders) and Emplacements (home, the startup volume, the network and the trash).

func DefaultPlaces added in v0.11.0

func DefaultPlaces() *Places

DefaultPlaces resolves the sidebar model for the running OS: real per-user paths on macOS (~/Desktop, ~/Documents, /Applications, ~/Pictures, ~/Movies, ~/Music, ~/Downloads; "Macintosh HD" -> /, ~/.Trash), the equivalent XDG user-dirs on Linux, and the known folders on Windows. When the home directory cannot be resolved (notably the js/wasm browser build, which has no real filesystem) it returns a minimal, non-crashing model so the sidebar still renders and the shell still builds.

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver answers MIME classification and application-association queries by combining a shared MIME-info database with a mimeapps.list resolver.

func NewResolver

func NewResolver(db *mime.Database, apps *mimeapps.Resolver) *Resolver

NewResolver wires a MIME database and an application-association resolver into a Resolver.

func (*Resolver) ResolveName

func (r *Resolver) ResolveName(name string, content []byte) OpenWith

ResolveName classifies a bare name plus optional content bytes and resolves the associated applications. It performs no I/O, so it is the pure seam the path-based helper builds on.

func (*Resolver) ResolvePath

func (r *Resolver) ResolvePath(path string) (OpenWith, error)

ResolvePath classifies path by both name and content and resolves its associated applications. Directories (and other non-regular paths) are classified by name only. A stat failure (e.g. the path does not exist) or a content-read failure is returned as an error.

type Thumbnailer

type Thumbnailer struct {
	// contains filtered or unexported fields
}

Thumbnailer derives the freedesktop Thumbnail Managing Standard cache keys and paths for a file grid. It delegates the URI canonicalization, MD5 hash, cache-path layout and the actual decode+downscale+cache to github.com/go-thumbnail/thumbnail (the owner of that mechanism) and adds only the shell's policy: which items are thumbnailed and under which stable key.

func NewThumbnailer

func NewThumbnailer(size thumbnail.Size) *Thumbnailer

NewThumbnailer builds a Thumbnailer for the given standard thumbnail size.

func (*Thumbnailer) Ensure added in v0.10.0

func (t *Thumbnailer) Ensure(it FileItem) string

Ensure generates (or refreshes) the item's thumbnail through the go-thumbnail cache — decode the source image, downscale it into the standard size bucket and write the freedesktop-keyed PNG under $XDG_CACHE_HOME/thumbnails — and returns its on-disk cache path (the IconBytes key the render layer decodes), or "" when the item is not thumbnailable or the thumbnail could not be produced (an unreadable or undecodable source). The outcome is memoized per source path, positive and negative, so a repeated call is a map lookup.

func (*Thumbnailer) Key

func (t *Thumbnailer) Key(it FileItem) string

Key returns the stable cache key (the MD5 hash of the file's canonical file:// URI) for a thumbnailable item, or "" when the item is not thumbnailable. Two items with the same path always produce the same key.

func (*Thumbnailer) Path

func (t *Thumbnailer) Path(it FileItem) string

Path returns the on-disk cache path where the item's thumbnail lives (or would be generated), or "" for a non-thumbnailable item.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL