Skip to content

Commit

Permalink
#4456 support simple aliases for xvfb command option
Browse files Browse the repository at this point in the history
  • Loading branch information
totaam committed Jan 6, 2025
1 parent b103148 commit 7b03c94
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 45 deletions.
14 changes: 11 additions & 3 deletions fs/etc/xpra/conf.d/55_server_x11.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,16 @@ input-method=auto
# sync-xvfb = 0
sync-xvfb = auto

# Virtual display command:
# - Xvfb option (limited DPI support)
# Virtual display command
# common aliases are resolved to full commands at runtime:
# xvfb = Xvfb
# xvfb = Xorg
# xvfb = Xephyr
# xvfb = weston+Xwayland
# xvfb = auto
#
# full commands:
# Xvfb option (limited DPI support)
# xvfb = Xvfb -nolisten tcp -noreset \
# +extension GLX +extension Composite \
# +extension RANDR +extension RENDER \
Expand All @@ -42,7 +50,7 @@ sync-xvfb = auto
# +extension GLX +extension Composite \
# -auth $XAUTHORITY \
# -screen 8192x4096x24+32
# - Xdummy (better with version 0.4.0 or later):
# - Xdummy:
#xvfb = %(xdummy_command)s
#
# Selecting virtual X server:
Expand Down
103 changes: 83 additions & 20 deletions xpra/scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,31 +155,37 @@ def get_Xdummy_confdir() -> str:

def get_Xdummy_command(xorg_cmd="Xorg",
log_dir="${XPRA_SESSION_DIR}",
xorg_conf="${XORG_CONFIG_PREFIX}/etc/xpra/xorg.conf") -> list[str]:
return [
xorg_conf="${XORG_CONFIG_PREFIX}/etc/xpra/xorg.conf",
dpi=0) -> list[str]:
cmd = [
# ie: "Xorg" or "xpra_Xdummy" or "./install/bin/xpra_Xdummy"
xorg_cmd,
"-noreset", "-novtswitch",
"-nolisten", "tcp",
"+extension", "GLX",
"+extension", "RANDR",
"+extension", "RENDER",
"-nolisten", "tcp",
"-noreset",
"-novtswitch",
"-auth", "$XAUTHORITY",
"-logfile", f"{log_dir}/Xorg.log",
# must be specified with some Xorg versions (ie: arch linux)
# this directory can store xorg config files, it does not need to be created:
"-configdir", f'"{get_Xdummy_confdir()}"',
"-config", f'"{xorg_conf}"',
"-configdir", f"{get_Xdummy_confdir()}",
"-config", f"{xorg_conf}",
]
if dpi > 0:
cmd += ["-dpi", f"{dpi}x{dpi}"]
return cmd


def get_Xvfb_command(width=8192, height=4096, dpi=96) -> list[str]:
def get_Xvfb_command(width=8192, height=4096, depth=24, dpi=96) -> list[str]:
cmd = [
"Xvfb",
"+extension", "GLX",
"+extension", "Composite",
"+extension", "RANDR", "+extension", "RENDER",
"-screen", "0", f"{width}x{height}x24+32",
"+extension", "RANDR",
"+extension", "RENDER",
"-screen", "0", f"{width}x{height}x{depth}+32",
# better than leaving to vfb after a resize?
"-nolisten", "tcp",
"-noreset",
Expand All @@ -190,28 +196,84 @@ def get_Xvfb_command(width=8192, height=4096, dpi=96) -> list[str]:
return cmd


def get_Xephyr_command(width=1920, height=1080, depth=24, dpi=96) -> list[str]:
cmd = [
"Xephyr",
"+extension", "GLX",
"+extension", "Composite",
"-screen", f"{width}x{height}x{depth}+32",
"-nolisten", "tcp",
"-noreset",
"-auth", "$XAUTHORITY",
]
if dpi > 0:
cmd += ["-dpi", f"{dpi}x{dpi}"]
return cmd


def get_weston_Xwayland_command(dpi=96) -> list[str]:
cmd = [
"/usr/libexec/xpra/xpra_weston_xvfb",
"+extension", "GLX",
"+extension", "Composite",
"-nolisten", "tcp",
"-noreset",
"-auth", "$XAUTHORITY",
]
if dpi > 0:
cmd += ["-dpi", f"{dpi}x{dpi}"]
return cmd


def xvfb_command(cmd: str, depth=32, dpi=0) -> list[str]:
parts = shlex.split(cmd)
if len(parts) > 1:
return parts
exe = parts[0]
if os.path.isabs(exe):
return parts
if exe == "Xvfb":
return get_Xvfb_command(depth=depth, dpi=dpi)
if exe == "Xephyr":
return get_Xephyr_command(depth=depth, dpi=dpi)
if exe in ("weston", "weston+Xwayland"):
return get_weston_Xwayland_command(dpi=dpi)
if exe in ("Xorg", "Xdummy"):
xorg_bin = get_xorg_bin()
return get_Xdummy_command(xorg_bin, dpi=dpi)
if exe == "auto":
return detect_xvfb_command(dpi=dpi)
return parts


def detect_xvfb_command(conf_dir="/etc/xpra/", bin_dir="",
Xdummy_ENABLED: bool | None = None, Xdummy_wrapper_ENABLED: bool | None = None,
warn_fn: Callable = warn) -> list[str]:
warn_fn: Callable = warn,
dpi=0,
) -> list[str]:
"""
This function returns the xvfb command to use.
It can either be an `Xvfb` command or one that uses `Xdummy`,
depending on the platform and file attributes.
"""
if WIN32: # pragma: no cover
return []

def vfb_default() -> list[str]:
return get_Xvfb_command(dpi=dpi)

if OSX: # pragma: no cover
return get_Xvfb_command()
return vfb_default()
if sys.platform.find("bsd") >= 0 and Xdummy_ENABLED is None: # pragma: no cover
warn_fn(f"Warning: sorry, no support for Xdummy on {sys.platform}")
return get_Xvfb_command()
return vfb_default()
if is_DEB():
# These distros do weird things and this can cause the real X11 server to crash
# see ticket #2834
return get_Xvfb_command()
return vfb_default()

if Xdummy_ENABLED is False:
return get_Xvfb_command()
return vfb_default()

if Xdummy_ENABLED is None:
debug("Xdummy support unspecified, will try to detect")
Expand All @@ -222,15 +284,16 @@ def detect_xvfb_command(conf_dir="/etc/xpra/", bin_dir="",
debug(f" found redhat-release: {relinfo!r}")
if relinfo.find("release 10") >= 0:
debug(" using `xpra_weston_xvfb` on RHEL 10")
return ["/usr/libexec/xpra/xpra_weston_xvfb"]
return detect_xdummy_command(conf_dir, bin_dir, Xdummy_wrapper_ENABLED, warn_fn)
return get_weston_Xwayland_command(dpi)
return detect_xdummy_command(conf_dir, bin_dir, Xdummy_wrapper_ENABLED, warn_fn, dpi=dpi)


def detect_xdummy_command(conf_dir="/etc/xpra/", bin_dir="",
Xdummy_wrapper_ENABLED: bool | None = None,
warn_fn: Callable = warn) -> list[str]:
warn_fn: Callable = warn,
dpi=0) -> list[str]:
if not POSIX or OSX:
return get_Xvfb_command()
return get_Xvfb_command(dpi=dpi)
xorg_bin = get_xorg_bin()
if Xdummy_wrapper_ENABLED is not None:
# honour what was specified:
Expand All @@ -245,7 +308,7 @@ def detect_xdummy_command(conf_dir="/etc/xpra/", bin_dir="",
if (xorg_stat.st_mode & stat.S_ISUID) != 0:
if (xorg_stat.st_mode & stat.S_IROTH) == 0:
warn_fn(f"{xorg_bin} is suid and not readable, Xdummy support unavailable")
return get_Xvfb_command()
return get_Xvfb_command(dpi=dpi)
debug(f"{xorg_bin} is suid and readable, using the xpra_Xdummy wrapper")
use_wrapper = True
else:
Expand All @@ -259,7 +322,7 @@ def detect_xdummy_command(conf_dir="/etc/xpra/", bin_dir="",
if bin_dir and os.path.exists(os.path.join(bin_dir, xorg_cmd)):
if bin_dir not in os.environ.get("PATH", "/bin:/usr/bin:/usr/local/bin").split(os.pathsep):
xorg_cmd = os.path.join(bin_dir, xorg_cmd)
return get_Xdummy_command(xorg_cmd, xorg_conf=xorg_conf)
return get_Xdummy_command(xorg_cmd, xorg_conf=xorg_conf, dpi=dpi)


def wrap_cmd_str(cmd) -> str:
Expand Down
13 changes: 7 additions & 6 deletions xpra/scripts/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
FALSE_OPTIONS, ALL_BOOLEAN_OPTIONS, OPTION_TYPES, CLIENT_ONLY_OPTIONS, CLIENT_OPTIONS,
parse_bool_or,
fixup_options, make_defaults_struct, read_config, dict_to_validated_config,
xvfb_command,
)
from xpra.common import (
CLOBBER_USE_DISPLAY, CLOBBER_UPGRADE, SSH_AGENT_DISPATCH,
Expand Down Expand Up @@ -1066,6 +1067,8 @@ def write_session_file(filename: str, contents) -> str:
# with the value supplied by the user:
protected_env["XDG_RUNTIME_DIR"] = xrd

xvfb_cmd = xvfb_command(opts.xvfb, opts.pixel_depth, opts.dpi)

sanitize_env()
if not shadowing:
os.environ.pop("WAYLAND_DISPLAY", None)
Expand All @@ -1081,7 +1084,7 @@ def write_session_file(filename: str, contents) -> str:
os.environ["DISPLAY"] = display_name
if POSIX:
os.environ["CKCON_X11_DISPLAY"] = display_name
elif not start_vfb or opts.xvfb.find("Xephyr") < 0:
elif not start_vfb or xvfb_cmd[0].find("Xephyr") < 0:
os.environ.pop("DISPLAY", None)
os.environ.update(protected_env)

Expand Down Expand Up @@ -1152,7 +1155,7 @@ def write_session_file(filename: str, contents) -> str:
if (starting or starting_desktop) and desktop_display and opts.notifications and not opts.dbus_launch:
print_DE_warnings()

if start_vfb and opts.sync_xvfb is None and any(opts.xvfb.find(x) >= 0 for x in ("Xephyr", "Xnest")):
if start_vfb and opts.sync_xvfb is None and any(xvfb_cmd[0].find(x) >= 0 for x in ("Xephyr", "Xnest")):
# automatically enable sync-xvfb for Xephyr and Xnest:
opts.sync_xvfb = 50

Expand Down Expand Up @@ -1320,7 +1323,7 @@ def write_session_file(filename: str, contents) -> str:
sizes = opts.resize_display.split(":", 1)[-1]
vfb_geom = parse_resolutions(sizes, opts.refresh_rate)[0]

xvfb, display_name = start_Xvfb(opts.xvfb, vfb_geom, pixel_depth, display_name, cwd,
xvfb, display_name = start_Xvfb(xvfb_cmd, vfb_geom, pixel_depth, display_name, cwd,
uid, gid, username, uinput_uuid)
assert xauthority
xauth_add(xauthority, display_name, xauth_data, uid, gid)
Expand All @@ -1333,7 +1336,7 @@ def xvfb_terminated() -> None:
if xvfb_pidfile:
os.unlink(xvfb_pidfile)

getChildReaper().add_process(xvfb, "xvfb", opts.xvfb, ignore=True, callback=xvfb_terminated)
getChildReaper().add_process(xvfb, "xvfb", xvfb_cmd, ignore=True, callback=xvfb_terminated)
# always update as we may now have the "real" display name:
os.environ["DISPLAY"] = display_name
os.environ["CKCON_X11_DISPLAY"] = display_name
Expand Down Expand Up @@ -1372,8 +1375,6 @@ def xvfb_terminated() -> None:
if uinput_uuid:
devices = create_input_devices(uinput_uuid, uid)

xvfb_cmd = opts.xvfb

def check_xvfb(timeout=0) -> bool:
if xvfb is None:
return True
Expand Down
4 changes: 2 additions & 2 deletions xpra/util/child_reaper.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import os
import signal
from typing import Any
from collections.abc import Callable
from collections.abc import Callable, Sequence

from xpra.util.env import envint, envbool
from xpra.os_util import POSIX, gi_import
Expand Down Expand Up @@ -103,7 +103,7 @@ def cleanup(self) -> None:
self._proc_info = []
self._quit = None

def add_process(self, process, name: str, command, ignore=False, forget=False, callback=None) -> ProcInfo:
def add_process(self, process, name: str, command: str | Sequence[str], ignore=False, forget=False, callback=None) -> ProcInfo:
pid = process.pid
if pid <= 0:
raise RuntimeError(f"process {process} has no pid!")
Expand Down
30 changes: 16 additions & 14 deletions xpra/x11/vfb_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from xpra.common import RESOLUTION_ALIASES, DEFAULT_REFRESH_RATE, get_refresh_rate_for_value
from xpra.scripts.config import InitException, get_Xdummy_confdir, FALSE_OPTIONS
from xpra.util.str_fn import csv
from xpra.util.env import envint, envbool, shellsub, osexpand, get_exec_env
from xpra.util.env import envint, envbool, shellsub, osexpand, get_exec_env, get_saved_env_var
from xpra.os_util import getuid, getgid, POSIX, OSX
from xpra.server.util import setuidgid
from xpra.util.io import is_writable, pollwait
Expand Down Expand Up @@ -190,22 +190,22 @@ def get_xauthority_path(display_name: str) -> str:
return os.path.join(d, filename)


def start_Xvfb(xvfb_str: str, vfb_geom, pixel_depth: int, display_name: str, cwd,
def start_Xvfb(xvfb_cmd: list[str], vfb_geom, pixel_depth: int, display_name: str, cwd,
uid: int, gid: int, username: str, uinput_uuid="") -> tuple[Popen, str]:
if not POSIX:
raise InitException(f"starting an Xvfb is not supported on {os.name}")
if OSX:
raise InitException("starting an Xvfb is not supported on MacOS")
if not xvfb_str:
if not xvfb_cmd:
raise InitException("the 'xvfb' command is not defined")

log = get_vfb_logger()
log("start_Xvfb%s XVFB_EXTRA_ARGS=%s",
(xvfb_str, vfb_geom, pixel_depth, display_name, cwd, uid, gid, username, uinput_uuid),
(xvfb_cmd, vfb_geom, pixel_depth, display_name, cwd, uid, gid, username, uinput_uuid),
XVFB_EXTRA_ARGS)
use_display_fd = display_name[0] == 'S'
if XVFB_EXTRA_ARGS:
xvfb_str += " "+XVFB_EXTRA_ARGS
xvfb_cmd += shlex.split(XVFB_EXTRA_ARGS)

subs: dict[str, str] = {}

Expand All @@ -222,9 +222,6 @@ def pathexpand(s: str) -> str:

# identify logfile argument if it exists,
# as we may have to rename it, or create the directory for it:
xvfb_cmd = shlex.split(xvfb_str)
if not xvfb_cmd:
raise InitException("cannot start Xvfb, the command definition is missing!")
# make sure all path values are expanded:
xvfb_cmd = [pathexpand(s) for s in xvfb_cmd]

Expand Down Expand Up @@ -295,9 +292,14 @@ def pathexpand(s: str) -> str:
if (xvfb_executable.endswith("Xorg") or xvfb_executable.endswith("Xdummy")) and pixel_depth > 0:
xvfb_cmd.append("-depth")
xvfb_cmd.append(str(pixel_depth))
env = get_exec_env(keep=("SHELL", "HOSTNAME", "XMODIFIERS",
"PWD", "HOME", "USERNAME", "LANG", "TERM", "USER",
"XDG_RUNTIME_DIR", "XDG_DATA_DIR", "PATH"))
keep = [
"SHELL", "HOSTNAME", "XMODIFIERS",
"PWD", "HOME", "USERNAME", "LANG", "TERM", "USER",
"XDG_RUNTIME_DIR", "XDG_DATA_DIR", "PATH",
]
env = get_exec_env(keep=keep)
if xvfb_executable.endswith("Xephyr"):
env["DISPLAY"] = get_saved_env_var("DISPLAY")
log(f"xvfb env={env}")
xvfb = None
try:
Expand Down Expand Up @@ -492,7 +494,7 @@ def preexec():
log.estr(e)


def check_xvfb_process(xvfb=None, cmd: str = "Xvfb", timeout: int = 0, command=None) -> bool:
def check_xvfb_process(xvfb=None, cmd: str = "Xvfb", timeout: int = 0, command=()) -> bool:
if xvfb is None:
# we don't have a process to check
return True
Expand All @@ -503,9 +505,9 @@ def check_xvfb_process(xvfb=None, cmd: str = "Xvfb", timeout: int = 0, command=N
log.error("")
log.error("%s command has terminated! xpra cannot continue", cmd)
log.error(" if the display is already running, try a different one,")
log.error(" or use the --use-display flag")
log.error(" if the `xvfb` command is invalid, try a different one")
if command:
log.error(" full command: %s", command)
log.error(" full command: %r", shlex.join(command))
log.error("")
return False

Expand Down

0 comments on commit 7b03c94

Please sign in to comment.