Page MenuHomedesp's stash

No OneTemporary

diff --git a/.gitignore b/.gitignore
index 867823d..3c8b0ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,11 @@
*
!fusion
!fusion/datatypes.py
!fusion/device.py
+
!.gitignore
!play.py
!requirements.txt
+!profiles.py
!test.py
diff --git a/fusion/datatypes.py b/fusion/datatypes.py
index 48f7a74..b16934b 100644
--- a/fusion/datatypes.py
+++ b/fusion/datatypes.py
@@ -1,194 +1,217 @@
-from enum import IntEnum
+from enum import IntEnum, _EnumDict
from dataclasses import dataclass
from typing import Optional, List
from math import floor
class FusionLightEffect(IntEnum):
Static = 1
Breathing = 2
Wave = 3
Fadeonkeypress = 4
Marquee = 5
Ripple = 6
Flashonkeypress = 7
Neon = 8
Rainbowmarquee = 9
Raindrop = 10
Circlemarquee = 11
Hedge = 12
Rotate = 13
Custom1 = 51
Custom2 = 52
Custom3 = 53
Custom4 = 54
Custom5 = 55
+ #accept just plain ints to bypass "technically correct" profile ids
+ #idk what hell i just unleashed upon myself with this but yes it works
+ #blame object.__new__ not being usable >:(
+ @classmethod
+ def _missing_(cls, value):
+ #need to reset member names quickly to bypass _check_for_existing_members
+ backup = FusionLightEffect._member_names_
+ FusionLightEffect._member_names_ = []
+
+ #create a subclass with an unknown member just for this value
+ enum_dict = _EnumDict()
+ enum_dict._cls_name = cls
+ enum_dict['Unknown'] = value
+ member = type('FusionLightEffectCustom', (FusionLightEffect,), enum_dict).Unknown
+
+ #restore the backup to avoid breaking things
+ FusionLightEffect._member_names_ = backup
+ return member
+
class FusionLightColor(IntEnum):
Black = 0
Red = 1
Green = 2
Yellow = 3
Blue = 4
Orange = 5
Purple = 6
White = 7
Random = 8
-class IoneDirection(IntEnum):
+class IoneLightDirection(IntEnum):
Left2Right = 0
Right2Left = 1
Up2Down = 2
Down2Up = 3
Clockwise = 4
AntiClockwise = 5
+class FusionLightDirection(IntEnum):
+ Left2Right = 1
+ Right2Left = 2
+ Down2Up = 3
+ Up2Down = 4
+ Clockwise = 1
+ AntiClockwise = 2
+
@dataclass
class FusionLightData:
fusion_effect: FusionLightEffect
#seems like speed actually means duration here since shorter = faster
fusion_speed: int
fusion_brightness: int
fusion_color: FusionLightColor
- fusion_direction: int
+ fusion_direction: FusionLightDirection
@dataclass
class RawInputDevice:
usUsagePage: int
usUsage: int
dwFlags: int
hwndTarget: Optional[int] = None
@dataclass
class RGB:
red: int
green: int
blue: int
@dataclass
class PictureMatrix:
pixels: List[RGB]
- def to_bytes(self) -> bytes:
+ def to_bytes(self, adjust: RGB = None) -> bytes:
if len(self.pixels) not in range(106):
raise TypeError('Too many pixels!')
arr = bytearray(512)
for i, pixel in enumerate(self.pixels):
idx = KEY_MATRIX_INDEX_VALUES[i]*4
- arr[idx:idx+4] = [0, pixel.red, pixel.green, pixel.blue]
+ if adjust:
+ arr[idx:idx+4] = [0, (pixel.red * adjust.red) // 255, (pixel.green * adjust.green) // 255, (pixel.blue * adjust.blue) // 255]
+ else:
+ arr[idx:idx+4] = [0, pixel.red, pixel.green, pixel.blue]
return arr
@classmethod
def from_bytes(cls, data: bytes) -> 'PictureMatrix':
if len(data) != 512:
raise TypeError('Invalid payload size!')
#the nones should be gone after the loop since KEY_MATRIX_INDEX_VALUES should have all the positions for 105 keys regardless of color
pixels = [None]*105
for chunk, idx in enumerate(range(0, len(data), 4)):
- if chunk in [92, 7]:
- breakpoint()
if chunk in KEY_MATRIX_INDEX_VALUES:
#apparently there are duplicates in the mapping so we need to loop through it
start = 0
try:
while True:
key_idx = KEY_MATRIX_INDEX_VALUES.index(chunk, start)
start = key_idx + 1
pixels[key_idx] = RGB(*data[idx+1:idx+4])
except ValueError:
continue
return PictureMatrix(pixels)
@classmethod
def pixel_matrix_to_keys(cls, list: List[RGB]) -> 'PictureMatrix':
"""Turns a proper 19x6 matrix of RGB values into the keyboard matrix by averaging the color over larger keys"""
if len(list) != 19*6:
raise TypeError('Has to be a 19x6 matrix!')
mat = [list[i:i+19] for i in range(0, 19*6, 19)]
def avg(ints: List[int]):
return sum(ints) // len(ints)
def merge(pos: int, length: int, pixels: List[RGB]) -> RGB:
curr_length = 1 - (pos - floor(pos)) #how much of the key is actually in the first pixel location
mat_pos = floor(pos) #first pixel location
length -= curr_length
r, g, b = [], [], []
while (curr_length + length) > 0:
#print(mat_pos, length, curr_length)
#apply weight
r.append(pixels[mat_pos].red * curr_length)
g.append(pixels[mat_pos].green * curr_length)
b.append(pixels[mat_pos].blue * curr_length)
#either it takes a full pixel, or a portion of the pixel if nothing else is left
curr_length = min(1, length)
length -= curr_length
mat_pos += 1
return RGB(round(avg(r)), round(avg(g)), round(avg(b)))
key_pixels = []
- for pixels, weights in zip(mat, KEY_WEIGHT):
+ for pixels, weights in zip(mat, KEY_WEIGHTS):
pos = 0
for weight in weights:
key_pixels.append(merge(pos, weight, pixels))
pos += weight
#account for the 2 vertical keys
def merge_vertical(*pos: int):
p1, p2 = key_pixels[pos[0]], key_pixels[pos[1]],
key_pixels[pos[1]] = RGB(avg([p1.red, p2.red]), avg([p1.green, p2.green]), avg([p1.blue, p2.blue]))
#first position is the one to be removed according to KEY_TITLES mapping
key_pixels.pop(pos[0])
#note the order - it has to be from top of list to bottom of list to avoid repositioning
merge_vertical(88, 102) #numpad enter
merge_vertical(54, 71) #numpad plus
return PictureMatrix(key_pixels)
-
-
-
-
-
KEY_MATRIX_INDEX_VALUES = [
11, 17, 23, 29, 35, 41, 47, 53, 59, 65,
71, 77, 83, 89, 95, 101, 107, 113, 119, 10,
16, 22, 28, 34, 40, 46, 52, 58, 64, 70,
76, 82, 94, 100, 106, 112, 118, 9, 15, 21,
27, 33, 39, 45, 51, 57, 63, 69, 75, 81,
87, 99, 105, 111, 8, 14, 20, 26, 32, 38,
44, 50, 56, 62, 68, 74, 92, 98, 104, 110,
116, 7, 19, 25, 31, 37, 43, 49, 55, 61,
67, 73, 85, 91, 97, 103, 109, 6, 12, 18,
24, 42, 60, 66, 72, 84, 90, 96, 102, 108,
114, 86, 92, 7, 13
]
KEY_TITLES = [
"btnEsc", "btnF1", "btnF2", "btnF3", "btnF4", "btnF5", "btnF6", "btnF7", "btnF8", "btnF9", "btnF10", "btnF11", "btnF12", "btnPause", "btnDel", "btnHome", "btnPgUp", "btnPgDn", "btnEnd",
"btnGrave", "btn1", "btn2", "btn3", "btn4", "btn5", "btn6", "btn7", "btn8", "btn9", "btn0", "btnHyphen", "btnEqual", "btnBackspace", "btnNumLk", "btnSlash2", "btnAsterisk", "btnMinus",
"btnTab", "btnQ", "btnW", "btnE", "btnR", "btnT", "btnY", "btnU", "btnI", "btnO", "btnP", "btnLsquarebracket", "btnRsquarebracket", "btnBackslash", "btn_7", "btn_8", "btn_9",
"btnCapsLock", "btnA", "btnS", "btnD", "btnF", "btnG", "btnH", "btnJ", "btnK", "btnL", "btnSemicolon", "btnApostrophe", "btnEnter", "btn_4", "btn_5", "btn_6", "btnPlus",
"btnLshift", "btnZ", "btnX", "btnC", "btnV", "btnB", "btnN", "btnM", "btnComma", "btnFullstop", "btn_Slash", "btnRshift", "btnUp", "btn_1", "btn_2", "btn_3",
"btnLctrl", "btnFn", "btnWin", "btnLalt", "btnSpace", "btnRalt", "btnApp", "btnRctrl", "btnLeft", "btnDown", "btnRight", "btn_0", "btn_Del", "btnEnter2",
"btnSharpUk", "btnEnterUk", "btnLshiftUk", "btnSlashUk"
]
-KEY_WEIGHT = [
+KEY_WEIGHTS = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1],
[1.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.5, 1, 1, 1, 1],
[1.8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.2, 1, 1, 1, 1],
[2.3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.7, 1, 1, 1, 1, 1],
[1.2, 1, 1, 1, 5.2, 1, 1, 1.6, 1, 1, 1, 1, 1, 1],
]
\ No newline at end of file
diff --git a/fusion/device.py b/fusion/device.py
index 76089e9..75c2dcb 100644
--- a/fusion/device.py
+++ b/fusion/device.py
@@ -1,165 +1,183 @@
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_light_effect(self) -> FusionLightData:
+ 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]), buf[7])
+ return FusionLightData(FusionLightEffect(buf[3]), buf[4], buf[5], FusionLightColor(buf[6]), FusionLightDirection(buf[7]))
- # this is needed to set the current working profile to custom too (set once only is enough)
+ #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] = effect.fusion_direction
+ 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
- #TODO seems to not be persistent, but also nice since we can just reset by hibernating + devmgmt disable/enable when it breaks
- def set_custom_light_effect(self, profile: int, pixels: PictureMatrix):
- if profile not in range(5):
- raise TypeError('Invalid profile number!')
+ #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?
+ 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()
+ 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!')
+ # 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)
- print(buf)
data = b''
for i in range(8):
- print(i)
data += bytes(self.h.read(65))
return data
- #TODO seems like a no-op? but even in FusionKeyboard.dll it's immediately succeeded by a call to set static white anyways hm
- #maybe it's for clearing custom profiles in firmware memory (or the unimplemented macro stuff)
+ #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')
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()
#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]
+ 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
diff --git a/play.py b/play.py
index 92e9dcf..4c0500c 100644
--- a/play.py
+++ b/play.py
@@ -1,69 +1,130 @@
from fusion.datatypes import *
from fusion.device import get_device
from cv2.typing import MatLike
-import sys, cv2
+import sys, cv2, time, numpy, atexit
+
+#10 is actually not transient - it is persistent as a secondary profile but for our purposes we can treat it as transient
+#since nobody should have the need to write to a profile number this high (switching between profiles require os interaction anyways so might as well write a new image)
+TRANSIENT_ID = 10
if len(sys.argv) < 2:
- print(f'Usage: {__file__} <image/video path> [profile id]')
+ print(f'Usage: {__file__} <video path> [profile id]')
exit(-1)
if len(sys.argv) >= 3:
try:
- profile = int(sys.argv[3])
+ profile = int(sys.argv[2])
if profile not in range(5):
raise TypeError()
except:
- print('Invalid profile id given, defaulting to 1...')
- profile = 1
+ print('Invalid profile id given, defaulting to transience (will be reverted after play (or reboot if interrupted))...')
+ profile = TRANSIENT_ID
else:
- profile = 1
-
+ profile = TRANSIENT_ID
dev = get_device()
+orig_effect = dev.get_simple_light_effect()
+
+#if transient, set it back to the old profile
+if profile == TRANSIENT_ID:
+ def reset():
+ if orig_effect.fusion_effect < FusionLightEffect.Custom1:
+ dev.set_simple_light_effect(orig_effect)
+ else:
+ load = PictureMatrix.from_bytes(dev.get_custom_light_effect(orig_effect.fusion_effect - FusionLightEffect.Custom1))
+ dev.set_custom_light_effect(profile, load)
+ atexit.register(reset)
-#change to profile for the image stream first
-dev.set_simple_light_effect(FusionLightData(int(FusionLightEffect.Custom1) + profile - 1, 0, 255, FusionLightColor.Random, 0))
+#change to profile for the image stream first (technically only need to change if not transient and current profile is non custom, but we revert afterwards anyway)
+#to avoid any flashing set to black first (mainly when we are in simple modes which means last seen image will be shown right when set_simple_light_effect is called)
+dev.set_custom_light_effect(profile, PictureMatrix([RGB(0,0,0) for i in range(105)]))
+#need to set it to one of the "proper" profiles for it to know to update
+dev.set_simple_light_effect(FusionLightData(FusionLightEffect.Custom1 + profile if profile != TRANSIENT_ID else FusionLightEffect.Custom1, 1, 255, FusionLightColor.White, FusionLightDirection.Left2Right))
-def set_image(raw: MatLike) -> None:
+
+def set_frame(raw: MatLike, fps_data: List[float]) -> None:
pre = cv2.resize(raw, (19, 6), interpolation=cv2.INTER_AREA)
im = cv2.cvtColor(pre, cv2.COLOR_BGR2RGB)
#display what's supposed to be encoded in the keyboard at this frame
cv2.imshow('preview', cv2.resize(pre, (190*5, 60*5)))
+ if len(fps_data) > 1:
+ cv2.setWindowTitle('preview', f'Preview (FPS: {1/numpy.average(numpy.diff(fps_data))})')
cv2.waitKey(1)
+ #turns out even if i do translate this into a list of RGB instead of just directly send mats it still gives basically around the same fps
+ pixels = im
pixels = []
h, w, _ = im.shape
for y in range(h):
for x in range(w):
pixels.append(RGB(*im[y, x]))
- dev.set_custom_light_effect(profile, PictureMatrix.pixel_matrix_to_keys(pixels))
+ #apply any adjustment needed to give true colors
+ dev.set_custom_light_effect(profile, PictureMatrix.pixel_matrix_to_keys(pixels), adjust=dev.ADJUST_RGB)
#apparently also works with static images
vid = cv2.VideoCapture(sys.argv[1])
#skip some frames so it's not too slow
-interval = vid.get(cv2.CAP_PROP_FPS) // 4
-
-count = 0
-while vid.isOpened():
- ret, frame = vid.read()
- if ret:
- set_image(frame)
- count += interval
- else:
- vid.release()
- break
+#TODO make this less hardcoded - this value is from testing on my laptop with bad apple
+AVG_FPS = 11
+interval = vid.get(cv2.CAP_PROP_FPS) // AVG_FPS
-#keep last frame until we manually quit
-cv2.waitKey(0)
+actual_fps = []
+
+#this actually doesnt help that much it just gives like 1 more fps and takes a while to buffer :(
+PRECOMPUTE = False
+if PRECOMPUTE:
+ buf = []
+ print('Buffering...')
+ count = 0
+ while vid.isOpened():
+
+ ret, frame = vid.read()
+ if ret:
+ buf.append(frame)
+ count += interval
+ vid.set(cv2.CAP_PROP_POS_FRAMES, count)
+ else:
+ vid.release()
+ break
+ print('Done. Starting player...')
+
+ for frame in buf:
+ actual_fps.append(time.time())
+
+ set_frame(frame, actual_fps)
+ #get an averagable data before popping
+ if len(actual_fps) > 10:
+ actual_fps.pop(0)
+else:
+ count = 0
+ while vid.isOpened():
+
+ actual_fps.append(time.time())
+
+ ret, frame = vid.read()
+ if ret:
+ set_frame(frame, actual_fps)
+ count += interval
+ vid.set(cv2.CAP_PROP_POS_FRAMES, count)
+ #get an averagable data before popping
+ if len(actual_fps) > 10:
+ actual_fps.pop(0)
+ else:
+ vid.release()
+ break
+
+
+#keep last frame until we manually quit
+cv2.waitKey(0)
\ No newline at end of file
diff --git a/profiles.py b/profiles.py
new file mode 100644
index 0000000..44b7130
--- /dev/null
+++ b/profiles.py
@@ -0,0 +1,50 @@
+from fusion.device import get_device
+from fusion.datatypes import *
+from cv2.typing import MatLike
+import sys, cv2
+
+"""
+For setting images in / switching between profiles.
+"""
+
+if len(sys.argv) < 2:
+ print(f'Usage: {__file__} <profile id> [image path]')
+ exit(-1)
+
+#only allow properly supported profile ids since theres not much point in supporting it especially when it gets reset after reboot
+#(even though profile ids way higher than this is actually writable and persistent, see set_custom_light_effect comments)
+try:
+ profile = int(sys.argv[1])
+ if profile not in range(5):
+ raise TypeError()
+except:
+ print('Invalid profile given (must be from 0-4).')
+ exit(-1)
+
+dev = get_device()
+
+if len(sys.argv) > 2:
+ #load a new image
+ def set_image(raw: MatLike) -> None:
+ pre = cv2.resize(raw, (19, 6), interpolation=cv2.INTER_AREA)
+ im = cv2.cvtColor(pre, cv2.COLOR_BGR2RGB)
+
+ pixels = []
+
+ h, w, _ = im.shape
+
+ for y in range(h):
+ for x in range(w):
+ pixels.append(RGB(*im[y, x]))
+
+ #adjustment needed to give true colors (maybe it's just my keyboard)
+ dev.set_custom_light_effect(profile, PictureMatrix.pixel_matrix_to_keys(pixels), adjust=dev.ADJUST_RGB)
+
+ set_image(cv2.imread(sys.argv[2]))
+else:
+ #at runtime this would not reload if we dont set_custom_light_effect so we need to do it anyways
+ #(unless we are not in any of the custom profiles then the last image will show up right when we do set_simple_light_effect)
+ load = PictureMatrix.from_bytes(dev.get_custom_light_effect(profile))
+ dev.set_custom_light_effect(profile, load)
+
+dev.set_simple_light_effect(FusionLightData(FusionLightEffect.Custom1 + profile, 1, 255, FusionLightColor.White, FusionLightDirection.Left2Right))
\ No newline at end of file
diff --git a/test.py b/test.py
index 652f5a2..fb129e8 100644
--- a/test.py
+++ b/test.py
@@ -1,23 +1,25 @@
from fusion.device import get_device
from fusion.datatypes import *
dev = get_device()
print(dev.get_version_string())
#set simple effect
-# print(dev.set_custom_light_effect(1, mat))
-# print(dev.set_simple_light_effect(FusionLightData(FusionLightEffect.Custom1, 1, 255//2, FusionLightColor.Random, 4)))
+#print(dev.set_simple_light_effect(FusionLightData(FusionLightEffect.Wave, 1, 50, FusionLightColor.Random, FusionLightDirection.Right2Left)))
+#print(dev.set_simple_light_effect(FusionLightData(FusionLightEffect.Static, 1, 50, FusionLightColor.White, FusionLightDirection.Right2Left)))
#get and set to verify if get is correctly implemented
# load = PictureMatrix.from_bytes(dev.get_custom_light_effect(1))
# print(load)
# dev.set_custom_light_effect(1, load)
#manually set color
-# mat = PictureMatrix([RGB(255, 0, 0) for i in range(105)])
-# dev.set_custom_light_effect(1, mat)
+mat = PictureMatrix([RGB(70, 255, 170) for i in range(105)]) #seems like the rgb algo in the keyboard firmware really sucks - this value seems more true white than anything (edit: it's actually known in the driver and has special cases)
+dev.set_custom_light_effect(10, mat)
+print(dev.set_simple_light_effect(FusionLightData(FusionLightEffect.Custom1+10, 1, 255, FusionLightColor.Random, FusionLightDirection.Left2Right)))
-#test reset
-print(dev.reset())
-print(dev.set_simple_light_effect(FusionLightData(FusionLightEffect.Static, 1, 255, FusionLightColor.White, 4)))
\ No newline at end of file
+#test reset (seems like after reset if we set the light effect to custom profiles without data it would crash? not if we immediately set the profile afterwards tho)
+#also seems like if we change profiles right(? seems to last longer than 100ms for sure) after reset some data (e.g wave direction and speed) would get reset
+# print(dev.reset())
+# print(dev.set_simple_light_effect(FusionLightData(FusionLightEffect.Static, 2, 255, FusionLightColor.White, FusionLightDirection.Up2Down)))
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 21, 8:10 AM (1 d, 5 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
35/80/eb3fa6491f554219ddc85b5f6e8e

Event Timeline