#!/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 + "{}: ".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:])