fix(dark): 撤掉不稳定的容器 portal 方案,浏览器深色回到 --force-dark-mode

实测极简 KasmVNC 容器里 portal 方案不可靠:autostart 起的 session dbus 会死
(gsettings 提交报 Connection refused),微信内核也不订阅 portal 的 color-scheme 信号,
所以微信不会变深、浏览器的"实时切换"也落空。故回到简单可靠的做法:
- 浏览器(Chromium):WOC_DARK=1 时 autostart 加 --force-dark-mode(开机即深,重启实例生效)。
- 微信:本开关无效,深色请在微信「设置→通用→深色模式」手动选。
- 移除:Dockerfile 的 xdg-desktop-portal/dconf 等包、autostart 的 portal 段、woc-dark.sh、
  docker.ts 的 setInstanceDark、index.ts 的在线切换循环。面板开关仅持久化 desktopDark(驱动开机明暗)。

去掉每实例常驻的 dbus/portal,也消除其对稳定性的潜在影响。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gloridust
2026-06-19 02:10:14 +08:00
parent eefce1e9ba
commit ba32cc90ae
7 changed files with 18 additions and 148 deletions

View File

@@ -91,9 +91,9 @@ WOC_INSTANCE_MEM_GB=
# 说明:设备伪装是「尽力而为」,非保证不被封;详见 doc/设备伪装.md。 # 说明:设备伪装是「尽力而为」,非保证不被封;详见 doc/设备伪装.md。
WOC_SPOOF_OS=1 WOC_SPOOF_OS=1
# 实例桌面深色:无需在此配置。面板顶栏右上角的主题开关(管理员)统一控制「面板 + 实例桌面」深色, # 实例桌面深色:无需在此配置。面板顶栏右上角的主题开关(管理员)控制,设置持久化在面板数据。
# 设置持久化在面板数据accounts.json并对运行中的实例实时切换微信内核是 Chromium跟随容器内 # 仅对**浏览器(Chromium)实例**生效(--force-dark-mode重启实例后生效微信不吃命令行参数、也不跟随
# xdg-desktop-portal 的 color-scheme 信号)。 # 极简容器的桌面信号,微信深色请在微信「设置→通用→深色模式」里手动选(随数据卷持久化)。
# ── 自愈 watchdog应对 KasmVNC/Xvnc 长跑内存泄漏) ─────────── # ── 自愈 watchdog应对 KasmVNC/Xvnc 长跑内存泄漏) ───────────
# 实测 Xvnc 长跑 24h 可膨胀到 ~9GiB原因在 KasmVNC/Xvnc 自身的 framebuffer / 软渲染 cache # 实测 Xvnc 长跑 24h 可膨胀到 ~9GiB原因在 KasmVNC/Xvnc 自身的 framebuffer / 软渲染 cache

View File

@@ -52,22 +52,6 @@ RUN set -eux; \
apt-get clean; \ apt-get clean; \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 深色模式支持(仅在实例环境 WOC_DARK=1 时由 autostart 启用)。微信内核是 Chromium它判断"系统是否深色"
# 读的是 freedesktop 桌面 portal 的 org.freedesktop.appearance→color-schemeD-Bus 信号),而非 GTK_THEME。
# 极简 KasmVNC 镜像默认没有 portal故装上 xdg-desktop-portal + gtk 后端 + dconf/gsettingsautostart 据
# WOC_DARK 跑一条 session 总线、把 color-scheme 设为 prefer-dark 并拉起 portal微信启动即跟随系统深色。
# 单独成层,避免动到上面缓存的安装层;关闭深色时这些服务完全不启动,零影响旧有稳定行为。
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
dbus-x11 \
xdg-desktop-portal \
xdg-desktop-portal-gtk \
dconf-service dconf-gsettings-backend \
gsettings-desktop-schemas; \
apt-get clean; \
rm -rf /var/lib/apt/lists/*
ENV LANG=zh_CN.UTF-8 \ ENV LANG=zh_CN.UTF-8 \
LC_ALL=zh_CN.UTF-8 \ LC_ALL=zh_CN.UTF-8 \
LIBGL_ALWAYS_SOFTWARE=1 LIBGL_ALWAYS_SOFTWARE=1
@@ -86,11 +70,6 @@ RUN chmod 755 /woc/woc-www-patch.sh && chmod 644 /woc/woc-ime.pl && /woc/woc-www
COPY wechat-ctl.sh /woc/wechat-ctl.sh COPY wechat-ctl.sh /woc/wechat-ctl.sh
RUN chmod 755 /woc/wechat-ctl.sh RUN chmod 755 /woc/wechat-ctl.sh
# 深色模式实时切换脚本:面板顶栏主题开关 → 经 docker exec 调用,改 gsettings color-scheme
# portal 通知微信实时重绘(见 woc-dark.sh / autostart 的 portal 段)
COPY woc-dark.sh /woc/woc-dark.sh
RUN chmod 755 /woc/woc-dark.sh
# v1.2.0 多应用应用定义app-defs.sh被 autostart/app-ctl 引用)+ 通用安装/状态控制app-ctl.sh # v1.2.0 多应用应用定义app-defs.sh被 autostart/app-ctl 引用)+ 通用安装/状态控制app-ctl.sh
# 微信委托回 wechat-ctl.sh、其它应用各自实现。app-defs.sh 是被 source 的、不需可执行位。 # 微信委托回 wechat-ctl.sh、其它应用各自实现。app-defs.sh 是被 source 的、不需可执行位。
COPY app-defs.sh app-ctl.sh /woc/ COPY app-defs.sh app-ctl.sh /woc/

View File

@@ -18,62 +18,15 @@ APP_TYPE="${WOC_APP_TYPE:-wechat}"
. /woc/app-defs.sh . /woc/app-defs.sh
woc_app_def "$APP_TYPE" woc_app_def "$APP_TYPE"
# 深色模式(freedesktop 桌面 portal 方案)。微信内核是 Chromium它判断"系统是否深色"读的是 portal 的 # 深色模式(实例环境 WOC_DARK=1 时;由面板顶栏主题开关持久化、经容器环境下发,改后需重启实例生效)。
# org.freedesktop.appearance→color-schemeD-Bus 信号),不是 GTK_THEME/settings.ini那只影响纯 GTK chrome # 仅浏览器(Chromium):加 --force-dark-mode 让其 UI + 网页走深色——这是极简容器里实测唯一稳的办法
# 极简 KasmVNC 默认无 portal故每个实例都常驻一套 session D-Bus + xdg-desktop-portal(gtk 后端) + dconf # 微信:内核虽是 Chromium 但不吃命令行参数,也不订阅桌面 portal 的 color-scheme 信号(实测 portal 方案在
# - 这样面板顶栏的主题开关能"远程拨开关"实时切换实例深色(面板 docker exec 改 gsettings color-scheme → # 极简容器里 session 总线不稳、微信也不跟随),故本开关对微信无效;微信深色请在微信「设置→通用→深色模式」
# portal 发 SettingChanged → 微信实时重绘),无需重启实例; # 里手动选(设置随数据卷持久化)。
# - 初始明暗由 WOC_DARK 决定(面板把开关持久化、经容器环境下发),微信由本脚本拉起、继承这套环境,启动即跟随。 if { [ "${WOC_DARK:-}" = "1" ] || [ "${WOC_DARK:-}" = "true" ]; } && [ "$APP_TYPE" = "chromium" ]; then
# 这些服务空闲极轻、且与 VNC 连接完全无关(不参与重连/重启),不影响连接稳定性。总线地址固定,便于 exec 复用。 APP_LAUNCH="${APP_LAUNCH} --force-dark-mode --enable-features=WebContentsForceDark"
# 整段 best-effort任一步失败都不阻断后续启动应用。 echo "[autostart] 浏览器深色已启用(--force-dark-mode"
woc_setup_portal() { fi
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp/woc-run-$(id -u)}"
mkdir -p "$XDG_RUNTIME_DIR" 2>/dev/null && chmod 700 "$XDG_RUNTIME_DIR" 2>/dev/null || true
# 让 xdg-desktop-portal 明确选用 gtk 后端来实现 Settings即 color-scheme 来源)
export XDG_CURRENT_DESKTOP="${XDG_CURRENT_DESKTOP:-GNOME}"
_xcfg="${HOME:-/config}/.config/xdg-desktop-portal"
mkdir -p "$_xcfg" 2>/dev/null || true
printf '[preferred]\ndefault=gtk\norg.freedesktop.impl.portal.Settings=gtk\n' >"$_xcfg/portals.conf" 2>/dev/null || true
# 注意:不要 export GTK_THEME=Adwaita:dark —— 那是静态环境变量、会让 GTK 程序恒为深色,无法跟随开关切回浅色。
# 明暗一律走 gsettingsdconf可被 woc-dark.sh 实时改),见下。
# 固定地址的 session 总线:微信(本脚本拉起,继承此变量)与面板 docker exec 的 woc-dark.sh 用同一条,
# 改的才是同一个 dconf、portal 才读得到。已在跑socket 存在)则复用。
export DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/woc-bus"
if [ ! -S "${XDG_RUNTIME_DIR}/woc-bus" ] && command -v dbus-daemon >/dev/null 2>&1; then
dbus-daemon --session --address="$DBUS_SESSION_BUS_ADDRESS" --nopidfile --fork 2>/dev/null || true
_i=0; while [ ! -S "${XDG_RUNTIME_DIR}/woc-bus" ] && [ "$_i" -lt 25 ]; do sleep 0.2; _i=$((_i + 1)); done
fi
[ -S "${XDG_RUNTIME_DIR}/woc-bus" ] || { echo "[autostart] 深色session 总线未就绪,跳过 portal"; return 0; }
# 落一份真实总线环境,供面板 docker exec 的 woc-dark.sh 复用。关键:容器内 XDG_RUNTIME_DIR 由 base 镜像设为
# /config/.XDG而 docker exec 不会继承会话环境;若 woc-dark.sh 自行猜测就会连错总线,改了 dconf 也通知不到
# 本进程拉起的 portal实测过的坑。故这里把实际地址写进文件woc-dark.sh 直接 source。
printf 'export XDG_RUNTIME_DIR=%s\nexport DBUS_SESSION_BUS_ADDRESS=%s\n' \
"$XDG_RUNTIME_DIR" "$DBUS_SESSION_BUS_ADDRESS" >"${HOME:-/config}/.woc-dark-env" 2>/dev/null || true
# 把环境推进总线的"激活环境",使 dbus 激活起来的 portal 能看到 DISPLAY/XDG_CURRENT_DESKTOP 等
command -v dbus-update-activation-environment >/dev/null 2>&1 && \
dbus-update-activation-environment --all >/dev/null 2>&1 || true
# 初始明暗dconf 共享后端portal 据此对外报告;之后面板 woc-dark.sh 改这同一个键即实时切换)
if command -v gsettings >/dev/null 2>&1; then
if [ "${WOC_DARK:-}" = "1" ] || [ "${WOC_DARK:-}" = "true" ]; then
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' 2>/dev/null || true
gsettings set org.gnome.desktop.interface gtk-theme 'Adwaita-dark' 2>/dev/null || true
else
gsettings set org.gnome.desktop.interface color-scheme 'default' 2>/dev/null || true
gsettings set org.gnome.desktop.interface gtk-theme 'Adwaita' 2>/dev/null || true
fi
fi
# 显式拉起 portalgtk 后端提供 Settings比纯 dbus 激活更稳,重复启动会自行退出、无害)
for _b in xdg-desktop-portal-gtk xdg-desktop-portal; do
for _d in /usr/libexec /usr/lib/xdg-desktop-portal /usr/lib; do
if [ -x "$_d/$_b" ]; then ( "$_d/$_b" >/dev/null 2>&1 & ); break; fi
done
done
echo "[autostart] 深色 portal 已就绪(初始 $([ "${WOC_DARK:-}" = "1" ] || [ "${WOC_DARK:-}" = "true" ] && echo prefer-dark || echo light)"
}
woc_setup_portal || true
# 注意:不给 Chromium 加 --force-dark-mode —— 那是启动期固定开关,会让浏览器恒为深色、无法跟随主题切回浅色
# (这正是"切浅色没反应、必须重启"的原因)。改为同微信一样纯靠 portal 的 color-scheme/gtk-theme 信号,
# 浏览器stock Chromium 原生支持 portal即可实时双向跟随。
# 防“最小化后丢失”本桌面openbox无任务栏窗口被最小化就无处恢复 → 黑屏。 # 防“最小化后丢失”本桌面openbox无任务栏窗口被最小化就无处恢复 → 黑屏。
# 看守进程:每 2s 把“被最小化(不可见)”的顶层窗口重新激活,相当于禁用最小化。对任意应用通用。 # 看守进程:每 2s 把“被最小化(不可见)”的顶层窗口重新激活,相当于禁用最小化。对任意应用通用。

View File

@@ -1,40 +0,0 @@
#!/bin/bash
# 实时切换本实例桌面深色(由面板经 docker exec 以 app 用户身份调用woc-dark.sh on|off
# 原理:改 GNOME color-schemedconf 共享后端)→ 与 autostart 同一条 session 总线上的 xdg-desktop-portal
# 收到变化、对外发 org.freedesktop.appearance→color-scheme 的 SettingChanged → 微信等 Chromium 系应用实时重绘。
# 须与 autostart 用完全相同的 XDG_RUNTIME_DIR / 总线地址,否则改的不是同一个 dconf、portal 读不到。
set -u
# 必须用与 autostart 完全相同的总线,否则改了 dconf 也通知不到那条总线上的 portal微信/浏览器就不会实时变)。
# autostart 已把真实 XDG_RUNTIME_DIR / DBUS_SESSION_BUS_ADDRESS 落到 /config/.woc-dark-envdocker exec 不继承
# 会话环境不能自行猜测——base 镜像里 XDG_RUNTIME_DIR 是 /config/.XDG 而非 /tmp/woc-run-<uid>)。
if [ -f /config/.woc-dark-env ]; then
# shellcheck source=/dev/null
. /config/.woc-dark-env
fi
: "${XDG_RUNTIME_DIR:=/config/.XDG}"
: "${DBUS_SESSION_BUS_ADDRESS:=unix:path=${XDG_RUNTIME_DIR}/woc-bus}"
export XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS
case "${1:-}" in
on | 1 | dark | true)
scheme='prefer-dark'
gtk='Adwaita-dark'
;;
*)
scheme='default'
gtk='Adwaita'
;;
esac
command -v gsettings >/dev/null 2>&1 || {
echo "[woc-dark] 容器内无 gsettings镜像未升级到含 portal 的版本),跳过;重启实例即用新镜像生效" >&2
exit 1
}
gsettings set org.gnome.desktop.interface color-scheme "$scheme" 2>/dev/null || {
echo "[woc-dark] 设置 color-scheme 失败portal/总线可能未就绪),可重启实例" >&2
exit 1
}
gsettings set org.gnome.desktop.interface gtk-theme "$gtk" 2>/dev/null || true
echo "[woc-dark] color-scheme=${scheme}"

View File

@@ -430,13 +430,6 @@ async function execCapture(inst: Instance, cmd: string[]): Promise<string> {
}); });
} }
// 实时切换运行中实例的桌面深色:以 app 用户身份执行 /woc/woc-dark.sh on|off它经同一条 session 总线
// 改 gsettings color-scheme → portal 发 SettingChanged → 微信等实时重绘。容器须为升级后镜像(含该脚本与
// portal旧镜像没有脚本会报错调用方按"best-effort"忽略即可(重启实例后即用新镜像、自动跟随)。
export async function setInstanceDark(inst: Instance, dark: boolean): Promise<void> {
await execCapture(inst, ['/woc/woc-dark.sh', dark ? 'on' : 'off']);
}
// 触发下载/安装detached立即返回后台下载。按实例 appType 分发app-ctl.sh wechat → 委托回 // 触发下载/安装detached立即返回后台下载。按实例 appType 分发app-ctl.sh wechat → 委托回
// wechat-ctl.shtelegram 等各自实现。兼容旧容器(升级前镜像里没有 /woc/app-ctl.sh有则用之无则 // wechat-ctl.shtelegram 等各自实现。兼容旧容器(升级前镜像里没有 /woc/app-ctl.sh有则用之无则
// 回退老的 wechat-ctl.sh旧实例都是微信。appType 取值受 instanceAppType 约束,可安全内插进 shell。 // 回退老的 wechat-ctl.sh旧实例都是微信。appType 取值受 instanceAppType 约束,可安全内插进 shell。

View File

@@ -44,7 +44,6 @@ import {
upgradeInstance, upgradeInstance,
removeInstance as removeInstanceContainer, removeInstance as removeInstanceContainer,
instanceRuntime, instanceRuntime,
setInstanceDark,
triggerWechat, triggerWechat,
wechatStatus, wechatStatus,
instanceTarget, instanceTarget,
@@ -209,29 +208,15 @@ app.get('/api/desktop-theme', async (req, reply) => {
if (!requireAuth(req, reply)) return; if (!requireAuth(req, reply)) return;
return { dark: getDesktopDark() }; return { dark: getDesktopDark() };
}); });
// 设置实例深色(管理员)。面板顶栏主题开关切到 深/浅 时调用:持久化 → 对所有运行中的实例实时切换 // 设置实例深色(管理员)。面板顶栏主题开关切到 深/浅 时调用:持久化即可。它作为浏览器(Chromium)实例
// docker exec woc-dark.sh。实时切换为 best-effort个别实例失败如旧镜像无该脚本不影响其余 // 启动时的明暗(经 envList → WOC_DARK 下发autostart 据此加 --force-dark-mode故**重启实例后生效**
// 重启该实例后即用新镜像、按持久化的初始明暗启动 // 不做在线切换(极简容器内无稳定的桌面 portal微信也不跟随详见 docker/autostart 注释)
app.post('/api/admin/desktop-theme', async (req, reply) => { app.post('/api/admin/desktop-theme', async (req, reply) => {
if (!requireAdmin(req, reply)) return; if (!requireAdmin(req, reply)) return;
const dark = !!(req.body as any)?.dark; const dark = !!(req.body as any)?.dark;
setDesktopDark(dark); setDesktopDark(dark);
let applied = 0; appendPanelLog('INFO', `实例深色设为 ${dark ? '深色' : '浅色'}(浏览器实例重启后生效)`);
let failed = 0; return { ok: true, dark };
const insts = listInstances();
await Promise.all(
insts.map(async (inst) => {
try {
if ((await instanceRuntime(inst)) !== 'running') return;
await setInstanceDark(inst, dark);
applied++;
} catch {
failed++; // 旧镜像/总线未就绪等:忽略,重启实例即生效
}
}),
);
appendPanelLog('INFO', `实例深色设为 ${dark ? '深色' : '浅色'}(实时应用 ${applied} 个,${failed} 个待重启生效)`);
return { ok: true, dark, applied, failed };
}); });
// ---------- 自助改密 ---------- // ---------- 自助改密 ----------

View File

@@ -315,7 +315,7 @@ function ThemeToggle() {
}; };
const label = mode === 'auto' ? '跟随系统' : mode === 'light' ? '亮色' : '深色'; const label = mode === 'auto' ? '跟随系统' : mode === 'light' ? '亮色' : '深色';
const hint = isAdmin const hint = isAdmin
? `主题:${label}同时切换面板与实例桌面深色;点击循环:跟随系统 / 亮色 / 深色)` ? `主题:${label}面板即时换肤;浏览器实例重启后跟随。点击循环:跟随系统 / 亮色 / 深色)`
: `主题:${label}(点击切换:跟随系统 / 亮色 / 深色)`; : `主题:${label}(点击切换:跟随系统 / 亮色 / 深色)`;
return ( return (
<button className="theme-toggle" onClick={cycle} title={hint} aria-label={`主题:${label}`}> <button className="theme-toggle" onClick={cycle} title={hint} aria-label={`主题:${label}`}>