Skip to content
Closed
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
2 changes: 2 additions & 0 deletions windows/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
- 显式外观与艺术元数据优先于分析结果;超宽图默认任务横幅,普通比例图默认环境背景,`off` 可关闭任务页图像。分析完全在渲染器本地完成,不上传图片。
- Windows 发行 payload 直接读取受管 `theme.json`,完整支持与 macOS 一致的外观、焦点、安全区和任务页模式契约,不再依赖预先设置的 renderer 全局变量。
- 新增纯 PowerShell/Windows Forms 系统托盘入口,可快速查看状态、应用或暂停皮肤、更换背景、保存和切换主题、打开图片文件夹,以及执行完整恢复;不引入第三方依赖。
- 系统托盘新增 0–100% 遮罩强度滑块,统一调整顶部栏、侧栏和内容区的壁纸可读性层,并在当前主题中持久保存。
- 新增 `%LOCALAPPDATA%\CodexDreamSkin` 主题仓库,用户图片会复制到受管目录,活动主题和已保存主题均保持图片与配置自包含。
- Windows 首次安装会把 UI-free 的 2560 × 1440「桥本有菜」设为活动主题并播种到「已保存主题」,无需再从 macOS 目录手动导入。

### 修复

- 保留 Codex 原生固定顶栏的定位与层级,避免打开任务侧边面板后开关被推出主区、导致面板无法关闭。
- 暗色外观下,原生顶部菜单栏现在使用深色半透明可读性层,并提高菜单按钮与图标的文字对比度,避免浅色壁纸让导航项难以辨认。
- 遮罩强度现在会随主题 payload 注入并由 watcher 热更新;更换背景或切换已保存主题时保留当前强度,无需重启 Codex。
- 渲染层现在只在检测到完整 Codex 主界面壳层时启用皮肤;宠物等透明辅助窗口会主动清理主题背景与装饰节点,避免出现遮挡宠物的矩形背景框。
- 16:9 及更宽图像现在作为侧栏与主区共享的单张整窗背景;首页、任务、插件、计划任务和 Pull Requests 路由使用同一透明顶栏与连续表面,不再在卡片或任务层重复裁切图片。
- 移除主区原生顶部渐隐和 composer 后方底部渐隐;浅色与深色 composer 均只保留一个可读表面,避免出现双层输入框或不连续底板。
Expand Down
35 changes: 18 additions & 17 deletions windows/assets/dream-skin.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@
--dream-hero-shade: color-mix(in oklab, var(--dream-surface) 93%, transparent);
--dream-ambient-opacity: .18;
--dream-shadow: 0 16px 42px color-mix(in oklab, var(--dream-accent) 9%, transparent);
--dream-immersive-edge: color-mix(in oklab, var(--dream-surface) 46%, transparent);
--dream-immersive-mid: color-mix(in oklab, var(--dream-surface) 28%, transparent);
--dream-immersive-far: color-mix(in oklab, var(--dream-surface) 14%, transparent);
--dream-immersive-sidebar: color-mix(in oklab, var(--dream-sidebar) 50%, transparent);
--dream-task-immersive-sidebar: color-mix(in oklab, var(--dream-sidebar) 72%, transparent);
--dream-overlay-opacity: 58%;
--dream-immersive-edge: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
--dream-immersive-mid: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
--dream-immersive-far: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
--dream-immersive-sidebar: color-mix(in oklab, var(--dream-sidebar) var(--dream-overlay-opacity), transparent);
--dream-task-immersive-sidebar: color-mix(in oklab, var(--dream-sidebar) var(--dream-overlay-opacity), transparent);
--dream-immersive-composer: color-mix(in oklab, var(--dream-surface-raised) 76%, var(--dream-accent) 4%);
--dream-immersive-line: color-mix(in oklab, var(--dream-line) 76%, transparent);
--dream-task-immersive-edge: color-mix(in oklab, var(--dream-surface) 86%, transparent);
--dream-task-immersive-mid: color-mix(in oklab, var(--dream-surface) 78%, transparent);
--dream-task-immersive-far: color-mix(in oklab, var(--dream-surface) 66%, transparent);
--dream-task-immersive-edge: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
--dream-task-immersive-mid: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
--dream-task-immersive-far: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
}

:root.codex-dream-skin.dream-theme-dark {
Expand All @@ -43,16 +44,16 @@
--dream-hero-shade: color-mix(in oklab, var(--dream-surface) 91%, transparent);
--dream-ambient-opacity: .22;
--dream-shadow: 0 18px 48px color-mix(in oklab, oklch(0.06 0.01 245) 72%, transparent);
--dream-immersive-edge: color-mix(in oklab, var(--dream-surface) 43%, transparent);
--dream-immersive-mid: color-mix(in oklab, var(--dream-surface) 27%, transparent);
--dream-immersive-far: color-mix(in oklab, var(--dream-surface) 16%, transparent);
--dream-immersive-sidebar: color-mix(in oklab, var(--dream-sidebar) 48%, transparent);
--dream-task-immersive-sidebar: color-mix(in oklab, var(--dream-sidebar) 70%, transparent);
--dream-immersive-edge: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
--dream-immersive-mid: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
--dream-immersive-far: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
--dream-immersive-sidebar: color-mix(in oklab, var(--dream-sidebar) var(--dream-overlay-opacity), transparent);
--dream-task-immersive-sidebar: color-mix(in oklab, var(--dream-sidebar) var(--dream-overlay-opacity), transparent);
--dream-immersive-composer: color-mix(in oklab, var(--dream-surface-raised) 88%, var(--dream-accent) 5%);
--dream-immersive-line: color-mix(in oklab, var(--dream-line) 82%, transparent);
--dream-task-immersive-edge: color-mix(in oklab, var(--dream-surface) 82%, transparent);
--dream-task-immersive-mid: color-mix(in oklab, var(--dream-surface) 74%, transparent);
--dream-task-immersive-far: color-mix(in oklab, var(--dream-surface) 60%, transparent);
--dream-task-immersive-edge: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
--dream-task-immersive-mid: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
--dream-task-immersive-far: color-mix(in oklab, var(--dream-surface) var(--dream-overlay-opacity), transparent);
}

html.codex-dream-skin body {
Expand Down Expand Up @@ -116,7 +117,7 @@ html.codex-dream-skin main.main-surface > header.app-header-tint {
main surface and needs its own dark-theme legibility layer. */
html.codex-dream-skin.dream-theme-dark [class~="group/application-menu-top-bar"] {
color: var(--dream-text) !important;
background: color-mix(in oklab, var(--dream-sidebar) 90%, transparent) !important;
background: color-mix(in oklab, var(--dream-sidebar) var(--dream-overlay-opacity), transparent) !important;
border-bottom: 1px solid var(--dream-line-soft) !important;
box-shadow: 0 1px 12px color-mix(in oklab, var(--dream-canvas) 72%, transparent) !important;
backdrop-filter: blur(16px) saturate(.9) !important;
Expand Down
3 changes: 3 additions & 0 deletions windows/assets/renderer-inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"--dream-accent",
"--dream-accent-ink",
"--dream-image-luma",
"--dream-overlay-opacity",
];
const HOME_UTILITY_CLASS = "dream-home-utility";
const installToken = {};
Expand Down Expand Up @@ -81,6 +82,7 @@
focusX: hasNumber(art.focusX) ? clamp(art.focusX) : null,
focusY: hasNumber(art.focusY) ? clamp(art.focusY) : null,
accent: safeAccent,
overlayOpacity: hasNumber(config.overlayOpacity) ? clamp(config.overlayOpacity) : .58,
initialAspect: Number.isFinite(metadataRatio) && metadataRatio > 0 ? metadataRatio : null,
};
};
Expand Down Expand Up @@ -319,6 +321,7 @@
root.style.setProperty("--dream-accent", accent);
root.style.setProperty("--dream-accent-ink", accentInk);
root.style.setProperty("--dream-image-luma", profile.luma.toFixed(3));
root.style.setProperty("--dream-overlay-opacity", `${Math.round(config.overlayOpacity * 100)}%`);
};

const ensure = () => {
Expand Down
1 change: 1 addition & 0 deletions windows/assets/theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"quote": "MAKE SOMETHING WONDERFUL",
"image": "dream-reference.jpg",
"appearance": "auto",
"overlayOpacity": 0.58,
"art": {
"focusX": 0.72,
"focusY": 0.45,
Expand Down
2 changes: 2 additions & 0 deletions windows/scripts/injector.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ async function loadTheme(themeDir) {
name: normalizedText(raw.name, "name", "Codex Dream Skin", 120),
image,
appearance: normalizedChoice(raw.appearance, "appearance", THEME_CHOICES.appearance, "auto"),
overlayOpacity: normalizedUnit(raw.overlayOpacity, "overlayOpacity") ?? .58,
art: {
focusX: normalizedUnit(art.focusX, "art.focusX"),
focusY: normalizedUnit(art.focusY, "art.focusY"),
Expand Down Expand Up @@ -985,6 +986,7 @@ if (path.resolve(process.argv[1] || "") === path.resolve(scriptPath)) {
payloadBytes: Buffer.byteLength(loaded.payload),
themeId: loaded.theme.id,
appearance: loaded.theme.appearance,
overlayOpacity: loaded.theme.overlayOpacity,
art: loaded.theme.art,
artMetadata: loaded.theme.artMetadata ?? null,
}));
Expand Down
34 changes: 34 additions & 0 deletions windows/scripts/theme-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,35 @@ function Write-DreamSkinTheme {
Write-DreamSkinUtf8FileAtomically -Path $themePath -Content ($json + "`r`n")
}

function Get-DreamSkinOverlayOpacity {
param([string]$StateRoot = (Join-Path $env:LOCALAPPDATA 'CodexDreamSkin'))
$paths = Get-DreamSkinThemePaths -StateRoot $StateRoot
try {
$theme = (Read-DreamSkinTheme -ThemeDirectory $paths.Active -SkipImageMetadata).Theme
if ($null -ne $theme.overlayOpacity) {
$value = [double]$theme.overlayOpacity
if ($value -ge 0 -and $value -le 1) { return $value }
}
} catch {}
return [double]0.58
}

function Set-DreamSkinOverlayOpacity {
param(
[Parameter(Mandatory = $true)][double]$Opacity,
[string]$StateRoot = (Join-Path $env:LOCALAPPDATA 'CodexDreamSkin')
)
if ([double]::IsNaN($Opacity) -or [double]::IsInfinity($Opacity) -or $Opacity -lt 0 -or $Opacity -gt 1) {
throw 'Overlay opacity must be between 0 and 1.'
}
$paths = Get-DreamSkinThemePaths -StateRoot $StateRoot
$loaded = Read-DreamSkinTheme -ThemeDirectory $paths.Active -SkipImageMetadata
$loaded.Theme | Add-Member -NotePropertyName overlayOpacity `
-NotePropertyValue ([Math]::Round($Opacity, 2)) -Force
Write-DreamSkinTheme -ThemeDirectory $paths.Active -Theme $loaded.Theme
return [double]$loaded.Theme.overlayOpacity
}

function Initialize-DreamSkinThemeStore {
param(
[Parameter(Mandatory = $true)][string]$SkillRoot,
Expand Down Expand Up @@ -253,13 +282,15 @@ function Set-DreamSkinActiveTheme {
$source = [System.IO.Path]::GetFullPath($ImagePath)
Assert-DreamSkinImageFile -Path $source
$extension = [System.IO.Path]::GetExtension($source).ToLowerInvariant()
$overlayOpacity = Get-DreamSkinOverlayOpacity -StateRoot $StateRoot
$oldImage = $null
try { $oldImage = (Read-DreamSkinTheme -ThemeDirectory $paths.Active).ImagePath } catch {}
if ($null -eq $Theme) {
$Theme = [pscustomobject]@{
id = 'custom'
name = '自定义主题'
appearance = 'auto'
overlayOpacity = $overlayOpacity
art = [pscustomobject]@{ focusX = $null; focusY = $null; safeArea = 'auto'; taskMode = 'auto' }
palette = [pscustomobject]@{}
}
Expand All @@ -280,6 +311,7 @@ function Set-DreamSkinActiveTheme {
if ($Name) { $Theme | Add-Member -NotePropertyName name -NotePropertyValue $Name -Force }
if (-not $Theme.id) { $Theme | Add-Member -NotePropertyName id -NotePropertyValue 'custom' -Force }
if (-not $Theme.appearance) { $Theme | Add-Member -NotePropertyName appearance -NotePropertyValue 'auto' -Force }
$Theme | Add-Member -NotePropertyName overlayOpacity -NotePropertyValue $overlayOpacity -Force
if (-not $Theme.art) {
$Theme | Add-Member -NotePropertyName art -NotePropertyValue `
([pscustomobject]@{ focusX = $null; focusY = $null; safeArea = 'auto'; taskMode = 'auto' }) -Force
Expand Down Expand Up @@ -372,6 +404,8 @@ function Use-DreamSkinSavedTheme {
}
$saved = Read-DreamSkinTheme -ThemeDirectory $directory
$theme = $saved.Theme | ConvertTo-Json -Depth 8 | ConvertFrom-Json
$theme | Add-Member -NotePropertyName overlayOpacity `
-NotePropertyValue (Get-DreamSkinOverlayOpacity -StateRoot $StateRoot) -Force
return Set-DreamSkinActiveTheme -ImagePath $saved.ImagePath -Theme $theme -StateRoot $StateRoot
}

Expand Down
73 changes: 72 additions & 1 deletion windows/scripts/tray-dream-skin.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ try {

function Add-DreamSkinTrayItem {
param(
[Parameter(Mandatory = $true)][System.Windows.Forms.ToolStripItemCollection]$Items,
[Parameter(Mandatory = $true)][AllowEmptyCollection()][System.Windows.Forms.ToolStripItemCollection]$Items,
[Parameter(Mandatory = $true)][string]$Text,
[AllowNull()][scriptblock]$Action,
[bool]$Enabled = $true
Expand All @@ -66,6 +66,63 @@ try {
return $item
}

function Show-DreamSkinOverlayOpacityDialog {
param([Parameter(Mandatory = $true)][int]$InitialPercent)
$form = [System.Windows.Forms.Form]::new()
$form.Text = '设置 Codex 遮罩强度'
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$form.ClientSize = [System.Drawing.Size]::new(420, 170)
$form.MaximizeBox = $false
$form.MinimizeBox = $false
$form.ShowInTaskbar = $false

$description = [System.Windows.Forms.Label]::new()
$description.Text = '统一调整顶部栏、侧栏和内容区的遮罩强度。'
$description.AutoSize = $true
$description.Location = [System.Drawing.Point]::new(18, 15)

$slider = [System.Windows.Forms.TrackBar]::new()
$slider.Minimum = 0
$slider.Maximum = 100
$slider.TickFrequency = 10
$slider.SmallChange = 1
$slider.LargeChange = 10
$slider.Value = [Math]::Max(0, [Math]::Min(100, $InitialPercent))
$slider.Location = [System.Drawing.Point]::new(18, 42)
$slider.Size = [System.Drawing.Size]::new(384, 45)

$valueLabel = [System.Windows.Forms.Label]::new()
$valueLabel.Text = "遮罩强度:$($slider.Value)%"
$valueLabel.AutoSize = $true
$valueLabel.Location = [System.Drawing.Point]::new(18, 92)
$slider.add_ValueChanged({ $valueLabel.Text = "遮罩强度:$($slider.Value)%" }.GetNewClosure())

$ok = [System.Windows.Forms.Button]::new()
$ok.Text = '确定'
$ok.DialogResult = [System.Windows.Forms.DialogResult]::OK
$ok.Location = [System.Drawing.Point]::new(238, 126)
$ok.Size = [System.Drawing.Size]::new(78, 28)

$cancel = [System.Windows.Forms.Button]::new()
$cancel.Text = '取消'
$cancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$cancel.Location = [System.Drawing.Point]::new(324, 126)
$cancel.Size = [System.Drawing.Size]::new(78, 28)

$form.AcceptButton = $ok
$form.CancelButton = $cancel
foreach ($control in @($description, $slider, $valueLabel, $ok, $cancel)) {
[void]$form.Controls.Add($control)
}
try {
if ($form.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { return [int]$slider.Value }
return $null
} finally {
$form.Dispose()
}
}

function Rebuild-DreamSkinTrayMenu {
$menu.Items.Clear()
$paused = Test-DreamSkinPaused -StateRoot $StateRoot
Expand Down Expand Up @@ -105,6 +162,20 @@ try {
$dialog.Dispose()
}
}
$overlayPercent = [int][Math]::Round((Get-DreamSkinOverlayOpacity -StateRoot $StateRoot) * 100)
$overlayAction = {
$selectedPercent = Show-DreamSkinOverlayOpacityDialog -InitialPercent $overlayPercent
if ($null -eq $selectedPercent) { return }
$null = Set-DreamSkinOverlayOpacity -Opacity ($selectedPercent / 100.0) -StateRoot $StateRoot
$notify.ShowBalloonTip(
1800,
'Codex Dream Skin',
"遮罩强度已更新为 $selectedPercent%。",
[System.Windows.Forms.ToolTipIcon]::Info
)
}.GetNewClosure()
$null = Add-DreamSkinTrayItem -Items $menu.Items `
-Text "设置遮罩强度($overlayPercent%)" -Action $overlayAction
$null = Add-DreamSkinTrayItem -Items $menu.Items -Text '保存当前主题' -Action {
$name = [Microsoft.VisualBasic.Interaction]::InputBox('输入主题名称:', '保存 Codex Dream Skin 主题', '')
if ($name.Trim()) {
Expand Down
3 changes: 3 additions & 0 deletions windows/tests/renderer-inject.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ const mainResult = vm.runInNewContext(payload, main.context);
assert.equal(mainResult.installed, true);
assert.equal(main.rootClasses.has("codex-dream-skin"), true);
assert.equal(main.rootStyles.get("--dream-art"), 'url("blob:fixture-1")');
assert.equal(main.rootStyles.get("--dream-overlay-opacity"), "58%");
assert.equal(main.nodes.has("codex-dream-skin-style"), true);
assert.equal(main.nodes.has("codex-dream-skin-chrome"), true);
assert.equal(main.rootClasses.has("dream-theme-dark"), true);
Expand Down Expand Up @@ -280,6 +281,7 @@ const configured = createFixture({
});
const configuredPayload = buildPayload({
appearance: "light",
overlayOpacity: .42,
palette: { accent: "#d45a70" },
art: { focusX: .15, focusY: .8, safeArea: "right", taskMode: "off" },
});
Expand All @@ -292,6 +294,7 @@ assert.equal(configured.rootClasses.has("dream-safe-right"), true);
assert.equal(configured.rootClasses.has("dream-task-off"), true);
assert.equal(configured.rootStyles.get("--dream-art-position"), "15% 80%");
assert.equal(configured.rootStyles.get("--dream-accent"), "#d45a70");
assert.equal(configured.rootStyles.get("--dream-overlay-opacity"), "42%");
assert.equal(configured.routeClasses.has("dream-home"), true);
assert.equal(configured.routeClasses.has("dream-task"), false);
assert.equal(configured.utilityClasses.has("dream-home-utility"), true);
Expand Down
Loading
Loading