Improve tray MacOS
This commit is contained in:
@@ -5,21 +5,14 @@ use std::{
|
|||||||
|
|
||||||
use hyper_headset::devices::{DeviceEvent, DeviceProperties, PropertyType};
|
use hyper_headset::devices::{DeviceEvent, DeviceProperties, PropertyType};
|
||||||
use tray_icon::{
|
use tray_icon::{
|
||||||
menu::{IconMenuItem, Menu, MenuEvent, MenuId, Submenu},
|
menu::{Menu, MenuEvent, MenuId, MenuItem, PredefinedMenuItem, Submenu},
|
||||||
TrayIcon, TrayIconBuilder,
|
TrayIcon, TrayIconBuilder,
|
||||||
};
|
};
|
||||||
use winit::{application::ApplicationHandler, event::StartCause, event_loop::ControlFlow};
|
use winit::{application::ApplicationHandler, event::StartCause};
|
||||||
|
|
||||||
//TODO: maybe use MenuItem instead of IconMenuItem but than I probably have to patch Muda because
|
|
||||||
//it crashes sometimes when trying to handle an image with zero size
|
|
||||||
|
|
||||||
const NO_COMPATIBLE_DEVICE: &str = "No compatible device found. Is the dongle plugged in?";
|
const NO_COMPATIBLE_DEVICE: &str = "No compatible device found. Is the dongle plugged in?";
|
||||||
const HEADSET_NOT_CONNECTED: &str = "Headset is not connected";
|
const HEADSET_NOT_CONNECTED: &str = "Headset is not connected";
|
||||||
|
|
||||||
fn placeholder_icon() -> tray_icon::menu::Icon {
|
|
||||||
tray_icon::menu::Icon::from_rgba(vec![0, 0, 0, 0], 1, 1).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn create_tray_icon() -> tray_icon::Icon {
|
fn create_tray_icon() -> tray_icon::Icon {
|
||||||
// embed a headset .ico/.png at compile time — no file needed at runtime
|
// embed a headset .ico/.png at compile time — no file needed at runtime
|
||||||
@@ -36,8 +29,6 @@ pub struct TrayApp {
|
|||||||
pub sender: Sender<DeviceEvent>,
|
pub sender: Sender<DeviceEvent>,
|
||||||
callbacks: CallbackMap,
|
callbacks: CallbackMap,
|
||||||
current_state: Option<Option<DeviceProperties>>,
|
current_state: Option<Option<DeviceProperties>>,
|
||||||
//TODO: maybe not needed anymore?
|
|
||||||
pending_update: Option<Option<DeviceProperties>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicationHandler<Option<DeviceProperties>> for TrayApp {
|
impl ApplicationHandler<Option<DeviceProperties>> for TrayApp {
|
||||||
@@ -54,7 +45,7 @@ impl ApplicationHandler<Option<DeviceProperties>> for TrayApp {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
self.tray_icon = Some(
|
self.tray_icon = Some(
|
||||||
TrayIconBuilder::new()
|
TrayIconBuilder::new()
|
||||||
@@ -72,21 +63,10 @@ impl ApplicationHandler<Option<DeviceProperties>> for TrayApp {
|
|||||||
|
|
||||||
fn user_event(
|
fn user_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
el: &winit::event_loop::ActiveEventLoop,
|
_el: &winit::event_loop::ActiveEventLoop,
|
||||||
device_properties: Option<DeviceProperties>,
|
device_properties: Option<DeviceProperties>,
|
||||||
) {
|
) {
|
||||||
// Don't call set_menu here — macOS menu is still active at this point.
|
self.update(device_properties);
|
||||||
// Buffer the update and apply it once the event loop is idle.
|
|
||||||
self.pending_update = Some(device_properties);
|
|
||||||
el.set_control_flow(ControlFlow::Poll); // wake about_to_wait immediately
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called once the event loop has drained all pending events — menu is closed by now
|
|
||||||
fn about_to_wait(&mut self, el: &winit::event_loop::ActiveEventLoop) {
|
|
||||||
if let Some(props) = self.pending_update.take() {
|
|
||||||
self.update(props);
|
|
||||||
}
|
|
||||||
el.set_control_flow(ControlFlow::Wait); // go back to sleeping
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resumed(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {}
|
fn resumed(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {}
|
||||||
@@ -120,7 +100,6 @@ impl TrayApp {
|
|||||||
sender,
|
sender,
|
||||||
callbacks,
|
callbacks,
|
||||||
current_state: None,
|
current_state: None,
|
||||||
pending_update: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,19 +113,18 @@ impl TrayApp {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let quit_item = IconMenuItem::new("Quit", true, Some(placeholder_icon()), None);
|
|
||||||
let menu = Menu::new();
|
let menu = Menu::new();
|
||||||
let mut new_callbacks: HashMap<MenuId, Box<dyn Fn() + Send + Sync>> = HashMap::new();
|
let mut new_callbacks: HashMap<MenuId, Box<dyn Fn() + Send + Sync>> = HashMap::new();
|
||||||
|
|
||||||
let Some(device_properties) = device_properties else {
|
let Some(device_properties) = device_properties else {
|
||||||
let _ = tray.set_tooltip(Some(NO_COMPATIBLE_DEVICE));
|
let _ = tray.set_tooltip(Some(NO_COMPATIBLE_DEVICE));
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(target_os = "macos")]
|
||||||
tray.set_title(Some(&format!("🎧?")));
|
tray.set_title(Some(&format!("🎧?")));
|
||||||
let status_item =
|
let status_item = MenuItem::new(NO_COMPATIBLE_DEVICE, false, None);
|
||||||
IconMenuItem::new(NO_COMPATIBLE_DEVICE, false, Some(placeholder_icon()), None);
|
|
||||||
menu.append(&status_item).unwrap();
|
menu.append(&status_item).unwrap();
|
||||||
menu.append(&quit_item).unwrap();
|
menu.append(&PredefinedMenuItem::separator()).unwrap();
|
||||||
new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0)));
|
menu.append(&PredefinedMenuItem::quit(Some("Quit")))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
*self.callbacks.lock().unwrap() = new_callbacks;
|
*self.callbacks.lock().unwrap() = new_callbacks;
|
||||||
tray.set_menu(Some(Box::new(menu)));
|
tray.set_menu(Some(Box::new(menu)));
|
||||||
@@ -156,13 +134,13 @@ impl TrayApp {
|
|||||||
|
|
||||||
if !device_properties.connected.unwrap_or(false) {
|
if !device_properties.connected.unwrap_or(false) {
|
||||||
let _ = tray.set_tooltip(Some(HEADSET_NOT_CONNECTED));
|
let _ = tray.set_tooltip(Some(HEADSET_NOT_CONNECTED));
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(target_os = "macos")]
|
||||||
tray.set_title(Some(&format!("🎧?")));
|
tray.set_title(Some(&format!("🎧?")));
|
||||||
let status_item =
|
let status_item = MenuItem::new(HEADSET_NOT_CONNECTED, false, None);
|
||||||
IconMenuItem::new(HEADSET_NOT_CONNECTED, false, Some(placeholder_icon()), None);
|
|
||||||
menu.append(&status_item).unwrap();
|
menu.append(&status_item).unwrap();
|
||||||
menu.append(&quit_item).unwrap();
|
menu.append(&PredefinedMenuItem::separator()).unwrap();
|
||||||
new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0)));
|
menu.append(&PredefinedMenuItem::quit(Some("Quit")))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
*self.callbacks.lock().unwrap() = new_callbacks;
|
*self.callbacks.lock().unwrap() = new_callbacks;
|
||||||
tray.set_menu(Some(Box::new(menu)));
|
tray.set_menu(Some(Box::new(menu)));
|
||||||
@@ -170,7 +148,7 @@ impl TrayApp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(target_os = "macos")]
|
||||||
let _ = tray.set_tooltip(Some(
|
let _ = tray.set_tooltip(Some(
|
||||||
device_properties
|
device_properties
|
||||||
.to_string_with_padding(0)
|
.to_string_with_padding(0)
|
||||||
@@ -191,7 +169,7 @@ impl TrayApp {
|
|||||||
.join("\n"),
|
.join("\n"),
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(target_os = "macos")]
|
||||||
if let Some(battery_level) = device_properties.battery_level {
|
if let Some(battery_level) = device_properties.battery_level {
|
||||||
tray.set_title(Some(&format!("🎧 {battery_level}%")));
|
tray.set_title(Some(&format!("🎧 {battery_level}%")));
|
||||||
}
|
}
|
||||||
@@ -202,10 +180,9 @@ impl TrayApp {
|
|||||||
let Some(current_value) = property.data else {
|
let Some(current_value) = property.data else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let menu_item = IconMenuItem::new(
|
let menu_item = MenuItem::new(
|
||||||
format!("{} {}{}", property.prefix, current_value, property.suffix),
|
format!("{} {}{}", property.prefix, current_value, property.suffix),
|
||||||
false,
|
false,
|
||||||
Some(placeholder_icon()),
|
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let _ = menu.append(&menu_item);
|
let _ = menu.append(&menu_item);
|
||||||
@@ -220,12 +197,8 @@ impl TrayApp {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for item_value in items {
|
for item_value in items {
|
||||||
let entry = IconMenuItem::new(
|
let entry =
|
||||||
format!("{}{}", item_value, property.suffix),
|
MenuItem::new(format!("{}{}", item_value, property.suffix), true, None);
|
||||||
true,
|
|
||||||
Some(placeholder_icon()),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
submenu.append(&entry).unwrap();
|
submenu.append(&entry).unwrap();
|
||||||
|
|
||||||
let create_event = property.create_event;
|
let create_event = property.create_event;
|
||||||
@@ -249,11 +222,10 @@ impl TrayApp {
|
|||||||
};
|
};
|
||||||
let create_event = property.create_event;
|
let create_event = property.create_event;
|
||||||
let update_sender = self.sender.clone();
|
let update_sender = self.sender.clone();
|
||||||
let menu_item = IconMenuItem::new(
|
let menu_item = MenuItem::new(
|
||||||
format!("{} {}{}", property.prefix, current_value, property.suffix),
|
format!("{} {}{}", property.prefix, current_value, property.suffix),
|
||||||
property.property_type == PropertyType::ReadWrite
|
property.property_type == PropertyType::ReadWrite
|
||||||
&& property.data.is_some(),
|
&& property.data.is_some(),
|
||||||
Some(placeholder_icon()),
|
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let _ = menu.append(&menu_item);
|
let _ = menu.append(&menu_item);
|
||||||
@@ -271,10 +243,9 @@ impl TrayApp {
|
|||||||
let Some(current_value) = property.data else {
|
let Some(current_value) = property.data else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let menu_item = IconMenuItem::new(
|
let menu_item = MenuItem::new(
|
||||||
format!("{} {}{}", property.prefix, current_value, property.suffix),
|
format!("{} {}{}", property.prefix, current_value, property.suffix),
|
||||||
false,
|
false,
|
||||||
Some(placeholder_icon()),
|
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let _ = menu.append(&menu_item);
|
let _ = menu.append(&menu_item);
|
||||||
@@ -282,8 +253,9 @@ impl TrayApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.append(&quit_item).unwrap();
|
menu.append(&PredefinedMenuItem::separator()).unwrap();
|
||||||
new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0)));
|
menu.append(&PredefinedMenuItem::quit(Some("Quit")))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
*self.callbacks.lock().unwrap() = new_callbacks;
|
*self.callbacks.lock().unwrap() = new_callbacks;
|
||||||
tray.set_menu(Some(Box::new(menu)));
|
tray.set_menu(Some(Box::new(menu)));
|
||||||
|
|||||||
Reference in New Issue
Block a user