pipewire_native/proxy/
device.rs

1// SPDX-License-Identifier: MIT
2// SPDX-FileCopyrightText: Copyright (c) 2025 Asymptotic Inc.
3// SPDX-FileCopyrightText: Copyright (c) 2025 Arun Raghavan
4
5use std::sync::{Arc, Mutex, RwLock};
6
7use bitflags::bitflags;
8use pipewire_native_spa as spa;
9
10use crate::{
11    core::Core,
12    new_refcounted,
13    properties::Properties,
14    protocol,
15    proxy::{HasProxy, Proxy},
16    proxy_object_invoke, refcounted,
17    types::{self, params::ParamBuilder},
18    HookId, Id, Refcounted,
19};
20
21refcounted! {
22    /// Proxy that represents a device that is connected to the server.
23    pub struct Device {
24        proxy: RwLock<Option<Proxy<Device>>>,
25        methods: Arc<Mutex<DeviceMethods<Device>>>,
26        hooks: Arc<Mutex<spa::hook::HookList<DeviceEvents>>>,
27    }
28}
29
30#[allow(clippy::type_complexity)]
31pub(crate) struct DeviceMethods<T: HasProxy + Refcounted> {
32    pub(crate) subscribe_params:
33        Box<dyn FnMut(&Proxy<T>, &[spa::param::ParamType]) -> std::io::Result<()>>,
34    pub(crate) enum_params: Box<
35        dyn FnMut(
36            &Proxy<T>,
37            u32,
38            Option<spa::param::ParamType>,
39            u32,
40            u32,
41            Option<ParamBuilder>,
42        ) -> std::io::Result<()>,
43    >,
44    pub(crate) set_param: Box<
45        dyn FnMut(
46            &Proxy<T>,
47            spa::param::ParamType,
48            spa::pod::types::ObjectType,
49            u32,
50            Box<dyn FnOnce(spa::pod::builder::ObjectBuilder) -> spa::pod::builder::ObjectBuilder>,
51        ) -> std::io::Result<()>,
52    >,
53}
54
55bitflags! {
56    /// A bit mask of changes signalled in the [DeviceEvents::info] event.
57    #[repr(C)]
58    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
59    pub struct DeviceChangeMask : u32 {
60        /// Device properties changed.
61        const PROPS = (1 << 0);
62        /// Device params changed.
63        const PARAMS = (1 << 1);
64    }
65}
66
67/// Device information that is provided in a [DeviceEvents::info] event.
68pub struct DeviceInfo<'a> {
69    /// The ID of the device.
70    pub id: Id,
71    /// What changed since the last call.
72    pub mask: DeviceChangeMask,
73    /// The device's properties.
74    pub props: &'a Properties,
75    /// Device parameters that changed.
76    pub params: &'a [(spa::param::ParamType, spa::param::ParamInfoFlags)],
77}
78
79/// Device events that can be subscribed to.
80#[allow(clippy::type_complexity)]
81#[derive(Default)]
82pub struct DeviceEvents {
83    /// Device information became available, or changed.
84    pub info: Option<Box<dyn FnMut(&DeviceInfo<'_>) + Send>>,
85    /// Device permissions, notified due to a [Device::subscribe_params] or [Device::enum_params]
86    /// call.
87    pub param:
88        Option<Box<dyn FnMut(u32, spa::param::ParamType, u32, u32, &spa::pod::RawPodOwned) + Send>>,
89}
90
91impl HasProxy for Device {
92    fn type_(&self) -> types::ObjectType {
93        types::interface::DEVICE
94    }
95
96    fn version(&self) -> u32 {
97        3
98    }
99
100    fn proxy(&self) -> Proxy<Self> {
101        self.inner
102            .proxy
103            .read()
104            .unwrap()
105            .as_ref()
106            .expect("Device proxy should be initialised on creation")
107            .clone()
108    }
109}
110
111impl Device {
112    pub(crate) fn new(core: &Core) -> Self {
113        let this = Self {
114            inner: new_refcounted(InnerDevice::new(core)),
115        };
116
117        let id = core.next_proxy_id();
118        this.inner
119            .proxy
120            .write()
121            .unwrap()
122            .replace(Proxy::new(id, &this));
123        core.add_proxy(&this, id);
124
125        this
126    }
127
128    /// Register for notifications of device events.
129    pub fn add_listener(&self, events: DeviceEvents) -> HookId {
130        self.inner.hooks.lock().unwrap().append(events)
131    }
132
133    /// Remove a set of event listeners.
134    pub fn remove_listener(&self, hook_id: HookId) {
135        self.inner.hooks.lock().unwrap().remove(hook_id);
136    }
137
138    /// Register for notifications of the specified param types.
139    pub fn subscribe_params(&self, ids: &[spa::param::ParamType]) -> std::io::Result<()> {
140        let proxy = self.proxy();
141        proxy_object_invoke!(proxy, subscribe_params, ids)
142    }
143
144    /// Enumerate params (via [DeviceEvents::param]). Set `id` to [None] to query all param types.
145    pub fn enum_params(
146        &self,
147        seq: u32,
148        id: Option<spa::param::ParamType>,
149        start: u32,
150        num: u32,
151        filter: Option<ParamBuilder>,
152    ) -> std::io::Result<()> {
153        let proxy = self.proxy();
154        proxy_object_invoke!(proxy, enum_params, seq, id, start, num, filter)
155    }
156
157    /// Set a parameter on the device.
158    pub fn set_param(
159        &self,
160        param_id: spa::param::ParamType,
161        object_type: spa::pod::types::ObjectType,
162        flags: u32,
163        builder: Box<
164            dyn FnOnce(spa::pod::builder::ObjectBuilder) -> spa::pod::builder::ObjectBuilder,
165        >,
166    ) -> std::io::Result<()> {
167        let proxy = self.proxy();
168        proxy_object_invoke!(proxy, set_param, param_id, object_type, flags, builder)
169    }
170
171    pub(crate) fn methods(&self) -> Arc<Mutex<DeviceMethods<Device>>> {
172        self.inner.methods.clone()
173    }
174
175    pub(crate) fn events(&self) -> Arc<Mutex<spa::hook::HookList<DeviceEvents>>> {
176        self.inner.hooks.clone()
177    }
178}
179
180impl InnerDevice {
181    fn new(core: &Core) -> Self {
182        Self {
183            proxy: RwLock::new(None),
184            methods: Arc::new(Mutex::new(protocol::marshal::device::Methods::marshal(
185                core.connection(),
186            ))),
187            hooks: spa::hook::HookList::new(),
188        }
189    }
190}