feat: 桌面启动器改 XiaoheAgent.app 形态——icns 图标、无 .command 后缀,Linux 桌面项同步带图标
This commit is contained in:
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user