Selkies
Developer Referencewebrtc_mode

WebRTCService

The WebRTC streaming service run under the centralized stream server.

Owns the whole WebRTC data path for a session: signaling, peer management, one media pipeline per display, input handling, monitoring, and the congestion-control/pacer loop. Mirrors the websockets service's policies (per-display settings, extended-desktop layout, capture pause rules) so the two transports stay in behavioral parity.

Attributes

attributesettingsOptional[AppSettings]
= settings
attributetasksList[asyncio.Task]
= []
attributeshutdown_event
= asyncio.Event()
attribute_shutdown_called
= False
attributesignaling_clientOptional[WebRTCSignalingClient]
= None
attributemedia_pipelineOptional[MediaPipelinePixel]
= None

The primary display's pipeline (also stored under the "primary" key of display_pipelines).

attributertc_appOptional[RTCApp]
= None
attributeinput_handlerOptional[WebRTCInput]
= None
attributesystem_monitorOptional[SystemMonitor]
= None
attributegpu_monitorOptional[GPUMonitor]
= None
attributemetricsOptional[Metrics]
= None
attributepeer_id
= 1
attributeargsOptional[SimpleNamespace]
= None

Mutable per-session snapshot of the client-tunable settings (seeded from SETTING_DEFINITIONS); the primary display's authority, and the seed for joining secondaries.

attributemonitoring_utils_usedDict[str, bool]
= {}
attributemon_hmac_turnOptional[HMACRTCMonitor]
= None
attributemon_rest_apiOptional[RESTRTCMonitor]
= None
attributemon_rtc_config_fileOptional[RTCConfigFileMonitor]
= None
attributemon_cloudflare_turnOptional[CloudflareRTCMonitor]
= None
attributepeer_managerOptional[WebRTCPeerManagement]
= None
attributesupervisor
= supervisor
attributedisplay_clientsDict[str, Dict[str, Any]]
= {}

Per-secondary-display registration state — requested dimensions, position, and the display's own copies of the client-tunable video settings.

attributedisplay_layoutsDict[str, Dict[str, int]]
= {}

The computed extended-desktop layout the input handler offsets coordinates against.

attributedisplay_pipelinesDict[str, MediaPipelinePixel]
= {}

One MediaPipelinePixel per connected display id.

attribute_last_idr_request_timesDict[str, float]
= {}
attribute_display_lock
= asyncio.Lock()
attribute_primary_dimsOptional[Tuple[int, int]]
= None

The primary's last layout-path resolution request, or None while the pipeline dimensions are its authority.

attribute_wayland_ctl_moduleOptional[Any]
= None

Fallback pixelflux handle for Wayland output management when the primary has no live capture module (any handle reaches the shared compositor backend).

attribute_host_output_capacityOptional[int]
= None

Host-capture mode: outputs the host compositor can back displays with; None until a query answers, never set when self-compositing (outputs are minted on demand there).

attribute_last_resize_requestOptional[Tuple[int, int]]
= None

Last (w, h) a client asked the primary to become; the realized size may differ (CVT cell alignment), so idempotence is judged against the request too.

attribute_wm_swap
= MultiMonitorWindowManager()

Swaps heavy DEs, which tile poorly across the per-display regions, for a minimal Openbox once a secondary joins.

attributeRECONNECT_GRACE_S
= 3.0

Seconds the primary capture outlives its last consumer, so a controller tab reload (drop and re-add within a second or two) reuses the warm capture while viewers and the secondary display stream through.

attribute_primary_stop_grace_taskOptional[asyncio.Task]
= None

The pending deferred primary-capture stop.

attribute_mic_controlOptional[AudioControl]
= None

Sound-server control connection for the shared SelkiesVirtualMic, provisioned once on the first mic packet (the data plane is per-peer pcmflux playback into the input sink).

attribute_mic_module_indexOptional[int]
= None

Loaded virtual-source module, reused when the websockets path already loaded it.

attribute_mic_module_owned
= False

Whether this path loaded the module and so unloads it on shutdown.

attribute_mic_provisioned
= False

Set once _mic_module_index is known.

attribute_mic_provision_lock
= asyncio.Lock()

Serializes concurrent first-packet provisioning across peers.

attribute_VIDEO_SETTING_APPLIERSDict[str, Callable[[MediaPipelinePixel, Any], Awaitable[Any]]]
= {'rate_control_mode': lambda p, v: p.update_rate_control_mode(RateControlMode(v)), 'video_crf': lambda p, v: p.set_crf(v), 'video_bitrate': lambda p, v: p.set_video_bitrate(v), 'framerate': lambda p, v: p.set_framerate(v), 'use_cpu': lambda p, v: p.set_use_cpu(bool(v)), 'encoder': lambda p, v: p.set_encoder(str(v)), 'video_fullcolor': lambda p, v: p.set_video_fullcolor(bool(v)), 'video_streaming_mode': lambda p, v: p.set_video_streaming_mode(bool(v)), 'use_paint_over_quality': lambda p, v: p.set_use_paint_over_quality(bool(v)), 'video_paintover_crf': lambda p, v: p.set_video_paintover_crf(int(v)), 'video_paintover_burst_frames': lambda p, v: p.set_video_paintover_burst_frames(int(v))}

Setting name to live per-pipeline setter; each display's pipeline owns its running values.

Functions

func__init__(self, supervisor) -> None
Source Code
def __init__(self, supervisor: CentralizedStreamServer) -> None:
    super().__init__("webrtc")
    self.settings: Optional[AppSettings] = settings
    self.tasks: List[asyncio.Task] = []
    self.shutdown_event = asyncio.Event()
    self._shutdown_called = False
    self.signaling_client: Optional[WebRTCSignalingClient] = None
    self.media_pipeline: Optional[MediaPipelinePixel] = None
    self.rtc_app: Optional[RTCApp] = None
    self.input_handler: Optional[WebRTCInput] = None
    self.system_monitor: Optional[SystemMonitor] = None
    self.gpu_monitor: Optional[GPUMonitor] = None
    self.metrics: Optional[Metrics] = None
    self.peer_id = 1
    self.args: Optional[SimpleNamespace] = None
    self.monitoring_utils_used: Dict[str, bool] = {}
    self.mon_hmac_turn: Optional[HMACRTCMonitor] = None
    self.mon_rest_api: Optional[RESTRTCMonitor] = None
    self.mon_rtc_config_file: Optional[RTCConfigFileMonitor] = None
    self.mon_cloudflare_turn: Optional[CloudflareRTCMonitor] = None
    self.peer_manager: Optional[WebRTCPeerManagement] = None
    self.supervisor = supervisor
    self.display_clients: Dict[str, Dict[str, Any]] = {}
    self.display_layouts: Dict[str, Dict[str, int]] = {}
    self.display_pipelines: Dict[str, MediaPipelinePixel] = {}
    self._last_idr_request_times: Dict[str, float] = {}
    self._display_lock = asyncio.Lock()
    self._primary_dims: Optional[Tuple[int, int]] = None
    self._wayland_ctl_module: Optional[Any] = None
    self._host_output_capacity: Optional[int] = None
    self._last_resize_request: Optional[Tuple[int, int]] = None
    self._wm_swap = MultiMonitorWindowManager()
    self.RECONNECT_GRACE_S = 3.0
    self._primary_stop_grace_task: Optional[asyncio.Task] = None

    self._mic_control: Optional[AudioControl] = None
    self._mic_module_index: Optional[int] = None
    self._mic_module_owned = False
    self._mic_provisioned = False
    self._mic_provision_lock = asyncio.Lock()

    self._init_default_settings()
paramself
paramsupervisorCentralizedStreamServer

Returns

None
func_init_default_settings(self) -> None

Seed self.args from SETTING_DEFINITIONS and derive the manual startup geometry.

Range settings pin to the single allowed value when the range is locked (min == max), otherwise take the definition's default; other types copy the configured value verbatim.

Source Code
def _init_default_settings(self) -> None:
    """Seed ``self.args`` from ``SETTING_DEFINITIONS`` and derive the manual
    startup geometry.

    Range settings pin to the single allowed value when the range is locked
    (min == max), otherwise take the definition's default; other types copy
    the configured value verbatim.
    """
    self.args = SimpleNamespace()
    try:
        for setting_def in SETTING_DEFINITIONS:
            name = setting_def["name"]
            stype = setting_def["type"]
            if stype == "bool":
                value = getattr(self.settings, name)[0]
            elif stype == "range":
                min, max = getattr(self.settings, name)
                value = (
                    min
                    if min == max
                    else setting_def.get("meta", {}).get("default_value", 0)
                )
            elif stype == "enum":
                value = getattr(self.settings, name)
            elif stype in ("int", "float", "str", "list"):
                value = getattr(self.settings, name)
            else:
                continue
            setattr(self.args, name, value)
    except Exception as e:
        logger.error(f"Error initializing default settings: {e}", exc_info=True)

    self._manual_dims: Optional[Tuple[int, int]] = None
    if getattr(self.args, "manual_resolution", False):
        width = int(getattr(self.args, "manual_width", 0) or 0)
        height = int(getattr(self.args, "manual_height", 0) or 0)
        if width > 0 and height > 0:
            self._manual_dims = (width - (width % 2), height - (height % 2))
paramself

Returns

None
funcinitialize_components(self) -> None

Build every component: metrics, signaling, the primary media pipeline, the RTC app, the input handler, and the monitors, then wire the peer manager with the fetched RTC configuration.

The settings are re-snapshotted first: the service is constructed once at boot, but a live transport switch lands here with the settings singleton already re-resolved for webrtc (encoder filter, rate-control default). Metrics backs both the Prometheus endpoint and the WebRTC CSV statistics, so it is built when either flag is on. A configured manual resolution is applied before the pipeline is sized: on X11 the screen is resized now and the pipeline takes what the X server realized (CVT cell alignment can widen the mode); on Wayland the dimensions are the resize, since the capture start sizes the compositor output from them, and the capture scale is seeded from the configured DPI so the first start honors it (handle_scaling updates it later). The interposer socket paths and the gamepad backend are process-wide state shared with the websockets service, so both transports read the same settings for them.

Source Code
async def initialize_components(self) -> None:
    """Build every component: metrics, signaling, the primary media
    pipeline, the RTC app, the input handler, and the monitors, then wire
    the peer manager with the fetched RTC configuration.

    The settings are re-snapshotted first: the service is constructed once
    at boot, but a live transport switch lands here with the settings
    singleton already re-resolved for webrtc (encoder filter, rate-control
    default). Metrics backs both the Prometheus endpoint and the WebRTC
    CSV statistics, so it is built when either flag is on. A configured
    manual resolution is applied before the pipeline is sized: on X11 the
    screen is resized now and the pipeline takes what the X server
    realized (CVT cell alignment can widen the mode); on Wayland the
    dimensions are the resize, since the capture start sizes the compositor
    output from them, and the capture scale is seeded from the configured
    DPI so the first start honors it (`handle_scaling` updates it later).
    The interposer socket paths and the gamepad backend are process-wide
    state shared with the websockets service, so both transports read the
    same settings for them.
    """

    self._init_default_settings()

    if self.args.enable_metrics_http or self.args.enable_webrtc_statistics:
        webrtc_csv = self.args.enable_webrtc_statistics
        self.metrics = Metrics(using_webrtc_csv=webrtc_csv)

    self.signaling_client = self.create_signaling_client()

    # Surround (>2ch) rides Chromium's multiopus codec; the offered codec set
    # must be swapped before any peer connection builds its capabilities.
    if int(self.args.audio_channels) > 2:
        configure_multiopus(int(self.args.audio_channels))

    self.media_pipeline = MediaPipelinePixel(
        async_event_loop=asyncio.get_running_loop(),
        encoder=self.args.encoder,
        framerate=int(self.args.framerate),
        # kbps, as consumed by pixelflux.
        video_bitrate=int(self.args.video_bitrate),
        # Enum with a wider server-side value_range: an operator override can
        # arrive as an arbitrary numeric string.
        audio_bitrate=int(float(self.args.audio_bitrate)),
        audio_channels=int(self.args.audio_channels),
        audio_enabled=self.args.audio_enabled,
        audio_device_name=self.args.audio_device_name,
        crf=int(self.args.video_crf),
        video_fullcolor=bool(self.args.video_fullcolor),
        use_cpu=bool(self.args.use_cpu),
        video_streaming_mode=bool(self.args.video_streaming_mode),
        use_paint_over_quality=bool(self.args.use_paint_over_quality),
        video_paintover_crf=int(self.args.video_paintover_crf),
        video_paintover_burst_frames=int(self.args.video_paintover_burst_frames),
    )
    if self._manual_dims:
        if not IS_WAYLAND:
            realized = await resize_display(f"{self._manual_dims[0]}x{self._manual_dims[1]}")
            if realized:
                self._manual_dims = realized
        self.media_pipeline.width, self.media_pipeline.height = self._manual_dims
    if self.args.enable_rate_control:
        self.media_pipeline.rc_mode = RateControlMode(self.args.rate_control_mode)
    else:
        # Rate control disabled runs CRF on both transports.
        self.media_pipeline.rc_mode = RateControlMode.CRF

    (
        stun_servers,
        turn_servers,
        rtc_config,
        self.monitoring_utils_used,
    ) = await get_rtc_configuration(self.args)
    self.rtc_app = RTCApp(
        async_event_loop=asyncio.get_running_loop(),
        encoder=self.args.encoder,
        stun_servers=stun_servers,
        turn_servers=turn_servers,
    )
    self.rtc_app.media_pipeline = self.media_pipeline
    self.rtc_app.provision_virtual_mic = self._provision_webrtc_virtual_mic
    self.display_pipelines["primary"] = self.media_pipeline

    self.input_handler = WebRTCInput(
        rtc_app=self.rtc_app,
        uinput_mouse_socket_path=getattr(self.args, "uinput_mouse_socket", "") or "",
        js_socket_path_prefix=getattr(self.args, "js_socket_path", "/tmp"),
        enable_clipboard=self.args.enable_clipboard,
        enable_binary_clipboard="true"
        if self.args.enable_binary_clipboard
        else "false",
        enable_cursors=self.args.enable_cursors,
        cursor_size=self.args.cursor_size,
        cursor_scale=1.0,
        cursor_debug=self.args.debug_cursors,
        upload_dir=self.args.file_manager_path,
        is_wayland=IS_WAYLAND,
        app_wayland_display=(getattr(self.args, "app_wayland_display", "")
                             or getattr(self.args, "wayland_host_display", "")),
        uinput_gamepad=getattr(self.args, "uinput_gamepad", "auto"),
        # Duck-typed layout source: send_x11_mouse offsets a secondary
        # display's coordinates by display_layouts[display_id].
        data_server_instance=self,
    )
    self.input_handler.initialize_upload_dir()
    if IS_WAYLAND:
        self.media_pipeline.scale = await self.input_handler.realize_wayland_dpi(
            getattr(settings, "scaling_dpi", "96") or 96)

    self.system_monitor = SystemMonitor()
    # Always enabled: gpu_stats reports nothing without a supported GPU/tool.
    # Keyed to the pipeline's render node so stats describe the encoding GPU.
    stats_gpu_id = parse_gpu_id(getattr(self.args, "gpu_id", ""))
    self.gpu_monitor = GPUMonitor(
        gpu_id=stats_gpu_id if (stats_gpu_id or 0) > 0 else 0,
        enabled=True,
        dri_node=getattr(self.args, "encode_dri", "") or "",
    )

    self.create_peer_manager(rtc_config)
paramself

Returns

None
funccreate_signaling_client(self) -> WebRTCSignalingClient

Create and configure signaling client.

Source Code
def create_signaling_client(self) -> WebRTCSignalingClient:
    """Create and configure signaling client."""
    using_https = self.args.enable_https
    using_basic_auth = self.args.enable_basic_auth
    ws_protocol = "wss:" if using_https else "ws:"

    prefix = self.settings.subfolder
    username = self.settings.basic_auth_user
    password = self.settings.basic_auth_password
    client = WebRTCSignalingClient(
        f"{ws_protocol}//127.0.0.1:{self.args.port}{prefix}/api/ws",
        enable_https=using_https,
        enable_basic_auth=using_basic_auth,
        basic_auth_user=username,
        basic_auth_password=password,
        server_token=getattr(self.settings, "master_token", None),
    )
    return client
paramself

Returns

selkies.webrtc_signaling.WebRTCSignalingClient
funchandle_signaling_error(self, error) -> None

Handle signaling errors.

Source Code
async def handle_signaling_error(self, error: Exception) -> None:
    """Handle signaling errors."""
    logger.error(f"Signaling client error: {error}. Closing the pipelines")
    await self.handle_signaling_disconnect()
paramself
paramerrorException

Returns

None
funchandle_signaling_disconnect(self) -> None

Tear down every RTC connection once the signaling link drops.

Source Code
async def handle_signaling_disconnect(self) -> None:
    """Tear down every RTC connection once the signaling link drops."""
    logger.info("Signaling disconnected, cleaning up all resources")
    try:
        await self.rtc_app.stop_all_rtc_connections()
    except Exception as e:
        logger.error(
            f"Error during signaling disconnect cleanup: {e}", exc_info=True
        )
paramself

Returns

None
funchandle_session_start(self, session_peer_id, client_type, client_token=None, display_id='primary', display_position='right') -> None

Start an RTC connection for a joining peer.

A secondary display's controller is gated on the effective second-screen availability first: the published setting can lag a host-side change, so the capacity is re-read and a refusal closes the peer's signaling socket with a fatal verdict (a bare return would leave the page on "Connecting..." forever).

Source Code
async def handle_session_start(
    self, session_peer_id: str, client_type: str, client_token: Optional[str] = None,
    display_id: str = "primary", display_position: str = "right",
) -> None:
    """Start an RTC connection for a joining peer.

    A secondary display's controller is gated on the effective second-screen
    availability first: the published setting can lag a host-side change, so
    the capacity is re-read and a refusal closes the peer's signaling socket
    with a fatal verdict (a bare return would leave the page on
    "Connecting..." forever).

    Args:
        session_peer_id: The signaling peer id of the joining client.
        client_type: "controller" or a viewer/shared role name.
        client_token: Optional per-client auth token (governs input role).
        display_id: The display this peer consumes ("primary" or a
            secondary id).
        display_position: Where a joining secondary sits relative to the
            primary ("right", "left", "up", "down").
    """
    logger.info(
        f"starting session for client peer id: {session_peer_id} of type: {client_type} (display '{display_id}')"
    )
    try:
        if display_id != "primary" and client_type == "controller":
            await self._refresh_second_screen_capacity()
            available, reason = self._second_screen_availability()
            if not available:
                logger.warning(
                    "Secondary display '%s' refused: %s", display_id, reason,
                )
                await self._close_peer_signaling_ws(
                    session_peer_id, 4000, reason.encode(),
                )
                return
            # Dimensions arrive through the client's first resize message.
            entry = self.display_clients.setdefault(display_id, {"width": 0, "height": 0})
            entry["position"] = display_position
            self._seed_display_settings(entry)
        await self.rtc_app.start_rtc_connection(session_peer_id, client_type, client_token, display_id)
        if self.args.enable_webrtc_statistics and self.metrics:
            await self.metrics.initialize_webrtc_csv_file(self.args.webrtc_statistics_dir)
        logger.info(f"started session for client peer id {session_peer_id}")
    except Exception as e:
        logger.error(
            f"Error starting session for client peer id {session_peer_id}: {e}",
            exc_info=True,
        )
        await self.rtc_app.stop_rtc_connection(session_peer_id, client_type)
paramself
paramsession_peer_idstr

The signaling peer id of the joining client.

paramclient_typestr

"controller" or a viewer/shared role name.

paramclient_tokenOptional[str]
= None

Optional per-client auth token (governs input role).

paramdisplay_idstr
= 'primary'

The display this peer consumes ("primary" or a secondary id).

paramdisplay_positionstr
= 'right'

Where a joining secondary sits relative to the primary ("right", "left", "up", "down").

Returns

None
funchandle_session_end(self, session_peer_id, client_type) -> None

Handle end of a session initiated by a client. Stops the RTC connection and media pipeline for the given session peer id.

Source Code
async def handle_session_end(self, session_peer_id: str, client_type: str) -> None:
    """Handle end of a session initiated by a client.
    Stops the RTC connection and media pipeline for the given session peer id.
    """
    try:
        if self.rtc_app:
            await self.rtc_app.stop_rtc_connection(session_peer_id, client_type)
        logger.info(
            f"session ended for client peer id {session_peer_id} of type {client_type}"
        )
    except Exception as e:
        logger.error(
            f"Error handling session end for {session_peer_id}: {e}", exc_info=True
        )
paramself
paramsession_peer_idstr
paramclient_typestr

Returns

None
funccreate_peer_manager(self, rtc_config) -> None

Build the signaling-side peer manager with the fetched RTC config and the TURN/STUN/sharing options the handshake needs.

Source Code
def create_peer_manager(self, rtc_config: Any) -> None:
    """Build the signaling-side peer manager with the fetched RTC config
    and the TURN/STUN/sharing options the handshake needs."""
    options = argparse.Namespace(
        keepalive_timeout=30,
        rtc_config_file=self.args.rtc_config_json,
        turn_shared_secret=self.args.turn_shared_secret,
        rtc_config=rtc_config,
        turn_host=self.args.turn_host,
        turn_port=self.args.turn_port,
        turn_protocol=self.args.turn_protocol,
        turn_tls=self.args.turn_tls,
        turn_auth_header_name=self.args.turn_rest_username_auth_header,
        stun_host=self.args.stun_host,
        stun_port=self.args.stun_port,
        enable_sharing=self.args.enable_sharing,
        enable_shared=self.args.enable_shared,
        enable_player2=self.args.enable_player2,
        enable_player3=self.args.enable_player3,
        enable_player4=self.args.enable_player4,
    )
    self.peer_manager = WebRTCPeerManagement(options)
    self.peer_manager.on_client_presence = self.supervisor.set_clients_present
paramself
paramrtc_configAny

Returns

None
funcsetup_callbacks(self) -> None

Wire signaling, RTC app, media pipeline, input handler and monitor callbacks to each other.

Cursors come from pixelflux on both backends (Wayland compositor / X11 XFixes monitor) and route through the input handler's transport callback, capped at its DPI-scaled cursor size. Offers resolve their codec and SDP munging per display, so displays can run different encoders and chroma formats and a live full-colour toggle reaches every later offer. DPI scaling is wired independently of enable_resize, which gates only the primary's dynamic resolution (in on_resize_handler): the websockets transport applies scaling through the SETTINGS payload regardless of the resize gate, and a secondary display's whole bring-up rides its resize message.

Source Code
def setup_callbacks(self) -> None:
    """Wire signaling, RTC app, media pipeline, input handler and monitor
    callbacks to each other.

    Cursors come from pixelflux on both backends (Wayland compositor / X11
    XFixes monitor) and route through the input handler's transport
    callback, capped at its DPI-scaled cursor size. Offers resolve their
    codec and SDP munging per display, so displays can run different
    encoders and chroma formats and a live full-colour toggle reaches
    every later offer. DPI scaling is wired independently of
    `enable_resize`, which gates only the primary's dynamic resolution
    (in `on_resize_handler`): the websockets transport applies scaling
    through the SETTINGS payload regardless of the resize gate, and a
    secondary display's whole bring-up rides its resize message.
    """
    if not self.rtc_app or not self.media_pipeline or not self.input_handler:
        return

    self.signaling_client.on_error = self.handle_signaling_error
    self.signaling_client.on_disconnect = self.handle_signaling_disconnect
    self.signaling_client.on_session_start = self.handle_session_start
    self.signaling_client.on_session_end = self.handle_session_end
    self.signaling_client.on_sdp = self.rtc_app.set_sdp
    self.signaling_client.on_ice = self.rtc_app.set_ice

    self.media_pipeline.produce_data = self.rtc_app.consume_data
    self.media_pipeline.on_pipeline_started = self.send_current_cursor

    self.rtc_app.request_idr_frame = self.request_idr_for_display
    self.rtc_app.start_display_media = self.start_display_media
    self.rtc_app.stop_display_media = self.stop_display_media
    self.rtc_app.on_sdp = self.signaling_client.send_sdp
    self.rtc_app.on_ice = self.signaling_client.send_ice
    self.rtc_app.on_data_open = self.handle_data_channel_open
    self.rtc_app.on_data_close = lambda: logger.info("Data channel closed")
    self.rtc_app.on_data_error = lambda e: logger.error(f"Data channel error: {e}")
    self.rtc_app.on_data_message = self.input_handler.on_message
    self.rtc_app.on_peer_gone = self.handle_peer_gone
    self.input_handler.on_request_keyframe = self.request_idr_for_display

    self.input_handler.on_cursor_change = lambda data: (
        self.rtc_app.send_cursor_data(data)
    )
    self.media_pipeline.on_cursor_data = lambda data: (
        self.input_handler.on_cursor_change(data)
    )
    self.media_pipeline.get_cursor_size_cap = lambda: getattr(
        self.input_handler, "cursor_size_cap", 0
    )
    self.input_handler.on_video_encoder_bit_rate = self.handle_video_bitrate_change
    self.input_handler.on_audio_encoder_bit_rate = self.handle_audio_bitrate_change
    self.input_handler.on_mouse_pointer_visible = self.handle_pointer_visible
    self.input_handler.on_clipboard_read = lambda d, t: (
        self.rtc_app.send_clipboard_data(d, t)
    )
    self.input_handler.on_set_fps = self.handle_fps_change
    self.input_handler.on_client_fps = lambda fps: (
        self.metrics.set_fps(fps) if self.metrics else None
    )
    self.input_handler.on_client_latency = lambda latency: (
        self.metrics.set_latency(latency) if self.metrics else None
    )
    self.input_handler.on_ping_response = lambda latency: (
        self.rtc_app.send_latency_time(latency)
    )
    self.input_handler.on_client_webrtc_stats = self.handle_client_werbtc_stats
    self.input_handler.on_update_settings = self.handle_update_settings
    self.input_handler.on_update_rate_control_mode = self.handle_rate_control_change
    self.input_handler.on_update_crf = self.handle_crf_change
    self.rtc_app.get_encoder_for_display = self._encoder_for_display
    self.rtc_app.get_fullcolor_for_display = self._fullcolor_for_display
    self.rtc_app.get_use_cpu_for_display = self._use_cpu_for_display
    self.rtc_app.on_video_consumer_active = self.handle_video_consumer_active
    self.rtc_app.on_consumers_changed = self.handle_consumers_changed
    # /api/tokens updates must reach live WebRTC peers too.
    selkies_module.webrtc_reconcile_hook = self.reconcile_webrtc_peers

    self.input_handler.on_scaling_ratio = self.handle_scaling
    self.input_handler.on_resize = self.on_resize_handler

    self.gpu_monitor.on_stats = self.handle_gpu_stats
    self.system_monitor.on_timer = self.handle_system_monitor
paramself

Returns

None
func_second_screen_availability(self) -> Tuple[bool, str]

Whether this session can actually attach a second display, and the reason when it cannot (websockets-mode parity).

The admin flag gates first; past it, X11 and the self-composited Wayland backend mint another output on demand, while host capture is bounded by the host compositor's real output count (unknown until a pipeline start establishes the host session).

Source Code
def _second_screen_availability(self) -> Tuple[bool, str]:
    """Whether this session can actually attach a second display, and the
    reason when it cannot (websockets-mode parity).

    The admin flag gates first; past it, X11 and the self-composited
    Wayland backend mint another output on demand, while host capture is
    bounded by the host compositor's real output count (unknown until a
    pipeline start establishes the host session).

    Returns:
        A ``(available, reason)`` pair; ``reason`` is empty when available.
    """
    enabled, _ = self.settings.second_screen
    if not enabled:
        return False, "Second screens are disabled on this server."
    if not IS_WAYLAND or not (self.settings.wayland_host_display or "").strip():
        return True, ""
    capacity = self._host_output_capacity
    if capacity is None or capacity < 0:
        return False, "The host compositor's outputs are not known yet."
    if capacity < 2:
        return False, "The host compositor has a single output, so a second display has nothing to capture."
    return True, ""
paramself

Returns

typing.Tuple

A (available, reason) pair; reason is empty when available.

func_refresh_second_screen_capacity(self) -> bool

Host-capture mode only: re-read how many outputs the host exposes.

Source Code
async def _refresh_second_screen_capacity(self) -> bool:
    """Host-capture mode only: re-read how many outputs the host exposes.

    Returns:
        True when the answer changed, i.e. the second-screen availability
        that clients were told may have flipped.
    """
    if not IS_WAYLAND or not (self.settings.wayland_host_display or "").strip():
        return False
    module = self._wayland_capture_handle()
    if module is None or not hasattr(module, "output_capacity"):
        return False
    try:
        capacity = int(await asyncio.to_thread(module.output_capacity))
    except Exception as e:
        logger.warning(f"Wayland output capacity query failed: {e}")
        return False
    changed = capacity != self._host_output_capacity
    self._host_output_capacity = capacity
    return changed
paramself

Returns

bool

True when the answer changed, i.e. the second-screen availability

func_server_settings_payload(self) -> Dict[str, Any]

get_server_settings with second_screen and ui_sidebar_show_apps published as EFFECTIVE availability — the admin flag AND what the backend can actually do — so dashboards never offer a second display the server would immediately refuse, nor an apps panel whose every button would fail. Adds the terminal the apps panel launches in, chosen by the session's windowing system (absent when none is installed: the client keeps its default).

Source Code
def _server_settings_payload(self) -> Dict[str, Any]:
    """get_server_settings with second_screen and ui_sidebar_show_apps
    published as EFFECTIVE availability — the admin flag AND what the
    backend can actually do — so dashboards never offer a second display
    the server would immediately refuse, nor an apps panel whose every
    button would fail. Adds the terminal the apps panel launches in, chosen
    by the session's windowing system (absent when none is installed: the
    client keeps its default)."""
    payload = get_server_settings()
    available, _ = self._second_screen_availability()
    entry = payload.get("settings", {}).get("second_screen")
    if isinstance(entry, dict) and entry.get("value") and not available:
        payload["settings"]["second_screen"] = dict(entry, value=False)
    apps = payload.get("settings", {}).get("ui_sidebar_show_apps")
    if (isinstance(apps, dict) and apps.get("value")
            and self.input_handler and not self.input_handler.apps_available()):
        payload["settings"]["ui_sidebar_show_apps"] = dict(apps, value=False)
    terminal = self.input_handler.app_terminal() if self.input_handler else None
    if terminal:
        payload["settings"]["app_terminal"] = {"value": terminal}
    return payload
paramself

Returns

typing.Dict[str, typing.Any]
funchandle_data_channel_open(self, channel=None) -> None

Greet the peer that just joined: every display page and viewer needs the server settings for conditional UI, the current cursor, and the display roster. Sent on ITS channel when given; without one, falls back to broadcasting.

Source Code
def handle_data_channel_open(self, channel: Optional[Any] = None) -> None:
    """Greet the peer that just joined: every display page and viewer needs
    the server settings for conditional UI, the current cursor, and the
    display roster. Sent on ITS channel when given; without one, falls back
    to broadcasting."""
    logger.info("opened peer data channel for user input to X11")
    server_settings_payload = self._server_settings_payload()
    if channel is not None:
        self.rtc_app.send_message_to_channel(
            channel, "server_settings", server_settings_payload
        )
        displays = ["primary"] + [d for d in self.display_clients.keys() if d != "primary"]
        self.rtc_app.send_message_to_channel(
            channel, "display_config_update", {"displays": displays}
        )
    else:
        self.rtc_app.send_media_data_over_channel(
            "server_settings", server_settings_payload
        )
        self._broadcast_display_config()
    self.send_current_cursor(channel)
paramself
paramchannelOptional[Any]
= None

Returns

None
funcsend_current_cursor(self, channel=None) -> None

Resend the current cursor (on channel open / video restart): to one peer's channel when given, otherwise to every connected peer.

Idempotent; a slept/woken tab clears its cursor canvas and needs it back.

Source Code
def send_current_cursor(self, channel: Optional[Any] = None) -> None:
    """Resend the current cursor (on channel open / video restart): to one
    peer's channel when given, otherwise to every connected peer.

    Idempotent; a slept/woken tab clears its cursor canvas and needs it back.
    """
    if not self.rtc_app:
        return
    cursor_data = None
    if self.input_handler:
        try:
            cursor_data = self.input_handler.get_current_cursor_data()
        except Exception as e:
            logger.warning(f"Failed to fetch current cursor data: {e}")
    if cursor_data is None:
        cursor_data = self.rtc_app.last_cursor_sent
    if not cursor_data:
        return
    try:
        if channel is not None:
            self.rtc_app.send_message_to_channel(channel, "cursor", cursor_data)
        else:
            self.rtc_app.send_cursor_data(cursor_data)
    except Exception as e:
        logger.warning(f"Failed to send current cursor to client: {e}")
paramself
paramchannelOptional[Any]
= None

Returns

None
funchandle_pointer_visible(self, visible) -> None

Compose the cursor into the captured video, on every display's capture (the websockets capture_cursor tunable is likewise global).

Source Code
async def handle_pointer_visible(self, visible: bool) -> None:
    """Compose the cursor into the captured video, on every display's capture
    (the websockets capture_cursor tunable is likewise global)."""
    for pipeline in list(self.display_pipelines.values()):
        if pipeline is not None:
            await pipeline.set_pointer_visible(visible)
paramself
paramvisiblebool

Returns

None
funchandle_video_bitrate_change(self, bitrate, display_id='primary') -> None

Video bitrate change for the display whose page sent it; sanitized against the server's configured range like the SETTINGS path, so the opcode cannot bypass a locked/narrowed range.

Source Code
async def handle_video_bitrate_change(self, bitrate: int, display_id: str = "primary") -> None:
    """Video bitrate change for the display whose page sent it; sanitized
    against the server's configured range like the SETTINGS path, so the
    opcode cannot bypass a locked/narrowed range."""
    sanitized = sanitize_client_setting("video_bitrate", bitrate, self.settings, logger)
    if sanitized is None or sanitized == self._display_setting(display_id, "video_bitrate"):
        return
    await self._apply_display_setting(display_id or "primary", "video_bitrate", sanitized)
paramself
parambitrateint
paramdisplay_idstr
= 'primary'

Returns

None
funchandle_audio_bitrate_change(self, bitrate) -> None

Handle audio bitrate change request (bps; sanitized like SETTINGS).

Source Code
async def handle_audio_bitrate_change(self, bitrate: int) -> None:
    """Handle audio bitrate change request (bps; sanitized like SETTINGS)."""
    sanitized = sanitize_client_setting("audio_bitrate", bitrate, self.settings, logger)
    if sanitized is None or sanitized == getattr(self.args, "audio_bitrate", None):
        return
    if self.media_pipeline:
        await self.media_pipeline.set_audio_bitrate(int(sanitized))
    self.args.audio_bitrate = sanitized
paramself
parambitrateint

Returns

None
funchandle_fps_change(self, fps, display_id='primary') -> None

Framerate change for the display whose page sent it; sanitized against the server's configured range like the SETTINGS path.

Source Code
async def handle_fps_change(self, fps: int, display_id: str = "primary") -> None:
    """Framerate change for the display whose page sent it; sanitized against
    the server's configured range like the SETTINGS path."""
    sanitized = sanitize_client_setting("framerate", fps, self.settings, logger)
    if sanitized is None or sanitized == self._display_setting(display_id, "framerate"):
        return
    await self._apply_display_setting(display_id or "primary", "framerate", sanitized)
paramself
paramfpsint
paramdisplay_idstr
= 'primary'

Returns

None
funchandle_rate_control_change(self, mode, display_id='primary') -> None

Rate-control switch for the display whose page sent it; honors the server's enable_rate_control lock like the SETTINGS path.

Source Code
async def handle_rate_control_change(self, mode: Any, display_id: str = "primary") -> None:
    """Rate-control switch for the display whose page sent it; honors the
    server's enable_rate_control lock like the SETTINGS path."""
    if self.args.enable_rate_control is False:
        logger.debug("Server has rate control disabled. Ignoring rate-control change.")
        return
    # Store the plain value: str(<str-Enum>) formats as the member name on
    # some supported Python versions, which would corrupt later comparisons.
    mode_str = mode.value if isinstance(mode, RateControlMode) else str(mode)
    await self._apply_display_setting(display_id or "primary", "rate_control_mode", mode_str)
paramself
parammodeAny
paramdisplay_idstr
= 'primary'

Returns

None
funchandle_crf_change(self, crf, display_id='primary') -> None

CRF change for the display whose page sent it; sanitized against the server's configured range like the SETTINGS path.

Source Code
async def handle_crf_change(self, crf: int, display_id: str = "primary") -> None:
    """CRF change for the display whose page sent it; sanitized against the
    server's configured range like the SETTINGS path."""
    sanitized = sanitize_client_setting("video_crf", int(crf), self.settings, logger)
    if sanitized is None or sanitized == self._display_setting(display_id, "video_crf"):
        return
    await self._apply_display_setting(display_id or "primary", "video_crf", sanitized)
paramself
paramcrfint
paramdisplay_idstr
= 'primary'

Returns

None
funchandle_client_werbtc_stats(self, webrtc_stat_type, webrtc_stats) -> None

Ingest client-reported WebRTC stats, gated on the Metrics object itself (built for metrics-http AND/OR the CSV statistics flag) so CSV-only configs actually ingest the stats they enabled.

Source Code
async def handle_client_werbtc_stats(
    self, webrtc_stat_type: str, webrtc_stats: str
) -> None:
    """Ingest client-reported WebRTC stats, gated on the Metrics object
    itself (built for metrics-http AND/OR the CSV statistics flag) so
    CSV-only configs actually ingest the stats they enabled."""
    if self.metrics:
        await self.metrics.set_webrtc_stats(webrtc_stat_type, webrtc_stats)
paramself
paramwebrtc_stat_typestr
paramwebrtc_statsstr

Returns

None
funcon_resize_handler(self, res, display_id='primary') -> None

Route a client resolution to its display: the primary resizes the real display directly while it is alone; once a secondary display is connected (or for any secondary), the resolution feeds the extended-desktop layout instead (websockets parity).

The layout path honors an admin manual-resolution lock the way the single-display path does: every display follows the server's geometry, so a second screen cannot be the way a client escapes the lock, and a locked size is the server's own, beyond a client alignment toggle.

Source Code
async def on_resize_handler(self, res: str, display_id: str = "primary") -> None:
    """Route a client resolution to its display: the primary resizes the real
    display directly while it is alone; once a secondary display is connected
    (or for any secondary), the resolution feeds the extended-desktop layout
    instead (websockets parity).

    The layout path honors an admin manual-resolution lock the way the
    single-display path does: every display follows the server's geometry,
    so a second screen cannot be the way a client escapes the lock, and a
    locked size is the server's own, beyond a client alignment toggle.
    """
    display_id = display_id or "primary"
    if display_id == "primary" and not self.args.enable_resize:
        logger.warning(f"remote resizing disabled, skipping resize to {res}")
        return
    if display_id != "primary" or self.display_clients:
        locked_dims = self._server_locked_dims()
        if locked_dims is not None:
            logger.warning(
                f"Client attempted to resize to {res} but server is in manual resolution mode. "
                f"Using the configured {locked_dims[0]}x{locked_dims[1]} instead."
            )
            w, h = locked_dims
        else:
            dims = parse_resize_dims(res)
            if dims is None:
                logger.error(f"Invalid resize request: {res}")
                return
            w, h = dims
            if self._display_setting(display_id, "force_aligned_resolution"):
                w, h = align_dims_16(w, h)
        if display_id == "primary":
            self._primary_dims = (w, h)
        else:
            entry = self.display_clients.get(display_id)
            if entry is None:
                logger.warning(f"Resize for unknown display '{display_id}' ignored.")
                return
            entry["width"], entry["height"] = w, h
        await self.reconfigure_displays()
        return
    self._primary_dims = None
    await self._resize_primary_display(res)
paramself
paramresstr
paramdisplay_idstr
= 'primary'

Returns

None
func_server_locked_dims(self) -> Optional[Tuple[int, int]]

The geometry an admin-configured manual-resolution lock pins the desktop to, or None when the server sets no lock. Derived on every read from the server settings (never from the client-writable args, whose manual trio is the client's own manual/auto toggle) and from the dimensions startup realized, so the lock cannot drift with client state. The settings layer guarantees positive manual dimensions while the lock is on; its own defaults stand in should one be unusable, so a locked server never falls back to honoring the client's request.

Source Code
def _server_locked_dims(self) -> Optional[Tuple[int, int]]:
    """The geometry an admin-configured manual-resolution lock pins the desktop
    to, or None when the server sets no lock. Derived on every read from the
    server settings (never from the client-writable args, whose manual trio is
    the client's own manual/auto toggle) and from the dimensions startup
    realized, so the lock cannot drift with client state. The settings layer
    guarantees positive manual dimensions while the lock is on; its own
    defaults stand in should one be unusable, so a locked server never
    falls back to honoring the client's request."""
    server_is_manual, _ = self.settings.manual_resolution
    if not server_is_manual:
        return None
    if self._manual_dims:
        return self._manual_dims
    width = int(getattr(self.settings, "manual_width", 0) or 0)
    height = int(getattr(self.settings, "manual_height", 0) or 0)
    if width <= 0:
        width = 1024
    if height <= 0:
        height = 768
    return (width - (width % 2), height - (height % 2))
paramself

Returns

typing.Optional[typing.Tuple[int, int]]
func_resize_primary_display(self, res) -> None

Resize the single (primary-only) display to a client-requested resolution, keep the capture dimensions in sync with what was realized, and tell the client the realized size when it differs.

Only an admin-configured manual-resolution lock blocks client resizes (websockets parity); the client's own manual/auto toggle in args must not gate here, since in client manual mode the chosen resolution arrives through this same path. Idempotent: clients re-assert their resolution on reconnects and settings broadcasts, and re-applying the current size would churn RandR (X11) or restart the capture (Wayland) for nothing; the last request counts as applied too, or a request the realized size differs from (CVT cell alignment) would read as pending forever. On Wayland there is no X server to resize: the compositor output follows the capture dimensions, so a running capture restarts and the compositor's realized geometry (it may even-mask or refuse the mode) is reconciled and pushed to the client.

Source Code
async def _resize_primary_display(self, res: str) -> None:
    """Resize the single (primary-only) display to a client-requested
    resolution, keep the capture dimensions in sync with what was realized,
    and tell the client the realized size when it differs.

    Only an admin-configured manual-resolution lock blocks client resizes
    (websockets parity); the client's own manual/auto toggle in `args`
    must not gate here, since in client manual mode the chosen resolution
    arrives through this same path. Idempotent: clients re-assert their
    resolution on reconnects and settings broadcasts, and re-applying the
    current size would churn RandR (X11) or restart the capture (Wayland)
    for nothing; the last request counts as applied too, or a request the
    realized size differs from (CVT cell alignment) would read as pending
    forever. On Wayland there is no X server to resize: the compositor
    output follows the capture dimensions, so a running capture restarts
    and the compositor's realized geometry (it may even-mask or refuse the
    mode) is reconciled and pushed to the client.
    """
    if self._server_locked_dims() is not None:
        logger.warning(
            f"Client attempted to resize to {res} but server is in manual resolution mode. Request ignored."
        )
        return
    try:
        dims = parse_resize_dims(res)
        if dims is None:
            logger.error(f"Invalid resize request: {res}. Ignoring")
            if self.media_pipeline:
                self.media_pipeline.last_resize_success = False
            return
        target_w, target_h = dims
        if getattr(self.args, "force_aligned_resolution", False):
            target_w, target_h = align_dims_16(target_w, target_h)

        if (
            self.media_pipeline
            and self.media_pipeline.last_resize_success
            and (
                (self.media_pipeline.width == target_w
                 and self.media_pipeline.height == target_h)
                or self._last_resize_request == (target_w, target_h)
            )
        ):
            logger.debug(f"Resolution already {target_w}x{target_h}; skipping re-apply.")
            return

        if IS_WAYLAND:
            self.media_pipeline.width = target_w
            self.media_pipeline.height = target_h
            # The capture, not the whole pipeline: the session's first request
            # can land while audio is still coming up. An unstarted capture
            # reads the new size when it starts.
            if self.media_pipeline.is_screen_capturing():
                await self.media_pipeline.restart_screen_capture()
                await self._push_wayland_realized_geometry("primary", self.media_pipeline)
            self.media_pipeline.last_resize_success = True
            self._last_resize_request = (target_w, target_h)
            logger.info(
                f"Wayland capture resized to {self.media_pipeline.width}x{self.media_pipeline.height}"
                f" (requested {target_w}x{target_h})"
            )
            return

        realized = await resize_display(f"{target_w}x{target_h}")
        if realized:
            realized_w, realized_h = realized
            if (realized_w, realized_h) != (target_w, target_h):
                logger.info(
                    f"resize_display realized {realized_w}x{realized_h} for request {target_w}x{target_h}"
                )
            else:
                logger.info(f"resize_display('{target_w}x{target_h}') reported success")
            # A zero-size region re-reads the live root now and keeps root-follow;
            # the auto-adjust poll trails ~30 frames, leaving new bands out of frame.
            capture_module = getattr(self.media_pipeline, "capture_module", None)
            if capture_module is not None:
                try:
                    await asyncio.to_thread(
                        capture_module.update_capture_region, 0, 0, 0, 0
                    )
                except Exception as e:
                    logger.warning(f"Capture re-follow after resize failed: {e}")
            self.media_pipeline.width = realized_w
            self.media_pipeline.height = realized_h
            self.media_pipeline.last_resize_success = True
            self._last_resize_request = (target_w, target_h)
            if self.rtc_app is not None:
                self.rtc_app.send_remote_resolution(f"{realized_w}x{realized_h}", "primary")
        else:
            logger.error(
                f"resize_display('{target_w}x{target_h}') reported failure"
            )
            self.media_pipeline.last_resize_success = False

    except Exception as e:
        logger.error(
            f"Error during resize handling for '{res}': {e}", exc_info=True
        )
        if self.media_pipeline:
            self.media_pipeline.last_resize_success = False
paramself
paramresstr

Returns

None
funcrequest_idr_for_display(self, display_id='primary') -> None

Schedule a dynamic IDR frame on the display's encoder, throttled by a per-display floor (websockets REQUEST_KEYFRAME parity).

Any number of viewers share one encoder, and an unthrottled data-channel request or PLI storm would let a single client force keyframe bursts for every consumer. A request landing inside the floor is satisfied by the IDR the previous request already scheduled.

Source Code
async def request_idr_for_display(self, display_id: str = "primary") -> None:
    """Schedule a dynamic IDR frame on the display's encoder, throttled by a
    per-display floor (websockets REQUEST_KEYFRAME parity).

    Any number of viewers share one encoder, and an unthrottled
    data-channel request or PLI storm would let a single client force
    keyframe bursts for every consumer. A request landing inside the floor
    is satisfied by the IDR the previous request already scheduled.
    """
    display_id = display_id or "primary"
    now = time.monotonic()
    if now - self._last_idr_request_times.get(display_id, 0.0) < 0.25:
        return
    self._last_idr_request_times[display_id] = now
    pipeline = self.display_pipelines.get(display_id)
    if pipeline is not None:
        await pipeline.dynamic_idr_frame()
paramself
paramdisplay_idstr
= 'primary'

Returns

None
func_provision_webrtc_virtual_mic(self) -> None

Bring up the SelkiesVirtualMic once for the WebRTC transport (shared provisioning with the websockets 0x02 path). Called from the per-peer mic playback start on the first mic packet; the lock + flag make concurrent first-packet calls across peers provision exactly once, and the shared helper reuses a source the websockets path already loaded rather than double-loading it.

Source Code
async def _provision_webrtc_virtual_mic(self) -> None:
    """Bring up the SelkiesVirtualMic once for the WebRTC transport (shared
    provisioning with the websockets 0x02 path). Called from the per-peer mic
    playback start on the first mic packet; the lock + flag make concurrent
    first-packet calls across peers provision exactly once, and the shared
    helper reuses a source the websockets path already loaded rather than
    double-loading it."""
    if self._mic_provisioned:
        return
    async with self._mic_provision_lock:
        if self._mic_provisioned:
            return
        if self._mic_control is None:
            self._mic_control = AudioControl("selkies-webrtc-mic")
        audio_device_name = getattr(self.media_pipeline, "audio_device_name", None)
        is_capturing = bool(getattr(self.media_pipeline, "_is_pcmflux_capturing", False))
        self._mic_module_index, self._mic_module_owned = (
            await self._mic_control.ensure_virtual_microphone(audio_device_name, is_capturing)
        )
        self._mic_provisioned = self._mic_module_index is not None
paramself

Returns

None
func_teardown_webrtc_virtual_mic(self) -> None

Unload the virtual-source module (only if this path loaded it) and release the mic control connection on shutdown.

Source Code
async def _teardown_webrtc_virtual_mic(self) -> None:
    """Unload the virtual-source module (only if this path loaded it) and
    release the mic control connection on shutdown."""
    control = self._mic_control
    self._mic_control = None
    if control is None:
        return
    if self._mic_module_index is not None and self._mic_module_owned:
        logger.info(f"Unloading WebRTC virtual mic module {self._mic_module_index}.")
        await control.unload_module(self._mic_module_index)
    self._mic_module_index = None
    self._mic_module_owned = False
    self._mic_provisioned = False
    await control.aclose()
paramself

Returns

None
funcstart_display_media(self, display_id) -> None

A display's consumer connected: the primary starts its pipeline right away; a secondary waits for its dimensions (the client's first resize message), which trigger the layout pass that creates its pipeline.

A consumer reclaiming the primary cancels a pending grace stop, and start_media_pipeline is idempotent, so a controller tab reload that reconnects inside the grace reuses the still-warm capture. A Wayland start only enqueues a compositor command, so its real outcome is read back through the same barrier the secondaries use: a pipeline that believes it is running with no live capture would leave the page waiting on frames that never arrive, so it is stopped and logged and the next consumer retries (this also surfaces host death, where reap_dead_host flips is_capturing off). The pipeline start is what establishes the host session in host-capture mode, so the host's output count — and the second-screen availability announced on channel open — can first become known here and is re-published.

Source Code
async def start_display_media(self, display_id: str) -> None:
    """A display's consumer connected: the primary starts its pipeline right
    away; a secondary waits for its dimensions (the client's first resize
    message), which trigger the layout pass that creates its pipeline.

    A consumer reclaiming the primary cancels a pending grace stop, and
    `start_media_pipeline` is idempotent, so a controller tab reload that
    reconnects inside the grace reuses the still-warm capture. A Wayland
    start only enqueues a compositor command, so its real outcome is read
    back through the same barrier the secondaries use: a pipeline that
    believes it is running with no live capture would leave the page
    waiting on frames that never arrive, so it is stopped and logged and
    the next consumer retries (this also surfaces host death, where
    `reap_dead_host` flips `is_capturing` off). The pipeline start is what
    establishes the host session in host-capture mode, so the host's
    output count — and the second-screen availability announced on
    channel open — can first become known here and is re-published.
    """
    if display_id == "primary" and self.media_pipeline:
        self._cancel_primary_stop_grace()
        await self.media_pipeline.start_media_pipeline()
        if (IS_WAYLAND and self.media_pipeline.is_media_pipeline_running()
                and not await self._wayland_capture_live("primary", self.media_pipeline)):
            last_error = self._wayland_capture_last_error(self.media_pipeline, "primary")
            logger.error(
                "Primary Wayland capture is not live after start"
                + (f": {last_error}." if last_error else "."))
            await self.media_pipeline.stop_media_pipeline()
            return
        caveat = (self._wayland_capture_last_error(self.media_pipeline, "primary")
                  if IS_WAYLAND else None)
        if caveat:
            logger.warning(f"Primary Wayland capture started with a caveat: {caveat}")
        if await self._refresh_second_screen_capacity() and self.rtc_app:
            self.rtc_app.send_media_data_over_channel(
                "server_settings", self._server_settings_payload()
            )
paramself
paramdisplay_idstr

Returns

None
funcstop_display_media(self, display_id) -> None

Release a display's pipeline: the primary's stop is deferred by a reconnect grace (a controller tab reload reconnects within a second or two and reuses the warm capture, and viewers/display2 keep streaming throughout — websockets _teardown_if_unclaimed parity); a secondary is fully unregistered and the desktop re-laid-out at once.

Source Code
async def stop_display_media(self, display_id: str) -> None:
    """Release a display's pipeline: the primary's stop is deferred by a
    reconnect grace (a controller tab reload reconnects within a second or
    two and reuses the warm capture, and viewers/display2 keep streaming
    throughout — websockets _teardown_if_unclaimed parity); a secondary is
    fully unregistered and the desktop re-laid-out at once."""
    if display_id == "primary":
        self._schedule_primary_stop_grace()
        return
    async with self._display_lock:
        pipeline = self.display_pipelines.pop(display_id, None)
        self.display_clients.pop(display_id, None)
        self.display_layouts.pop(display_id, None)
        if pipeline is not None:
            await pipeline.stop_media_pipeline()
    await self.reconfigure_displays()
paramself
paramdisplay_idstr

Returns

None
func_cancel_primary_stop_grace(self) -> None

Drop a pending deferred primary-capture stop: a consumer reclaimed the display before the grace elapsed.

Source Code
def _cancel_primary_stop_grace(self) -> None:
    """Drop a pending deferred primary-capture stop: a consumer reclaimed
    the display before the grace elapsed."""
    task = self._primary_stop_grace_task
    self._primary_stop_grace_task = None
    if task is not None and not task.done():
        task.cancel()
paramself

Returns

None
func_schedule_primary_stop_grace(self) -> None

Stop the primary capture after RECONNECT_GRACE_S unless a consumer reclaims it first. A page reload drops and re-adds its peer within the window, so tearing the capture down immediately would black out a reconnecting controller (and stall the audio fan-out the viewers share) for no reason; if nobody reclaims the primary, the stop runs after the grace. At most one grace is pending at a time.

Source Code
def _schedule_primary_stop_grace(self) -> None:
    """Stop the primary capture after RECONNECT_GRACE_S unless a consumer
    reclaims it first. A page reload drops and re-adds its peer within the
    window, so tearing the capture down immediately would black out a
    reconnecting controller (and stall the audio fan-out the viewers share)
    for no reason; if nobody reclaims the primary, the stop runs after the
    grace. At most one grace is pending at a time."""
    if self._primary_stop_grace_task is not None and not self._primary_stop_grace_task.done():
        return
    if self.media_pipeline is None or not self.media_pipeline.is_media_pipeline_running():
        return

    async def _stop_after_grace() -> None:
        """Stop the primary unless reclaimed. No lock: the event loop
        serializes start/stop with this coroutine's resume."""
        try:
            await asyncio.sleep(self.RECONNECT_GRACE_S)
        except asyncio.CancelledError:
            return
        self._primary_stop_grace_task = None
        if self._primary_display_has_consumer():
            logger.info("Primary reclaimed within the grace; capture kept.")
            return
        if self.media_pipeline is not None:
            logger.info("Primary unclaimed after the grace; stopping its capture.")
            await self.media_pipeline.stop_media_pipeline()

    self._primary_stop_grace_task = asyncio.create_task(_stop_after_grace())
paramself

Returns

None
func_primary_display_has_consumer(self) -> bool

Whether any peer (controller or viewer) still consumes the primary.

Source Code
def _primary_display_has_consumer(self) -> bool:
    """Whether any peer (controller or viewer) still consumes the primary."""
    if self.rtc_app is None:
        return False
    return any(
        (p.get("display_id") or "primary") == "primary"
        for p in self.rtc_app.peer_connections.values()
    )
paramself

Returns

bool
func_wayland_capture_handle(self) -> Optional[Any]

A pixelflux handle for compositor output management (any ScreenCapture reaches the shared Wayland backend); prefers the primary pipeline's live capture module.

Source Code
def _wayland_capture_handle(self) -> Optional[Any]:
    """A pixelflux handle for compositor output management (any ScreenCapture
    reaches the shared Wayland backend); prefers the primary pipeline's live
    capture module."""
    module = getattr(self.media_pipeline, "capture_module", None)
    if module is not None:
        return module
    if PixelfluxScreenCapture is None:
        return None
    if self._wayland_ctl_module is None:
        self._wayland_ctl_module = PixelfluxScreenCapture()
    return self._wayland_ctl_module
paramself

Returns

typing.Optional[typing.Any]
func_destroy_wayland_secondary_outputs(self, keep_oid=None) -> None

Retire every secondary compositor output except keep_oid (the primary, output 0, always persists).

Source Code
async def _destroy_wayland_secondary_outputs(self, keep_oid: Optional[int] = None) -> None:
    """Retire every secondary compositor output except `keep_oid` (the
    primary, output 0, always persists)."""
    module = self._wayland_capture_handle()
    if module is None:
        return
    try:
        for out in await asyncio.to_thread(module.list_outputs):
            if out[0] != 0 and out[0] != keep_oid:
                await asyncio.to_thread(module.destroy_output, out[0])
    except Exception as e:
        logger.warning(f"Wayland output teardown failed: {e}")
paramself
paramkeep_oidOptional[int]
= None

Returns

None
func_apply_wayland_extension(self, did, layouts) -> bool

Realize the extended layout as compositor outputs, BEFORE the secondary's pipeline binds a capture — the Wayland counterpart of apply_extended_layout.

The primary (output 0) MOVES to its layout offset ('left'/'up' place it off-origin); a secondary reposition is a destroy + recreate (its capture rebinds on the pipeline restart that follows), destroyed before the primary moves so the rectangles never overlap.

Source Code
async def _apply_wayland_extension(self, did: str, layouts: Dict[str, Dict[str, int]]) -> bool:
    """Realize the extended layout as compositor outputs, BEFORE the
    secondary's pipeline binds a capture — the Wayland counterpart of
    apply_extended_layout.

    The primary (output 0) MOVES to its layout offset ('left'/'up' place it
    off-origin); a secondary reposition is a destroy + recreate (its
    capture rebinds on the pipeline restart that follows), destroyed before
    the primary moves so the rectangles never overlap.

    Returns:
        False when the output cannot be created or the primary cannot move
        (the caller drops the display).
    """
    module = self._wayland_capture_handle()
    if module is None:
        return False
    oid = wayland_output_id(did)
    s = layouts[did]
    scale = float(getattr(self.media_pipeline, "scale", 1.0) or 1.0)
    try:
        outputs = {o[0]: o for o in await asyncio.to_thread(module.list_outputs)}
    except Exception as e:
        logger.error(f"Wayland list_outputs failed: {e}")
        outputs = {}
    await self._destroy_wayland_secondary_outputs(keep_oid=oid)
    existing = outputs.get(oid)
    if existing is not None and (existing[1], existing[2]) != (s["x"], s["y"]):
        logger.info(f"Wayland output {oid} moves to +{s['x']}+{s['y']}; recreating it.")
        await asyncio.to_thread(module.destroy_output, oid)
        existing = None
    p = layouts.get("primary") or {"x": 0, "y": 0}
    existing0 = outputs.get(0)
    current0 = (existing0[1], existing0[2]) if existing0 is not None else (0, 0)
    if (p["x"], p["y"]) != current0:
        if not await wayland_reposition_primary(module, p["x"], p["y"]):
            return False
    if existing is not None:
        return True
    try:
        created = bool(await asyncio.to_thread(
            module.create_output, oid, s["w"], s["h"], s["x"], s["y"], scale))
    except Exception as e:
        logger.error(f"Wayland create_output {oid} failed: {e}")
        return False
    if created and self.input_handler:
        # Which of the session's own screens a capture drives changed.
        self.input_handler.resync_session_screens()
    return created
paramself
paramdidstr
paramlayoutsDict[str, Dict[str, int]]

Returns

bool

False when the output cannot be created or the primary cannot move

func_wayland_capture_live(self, did, pipeline) -> bool

Whether the display's capture really runs in the compositor. The geometry read is a barrier: it is answered only after the queued capture start finished, so is_capturing is authoritative afterwards.

Source Code
async def _wayland_capture_live(self, did: str, pipeline: MediaPipelinePixel) -> bool:
    """Whether the display's capture really runs in the compositor. The
    geometry read is a barrier: it is answered only after the queued capture
    start finished, so is_capturing is authoritative afterwards."""
    module = getattr(pipeline, "capture_module", None)
    if module is None:
        return False
    try:
        await asyncio.to_thread(module.get_realized_geometry, wayland_output_id(did))
        return bool(module.is_capturing)
    except Exception:
        return False
paramself
paramdidstr
parampipelineMediaPipelinePixel

Returns

bool
func_wayland_capture_last_error(self, pipeline, did) -> Optional[str]

The reason a display's Wayland capture failed, or a caveat a live one came up with (encoder fell back to CPU, host connect refused), or None. Read straight from capture_state (no barrier); the caller ensures ordering. None on an older pixelflux without the readback.

Source Code
def _wayland_capture_last_error(
    self, pipeline: Optional[MediaPipelinePixel], did: str
) -> Optional[str]:
    """The reason a display's Wayland capture failed, or a caveat a live one came up
    with (encoder fell back to CPU, host connect refused), or None. Read straight from
    ``capture_state`` (no barrier); the caller ensures ordering. None on an older
    pixelflux without the readback."""
    module = getattr(pipeline, "capture_module", None) if pipeline is not None else None
    getter = getattr(module, "capture_state", None) if module is not None else None
    if getter is None:
        return None
    try:
        _state, last_error = getter(wayland_output_id(did))
        return last_error
    except Exception:
        return None
paramself
parampipelineOptional[MediaPipelinePixel]
paramdidstr

Returns

typing.Optional[str]
func_realized_wayland_dims(self, did) -> Optional[Tuple[int, int]]

The (width, height) the compositor currently has for this display's output, or None when it cannot be read.

Source Code
async def _realized_wayland_dims(self, did: str) -> Optional[Tuple[int, int]]:
    """The ``(width, height)`` the compositor currently has for this
    display's output, or None when it cannot be read."""
    if not IS_WAYLAND:
        return None
    pipeline = (self.media_pipeline if did == "primary"
                else self.display_pipelines.get(did))
    module = getattr(pipeline, "capture_module", None)
    if module is None or not hasattr(module, "get_realized_geometry"):
        return None
    try:
        geom = await asyncio.to_thread(
            module.get_realized_geometry, wayland_output_id(did))
    except Exception as e:
        logger.warning(f"Wayland realized-geometry read failed for '{did}': {e}")
        return None
    if geom is None:
        logger.warning(f"Wayland realized-geometry read for '{did}' timed out; size unknown.")
        return None
    w, h, _scale = geom
    return (w, h) if w > 0 and h > 0 else None
paramself
paramdidstr

Returns

typing.Optional[typing.Tuple[int, int]]
func_push_wayland_realized_geometry(self, did, pipeline) -> None

Read what the pixelflux compositor actually realized on this display's output after a capture (re)start (it may even-mask dimensions or keep the old mode on a GBM allocation failure), fold it into the pipeline/layout state the input math offsets against, and push the corrected size to the clients over the existing system resolution message — the WR counterpart of the WS realized clamp + broadcast. The stream itself re-negotiates through the encoder (the track's intrinsic size IS the realized resolution); this closes the control-plane loop. The read is also a barrier: the compositor answers only after the queued capture (re)start finished. The push is unconditional (idempotent, WS-broadcast parity: the client's request may have been snapped by sanitization before the pipeline saw it) and scoped to this display's channels so a secondary's size never rescales the primary page.

Source Code
async def _push_wayland_realized_geometry(
    self, did: str, pipeline: Optional[MediaPipelinePixel]
) -> None:
    """Read what the pixelflux compositor actually realized on this
    display's output after a capture (re)start (it may even-mask dimensions
    or keep the old mode on a GBM allocation failure), fold it into the
    pipeline/layout state the input math offsets against, and push the
    corrected size to the clients over the existing system resolution
    message — the WR counterpart of the WS realized clamp + broadcast. The
    stream itself re-negotiates through the encoder (the track's intrinsic
    size IS the realized resolution); this closes the control-plane loop.
    The read is also a barrier: the compositor answers only after the
    queued capture (re)start finished. The push is unconditional
    (idempotent, WS-broadcast parity: the client's request may have been
    snapped by sanitization before the pipeline saw it) and scoped to this
    display's channels so a secondary's size never rescales the primary
    page."""
    if not IS_WAYLAND or pipeline is None:
        return
    module = getattr(pipeline, "capture_module", None)
    if module is None or not hasattr(module, "get_realized_geometry"):
        return
    try:
        geom = await asyncio.to_thread(
            module.get_realized_geometry, wayland_output_id(did))
    except Exception as e:
        logger.warning(f"Wayland realized-geometry read failed for '{did}': {e}")
        return
    if geom is None:
        # A timeout is unknown geometry, not "nothing to reconcile".
        logger.warning(f"Wayland realized-geometry read for '{did}' timed out; state left unreconciled.")
        return
    w, h, scale = geom
    if w <= 0 or h <= 0:
        return
    pipeline.width, pipeline.height = w, h
    if did == "primary":
        if self._primary_dims is not None:
            self._primary_dims = (w, h)
    else:
        entry = self.display_clients.get(did)
        if entry is not None:
            entry["width"], entry["height"] = w, h
    layout = self.display_layouts.get(did)
    if layout is not None:
        layout["w"], layout["h"] = w, h
    logger.info(f"Wayland realized geometry for '{did}': {w}x{h} @ scale {scale}")
    if self.rtc_app is not None:
        self.rtc_app.send_remote_resolution(f"{w}x{h}", did)
paramself
paramdidstr
parampipelineOptional[MediaPipelinePixel]

Returns

None
func_apply_wayland_cursor_size(self, dpi_value) -> None

Wayland counterpart of the X11 per-DPI cursor resize: the compositor reloads its theme cursor (composited overlay and named-cursor delivery both re-render) at the DPI-scaled size, live, no capture restart.

Source Code
async def _apply_wayland_cursor_size(self, dpi_value: float) -> None:
    """Wayland counterpart of the X11 per-DPI cursor resize: the compositor
    reloads its theme cursor (composited overlay and named-cursor delivery
    both re-render) at the DPI-scaled size, live, no capture restart."""
    if CURSOR_SIZE is None:
        return
    module = self._wayland_capture_handle()
    setter = getattr(module, "set_cursor_size", None) if module else None
    if setter is None:
        logger.warning("Wayland cursor resize unavailable (no set_cursor_size).")
        return
    size = cursor_size_for_dpi(dpi_value, CURSOR_SIZE)
    try:
        if await asyncio.to_thread(setter, size):
            logger.info(f"Wayland cursor size set to {size} (DPI {dpi_value}).")
        else:
            logger.warning(f"Wayland compositor refused cursor size {size}.")
    except Exception as e:
        logger.warning(f"Wayland cursor resize failed: {e}")
paramself
paramdpi_valuefloat

Returns

None
func_display_consumers(self, display_id) -> List[Dict[str, Any]]

Registered peers (controller + viewers) consuming this display's stream.

Source Code
def _display_consumers(self, display_id: str) -> List[Dict[str, Any]]:
    """Registered peers (controller + viewers) consuming this display's
    stream."""
    if self.rtc_app is None:
        return []
    return [
        p for p in self.rtc_app.peer_connections.values()
        if (p.get("display_id") or "primary") == display_id
    ]
paramself
paramdisplay_idstr

Returns

typing.List[typing.Dict[str, typing.Any]]
funchandle_peer_gone(self, peer_id, peer=None) -> None

Release the input a departing peer may still be holding, matching the websockets disconnect cleanup. Gamepad slots are per-connection, so they are always released. Held keys and pointer buttons are one global desktop state instead: they are force-released only when the departing peer could drive input and no input-capable peer is left, so a viewer (or a second display's peer) leaving never drops the controller's held keys or its in-flight drag. A controller that vanishes while others remain is covered by the input handler's heartbeat stale-sweep.

Source Code
async def handle_peer_gone(
    self, peer_id: str, peer: Optional[Dict[str, Any]] = None
) -> None:
    """Release the input a departing peer may still be holding, matching the
    websockets disconnect cleanup. Gamepad slots are per-connection, so they
    are always released. Held keys and pointer buttons are one global desktop
    state instead: they are force-released only when the departing peer could
    drive input and no input-capable peer is left, so a viewer (or a second
    display's peer) leaving never drops the controller's held keys or its
    in-flight drag. A controller that vanishes while others remain is covered
    by the input handler's heartbeat stale-sweep."""
    if self.input_handler is None:
        return
    try:
        await self.input_handler.release_gamepads_for_conn(peer_id)
    except Exception as e:
        logger.warning(f"Gamepad release for departed peer {peer_id} failed: {e}")
    if self.rtc_app is None or not self.rtc_app.peer_holds_input_authority(peer):
        return
    if (peer.get("display_id") or "primary") != "primary":
        # The departing peer is already out of peer_connections: survivors only.
        for survivor in list(self.rtc_app.peer_connections.values()):
            if self.rtc_app.peer_holds_input_authority(survivor):
                return
    for release in (self.input_handler.release_mouse_buttons,
                    self.input_handler.reset_keyboard):
        try:
            await release()
        except Exception as e:
            logger.warning(f"Input release for departed peer {peer_id} failed: {e}")
paramself
parampeer_idstr
parampeerOptional[Dict[str, Any]]
= None

Returns

None
funchandle_video_consumer_active(self, peer_id, display_id, active) -> None

Tab-visibility pause/resume for ONE peer (data-channel STOP_VIDEO / START_VIDEO, websockets parity). The peer's own RTP sender gates its delivery; the shared capture only stops once EVERY consumer of the display (controller and viewers alike) is paused. A resuming peer always gets an IDR — its decoder needs a resync even when the capture kept running for other consumers — with PLI as the fallback.

Source Code
async def handle_video_consumer_active(self, peer_id: str, display_id: str, active: bool) -> None:
    """Tab-visibility pause/resume for ONE peer (data-channel STOP_VIDEO /
    START_VIDEO, websockets parity). The peer's own RTP sender gates its
    delivery; the shared capture only stops once EVERY consumer of the
    display (controller and viewers alike) is paused. A resuming peer always
    gets an IDR — its decoder needs a resync even when the capture kept
    running for other consumers — with PLI as the fallback."""
    display_id = display_id or "primary"
    peer = self.rtc_app.peer_connections.get(peer_id) if self.rtc_app else None
    if peer is None:
        return
    peer["video_paused"] = not active
    sender = peer.get("video_sender")
    if sender is not None:
        # A disabled sender keeps draining its relay proxy but sends no RTP,
        # so only this peer's stream stalls.
        sender._enabled = active
    pipeline = self.display_pipelines.get(display_id)
    if pipeline is None:
        return
    if active:
        await self._resume_display_capture(display_id, pipeline,
                                           "consumer resume", idr_always=True)
    elif all(p.get("video_paused", False) for p in self._display_consumers(display_id)):
        if await pipeline.pause_screen_capture():
            logger.info(
                f"All consumers of display '{display_id}' are paused; capture stopped."
            )
paramself
parampeer_idstr
paramdisplay_idstr
paramactivebool

Returns

None
func_close_peer_signaling_ws(self, peer_id, code, message) -> None

Fatal verdict on ONE peer's signaling socket (websockets KILL parity); bounded so a wedged socket cannot stall the caller.

Source Code
async def _close_peer_signaling_ws(self, peer_id: str, code: int, message: bytes) -> None:
    """Fatal verdict on ONE peer's signaling socket (websockets KILL parity);
    bounded so a wedged socket cannot stall the caller."""
    if self.peer_manager is None:
        return
    async with self.peer_manager.lock:
        peer = self.peer_manager.peers.get(peer_id)
        peer_ws = getattr(peer, "ws", None) if peer is not None else None
    if peer_ws is not None and not peer_ws.closed:
        try:
            await asyncio.wait_for(peer_ws.close(code=code, message=message), timeout=2.0)
        except Exception:
            pass
paramself
parampeer_idstr
paramcodeint
parammessagebytes

Returns

None
funcreconcile_webrtc_peers(self) -> None

Token-update reconciliation for LIVE WebRTC peers (websockets reconcile_clients parity): a revoked or role-changed token closes the peer (signaling verdict 4002 + pipeline stop); an mk-token handoff pushes the new input verdict to every surviving peer over its data channel, controllers included (a handoff strips their authority too). Per-message input authority already reads the live store — this covers the media stream and the client-side grant, which otherwise persist until the peer disconnects itself. A slot-only change keeps the peer but is pushed as a role_update (websockets ROLE_UPDATE parity): the gamepad slot mapping lives client-side and would silently desync.

Source Code
async def reconcile_webrtc_peers(self) -> None:
    """Token-update reconciliation for LIVE WebRTC peers (websockets
    reconcile_clients parity): a revoked or role-changed token closes the
    peer (signaling verdict 4002 + pipeline stop); an mk-token handoff
    pushes the new input verdict to every surviving peer over its data
    channel, controllers included (a handoff strips their authority too).
    Per-message input authority already reads the live store — this covers
    the media stream and the client-side grant, which otherwise persist
    until the peer disconnects itself. A slot-only change keeps the peer
    but is pushed as a role_update (websockets ROLE_UPDATE parity): the
    gamepad slot mapping lives client-side and would silently desync."""
    if self.rtc_app is None:
        return
    tokens, mk = current_session_tokens()
    for peer_id, peer in list(self.rtc_app.peer_connections.items()):
        token = peer.get("client_token")
        if not token:
            # Token-less peer: governed by its URL role only.
            continue
        ctype = peer.get("client_type")
        role_now = "controller" if ctype == ClientType.CONTROLLER else "viewer"
        new_perms = tokens.get(token)
        if not new_perms or (new_perms.get("role") or "controller") != role_now:
            reason = "Token revoked" if not new_perms else "Permissions changed significantly"
            logger.info(f"Disconnecting WebRTC peer {peer_id}: {reason}")
            await self._close_peer_signaling_ws(peer_id, 4002, reason.encode())
            try:
                await self.rtc_app.stop_rtc_connection(peer_id, role_now)
            except Exception:
                logger.warning(f"stop_rtc_connection failed for {peer_id}", exc_info=True)
            continue
        self.rtc_app._send_collab_state(peer.get("data_channel"), ctype, token)
        new_slot = new_perms.get("slot")
        if new_slot != peer.get("client_slot"):
            peer["client_slot"] = new_slot
            channel = peer.get("data_channel")
            if channel is not None and channel.readyState == "open":
                try:
                    verdict = json.dumps({"role": role_now, "slot": new_slot})
                    channel.send(json.dumps(
                        {"type": "system", "data": {"action": f"role_update,{verdict}"}}))
                except Exception:
                    logger.debug("role_update send failed (channel closing)", exc_info=True)
paramself

Returns

None
funchandle_consumers_changed(self, display_id) -> None

A peer joined or left a display's consumer set: re-evaluate the all-paused stop in both directions — a departing unpaused peer cannot leave the capture running for hidden-only consumers, and a JOINING unpaused peer must re-open a capture the rule stopped (else a viewer arriving while every prior consumer is hidden gets a permanently black stream: a paused capture emits no RTP, so the browser never even PLIs). (A departing controller's pipeline is torn down elsewhere; both pause_screen_capture and resume_screen_capture no-op on a stopped pipeline.)

Source Code
async def handle_consumers_changed(self, display_id: str) -> None:
    """A peer joined or left a display's consumer set: re-evaluate the
    all-paused stop in both directions — a departing unpaused peer cannot
    leave the capture running for hidden-only consumers, and a JOINING
    unpaused peer must re-open a capture the rule stopped (else a viewer
    arriving while every prior consumer is hidden gets a permanently black
    stream: a paused capture emits no RTP, so the browser never even PLIs).
    (A departing controller's pipeline is torn down elsewhere; both
    pause_screen_capture and resume_screen_capture no-op on a stopped
    pipeline.)"""
    display_id = display_id or "primary"
    pipeline = self.display_pipelines.get(display_id)
    if pipeline is None:
        return
    consumers = self._display_consumers(display_id)
    if not consumers:
        return
    if all(p.get("video_paused", False) for p in consumers):
        if await pipeline.pause_screen_capture():
            logger.info(
                f"All remaining consumers of display '{display_id}' are paused; capture stopped."
            )
    else:
        # A live capture already flows RTP; the joining browser's PLI resyncs it.
        await self._resume_display_capture(display_id, pipeline, "joining consumer")
paramself
paramdisplay_idstr

Returns

None
func_resume_display_capture(self, display_id, pipeline, why, idr_always=False) -> None

Resume a capture stopped by the all-consumers-paused rule (no-op on a live or fully-stopped pipeline) and request the resync IDR — always, or only when the resume actually restarted the capture.

Source Code
async def _resume_display_capture(self, display_id: str, pipeline: MediaPipelinePixel,
                                  why: str, idr_always: bool = False) -> None:
    """Resume a capture stopped by the all-consumers-paused rule (no-op on a
    live or fully-stopped pipeline) and request the resync IDR — always, or
    only when the resume actually restarted the capture."""
    restarted = False
    try:
        restarted = await pipeline.resume_screen_capture()
        if restarted:
            logger.info(f"Display '{display_id}': capture restarted ({why}).")
    except Exception as e:
        logger.error(f"Display '{display_id}': capture resume failed ({why}): {e}")
    if idr_always or restarted:
        await self.request_idr_for_display(display_id)
paramself
paramdisplay_idstr
parampipelineMediaPipelinePixel
paramwhystr
paramidr_alwaysbool
= False

Returns

None
func_drop_wayland_secondary(self, did, reason) -> None

Refuse a secondary display the compositor cannot realize: unregister it, stop its pipeline, destroy its output, and close its peers with a fatal signaling verdict (4000) so the client does not re-register in a loop — the Wayland mirror of the X11 unrealizable-extension drop. The primary output, which may sit at a 'left'/'up' offset for the arrangement this display anchored, goes back to the origin. Caller holds _display_lock.

Source Code
async def _drop_wayland_secondary(self, did: str, reason: str) -> None:
    """Refuse a secondary display the compositor cannot realize: unregister
    it, stop its pipeline, destroy its output, and close its peers with a
    fatal signaling verdict (4000) so the client does not re-register in a
    loop — the Wayland mirror of the X11 unrealizable-extension drop. The
    primary output, which may sit at a 'left'/'up' offset for the
    arrangement this display anchored, goes back to the origin. Caller
    holds _display_lock."""
    pipeline = self.display_pipelines.pop(did, None)
    self.display_clients.pop(did, None)
    self.display_layouts.pop(did, None)
    primary_layout = self.display_layouts.get("primary")
    if primary_layout:
        # Input offsets follow the layout; the primary re-anchors at the origin.
        primary_layout["x"], primary_layout["y"] = 0, 0
    if pipeline is not None:
        await pipeline.stop_media_pipeline()
    module = self._wayland_capture_handle()
    if module is not None:
        try:
            await asyncio.to_thread(module.destroy_output, wayland_output_id(did))
        except Exception:
            pass
        await wayland_reposition_primary(module, 0, 0)
    if self.peer_manager is not None:
        async with self.peer_manager.lock:
            doomed = [
                p.ws for p in self.peer_manager.peers.values()
                if p.peer_type != "server"
                and getattr(p, "display_id", "primary") == did
                and getattr(p, "ws", None) is not None and not p.ws.closed
            ]
        for peer_ws in doomed:
            try:
                await asyncio.wait_for(
                    peer_ws.close(code=4000, message=reason.encode("utf-8")),
                    timeout=2.0,
                )
            except Exception:
                pass
    if self.rtc_app is not None:
        await self.rtc_app.close_display_peers(did)
    logger.error(f"Secondary display '{did}' dropped on Wayland: {reason}")
paramself
paramdidstr
paramreasonstr

Returns

None
func_drop_x11_secondary(self, did, reason) -> None

Refuse a secondary display the X server cannot fit in its root: unregister it, stop its pipeline, and close its peers with a fatal signaling verdict (4000) so the client does not reload and re-register in a loop — the X11 mirror of the compositor-side drop, and the websockets engine's KILL parity. Caller holds _display_lock.

Unregistering inline rather than through stop_display_media, which would re-acquire the lock.

Source Code
async def _drop_x11_secondary(self, did: str, reason: str) -> None:
    """Refuse a secondary display the X server cannot fit in its root:
    unregister it, stop its pipeline, and close its peers with a fatal
    signaling verdict (4000) so the client does not reload and re-register
    in a loop — the X11 mirror of the compositor-side drop, and the
    websockets engine's KILL parity. Caller holds _display_lock.

    Unregistering inline rather than through stop_display_media, which
    would re-acquire the lock.
    """
    pipeline = self.display_pipelines.pop(did, None)
    self.display_clients.pop(did, None)
    self.display_layouts.pop(did, None)
    primary_layout = self.display_layouts.get("primary")
    if primary_layout:
        # Input offsets follow the layout; the primary is back at the origin.
        primary_layout["x"], primary_layout["y"] = 0, 0
    if pipeline is not None:
        await pipeline.stop_media_pipeline()
    if self.peer_manager is not None:
        async with self.peer_manager.lock:
            doomed = [
                p.ws for p in self.peer_manager.peers.values()
                if p.peer_type != "server"
                and getattr(p, "display_id", "primary") == did
                and getattr(p, "ws", None) is not None and not p.ws.closed
            ]
        for peer_ws in doomed:
            try:
                await asyncio.wait_for(
                    peer_ws.close(code=4000, message=reason.encode("utf-8")),
                    timeout=2.0,
                )
            except Exception:
                pass
    if self.rtc_app is not None:
        await self.rtc_app.close_display_peers(did)
    logger.error(
        f"Extended layout for '{did}' is unrealizable; the secondary display stays "
        f"disabled. {reason}"
    )
paramself
paramdidstr
paramreasonstr

Returns

None
funcreconfigure_displays(self) -> None

Lay the extended desktop out for the connected displays and point each display's capture at its region — the WR counterpart of the websockets reconfigure engine, for the primary plus one secondary display. On Wayland the layout realizes as compositor outputs instead of xrandr monitors.

With no laid-out secondary the plain full-screen capture is restored: the secondary's compositor output goes away (its windows relocate to the primary) and the primary re-anchors at the origin, or on X11 the stale selkies-* monitors are cleared and the framebuffer shrunk; a secondary registered without dimensions yet (or whose layout was unrealizable) sends the primary's diverted resolution request straight to the real display. Otherwise the primary size comes from its last layout-path request, else its pipeline dimensions (or the live screen resolution on X11), passes the auto-resize feedback clamp, and the dual layout is realized. A layout the server cannot realize takes the secondary down with it, since an input channel left connected would keep feeding a display with no laid-out region. A new secondary's pipeline is built from its own settings (its SETTINGS arrive before its first resize lays it out), falling back per key to the service defaults, and a bring-up failure drops it so the next reconfigure retries instead of finding a dead pipeline.

Source Code
async def reconfigure_displays(self) -> None:
    """Lay the extended desktop out for the connected displays and point each
    display's capture at its region — the WR counterpart of the websockets
    reconfigure engine, for the primary plus one secondary display. On
    Wayland the layout realizes as compositor outputs instead of xrandr
    monitors.

    With no laid-out secondary the plain full-screen capture is restored:
    the secondary's compositor output goes away (its windows relocate to
    the primary) and the primary re-anchors at the origin, or on X11 the
    stale selkies-* monitors are cleared and the framebuffer shrunk; a
    secondary registered without dimensions yet (or whose layout was
    unrealizable) sends the primary's diverted resolution request straight
    to the real display. Otherwise the primary size comes from its last
    layout-path request, else its pipeline dimensions (or the live screen
    resolution on X11), passes the auto-resize feedback clamp, and the
    dual layout is realized. A layout the server cannot realize takes the
    secondary down with it, since an input channel left connected would
    keep feeding a display with no laid-out region. A new secondary's
    pipeline is built from its own settings (its SETTINGS arrive before
    its first resize lays it out), falling back per key to the service
    defaults, and a bring-up failure drops it so the next reconfigure
    retries instead of finding a dead pipeline.
    """
    async with self._display_lock:
        secondary = next(
            ((did, info) for did, info in self.display_clients.items()
             if did != "primary" and info.get("width", 0) > 0 and info.get("height", 0) > 0),
            None,
        )
        if secondary is None:
            if self.display_layouts:
                self.display_layouts = {}
                p_w, p_h = self._primary_dims or (self.media_pipeline.width, self.media_pipeline.height)
                # The pipeline dimensions are the primary's authority again.
                self._primary_dims = None
                if IS_WAYLAND:
                    await self._destroy_wayland_secondary_outputs()
                    await wayland_reposition_primary(self._wayland_capture_handle(), 0, 0)
                    self.media_pipeline.capture_region = None
                    if (self.media_pipeline.width, self.media_pipeline.height) != (p_w, p_h):
                        self.media_pipeline.width, self.media_pipeline.height = p_w, p_h
                        if self.media_pipeline.is_media_pipeline_running():
                            await self.media_pipeline.restart_screen_capture()
                    self._broadcast_display_config()
                    return
                # Before the shrink, so no monitor lingers outside the framebuffer.
                await clear_selkies_monitors()
                realized = await resize_display(f"{p_w}x{p_h}")
                if realized:
                    p_w, p_h = realized
                self.media_pipeline.capture_region = None
                self.media_pipeline.width, self.media_pipeline.height = p_w, p_h
                if self.media_pipeline.is_media_pipeline_running():
                    await self.media_pipeline.restart_screen_capture()
                self._push_x11_layout_geometry({"primary": {"w": p_w, "h": p_h}})
            elif self._primary_dims is not None:
                await self._resize_primary_display(
                    "{}x{}".format(*self._primary_dims)
                )
            self._broadcast_display_config()
            return
        did, info = secondary
        await self._wm_swap.ensure_for(len(self.display_clients), IS_WAYLAND)
        if self._primary_dims is None:
            p_w, p_h = self.media_pipeline.width, self.media_pipeline.height
            if IS_WAYLAND:
                # The compositor rejects overlapping outputs, so the offset comes
                # from its realized geometry, not a capture size trailing a resize.
                realized = await self._realized_wayland_dims("primary")
                if realized is not None:
                    p_w, p_h = realized
            if p_w <= 0 or p_h <= 0:
                if IS_WAYLAND:
                    logger.error("Cannot determine primary display size; aborting layout.")
                    return
                curr, _, _, _, _ = await get_new_res("1x1")
                try:
                    p_w, p_h = (int(v) for v in curr.lower().split("x"))
                except (ValueError, AttributeError):
                    logger.error("Cannot determine primary display size; aborting layout.")
                    return
            self._primary_dims = (p_w, p_h)
        position = info.get("position", "right")
        self._primary_dims = clamp_primary_feedback(
            self._primary_dims, self.display_layouts, position
        )
        layouts, total_w, total_h = compute_dual_layout(
            self._primary_dims, (info["width"], info["height"]), position,
        )
        layouts[did] = layouts.pop("secondary")
        if IS_WAYLAND:
            if not await self._apply_wayland_extension(did, layouts):
                await self._drop_wayland_secondary(
                    did, "The compositor cannot create an output for this display."
                )
                return
        else:
            # apply_extended_layout fits `layouts` to the root really produced:
            # kept displays may shrink and an unplaceable one disappears from it.
            requested = {d: (r["w"], r["h"]) for d, r in layouts.items()}
            if (not await apply_extended_layout(layouts, total_w, total_h)
                    or did not in layouts):
                await self._drop_x11_secondary(
                    did, "The X server cannot extend the desktop to fit this display."
                )
                return
            # Write back only what the root cut down: a rectangle the layout
            # itself derived must not feed the next pass as its own input.
            for fitted_id, fitted in layouts.items():
                if (fitted["w"], fitted["h"]) == requested[fitted_id]:
                    continue
                client = self.display_clients.get(fitted_id)
                if client is not None:
                    client["width"], client["height"] = fitted["w"], fitted["h"]
                if fitted_id == "primary":
                    self._primary_dims = (fitted["w"], fitted["h"])
        self.display_layouts = layouts
        p = layouts["primary"]
        if IS_WAYLAND:
            # _apply_wayland_extension already moved the primary output; only a
            # size change needs the capture restart that resizes it.
            if (self.media_pipeline.width, self.media_pipeline.height) != (p["w"], p["h"]):
                await self.media_pipeline.update_capture_region(p["x"], p["y"], p["w"], p["h"])
                await self._push_wayland_realized_geometry("primary", self.media_pipeline)
        else:
            await self.media_pipeline.update_capture_region(p["x"], p["y"], p["w"], p["h"])
        s = layouts[did]
        pipeline = self.display_pipelines.get(did)
        if pipeline is None:
            setting = lambda key: self._display_setting(did, key)
            pipeline = MediaPipelinePixel(
                async_event_loop=asyncio.get_running_loop(),
                encoder=str(setting("encoder")),
                framerate=int(setting("framerate")),
                video_bitrate=int(setting("video_bitrate")),
                audio_enabled=False,
                width=s["w"],
                height=s["h"],
                crf=int(setting("video_crf")),
                video_fullcolor=bool(setting("video_fullcolor")),
                use_cpu=bool(setting("use_cpu")),
                video_streaming_mode=bool(setting("video_streaming_mode")),
                use_paint_over_quality=bool(setting("use_paint_over_quality")),
                video_paintover_crf=int(setting("video_paintover_crf")),
                video_paintover_burst_frames=int(setting("video_paintover_burst_frames")),
                display_id=did,
                capture_region=(s["x"], s["y"]),
            )
            if self.args.enable_rate_control:
                pipeline.rc_mode = RateControlMode(setting("rate_control_mode"))
            else:
                pipeline.rc_mode = self.media_pipeline.rc_mode
            # The scale ladder runs for this display's own screen rather than
            # copying whatever the primary was left with (a no-op field on X11).
            if IS_WAYLAND and self.input_handler is not None:
                pipeline.scale = await self.input_handler.realize_wayland_dpi(
                    getattr(self, "_last_applied_dpi", None)
                    or getattr(settings, "scaling_dpi", 96) or 96,
                    session_screen_index(did), (s["w"], s["h"]))
            else:
                pipeline.scale = getattr(self.media_pipeline, "scale", 1.0)
            # The native-cursor toggle is global across displays.
            pipeline.capture_cursor = self.media_pipeline.capture_cursor
            pipeline.produce_data = (
                lambda buf, pts, kind, _did=did: self.rtc_app.consume_data(buf, pts, kind, _did)
            )
            # pixelflux's cursor-callback slot is process-global (last registration
            # wins), so every display must route cursors into the same sink.
            pipeline.on_cursor_data = self.media_pipeline.on_cursor_data
            pipeline.get_cursor_size_cap = self.media_pipeline.get_cursor_size_cap
            self.display_pipelines[did] = pipeline
            try:
                await pipeline.start_media_pipeline()
            except Exception as e:
                logger.error(f"Secondary display '{did}' pipeline failed to start ({e}); will retry on next reconfigure.")
                self.display_pipelines.pop(did, None)
                if IS_WAYLAND:
                    await self._drop_wayland_secondary(
                        did, "The compositor could not start a capture for this display."
                    )
                return
            if IS_WAYLAND and not await self._wayland_capture_live(did, pipeline):
                last_error = self._wayland_capture_last_error(pipeline, did)
                await self._drop_wayland_secondary(
                    did,
                    last_error or "The compositor could not start a capture for this "
                    "display (encoder session or GPU resources exhausted).",
                )
                return
            if IS_WAYLAND:
                await self._push_wayland_realized_geometry(did, pipeline)
            logger.info(f"Secondary display '{did}' pipeline started at {s}")
        else:
            # A Wayland restart is a full capture reconfigure: skip it when the
            # region is unchanged and the capture is verifiably live.
            unchanged = IS_WAYLAND and (
                (pipeline.width, pipeline.height) == (s["w"], s["h"])
                and pipeline.capture_region == (s["x"], s["y"])
                and await self._wayland_capture_live(did, pipeline)
            )
            if not unchanged:
                await pipeline.update_capture_region(s["x"], s["y"], s["w"], s["h"])
                if IS_WAYLAND:
                    await self._push_wayland_realized_geometry(did, pipeline)
        if not IS_WAYLAND:
            self._push_x11_layout_geometry(layouts)
    self._broadcast_display_config()
paramself

Returns

None
func_push_x11_layout_geometry(self, layouts) -> None

Tell each laid-out display's pages the size the X11 layout pass gave it (websockets parity: the engine broadcasts every display's realized resolution after each pass). The root may not fit the request, and a page whose display the server cut down would otherwise keep its requested size in its manual-mode bookkeeping and re-assert it forever; idempotent on the client for an unchanged size. The Wayland branches push through the compositor's realized geometry instead.

Source Code
def _push_x11_layout_geometry(self, layouts: Dict[str, Dict[str, int]]) -> None:
    """Tell each laid-out display's pages the size the X11 layout pass gave
    it (websockets parity: the engine broadcasts every display's realized
    resolution after each pass). The root may not fit the request, and a
    page whose display the server cut down would otherwise keep its
    requested size in its manual-mode bookkeeping and re-assert it
    forever; idempotent on the client for an unchanged size. The Wayland
    branches push through the compositor's realized geometry instead."""
    if self.rtc_app is None:
        return
    for did, rect in layouts.items():
        w, h = int(rect.get("w", 0)), int(rect.get("h", 0))
        if w > 0 and h > 0:
            self.rtc_app.send_remote_resolution(f"{w}x{h}", did)
paramself
paramlayoutsDict[str, Dict[str, int]]

Returns

None
func_broadcast_display_config(self) -> None

Tell every connected page which displays are attached (websockets parity: the primary page forces browser-cursor rendering while a secondary is connected, keyed off this broadcast).

Source Code
def _broadcast_display_config(self) -> None:
    """Tell every connected page which displays are attached (websockets
    parity: the primary page forces browser-cursor rendering while a
    secondary is connected, keyed off this broadcast)."""
    if not self.rtc_app:
        return
    displays = ["primary"] + [d for d in self.display_clients.keys() if d != "primary"]
    self.rtc_app.send_media_data_over_channel(
        "display_config_update", {"displays": displays}
    )
paramself

Returns

None
func_update_cursor_cap(self, dpi_value) -> None

Scale the remote-cursor delivery cap with the DPI and push it to every running capture (pixelflux applies cursor_size_cap live through update_tunables; later (re)starts read it through CaptureSettings).

Source Code
def _update_cursor_cap(self, dpi_value: float) -> None:
    """Scale the remote-cursor delivery cap with the DPI and push it to
    every running capture (pixelflux applies cursor_size_cap live through
    update_tunables; later (re)starts read it through CaptureSettings)."""
    ih = self.input_handler
    if ih is None:
        return
    try:
        ih.system_dpi = float(dpi_value)
        ih.cursor_size_cap = int(ih.max_cursor_size * float(dpi_value) / 96.0)
    except Exception as e:
        logger.debug(f"cursor cap update skipped: {e}")
        return
    updated = 0
    for did, pipeline in list(self.display_pipelines.items()):
        module = getattr(pipeline, "capture_module", None)
        if pipeline is None or module is None or not pipeline.is_media_pipeline_running():
            continue
        try:
            module.update_tunables(pipeline.generate_capture_settings())
            updated += 1
        except Exception as e:
            logger.debug(f"Live cursor cap update skipped for '{did}': {e}")
    logger.info(f"Cursor size cap {ih.cursor_size_cap}px for DPI {dpi_value} "
                f"({updated} live capture(s) updated).")
paramself
paramdpi_valuefloat

Returns

None
funchandle_scaling(self, dpi_value) -> None

Apply a client DPI sync to the desktop (X11 xrdb/cursor themes) or run the per-display Wayland scale ladder.

Fractional DPI is legal on the shared verb (websockets parity); the desktop DPI property itself is integral. Bounded by the declared scaling_dpi span so a client cannot drive xrdb — and, with it, the cursor size and the Wayland compositor scale — to an arbitrary value. An operator-set DPI (CLI/env) governs the desktop and is never clobbered by a client sync. Idempotent: the dashboard and the core each re-assert their DPI on settings broadcasts, and every apply churns xrdb, xsettingsd SIGHUP and cursor themes. On Wayland the DPI runs the scale ladder per display: the session compositor scales the screen backing it, and only what it leaves becomes that display's capture scale, whose change restarts the capture (the WS path threads the same scale through CaptureSettings).

Source Code
async def handle_scaling(self, dpi_value: float) -> None:
    """Apply a client DPI sync to the desktop (X11 xrdb/cursor themes) or
    run the per-display Wayland scale ladder.

    Fractional DPI is legal on the shared verb (websockets parity); the
    desktop DPI property itself is integral. Bounded by the declared
    scaling_dpi span so a client cannot drive xrdb — and, with it, the
    cursor size and the Wayland compositor scale — to an arbitrary value.
    An operator-set DPI (CLI/env) governs the desktop and is never
    clobbered by a client sync. Idempotent: the dashboard and the core
    each re-assert their DPI on settings broadcasts, and every apply churns
    xrdb, xsettingsd SIGHUP and cursor themes. On Wayland the DPI runs the
    scale ladder per display: the session compositor scales the screen
    backing it, and only what it leaves becomes that display's capture
    scale, whose change restarts the capture (the WS path threads the same
    scale through CaptureSettings).
    """
    try:
        dpi_value = min(SCALING_DPI_MAX,
                        max(SCALING_DPI_MIN, int(round(float(dpi_value)))))
    except (TypeError, ValueError, OverflowError):
        logger.warning(f"Ignoring malformed DPI sync: {dpi_value!r}")
        return
    if settings._overridden.get("scaling_dpi", False):
        logger.info("Ignoring client DPI sync: scaling_dpi is operator-overridden.")
        return
    if getattr(self, "_last_applied_dpi", None) == int(dpi_value):
        logger.debug(f"DPI already {int(dpi_value)}; skipping re-apply.")
        return
    if not IS_WAYLAND:
        if await set_dpi(int(dpi_value)):
            self._last_applied_dpi = int(dpi_value)
            logger.info(f"Successfully set DPI to {dpi_value}")
        else:
            logger.error(f"Failed to set DPI to {dpi_value}")

    # Before the Wayland restarts below, which read the cap through
    # CaptureSettings; a compositor that absorbs the scale restarts nothing.
    self._update_cursor_cap(dpi_value)

    if IS_WAYLAND:
        self._last_applied_dpi = int(dpi_value)
        for did, pipeline in list(self.display_pipelines.items()):
            if pipeline is None:
                continue
            new_scale = (await self.input_handler.realize_wayland_dpi(
                dpi_value, session_screen_index(did),
                (pipeline.width, pipeline.height))
                if self.input_handler else float(dpi_value) / 96.0)
            if pipeline.scale == new_scale:
                continue
            pipeline.scale = new_scale
            if pipeline.is_media_pipeline_running():
                await pipeline.restart_screen_capture()
                await self._push_wayland_realized_geometry(did, pipeline)
        await self._apply_wayland_cursor_size(dpi_value)
        return

    if CURSOR_SIZE is None:
        # Auto: only the DPI itself is applied.
        return

    new_cursor_size = cursor_size_for_dpi(dpi_value, CURSOR_SIZE)

    logger.info(
        f"Attempting to set cursor size to: {new_cursor_size} (based on DPI {dpi_value})"
    )
    if await set_cursor_size(new_cursor_size):
        logger.info(f"Successfully set cursor size to {new_cursor_size}")
    else:
        logger.error(f"Failed to set cursor size to {new_cursor_size}")
paramself
paramdpi_valuefloat

Returns

None
funchandle_system_monitor(self, t) -> None

System-monitor tick: push CPU/memory stats and a ping to clients, and recover the audio capture if its worker died since the last tick.

pcmflux reports a clean start while its worker is still coming up, so a device that dies during bring-up (or later) only shows through last_error; polling it here cycles the audio capture. Audio lives on the primary pipeline; the call no-ops on the audio-less secondaries.

Source Code
async def handle_system_monitor(self, t: float) -> None:
    """System-monitor tick: push CPU/memory stats and a ping to clients,
    and recover the audio capture if its worker died since the last tick.

    pcmflux reports a clean start while its worker is still coming up, so a
    device that dies during bring-up (or later) only shows through
    `last_error`; polling it here cycles the audio capture. Audio lives on
    the primary pipeline; the call no-ops on the audio-less secondaries.
    """
    if self.input_handler and self.rtc_app and self.system_monitor:
        self.input_handler.ping_start = t
        self.rtc_app.send_system_stats(
            self.system_monitor.cpu_percent,
            self.system_monitor.mem_total,
            self.system_monitor.mem_used,
        )
        self.rtc_app.send_ping(t)
    if self.media_pipeline is not None:
        try:
            await self.media_pipeline.recover_audio_if_failed()
        except Exception as e:
            logger.debug(f"audio health poll skipped: {e}")
paramself
paramtfloat

Returns

None
funchandle_gpu_stats(self, load, memory_total, memory_used) -> None

GPU-monitor tick: push GPU stats to clients and into metrics.

Source Code
async def handle_gpu_stats(
    self, load: float, memory_total: int, memory_used: int
) -> None:
    """GPU-monitor tick: push GPU stats to clients and into metrics."""
    if self.rtc_app:
        self.rtc_app.send_gpu_stats(load, memory_total, memory_used)
    if self.metrics:
        self.metrics.set_gpu_utilization(load * 100)
paramself
paramloadfloat
parammemory_totalint
parammemory_usedint

Returns

None
func_seed_display_settings(self, entry) -> None

Give a joining secondary display its own copy of every client-tunable video setting, taken from the args at join time (websockets parity: a display registers with a full seeded snapshot). Sharing the args instead would make a later primary change move what the secondary reports as its current value while its stream keeps running the old one, and the equality guards on its own controls would then compare against a value that display never ran.

Source Code
def _seed_display_settings(self, entry: Dict[str, Any]) -> None:
    """Give a joining secondary display its own copy of every client-tunable
    video setting, taken from the args at join time (websockets parity: a
    display registers with a full seeded snapshot). Sharing the args instead
    would make a later primary change move what the secondary reports as its
    current value while its stream keeps running the old one, and the equality
    guards on its own controls would then compare against a value that display
    never ran."""
    for key in list(self._VIDEO_SETTING_APPLIERS) + ["force_aligned_resolution"]:
        if key in entry:
            continue
        value = getattr(self.args, key, None)
        if value is not None:
            entry[key] = value
paramself
paramentryDict[str, Any]

Returns

None
func_display_setting(self, display_id, key) -> Any

A display's current value for a client-tunable setting: the primary reads the service args; a secondary reads its own stored overrides, falling back to the args it was seeded from (websockets model: each display's SETTINGS payload configures only that display's stream).

Source Code
def _display_setting(self, display_id: str, key: str) -> Any:
    """A display's current value for a client-tunable setting: the primary
    reads the service args; a secondary reads its own stored overrides,
    falling back to the args it was seeded from (websockets model: each
    display's SETTINGS payload configures only that display's stream)."""
    if display_id != "primary":
        entry = self.display_clients.get(display_id)
        if entry is not None and key in entry:
            return entry[key]
    return getattr(self.args, key, None)
paramself
paramdisplay_idstr
paramkeystr

Returns

typing.Any
func_store_display_setting(self, display_id, key, value) -> None

Record a setting as the display's current value (args for the primary, the display's own entry for a secondary).

Source Code
def _store_display_setting(self, display_id: str, key: str, value: Any) -> None:
    """Record a setting as the display's current value (args for the
    primary, the display's own entry for a secondary)."""
    if display_id == "primary":
        setattr(self.args, key, value)
    else:
        entry = self.display_clients.get(display_id)
        if entry is not None:
            entry[key] = value
paramself
paramdisplay_idstr
paramkeystr
paramvalueAny

Returns

None
func_apply_display_setting(self, display_id, key, value) -> None

Store one video setting as the display's current value and apply it to the display's live pipeline when it exists (a secondary that has not been laid out yet picks the stored value up at pipeline creation).

The primary's encoder is written through to the settings singleton: transport services re-seed from it on a mode switch, so a client pick must live there to survive the trip, and _encoder_client_set marks a fresh pick during the webrtc leg, which outranks any stashed pre-clamp value on the switch back. The RTCApp's global encoder is kept current too: it is the default codec/munge choice for connections created later, while secondaries resolve per display.

Source Code
async def _apply_display_setting(self, display_id: str, key: str, value: Any) -> None:
    """Store one video setting as the display's current value and apply it to
    the display's live pipeline when it exists (a secondary that has not been
    laid out yet picks the stored value up at pipeline creation).

    The primary's encoder is written through to the settings singleton:
    transport services re-seed from it on a mode switch, so a client pick
    must live there to survive the trip, and `_encoder_client_set` marks a
    fresh pick during the webrtc leg, which outranks any stashed pre-clamp
    value on the switch back. The RTCApp's global encoder is kept current
    too: it is the default codec/munge choice for connections created
    later, while secondaries resolve per display.
    """
    applier = self._VIDEO_SETTING_APPLIERS.get(key)
    if applier is None:
        return
    self._store_display_setting(display_id, key, value)
    pipeline = self.display_pipelines.get(display_id)
    if pipeline is not None:
        await applier(pipeline, value)
    if key == "encoder" and display_id == "primary":
        self.settings.encoder = str(value)
        self.settings._encoder_client_set = True
        if self.rtc_app:
            self.rtc_app.encoder = str(value)
paramself
paramdisplay_idstr
paramkeystr
paramvalueAny

Returns

None
func_encoder_for_display(self, display_id) -> str
Source Code
def _encoder_for_display(self, display_id: str) -> str:
    return str(self._display_setting(display_id, "encoder") or self.args.encoder)
paramself
paramdisplay_idstr

Returns

str
func_fullcolor_for_display(self, display_id) -> bool
Source Code
def _fullcolor_for_display(self, display_id: str) -> bool:
    return bool(self._display_setting(display_id, "video_fullcolor"))
paramself
paramdisplay_idstr

Returns

bool
func_use_cpu_for_display(self, display_id) -> bool
Source Code
def _use_cpu_for_display(self, display_id: str) -> bool:
    return bool(self._display_setting(display_id, "use_cpu"))
paramself
paramdisplay_idstr

Returns

bool
funchandle_update_settings(self, settings_json, display_id='primary') -> None

Apply a client SETTINGS payload to the display that sent it.

Every allowed entry needs server-side backing: a live setter dispatched below, or state the server reads later (the manual-resolution trio feeds the start-time resize and resolution policy; the live resize itself rides the r, input message). Video keys apply to the SENDING display only (websockets model); audio and the clipboard policy are stream-global whichever display asserts them. A scaling_dpi in the primary's payload runs handle_scaling (websockets parity: the client seeds its DPR-derived value into its very first payload, so the right scale lands on the first sync rather than the dashboard's later correction); a secondary's displayPosition may move it to any side of the primary after joining.

Source Code
async def handle_update_settings(
    self, settings_json: Dict[str, Any], display_id: str = "primary"
) -> None:
    """Apply a client SETTINGS payload to the display that sent it.

    Every allowed entry needs server-side backing: a live setter dispatched
    below, or state the server reads later (the manual-resolution trio feeds
    the start-time resize and resolution policy; the live resize itself
    rides the `r,` input message). Video keys apply to the SENDING display
    only (websockets model); audio and the clipboard policy are
    stream-global whichever display asserts them. A `scaling_dpi` in the
    primary's payload runs `handle_scaling` (websockets parity: the client
    seeds its DPR-derived value into its very first payload, so the right
    scale lands on the first sync rather than the dashboard's later
    correction); a secondary's `displayPosition` may move it to any side
    of the primary after joining.
    """
    settings_allowed_to_update = [
        "rate_control_mode",
        "video_crf",
        "video_bitrate",
        "audio_bitrate",
        "framerate",
        "use_cpu",
        "enable_binary_clipboard",
        "manual_resolution",
        "manual_width",
        "manual_height",
        "force_aligned_resolution",
        "encoder",
        "video_fullcolor",
        "video_streaming_mode",
        "use_paint_over_quality",
        "video_paintover_crf",
        "video_paintover_burst_frames",
    ]
    # Startup resolution policy, not a per-stream tunable.
    primary_only_keys = ("manual_resolution", "manual_width", "manual_height")

    display_id = display_id or "primary"
    if display_id != "primary" and display_id not in self.display_clients:
        logger.warning(
            f"Ignoring settings for unknown display '{display_id}' (not connected)."
        )
        return

    # Seat-global hint: base-layout push on Wayland, informational on X11.
    kb_layout = settings_json.get("keyboardLayout")
    if kb_layout and self.input_handler is not None:
        await self.input_handler.apply_client_keyboard_layout(kb_layout)

    dpi_val = settings_json.get("scaling_dpi")
    if dpi_val is not None and display_id == "primary":
        try:
            await self.handle_scaling(float(dpi_val))
        except (TypeError, ValueError):
            logger.warning(f"Ignoring malformed scaling_dpi in SETTINGS: {dpi_val!r}")

    def sanitize_value(name: str, client_value: Any) -> Any:
        """One-transport wrapper over the shared sanitizer (settings.py)."""
        return sanitize_client_setting(name, client_value, self.settings, logger)

    new_position = settings_json.get("displayPosition")
    if new_position is not None and display_id != "primary":
        new_position = str(new_position)
        if new_position not in ("right", "left", "up", "down"):
            logger.warning(f"Ignoring invalid displayPosition from '{display_id}': {new_position!r}")
        else:
            entry = self.display_clients.get(display_id)
            if entry is not None and entry.get("position", "right") != new_position:
                entry["position"] = new_position
                await self.reconfigure_displays()

    for key in settings_allowed_to_update:
        client_value = settings_json.get(key)
        if client_value is None:
            continue
        if key == "rate_control_mode" and self.args.enable_rate_control is False:
            logger.debug(
                f"Server has rate control disabled. Ignoring update for '{key}'."
            )
            continue
        if key in primary_only_keys and display_id != "primary":
            continue
        if getattr(self.args, key, None) is None:
            logger.warning(f"Received unknown setting '{key}' from client")
            continue
        current_value = self._display_setting(display_id, key)
        sanitized_value = sanitize_value(key, client_value)
        if sanitized_value is None or sanitized_value == current_value:
            continue
        if key == "audio_bitrate":
            # Audio lives only on the primary pipeline.
            if self.media_pipeline:
                await self.media_pipeline.set_audio_bitrate(int(sanitized_value))
            setattr(self.args, key, sanitized_value)
        elif key == "enable_binary_clipboard":
            await self.input_handler.update_binary_clipboard_setting(sanitized_value)
            setattr(self.args, key, sanitized_value)
        elif key in self._VIDEO_SETTING_APPLIERS:
            await self._apply_display_setting(display_id, key, sanitized_value)
        else:
            # No live setter: stored for the resize paths / startup to read.
            self._store_display_setting(display_id, key, sanitized_value)
        logger.debug(
            f"Updated setting '{key}' for display '{display_id}' from {current_value} to {sanitized_value}"
        )
paramself
paramsettings_jsonDict[str, Any]
paramdisplay_idstr
= 'primary'

Returns

None
funcmon_rtc_config(self, stun_servers, turn_servers, rtc_config) -> None

Monitor callback: fan a refreshed RTC config out to the signaling server (for clients) and the RTC app (for its own ICE agents).

Source Code
def mon_rtc_config(
    self, stun_servers: List[str], turn_servers: List[str], rtc_config: Any
) -> None:
    """Monitor callback: fan a refreshed RTC config out to the signaling
    server (for clients) and the RTC app (for its own ICE agents)."""
    if self.peer_manager:
        logger.debug("updating signaling server RTC config")
        self.peer_manager.set_rtc_config(rtc_config)
    if self.rtc_app:
        logger.debug("updating STUN/TURN servers in RTC app")
        self.rtc_app.update_rtc_config(stun_servers, turn_servers)
paramself
paramstun_serversList[str]
paramturn_serversList[str]
paramrtc_configAny

Returns

None
func_ensure_pacer(self, pc, peer, display_id) -> Optional[Any]

Ensure the per-transport packet pacer is enabled/configured (called from the congestion loop; idempotent and cheap).

Encoder ceiling: the display's configured video bitrate, CBR or not. The shared DTLS transport is reachable via pc.sctp only once the data-channel m-line is negotiated, while media (and TWCC estimates) can flow before that, so any transceiver's sender transport — the same shared RTCDtlsTransport — serves as the fallback. The IDR floor is bootstrapped from the session-start keyframe: on a late attach, waiting for the next natural IDR would start it at 0 and reset on the first real burst.

Source Code
def _ensure_pacer(self, pc: Any, peer: Dict[str, Any], display_id: str) -> Optional[Any]:
    """Ensure the per-transport packet pacer is enabled/configured (called
    from the congestion loop; idempotent and cheap).

    Encoder ceiling: the display's configured video bitrate, CBR or not.
    The shared DTLS transport is reachable via `pc.sctp` only once the
    data-channel m-line is negotiated, while media (and TWCC estimates)
    can flow before that, so any transceiver's sender transport — the same
    shared RTCDtlsTransport — serves as the fallback. The IDR floor is
    bootstrapped from the session-start keyframe: on a late attach, waiting
    for the next natural IDR would start it at 0 and reset on the first
    real burst.

    Returns:
        The DTLS transport (so callers can snapshot its pacer), or None
        when it is not available yet.
    """
    transport = getattr(getattr(pc, "sctp", None), "transport", None)
    if transport is None:
        for tr in pc.getTransceivers() or []:
            transport = getattr(getattr(tr, "sender", None), "transport", None)
            if transport is not None:
                break
    if transport is None:
        return None
    lo_kbps, hi_kbps = settings.video_bitrate
    enc_kbps = float(self._display_setting(display_id, "video_bitrate") or hi_kbps)
    enc_bps = int(max(lo_kbps, min(hi_kbps, enc_kbps)) * 1000)
    if not transport.pacer_enabled():
        vsender = None
        for tr in pc.getTransceivers() or []:
            if getattr(tr, "kind", None) == "video":
                vsender = tr.sender
                break
        transport.enable_pacer(
            encoder_bps=enc_bps,
            # Must hit the encoder pipeline: video rides the pre-encoded pack()
            # path, where the sender's __force_keyframe flag is silently ignored.
            request_keyframe=lambda did_=display_id: asyncio.ensure_future(
                self.request_idr_for_display(did_)),
        )
        kf_bytes = getattr(vsender, "_keyframe_bytes", None)
        if kf_bytes:
            transport.note_video_keyframe(
                kf_bytes, natural=getattr(vsender, "_keyframe_natural", True))
        if transport.pacer_enabled():
            logger.info(
                f"WebRTC pacer enabled for display '{display_id}' "
                f"(encoder ceiling {enc_bps // 1000} kbps)")
    else:
        transport.set_pacer_encoder_bps(enc_bps)
    return transport
paramself
parampcAny
parampeerDict[str, Any]
paramdisplay_idstr

Returns

typing.Optional

The DTLS transport (so callers can snapshot its pacer), or None

func_congestion_control_loop(self) -> None

GCC-style bitrate adaptation from transport-wide-cc receiver feedback: per display, follow the slowest of ITS peers' goodput estimates with headroom, back off multiplicatively on loss, and retarget that display's encoder within the allowed video_bitrate range — one display's congested link never steers another's stream. Only CBR mode has a target to steer.

The user-selected bitrate is the ceiling: control only backs off below it and recovers up to it. Clamping to the allowed range instead let a fast local segment ramp an 8000 kbps session to 80000+ kbps, saturating the real path (TURN/WAN) with queuing lag and loss-corrupted frames. Damage-gated encoders are application-limited, so measured goodput is merely what was sent (an idle screen reads ~0), not link capacity: it may lift the target when it shows real headroom but never drags it down; otherwise the target recovers multiplicatively toward the ceiling after a loss backoff. The pacer rides the same tick but is configured for every peer before the feedback gate, since it must run on links that never send transport-cc; this is its only configuration path, so one bad peer must never kill the loop.

Source Code
async def _congestion_control_loop(self) -> None:
    """GCC-style bitrate adaptation from transport-wide-cc receiver feedback:
    per display, follow the slowest of ITS peers' goodput estimates with
    headroom, back off multiplicatively on loss, and retarget that display's
    encoder within the allowed video_bitrate range — one display's congested
    link never steers another's stream. Only CBR mode has a target to steer.

    The user-selected bitrate is the ceiling: control only backs off below
    it and recovers up to it. Clamping to the allowed range instead let a
    fast local segment ramp an 8000 kbps session to 80000+ kbps, saturating
    the real path (TURN/WAN) with queuing lag and loss-corrupted frames.
    Damage-gated encoders are application-limited, so measured goodput is
    merely what was sent (an idle screen reads ~0), not link capacity: it
    may lift the target when it shows real headroom but never drags it
    down; otherwise the target recovers multiplicatively toward the
    ceiling after a loss backoff. The pacer rides the same tick but is
    configured for every peer before the feedback gate, since it must run
    on links that never send transport-cc; this is its only configuration
    path, so one bad peer must never kill the loop.
    """
    lo_kbps, hi_kbps = settings.video_bitrate
    logger.info(
        f"Congestion control loop started (CBR only, range {lo_kbps}-{hi_kbps} kbps)."
    )
    # No getattr default: a misnamed setting must raise, not disable the pacer.
    pacer_on = bool(settings.webrtc_pacer[0])
    logger.info(f"WebRTC pacer setting: {'ON' if pacer_on else 'OFF'}.")
    while True:
        await asyncio.sleep(1.0)
        rtc_app = self.rtc_app
        if not rtc_app:
            continue
        per_display: Dict[str, Dict[str, Any]] = {}
        for peer in rtc_app.peer_connections.values():
            pc = peer.get("peer_conn")
            did = peer.get("display_id", "primary") or "primary"
            if pacer_on:
                try:
                    dtls = self._ensure_pacer(pc, peer, did)
                    if self.metrics is not None and dtls is not None:
                        self.metrics.set_pacer_snapshot(did, dtls.pacer_snapshot())
                except Exception:
                    logger.exception("_ensure_pacer failed (display %s)", did)
            sctp = getattr(pc, "sctp", None)
            estimate = getattr(getattr(sctp, "transport", None), "twcc_estimate", None)
            if not estimate:
                continue
            bucket = per_display.setdefault(
                did, {"goodputs": [], "worst_loss": 0.0})
            if estimate.get("goodput_bps"):
                bucket["goodputs"].append(estimate["goodput_bps"])
            bucket["worst_loss"] = max(bucket["worst_loss"], estimate.get("loss_fraction", 0.0))
        for did, bucket in per_display.items():
            if not self.args.congestion_control:
                continue
            pipeline = self.display_pipelines.get(did)
            if (
                pipeline is None
                or getattr(pipeline, "rc_mode", None) != RateControlMode.CBR
            ):
                continue
            goodputs, worst_loss = bucket["goodputs"], bucket["worst_loss"]
            if not goodputs:
                continue
            current = float(pipeline.video_bitrate)
            ceiling = float(self._display_setting(did, "video_bitrate") or hi_kbps)
            ceiling = max(lo_kbps, min(hi_kbps, ceiling))
            if worst_loss > 0.10:
                target = current * 0.7
            else:
                # Goodput may lift the target, never drag it down (see docstring).
                target = min(ceiling, max(current * 1.15, min(goodputs) * 0.85 / 1_000))
            target = round(max(lo_kbps, min(ceiling, target)))
            if target != round(current):
                logger.info(
                    f"Congestion control[{did}]: video bitrate {current:.0f} -> {target:.0f} kbps "
                    f"(goodput {min(goodputs) / 1e3:.1f} kbps, loss {worst_loss:.1%})"
                )
                await pipeline.set_video_bitrate(target)
paramself

Returns

None
funcstart_components(self) -> None

Start the background tasks: input/clipboard/cursor workers, startup DPI and cursor size, the congestion/pacer loop, the monitors, the signaling client, and the configured TURN credential refreshers.

The configured desktop DPI is applied at startup so the first session sees it before any client syncs its own (96 is unity, so nothing is applied then); on Wayland it becomes the session compositor's output scale, and with none up yet the input handler re-applies when it adopts one. An explicit cursor size goes to the X server here (handle_scaling re-derives it on DPI changes); Wayland gets it via CaptureSettings. Logical monitors describe the layout of whichever transport defined them, and a live switch leaves the previous one's behind — a desktop keeps tiling against a stale rectangle — so on X11 they are cleared; this service defines its own when a second display arrives and needs none for a single one.

Source Code
async def start_components(self) -> None:
    """Start the background tasks: input/clipboard/cursor workers, startup
    DPI and cursor size, the congestion/pacer loop, the monitors, the
    signaling client, and the configured TURN credential refreshers.

    The configured desktop DPI is applied at startup so the first session
    sees it before any client syncs its own (96 is unity, so nothing is
    applied then); on Wayland it becomes the session compositor's output
    scale, and with none up yet the input handler re-applies when it
    adopts one. An explicit cursor size goes to the X server here
    (`handle_scaling` re-derives it on DPI changes); Wayland gets it via
    CaptureSettings. Logical monitors describe the layout of whichever
    transport defined them, and a live switch leaves the previous one's
    behind — a desktop keeps tiling against a stale rectangle — so on X11
    they are cleared; this service defines its own
    when a second display arrives and needs none for a single one.
    """
    if self.input_handler:
        self.tasks.append(asyncio.create_task(self.input_handler.connect()))
        self.tasks.append(asyncio.create_task(self.input_handler.start_clipboard()))
        self.tasks.append(asyncio.create_task(self.input_handler.probe_apps_runner()))
        self.tasks.append(
            asyncio.create_task(self.input_handler.start_cursor_monitor())
        )

    startup_dpi = int(float(getattr(settings, "scaling_dpi", "96") or 96))
    if startup_dpi != 96:
        if IS_WAYLAND:
            if self.input_handler is not None:
                await self.input_handler.realize_wayland_dpi(startup_dpi)
            self._last_applied_dpi = startup_dpi
        elif await set_dpi(startup_dpi):
            self._last_applied_dpi = startup_dpi

    if not IS_WAYLAND and settings.cursor_size > 0:
        initial_dpi = float(getattr(settings, "scaling_dpi", "96"))
        await set_cursor_size(cursor_size_for_dpi(initial_dpi, CURSOR_SIZE))

    if not IS_WAYLAND:
        await clear_selkies_monitors()

    # The pacer rides the congestion loop's tick but is gated on its own flag.
    if self.args.congestion_control or bool(settings.webrtc_pacer[0]):
        self.tasks.append(asyncio.create_task(self._congestion_control_loop()))

    if self.gpu_monitor:
        self.gpu_monitor.start()
    if self.system_monitor:
        self.system_monitor.start()
    if self.signaling_client:
        self.signaling_client.start()

    if self.monitoring_utils_used:
        turn_rest_username = self.args.turn_rest_username.replace(":", "-")
        if self.monitoring_utils_used.get("using_hmac_turn", False):
            self.mon_hmac_turn = HMACRTCMonitor(
                turn_host=self.args.turn_host,
                turn_port=self.args.turn_port,
                turn_shared_secret=self.args.turn_shared_secret,
                turn_username=turn_rest_username,
                turn_protocol=self.args.turn_protocol,
                turn_tls=self.args.turn_tls,
                stun_host=self.args.stun_host,
                stun_port=self.args.stun_port,
                period=60,
                enabled=True,
            )
            self.mon_hmac_turn.on_rtc_config = self.mon_rtc_config
            self.mon_hmac_turn.start()
        if self.monitoring_utils_used.get("using_rest_api", False):
            self.mon_rest_api = RESTRTCMonitor(
                turn_rest_uri=self.args.turn_rest_uri,
                turn_rest_username=turn_rest_username,
                turn_rest_username_auth_header=self.args.turn_rest_username_auth_header,
                turn_protocol=self.args.turn_protocol,
                turn_rest_protocol_header=self.args.turn_rest_protocol_header,
                turn_tls=self.args.turn_tls,
                turn_rest_tls_header=self.args.turn_rest_tls_header,
                turn_api_key=self.args.turn_rest_api_key,
                period=60,
                enabled=True,
            )
            self.mon_rest_api.on_rtc_config = self.mon_rtc_config
            self.mon_rest_api.start()
        if self.monitoring_utils_used.get("using_rtc_config_json", False):
            self.mon_rtc_config_file = RTCConfigFileMonitor(
                rtc_file=self.args.rtc_config_json, enabled=True
            )
            self.mon_rtc_config_file.on_rtc_config = self.mon_rtc_config
            await self.mon_rtc_config_file.start()
        if self.monitoring_utils_used.get("using_cloudflare_turn", False):
            self.mon_cloudflare_turn = CloudflareRTCMonitor(
                turn_token_id=self.args.cloudflare_turn_token_id,
                api_token=self.args.cloudflare_turn_api_token,
                enabled=True,
            )
            self.mon_cloudflare_turn.on_rtc_config = self.mon_rtc_config
            self.mon_cloudflare_turn.start()
paramself

Returns

None
funcshutdown(self) -> None

Gracefully shutdown all components.

Source Code
async def shutdown(self) -> None:
    """Gracefully shutdown all components."""
    if self._shutdown_called:
        logger.info("Shutdown already called, skipping")
        return
    self._shutdown_called = True
    logger.info("Starting shutdown sequence")

    self._cancel_primary_stop_grace()
    for task in list(self.tasks):
        try:
            if not task.done():
                task.cancel()
        except Exception:
            logger.exception("Error cancelling task during shutdown")

    async def _await_with_timeout(
        coro: Awaitable[Any], name: str, timeout: float = 3.0
    ) -> Optional[Any]:
        """Await one component's stop with a timeout, swallowing every
        error so one wedged component cannot abort the whole shutdown."""
        try:
            return await asyncio.wait_for(coro, timeout=timeout)
        except asyncio.TimeoutError:
            logger.warning(
                f"Timeout while waiting for {name} to stop (after {timeout}s)"
            )
        except asyncio.CancelledError:
            logger.info(f"{name} was cancelled during shutdown")
        except Exception as e:
            logger.exception(f"Error while stopping {name}: {e}")
        return None

    try:
        await asyncio.wait_for(
            asyncio.gather(*self.tasks, return_exceptions=True), timeout=5.0
        )
    except asyncio.TimeoutError:
        logger.warning(
            "Some background tasks did not exit within timeout; continuing with component shutdown"
        )
    except Exception:
        logger.exception("Unexpected error while awaiting background tasks")

    stop_coros = []
    if self.signaling_client:
        stop_coros.append(
            (
                _await_with_timeout(
                    self.signaling_client.stop(), "signaling_client", 3.0
                )
            )
        )
    for display_id, pipeline in list(self.display_pipelines.items()):
        stop_coros.append(
            (
                _await_with_timeout(
                    pipeline.stop_media_pipeline(), f"media_pipeline[{display_id}]", 3.0
                )
            )
        )
    if self.media_pipeline and "primary" not in self.display_pipelines:
        stop_coros.append(
            (
                _await_with_timeout(
                    self.media_pipeline.stop_media_pipeline(), "media_pipeline", 3.0
                )
            )
        )
    if self.rtc_app:
        stop_coros.append(
            (
                _await_with_timeout(
                    self.rtc_app.stop_all_rtc_connections(), "rtc_app", 3.0
                )
            )
        )
    stop_coros.append(
        _await_with_timeout(
            self._teardown_webrtc_virtual_mic(), "webrtc_virtual_mic", 3.0
        )
    )
    if self.input_handler:
        try:
            self.input_handler.stop_clipboard()
        except Exception:
            logger.exception("Error stopping clipboard monitor")
        try:
            self.input_handler.stop_cursor_monitor()
        except Exception:
            logger.exception("Error stopping cursor monitor")
        stop_coros.append(
            (
                _await_with_timeout(
                    self.input_handler.disconnect(), "input_handler.disconnect", 3.0
                )
            )
        )

    if self.gpu_monitor:
        stop_coros.append(
            (_await_with_timeout(self.gpu_monitor.stop(), "gpu_monitor", 2.0))
        )
    if self.system_monitor:
        stop_coros.append(
            (_await_with_timeout(self.system_monitor.stop(), "system_monitor", 2.0))
        )

    if self.mon_hmac_turn:
        stop_coros.append(
            (
                _await_with_timeout(
                    self.mon_hmac_turn.stop(), "HMAC RTC Monitor", 2.0
                )
            )
        )
    if self.mon_rest_api:
        stop_coros.append(
            (_await_with_timeout(self.mon_rest_api.stop(), "REST RTC Monitor", 2.0))
        )
    if self.mon_rtc_config_file:
        stop_coros.append(
            (
                _await_with_timeout(
                    self.mon_rtc_config_file.stop(), "RTC Config File Monitor", 2.0
                )
            )
        )
    if self.mon_cloudflare_turn:
        stop_coros.append(
            (
                _await_with_timeout(
                    self.mon_cloudflare_turn.stop(), "Cloudflare TURN RTC Monitor", 2.0
                )
            )
        )

    if stop_coros:
        try:
            await asyncio.wait_for(
                asyncio.gather(*stop_coros, return_exceptions=True), timeout=5
            )
        except asyncio.TimeoutError:
            logger.warning(
                "Component shutdown exceeded global timeout; some components may still be cleaning up"
            )
        except Exception:
            logger.exception(
                "Unexpected error during concurrent component shutdown"
            )
    if self.metrics:
        try:
            # unregister() drains the CSV executor with shutdown(wait=True).
            await asyncio.to_thread(self.metrics.unregister)
        except Exception as e:
            logger.exception(f"Error unregistering metrics: {e}")

    self.tasks.clear()

    self.signaling_client = None
    self.media_pipeline = None
    self.rtc_app = None
    self.input_handler = None
    self.system_monitor = None
    self.gpu_monitor = None
    self.metrics = None
    self.mon_hmac_turn = None
    self.mon_rest_api = None
    self.mon_rtc_config_file = None

    logger.info("Shutdown complete")
paramself

Returns

None
funcrun(self) -> None

Bring the service up and block until the shutdown event fires.

Source Code
async def run(self) -> None:
    """Bring the service up and block until the shutdown event fires."""
    self._shutdown_called = False
    try:
        _install_webrtc_teardown_noise_filters(asyncio.get_running_loop())
        await self.initialize_components()
        self.setup_callbacks()

        await self.start_components()
        await self.shutdown_event.wait()

    except asyncio.CancelledError:
        logger.info("Received webrtc stream mode shutdown")
    except Exception as e:
        logger.critical(f"Fatal error: {e}", exc_info=True)
        sys.exit(1)
    finally:
        await self.shutdown()
paramself

Returns

None
funcstart(self) -> None

Supervisor entry point: run the service until stop() is called.

Source Code
async def start(self) -> None:
    """Supervisor entry point: run the service until stop() is called."""
    self.shutdown_event.clear()
    await self.run()
paramself

Returns

None
funcstop(self) -> None

Signal run() to exit and shut the service down.

Source Code
async def stop(self) -> None:
    """Signal run() to exit and shut the service down."""
    self.shutdown_event.set()
paramself

Returns

None
funcregister_routes(self, api_prefix, main_router) -> None

Register the WebRTC HTTP/WebSocket endpoints.

Every endpoint lives under /api so the single nginx /api proxy rule fronts them — the signaling socket included, since that location forwards WebSocket upgrades. Paths off /api (such as a bare /turn or /webrtc/signaling) would sit unproxied behind the LSIO nginx: the TURN fetch and the signaling handshake would 404 and freeze the dashboard.

Source Code
def register_routes(self, api_prefix: str, main_router: web.UrlDispatcher) -> None:
    """Register the WebRTC HTTP/WebSocket endpoints.

    Every endpoint lives under /api so the single nginx /api proxy rule
    fronts them — the signaling socket included, since that location
    forwards WebSocket upgrades. Paths off /api (such as a bare /turn or
    /webrtc/signaling) would sit unproxied behind the LSIO nginx: the TURN
    fetch and the signaling handshake would 404 and freeze the dashboard.
    """
    main_router.add_get(
        f"{api_prefix}/api/webrtc/signaling{{slash:/?}}", self.rtc_ws_handler
    )
    main_router.add_get(f"{api_prefix}/api/ws", self.rtc_ws_handler)
    main_router.add_get(f"{api_prefix}/api/turn", self.handle_turn_req)
paramself
paramapi_prefixstr
parammain_routerweb.UrlDispatcher

Returns

None
funcrtc_ws_handler(self, request) -> Union[web.Response, web.WebSocketResponse]

Accept a signaling WebSocket, refusing with 409/503 while the WebRTC mode is inactive or still starting.

Source Code
async def rtc_ws_handler(
    self, request: web.Request
) -> Union[web.Response, web.WebSocketResponse]:
    """Accept a signaling WebSocket, refusing with 409/503 while the WebRTC
    mode is inactive or still starting."""
    if self.supervisor.current_mode != self.mode:
        return web.Response(status=409, text="WebRTC mode is inactive")
    if self.peer_manager is None:
        return web.Response(status=503, headers={"Retry-After": "1"},
                            text="WebRTC service is still starting")
    # autoping=False so the signaling loop sees PONG frames and can feed
    # the upload uplink gauge's clock; the loop answers PING itself.
    ws = web.WebSocketResponse(autoping=False)
    await ws.prepare(request)

    peername = request.transport.get_extra_info("peername")
    remote_address = peername if peername else (request.remote, 0)
    await self.peer_manager.signaling_handler(
        ws, remote_address, auth_role_ceiling=request.get("auth_role_ceiling")
    )
    return ws
paramself
paramrequestweb.Request

Returns

typing.Union[aiohttp.web.aiohttp.web.Response, aiohttp.web.aiohttp.web.WebSocketResponse]
funcuplink_session_conns(self) -> List[Tuple[Any, Optional[str], Optional[str]]]

(websocket, session token, peer ip) per connected browser signaling peer, for the supervisor's upload uplink gauge. The signaling socket stays open for the session's life, so it is the WebRTC transport's window onto the client's uplink.

Source Code
def uplink_session_conns(self) -> List[Tuple[Any, Optional[str], Optional[str]]]:
    """``(websocket, session token, peer ip)`` per connected browser
    signaling peer, for the supervisor's upload uplink gauge. The
    signaling socket stays open for the session's life, so it is the
    WebRTC transport's window onto the client's uplink."""
    if self.peer_manager is None:
        return []
    conns = []
    for peer in list(self.peer_manager.peers.values()):
        if peer.peer_type != "client":
            continue
        raddr = peer.raddr
        ip = raddr[0] if isinstance(raddr, (tuple, list)) and raddr else None
        conns.append((peer.ws, peer.client_token, ip))
    return conns
paramself

Returns

typing.List[typing.Tuple[typing.Any, typing.Optional[str], typing.Optional[str]]]
funchandle_turn_req(self, request) -> web.Response

Serve a TURN credential request via the peer manager, refusing with 409/503 while the WebRTC mode is inactive or still starting.

Source Code
async def handle_turn_req(self, request: web.Request) -> web.Response:
    """Serve a TURN credential request via the peer manager, refusing with
    409/503 while the WebRTC mode is inactive or still starting."""
    if self.supervisor.current_mode != self.mode:
        return web.json_response({"error": "WebRTC mode is inactive"}, status=409)
    if self.peer_manager is None:
        return web.json_response(
            {"error": "WebRTC service is still starting"},
            status=503, headers={"Retry-After": "1"})
    return await self.peer_manager.handle_turn_req(request)
paramself
paramrequestweb.Request

Returns

aiohttp.web.aiohttp.web.Response

On this page

Edit on GitHub