diff --git a/doc/conf.py.in b/doc/conf.py.in index c9c7538d..128a344c 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # @PACKAGE_NAME@ documentation build configuration file, created by # sphinx-quickstart on Wed Oct 11 13:45:08 2017. diff --git a/pyproject.toml b/pyproject.toml index 2362fa25..b1a4b006 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,6 @@ select = [ ] ignore = [ # Line too long. - # We have some places where it's just impossible to make it fit, + # We have some places where it's just impossible to make it fit. "E501", ] diff --git a/test/data-parse-test.py b/test/data-parse-test.py index 453b7034..79e4b5af 100755 --- a/test/data-parse-test.py +++ b/test/data-parse-test.py @@ -39,12 +39,12 @@ T = TypeVar("T") def assertIn(element: T, l: Collection[T]): if element not in l: - raise AssertionError("{} must be in {}".format(element, l)) + raise AssertionError(f"{element} must be in {l}") def assertNotIn(element: T, l: Collection[T]): if element in l: - raise AssertionError("{} must not be in {}".format(element, l)) + raise AssertionError(f"{element} must not be in {l}") def check_match_str(string: str): @@ -59,9 +59,9 @@ def check_match_str(string: str): assert len(parts) == 3 assertIn(parts[0], bustypes) vid = parts[1] - assert vid == "{:04x}".format(int(vid, 16)) + assert vid == f"{int(vid, 16):04x}" pid = parts[2] - assert pid == "{:04x}".format(int(pid, 16)) + assert pid == f"{int(pid, 16):04x}" def check_devicetype_str(string): @@ -97,7 +97,7 @@ def check_dpi_range_str(string: str): if int(steps) == steps: steps = int(steps) - assert string == "{}:{}@{}".format(min, max, steps) + assert string == f"{min}:{max}@{steps}" def check_dpi_list_str(string: str): @@ -273,7 +273,7 @@ def check_section_driver(driver: str, section: configparser.SectionProxy): check_section_steelseries(section) return - raise AssertionError("Unsupported driver section {}".format(driver)) + raise AssertionError(f"Unsupported driver section {driver}") def validate_data_file_name(path: str): @@ -291,7 +291,7 @@ def validate_data_file_name(path: str): def parse_data_file(path: str): - print("Parsing file {}".format(path)) + print(f"Parsing file {path}") data = configparser.ConfigParser(strict=True) # Don't convert to lowercase data.optionxform = lambda option: option @@ -301,7 +301,7 @@ def parse_data_file(path: str): check_section_device(data["Device"]) driver = data["Device"]["Driver"] - driver_section = "Driver/{}".format(driver) + driver_section = f"Driver/{driver}" permitted_sections = ["Device", driver_section] # FIXME: remove this once `sinowealth` driver has a better way of defining data. diff --git a/test/receiver-check.py b/test/receiver-check.py index add228e2..1c876a54 100755 --- a/test/receiver-check.py +++ b/test/receiver-check.py @@ -49,7 +49,7 @@ logitech_receivers = [ 0xC547, # USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3 0xC548, # Bolt receiver ] -RECEIVERS = ["usb:046d:{}".format(r) for r in logitech_receivers] +RECEIVERS = [f"usb:046d:{r}" for r in logitech_receivers] def parse_data_file(path): @@ -74,7 +74,7 @@ if __name__ == "__main__": fname = os.path.basename(path) for m in matches: if m in RECEIVERS: - print("Receiver ID {} found in file {}".format(m, fname)) + print(f"Receiver ID {m} found in file {fname}") receiver_found = True if receiver_found: diff --git a/tools/check_scan_build.py b/tools/check_scan_build.py index ac34858d..33918d55 100755 --- a/tools/check_scan_build.py +++ b/tools/check_scan_build.py @@ -17,7 +17,7 @@ import re import sys -class Bug(object): +class Bug: def __init__(self): self.line_number = None self.cfile = None @@ -37,7 +37,7 @@ def main(argv): line_re = re.compile(r"line (\d+), column (\d+)") - for root, dirs, files in os.walk(scanbuild_path): + for root, _dirs, files in os.walk(scanbuild_path): for filename in files: if not filename.startswith("report"): continue diff --git a/tools/merge_ratbagd.py b/tools/merge_ratbagd.py index df342b07..7653f401 100755 --- a/tools/merge_ratbagd.py +++ b/tools/merge_ratbagd.py @@ -30,8 +30,8 @@ from typing import TextIO def write_ratbagctl( output_file: TextIO, ratbagctl_path: str, ratbagd_path: str, version_string: str ) -> None: - with open(ratbagctl_path, "r", encoding="utf-8") as ratbagctl, open( - ratbagd_path, "r", encoding="utf-8" + with open(ratbagctl_path, encoding="utf-8") as ratbagctl, open( + ratbagd_path, encoding="utf-8" ) as ratbagd: for line in ratbagctl.readlines(): if line.startswith("from ratbagd import "): diff --git a/tools/ratbagc.py.in b/tools/ratbagc.py.in index b137d7f1..6de39d24 100644 --- a/tools/ratbagc.py.in +++ b/tools/ratbagc.py.in @@ -151,7 +151,7 @@ EXCEPTION_TABLE = { } -class Ratbagd(object): +class Ratbagd: """The ratbagd top-level object. Provides a list of devices available through libratbag; actual interaction with the devices is via the RatbagdDevice, RatbagdProfile, RatbagdResolution and RatbagdButton objects. @@ -219,7 +219,7 @@ class Ratbagd(object): def __getitem__(self, id): """Returns the requested device, or None.""" if id.startswith("hidraw"): - id = "/dev/{}".format(id) + id = f"/dev/{id}" if id not in self._devices.keys(): try: @@ -245,9 +245,9 @@ class Ratbagd(object): def get_capabilities(type, object): capabilities = [] for k in libratbag.__dict__.keys(): - if k.startswith("RATBAG_{}_CAP_".format(type.upper())) and "CAP_NONE" not in k: + if k.startswith(f"RATBAG_{type.upper()}_CAP_") and "CAP_NONE" not in k: cap = getattr(libratbag, k) - func = getattr(libratbag, "ratbag_{}_has_capability".format(type.lower())) + func = getattr(libratbag, f"ratbag_{type.lower()}_has_capability") if func(object, cap): capabilities.append(cap) return capabilities @@ -289,7 +289,7 @@ class RatbagdDevice(metaclass=MetaRatbag): vid = libratbag.ratbag_device_get_vendor_id(self._device) pid = libratbag.ratbag_device_get_product_id(self._device) version = libratbag.ratbag_device_get_product_version(self._device) - return "{}:{:04x}:{:04x}:{:d}".format(bus, vid, pid, version) + return f"{bus}:{vid:04x}:{pid:04x}:{version:d}" @property def name(self): @@ -768,7 +768,7 @@ class RatbagdMacro(metaclass=MetaRatbag): RatbagdButton.Macro.KEY_RELEASE: lambda key: "↑{}".format( ecodes.KEY[key][RatbagdMacro._PREFIX_LEN :] ), - RatbagdButton.Macro.WAIT: lambda val: "{}ms".format(val), + RatbagdButton.Macro.WAIT: lambda val: f"{val}ms", _MACRO_KEY: lambda key: "↕{}".format( ecodes.KEY[key][RatbagdMacro._PREFIX_LEN :] ), diff --git a/tools/ratbagctl.body.py.in b/tools/ratbagctl.body.py.in index 962ac5fd..c4c0e9ae 100755 --- a/tools/ratbagctl.body.py.in +++ b/tools/ratbagctl.body.py.in @@ -24,7 +24,6 @@ import evdev import sys import argparse -import re # This must be on a single line, as we replace it using merge_ratbagd.py while building. # fmt: off @@ -52,13 +51,13 @@ button_specials_strmap = { def convert_str_to_evcode(s: str) -> int: if not s.startswith("KEY_") and not s.startswith("BTN_"): - msg = "Don't know how to convert {}".format(s) + msg = f"Don't know how to convert {s}" raise argparse.ArgumentTypeError(msg) try: return evdev.ecodes.ecodes[s] except KeyError: - msg = "No such button or key `{}`".format(s) + msg = f"No such button or key `{s}`" raise argparse.ArgumentTypeError(msg) @@ -76,7 +75,7 @@ def find_device(r, args): for d in r.devices: if args.device in d.name: return d - print("Unable to find device {}".format(args.device)) + print(f"Unable to find device {args.device}") sys.exit(1) return dev @@ -86,7 +85,7 @@ def find_profile(r, args): try: p = d.profiles[args.profile_n] except IndexError: - print("Invalid profile index {}".format(args.profile_n)) + print(f"Invalid profile index {args.profile_n}") sys.exit(1) except AttributeError: p = d.active_profile @@ -101,7 +100,7 @@ def find_resolution(r, args): try: r = p.resolutions[args.resolution_n] except IndexError: - print("Invalid resolution index {}".format(args.resolution_n)) + print(f"Invalid resolution index {args.resolution_n}") sys.exit(1) except AttributeError: r = p.active_resolution @@ -116,7 +115,7 @@ def find_button(r, args): try: b = p.buttons[args.button_n] except IndexError: - print("Invalid button index {}".format(args.button_n)) + print(f"Invalid button index {args.button_n}") sys.exit(1) return b, p, d @@ -126,7 +125,7 @@ def find_led(r, args): try: l = p.leds[args.led_n] except IndexError: - print("Invalid LED index {}".format(args.led_n)) + print(f"Invalid LED index {args.led_n}") sys.exit(1) return l, p, d @@ -201,31 +200,31 @@ def print_led_caps(d, p, l, level): def print_button(d, p, b, level): - header = " " * level + "Button: {} is mapped to ".format(b.index) + header = " " * level + f"Button: {b.index} is mapped to " if b.action_type == RatbagdButton.ActionType.BUTTON: - print("{}'button {}'".format(header, b.mapping)) + print(f"{header}'button {b.mapping}'") elif b.action_type == RatbagdButton.ActionType.SPECIAL: - print("{}'{}'".format(header, button_specials_strmap[b.special])) + print(f"{header}'{button_specials_strmap[b.special]}'") elif b.action_type == RatbagdButton.ActionType.KEY: key_name = evdev.ecodes.KEY[b.key][RatbagdMacro._PREFIX_LEN :] - print("{}key '{}'".format(header, key_name)) + print(f"{header}key '{key_name}'") elif b.action_type == RatbagdButton.ActionType.MACRO: - print("{}macro '{}'".format(header, str(b.macro))) + print(f"{header}macro '{str(b.macro)}'") elif b.action_type == RatbagdButton.ActionType.NONE: - print("{}none".format(header)) + print(f"{header}none") else: - print("{}UNKNOWN".format(header)) + print(f"{header}UNKNOWN") def print_resolution(d, p, r, level): if r.resolution == (0, 0): - print(" " * level + "{}: ".format(r.index)) + print(" " * level + f"{r.index}: ") return if len(r.resolution) == 2: - dpi = "{}x{}".format(r.resolution[0], r.resolution[1]) + dpi = f"{r.resolution[0]}x{r.resolution[1]}" else: - dpi = "{}".format(r.resolution[0]) + dpi = f"{r.resolution[0]}" print( " " * level @@ -252,7 +251,7 @@ def print_profile(d, p, level): if not p.disabled: print(" " * level + "Name: {}".format(p.name or "n/a")) if p.report_rate: - print(" " * level + "Report Rate: {}Hz".format(p.report_rate)) + print(" " * level + f"Report Rate: {p.report_rate}Hz") if p.resolutions: print(" " * level + "Resolutions:") for r in p.resolutions: @@ -268,16 +267,14 @@ def print_profile(d, p, level): def print_device(d, level): p = d.profiles[0] # there should be always one - print(" " * level + "{} - {}".format(d.id, d.name)) - print(" " * level + " Model: {}".format(d.model)) - print( - " " * level + " Device Type: {}".format(d.device_type.name.capitalize()) - ) + print(" " * level + f"{d.id} - {d.name}") + print(" " * level + f" Model: {d.model}") + print(" " * level + f" Device Type: {d.device_type.name.capitalize()}") if d.firmware_version: - print(" " * level + " Firmware version: {}".format(d.firmware_version)) - 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))) + print(" " * level + f" Firmware version: {d.firmware_version}") + print(" " * level + f" Number of Buttons: {len(p.buttons)}") + print(" " * level + f" Number of Leds: {len(p.leds)}") + print(" " * level + f"Number of Profiles: {len(d.profiles)}") for p in d.profiles: print_profile(d, p, level + 2) @@ -289,7 +286,7 @@ def show_device(r, args): def show_profile(r, args): p, d = find_profile(r, args) - print("Profile {} on {} ({})".format(args.profile, d.id, d.name)) + print(f"Profile {args.profile} on {d.id} ({d.name})") print_profile(d, p, 0) @@ -377,7 +374,7 @@ def func_button_get(r, args): def func_button_action_set_none(r, args): b, p, d = find_button(r, args) - if not b.ActionType.NONE in b.action_types: + if b.ActionType.NONE not in b.action_types: raise RatbagErrorCapability("disabling buttons is not supported on this device") b.disable() @@ -403,7 +400,7 @@ def func_button_action_set_special(r, args): def func_button_action_set_key(r, args): b, p, d = find_button(r, args) - if not b.ActionType.KEY in b.action_types: + if b.ActionType.KEY not in b.action_types: raise RatbagErrorCapability("assigning a key is not supported on this device") try: @@ -417,7 +414,7 @@ def func_button_action_set_key(r, args): def func_button_action_set_macro(r, args): b, p, d = find_button(r, args) - if not b.ActionType.MACRO in b.action_types: + if b.ActionType.MACRO not in b.action_types: raise RatbagErrorCapability("assigning a macro is not supported on this device") macro_keys = args.target_macro @@ -461,9 +458,9 @@ def func_button_count(r, args): def func_dpi_get(r, args): r, p, d = find_resolution(r, args) if len(r.resolution) == 2: - print("{}x{}dpi".format(r.resolution[0], r.resolution[1])) + print(f"{r.resolution[0]}x{r.resolution[1]}dpi") else: - print("{}dpi".format(r.resolution[0])) + print(f"{r.resolution[0]}dpi") def func_dpi_get_all(r, args): @@ -1280,7 +1277,7 @@ class ParseError(Exception): pass -class RatbagParser(object): +class RatbagParser: tagged = {} def __init__(self, type, name, group=None, tag=None, func=None, help=None): @@ -1299,20 +1296,20 @@ class RatbagParser(object): ) def __repr__(self): - return "{}({})".format(type(self), self.repr_args()) + return f"{type(self)}({self.repr_args()})" def store_function(self, parser): if self.func is not None: parser.set_defaults(func=self.func) def _add_to_subparsers(self, parent): - raise ParseError("please implement _add_to_subparsers on {}".format(type(self))) + raise ParseError(f"please implement _add_to_subparsers on {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))) + raise ParseError(f"please implement _sub_parse on {type(self)}") def sub_parse(self, input_string, ns): r = self._sub_parse(input_string, ns) @@ -1320,11 +1317,11 @@ class RatbagParser(object): def print_help(self, group, prefix=""): if self.group is not None: - print("\n{} Commands:".format(self.group)) + print(f"\n{self.group} Commands:") self._print_help(prefix) def _print_help(self, prefix): - raise ParseError("please implement _print_help on {}".format(type(self))) + raise ParseError(f"please implement _print_help on {type(self)}") class RatbagParserSwitch(RatbagParser): @@ -1369,7 +1366,7 @@ class RatbagParserSwitch(RatbagParser): return self.N_access._sub_parse(self, input_string, ns) parser = argparse.ArgumentParser( - prog="{} {}".format(sys.argv[0], self.name), + prog=f"{sys.argv[0]} {self.name}", description=self.help, add_help=False, ) @@ -1387,12 +1384,12 @@ class RatbagParserSwitch(RatbagParser): string = "\n ".join(string) print(" ", string, "\n") for e in self.switch: - e.print_help(None, "{}{} ".format(prefix, self.name)) + e.print_help(None, f"{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()) + return f"switch({self.repr_args()})" class RatbagParserNAccess(RatbagParserSwitch): @@ -1412,7 +1409,7 @@ class RatbagParserNAccess(RatbagParserSwitch): def _sub_parse(self, parent, input_string, ns): parser = argparse.ArgumentParser( - prog="{} {}".format(sys.argv[0], parent.name), add_help=False + prog=f"{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 @@ -1427,11 +1424,11 @@ class RatbagParserNAccess(RatbagParserSwitch): ) def __repr__(self): - return "N_Access({})".format(self.repr_args()) + return f"N_Access({self.repr_args()})" def _print_help(self, prefix): for e in self.switch: - e.print_help(None, "{}N ".format(prefix)) + e.print_help(None, f"{prefix}N ") class RatbagParserSet(RatbagParserSwitch): @@ -1454,7 +1451,7 @@ class RatbagParserSet(RatbagParserSwitch): def _sub_parse(self, input_string, ns): parser = argparse.ArgumentParser( - prog="{} {}".format(sys.argv[0], self.name), add_help=False + prog=f"{sys.argv[0]} {self.name}", add_help=False ) # create a new subparser to handle all commands subs = parser.add_subparsers(title="COMMANDS", help=None) @@ -1467,7 +1464,7 @@ class RatbagParserSet(RatbagParserSwitch): return parser def __repr__(self): - return "set({})".format(self.repr_args()) + return f"set({self.repr_args()})" def _print_help(self, prefix): command = prefix + "{COMMAND} ..." @@ -1490,13 +1487,13 @@ class RatbagParserCommand(RatbagParser): self.store_function(parser) def __repr__(self): - return "command({})".format(self.repr_args()) + return f"command({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) + command += f" {a.metavar}" else: command += " [{}]".format("|".join(a.choices)) print(" {:<36}{}".format(command, self.help if self.help else "")) @@ -1542,7 +1539,7 @@ class RatbagParserLink(RatbagParser): try: dest = RatbagParser.tagged[self.dest] except KeyError: - raise ParseError("link '{}' points to nothing".format(self.dest)) + raise ParseError(f"link '{self.dest}' points to nothing") return dest def _add_to_subparsers(self, parent): @@ -1568,7 +1565,7 @@ classes = { } -class RatbagParserRoot(object): +class RatbagParserRoot: def __init__(self, commands): self.children = [ classes[def_parser[of_type]](**def_parser) for def_parser in commands @@ -1609,7 +1606,7 @@ class RatbagParserRoot(object): # we need a new parser or 'device_or_list' will eat all of our commands command_parser = argparse.ArgumentParser( description="command parser", - prog="{} ".format(sys.argv[0]), + prog=f"{sys.argv[0]} ", add_help=False, ) @@ -1638,8 +1635,8 @@ class RatbagParserRoot(object): return ns def print_help(self): - print("usage: {} [OPTIONS] list".format(self.parser.prog)) - print(" {} [OPTIONS] {{COMMAND}} ...\n".format(self.parser.prog)) + print(f"usage: {self.parser.prog} [OPTIONS] list") + print(f" {self.parser.prog} [OPTIONS] {{COMMAND}} ...\n") print(self.parser.description) print( """ @@ -1774,7 +1771,7 @@ def open_ratbagd(ratbagd_process=None, verbose=0): pass # the ratbag-command case except RatbagdUnavailable as e: - print("Unable to connect to ratbagd: {}".format(e)) + print(f"Unable to connect to ratbagd: {e}") return None if ratbagd_process is not None: @@ -1810,7 +1807,7 @@ def main(argv): try: f(r, cmd) except RatbagErrorCapability as e: - print("Error: {}".format(e), file=sys.stderr) + print(f"Error: {e}", file=sys.stderr) if __name__ == "__main__": diff --git a/tools/ratbagctl.devel.py.in b/tools/ratbagctl.devel.py.in index e34447ae..bb40573e 100755 --- a/tools/ratbagctl.devel.py.in +++ b/tools/ratbagctl.devel.py.in @@ -73,7 +73,7 @@ def main(argv, start_ratbagd=True): # we try to terminate it time.sleep(0.5) except RatbagErrorCapability as e: - print("Error: {}".format(e), file=sys.stderr) + print(f"Error: {e}", file=sys.stderr) finally: try: if cmd.keepalive: diff --git a/tools/ratbagctl.test.py.in b/tools/ratbagctl.test.py.in index caf8699a..10dc83c9 100755 --- a/tools/ratbagctl.test.py.in +++ b/tools/ratbagctl.test.py.in @@ -146,7 +146,7 @@ class TestRatbagCtl(unittest.TestCase): @classmethod def setProfile(cls, profile): - cls.run_ratbagctl("test_device profile active set {}".format(profile)) + cls.run_ratbagctl(f"test_device profile active set {profile}") class TestRatbagCtlList(TestRatbagCtl): @@ -467,7 +467,7 @@ class TestRatbagCtlDPI(TestRatbagCtl): self.setProfile(0) r = self.launch_good_test("test_device dpi get ") res = int(r[:-3]) + 50 # drop 'dpi' suffix - r = self.launch_good_test("test_device " + command + " {}".format(res)) + r = self.launch_good_test("test_device " + command + f" {res}") self.assertEqual(r, "") r = self.launch_good_test("test_device dpi get") self.assertEqual(int(r[:-3]), res) # drop 'dpi' suffix @@ -549,7 +549,7 @@ class TestRatbagCtlReportRate(TestRatbagCtl): command = "rate set" r = self.launch_good_test("test_device rate get") res = int(r) + 500 - r = self.launch_good_test("test_device " + command + " {}".format(res)) + r = self.launch_good_test("test_device " + command + f" {res}") self.assertEqual(r, "") r = self.launch_good_test("test_device rate get") self.assertEqual(int(r), res) diff --git a/tools/ratbagd.py b/tools/ratbagd.py index f588cb9f..b2642049 100644 --- a/tools/ratbagd.py +++ b/tools/ratbagd.py @@ -169,7 +169,7 @@ class _RatbagdDBus(GObject.GObject): object_path = "/" + ratbag1.replace(".", "/") self._object_path = object_path - self._interface = "{}.{}".format(ratbag1, interface) + self._interface = f"{ratbag1}.{interface}" try: self._proxy = Gio.DBusProxy.new_sync( @@ -185,7 +185,7 @@ class _RatbagdDBus(GObject.GObject): raise RatbagdUnavailable(e.message) if self._proxy.get_name_owner() is None: - raise RatbagdUnavailable("No one currently owns {}".format(ratbag1)) + raise RatbagdUnavailable(f"No one currently owns {ratbag1}") self._proxy.connect("g-properties-changed", self._on_properties_changed) self._proxy.connect("g-signal", self._on_signal_received) @@ -220,7 +220,7 @@ class _RatbagdDBus(GObject.GObject): # org.freedesktop.DBus.Properties.Set we need to wrap that again # into a (ssv), where v is our value's variant. # args to .Set are "interface name", "function name", value-variant - val = GLib.Variant("{}".format(type), value) + val = GLib.Variant(f"{type}", value) if readwrite: pval = GLib.Variant("(ssv)", (self._interface, property, val)) self._proxy.call_sync( @@ -243,7 +243,7 @@ class _RatbagdDBus(GObject.GObject): # appropriate RatbagError* or RatbagdDBus* exception, or GLib.Error if # it is an unexpected exception that probably shouldn't be passed up to # the UI. - val = GLib.Variant("({})".format(type), value) + val = GLib.Variant(f"({type})", value) try: res = self._proxy.call_sync( method, val, Gio.DBusCallFlags.NO_AUTO_START, 2000, None @@ -908,7 +908,7 @@ class RatbagdMacro(GObject.Object): RatbagdButton.Macro.KEY_RELEASE: lambda key: "↑{}".format( ecodes.KEY[key][RatbagdMacro._PREFIX_LEN :] ), - RatbagdButton.Macro.WAIT: lambda val: "{}ms".format(val), + RatbagdButton.Macro.WAIT: lambda val: f"{val}ms", _MACRO_KEY: lambda key: "↕{}".format( ecodes.KEY[key][RatbagdMacro._PREFIX_LEN :] ),