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}