Skip to main content

hidpp/feature/
change_host.rs

1//! Implements the `ChangeHost` feature (ID `0x1814`) that selects which host /
2//! RF channel a multi-host device is connected to.
3
4use openlogi_hidpp_derive::Feature;
5
6use crate::{feature::FeatureEndpoint, protocol::v20::Hidpp20Error};
7
8bitflags::bitflags! {
9    /// Host-switching capabilities reported by [`ChangeHostFeature::get_host_info`].
10    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
12    pub struct ChangeHostCapabilities: u8 {
13        /// Enhanced host switching is enabled: on a failed connection the device
14        /// falls back to another host with a non-zero cookie before returning to
15        /// the original host.
16        const ENHANCED_HOST_SWITCH = 1 << 0;
17    }
18}
19
20/// Host configuration returned by [`ChangeHostFeature::get_host_info`].
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[non_exhaustive]
24pub struct ChangeHostInfo {
25    /// Number of hosts / RF channels.
26    pub host_count: u8,
27    /// Current host index, in `0..host_count`.
28    pub current_host: u8,
29    /// Host-switching capabilities.
30    pub capabilities: ChangeHostCapabilities,
31}
32
33/// Implements the `ChangeHost` / `0x1814` feature.
34#[derive(Clone, Feature)]
35#[creatable(id = 0x1814, version = 0)]
36pub struct ChangeHostFeature {
37    /// The endpoint this feature talks to.
38    endpoint: FeatureEndpoint,
39}
40
41impl ChangeHostFeature {
42    /// Retrieves the host count, current host and host-switching flags.
43    pub async fn get_host_info(&self) -> Result<ChangeHostInfo, Hidpp20Error> {
44        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
45        Ok(ChangeHostInfo {
46            host_count: payload[0],
47            current_host: payload[1],
48            capabilities: ChangeHostCapabilities::from_bits_retain(payload[2]),
49        })
50    }
51
52    /// Selects `host` as the current host.
53    ///
54    /// This is sent fire-and-forget: a successful switch usually resets the
55    /// device, so no response is awaited. The device drops off the current host
56    /// once it acts on the request.
57    pub async fn set_current_host(&self, host: u8) -> Result<(), Hidpp20Error> {
58        self.endpoint.notify(1, [host, 0, 0]).await
59    }
60
61    /// Retrieves the persistent per-host cookie bytes.
62    ///
63    /// `host_count` is the value from [`ChangeHostInfo::host_count`]; the device
64    /// returns one cookie byte per host and does not delimit the list.
65    pub async fn get_cookies(&self, host_count: u8) -> Result<Vec<u8>, Hidpp20Error> {
66        let count = usize::from(host_count);
67        let payload = self.endpoint.call(2, [0; 3]).await?.extend_payload();
68        if count > payload.len() {
69            return Err(Hidpp20Error::UnsupportedResponse);
70        }
71        Ok(payload[..count].to_vec())
72    }
73
74    /// Writes the persistent `cookie` byte for `host`.
75    ///
76    /// Cookies are arbitrary software-defined bytes stored in the device's
77    /// non-volatile memory; the firmware clears a host's cookie when a new host
78    /// connects to that slot.
79    pub async fn set_cookie(&self, host: u8, cookie: u8) -> Result<(), Hidpp20Error> {
80        self.endpoint.call(3, [host, cookie, 0]).await?;
81        Ok(())
82    }
83}