Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
| 用途 | 路径 |
|------|------|
| 源码(本整理包) | `Codex-Dream-Skin/windows/` |
| 安装后的受管运行时 | `%LOCALAPPDATA%\CodexDreamSkin\engine` |
| 状态 / 日志 | `%LOCALAPPDATA%\CodexDreamSkin` |
| Codex 配置 | `%USERPROFILE%\.codex\config.toml` |
| 默认 CDP 端口 | 首选 `9335`,冲突时自动选空闲口(Mac 包默认从 `9341` 起) |
Expand Down Expand Up @@ -74,7 +75,7 @@ Windows 启动、失败回滚与恢复重开均从已注册的 `OpenAI.Codex`
### 平台差异

- macOS 的选图脚本会把这些字段写入主题库,可通过 `--appearance`、`--focus-x`、`--focus-y`、`--safe-area`、`--task-mode` 设置。
- Windows 安装会初始化 `%LOCALAPPDATA%\CodexDreamSkin\active-theme`、`themes` 与 `images`,并把「桥本有菜」同时设为首次默认和可切换的已保存主题。系统托盘支持更换背景、保存当前主题、从「已保存主题」切换、暂停和恢复;图片与 `theme.json` 保存在主题仓库中,不写进 Codex 的 `config.toml`。安装会保留用户已有的 `appearanceTheme`;仅在识别到旧版精确托管的浅色三元组时按备份迁移。
- Windows 安装会把运行所需的 `assets/` 与 `scripts/` 原子复制到 `%LOCALAPPDATA%\CodexDreamSkin\engine`,所有快捷方式均指向该受管副本,因此安装后可移动或删除源码目录。安装还会初始化 `active-theme`、`themes` 与 `images`,并把「桥本有菜」同时设为首次默认和可切换的已保存主题。系统托盘支持更换背景、保存当前主题、从「已保存主题」切换、暂停和恢复;图片与 `theme.json` 保存在主题仓库中,不写进 Codex 的 `config.toml`。安装会保留用户已有的 `appearanceTheme`;仅在识别到旧版精确托管的浅色三元组时按备份迁移。
- Windows 渲染器仍支持在注入前用 `window.__CODEX_DREAM_SKIN_CONFIG__` 提供内存级可选覆盖(形状同上,颜色覆盖使用 `palette.accent`),但普通用户应优先使用持久化主题仓库与托盘。

## 预设与图片类型
Expand Down
1 change: 1 addition & 0 deletions windows/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### 新增

- Windows 安装器会先校验并原子复制运行所需的 `assets/` 与 `scripts/` 到 `%LOCALAPPDATA%\CodexDreamSkin\engine`,启动、恢复和托盘快捷方式统一指向该受管副本;安装完成后可移动或删除源码克隆。重装前若旧托盘仍在运行,安装器会明确要求退出,避免新旧脚本混用。
- 渲染层支持通用自适应图像主题:本地 Canvas 采样图像亮度、主色、焦点和比例,为壁纸层提供自适应色彩与构图建议;支持 `appearance: auto | light | dark`、`art.focusX/focusY`(`0..1`)、`art.safeArea: auto | left | right | center | none`、`art.taskMode: auto | ambient | banner | off`。外观壳仍由显式主题或原生外观信号决定。
- 显式外观与艺术元数据优先于分析结果;超宽图默认任务横幅,普通比例图默认环境背景,`off` 可关闭任务页图像。分析完全在渲染器本地完成,不上传图片。
- Windows 发行 payload 直接读取受管 `theme.json`,完整支持与 macOS 一致的外观、焦点、安全区和任务页模式契约,不再依赖预先设置的 renderer 全局变量。
Expand Down
9 changes: 5 additions & 4 deletions windows/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ Apply a reversible renderer skin through Chromium DevTools Protocol while launch

## Workflow

1. Install Node.js 22 or newer, close Codex, then run `scripts/install-dream-skin.ps1` once to preserve the user's native appearance settings, seed the Arina Hashimoto theme, and create launch/restore/tray shortcuts.
2. Run `scripts/start-dream-skin.ps1`. The shortcut asks before restarting an already-open Codex app; CLI callers must explicitly add `-RestartExisting`.
1. Install Node.js 22 or newer, close Codex, then run `scripts/install-dream-skin.ps1` once. The installer preserves the user's native appearance settings, seeds the Arina Hashimoto theme, copies the runtime to `%LOCALAPPDATA%\CodexDreamSkin\engine`, and creates launch/restore/tray shortcuts that do not depend on the source checkout.
2. Use the `Codex Dream Skin` shortcut, or run `%LOCALAPPDATA%\CodexDreamSkin\engine\scripts\start-dream-skin.ps1`. The shortcut asks before restarting an already-open Codex app; CLI callers must explicitly add `-RestartExisting`.
3. Run `scripts/verify-dream-skin.ps1 -ScreenshotPath <absolute-path>` after launch. Treat a missing continuous wallpaper, home shell, native composer, sidebar layer, or injection marker as failure. The native suggestion count is responsive and may be two to four.
4. Inspect the screenshot against `references/qa-inventory.md`. Verify both the home screen and a normal task before signing off.
5. Run `scripts/restore-dream-skin.ps1` to remove the live skin, close the saved CDP session, and reopen Codex normally. Add `-RestoreBaseTheme` to restore only saved appearance keys, `-RecoverConfigBackup` for explicit byte-for-byte recovery of a damaged config, or `-Uninstall` to delete shortcuts. A completed config restore archives that install's backup so a later install captures a fresh baseline.
Expand All @@ -33,6 +33,7 @@ Apply a reversible renderer skin through Chromium DevTools Protocol while launch
- Loopback prevents LAN exposure, but Chromium CDP has no same-user authentication. Run only trusted local software while the skin is active, and use restore to close the debug session when it is no longer needed.
- Preserve `config.toml` as strict UTF-8. Never use encoding-dependent whole-file PowerShell reads/writes, silently transcode UTF-16, or overwrite a file that changed after it was read. Ambiguous TOML shapes must fail before writing rather than receive a best-effort rewrite.
- Keep install/start/restore/verify serialized with the per-user operation lock in `common-windows.ps1`.
- Treat `%LOCALAPPDATA%\CodexDreamSkin\engine` as an installer-managed runtime. Exit the Dream Skin tray before reinstalling so the installer can replace that runtime atomically and update every shortcut to the same copy.

## Checks

Expand All @@ -45,7 +46,7 @@ node --check assets\renderer-inject.js
## Resources

- `scripts/injector.mjs`: CDP connection, renderer injection, verification, screenshot, and removal.
- `scripts/common-windows.ps1`: Store-package discovery, Node validation, port ownership, state, and process identity safety.
- `scripts/common-windows.ps1`: Store-package discovery, Node validation, managed runtime installation, port ownership, state, and process identity safety.
- `scripts/config-utf8.ps1`: atomic UTF-8 configuration backup, selective restore, and explicit recovery.
- `assets/dream-skin.css`: full visual layer.
- `assets/renderer-inject.js`: idempotent DOM integration and cleanup.
Expand All @@ -55,4 +56,4 @@ node --check assets\renderer-inject.js
- `scripts/tray-dream-skin.ps1`: Windows Forms tray for apply, pause, import, save, switch, and complete restore.
- `references/qa-inventory.md`: required functional and visual signoff coverage.
- `references/runtime-notes.md`: troubleshooting and update behavior.
- `tests/run-tests.ps1`: configuration, state, recovery, payload, and CDP validation regression checks.
- `tests/run-tests.ps1`: managed runtime, configuration, state, recovery, payload, and CDP validation regression checks. Use `-EngineOnly` for the source-independence contract.
190 changes: 190 additions & 0 deletions windows/scripts/common-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,196 @@ function Test-DreamSkinPathWithin {
}
}

function Get-DreamSkinRuntimeEnginePaths {
param([string]$StateRoot = (Join-Path $env:LOCALAPPDATA 'CodexDreamSkin'))
$root = Join-Path ([System.IO.Path]::GetFullPath($StateRoot)) 'engine'
$scripts = Join-Path $root 'scripts'
return [pscustomobject]@{
Root = $root
Scripts = $scripts
Start = Join-Path $scripts 'start-dream-skin.ps1'
Restore = Join-Path $scripts 'restore-dream-skin.ps1'
Tray = Join-Path $scripts 'tray-dream-skin.ps1'
}
}

function Test-DreamSkinTrayActive {
$sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
$mutex = [System.Threading.Mutex]::new($false, "Local\CodexDreamSkin.$sid.Tray")
$acquired = $false
try {
try { $acquired = $mutex.WaitOne(0) } catch [System.Threading.AbandonedMutexException] {
$acquired = $true
}
if ($acquired) {
$mutex.ReleaseMutex()
$acquired = $false
return $false
}
return $true
} finally {
if ($acquired) { try { $mutex.ReleaseMutex() } catch {} }
$mutex.Dispose()
}
}

function Assert-DreamSkinRuntimeTree {
param([Parameter(Mandatory = $true)][string]$Path)
$root = [System.IO.Path]::GetFullPath($Path)
if (-not (Test-Path -LiteralPath $root -PathType Container)) {
throw "Dream Skin runtime directory does not exist: $root"
}
if (-not (Get-Command Assert-DreamSkinNoReparseComponents -ErrorAction SilentlyContinue)) {
throw 'Dream Skin managed-path validation is unavailable.'
}
Assert-DreamSkinNoReparseComponents -Path $root
foreach ($item in Get-ChildItem -LiteralPath $root -Recurse -Force -ErrorAction Stop) {
if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
throw "Dream Skin runtime contains a junction or symbolic link: $($item.FullName)"
}
}
}

function Remove-DreamSkinRuntimeTree {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][string]$StateRoot
)
$fullPath = [System.IO.Path]::GetFullPath($Path)
$fullStateRoot = [System.IO.Path]::GetFullPath($StateRoot)
if (-not (Test-DreamSkinPathWithin -Path $fullPath -Root $fullStateRoot)) {
throw "Refusing to remove a runtime path outside the Dream Skin state root: $fullPath"
}
if (-not (Test-Path -LiteralPath $fullPath)) { return }
Assert-DreamSkinRuntimeTree -Path $fullPath
Remove-Item -LiteralPath $fullPath -Recurse -Force -ErrorAction Stop
}

function Install-DreamSkinRuntimeEngine {
param(
[Parameter(Mandatory = $true)][string]$SkillRoot,
[Parameter(Mandatory = $true)][string]$StateRoot
)
if (-not (Get-Command Ensure-DreamSkinManagedDirectory -ErrorAction SilentlyContinue)) {
throw 'Dream Skin managed-directory validation is unavailable.'
}

$sourceRoot = [System.IO.Path]::GetFullPath($SkillRoot)
$fullStateRoot = [System.IO.Path]::GetFullPath($StateRoot)
$engine = Get-DreamSkinRuntimeEnginePaths -StateRoot $fullStateRoot
$required = @(
'assets\dream-reference.jpg',
'assets\dream-skin.css',
'assets\renderer-inject.js',
'assets\theme.json',
'scripts\common-windows.ps1',
'scripts\config-utf8.ps1',
'scripts\image-metadata.mjs',
'scripts\injector.mjs',
'scripts\install-dream-skin.ps1',
'scripts\restore-dream-skin.ps1',
'scripts\start-dream-skin.ps1',
'scripts\theme-windows.ps1',
'scripts\tray-dream-skin.ps1',
'scripts\verify-dream-skin.ps1'
)
foreach ($relative in $required) {
if (-not (Test-Path -LiteralPath (Join-Path $sourceRoot $relative) -PathType Leaf)) {
throw "Dream Skin runtime source is incomplete: $relative"
}
}
foreach ($directoryName in @('assets', 'scripts')) {
$sourceDirectory = Join-Path $sourceRoot $directoryName
if ((Test-DreamSkinPathEqual -Left $fullStateRoot -Right $sourceDirectory) -or
(Test-DreamSkinPathWithin -Path $fullStateRoot -Root $sourceDirectory)) {
throw "Dream Skin state root cannot be created inside its runtime source: $fullStateRoot"
}
Assert-DreamSkinRuntimeTree -Path $sourceDirectory
}

Ensure-DreamSkinManagedDirectory -Path $fullStateRoot -Root $fullStateRoot
$token = [guid]::NewGuid().ToString('N')
$stagingRoot = Join-Path $fullStateRoot ".engine-staging-$token"
$backupRoot = Join-Path $fullStateRoot ".engine-backup-$token"
Ensure-DreamSkinManagedDirectory -Path $stagingRoot -Root $fullStateRoot

try {
foreach ($directoryName in @('assets', 'scripts')) {
Copy-Item -LiteralPath (Join-Path $sourceRoot $directoryName) -Destination $stagingRoot `
-Recurse -Force -ErrorAction Stop
}
Assert-DreamSkinRuntimeTree -Path $stagingRoot
foreach ($relative in $required) {
if (-not (Test-Path -LiteralPath (Join-Path $stagingRoot $relative) -PathType Leaf)) {
throw "Staged Dream Skin runtime is incomplete: $relative"
}
}

$sourcePrefix = $sourceRoot.TrimEnd('\') + '\'
$sourceFiles = @(
Get-ChildItem -LiteralPath (Join-Path $sourceRoot 'assets'), (Join-Path $sourceRoot 'scripts') `
-Recurse -File -Force -ErrorAction Stop
)
$stagedFiles = @(
Get-ChildItem -LiteralPath (Join-Path $stagingRoot 'assets'), (Join-Path $stagingRoot 'scripts') `
-Recurse -File -Force -ErrorAction Stop
)
if ($sourceFiles.Count -ne $stagedFiles.Count) {
throw 'Staged Dream Skin runtime file count does not match its source.'
}
foreach ($sourceFile in $sourceFiles) {
$relative = $sourceFile.FullName.Substring($sourcePrefix.Length)
$stagedFile = Join-Path $stagingRoot $relative
if (-not (Test-Path -LiteralPath $stagedFile -PathType Leaf) -or
(Get-FileHash -Algorithm SHA256 -LiteralPath $sourceFile.FullName).Hash -cne
(Get-FileHash -Algorithm SHA256 -LiteralPath $stagedFile).Hash) {
throw "Staged Dream Skin runtime failed hash verification: $relative"
}
}

$hasBackup = $false
if (Test-Path -LiteralPath $engine.Root) {
Assert-DreamSkinRuntimeTree -Path $engine.Root
Move-Item -LiteralPath $engine.Root -Destination $backupRoot -ErrorAction Stop
$hasBackup = $true
}
try {
Move-Item -LiteralPath $stagingRoot -Destination $engine.Root -ErrorAction Stop
} catch {
$installError = $_.Exception.Message
if ($hasBackup -and -not (Test-Path -LiteralPath $engine.Root)) {
try {
Move-Item -LiteralPath $backupRoot -Destination $engine.Root -ErrorAction Stop
$hasBackup = $false
} catch {
throw "Dream Skin runtime update failed and its previous engine could not be restored. Backup preserved at ${backupRoot}: $installError"
}
}
throw
}
if ($hasBackup) {
try { Remove-DreamSkinRuntimeTree -Path $backupRoot -StateRoot $fullStateRoot } catch {
try {
Write-Warning "Installed the new runtime but could not remove its previous backup: $($_.Exception.Message)"
} catch {
# Cleanup must never make a committed runtime update look unsuccessful.
}
}
}
return Get-DreamSkinRuntimeEnginePaths -StateRoot $fullStateRoot
} finally {
if (Test-Path -LiteralPath $stagingRoot) {
try { Remove-DreamSkinRuntimeTree -Path $stagingRoot -StateRoot $fullStateRoot } catch {
try {
Write-Warning "Could not remove the staged Dream Skin runtime: $($_.Exception.Message)"
} catch {
# Cleanup must never mask the runtime installation result.
}
}
}
}
}

function Test-DreamSkinCommandLineToken {
param([string]$CommandLine, [string]$Token)
if (-not $CommandLine -or -not $Token) { return $false }
Expand Down
20 changes: 12 additions & 8 deletions windows/scripts/install-dream-skin.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ try {
(Get-DreamSkinCodexProcesses -Codex $savedPathCandidate).Count -gt 0) {
throw 'The saved Codex path is still running but no longer matches a registered Store package. Close it manually before installing.'
}
$null = Initialize-DreamSkinThemeStore -SkillRoot $SkillRoot -StateRoot $StateRoot
if (Test-DreamSkinTrayActive) {
throw 'Exit the Codex Dream Skin tray before reinstalling so every shortcut can move to the new runtime safely.'
}
$engine = Install-DreamSkinRuntimeEngine -SkillRoot $SkillRoot -StateRoot $StateRoot
$null = Initialize-DreamSkinThemeStore -SkillRoot $engine.Root -StateRoot $StateRoot
$ConfigPath = Join-Path $HOME '.codex\config.toml'
$BackupPath = Join-Path $StateRoot 'config.before-dream-skin.toml'
Install-DreamSkinBaseTheme -ConfigPath $ConfigPath -BackupPath $BackupPath
Expand All @@ -45,32 +49,32 @@ try {
$desktop = [Environment]::GetFolderPath('Desktop')
$startMenu = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs'
$powershell = (Get-Command powershell.exe -ErrorAction Stop).Source
$startScript = Join-Path $PSScriptRoot 'start-dream-skin.ps1'
$restoreScript = Join-Path $PSScriptRoot 'restore-dream-skin.ps1'
$trayScript = Join-Path $PSScriptRoot 'tray-dream-skin.ps1'
$startScript = $engine.Start
$restoreScript = $engine.Restore
$trayScript = $engine.Tray
$portArgument = if ($PortExplicit) { " -Port $Port" } else { '' }

foreach ($folder in @($desktop, $startMenu)) {
$shortcut = $shell.CreateShortcut((Join-Path $folder 'Codex Dream Skin.lnk'))
$shortcut.TargetPath = $powershell
$shortcut.Arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$startScript`"$portArgument -PromptRestart"
$shortcut.WorkingDirectory = $SkillRoot
$shortcut.WorkingDirectory = $engine.Root
$shortcut.Description = 'Launch the official Codex app with Codex Dream Skin'
$shortcut.Save()
}

$restore = $shell.CreateShortcut((Join-Path $desktop 'Codex Dream Skin - Restore.lnk'))
$restore.TargetPath = $powershell
$restore.Arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$restoreScript`"$portArgument -RestoreBaseTheme -PromptRestart"
$restore.WorkingDirectory = $SkillRoot
$restore.WorkingDirectory = $engine.Root
$restore.Description = 'Restore the official Codex appearance and close the CDP session'
$restore.Save()

foreach ($folder in @($desktop, $startMenu)) {
$tray = $shell.CreateShortcut((Join-Path $folder 'Codex Dream Skin - Tray.lnk'))
$tray.TargetPath = $powershell
$tray.Arguments = "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$trayScript`"$portArgument"
$tray.WorkingDirectory = $SkillRoot
$tray.WorkingDirectory = $engine.Root
$tray.Description = 'Open Codex Dream Skin status and theme controls in the system tray'
$tray.Save()
}
Expand All @@ -80,7 +84,7 @@ try {
}

if ($NoShortcuts) {
Write-Host 'Codex Dream Skin base theme installed. Run start-dream-skin.ps1 to launch it.'
Write-Host "Codex Dream Skin base theme installed at $($engine.Root). Run $($engine.Start) to launch it."
} else {
Write-Host 'Codex Dream Skin installed. The launch shortcut asks before restarting an open Codex window.'
}
Expand Down
Loading
Loading