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]= settingsattributetasksList[asyncio.Task]= []attributeshutdown_event= asyncio.Event()attribute_shutdown_called= Falseattributesignaling_clientOptional[WebRTCSignalingClient]= Noneattributemedia_pipelineOptional[MediaPipelinePixel]= NoneThe primary display's pipeline (also stored under the
"primary" key of display_pipelines).
attributertc_appOptional[RTCApp]= Noneattributeinput_handlerOptional[WebRTCInput]= Noneattributeresource_monitorOptional[resource_stats.ResourceMonitor]= NoneattributemetricsOptional[Metrics]= Noneattributepeer_id= 1attributeargsOptional[SimpleNamespace]= NoneMutable 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]= Noneattributemon_rest_apiOptional[RESTRTCMonitor]= Noneattributemon_rtc_config_fileOptional[RTCConfigFileMonitor]= Noneattributemon_cloudflare_turnOptional[CloudflareRTCMonitor]= Noneattributepeer_managerOptional[WebRTCPeerManagement]= Noneattributesupervisor= supervisorattributedisplay_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.
attribute_client_scalesDict[str, float]= {}attribute_client_stream_boxesDict[str, Tuple[Tuple[float, float, float, float], float]]= {}attributedisplay_pipelinesDict[str, MediaPipelinePixel]= {}One MediaPipelinePixel per connected display id.
attribute_display_dpisDict[str, int]= {}attribute_last_idr_request_timesDict[str, float]= {}attribute_display_lock= asyncio.Lock()attribute_primary_dimsOptional[Tuple[int, int]]= NoneThe primary's last layout-path resolution request, or None while the pipeline dimensions are its authority.
attribute_wayland_ctl_moduleOptional[Any]= NoneFallback 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]= NoneHost-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]]= NoneLast (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.
attribute_congestion_steerDict[str, CongestionSteer]= {}Each display's CongestionSteer, the state its
CBR target is steered with.
attributeRECONNECT_GRACE_S= 3.0Seconds 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]= NoneThe pending deferred primary-capture stop.
attribute_mic_controlOptional[AudioControl]= NoneSound-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]= NoneLoaded virtual-source module, reused when the websockets path already loaded it.
attribute_mic_module_owned= FalseWhether this path loaded the module and so unloads it on shutdown.
attribute_mic_provisioned= FalseSet 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
constructor__init__(supervisor) -> NoneSource 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.resource_monitor: Optional[resource_stats.ResourceMonitor] = 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]] = {}
# Each page's reported CSS-to-remote scale, by display id (the primary
# included), rebroadcast with the layout for cross-display drags.
self._client_scales: Dict[str, float] = {}
# Each page's stream box on the user's desktop, by display id: origin
# and remote pixels per desktop pixel, with when it last changed.
self._client_stream_boxes: Dict[
str, Tuple[Tuple[float, float, float, float], float]] = {}
self.display_pipelines: Dict[str, MediaPipelinePixel] = {}
# The DPI each secondary's page asked for; the primary's is _last_applied_dpi.
self._display_dpis: Dict[str, int] = {}
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._congestion_steer: Dict[str, CongestionSteer] = {}
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()paramsupervisorCentralizedStreamServerReturns
Nonefunc_init_default_settings() -> NoneSeed 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))Returns
Nonefuncinitialize_components() -> NoneBuild 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
await self.rtc_app.open_ice_muxes()
if self.rtc_app.ice_lite_enabled():
logger.info("WebRTC ICE-lite: the server offers host candidates only and answers the client's checks; STUN/TURN serve the client side")
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)
# Keyed to the pipeline's render node so the GPU stats describe the encoding GPU.
stats_gpu_id = parse_gpu_id(getattr(self.args, "gpu_id", ""))
self.resource_monitor = resource_stats.ResourceMonitor(
gpu_id=stats_gpu_id if (stats_gpu_id or 0) > 0 else 0,
dri_node=getattr(self.args, "encode_dri", "") or "",
metrics=self.metrics,
)
self.create_peer_manager(rtc_config)Returns
Nonefunccreate_signaling_client() -> WebRTCSignalingClientCreate 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}//localhost:{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 clientReturns
selkies.webrtc_signaling_client.WebRTCSignalingClientfunchandle_signaling_error(error) -> NoneHandle 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()paramerrorExceptionReturns
Nonefunchandle_signaling_disconnect() -> NoneTear 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
)Returns
Nonefunchandle_session_start(session_peer_id, client_type, client_token=None, display_id='primary', display_position='right', fullcolor_codecs=None) -> NoneStart 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",
fullcolor_codecs: Optional[List[str]] = None,
) -> 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").
"""
# From the registry, not SESSION_START: the signaling server that validated
# the claim is this process, and the relay's fields are positional.
peer = self.peer_manager.peers.get(session_peer_id) if self.peer_manager else None
client_slot = getattr(peer, "client_slot", None) if peer else None
logger.debug(
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, client_slot,
fullcolor_codecs=fullcolor_codecs)
if self.args.enable_webrtc_statistics and self.metrics:
await self.metrics.initialize_webrtc_csv_file(self.args.webrtc_statistics_dir)
logger.info(f"Session started for peer {session_peer_id} ({client_type}, display '{display_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)paramsession_peer_idstrThe signaling peer id of the joining client.
paramclient_typestr"controller" or a viewer/shared role name.
paramclient_tokenOptional[str]= NoneOptional 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").
paramfullcolor_codecsOptional[List[str]]= NoneReturns
Nonefunchandle_session_end(session_peer_id, client_type) -> NoneHandle 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
)paramsession_peer_idstrparamclient_typestrReturns
Nonefunccreate_peer_manager(rtc_config) -> NoneBuild 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_presentparamrtc_configAnyReturns
Nonefuncsetup_callbacks() -> NoneWire 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-color 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-color 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_encoder_demoted = (
lambda encoder: asyncio.ensure_future(self._encoder_demoted("primary", encoder))
)
self.media_pipeline.on_stream_info = self._publish_stream_info
self.media_pipeline.on_pipeline_started = self.send_current_cursor
self.rtc_app.request_idr_frame = self.request_idr_for_display
self.rtc_app.invalidate_reference = self.invalidate_reference_for_display
self._invalidation_log: Dict[str, tuple] = {}
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.on_video_codec_declined = self._video_codec_declined
self.rtc_app.on_fullcolor_declined = self._fullcolor_declined
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_audio_consumer_active = self.handle_audio_consumer_active
self.rtc_app.on_consumers_changed = self.handle_consumers_changed
# /api/tokens updates must reach live WebRTC peers too.
sessions.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.input_handler.on_session_compositor_adopted = self._resync_wayland_session_scale
self.resource_monitor.on_tick = self.handle_resource_tick
self.resource_monitor.watched = lambda: bool(self.rtc_app and self.rtc_app.stats_displays())Returns
Nonefunc_second_screen_availability() -> 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 mints a RandR monitor on demand; host capture is bounded by the host compositor's real output count (unknown until a pipeline start establishes the host session); and the self-composited Wayland backend rides the input handler's session-screen ladder: the session compositor's control socket grows a screen on demand, a spare screen the session already opened is arranged instead without one, and a session running directly on the capture compositor needs neither.
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 mints a RandR monitor on
demand; host capture is bounded by the host compositor's real output
count (unknown until a pipeline start establishes the host session);
and the self-composited Wayland backend rides the
input handler's session-screen ladder: the session compositor's
control socket grows a screen on demand, a spare screen the session
already opened is arranged instead without one, and a session running
directly on the capture compositor needs neither.
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:
return True, ""
if (self.settings.wayland_host_display or "").strip():
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, ""
if self.input_handler is None:
return False, "The input system is not up yet."
return self.input_handler.session_screen_capability()Returns
typing.TupleA (available, reason) pair; reason is empty when available.
func_refresh_second_screen_capacity() -> boolRe-read what bounds a second display on this backend.
Host capture re-reads how many outputs the host exposes; the nested Wayland backend re-probes the session compositor's control socket. X11 has no bound.
Source Code
async def _refresh_second_screen_capacity(self) -> bool:
"""Re-read what bounds a second display on this backend.
Host capture re-reads how many outputs the host exposes; the nested
Wayland backend re-probes the session compositor's control socket.
X11 has no bound.
Returns:
True when the answer changed, i.e. the second-screen availability
that clients were told may have flipped.
"""
if not IS_WAYLAND:
return False
if not (self.settings.wayland_host_display or "").strip():
if self.input_handler is None:
return False
before = self.input_handler.session_screen_capability()[0]
fresh = await self.input_handler.probe_session_screen_capability()
return fresh[0] != before
module = self._wayland_capture_handle()
if module is None:
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 changedReturns
boolTrue when the answer changed, i.e. the second-screen availability
func_server_settings_payload() -> 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), and what the apps panel already has installed, which no browser's own storage can answer for a session opened somewhere else — absent until the runner has answered, because a client told nothing is installed clears its own record.
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), and what the apps panel already has
installed, which no browser's own storage can answer for a session
opened somewhere else — absent until the runner has answered, because a
client told nothing is installed clears its own record."""
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)
installed = self.input_handler.installed_apps() if self.input_handler else None
if installed is not None:
payload["settings"]["apps_installed"] = {"value": installed}
return payloadReturns
typing.Dict[str, typing.Any]funchandle_data_channel_open(channel=None) -> NoneGreet 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("Peer data channel open for input.")
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
)
self.rtc_app.send_message_to_channel(
channel, "display_config_update", self._display_config_payload()
)
peer = next((obj for obj in self.rtc_app.peer_connections.values()
if obj.get("data_channel") is channel), None)
if peer is not None and peer.get("client_type") == ClientType.CONTROLLER \
and (peer.get("display_id") or "primary") == "primary":
for name, size in self.supervisor.pending_print_documents():
self.rtc_app.send_print_document(name, size, channel)
if peer is not None and peer.get("client_type") == ClientType.CONTROLLER:
did = peer.get("display_id") or "primary"
settled = getattr(getattr(self.display_pipelines.get(did), "stream_watch", None), "info", None)
if settled:
self.rtc_app.send_stream_info(did, settled, channel)
else:
self.rtc_app.send_media_data_over_channel(
"server_settings", server_settings_payload
)
self._broadcast_display_config()
self.send_current_cursor(channel)paramchannelOptional[Any]= NoneReturns
Nonefuncannounce_print_document(name, size) -> NoneTell every controller peer a printed document waits in the spool.
Source Code
async def announce_print_document(self, name: str, size: int) -> None:
"""Tell every controller peer a printed document waits in the spool."""
if self.rtc_app:
self.rtc_app.send_print_document(name, size)paramnamestrparamsizeintReturns
Nonefuncsessions() -> List[Dict[str, Any]]Source Code
async def sessions(self) -> List[Dict[str, Any]]:
return await self.rtc_app.sessions() if self.rtc_app else []Returns
typing.List[typing.Dict[str, typing.Any]]funcdisconnect_session(session_id) -> boolSource Code
async def disconnect_session(self, session_id: str) -> bool:
return bool(self.rtc_app) and await self.rtc_app.disconnect_peer(session_id)paramsession_idstrReturns
boolfuncsend_current_cursor(channel=None) -> NoneResend 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}")paramchannelOptional[Any]= NoneReturns
Nonefunchandle_pointer_visible(visible) -> NoneCompose 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)paramvisibleboolReturns
Nonefunchandle_video_bitrate_change(bitrate, display_id='primary') -> NoneVideo 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)parambitrateintparamdisplay_idstr= 'primary'Returns
Nonefunchandle_audio_bitrate_change(bitrate) -> NoneHandle 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 = sanitizedparambitrateintReturns
Nonefunchandle_fps_change(fps, display_id='primary') -> NoneFramerate 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)paramfpsintparamdisplay_idstr= 'primary'Returns
Nonefunchandle_rate_control_change(mode, display_id='primary') -> NoneRate-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)parammodeAnyparamdisplay_idstr= 'primary'Returns
Nonefunchandle_crf_change(crf, display_id='primary') -> NoneCRF 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)paramcrfintparamdisplay_idstr= 'primary'Returns
Nonefunchandle_client_werbtc_stats(webrtc_stat_type, webrtc_stats) -> NoneIngest 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)paramwebrtc_stat_typestrparamwebrtc_statsstrReturns
Nonefuncon_resize_handler(res, display_id='primary') -> NoneRoute 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"
logger.debug(f"Resize message for display '{display_id}': {res}")
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)paramresstrparamdisplay_idstr= 'primary'Returns
Nonefunc_server_locked_dims() -> 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))Returns
typing.Optional[typing.Tuple[int, int]]func_resize_primary_display(res) -> NoneResize 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 screen is
grown ahead of the capture restart, the restarted capture resizes the
view, the compositor's realized geometry (it may even-mask or refuse
the mode) is reconciled and pushed to the client, the screen is fitted
to it, and a nested session's own screen is re-sized to the same
geometry at the DPI in force.
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 screen is
grown ahead of the capture restart, the restarted capture resizes the
view, the compositor's realized geometry (it may even-mask or refuse
the mode) is reconciled and pushed to the client, the screen is fitted
to it, and a nested session's own screen is re-sized to the same
geometry at the DPI in force.
"""
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"Redundant resize request for primary to {target_w}x{target_h}. No action.")
return
logger.info(f"Resize requested for display 'primary' with resolution: {target_w}x{target_h}")
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._size_wayland_screen(target_w, target_h, grow_only=True)
await self.media_pipeline.restart_screen_capture()
await self._push_wayland_realized_geometry("primary", self.media_pipeline)
await self._size_wayland_screen(
self.media_pipeline.width, self.media_pipeline.height)
# A nested session's screen is its own compositor's, not the
# capture's: sizing only the capture leaves its applications
# laid out for the size the last DPI change realized.
if self.input_handler is not None:
await self.input_handler.realize_wayland_dpi(
getattr(self, "_last_applied_dpi", None)
or getattr(settings, "scaling_dpi", 96) or 96,
"primary",
(self.media_pipeline.width, self.media_pipeline.height))
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.debug(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 = FalseparamresstrReturns
Nonefuncrequest_idr_for_display(display_id='primary', unless_pending=False) -> NoneSchedule 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",
unless_pending: bool = False) -> 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.
Args:
unless_pending: Hold the request only while that IDR has yet to be
captured, whatever the floor says: a pacer's GOP reset needs a
keyframe from after the reset, and one captured before it
leaves the stream dead until the pacer's resurrect timeout.
"""
display_id = display_id or "primary"
pipeline = self.display_pipelines.get(display_id)
if pipeline is None:
return
now = time.monotonic()
if unless_pending:
if pipeline.idr_pending:
return
elif now - self._last_idr_request_times.get(display_id, 0.0) < IDR_REQUEST_FLOOR_S:
return
self._last_idr_request_times[display_id] = now
await pipeline.dynamic_idr_frame()paramdisplay_idstr= 'primary'paramunless_pendingbool= FalseHold the request only while that IDR has yet to be captured, whatever the floor says: a pacer's GOP reset needs a keyframe from after the reset, and one captured before it leaves the stream dead until the pacer's resurrect timeout.
Returns
Nonefuncinvalidate_reference_for_display(display_id, frame_id) -> NoneTell the display's encoder a peer lost frame_id, so the frames after it stop
predicting from it (websockets LOST_FRAME parity). Logged once per display per
five seconds with the count of the rest, since loss comes in bursts.
Source Code
def invalidate_reference_for_display(self, display_id: str, frame_id: int) -> None:
"""Tell the display's encoder a peer lost `frame_id`, so the frames after it stop
predicting from it (websockets LOST_FRAME parity). Logged once per display per
five seconds with the count of the rest, since loss comes in bursts."""
display_id = display_id or "primary"
pipeline = self.display_pipelines.get(display_id)
if pipeline is None:
return
pipeline.invalidate_reference(frame_id)
now = time.monotonic()
last, more = self._invalidation_log.get(display_id, (0.0, 0))
if now - last >= 5.0:
suffix = f" (+{more} more in the last 5 s)" if more else ""
logger.info(f"Display '{display_id}': frame {frame_id} lost by a peer; the encoder predicts past it.{suffix}")
self._invalidation_log[display_id] = (now, 0)
else:
self._invalidation_log[display_id] = (last, more + 1)paramdisplay_idstrparamframe_idintReturns
Nonefunc_provision_webrtc_virtual_mic() -> NoneBring 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 NoneReturns
Nonefunc_teardown_webrtc_virtual_mic() -> NoneUnload 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()Returns
Nonefuncstart_display_media(display_id) -> NoneA 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. The
pipeline starts only the captures its consumers receive: a peer whose
session policy starts video or audio off is registered paused, and a
warm pipeline is settled against the consumer set afterwards, so a
paused reconnect stops what it no longer receives. 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. The
pipeline starts only the captures its consumers receive: a peer whose
session policy starts video or audio off is registered paused, and a
warm pipeline is settled against the consumer set afterwards, so a
paused reconnect stops what it no longer receives. 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()
consumers = self._display_consumers("primary")
video_wanted = any(not p.get("video_paused", False) for p in consumers) or not consumers
audio_wanted = any(not p.get("audio_paused", False) for p in consumers) or not consumers
await self.media_pipeline.start_media_pipeline(video=video_wanted, audio=audio_wanted)
await self._settle_primary_consumers()
if (IS_WAYLAND and video_wanted 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()
)paramdisplay_idstrReturns
Nonefuncstop_display_media(display_id) -> NoneRelease 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_dpis.pop(display_id, None)
self.display_layouts.pop(display_id, None)
self._client_scales.pop(display_id, None)
self._client_stream_boxes.pop(display_id, None)
if pipeline is not None:
await pipeline.stop_media_pipeline()
await self.reconfigure_displays()paramdisplay_idstrReturns
Nonefunc_cancel_primary_stop_grace() -> NoneDrop 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()Returns
Nonefunc_schedule_primary_stop_grace() -> NoneStop 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())Returns
Nonefunc_primary_display_has_consumer() -> boolWhether 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()
)Returns
boolfunc_wayland_capture_handle() -> 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_moduleReturns
typing.Optional[typing.Any]func_size_wayland_screen(width, height, grow_only=False) -> NoneSize the primary's screen (output 0) to its display rectangle.
The primary's capture binds to the view covering that screen, and a capture start sizes the view alone, which the screen must already hold: so the screen is grown ahead of the capture restart and fitted to the rectangle once the capture carries it -- an early shrink is refused by the compositor, which leaves no view hanging outside its screen.
Source Code
async def _size_wayland_screen(self, width: int, height: int,
grow_only: bool = False) -> None:
"""Size the primary's screen (output 0) to its display rectangle.
The primary's capture binds to the view covering that screen, and a
capture start sizes the view alone, which the screen must already hold:
so the screen is grown ahead of the capture restart and fitted to the
rectangle once the capture carries it -- an early shrink is refused by
the compositor, which leaves no view hanging outside its screen."""
module = self._wayland_capture_handle()
if module is None or width <= 0 or height <= 0:
return
scale = float(getattr(self.media_pipeline, "scale", 1.0) or 1.0)
try:
if grow_only:
outputs = {o[0]: o for o in await asyncio.to_thread(module.list_outputs)}
screen = outputs.get(WAYLAND_SCREEN_OUTPUT_ID)
if screen:
width, height = max(width, screen[3]), max(height, screen[4])
ok = await asyncio.to_thread(
module.resize_output, WAYLAND_SCREEN_OUTPUT_ID, width, height, scale)
if not ok:
logger.warning(f"Wayland screen resize to {width}x{height} refused.")
except Exception as e:
logger.error(f"Wayland resize_output failed: {e}")paramwidthintparamheightintparamgrow_onlybool= FalseReturns
Nonefunc_destroy_wayland_secondary_outputs(keep_did=None) -> NoneRetire every secondary display's compositor screen except keep_did's.
The primary's screen (output 0) and the view its capture binds to always persist -- the primary is the session, not an extension of it.
Source Code
async def _destroy_wayland_secondary_outputs(self, keep_did: Optional[str] = None) -> None:
"""Retire every secondary display's compositor screen except `keep_did`'s.
The primary's screen (output 0) and the view its capture binds to
always persist -- the primary is the session, not an extension of it.
"""
module = self._wayland_capture_handle()
if module is None:
return
keep = {WAYLAND_SCREEN_OUTPUT_ID, wayland_output_id("primary")}
if keep_did:
keep.add(wayland_output_id(keep_did))
try:
for out in await asyncio.to_thread(module.list_outputs):
if out[0] not in keep:
await asyncio.to_thread(module.destroy_output, out[0])
except Exception as e:
logger.warning(f"Wayland output teardown failed: {e}")
if self.input_handler:
# The session keeps a screen per surviving display: the kept
# secondary's while it is being (re)placed, only the primary's on
# teardown, where a removed screen's windows return to the primary.
await self.input_handler.ensure_session_screens(
[keep_did] if keep_did else [])
# Which of the session's own screens a capture drives changed.
self.input_handler.resync_session_screens()paramkeep_didOptional[str]= NoneReturns
Nonefunc_apply_wayland_extension(did, layouts) -> boolRealize the extended layout as compositor screens, BEFORE the secondary's pipeline binds a capture — the Wayland counterpart of apply_extended_layout.
Every display is a screen of the session compositor's own, so it lays its windows and panels out per monitor. 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. A secondary that keeps its origin but shrinks gives the room up in place ahead of the move, and grows into the room the move leaves on the same restart; a retained output the compositor still finds in the primary's way is recreated after all.
Source Code
async def _apply_wayland_extension(self, did: str, layouts: Dict[str, Dict[str, int]]) -> bool:
"""Realize the extended layout as compositor screens, BEFORE the
secondary's pipeline binds a capture — the Wayland counterpart of
apply_extended_layout.
Every display is a screen of the session compositor's own, so it lays
its windows and panels out per monitor. 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. A secondary that keeps its origin but shrinks
gives the room up in place ahead of the move, and grows into the room
the move leaves on the same restart; a retained output the compositor
still finds in the primary's way is recreated after all.
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]
dpi = self._display_dpi(did)
scale = float(dpi) / 96.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_did=did)
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
elif (existing is not None
and not await wayland_shrink_output(module, existing, s["w"], s["h"])):
logger.info(f"Wayland output {oid} cannot shrink to {s['w']}x{s['h']}; recreating it.")
await asyncio.to_thread(module.destroy_output, oid)
existing = None
p = layouts.get("primary") or {"x": 0, "y": 0}
existing0 = outputs.get(WAYLAND_SCREEN_OUTPUT_ID)
current0 = (existing0[1], existing0[2]) if existing0 is not None else (0, 0)
if (p["x"], p["y"]) != current0:
moved = await wayland_reposition_primary(module, p["x"], p["y"])
if not moved and existing is not None:
logger.info(f"Wayland output {oid} blocks the primary's move; recreating it.")
await asyncio.to_thread(module.destroy_output, oid)
existing = None
moved = await wayland_reposition_primary(module, p["x"], p["y"])
if not moved:
return False
if existing is not None:
return True
pw, ph = p.get("w"), p.get("h")
if (existing0 is not None and pw and ph
and (pw < existing0[3] or ph < existing0[4])):
# The secondary drops into the room a shrinking primary frees, and the
# compositor refuses an output overlapping the primary's rectangle, as
# it refuses a screen too small for the live view its capture holds:
# the capture takes the new rectangle, then the screen gives the room
# up, before the secondary is placed.
if (self.media_pipeline.width, self.media_pipeline.height) != (pw, ph):
await self.media_pipeline.update_capture_region(p["x"], p["y"], pw, ph)
await self._push_wayland_realized_geometry("primary", self.media_pipeline)
if not await asyncio.to_thread(
module.resize_output, WAYLAND_SCREEN_OUTPUT_ID, pw, ph, existing0[5]):
logger.warning(f"Wayland screen shrink to {pw}x{ph} refused.")
if self.input_handler:
# The screen this display owns, grown just ahead of the output
# that adopts its host window, then given the display's own DPI;
# what the session leaves is this output's capture scale.
await self.input_handler.ensure_session_screen(
did, size=(s["w"], s["h"]), scale=scale)
scale = await self.input_handler.realize_wayland_dpi(dpi, did, (s["w"], s["h"]))
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 createdparamdidstrparamlayoutsDict[str, Dict[str, int]]Returns
boolFalse when the output cannot be created or the primary cannot move
func_wayland_capture_live(did, pipeline) -> boolWhether 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 FalseparamdidstrparampipelineMediaPipelinePixelReturns
boolfunc_wayland_capture_last_error(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.
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."""
module = getattr(pipeline, "capture_module", None) if pipeline is not None else None
if module is None:
return None
try:
_state, last_error = module.capture_state(wayland_output_id(did))
return last_error
except Exception:
return NoneparampipelineOptional[MediaPipelinePixel]paramdidstrReturns
typing.Optional[str]func_realized_wayland_dims(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:
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 NoneparamdidstrReturns
typing.Optional[typing.Tuple[int, int]]func_push_wayland_realized_geometry(did, pipeline) -> NoneRead 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:
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)paramdidstrparampipelineOptional[MediaPipelinePixel]Returns
Nonefunc_apply_wayland_cursor_size(dpi_value) -> NoneWayland 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()
if module is None:
return
size = cursor_size_for_dpi(dpi_value, CURSOR_SIZE)
try:
if await asyncio.to_thread(module.set_cursor_size, size):
logger.debug(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}")paramdpi_valuefloatReturns
Nonefunc_display_consumers(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
]paramdisplay_idstrReturns
typing.List[typing.Dict[str, typing.Any]]funchandle_peer_gone(peer_id, peer=None) -> NoneRelease 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}")parampeer_idstrparampeerOptional[Dict[str, Any]]= NoneReturns
Nonefunchandle_video_consumer_active(peer_id, display_id, active) -> NoneTab-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."
)parampeer_idstrparamdisplay_idstrparamactiveboolReturns
Nonefunchandle_audio_consumer_active(peer_id, active) -> NoneThe side menu's audio toggle for ONE peer (data-channel STOP_AUDIO / START_AUDIO). The peer's own RTP sender gates its delivery, so a late joiner is unaffected; the shared pcmflux capture stops once no peer receives audio and restarts for the first that asks again. Audio lives on the primary display, whatever display the peer renders.
Source Code
async def handle_audio_consumer_active(self, peer_id: str, active: bool) -> None:
"""The side menu's audio toggle for ONE peer (data-channel STOP_AUDIO /
START_AUDIO). The peer's own RTP sender gates its delivery, so a
late joiner is unaffected; the shared pcmflux capture stops once no
peer receives audio and restarts for the first that asks again. Audio
lives on the primary display, whatever display the peer renders."""
peer = self.rtc_app.peer_connections.get(peer_id) if self.rtc_app else None
if peer is None:
return
peer["audio_paused"] = not active
sender = peer.get("audio_sender")
if sender is not None:
sender._enabled = active
await self._settle_primary_audio()parampeer_idstrparamactiveboolReturns
Nonefunc_settle_primary_audio() -> NonePause the primary's audio capture once every peer is audio-paused, and resume it while any peer receives audio; a no-op on a pipeline that is not running (start_display_media decides what starts).
Source Code
async def _settle_primary_audio(self) -> None:
"""Pause the primary's audio capture once every peer is audio-paused,
and resume it while any peer receives audio; a no-op on a pipeline
that is not running (start_display_media decides what starts)."""
pipeline = self.media_pipeline
if pipeline is None or not pipeline.is_media_pipeline_running():
return
consumers = self._display_consumers("primary")
if consumers and all(p.get("audio_paused", False) for p in consumers):
if await pipeline.pause_audio_capture():
logger.info("No peer receives audio; audio capture stopped.")
elif consumers and await pipeline.resume_audio_capture():
logger.info("Audio capture restarted for a peer that receives audio.")Returns
Nonefunc_settle_primary_consumers() -> NoneApply the all-paused rule to both of the primary's captures after a pipeline start that may have found them warm.
Source Code
async def _settle_primary_consumers(self) -> None:
"""Apply the all-paused rule to both of the primary's captures after
a pipeline start that may have found them warm."""
pipeline = self.display_pipelines.get("primary")
if pipeline is not None:
consumers = self._display_consumers("primary")
if consumers and all(p.get("video_paused", False) for p in consumers):
if await pipeline.pause_screen_capture():
logger.info("No peer receives video; capture stopped.")
await self._settle_primary_audio()Returns
Nonefunc_close_peer_signaling_ws(peer_id, code, message) -> NoneFatal 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:
passparampeer_idstrparamcodeintparammessagebytesReturns
Nonefuncreconcile_webrtc_peers() -> NoneToken-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)Returns
Nonefunchandle_consumers_changed(display_id) -> NoneA 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). The primary's audio capture follows the same rule over the peers' audio pauses. (A departing controller's pipeline is torn down elsewhere; the pause and resume calls 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).
The primary's audio capture follows the same rule over the peers'
audio pauses. (A departing controller's pipeline is torn down
elsewhere; the pause and resume calls 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")
if display_id == "primary":
await self._settle_primary_audio()paramdisplay_idstrReturns
Nonefunc_resume_display_capture(display_id, pipeline, why, idr_always=False) -> NoneResume 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)paramdisplay_idstrparampipelineMediaPipelinePixelparamwhystrparamidr_alwaysbool= FalseReturns
Nonefunc_drop_wayland_secondary(did, reason) -> NoneRefuse a secondary display the compositor cannot realize: unregister it, stop its pipeline, destroy its screen, 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's screen, 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 screen, 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's screen, 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_dpis.pop(did, None)
self.display_layouts.pop(did, None)
self._client_scales.pop(did, None)
self._client_stream_boxes.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.input_handler:
await self.input_handler.ensure_session_screens([])
self.input_handler.resync_session_screens()
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}")paramdidstrparamreasonstrReturns
Nonefunc_drop_x11_secondary(did, reason) -> NoneRefuse 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_dpis.pop(did, None)
self.display_layouts.pop(did, None)
self._client_scales.pop(did, None)
self._client_stream_boxes.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}"
)paramdidstrparamreasonstrReturns
Nonefuncreconfigure_displays() -> NoneLay 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()
await self._size_wayland_screen(
self.media_pipeline.width, self.media_pipeline.height)
self._broadcast_display_config()
return
# Before the shrink, so no monitor lingers outside the framebuffer.
await retire_displays()
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
# display_clients holds the secondaries; the primary counts too.
await self._wm_swap.ensure_for(len({"primary", *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(
self._display_dpi(did), 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, keyframe=True, timing=None, dependency=None, _did=did:
self.rtc_app.consume_data(buf, pts, kind, keyframe, _did, timing, dependency)
)
# 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.on_encoder_demoted = (
lambda encoder, _did=did: asyncio.ensure_future(self._encoder_demoted(_did, encoder))
)
pipeline.on_stream_info = self._publish_stream_info
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)
elif self.input_handler:
# The capture outputs now sit where the arrangement asks; the
# session compositor arranges its own screens by its own rule
# until it is told this one.
self.input_handler.schedule_session_screen_layout(layouts)
self._broadcast_display_config()Returns
Nonefunc_push_x11_layout_geometry(layouts) -> NoneTell 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)paramlayoutsDict[str, Dict[str, int]]Returns
Nonefuncset_client_stream_box(display_id, origin_x, origin_y, scale_x, scale_y) -> NoneRecord where a display's page draws its stream on the user's desktop.
Rebroadcast with the layout, since a page maps a drag that crossed onto a neighbor through the neighbor's box rather than off its own edge. Only the browser knows those origins, and they are the only thing relating two viewports whose monitors, window chrome and device pixel ratios all differ. Ignored for an unknown display or an impossible box.
Source Code
async def set_client_stream_box(self, display_id: str, origin_x: float,
origin_y: float, scale_x: float,
scale_y: float) -> None:
"""Record where a display's page draws its stream on the user's desktop.
Rebroadcast with the layout, since a page maps a drag that crossed onto
a neighbor through the neighbor's box rather than off its own edge.
Only the browser knows those origins, and they are the only thing
relating two viewports whose monitors, window chrome and device pixel
ratios all differ. Ignored for an unknown display or an impossible box.
"""
if display_id not in self.display_layouts:
return
if not (0.05 <= scale_x <= 100.0 and 0.05 <= scale_y <= 100.0):
return
if not (abs(origin_x) <= 100000.0 and abs(origin_y) <= 100000.0):
return
box = (origin_x, origin_y, scale_x, scale_y)
now = time.monotonic()
stored = self._client_stream_boxes.get(display_id)
if stored is not None:
if stored[0] == box:
return
# One box a page publishes is one broadcast to every client; a page
# that alternated two would otherwise amplify at whatever rate it
# sent. The page republishes what the layout comes back missing, so
# a dropped update is not a lost one.
if now - stored[1] < 0.2:
return
self._client_stream_boxes[display_id] = (box, now)
self._broadcast_display_config()paramdisplay_idstrparamorigin_xfloatparamorigin_yfloatparamscale_xfloatparamscale_yfloatReturns
Nonefunc_display_config_payload() -> Dict[str, Any]display_config_update body: the display roster, the backend, plus each laid-out display's rectangle, its client's reported CSS-to-remote scale and the desktop box that client draws it in, so a page can map a cross-display drag into its neighbor's region and, on X11, a secondary can follow the primary's density.
Source Code
def _display_config_payload(self) -> Dict[str, Any]:
"""display_config_update body: the display roster, the backend, plus
each laid-out display's rectangle, its client's reported CSS-to-remote
scale and the desktop box that client draws it in, so a page can map a
cross-display drag into its neighbor's region and, on X11, a secondary
can follow the primary's density."""
displays = ["primary"] + [d for d in self.display_clients.keys() if d != "primary"]
payload: Dict[str, Any] = {"displays": displays, "wayland": IS_WAYLAND}
layouts = {}
for did, rect in (self.display_layouts or {}).items():
entry: Dict[str, Any] = dict(rect)
scale = self._client_scales.get(did)
if scale:
entry["scale"] = scale
stored = self._client_stream_boxes.get(did)
if stored:
(entry["originX"], entry["originY"],
entry["scaleX"], entry["scaleY"]) = stored[0]
layouts[did] = entry
if layouts:
payload["layouts"] = layouts
return payloadReturns
typing.Dict[str, typing.Any]func_broadcast_display_config() -> NoneTell 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
self.rtc_app.send_media_data_over_channel(
"display_config_update", self._display_config_payload()
)Returns
Nonefunc_update_cursor_cap(dpi_value) -> NoneScale 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.debug(f"Cursor size cap {ih.cursor_size_cap}px for DPI {dpi_value} "
f"({updated} live capture(s) updated).")paramdpi_valuefloatReturns
Nonefunchandle_scaling(dpi_value, display_id='primary') -> NoneApply 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). On Wayland a secondary's page scales the screen it owns and nothing else: the cursor cap and size stay the primary's. X11 has one DPI, so a secondary's is refused there.
Source Code
async def handle_scaling(self, dpi_value: float, display_id: str = "primary") -> 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). On Wayland a secondary's page scales
the screen it owns and nothing else: the cursor cap and size stay the
primary's. X11 has one DPI, so a secondary's is refused there.
Args:
dpi_value: The DPI the page asked for.
display_id: The display whose page sent it.
"""
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
display_id = display_id or "primary"
if display_id != "primary":
if not IS_WAYLAND:
logger.info(f"Ignoring DPI {dpi_value} from '{display_id}': "
"the desktop DPI follows the primary display.")
return
if self._display_dpis.get(display_id) == int(dpi_value):
return
self._display_dpis[display_id] = int(dpi_value)
await self._realize_wayland_dpi(dpi_value, display_id)
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)
await self._realize_wayland_dpi(dpi_value)
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.debug(
f"Attempting to set cursor size to: {new_cursor_size} (based on DPI {dpi_value})"
)
if await set_cursor_size(new_cursor_size):
logger.debug(f"Successfully set cursor size to {new_cursor_size}")
else:
logger.error(f"Failed to set cursor size to {new_cursor_size}")paramdpi_valuefloatThe DPI the page asked for.
paramdisplay_idstr= 'primary'The display whose page sent it.
Returns
Nonefunc_realize_wayland_dpi(dpi_value, display_id=None) -> NoneRun the Wayland scale ladder for one display's pipeline, or the primary's and every secondary at the DPI its own page asked for, and restart the captures whose scale changed.
Source Code
async def _realize_wayland_dpi(self, dpi_value: int,
display_id: Optional[str] = None) -> None:
"""Run the Wayland scale ladder for one display's pipeline, or the
primary's and every secondary at the DPI its own page asked for, and
restart the captures whose scale changed.
Args:
dpi_value: The DPI to realize; the primary's when `display_id` is None.
display_id: The one display to scale, else all of them.
"""
targets = ([(display_id, self.display_pipelines.get(display_id))]
if display_id else list(self.display_pipelines.items()))
for did, pipeline in targets:
if pipeline is None:
continue
# Per display, never over the argument: a session-wide pass that
# reassigned it would hand the next display the last secondary's
# DPI, and the primary is not always the first pipeline (it is
# re-inserted last when it reconnects behind a live secondary).
target_dpi = (self._display_dpi(did)
if display_id is None and did != "primary" else dpi_value)
new_scale = (await self.input_handler.realize_wayland_dpi(
target_dpi, did, (pipeline.width, pipeline.height))
if self.input_handler else float(target_dpi) / 96.0)
if pipeline.scale == new_scale:
continue
pipeline.scale = new_scale
if pipeline.is_media_pipeline_running():
if did == "primary":
# The capture is a view over the primary's screen, and a
# capture start sizes the view alone: the screen carries
# the scale to the session (its window's preferred
# fractional scale).
await self._size_wayland_screen(pipeline.width, pipeline.height)
await pipeline.restart_screen_capture()
await self._push_wayland_realized_geometry(did, pipeline)paramdpi_valueintThe DPI to realize; the primary's when display_id is None.
paramdisplay_idOptional[str]= NoneThe one display to scale, else all of them.
Returns
Nonefunc_resync_wayland_session_scale(dpi) -> NoneA session compositor was adopted after captures started: run the scale ladder again for every display, so the session takes the desktop DPI as its output scale and the capture output, which took it while the session was still starting, drops back to 1.0.
Serialized against the layout pass: the adoption can land during the one that started the captures it restarts.
Source Code
async def _resync_wayland_session_scale(self, dpi: Any) -> None:
"""A session compositor was adopted after captures started: run the
scale ladder again for every display, so the session takes the desktop
DPI as its output scale and the capture output, which took it while the
session was still starting, drops back to 1.0.
Serialized against the layout pass: the adoption can land during the
one that started the captures it restarts.
Args:
dpi: The primary's DPI; each other display re-applies its own.
"""
if not IS_WAYLAND or self.input_handler is None:
return
try:
dpi_value = int(float(dpi))
except (TypeError, ValueError):
return
async with self._display_lock:
await self._realize_wayland_dpi(dpi_value)paramdpiAnyThe primary's DPI; each other display re-applies its own.
Returns
Nonefunc_display_dpi(display_id) -> intThe DPI a display's page asked for, else the primary's, else the configured default.
Source Code
def _display_dpi(self, display_id: str) -> int:
"""The DPI a display's page asked for, else the primary's, else the configured default."""
own = self._display_dpis.get(display_id) if display_id != "primary" else None
return int(own or getattr(self, "_last_applied_dpi", None)
or float(getattr(settings, "scaling_dpi", 96) or 96))paramdisplay_idstrReturns
intfunc_publish_stream_info(display_id, info) -> NoneTell a display's controllers what its capture streams and how.
Source Code
async def _publish_stream_info(self, display_id: str, info: Dict[str, Any]) -> None:
"""Tell a display's controllers what its capture streams and how."""
if self.rtc_app:
self.rtc_app.send_stream_info(display_id, info)paramdisplay_idstrparaminfoDict[str, Any]Returns
Nonefunc_send_stream_stats() -> NoneOne stream_stats to the controllers with their stats open: the host's
figures and their display's encode. The link is the page's own to measure.
Source Code
def _send_stream_stats(self) -> None:
"""One `stream_stats` to the controllers with their stats open: the host's
figures and their display's encode. The link is the page's own to measure."""
host = stream_stats.host_stats(self.resource_monitor)
for did in self.rtc_app.stats_displays():
stats = dict(host)
pipeline = self.display_pipelines.get(did)
watch = getattr(pipeline, "stream_watch", None)
if watch is not None:
stats.update(watch.rates())
self.rtc_app.send_stream_stats(did, stats)Returns
Nonefunchandle_resource_tick(t) -> NoneResource-monitor tick: send stream_stats and a ping to the
controllers watching their stats, 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_resource_tick(self, t: float) -> None:
"""Resource-monitor tick: send `stream_stats` and a ping to the
controllers watching their stats, 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.rtc_app.stats_displays():
self.input_handler.ping_start = t
self._send_stream_stats()
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}")paramtfloatReturns
Nonefunc_seed_display_settings(entry) -> NoneGive 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] = valueparamentryDict[str, Any]Returns
Nonefunc_display_setting(display_id, key) -> AnyA 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)paramdisplay_idstrparamkeystrReturns
typing.Anyfunc_store_display_setting(display_id, key, value) -> NoneRecord 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] = valueparamdisplay_idstrparamkeystrparamvalueAnyReturns
Nonefunc_apply_display_setting(display_id, key, value) -> NoneStore 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)
# The senders take the new codec ahead of the capture restart that
# produces it, so no frame of one codec goes out packed as another.
if key == "encoder" and self.rtc_app:
self.rtc_app.switch_display_codec(display_id, str(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)paramdisplay_idstrparamkeystrparamvalueAnyReturns
Nonefunc_encoder_for_display(display_id) -> strSource Code
def _encoder_for_display(self, display_id: str) -> str:
return str(self._display_setting(display_id, "encoder") or self.args.encoder)paramdisplay_idstrReturns
strfunc_encoder_demoted(display_id, encoder) -> NoneA display's capture streams encoder in place of the one asked for: the
setting follows, its RTP senders switch codec, and every client hears of it.
Source Code
async def _encoder_demoted(self, display_id: str, encoder: str) -> None:
"""A display's capture streams `encoder` in place of the one asked for: the
setting follows, its RTP senders switch codec, and every client hears of it."""
self._store_display_setting(display_id, "encoder", encoder)
if display_id == "primary":
self.settings.encoder = encoder
if self.rtc_app:
self.rtc_app.encoder = encoder
if self.rtc_app:
self.rtc_app.switch_display_codec(display_id, encoder)
self.rtc_app.send_media_data_over_channel("server_settings", self._server_settings_payload())paramdisplay_idstrparamencoderstrReturns
Nonefunc_video_codec_declined(display_id, mime, fallback) -> boolA peer's answer left out the display's codec: the display moves to
fallback and every client hears of it, unless the operator's menu
holds the encoder, which leaves that peer without video.
Source Code
async def _video_codec_declined(self, display_id: str, mime: str, fallback: str) -> bool:
"""A peer's answer left out the display's codec: the display moves to
`fallback` and every client hears of it, unless the operator's menu
holds the encoder, which leaves that peer without video."""
current = self._encoder_for_display(display_id)
if current == fallback:
return True
definition = next(d for d in SETTING_DEFINITIONS if d["name"] == "encoder")
allowed = definition.get("meta", {}).get("allowed", [])
if fallback not in allowed:
return False
logger.warning(
"Encoder %r (%s) is not decoded by a WebRTC peer of display %r; using %r.",
current, mime, display_id, fallback)
await self._apply_display_setting(display_id, "encoder", fallback)
if self.rtc_app:
self.rtc_app.send_media_data_over_channel(
"server_settings", self._server_settings_payload())
return Trueparamdisplay_idstrparammimestrparamfallbackstrReturns
boolfunc_fullcolor_declined(display_id) -> boolA joining WebRTC peer decodes none of the 4:4:4 the display's codec carries: full color goes off for the display, so the offer describes 4:2:0 from its first frame, and every client hears of it; a full color the operator holds stays, which leaves that peer without a picture.
Source Code
async def _fullcolor_declined(self, display_id: str) -> bool:
"""A joining WebRTC peer decodes none of the 4:4:4 the display's codec
carries: full color goes off for the display, so the offer describes
4:2:0 from its first frame, and every client hears of it; a full color
the operator holds stays, which leaves that peer without a picture."""
if not self._fullcolor_for_display(display_id):
return True
limit = getattr(self.settings, "video_fullcolor", None)
if isinstance(limit, (tuple, list)) and len(limit) > 1 and limit[1]:
return False
logger.warning("A WebRTC peer of display %r decodes no 4:4:4 of its codec; full color is off.",
display_id)
await self._apply_display_setting(display_id, "video_fullcolor", False)
if display_id == "primary":
# The settings payload advertises the primary's value: an operator override
# left in it would come back from the client and flip the stream to 4:4:4.
self.settings.video_fullcolor = (False, False)
self.settings._overridden["video_fullcolor"] = False
if self.rtc_app:
self.rtc_app.send_media_data_over_channel(
"server_settings", self._server_settings_payload())
return Trueparamdisplay_idstrReturns
boolfunc_fullcolor_for_display(display_id) -> boolSource Code
def _fullcolor_for_display(self, display_id: str) -> bool:
return bool(self._display_setting(display_id, "video_fullcolor"))paramdisplay_idstrReturns
boolfunc_use_cpu_for_display(display_id) -> boolSource Code
def _use_cpu_for_display(self, display_id: str) -> bool:
return bool(self._display_setting(display_id, "use_cpu"))paramdisplay_idstrReturns
boolfunchandle_update_settings(settings_json, display_id='primary') -> NoneApply 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, or in any display's on Wayland, 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, or in any display's on Wayland, 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" or IS_WAYLAND):
try:
await self.handle_scaling(float(dpi_val), display_id)
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)
# The page's CSS-to-remote scale, rebroadcast with the layout so a
# neighboring display can scale a cross-display drag over it. Stored
# before any position-triggered reconfigure, whose broadcast carries it.
if settings_json.get("displayScale") is not None:
try:
client_scale = float(settings_json.get("displayScale"))
except (TypeError, ValueError):
client_scale = 0.0
if 0.05 <= client_scale <= 100.0 and \
self._client_scales.get(display_id) != client_scale:
self._client_scales[display_id] = client_scale
self._broadcast_display_config()
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}"
)paramsettings_jsonDict[str, Any]paramdisplay_idstr= 'primary'Returns
Nonefuncmon_rtc_config(stun_servers, turn_servers, rtc_config) -> NoneMonitor 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)paramstun_serversList[str]paramturn_serversList[str]paramrtc_configAnyReturns
Nonefunc_ensure_pacer(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_, unless_pending=True)),
)
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 transportparampcAnyparampeerDict[str, Any]paramdisplay_idstrReturns
typing.OptionalThe DTLS transport (so callers can snapshot its pacer), or None
func_congestion_control_loop() -> NoneGCC-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 two ticks of loss in a row and
hold there before recovering (CongestionSteer), 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. Each peer's own loss also sets how many FlexFEC repair packets
its sender adds per group (RTCRtpSender.steer_fec).
Each peer's feedback is drained per tick, so a decision is taken over a tick's worth of it rather than whichever window landed last: a single window is a few tens of packets, too few for its loss fraction to mean anything, and a display that sends little (a still second screen) is made of such windows. A tick that drains nothing steers nothing.
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 two ticks of loss in a row and
hold there before recovering (`CongestionSteer`), 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. Each peer's own loss also sets how many FlexFEC repair packets
its sender adds per group (`RTCRtpSender.steer_fec`).
Each peer's feedback is drained per tick, so a decision is taken over a
tick's worth of it rather than whichever window landed last: a single
window is a few tens of packets, too few for its loss fraction to mean
anything, and a display that sends little (a still second screen) is
made of such windows. A tick that drains nothing steers nothing.
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.debug(
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.debug(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)
transport = getattr(getattr(pc, "sctp", None), "transport", None)
window = transport.take_twcc_window() if transport is not None else None
if window is None:
continue
sender = peer.get("video_sender")
if sender is not None:
sender.steer_fec(window["loss_fraction"])
bucket = per_display.setdefault(
did, {"goodputs": [], "worst_loss": 0.0})
if window["goodput_bps"]:
bucket["goodputs"].append(window["goodput_bps"])
bucket["worst_loss"] = max(bucket["worst_loss"], window["loss_fraction"])
if self.metrics is not None:
self.metrics.set_bridge_drops(rtc_app.bridge_drops())
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))
# Goodput may lift the target, never drag it down (see docstring).
steer = self._congestion_steer.setdefault(did, CongestionSteer())
target = round(steer.target(
current, ceiling, lo_kbps, min(goodputs), worst_loss, time.monotonic()))
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)Returns
Nonefuncstart_components() -> NoneStart the background tasks: input/clipboard/cursor workers, the startup scale, the congestion/pacer loop, the monitors, the signaling client, and the configured TURN credential refreshers.
On Wayland the configured DPI becomes the session compositor's output
scale (96 is unity, so nothing is applied then), and with none up yet
the input handler re-applies when it adopts one; the X11 desktop's
density was settled before the listener opened, so the first session
compares against it (handle_scaling re-derives the cursor size 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, the
startup scale, the congestion/pacer loop, the monitors, the signaling
client, and the configured TURN credential refreshers.
On Wayland the configured DPI becomes the session compositor's output
scale (96 is unity, so nothing is applied then), and with none up yet
the input handler re-applies when it adopts one; the X11 desktop's
density was settled before the listener opened, so the first session
compares against it (`handle_scaling` re-derives the cursor size 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()))
startup_dpi = int(float(getattr(settings, "scaling_dpi", "96") or 96))
if IS_WAYLAND:
if startup_dpi != 96:
if self.input_handler is not None:
await self.input_handler.realize_wayland_dpi(startup_dpi)
self._last_applied_dpi = startup_dpi
else:
# Settled before the listener opened; a page's first DPI compares against it.
self._last_applied_dpi = applied_dpi() or startup_dpi
if not IS_WAYLAND:
await retire_displays()
# 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.resource_monitor:
self.resource_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()Returns
Nonefuncshutdown() -> NoneGracefully shutdown all components.
Source Code
async def shutdown(self) -> None:
"""Gracefully shutdown all components."""
if self._shutdown_called:
logger.debug("Shutdown already called, skipping")
return
self._shutdown_called = True
logger.debug("Starting shutdown sequence")
if self.rtc_app is not None:
capture_demand.detach(self.rtc_app)
self._cancel_primary_stop_grace()
for task in list(self.tasks):
try:
if not task.done():
task.cancel()
except Exception:
logger.exception("Error canceling 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.debug(f"{name} was canceled 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")
stop_coros.append(
(
_await_with_timeout(
self.input_handler.disconnect(), "input_handler.disconnect", 3.0
)
)
)
if self.resource_monitor:
stop_coros.append(
(_await_with_timeout(self.resource_monitor.stop(), "resource_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
if self.rtc_app is not None:
try:
await self.rtc_app.close_ice_muxes()
except Exception:
logger.exception("Error releasing the shared ICE ports")
self.rtc_app = None
self.input_handler = None
self.resource_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")Returns
Nonefuncrun() -> NoneBring 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()Returns
Nonefuncstart() -> NoneSupervisor 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()Returns
Nonefuncstop() -> NoneSignal 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()Returns
Nonefuncregister_routes(api_prefix, main_router) -> NoneRegister 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)paramapi_prefixstrparammain_routerweb.UrlDispatcherReturns
Nonefuncrtc_ws_handler(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 wsparamrequestweb.RequestReturns
typing.Union[aiohttp.web.aiohttp.web.Response, aiohttp.web.aiohttp.web.WebSocketResponse]funcuplink_session_conns() -> 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 connsReturns
typing.List[typing.Tuple[typing.Any, typing.Optional[str], typing.Optional[str]]]funchandle_turn_req(request) -> web.ResponseServe 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)paramrequestweb.RequestReturns
aiohttp.web.aiohttp.web.Response