Skip to main content

hidpp/feature/
hosts_info.rs

1//! Implements `HostsInfo` (feature `0x1815`) for multi-host devices.
2
3use num_enum::TryFromPrimitive;
4use openlogi_hidpp_derive::Feature;
5
6use crate::{feature::FeatureEndpoint, protocol::v20::Hidpp20Error};
7
8bitflags::bitflags! {
9    /// Host-management capabilities.
10    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
12    pub struct HostsInfoCapabilities: u8 {
13        /// Host names can be read.
14        const GET_NAME = 1 << 0;
15        /// Host names can be written.
16        const SET_NAME = 1 << 1;
17        /// Host slots can be moved.
18        const MOVE_HOST = 1 << 2;
19        /// Host slots can be deleted.
20        const DELETE_HOST = 1 << 3;
21        /// Host OS versions can be written.
22        const SET_OS_VERSION = 1 << 4;
23    }
24}
25
26bitflags::bitflags! {
27    /// Supported host descriptor families.
28    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
30    pub struct HostDescriptorCapabilities: u8 {
31        /// eQuad host descriptors are available.
32        const EQUAD = 1 << 0;
33        /// USB host descriptors are available.
34        const USB = 1 << 1;
35        /// Bluetooth classic host descriptors are available.
36        const BT = 1 << 2;
37        /// Bluetooth Low Energy host descriptors are available.
38        const BLE = 1 << 3;
39    }
40}
41
42/// A host slot selector.
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize))]
45#[non_exhaustive]
46pub enum HostIndex {
47    /// The host slot currently selected by the device.
48    Current,
49    /// A zero-based host slot index.
50    Slot(u8),
51}
52
53impl From<HostIndex> for u8 {
54    fn from(value: HostIndex) -> Self {
55        match value {
56            HostIndex::Current => 0xff,
57            HostIndex::Slot(index) => index,
58        }
59    }
60}
61
62impl From<u8> for HostIndex {
63    fn from(value: u8) -> Self {
64        if value == 0xff {
65            Self::Current
66        } else {
67            Self::Slot(value)
68        }
69    }
70}
71
72/// Pairing status for a host slot.
73#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TryFromPrimitive)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize))]
75#[non_exhaustive]
76#[repr(u8)]
77pub enum HostSlotStatus {
78    /// The host slot is empty.
79    Empty = 0,
80    /// The host slot is paired.
81    Paired = 1,
82}
83
84/// Bus type associated with a host slot.
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TryFromPrimitive)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize))]
87#[non_exhaustive]
88#[repr(u8)]
89pub enum HostBusType {
90    /// Undefined or unknown bus type.
91    Undefined = 0,
92    /// eQuad wireless.
93    Equad = 1,
94    /// USB.
95    Usb = 2,
96    /// Bluetooth classic.
97    Bt = 3,
98    /// Bluetooth Low Energy.
99    Ble = 4,
100    /// BLE Pro / Logi Bolt.
101    BlePro = 5,
102}
103
104/// Static information about the `HostsInfo` feature.
105#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize))]
107#[non_exhaustive]
108pub struct HostsInfoFeatureInfo {
109    /// Host-management capabilities.
110    pub capabilities: HostsInfoCapabilities,
111    /// Host descriptor capabilities.
112    pub descriptor_capabilities: HostDescriptorCapabilities,
113    /// Number of host slots.
114    pub host_count: u8,
115    /// Current host slot index.
116    pub current_host: HostIndex,
117}
118
119/// Information about one host slot.
120#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize))]
122#[non_exhaustive]
123pub struct HostInfo {
124    /// Host slot index returned by the device.
125    pub host_index: HostIndex,
126    /// Pairing status.
127    pub status: HostSlotStatus,
128    /// Bus type used by this host slot.
129    pub bus_type: HostBusType,
130    /// Number of descriptor pages.
131    pub page_count: u8,
132    /// Current friendly-name length.
133    pub name_len: u8,
134    /// Maximum friendly-name length.
135    pub name_max_len: u8,
136}
137
138/// Raw host descriptor page.
139#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize))]
141#[non_exhaustive]
142pub struct HostDescriptorPage {
143    /// Host slot index returned by the device.
144    pub host_index: HostIndex,
145    /// Descriptor bus type, decoded from the page header when known.
146    pub bus_type: HostBusType,
147    /// Descriptor page index, decoded from the page header.
148    pub page_index: u8,
149    /// Raw descriptor body bytes.
150    pub body: [u8; 14],
151}
152
153/// Implements the `HostsInfo` / `0x1815` feature.
154#[derive(Clone, Feature)]
155#[creatable(id = 0x1815, version = 2)]
156pub struct HostsInfoFeature {
157    /// The endpoint this feature talks to.
158    endpoint: FeatureEndpoint,
159}
160
161impl HostsInfoFeature {
162    /// Retrieves feature capabilities and host-slot count.
163    pub async fn get_feature_info(&self) -> Result<HostsInfoFeatureInfo, Hidpp20Error> {
164        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
165        Ok(HostsInfoFeatureInfo {
166            capabilities: HostsInfoCapabilities::from_bits_retain(payload[0]),
167            descriptor_capabilities: HostDescriptorCapabilities::from_bits_retain(payload[1]),
168            host_count: payload[2],
169            current_host: HostIndex::from(payload[3]),
170        })
171    }
172
173    /// Retrieves information for `host`.
174    pub async fn get_host_info(&self, host: HostIndex) -> Result<HostInfo, Hidpp20Error> {
175        let payload = self
176            .endpoint
177            .call(1, [u8::from(host), 0, 0])
178            .await?
179            .extend_payload();
180        Ok(HostInfo {
181            host_index: HostIndex::from(payload[0]),
182            status: HostSlotStatus::try_from(payload[1])
183                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
184            bus_type: HostBusType::try_from(payload[2])
185                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
186            page_count: payload[3],
187            name_len: payload[4],
188            name_max_len: payload[5],
189        })
190    }
191
192    /// Retrieves a raw descriptor `page` for `host`.
193    pub async fn get_host_descriptor(
194        &self,
195        host: HostIndex,
196        page: u8,
197    ) -> Result<HostDescriptorPage, Hidpp20Error> {
198        let payload = self
199            .endpoint
200            .call(2, [u8::from(host), page, 0])
201            .await?
202            .extend_payload();
203        let mut body = [0; 14];
204        body.copy_from_slice(&payload[2..16]);
205        Ok(HostDescriptorPage {
206            host_index: HostIndex::from(payload[0]),
207            bus_type: HostBusType::try_from(payload[1] >> 4)
208                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
209            page_index: payload[1] & 0x0f,
210            body,
211        })
212    }
213}