Skip to main content

libobsbot_core/transport/
usb.rs

1// SPDX-License-Identifier: GPL-3.0-only
2//! Linux UVC class-specific control transfer transport.
3//!
4//! Issues UVC class-specific `SET_CUR` and `GET_*` requests via the
5//! `uvcvideo` driver's ioctl interface on `/dev/videoN`. This coexists
6//! with v4l2 streaming apps (Zoom / OBS / Cheese keep working). See
7//! See `super::uvcvideo` for the V4L2 + UVCIOC dispatch.
8//!
9//! macOS and Windows transports are planned and will live in sibling
10//! modules with the same `Transport` surface.
11
12#![cfg(target_os = "linux")]
13
14use std::fs::File;
15
16use crate::discovery::DeviceInfo;
17use crate::transport::Transport;
18use crate::uvc::UvcGet;
19use crate::Result;
20
21/// UVC class-specific `SET_CUR` `bRequest`.
22const SET_CUR: u8 = 0x01;
23
24/// Per-device transport. Owns the `/dev/videoN` handle the kernel routes
25/// our class-specific transfers through.
26pub(crate) struct UsbTransport {
27    vendor_id: u16,
28    product_id: u16,
29    v4l2: File,
30}
31
32impl UsbTransport {
33    pub(crate) fn open(info: &DeviceInfo) -> Result<Self> {
34        let v4l2 = super::uvcvideo::open_for(info.busnum, info.devnum)?;
35        tracing::debug!(
36            vid = format_args!("{:04x}", info.vendor_id),
37            pid = format_args!("{:04x}", info.product_id),
38            busnum = info.busnum,
39            devnum = info.devnum,
40            "opened uvcvideo transport",
41        );
42        Ok(Self {
43            vendor_id: info.vendor_id,
44            product_id: info.product_id,
45            v4l2,
46        })
47    }
48}
49
50impl Transport for UsbTransport {
51    fn uvc_set(&self, entity: u8, selector: u8, payload: &[u8]) -> Result<()> {
52        tracing::trace!(
53            vid = format_args!("{:04x}", self.vendor_id),
54            pid = format_args!("{:04x}", self.product_id),
55            entity,
56            selector = format_args!("{selector:#04x}"),
57            len = payload.len(),
58            "uvc_set",
59        );
60        if let Some(result) = super::uvcvideo::v4l2_set(&self.v4l2, entity, selector, payload) {
61            return result;
62        }
63        // Vendor XU goes through UVCIOC_CTRL_QUERY (requires UVCIOC_CTRL_MAP
64        // for each control; that part lands with per-XU pcaps).
65        let mut buf = payload.to_vec();
66        super::uvcvideo::xu_query(&self.v4l2, entity, selector, SET_CUR, &mut buf).map(|_| ())
67    }
68
69    fn uvc_get(&self, req: UvcGet, entity: u8, selector: u8, out: &mut [u8]) -> Result<usize> {
70        tracing::trace!(
71            vid = format_args!("{:04x}", self.vendor_id),
72            pid = format_args!("{:04x}", self.product_id),
73            entity,
74            selector = format_args!("{selector:#04x}"),
75            req = ?req,
76            length = out.len(),
77            "uvc_get",
78        );
79        if let Some(result) = super::uvcvideo::v4l2_get(&self.v4l2, req, entity, selector, out) {
80            return result;
81        }
82        super::uvcvideo::xu_query(&self.v4l2, entity, selector, req as u8, out)
83    }
84}