Skip to content

Give the Dock an icon outside the bundle, and macOS 26 the Icon Composer one - #8

Closed
huntharo wants to merge 4 commits into
mainfrom
codex/pwrsnap-pwragnt-dock-icon-d840ce
Closed

huntharo wants to merge 4 commits into
mainfrom
codex/pwrsnap-pwragnt-dock-icon-d840ce

Conversation

@huntharo

@huntharo huntharo commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

This fixes two problems with disktree's icon on macOS.

  • A run outside the app bundle has no icon. make run, cargo run and the bare release binary have no bundle for macOS to read, so the Dock shows a generic "exec" tile.
  • macOS 26 has to guess at the bundled icon. The bundle ships only a legacy .icns, so macOS 26 squeezes it into its own icon shape instead of drawing a Liquid Glass icon. On 26.6.1 the mark's square panel runs into the corners of the mask, as the before image shows. On 26.6.2 a padded .icns like this one comes out as a dark tile on a light grey plate. PwrSnap and PwrGit hit that and wrote it up in fix(desktop): ship an Icon Composer .icon alongside the legacy .icns pwrdrvr/PwrSnap#563.

The fix follows what PwrSnap and PwrAgent settled on.

Before and after

In the Dock, left to right: an unbundled run before this PR, main's bundle, an unbundled run of this branch, and this branch's bundle.

The Dock: the exec tile, main's bundle, this branch's unbundled run, and this branch's bundle

The bundled icon as macOS 26.6.1 draws it, read back through NSWorkspace.shared.icon(forFile:) at 512 px:

Before and after: main's bundled icon with the panel running into the mask, and this PR's with the panel inset

What changes

  • An Icon Composer icon in the bundle. packaging/macos/AppIcon.icon is the package: icon.json with the mark's background as the fill, and one layer drawn by sips from the new AppGlyph.svg, the mark without its background. When xcrun actool is 26 or newer, cargo xtask bundle compiles it into Contents/Resources/Assets.car and adds CFBundleIconName to the plist. AppIcon.icns stays as CFBundleIconFile for macOS 15 and earlier. Without actool 26, as with the Command Line Tools alone, an older Xcode, or the macos-15 runner, the bundle is built as before and the task prints a note.
  • A Dock icon for unbundled runs. crates/disktree-app/src/dock_icon.rs checks whether the main bundle names an icon. When it does not, it sets NSApplication.applicationIconImage from a PNG embedded in the binary: DockIcon-glass.png on macOS 26 and later, DockIcon.png before that. It reads the Darwin kernel release to decide, because macOS 26 reports itself as 16.0 to a binary linked against an older SDK. AppKit comes through the objc2 crates GPUI already builds, with one unsafe call, allowed with its reason the way macos.rs does.
  • Rendering the glass. macOS never applies Liquid Glass to an image set at run time, so the glass is baked into DockIcon-glass.png. make dock-icons compiles the package into a throwaway bundle and reads back what macOS draws for it. It then checks that the tile lands where the flat icon's does and that the Mac is set to the Default icon style. It needs macOS 26 and Xcode 26. Run it after either SVG changes.
  • CI. A new Bundle (macOS 26) job builds the bundle on macos-26 and checks the catalog and both plist keys are there. The macos-15 job keeps covering the bundle without it. The aarch64 release build moves to macos-26 so the shipped app carries the catalog. There is no macOS 26 Intel runner, so the x86_64 build keeps the legacy icon alone.

The mark's size (last commit, droppable)

At the full canvas, the mark's square panel reaches into the mask's corners and gets clipped, which is what main's bundle shows above. Apple's documentation points to the grid in its Design Resources templates rather than giving a number. So I rendered sizes through macOS beside the icons around disktree in the Dock. The last commit draws the panel at 700 pt of the 1024 pt canvas, 162 pt in from each edge, with rounded corners, so it reads as a squircle inside the squircle. That matches the weight of Slack's glyph, 1Password's ring, and the PwrSnap and PwrGit marks.

Candidate mark sizes beside Terminal, 1Password, Slack, Edge, PwrSnap and PwrGit

The size change is a commit of its own so it can be dropped. Without it, the mark fills the canvas:

The bundled icon with the full-size mark, its panel corners clipped by the mask

Verified

  • cargo xtask lint and cargo xtask test pass on macOS 26.6.1 with Xcode 26.5. CI passes all seven checks, including the new macOS 26 bundle job.
  • cargo xtask bundle here gives Assets.car and AppIcon.icns in Resources, CFBundleIconName = Icon, CFBundleIconFile = AppIcon, and a valid signature.
  • The Dock images are real captures of running apps, not the PNGs.
  • The "main's bundle" images come from this branch's bundle with Assets.car and CFBundleIconName removed and the bundle re-signed. AppIcon.svg and the icns it is drawn into are unchanged from main, so that is the icon main ships.
  • Not verified: macOS 26.6.2, where the plate shows, since this Mac runs 26.6.1. Also not verified on macOS 15, which should still draw AppIcon.icns as before.

…ser one

A run outside the app bundle has no icon for macOS to draw, so the Dock
shows the generic executable. dock_icon.rs now paints one when the main
bundle names none: the flat legacy tile, or on macOS 26 the shipped icon
as that macOS draws it, since Liquid Glass is never applied to an image
set at run time. make dock-icons renders both, the glass one by macOS
itself through NSWorkspace from a throwaway bundle.

The bundle also ships packaging/macos/AppIcon.icon when Xcode 26's actool
is at hand: Assets.car and CFBundleIconName beside the icns, so macOS 26
draws its own shape and glass instead of normalizing a padded icns, which
26.6.2 puts on a light plate. Without that actool the bundle is as before.
CI builds the bundle on macos-26 too, and the aarch64 release moves there.
…con at size

macOS 26 reports 16.0 to a binary linked against an older SDK, so
NSProcessInfo picked the flat icon for a build made with the Command Line
Tools or Xcode 16. The kernel release does not depend on the SDK.

The flat Dock icon now comes from sips at 512 px, as the iconset does,
rather than a resample of the 1024 px check render. The plist marker is
the bare token, so a reindented template still takes it.
The style check compared the glass and flat renders at the mark's title
bar and green block, which only line up while AppGlyph.svg draws the mark
at the flat icon's size. A smaller mark put the title-bar probe on the
tile, and the script blamed the icon style. The probes are now the canvas
centre, which is the mark's green block at any centred size, and the tile
just inside its edge.
At the full 1024 pt canvas the mark's panel reached 80 pt from the edge
and macOS 26's mask clipped its square corners. The panel is now 700 pt,
68% of the canvas and 162 pt in from each edge, about the weight of
Slack's glyph or 1Password's ring beside it in the Dock, with 100 pt
corners so it reads as a squircle inside the squircle, as Finder's does.

Only the Icon Composer layer and the glass Dock icon change. Drop this
commit to keep the mark at full size.
@huntharo
huntharo force-pushed the codex/pwrsnap-pwragnt-dock-icon-d840ce branch from 743b89b to 290814c Compare October 14, 2026 17:30
@huntharo huntharo closed this Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant