Make private desktop tools opt-in and preserve host defaults

This commit is contained in:
FrameBridge contributors committed 2026-10-27 15:10:11 -04:00
1 parent 0ae1fccfba
commit e84362b999
15 files changed
+219 -45

No files matched your search

@@ -1 +1 @@
__version__ = "0.3.2~compat.20261027.1"
__version__ = "0.3.2~compat.20261027.2"
@@ -441,6 +441,12 @@ def _doctor_command(args, state):
print("Missing command: " + str(item))
for item in value.get("issues", []):
print("Issue: " + str(item))
virtual_missing = value.get("optional_missing_commands", {}).get("private_desktop", [])
if virtual_missing:
print("Optional private desktop tools missing: " + ", ".join(virtual_missing))
hint = value.get("optional_install_commands", {}).get("private_desktop")
if hint:
print("Install only for an explicit virtual desktop: " + hint)
print("This checks prerequisites; use fb status to inspect the current connection.")
return _result_code(value)
@@ -7,6 +7,11 @@ from .real_screen.screen_capture import CaptureError, foreground, _x11_context
BINDING = "FRAMEBRIDGE_DISPLAY_BINDING"
GRAPHICAL_COMMANDS = ("wmctrl", "xdotool", "xwininfo", "xclip")
VIRTUAL_COMMANDS = ("Xvfb", "openbox", "xauth")
VIRTUAL_INSTALL_COMMAND = "sudo apt install xvfb openbox xauth"
def missing_virtual_commands(env):
return [name for name in VIRTUAL_COMMANDS if not shutil.which(name, path=env.get("PATH"))]
def missing_graphical_commands(env):
@@ -302,7 +302,7 @@ GUIDE = {'start': {'title': 'Start with FrameBridge',
'partial invented view as a real screenshot.',
'commands': []},
{'heading': 'Automatic graphical discovery',
'body': 'The installer and control console stay text interfaces in both TTY and a desktop terminal. Compatible graphical CUA is discovered automatically; availability depends on the actual display/session and permissions. A loaded module is not proof that real pixel capture or input succeeded. New profiles use adaptive auto mode: without a verified active X11 session the runtime provides FBEXEC, FBTERM and CUA-TTY in text mode, without requiring a graphical login or automatically creating a private desktop. Explicit virtual-desktop settings remain advanced options and are labelled separately from physical-screen evidence. Physical-screen capture and input currently require the supported active X11 session. Native Wayland control is not validated; showing an XWayland window does not establish full-desktop capture or input.',
'body': 'The installer and control console stay text interfaces in both TTY and a desktop terminal. Compatible graphical CUA is discovered automatically; availability depends on the actual display/session and permissions. A loaded module is not proof that real pixel capture or input succeeded. New profiles use adaptive auto mode: without a verified active X11 session the runtime provides FBEXEC, FBTERM and CUA-TTY in text mode, without requiring a graphical login or automatically creating a private desktop. Explicit virtual-desktop settings remain advanced options and are labelled separately from physical-screen evidence. Xvfb and Openbox are optional package suggestions and are not installed for the normal connection. Only when you choose a private virtual desktop, install its tools explicitly with sudo apt install xvfb openbox xauth and rerun fb doctor. Installing Openbox can register it as the generic window-manager alternative; use your distribution settings to retain your preferred manager when needed. Physical-screen capture and input currently require the supported active X11 session. Native Wayland control is not validated; showing an XWayland window does not establish full-desktop capture or input.',
'commands': ['fb status', 'fb doctor']},
{'heading': 'Frame-buffer read access',
'body': 'screen-access is a narrow, explicit administrative permission '
@@ -151,9 +151,9 @@ def doctor(state):
commands = ["bash", "tmux", "tee", "flock", "awk", "sha256sum", "timeout",
"dbus-run-session", "ip", "lsblk", "systemctl", "systemd-run", "sudo", "uptime"]
missing = [x for x in commands if not shutil.which(x, path=env.get("PATH"))]
from .display_context import missing_graphical_commands, VIRTUAL_COMMANDS
from .display_context import missing_graphical_commands, missing_virtual_commands, VIRTUAL_INSTALL_COMMAND
graphical_missing = missing_graphical_commands(env)
virtual_missing = [x for x in VIRTUAL_COMMANDS if not shutil.which(x, path=env.get("PATH"))]
virtual_missing = missing_virtual_commands(env)
console_missing = [x for x in ("gnome-terminal", "xterm") if not shutil.which(x, path=env.get("PATH"))]
if config.get("headless") in ("no", False):
missing.extend(graphical_missing)
@@ -182,8 +182,10 @@ def doctor(state):
"display_context": display_context,
"capabilities": {"fbexec": not missing, "fbterm": not missing, "cua_tty_module": True,
"graphical_cua": display_context.get("kind") in ("physical-x11", "virtual-x11") and not graphical_missing,
"private_desktop": compatible and not missing and not graphical_missing and not virtual_missing,
"mode": "graphical" if display_context.get("kind") in ("physical-x11", "virtual-x11") else "text"},
"optional_missing_commands": {"graphical_cua": graphical_missing, "private_desktop": virtual_missing, "graphical_terminal": console_missing},
"optional_install_commands": {"private_desktop": VIRTUAL_INSTALL_COMMAND if virtual_missing else None},
"network": inspect_network(),
"role": config["role"], "issues": issues}
@@ -372,6 +374,10 @@ def _manager(state):
from .display_context import bind_text
bind_text(env, state, reason="PHYSICAL_X11_UNAVAILABLE_TEXT_TOOLS_ACTIVE")
if private_display:
from .display_context import missing_virtual_commands, VIRTUAL_INSTALL_COMMAND
missing_virtual = missing_virtual_commands(env)
if missing_virtual:
raise RuntimeError("Private virtual desktop tools are missing: " + ", ".join(missing_virtual) + ". Install them explicitly: " + VIRTUAL_INSTALL_COMMAND)
display = next((n for n in range(140, 240) if not Path("/tmp/.X11-unix/X" + str(n)).exists() and not Path("/tmp/.X" + str(n) + "-lock").exists()), None)
if display is None:
raise RuntimeError("No private X11 display number is available.")
@@ -424,7 +430,11 @@ def start(state):
return current
checks = doctor(state)
if not checks["ok"]:
return {"ok": False, "error": "Preflight failed. Run framebridge doctor.", "doctor": checks}
error = "Preflight failed. Run framebridge doctor."
hint = checks.get("optional_install_commands", {}).get("private_desktop")
if config.get("headless") in ("yes", True) and hint:
error = "Private virtual desktop tools are missing. Install them explicitly: " + hint + ". Then run framebridge doctor."
return {"ok": False, "error": error, "doctor": checks}
for name in ("logs", "run"):
private_dir(state / name)
for name in ("ready.json", "last-error.json", "worker.json"):