Compare commits
3 Commits
58d139d156
...
7138c941d9
Author | SHA1 | Date |
---|---|---|
Timofey Gelazoniya | 7138c941d9 | |
Timofey Gelazoniya | 0a4c10de34 | |
Timofey Gelazoniya | 559b0e843c |
|
@ -1813,7 +1813,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "razer-battery-report"
|
name = "razer-battery-report"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hidapi",
|
"hidapi",
|
||||||
"image",
|
"image",
|
||||||
|
@ -1821,6 +1821,7 @@ dependencies = [
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"tao",
|
"tao",
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "razer-battery-report"
|
name = "razer-battery-report"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
authors = ["xzeldon <contact@zeldon.ru>"]
|
authors = ["xzeldon <contact@zeldon.ru>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Razer Battery Level Tray Indicator"
|
description = "Razer Battery Level Tray Indicator"
|
||||||
|
@ -25,3 +25,6 @@ tray-icon = "0.17.0"
|
||||||
|
|
||||||
# Image manipulation
|
# Image manipulation
|
||||||
image = "0.25.2"
|
image = "0.25.2"
|
||||||
|
|
||||||
|
# Windows API
|
||||||
|
winapi = { version = "0.3.9", features = ["winuser", "wincon", "consoleapi"] }
|
||||||
|
|
20
README.md
20
README.md
|
@ -12,12 +12,13 @@ Show your wireless Razer devices battery levels in your system tray.
|
||||||
|
|
||||||
> This is a work in progress and currently support only **Razer DeathAdder V3 Pro**.
|
> This is a work in progress and currently support only **Razer DeathAdder V3 Pro**.
|
||||||
|
|
||||||
> This works pretty well on **Windows**, should work on **Linux** if you *add udev rule to get access to usb devices* (see [here](https://github.com/libusb/hidapi/blob/master/udev/69-hid.rules)). But I haven't tested yet.
|
> This works pretty well on **Windows**, should work on **Linux** if you _add udev rule to get access to usb devices_ (see [here](https://github.com/libusb/hidapi/blob/master/udev/69-hid.rules)). But I haven't tested yet.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Downloading a Prebuilt Binary
|
### Downloading a Prebuilt Binary
|
||||||
> *Todo*
|
|
||||||
|
> _Todo_
|
||||||
|
|
||||||
### Building from Source
|
### Building from Source
|
||||||
|
|
||||||
|
@ -30,21 +31,24 @@ To build, you must have [Rust](https://www.rust-lang.org/) and
|
||||||
4. Executable will be located at `target/release/razer-battery-report.exe`
|
4. Executable will be located at `target/release/razer-battery-report.exe`
|
||||||
|
|
||||||
## Adding new devices yourself
|
## Adding new devices yourself
|
||||||
* add device with `name`, `pid`, `interface`, `usage_page`, `usage` to [devices.rs](/src/devices.rs)
|
|
||||||
* add `transaction_id` to switch statement in `DeviceInfo` in [devices.rs](/src/devices.rs)
|
- add device with `name`, `pid`, `interface`, `usage_page`, `usage` to [devices.rs](/src/devices.rs)
|
||||||
|
- add `transaction_id` to switch statement in `DeviceInfo` in [devices.rs](/src/devices.rs)
|
||||||
|
|
||||||
> You can grab `pid` and other data from the [openrazer](https://github.com/openrazer/openrazer/blob/352d13c416f42e572016c02fd10a52fc9848644a/driver/razermouse_driver.h#L9)
|
> You can grab `pid` and other data from the [openrazer](https://github.com/openrazer/openrazer/blob/352d13c416f42e572016c02fd10a52fc9848644a/driver/razermouse_driver.h#L9)
|
||||||
|
|
||||||
## Todo
|
## Todo
|
||||||
|
|
||||||
- [x] Tray Applet
|
- [x] Tray Applet
|
||||||
- [ ] Force update devices button in tray menu
|
- [ ] Force update devices button in tray menu
|
||||||
- [ ] Colored tray icons for different battery levels
|
- [ ] Colored tray icons for different battery levels
|
||||||
- [ ] Show log window button in tray menu
|
- [x] Show log window button in tray menu
|
||||||
- [ ] Prebuilt Binary
|
- [ ] Prebuilt Binary
|
||||||
- [ ] Command Line Arguments for update frequency
|
- [ ] Command Line Arguments for update frequency
|
||||||
- [ ] Support for other Razer Devices (I only have DeathAdder V3 Pro, so I won't be able to test it with other devices)
|
- [ ] Support for other Razer Devices (I only have DeathAdder V3 Pro, so I won't be able to test it with other devices)
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
* Linux Drivers for Razer devices: https://github.com/openrazer/openrazer
|
|
||||||
* This python script: https://github.com/spozer/razer-battery-checker
|
- Linux Drivers for Razer devices: https://github.com/openrazer/openrazer
|
||||||
* 🖱️ Logitech Battery Level Tray Indicator (Elem): https://github.com/Fuwn/elem
|
- This python script: https://github.com/spozer/razer-battery-checker
|
||||||
|
- 🖱️ Logitech Battery Level Tray Indicator (Elem): https://github.com/Fuwn/elem
|
||||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -1,6 +1,7 @@
|
||||||
#![windows_subsystem = "windows"]
|
#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
use tray::TrayApp;
|
use tray::TrayApp;
|
||||||
|
use winapi::um::{self, wincon, winuser};
|
||||||
|
|
||||||
mod controller;
|
mod controller;
|
||||||
mod devices;
|
mod devices;
|
||||||
|
@ -8,6 +9,25 @@ mod manager;
|
||||||
mod tray;
|
mod tray;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
unsafe {
|
||||||
|
// Allocate new console for the process
|
||||||
|
um::consoleapi::AllocConsole();
|
||||||
|
|
||||||
|
// Modify the console window's style to remove the system menu (close, minimize, etc.).
|
||||||
|
winuser::SetWindowLongPtrW(
|
||||||
|
wincon::GetConsoleWindow(),
|
||||||
|
winuser::GWL_STYLE,
|
||||||
|
#[allow(clippy::cast_possible_wrap)]
|
||||||
|
{
|
||||||
|
winuser::GetWindowLongPtrW(wincon::GetConsoleWindow(), winuser::GWL_STYLE)
|
||||||
|
& !winuser::WS_SYSMENU as isize
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hide the console window
|
||||||
|
winuser::ShowWindow(wincon::GetConsoleWindow(), 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();
|
||||||
|
|
|
@ -85,7 +85,6 @@ impl DeviceManager {
|
||||||
let mut added_devices = HashSet::new();
|
let mut added_devices = HashSet::new();
|
||||||
|
|
||||||
for device in RAZER_DEVICE_LIST.iter() {
|
for device in RAZER_DEVICE_LIST.iter() {
|
||||||
// Create a new HidApi instance
|
|
||||||
let api = match HidApi::new() {
|
let api = match HidApi::new() {
|
||||||
Ok(api) => api,
|
Ok(api) => api,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -94,7 +93,6 @@ impl DeviceManager {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Iterate over the device list to find matching devices
|
|
||||||
for hid_device in api.device_list() {
|
for hid_device in api.device_list() {
|
||||||
if hid_device.vendor_id() == device.vid
|
if hid_device.vendor_id() == device.vid
|
||||||
&& hid_device.product_id() == device.pid
|
&& hid_device.product_id() == device.pid
|
||||||
|
@ -110,7 +108,6 @@ impl DeviceManager {
|
||||||
|
|
||||||
// Only add the device if it hasn't been added yet
|
// Only add the device if it hasn't been added yet
|
||||||
if !added_devices.contains(&device.pid) {
|
if !added_devices.contains(&device.pid) {
|
||||||
// Create a new DeviceController
|
|
||||||
match DeviceController::new(
|
match DeviceController::new(
|
||||||
device.name.to_owned(),
|
device.name.to_owned(),
|
||||||
device.pid,
|
device.pid,
|
||||||
|
|
116
src/tray.rs
116
src/tray.rs
|
@ -6,12 +6,16 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::manager::DeviceManager;
|
use crate::manager::DeviceManager;
|
||||||
use log::{error, info};
|
use log::{error, info, trace};
|
||||||
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: std::time::Duration = std::time::Duration::from_secs(60);
|
const BATTERY_UPDATE_INTERVAL: std::time::Duration = std::time::Duration::from_secs(60);
|
||||||
const DEVICE_FETCH_INTERVAL: std::time::Duration = std::time::Duration::from_secs(5);
|
const DEVICE_FETCH_INTERVAL: std::time::Duration = std::time::Duration::from_secs(5);
|
||||||
|
@ -38,10 +42,59 @@ impl MemoryDevice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TrayInner {
|
||||||
|
tray_icon: Rc<Mutex<Option<TrayIcon>>>,
|
||||||
|
console_state: Arc<Mutex<bool>>,
|
||||||
|
menu_items: Arc<Mutex<Vec<MenuItem>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrayInner {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
tray_icon: Rc::new(Mutex::new(None)),
|
||||||
|
console_state: Arc::new(Mutex::new(false)),
|
||||||
|
menu_items: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_menu(&self) -> Menu {
|
||||||
|
let tray_menu = Menu::new();
|
||||||
|
|
||||||
|
let show_console_item = MenuItem::new("Show Log Window", true, None);
|
||||||
|
let quit_item = MenuItem::new("Exit", true, None);
|
||||||
|
|
||||||
|
let mut menu_items = self.menu_items.lock().unwrap();
|
||||||
|
menu_items.push(show_console_item);
|
||||||
|
menu_items.push(quit_item);
|
||||||
|
|
||||||
|
tray_menu
|
||||||
|
.append_items(&[&menu_items[0], &menu_items[1]])
|
||||||
|
.unwrap();
|
||||||
|
tray_menu
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_tray(
|
||||||
|
tray_icon: &Rc<Mutex<Option<TrayIcon>>>,
|
||||||
|
tray_menu: &Menu,
|
||||||
|
icon: tray_icon::Icon,
|
||||||
|
) {
|
||||||
|
let tray_builder = TrayIconBuilder::new()
|
||||||
|
.with_menu(Box::new(tray_menu.clone()))
|
||||||
|
.with_tooltip("Service is running")
|
||||||
|
.with_icon(icon)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
match tray_builder {
|
||||||
|
Ok(tray) => *tray_icon.lock().unwrap() = Some(tray),
|
||||||
|
Err(err) => error!("Failed to create tray icon: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TrayApp {
|
pub struct TrayApp {
|
||||||
device_manager: Arc<Mutex<DeviceManager>>,
|
device_manager: Arc<Mutex<DeviceManager>>,
|
||||||
devices: Arc<Mutex<HashMap<u32, MemoryDevice>>>,
|
devices: Arc<Mutex<HashMap<u32, MemoryDevice>>>,
|
||||||
tray_icon: Rc<Mutex<Option<TrayIcon>>>,
|
tray_inner: TrayInner,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrayApp {
|
impl TrayApp {
|
||||||
|
@ -49,14 +102,14 @@ impl TrayApp {
|
||||||
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_icon: Rc::new(Mutex::new(None)),
|
tray_inner: TrayInner::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self) {
|
pub fn run(&self) {
|
||||||
let icon = Self::create_icon();
|
let icon = Self::create_icon();
|
||||||
let event_loop = EventLoopBuilder::new().build();
|
let event_loop = EventLoopBuilder::new().build();
|
||||||
let tray_menu = Self::create_menu();
|
let tray_menu = self.tray_inner.create_menu();
|
||||||
|
|
||||||
let (sender, receiver) = mpsc::channel();
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
|
||||||
|
@ -77,13 +130,6 @@ impl TrayApp {
|
||||||
tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to create icon")
|
tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to create icon")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_menu() -> Menu {
|
|
||||||
let tray_menu = Menu::new();
|
|
||||||
let quit_item = MenuItem::new("Exit", true, None);
|
|
||||||
tray_menu.append_items(&[&quit_item]).unwrap();
|
|
||||||
tray_menu
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_device_fetch_thread(&self, tx: mpsc::Sender<Vec<u32>>) {
|
fn spawn_device_fetch_thread(&self, tx: mpsc::Sender<Vec<u32>>) {
|
||||||
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);
|
||||||
|
@ -139,9 +185,11 @@ impl TrayApp {
|
||||||
tray_menu: Menu,
|
tray_menu: Menu,
|
||||||
rx: mpsc::Receiver<Vec<u32>>,
|
rx: mpsc::Receiver<Vec<u32>>,
|
||||||
) {
|
) {
|
||||||
let tray_icon = Rc::clone(&self.tray_icon);
|
|
||||||
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 console_state = Arc::clone(&self.tray_inner.console_state);
|
||||||
|
let menu_items = Arc::clone(&self.tray_inner.menu_items);
|
||||||
|
|
||||||
let menu_channel = MenuEvent::receiver();
|
let menu_channel = MenuEvent::receiver();
|
||||||
|
|
||||||
|
@ -155,34 +203,40 @@ impl TrayApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let tao::event::Event::NewEvents(tao::event::StartCause::Init) = event {
|
if let tao::event::Event::NewEvents(tao::event::StartCause::Init) = event {
|
||||||
Self::create_tray_icon(&tray_icon, &tray_menu, icon.clone());
|
// We create the icon once the event loop is actually running
|
||||||
|
// to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
|
||||||
|
TrayInner::build_tray(&tray_icon, &tray_menu, icon.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(event) = menu_channel.try_recv() {
|
if let Ok(event) = menu_channel.try_recv() {
|
||||||
if event.id == tray_menu.items()[0].id() {
|
let menu_items = menu_items.lock().unwrap();
|
||||||
|
|
||||||
|
let show_console_item = &menu_items[0];
|
||||||
|
let quit_item = &menu_items[1];
|
||||||
|
|
||||||
|
if event.id == show_console_item.id() {
|
||||||
|
let mut visible = console_state.lock().unwrap();
|
||||||
|
|
||||||
|
if *visible {
|
||||||
|
unsafe { ShowWindow(GetConsoleWindow(), winuser::SW_HIDE) };
|
||||||
|
show_console_item.set_text("Show Log Window");
|
||||||
|
trace!("hiding log window");
|
||||||
|
*visible = false;
|
||||||
|
} else {
|
||||||
|
unsafe { ShowWindow(GetConsoleWindow(), SW_SHOW) };
|
||||||
|
show_console_item.set_text("Hide Log Window");
|
||||||
|
trace!("showing log window");
|
||||||
|
*visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.id == quit_item.id() {
|
||||||
*control_flow = tao::event_loop::ControlFlow::Exit;
|
*control_flow = tao::event_loop::ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_tray_icon(
|
|
||||||
tray_icon: &Rc<Mutex<Option<TrayIcon>>>,
|
|
||||||
tray_menu: &Menu,
|
|
||||||
icon: tray_icon::Icon,
|
|
||||||
) {
|
|
||||||
let tray_builder = TrayIconBuilder::new()
|
|
||||||
.with_menu(Box::new(tray_menu.clone()))
|
|
||||||
.with_tooltip("Service is running")
|
|
||||||
.with_icon(icon)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
match tray_builder {
|
|
||||||
Ok(tray) => *tray_icon.lock().unwrap() = Some(tray),
|
|
||||||
Err(err) => error!("Failed to create tray icon: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
devices: &Arc<Mutex<HashMap<u32, MemoryDevice>>>,
|
devices: &Arc<Mutex<HashMap<u32, MemoryDevice>>>,
|
||||||
manager: &Arc<Mutex<DeviceManager>>,
|
manager: &Arc<Mutex<DeviceManager>>,
|
||||||
|
|
Loading…
Reference in New Issue