Merge pull request #33 from navrozashvili/windows-startup
Windows improvements
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -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"
|
||||||
|
|||||||
@@ -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",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user