Merge pull request #33 from navrozashvili/windows-startup

Windows improvements
This commit is contained in:
Lennard Kittner
2026-03-22 22:59:22 +01:00
committed by GitHub
4 changed files with 136 additions and 1 deletions

11
Cargo.lock generated
View File

@@ -1343,6 +1343,7 @@ dependencies = [
"tray-icon", "tray-icon",
"windows 0.62.2", "windows 0.62.2",
"winit", "winit",
"winreg",
] ]
[[package]] [[package]]
@@ -3870,6 +3871,16 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "winreg"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
dependencies = [
"cfg-if 1.0.4",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.51.0" version = "0.51.0"

View File

@@ -21,6 +21,7 @@ winit = "0.30.13"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
image = "0.25.10" image = "0.25.10"
winreg = "0.56.0"
windows = { version = "0.62.2", features = [ windows = { version = "0.62.2", features = [
"Win32_System_LibraryLoader", "Win32_System_LibraryLoader",
] } ] }

View File

@@ -1,3 +1,5 @@
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
mod status_tray; mod status_tray;

View File

@@ -7,10 +7,15 @@ use std::{
use image::{Rgba, RgbaImage}; use image::{Rgba, RgbaImage};
use hyper_headset::devices::{DeviceEvent, DeviceProperties, PropertyType}; use hyper_headset::devices::{DeviceEvent, DeviceProperties, PropertyType};
use tray_icon::{ use tray_icon::{
menu::{Menu, MenuEvent, MenuId, MenuItem, PredefinedMenuItem, Submenu}, menu::{CheckMenuItem, Menu, MenuEvent, MenuId, MenuItem, PredefinedMenuItem, Submenu},
TrayIcon, TrayIconBuilder, TrayIcon, TrayIconBuilder,
}; };
use winit::{application::ApplicationHandler, event::StartCause}; use winit::{application::ApplicationHandler, event::StartCause};
#[cfg(target_os = "windows")]
use winreg::{
enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_SET_VALUE},
RegKey, RegValue,
};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use crate::tray_battery_icon_state::{TrayBatteryIconState, WindowsIconKey}; use crate::tray_battery_icon_state::{TrayBatteryIconState, WindowsIconKey};
@@ -18,6 +23,12 @@ use crate::tray_battery_icon_state::{TrayBatteryIconState, WindowsIconKey};
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";
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
const RUN_KEY_PATH: &str = r"Software\Microsoft\Windows\CurrentVersion\Run";
#[cfg(target_os = "windows")]
const STARTUP_APPROVED_RUN_KEY_PATH: &str =
r"Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run";
#[cfg(target_os = "windows")]
const STARTUP_VALUE_NAME: &str = "HyperHeadset";
const WINDOWS_ICON_SIZE: u32 = 16; const WINDOWS_ICON_SIZE: u32 = 16;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@@ -320,6 +331,7 @@ impl TrayApp {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
append_startup_toggle(&menu, &mut new_callbacks);
menu.append(&quit_item).unwrap(); menu.append(&quit_item).unwrap();
new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0))); new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0)));
} }
@@ -344,6 +356,7 @@ impl TrayApp {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
append_startup_toggle(&menu, &mut new_callbacks);
menu.append(&quit_item).unwrap(); menu.append(&quit_item).unwrap();
new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0))); new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0)));
} }
@@ -467,6 +480,7 @@ impl TrayApp {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
append_startup_toggle(&menu, &mut new_callbacks);
menu.append(&quit_item).unwrap(); menu.append(&quit_item).unwrap();
new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0))); new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0)));
} }
@@ -481,6 +495,113 @@ impl TrayApp {
} }
} }
#[cfg(target_os = "windows")]
fn append_startup_toggle(
menu: &Menu,
callbacks: &mut HashMap<MenuId, Box<dyn Fn() + Send + Sync>>,
) {
let startup_enabled = is_start_with_windows_enabled();
let startup_item = CheckMenuItem::new("Start with Windows", true, startup_enabled, None);
let _ = menu.append(&startup_item);
callbacks.insert(
startup_item.id().clone(),
Box::new(|| {
let currently_enabled = is_start_with_windows_enabled();
if let Err(error) = set_start_with_windows_enabled(!currently_enabled) {
eprintln!("Failed to update startup setting: {error}");
}
}),
);
}
#[cfg(target_os = "windows")]
fn startup_command_line() -> std::io::Result<String> {
let exe_path = std::env::current_exe()?;
Ok(format!("\"{}\"", exe_path.display()))
}
#[cfg(target_os = "windows")]
fn open_run_key_with_access(access: u32) -> std::io::Result<RegKey> {
RegKey::predef(HKEY_CURRENT_USER).open_subkey_with_flags(RUN_KEY_PATH, access)
}
#[cfg(target_os = "windows")]
fn open_or_create_run_key_with_access(access: u32) -> std::io::Result<RegKey> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let (run_key, _) = hkcu.create_subkey_with_flags(RUN_KEY_PATH, access)?;
Ok(run_key)
}
#[cfg(target_os = "windows")]
fn open_startup_approved_key_with_access(access: u32) -> std::io::Result<RegKey> {
RegKey::predef(HKEY_CURRENT_USER).open_subkey_with_flags(STARTUP_APPROVED_RUN_KEY_PATH, access)
}
#[cfg(target_os = "windows")]
fn open_or_create_startup_approved_key_with_access(access: u32) -> std::io::Result<RegKey> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let (key, _) = hkcu.create_subkey_with_flags(STARTUP_APPROVED_RUN_KEY_PATH, access)?;
Ok(key)
}
#[cfg(target_os = "windows")]
fn startup_approved_state() -> Option<bool> {
let Ok(key) = open_startup_approved_key_with_access(KEY_READ) else {
return None;
};
let Ok(value) = key.get_raw_value(STARTUP_VALUE_NAME) else {
return None;
};
match value.bytes.first().copied() {
Some(0x02) => Some(true),
Some(0x03) => Some(false),
_ => None,
}
}
#[cfg(target_os = "windows")]
fn set_startup_approved_state(enabled: bool) -> std::io::Result<()> {
let key = open_or_create_startup_approved_key_with_access(KEY_SET_VALUE)?;
// 0x02 => enabled, 0x03 => disabled (same convention used by Startup Apps)
let state = if enabled { 0x02u8 } else { 0x03u8 };
key.set_raw_value(
STARTUP_VALUE_NAME,
&RegValue {
vtype: RegType::REG_BINARY,
bytes: vec![state, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].into(),
},
)?;
Ok(())
}
#[cfg(target_os = "windows")]
fn is_start_with_windows_enabled() -> bool {
let Ok(run_key) = open_run_key_with_access(KEY_READ) else {
return false;
};
if run_key.get_value::<String, _>(STARTUP_VALUE_NAME).is_err() {
return false;
}
startup_approved_state().unwrap_or(true)
}
#[cfg(target_os = "windows")]
fn set_start_with_windows_enabled(enabled: bool) -> std::io::Result<()> {
let run_key = open_or_create_run_key_with_access(KEY_SET_VALUE)?;
if enabled {
run_key.set_value(STARTUP_VALUE_NAME, &startup_command_line()?)?;
set_startup_approved_state(true)?;
} else {
// Keep the Run entry so Windows Startup Apps can manage the toggle too.
if run_key.get_value::<String, _>(STARTUP_VALUE_NAME).is_err() {
run_key.set_value(STARTUP_VALUE_NAME, &startup_command_line()?)?;
}
set_startup_approved_state(false)?;
}
Ok(())
}
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
/// Dark magic to set dark mode /// Dark magic to set dark mode
unsafe fn enable_dark_context_menus() { unsafe fn enable_dark_context_menus() {