dsstore

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: BSD-3-Clause Imports: 9 Imported by: 0

README

go-macos/dsstore

go-macos/dsstore

Go Reference CI

The .DS_Store the Finder keeps in a directory — a window's background picture and where its icons sit — written in pure Go, CGO_ENABLED=0, no shelling out.

A disk image that opens showing a background with the application on the left and a link to /Applications on the right is not doing anything clever with the volume, the image format, or the filesystem. All of it lives in one file in the volume's root, and until now nothing in Go could write one.

var s dsstore.Store
s.SetIconView(dsstore.IconView{
    Background: "/.background/bg.png", // hidden folder, conventional
    VolumeName: "MyApp",
    IconSize:   96,
})
s.SetIconPosition("MyApp.app", 160, 220)
s.SetIconPosition("Applications", 440, 220)

b, err := s.Bytes()          // write b to <volume>/.DS_Store

Parse reads one back, which is how the tests check this package against a file the Finder itself wrote rather than against its own output.

What the format is

A buddy-allocator image (magic Bud1) holding one B-tree named DSDB, whose records are (filename, four-character structure id, typed value) triples. Everything is big-endian, and every stored offset is relative to byte 4.

It is an allocator image, not a container with blocks placed in it, and the Finder reads it as one — a file whose blocks are laid out by hand parses perfectly and is silently ignored.

Two placements are load-bearing and no published description mentions either:

  • block 0 must be the bookkeeping block itself, 1 the DSDB master, 2 the root node;
  • relative offsets 0–63 are the header; the Finder never places a block below 64.

Three corrections to the descriptions that do exist, each measured against a file the Finder wrote on macOS 26 and each noted at the line it matters:

  • alias tags 14 and 15 carry a uint16 count of UTF-16 code units before the text (mac_alias's documentation omits it);
  • icvl is not written at all, though one account lists it;
  • the width of an allocator block is bounded on n-1, so a 4096-byte page does not claim 8192 (the Python reference has that bug).

What it deliberately does not do

The B-tree is written as a single leaf node. A window with a background has a handful of entries, which fits a 4 KiB page many times over; a store that needs more returns ErrTooLarge rather than emitting a file with a half-implemented split in it.

Verifying against macOS

The Finder is the only judge that matters and it is easy to get a wrong answer out of it — a fresh volume proves nothing, because the Finder ignores its own file on one too, and icon size alone is not a discriminator because the Finder carries the last window's settings over. VERIFY.md records the harness that does work, the positive control it needs, and how the background picture is proven without a screenshot.

Standards

Pure Go, CGO_ENABLED=0, no shelling out to a command-line tool in place of a library. Built and tested on amd64, arm64, riscv64, loong64, ppc64le and s390x — the last being big-endian, which for this format is the lane that matters. BSD-3-Clause.

Coverage is 96.6%, below the fleet's 100%, and the missing statements are named in the CI file rather than waved at: guards that no input reaching Parse can provoke.

Documentation

Overview

Package dsstore writes the .DS_Store file the Finder keeps in a directory, in pure Go with CGO_ENABLED=0 and no shelling out.

It exists for one job the rest of this fleet could not finish: a disk image that opens showing a background picture with its icons arranged on it. The picture is not a property of the volume, the image, or the filesystem — it is a record inside .DS_Store, and nothing in Go could write one.

The format

A .DS_Store is a "buddy allocator" image (magic "Bud1") holding one B-tree named DSDB, whose records are (filename, four-character structure id, typed value) triples sorted by case-insensitive filename. EVERYTHING is big-endian, and every stored offset is relative to byte 4 of the file.

It is an allocator IMAGE, not a container with blocks placed in it, and the Finder reads it as one: a file whose blocks are laid out by hand parses perfectly and is silently ignored.

The layout here was read off files the Finder itself wrote on macOS 26, for exactly this case — a volume with a background picture and positioned icons. Where the published descriptions disagree with what the Finder does, the comments say so at the point it matters.

What it deliberately does not do

The B-tree is written as a SINGLE leaf node. A window with a background has a handful of entries, which fits a 4 KiB page many times over; a store that needs more returns ErrTooLarge rather than emitting a file with a half-implemented split in it. Growing to a real B-tree is a change to this package, not to its callers.

Index

Constants

This section is empty.

Variables

View Source
var ErrTooLarge = errors.New("dsstore: records exceed one 4 KiB node")

ErrTooLarge is returned when the records do not fit the single leaf node this package writes. See the package comment: a partial B-tree is worse than a refusal.

Functions

func BuildAlias

func BuildAlias(volumeName, relPath string) ([]byte, error)

BuildAlias encodes an alias v2 pointing at relPath on the named volume.

relPath is the path INSIDE the volume, leading slash and all, because that is what tag 18 carries and what lets the reference survive being mounted somewhere else.

Types

type Blob

type Blob []byte

Blob is the "blob" type: a length-prefixed byte string. Both the window settings (a binary plist) and an icon position are blobs.

type Bool

type Bool bool

Bool is the "bool" type: a single byte.

type IconView

type IconView struct {
	// Background names the picture inside the volume, leading slash and all
	// (".background/bg.png" is conventional and hidden). Empty means no
	// picture, and backgroundType drops to 1 — a plain colour.
	Background string
	// VolumeName is the volume the picture lives on. The alias records it,
	// which is how the reference survives a different mount point.
	VolumeName string

	IconSize    float64 // 96 is what a disk-image window usually wants
	TextSize    float64 // 12 in the Finder's own file
	GridSpacing float64 // 100
	LabelBottom bool
	ShowPreview bool
	ShowInfo    bool
}

IconView is the window's icon-view settings — the "icvp" record, which is what carries the background picture.

The keys and their types were read off the Finder's own record: the numbers are REALS, not integers, and backgroundType is 2 for "a picture". The older BKGD and pict records are dead on modern macOS: a file carrying a valid BKGD/pict pair and no icvp made the Finder write a fresh icvp with backgroundType 0, ignoring them.

type Long

type Long uint32

Long is the "long" type: a 32-bit integer.

type Record

type Record struct {
	Name string
	ID   string // four characters: "Iloc", "icvp", "bwsp", "vSrn", …
	Val  Value
}

A Record is one (name, structure id, value) triple. Name is the entry the record is about; "." means the directory itself, which is where the window's own settings live.

type Store

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

Store is a set of records destined for one directory's .DS_Store.

func Parse

func Parse(b []byte) (*Store, error)

Parse reads a .DS_Store.

It exists as much for the tests as for callers: the only way to know this package writes what the Finder writes is to read the Finder's own file and compare. A writer checked against nothing but itself is checked against nothing.

func (*Store) Add

func (s *Store) Add(r Record)

Add appends a record, replacing any earlier one with the same name and id so that a builder layering defaults over a caller's wishes works.

func (*Store) Bytes

func (s *Store) Bytes() ([]byte, error)

Bytes serialises the store.

func (*Store) Records

func (s *Store) Records() []Record

Records returns the records in the order they will be written.

func (*Store) SetIconPosition

func (s *Store) SetIconPosition(name string, x, y uint32)

SetIconPosition places one entry's icon. x and y are the icon's CENTRE, in the window's coordinates.

func (*Store) SetIconView

func (s *Store) SetIconView(v IconView) error

SetIconView records the window's icon-view settings.

func (*Store) SetWindow

func (s *Store) SetWindow(w Window) error

SetWindow records where the window opens and how large it is.

type Type

type Type string

Type is the "type" type: four characters, such as 'icnv'.

type Value

type Value interface {
	// contains filtered or unexported methods
}

A Value is one of the typed payloads the format defines. Only the four this package needs are implemented; the rest are rejected rather than guessed at.

type Window

type Window struct {
	// X and Y are the window's BOTTOM-left corner, y measured up from the
	// bottom of the screen -- a Cocoa rect, not the top-left corner it looks
	// like. Asking for {{100, 100}, {600, 400}} on a screen whose desktop
	// ends at 1117 put the window's bottom edge at 1017, which is where the
	// Finder reported it. Width and Height are the content area, which is
	// the area the background covers.
	X, Y, Width, Height int
}

A Window is where the window opens and how large it is — the "bwsp" record.

It is separate from IconView because it is a different record and answers a different question: icvp says what the window shows behind its icons, bwsp says how big the window is. A background picture with no bwsp is a picture cropped to whatever size the Finder last used.

Jump to

Keyboard shortcuts

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