Skip to content

Commit 8787e5f

Browse files
authored
Merge pull request #2916 from square/py/explorer-named-dock-icon
Give a runNamed explorer the shark icon its bundle already declares
2 parents 872e253 + e8884f8 commit 8787e5f

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

shark/shark-explorer/AGENTS.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ one line from the build script and the flag is gone from the run's JVM arguments
113113
icon is back. So `java.awt.Taskbar` has nothing to add here. `Window(icon = …)` does, but only for the
114114
Windows and Linux title bar — macOS ignores it.
115115

116+
**`-Xdock:icon` is what puts the icon on the tile, and a bundle around the JVM is not a substitute.**
117+
AWT sets the dock tile from that flag as it starts, and when the flag is absent it sets the tile to
118+
the Java icon — over whatever the bundle asked for. So `runNamed` passes the flag too, even though its
119+
generated bundle declares `CFBundleIconFile`. `CFBundleIconFile` is not ignored, it is just overwritten:
120+
`NSRunningApplication.icon` for a `runNamed` process without the flag hands back the shark, because
121+
that is the LaunchServices record, while the tile on screen is Duke. **Which is the trap** — every API
122+
an agent can read says the icon is right, and only a picture of the dock says otherwise.
123+
116124
## What macOS calls the run, as against what it calls a window
117125

118126
A run is one process and many windows, so the OS gets one name for all of them, and `main` sets it from
@@ -195,8 +203,21 @@ what the dock says is to ask the person in front of it. It is granted per respon
195203
agent, whichever app launched the session — in System Settings → Privacy & Security → Accessibility.
196204
Screen Recording is separate, and without it a screenshot of another process comes back as wallpaper.
197205

198-
An icon can be checked without either: `NSWorkspace.iconForFile` on a bundle hands back what macOS
199-
resolved for it, and writing that to a PNG is a picture an agent can open.
206+
**A tile's icon, though, only a screenshot of the dock will tell you.** `NSWorkspace.iconForFile` on a
207+
bundle and `NSRunningApplication.icon` on a pid both hand back a PNG an agent can open, but both read
208+
the LaunchServices record rather than the tile, so both are wrong the moment AWT overwrites it — see
209+
the `-Xdock:icon` section. With Screen Recording granted, this is the picture that settles it:
210+
211+
```bash
212+
# The dock has no window while it is hidden, so a capture of where AX says the tile is comes back blank.
213+
# Post mouse moves down to the bottom edge — one warp isn't enough, it takes an approach and a dwell —
214+
# then ask AX for the tile again: a y that has moved up by the dock's height means it is on screen.
215+
osascript -e 'tell application "System Events" to tell process "Dock" \
216+
to get {position, size} of (first UI element of list 1 whose name is "<the run>")'
217+
screencapture -x -R <x>,<y>,<w>,<h> tile.png
218+
```
219+
220+
Put the cursor back where it was afterwards, since it is someone's cursor.
200221

201222
## Build and test
202223

shark/shark-explorer/shark-explorer-app/build.gradle.kts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ tasks.withType<JavaExec>().matching { it.name == "run" }.configureEach {
4747
/** Shared by the Compose plugin's `run` and by `runNamed`, which launches the same classes itself. */
4848
val explorerMainClass = "shark.explorer.app.MainKt"
4949

50-
/** The dock icon of a `run`, through `-Xdock:icon`, and of a `runNamed`, through its bundle. */
50+
/** The dock icon of both tasks, each through `-Xdock:icon`: a bundle's own icon does not survive AWT. */
5151
val macOsIconFile = project.file("icons/shark-explorer-icon.icns")
5252

5353
// Launching under a name the dock shows. `run` is the one to use while working; see AGENTS.md for why
@@ -203,6 +203,10 @@ abstract class RunNamedExplorer : DefaultTask() {
203203
* A script rather than a launcher binary, and `exec` rather than a child process: the JVM has to end
204204
* up being the process macOS launched from the bundle, or it is a process of its own again and the
205205
* dock is back to calling it java.
206+
*
207+
* It passes `-Xdock:icon` even though the bundle already declares `CFBundleIconFile`, because that
208+
* key only reaches LaunchServices: AWT overwrites the tile with its own Java icon as it starts, and
209+
* without the flag the dock shows that instead. See AGENTS.md.
206210
*/
207211
private fun launcherScript(
208212
args: String,
@@ -215,6 +219,7 @@ abstract class RunNamedExplorer : DefaultTask() {
215219
cd ${workingDirectory.get().asFile.path.shellQuoted()} || exit 1
216220
exec ${javaExecutable.get().shellQuoted()} \
217221
-Dcompose.application.configure.swing.globals=true \
222+
-Xdock:icon=${iconFile.get().asFile.path.shellQuoted()} \
218223
-cp ${classpath.shellQuoted()} \
219224
${mainClass.get()} $args >>${output.path.shellQuoted()} 2>&1
220225
""".trimIndent() + "\n"

0 commit comments

Comments
 (0)