Some features are read-only: either they require Windows APO, or they really don't have a software command available. Add the plumbing to detect and report this. Also, add documentation for the featureset.
This commit is contained in:
89
FEATURE_CAPABILITIES.md
Normal file
89
FEATURE_CAPABILITIES.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# HyperX Headset Feature Capabilities
|
||||||
|
|
||||||
|
This document summarizes which features can be controlled vs only monitored for each headset model.
|
||||||
|
|
||||||
|
## Cloud II Wireless (Non-DTS)
|
||||||
|
|
||||||
|
### Writable Features (Can be SET via HID commands)
|
||||||
|
|
||||||
|
- ✅ **Auto Power Off** - Can set automatic shutdown timer (0-30 minutes)
|
||||||
|
- ✅ **Sidetone** - Can enable/disable sidetone (on/off only, no volume control)
|
||||||
|
|
||||||
|
### Read-Only Features (Can only monitor, not control)
|
||||||
|
|
||||||
|
- ❌ **Microphone Mute** - Hardware button only, cannot be controlled via HID
|
||||||
|
- ❌ **Surround Sound (7.1)** - Controlled via Windows DTS APO or physical button, not HID
|
||||||
|
- ❌ **Battery Level** - Read-only status
|
||||||
|
- ❌ **Charging Status** - Read-only status
|
||||||
|
- ❌ **Connection Status** - Read-only status
|
||||||
|
- ❌ **Firmware Version** - Read-only information
|
||||||
|
|
||||||
|
## Cloud II Wireless DTS
|
||||||
|
|
||||||
|
### Writable Features
|
||||||
|
|
||||||
|
- ✅ **Auto Power Off**
|
||||||
|
- ✅ **Sidetone** - With volume control (0-100)
|
||||||
|
- ✅ **Surround Sound** - Via Windows DTS APO system calls (not direct HID)
|
||||||
|
|
||||||
|
### Read-Only Features
|
||||||
|
|
||||||
|
- ❌ **Microphone Mute** - Hardware button only
|
||||||
|
- ❌ **Battery Level**
|
||||||
|
- ❌ **Charging Status**
|
||||||
|
- ❌ **Connection Status**
|
||||||
|
|
||||||
|
## Cloud III Wireless
|
||||||
|
|
||||||
|
### Writable Features
|
||||||
|
|
||||||
|
- ✅ **Auto Power Off**
|
||||||
|
- ✅ **Sidetone** - With volume control (0-100)
|
||||||
|
- ✅ **Microphone Mute** - Can be controlled programmatically
|
||||||
|
- ✅ **Voice Prompt** - Can enable/disable voice prompts
|
||||||
|
- ✅ **Playback Mute (Silent Mode)** - Can mute headphone output
|
||||||
|
|
||||||
|
### Read-Only Features
|
||||||
|
|
||||||
|
- ❌ **Surround Sound** - Not supported via HID
|
||||||
|
- ❌ **Battery Level**
|
||||||
|
- ❌ **Charging Status**
|
||||||
|
- ❌ **Connection Status**
|
||||||
|
- ❌ **Product Color**
|
||||||
|
|
||||||
|
## CLI Error Handling
|
||||||
|
|
||||||
|
The CLI application now provides clear error messages when attempting to use unsupported features:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example: Trying to control surround sound on Cloud II Wireless
|
||||||
|
$ ./hyper_headset_cli --surround_sound true
|
||||||
|
ERROR: Surround sound control is not supported on this device
|
||||||
|
Use the physical headset button or Windows audio settings to toggle surround sound.
|
||||||
|
|
||||||
|
# Example: Trying to mute on Cloud II Wireless
|
||||||
|
$ ./hyper_headset_cli --mute true
|
||||||
|
ERROR: Microphone mute control is not supported on this device (hardware button only)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tray Application UI
|
||||||
|
|
||||||
|
The system tray application now displays "(read-only)" markers next to features that cannot be controlled:
|
||||||
|
|
||||||
|
```
|
||||||
|
Battery level: 92%
|
||||||
|
Charging status: Not charging
|
||||||
|
Muted: false (read-only)
|
||||||
|
Automatic shutdown after: 20min
|
||||||
|
Side tone: false
|
||||||
|
Surround sound: true (read-only)
|
||||||
|
Connected: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
- Feature capabilities are checked once during device initialization via `init_capabilities()`
|
||||||
|
- Capability flags are stored in `DeviceState` structure
|
||||||
|
- CLI flags for unsupported features are hidden in the help menu
|
||||||
|
- CLI exits with error code 1 when attempting to use unsupported features
|
||||||
|
- Tray UI shows read-only markers based on device capabilities
|
||||||
@@ -93,13 +93,15 @@ fn main() {
|
|||||||
println!("sending automatic_shutdown packet");
|
println!("sending automatic_shutdown packet");
|
||||||
device.prepare_write();
|
device.prepare_write();
|
||||||
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
||||||
println!("Failed to set automatic shutdown with error: {:?}", err)
|
eprintln!("Failed to set automatic shutdown with error: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
if let Some(events) = device.wait_for_updates(Duration::from_secs(1)) {
|
if let Some(events) = device.wait_for_updates(Duration::from_secs(1)) {
|
||||||
println!("{:?}", events);
|
println!("{:?}", events);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Automatic shutdown can't be enabled on this device")
|
eprintln!("ERROR: Automatic shutdown is not supported on this device");
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("not sending automatic_shutdown packet");
|
println!("not sending automatic_shutdown packet");
|
||||||
@@ -109,10 +111,12 @@ fn main() {
|
|||||||
if let Some(packet) = device.set_mute_packet(*mute) {
|
if let Some(packet) = device.set_mute_packet(*mute) {
|
||||||
device.prepare_write();
|
device.prepare_write();
|
||||||
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
||||||
println!("Failed to mute with error: {:?}", err)
|
eprintln!("Failed to mute with error: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Can't mute this device")
|
eprintln!("ERROR: Microphone mute control is not supported on this device (hardware button only)");
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,14 +126,16 @@ fn main() {
|
|||||||
println!("sending enable_side_tone packet");
|
println!("sending enable_side_tone packet");
|
||||||
device.prepare_write();
|
device.prepare_write();
|
||||||
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
||||||
println!("Failed to enable side tone with error: {:?}", err)
|
eprintln!("Failed to enable side tone with error: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::from_millis(50));
|
std::thread::sleep(Duration::from_millis(50));
|
||||||
if let Some(events) = device.wait_for_updates(Duration::from_secs(1)) {
|
if let Some(events) = device.wait_for_updates(Duration::from_secs(1)) {
|
||||||
println!("{:?}", events);
|
println!("{:?}", events);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Can't enable side tone on this device")
|
eprintln!("ERROR: Side tone control is not supported on this device");
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("not sending enable_side_tone packet");
|
println!("not sending enable_side_tone packet");
|
||||||
@@ -139,10 +145,12 @@ fn main() {
|
|||||||
if let Some(packet) = device.set_side_tone_volume_packet(*volume) {
|
if let Some(packet) = device.set_side_tone_volume_packet(*volume) {
|
||||||
device.prepare_write();
|
device.prepare_write();
|
||||||
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
||||||
println!("Failed to set side tone volume with error: {:?}", err)
|
eprintln!("Failed to set side tone volume with error: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Can't set side tone volume on this device")
|
eprintln!("ERROR: Side tone volume control is not supported on this device");
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,10 +158,12 @@ fn main() {
|
|||||||
if let Some(packet) = device.set_voice_prompt_packet(*enable) {
|
if let Some(packet) = device.set_voice_prompt_packet(*enable) {
|
||||||
device.prepare_write();
|
device.prepare_write();
|
||||||
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
||||||
println!("Failed to enable voice prompt with error: {:?}", err)
|
eprintln!("Failed to enable voice prompt with error: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Can't enable voice prompt on this device")
|
eprintln!("ERROR: Voice prompt control is not supported on this device");
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,10 +171,13 @@ fn main() {
|
|||||||
if let Some(packet) = device.set_surround_sound_packet(*surround_sound) {
|
if let Some(packet) = device.set_surround_sound_packet(*surround_sound) {
|
||||||
device.prepare_write();
|
device.prepare_write();
|
||||||
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
||||||
println!("Failed to set surround sound with error: {:?}", err)
|
eprintln!("Failed to set surround sound with error: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Can't change surround sound on this device")
|
eprintln!("ERROR: Surround sound control is not supported on this device");
|
||||||
|
eprintln!(" Use the physical headset button or Windows audio settings to toggle surround sound.");
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,10 +185,12 @@ fn main() {
|
|||||||
if let Some(packet) = device.set_silent_mode_packet(*mute_playback) {
|
if let Some(packet) = device.set_silent_mode_packet(*mute_playback) {
|
||||||
device.prepare_write();
|
device.prepare_write();
|
||||||
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
if let Err(err) = device.get_device_state().hid_device.write(&packet) {
|
||||||
println!("Failed to mute playback with error: {:?}", err)
|
eprintln!("Failed to mute playback with error: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Can't mute playback on this device")
|
eprintln!("ERROR: Playback mute control is not supported on this device");
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,27 +25,32 @@ pub fn connect_compatible_device() -> Result<Box<dyn Device>, DeviceError> {
|
|||||||
.get_product_string()?
|
.get_product_string()?
|
||||||
.ok_or(DeviceError::NoDeviceFound())?;
|
.ok_or(DeviceError::NoDeviceFound())?;
|
||||||
println!("Connecting to {}", name);
|
println!("Connecting to {}", name);
|
||||||
match (state.vendor_id, state.product_id) {
|
let mut device: Box<dyn Device> = match (state.vendor_id, state.product_id) {
|
||||||
(v, p)
|
(v, p)
|
||||||
if cloud_ii_wireless::VENDOR_IDS.contains(&v)
|
if cloud_ii_wireless::VENDOR_IDS.contains(&v)
|
||||||
&& cloud_ii_wireless::PRODUCT_IDS.contains(&p) =>
|
&& cloud_ii_wireless::PRODUCT_IDS.contains(&p) =>
|
||||||
{
|
{
|
||||||
Ok(Box::new(CloudIIWireless::new_from_state(state)))
|
Box::new(CloudIIWireless::new_from_state(state))
|
||||||
}
|
}
|
||||||
(v, p)
|
(v, p)
|
||||||
if cloud_ii_wireless_dts::VENDOR_IDS.contains(&v)
|
if cloud_ii_wireless_dts::VENDOR_IDS.contains(&v)
|
||||||
&& cloud_ii_wireless_dts::PRODUCT_IDS.contains(&p) =>
|
&& cloud_ii_wireless_dts::PRODUCT_IDS.contains(&p) =>
|
||||||
{
|
{
|
||||||
Ok(Box::new(CloudIIWirelessDTS::new_from_state(state)))
|
Box::new(CloudIIWirelessDTS::new_from_state(state))
|
||||||
}
|
}
|
||||||
(v, p)
|
(v, p)
|
||||||
if cloud_iii_wireless::VENDOR_IDS.contains(&v)
|
if cloud_iii_wireless::VENDOR_IDS.contains(&v)
|
||||||
&& cloud_iii_wireless::PRODUCT_IDS.contains(&p) =>
|
&& cloud_iii_wireless::PRODUCT_IDS.contains(&p) =>
|
||||||
{
|
{
|
||||||
Ok(Box::new(CloudIIIWireless::new_from_state(state)))
|
Box::new(CloudIIIWireless::new_from_state(state))
|
||||||
}
|
|
||||||
(_, _) => Err(DeviceError::NoDeviceFound()),
|
|
||||||
}
|
}
|
||||||
|
(_, _) => return Err(DeviceError::NoDeviceFound()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize capability flags
|
||||||
|
device.init_capabilities();
|
||||||
|
|
||||||
|
Ok(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -67,6 +72,14 @@ pub struct DeviceState {
|
|||||||
pub voice_prompt_on: Option<bool>,
|
pub voice_prompt_on: Option<bool>,
|
||||||
pub connected: Option<bool>,
|
pub connected: Option<bool>,
|
||||||
pub silent: Option<bool>,
|
pub silent: Option<bool>,
|
||||||
|
// Capability flags - set once during device initialization
|
||||||
|
pub can_set_mute: bool,
|
||||||
|
pub can_set_surround_sound: bool,
|
||||||
|
pub can_set_side_tone: bool,
|
||||||
|
pub can_set_automatic_shutdown: bool,
|
||||||
|
pub can_set_side_tone_volume: bool,
|
||||||
|
pub can_set_voice_prompt: bool,
|
||||||
|
pub can_set_silent_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for DeviceState {
|
impl Display for DeviceState {
|
||||||
@@ -114,6 +127,14 @@ impl DeviceState {
|
|||||||
voice_prompt_on: None,
|
voice_prompt_on: None,
|
||||||
connected: None,
|
connected: None,
|
||||||
silent: None,
|
silent: None,
|
||||||
|
// Capability flags - will be set by init_capabilities()
|
||||||
|
can_set_mute: false,
|
||||||
|
can_set_surround_sound: false,
|
||||||
|
can_set_side_tone: false,
|
||||||
|
can_set_automatic_shutdown: false,
|
||||||
|
can_set_side_tone_volume: false,
|
||||||
|
can_set_voice_prompt: false,
|
||||||
|
can_set_silent_mode: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,51 +144,79 @@ impl DeviceState {
|
|||||||
"Battery level:",
|
"Battery level:",
|
||||||
self.battery_level.map(|l| l.to_string()),
|
self.battery_level.map(|l| l.to_string()),
|
||||||
"%",
|
"%",
|
||||||
|
false, // read-only flag (not applicable for read-only fields)
|
||||||
),
|
),
|
||||||
("Charging status:", self.charging.map(|c| c.to_string()), ""),
|
(
|
||||||
("Muted:", self.muted.map(|c| c.to_string()), ""),
|
"Charging status:",
|
||||||
|
self.charging.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
("Muted:", self.muted.map(|c| c.to_string()), "", false),
|
||||||
(
|
(
|
||||||
"Mic connected:",
|
"Mic connected:",
|
||||||
self.mic_connected.map(|c| c.to_string()),
|
self.mic_connected.map(|c| c.to_string()),
|
||||||
"",
|
"",
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Automatic shutdown after:",
|
"Automatic shutdown after:",
|
||||||
self.automatic_shutdown_after
|
self.automatic_shutdown_after
|
||||||
.map(|c| (c.as_secs() / 60).to_string()),
|
.map(|c| (c.as_secs() / 60).to_string()),
|
||||||
"min",
|
"min",
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"pairing info:",
|
"pairing info:",
|
||||||
self.pairing_info.map(|c| c.to_string()),
|
self.pairing_info.map(|c| c.to_string()),
|
||||||
"",
|
"",
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"product color:",
|
"product color:",
|
||||||
self.product_color.map(|c| c.to_string()),
|
self.product_color.map(|c| c.to_string()),
|
||||||
"",
|
"",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Side tone:",
|
||||||
|
self.side_tone_on.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
("Side tone:", self.side_tone_on.map(|c| c.to_string()), ""),
|
|
||||||
(
|
(
|
||||||
"Side tone volume:",
|
"Side tone volume:",
|
||||||
self.side_tone_volume.map(|c| c.to_string()),
|
self.side_tone_volume.map(|c| c.to_string()),
|
||||||
"",
|
"",
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Surround sound:",
|
"Surround sound:",
|
||||||
self.surround_sound.map(|c| c.to_string()),
|
self.surround_sound.map(|c| c.to_string()),
|
||||||
"",
|
"",
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Voice prompt:",
|
"Voice prompt:",
|
||||||
self.voice_prompt_on.map(|c| c.to_string()),
|
self.voice_prompt_on.map(|c| c.to_string()),
|
||||||
"",
|
"",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Connected:",
|
||||||
|
self.connected.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Playback muted:",
|
||||||
|
self.silent.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
("Connected:", self.connected.map(|c| c.to_string()), ""),
|
|
||||||
("Playback muted:", self.silent.map(|c| c.to_string()), ""),
|
|
||||||
];
|
];
|
||||||
data.iter()
|
data.iter()
|
||||||
.filter_map(|(prefix, data, suffix)| {
|
.filter_map(|(prefix, data, suffix, _)| {
|
||||||
if let Some(data) = data {
|
if let Some(data) = data {
|
||||||
Some(format!("{:<padding$} {}{}", prefix, data, suffix))
|
Some(format!("{:<padding$} {}{}", prefix, data, suffix))
|
||||||
} else {
|
} else {
|
||||||
@@ -178,6 +227,104 @@ impl DeviceState {
|
|||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_string_with_readonly_info(&self, padding: usize) -> String {
|
||||||
|
let data = [
|
||||||
|
(
|
||||||
|
"Battery level:",
|
||||||
|
self.battery_level.map(|l| l.to_string()),
|
||||||
|
"%",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Charging status:",
|
||||||
|
self.charging.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Muted:",
|
||||||
|
self.muted.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
!self.can_set_mute,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Mic connected:",
|
||||||
|
self.mic_connected.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Automatic shutdown after:",
|
||||||
|
self.automatic_shutdown_after
|
||||||
|
.map(|c| (c.as_secs() / 60).to_string()),
|
||||||
|
"min",
|
||||||
|
!self.can_set_automatic_shutdown,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pairing info:",
|
||||||
|
self.pairing_info.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"product color:",
|
||||||
|
self.product_color.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Side tone:",
|
||||||
|
self.side_tone_on.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
!self.can_set_side_tone,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Side tone volume:",
|
||||||
|
self.side_tone_volume.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
!self.can_set_side_tone_volume,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Surround sound:",
|
||||||
|
self.surround_sound.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
!self.can_set_surround_sound,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Voice prompt:",
|
||||||
|
self.voice_prompt_on.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
!self.can_set_voice_prompt,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Connected:",
|
||||||
|
self.connected.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Playback muted:",
|
||||||
|
self.silent.map(|c| c.to_string()),
|
||||||
|
"",
|
||||||
|
!self.can_set_silent_mode,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
data.iter()
|
||||||
|
.filter_map(|(prefix, data, suffix, readonly)| {
|
||||||
|
if let Some(data) = data {
|
||||||
|
let readonly_marker = if *readonly { " (read-only)" } else { "" };
|
||||||
|
Some(format!(
|
||||||
|
"{:<padding$} {}{}{}",
|
||||||
|
prefix, data, suffix, readonly_marker
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
fn update_self_with_event(&mut self, event: &DeviceEvent) {
|
fn update_self_with_event(&mut self, event: &DeviceEvent) {
|
||||||
match event {
|
match event {
|
||||||
DeviceEvent::BatterLevel(level) => self.battery_level = Some(*level),
|
DeviceEvent::BatterLevel(level) => self.battery_level = Some(*level),
|
||||||
@@ -342,6 +489,53 @@ pub trait Device {
|
|||||||
fn get_device_state(&self) -> &DeviceState;
|
fn get_device_state(&self) -> &DeviceState;
|
||||||
fn get_device_state_mut(&mut self) -> &mut DeviceState;
|
fn get_device_state_mut(&mut self) -> &mut DeviceState;
|
||||||
fn prepare_write(&mut self) {}
|
fn prepare_write(&mut self) {}
|
||||||
|
|
||||||
|
// Helper methods to check if features are writable
|
||||||
|
fn can_set_mute(&self) -> bool {
|
||||||
|
self.set_mute_packet(false).is_some()
|
||||||
|
}
|
||||||
|
fn can_set_surround_sound(&self) -> bool {
|
||||||
|
self.set_surround_sound_packet(false).is_some()
|
||||||
|
}
|
||||||
|
fn can_set_side_tone(&self) -> bool {
|
||||||
|
self.set_side_tone_packet(false).is_some()
|
||||||
|
}
|
||||||
|
fn can_set_automatic_shutdown(&self) -> bool {
|
||||||
|
self.set_automatic_shut_down_packet(Duration::from_secs(0))
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
fn can_set_side_tone_volume(&self) -> bool {
|
||||||
|
self.set_side_tone_volume_packet(0).is_some()
|
||||||
|
}
|
||||||
|
fn can_set_voice_prompt(&self) -> bool {
|
||||||
|
self.set_voice_prompt_packet(false).is_some()
|
||||||
|
}
|
||||||
|
fn can_set_silent_mode(&self) -> bool {
|
||||||
|
self.set_silent_mode_packet(false).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize capability flags in device state
|
||||||
|
fn init_capabilities(&mut self) {
|
||||||
|
// Collect capabilities first to avoid borrowing conflicts
|
||||||
|
let can_set_mute = self.can_set_mute();
|
||||||
|
let can_set_surround_sound = self.can_set_surround_sound();
|
||||||
|
let can_set_side_tone = self.can_set_side_tone();
|
||||||
|
let can_set_automatic_shutdown = self.can_set_automatic_shutdown();
|
||||||
|
let can_set_side_tone_volume = self.can_set_side_tone_volume();
|
||||||
|
let can_set_voice_prompt = self.can_set_voice_prompt();
|
||||||
|
let can_set_silent_mode = self.can_set_silent_mode();
|
||||||
|
|
||||||
|
// Now set them in device state
|
||||||
|
let state = self.get_device_state_mut();
|
||||||
|
state.can_set_mute = can_set_mute;
|
||||||
|
state.can_set_surround_sound = can_set_surround_sound;
|
||||||
|
state.can_set_side_tone = can_set_side_tone;
|
||||||
|
state.can_set_automatic_shutdown = can_set_automatic_shutdown;
|
||||||
|
state.can_set_side_tone_volume = can_set_side_tone_volume;
|
||||||
|
state.can_set_voice_prompt = can_set_voice_prompt;
|
||||||
|
state.can_set_silent_mode = can_set_silent_mode;
|
||||||
|
}
|
||||||
|
|
||||||
fn execute_headset_specific_functionality(&mut self) -> Result<(), DeviceError> {
|
fn execute_headset_specific_functionality(&mut self) -> Result<(), DeviceError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl TrayHandler {
|
|||||||
device_state.device_name.clone(),
|
device_state.device_name.clone(),
|
||||||
),
|
),
|
||||||
Some(true) => (
|
Some(true) => (
|
||||||
device_state.to_string_with_padding(0),
|
device_state.to_string_with_readonly_info(0),
|
||||||
device_state.device_name.clone(),
|
device_state.device_name.clone(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user