diff --git a/.env.example b/.env.example index a04fb53..8333a13 100644 --- a/.env.example +++ b/.env.example @@ -91,9 +91,9 @@ WOC_INSTANCE_MEM_GB= # 说明:设备伪装是「尽力而为」,非保证不被封;详见 doc/设备伪装.md。 WOC_SPOOF_OS=1 -# 实例桌面深色:无需在此配置。面板顶栏右上角的主题开关(管理员)统一控制「面板 + 实例桌面」深色, -# 设置持久化在面板数据(accounts.json),并对运行中的实例实时切换(微信内核是 Chromium,跟随容器内 -# xdg-desktop-portal 的 color-scheme 信号)。 +# 实例桌面深色:无需在此配置。面板顶栏右上角的主题开关(管理员)控制,设置持久化在面板数据。 +# 仅对**浏览器(Chromium)实例**生效(--force-dark-mode,重启实例后生效);微信不吃命令行参数、也不跟随 +# 极简容器的桌面信号,微信深色请在微信「设置→通用→深色模式」里手动选(随数据卷持久化)。 # ── 自愈 watchdog(应对 KasmVNC/Xvnc 长跑内存泄漏) ─────────── # 实测 Xvnc 长跑 24h 可膨胀到 ~9GiB,原因在 KasmVNC/Xvnc 自身的 framebuffer / 软渲染 cache diff --git a/docker/Dockerfile b/docker/Dockerfile index 8f6695c..6d55207 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -52,22 +52,6 @@ RUN set -eux; \ apt-get clean; \ rm -rf /var/lib/apt/lists/* -# 深色模式支持(仅在实例环境 WOC_DARK=1 时由 autostart 启用)。微信内核是 Chromium,它判断"系统是否深色" -# 读的是 freedesktop 桌面 portal 的 org.freedesktop.appearance→color-scheme(D-Bus 信号),而非 GTK_THEME。 -# 极简 KasmVNC 镜像默认没有 portal,故装上 xdg-desktop-portal + gtk 后端 + dconf/gsettings:autostart 据 -# 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 \ LC_ALL=zh_CN.UTF-8 \ 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 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, # 微信委托回 wechat-ctl.sh、其它应用各自实现)。app-defs.sh 是被 source 的、不需可执行位。 COPY app-defs.sh app-ctl.sh /woc/ diff --git a/docker/autostart b/docker/autostart index 39bda69..cbe24d6 100644 --- a/docker/autostart +++ b/docker/autostart @@ -18,62 +18,15 @@ APP_TYPE="${WOC_APP_TYPE:-wechat}" . /woc/app-defs.sh woc_app_def "$APP_TYPE" -# 深色模式(freedesktop 桌面 portal 方案)。微信内核是 Chromium,它判断"系统是否深色"读的是 portal 的 -# org.freedesktop.appearance→color-scheme(D-Bus 信号),不是 GTK_THEME/settings.ini(那只影响纯 GTK chrome)。 -# 极简 KasmVNC 默认无 portal,故每个实例都常驻一套 session D-Bus + xdg-desktop-portal(gtk 后端) + dconf: -# - 这样面板顶栏的主题开关能"远程拨开关"实时切换实例深色(面板 docker exec 改 gsettings color-scheme → -# portal 发 SettingChanged → 微信实时重绘),无需重启实例; -# - 初始明暗由 WOC_DARK 决定(面板把开关持久化、经容器环境下发),微信由本脚本拉起、继承这套环境,启动即跟随。 -# 这些服务空闲极轻、且与 VNC 连接完全无关(不参与重连/重启),不影响连接稳定性。总线地址固定,便于 exec 复用。 -# 整段 best-effort:任一步失败都不阻断后续启动应用。 -woc_setup_portal() { - 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 程序恒为深色,无法跟随开关切回浅色。 - # 明暗一律走 gsettings(dconf,可被 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 - # 显式拉起 portal(gtk 后端提供 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)即可实时双向跟随。 +# 深色模式(实例环境 WOC_DARK=1 时;由面板顶栏主题开关持久化、经容器环境下发,改后需重启实例生效)。 +# 仅浏览器(Chromium):加 --force-dark-mode 让其 UI + 网页走深色——这是极简容器里实测唯一稳的办法。 +# 微信:内核虽是 Chromium 但不吃命令行参数,也不订阅桌面 portal 的 color-scheme 信号(实测 portal 方案在 +# 极简容器里 session 总线不稳、微信也不跟随),故本开关对微信无效;微信深色请在微信「设置→通用→深色模式」 +# 里手动选(设置随数据卷持久化)。 +if { [ "${WOC_DARK:-}" = "1" ] || [ "${WOC_DARK:-}" = "true" ]; } && [ "$APP_TYPE" = "chromium" ]; then + APP_LAUNCH="${APP_LAUNCH} --force-dark-mode --enable-features=WebContentsForceDark" + echo "[autostart] 浏览器深色已启用(--force-dark-mode)" +fi # 防“最小化后丢失”:本桌面(openbox)无任务栏,窗口被最小化就无处恢复 → 黑屏。 # 看守进程:每 2s 把“被最小化(不可见)”的顶层窗口重新激活,相当于禁用最小化。对任意应用通用。 diff --git a/docker/woc-dark.sh b/docker/woc-dark.sh deleted file mode 100644 index 8856730..0000000 --- a/docker/woc-dark.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -# 实时切换本实例桌面深色(由面板经 docker exec 以 app 用户身份调用:woc-dark.sh on|off)。 -# 原理:改 GNOME color-scheme(dconf 共享后端)→ 与 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-env(docker exec 不继承 -# 会话环境,不能自行猜测——base 镜像里 XDG_RUNTIME_DIR 是 /config/.XDG 而非 /tmp/woc-run-)。 -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}" diff --git a/panel/server/src/docker.ts b/panel/server/src/docker.ts index 127eb88..4ab5b04 100644 --- a/panel/server/src/docker.ts +++ b/panel/server/src/docker.ts @@ -430,13 +430,6 @@ async function execCapture(inst: Instance, cmd: string[]): Promise { }); } -// 实时切换运行中实例的桌面深色:以 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 { - await execCapture(inst, ['/woc/woc-dark.sh', dark ? 'on' : 'off']); -} - // 触发下载/安装(detached,立即返回,后台下载)。按实例 appType 分发:app-ctl.sh wechat → 委托回 // wechat-ctl.sh;telegram 等各自实现。兼容旧容器(升级前镜像里没有 /woc/app-ctl.sh):有则用之,无则 // 回退老的 wechat-ctl.sh(旧实例都是微信)。appType 取值受 instanceAppType 约束,可安全内插进 shell。 diff --git a/panel/server/src/index.ts b/panel/server/src/index.ts index 582a1e3..cf11268 100644 --- a/panel/server/src/index.ts +++ b/panel/server/src/index.ts @@ -44,7 +44,6 @@ import { upgradeInstance, removeInstance as removeInstanceContainer, instanceRuntime, - setInstanceDark, triggerWechat, wechatStatus, instanceTarget, @@ -209,29 +208,15 @@ app.get('/api/desktop-theme', async (req, reply) => { if (!requireAuth(req, reply)) return; return { dark: getDesktopDark() }; }); -// 设置实例深色(管理员)。面板顶栏主题开关切到 深/浅 时调用:持久化 → 对所有运行中的实例实时切换 -// (docker exec woc-dark.sh)。实时切换为 best-effort:个别实例失败(如旧镜像无该脚本)不影响其余, -// 重启该实例后即用新镜像、按持久化的初始明暗启动。 +// 设置实例深色(管理员)。面板顶栏主题开关切到 深/浅 时调用:持久化即可。它作为浏览器(Chromium)实例 +// 启动时的明暗(经 envList → WOC_DARK 下发,autostart 据此加 --force-dark-mode),故**重启实例后生效**, +// 不做在线切换(极简容器内无稳定的桌面 portal,微信也不跟随,详见 docker/autostart 注释)。 app.post('/api/admin/desktop-theme', async (req, reply) => { if (!requireAdmin(req, reply)) return; const dark = !!(req.body as any)?.dark; setDesktopDark(dark); - let applied = 0; - let failed = 0; - 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 }; + appendPanelLog('INFO', `实例深色设为 ${dark ? '深色' : '浅色'}(浏览器实例重启后生效)`); + return { ok: true, dark }; }); // ---------- 自助改密 ---------- diff --git a/panel/web/src/AppShell.tsx b/panel/web/src/AppShell.tsx index df87a41..3c79995 100644 --- a/panel/web/src/AppShell.tsx +++ b/panel/web/src/AppShell.tsx @@ -315,7 +315,7 @@ function ThemeToggle() { }; const label = mode === 'auto' ? '跟随系统' : mode === 'light' ? '亮色' : '深色'; const hint = isAdmin - ? `主题:${label}(同时切换面板与实例桌面深色;点击循环:跟随系统 / 亮色 / 深色)` + ? `主题:${label}(面板即时换肤;浏览器实例重启后跟随。点击循环:跟随系统 / 亮色 / 深色)` : `主题:${label}(点击切换:跟随系统 / 亮色 / 深色)`; return (