Main Loop And Registry Discovery
Main Loop And Registry Discovery
PipeWire registry updates are delivered through a GLib main context. A program
that starts PwgRegistry should run a GMainLoop or otherwise
iterate the thread-default main context. Live wrappers such as PwgNode,
PwgDevice, PwgMetadata, and PwgStream deliver their
signals through the same context.
import gi
gi.require_version("GLib", "2.0")
gi.require_version("Pwg", "0.1")
from gi.repository import GLib, Pwg
Pwg.init()
core = Pwg.Core.new()
registry = Pwg.Registry.new(core)
registry.start()
loop = GLib.MainLoop()
def on_global_added(_registry, global_):
print(global_.get_id(), global_.get_interface_type(), global_.dup_name() or "")
registry.connect("global-added", on_global_added)
GLib.timeout_add_seconds(2, loop.quit)
loop.run()
The registry keeps immutable PwgGlobal descriptors in a
GListModel. Snapshot helper methods return new list models so callers
can inspect a stable set of objects without holding the registry’s internal model.
For client-specific discovery, wrap a client global in PwgClientInfo:
clients = registry.dup_globals_by_interface("PipeWire:Interface:Client")
if clients.get_n_items() > 0:
client = Pwg.ClientInfo.new_from_global(clients.get_item(0))
if client is not None:
print(
client.dup_app_name() or client.dup_name() or "",
client.dup_app_id() or "",
client.dup_process_binary() or "",
)
For device-specific discovery, wrap a device global in PwgDeviceInfo:
devices = registry.dup_globals_by_interface("PipeWire:Interface:Device")
if devices.get_n_items() > 0:
device = Pwg.DeviceInfo.new_from_global(devices.get_item(0))
if device is not None:
print(
device.dup_description() or device.dup_nick() or device.dup_name() or "",
device.dup_api() or "",
device.dup_form_factor() or "",
)
For link-specific discovery, wrap a link global in PwgLinkInfo:
links = registry.dup_globals_by_interface("PipeWire:Interface:Link")
if links.get_n_items() > 0:
link = Pwg.LinkInfo.new_from_global(links.get_item(0))
if link is not None:
print(
link.dup_output_node_id() or "",
link.dup_output_port_id() or "",
"->",
link.dup_input_node_id() or "",
link.dup_input_port_id() or "",
)
For node-specific discovery, wrap a node global in PwgNodeInfo:
nodes = registry.dup_globals_by_interface("PipeWire:Interface:Node")
if nodes.get_n_items() > 0:
node_info = Pwg.NodeInfo.new_from_global(nodes.get_item(0))
if node_info is not None:
print(node_info.dup_name() or "", node_info.dup_media_class() or "")
For node parameter inspection, bind the same global with
PwgNode. The live node proxy exposes copied PwgParamInfo
descriptors and emits copied PwgParam values when enumeration results arrive:
node = Pwg.Node.new(core, nodes.get_item(0))
if node is not None:
node.start()
for index in range(node.get_param_infos().get_n_items()):
param_info = node.get_param_infos().get_item(index)
print(param_info.get_id(), param_info.dup_name() or "")
def on_param(_node, param):
print(
param.get_id(),
param.dup_name() or "",
param.dup_format_media_type_name() or "",
param.dup_format_media_subtype_name() or "",
param.dup_summary(),
)
audio_format = param.dup_audio_format()
if audio_format is not None:
print(
audio_format.get_sample_format(),
audio_format.get_rate(),
audio_format.get_channels(),
)
node.connect("param", on_param)
node.enum_all_params()
To observe later node parameter changes, subscribe to the parameter ids that the
application cares about. The subscription call replaces the active set of ids;
an empty au clears it. PipeWire may deliver current values and later changes
through the same signal, so applications should treat subscription events as
“the parameter changed or was delivered” rather than trying to infer intent from
the parameter sequence number.
volume_param_id = 2
node.connect("param", on_param)
node.subscribe_params(GLib.Variant("au", [volume_param_id]))
# Later, clear the active subscription.
node.subscribe_params(GLib.Variant("au", []))
Nodes can also queue copied parameter updates that were built by this library. The return value only means PipeWire accepted the request for dispatch; it is not an applied-state acknowledgment:
param = Pwg.Param.new_props_volume(0.75)
if param is not None:
node.set_param(param)
For live device parameter inspection, bind a device global with
PwgDevice. The API mirrors PwgNode: parameter descriptors are
published through PwgParamInfo, enumeration results are copied
PwgParam objects, and subscriptions are event-driven.
PipeWire devices often advertise route parameters that describe input or output
routes exposed by the device. Wrap copied EnumRoute or Route parameters in
PwgRouteInfo to inspect route fields without parsing SPA POD data in
application code:
device_proxy = Pwg.Device.new(core, devices.get_item(0))
if device_proxy is not None:
device_proxy.start()
route_ids = []
for index in range(device_proxy.get_param_infos().get_n_items()):
param_info = device_proxy.get_param_infos().get_item(index)
if param_info.dup_name() == "Route":
route_ids.append(param_info.get_id())
def on_device_param(_device, param):
route = Pwg.RouteInfo.new_from_param(param)
if route is not None:
print(
route.get_index(),
route.dup_direction() or "",
route.dup_name() or "",
route.dup_description() or "",
route.dup_availability() or "",
)
device_proxy.connect("param", on_device_param)
for route_id in route_ids:
device_proxy.enum_params(route_id, 0, 0)
device_proxy.subscribe_params(GLib.Variant("au", route_ids))
Route parameters are inspection data from PipeWire. pipewire-gobject does not
select hardware routes, change defaults, or replace WirePlumber policy. Apps
that need to react to route changes should subscribe and update their own UI or
state when new copied route params arrive.
For modules that expose named float controls, build a Props parameter from a
GLib.Variant dictionary with signature a{sd}:
controls = GLib.Variant(
"a{sd}",
{
"band_l_0:b0": 1.0,
"band_l_0:b1": -0.25,
},
)
param = Pwg.Param.new_props_controls(controls)
if param is not None:
node.set_param(param)
App-owned PipeWire implementation modules can be loaded from PwgCore.
The returned PwgImplModule keeps the module alive until it is unloaded
or dropped:
module = core.load_module("libpipewire-module-loopback", "node.name=pwg-loopback")
try:
print(module.get_loaded())
finally:
module.unload()
For app-owned audio capture, PwgStream is the canonical API. It exposes
negotiated format, level, and optional copied audio blocks. The requested format
defaults to F32, 48000 Hz, stereo; set it before starting the stream when the
application needs a different F32 mono or stereo rate:
stream = Pwg.Stream.new_audio_capture("alsa_output.example", True)
stream.set_requested_format("F32", 48000, 2)
stream.set_deliver_audio_blocks(True)
def on_audio_block(_stream, block):
print(block.get_n_frames(), block.get_peak())
stream.connect("audio-block", on_audio_block)
stream.start()
For port-specific discovery, wrap a port global in PwgPortInfo:
ports = registry.dup_globals_by_interface("PipeWire:Interface:Port")
if ports.get_n_items() > 0:
port = Pwg.PortInfo.new_from_global(ports.get_item(0))
if port is not None:
print(
port.dup_direction() or "",
port.dup_name() or "",
port.dup_audio_channel() or "",
)
The registry is a discovery API, not a session manager. It does not make routing or default-device policy decisions.