use std::fmt;
use serde::{Deserialize, Serialize};
use super::error::WriteError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HostConnection {
Unknown,
Unifying,
Usb,
Bluetooth,
Bolt,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HostOsKind {
Windows,
WindowsEmbedded,
Linux,
ChromeOs,
Android,
MacOs,
Ios,
WebOs,
Tizen,
}
impl fmt::Display for HostOsKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Windows => "Windows",
Self::WindowsEmbedded => "Windows Embedded",
Self::Linux => "Linux",
Self::ChromeOs => "ChromeOS",
Self::Android => "Android",
Self::MacOs => "macOS",
Self::Ios => "iOS",
Self::WebOs => "webOS",
Self::Tizen => "Tizen",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct HostOsInfo {
pub kind: HostOsKind,
pub major: u8,
pub minor: u16,
pub patch: u16,
}
impl fmt::Display for HostOsInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.major == 0 && self.minor == 0 {
write!(f, "{}", self.kind)
} else {
write!(f, "{} {}.{}", self.kind, self.major, self.minor)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EasySwitchSlot {
pub index: u8,
pub paired: bool,
pub connection: HostConnection,
pub name: Option<String>,
pub name_max_len: u8,
pub os: Option<HostOsInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EasySwitchHosts {
pub current: Option<u8>,
pub slots: Vec<EasySwitchSlot>,
pub can_rename: bool,
}
impl EasySwitchHosts {
#[must_use]
pub fn slot(&self, index: u8) -> Option<&EasySwitchSlot> {
self.slots.iter().find(|slot| slot.index == index)
}
pub fn name_for<'a>(&self, index: u8, name: &'a str) -> Result<&'a str, WriteError> {
if !self.can_rename {
return Err(WriteError::HostRenameUnsupported);
}
let slot = self
.slot(index)
.filter(|slot| slot.paired)
.ok_or(WriteError::HostSlotEmpty { slot: index })?;
let name = name.trim();
if name.is_empty() || name.contains('\0') || name.len() > usize::from(slot.name_max_len) {
return Err(WriteError::HostNameRejected {
max_len: slot.name_max_len,
});
}
Ok(name)
}
}
#[cfg(test)]
mod tests {
use super::{
EasySwitchHosts, EasySwitchSlot, HostConnection, HostOsInfo, HostOsKind, WriteError,
};
fn slot(index: u8, paired: bool) -> EasySwitchSlot {
EasySwitchSlot {
index,
paired,
connection: HostConnection::Bluetooth,
name: paired.then(|| "Studio".to_owned()),
name_max_len: 8,
os: None,
}
}
fn hosts(can_rename: bool) -> EasySwitchHosts {
EasySwitchHosts {
current: Some(0),
slots: vec![slot(0, true), slot(1, false)],
can_rename,
}
}
#[test]
fn a_name_is_trimmed_and_must_fit_the_slot() {
let hosts = hosts(true);
assert_eq!(hosts.name_for(0, " Laptop \n"), Ok("Laptop"));
assert_eq!(hosts.name_for(0, "12345678"), Ok("12345678"));
for rejected in ["", " ", "123456789", "a\0b"] {
assert_eq!(
hosts.name_for(0, rejected),
Err(WriteError::HostNameRejected { max_len: 8 }),
"{rejected:?}"
);
}
assert_eq!(
hosts.name_for(0, "üüüüü"),
Err(WriteError::HostNameRejected { max_len: 8 })
);
}
#[test]
fn only_a_paired_slot_of_a_renaming_device_takes_a_name() {
assert_eq!(
hosts(false).name_for(0, "Laptop"),
Err(WriteError::HostRenameUnsupported)
);
assert_eq!(
hosts(true).name_for(1, "Laptop"),
Err(WriteError::HostSlotEmpty { slot: 1 })
);
assert_eq!(
hosts(true).name_for(2, "Laptop"),
Err(WriteError::HostSlotEmpty { slot: 2 })
);
}
#[test]
fn an_os_shows_major_and_minor_unless_both_are_zero() {
let os = |major, minor| HostOsInfo {
kind: HostOsKind::MacOs,
major,
minor,
patch: 3,
};
assert_eq!(os(15, 2).to_string(), "macOS 15.2");
assert_eq!(os(10, 0).to_string(), "macOS 10.0");
assert_eq!(os(0, 0).to_string(), "macOS");
}
}