From e25e4df3cf765593eab07df2042695dcfa1369a4 Mon Sep 17 00:00:00 2001 From: xzeldon Date: Tue, 27 Aug 2024 21:08:03 +0300 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 311 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 20 +++ LICENSE | 20 +++ README.md | 47 +++++++ img/log.jpg | Bin 0 -> 67902 bytes src/controller.rs | 209 +++++++++++++++++++++++++++++++ src/devices.rs | 48 +++++++ src/main.rs | 154 +++++++++++++++++++++++ src/manager.rs | 131 +++++++++++++++++++ 10 files changed, 941 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 img/log.jpg create mode 100644 src/controller.rs create mode 100644 src/devices.rs create mode 100644 src/main.rs create mode 100644 src/manager.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..09ae9ec --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,311 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "cc" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hidapi" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "pkg-config", + "windows-sys 0.48.0", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "razer-battery-report" +version = "0.1.0" +dependencies = [ + "hidapi", + "log", + "pretty_env_logger", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..25725c4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "razer-battery-report" +version = "0.1.0" +authors = ["xzeldon "] +edition = "2021" +description = "Razer Battery Level Tray Indicator" + +# Slower builds, faster executables +[profile.release] +lto = "fat" +codegen-units = 1 +opt-level = 3 + +[dependencies] +# Communicate with HID devices +hidapi = "2.6.3" + +# Logging +log = "0.4.22" +pretty_env_logger = "0.5.0" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..987aa3b --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright 2024 Timofey Gelazoniya + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ce76a9 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +

razer-battery-report

+ +

+ Razer Battery Level Tray¹ Indicator +

+ +![stdout](/img/log.jpg) + +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 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. + +> ¹ — Tray feature coming soon + +## Usage + +### Downloading a Prebuilt Binary +> *Todo* + +### Building from Source + +To build, you must have [Rust](https://www.rust-lang.org/) and +[Git](https://git-scm.com/) installed on your system. + +1. Clone this repository: `git clone https://github.com/xzeldon/razer-battery-report.git` +2. Navigate into your local repository: `cd razer-battery-report` +3. Build: `cargo build razer-battery-report --release` +4. Executable will be located at `target/release/razer-battery-report` + +## 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) + +> You can grab `pid` and other data from the [openrazer](https://github.com/openrazer/openrazer/blob/352d13c416f42e572016c02fd10a52fc9848644a/driver/razermouse_driver.h#L9) + +## Todo +- [ ] Tray Applet +- [ ] Prebuilt Binary +- [ ] 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) + +## Acknowledgments +* Linux Drivers for Razer devices: https://github.com/openrazer/openrazer +* This python script: https://github.com/spozer/razer-battery-checker +* 🖱️ Logitech Battery Level Tray Indicator (Elem): https://github.com/Fuwn/elem \ No newline at end of file diff --git a/img/log.jpg b/img/log.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e1de1de95e2cd6aca15a02129c7074bf36cc61df GIT binary patch literal 67902 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 imuEeRyVL*i*7r?(yWHvf Self { + RazerReport { + status: 0, + transaction_id: 0, + remaining_packets: 0, + protocol_type: 0, + data_size: 0, + command_class: 0, + command_id: 0, + arguments: [0; 80], + crc: 0, + reserved: 0, + } + } + + pub fn from_bytes(data: &[u8]) -> Result { + if data.len() != 90 { + return Err("Expected 90 bytes of data as razer report"); + } + + let mut report = RazerReport::new(); + report.status = data[0]; + report.transaction_id = data[1]; + report.remaining_packets = u16::from_be_bytes([data[2], data[3]]); + report.protocol_type = data[4]; + report.data_size = data[5]; + report.command_class = data[6]; + report.command_id = data[7]; + report.arguments.copy_from_slice(&data[8..88]); + report.crc = data[88]; + report.reserved = data[89]; + + Ok(report) + } + + pub fn pack(&self) -> Vec { + let mut data = vec![ + self.status, + self.transaction_id, + (self.remaining_packets >> 8) as u8, + (self.remaining_packets & 0xFF) as u8, + self.protocol_type, + self.data_size, + self.command_class, + self.command_id, + ]; + data.extend_from_slice(&self.arguments); + data.push(self.crc); + data.push(self.reserved); + data + } + + pub fn calculate_crc(&self) -> u8 { + let data = self.pack(); + data[2..88].iter().fold(0, |crc, &byte| crc ^ byte) + } + + pub fn is_valid(&self) -> bool { + self.calculate_crc() == self.crc + } +} + +pub struct DeviceController { + pub handle: HidDevice, + pub name: String, + pub pid: u16, + pub report_id: u8, + pub transaction_id: u8, +} + +impl DeviceController { + pub fn new(name: String, pid: u16, path: String) -> Result> { + let api = HidApi::new()?; + + // Convert the path String to a CString + let c_path = CString::new(path)?; + + let handle = api.open_path(c_path.as_ref())?; + + let transaction_id = RAZER_DEVICE_LIST + .iter() + .find(|device| device.pid == pid) + .map_or(0x3F, |device| device.transaction_id()); + + Ok(DeviceController { + handle, + name, + pid, + report_id: 0x00, + transaction_id, + }) + } + + pub fn get_battery_level(&self) -> Result> { + let request = self.create_command(0x07, 0x80, 0x02); + let response = self.send_payload(request)?; + let battery_level = (response.arguments[1] as f32 / 255.0) * 100.0; + // println!("{}\t battery level: {:.2}%", self.name, battery_level); + Ok(battery_level.round() as i32) + } + + pub fn get_charging_status(&self) -> Result> { + let request = self.create_command(0x07, 0x84, 0x02); + let response = self.send_payload(request)?; + let charging_status = response.arguments[1] != 0; + // println!("{}\t charging status: {}", self.name, charging_status); + Ok(charging_status) + } + + pub fn send_payload( + &self, + mut request: RazerReport, + ) -> Result> { + request.crc = request.calculate_crc(); + + for _ in 0..MAX_TRIES_SEND { + self.usb_send(&request)?; + let response = self.usb_receive()?; + + if response.remaining_packets != request.remaining_packets + || response.command_class != request.command_class + || response.command_id != request.command_id + { + return Err("Response doesn't match request".into()); + } + + match response.status { + RazerReport::STATUS_SUCCESSFUL => return Ok(response), + RazerReport::STATUS_BUSY => info!("Device is busy"), + RazerReport::STATUS_NO_RESPONSE => info!("Command timed out"), + RazerReport::STATUS_NOT_SUPPORTED => return Err("Command not supported".into()), + RazerReport::STATUS_FAILURE => return Err("Command failed".into()), + _ => return Err("Error unknown report status".into()), + } + + thread::sleep(TIME_BETWEEN_SEND); + warn!("Trying to resend command"); + } + + Err(format!("Abort command (tries: {})", MAX_TRIES_SEND).into()) + } + + pub fn create_command(&self, command_class: u8, command_id: u8, data_size: u8) -> RazerReport { + let mut report = RazerReport::new(); + report.status = RazerReport::STATUS_NEW_COMMAND; + report.transaction_id = self.transaction_id; + report.command_class = command_class; + report.command_id = command_id; + report.data_size = data_size; + report + } + + pub fn usb_send(&self, report: &RazerReport) -> Result<(), Box> { + let mut data = vec![self.report_id]; + data.extend_from_slice(&report.pack()); + self.handle.send_feature_report(&data)?; + thread::sleep(Duration::from_millis(60)); + Ok(()) + } + + pub fn usb_receive(&self) -> Result> { + let expected_length = 91; + let mut buf = vec![0u8; expected_length]; + let bytes_read = self.handle.get_feature_report(&mut buf)?; + + if bytes_read != expected_length { + return Err("Error while getting feature report".into()); + } + + let report = RazerReport::from_bytes(&buf[1..])?; + if !report.is_valid() { + return Err("Get report has no valid crc".into()); + } + + Ok(report) + } +} diff --git a/src/devices.rs b/src/devices.rs new file mode 100644 index 0000000..d97ca2d --- /dev/null +++ b/src/devices.rs @@ -0,0 +1,48 @@ +pub struct DeviceInfo { + pub name: &'static str, + pub pid: u16, + pub interface: u8, + pub usage_page: u16, + pub usage: u16, + pub vid: u16, +} + +impl DeviceInfo { + pub const fn new( + name: &'static str, + pid: u16, + interface: u8, + usage_page: u16, + usage: u16, + ) -> Self { + DeviceInfo { + name, + pid, + interface, + usage_page, + usage, + vid: 0x1532, + } + } + + pub const fn transaction_id(&self) -> u8 { + match self.pid { + pid if pid == RAZER_DEATHADDER_V3_PRO_WIRED.pid + || pid == RAZER_DEATHADDER_V3_PRO_WIRELESS.pid => + { + 0x1F + } + _ => 0x3F, + } + } +} + +pub const RAZER_DEATHADDER_V3_PRO_WIRED: DeviceInfo = + DeviceInfo::new("Razer DeathAdder V3 Pro", 0x00B6, 0, 1, 2); +pub const RAZER_DEATHADDER_V3_PRO_WIRELESS: DeviceInfo = + DeviceInfo::new("Razer DeathAdder V3 Pro", 0x00B7, 0, 1, 2); + +pub const RAZER_DEVICE_LIST: [DeviceInfo; 2] = [ + RAZER_DEATHADDER_V3_PRO_WIRED, + RAZER_DEATHADDER_V3_PRO_WIRELESS, +]; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..629933c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,154 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, + thread, + time::Duration, +}; + +use log::{error, info}; +use manager::DeviceManager; + +mod controller; +mod devices; +mod manager; + +const BATTERY_UPDATE_INTERVAL: u64 = 60; // seconds +const DEVICE_FETCH_INTERVAL: u64 = 5; // seconds + +struct MemoryDevice { + name: String, + #[allow(unused)] + id: u32, + battery_level: i32, + old_battery_level: i32, + is_charging: bool, +} + +impl MemoryDevice { + fn new(name: String, id: u32) -> Self { + MemoryDevice { + name, + id, + battery_level: -1, + old_battery_level: 50, + is_charging: false, + } + } +} + +struct BatteryChecker { + device_manager: Arc>, + devices: Arc>>, +} + +impl BatteryChecker { + fn new() -> Self { + BatteryChecker { + device_manager: Arc::new(Mutex::new(DeviceManager::new())), + devices: Arc::new(Mutex::new(HashMap::new())), + } + } + + fn run(&self) { + let devices = Arc::clone(&self.devices); + let device_manager = Arc::clone(&self.device_manager); + + // Device fetching thread + thread::spawn(move || loop { + let (removed_devices, connected_devices) = { + let mut manager = device_manager.lock().unwrap(); + manager.fetch_devices() + }; + + { + let mut devices = devices.lock().unwrap(); + for id in removed_devices { + if let Some(device) = devices.remove(&id) { + info!("Device removed: {}", device.name); + } + } + + for id in &connected_devices { + if !devices.contains_key(id) { + if let Some(name) = device_manager.lock().unwrap().get_device_name(*id) { + devices.insert(*id, MemoryDevice::new(name.clone(), *id)); + info!("New device: {}", name); + } else { + error!("Failed to get device name for id: {}", id); + } + } + } + } + + if !connected_devices.is_empty() { + Self::update(&devices, &device_manager, &connected_devices); + } + + thread::sleep(Duration::from_secs(DEVICE_FETCH_INTERVAL)); + }); + + // Battery check thread + loop { + let device_ids: Vec = { + let devices = self.devices.lock().unwrap(); + devices.keys().cloned().collect() + }; + Self::update(&self.devices, &self.device_manager, &device_ids); + thread::sleep(Duration::from_secs(BATTERY_UPDATE_INTERVAL)); + } + } + + fn update( + devices: &Arc>>, + manager: &Arc>, + device_ids: &[u32], + ) { + let mut devices = devices.lock().unwrap(); + let manager = manager.lock().unwrap(); + + for &id in device_ids { + if let Some(device) = devices.get_mut(&id) { + if let Some(battery_level) = manager.get_device_battery_level(id) { + if let Some(is_charging) = manager.is_device_charging(id) { + info!("{} battery level: {}%", device.name, battery_level); + info!("{} charging status: {}", device.name, is_charging); + + device.old_battery_level = device.battery_level; + device.battery_level = battery_level; + device.is_charging = is_charging; + + Self::check_notify(device); + } + } + } + } + } + + fn check_notify(device: &MemoryDevice) { + if device.battery_level == -1 { + return; + } + + if !device.is_charging + && (device.battery_level <= 5 + || (device.old_battery_level > 15 && device.battery_level <= 15)) + { + info!("{}: Battery low ({}%)", device.name, device.battery_level); + } else if device.old_battery_level <= 99 + && device.battery_level == 100 + && device.is_charging + { + info!( + "{}: Battery fully charged ({}%)", + device.name, device.battery_level + ); + } + } +} + +fn main() { + std::env::set_var("RUST_LOG", "trace"); + pretty_env_logger::init(); + let checker = BatteryChecker::new(); + checker.run(); +} diff --git a/src/manager.rs b/src/manager.rs new file mode 100644 index 0000000..7835818 --- /dev/null +++ b/src/manager.rs @@ -0,0 +1,131 @@ +use hidapi::HidApi; +use log::warn; +use std::collections::HashSet; +use std::sync::{Arc, Mutex}; +use std::vec::Vec; + +use crate::controller::DeviceController; +use crate::devices::RAZER_DEVICE_LIST; + +pub struct DeviceManager { + pub device_controllers: Arc>>, +} + +impl DeviceManager { + pub fn new() -> Self { + Self { + device_controllers: Arc::new(Mutex::new(Vec::new())), + } + } + + pub fn fetch_devices(&mut self) -> (Vec, Vec) { + let old_ids: HashSet = { + let controllers = self.device_controllers.lock().unwrap(); + controllers + .iter() + .map(|controller| controller.pid as u32) + .collect() + }; + + let new_controllers = self.get_connected_devices(); + let new_ids: HashSet = new_controllers + .iter() + .map(|controller| controller.pid as u32) + .collect(); + + let removed_devices: Vec = old_ids.difference(&new_ids).cloned().collect(); + let connected_devices: Vec = new_ids.difference(&old_ids).cloned().collect(); + + *self.device_controllers.lock().unwrap() = new_controllers; + + (removed_devices, connected_devices) + } + + pub fn get_device_name(&self, id: u32) -> Option { + let controllers = self.device_controllers.lock().unwrap(); + controllers + .iter() + .find(|controller| controller.pid as u32 == id) + .map(|controller| controller.name.clone()) + } + + pub fn get_device_battery_level(&self, id: u32) -> Option { + let controllers = self.device_controllers.lock().unwrap(); + let controller = controllers + .iter() + .find(|controller| controller.pid as u32 == id)?; + + match controller.get_battery_level() { + Ok(level) => Some(level), + Err(err) => { + warn!("Failed to get battery level: {:?}", err); + None + } + } + } + + pub fn is_device_charging(&self, id: u32) -> Option { + let controllers = self.device_controllers.lock().unwrap(); + let controller = controllers + .iter() + .find(|controller| controller.pid as u32 == id)?; + + match controller.get_charging_status() { + Ok(status) => Some(status), + Err(err) => { + warn!("Failed to get charging status: {:?}", err); + None + } + } + } + + fn get_connected_devices(&self) -> Vec { + let mut connected_devices = Vec::new(); + let mut added_devices = HashSet::new(); + + for device in RAZER_DEVICE_LIST.iter() { + // Create a new HidApi instance + let api = match HidApi::new() { + Ok(api) => api, + Err(err) => { + warn!("Failed to initialize HidApi: {:?}", err); + continue; + } + }; + + // Iterate over the device list to find matching devices + for hid_device in api.device_list() { + if hid_device.vendor_id() == device.vid + && hid_device.product_id() == device.pid + && hid_device.interface_number() == device.interface.into() + { + // Check platform-specific usage if on Windows + if cfg!(target_os = "windows") + && (hid_device.usage_page() != device.usage_page + || hid_device.usage() != device.usage) + { + continue; + } + + // Only add the device if it hasn't been added yet + if !added_devices.contains(&device.pid) { + // Create a new DeviceController + match DeviceController::new( + device.name.to_owned(), + device.pid, + hid_device.path().to_string_lossy().into_owned(), + ) { + Ok(controller) => { + connected_devices.push(controller); + added_devices.insert(device.pid); + } + Err(err) => warn!("Failed to create device controller: {:?}", err), + } + } + } + } + } + + connected_devices + } +}