Page MenuHomedesp's stash

No OneTemporary

diff --git a/lib/device.py b/lib/device.py
index 75c2dcb..6d1f63b 100644
--- a/lib/device.py
+++ b/lib/device.py
@@ -1,183 +1,184 @@
import hid
-import winreg
from .datatypes import *
class FusionDevice():
VID = 0x1044
PID = [0x7a39, 0x7a3a]
ADJUST_RGB = None
#unsure what these devices are for yet
DEVICES = [RawInputDevice(1, 6, 768), RawInputDevice(1, 2, 256), RawInputDevice(65281, 8713, 256), RawInputDevice(65282, 1, 256), RawInputDevice(65280, 65280, 256)]
def __init__(self, path: bytes) -> None:
self.h = hid.device()
self.h.open_path(path)
self.path = path
def compute_simple_checksum(self, buf):
acc = 0
for i in range(8):
acc += buf[i]
acc %= 0xFF
buf[8] = 0xFF - acc
def send_simple_command_and_recv(self, opcode: int, report_num = 0):
buf = bytearray(9)
buf[1] = opcode
self.compute_simple_checksum(buf)
sent = self.h.send_feature_report(buf)
if sent == -1:
raise IOError(self.h.error())
buf = self.h.get_feature_report(0, 9)
return buf
def get_firmware_version(self) -> bytes:
return self.send_simple_command_and_recv(0x80)
def get_version_string(self) -> str:
buf = self.get_firmware_version()
minor = list(str(buf[3]))
return f'{buf[2]}.{"0" if len(minor) == 1 else minor.pop(0)}.{minor.pop(0)}'
def get_simple_light_effect(self) -> FusionLightData:
buf = self.send_simple_command_and_recv(0x88)
return FusionLightData(FusionLightEffect(buf[3]), buf[4], buf[5], FusionLightColor(buf[6]), FusionLightDirection(buf[7]))
#this is not strictly needed when setting custom profiles -
#sending the payload via set_custom_light_effect will update the profile and set the current keyboard lighting to it
#but for the profile selection to persist across reboots etc explicitly calling this to set the custom profile is needed
#(note that just calling this will also not change the current keyboard lighting, only on reboots,
# unless we are not in any of the custom profiles then regardless of the actual profile chosen
# the last image will show up right when we do set_simple_light_effect)
def set_simple_light_effect(self, effect: FusionLightData) -> None:
#technically the UI only allows setting 1-10 it looks like, but 255 still works
#but if speed is 0 the firmware would crash when switching to a profile that utilizes speed
if effect.fusion_speed not in range(1, 256):
raise TypeError('Invalid speed!')
buf = bytearray(9)
buf[1] = 0x8
buf[3] = int(effect.fusion_effect)
buf[4] = effect.fusion_speed
#also technically this should be a range from 0-50 but up to 255 works (even though it behaves the same as 50 afaict?)
buf[5] = effect.fusion_brightness
buf[6] = int(effect.fusion_color)
buf[7] = int(effect.fusion_direction)
self.compute_simple_checksum(buf)
sent = self.h.send_feature_report(buf)
if sent == -1:
raise IOError(self.h.error())
#aka SetPictureMatrix2Device
#requires some cooperation from set_simple_light_effect to properly "persist", see above
def set_custom_light_effect(self, profile: int, pixels: PictureMatrix, adjust: RGB = None):
#seems like profile can be anything (just that outside of 0-4 it doesnt persist?)
#edit: so apparently profiles outside of 0-4 (at least up until 255) actually persists and is accessible, just that on a reboot it will assume the data doesnt exist
#switching to another profile and then switching back would still work
#aka the profile restriction only exists in set_simple_light_effect
# if profile not in range(5):
# raise TypeError('Invalid profile number!')
buf = bytearray(9)
buf[1] = 0x12
buf[3] = profile
buf[4] = 8 #why? its also always returned by get_custom_light_effect
self.compute_simple_checksum(buf)
sent = self.h.send_feature_report(buf)
if sent == -1:
raise IOError(self.h.error())
data = pixels.to_bytes(adjust)
for split in [data[i:i+64] for i in range(0, len(data), 64)]:
self.h.write(b'\0' + split)
#writing all at once would just freeze the keyboard
#self.h.write(b'\0' + b'\0'.join([data[i:i+64] for i in range(0, len(data), 64)]))
#aka LoadPictureMatrixValue
#use PictureMatrix.from_bytes to convert it back into a usable PictureMatrix
def get_custom_light_effect(self, profile: int) -> bytes:
# if profile not in range(5):
# raise TypeError('Invalid profile number!')
buf = bytearray(9)
buf[1] = 0x92
buf[3] = profile
self.compute_simple_checksum(buf)
sent = self.h.send_feature_report(buf)
if sent == -1:
raise IOError(self.h.error())
buf = self.h.get_feature_report(0, 9)
data = b''
for i in range(8):
data += bytes(self.h.read(65))
return data
#clears all data in firmware memory (custom profiles, macros, etc)
#in FusionKeyboard.dll it's usually immediately succeeded by a call to reset light profile to static white
def reset(self) -> None:
buf = bytearray(9)
buf[1] = 0x13
buf[2] = 0xFF
self.compute_simple_checksum(buf)
sent = self.h.send_feature_report(buf)
if sent == -1:
raise IOError(self.h.error())
#TODO untested classes below
class FusionDeviceIone(FusionDevice):
PID = [0x7a3c, 0x7a3d, 0x7a3e] #normal, UK, JP
def get_version_string(self) -> str:
buf = bytearray(264)
buf[0] = 0x7
buf[1] = 0x17
report_num = self.h.send_feature_report(buf)
print(report_num)
if report_num == -1:
raise IOError(self.h.error())
- with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "Software\\GigabyteFusion") as key:
- return winreg.QueryValue(key, 'Version')
+ buf = self.h.read(9)
+ return buf
def get_firmware_version(self) -> bytes:
- #theres actually no raw response directly so just fake it with the version string
- return self.get_version_string().encode()
+ buf = self.get_version_string()
+ if len(buf) != 9 or buf[3] != 0x17:
+ raise IOError('Invalid response received from IONE device!')
+ return f'{buf[6]}.{buf[7] // 16}.{buf[7] % 16}'
#TODO changeIoneAnimationListUI, changeIoneAnimationTrigger, changeIoneColorGroupUI
#TODO seems like the way to do complex profile is changeLightEffect_Details for ione
class FusionDeviceIte(FusionDevice):
PID = [0x7a38, 0x7a3b, 0x7a3f] #normal, UK, JP
#Ite requires RGB color adjustment (it's actually a special case even in FusionKeyboard.dll for Ite)
#this value is guessed by eyeballing though
ADJUST_RGB = RGB(70, 255, 130)
class FusionDeviceX9(FusionDevice):
VID = 0x4D9
PID = [0x8008]
def get_version_string(self) -> str:
buf = self.get_firmware_version()
return f'{buf[2]}.{"0" if len(str(buf[3])) == 1 else buf[3] // 16}.{buf[3] % 16}'
def get_device() -> FusionDevice:
for dev in hid.enumerate():
for type in [FusionDevice, FusionDeviceX9, FusionDeviceIone, FusionDeviceIte]:
#print(type, type.VID, type.PID, dev['vendor_id'], dev['product_id'])
if dev['vendor_id'] == type.VID and dev['product_id'] in type.PID:
try:
obj = type(dev['path'])
#there should only be one that we can get the firmware version successfully from
obj.get_firmware_version()
return obj
except IOError:
continue
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sun, Mar 16, 4:34 AM (1 d, 13 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
af/7d/7da9ef38397f621ccb80ac681eee

Event Timeline