mirror of
https://github.com/libratbag/libratbag.git
synced 2026-11-07 23:23:52 +01:00
ratbagd: add macro support
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
committed by
Benjamin Tissoires
parent
129551f3f4
commit
b7f53effa1
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user