diff --git a/xpra/scripts/main.py b/xpra/scripts/main.py index 46afddb80a..6391709630 100755 --- a/xpra/scripts/main.py +++ b/xpra/scripts/main.py @@ -216,6 +216,7 @@ def configure_logging(options, mode) -> None: if mode in ( "seamless", "desktop", "monitor", "expand", "shadow", "shadow-screen", + "encoder", "recover", "attach", "listen", "proxy", "version", "info", "id", @@ -453,6 +454,7 @@ def run_mode(script_file: str, cmdline, error_cb, options, args, full_mode: str, if mode in ( "seamless", "desktop", "shadow", "shadow-screen", "expand", "upgrade", "upgrade-seamless", "upgrade-desktop", + "encoder", ) and not display_is_remote and options.daemon and use_systemd_run(options.systemd_run): # make sure we run via the same interpreter, # inject it into the command line if we have to: @@ -522,6 +524,7 @@ def run_mode(script_file: str, cmdline, error_cb, options, args, full_mode: str, if mode in ( "seamless", "desktop", "shadow", "shadow-screen", "expand", "recover", + "encoder", ) or mode.startswith("upgrade") or mode.startswith("request-"): options.encodings = validated_encodings(options.encodings) try: @@ -615,6 +618,7 @@ def do_run_mode(script_file: str, cmdline, error_cb, options, args, full_mode: s "seamless", "desktop", "monitor", "expand", "shadow", "shadow-screen", "upgrade", "upgrade-seamless", "upgrade-desktop", "proxy", + "encoder", ): return run_server(script_file, cmdline, error_cb, options, args, full_mode, defaults) if mode in ( @@ -2302,6 +2306,7 @@ def run_server(script_file, cmdline, error_cb, options, args, full_mode: str, de if mode in ( "seamless", "desktop", "monitor", "expand", "upgrade", "upgrade-seamless", "upgrade-desktop", "upgrade-monitor", + "encoder", ): if OSX or WIN32: raise InitException(f"{mode} is not supported on this platform") @@ -2338,9 +2343,10 @@ def run_server(script_file, cmdline, error_cb, options, args, full_mode: str, de scaled_h = round(root_h / scaling_y) options.resize_display = f"{scaled_w}x{scaled_h}" - r = start_server_via_proxy(cmdline, error_cb, options, args, full_mode) - if isinstance(r, int): - return r + if mode != "encoder": + r = start_server_via_proxy(cmdline, error_cb, options, args, full_mode) + if isinstance(r, int): + return r try: from xpra import server diff --git a/xpra/scripts/server.py b/xpra/scripts/server.py index 75c71a593f..bc7405db18 100644 --- a/xpra/scripts/server.py +++ b/xpra/scripts/server.py @@ -394,6 +394,11 @@ def make_expand_server(attrs: dict[str, str]): return ExpandServer(attrs) +def make_encoder_server(): + from xpra.server.encoder_server import EncoderServer + return EncoderServer() + + def verify_display(xvfb=None, display_name=None, shadowing=False, log_errors=True, timeout=None) -> bool: # check that we can access the X11 display: from xpra.log import Logger @@ -629,6 +634,7 @@ def do_run_server(script_file: str, cmdline, error_cb, opts, extra_args, full_mo "seamless", "desktop", "monitor", "expand", "shadow", "shadow-screen", "upgrade", "upgrade-seamless", "upgrade-desktop", "upgrade-monitor", "proxy", + "encoder", ) validate_encryption(opts) if opts.encoding == "help" or "help" in opts.encodings: @@ -694,6 +700,7 @@ def _do_run_server(script_file: str, cmdline, upgrading = mode.startswith("upgrade") shadowing = mode.startswith("shadow") proxying = mode == "proxy" + encoder = mode == "encoder" use_display = parse_bool_or("use-display", opts.use_display) if shadowing or expanding: use_display = True @@ -751,7 +758,7 @@ def _do_run_server(script_file: str, cmdline, if not shadowing and not upgrading and not use_display: display_name_check(display_name) else: - if proxying: + if proxying or encoder: # find a free display number: dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs) all_displays = dotxpra.sockets() @@ -796,7 +803,7 @@ def _do_run_server(script_file: str, cmdline, else: warn(f"server for {display_name} is not exiting") - if not (shadowing or proxying or upgrading) and opts.exit_with_children and not has_child_arg: + if not (shadowing or proxying or upgrading or encoder) and opts.exit_with_children and not has_child_arg: error_cb("--exit-with-children specified without any children to spawn; exiting immediately") # Generate the script text now, because os.getcwd() will @@ -864,7 +871,7 @@ def write_session_file(filename: str, contents) -> str: del e clobber = int(upgrading) * CLOBBER_UPGRADE | int(use_display or 0) * CLOBBER_USE_DISPLAY - start_vfb: bool = not (shadowing or proxying or clobber or expanding) + start_vfb: bool = not (shadowing or proxying or clobber or expanding or encoder) xauth_data: str = get_hex_uuid() if start_vfb else "" # if pam is present, try to create a new session: @@ -929,7 +936,9 @@ 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) + xvfb_cmd: list[str] = [] + if start_vfb: + xvfb_cmd = xvfb_command(opts.xvfb, opts.pixel_depth, opts.dpi) sanitize_env() if not shadowing: @@ -1052,7 +1061,9 @@ def write_session_file(filename: str, contents) -> str: commands += list(getattr(opts, start_prop.replace("-", "_"))) if not commands: opts.start.append("xpra desktop-greeter") - if POSIX and configure_imsettings_env(opts.input_method) == "ibus" and not (upgrading or shadowing or proxying): + if POSIX and not ( + upgrading or shadowing or proxying or encoder + ) and configure_imsettings_env(opts.input_method) == "ibus": # start ibus-daemon unless already specified in 'start': if IBUS_DAEMON_COMMAND and not (any(x.find("ibus-daemon") >= 0 for x in opts.start) or any( x.find("ibus-daemon") >= 0 for x in opts.start_late) @@ -1129,7 +1140,7 @@ def write_session_file(filename: str, contents) -> str: log(f"using XAUTHORITY file {xauthority!r}") os.environ["XAUTHORITY"] = xauthority # resolve use-display='auto': - if (use_display is None or upgrading) and not proxying: + if (use_display is None or upgrading) and not proxying and not encoder: # figure out if we have to start the vfb or not # bail out if we need a display that is not running if not display_name: @@ -1171,7 +1182,7 @@ def write_session_file(filename: str, contents) -> str: if POSIX and not OSX: from xpra.server.util import has_uinput, UINPUT_UUID_LEN uinput_uuid = "" - use_uinput = not (shadowing or proxying) and opts.input_devices.lower() in ("uinput", "auto") and has_uinput() + use_uinput = not (shadowing or proxying or encoder) and opts.input_devices.lower() in ("uinput", "auto") and has_uinput() if start_vfb: progress(40, "starting a virtual display") from xpra.x11.vfb_util import start_Xvfb, parse_resolutions, xauth_add @@ -1335,7 +1346,7 @@ def check_xvfb(timeout=0) -> bool: if dbus_env: os.environ.update(dbus_env) - if SSH_AGENT_DISPATCH and not (shadowing or proxying) and opts.ssh.lower() not in FALSE_OPTIONS: + if SSH_AGENT_DISPATCH and not (shadowing or proxying or encoder) and opts.ssh.lower() not in FALSE_OPTIONS: progress(50, "setup ssh agent forwarding") try: from xpra.net.ssh.agent import setup_ssh_auth_sock @@ -1346,7 +1357,7 @@ def check_xvfb(timeout=0) -> bool: log.error("Error setting up ssh agent forwarding", exc_info=True) progress(50, f"error setting up ssh agent forwarding: {e}") - if not proxying: + if not (proxying or encoder): if POSIX and not OSX: no_gtk() if starting or starting_desktop or starting_monitor: @@ -1389,7 +1400,7 @@ def init_local_sockets() -> None: if envbool("XPRA_ENFORCE_FEATURES", True): enforce_server_features() - if not (proxying or shadowing) and POSIX and not OSX: + if not (proxying or shadowing or encoder) and POSIX and not OSX: if not check_xvfb(): return 1 from xpra.x11.gtk.display_source import init_gdk_display_source @@ -1422,6 +1433,8 @@ def _get_int(prop) -> int: app = make_proxy_server() elif expanding: app = make_expand_server(mode_attrs) + elif encoder: + app = make_encoder_server() else: if starting or upgrading_seamless: app = make_seamless_server(clobber) @@ -1456,7 +1469,7 @@ def server_not_started(msg="server not started"): init_local_sockets() app.init_sockets(sockets) app.init_dbus(dbus_pid, dbus_env) - if not shadowing and not proxying and not expanding: + if not (shadowing or proxying or expanding or encoder): app.init_display_pid(xvfb_pid) app.save_pid() app.original_desktop_display = desktop_display diff --git a/xpra/server/core.py b/xpra/server/core.py index 966e95790b..acd9ef35d1 100644 --- a/xpra/server/core.py +++ b/xpra/server/core.py @@ -250,7 +250,7 @@ def __init__(self): self._default_packet_handlers: dict[str, Callable] = {} def get_server_mode(self) -> str: - return "core" + return self.session_type def init(self, opts) -> None: log("ServerCore.init(%s)", opts) diff --git a/xpra/server/encoder_server.py b/xpra/server/encoder_server.py new file mode 100644 index 0000000000..df126c9182 --- /dev/null +++ b/xpra/server/encoder_server.py @@ -0,0 +1,66 @@ +# This file is part of Xpra. +# Copyright (C) 2025 Antoine Martin +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +from typing import Any +from collections.abc import Callable + +from xpra.os_util import gi_import +from xpra.gtk.signals import register_os_signals, register_SIGUSR_signals +from xpra.server.mixins.encoding import EncodingServer +from xpra.server.core import ServerCore +from xpra.log import Logger + +GLib = gi_import("GLib") + +log = Logger("server") + + +class EncoderServer(ServerCore, EncodingServer): + + def __init__(self): + log("EncoderServer.__init__()") + super().__init__() + self.session_type = "encoder" + self.loop = GLib.MainLoop() + + def init(self, opts): + opts.start_new_commands = False + ServerCore.init(self, opts) + EncodingServer.init(self, opts) + + def threaded_init(self) -> None: + EncoderServer.threaded_setup(self) + + def install_signal_handlers(self, callback: Callable[[int], None]) -> None: + sstr = self.get_server_mode() + " server" + register_os_signals(callback, sstr) + register_SIGUSR_signals(sstr) + + def do_run(self) -> None: + log("do_run() calling %s", self.loop.run) + self.loop.run() + log("do_run() end of %()", self.loop.run) + + def do_quit(self) -> None: + log("do_quit: calling loop.quit()") + self.loop.quit() + # from now on, we can't rely on the main loop: + from xpra.util.system import register_SIGUSR_signals + register_SIGUSR_signals() + + def get_info(self, proto) -> dict[str, Any]: + info = ServerCore.get_info(self, proto) + info.update(EncodingServer.get_info(self, proto)) + return info + + def cleanup(self): + EncodingServer.cleanup(self) + ServerCore.cleanup(self) + + def make_hello(self, source) -> dict[str, Any]: + capabilities = super().make_hello(source) + if "features" in source.wants: + capabilities.update(EncoderServer.get_server_features(source)) + return capabilities