Skip to content

Commit

Permalink
#3964 support mode options
Browse files Browse the repository at this point in the history
ie: xpra shadow,backend=x11
  • Loading branch information
totaam committed Mar 24, 2024
1 parent df9b49e commit 65574e4
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 48 deletions.
6 changes: 3 additions & 3 deletions xpra/gtk/configure/shadow.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
"this option should always find a suitable capture strategy",
"and it may choose not to use a video stream",
),
"xshm": (
"X11 shared memory capture",
"copies the X11 server's pixel data using XShm,",
"x11": (
"X11 screen capture",
"copies the X11 server's pixel data, preferably using XShm,",
"this option only requires the X11 bindings",
"Wayland displays with XWayland will look blank"
),
Expand Down
4 changes: 2 additions & 2 deletions xpra/platform/darwin/shadow_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ def take_screenshot(self) -> tuple[int, int, str, int, bytes]:

class ShadowServer(GTKShadowServerBase):

def __init__(self, display=None, multi_window=True):
super().__init__(multi_window)
def __init__(self, display: str, attrs: dict[str, str]):
super().__init__(attrs)
# sanity check:
check_display()
image = CG.CGWindowListCreateImage(CG.CGRectInfinite,
Expand Down
6 changes: 3 additions & 3 deletions xpra/platform/posix/fd_portal_shadow.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ def __init__(self, root_window, capture, title: str, geometry, node_id: int, pro


class PortalShadow(GTKShadowServerBase):
def __init__(self, multi_window=True):
def __init__(self, attrs: dict[str, str]):
# we're not using X11, so no need for this check:
os.environ["XPRA_UI_THREAD_CHECK"] = "0"
os.environ["XPRA_NOX11"] = "1"
GTKShadowServerBase.__init__(self, multi_window=multi_window)
GTKShadowServerBase.__init__(self, attrs)
self.session = None
self.session_type = "portal desktop"
self.session_path: str = ""
Expand All @@ -60,7 +60,7 @@ def __init__(self, multi_window=True):
self.capture: Capture | None = None
self.portal_interface = get_portal_interface()
self.input_devices = 0
log(f"PortalShadow({multi_window}) portal_interface={self.portal_interface}")
log(f"PortalShadow({attrs}) portal_interface={self.portal_interface}")

def get_server_mode(self) -> str:
return "portal shadow"
Expand Down
36 changes: 21 additions & 15 deletions xpra/platform/posix/shadow_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def load_wayland(display: str = "") -> type | None:
return c


def load_xshm(display: str = "") -> type | None:
def load_x11(display: str = "") -> type | None:
gdkb = os.environ.get("GDK_BACKEND")
try:
os.environ["GDK_BACKEND"] = "x11"
Expand All @@ -70,26 +70,32 @@ def load_xshm(display: str = "") -> type | None:
return None


load_nvfbc = load_x11


def load_auto(display: str = "") -> type | None:
c: type | None = None
if display.startswith("wayland-") or os.path.isabs(display):
c = load_wayland(display)
elif display.startswith(":"):
c = load_xshm(display)
return c or load_remotedesktop(display) or load_screencast(display) or load_xshm(display)
c = load_x11(display)
return c or load_remotedesktop(display) or load_screencast(display) or load_x11(display)


def ShadowServer(display: str = "", multi_window: bool = True):
env_setting = os.environ.get("XPRA_SHADOW_BACKEND", "auto").lower()
if env_setting not in SHADOW_OPTIONS:
raise ValueError(f"invalid 'XPRA_SHADOW_BACKEND' {env_setting!r}, use: {SHADOW_OPTIONS}")
load_fn = globals().get(f"load_{env_setting}")
shadow_server = load_fn()
if not shadow_server:
raise RuntimeError(f"shadow backend {env_setting} is not available")
def ShadowServer(display: str, attrs: dict[str, str]):
log = Logger("server", "shadow")
log(f"ShadowServer({display}, {multi_window}) {env_setting}={shadow_server}")
return shadow_server(multi_window)
setting = (attrs.get("backend", os.environ.get("XPRA_SHADOW_BACKEND", "auto"))).lower()
log(f"ShadowServer({display}, {attrs}) {setting=}")
if setting not in SHADOW_OPTIONS:
raise ValueError(f"invalid shadow backend {setting!r}, use: {SHADOW_OPTIONS.keys()}")
load_fn = globals().get(f"load_{setting}")
if not load_fn:
raise RuntimeError(f"missing shadow loader for {setting!r}")
shadow_server = load_fn(display)
if not shadow_server:
raise RuntimeError(f"shadow backend {setting} is not available")
log(f"ShadowServer({display}, {attrs}) {setting}={shadow_server}")
return shadow_server(attrs)


def check_nvfbc() -> bool:
Expand All @@ -112,7 +118,7 @@ def check_pipewire() -> bool:
return has_plugins("pipewiresrc")


def check_xshm() -> bool:
def check_x11() -> bool:
from xpra.x11.bindings.ximage import XImageBindings # pylint: disable=import-outside-toplevel
assert XImageBindings
return True
Expand All @@ -127,6 +133,6 @@ def nocheck() -> bool:
"nvfbc": check_nvfbc,
"gstreamer": check_gstreamer,
"pipewire": check_pipewire,
"xshm": check_xshm,
"x11": check_x11,
"gtk": nocheck,
}
4 changes: 2 additions & 2 deletions xpra/platform/win32/shadow_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ def __repr__(self):

class ShadowServer(GTKShadowServerBase):

def __init__(self, display=None, multi_window=True):
super().__init__(multi_window)
def __init__(self, display, attrs: dict[str, str]):
super().__init__(attrs)
self.pixel_depth = 32
self.cursor_handle = None
self.cursor_data = None
Expand Down
24 changes: 14 additions & 10 deletions xpra/scripts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,14 +418,16 @@ def verify_gir():
raise InitExit(ExitCode.FAILURE, f"the python gobject introspection bindings are missing: \n{e}")


def run_mode(script_file: str, cmdline, error_cb, options, args, mode: str, defaults) -> ExitValue:
def run_mode(script_file: str, cmdline, error_cb, options, args, full_mode: str, defaults) -> ExitValue:
mode_parts = full_mode.split(",")
mode = MODE_ALIAS.get(mode_parts[0], mode_parts[0])

# configure default logging handler:
if POSIX and getuid() == options.uid == 0 and mode not in (
"proxy", "autostart", "showconfig",
) and not NO_ROOT_WARNING:
warn("\nWarning: running as root\n")

mode = MODE_ALIAS.get(mode, mode)
display_is_remote = isdisplaytype(args, "ssh", "tcp", "ssl", "vsock", "quic")
if mode.startswith("shadow") and WIN32 and not envbool("XPRA_PAEXEC_WRAP", False):
# are we started from a non-interactive context?
Expand Down Expand Up @@ -511,7 +513,7 @@ def run_mode(script_file: str, cmdline, error_cb, options, args, mode: str, defa
) or mode.startswith("upgrade") or mode.startswith("request-"):
options.encodings = validated_encodings(options.encodings)
try:
return do_run_mode(script_file, cmdline, error_cb, options, args, mode, defaults)
return do_run_mode(script_file, cmdline, error_cb, options, args, full_mode, defaults)
except KeyboardInterrupt as e:
info(f"\ncaught {e!r}, exiting")
return 128 + signal.SIGINT
Expand All @@ -534,8 +536,9 @@ def DotXpra(*args, **kwargs):
return dotxpra.DotXpra(*args, **kwargs)


def do_run_mode(script_file: str, cmdline, error_cb, options, args, mode: str, defaults) -> ExitValue:
mode = MODE_ALIAS.get(mode, mode)
def do_run_mode(script_file: str, cmdline, error_cb, options, args, full_mode: str, defaults) -> ExitValue:
mode_parts = full_mode.split(",", 1)
mode = MODE_ALIAS.get(mode_parts[0], mode_parts[0])
display_is_remote = isdisplaytype(args, "ssh", "tcp", "ssl", "vsock", "quic")
if args and mode in ("seamless", "desktop", "monitor"):
# all args that aren't specifying a connection will be interpreted as a start-child command:
Expand Down Expand Up @@ -593,7 +596,7 @@ def do_run_mode(script_file: str, cmdline, error_cb, options, args, mode: str, d
"upgrade", "upgrade-seamless", "upgrade-desktop",
"proxy",
):
return run_server(script_file, cmdline, error_cb, options, args, mode, defaults)
return run_server(script_file, cmdline, error_cb, options, args, full_mode, defaults)
if mode in (
"attach", "listen", "detach",
"screenshot", "version", "info", "id",
Expand Down Expand Up @@ -2014,8 +2017,9 @@ def strip_defaults_start_child(start_child, defaults_start_child):
return start_child


def run_server(script_file, cmdline, error_cb, options, args, mode: str, defaults) -> ExitValue:
mode = MODE_ALIAS.get(mode, mode)
def run_server(script_file, cmdline, error_cb, options, args, full_mode: str, defaults) -> ExitValue:
mode_parts = full_mode.split(",", 1)
mode = MODE_ALIAS.get(mode_parts[0], mode_parts[0])
if mode in (
"seamless", "desktop", "monitor", "expand",
"upgrade", "upgrade-seamless", "upgrade-desktop", "upgrade-monitor",
Expand Down Expand Up @@ -2068,7 +2072,7 @@ def run_server(script_file, cmdline, error_cb, options, args, mode: str, default
if scaling == (1, 1):
options.resize_display = f"{root_w}x{root_h}"

r = start_server_via_proxy(script_file, cmdline, error_cb, options, args, mode)
r = start_server_via_proxy(script_file, cmdline, error_cb, options, args, full_mode)
if isinstance(r, int):
return r

Expand All @@ -2079,7 +2083,7 @@ def run_server(script_file, cmdline, error_cb, options, args, mode: str, default
except ImportError:
error_cb("`xpra-server` is not installed")
sys.exit(1)
return do_run_server(script_file, cmdline, error_cb, options, args, mode, str(display or ""), defaults)
return do_run_server(script_file, cmdline, error_cb, options, args, full_mode, str(display or ""), defaults)


def start_server_via_proxy(script_file: str, cmdline, error_cb, options, args, mode: str) -> int | ExitCode | None:
Expand Down
24 changes: 17 additions & 7 deletions xpra/scripts/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

from xpra import __version__
from xpra.util.io import info, warn
from xpra.util.parsing import parse_str_dict
from xpra.scripts.parsing import fixup_defaults, MODE_ALIAS
from xpra.scripts.main import (
no_gtk, bypass_no_gtk, nox,
validate_encryption, parse_env, configure_env,
Expand Down Expand Up @@ -335,9 +337,9 @@ def make_seamless_server(clobber):
return SeamlessServer(clobber)


def make_shadow_server(display, multi_window=False):
def make_shadow_server(display, attrs: dict[str, str]):
from xpra.platform.shadow_server import ShadowServer
return ShadowServer(display, multi_window)
return ShadowServer(display, attrs)


def make_proxy_server():
Expand Down Expand Up @@ -549,7 +551,6 @@ def clean_session_path(path) -> None:


def get_options_file_contents(opts, mode: str = "seamless") -> str:
from xpra.scripts.parsing import fixup_defaults
defaults = make_defaults_struct()
fixup_defaults(defaults)
fixup_options(defaults)
Expand Down Expand Up @@ -718,7 +719,9 @@ def request_exit(uri: str) -> bool:
return p.poll() in (ExitCode.OK, ExitCode.UPGRADE)


def do_run_server(script_file: str, cmdline, error_cb, opts, extra_args, mode: str, display_name: str, defaults):
def do_run_server(script_file: str, cmdline, error_cb, opts, extra_args, full_mode: str, display_name: str, defaults):
mode_parts = full_mode.split(",", 1)
mode = MODE_ALIAS.get(mode_parts[0], mode_parts[0])
assert mode in (
"seamless", "desktop", "monitor", "expand", "shadow", "shadow-screen",
"upgrade", "upgrade-seamless", "upgrade-desktop", "upgrade-monitor",
Expand Down Expand Up @@ -772,16 +775,21 @@ def noprogressshown(*_args):
progress(10, "initializing environment")
try:
return _do_run_server(script_file, cmdline,
error_cb, opts, extra_args, mode, display_name, defaults,
error_cb, opts, extra_args, full_mode, display_name, defaults,
splash_process, progress)
except Exception as e:
progress(100, f"error: {e}")
raise


def _do_run_server(script_file: str, cmdline,
error_cb, opts, extra_args, mode: str, display_name: str, defaults,
error_cb, opts, extra_args, full_mode: str, display_name: str, defaults,
splash_process, progress):
mode_parts = full_mode.split(",", 1)
mode = MODE_ALIAS.get(mode_parts[0], mode_parts[0])
mode_attrs: dict[str, str] = {}
if len(mode_parts) > 1:
mode_attrs = parse_str_dict(mode_parts[1])
desktop_display = nox()
try:
cwd = os.getcwd()
Expand Down Expand Up @@ -1511,7 +1519,9 @@ def _get_int(prop):

progress(80, "initializing server")
if shadowing:
app = make_shadow_server(display_name, multi_window=mode == "shadow")
# "shadow" -> multi-window=True, "shadow-screen" -> multi-window=False
mode_attrs["multi-window"] = str(mode == "shadow")
app = make_shadow_server(display_name, mode_attrs)
elif proxying:
app = make_proxy_server()
elif expanding:
Expand Down
4 changes: 2 additions & 2 deletions xpra/server/shadow/gtk_shadow_server_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ def parse_geometries(s) -> list:

class GTKShadowServerBase(ShadowServerBase, GTKServerBase):

def __init__(self, multi_window=True):
def __init__(self, attrs: dict[str, str]):
ShadowServerBase.__init__(self, get_default_root_window())
GTKServerBase.__init__(self)
self.session_type = "shadow"
self.multi_window = multi_window
self.multi_window = parse_bool("multi-window", attrs.get("multi-window", True), True)
# for managing the systray
self.tray_menu = None
self.tray_menu_shown = False
Expand Down
15 changes: 13 additions & 2 deletions xpra/util/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,13 @@ def parse_item(v) -> float:
return sx, sy


def parse_simple_dict(s: str = "", sep: str = ",") -> dict[str, str | list[str] | dict[str, str]]:
def parse_simple_dict(s: str, sep: str = ",") -> dict[str, str | list[str] | dict[str, str]]:
# parse the options string and add the pairs:
d: dict[str, str | list[str]] = {}
for el in s.split(sep):
if not el:
continue
if el.startswith("#") and el.find("=") < 0:
if el.startswith("#") or el.find("=") < 0:
continue
try:
k, v = el.split("=", 1)
Expand Down Expand Up @@ -185,6 +185,17 @@ def may_add() -> str | list[str] | dict[str, str]:
return d


def parse_str_dict(s: str, sep: str = ",") -> dict[str, str]:
# parse the options string and add the pairs:
d: dict[str, str] = {}
for el in s.split(sep):
if not el or el.find("=") < 0:
continue
k, v = el.split("=", 1)
d[k.strip()] = v.strip()
return d


def parse_scaling_value(v) -> tuple[int, int] | None:
if not v:
return None
Expand Down
4 changes: 2 additions & 2 deletions xpra/x11/server/shadow.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,8 @@ def __repr__(self) -> str:
# so many calls will happen twice there (__init__ and init)
class ShadowX11Server(GTKShadowServerBase, X11ServerCore):

def __init__(self, multi_window: bool = True):
GTKShadowServerBase.__init__(self, multi_window=multi_window)
def __init__(self, attrs: dict[str, str]):
GTKShadowServerBase.__init__(self, attrs)
X11ServerCore.__init__(self)
self.session_type = "X11"
self.modify_keymap = False
Expand Down

0 comments on commit 65574e4

Please sign in to comment.