Skip to content

Commit def6bdc

Browse files
committed
2025/1/30 14:55
1 parent 3dbd1bd commit def6bdc

File tree

4 files changed

+175
-50
lines changed

4 files changed

+175
-50
lines changed

README-en.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
## Function
2+
3+
- [ ] Setting:设备电量作为托盘图标(字体或电池图标)
4+
- [x] Setting:更新时间
5+
- [ ] Setting:Auto start
6+
- [x] Setting-notice:Mute notice
7+
- [x] Setting-notice:Low battery notice
8+
- [x] Setting-notice:Notification when reconnecting the device
9+
- [x] Setting-notice:Notification when disconnecting the device
10+
- [x] Setting-notice:Notification when adding a new device
11+
- [x] Setting-notice:Notification when moving a new device
12+
13+
## Known Issues & Suggested Solutions
14+
15+
### 1. Currently, BlueGauge successfully retrieves battery levels from low-energy Bluetooth devices and Plug-and-Play (PnP) devices. However, we are unable to fetch the battery status from devices like AirPods and Xbox controllers, which operate on proprietary communication protocols.
16+
17+
**Solution:**
18+
19+
Welcome contributions from developers who can help us extend support for these devices.
20+
21+
22+
### 2. The character length of tray tooltip is currently limited. When the tooltip text exceeds this limit, it gets truncated, which can result in incomplete device names being displayed. This can cause confusion for users, especially when multiple devices are connected.
23+
24+
**Solution:**
25+
26+
1. Limit Device Name Length: Implement a character limit for device names that ensures they fit within the available space of the tray notification. This may require shortening longer names to prevent truncation.
27+
28+
2. Hide Disconnected Devices: Consider not displaying disconnected devices in the tray notifications. This approach would reduce clutter and ensure that only relevant information is shown, thereby preventing text overflow.
29+
30+
31+
### 3.When BlueGauge updates Bluetooth Information related to Bluetooth devices and sends notifications, if the tray menu is active (open), it can lead to the tray menu freezing. Currently considering a bug in the tray-icon library.
32+
33+
- Temporary Fix: Press `Ctrl + Shift + Esc` to open the `Task Manager`. Search for `BlueGauge.exe`. Select the process and click `End Task` to stop it.

README.md

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,41 @@ A lightweight tray tool for easily checking the battery level of your Bluetooth
55

66
![image](https://raw.githubusercontent.com/iKineticate/BlueGauge/main/screenshots/app.png)
77

8+
<h3 align="center"> 简体中文 | <a href='./README-en.md'>English</a></h3>
89

9-
- [x] 左键单击托盘显示通知
10-
- [x] 支持非低功耗蓝牙设备(PnP设备)
11-
- [ ] 左键点击托盘显示通知
12-
- [ ] 菜单:自定义更新时间
13-
- [ ] 菜单:添加开机启动
14-
- [ ] 菜单:更新按钮
15-
- [ ] 托盘图标替换为指定蓝牙设备的电量(数字或电池图标)
16-
- [ ] 低电量通知(可选通知阈值)
17-
- [ ] 定时通知指定已连接设备的电量
18-
- [ ] 通知设置:可选静音及其它声音
19-
- [ ] 通知设置:可选是否展示进度条
20-
21-
22-
# 问题:
23-
1. 托盘提示的长度受到限制
24-
2. 托盘提示的行数受到限制
25-
3. 使用PnP获取电量时,CPU使用率过高(≈12%)
26-
4. 当更新托盘时,右键菜单会消失
10+
## 功能
11+
12+
- [ ] 设置:设备电量作为托盘图标(字体或电池图标)
13+
- [x] 设置:更新时间
14+
- [ ] 设置:开机启动
15+
- [x] 设置-通知:静音通知
16+
- [x] 设置-通知:低电量通知
17+
- [x] 设置-通知:重新连接设备通知
18+
- [x] 设置-通知:断开连接设备通知
19+
- [x] 设置-通知:新添加设备通知
20+
- [x] 设置-通知:被移除设备通知
21+
22+
## 已知问题与建议
23+
24+
### 1. 无法获取某些设备电量信息
25+
26+
目前,BlueGauge 可检索蓝牙低功耗(BLE)设备和即插即用(PnP)设备的电量,但对于像 **AirPods****Xbox 控制器** 等使用专有通信协议的设备,可能无法获取电量信息。
27+
28+
**解决方案:**
29+
欢迎有能力的开发者贡献代码,帮助扩展对这些设备的支持。
30+
31+
### 2. 托盘提示文本被截断
32+
33+
托盘提示的字符长度有限,当设备名称过长时,提示文本会被截断,导致无法完整显示设备名称。尤其在连接多个设备时,设备名称可能不完整。
34+
35+
**建议的解决办法:**
36+
37+
1. **设置设备名称长度限制**:对设备名称的字符长度进行限制,确保其在托盘通知区域内完整显示。
38+
39+
2. **隐藏未连接的设备**:对于未连接的设备,可以考虑不在托盘通知中显示,从而减少杂乱,避免文本溢出。
40+
41+
### 3. 当蓝牙设备的状态更新时,已显示的托盘菜单可能冻结
42+
43+
当设备重新或断开连接时,处于显示状态下的托盘菜单可能会冻结,导致用户无法操作菜单。目前考虑是tray-icon库的BUG。
44+
45+
- **临时解决方案**:按 `Ctrl + Shift + Esc` 打开 `任务管理器`,找到 `BlueGauge.exe` 进程,选择该进程并点击 `结束任务`

src/config.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use glob::glob;
99
pub struct Config {
1010
pub update_interval: u64,
1111
pub show_disconnected_devices: bool,
12+
pub truncate_bluetooth_name: bool,
13+
pub battery_prefix_name: bool,
1214
pub icon: Option<ShowIcon>,
1315
pub notify_mute: bool,
1416
pub notify_low_battery: Option<u8>,
@@ -43,7 +45,9 @@ fn create_new_ini(ini_path: PathBuf) -> Result<(Config, PathBuf)> {
4345
ini.with_section(Some("Settings"))
4446
.set("update_interval", "30") // 默认30(单位秒)
4547
.set("icon", "none") // Value: none、logo、ttf、battery_png(若为图标exe同一目录中存放*.png任一数量的照片,*的范围为0~100,要求每组照片宽高一致)
46-
.set("show_disconnected_devices", "false");
48+
.set("show_disconnected_devices", "false")
49+
.set("truncate_bluetooth_name", "false")
50+
.set("battery_prefix_name", "false");
4751

4852
ini.with_section(Some("Notifications"))
4953
.set("notify_low_battery", "none") // Value:none、number(0~100,单位百分比)
@@ -59,6 +63,8 @@ fn create_new_ini(ini_path: PathBuf) -> Result<(Config, PathBuf)> {
5963
update_interval: 30,
6064
icon: None,
6165
show_disconnected_devices: false,
66+
truncate_bluetooth_name: false,
67+
battery_prefix_name: false,
6268
notify_low_battery: None,
6369
notify_reconnection: false,
6470
notify_disconnection: false,
@@ -87,6 +93,12 @@ fn read_ini(exe_dir: &Path, ini_path: PathBuf) -> Result<(Config, PathBuf)> {
8793
let show_disconnected_devices = setting_section.get("show_disconnected_devices")
8894
.map_or(false, |v| v.trim().to_lowercase() == "true");
8995

96+
let truncate_bluetooth_name = setting_section.get("truncate_bluetooth_name")
97+
.map_or(false, |v| v.trim().to_lowercase() == "true");
98+
99+
let battery_prefix_name = setting_section.get("battery_prefix_name")
100+
.map_or(false, |v| v.trim().to_lowercase() == "true");
101+
90102
let icon = setting_section.get("icon").map(|v| {
91103
match v.trim().to_lowercase().as_str() {
92104
"logo" => {
@@ -147,6 +159,8 @@ fn read_ini(exe_dir: &Path, ini_path: PathBuf) -> Result<(Config, PathBuf)> {
147159
update_interval,
148160
icon,
149161
show_disconnected_devices,
162+
truncate_bluetooth_name,
163+
battery_prefix_name,
150164
notify_low_battery,
151165
notify_reconnection,
152166
notify_disconnection,
@@ -158,26 +172,14 @@ fn read_ini(exe_dir: &Path, ini_path: PathBuf) -> Result<(Config, PathBuf)> {
158172
Ok((config, ini_path))
159173
}
160174

161-
pub fn write_ini_update_interval(ini_path: &Path, value: u64) {
162-
let mut ini = Ini::load_from_file(ini_path).expect("Failed to load config.ini in BlueGauge.exe directory");
163-
ini.set_to(Some("Settings"), "update_interval".to_owned(), value.to_string());
164-
ini.write_to_file(ini_path).expect("Failed to write INI file");
165-
}
166-
167175
pub fn write_ini_notifications(ini_path: &Path, key: &str, value: String) {
168176
let mut ini = Ini::load_from_file(ini_path).expect("Failed to load config.ini in BlueGauge.exe directory");
169177
ini.set_to(Some("Notifications"), key.to_owned(), value);
170178
ini.write_to_file(ini_path).expect("Failed to write INI file");
171179
}
172180

173-
pub fn write_ini_notify_low_battery(ini_path: &Path, value: u8) {
174-
let mut ini = Ini::load_from_file(ini_path).expect("Failed to load config.ini in BlueGauge.exe directory");
175-
ini.set_to(Some("Notifications"), "notify_low_battery".to_owned(), value.to_string());
176-
ini.write_to_file(ini_path).expect("Failed to write INI file");
177-
}
178-
179-
pub fn write_ini_show_disconnected(ini_path: &Path, value: String) {
181+
pub fn write_ini_settings(ini_path: &Path, key: &str, value: String) {
180182
let mut ini = Ini::load_from_file(ini_path).expect("Failed to load config.ini in BlueGauge.exe directory");
181-
ini.set_to(Some("Settings"), "show_disconnected_devices".to_owned(), value);
183+
ini.set_to(Some("Settings"), key.to_owned(), value);
182184
ini.write_to_file(ini_path).expect("Failed to write INI file");
183185
}

src/systray.rs

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ async fn loop_systray() -> Result<()> {
9797

9898
std::thread::sleep(std::time::Duration::from_secs(update_interval));
9999

100-
if let Ok(mut updated_in_advance) = updated_in_advance.lock() {
100+
if let Ok(mut updated_in_advance) = updated_in_advance.try_lock() {
101101
if std::mem::replace(&mut *updated_in_advance, false) {
102102
continue;
103103
}
@@ -197,18 +197,19 @@ async fn loop_systray() -> Result<()> {
197197
// 如菜单ID可以格式化为u64,则菜单事件对应的是更新频率的设置
198198
if let Ok(update_interval) = menu_id.trim().parse::<u64>() {
199199
config.update_interval = update_interval;
200-
write_ini_update_interval(&ini_path, update_interval);
200+
write_ini_settings(&ini_path, "update_interval",update_interval.to_string());
201201
if let Ok(mut update_menu_event) = update_menu_event.lock() {
202202
if let Err(err) = proxy_menu.send_event(TrayEvent::ForwardUpdate) {
203203
eprintln!("{err}")
204204
} else {
205205
*update_menu_event = true;
206206
}
207207
}
208+
// 如菜单ID可以格式化为f64,则菜单事件对应的是设备低电量的设置
208209
} else if let Ok(low_battery) = menu_id.trim().parse::<f64>() {
209210
let low_battery = (low_battery * 100.0).floor().clamp(0.0, 99.0) as u8;
210211
config.notify_low_battery = if low_battery == 0 { None } else { Some(low_battery) };
211-
write_ini_notify_low_battery(&ini_path, low_battery);
212+
write_ini_settings(&ini_path, "notify_low_battery",low_battery.to_string());
212213
if let Ok(mut update_menu_event) = update_menu_event.lock() {
213214
if let Err(err) = proxy_menu.send_event(TrayEvent::ForwardUpdate) {
214215
eprintln!("{err}");
@@ -240,7 +241,41 @@ async fn loop_systray() -> Result<()> {
240241
},
241242
"show_disconnected_devices" => {
242243
config.show_disconnected_devices = !config.show_disconnected_devices;
243-
write_ini_show_disconnected(&ini_path, config.show_disconnected_devices.to_string());
244+
write_ini_settings(
245+
&ini_path,
246+
"show_disconnected_devices",
247+
config.show_disconnected_devices.to_string()
248+
);
249+
if let Ok(mut update_menu_event) = update_menu_event.lock() {
250+
if let Err(err) = proxy_menu.send_event(TrayEvent::ForwardUpdate) {
251+
eprintln!("{err}")
252+
} else {
253+
*update_menu_event = true;
254+
}
255+
}
256+
},
257+
"truncate_bluetooth_name" => {
258+
config.truncate_bluetooth_name = !config.truncate_bluetooth_name;
259+
write_ini_settings(
260+
&ini_path,
261+
"truncate_bluetooth_name",
262+
config.truncate_bluetooth_name.to_string()
263+
);
264+
if let Ok(mut update_menu_event) = update_menu_event.lock() {
265+
if let Err(err) = proxy_menu.send_event(TrayEvent::ForwardUpdate) {
266+
eprintln!("{err}")
267+
} else {
268+
*update_menu_event = true;
269+
}
270+
}
271+
},
272+
"battery_prefix_name" => {
273+
config.battery_prefix_name = !config.battery_prefix_name;
274+
write_ini_settings(
275+
&ini_path,
276+
"battery_prefix_name",
277+
config.battery_prefix_name.to_string()
278+
);
244279
if let Ok(mut update_menu_event) = update_menu_event.lock() {
245280
if let Err(err) = proxy_menu.send_event(TrayEvent::ForwardUpdate) {
246281
eprintln!("{err}")
@@ -429,19 +464,32 @@ async fn get_bluetooth_tray_info(config: Arc<Mutex<Config>>) -> Result<(Vec<Stri
429464
.await
430465
.map_err(|e| anyhow!("Failed to get bluetooth devices info - {e}"))?;
431466
let show_disconnected_devices = config.lock().map_or(false, |c| c.show_disconnected_devices);
432-
let (tooltip, menu_devices) = convert_tray_info(&bluetooth_devices_info, show_disconnected_devices);
467+
let truncate_bluetooth_name = config.lock().map_or(false, |c| c.truncate_bluetooth_name);
468+
let battery_prefix_name = config.lock().map_or(false, |c| c.battery_prefix_name);
469+
let (tooltip, menu_devices) = convert_tray_info(
470+
&bluetooth_devices_info,
471+
show_disconnected_devices,
472+
truncate_bluetooth_name,
473+
battery_prefix_name,
474+
);
433475
Ok((tooltip, menu_devices, bluetooth_devices_info))
434476
}
435477

436478
fn convert_tray_info(
437479
bluetooth_devices_info: &HashSet<BluetoothInfo>,
438480
show_disconnected_devices: bool,
481+
truncate_bluetooth_name: bool,
482+
battery_prefix_name: bool,
439483
) -> (Vec<String>, Vec<String>) {
440484
bluetooth_devices_info.iter().fold((Vec::new(), Vec::new()), |mut acc, blue_info| {
441-
let name = truncate_with_ellipsis(&blue_info.name, 10);
485+
let name = truncate_with_ellipsis(truncate_bluetooth_name, &blue_info.name, 10);
442486
let battery = blue_info.battery;
443-
let status_icon = if blue_info.status { "🟢" } else { "🔴" }; // { "[●]" } else { "[−]" }
444-
let info = format!("{status_icon}{battery:3}% - {name}");
487+
let status_icon = if blue_info.status { "🟢" } else { "🔴" };
488+
let info = if battery_prefix_name {
489+
format!("{status_icon}{battery:3}% - {name}")
490+
} else {
491+
format!("{status_icon}{name} - {battery:3}%")
492+
};
445493
match blue_info.status {
446494
true => {
447495
acc.0.insert(0, info);
@@ -459,8 +507,8 @@ fn convert_tray_info(
459507
})
460508
}
461509

462-
fn truncate_with_ellipsis(s: &str, max_chars: usize) -> String {
463-
if s.chars().count() > max_chars {
510+
fn truncate_with_ellipsis(truncate_bluetooth_name: bool, s: &str, max_chars: usize) -> String {
511+
if truncate_bluetooth_name && s.chars().count() > max_chars {
464512
let mut result = s.chars().take(max_chars).collect::<String>();
465513
result.push_str("...");
466514
result
@@ -478,12 +526,28 @@ fn create_tray_menu(menu_devices: &Vec<String>, config: &Config) -> Result<Menu>
478526

479527
let menu_show_disconnected_devices = CheckMenuItem::with_id(
480528
"show_disconnected_devices",
481-
"Show Disconnected",
529+
"Show Disconnected Devices",
482530
true,
483531
config.show_disconnected_devices,
484532
None
485533
);
486534

535+
let truncate_bluetooth_name = CheckMenuItem::with_id(
536+
"truncate_bluetooth_name",
537+
"Truncate Device Name",
538+
true,
539+
config.truncate_bluetooth_name,
540+
None
541+
);
542+
543+
let battery_prefix_name = CheckMenuItem::with_id(
544+
"battery_prefix_name",
545+
"Battery Prefix Name",
546+
true,
547+
config.battery_prefix_name,
548+
None
549+
);
550+
487551
let update_items = &[
488552
&CheckMenuItem::with_id("15", "15s", true, config.update_interval == 15, None) as &dyn IsMenuItem,
489553
&CheckMenuItem::with_id("30", "30s", true, config.update_interval == 30, None) as &dyn IsMenuItem,
@@ -510,6 +574,7 @@ fn create_tray_menu(menu_devices: &Vec<String>, config: &Config) -> Result<Menu>
510574
&CheckMenuItem::with_id("0.25", "25%", true, low_battery.map_or(false, |v| v == 25), None) as &dyn IsMenuItem,
511575
];
512576
let notify_low_battery = Submenu::with_items("Low Battery", true, low_battery_items)?;
577+
513578
let notify_items = &[
514579
&CheckMenuItem::with_id("notify_mute", "Notify Silently", true, config.notify_mute, None) as &dyn IsMenuItem,
515580
&notify_low_battery as &dyn IsMenuItem,
@@ -518,7 +583,6 @@ fn create_tray_menu(menu_devices: &Vec<String>, config: &Config) -> Result<Menu>
518583
&CheckMenuItem::with_id("notify_added_devices", "New Devices", true, config.notify_added_devices, None) as &dyn IsMenuItem,
519584
&CheckMenuItem::with_id("notify_remove_devices", "Remove Devices", true, config.notify_remove_devices, None) as &dyn IsMenuItem,
520585
];
521-
522586
let menu_notify = Submenu::with_items("Notifications", true, notify_items)?;
523587

524588
let menu_about = PredefinedMenuItem::about(
@@ -531,16 +595,23 @@ fn create_tray_menu(menu_devices: &Vec<String>, config: &Config) -> Result<Menu>
531595
..Default::default()
532596
}));
533597

598+
let settings_items = &[
599+
&menu_show_disconnected_devices as &dyn IsMenuItem,
600+
&truncate_bluetooth_name as &dyn IsMenuItem,
601+
&battery_prefix_name as &dyn IsMenuItem,
602+
&menu_update as &dyn IsMenuItem,
603+
&menu_notify as &dyn IsMenuItem,
604+
];
605+
606+
let menu_setting = Submenu::with_items("Settings", true, settings_items)?;
607+
534608
for text in menu_devices {
535609
let item = CheckMenuItem::with_id(text, text, true, false, None);
536610
tray_menu.append(&item).map_err(|_| anyhow!("Failed to append 'Devices' to Tray Menu"))?;
537611
}
612+
538613
tray_menu.append(&menu_separator).context("Failed to apped 'Separator' to Tray Menu")?;
539-
tray_menu.append(&menu_show_disconnected_devices).context("Failed to apped 'Separator' to Tray Menu")?;
540-
tray_menu.append(&menu_separator).context("Failed to apped 'Separator' to Tray Menu")?;
541-
tray_menu.append(&menu_update).context("Failed to apped 'Update Interval' to Tray Menu")?;
542-
tray_menu.append(&menu_separator).context("Failed to apped 'Separator' to Tray Menu")?;
543-
tray_menu.append(&menu_notify).context("Failed to apped 'Update Interval' to Tray Menu")?;
614+
tray_menu.append(&menu_setting).context("Failed to apped 'Update Interval' to Tray Menu")?;
544615
tray_menu.append(&menu_separator).context("Failed to apped 'Separator' to Tray Menu")?;
545616
tray_menu.append(&menu_about).context("Failed to apped 'About' to Tray Menu")?;
546617
tray_menu.append(&menu_separator).context("Failed to apped 'Separator' to Tray Menu")?;

0 commit comments

Comments
 (0)