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