utility-scripts/redragon-lights.py

229 lines
14 KiB
Python
Executable File

#!/usr/bin/python3
import argparse
import atexit
import dbus
import dbus.mainloop.glib
import gi.repository.GLib
import pprint
import sys
import time
import traceback
import usb.core
# Redragon K621 Horus
vendor=0x258a
product=0x0049
debug = 0
screensaver_list = [
'org.gnome.ScreenSaver',
'org.cinnamon.ScreenSaver',
'org.kde.screensaver',
'org.freedesktop.ScreenSaver'
]
def main():
global debug
parser = argparse.ArgumentParser(
description='Adjust backlight for Redgradon Horus K621',
add_help=True,
)
parser.add_argument('--on', '-1', dest='switch', action='store_true',
default=None, help='Turn keyboard backlight on')
parser.add_argument('--off', '-0', dest='switch', action='store_false',
default=None, help='Turn keyboard backlight off')
parser.add_argument('--screensaver', '-s', dest='screensaver', action='store_true',
default=None, help='Turn keyboard backlight off')
parser.add_argument('--verbose', '-v', action='count',
default=0, help='Print more verbose information (can be specified multiple times)')
args = parser.parse_args()
if args.switch is None and args.screensaver is None:
parser.print_help()
sys.exit(1)
debug = args.verbose
if args.screensaver:
dbus_loop = dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
session = dbus.SessionBus(mainloop=dbus_loop)
for screensaver in screensaver_list:
session.add_match_string_non_blocking("interface='{}'".format(screensaver))
#session.add_match_string_non_blocking("interface='org.gnome.Mutter.IdleMonitor'")
session.add_message_filter(screensaver_message_callback)
loop = gi.repository.GLib.MainLoop()
loop.run()
else:
kbd = setup_usb()
send_packets(kbd, args.switch)
def screensaver_message_callback(session, message):
global debug
for screensaver in screensaver_list:
if message.get_interface() == screensaver:
if debug > 2:
pprint.pprint(message)
# not supported in org.freedesktop.ScreenSaver:
# Error org.freedesktop.DBus.Error.NotSupported: This method is not part of the idle inhibition specification: https://specifications.freedesktop.org/idle-inhibit-spec/latest/
if message.get_member() == "ActiveChanged" or message.get_member() == "WakeUpScreen":
try:
screensaver_changed = bool(message.get_args_list()[0])
if debug > 1:
print("Screen saver {} changed: ActiveChanged is {}".format(screensaver, screensaver_changed))
except:
pass
# Capture the GetActive method call in a try/except, or the screensavers that
# don't support GetActive will block those that do
try:
screensaver_path = '/{0}'.format(screensaver.replace('.', '/'))
screensaver_obj = session.get_object(screensaver, screensaver_path)
screensaver_iface = dbus.Interface(screensaver_obj, screensaver)
# Delay to allow for a more accurate reading of GetActive()
time.sleep(0.25)
status = bool(screensaver_iface.GetActive())
if debug > 1:
print("Screen saver {} changed. GetActive: {}".format(screensaver, status))
# Screensaver is now active; turning the screen back on is handled here with WakeUpScreen
if status:
kbd = setup_usb()
send_packets(kbd, not status)
except:
continue
# Always wake keyboard when screen is woken
if message.get_member() == "WakeUpScreen":
try:
# Delay slightly to avoid conflicting with ActiveChanged's GetActive() above
time.sleep(0.25)
if debug > 1:
print("Screen saver {} changed: WakeUpScreen".format(screensaver))
kbd = setup_usb()
send_packets(kbd, True)
except:
continue
# Set up USB
def setup_usb():
global debug
kbd = usb.core.find(idVendor=vendor, idProduct=product)
if kbd is None:
raise ValueError('Keyboard not found; perhaps switch to USB mode?')
if debug > 0:
print("Product: ", kbd.product)
print("Manufacturer: ", kbd.manufacturer)
return kbd
# Send packet/s
def send_packets(kbd, switch):
global debug
if kbd is None:
setup_usb()
if switch is True:
#pattern = 0x01 # solid white
#pattern = 0x02 #
#pattern = 0x03 #
#pattern = 0x04 #
#pattern = 0x05 #
#pattern = 0x06 #
#pattern = 0x07 #
#pattern = 0x08 #
pattern = 0x09 # shadow_disappear
#pattern = 0x10 #
#pattern = 0x11 #
#pattern = 0x12 #
#pattern = 0x13 #
#pattern = 0x14 #
#pattern = 0x20 # custom pattern
else:
pattern = 0x00 # off
if debug > 1:
print("Sending pattern {} command".format(pattern))
# This message tweaks the pattern between OFF (0x0) and the white shadow_disappear pattern (0x9)
# with brightness 1/4 and speed 3/4 (I think)
#
# To modify the "on" pattern, capture the packets from the K621-RGB app with wireshark and usbpcap,
# and find the last SET_REPORT request of a set of 4 with size 1068 bytes (1031 data bytes);
# the first 20 bytes are identical in all four packets.
#
# You may need the preceeding three packets as well, and possibly the preceeding SET_REPORT request
#(of size 42; I think this is a request for current settings) and GET_REPORT response (of size 290 bytes).
#
msg = [ 0x06, 0x03, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0xa5, 0x03, 0x03, 0x00, 0x00, 0x00, pattern, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x31, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x00, 0x31, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x07, 0x39, 0x5a, 0xa5, 0x00, 0x10, 0x07, 0x49, 0x07, 0x49, 0x07, 0x49, 0x07, 0x49, 0x07, 0x49, 0x07, 0x49, 0x07, 0x49, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0xa5, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]
try:
ret = kbd.ctrl_transfer(0x21, 0x09, 0x0306, 1, msg)
assert ret == len(msg)
# Prevent [Errno 32] Pipe error when running multiple commands, if we add any more ctrl_transfer()s after this one
time.sleep(0.25)
except usb.core.USBError as e:
print(e)
if e.errno == 16:
print("This issue needs more investigation... it may simply be previous invalid commands")
# The issue seems to be that the device configuration loses the report descriptors for interface 1 (the second interface)
# May need to reconfigure the device, or detach/reattach kernel driver, either for interface 0 or 0 _and_ 1
reset_driver(kbd)
elif e.errno == 2:
print("Try unplugging and replugging the keyboard...")
def reset_driver(kbd):
global debug
interfaces = list()
for cfg in kbd:
for intf in cfg:
interfaces.append(intf.bInterfaceNumber)
# Remove the driver
for intfNum in interfaces:
if kbd.is_kernel_driver_active(intfNum) is False:
print("kernel driver already detached")
else:
print("detaching kernel driver for interface {}".format(intfNum), flush=True)
try:
kbd.detach_kernel_driver(intfNum)
except usb.core.USBError as e:
print('Could not detach kernel driver: %s' % str(e), flush=True)
print(traceback.format_exc(), flush=True)
# Send a command (I think this is a short command to get the current keyboard state)
# This seems to reset things
msg = [ 0x05, 0x83, 0xc6, 0x00, 0x00, 0x00 ]
try:
print("trying to get kbd state")
ret = kbd.ctrl_transfer(0x21, 0x09, 0x0305, 1, msg)
#print(len(msg))
#print(ret)
assert ret == len(msg)
except:
pass
# Re-add driver
for intfNum in interfaces:
if kbd.is_kernel_driver_active(intfNum):
print("kernel driver already attached")
else:
print("reattaching kernel driver for interface {}".format(intfNum), flush=True)
try:
kbd.attach_kernel_driver(intfNum)
except usb.core.USBError as e:
print('Could not reattach kernel driver: %s' % str(e), flush=True)
if __name__ == '__main__':
main()