Skip to content

Commit fc2037e

Browse files
committed
feat: add charge detect and new tray icon
1 parent 58490c2 commit fc2037e

File tree

5 files changed

+5999
-0
lines changed

5 files changed

+5999
-0
lines changed

src/controller.py

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import time
2+
import usb.core
3+
import usb.util
4+
from usb.backend import libusb1
5+
from storage import RebtStorage, DEVICE
6+
7+
8+
# You can learn more info from https://github.com/hsutungyu/razer-mouse-battery-windows/blob/main/mamba.pyw
9+
class Rebt:
10+
11+
def __init__(self) -> None:
12+
self.config = RebtStorage()
13+
self.findAll = True
14+
self.hasDefault = self.config.isExist()
15+
self.firstRun = not self.hasDefault
16+
self.backend = libusb1.get_backend(find_library=lambda x: "../libusb-1.0.dll")
17+
self.mouse = self.get_mouse()
18+
19+
def map_mouse(self):
20+
mouses = usb.core.find(find_all=True, idVendor=0x1532, backend=self.backend)
21+
for mouse in mouses:
22+
for name, device in DEVICE.items():
23+
knowProduct = int(device["usbId"], 16)
24+
if knowProduct == mouse.idProduct:
25+
self.config.generate(name, device["usbId"], device["tranId"])
26+
self.hasDefault = True
27+
return mouse
28+
29+
def get_mouse(self):
30+
mouse = None
31+
if self.hasDefault:
32+
idProduct = self.config.getDefault("usbId")
33+
mouse = usb.core.find(idVendor=0x1532, idProduct=idProduct, backend=self.backend)
34+
if not mouse and self.findAll:
35+
mouse = self.map_mouse()
36+
if not mouse:
37+
raise RuntimeError(f"Mouse cannot be found.")
38+
39+
return mouse
40+
41+
def generate_msg(self, transaction_id, command_class, command_id, data_size):
42+
msgs = [0x00, transaction_id, 0x00, 0x00, 0x00, command_class, command_id, data_size]
43+
msg = bytes(msgs)
44+
crc = 0
45+
for i in msg[2:]:
46+
crc ^= i
47+
msg += bytes(80)
48+
msg += bytes([crc, 0])
49+
return msg
50+
51+
def battery_msg(self):
52+
return self.generate_msg(self.config.getDefault("tranId"), 0x02, 0x07, 0x80)
53+
54+
def charge_msg(self):
55+
return self.generate_msg(self.config.getDefault("tranId"), 0x02, 0x07, 0x84)
56+
57+
def send_msg(self, msg):
58+
self.mouse = self.get_mouse()
59+
usb.util.claim_interface(self.mouse, 0)
60+
self.mouse.set_configuration()
61+
self.mouse.ctrl_transfer(bmRequestType=0x21,
62+
bRequest=0x09,
63+
wValue=0x300,
64+
data_or_wLength=msg,
65+
wIndex=0x00)
66+
time.sleep(0.1)
67+
result = self.mouse.ctrl_transfer(bmRequestType=0xa1,
68+
bRequest=0x01,
69+
wValue=0x300,
70+
data_or_wLength=90,
71+
wIndex=0x00)
72+
usb.util.dispose_resources(self.mouse)
73+
usb.util.release_interface(self.mouse, 0)
74+
return result[9]
75+
76+
def get_battery(self):
77+
battery_msg = self.battery_msg()
78+
battery = self.send_msg(battery_msg)
79+
if battery == 0: return -1
80+
return int(battery / 255 * 100)
81+
82+
def is_charging(self):
83+
# 1 charging, 0 not
84+
charge_msg = self.charge_msg()
85+
return self.send_msg(charge_msg)

src/drawtray.py

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from PySide2.QtCore import Qt, QRect, QRectF, QSize, QPointF
2+
from PySide2.QtGui import QPixmap, QPainter, QFont, QColor, QPen, QFontMetrics
3+
from PySide2.QtWidgets import QApplication
4+
5+
6+
class TrayIcon:
7+
8+
def __init__(self):
9+
self.bgWidth = 128
10+
self.bgHeight = 128
11+
self.pixmap = QPixmap(self.bgWidth, self.bgHeight)
12+
self.pixmap.fill(Qt.transparent)
13+
self.painter = QPainter(self.pixmap)
14+
15+
def centerWidth(self, width):
16+
return (self.bgWidth - width) >> 1
17+
18+
def centerHeight(self, height):
19+
return (self.bgHeight - height) >> 1
20+
21+
def drawRoundedRect(self, margin, radius, battery=1.0):
22+
marginX = margin[0]
23+
marginY = margin[1]
24+
bRectWidth = self.bgWidth - marginX * 2
25+
bRectHeight = self.bgHeight - marginY * 2
26+
brt = QRectF(self.centerWidth(bRectWidth), self.centerHeight(bRectHeight), bRectWidth * battery, bRectHeight)
27+
self.painter.drawRoundedRect(brt, radius, radius)
28+
return brt
29+
30+
def drawChord(self, brt):
31+
width = height = 15
32+
cr = QRectF(brt.x() + brt.width() - 5, self.centerHeight(height), width, height)
33+
self.painter.drawChord(cr, 90 * 16, -180 * 16)
34+
35+
def drawChargeIcon(self, r=20):
36+
p = QRectF(self.pixmap.width() - r, 0, r, r)
37+
self.painter.setPen(Qt.red)
38+
self.painter.setBrush(Qt.red)
39+
self.painter.drawChord(p, 0, 360 * 16)
40+
41+
def drawNumIcon(self, battery, isCharge):
42+
if isCharge: self.drawChargeIcon(26)
43+
ft = QFont("Microsoft YaHei", 48)
44+
ft_rect = QFontMetrics(ft).boundingRect(str(battery))
45+
scale = ft_rect.width() / self.pixmap.width()
46+
if scale > 1:
47+
ft.setPointSize(36)
48+
elif scale < 0.5:
49+
ft.setPointSize(62)
50+
self.painter.setFont(ft)
51+
self.painter.setPen(Qt.black)
52+
self.painter.setRenderHint(QPainter.HighQualityAntialiasing)
53+
self.painter.drawText(self.pixmap.rect(), Qt.AlignCenter, str(battery))
54+
55+
self.painter.end()
56+
57+
def drawRectIcon(self, battery, isCharge):
58+
self.painter.setBrush(Qt.black)
59+
self.painter.setPen(Qt.transparent)
60+
brt = self.drawRoundedRect((16, 42), 6)
61+
62+
self.painter.setCompositionMode(QPainter.CompositionMode_SourceOut)
63+
percentage = battery / 100
64+
mask_x = brt.x() + percentage * brt.width()
65+
mask_width = brt.width() * (1 - percentage)
66+
maskBrt = QRectF(mask_x, brt.y(), mask_width, brt.height())
67+
self.painter.fillRect(maskBrt, Qt.transparent)
68+
69+
# big rect
70+
self.painter.setCompositionMode(QPainter.CompositionMode_DestinationOver)
71+
self.painter.setPen(QPen(Qt.black, 9))
72+
self.painter.setBrush(Qt.transparent)
73+
brt_ = self.drawRoundedRect((6, 30), 16)
74+
if isCharge: self.drawChargeIcon()
75+
self.painter.end()
76+
77+
def draw(self, battery, isCharge, style=0):
78+
if style == 0:
79+
self.drawNumIcon(battery, isCharge)
80+
else:
81+
self.drawRectIcon(battery, isCharge)
82+
return self.pixmap

0 commit comments

Comments
 (0)