Upload files to "/"
This commit is contained in:
commit
e3a4d665fd
5 files changed
+1125
No files matched your search
+278
@@ -0,0 +1,278 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
import ctypes
|
||||
|
||||
import hid
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import wx
|
||||
from wx.adv import TaskBarIcon
|
||||
|
||||
ctypes.windll.shcore.SetProcessDpiAwareness(2)
|
||||
|
||||
MODEL = "Ninjutso Sora V2"
|
||||
VID = 0x1915
|
||||
PID_WIRELESS = 0xAE8C
|
||||
PID_WIRED = 0xAE12
|
||||
USAGE_PAGE = 0xFFA0
|
||||
|
||||
# Colors
|
||||
RED = (255, 0, 0)
|
||||
GREEN = (71, 255, 12)
|
||||
BLUE = (91, 184, 255)
|
||||
YELLOW = (255, 255, 0)
|
||||
|
||||
# Settings
|
||||
poll_rate = 60
|
||||
foreground_color = BLUE
|
||||
background_color = (0, 0, 0, 0)
|
||||
font = "consola.ttf"
|
||||
notification_threshold = 20 # Battery percentage threshold for notification
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
def get_resource(relative_path):
|
||||
try:
|
||||
base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
base_path = os.path.abspath(".")
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
def get_device_path(device_list, usage_page):
|
||||
for device in device_list:
|
||||
if device['usage_page'] == usage_page:
|
||||
return device['path']
|
||||
|
||||
def get_device_list():
|
||||
device_list = hid.enumerate(VID, PID_WIRELESS)
|
||||
if not device_list:
|
||||
device_list = hid.enumerate(VID, PID_WIRED)
|
||||
if not device_list:
|
||||
raise RuntimeError(f"The specified device ({VID:X}:{PID_WIRELESS:X} or {VID:X}:{PID_WIRED:X}) cannot be found.")
|
||||
return device_list
|
||||
|
||||
def get_battery():
|
||||
try:
|
||||
device_list = get_device_list()
|
||||
except RuntimeError as e:
|
||||
logging.error(e)
|
||||
return None
|
||||
path = get_device_path(device_list, USAGE_PAGE)
|
||||
logging.info(f"Device path: {path}")
|
||||
device = hid.device()
|
||||
device.open_path(path)
|
||||
report = [0] * 32
|
||||
report[0] = 5 # Report ID
|
||||
report[1] = 21
|
||||
report[4] = 1
|
||||
logging.info(f"Sending report: {report}")
|
||||
device.send_feature_report(report)
|
||||
time.sleep(0.09)
|
||||
res = device.get_feature_report(5, 32)
|
||||
logging.info(f"Received report: {res}")
|
||||
device.close()
|
||||
battery = res[9]
|
||||
charging = res[10]
|
||||
full_charge = res[11]
|
||||
online = res[12]
|
||||
logging.info(f"Battery: {battery}")
|
||||
logging.info(f"Charging: {charging}")
|
||||
logging.info(f"Full_charge: {full_charge}")
|
||||
logging.info(f"Online: {online}")
|
||||
return battery, charging, full_charge, online
|
||||
|
||||
def create_icon(text: str, color, font_path):
|
||||
def PIL2wx(image):
|
||||
return wx.Bitmap.FromBufferRGBA(image.width, image.height, image.tobytes())
|
||||
|
||||
# Use high resolution base image
|
||||
base_size = 350 # Large canvas for crisp text
|
||||
image = Image.new("RGBA", (base_size, base_size), background_color)
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
# Font size configuration
|
||||
size_config = {
|
||||
1: 400, # Single digit size
|
||||
2: 340, # Two digits size
|
||||
3: 280 # Three digits size
|
||||
}
|
||||
|
||||
font_size = size_config.get(len(text), 280)
|
||||
|
||||
try:
|
||||
font = ImageFont.truetype(get_resource(font_path), font_size)
|
||||
except IOError:
|
||||
font = ImageFont.load_default()
|
||||
logging.warning("Using fallback font")
|
||||
|
||||
# Precise text positioning using textbbox
|
||||
bbox = draw.textbbox((0, 0), text, font=font, anchor="mm")
|
||||
x = base_size // 2
|
||||
y = (base_size - (bbox[3] - bbox[1])) // 2 - bbox[1]
|
||||
|
||||
# Draw text with shadow for better visibility
|
||||
draw.text((x, y), text, font=font, fill=color, anchor="mm")
|
||||
|
||||
# Convert directly without resizing
|
||||
return PIL2wx(image)
|
||||
|
||||
# Define the custom directory and file name
|
||||
SAVE_DIR = r"C:\Users\marcn\AppData\Roaming\Macro Deck\plugins\Miranto.SoraV2" # Change this to your preferred location
|
||||
SAVE_FILE = os.path.join(SAVE_DIR, "battery_log.txt")
|
||||
|
||||
def save_battery_to_file(battery):
|
||||
"""Save battery level to a text file."""
|
||||
try:
|
||||
if not os.path.exists(SAVE_DIR):
|
||||
os.makedirs(SAVE_DIR) # Create the directory if it doesn't exist
|
||||
with open(SAVE_FILE, "w") as file:
|
||||
file.write(str(battery)) # Write only the number
|
||||
print(f"Battery level saved to {SAVE_FILE}")
|
||||
except Exception as e:
|
||||
print(f"Error saving battery level: {e}")
|
||||
|
||||
class MyTaskBarIcon(TaskBarIcon):
|
||||
def __init__(self, frame):
|
||||
super().__init__()
|
||||
self.frame = frame
|
||||
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.OnClick)
|
||||
|
||||
def CreatePopupMenu(self):
|
||||
menu = wx.Menu()
|
||||
item_settings = wx.MenuItem(menu, wx.ID_ANY, "Settings")
|
||||
self.Bind(wx.EVT_MENU, self.OnTaskBarActivate, id=item_settings.GetId())
|
||||
item_exit = wx.MenuItem(menu, wx.ID_ANY, "Exit")
|
||||
self.Bind(wx.EVT_MENU, self.OnTaskBarExit, id=item_exit.GetId())
|
||||
# menu.Append(item_settings)
|
||||
menu.Append(item_exit)
|
||||
return menu
|
||||
|
||||
def OnTaskBarActivate(self, event):
|
||||
if not self.frame.IsShown():
|
||||
self.frame.Show()
|
||||
|
||||
def OnTaskBarExit(self, event):
|
||||
self.Destroy()
|
||||
self.frame.Destroy()
|
||||
|
||||
def OnClick(self, event):
|
||||
if self.frame.battery_str == "Zzz" or self.frame.battery_str == "-":
|
||||
self.frame.show_battery()
|
||||
|
||||
class MyFrame(wx.Frame):
|
||||
def __init__(self, parent, title):
|
||||
super().__init__(parent, title=title, pos=(-1, -1), size=(290, 280))
|
||||
self.SetSize((350, 250))
|
||||
self.tray_icon = MyTaskBarIcon(self)
|
||||
self.tray_icon.SetIcon(create_icon(" ", foreground_color, font), "")
|
||||
self.battery_str = ""
|
||||
self.Bind(wx.EVT_CLOSE, self.OnClose)
|
||||
self.Centre()
|
||||
|
||||
self.notification = wx.adv.NotificationMessage(title=MODEL, message="Charged 100%")
|
||||
self.notification.SetFlags(wx.ICON_INFORMATION)
|
||||
self.notification.UseTaskBarIcon(self.tray_icon)
|
||||
self.animation_thread = threading.Thread(target=self.charge_animation, daemon=True)
|
||||
self.thread = threading.Thread(target=self.thread_worker, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def OnClose(self, event):
|
||||
if self.IsShown():
|
||||
self.Hide()
|
||||
|
||||
def thread_worker(self):
|
||||
self.fullcharged = False
|
||||
while True:
|
||||
self.show_battery()
|
||||
if self.battery_str == "-" or self.battery_str == "Zzz":
|
||||
time.sleep(1)
|
||||
else:
|
||||
time.sleep(poll_rate)
|
||||
|
||||
def show_battery(self):
|
||||
result = get_battery()
|
||||
|
||||
if result is None:
|
||||
self.stop_animation = True
|
||||
self.battery_str = "-"
|
||||
if self.animation_thread.is_alive():
|
||||
self.animation_thread.join()
|
||||
self.tray_icon.SetIcon(create_icon(self.battery_str, foreground_color, font), MODEL)
|
||||
return
|
||||
|
||||
battery, charging, full_charge, online = result
|
||||
|
||||
# Save battery level to file
|
||||
save_battery_to_file(battery)
|
||||
|
||||
if charging:
|
||||
self.fullcharged = False
|
||||
self.stop_animation = False
|
||||
if not self.animation_thread.is_alive():
|
||||
self.animation_thread.start()
|
||||
return
|
||||
|
||||
if full_charge:
|
||||
if not self.fullcharged:
|
||||
self.fullcharged = True
|
||||
self.notification.Show(timeout=wx.adv.NotificationMessage.Timeout_Auto)
|
||||
self.stop_animation = True
|
||||
if self.animation_thread.is_alive():
|
||||
self.animation_thread.join()
|
||||
self.tray_icon.SetIcon(wx.Icon(get_resource(R".\icons\battery_100_green.ico")), MODEL)
|
||||
return
|
||||
|
||||
if not online or battery == 0:
|
||||
self.fullcharged = False
|
||||
self.stop_animation = True
|
||||
self.battery_str = "Zzz"
|
||||
if self.animation_thread.is_alive():
|
||||
self.animation_thread.join()
|
||||
self.tray_icon.SetIcon(create_icon(self.battery_str, foreground_color, font), MODEL)
|
||||
return
|
||||
|
||||
if battery == 100:
|
||||
self.fullcharged = False
|
||||
self.stop_animation = True
|
||||
self.battery_str = str(battery)
|
||||
if self.animation_thread.is_alive():
|
||||
self.animation_thread.join()
|
||||
self.tray_icon.SetIcon(wx.Icon(get_resource(R".\icons\battery_100.ico")), MODEL)
|
||||
return
|
||||
|
||||
if battery <= notification_threshold:
|
||||
self.notification.SetMessage(f"Battery Low: {battery}%")
|
||||
self.notification.Show(timeout=wx.adv.NotificationMessage.Timeout_Auto)
|
||||
|
||||
self.fullcharged = False
|
||||
self.stop_animation = True
|
||||
self.battery_str = str(battery)
|
||||
if self.animation_thread.is_alive():
|
||||
self.animation_thread.join()
|
||||
self.tray_icon.SetIcon(create_icon(self.battery_str, foreground_color, font), MODEL)
|
||||
|
||||
def charge_animation(self):
|
||||
while not self.stop_animation:
|
||||
self.tray_icon.SetIcon(wx.Icon(get_resource(R".\icons\battery_0.ico")), MODEL)
|
||||
time.sleep(0.5)
|
||||
self.tray_icon.SetIcon(wx.Icon(get_resource(R".\icons\battery_50.ico")), MODEL)
|
||||
time.sleep(0.5)
|
||||
self.tray_icon.SetIcon(wx.Icon(get_resource(R".\icons\battery_100.ico")), MODEL)
|
||||
time.sleep(0.5)
|
||||
|
||||
class MyApp(wx.App):
|
||||
def OnInit(self):
|
||||
frame = MyFrame(None, title='Sora Tray settings')
|
||||
frame.Show(False)
|
||||
self.SetTopWindow(frame)
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
app = MyApp()
|
||||
app.MainLoop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user