Upload files to "/"

This commit is contained in:
Honey committed 2026-09-08 22:42:28 +02:00
commit e3a4d665fd
5 files changed
+1125

No files matched your search

+278
View File
@@ -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()