mirror of
https://github.com/libratbag/libratbag.git
synced 2026-11-07 23:23:52 +01:00
1547 lines
54 KiB
Python
Executable File
1547 lines
54 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# vim: set expandtab shiftwidth=4 tabstop=4:
|
|
#
|
|
# Copyright 2016 Red Hat, Inc.
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
# copy of this software and associated documentation files (the "Software"),
|
|
# to deal in the Software without restriction, including without limitation
|
|
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
# and/or sell copies of the Software, and to permit persons to whom the
|
|
# Software is furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice (including the next
|
|
# paragraph) shall be included in all copies or substantial portions of the
|
|
# Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
# DEALINGS IN THE SOFTWARE.
|
|
|
|
import evdev
|
|
import sys
|
|
import argparse
|
|
|
|
from ratbagd import Ratbagd, RatbagdDevice, RatbagdProfile, RatbagdMacro, RatbagdResolution, RatbagdButton, RatbagdLed, RatbagdDBusUnavailable, RatbagError, RatbagErrorCode, RatbagErrorCapability # NOQA
|
|
|
|
button_specials_strmap = {
|
|
RatbagdButton.ACTION_SPECIAL_UNKNOWN: "unknown",
|
|
RatbagdButton.ACTION_SPECIAL_DOUBLECLICK: "doubleclick",
|
|
RatbagdButton.ACTION_SPECIAL_WHEEL_LEFT: "wheel-left",
|
|
RatbagdButton.ACTION_SPECIAL_WHEEL_RIGHT: "wheel-right",
|
|
RatbagdButton.ACTION_SPECIAL_WHEEL_UP: "wheel-up",
|
|
RatbagdButton.ACTION_SPECIAL_WHEEL_DOWN: "wheel-down",
|
|
RatbagdButton.ACTION_SPECIAL_RATCHET_MODE_SWITCH: "ratchet-mode-switch",
|
|
RatbagdButton.ACTION_SPECIAL_RESOLUTION_CYCLE_UP: "resolution-cycle-up",
|
|
RatbagdButton.ACTION_SPECIAL_RESOLUTION_CYCLE_DOWN: "resolution-cycle-down",
|
|
RatbagdButton.ACTION_SPECIAL_RESOLUTION_UP: "resolution-up",
|
|
RatbagdButton.ACTION_SPECIAL_RESOLUTION_DOWN: "resolution-down",
|
|
RatbagdButton.ACTION_SPECIAL_RESOLUTION_ALTERNATE: "resolution-alternate",
|
|
RatbagdButton.ACTION_SPECIAL_RESOLUTION_DEFAULT: "resolution-default",
|
|
RatbagdButton.ACTION_SPECIAL_PROFILE_CYCLE_UP: "profile-cycle-up",
|
|
RatbagdButton.ACTION_SPECIAL_PROFILE_CYCLE_DOWN: "profile-cycle-down",
|
|
RatbagdButton.ACTION_SPECIAL_PROFILE_UP: "profile-up",
|
|
RatbagdButton.ACTION_SPECIAL_PROFILE_DOWN: "profile-down",
|
|
RatbagdButton.ACTION_SPECIAL_SECOND_MODE: "second-mode",
|
|
RatbagdButton.ACTION_SPECIAL_BATTERY_LEVEL: "battery-level",
|
|
}
|
|
for k, v in [(v, k) for k, v in button_specials_strmap.items()]:
|
|
button_specials_strmap[k] = v
|
|
|
|
|
|
def list_devices(r, args):
|
|
if not r.devices:
|
|
print("No devices available.")
|
|
|
|
for d in r.devices:
|
|
print("{:10s} {:32s}".format(d.id + ":", d.name))
|
|
|
|
|
|
def find_device(r, args):
|
|
dev = r[args.device]
|
|
if dev is None:
|
|
print("Unable to find device {}".format(args.device))
|
|
sys.exit(1)
|
|
return dev
|
|
|
|
|
|
def find_profile(r, args):
|
|
d = find_device(r, args)
|
|
try:
|
|
p = d.profiles[args.profile_n]
|
|
except IndexError:
|
|
print("Invalid profile index {}".format(args.profile_n))
|
|
sys.exit(1)
|
|
except AttributeError:
|
|
p = d.active_profile
|
|
return p, d
|
|
|
|
|
|
def find_resolution(r, args):
|
|
p, d = find_profile(r, args)
|
|
try:
|
|
r = p.resolutions[args.resolution_n]
|
|
except IndexError:
|
|
print("Invalid resolution index {}".format(args.resolution_n))
|
|
sys.exit(1)
|
|
except AttributeError:
|
|
r = p.active_resolution
|
|
return r, p, d
|
|
|
|
|
|
def find_button(r, args):
|
|
p, d = find_profile(r, args)
|
|
try:
|
|
b = p.buttons[args.button_n]
|
|
except IndexError:
|
|
print("Invalid button index {}".format(args.button_n))
|
|
sys.exit(1)
|
|
return b, p, d
|
|
|
|
|
|
def find_led(r, args):
|
|
p, d = find_profile(r, args)
|
|
try:
|
|
l = p.leds[args.led_n]
|
|
except IndexError:
|
|
print("Invalid LED index {}".format(args.led_n))
|
|
sys.exit(1)
|
|
return l, p, d
|
|
|
|
|
|
def print_led(d, p, l, level):
|
|
leds = {
|
|
RatbagdLed.MODE_BREATHING: "breathing",
|
|
RatbagdLed.MODE_CYCLE: "cycle",
|
|
RatbagdLed.MODE_OFF: "off",
|
|
RatbagdLed.MODE_ON: "on",
|
|
}
|
|
types = {
|
|
RatbagdLed.TYPE_LOGO: "logo",
|
|
RatbagdLed.TYPE_SIDE: "side",
|
|
RatbagdLed.TYPE_BATTERY: "battery",
|
|
RatbagdLed.TYPE_DPI: "dpi",
|
|
RatbagdLed.TYPE_WHEEL: "wheel",
|
|
}
|
|
depths = {
|
|
RatbagdLed.COLORDEPTH_MONOCHROME: "monochrome",
|
|
RatbagdLed.COLORDEPTH_RGB_888: "rgb",
|
|
RatbagdLed.COLORDEPTH_RGB_111: "rgb111",
|
|
}
|
|
if l.mode == RatbagdLed.MODE_OFF:
|
|
print(" " * level + "LED: {} type: {}, depth: {}, mode: {}".format(l.index,
|
|
types[l.type],
|
|
depths[l.colordepth],
|
|
leds[l.mode]))
|
|
elif l.mode == RatbagdLed.MODE_ON:
|
|
print(" " * level + "LED: {} type: {}, depth: {}, mode: {}, color: {:02x}{:02x}{:02x}".format(l.index,
|
|
types[l.type],
|
|
depths[l.colordepth],
|
|
leds[l.mode],
|
|
l.color[0],
|
|
l.color[1],
|
|
l.color[2]))
|
|
elif l.mode == RatbagdLed.MODE_CYCLE:
|
|
print(" " * level + "LED: {} type: {}, depth: {}, mode: {}, duration: {}, brightness: {}".format(l.index,
|
|
types[l.type],
|
|
depths[l.colordepth],
|
|
leds[l.mode],
|
|
l.effect_duration,
|
|
l.brightness))
|
|
elif l.mode == RatbagdLed.MODE_BREATHING:
|
|
print(" " * level + "LED: {} type: {}, depth: {}, mode: {}, color: {:02x}{:02x}{:02x}, duration: {}, brightness: {}".format(l.index,
|
|
types[l.type],
|
|
depths[l.colordepth],
|
|
leds[l.mode],
|
|
l.color[0],
|
|
l.color[1],
|
|
l.color[2],
|
|
l.effect_duration,
|
|
l.brightness))
|
|
|
|
|
|
def print_led_caps(d, p, l, level):
|
|
leds = {
|
|
RatbagdLed.MODE_BREATHING: "breathing",
|
|
RatbagdLed.MODE_CYCLE: "cycle",
|
|
RatbagdLed.MODE_OFF: "off",
|
|
RatbagdLed.MODE_ON: "on",
|
|
}
|
|
supported = sorted([v for k, v in leds.items() if k in l.modes])
|
|
print(" " * level + "Modes: {}".format(", ".join(supported)))
|
|
|
|
|
|
def print_button(d, p, b, level):
|
|
types = {
|
|
RatbagdButton.TYPE_UNKNOWN: "unknown",
|
|
RatbagdButton.TYPE_LEFT: "left",
|
|
RatbagdButton.TYPE_MIDDLE: "middle",
|
|
RatbagdButton.TYPE_RIGHT: "right",
|
|
RatbagdButton.TYPE_THUMB: "thumb",
|
|
RatbagdButton.TYPE_THUMB2: "thumb2",
|
|
RatbagdButton.TYPE_THUMB3: "thumb3",
|
|
RatbagdButton.TYPE_THUMB4: "thumb4",
|
|
RatbagdButton.TYPE_WHEEL_LEFT: "wheel-left",
|
|
RatbagdButton.TYPE_WHEEL_RIGHT: "wheel-right",
|
|
RatbagdButton.TYPE_WHEEL_CLICK: "wheel-click",
|
|
RatbagdButton.TYPE_WHEEL_UP: "wheel-up",
|
|
RatbagdButton.TYPE_WHEEL_DOWN: "wheel-down",
|
|
RatbagdButton.TYPE_WHEEL_RATCHET_MODE_SHIFT: "wheel-ratchet-mode-shift",
|
|
RatbagdButton.TYPE_EXTRA: "extra",
|
|
RatbagdButton.TYPE_SIDE: "side",
|
|
RatbagdButton.TYPE_PINKIE: "pinkie",
|
|
RatbagdButton.TYPE_PINKIE2: "pinkie2",
|
|
RatbagdButton.TYPE_RESOLUTION_CYCLE_UP: "resolution-cycle-up",
|
|
RatbagdButton.TYPE_RESOLUTION_UP: "resolution-up",
|
|
RatbagdButton.TYPE_RESOLUTION_DOWN: "resolution-down",
|
|
RatbagdButton.TYPE_PROFILE_CYCLE_UP: "profile-cycle-up",
|
|
RatbagdButton.TYPE_PROFILE_UP: "profile-up",
|
|
RatbagdButton.TYPE_PROFILE_DOWN: "profile-down",
|
|
}
|
|
t = types[b.type]
|
|
header = " " * level + "Button: {} type {} is mapped to ".format(b.index, t)
|
|
|
|
if b.action_type == RatbagdButton.ACTION_TYPE_BUTTON:
|
|
print("{}'button {}'".format(header, b.mapping))
|
|
elif b.action_type == RatbagdButton.ACTION_TYPE_SPECIAL:
|
|
print("{}'{}'".format(header, button_specials_strmap[b.special]))
|
|
elif b.action_type == RatbagdButton.ACTION_TYPE_MACRO:
|
|
print("{}macro '{}'".format(header, str(b.macro)))
|
|
elif b.action_type == RatbagdButton.ACTION_TYPE_NONE:
|
|
print("{}none".format(header))
|
|
else:
|
|
print("{}UNKNOWN".format(header))
|
|
|
|
|
|
def print_resolution(d, p, r, level):
|
|
if r.resolution == (0, 0):
|
|
print(" " * level + "{}: <disabled>".format(r.index))
|
|
return
|
|
if RatbagdResolution.CAP_SEPARATE_XY_RESOLUTION in r.capabilities:
|
|
dpi = "{}x{}".format(r.resolution[0], r.resolution[1])
|
|
else:
|
|
dpi = "{}".format(r.resolution[0])
|
|
|
|
print(" " * level + "{}: {}dpi @ {}Hz{}{}".format(r.index,
|
|
dpi,
|
|
r.report_rate,
|
|
" (active)" if r.is_active else "",
|
|
" (default)" if r.is_default else "",
|
|
))
|
|
|
|
|
|
def print_profile(d, p, level):
|
|
print(" " * (level - 2) + "Profile {}:{}{}".format(p.index,
|
|
" (disabled)" if not p.enabled else "",
|
|
" (active)" if p.is_active else ""))
|
|
if p.enabled:
|
|
print(" " * level + "Name : '{}'".format(p.name))
|
|
print(" " * level + "Resolutions:")
|
|
for r in p.resolutions:
|
|
print_resolution(d, p, r, level + 2)
|
|
for b in p.buttons:
|
|
print_button(d, p, b, level)
|
|
for l in p.leds:
|
|
print_led(d, p, l, level)
|
|
|
|
|
|
def print_device(d, level):
|
|
p = d.profiles[0] # there should be always one
|
|
|
|
caps = {RatbagdDevice.CAP_RESOLUTION: "resolution",
|
|
RatbagdDevice.CAP_PROFILE: "profile",
|
|
RatbagdDevice.CAP_BUTTON: "button",
|
|
RatbagdDevice.CAP_SWITCHABLE_RESOLUTION: "switchable-resolution",
|
|
RatbagdDevice.CAP_SWITCHABLE_PROFILE: "switchable-profile",
|
|
RatbagdDevice.CAP_BUTTON_KEY: "button-keys",
|
|
RatbagdDevice.CAP_BUTTON_MACROS: "button-macros",
|
|
RatbagdDevice.CAP_DEFAULT_PROFILE: "default-profile",
|
|
RatbagdDevice.CAP_QUERY_CONFIGURATION: "query-configuration",
|
|
RatbagdDevice.CAP_DISABLE_PROFILE: "disable-profile",
|
|
RatbagdDevice.CAP_LED: "led"}
|
|
capabilities = [caps[c] for c in d.capabilities]
|
|
|
|
print(" " * level + "{} - {}".format(d.id, d.name))
|
|
print(" " * level + " SVG: {}".format(d.get_svg('gnome')))
|
|
print(" " * level + " Capabilities: {}".format(", ".join(capabilities)))
|
|
print(" " * level + " Number of Buttons: {}".format(len(p.buttons)))
|
|
print(" " * level + " Number of Leds: {}".format(len(p.leds)))
|
|
print(" " * level + "Number of Profiles: {}".format(len(d.profiles)))
|
|
for p in d.profiles:
|
|
print_profile(d, p, level + 2)
|
|
|
|
|
|
def show_device(r, args):
|
|
d = find_device(r, args)
|
|
print_device(d, 0)
|
|
|
|
|
|
def show_profile(r, args):
|
|
p, d = find_profile(r, args)
|
|
print("Profile {} on {} ({})".format(args.profile, d.id, d.name))
|
|
print_profile(d, p, 0)
|
|
|
|
|
|
def show_resolution(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
print("Resolution {} on Profile {} on {} ({})".format(args.resolution,
|
|
args.profile,
|
|
d.id,
|
|
d.name))
|
|
print_resolution(d, p, r, 0)
|
|
caps = {RatbagdResolution.CAP_INDIVIDUAL_REPORT_RATE: "individual-report-rate",
|
|
RatbagdResolution.CAP_SEPARATE_XY_RESOLUTION: "separate-xy-resolution"}
|
|
capabilities = [caps[c] for c in r.capabilities]
|
|
print(" Capabilities: {}".format(", ".join(capabilities)))
|
|
|
|
|
|
def show_button(r, args):
|
|
b, p, d = find_button(r, args)
|
|
print("Button {} on Profile {} on {} ({})".format(args.button,
|
|
args.profile,
|
|
d.id,
|
|
d.name))
|
|
print_button(d, p, b, 0)
|
|
|
|
|
|
def func_led_get(r, args):
|
|
l, p, d = find_led(r, args)
|
|
print_led(d, p, l, 0)
|
|
|
|
|
|
def func_led_caps(r, args):
|
|
l, p, d = find_led(r, args)
|
|
print_led_caps(d, p, l, 0)
|
|
|
|
|
|
def func_led_set(r, args):
|
|
l, p, d = find_led(r, args)
|
|
try:
|
|
mode = args.mode
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
leds = {
|
|
"breathing": RatbagdLed.MODE_BREATHING,
|
|
"cycle": RatbagdLed.MODE_CYCLE,
|
|
"off": RatbagdLed.MODE_OFF,
|
|
"on": RatbagdLed.MODE_ON,
|
|
}
|
|
l.mode = leds[mode]
|
|
try:
|
|
color = args.color
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
l.color = color
|
|
try:
|
|
duration = args.duration
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
l.effect_duration = duration
|
|
try:
|
|
brightness = args.brightness
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
l.brightness = brightness
|
|
commit(d, args)
|
|
|
|
|
|
def func_led_get_all(r, args):
|
|
p, d = find_profile(r, args)
|
|
for l in p.leds:
|
|
print_led(d, p, l, 0)
|
|
|
|
|
|
def func_button_get(r, args):
|
|
b, p, d = find_button(r, args)
|
|
print_button(b, p, b, 0)
|
|
|
|
|
|
def func_button_action_set_button(r, args):
|
|
b, p, d = find_button(r, args)
|
|
b.mapping = args.target_button
|
|
commit(d, args)
|
|
|
|
|
|
def func_button_action_set_special(r, args):
|
|
b, p, d = find_button(r, args)
|
|
try:
|
|
special = args.target_special
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
b.special = button_specials_strmap[special]
|
|
commit(d, args)
|
|
|
|
|
|
def func_button_action_set_macro(r, args):
|
|
b, p, d = find_button(r, args)
|
|
macro_keys = args.target_macro
|
|
macro = RatbagdMacro()
|
|
for s in macro_keys:
|
|
is_press = True
|
|
is_release = True
|
|
is_timeout = False
|
|
|
|
s = s.upper()
|
|
if s[0] == 'T':
|
|
is_timeout = True
|
|
is_press = False
|
|
is_release = False
|
|
elif s[0] == '+':
|
|
is_release = False
|
|
s = s[1:]
|
|
elif s[0] == '-':
|
|
is_press = False
|
|
s = s[1:]
|
|
|
|
if is_timeout:
|
|
t = int(s[1:])
|
|
macro.append(RatbagdButton.MACRO_WAIT, t)
|
|
else:
|
|
if not s.startswith("KEY_") and not s.startswith("BTN_"):
|
|
msg = "Don't know how to convert {}".format(s)
|
|
raise argparse.ArgumentTypeError(msg)
|
|
|
|
code = evdev.ecodes.ecodes[s]
|
|
if is_press:
|
|
macro.append(RatbagdButton.MACRO_KEY_PRESS, code)
|
|
if is_release:
|
|
macro.append(RatbagdButton.MACRO_KEY_RELEASE, code)
|
|
|
|
b.macro = macro
|
|
commit(d, args)
|
|
|
|
|
|
def func_button_count(r, args):
|
|
p, d = find_profile(r, args)
|
|
print(len(p.buttons))
|
|
|
|
|
|
def func_dpi_get(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
if RatbagdResolution.CAP_SEPARATE_XY_RESOLUTION in r.capabilities:
|
|
print("{}x{}dpi".format(r.resolution[0], r.resolution[1]))
|
|
else:
|
|
print("{}dpi".format(r.resolution[0]))
|
|
|
|
|
|
def func_dpi_get_all(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
dpis = r.resolutions
|
|
print(" ".join([str(x) for x in dpis]))
|
|
|
|
|
|
def func_dpi_set(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
r.resolution = (args.dpi_n[0], args.dpi_n[1])
|
|
commit(d, args)
|
|
|
|
|
|
def func_report_rate_get(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
print(r.report_rate)
|
|
|
|
|
|
def func_report_rate_get_all(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
rates = r.report_rates
|
|
print(" ".join([str(x) for x in rates]))
|
|
|
|
|
|
def func_report_rate_set(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
r.report_rate = args.rate_n
|
|
|
|
|
|
def func_resolution_get(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
print_resolution(d, p, r, 0)
|
|
|
|
|
|
def func_resolution_active_get(r, args):
|
|
p, d = find_profile(r, args)
|
|
print(p.active_resolution.index)
|
|
|
|
|
|
def func_resolution_active_set(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
r.set_active()
|
|
commit(d, args)
|
|
|
|
|
|
def func_resolution_default_get(r, args):
|
|
p, d = find_profile(r, args)
|
|
for r in p.resolutions:
|
|
if r.is_default:
|
|
break
|
|
else:
|
|
r = None
|
|
print(r.index)
|
|
|
|
|
|
def func_default_resolution_set(r, args):
|
|
# FIXME: capabilities check?
|
|
r, p, d = find_resolution(r, args)
|
|
r.set_default()
|
|
commit(d, args)
|
|
|
|
|
|
def func_profile_get(r, args):
|
|
p, d = find_profile(r, args)
|
|
print_profile(d, p, 0)
|
|
|
|
|
|
def func_profile_name_get(r, args):
|
|
p, d = find_profile(r, args)
|
|
print(p.name)
|
|
|
|
|
|
def func_profile_name_set(r, args):
|
|
p, d = find_profile(r, args)
|
|
if RatbagdProfile.CAP_WRITABLE_NAME not in p.capabilities:
|
|
raise RatbagErrorCapability("assigning a profile name is not supported on this profile")
|
|
p.name = args.name
|
|
commit(d, args)
|
|
|
|
|
|
def func_profile_active_get(r, args):
|
|
d = find_device(r, args)
|
|
print(d.active_profile.index)
|
|
|
|
|
|
def func_profile_active_set(r, args):
|
|
p, d = find_profile(r, args)
|
|
p.set_active()
|
|
commit(d, args)
|
|
|
|
|
|
def func_profile_enable(r, args):
|
|
p, d = find_profile(r, args)
|
|
p.enabled = True
|
|
commit(d, args)
|
|
|
|
|
|
def func_profile_disable(r, args):
|
|
p, d = find_profile(r, args)
|
|
p.enabled = False
|
|
commit(d, args)
|
|
|
|
|
|
def func_device_name_get(r, args):
|
|
d = find_device(r, args)
|
|
print(d.name)
|
|
|
|
|
|
################################################################################
|
|
# these are definitions to be reused in the dict that defines our language
|
|
|
|
# key elements
|
|
"""the type of the element (see 'types' below)"""
|
|
of_type = 'type'
|
|
"""the name of the element, it'll be the one matching the args on the CLI"""
|
|
name = 'name'
|
|
"""the group to logically associate commands while printing the help"""
|
|
group = 'group'
|
|
"""list of positional arguments for the given command"""
|
|
pos_args = 'pos_args'
|
|
"""a tag that we can refer latrer in an element of type 'link'"""
|
|
tag = 'tag'
|
|
"""the element pointed to in an element of type 'link'"""
|
|
dest = 'dest'
|
|
"""the function to associate to the switch or command"""
|
|
func = 'func'
|
|
"""this is a particular command that is an integer, but not an terminating argument.
|
|
example:
|
|
profile active get
|
|
profile **2** button 3 get
|
|
- "profile" needs to be a switch
|
|
- "2" needs to be translated as a N_access, given it is a requirement to be able to call 'button'
|
|
"""
|
|
N_access = 'N_access'
|
|
|
|
# argparse.add_argument parameters (forwarded as such)
|
|
"""'type' of the argument"""
|
|
arg_type = 'arg_type'
|
|
"""'metavar' of the argument"""
|
|
metavar = 'metavar'
|
|
"""'help' of the argument"""
|
|
help_str = 'help'
|
|
"""'nargs' of the argument"""
|
|
nargs = 'nargs'
|
|
"""'choices' of the argument"""
|
|
choices = 'choices'
|
|
|
|
# types
|
|
"""an option to interprete as a command (example 'list', 'info')"""
|
|
command = 'command'
|
|
"""an argument that is required for the given command arguments are leaf nodes
|
|
and can not have children
|
|
"""
|
|
argument = 'argument'
|
|
"""provides a list of choice of commands for instance, a switch of [A, B] means
|
|
we can have A or B only when parsing the command line
|
|
"""
|
|
switch = 'switch'
|
|
"""same as list, except we can loop inside the list for instance, a set of
|
|
[A, B] means we can have A and B (and A, ...) one after the other, no matter
|
|
the order
|
|
"""
|
|
set = 'set'
|
|
"""a reference to any other element in the tree marked with a tag"""
|
|
link = 'link'
|
|
|
|
################################################################################
|
|
|
|
|
|
def commit_cb(status):
|
|
if not status == RatbagErrorCode.RATBAG_SUCCESS:
|
|
print("Could not commit changes to the device")
|
|
|
|
|
|
def commit(device, args):
|
|
if args.nocommit:
|
|
return
|
|
device.commit(commit_cb)
|
|
|
|
|
|
def color(string):
|
|
try:
|
|
int_value = int(string, 16)
|
|
except ValueError:
|
|
msg = "%r is not a color in hex format" % string
|
|
raise argparse.ArgumentTypeError(msg)
|
|
r = (int_value >> 16) & 0xff
|
|
g = (int_value >> 8) & 0xff
|
|
b = (int_value >> 0) & 0xff
|
|
return (r, g, b)
|
|
|
|
|
|
def u8(string):
|
|
int_value = int(string)
|
|
msg = "%r is not a single byte" % string
|
|
if int_value < 0 or int_value > 255:
|
|
raise argparse.ArgumentTypeError(msg)
|
|
return int_value
|
|
|
|
|
|
def dpi(string):
|
|
try:
|
|
int_value = int(string)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
return (int_value, int_value)
|
|
if string.endswith("dpi"):
|
|
string = string[:-3]
|
|
x, y = string.split("x")
|
|
try:
|
|
int_x = int(x)
|
|
int_y = int(y)
|
|
except ValueError:
|
|
raise argparse.ArgumentTypeError("%r is not a valid dpi" % string)
|
|
else:
|
|
return (int_x, int_y)
|
|
|
|
# note: 'eventX' is assumed at the end of each command
|
|
parser_def = [
|
|
{
|
|
of_type: command,
|
|
name: 'list',
|
|
help_str: 'List supported devices (does not take a device argument)',
|
|
func: list_devices,
|
|
group: 'General',
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'info',
|
|
help_str: 'Show device information',
|
|
func: show_device,
|
|
group: 'Device',
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'name',
|
|
help_str: 'Returns the device name',
|
|
func: func_device_name_get,
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'profile',
|
|
help_str: 'Access profile information',
|
|
tag: 'profile',
|
|
group: 'Profile',
|
|
switch: [
|
|
{
|
|
of_type: switch,
|
|
name: 'active',
|
|
help_str: 'access active profile information',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show current active profile',
|
|
func: func_profile_active_get,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'set',
|
|
help_str: 'Set current active profile',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'profile_n',
|
|
metavar: 'N',
|
|
help_str: 'The profile to set as current',
|
|
arg_type: int,
|
|
},
|
|
],
|
|
func: func_profile_active_set,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
N_access: {
|
|
of_type: N_access,
|
|
name: 'profile_n',
|
|
metavar: 'N',
|
|
help_str: 'The profile to act on',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show selected profile information',
|
|
func: func_profile_get,
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'name',
|
|
help_str: 'access profile name information',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show the name of the profile',
|
|
func: func_profile_name_get,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'set',
|
|
help_str: 'Set the name of the profile',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'name',
|
|
metavar: 'blah',
|
|
help_str: 'The name to set',
|
|
},
|
|
],
|
|
func: func_profile_name_set,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'enable',
|
|
help_str: 'Enable a profile',
|
|
func: func_profile_enable,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'disable',
|
|
help_str: 'Disable a profile',
|
|
func: func_profile_disable,
|
|
},
|
|
{
|
|
of_type: link,
|
|
dest: 'resolution',
|
|
},
|
|
{
|
|
of_type: link,
|
|
dest: 'dpi',
|
|
},
|
|
{
|
|
of_type: link,
|
|
dest: 'rate',
|
|
},
|
|
{
|
|
of_type: link,
|
|
dest: 'button',
|
|
},
|
|
{
|
|
of_type: link,
|
|
dest: 'led',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'resolution',
|
|
help_str: """Access resolution information
|
|
|
|
Resolution commands work on the given profile, or on the
|
|
active profile if none is given.""",
|
|
tag: 'resolution',
|
|
group: 'Resolution',
|
|
switch: [
|
|
{
|
|
of_type: switch,
|
|
name: 'active',
|
|
help_str: 'access active resolution information',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show current active resolution',
|
|
func: func_resolution_active_get,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'set',
|
|
help_str: 'Set current active resolution',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'resolution_n',
|
|
metavar: 'N',
|
|
help_str: 'The resolution to set as current',
|
|
arg_type: int,
|
|
},
|
|
],
|
|
func: func_resolution_active_set,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'default',
|
|
help_str: 'access default resolution information',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show current default resolution',
|
|
func: func_resolution_default_get,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'set',
|
|
help_str: 'Set current default resolution',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'resolution_n',
|
|
metavar: 'N',
|
|
help_str: 'The resolution to set as default',
|
|
arg_type: int,
|
|
},
|
|
],
|
|
func: func_default_resolution_set,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
N_access: {
|
|
of_type: N_access,
|
|
name: 'resolution_n',
|
|
metavar: 'N',
|
|
help_str: 'The resolution to act on',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show selected resolution',
|
|
func: func_resolution_get,
|
|
},
|
|
{
|
|
of_type: link,
|
|
dest: 'dpi',
|
|
},
|
|
{
|
|
of_type: link,
|
|
dest: 'rate',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'dpi',
|
|
help_str: """Access DPI information
|
|
|
|
DPI commands work on the given profile and resolution, or on the
|
|
active resolution of the active profile if none are given.""",
|
|
tag: 'dpi',
|
|
group: 'DPI',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show current DPI value',
|
|
func: func_dpi_get,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'get-all',
|
|
help_str: 'Show all available DPIs',
|
|
func: func_dpi_get_all,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'set',
|
|
help_str: 'Set the DPI value to N',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'dpi_n',
|
|
metavar: 'N',
|
|
help_str: 'The resolution to set as current',
|
|
arg_type: dpi,
|
|
},
|
|
],
|
|
func: func_dpi_set,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'rate',
|
|
help_str: """Access report rate information
|
|
|
|
Rate commands work on the given profile and resolution, or on the
|
|
active resolution of the active profile if none are given.""",
|
|
tag: 'rate',
|
|
group: 'Rate',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show current report rate',
|
|
func: func_report_rate_get,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'get-all',
|
|
help_str: 'Show all available report rates',
|
|
func: func_report_rate_get_all,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'set',
|
|
help_str: 'Set the report rate to N',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'rate_n',
|
|
metavar: 'N',
|
|
help_str: 'The report rate to set as current',
|
|
arg_type: int,
|
|
},
|
|
],
|
|
func: func_report_rate_set,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'button',
|
|
help_str: """Access Button information
|
|
|
|
Button commands work on the given profile, or on the
|
|
active profile if none is given.""",
|
|
tag: 'button',
|
|
group: 'Button',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'count',
|
|
help_str: 'Print the number of buttons',
|
|
func: func_button_count,
|
|
},
|
|
],
|
|
N_access: {
|
|
of_type: N_access,
|
|
name: 'button_n',
|
|
metavar: 'N',
|
|
help_str: 'The button to act on',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show selected button',
|
|
func: func_button_get,
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'action',
|
|
help_str: 'Act on the selected button',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Print the button action',
|
|
func: func_button_get,
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'set',
|
|
help_str: 'Set an action on the selected button',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'button',
|
|
help_str: 'Set the button action to button B',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'target_button',
|
|
metavar: 'B',
|
|
help_str: 'The new button value to assign',
|
|
arg_type: int,
|
|
},
|
|
],
|
|
func: func_button_action_set_button,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'special',
|
|
help_str: 'Set the button action to special action S',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'target_special',
|
|
metavar: 'S',
|
|
help_str: 'The new special value to assign',
|
|
choices: [
|
|
"unknown",
|
|
"doubleclick",
|
|
"wheel-left",
|
|
"wheel-right",
|
|
"wheel-up",
|
|
"wheel-down",
|
|
"ratchet-mode-switch",
|
|
"resolution-cycle-up",
|
|
"resolution-cycle-down",
|
|
"resolution-up",
|
|
"resolution-down",
|
|
"resolution-alternate",
|
|
"resolution-default",
|
|
"profile-cycle-up",
|
|
"profile-cycle-down",
|
|
"profile-up",
|
|
"profile-down",
|
|
"second-mode",
|
|
"battery-level",
|
|
]
|
|
},
|
|
],
|
|
func: func_button_action_set_special,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'macro',
|
|
help_str: """Set the button action to the given macro
|
|
|
|
Macro syntax:
|
|
A macro is a series of key events or waiting periods.
|
|
Keys must be specified in linux/input.h key names.
|
|
KEY_A Press and release 'a'
|
|
+KEY_A Press 'a'
|
|
-KEY_A Release 'a'
|
|
t300 Wait 300ms""",
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'target_macro',
|
|
metavar: '...',
|
|
help_str: 'The new macro to assign',
|
|
nargs: argparse.REMAINDER,
|
|
},
|
|
],
|
|
func: func_button_action_set_macro,
|
|
},
|
|
]
|
|
},
|
|
]
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'led',
|
|
help_str: """Access LED information
|
|
|
|
LED commands work on the given profile, or on the
|
|
active profile if none is given.""",
|
|
tag: 'led',
|
|
group: 'LED',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show current LED value',
|
|
func: func_led_get_all,
|
|
},
|
|
],
|
|
N_access: {
|
|
of_type: N_access,
|
|
name: 'led_n',
|
|
metavar: 'N',
|
|
help_str: 'The LED to act on',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show current LED value',
|
|
func: func_led_get,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'capabilities',
|
|
help_str: 'Show LED capabilities',
|
|
func: func_led_caps,
|
|
},
|
|
{
|
|
of_type: set,
|
|
name: 'set',
|
|
help_str: 'Act on the selected LED',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'mode',
|
|
help_str: 'The mode to set as current',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'mode',
|
|
metavar: 'mode',
|
|
help_str: 'The mode to set as current',
|
|
choices: ['on', 'off', 'cycle', 'breathing'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'color',
|
|
help_str: 'The color to set as current',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'color',
|
|
metavar: 'RRGGBB',
|
|
help_str: 'The color in hex format to set as current',
|
|
arg_type: color,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'duration',
|
|
help_str: 'The duration to set as current',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'duration',
|
|
metavar: 'R',
|
|
help_str: 'The duration in ms to set as current',
|
|
arg_type: int,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'brightness',
|
|
help_str: 'The brightness to set as current',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'brightness',
|
|
metavar: 'B',
|
|
help_str: 'The brightness to set as current',
|
|
arg_type: u8,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
func: func_led_set,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
|
|
class ParseError(Exception):
|
|
pass
|
|
|
|
|
|
class RatbagParser(object):
|
|
tagged = {}
|
|
|
|
def __init__(self, type, name, group=None, tag=None, func=None, help=None):
|
|
self.type = type
|
|
self.name = name
|
|
self.tag = tag
|
|
self.group = group
|
|
if tag is not None:
|
|
RatbagParser.tagged[tag] = self
|
|
self.func = func
|
|
self.help = help
|
|
|
|
def repr_args(self):
|
|
return "name='{}', tag='{}', func='{}', help='{}'".format(self.name, self.tag, self.func, self.help)
|
|
|
|
def __repr__(self):
|
|
return "{}({})".format(type(self), self.repr_args())
|
|
|
|
def store_function(self, parser):
|
|
if self.func is not None:
|
|
parser.set_defaults(func=self.func)
|
|
if 'list' not in self.name:
|
|
parser.add_argument("device", metavar='eventX')
|
|
|
|
def _add_to_subparsers(self, parent, input_string, ns):
|
|
raise ParseError("please implement _add_to_subparsers on {}".format(type(self)))
|
|
|
|
def add_to_subparsers(self, parent):
|
|
self._add_to_subparsers(parent)
|
|
|
|
def _sub_parse(self, input_string, ns):
|
|
raise ParseError("please implement _sub_parse on {}".format(type(self)))
|
|
|
|
def sub_parse(self, input_string, ns):
|
|
r = self._sub_parse(input_string, ns)
|
|
return r
|
|
|
|
def build_cmd_args_name(self):
|
|
"""uniquely tag the arguments of the command in the namespace"""
|
|
return "{}_args_{}".format(self.name)
|
|
|
|
def print_help(self, group, prefix=""):
|
|
if self.group is not None:
|
|
print("\n{} Commands:".format(self.group))
|
|
self._print_help(prefix)
|
|
|
|
def _print_help(self, prefix):
|
|
raise ParseError("please implement _print_help on {}".format(type(self)))
|
|
|
|
|
|
class RatbagParserSwitch(RatbagParser):
|
|
def __init__(self, type, name, group=None, switch=[], N_access=None, tag=None, func=None, help=None):
|
|
super(RatbagParserSwitch, self).__init__(type, name, group, tag, func, help)
|
|
self.switch = [classes[obj[of_type]](**obj) for obj in switch]
|
|
if N_access is not None:
|
|
self.N_access = RatbagParserNAccess(**N_access)
|
|
else:
|
|
self.N_access = None
|
|
|
|
def repr_args(self):
|
|
return """switch='{}', N_access='{}', {}""".format([repr(o) for o in self.switch], self.N_access, RatbagParser.repr_args(self))
|
|
|
|
def _add_to_subparsers(self, parent):
|
|
parser = parent.add_parser(self.name, help=self.help)
|
|
parser.set_defaults(subparse=self.sub_parse)
|
|
|
|
def _sub_parse(self, input_string, ns):
|
|
if input_string and self.N_access is not None:
|
|
# retrieve first numbered element if any
|
|
try:
|
|
int(input_string[0])
|
|
except ValueError:
|
|
# there are arguments, but they look like commands
|
|
pass
|
|
else:
|
|
# we have a single int as first argument, switch to the
|
|
# N_access subtree of the command
|
|
return self.N_access._sub_parse(self, input_string, ns)
|
|
|
|
parser = argparse.ArgumentParser(prog="{} {}".format(sys.argv[0], self.name),
|
|
description=self.help,
|
|
add_help=False)
|
|
|
|
# create a new subparser to handle all commands
|
|
subs = parser.add_subparsers(title="COMMANDS", help=None)
|
|
for e in self.switch:
|
|
e.add_to_subparsers(subs)
|
|
|
|
return parser
|
|
|
|
def _print_help(self, prefix):
|
|
if self.help and self.group is not None:
|
|
string = self.help.split('\n')
|
|
string = "\n ".join(string)
|
|
print(" ", string, '\n')
|
|
for e in self.switch:
|
|
e.print_help(None, "{}{} ".format(prefix, self.name))
|
|
if self.N_access is not None:
|
|
self.N_access.print_help(None, self.name + " ")
|
|
|
|
def __repr__(self):
|
|
return "switch({})".format(self.repr_args())
|
|
|
|
|
|
class RatbagParserNAccess(RatbagParserSwitch):
|
|
def __init__(self, type, name, group=None, switch=[], metavar=None, tag=None, func=None, help=None):
|
|
super(RatbagParserNAccess, self).__init__(type, name, group, switch, None, tag, func, help)
|
|
self.metavar = metavar
|
|
|
|
def _sub_parse(self, parent, input_string, ns):
|
|
parser = argparse.ArgumentParser(prog="{} {}".format(sys.argv[0], parent.name),
|
|
add_help=False)
|
|
parser.add_argument(self.name, help=self.help, type=int)
|
|
# create a new subparser to handle all commands
|
|
subs = parser.add_subparsers(title="COMMANDS", help=None)
|
|
for e in self.switch:
|
|
e.add_to_subparsers(subs)
|
|
return parser
|
|
|
|
def repr_args(self):
|
|
return """switch='{}', metavar = '{}', {}""".format([repr(o) for o in self.switch], self.metavar, RatbagParser.repr_args(self))
|
|
|
|
def __repr__(self):
|
|
return "N_Access({})".format(self.repr_args())
|
|
|
|
def _print_help(self, prefix):
|
|
for e in self.switch:
|
|
e.print_help(None, "{}N ".format(prefix))
|
|
|
|
|
|
class RatbagParserSet(RatbagParserSwitch):
|
|
def __init__(self, type, name, group=None, switch=[], N_access=None, tag=None, func=None, help=None):
|
|
super(RatbagParserSet, self).__init__(type, name, group, switch, N_access, tag, func, help)
|
|
|
|
def _add_to_subparsers(self, parent):
|
|
parser = parent.add_parser(self.name, help=self.help)
|
|
parser.set_defaults(subparse=self.sub_parse)
|
|
|
|
def _sub_parse(self, input_string, ns):
|
|
parser = argparse.ArgumentParser(prog="{} {}".format(sys.argv[0], self.name),
|
|
add_help=False)
|
|
# create a new subparser to handle all commands
|
|
subs = parser.add_subparsers(title="COMMANDS", help=None)
|
|
for e in self.switch:
|
|
e.add_to_subparsers(subs)
|
|
if len(input_string) == 3:
|
|
self.store_function(parser)
|
|
elif len(input_string) == 2:
|
|
# missing device argument
|
|
parser.add_argument("device", metavar='eventX')
|
|
else:
|
|
parser.set_defaults(subparse=self.sub_parse)
|
|
return parser
|
|
|
|
def __repr__(self):
|
|
return "set({})".format(self.repr_args())
|
|
|
|
def _print_help(self, prefix):
|
|
command = prefix + "{COMMAND} ..."
|
|
print(" {:<36}{}".format(command,
|
|
self.help if self.help else ""))
|
|
for e in self.switch:
|
|
e.print_help(None, " " * len(prefix))
|
|
|
|
|
|
class RatbagParserCommand(RatbagParser):
|
|
def __init__(self, type, name, group=None, pos_args=[], tag=None, func=None, help=None):
|
|
super(RatbagParserCommand, self).__init__(type, name, group, tag, func, help)
|
|
self.pos_args = [classes[obj[of_type]](**obj) for obj in pos_args]
|
|
|
|
def _add_to_subparsers(self, parent):
|
|
parser = parent.add_parser(self.name, help=self.help)
|
|
for a in self.pos_args:
|
|
a.add_to_subparsers(parser)
|
|
self.store_function(parser)
|
|
|
|
def __repr__(self):
|
|
return "command({})".format(self.repr_args())
|
|
|
|
def _print_help(self, prefix):
|
|
command = prefix + self.name
|
|
for a in self.pos_args:
|
|
if a.choices is None or len(a.choices) > 5:
|
|
command += " {}".format(a.metavar)
|
|
else:
|
|
command += " [{}]".format("|".join(a.choices))
|
|
print(" {:<36}{}".format(command,
|
|
self.help if self.help else ""))
|
|
|
|
|
|
class RatbagParserArgument(RatbagParser):
|
|
def __init__(self, type, name, group=None, arg_type=None, metavar=None, nargs=None, choices=None, tag=None, func=None, help=None):
|
|
super(RatbagParserArgument, self).__init__(type, name, group, tag, func, help)
|
|
self.arg_type = arg_type
|
|
self.metavar = metavar
|
|
self.nargs = nargs
|
|
self.choices = choices
|
|
|
|
def _add_to_subparsers(self, parent):
|
|
parent.add_argument(self.name, metavar=self.metavar, help=self.help, type=self.arg_type, nargs=self.nargs, choices=self.choices)
|
|
|
|
|
|
class RatbagParserLink(RatbagParser):
|
|
def __init__(self, type, dest=None, group=None, tag=None, func=None, help=None):
|
|
super(RatbagParserLink, self).__init__(type, dest, group, tag, func, help)
|
|
self.dest = dest
|
|
|
|
def get_dest(self):
|
|
try:
|
|
dest = RatbagParser.tagged[self.dest]
|
|
except KeyError:
|
|
raise ParseError("link '{}' points to nothing".format(self.dest))
|
|
return dest
|
|
|
|
def _add_to_subparsers(self, parent):
|
|
dest = self.get_dest()
|
|
dest.add_to_subparsers(parent)
|
|
|
|
def _print_help(self, prefix):
|
|
dest = self.get_dest()
|
|
print(" {:<36}Use {}for '{} Commands'".format(prefix + self.dest + " ...", prefix, dest.group))
|
|
|
|
classes = {
|
|
switch: RatbagParserSwitch,
|
|
set: RatbagParserSet,
|
|
command: RatbagParserCommand,
|
|
argument: RatbagParserArgument,
|
|
link: RatbagParserLink,
|
|
N_access: RatbagParserNAccess,
|
|
}
|
|
|
|
|
|
class RatbagParserRoot(object):
|
|
def __init__(self, commands):
|
|
self.children = [classes[def_parser[of_type]](**def_parser) for def_parser in commands]
|
|
self.want_keepalive = False
|
|
|
|
def parse(self, input_string):
|
|
# we do not care about case, use lower case for the input string
|
|
for i, string in enumerate(input_string):
|
|
if not string.startswith('-'):
|
|
input_string[i] = string.lower()
|
|
|
|
self.parser = argparse.ArgumentParser(description="Inspect and modify a configurable device",
|
|
add_help=False)
|
|
self.parser.add_argument("-V", "--version", action="version", version="@version@")
|
|
self.parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
self.parser.add_argument('--help', '-h', action='store_true', default=False)
|
|
self.parser.add_argument('--nocommit', action='store_true', default=False)
|
|
if self.want_keepalive:
|
|
self.parser.add_argument('--keepalive', action='store_true', default=False)
|
|
|
|
# retrieve the global options now and remove them from the processing
|
|
ns, rest = self.parser.parse_known_args(input_string)
|
|
|
|
if ns.help:
|
|
return ns
|
|
|
|
subs = self.parser.add_subparsers(title="COMMANDS")
|
|
|
|
subparser = self.parser
|
|
|
|
for child in self.children:
|
|
child.add_to_subparsers(subs)
|
|
|
|
ns.subparse = None
|
|
|
|
while rest and subparser:
|
|
old_rest = rest
|
|
ns, rest = subparser.parse_known_args(rest, namespace=ns)
|
|
if old_rest == rest:
|
|
break
|
|
if ns.subparse:
|
|
subparser = ns.subparse(rest, ns)
|
|
|
|
if rest:
|
|
self.parser.error("extra arguments: '{}'".format(" ".join(rest)))
|
|
|
|
return ns
|
|
|
|
def print_help(self):
|
|
print("usage: {} [OPTIONS] {{COMMAND}} ... /path/to/device\n".format(self.parser.prog))
|
|
print(self.parser.description)
|
|
print("""
|
|
Common options:
|
|
--version -V show program's version number and exit
|
|
--verbose, -v increase verbosity level
|
|
--nocommit Do not immediately write the settings to the mouse
|
|
--help, -h show this help and exit""")
|
|
if self.want_keepalive:
|
|
print(" --keepalive do not terminate ratbagd after the processing")
|
|
for c in self.children:
|
|
c.print_help(None)
|
|
print("""
|
|
Examples:
|
|
{0} profile active get
|
|
{0} profile 0 resolution active set 4
|
|
{0} profile 0 resolution 1 dpi get
|
|
{0} resolution 4 rate get
|
|
{0} dpi set 800
|
|
{0} profile 0 led 0 set mode on
|
|
{0} profile 0 led 0 set color ff00ff
|
|
{0} profile 0 led 0 set duration 50
|
|
|
|
Exit codes:
|
|
0 Success
|
|
1 Unsupported feature, index out of available range or invalid device
|
|
2 Commandline arguments are invalid
|
|
3 A command failed on the device
|
|
""".format(self.parser.prog))
|
|
|
|
|
|
def get_parser():
|
|
return RatbagParserRoot(parser_def)
|
|
|
|
|
|
def open_ratbagd(ratbagd_process=None, verbose=0):
|
|
try:
|
|
r = Ratbagd()
|
|
r.verbose = verbose
|
|
except RatbagdDBusUnavailable:
|
|
print("Unable to connect to ratbagd over dbus")
|
|
return None
|
|
|
|
if ratbagd_process is not None:
|
|
# if some old version of ratbagd is still running, ratbagd_process may
|
|
# have never started but our DBus bindings may succeed. Check for the
|
|
# return code here, this also gives ratbagd enough time to start and
|
|
# die. If we check immediately we may not have terminated yet.
|
|
ratbagd_process.poll()
|
|
assert ratbagd_process.returncode is None
|
|
|
|
return r
|
|
|
|
|
|
def main(argv):
|
|
if not argv:
|
|
argv = ["list"]
|
|
|
|
parser = get_parser()
|
|
cmd = parser.parse(argv)
|
|
if cmd.help:
|
|
parser.print_help()
|
|
return
|
|
|
|
_r = open_ratbagd(verbose=cmd.verbose)
|
|
if _r is not None:
|
|
with _r as r:
|
|
try:
|
|
f = cmd.func
|
|
except AttributeError:
|
|
parser.print_help()
|
|
return
|
|
else:
|
|
f(r, cmd)
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1:])
|