Skip to main content

hidpp/feature/hosts_info/
mod.rs

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