Merge pull request #11 from TheAquaSheep/lightsync_support
Added support for Lightsync variant of the G203 and setting the DPI.
This commit is contained in:
commit
372347c214
177
g203-led.py
177
g203-led.py
|
@ -1,6 +1,7 @@
|
||||||
#!env/bin/python
|
#!env/bin/python
|
||||||
|
|
||||||
# Logitech G203 Prodigy Mouse LED control
|
# Logitech G203 Prodigy Mouse LED control
|
||||||
|
# Altered to work with G203 LightSync instead by TheAquaSheep
|
||||||
# https://github.com/smasty/g203-led
|
# https://github.com/smasty/g203-led
|
||||||
# Author: Smasty, hello@smasty.net
|
# Author: Smasty, hello@smasty.net
|
||||||
# Licensed under the MIT license.
|
# Licensed under the MIT license.
|
||||||
|
@ -12,10 +13,13 @@ import re
|
||||||
import binascii
|
import binascii
|
||||||
|
|
||||||
g203_vendor_id = 0x046d
|
g203_vendor_id = 0x046d
|
||||||
g203_product_id = 0xc084
|
g203_prodigy_product_id = 0xc084
|
||||||
|
g203_lightsync_product_id = 0xc092
|
||||||
|
g203_product_id = g203_prodigy_product_id
|
||||||
|
|
||||||
default_rate = 10000
|
default_rate = 10000
|
||||||
default_brightness = 100
|
default_brightness = 100
|
||||||
|
default_direction = 'right'
|
||||||
|
|
||||||
|
|
||||||
dev = None
|
dev = None
|
||||||
|
@ -23,18 +27,33 @@ wIndex = None
|
||||||
|
|
||||||
|
|
||||||
def help():
|
def help():
|
||||||
print("""Logitech G203 Prodigy Mouse LED control
|
print("""Logitech G203 Prodigy / Lightsync Mouse LED control
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
\tg203-led solid {color} - Solid color mode
|
\tg203-led [lightsync] solid {color} - Solid color mode
|
||||||
\tg203-led cycle [{rate} [{brightness}]] - Cycle through all colors
|
\tg203-led [lightsync] cycle [{rate} [{brightness}]] - Cycle through all colors
|
||||||
\tg203-led breathe {color} [{rate} [{brightness}]] - Single color breathing
|
\tg203-led [lightsync] breathe {color} [{rate} [{brightness}]] - Single color breathing
|
||||||
\tg203-led intro {on|off} - Enable/disable startup effect
|
\tg203-led [lightsync] intro {on|off} - Enable/disable startup effect
|
||||||
|
\tg203-led [lightsync] dpi {dpi} - Set mouse dpi
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
\tColor: RRGGBB (RGB hex value)
|
\tColor: RRGGBB (RGB hex value)
|
||||||
\tRate: 100-60000 (Number of milliseconds. Default: 10000ms)
|
\tRate: 1000-60000 (Number of milliseconds. Default: 10000ms)
|
||||||
\tBrightness: 0-100 (Percentage. Default: 100%)""")
|
\tBrightness: 1-100 (Percentage. Default: 100%)
|
||||||
|
\tDPI: 200-8000 (Prodigy), 50-8000 (Lightsync)
|
||||||
|
|
||||||
|
Assumes Prodigy by default unless "lightsync" is given as the first command argument.
|
||||||
|
This ensures backward compatibility.
|
||||||
|
Lightsync additional features:
|
||||||
|
\tg203-led lightsync triple {color color color} - Sets all 3 colors from left to right.
|
||||||
|
\tg203-led lightsync wave {rate} [{brightness} [{direction}]] - Like cycle but appears to move right or left.
|
||||||
|
\tg203-led lightsync blend [{rate} [{brightness}]] - Like breathe with the side colors changing after some delay.
|
||||||
|
|
||||||
|
\tDirection is either "left" or "right". Default: right).
|
||||||
|
|
||||||
|
Note that the lightsync setting will not persist.
|
||||||
|
There is onboard memory for persistence but it is not used by this script.""")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -42,7 +61,7 @@ def main():
|
||||||
help()
|
help()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
args = sys.argv + [None] * (5 - len(sys.argv))
|
args = sys.argv + [None] * (6 - len(sys.argv))
|
||||||
|
|
||||||
mode = args[1]
|
mode = args[1]
|
||||||
if mode == 'solid':
|
if mode == 'solid':
|
||||||
|
@ -54,9 +73,45 @@ def main():
|
||||||
process_color(args[2]),
|
process_color(args[2]),
|
||||||
process_rate(args[3]),
|
process_rate(args[3]),
|
||||||
process_brightness(args[4])
|
process_brightness(args[4])
|
||||||
)
|
)
|
||||||
elif mode == 'intro':
|
elif mode == 'intro':
|
||||||
set_intro_effect(args[2])
|
set_intro_effect(args[2])
|
||||||
|
elif mode == 'dpi':
|
||||||
|
set_dpi(process_dpi(args[2]))
|
||||||
|
elif mode == 'lightsync':
|
||||||
|
global g203_product_id
|
||||||
|
g203_product_id = g203_lightsync_product_id
|
||||||
|
mode = args[2]
|
||||||
|
if mode == 'solid':
|
||||||
|
set_ls_solid(process_color(args[3]))
|
||||||
|
elif mode == 'cycle':
|
||||||
|
set_ls_cycle(process_rate(args[3]), process_brightness(args[4]))
|
||||||
|
elif mode == 'breathe':
|
||||||
|
set_ls_breathe(
|
||||||
|
process_color(args[3]),
|
||||||
|
process_rate(args[4]),
|
||||||
|
process_brightness(args[5])
|
||||||
|
)
|
||||||
|
elif mode == 'intro':
|
||||||
|
set_ls_intro(args[3])
|
||||||
|
elif mode == 'dpi':
|
||||||
|
set_dpi(process_dpi(args[3]))
|
||||||
|
elif mode == 'triple':
|
||||||
|
set_ls_triple(
|
||||||
|
process_color(args[3]),
|
||||||
|
process_color(args[4]),
|
||||||
|
process_color(args[5])
|
||||||
|
)
|
||||||
|
elif mode == 'wave':
|
||||||
|
set_ls_wave(
|
||||||
|
process_rate(args[3]),
|
||||||
|
process_brightness(args[4]),
|
||||||
|
process_direction(args[5])
|
||||||
|
)
|
||||||
|
elif mode == 'blend':
|
||||||
|
set_ls_blend(process_rate(args[3]), process_brightness(args[4]))
|
||||||
|
else:
|
||||||
|
print_error('Unknown lightsync mode.')
|
||||||
else:
|
else:
|
||||||
print_error('Unknown mode.')
|
print_error('Unknown mode.')
|
||||||
|
|
||||||
|
@ -68,7 +123,7 @@ def print_error(msg):
|
||||||
|
|
||||||
def process_color(color):
|
def process_color(color):
|
||||||
if not color:
|
if not color:
|
||||||
print_error('No color specifed.')
|
print_error('No color specified.')
|
||||||
if color[0] == '#':
|
if color[0] == '#':
|
||||||
color = color[1:]
|
color = color[1:]
|
||||||
if not re.match('^[0-9a-fA-F]{6}$', color):
|
if not re.match('^[0-9a-fA-F]{6}$', color):
|
||||||
|
@ -79,7 +134,7 @@ def process_rate(rate):
|
||||||
if not rate:
|
if not rate:
|
||||||
rate = default_rate
|
rate = default_rate
|
||||||
try:
|
try:
|
||||||
return '{:04x}'.format(max(100, min(65535, int(rate))))
|
return '{:04x}'.format(max(1000, min(65535, int(rate))))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print_error('Invalid rate specified.')
|
print_error('Invalid rate specified.')
|
||||||
|
|
||||||
|
@ -91,6 +146,26 @@ def process_brightness(brightness):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print_error('Invalid brightness specified.')
|
print_error('Invalid brightness specified.')
|
||||||
|
|
||||||
|
def process_direction(direction):
|
||||||
|
if not direction:
|
||||||
|
direction = default_direction
|
||||||
|
else:
|
||||||
|
if not (direction == 'left' or direction == 'right'):
|
||||||
|
print_error('Invalid direction specified.')
|
||||||
|
return direction
|
||||||
|
|
||||||
|
def process_dpi(dpi):
|
||||||
|
if not dpi:
|
||||||
|
print_error('No DPI specified.')
|
||||||
|
lower_lim = 200
|
||||||
|
if g203_product_id == g203_lightsync_product_id:
|
||||||
|
lower_lim = 50
|
||||||
|
try:
|
||||||
|
return '{:04x}'.format(max(lower_lim, min(8000, int(dpi))))
|
||||||
|
except ValueError:
|
||||||
|
print_error('Invalid DPI specified.')
|
||||||
|
return dpi
|
||||||
|
|
||||||
|
|
||||||
def set_led_solid(color):
|
def set_led_solid(color):
|
||||||
return set_led('01', color + '0000000000')
|
return set_led('01', color + '0000000000')
|
||||||
|
@ -99,7 +174,6 @@ def set_led_breathe(color, rate, brightness):
|
||||||
return set_led('03', color + rate + '00' + brightness + '00')
|
return set_led('03', color + rate + '00' + brightness + '00')
|
||||||
|
|
||||||
def set_led_cycle(rate, brightness):
|
def set_led_cycle(rate, brightness):
|
||||||
print(rate, brightness)
|
|
||||||
return set_led('02', '0000000000' + rate + brightness)
|
return set_led('02', '0000000000' + rate + brightness)
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,10 +196,83 @@ def set_intro_effect(arg):
|
||||||
|
|
||||||
send_command('11ff0e5b0001'+toggle+'00000000000000000000000000')
|
send_command('11ff0e5b0001'+toggle+'00000000000000000000000000')
|
||||||
|
|
||||||
|
def set_dpi(dpi):
|
||||||
|
cmd = '10ff0a3b00{}'.format(dpi)
|
||||||
|
send_command(cmd, disable_ls_onboard_memory=False)
|
||||||
|
|
||||||
def send_command(data):
|
def set_ls_solid(color):
|
||||||
|
cmd = '11ff0e1b0001{}0000000000000001000000'.format(color)
|
||||||
|
send_command(cmd, disable_ls_onboard_memory=True)
|
||||||
|
|
||||||
|
def set_ls_cycle(rate, brightness):
|
||||||
|
cmd = '11ff0e1b00020000000000{}{}000001000000'.format(rate, brightness)
|
||||||
|
send_command(cmd, disable_ls_onboard_memory=True)
|
||||||
|
|
||||||
|
def set_ls_breathe(color, rate, brightness):
|
||||||
|
cmd = '11ff0e1b0004{}{}00{}00000001000000'.format(color, rate, brightness)
|
||||||
|
send_command(cmd, disable_ls_onboard_memory=True)
|
||||||
|
|
||||||
|
def set_ls_intro(arg):
|
||||||
|
if arg == 'on' or arg == '1':
|
||||||
|
toggle = '01'
|
||||||
|
elif arg == 'off' or arg == '0':
|
||||||
|
toggle = '02'
|
||||||
|
else:
|
||||||
|
print_error('Invalid value.')
|
||||||
|
cmd = '11ff0e3b010001{}000000000000000000000000'.format(toggle)
|
||||||
|
send_command(cmd, disable_ls_onboard_memory=False)
|
||||||
|
|
||||||
|
def set_ls_triple(color_left, color_middle, color_right):
|
||||||
|
cmd = '11ff121b01{}02{}03{}00000000'.format(color_left, color_middle, color_right)
|
||||||
|
send_command(cmd, disable_ls_onboard_memory=False)
|
||||||
|
|
||||||
|
def set_ls_wave(rate, brightness, direction):
|
||||||
|
rate_U8 = rate[0:2]
|
||||||
|
rate_L8 = rate[2:4]
|
||||||
|
state = '01'
|
||||||
|
if direction == 'left':
|
||||||
|
state = '06'
|
||||||
|
cmd = '11ff0e1b0003000000000000{}{}{}{}01000000'.format(rate_L8, state, brightness, rate_U8)
|
||||||
|
send_command(cmd, disable_ls_onboard_memory=True)
|
||||||
|
|
||||||
|
def set_ls_blend(rate, brightness):
|
||||||
|
rate_U8 = rate[0:2]
|
||||||
|
rate_L8 = rate[2:4]
|
||||||
|
cmd = '11ff0e1b0006000000000000{}{}{}0001000000'.format(rate_L8, rate_U8, brightness)
|
||||||
|
send_command(cmd, disable_ls_onboard_memory=True)
|
||||||
|
|
||||||
|
def clear_ls_buffer(): #tested on lightsync but may also affect prodigy
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
dev.read(0x82, 20)
|
||||||
|
except usb.core.USBError:
|
||||||
|
return
|
||||||
|
|
||||||
|
def send_command(data, disable_ls_onboard_memory=False, clear_ls_buf=False):
|
||||||
attach_mouse()
|
attach_mouse()
|
||||||
dev.ctrl_transfer(0x21, 0x09, 0x0211, wIndex, binascii.unhexlify(data))
|
|
||||||
|
if clear_ls_buf: # if this is ever needed in practise the default can be changed above.
|
||||||
|
clear_ls_buffer()
|
||||||
|
|
||||||
|
if disable_ls_onboard_memory:
|
||||||
|
dev.ctrl_transfer(0x21, 0x09, 0x210, wIndex, binascii.unhexlify('10ff0e5b010305'))
|
||||||
|
dev.read(0x82, 20)
|
||||||
|
|
||||||
|
wValue=0x211
|
||||||
|
if len(data) == 14:
|
||||||
|
wValue = 0x210
|
||||||
|
|
||||||
|
dev.ctrl_transfer(0x21, 0x09, wValue, wIndex, binascii.unhexlify(data))
|
||||||
|
dev.read(0x82, 20)
|
||||||
|
|
||||||
|
if data[0:8] == '11ff121b':
|
||||||
|
apply_triple_cmd = '11ff127b00000000000000000000000000000000'
|
||||||
|
dev.ctrl_transfer(0x21, 0x09, 0x211, wIndex, binascii.unhexlify(apply_triple_cmd))
|
||||||
|
dev.read(0x82, 20)
|
||||||
|
|
||||||
|
if clear_ls_buf: # done again to ensure the buffer did not fill between the last clear and cmd
|
||||||
|
clear_ls_buffer()
|
||||||
|
|
||||||
detach_mouse()
|
detach_mouse()
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user