From b7f53effa11451f78b8d812dee6886cefee147d7 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 25 Jul 2017 13:22:37 +1000 Subject: [PATCH] ratbagd: add macro support Signed-off-by: Peter Hutterer --- ratbagd/ratbagd-button.c | 87 ++++++++++++++++++++++++++++++++++++++++ tools/ratbagctl.in | 63 +++++++++++++++++++++++++---- tools/ratbagctl.test.in | 20 +++++++-- tools/ratbagd.py | 36 ++++++++++++++--- 4 files changed, 190 insertions(+), 16 deletions(-) diff --git a/ratbagd/ratbagd-button.c b/ratbagd/ratbagd-button.c index 1c7c190a..4e7043e1 100644 --- a/ratbagd/ratbagd-button.c +++ b/ratbagd/ratbagd-button.c @@ -454,6 +454,91 @@ static int ratbagd_button_get_action_types(sd_bus *bus, return sd_bus_message_close_container(reply); } +DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbag_button_macro *, ratbag_button_macro_unref); + +static int ratbagd_button_get_macro(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + _cleanup_(ratbag_button_macro_unrefp) struct ratbag_button_macro *macro = NULL; + int r; + unsigned int idx; + + r = sd_bus_message_open_container(reply, 'a', "(uu)"); + if (r < 0) + return r; + + macro = ratbag_button_get_macro(button->lib_button); + if (!macro) + goto out; + + for (idx = 0; idx < ratbag_button_macro_get_num_events(macro); idx++) { + enum ratbag_macro_event_type type; + int value; + + type = ratbag_button_macro_get_event_type(macro, idx); + switch (type) { + case RATBAG_MACRO_EVENT_INVALID: + abort(); + break; + case RATBAG_MACRO_EVENT_NONE: + goto out; + case RATBAG_MACRO_EVENT_KEY_PRESSED: + case RATBAG_MACRO_EVENT_KEY_RELEASED: + value = ratbag_button_macro_get_event_key(macro, idx); + break; + case RATBAG_MACRO_EVENT_WAIT: + value = ratbag_button_macro_get_event_timeout(macro, idx); + break; + } + + r = sd_bus_message_append(reply, "(uu)", type, value); + if (r < 0) + return r; + } + +out: + return sd_bus_message_close_container(reply); +} + +static int ratbagd_button_set_macro(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + unsigned int type, value; + int r, idx = 0; + _cleanup_(ratbag_button_macro_unrefp) struct ratbag_button_macro *macro = NULL; + + r = sd_bus_message_enter_container(m, 'a', "(uu)"); + if (r < 0) + return r; + + macro = ratbag_button_macro_new("macro"); + while ((r = sd_bus_message_read(m, "(uu)", &type, &value)) > 0) { + r = ratbag_button_macro_set_event(macro, idx++, type, value); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + r = ratbag_button_set_macro(button->lib_button, macro); + if (r < 0) + return r; + + return sd_bus_reply_method_return(m, "u", r); +} + static int ratbagd_button_disable(sd_bus_message *m, void *userdata, sd_bus_error *error) @@ -479,9 +564,11 @@ const sd_bus_vtable ratbagd_button_vtable[] = { SD_BUS_PROPERTY("KeyMapping", "au", ratbagd_button_get_key, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("ActionType", "u", ratbagd_button_get_action_type, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("ActionTypes", "au", ratbagd_button_get_action_types, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Macro", "a(uu)", ratbagd_button_get_macro, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_METHOD("SetButtonMapping", "u", "u", ratbagd_button_set_button, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetSpecialMapping", "s", "u", ratbagd_button_set_special, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetKeyMapping", "au", "u", ratbagd_button_set_key, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetMacro", "a(uu)", "u", ratbagd_button_set_macro, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("Disable", "", "u", ratbagd_button_disable, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; diff --git a/tools/ratbagctl.in b/tools/ratbagctl.in index 67bf0525..948c18d2 100755 --- a/tools/ratbagctl.in +++ b/tools/ratbagctl.in @@ -143,17 +143,28 @@ def print_button(d, p, b, level): elif b.action_type == RatbagdButton.ACTION_TYPE_KEY: bmap = "" for m in b.key: - try: - code = evdev.ecodes.KEY[m] - except KeyError: - code = m - + code = evdev.ecodes.KEY.get(m, m) bmap = bmap + "{}".format(code) + print("{}'{}'".format(header, bmap)) elif b.action_type == RatbagdButton.ACTION_TYPE_SPECIAL: print("{}'{}'".format(header, b.special)) elif b.action_type == RatbagdButton.ACTION_TYPE_MACRO: - print("{}macro".format(header)) + bmap = "" + for t, v in b.macro: + bt = 'unknown' + if t == RatbagdButton.MACRO_KEY_PRESS: + bt = '+' + bv = evdev.ecodes.KEY.get(v, v) + elif t == RatbagdButton.MACRO_KEY_RELEASE: + bt = '-' + bv = evdev.ecodes.KEY.get(v, v) + elif t == RatbagdButton.MACRO_WAIT: + bt = 't' + bv = v + bmap = bmap + "{}{} ".format(bt, bv) + + print("{}macro '{}'".format(header, bmap[:-1])) elif b.action_type == RatbagdButton.ACTION_TYPE_NONE: print("{}none".format(header)) else: @@ -306,6 +317,44 @@ def func_button_action_set_button(r, args): b.mapping = args.target_button d.commit() +def func_button_action_set_macro(r, args): + b, p, d = find_button(r, args) + macro_keys = args.target_macro + macro = [] + 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 + d.commit() + def func_button_count(r, args): p, d = find_profile(r, args) @@ -825,7 +874,7 @@ parser_def = [ nargs: argparse.REMAINDER, }, ], - func: func_dummy, # FIXME: func_button_action_set_macro, + func: func_button_action_set_macro, }, ] }, diff --git a/tools/ratbagctl.test.in b/tools/ratbagctl.test.in index 8424f4b2..b238e050 100755 --- a/tools/ratbagctl.test.in +++ b/tools/ratbagctl.test.in @@ -377,7 +377,7 @@ class TestRatbgagCtlButton(TestRatbagCtl): # FIXME: special maps to UNKNOWN???? self.assertEqual(r, "Button: 2 type unknown is mapped to UNKNOWN") r = self.launch_good_test("button 3 get test_device") - self.assertEqual(r, "Button: 3 type unknown is mapped to macro") + self.assertEqual(r, "Button: 3 type unknown is mapped to macro '+KEY_B -KEY_B t300'") self.launch_fail_test("button 1 get 10 test_device") self.launch_fail_test("button 10 get test_device") self.launch_fail_test("button 1 get test_device X") @@ -408,10 +408,22 @@ class TestRatbgagCtlButton(TestRatbagCtl): pass # FIXME - @unittest.skip("Button action set macro not implemented") def test_button_n_action_set_macro(self): - pass - # FIXME + command = "button 2 action set macro " + self.launch_good_test(command + '+KEY_B KEY_A -KEY_B KEY_C test_device') + r = self.launch_good_test("button 2 get test_device") + self.assertEqual(r, "Button: 2 type unknown is mapped to macro '+KEY_B +KEY_A -KEY_A -KEY_B +KEY_C -KEY_C'") + self.launch_good_test(command + 't100 t200 test_device') + r = self.launch_good_test("button 2 get test_device") + self.assertEqual(r, "Button: 2 type unknown is mapped to macro 't100 t200'") + self.launch_good_test(command + 'KeY_z test_device') + r = self.launch_good_test("button 2 get test_device") + self.assertEqual(r, "Button: 2 type unknown is mapped to macro '+KEY_Z -KEY_Z'") + self.launch_good_test(command + ' test_device') + r = self.launch_good_test("button 2 get test_device") + self.assertEqual(r, "Button: 2 type unknown is mapped to macro ''") + self.launch_fail_test(command) + self.launch_fail_test(command + " KEY_A test_device X") def test_prefix_button(self): r = self.launch_good_test("Profile 2 Button 3 get test_device") diff --git a/tools/ratbagd.py b/tools/ratbagd.py index 56036b28..fd83f2c0 100644 --- a/tools/ratbagd.py +++ b/tools/ratbagd.py @@ -147,7 +147,7 @@ class _RatbagdDBus(GObject.GObject): return p.unpack() return p - def _set_dbus_property(self, property, type, value): + def _set_dbus_property(self, property, type, value, readwrite=True): # Sets a cached property on the bus. # Take our real value and wrap it into a variant. To call @@ -155,10 +155,11 @@ class _RatbagdDBus(GObject.GObject): # 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) - pval = GLib.Variant("(ssv)".format(type), (self._interface, property, val)) - self._proxy.call_sync("org.freedesktop.DBus.Properties.Set", - pval, Gio.DBusCallFlags.NO_AUTO_START, - 500, None) + if readwrite: + pval = GLib.Variant("(ssv)".format(type), (self._interface, property, val)) + self._proxy.call_sync("org.freedesktop.DBus.Properties.Set", + pval, Gio.DBusCallFlags.NO_AUTO_START, + 500, None) # This is our local copy, so we don't have to wait for the async # update @@ -435,6 +436,10 @@ class RatbagdButton(_RatbagdDBus): ACTION_TYPE_KEY = 3 ACTION_TYPE_MACRO = 4 + MACRO_KEY_PRESS = 1 + MACRO_KEY_RELEASE = 2 + MACRO_WAIT = 3 + def __init__(self, object_path): _RatbagdDBus.__init__(self, "Button", object_path) @@ -463,6 +468,27 @@ class RatbagdButton(_RatbagdDBus): self._set_dbus_property("ButtonMapping", "u", button) return ret + @GObject.Property + def macro(self): + """A list of (type, value) tuples that form the currently set macro, + where type is either RatbagdButton.KEY_PRESS, RatbagdButton.KEY_RELEASE + or RatbagdButton.MACRO_WAIT and the value is the keycode as specified + in linux/input.h for key related types, or the timeout in milliseconds.""" + return self._get_dbus_property("Macro") + + @macro.setter + def macro(self, macro): + """Set the macro to the given macro. Note that the type must be one of + RatbagdButton.KEY_PRESS, RatbagdButton.KEY_RELEASE or + RatbagdButton.MACRO_WAIT and the keycodes must be as specified in + linux/input.h, and the timeout must be in milliseconds. + + @param macro A list of (type, value) tuples to form the new macro. + """ + ret = self._dbus_call("SetMacro", "a(uu)", macro) + self._set_dbus_property("Macro", "a(uu)", macro, readwrite=False) + return ret + @GObject.Property def special(self): """A string of the current special mapping, if mapped to special."""