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
7 changes: 4 additions & 3 deletions windows/installer/codex-dream-skin.iss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#define AppPublisher "Codex Dream Skin contributors"
#define AppUrl "https://dreamskin.cc"
#define PowerShellPath "{sysnative}\WindowsPowerShell\v1.0\powershell.exe"
#define PersistentPowerShellPath "{win}\System32\WindowsPowerShell\v1.0\powershell.exe"

[Setup]
AppId={{DCCDAF1A-9ACD-4AAB-B55B-DF17EB2CDA2E}
Expand Down Expand Up @@ -72,14 +73,14 @@ Source: "{#StageRoot}\NOTICE.md"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#StageRoot}\payload\*"; DestDir: "{app}\payload"; Flags: ignoreversion recursesubdirs createallsubdirs

[Icons]
Name: "{group}\Codex Dream Skin"; Filename: "{#PowerShellPath}"; Parameters: "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{app}\setup-bootstrap.ps1"" -LaunchTray"; WorkingDir: "{app}"; IconFilename: "{app}\payload\assets\codex-dream-skin.ico"
Name: "{userstartup}\Codex Dream Skin"; Filename: "{#PowerShellPath}"; Parameters: "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{app}\setup-bootstrap.ps1"" -LaunchTray"; WorkingDir: "{app}"; IconFilename: "{app}\payload\assets\codex-dream-skin.ico"; Tasks: startup
Name: "{group}\Codex Dream Skin"; Filename: "{#PersistentPowerShellPath}"; Parameters: "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{app}\setup-bootstrap.ps1"" -LaunchTray"; WorkingDir: "{app}"; IconFilename: "{app}\payload\assets\codex-dream-skin.ico"
Name: "{userstartup}\Codex Dream Skin"; Filename: "{#PersistentPowerShellPath}"; Parameters: "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{app}\setup-bootstrap.ps1"" -LaunchTray"; WorkingDir: "{app}"; IconFilename: "{app}\payload\assets\codex-dream-skin.ico"; Tasks: startup

[Registry]
Root: HKCU; Subkey: "Software\Classes\dreamskin"; ValueType: string; ValueName: ""; ValueData: "URL:DreamSkin Protocol"; Flags: uninsdeletekey
Root: HKCU; Subkey: "Software\Classes\dreamskin"; ValueType: string; ValueName: "URL Protocol"; ValueData: ""
Root: HKCU; Subkey: "Software\Classes\dreamskin\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\payload\assets\codex-dream-skin.ico"
Root: HKCU; Subkey: "Software\Classes\dreamskin\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{#PowerShellPath}"" -NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{localappdata}\CodexDreamSkin\engine\scripts\apply-community-theme.ps1"" ""%1"""
Root: HKCU; Subkey: "Software\Classes\dreamskin\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{#PersistentPowerShellPath}"" -NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{localappdata}\CodexDreamSkin\engine\scripts\apply-community-theme.ps1"" ""%1"""

[Run]
Filename: "{#PowerShellPath}"; Parameters: "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{app}\setup-bootstrap.ps1"" -LaunchTray"; WorkingDir: "{app}"; Description: "Launch Codex Dream Skin"; Flags: nowait postinstall skipifsilent
Expand Down
18 changes: 16 additions & 2 deletions windows/scripts/apply-community-theme.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ function Show-DreamSkinCommunityMessage {
)
}

function Format-DreamSkinCommunitySuccessMessage {
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Name
)
return "主题「$Name」已通过下载、SHA-256、主题包与 Safe CSS 校验,并已应用到 Codex。"
}

function Confirm-DreamSkinCommunityApply {
param([Parameter(Mandatory = $true)][object]$Metadata)
Add-Type -AssemblyName System.Windows.Forms -ErrorAction Stop
Expand Down Expand Up @@ -308,7 +317,12 @@ function Invoke-DreamSkinCommunityStartAndVerify {
' -RequireUnpaused -OperationLockTimeoutMilliseconds ' +
"$OperationLockTimeoutMilliseconds"
$startProcess = Start-Process -FilePath $powershell -ArgumentList $argumentLine `
-WindowStyle Hidden -Wait -PassThru
-WindowStyle Hidden -PassThru
if (-not $startProcess.WaitForExit($OperationLockTimeoutMilliseconds)) {
try { Stop-Process -InputObject $startProcess -Force -ErrorAction SilentlyContinue } catch {}
[void]$startProcess.WaitForExit(15000)
throw "Dream Skin start verification did not finish within $OperationLockTimeoutMilliseconds ms."
}
if ($startProcess.ExitCode -ne 0) {
throw "Dream Skin could not start and visibly verify the active theme (exit code $($startProcess.ExitCode))."
}
Expand Down Expand Up @@ -763,7 +777,7 @@ try {
$result = Invoke-DreamSkinCommunityApply -ApplyUri $Uri
if (-not $result.Canceled) {
Show-DreamSkinCommunityMessage `
-Message "“$($result.Name)”已通过下载、SHA-256、主题包与 Safe CSS 校验,并已应用到 Codex。"
-Message (Format-DreamSkinCommunitySuccessMessage -Name $result.Name)
}
exit 0
} catch {
Expand Down
33 changes: 28 additions & 5 deletions windows/scripts/common-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1052,8 +1052,13 @@ function Stop-DreamSkinRecordedInjector {
param([AllowNull()][object]$State)
if ($null -eq $State -or -not $State.injectorPid) { return $true }
$processId = [int]$State.injectorPid
$processHandle = Get-Process -Id $processId -ErrorAction SilentlyContinue
if (-not $processHandle) { return $true }
$process = Get-CimInstance Win32_Process -Filter "ProcessId = $processId" -ErrorAction SilentlyContinue
if (-not $process) { return $true }
if (-not $process) {
if ($processHandle.HasExited) { return $true }
throw "The recorded injector PID $processId is running, but its identity cannot be inspected. State was preserved."
}

$expectedInjector = if ($State.injectorPath) {
"$($State.injectorPath)"
Expand Down Expand Up @@ -1083,17 +1088,22 @@ function Stop-DreamSkinRecordedInjector {
$browserPattern = '(?:^|\s)(?i:--browser-id)(?:=|\s+)' + [regex]::Escape("$($State.browserId)") + '(?=$|\s)'
$injectorMatches = $injectorMatches -and [regex]::IsMatch($commandLine, $browserPattern)
}
$startedAt = Get-DreamSkinProcessStartedAt -ProcessId $processId
try {
$startedAt = $processHandle.StartTime.ToUniversalTime().ToString('o')
} catch {
if ($processHandle.HasExited) { return $true }
throw "The recorded injector PID $processId is running, but its start time cannot be inspected. State was preserved."
}
$startMatches = -not $State.injectorStartedAt -or $startedAt -eq "$($State.injectorStartedAt)"
$identityMatches = [bool]($isNodeExecutable -and $nodeMatches -and $injectorMatches -and $startMatches)

if (-not $identityMatches) {
throw "The recorded injector PID $processId is running, but its visible identity does not match the saved Dream Skin process. State was preserved."
}

Stop-Process -Id $processId -Force -ErrorAction Stop
try { Wait-Process -Id $processId -Timeout 5 -ErrorAction Stop } catch {}
if (Get-Process -Id $processId -ErrorAction SilentlyContinue) {
Stop-Process -InputObject $processHandle -Force -ErrorAction Stop
[void]$processHandle.WaitForExit(15000)
if (-not $processHandle.HasExited) {
throw "The recorded Dream Skin injector did not stop: PID $processId"
}
return $true
Expand Down Expand Up @@ -1165,3 +1175,16 @@ function Confirm-DreamSkinRestart {
$shell = New-Object -ComObject WScript.Shell
return $shell.Popup($Message, 0, 'Codex Dream Skin', 52) -eq 6
}

function Invoke-DreamSkinCodexWindowActivation {
param([Parameter(Mandatory = $true)][object]$Codex)
$processes = @(Get-DreamSkinCodexProcesses -Codex $Codex)
if ($processes.Count -eq 0) { return $false }
$shell = New-Object -ComObject WScript.Shell
foreach ($process in $processes) {
try {
if ($shell.AppActivate([int]$process.ProcessId)) { return $true }
} catch {}
}
return $false
}
57 changes: 38 additions & 19 deletions windows/scripts/injector.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -535,24 +535,17 @@ export async function loadTheme(themeDir) {
throw new Error("Theme image cannot escape through a link or junction");
}
const art = raw.art && typeof raw.art === "object" && !Array.isArray(raw.art) ? raw.art : {};
const palette = raw.palette && typeof raw.palette === "object" && !Array.isArray(raw.palette)
? raw.palette : {};
const rawColors = raw.colors && typeof raw.colors === "object" && !Array.isArray(raw.colors)
? raw.colors : null;
const colorKeys = [
"background", "panel", "panelAlt", "accent", "accentAlt", "secondary",
"highlight", "text", "muted", "line",
];
const paletteAccent = typeof palette.accent === "string" && palette.accent.trim()
? palette.accent.trim() : "";
if (paletteAccent && !/^(?:#[\da-f]{3,8}|(?:rgb|hsl|oklch|oklab)\([^;{}]{1,96}\))$/i.test(paletteAccent)) {
throw new Error("palette.accent is not a supported CSS color");
}
const colors = {
background: normalizeThemeColor(rawColors?.background, "#071116"),
panel: normalizeThemeColor(rawColors?.panel, "#0b1a20"),
panelAlt: normalizeThemeColor(rawColors?.panelAlt, "#10272c"),
accent: normalizeThemeColor(rawColors?.accent, normalizeThemeColor(paletteAccent, "#7cff46")),
accent: normalizeThemeColor(rawColors?.accent, "#7cff46"),
accentAlt: normalizeThemeColor(rawColors?.accentAlt, "#b8ff3d"),
secondary: normalizeThemeColor(rawColors?.secondary, "#36d7e8"),
highlight: normalizeThemeColor(rawColors?.highlight, "#642a8c"),
Expand All @@ -577,14 +570,10 @@ export async function loadTheme(themeDir) {
safeArea: normalizedChoice(art.safeArea, "art.safeArea", THEME_CHOICES.safeArea, "auto"),
taskMode: normalizedChoice(art.taskMode, "art.taskMode", THEME_CHOICES.taskMode, "auto"),
},
colorMode: rawColors ? "explicit" : (paletteAccent ? "explicit" : "auto"),
explicitColorKeys: rawColors
? colorKeys.filter((key) => Object.hasOwn(rawColors, key))
: (paletteAccent ? ["accent"] : []),
colorMode: rawColors ? "explicit" : "auto",
explicitColorKeys: rawColors ? colorKeys.filter((key) => Object.hasOwn(rawColors, key)) : [],
colors,
palette: {},
};
if (paletteAccent) theme.palette.accent = paletteAccent;
const [themeStat, imageStat, safeCss] = await Promise.all([
fs.stat(themePath),
fs.stat(realImagePath),
Expand Down Expand Up @@ -1153,10 +1142,27 @@ export async function verifySession(
// already-resolved semantic home container so a healthy home session is
// not rejected solely because the stricter home-icon selector is late.
const home = document.querySelector(${selectorLiteral("home-route")}) ?? homeRoute;
const suggestions = home?.querySelector(${selectorLiteral("home-suggestions")}) ?? null;
const cardButtons = suggestions ? [...suggestions.querySelectorAll('button')] : [];
const cards = cardButtons.map(box);
const visibleCards = cards.filter((item) => item?.visible);
const suggestionLabels = cardButtons.flatMap((button) => {
const expectedColor = getComputedStyle(button).color;
return [...button.querySelectorAll('*')]
.filter((node) => [...node.childNodes].some((child) =>
child.nodeType === 3 && child.textContent.trim()))
.map((node) => ({
...box(node),
text: String(node.textContent ?? "").trim().slice(0, 80),
color: getComputedStyle(node).color,
expectedColor,
}));
});
const visibleSuggestionLabels = suggestionLabels.filter((item) => item?.visible);
const suggestionLabelColorsMatch = visibleSuggestionLabels.every((item) =>
item.color === item.expectedColor);
const settingsAnchor = document.querySelector(${selectorLiteral("appearance-radio")}) ||
document.querySelector(${stableTestidLiteral("theme-preview")});
const suggestions = home?.querySelector(${selectorLiteral("home-suggestions")}) ?? null;
const cards = suggestions ? [...suggestions.querySelectorAll('button')].map(box) : [];
const runtime = window.__CODEX_DREAM_SKIN_STATE__;
const adopted = runtime?.styleMode === 'adopted' &&
[...document.adoptedStyleSheets].includes(runtime.styleSheet);
Expand Down Expand Up @@ -1198,6 +1204,9 @@ export async function verifySession(
settingsAnchor: box(settingsAnchor),
hero,
cards,
visibleCardCount: visibleCards.length,
suggestionLabels,
suggestionLabelColorsMatch,
composer: box(document.querySelector(${selectorLiteral("composer-chrome")})),
shell: box(document.querySelector(${selectorLiteral("shell-main")})),
sidebar: box(document.querySelector(${selectorLiteral("left-panel")})),
Expand Down Expand Up @@ -1238,12 +1247,18 @@ export async function verifySession(
windowPass, documentPass, viewportPass, structurePass,
nativeWindowPass, fallbackWindowPass,
};
const homePass = !result.homePresent || (
Boolean(result.homeSurface?.visible && result.hero?.visible) &&
result.hero.width >= 280 && result.hero.height >= 120 &&
(!result.suggestionsPresent || result.visibleCardCount === 0 || (
result.suggestionLabels.filter((item) => item?.visible).length >= result.visibleCardCount &&
result.suggestionLabelColorsMatch
))
);
result.pass = result.installed && result.version === result.expectedVersion &&
result.stylePresent && result.businessClassPollution === 0 && windowPass &&
documentPass && viewportPass && structurePass &&
payloadPass &&
(!result.homePresent || (Boolean(result.homeSurface?.visible && result.hero?.visible) &&
(!result.suggestionsPresent || (result.cards.length >= 2 && result.cards.length <= 4))));
payloadPass && homePass;
return result;
})()`);
}
Expand Down Expand Up @@ -1740,6 +1755,10 @@ if (path.resolve(process.argv[1] || "") === path.resolve(scriptPath)) {
payloadBytes: Buffer.byteLength(loaded.payload),
themeId: loaded.theme.id,
appearance: loaded.theme.appearance,
colorMode: loaded.theme.colorMode,
explicitColorKeys: loaded.theme.explicitColorKeys,
hasColors: !!loaded.theme.colors && typeof loaded.theme.colors === "object",
hasPalette: Object.hasOwn(loaded.theme, "palette"),
art: loaded.theme.art,
artMetadata: loaded.theme.artMetadata ?? null,
safeCssStatus: loaded.safeCssStatus,
Expand Down
13 changes: 13 additions & 0 deletions windows/scripts/start-dream-skin.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ try {
# and a single early miss used to tear the whole startup down. The
# watcher keeps applying in the background, so retry until a deadline.
$verifyDeadline = (Get-Date).AddSeconds(90)
$forceInjectedAfterVerifyFailure = $false
while ($true) {
$verify = Invoke-DreamSkinNative -FilePath $node.Path -ArgumentList @(
$Injector, '--verify', '--port', "$Port",
Expand All @@ -304,6 +305,18 @@ try {
} catch {
$skinLooksRendered = $false
}
if (-not $forceInjectedAfterVerifyFailure) {
$forceInjectedAfterVerifyFailure = $true
try { [void](Invoke-DreamSkinCodexWindowActivation -Codex $codex) } catch {}
$once = Invoke-DreamSkinNative -FilePath $node.Path -ArgumentList @(
$Injector, '--once', '--port', "$Port",
'--browser-id', $cdpIdentity.BrowserId, '--theme-dir', $themePaths.Active,
'--timeout-ms', '15000')
Write-DreamSkinUtf8FileAtomically -Path $VerifyPath -Content (
(($verify.Output + $once.Output) -join "`r`n") + "`r`n"
)
if ($once.ExitCode -eq 0) { break }
}
if ($daemon.HasExited) { throw "The injector exited during startup. See $StderrPath" }
if ((Get-Date) -ge $verifyDeadline) { throw "Dream Skin verification failed. See $VerifyPath" }
Start-Sleep -Seconds 3
Expand Down
48 changes: 23 additions & 25 deletions windows/scripts/theme-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function Resolve-DreamSkinCommunityApplyUri {
param([Parameter(Mandatory = $true)][string]$Uri)
$match = [regex]::Match(
$Uri,
'\Adreamskin://apply\?version=(ver_[a-z0-9]{8,64})\z',
'\Adreamskin://apply/?\?version=(ver_[a-z0-9]{8,64})\z',
[System.Text.RegularExpressions.RegexOptions]::CultureInvariant
)
if (-not $match.Success) {
Expand Down Expand Up @@ -378,10 +378,28 @@ function Read-DreamSkinTheme {
Directory = $directory
ThemePath = $themePath
ImagePath = $imagePath
Theme = $theme
Theme = Normalize-DreamSkinThemeContract -Theme $theme
}
}

function Normalize-DreamSkinThemeContract {
param([Parameter(Mandatory = $true)][object]$Theme)
if ($null -eq $Theme -or $Theme -is [string] -or $Theme -is [array]) {
throw 'Theme contract must be a JSON object.'
}
if (-not $Theme.PSObject.Properties['id'] -or -not $Theme.PSObject.Properties['id'].Value) {
$Theme | Add-Member -NotePropertyName id -NotePropertyValue 'custom' -Force
}
if (-not $Theme.PSObject.Properties['appearance'] -or -not $Theme.PSObject.Properties['appearance'].Value) {
$Theme | Add-Member -NotePropertyName appearance -NotePropertyValue 'auto' -Force
}
if (-not $Theme.PSObject.Properties['art'] -or -not $Theme.PSObject.Properties['art'].Value) {
$Theme | Add-Member -NotePropertyName art -NotePropertyValue `
([pscustomobject]@{ focusX = $null; focusY = $null; safeArea = 'auto'; taskMode = 'auto' }) -Force
}
return $Theme
}

function Write-DreamSkinTheme {
param(
[Parameter(Mandatory = $true)][string]$ThemeDirectory,
Expand All @@ -390,6 +408,7 @@ function Write-DreamSkinTheme {
Assert-DreamSkinNoReparseComponents -Path $ThemeDirectory
New-Item -ItemType Directory -Force -Path $ThemeDirectory | Out-Null
Assert-DreamSkinNoReparseComponents -Path $ThemeDirectory
$Theme = Normalize-DreamSkinThemeContract -Theme $Theme
$json = $Theme | ConvertTo-Json -Depth 8
$themePath = Join-Path $ThemeDirectory 'theme.json'
Assert-DreamSkinNoReparseComponents -Path $themePath
Expand Down Expand Up @@ -538,7 +557,6 @@ function Set-DreamSkinActiveTheme {
name = '自定义主题'
appearance = 'auto'
art = [pscustomobject]@{ focusX = $null; focusY = $null; safeArea = 'auto'; taskMode = 'auto' }
palette = [pscustomobject]@{}
}
}
$imageName = New-DreamSkinThemeImageName -Extension $extension
Expand All @@ -564,15 +582,7 @@ function Set-DreamSkinActiveTheme {
Assert-DreamSkinImageFile -Path $target
$Theme | Add-Member -NotePropertyName image -NotePropertyValue $imageName -Force
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 }
if (-not $Theme.art) {
$Theme | Add-Member -NotePropertyName art -NotePropertyValue `
([pscustomobject]@{ focusX = $null; focusY = $null; safeArea = 'auto'; taskMode = 'auto' }) -Force
}
if (-not $Theme.palette) {
$Theme | Add-Member -NotePropertyName palette -NotePropertyValue ([pscustomobject]@{}) -Force
}
$Theme = Normalize-DreamSkinThemeContract -Theme $Theme
$activeCss = Join-Path $paths.Active 'theme.css'
Assert-DreamSkinNoReparseComponents -Path $activeCss
if ($temporaryCss) {
Expand Down Expand Up @@ -715,19 +725,7 @@ function Get-DreamSkinThemeRuntimeContentFingerprint {
$loaded = Read-DreamSkinTheme -ThemeDirectory $ThemeDirectory -SkipImageMetadata
$runtimeTheme = $loaded.Theme | ConvertTo-Json -Depth 8 | ConvertFrom-Json
$runtimeTheme.image = '<runtime-image>'
if (-not $runtimeTheme.id) {
$runtimeTheme | Add-Member -NotePropertyName id -NotePropertyValue 'custom' -Force
}
if (-not $runtimeTheme.appearance) {
$runtimeTheme | Add-Member -NotePropertyName appearance -NotePropertyValue 'auto' -Force
}
if (-not $runtimeTheme.art) {
$runtimeTheme | Add-Member -NotePropertyName art -NotePropertyValue `
([pscustomobject]@{ focusX = $null; focusY = $null; safeArea = 'auto'; taskMode = 'auto' }) -Force
}
if (-not $runtimeTheme.palette) {
$runtimeTheme | Add-Member -NotePropertyName palette -NotePropertyValue ([pscustomobject]@{}) -Force
}
$runtimeTheme = Normalize-DreamSkinThemeContract -Theme $runtimeTheme
$canonicalTheme = ConvertTo-DreamSkinCanonicalJsonValue -Value $runtimeTheme
$themeBytes = [System.Text.Encoding]::UTF8.GetBytes(
($canonicalTheme | ConvertTo-Json -Depth 8 -Compress)
Expand Down
1 change: 1 addition & 0 deletions windows/scripts/verify-dream-skin.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ $ErrorActionPreference = 'Stop'
$PortExplicit = $PSBoundParameters.ContainsKey('Port')
$injector = Join-Path $PSScriptRoot 'injector.mjs'
. (Join-Path $PSScriptRoot 'common-windows.ps1')
. (Join-Path $PSScriptRoot 'theme-windows.ps1')

$operationLock = Enter-DreamSkinOperationLock
$verifyExitCode = 1
Expand Down
Loading
Loading