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
- Variables
- func CopyFile(src, destDir string) (string, error)
- func CopyFileReplace(src, destDir string) (string, error)
- func HumanBytes(n int64) string
- func IsImageName(name string) bool
- func MimeByExt(name string) string
- func MoveFile(src, destDir string) (string, error)
- func MoveFileReplace(src, destDir string) (string, error)
- func Thumbnailable(it FileItem) bool
- type App
- type AppIndex
- type AppSource
- type Category
- type ClipOp
- type Clipboard
- func (c *Clipboard) Clear()
- func (c *Clipboard) Empty() bool
- func (c *Clipboard) IsCut(path string) bool
- func (c *Clipboard) Op() ClipOp
- func (c *Clipboard) PasteKindFor(src, destDir string, forceMove bool) PasteKind
- func (c *Clipboard) Paths() []string
- func (c *Clipboard) SetCopy(paths ...string)
- func (c *Clipboard) SetCut(paths ...string)
- type Dir
- type FileItem
- type MenuModel
- type OpenWith
- type PasteKind
- type Place
- type PlaceKind
- type Places
- type Resolver
- type Thumbnailer
Constants ¶
const MimeDirectory = "inode/directory"
MimeDirectory is the MIME type the shell assigns to directory entries.
Variables ¶
var ( // ErrCopyExists is returned by CopyFile when an entry of the same name // already exists in a DIFFERENT destination directory: the copy is refused // rather than silently overwriting it, so the caller can offer a // confirm-overwrite dialog (then call CopyFileReplace). A SAME-directory // copy never returns this — it auto-suffixes (" copie") instead. ErrCopyExists = errors.New("copy: an item with that name already exists") // ErrCopyNotDir is returned when destDir exists but is not a directory. ErrCopyNotDir = errors.New("copy: destination is not a directory") // ErrCopyIntoSelf is returned when the copy would place a directory inside // its own subtree (an infinite recursion), e.g. copying /a into /a/b. ErrCopyIntoSelf = errors.New("copy: cannot copy a directory into itself") )
Copy refusals: CopyFile returns one of these sentinel errors when it declines a copy for a structural reason (as opposed to an OS error it passes through).
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 CopyFile ¶ added in v0.14.0
CopyFile copies the file (or directory) at src into destDir and returns the final destination path. Directories are copied recursively; every entry's permission bits are preserved.
Collisions are handled the way macOS's Finder does:
- a SAME-directory copy (destDir is src's own parent) is never a refusal: the copy is written under a free " copie"-suffixed name (then " copie 2", …), so ⌘C/⌘V in place always duplicates;
- a copy into a DIFFERENT directory that already holds an entry of src's base name returns ErrCopyExists (nothing written), so the caller can confirm an overwrite and, on confirmation, call CopyFileReplace.
It also refuses, without touching the filesystem, a destDir that is not a directory (ErrCopyNotDir) and a copy of a directory into its own subtree (ErrCopyIntoSelf). Any other filesystem error (a missing destDir, a permission denial, an unreadable source) is returned unwrapped — CopyFile never panics on one.
func CopyFileReplace ¶ added in v0.14.0
CopyFileReplace is CopyFile's overwrite peer: it copies src into destDir under src's own base name, first removing any existing entry of that name (so a confirmed "Remplacer" overwrites it). It is the call a caller makes after a CopyFile reported ErrCopyExists and the user confirmed the replacement. A no-op self-copy (the computed destination is src itself) is left untouched.
func HumanBytes ¶ added in v0.11.0
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
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
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
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 MoveFileReplace ¶ added in v0.14.0
MoveFileReplace is MoveFile's overwrite peer: it removes any existing entry of src's base name in destDir, then moves src in under that name — so a confirmed "Remplacer"/"Déplacer" overwrites the collision. It delegates the move itself to MoveFile, inheriting its same-volume rename and cross-volume (EXDEV) copy+remove fallback and its destDir/not-a-directory checks. It refuses a no-op (src already directly inside destDir, ErrMoveIntoSelf) before removing anything, so a same-directory call can never delete the source. Other filesystem errors are returned unwrapped.
func Thumbnailable ¶
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.
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.
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 ¶
Category is one branch of the application menu: a user-visible directory name (plus its icon) and the launchable apps allocated to it.
type ClipOp ¶ added in v0.14.0
type ClipOp int
ClipOp is the pending file-clipboard operation: nothing, a copy (⌘C/Ctrl+C) or a cut/move (⌘X/Ctrl+X). A Paste applies whichever op the clipboard holds.
const ( // ClipNone is an empty clipboard: a paste is a no-op. ClipNone ClipOp = iota // ClipCopy marks the paths to be COPIED on paste (the originals stay). ClipCopy // ClipCut marks the paths to be MOVED on paste (the originals disappear). // Cut paths are drawn dimmed in the view (IsCut) until pasted or cleared. ClipCut )
type Clipboard ¶ added in v0.14.0
type Clipboard struct {
// contains filtered or unexported fields
}
Clipboard is the Finder's file clipboard: a small, filesystem-free record of which path(s) the user marked with ⌘C / ⌘X (Ctrl on Linux/Windows) and which operation to apply when they ⌘V. It is the pure state half of the copy/paste feature — the actual bytes move through CopyFile / MoveFile once a paste is classified by PasteKindFor — so the whole state machine is unit-testable without a window.
func (*Clipboard) Clear ¶ added in v0.14.0
func (c *Clipboard) Clear()
Clear empties the clipboard (after a move-paste consumes it, or on Escape).
func (*Clipboard) Empty ¶ added in v0.14.0
Empty reports whether a paste would do nothing (no op, or no paths).
func (*Clipboard) IsCut ¶ added in v0.14.0
IsCut reports whether path is currently marked for a MOVE (so the view draws it dimmed). It is false for a copy mark and for any path not on the clipboard.
func (*Clipboard) PasteKindFor ¶ added in v0.14.0
PasteKindFor classifies pasting src into destDir under the current clipboard op. forceMove requests a move regardless of the op (macOS ⌘⌥V, "Move Item Here"). It reads the filesystem only to test for a name collision; it writes nothing.
func (*Clipboard) Paths ¶ added in v0.14.0
Paths is a copy of the marked paths (nil when empty), safe for the caller to keep or mutate.
type Dir ¶
Dir is a listed directory: its path and its items, directories first then files, each group ordered case-insensitively by name.
func ListDir ¶
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 ¶
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
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
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
ModTimeString formats the item's modification time for the list view's Date column ("--" when the time is unavailable).
type MenuModel ¶
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 ¶
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.
type OpenWith ¶
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 PasteKind ¶ added in v0.14.0
type PasteKind int
PasteKind classifies how a single clipboard entry will paste into a target directory, so the UI knows whether to proceed silently, confirm a move, or confirm an overwrite — WITHOUT mutating the filesystem.
const ( // PasteNoop: nothing to do (empty clipboard, or a move whose source is // already in the target directory). PasteNoop PasteKind = iota // PasteCopyPlain: a copy into another directory with no name collision — // proceed silently (macOS copies without a modal), then show a brief result. PasteCopyPlain // PasteCopySuffix: a copy back into the source's own directory — write a // " copie"-suffixed duplicate, no modal. PasteCopySuffix // PasteMoveConfirm: a move into another directory with no collision — // confirm "Déplacer « X » ici ?" before mutating. PasteMoveConfirm // PasteOverwriteConfirm: the target directory already holds an entry of the // source's name — confirm "Remplacer « X » ?" before overwriting. PasteOverwriteConfirm )
type Place ¶ added in v0.11.0
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.
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
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 ¶
NewResolver wires a MIME database and an application-association resolver into a Resolver.
func (*Resolver) ResolveName ¶
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 ¶
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.