forked from drath/libratbag
On devices supporting the commit API, ratbagctl did not write anything to the device. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
1133 lines
38 KiB
Python
Executable File
1133 lines
38 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 sys
|
|
import argparse
|
|
|
|
from ratbagd import Ratbagd, RatbagdDevice, RatbagdProfile, RatbagdResolution, RatbagdLed, RatbagdDBusUnavailable # NOQA
|
|
|
|
|
|
def list_devices(r, args):
|
|
for d in r.devices:
|
|
print("{:10s} {:32s}".format(d.id + ":", d.name))
|
|
|
|
|
|
def find_device(r, args):
|
|
dev = None
|
|
for d in r.devices:
|
|
if d.id == args.device:
|
|
dev = d
|
|
break
|
|
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:
|
|
for p in d.profiles:
|
|
if p.is_active:
|
|
break
|
|
else:
|
|
p = None
|
|
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:
|
|
for r in p.resolutions:
|
|
if r.is_active:
|
|
break
|
|
else:
|
|
r = None
|
|
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",
|
|
}
|
|
if l.mode == RatbagdLed.MODE_OFF:
|
|
print(" " * level + "LED: {} type: {}, mode: {}".format(l.index,
|
|
l.type,
|
|
leds[l.mode]))
|
|
elif l.mode == RatbagdLed.MODE_ON:
|
|
print(" " * level + "LED: {} type: {}, mode: {}, color: {:02x}{:02x}{:02x}".format(l.index,
|
|
l.type,
|
|
leds[l.mode],
|
|
l.color[0],
|
|
l.color[1],
|
|
l.color[2]))
|
|
elif l.mode == RatbagdLed.MODE_CYCLE:
|
|
print(" " * level + "LED: {} type: {}, mode: {}, rate: {}, brightness: {}".format(l.index,
|
|
l.type,
|
|
leds[l.mode],
|
|
l.effect_rate,
|
|
l.brightness))
|
|
elif l.mode == RatbagdLed.MODE_BREATHING:
|
|
print(" " * level + "LED: {} type: {}, mode: {}, color: {:02x}{:02x}{:02x}, rate: {}, brightness: {}".format(l.index,
|
|
l.type,
|
|
leds[l.mode],
|
|
l.color[0],
|
|
l.color[1],
|
|
l.color[2],
|
|
l.effect_rate,
|
|
l.brightness))
|
|
|
|
|
|
def print_button(d, p, b, level):
|
|
header = " " * level + "Button: {} type {} is mapped to ".format(b.index,
|
|
b.type)
|
|
|
|
if b.action_type == "button":
|
|
print("{}'button {}'".format(header, b.mapping))
|
|
elif b.action_type == "key":
|
|
print("{}'{}'".format(header, b.key))
|
|
elif b.action_type == "special":
|
|
print("{}'{}'".format(header, b.special))
|
|
else:
|
|
print("{}UNKOWN".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 + "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_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:
|
|
rate = args.rate
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
l.effect_rate = rate
|
|
try:
|
|
brightness = args.brightness
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
l.brightness = brightness
|
|
d.commit()
|
|
|
|
|
|
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
|
|
d.commit()
|
|
|
|
|
|
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(r.resolution[0])
|
|
|
|
|
|
def func_dpi_set(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
if RatbagdResolution.CAP_SEPARATE_XY_RESOLUTION in r.capabilities:
|
|
print(args)
|
|
else:
|
|
r.resolution = (args.dpi_n, args.dpi_n)
|
|
d.commit()
|
|
|
|
|
|
def func_report_rate_get(r, args):
|
|
r, p, d = find_resolution(r, args)
|
|
print(r.report_rate)
|
|
|
|
|
|
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)
|
|
for r in p.resolutions:
|
|
if r.is_active:
|
|
break
|
|
else:
|
|
r = None
|
|
print(r.index)
|
|
|
|
|
|
def func_profile_get(r, args):
|
|
p, d = find_profile(r, args)
|
|
print_profile(d, p, 0)
|
|
|
|
|
|
def func_profile_active_get(r, args):
|
|
d = find_device(r, args)
|
|
for p in d.profiles:
|
|
if p.is_active:
|
|
break
|
|
else:
|
|
p = None
|
|
print(p.index)
|
|
|
|
|
|
def func_profile_active_set(r, args):
|
|
p, d = find_profile(r, args)
|
|
p.set_active()
|
|
d.commit()
|
|
|
|
|
|
def func_dummy(r, args):
|
|
print("uh, oh, not implemented")
|
|
pr_debug(args, r, args)
|
|
|
|
|
|
################################################################################
|
|
# 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'
|
|
"""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 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
|
|
|
|
# note: 'eventX' is assumed at the end of each command
|
|
parser_def = [
|
|
{
|
|
of_type: command,
|
|
name: 'list',
|
|
help_str: 'List available configurable devices',
|
|
func: list_devices,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'info',
|
|
help_str: 'Show device information',
|
|
func: show_device,
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'profile',
|
|
help_str: 'Access profile information',
|
|
tag: '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,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'enable',
|
|
help_str: 'Enable a profile',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'profile_n',
|
|
metavar: 'N',
|
|
help_str: 'The profile to enable',
|
|
arg_type: int,
|
|
},
|
|
],
|
|
func: func_dummy, # FIXME: func_profile_enable,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'disable',
|
|
help_str: 'Disable a profile',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'profile_n',
|
|
metavar: 'N',
|
|
help_str: 'The profile to disable',
|
|
arg_type: int,
|
|
},
|
|
],
|
|
func: func_dummy, # FIXME: func_profile_disable,
|
|
},
|
|
],
|
|
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 current active profile',
|
|
func: func_profile_get,
|
|
},
|
|
{
|
|
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',
|
|
tag: '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_dummy, # FIXME: func_active_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',
|
|
tag: 'dpi',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show current DPI value',
|
|
func: func_dpi_get,
|
|
},
|
|
{
|
|
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: int,
|
|
},
|
|
],
|
|
func: func_dpi_set,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'rate',
|
|
help_str: 'Access report rate information',
|
|
tag: 'rate',
|
|
switch: [
|
|
{
|
|
of_type: command,
|
|
name: 'get',
|
|
help_str: 'Show current report rate',
|
|
func: func_report_rate_get,
|
|
},
|
|
{
|
|
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',
|
|
tag: '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 buton 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',
|
|
},
|
|
],
|
|
func: func_dummy, # FIXME: func_button_action_set_special,
|
|
},
|
|
{
|
|
of_type: command,
|
|
name: 'macro',
|
|
help_str: 'Set the button action to the given macro',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'target_macro',
|
|
metavar: 'macro',
|
|
help_str: 'The new macro to assign',
|
|
nargs: argparse.REMAINDER,
|
|
},
|
|
],
|
|
func: func_dummy, # FIXME: func_button_action_set_macro,
|
|
},
|
|
]
|
|
},
|
|
]
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
of_type: switch,
|
|
name: 'led',
|
|
help_str: 'Access LED information',
|
|
tag: '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: 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: 'rate',
|
|
help_str: 'The rate to set as current',
|
|
pos_args: [
|
|
{
|
|
of_type: argument,
|
|
name: 'rate',
|
|
metavar: 'rate',
|
|
help_str: 'The rate in Hz 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: 'brightness',
|
|
help_str: 'The brightness to set as current',
|
|
arg_type: u8,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
func: func_led_set,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
|
|
def pr_debug(ns, *args):
|
|
try:
|
|
if ns.verbose > 2:
|
|
print(*args)
|
|
except AttributeError:
|
|
print(ns)
|
|
|
|
|
|
class ParseError(Exception):
|
|
pass
|
|
|
|
|
|
class RatbagParser(object):
|
|
tagged = {}
|
|
|
|
def __init__(self, type, name, tag=None, func=None, help=None):
|
|
self.type = type
|
|
self.name = name
|
|
self.tag = tag
|
|
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 _parse(self, parent, input_string, ns):
|
|
raise ParseError("please implement _parse on {}".format(type(self)))
|
|
|
|
def parse(self, parent, input_string, ns):
|
|
pr_debug(ns, ">>> {}: {} | {}".format(self.type, self.name, input_string))
|
|
self._parse(parent, input_string, ns)
|
|
pr_debug(ns, "<<< {}: {} | {}".format(self.type, self.name, input_string))
|
|
|
|
def _sub_parse(self, input_string, ns):
|
|
raise ParseError("please implement _sub_parse on {}".format(type(self)))
|
|
|
|
def sub_parse(self, input_string, ns):
|
|
pr_debug(ns, ">.. {}: {} | {}".format(self.type, self.name, input_string))
|
|
r = self._sub_parse(input_string, ns)
|
|
pr_debug(ns, "<.. {}: {} | {}".format(self.type, self.name, input_string))
|
|
return r
|
|
|
|
def print_help(self):
|
|
return str(type(self))
|
|
|
|
def build_cmd_args_name(self):
|
|
"""uniquely tag the arguments of the command in the namespace"""
|
|
return "{}_args_{}".format(self.name)
|
|
|
|
|
|
class RatbagParserSwitch(RatbagParser):
|
|
def __init__(self, type, name, switch=[], N_access=None, tag=None, func=None, help=None):
|
|
super(RatbagParserSwitch, self).__init__(type, name, 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 _parse(self, parent, input_string, ns):
|
|
parser = parent.add_parser(self.name, help=self.help)
|
|
parser.set_defaults(subparse=self.sub_parse)
|
|
|
|
def _sub_parse(self, input_string, ns):
|
|
if len(input_string) >= 0 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(input_string, ns)
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
# create a new subparser to handle all commands
|
|
subs = parser.add_subparsers(title="COMMANDS", help=None)
|
|
for e in self.switch:
|
|
e.parse(subs, input_string, ns)
|
|
|
|
return parser
|
|
|
|
def __repr__(self):
|
|
return "switch({})".format(self.repr_args())
|
|
|
|
|
|
class RatbagParserNAccess(RatbagParserSwitch):
|
|
def __init__(self, type, name, switch=[], metavar=None, tag=None, func=None, help=None):
|
|
super(RatbagParserNAccess, self).__init__(type, name, switch, None, tag, func, help)
|
|
self.metavar = metavar
|
|
|
|
def _sub_parse(self, input_string, ns):
|
|
parser = argparse.ArgumentParser()
|
|
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.parse(subs, input_string, ns)
|
|
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())
|
|
|
|
|
|
class RatbagParserSet(RatbagParserSwitch):
|
|
def __init__(self, type, name, switch=[], N_access=None, tag=None, func=None, help=None):
|
|
super(RatbagParserSet, self).__init__(type, name, switch, N_access, tag, func, help)
|
|
|
|
def _parse(self, parent, input_string, ns):
|
|
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()
|
|
# create a new subparser to handle all commands
|
|
subs = parser.add_subparsers(title="COMMANDS", help=None)
|
|
for e in self.switch:
|
|
e.parse(subs, input_string, ns)
|
|
if len(input_string) == 3:
|
|
self.store_function(parser)
|
|
else:
|
|
parser.set_defaults(subparse=self.sub_parse)
|
|
return parser
|
|
|
|
def __repr__(self):
|
|
return "set({})".format(self.repr_args())
|
|
|
|
|
|
class RatbagParserCommand(RatbagParser):
|
|
def __init__(self, type, name, pos_args=[], tag=None, func=None, help=None):
|
|
super(RatbagParserCommand, self).__init__(type, name, tag, func, help)
|
|
self.pos_args = [classes[obj[of_type]](**obj) for obj in pos_args]
|
|
|
|
def _parse(self, parent, input_string, ns):
|
|
parser = parent.add_parser(self.name, help=self.help)
|
|
for a in self.pos_args:
|
|
a.parse(parser, input_string, ns)
|
|
self.store_function(parser)
|
|
|
|
def __repr__(self):
|
|
return "command({})".format(self.repr_args())
|
|
|
|
|
|
class RatbagParserArgument(RatbagParser):
|
|
def __init__(self, type, name, arg_type=None, metavar=None, nargs=None, choices=None, tag=None, func=None, help=None):
|
|
super(RatbagParserArgument, self).__init__(type, name, tag, func, help)
|
|
self.arg_type = arg_type
|
|
self.metavar = metavar
|
|
self.nargs = nargs
|
|
self.choices = choices
|
|
|
|
def _parse(self, parent, input_string, ns):
|
|
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, tag=None, func=None, help=None):
|
|
super(RatbagParserLink, self).__init__(type, dest, tag, func, help)
|
|
self.dest = dest
|
|
|
|
def _parse(self, parent, input_string, ns):
|
|
try:
|
|
e = RatbagParser.tagged[self.dest]
|
|
except KeyError:
|
|
raise ParseError("link '{}' points to nothing".format(self.dest))
|
|
else:
|
|
e.parse(parent, input_string, ns)
|
|
|
|
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]
|
|
|
|
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 configurable mice")
|
|
self.parser.add_argument("-V", "--version", action="version", version="@version@")
|
|
self.parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
|
|
ns, rest = self.parser.parse_known_args(input_string)
|
|
|
|
subs = self.parser.add_subparsers(title="COMMANDS", help='sub-command help')
|
|
|
|
subparser = self.parser
|
|
rest = input_string
|
|
|
|
for child in self.children:
|
|
child.parse(subs, input_string, ns)
|
|
|
|
while subparser:
|
|
ns, rest = subparser.parse_known_args(rest, namespace=ns)
|
|
if not rest:
|
|
break
|
|
pr_debug(ns, ns, rest)
|
|
try:
|
|
subparse_call = ns.subparse
|
|
except AttributeError:
|
|
break
|
|
else:
|
|
subparser = subparse_call(rest, ns)
|
|
|
|
if rest:
|
|
self.parser.error("extra arguments: '{}'".format(" ".join(rest)))
|
|
|
|
return ns
|
|
|
|
def print_help(self):
|
|
self.parser.print_help()
|
|
|
|
|
|
def get_parser():
|
|
return RatbagParserRoot(parser_def)
|
|
|
|
|
|
def open_ratbagd():
|
|
try:
|
|
r = Ratbagd()
|
|
except RatbagdDBusUnavailable:
|
|
print("Unable to connect to ratbagd over dbus")
|
|
return None
|
|
else:
|
|
if not r.devices:
|
|
print("No devices available.")
|
|
return r
|
|
|
|
|
|
def main(argv):
|
|
if not argv:
|
|
argv = ["list"]
|
|
|
|
parser = get_parser()
|
|
cmd = parser.parse(argv)
|
|
|
|
r = open_ratbagd()
|
|
if r is not None:
|
|
cmd.func(r, cmd)
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1:])
|