Skip to main content

hidpp/feature/change_host/
mod.rs

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