From 818e66585376db8dfdf1a695a49dce6f801466ed Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 26 Jul 2019 16:01:10 +1000 Subject: [PATCH] ratbagd: add ability to load json for test devices Signed-off-by: Peter Hutterer --- circle.yml | 6 +- meson.build | 4 + ratbagd/ratbagd-json.c | 562 +++++++++++++++++++++++++++++++++++++++++ ratbagd/ratbagd-json.h | 28 ++ ratbagd/ratbagd-test.c | 69 ++++- ratbagd/ratbagd-test.h | 3 + ratbagd/ratbagd.c | 1 + src/libratbag-test.h | 2 +- 8 files changed, 665 insertions(+), 10 deletions(-) create mode 100644 ratbagd/ratbagd-json.c create mode 100644 ratbagd/ratbagd-json.h diff --git a/circle.yml b/circle.yml index 00a62688..9f9fb62f 100644 --- a/circle.yml +++ b/circle.yml @@ -84,7 +84,7 @@ fedora_prep_cache: &fedora_prep_cache steps: - run: name: Initializing Fedora dnf cache - command: dnf install -y --downloadonly libsolv tree git gcc gcc-c++ meson check-devel libudev-devel libevdev-devel valgrind python3-gobject python3-evdev glib2-devel python3-lxml libunistring-devel dbus-daemon + command: dnf install -y --downloadonly libsolv tree git gcc gcc-c++ meson check-devel libudev-devel libevdev-devel valgrind python3-gobject python3-evdev glib2-devel python3-lxml libunistring-devel dbus-daemon json-glib-devel - persist_to_workspace: root: /var/cache/ paths: @@ -100,7 +100,7 @@ fedora_install: &fedora_install command: | echo keepcache=1 >> /etc/dnf/dnf.conf dnf upgrade -y libsolv - dnf install -y tree git gcc gcc-c++ meson check-devel libudev-devel libevdev-devel valgrind python3-gobject python3-evdev glib2-devel python3-lxml python3-devel swig libunistring-devel dbus-daemon + dnf install -y tree git gcc gcc-c++ meson check-devel libudev-devel libevdev-devel valgrind python3-gobject python3-evdev glib2-devel python3-lxml python3-devel swig libunistring-devel dbus-daemon json-glib-devel sed -i 's/systemd//' /etc/nsswitch.conf fedora_settings: &fedora_settings @@ -130,7 +130,7 @@ ubuntu_settings: &ubuntu_settings apt-get remove -y libnss-systemd add-apt-repository universe apt-get update - apt-get install -y tree git gcc g++ pkg-config meson check libudev-dev libevdev-dev libsystemd-dev valgrind python3-gi python3-evdev libglib2.0-dev python3-lxml python3-dev swig systemd libunistring-dev + apt-get install -y tree git gcc g++ pkg-config meson check libudev-dev libevdev-dev libsystemd-dev valgrind python3-gi python3-evdev libglib2.0-dev python3-lxml python3-dev swig systemd libunistring-dev libjson-glib-dev - checkout - *start_dbus - *build_and_test diff --git a/meson.build b/meson.build index 9b4ed700..b2e792b1 100644 --- a/meson.build +++ b/meson.build @@ -65,6 +65,7 @@ pkgconfig = import('pkgconfig') dep_udev = dependency('libudev') dep_libevdev = dependency('libevdev') dep_glib = dependency('glib-2.0') +dep_json_glib = dependency('json-glib-1.0') dep_lm = cc.find_library('m') dep_unistring = cc.find_library('unistring') @@ -183,6 +184,7 @@ deps_libratbag = [ dep_udev, dep_libevdev, dep_glib, + dep_json_glib, dep_libutil, dep_libhidpp, ] @@ -394,6 +396,8 @@ src_ratbagd = [ 'ratbagd/ratbagd-profile.c', 'ratbagd/ratbagd-resolution.c', 'ratbagd/ratbagd-test.c', + 'ratbagd/ratbagd-json.c', + 'ratbagd/ratbagd-json.h', 'src/libratbag-util.h', 'src/libratbag-util.c', ] diff --git a/ratbagd/ratbagd-json.c b/ratbagd/ratbagd-json.c new file mode 100644 index 00000000..c24ff051 --- /dev/null +++ b/ratbagd/ratbagd-json.c @@ -0,0 +1,562 @@ +/* + * Copyright © 2019 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. + */ + +/* JSON parser for ratbag test devices. The JSON format is not fixed and may + change at any time, but basically looks like this: + + { + "profiles": [ + { + "is_active": bool, + "is_default": bool, + "is_disabled": bool, + "rate": int, + "report_rates": [ int, ...], + "capabilities": [int, ...] # profile capabilities + "resolutions": [ + { + "xres": int, + "yres": int, + "dpi_min", int, + "dpi_max", int, + "is_active": bool, + "is_default": bool, + "capabilities": [int, ...] # resolution capabilities + } + ] + "buttons": [ + "action_type": "" # none, button, key, special, macro, unknown + "button": int, # if button + "key": int, # if key + "special": "", # if special, for values see see special_lookup() + "macro": ["+B", "-B", "t400"] # if macro. uppercase only + + ] + "leds": [ ] + }, + { + ... next profile ... + } + ] + } + + ratbagd uses a minimum sane device (1 profile, 1 resolution, etc.) and any + JSON instructions get merged into that device. Which means you usually + only need to set those bits you care about being checked. + */ + +#include "libratbag-util.h" +#include "ratbagd-json.h" +#include "ratbagd-test.h" +#include +#include + +/* Hack because we don't do proper data passing */ +static int num_resolutions; +static int num_buttons; +static int num_leds; + +static int parse_error = 0; + +#define parser_error(element_) { \ + log_error("json: parser error: %s:%d: element '%s'\n", __func__, __LINE__, (element_)); \ + parse_error = -EINVAL; \ + goto out; \ +} + +static void parse_resolution_member(JsonObject *obj, const gchar *name, + JsonNode *node, void *data) +{ + struct ratbag_test_resolution *resolution = data; + + if (streq(name, "xres")) { + gboolean v = json_object_get_int_member(obj, name); + + if (v < 0 || v > 20000) + parser_error("xres"); + + resolution->xres = v; + log_verbose("json: xres: %d\n", v); + } else if (streq(name, "yres")) { + gboolean v = json_object_get_int_member(obj, name); + + if (v < 0 || v > 20000) + parser_error("yres"); + + resolution->yres = v; + log_verbose("json: yres: %d\n", v); + } else if (streq(name, "dpi_min")) { + gboolean v = json_object_get_int_member(obj, name); + + if (v < 0 || v > 20000) + parser_error("dpi_min"); + + resolution->dpi_min = v; + log_verbose("json: dpi_min: %d\n", v); + } else if (streq(name, "dpi_max")) { + gboolean v = json_object_get_int_member(obj, name); + + if (v < 0 || v > 20000) + parser_error("dpi_min"); + + resolution->dpi_max = v; + log_verbose("json: dpi_max: %d\n", v); + } else if (streq(name, "is_active")) { + gboolean v = json_object_get_boolean_member(obj, name); + resolution->active = v; + log_verbose("json: is_active: %d\n", v); + } else if (streq(name, "is_default")) { + gboolean v = json_object_get_boolean_member(obj, name); + resolution->dflt = v; + log_verbose("json: is_default: %d\n", v); + } else if (streq(name, "capabilities")) { + JsonArray *a = json_object_get_array_member(obj, name); + + assert(json_array_get_length(a) < ARRAY_LENGTH(resolution->caps)); + for (size_t s = 0; s < json_array_get_length(a); s++) { + int v = json_array_get_int_element(a, s); + if (v > RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION) + parser_error("capabilities"); + resolution->caps[s] = v; + } + + log_verbose("json: caps: %d %d %d %d %d...\n", + resolution->caps[0], + resolution->caps[1], + resolution->caps[2], + resolution->caps[3], + resolution->caps[4]); + } else { + log_error("json: unknown resolution key '%s'\n", name); + parse_error = -EINVAL; + } +out: + return; +} + +static void parse_resolution(JsonNode *node, + struct ratbag_test_resolution *resolution) +{ + JsonObject *obj = json_node_get_object(node); + + json_object_foreach_member(obj, parse_resolution_member, resolution); +} + +static void parse_led_member(JsonObject *obj, const gchar *name, + JsonNode *node, void *data) +{ + struct ratbag_test_led *led = data; + + if (streq(name, "type")) { + gboolean v = json_object_get_int_member(obj, name); + + if (v < 0 || v > RATBAG_LED_TYPE_WHEEL) + parser_error("type"); + + led->type = v; + log_verbose("json: type: %d\n", v); + } else if (streq(name, "mode")) { + gboolean v = json_object_get_int_member(obj, name); + + if (v < 0 || v > RATBAG_LED_BREATHING) + parser_error("mode"); + + led->mode = v; + log_verbose("json: mode: %d\n", v); + } else if (streq(name, "duration")) { + gboolean v = json_object_get_int_member(obj, name); + + if (v < 0 || v > 10000) + parser_error("duration"); + + led->ms = v; + log_verbose("json: duration: %d\n", v); + } else if (streq(name, "brightness")) { + gboolean v = json_object_get_int_member(obj, name); + + if (v < 0 || v > 100) + parser_error("brightness"); + + led->brightness = v; + log_verbose("json: brightness: %d\n", v); + } else if (streq(name, "color")) { + JsonArray *a = json_object_get_array_member(obj, name); + + assert(json_array_get_length(a) == 3); + led->color.red = json_array_get_int_element(a, 0); + led->color.green = json_array_get_int_element(a, 1); + led->color.blue = json_array_get_int_element(a, 2); + log_verbose("json: color: %02x%02x%02x\n", + led->color.red, + led->color.green, + led->color.blue); + } else { + log_error("json: unknown led key '%s'\n", name); + parse_error = -EINVAL; + } +out: + return; +} + +static void parse_led(JsonNode *node, struct ratbag_test_led *led) +{ + JsonObject *obj = json_node_get_object(node); + + json_object_foreach_member(obj, parse_led_member, led); +} + +static enum ratbag_button_action_special +special_lookup(const char *string) +{ + const struct { + const char *key; + enum ratbag_button_action_special special; + } lut[] = { + { "invalid", RATBAG_BUTTON_ACTION_SPECIAL_INVALID}, + { "unknown", RATBAG_BUTTON_ACTION_SPECIAL_UNKNOWN}, + { "doubleclick", RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK}, + { "wheel-left", RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT}, + { "wheel-right", RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT}, + { "wheel-up", RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP}, + { "wheel-down", RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN}, + { "ratchet-mode-switch", RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH}, + { "resolution-cycle-up", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP}, + { "resolution-cycledown", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_DOWN}, + { "resolution-up", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP}, + { "resolution-down", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN}, + { "resolution-alternate", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE}, + { "resolution-default", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT}, + { "profile-cycle-up", RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP}, + { "profile-cycle-down", RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_DOWN}, + { "profile-up", RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP}, + { "profile-down", RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN}, + { "second-mode", RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE}, + { "battery-level", RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL}, + }; + + for (size_t i = 0; i < ARRAY_LENGTH(lut); i++) { + if (streq(string, lut[i].key)) + return lut[i].special; + } + + parser_error("special"); + +out: + return RATBAG_BUTTON_ACTION_SPECIAL_INVALID; +} + +static enum ratbag_button_action_type +action_type_lookup(const char *string) +{ + const struct { + const char *key; + enum ratbag_button_action_type type; + } lut[] = { + { "none", RATBAG_BUTTON_ACTION_TYPE_NONE}, + { "button", RATBAG_BUTTON_ACTION_TYPE_BUTTON}, + { "special", RATBAG_BUTTON_ACTION_TYPE_SPECIAL}, + { "key", RATBAG_BUTTON_ACTION_TYPE_KEY}, + { "macro", RATBAG_BUTTON_ACTION_TYPE_MACRO}, + { "unknown", RATBAG_BUTTON_ACTION_TYPE_UNKNOWN}, + }; + + for (size_t i = 0; i < ARRAY_LENGTH(lut); i++) { + if (streq(string, lut[i].key)) + return lut[i].type; + } + + parser_error("action_type"); + +out: + return RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; +} + +static inline struct ratbag_test_macro_event +parse_macro(const char *m) +{ + struct ratbag_test_macro_event event = { + .type = RATBAG_MACRO_EVENT_INVALID, + .value = 0, + }; + char keyname[32]; + + if (strlen(m) < 2) + goto out; + + switch(m[0]) { + case '+': + event.type = RATBAG_MACRO_EVENT_KEY_PRESSED; + snprintf(keyname, sizeof(keyname), "KEY_%s", m + 1); + event.value = libevdev_event_code_from_name(EV_KEY, + keyname); + log_verbose("json: macro: +%s\n", keyname); + break; + case '-': + event.type = RATBAG_MACRO_EVENT_KEY_RELEASED; + snprintf(keyname, sizeof(keyname), "KEY_%s", m + 1); + event.value = libevdev_event_code_from_name(EV_KEY, + keyname); + log_verbose("json: macro: -%s\n", keyname); + break; + case 't': + event.type = RATBAG_MACRO_EVENT_WAIT; + event.value = atoi(m+1); + log_verbose("json: macro: t%d\n", event.value); + break; + default: + parser_error("macro"); + break; + } + +out: + return event; +} + + +static void parse_button_member(JsonObject *obj, const gchar *name, + JsonNode *node, void *data) +{ + struct ratbag_test_button *button = data; + + if (streq(name, "action_type")) { + const gchar* v = json_object_get_string_member(obj, name); + button->action_type = action_type_lookup(v); + log_verbose("json: action_type: %s\n", v); + } else if (streq(name, "button")) { + gint v = json_object_get_int_member(obj, name); + + if (v < 0 || v > 32) + parser_error("button"); + + button->button = v; + log_verbose("json: button: %d\n", v); + } else if (streq(name, "key")) { + gint v = json_object_get_int_member(obj, name); + + if (v < 0 || v > KEY_MAX) + parser_error("key"); + + button->key = v; + log_verbose("json: key: %d\n", v); + } else if (streq(name, "special")) { + const gchar *v = json_object_get_string_member(obj, name); + button->special = special_lookup(v); + log_verbose("json: special: %s\n", v); + } else if (streq(name, "macro")) { + JsonArray *a = json_object_get_array_member(obj, name); + + assert(json_array_get_length(a) < ARRAY_LENGTH(button->macro)); + for (size_t s = 0; s < json_array_get_length(a); s++) { + const gchar *v = json_array_get_string_element(a, s); + button->macro[s] = parse_macro(v); + } + } else { + log_error("json: unknown button key '%s'\n", name); + parse_error = -EINVAL; + } +out: + return; +} + +static void parse_button(JsonNode *node, struct ratbag_test_button *button) +{ + JsonObject *obj = json_node_get_object(node); + + json_object_foreach_member(obj, parse_button_member, button); +} + +static void parse_profile_member(JsonObject *obj, const gchar *name, + JsonNode *node, void *data) +{ + struct ratbag_test_profile *profile = data; + + if (streq(name, "name")) { + const char *v = json_object_get_string_member(obj, name); + + if (v == NULL) + parser_error("name"); + + free(profile->name); + profile->name = strdup_safe(v); + log_verbose("name: %s\n", v); + } else if (streq(name, "is_default")) { + gboolean v = json_object_get_boolean_member(obj, name); + profile->dflt = v; + log_verbose("json: is_default: %d\n", v); + } else if (streq(name, "is_active")) { + gboolean v = json_object_get_boolean_member(obj, name); + profile->active = v; + log_verbose("json: is_active: %d\n", v); + } else if (streq(name, "is_disabled")) { + gboolean v = json_object_get_boolean_member(obj, name); + profile->disabled = v; + log_verbose("json: is_disabled: %d\n", v); + } else if (streq(name, "rate")) { + gboolean v = json_object_get_int_member(obj, name); + + if (v < 0 || v > 20000) + parser_error("rate"); + + profile->hz = v; + log_verbose("json: rate: %d\n", v); + } else if (streq(name, "report_rates")) { + JsonArray *a = json_object_get_array_member(obj, name); + assert(json_array_get_length(a) < ARRAY_LENGTH(profile->report_rates)); + for (size_t s = 0; s < json_array_get_length(a); s++) { + int v = json_array_get_int_element(a, s); + if (v < 0 || v > 20000) + parser_error("report_rate"); + profile->report_rates[s] = v; + } + + log_verbose("json: report rates: %d %d %d %d %d\n", + profile->report_rates[0], + profile->report_rates[1], + profile->report_rates[2], + profile->report_rates[3], + profile->report_rates[4]); + } else if (streq(name, "capabilities")) { + JsonArray *a = json_object_get_array_member(obj, name); + + assert(json_array_get_length(a) < ARRAY_LENGTH(profile->caps)); + for (size_t s = 0; s < json_array_get_length(a); s++) { + int v = json_array_get_int_element(a, s); + profile->caps[s] = v; + } + + log_verbose("json: caps: %d %d %d %d %d...\n", + profile->caps[0], + profile->caps[1], + profile->caps[2], + profile->caps[3], + profile->caps[4]); + } else if (streq(name, "resolutions")) { + JsonArray *a = json_object_get_array_member(obj, name); + GList *list = json_array_get_elements(a); + GList *l = list; + int idx = 0; + while (l != NULL) { + log_verbose("json: processing resolution %d\n", idx); + parse_resolution(l->data, &profile->resolutions[idx]); + l = g_list_next(l); + idx++; + } + g_list_free(list); + num_resolutions = max(idx, num_resolutions); + } else if (streq(name, "leds")) { + JsonArray *a = json_object_get_array_member(obj, name); + GList *list = json_array_get_elements(a); + GList *l = list; + int idx = 0; + while (l != NULL) { + log_verbose("json: processing LED %d\n", idx); + parse_led(l->data, &profile->leds[idx]); + l = g_list_next(l); + idx++; + } + g_list_free(list); + num_leds = max(idx, num_leds); + } else if (streq(name, "buttons")) { + JsonArray *a = json_object_get_array_member(obj, name); + GList *list = json_array_get_elements(a); + GList *l = list; + int idx = 0; + while (l != NULL) { + log_verbose("json: processing button %d\n", idx); + parse_button(l->data, &profile->buttons[idx]); + l = g_list_next(l); + idx++; + } + g_list_free(list); + num_buttons = max(idx, num_buttons); + } else { + log_error("json: unknown profile key '%s'\n", name); + parse_error = -EINVAL; + } +out: + return; +} + +static void parse_profile(JsonNode *node, + struct ratbag_test_profile *profile) +{ + JsonObject *obj = json_node_get_object(node); + + json_object_foreach_member(obj, parse_profile_member, profile); +} + +/* declared here because this isn't really public API, we just need to + * access it from the tests */ +int ratbagd_parse_json(const char *data, struct ratbag_test_device *device) +{ + g_autoptr(JsonParser) parser = NULL; + JsonNode *root; + JsonObject *obj; + JsonArray *arr; + int r = -EINVAL; + g_autoptr(GError) error = NULL; + + error = 0; + parser = json_parser_new(); + if (!json_parser_load_from_data(parser, data, strlen(data), &error)) { + log_error("Failed to load JSON: %s\n", error->message); + r = -EINVAL; + goto out; + } + + log_verbose("json: data: %s\n", data); + + root = json_parser_get_root(parser); + if (JSON_NODE_TYPE(root) != JSON_NODE_OBJECT) + parser_error("root"); + + obj = json_node_get_object(root); + arr = json_object_get_array_member(obj, "profiles"); + if (!arr) + parser_error("profiles"); + + /* Our test device is preloaded with sane defaults, let's keep those */ + num_resolutions = device->num_resolutions; + num_buttons = device->num_buttons; + num_leds = device->num_leds; + + g_autoptr(GList) list = json_array_get_elements(arr); + GList *l = list; + int idx = 0; + while (l != NULL) { + log_verbose("json: processing profile %d\n", idx); + parse_profile(l->data, &device->profiles[idx]); + if (parse_error != 0) + goto out;; + l = g_list_next(l); + idx++; + } + device->num_profiles = idx; + device->num_resolutions = num_resolutions; + device->num_buttons = num_buttons; + device->num_leds = num_leds; + + r = 0; +out: + return r; +} diff --git a/ratbagd/ratbagd-json.h b/ratbagd/ratbagd-json.h new file mode 100644 index 00000000..68944a46 --- /dev/null +++ b/ratbagd/ratbagd-json.h @@ -0,0 +1,28 @@ +/* + * Copyright © 2019 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. + */ + +#pragma once + +#include "libratbag-test.h" + +int ratbagd_parse_json(const char *data, struct ratbag_test_device *device); diff --git a/ratbagd/ratbagd-test.c b/ratbagd/ratbagd-test.c index 0b11ff45..3e8a28d1 100644 --- a/ratbagd/ratbagd-test.c +++ b/ratbagd/ratbagd-test.c @@ -30,6 +30,7 @@ #include #include "libratbag-test.h" +#include "ratbagd-json.h" /* A pre-setup sane device. Use for sanity testing by toggling the various * error conditions. @@ -173,14 +174,12 @@ static const struct ratbag_test_device ratbagd_test_device_descr = { .destroyed_data = NULL, }; - -int ratbagd_reset_test_device(sd_bus_message *m, - void *userdata, - sd_bus_error *error) +static int load_test_device(sd_bus_message *m, + struct ratbagd *ctx, + const struct ratbag_test_device *source) { static int count; static struct ratbagd_device *ratbagd_test_device = NULL; - struct ratbagd *ctx = userdata; struct ratbag_device *device; int r; char devicename[64]; @@ -196,7 +195,7 @@ int ratbagd_reset_test_device(sd_bus_message *m, NULL); } - device = ratbag_device_new_test_device(ctx->lib_ctx, &ratbagd_test_device_descr); + device = ratbag_device_new_test_device(ctx->lib_ctx, source); snprintf(devicename, sizeof(devicename), "testdevice%d", count++); r = ratbagd_device_new(&ratbagd_test_device, ctx, devicename, device); @@ -222,6 +221,64 @@ int ratbagd_reset_test_device(sd_bus_message *m, return 0; } +int ratbagd_reset_test_device(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd *ctx = userdata; + + return load_test_device(m, ctx, &ratbagd_test_device_descr); +} + +static const struct ratbag_test_device default_device_descr = { + .num_profiles = 1, + .num_resolutions = 1, + .num_buttons = 1, + .num_leds = 0, + .profiles = { + { + .name = NULL, + .buttons = { + { .button_type = RATBAG_BUTTON_TYPE_LEFT, + .action_type = RATBAG_BUTTON_ACTION_TYPE_BUTTON, + .button = 0 }, + }, + .resolutions = { + { .xres = 1000, .yres = 1000, + .dpi_min = 1000, .dpi_max = 1000}, + }, + .disabled = false, + .active = true, + .dflt = true, + .hz = 1000, + .report_rates = {1000}, + }, + }, + .destroyed = NULL, + .destroyed_data = NULL, +}; + + +int ratbagd_load_test_device(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd *ctx = userdata; + struct ratbag_test_device td = default_device_descr; + char *data; + int r = 0; + + CHECK_CALL(sd_bus_message_read(m, "s", &data)); + + r = ratbagd_parse_json(data, &td); + if (r != 0) { + log_error("Failed to parse JSON data\n"); + } else { + r = load_test_device(m, ctx, &td); + } + return sd_bus_reply_method_return(m, "i", r); +} + #endif void ratbagd_init_test_device(struct ratbagd *ctx) diff --git a/ratbagd/ratbagd-test.h b/ratbagd/ratbagd-test.h index 3fa8825f..432e915c 100644 --- a/ratbagd/ratbagd-test.h +++ b/ratbagd/ratbagd-test.h @@ -32,4 +32,7 @@ void ratbagd_init_test_device(struct ratbagd *ctx); int ratbagd_reset_test_device(sd_bus_message *m, void *userdata, sd_bus_error *error); +int ratbagd_load_test_device(sd_bus_message *m, + void *userdata, + sd_bus_error *error); #endif /* RATBAG_DEVELOPER_EDITION */ diff --git a/ratbagd/ratbagd.c b/ratbagd/ratbagd.c index 4596e2d9..f1b5be8d 100644 --- a/ratbagd/ratbagd.c +++ b/ratbagd/ratbagd.c @@ -169,6 +169,7 @@ static const sd_bus_vtable ratbagd_vtable[] = { SD_BUS_PROPERTY("Devices", "ao", ratbagd_get_devices, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), #ifdef RATBAG_DEVELOPER_EDITION SD_BUS_METHOD("ResetTestDevice", "", "u", ratbagd_reset_test_device, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("LoadTestDevice", "s", "i", ratbagd_load_test_device, SD_BUS_VTABLE_UNPRIVILEGED), #endif /* RATBAG_DEVELOPER_EDITION */ SD_BUS_VTABLE_END, }; diff --git a/src/libratbag-test.h b/src/libratbag-test.h index 6c72fc52..325dc0a5 100644 --- a/src/libratbag-test.h +++ b/src/libratbag-test.h @@ -73,7 +73,7 @@ struct ratbag_test_led { }; struct ratbag_test_profile { - const char *name; + char *name; struct ratbag_test_button buttons[RATBAG_TEST_MAX_BUTTONS]; struct ratbag_test_resolution resolutions[RATBAG_TEST_MAX_RESOLUTIONS]; struct ratbag_test_led leds[RATBAG_TEST_MAX_LEDS];