1use std::fmt;
7
8use serde::{Deserialize, Serialize};
9
10use super::error::WriteError;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum HostConnection {
18 Unknown,
20 Unifying,
22 Usb,
24 Bluetooth,
26 Bolt,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35pub enum HostOsKind {
36 Windows,
38 WindowsEmbedded,
40 Linux,
42 ChromeOs,
44 Android,
46 MacOs,
48 Ios,
50 WebOs,
52 Tizen,
54}
55
56impl fmt::Display for HostOsKind {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 f.write_str(match self {
59 Self::Windows => "Windows",
60 Self::WindowsEmbedded => "Windows Embedded",
61 Self::Linux => "Linux",
62 Self::ChromeOs => "ChromeOS",
63 Self::Android => "Android",
64 Self::MacOs => "macOS",
65 Self::Ios => "iOS",
66 Self::WebOs => "webOS",
67 Self::Tizen => "Tizen",
68 })
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74pub struct HostOsInfo {
75 pub kind: HostOsKind,
77 pub major: u8,
79 pub minor: u16,
81 pub patch: u16,
83}
84
85impl fmt::Display for HostOsInfo {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 if self.major == 0 && self.minor == 0 {
90 write!(f, "{}", self.kind)
91 } else {
92 write!(f, "{} {}.{}", self.kind, self.major, self.minor)
93 }
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
99pub struct EasySwitchSlot {
100 pub index: u8,
102 pub paired: bool,
104 pub connection: HostConnection,
106 pub name: Option<String>,
110 pub name_max_len: u8,
112 pub os: Option<HostOsInfo>,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
119pub struct EasySwitchHosts {
120 pub current: Option<u8>,
122 pub slots: Vec<EasySwitchSlot>,
124 pub can_rename: bool,
126}
127
128impl EasySwitchHosts {
129 #[must_use]
131 pub fn slot(&self, index: u8) -> Option<&EasySwitchSlot> {
132 self.slots.iter().find(|slot| slot.index == index)
133 }
134
135 pub fn name_for<'a>(&self, index: u8, name: &'a str) -> Result<&'a str, WriteError> {
143 if !self.can_rename {
144 return Err(WriteError::HostRenameUnsupported);
145 }
146 let slot = self
147 .slot(index)
148 .filter(|slot| slot.paired)
149 .ok_or(WriteError::HostSlotEmpty { slot: index })?;
150 let name = name.trim();
151 if name.is_empty() || name.contains('\0') || name.len() > usize::from(slot.name_max_len) {
152 return Err(WriteError::HostNameRejected {
153 max_len: slot.name_max_len,
154 });
155 }
156 Ok(name)
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::{
163 EasySwitchHosts, EasySwitchSlot, HostConnection, HostOsInfo, HostOsKind, WriteError,
164 };
165
166 fn slot(index: u8, paired: bool) -> EasySwitchSlot {
167 EasySwitchSlot {
168 index,
169 paired,
170 connection: HostConnection::Bluetooth,
171 name: paired.then(|| "Studio".to_owned()),
172 name_max_len: 8,
173 os: None,
174 }
175 }
176
177 fn hosts(can_rename: bool) -> EasySwitchHosts {
178 EasySwitchHosts {
179 current: Some(0),
180 slots: vec![slot(0, true), slot(1, false)],
181 can_rename,
182 }
183 }
184
185 #[test]
186 fn a_name_is_trimmed_and_must_fit_the_slot() {
187 let hosts = hosts(true);
188 assert_eq!(hosts.name_for(0, " Laptop \n"), Ok("Laptop"));
189 assert_eq!(hosts.name_for(0, "12345678"), Ok("12345678"));
190 for rejected in ["", " ", "123456789", "a\0b"] {
191 assert_eq!(
192 hosts.name_for(0, rejected),
193 Err(WriteError::HostNameRejected { max_len: 8 }),
194 "{rejected:?}"
195 );
196 }
197 assert_eq!(
199 hosts.name_for(0, "üüüüü"),
200 Err(WriteError::HostNameRejected { max_len: 8 })
201 );
202 }
203
204 #[test]
205 fn only_a_paired_slot_of_a_renaming_device_takes_a_name() {
206 assert_eq!(
207 hosts(false).name_for(0, "Laptop"),
208 Err(WriteError::HostRenameUnsupported)
209 );
210 assert_eq!(
211 hosts(true).name_for(1, "Laptop"),
212 Err(WriteError::HostSlotEmpty { slot: 1 })
213 );
214 assert_eq!(
215 hosts(true).name_for(2, "Laptop"),
216 Err(WriteError::HostSlotEmpty { slot: 2 })
217 );
218 }
219
220 #[test]
221 fn an_os_shows_major_and_minor_unless_both_are_zero() {
222 let os = |major, minor| HostOsInfo {
223 kind: HostOsKind::MacOs,
224 major,
225 minor,
226 patch: 3,
227 };
228 assert_eq!(os(15, 2).to_string(), "macOS 15.2");
229 assert_eq!(os(10, 0).to_string(), "macOS 10.0");
230 assert_eq!(os(0, 0).to_string(), "macOS");
231 }
232}