Skip to main content

hidpp/feature/device_friendly_name/
mod.rs

1//! Implements the `DeviceFriendlyName` feature (ID `0x0007`) that provides
2//! functionality to set and retrieve a custom device name.
3
4use std::sync::Arc;
5
6use crate::{
7    channel::HidppChannel,
8    feature::{CreatableFeature, Feature, FeatureEndpoint},
9    protocol::v20::Hidpp20Error,
10};
11
12/// Implements the `DeviceFriendlyName` / `0x0007` feature.
13#[derive(Clone)]
14pub struct DeviceFriendlyNameFeature {
15    /// The endpoint this feature talks to.
16    endpoint: FeatureEndpoint,
17}
18
19impl CreatableFeature for DeviceFriendlyNameFeature {
20    const ID: u16 = 0x0007;
21    const STARTING_VERSION: u8 = 0;
22
23    fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
24        Self {
25            endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
26        }
27    }
28}
29
30impl Feature for DeviceFriendlyNameFeature {}
31
32impl DeviceFriendlyNameFeature {
33    /// Retrieves the length data of the friendly device name feature.
34    pub async fn get_friendly_name_length(&self) -> Result<DeviceFriendlyNameLength, Hidpp20Error> {
35        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
36
37        Ok(DeviceFriendlyNameLength {
38            name_length: payload[0],
39            name_max_length: payload[1],
40            default_name_length: payload[2],
41        })
42    }
43
44    /// Retrieves a chunk of characters of the friendly name of the device,
45    /// starting at a specific index (inclusive).
46    ///
47    /// This function will always retrieve 15 bytes, filling up the rest with
48    /// zeroes if the chunk is shorter than that.
49    ///
50    /// Use this function in conjunction with [`Self::get_friendly_name_length`]
51    /// to retrieve the whole friendly name of the device.\
52    /// A convenience wrapper implementing this functionality is provided as
53    /// [`Self::get_whole_friendly_name`].
54    pub async fn get_friendly_name(&self, index: u8) -> Result<[u8; 15], Hidpp20Error> {
55        let payload = self
56            .endpoint
57            .call(1, [index, 0x00, 0x00])
58            .await?
59            .extend_payload();
60
61        Ok(payload[1..].try_into().unwrap())
62    }
63
64    /// Retrieves the whole friendly name of the device by first calling
65    /// [`Self::get_friendly_name_length`] once and then repeatedly calling
66    /// [`Self::get_friendly_name`] until all characters were received.
67    pub async fn get_whole_friendly_name(&self) -> Result<String, Hidpp20Error> {
68        let count = self.get_friendly_name_length().await?.name_length;
69        let mut string = String::with_capacity(count as usize);
70
71        let mut len = 0;
72        while len < count as usize {
73            let part = self.get_friendly_name(len as u8).await?;
74            string.push_str(str::from_utf8(&part).map_err(|_| Hidpp20Error::UnsupportedResponse)?);
75            len = string.len();
76        }
77
78        Ok(string.trim_end_matches(char::from(0)).to_string())
79    }
80
81    /// Retrieves a chunk of characters of the default friendly name of the
82    /// device, starting at a specific index (inclusive).
83    ///
84    /// This function will always retrieve 15 bytes, filling up the rest with
85    /// zeroes if the chunk is shorter than that.
86    ///
87    /// Use this function in conjunction with [`Self::get_friendly_name_length`]
88    /// to retrieve the whole default friendly name of the device.\
89    /// A convenience wrapper implementing this functionality is provided as
90    /// [`Self::get_whole_default_friendly_name`].
91    pub async fn get_default_friendly_name(&self, index: u8) -> Result<[u8; 15], Hidpp20Error> {
92        let payload = self
93            .endpoint
94            .call(2, [index, 0x00, 0x00])
95            .await?
96            .extend_payload();
97
98        Ok(payload[1..].try_into().unwrap())
99    }
100
101    /// Retrieves the whole default friendly name of the device by first calling
102    /// [`Self::get_friendly_name_length`] once and then repeatedly calling
103    /// [`Self::get_default_friendly_name`] until all characters were received.
104    pub async fn get_whole_default_friendly_name(&self) -> Result<String, Hidpp20Error> {
105        let count = self.get_friendly_name_length().await?.default_name_length;
106        let mut string = String::with_capacity(count as usize);
107
108        let mut len = 0;
109        while len < count as usize {
110            let part = self.get_default_friendly_name(len as u8).await?;
111            string.push_str(str::from_utf8(&part).map_err(|_| Hidpp20Error::UnsupportedResponse)?);
112            len = string.len();
113        }
114
115        Ok(string.trim_end_matches(char::from(0)).to_string())
116    }
117
118    /// Sets a chunk of the friendly device name, starting at a specific index
119    /// (inclusive).
120    ///
121    /// If the index and chunk combination would exceed the
122    /// [`DeviceFriendlyNameLength::name_max_length`], the name is automatically
123    /// truncated by the device.
124    ///
125    /// Returns the new total length of the friendly device name.
126    ///
127    /// A convenience wrapper setting the whole friendly device name at once is
128    /// provided as [`Self::set_whole_device_name`].
129    pub async fn set_friendly_name(&self, index: u8, chunk: [u8; 15]) -> Result<u8, Hidpp20Error> {
130        let mut data = [0u8; 16];
131        data[0] = index;
132        data[1..].copy_from_slice(&chunk);
133
134        let payload = self.endpoint.call_long(3, data).await?.extend_payload();
135
136        Ok(payload[0])
137    }
138
139    /// Sets the whole friendly device name, truncating the value to a maximum
140    /// of [`DeviceFriendlyNameLength::name_max_length`] bytes.
141    ///
142    /// This method calls [`Self::get_friendly_name_length`] first to retrieve
143    /// the maximum length and then repeatedly calls [`Self::set_friendly_name`]
144    /// until the whole name is set.
145    ///
146    /// Returns the total length of the name after setting it,
147    pub async fn set_whole_device_name(&self, name: String) -> Result<u8, Hidpp20Error> {
148        let max_len = self.get_friendly_name_length().await?.name_max_length;
149        let mut bytes = name.into_bytes();
150        bytes.truncate(max_len as usize);
151        let chunks = bytes.chunks_exact(15);
152        let remainder = chunks.remainder();
153
154        let mut index = 0;
155        for chunk in chunks {
156            index += self
157                .set_friendly_name(index, chunk.try_into().unwrap())
158                .await?;
159        }
160
161        if !remainder.is_empty() {
162            let mut chunk = [0u8; 15];
163            chunk[..remainder.len()].copy_from_slice(remainder);
164            index += self.set_friendly_name(index, chunk).await?;
165        }
166
167        Ok(index)
168    }
169
170    /// Resets the friendly device name to the default one.
171    ///
172    /// Returns the total length of the name after resetting it,
173    pub async fn reset_friendly_name(&self) -> Result<u8, Hidpp20Error> {
174        Ok(self.endpoint.call(4, [0; 3]).await?.extend_payload()[0])
175    }
176}
177
178/// Represents the length data as returned by
179/// [`DeviceFriendlyNameFeature::get_friendly_name_length`].
180#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
181#[cfg_attr(feature = "serde", derive(serde::Serialize))]
182#[non_exhaustive]
183pub struct DeviceFriendlyNameLength {
184    /// The current length of the friendly device name.
185    pub name_length: u8,
186
187    /// The maximum length of the friendly device name.
188    pub name_max_length: u8,
189
190    /// The length of the default friendly device name.
191    pub default_name_length: u8,
192}