refactor: abstract debug console logic and migrate to parking_lot

- Added dependency parking_lot to Cargo.toml configuration.
- Added a DebugConsole struct to manage and toggle the visibility of a debug console window.
This commit is contained in:
Timofey Gelazoniya 2024-09-10 00:16:21 +03:00
parent fc7dd809fe
commit 8577920504
Signed by: zeldon
GPG Key ID: 047886915281DD2A
6 changed files with 100 additions and 72 deletions

1
Cargo.lock generated
View File

@ -1818,6 +1818,7 @@ dependencies = [
"hidapi", "hidapi",
"image", "image",
"log", "log",
"parking_lot",
"pretty_env_logger", "pretty_env_logger",
"tao", "tao",
"tray-icon", "tray-icon",

View File

@ -28,3 +28,6 @@ image = "0.25.2"
# Windows API # Windows API
winapi = { version = "0.3.9", features = ["winuser", "wincon", "consoleapi"] } winapi = { version = "0.3.9", features = ["winuser", "wincon", "consoleapi"] }
# Efficient synchronization primitives (e.g. Mutex, RwLock and etc.)
parking_lot = "0.12"

58
src/console.rs Normal file
View File

@ -0,0 +1,58 @@
use parking_lot::Mutex;
use std::{ffi::OsStr, os::windows::ffi::OsStrExt, sync::Arc};
use winapi::um::{consoleapi, wincon, winuser};
pub struct DebugConsole {
hwnd: *mut winapi::shared::windef::HWND__,
visible: Arc<Mutex<bool>>,
}
impl DebugConsole {
pub fn new(title: &str) -> Self {
unsafe {
consoleapi::AllocConsole();
let title: Vec<u16> = OsStr::new(title)
.encode_wide()
.chain(std::iter::once(0))
.collect();
wincon::SetConsoleTitleW(title.as_ptr());
let hwnd = wincon::GetConsoleWindow();
if !hwnd.is_null() {
let hmenu = winuser::GetSystemMenu(hwnd, 0);
if !hmenu.is_null() {
winuser::DeleteMenu(hmenu, winuser::SC_CLOSE as u32, winuser::MF_BYCOMMAND);
}
winuser::ShowWindow(hwnd, winuser::SW_HIDE);
}
Self {
hwnd,
visible: Arc::new(Mutex::new(false)),
}
}
}
pub fn toggle_visibility(&self) {
if !self.hwnd.is_null() {
let mut visible = self.visible.lock();
*visible = !*visible;
unsafe {
winuser::ShowWindow(
self.hwnd,
if *visible {
winuser::SW_SHOW
} else {
winuser::SW_HIDE
},
);
}
}
}
pub fn is_visible(&self) -> bool {
*self.visible.lock()
}
}

View File

@ -1,47 +1,20 @@
#![windows_subsystem = "windows"] #![windows_subsystem = "windows"]
use std::{ffi::OsStr, os::windows::ffi::OsStrExt}; use console::DebugConsole;
use tray::TrayApp; use tray::TrayApp;
mod console;
mod controller; mod controller;
mod devices; mod devices;
mod manager; mod manager;
mod tray; mod tray;
fn main() { fn main() {
unsafe { let console = DebugConsole::new("Razer Battery Report Debug Console");
// Allocate new console for the process
winapi::um::consoleapi::AllocConsole();
let title: Vec<u16> = OsStr::new("Razer Battery Report Debug Console")
.encode_wide()
.chain(std::iter::once(0))
.collect();
winapi::um::wincon::SetConsoleTitleW(title.as_ptr());
let hwnd = winapi::um::wincon::GetConsoleWindow();
// Disable close command in the sys.menu of the new console, otherwise the whole process will quit: https://stackoverflow.com/a/12015131/126995
if !hwnd.is_null() {
let hmenu = winapi::um::winuser::GetSystemMenu(hwnd, 0);
if !hmenu.is_null() {
winapi::um::winuser::DeleteMenu(
hmenu,
winapi::um::winuser::SC_CLOSE as u32,
winapi::um::winuser::MF_BYCOMMAND,
);
}
}
// Hide the console window
if !hwnd.is_null() {
winapi::um::winuser::ShowWindow(hwnd, winapi::um::winuser::SW_HIDE);
}
}
std::env::set_var("RUST_LOG", "trace"); std::env::set_var("RUST_LOG", "trace");
pretty_env_logger::init(); pretty_env_logger::init();
let checker = TrayApp::new();
let checker = TrayApp::new(console);
checker.run(); checker.run();
} }

View File

@ -1,7 +1,8 @@
use hidapi::HidApi; use hidapi::HidApi;
use log::warn; use log::warn;
use parking_lot::Mutex;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::{Arc, Mutex}; use std::sync::Arc;
use std::vec::Vec; use std::vec::Vec;
use crate::controller::DeviceController; use crate::controller::DeviceController;
@ -21,7 +22,7 @@ impl DeviceManager {
pub fn fetch_devices(&mut self) -> (Vec<u32>, Vec<u32>) { pub fn fetch_devices(&mut self) -> (Vec<u32>, Vec<u32>) {
let old_ids: HashSet<u32> = { let old_ids: HashSet<u32> = {
let controllers = self.device_controllers.lock().unwrap(); let controllers = self.device_controllers.lock();
controllers controllers
.iter() .iter()
.map(|controller| controller.pid as u32) .map(|controller| controller.pid as u32)
@ -37,13 +38,13 @@ impl DeviceManager {
let removed_devices: Vec<u32> = old_ids.difference(&new_ids).cloned().collect(); let removed_devices: Vec<u32> = old_ids.difference(&new_ids).cloned().collect();
let connected_devices: Vec<u32> = new_ids.difference(&old_ids).cloned().collect(); let connected_devices: Vec<u32> = new_ids.difference(&old_ids).cloned().collect();
*self.device_controllers.lock().unwrap() = new_controllers; *self.device_controllers.lock() = new_controllers;
(removed_devices, connected_devices) (removed_devices, connected_devices)
} }
pub fn get_device_name(&self, id: u32) -> Option<String> { pub fn get_device_name(&self, id: u32) -> Option<String> {
let controllers = self.device_controllers.lock().unwrap(); let controllers = self.device_controllers.lock();
controllers controllers
.iter() .iter()
.find(|controller| controller.pid as u32 == id) .find(|controller| controller.pid as u32 == id)
@ -51,7 +52,7 @@ impl DeviceManager {
} }
pub fn get_device_battery_level(&self, id: u32) -> Option<i32> { pub fn get_device_battery_level(&self, id: u32) -> Option<i32> {
let controllers = self.device_controllers.lock().unwrap(); let controllers = self.device_controllers.lock();
let controller = controllers let controller = controllers
.iter() .iter()
.find(|controller| controller.pid as u32 == id)?; .find(|controller| controller.pid as u32 == id)?;
@ -66,7 +67,7 @@ impl DeviceManager {
} }
pub fn is_device_charging(&self, id: u32) -> Option<bool> { pub fn is_device_charging(&self, id: u32) -> Option<bool> {
let controllers = self.device_controllers.lock().unwrap(); let controllers = self.device_controllers.lock();
let controller = controllers let controller = controllers
.iter() .iter()
.find(|controller| controller.pid as u32 == id)?; .find(|controller| controller.pid as u32 == id)?;

View File

@ -1,22 +1,19 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
rc::Rc, rc::Rc,
sync::{mpsc, Arc, Mutex}, sync::{mpsc, Arc},
thread, thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use crate::manager::DeviceManager; use crate::{console::DebugConsole, manager::DeviceManager};
use log::{error, info, trace}; use log::{error, info, trace};
use parking_lot::Mutex;
use tao::event_loop::EventLoopBuilder; use tao::event_loop::EventLoopBuilder;
use tray_icon::{ use tray_icon::{
menu::{Menu, MenuEvent, MenuItem}, menu::{Menu, MenuEvent, MenuItem},
TrayIcon, TrayIconBuilder, TrayIcon, TrayIconBuilder,
}; };
use winapi::um::{
wincon::GetConsoleWindow,
winuser::{self, ShowWindow, SW_SHOW},
};
const BATTERY_UPDATE_INTERVAL: Duration = Duration::from_secs(60); const BATTERY_UPDATE_INTERVAL: Duration = Duration::from_secs(60);
const DEVICE_FETCH_INTERVAL: Duration = Duration::from_secs(5); const DEVICE_FETCH_INTERVAL: Duration = Duration::from_secs(5);
@ -45,16 +42,16 @@ impl MemoryDevice {
pub struct TrayInner { pub struct TrayInner {
tray_icon: Rc<Mutex<Option<TrayIcon>>>, tray_icon: Rc<Mutex<Option<TrayIcon>>>,
console_state: Arc<Mutex<bool>>,
menu_items: Arc<Mutex<Vec<MenuItem>>>, menu_items: Arc<Mutex<Vec<MenuItem>>>,
debug_console: Arc<DebugConsole>,
} }
impl TrayInner { impl TrayInner {
fn new() -> Self { fn new(debug_console: Arc<DebugConsole>) -> Self {
Self { Self {
tray_icon: Rc::new(Mutex::new(None)), tray_icon: Rc::new(Mutex::new(None)),
console_state: Arc::new(Mutex::new(false)),
menu_items: Arc::new(Mutex::new(Vec::new())), menu_items: Arc::new(Mutex::new(Vec::new())),
debug_console,
} }
} }
@ -64,7 +61,7 @@ impl TrayInner {
let show_console_item = MenuItem::new("Show Log Window", true, None); let show_console_item = MenuItem::new("Show Log Window", true, None);
let quit_item = MenuItem::new("Exit", true, None); let quit_item = MenuItem::new("Exit", true, None);
let mut menu_items = self.menu_items.lock().unwrap(); let mut menu_items = self.menu_items.lock();
menu_items.push(show_console_item); menu_items.push(show_console_item);
menu_items.push(quit_item); menu_items.push(quit_item);
@ -86,7 +83,7 @@ impl TrayInner {
.build(); .build();
match tray_builder { match tray_builder {
Ok(tray) => *tray_icon.lock().unwrap() = Some(tray), Ok(tray) => *tray_icon.lock() = Some(tray),
Err(err) => error!("Failed to create tray icon: {}", err), Err(err) => error!("Failed to create tray icon: {}", err),
} }
} }
@ -99,11 +96,11 @@ pub struct TrayApp {
} }
impl TrayApp { impl TrayApp {
pub fn new() -> Self { pub fn new(debug_console: DebugConsole) -> Self {
Self { Self {
device_manager: Arc::new(Mutex::new(DeviceManager::new())), device_manager: Arc::new(Mutex::new(DeviceManager::new())),
devices: Arc::new(Mutex::new(HashMap::new())), devices: Arc::new(Mutex::new(HashMap::new())),
tray_inner: TrayInner::new(), tray_inner: TrayInner::new(Arc::new(debug_console)),
} }
} }
@ -137,12 +134,12 @@ impl TrayApp {
thread::spawn(move || loop { thread::spawn(move || loop {
let (removed_devices, connected_devices) = { let (removed_devices, connected_devices) = {
let mut manager = device_manager.lock().unwrap(); let mut manager = device_manager.lock();
manager.fetch_devices() manager.fetch_devices()
}; };
{ {
let mut devices = devices.lock().unwrap(); let mut devices = devices.lock();
for id in removed_devices { for id in removed_devices {
if let Some(device) = devices.remove(&id) { if let Some(device) = devices.remove(&id) {
info!("Device removed: {}", device.name); info!("Device removed: {}", device.name);
@ -151,7 +148,7 @@ impl TrayApp {
for &id in &connected_devices { for &id in &connected_devices {
if let std::collections::hash_map::Entry::Vacant(e) = devices.entry(id) { if let std::collections::hash_map::Entry::Vacant(e) = devices.entry(id) {
if let Some(name) = device_manager.lock().unwrap().get_device_name(id) { if let Some(name) = device_manager.lock().get_device_name(id) {
e.insert(MemoryDevice::new(name.clone(), id)); e.insert(MemoryDevice::new(name.clone(), id));
info!("New device: {}", name); info!("New device: {}", name);
} else { } else {
@ -173,7 +170,7 @@ impl TrayApp {
let devices = Arc::clone(&self.devices); let devices = Arc::clone(&self.devices);
thread::spawn(move || loop { thread::spawn(move || loop {
let device_ids: Vec<u32> = devices.lock().unwrap().keys().cloned().collect(); let device_ids: Vec<u32> = devices.lock().keys().cloned().collect();
tx.send(device_ids).unwrap(); tx.send(device_ids).unwrap();
thread::sleep(BATTERY_UPDATE_INTERVAL); thread::sleep(BATTERY_UPDATE_INTERVAL);
}); });
@ -189,7 +186,7 @@ impl TrayApp {
let devices = Arc::clone(&self.devices); let devices = Arc::clone(&self.devices);
let device_manager = Arc::clone(&self.device_manager); let device_manager = Arc::clone(&self.device_manager);
let tray_icon = Rc::clone(&self.tray_inner.tray_icon); let tray_icon = Rc::clone(&self.tray_inner.tray_icon);
let console_state = Arc::clone(&self.tray_inner.console_state); let debug_console = Arc::clone(&self.tray_inner.debug_console);
let menu_items = Arc::clone(&self.tray_inner.menu_items); let menu_items = Arc::clone(&self.tray_inner.menu_items);
let menu_channel = MenuEvent::receiver(); let menu_channel = MenuEvent::receiver();
@ -210,25 +207,20 @@ impl TrayApp {
} }
if let Ok(event) = menu_channel.try_recv() { if let Ok(event) = menu_channel.try_recv() {
let menu_items = menu_items.lock().unwrap(); let menu_items = menu_items.lock();
let show_console_item = &menu_items[0]; let show_console_item = &menu_items[0];
let quit_item = &menu_items[1]; let quit_item = &menu_items[1];
if event.id == show_console_item.id() { if event.id == show_console_item.id() {
let mut visible = console_state.lock().unwrap(); debug_console.toggle_visibility();
let visible = debug_console.is_visible();
if *visible { show_console_item.set_text(if visible {
unsafe { ShowWindow(GetConsoleWindow(), winuser::SW_HIDE) }; "Hide Log Window"
show_console_item.set_text("Show Log Window");
trace!("hiding log window");
*visible = false;
} else { } else {
unsafe { ShowWindow(GetConsoleWindow(), SW_SHOW) }; "Show Log Window"
show_console_item.set_text("Hide Log Window"); });
trace!("showing log window"); trace!("{} log window", if visible { "showing" } else { "hiding" });
*visible = true
}
} }
if event.id == quit_item.id() { if event.id == quit_item.id() {
@ -244,8 +236,8 @@ impl TrayApp {
device_ids: &[u32], device_ids: &[u32],
tray_icon: &Rc<Mutex<Option<TrayIcon>>>, tray_icon: &Rc<Mutex<Option<TrayIcon>>>,
) { ) {
let mut devices = devices.lock().unwrap(); let mut devices = devices.lock();
let manager = manager.lock().unwrap(); let manager = manager.lock();
for &id in device_ids { for &id in device_ids {
if let Some(device) = devices.get_mut(&id) { if let Some(device) = devices.get_mut(&id) {
@ -262,7 +254,7 @@ impl TrayApp {
Self::check_notify(device); Self::check_notify(device);
if let Some(tray_icon) = tray_icon.lock().unwrap().as_mut() { if let Some(tray_icon) = tray_icon.lock().as_mut() {
let _ = tray_icon let _ = tray_icon
.set_tooltip(Some(format!("{}: {}%", device.name, battery_level))); .set_tooltip(Some(format!("{}: {}%", device.name, battery_level)));
} }