feat: 桌面启动器改 XiaoheAgent.app 形态——icns 图标、无 .command 后缀,Linux 桌面项同步带图标

This commit is contained in:
Zhengshou Lai
2026-07-16 18:32:55 +08:00
parent f3d5414bf9
commit 3b8d458fbb
2 changed files with 118 additions and 11 deletions
+66 -11
View File
@@ -197,24 +197,79 @@ def _print_report(workspace: Path, report: dict) -> None:
console.print(f" [dim]- {rel}[/dim]") console.print(f" [dim]- {rel}[/dim]")
_APP_INFO_PLIST = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>XiaoheAgent</string>
<key>CFBundleDisplayName</key><string>XiaoheAgent</string>
<key>CFBundleIdentifier</key><string>com.xiaohe.agent</string>
<key>CFBundleVersion</key><string>1</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleExecutable</key><string>XiaoheAgent</string>
<key>CFBundleIconFile</key><string>XiaoheAgent</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>NSHighResolutionCapable</key><true/>
</dict>
</plist>
"""
_APP_EXECUTABLE = """#!/usr/bin/env bash
# XiaoheAgent launcher — opens Terminal running xiaohe.
export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
if command -v xiaohe >/dev/null 2>&1; then
osascript -e 'tell application "Terminal" to activate' \\
-e 'tell application "Terminal" to do script "xiaohe"'
else
osascript -e 'display dialog "xiaohe command not found — run install.sh first" buttons {"OK"}'
fi
"""
def _icon_assets() -> tuple[Path | None, Path | None]:
"""(icns, png) shipped in the runtime tree, if present."""
runtime = get_runtime_root()
if runtime is None:
return None, None
icons = runtime / "assets" / "icon"
icns = icons / "XiaoheAgent.icns"
png = icons / "xiaohe-icon-512.png"
return (icns if icns.is_file() else None), (png if png.is_file() else None)
def _create_launcher() -> None: def _create_launcher() -> None:
"""Double-click launcher that opens a terminal running ``xiaohe``.""" """Double-click launcher that opens a terminal running ``xiaohe``."""
home = Path.home() home = Path.home()
icns, png = _icon_assets()
if os.uname().sysname == "Darwin": # noqa: PLR2004 — platform check if os.uname().sysname == "Darwin": # noqa: PLR2004 — platform check
desktop = home / "Desktop" desktop = home / "Desktop"
if desktop.is_dir(): if not desktop.is_dir():
launcher = desktop / "XiaoheAgent.command" return
launcher.write_text( # Legacy .command launcher is superseded by the .app bundle.
"#!/usr/bin/env bash\n" legacy = desktop / "XiaoheAgent.command"
"# XiaoheAgent launcher — double-click opens Terminal running xiaohe.\n" if legacy.exists():
"exec xiaohe\n", legacy.unlink()
encoding="utf-8", app = desktop / "XiaoheAgent.app"
) macos = app / "Contents" / "MacOS"
launcher.chmod(0o755) resources = app / "Contents" / "Resources"
console.print(f" [green]launcher:[/green] {launcher}") macos.mkdir(parents=True, exist_ok=True)
resources.mkdir(parents=True, exist_ok=True)
(app / "Contents" / "Info.plist").write_text(_APP_INFO_PLIST, encoding="utf-8")
executable = macos / "XiaoheAgent"
executable.write_text(_APP_EXECUTABLE, encoding="utf-8")
executable.chmod(0o755)
if icns is not None:
shutil.copy2(icns, resources / "XiaoheAgent.icns")
console.print(f" [green]launcher:[/green] {app}")
else: else:
apps = home / ".local" / "share" / "applications" apps = home / ".local" / "share" / "applications"
apps.mkdir(parents=True, exist_ok=True) apps.mkdir(parents=True, exist_ok=True)
icon_line = "Icon=utilities-terminal\n"
if png is not None:
icon_dir = home / ".local" / "share" / "icons" / "hicolor" / "512x512" / "apps"
icon_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(png, icon_dir / "xiaohe-agent.png")
icon_line = "Icon=xiaohe-agent\n"
(apps / "XiaoheAgent.desktop").write_text( (apps / "XiaoheAgent.desktop").write_text(
"[Desktop Entry]\n" "[Desktop Entry]\n"
"Type=Application\n" "Type=Application\n"
@@ -222,7 +277,7 @@ def _create_launcher() -> None:
"Comment=Xiaohe Agent terminal\n" "Comment=Xiaohe Agent terminal\n"
"Exec=xiaohe\n" "Exec=xiaohe\n"
"Terminal=true\n" "Terminal=true\n"
"Icon=utilities-terminal\n" f"{icon_line}"
"Categories=Utility;\n", "Categories=Utility;\n",
encoding="utf-8", encoding="utf-8",
) )
+52
View File
@@ -85,3 +85,55 @@ class TestSyncWorkspace:
sync_mod.sync_workspace(workspace) sync_mod.sync_workspace(workspace)
report = sync_mod.sync_workspace(workspace, force=True) report = sync_mod.sync_workspace(workspace, force=True)
assert report["added"] assert report["added"]
class TestCreateLauncher:
@pytest.fixture()
def fake_home(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
home = tmp_path / "home"
(home / "Desktop").mkdir(parents=True)
monkeypatch.setattr(Path, "home", staticmethod(lambda: home))
return home
def test_macos_app_bundle_with_icon(
self, runtime: Path, fake_home: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
(runtime / "assets" / "icon").mkdir(parents=True)
(runtime / "assets" / "icon" / "XiaoheAgent.icns").write_bytes(b"icns")
legacy = fake_home / "Desktop" / "XiaoheAgent.command"
legacy.write_text("#!/bin/sh\n")
monkeypatch.setattr(
sync_mod.os, "uname", lambda: type("U", (), {"sysname": "Darwin"})
)
sync_mod._create_launcher()
app = fake_home / "Desktop" / "XiaoheAgent.app"
executable = app / "Contents" / "MacOS" / "XiaoheAgent"
assert (app / "Contents" / "Info.plist").is_file()
assert (app / "Contents" / "Resources" / "XiaoheAgent.icns").is_file()
assert executable.stat().st_mode & 0o111
assert "xiaohe" in executable.read_text()
assert not legacy.exists() # superseded .command removed
def test_linux_desktop_entry_with_icon(
self, runtime: Path, fake_home: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
(runtime / "assets" / "icon").mkdir(parents=True)
(runtime / "assets" / "icon" / "xiaohe-icon-512.png").write_bytes(b"png")
monkeypatch.setattr(
sync_mod.os, "uname", lambda: type("U", (), {"sysname": "Linux"})
)
sync_mod._create_launcher()
entry = (
fake_home / ".local" / "share" / "applications" / "XiaoheAgent.desktop"
)
assert "Icon=xiaohe-agent" in entry.read_text()
assert (
fake_home
/ ".local"
/ "share"
/ "icons"
/ "hicolor"
/ "512x512"
/ "apps"
/ "xiaohe-agent.png"
).is_file()