From 370dbd91f7605d55f3cad8eb9e8d8d7794543953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Thu, 5 Nov 2026 08:10:13 +0800 Subject: [PATCH] Hands: their own virtual screen, even next to a logged-in desktop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A service set up without a screen (over SSH, or on Wayland) used to pick up the desktop session's DISPLAY/WAYLAND_DISPLAY from systemd, so screenshots waited for a 「分享螢幕」 answer nobody could give. Now it starts its own virtual screen; `anybot setup --real-screen` keeps the desktop. `anybot setup --refresh-service` rewrites the start script after an update (install.sh runs it), so joined computers pick up such changes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa --- install.sh | 4 ++-- src/setup.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index 16e8923..cff4f5c 100755 --- a/install.sh +++ b/install.sh @@ -303,8 +303,8 @@ case $MODE in ok "程式放在 ~/.local/bin/anybot" [ "$MODE" = app ] && ok "應用程式選單裡有 AnyBot" # 更新: the one running in the background (anybot setup's service) switches to the new one. - if systemctl --user is-active --quiet anybot 2>/dev/null; then - systemctl --user restart anybot && ok "重新啟動背景執行的 AnyBot(換成新版)" + if [ -f "$HOME/.local/bin/anybot-run" ] && systemctl --user is-enabled --quiet anybot 2>/dev/null; then + "$HOME/.local/bin/anybot" setup --refresh-service >/dev/null 2>&1 && ok "重新啟動背景執行的 AnyBot(換成新版)" fi fi fi diff --git a/src/setup.rs b/src/setup.rs index 17f96a2..a4cd7ab 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -795,6 +795,42 @@ impl Plan { } } +/// `anybot setup --refresh-service` (install.sh runs it after an update): write the start script +/// again — a newer AnyBot may start differently — and restart. The computer stays joined as it was, +/// and a screen remembered at setup stays. +fn refresh_service() -> i32 { + let Some(home) = dirs::home_dir() else { return 1 }; + let launcher = home.join(".local/bin/anybot-run"); + let Ok(old) = std::fs::read_to_string(&launcher) else { + println!("這台沒有設成開機自動執行,不用更新服務。"); + return 0; + }; + let role = if old.contains("accessibility bus") { Role::Hands } else { Role::Brain }; + let display = old.lines().find_map(|l| l.strip_prefix("export DISPLAY=\"${DISPLAY:-").and_then(|r| r.strip_suffix("}\""))).unwrap_or("").to_string(); + let env = Env { linux: cfg!(target_os = "linux"), display, systemd: true }; + let exe = match std::env::current_exe().and_then(|p| p.canonicalize()) { + Ok(e) => e, + Err(e) => { + eprintln!("✗ 找不到 AnyBot 程式:{e}"); + return 1; + } + }; + if let Err(e) = std::fs::write(&launcher, launcher_script(role, &exe, &env)) { + eprintln!("✗ 寫入 {} 失敗:{e}", launcher.display()); + return 1; + } + match systemctl(&["restart", UNIT]) { + Ok(_) => { + println!("已更新並重新啟動 AnyBot 服務。"); + 0 + } + Err(e) => { + eprintln!("✗ 重新啟動失敗:{e}"); + 1 + } + } +} + fn launcher_script(role: Role, exe: &std::path::Path, env: &Env) -> String { let q = |s: &str| format!("'{}'", s.replace('\'', "'\\''")); let mut s = String::from("#!/bin/sh\n# Started by systemd (anybot.service); written by `anybot setup`.\n"); @@ -806,11 +842,17 @@ fn launcher_script(role: Role, exe: &std::path::Path, env: &Env) -> String { if role == Role::Hands { s += "# The accessibility bus, so bots can read apps' buttons instead of only looking at pixels.\n"; s += "export NO_AT_BRIDGE=0 GTK_A11Y=atspi QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1\n"; + let real_screen = std::env::args().any(|a| a == "--real-screen"); if env.has_display() && env.display != "wayland" && env.display != "desktop" { s += &format!("# The screen this computer had when it was set up.\nexport DISPLAY=\"${{DISPLAY:-{}}}\"\n", env.display); if let Ok(x) = std::env::var("XAUTHORITY") { s += &format!("export XAUTHORITY=\"${{XAUTHORITY:-{x}}}\"\n"); } + } else if !real_screen { + // Set up without a screen (over SSH, or on Wayland): the bots get a virtual screen of + // their own, even when a desktop session is logged in here — nobody would be there to + // answer its 「允許分享螢幕」 dialog. `anybot setup --real-screen` uses the desktop. + s += "unset DISPLAY WAYLAND_DISPLAY XDG_SESSION_TYPE\n"; } s += "if [ -z \"$DISPLAY\" ] && [ -z \"$WAYLAND_DISPLAY\" ] && command -v xvfb-run >/dev/null; then\n"; s += " # No screen: a virtual one, a window manager, and a session bus for the accessibility bus.\n"; @@ -843,9 +885,12 @@ const VALUED: [&str; 4] = ["--server", "--user", "--name", "--brain-name"]; pub fn run(args: &[String]) -> i32 { let flag = |k: &str| args.iter().position(|a| a == k).and_then(|i| args.get(i + 1)).cloned(); if args.iter().any(|a| a == "-h" || a == "--help") { - println!("anybot setup:把這台電腦接到你的 AnyBot。\n\n anybot setup 網址 配對碼 (手機或電腦的 AnyBot 按「加一台電腦」會給你這一行)\n anybot setup 一個畫面:填網址和配對碼(或帳號密碼)\n 加 --brain 這台當大腦,不當手腳\n 加 --no-service 不設開機自動執行\n\n給程式用:anybot setup --server 網址 --user 帳號 [--brain-name 大腦名字],密碼放在 ANYBOT_ACCOUNT_PASSWORD。"); + println!("anybot setup:把這台電腦接到你的 AnyBot。\n\n anybot setup 網址 配對碼 (手機或電腦的 AnyBot 按「加一台電腦」會給你這一行)\n anybot setup 一個畫面:填網址和配對碼(或帳號密碼)\n 加 --brain 這台當大腦,不當手腳\n 加 --no-service 不設開機自動執行\n 加 --real-screen 讓 Bot 用這台登入中的桌面(預設用自己的虛擬螢幕)\n\n給程式用:anybot setup --server 網址 --user 帳號 [--brain-name 大腦名字],密碼放在 ANYBOT_ACCOUNT_PASSWORD。"); return 0; } + if args.iter().any(|a| a == "--refresh-service") { + return refresh_service(); + } let role = if args.iter().any(|a| a == "--brain") { Role::Brain } else { Role::Hands }; let mut pos = vec![]; let mut skip = false;