├── src ├── assets │ ├── battery_0.png │ ├── battery_10.png │ ├── battery_100.png │ ├── battery_20.png │ ├── battery_30.png │ ├── battery_40.png │ ├── battery_50.png │ ├── battery_60.png │ ├── battery_70.png │ ├── battery_80.png │ └── battery_90.png └── main.js ├── forge.config.js ├── package.json ├── README.md └── .gitignore /src/assets/battery_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_0.png -------------------------------------------------------------------------------- /src/assets/battery_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_10.png -------------------------------------------------------------------------------- /src/assets/battery_100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_100.png -------------------------------------------------------------------------------- /src/assets/battery_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_20.png -------------------------------------------------------------------------------- /src/assets/battery_30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_30.png -------------------------------------------------------------------------------- /src/assets/battery_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_40.png -------------------------------------------------------------------------------- /src/assets/battery_50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_50.png -------------------------------------------------------------------------------- /src/assets/battery_60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_60.png -------------------------------------------------------------------------------- /src/assets/battery_70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_70.png -------------------------------------------------------------------------------- /src/assets/battery_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_80.png -------------------------------------------------------------------------------- /src/assets/battery_90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tekk-Know/RazerBatteryTaskbar/HEAD/src/assets/battery_90.png -------------------------------------------------------------------------------- /forge.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | packagerConfig: { 3 | icon: 'src/assets/battery_90.png' 4 | }, 5 | rebuildConfig: {}, 6 | makers: [ 7 | { 8 | name: '@electron-forge/maker-squirrel', 9 | config: {}, 10 | }, 11 | { 12 | name: '@electron-forge/maker-zip', 13 | platforms: ['darwin'], 14 | }, 15 | { 16 | name: '@electron-forge/maker-deb', 17 | config: {}, 18 | }, 19 | { 20 | name: '@electron-forge/maker-rpm', 21 | config: {}, 22 | }, 23 | ] 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "razerbatterytaskbar", 3 | "version": "1.0.7", 4 | "main": "src/main.js", 5 | "author": "Tyler Dougherty", 6 | "license": "ISC", 7 | "description": "Razer battery life", 8 | "scripts": { 9 | "start": "electron-forge start", 10 | "package": "electron-forge package", 11 | "make": "electron-forge make" 12 | }, 13 | "dependencies": { 14 | "electron-squirrel-startup": "^1.0.0", 15 | "usb": "^2.6.0" 16 | }, 17 | "devDependencies": { 18 | "@electron-forge/cli": "^6.0.4", 19 | "@electron-forge/maker-deb": "^6.0.4", 20 | "@electron-forge/maker-rpm": "^6.0.4", 21 | "@electron-forge/maker-squirrel": "^6.0.4", 22 | "@electron-forge/maker-zip": "^6.0.4", 23 | "electron": "^22.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Razer Battery Taskbar 2 | ## A simple electron taskbar icon for wireless device battery level. 3 | This project is a work in progress developed mainly for personal use. 4 | 5 | Much of this code is adapted from other works, credit below: 6 | * OpenRazer https://github.com/openrazer/openrazer 7 | * Hsutungyu https://github.com/hsutungyu/razer-mouse-battery-windows 8 | 9 | ## Supported Hardware 10 | * Basilisk 11 | * Basilisk X HyperSpeed 12 | * Naga v2 Pro (non-BT) 13 | * DeathAdder V2 Pro 14 | * DeathAdder V2 X Hyperspeed 15 | * DeathAdder V3 Pro 16 | * HyperPolling Wireless Dongle 17 | * Viper Ultimate 18 | * Mouse Dock Pro 19 | * BlackShark V2 Pro RZ04-0322 (2020 Version) 20 | * BlackShark V2 Pro RZ04-0453 (2023 Version) 21 | 22 | ## Transaction IDs 23 | * https://github.com/openrazer/openrazer/blob/1c36fb458af31fdf7c1d84897e2107aad5ba7669/driver/razermouse_driver.c#L1168 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | out/ 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | node_modules 107 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | var { 2 | WebUSB 3 | } = require('usb'); 4 | const { 5 | app, 6 | Tray, 7 | Menu, 8 | nativeImage, 9 | Notification 10 | } = require('electron'); 11 | if (require('electron-squirrel-startup')) app.quit(); 12 | 13 | const path = require('path'); 14 | const rootPath = app.getAppPath(); 15 | let tray; 16 | let batteryCheckInterval; 17 | 18 | app.whenReady().then(() => { 19 | const icon = nativeImage.createFromPath(path.join(rootPath, 'src/assets/battery_0.png')); 20 | tray = new Tray(icon); 21 | 22 | const contextMenu = Menu.buildFromTemplate([ 23 | { label: 'Quit', type: 'normal', click: QuitClick } 24 | ]); 25 | 26 | batteryCheckInterval = setInterval(() => { 27 | SetTrayDetails(tray); 28 | }, 30000); 29 | 30 | SetTrayDetails(tray); 31 | 32 | tray.setContextMenu(contextMenu); 33 | tray.setToolTip('Searching for device'); 34 | tray.setTitle('Razer battery life'); 35 | }) 36 | 37 | function SetTrayDetails(tray) { 38 | GetBattery().then(battLife => { 39 | if (battLife === 0 || battLife === undefined) return; 40 | 41 | let assetPath = GetBatteryIconPath(battLife); 42 | 43 | tray.setImage(nativeImage.createFromPath(path.join(rootPath, assetPath))); 44 | tray.setToolTip(battLife == 0 ? "Device disconnected" : battLife + '%'); 45 | }); 46 | } 47 | 48 | function GetBatteryIconPath(val) { 49 | let iconName; 50 | iconName = Math.floor(val/10) * 10; 51 | return `src/assets/battery_${iconName}.png`; 52 | } 53 | 54 | function QuitClick() { 55 | clearInterval(batteryCheckInterval); 56 | if (process.platform !== 'darwin') app.quit(); 57 | }; 58 | 59 | // mouse stuff 60 | const RazerVendorId = 0x1532; 61 | const RazerProducts = { 62 | 0x00A4: { 63 | name: 'Razer Mouse Dock Pro', 64 | transactionId: 0x1f 65 | }, 66 | 0x00AA: { 67 | name: 'Razer Basilisk V3 Pro Wired', 68 | transactionId: 0x1f 69 | }, 70 | 0x00AB: { 71 | name: 'Razer Basilisk V3 Pro Wireless', 72 | transactionId: 0x1f 73 | }, 74 | 0x00B9: { 75 | name: 'Razer Basilisk V3 X HyperSpeed', 76 | transactionId: 0x1f 77 | }, 78 | 0x007C: { 79 | name: "Razer DeathAdder V2 Pro Wired", 80 | transactionId: 0x3f 81 | }, 82 | 0x007D: { 83 | name: "Razer DeathAdder V2 Pro Wireless", 84 | transactionId: 0x3f 85 | }, 86 | 0x009C: { 87 | name: "Razer DeathAdder V2 X HyperSpeed", 88 | transactionId: 0x1f 89 | }, 90 | 0x00B3: { 91 | name: 'Razer Hyperpolling Wireless Dongle', 92 | transactionId: 0x1f 93 | }, 94 | 0x00B6: { 95 | name: 'Razer Deathadder V3 Pro Wired', 96 | transactionId: 0x1f 97 | }, 98 | 0x00B7: { 99 | name: 'Razer Deathadder V3 Pro Wireless', 100 | transactionId: 0x1f 101 | }, 102 | 0x0083: { 103 | name: "Razer Basilsk X HyperSpeed", 104 | transactionId: 0x1f 105 | }, 106 | 0x0086: { 107 | name: "Razer Basilisk Ultimate", 108 | transactionId: 0x1f 109 | }, 110 | 0x0088: { 111 | name: "Razer Basilisk Ultimate Dongle", 112 | transactionId: 0x1f 113 | }, 114 | 0x008F: { 115 | name: 'Razer Naga v2 Pro Wired', 116 | transactionId: 0x1f 117 | }, 118 | 0x0090: { 119 | name: 'Razer Naga v2 Pro Wireless', 120 | transactionId: 0x1f 121 | }, 122 | 0x00a5: { 123 | name: 'Razer Viper V2 Pro Wired', 124 | transactionId: 0x1f 125 | }, 126 | 0x00a6: { 127 | name: 'Razer Viper V2 Pro Wireless', 128 | transactionId: 0x1f 129 | }, 130 | 0x007b: { 131 | name: 'Razer Viper Ultimate Wired', 132 | transactionId: 0x3f 133 | }, 134 | 0x0078: { 135 | name: 'Razer Viper Ultimate Wireless', 136 | transactionId: 0x3f 137 | }, 138 | 0x007a: { 139 | name: 'Razer Viper Ultimate Dongle', 140 | transactionId: 0x3f 141 | }, 142 | 0x0555: { 143 | name: 'Razer Blackshark V2 Pro RZ04-0453', 144 | transactionId: 0x3f 145 | }, 146 | 0x0528: { 147 | name: 'Razer Blackshark V2 Pro RZ04-0322', 148 | transactionId: 0x3f 149 | }, 150 | 0x00af: { 151 | name: 'Razer Cobra Pro Wired', 152 | transactionId: 0x1f 153 | }, 154 | 0x00b0: { 155 | name: 'Razer Cobra Pro Wireless', 156 | transactionId: 0x1f 157 | }, 158 | }; 159 | 160 | function GetMessage(mouse) { 161 | // Function that creates and returns the message to be sent to the device 162 | let msg = Buffer.from([0x00, mouse.transactionId, 0x00, 0x00, 0x00, 0x02, 0x07, 0x80]); 163 | let crc = 0; 164 | 165 | for (let i = 2; i < msg.length; i++) { 166 | crc = crc ^ msg[i]; 167 | } 168 | 169 | // the next 80 bytes would be storing the data to be sent, but for getting the battery no data is sent 170 | msg = Buffer.concat([msg, Buffer.alloc(80)]) 171 | 172 | // the last 2 bytes would be the crc and a zero byte 173 | msg = Buffer.concat([msg, Buffer.from([crc, 0])]); 174 | 175 | return msg; 176 | }; 177 | async function GetMouse() { 178 | const customWebUSB = new WebUSB({ 179 | // This function can return a promise which allows a UI to be displayed if required 180 | devicesFound: devices => { 181 | // let dStr = devices.reduce((acc, d) => acc += `${d.productId}||${d.productName}\r\n`,'') 182 | // new Notification({title: 'Info', body: dStr}).show() 183 | return devices.find(device => RazerVendorId && RazerProducts[device.productId] != undefined) 184 | } 185 | }); 186 | 187 | // Returns device based on injected 'devicesFound' function 188 | const device = await customWebUSB.requestDevice({ 189 | filters: [{}] 190 | }) 191 | 192 | if (device) { 193 | return device; 194 | } else { 195 | throw new Error('No Razer device found on system'); 196 | } 197 | }; 198 | async function GetBattery() { 199 | try { 200 | const mouse = await GetMouse(); 201 | 202 | const msg = GetMessage(mouse); 203 | 204 | await mouse.open(); 205 | 206 | if (mouse.configuration === null) { 207 | await mouse.selectConfiguration(1) 208 | } 209 | 210 | await mouse.claimInterface(mouse.configuration.interfaces[0].interfaceNumber); 211 | 212 | const request = await mouse.controlTransferOut({ 213 | requestType: 'class', 214 | recipient: 'interface', 215 | request: 0x09, 216 | value: 0x300, 217 | index: 0x00 218 | }, msg) 219 | 220 | await new Promise(res => setTimeout(res, 500)); 221 | 222 | const reply = await mouse.controlTransferIn({ 223 | requestType: 'class', 224 | recipient: 'interface', 225 | request: 0x01, 226 | value: 0x300, 227 | index: 0x00 228 | }, 90) 229 | 230 | return (reply.data.getUint8(9) / 255 * 100).toFixed(1); 231 | } catch (error) { 232 | console.error(error); 233 | } 234 | }; 235 | --------------------------------------------------------------------------------