Hands: their own virtual screen, even next to a logged-in desktop
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
This commit is contained in:
parent
8615881634
commit
370dbd91f7
|
|
@ -303,8 +303,8 @@ case $MODE in
|
||||||
ok "程式放在 ~/.local/bin/anybot"
|
ok "程式放在 ~/.local/bin/anybot"
|
||||||
[ "$MODE" = app ] && ok "應用程式選單裡有 AnyBot"
|
[ "$MODE" = app ] && ok "應用程式選單裡有 AnyBot"
|
||||||
# 更新: the one running in the background (anybot setup's service) switches to the new one.
|
# 更新: 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
|
if [ -f "$HOME/.local/bin/anybot-run" ] && systemctl --user is-enabled --quiet anybot 2>/dev/null; then
|
||||||
systemctl --user restart anybot && ok "重新啟動背景執行的 AnyBot(換成新版)"
|
"$HOME/.local/bin/anybot" setup --refresh-service >/dev/null 2>&1 && ok "重新啟動背景執行的 AnyBot(換成新版)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
47
src/setup.rs
47
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 {
|
fn launcher_script(role: Role, exe: &std::path::Path, env: &Env) -> String {
|
||||||
let q = |s: &str| format!("'{}'", s.replace('\'', "'\\''"));
|
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");
|
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 {
|
if role == Role::Hands {
|
||||||
s += "# The accessibility bus, so bots can read apps' buttons instead of only looking at pixels.\n";
|
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";
|
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" {
|
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);
|
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") {
|
if let Ok(x) = std::env::var("XAUTHORITY") {
|
||||||
s += &format!("export XAUTHORITY=\"${{XAUTHORITY:-{x}}}\"\n");
|
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 += "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";
|
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 {
|
pub fn run(args: &[String]) -> i32 {
|
||||||
let flag = |k: &str| args.iter().position(|a| a == k).and_then(|i| args.get(i + 1)).cloned();
|
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") {
|
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;
|
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 role = if args.iter().any(|a| a == "--brain") { Role::Brain } else { Role::Hands };
|
||||||
let mut pos = vec![];
|
let mut pos = vec![];
|
||||||
let mut skip = false;
|
let mut skip = false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue