/* * Copyright © 2011 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of Red Hat * not be used in advertising or publicity pertaining to distribution * of the software without specific, written prior permission. Red * Hat makes no representations about the suitability of this software * for any purpose. It is provided "as is" without express or implied * warranty. * * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Authors: * Peter Hutterer (peter.hutterer@redhat.com) */ #include "config.h" #define _GNU_SOURCE 1 #include "libwacomint.h" #include "util-strings.h" #include #include #include #include #include #include #include #include #define TABLET_SUFFIX ".tablet" #define STYLUS_SUFFIX ".stylus" #define FEATURES_GROUP "Features" #define DEVICE_GROUP "Device" #define BUTTONS_GROUP "Buttons" static WacomClass libwacom_class_string_to_enum(const char *class) { if (class == NULL || *class == '\0') return WCLASS_UNKNOWN; if (g_str_equal(class, "Intuos3")) return WCLASS_INTUOS3; if (g_str_equal(class, "Intuos4")) return WCLASS_INTUOS4; if (g_str_equal(class, "Intuos5")) return WCLASS_INTUOS5; if (g_str_equal(class, "Cintiq")) return WCLASS_CINTIQ; if (g_str_equal(class, "Bamboo")) return WCLASS_BAMBOO; if (g_str_equal(class, "Graphire")) return WCLASS_GRAPHIRE; if (g_str_equal(class, "Intuos")) return WCLASS_INTUOS; if (g_str_equal(class, "Intuos2")) return WCLASS_INTUOS2; if (g_str_equal(class, "ISDV4")) return WCLASS_ISDV4; if (g_str_equal(class, "PenDisplay")) return WCLASS_PEN_DISPLAYS; if (g_str_equal(class, "Remote")) return WCLASS_REMOTE; return WCLASS_UNKNOWN; } static WacomStylusType type_from_str (const char *type) { if (type == NULL) return WSTYLUS_UNKNOWN; if (g_str_equal(type, "General")) return WSTYLUS_GENERAL; if (g_str_equal(type, "Inking")) return WSTYLUS_INKING; if (g_str_equal(type, "Airbrush")) return WSTYLUS_AIRBRUSH; if (g_str_equal(type, "Classic")) return WSTYLUS_CLASSIC; if (g_str_equal(type, "Marker")) return WSTYLUS_MARKER; if (g_str_equal(type, "Stroke")) return WSTYLUS_STROKE; if (g_str_equal(type, "Puck")) return WSTYLUS_PUCK; if (g_str_equal(type, "3D")) return WSTYLUS_3D; if (g_str_equal(type, "Mobile")) return WSTYLUS_MOBILE; return WSTYLUS_UNKNOWN; } static WacomEraserType eraser_type_from_str (const char *type) { if (type == NULL) return WACOM_ERASER_NONE; if (g_str_equal(type, "None")) return WACOM_ERASER_NONE; if (g_str_equal(type, "Invert")) return WACOM_ERASER_INVERT; if (g_str_equal(type, "Button")) return WACOM_ERASER_BUTTON; return WACOM_ERASER_UNKNOWN; } WacomBusType bus_from_str (const char *str) { if (g_str_equal(str, "usb")) return WBUSTYPE_USB; if (g_str_equal(str, "serial")) return WBUSTYPE_SERIAL; if (g_str_equal(str, "bluetooth")) return WBUSTYPE_BLUETOOTH; if (g_str_equal(str, "i2c")) return WBUSTYPE_I2C; return WBUSTYPE_UNKNOWN; } const char * bus_to_str (WacomBusType bus) { switch (bus) { case WBUSTYPE_UNKNOWN: g_assert_not_reached(); break; case WBUSTYPE_USB: return "usb"; case WBUSTYPE_SERIAL: return "serial"; case WBUSTYPE_BLUETOOTH: return "bluetooth"; case WBUSTYPE_I2C: return "i2c"; } g_assert_not_reached (); } char * make_match_string (const char *name, WacomBusType bus, int vendor_id, int product_id) { return g_strdup_printf("%s:%04x:%04x%s%s", bus_to_str (bus), vendor_id, product_id, name ? ":" : "", name ? name : ""); } static gboolean match_from_string(const char *str, WacomBusType *bus, int *vendor_id, int *product_id, char **name) { int rc = 1, len = 0; char busstr[64]; rc = sscanf(str, "%63[^:]:%x:%x:%n", busstr, vendor_id, product_id, &len); if (len > 0) { /* Grumble grumble scanf handling of %n */ *name = g_strdup(str+len); } else if (rc == 3) { *name = NULL; } else { return FALSE; } *bus = bus_from_str (busstr); return TRUE; } static WacomMatch * libwacom_match_from_string(const char *matchstr) { char *name = NULL; int vendor_id, product_id; WacomBusType bus; WacomMatch *match; if (matchstr == NULL) return NULL; if (g_str_equal(matchstr, GENERIC_DEVICE_MATCH)) { name = NULL; bus = WBUSTYPE_UNKNOWN; vendor_id = 0; product_id = 0; } else if (!match_from_string(matchstr, &bus, &vendor_id, &product_id, &name)) { DBG("failed to match '%s' for product/vendor IDs. Skipping.\n", matchstr); return NULL; } match = libwacom_match_new(name, bus, vendor_id, product_id); free(name); return match; } static gboolean libwacom_matchstr_to_paired(WacomDevice *device, const char *matchstr) { char *name = NULL; int vendor_id, product_id; WacomBusType bus; g_return_val_if_fail(device->paired == NULL, FALSE); if (!match_from_string(matchstr, &bus, &vendor_id, &product_id, &name)) { DBG("failed to match '%s' for product/vendor IDs. Ignoring.\n", matchstr); return FALSE; } device->paired = libwacom_match_new(name, bus, vendor_id, product_id); free(name); return TRUE; } static void libwacom_parse_stylus_keyfile(WacomDeviceDatabase *db, const char *path) { GKeyFile *keyfile; GError *error = NULL; char **groups; gboolean rc; guint i; keyfile = g_key_file_new(); rc = g_key_file_load_from_file(keyfile, path, G_KEY_FILE_NONE, &error); g_assert (rc); groups = g_key_file_get_groups (keyfile, NULL); for (i = 0; groups[i]; i++) { WacomStylus *stylus; GError *error = NULL; char *type; int id; char **string_list; if (!safe_atoi_base (groups[i], &id, 16)) { g_warning ("Failed to parse stylus ID '%s'", groups[i]); continue; } stylus = g_new0 (WacomStylus, 1); stylus->refcnt = 1; stylus->id = id; stylus->name = g_key_file_get_string(keyfile, groups[i], "Name", NULL); stylus->group = g_key_file_get_string(keyfile, groups[i], "Group", NULL); type = g_key_file_get_string(keyfile, groups[i], "EraserType", NULL); stylus->eraser_type = eraser_type_from_str (type); g_free (type); string_list = g_key_file_get_string_list (keyfile, groups[i], "PairedStylusIds", NULL, NULL); stylus->paired_ids = g_array_new (FALSE, FALSE, sizeof(int)); if (string_list) { guint j; for (j = 0; string_list[j]; j++) { int val; if (safe_atoi_base (string_list[j], &val, 16)) { g_array_append_val (stylus->paired_ids, val); } else { g_warning ("Stylus %s (%s) Ignoring invalid PairedId value\n", stylus->name, groups[i]); } } g_strfreev (string_list); } stylus->has_lens = g_key_file_get_boolean(keyfile, groups[i], "HasLens", &error); if (error && error->code == G_KEY_FILE_ERROR_INVALID_VALUE) g_warning ("Stylus %s (%s) %s\n", stylus->name, groups[i], error->message); g_clear_error (&error); stylus->has_wheel = g_key_file_get_boolean(keyfile, groups[i], "HasWheel", &error); if (error && error->code == G_KEY_FILE_ERROR_INVALID_VALUE) g_warning ("Stylus %s (%s) %s\n", stylus->name, groups[i], error->message); g_clear_error (&error); stylus->num_buttons = g_key_file_get_integer(keyfile, groups[i], "Buttons", &error); if (stylus->num_buttons == 0 && error != NULL) { stylus->num_buttons = -1; g_clear_error (&error); } string_list = g_key_file_get_string_list (keyfile, groups[i], "Axes", NULL, NULL); if (string_list) { WacomAxisTypeFlags axes = WACOM_AXIS_TYPE_NONE; guint j; for (j = 0; string_list[j]; j++) { WacomAxisTypeFlags flag = WACOM_AXIS_TYPE_NONE; if (g_str_equal(string_list[j], "Tilt")) { flag = WACOM_AXIS_TYPE_TILT; } else if (g_str_equal(string_list[j], "RotationZ")) { flag = WACOM_AXIS_TYPE_ROTATION_Z; } else if (g_str_equal(string_list[j], "Distance")) { flag = WACOM_AXIS_TYPE_DISTANCE; } else if (g_str_equal(string_list[j], "Pressure")) { flag = WACOM_AXIS_TYPE_PRESSURE; } else if (g_str_equal(string_list[j], "Slider")) { flag = WACOM_AXIS_TYPE_SLIDER; } else { g_warning ("Invalid axis %s for stylus ID %s\n", string_list[j], groups[i]); } if (axes & flag) g_warning ("Duplicate axis %s for stylus ID %s\n", string_list[j], groups[i]); axes |= flag; } stylus->axes = axes; g_strfreev (string_list); } type = g_key_file_get_string(keyfile, groups[i], "Type", NULL); stylus->type = type_from_str (type); g_free (type); if (g_hash_table_lookup (db->stylus_ht, GINT_TO_POINTER (id)) != NULL) g_warning ("Duplicate definition for stylus ID '%#x'", id); g_hash_table_insert (db->stylus_ht, GINT_TO_POINTER (id), stylus); } g_strfreev (groups); if (keyfile) g_key_file_free (keyfile); } static void libwacom_setup_paired_attributes(WacomDeviceDatabase *db) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, db->stylus_ht); while (g_hash_table_iter_next (&iter, &key, &value)) { WacomStylus *stylus = value; const int* ids; int count; int i; ids = libwacom_stylus_get_paired_ids(stylus, &count); for (i = 0; i < count; i++) { const WacomStylus *pair; pair = libwacom_stylus_get_for_id(db, ids[i]); if (pair == NULL) { g_warning("Paired stylus '0x%x' not found, ignoring.", ids[i]); continue; } if (libwacom_stylus_is_eraser(pair)) { stylus->has_eraser = true; } } } } static const struct { const char *key; WacomButtonFlags flag; } options[] = { { "Left", WACOM_BUTTON_POSITION_LEFT }, { "Right", WACOM_BUTTON_POSITION_RIGHT }, { "Top", WACOM_BUTTON_POSITION_TOP }, { "Bottom", WACOM_BUTTON_POSITION_BOTTOM }, { "Ring", WACOM_BUTTON_RING_MODESWITCH }, { "Ring2", WACOM_BUTTON_RING2_MODESWITCH }, { "Touchstrip", WACOM_BUTTON_TOUCHSTRIP_MODESWITCH }, { "Touchstrip2", WACOM_BUTTON_TOUCHSTRIP2_MODESWITCH }, { "OLEDs", WACOM_BUTTON_OLED } }; static const struct { const char *key; WacomStatusLEDs value; } supported_leds[] = { { "Ring", WACOM_STATUS_LED_RING }, { "Ring2", WACOM_STATUS_LED_RING2 }, { "Touchstrip", WACOM_STATUS_LED_TOUCHSTRIP }, { "Touchstrip2", WACOM_STATUS_LED_TOUCHSTRIP2 } }; static const struct { const char *key; WacomIntegrationFlags value; } integration_flags[] = { { "Display", WACOM_DEVICE_INTEGRATED_DISPLAY }, { "System", WACOM_DEVICE_INTEGRATED_SYSTEM } }; static void libwacom_parse_buttons_key(WacomDevice *device, GKeyFile *keyfile, const char *key, WacomButtonFlags flag) { guint i; char **vals; vals = g_key_file_get_string_list (keyfile, BUTTONS_GROUP, key, NULL, NULL); if (vals == NULL) return; for (i = 0; vals[i] != NULL; i++) { char val; WacomButton *button; val = *vals[i]; if (strlen (vals[i]) > 1 || val < 'A' || val > 'Z') { g_warning ("Ignoring value '%s' in key '%s'", vals[i], key); continue; } button = g_hash_table_lookup(device->buttons, GINT_TO_POINTER(val)); if (!button) { button = g_new0(WacomButton, 1); g_hash_table_insert(device->buttons, GINT_TO_POINTER(val), button); } button->flags |= flag; } g_strfreev (vals); } static void reset_code(gpointer key, gpointer value, gpointer user_data) { WacomButton *button = value; button->code = 0; } static inline bool set_button_codes_from_string(WacomDevice *device, char **strvals) { bool success = false; assert(strvals); for (guint i = 0; i < g_hash_table_size(device->buttons); i++) { char key = 'A' + i; int code; WacomButton *button = g_hash_table_lookup(device->buttons, GINT_TO_POINTER(key)); if (!button) { g_error("%s: Button %c is not defined, ignoring all codes\n", device->name, key); goto out; } if (!strvals[i]) { g_error ("%s: Missing EvdevCode for button %d, ignoring all codes\n", device->name, i); goto out; } if (!safe_atoi_base (strvals[i], &code, 16) || code < BTN_MISC || code >= BTN_DIGI) { g_warning ("%s: Invalid EvdevCode %s for button %c, ignoring all codes\n", device->name, strvals[i], key); goto out; } button->code = code; } success = true; out: if (!success) g_hash_table_foreach(device->buttons, reset_code, NULL); return success; } static inline void set_button_codes_from_heuristics(WacomDevice *device) { for (char key = 'A'; key <= 'Z'; key++) { int code = 0; WacomButton *button; button = g_hash_table_lookup(device->buttons, GINT_TO_POINTER(key)); if (!button) continue; if (device->cls == WCLASS_BAMBOO || device->cls == WCLASS_GRAPHIRE) { switch (key) { case 'A': code = BTN_LEFT; break; case 'B': code = BTN_RIGHT; break; case 'C': code = BTN_FORWARD; break; case 'D': code = BTN_BACK; break; default: break; } } else { /* Assume traditional ExpressKey ordering */ switch (key) { case 'A': code = BTN_0; break; case 'B': code = BTN_1; break; case 'C': code = BTN_2; break; case 'D': code = BTN_3; break; case 'E': code = BTN_4; break; case 'F': code = BTN_5; break; case 'G': code = BTN_6; break; case 'H': code = BTN_7; break; case 'I': code = BTN_8; break; case 'J': code = BTN_9; break; case 'K': code = BTN_A; break; case 'L': code = BTN_B; break; case 'M': code = BTN_C; break; case 'N': code = BTN_X; break; case 'O': code = BTN_Y; break; case 'P': code = BTN_Z; break; case 'Q': code = BTN_BASE; break; case 'R': code = BTN_BASE2; break; default: break; } } if (code == 0) g_warning ("Unable to determine evdev code for button %c (%s)", key, device->name); button->code = code; } } static void libwacom_parse_button_codes(WacomDevice *device, GKeyFile *keyfile) { char **vals; vals = g_key_file_get_string_list(keyfile, BUTTONS_GROUP, "EvdevCodes", NULL, NULL); if (!vals || !set_button_codes_from_string(device, vals)) set_button_codes_from_heuristics(device); g_strfreev (vals); } static int libwacom_parse_num_modes (WacomDevice *device, GKeyFile *keyfile, const char *key, WacomButtonFlags flag) { GHashTableIter iter; int num; gpointer k, v; num = g_key_file_get_integer (keyfile, BUTTONS_GROUP, key, NULL); if (num > 0) return num; g_hash_table_iter_init(&iter, device->buttons); while (g_hash_table_iter_next(&iter, &k, &v)) { WacomButton *button = v; if (button->flags & flag) num++; } return num; } static void libwacom_parse_buttons(WacomDevice *device, GKeyFile *keyfile) { guint i; for (i = 0; i < G_N_ELEMENTS (options); i++) libwacom_parse_buttons_key(device, keyfile, options[i].key, options[i].flag); libwacom_parse_button_codes(device, keyfile); device->ring_num_modes = libwacom_parse_num_modes(device, keyfile, "RingNumModes", WACOM_BUTTON_RING_MODESWITCH); device->ring2_num_modes = libwacom_parse_num_modes(device, keyfile, "Ring2NumModes", WACOM_BUTTON_RING2_MODESWITCH); device->strips_num_modes = libwacom_parse_num_modes(device, keyfile, "StripsNumModes", WACOM_BUTTON_TOUCHSTRIP_MODESWITCH); } static int styli_id_sort(gconstpointer pa, gconstpointer pb) { const int *a = pa, *b = pb; return *a > *b ? 1 : *a == *b ? 0 : -1; } static void libwacom_parse_styli_list(WacomDeviceDatabase *db, WacomDevice *device, char **ids) { GArray *array; guint i; array = g_array_new (FALSE, FALSE, sizeof(int)); for (i = 0; ids[i]; i++) { const char *id = ids[i]; if (g_str_has_prefix(id, "0x")) { int int_value; if (safe_atoi_base (ids[i], &int_value, 16)) { g_array_append_val (array, int_value); } } else if (g_str_has_prefix(id, "@")) { const char *group = &id[1]; GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, db->stylus_ht); while (g_hash_table_iter_next (&iter, &key, &value)) { WacomStylus *stylus = value; if (stylus->group && g_str_equal(group, stylus->group)) { g_array_append_val (array, stylus->id); } } } else { g_warning ("Invalid prefix for '%s'!", id); } } /* Using groups means we don't get the styli in ascending order. Sort it so the output is predictable */ g_array_sort(array, styli_id_sort); device->styli = array; } static WacomDevice* libwacom_parse_tablet_keyfile(WacomDeviceDatabase *db, const char *datadir, const char *filename) { WacomDevice *device = NULL; GKeyFile *keyfile; GError *error = NULL; gboolean rc; char *path; char *layout; char *class; char *paired; char **string_list; bool success = FALSE; int num_buttons; keyfile = g_key_file_new(); path = g_build_filename (datadir, filename, NULL); rc = g_key_file_load_from_file(keyfile, path, G_KEY_FILE_NONE, &error); if (!rc) { DBG("%s: %s\n", path, error->message); goto out; } device = g_new0 (WacomDevice, 1); device->refcnt = 1; device->matches = g_array_new(TRUE, TRUE, sizeof(WacomMatch*)); string_list = g_key_file_get_string_list(keyfile, DEVICE_GROUP, "DeviceMatch", NULL, NULL); if (!string_list) { DBG("Missing DeviceMatch= line in '%s'\n", path); goto out; } else { guint i; guint nmatches = 0; for (i = 0; string_list[i]; i++) { WacomMatch *m = libwacom_match_from_string(string_list[i]); if (!m) { DBG("'%s' is an invalid DeviceMatch in '%s'\n", string_list[i], path); continue; } libwacom_add_match(device, m); nmatches++; /* set default to first entry */ if (nmatches == 1) libwacom_set_default_match(device, m); libwacom_match_unref(m); } g_strfreev (string_list); if (nmatches == 0) goto out; } paired = g_key_file_get_string(keyfile, DEVICE_GROUP, "PairedID", NULL); if (paired) { libwacom_matchstr_to_paired(device, paired); g_free(paired); } device->name = g_key_file_get_string(keyfile, DEVICE_GROUP, "Name", NULL); device->model_name = g_key_file_get_string(keyfile, DEVICE_GROUP, "ModelName", NULL); /* ModelName= would give us the empty string, let's make it NULL * instead */ if (device->model_name && strlen(device->model_name) == 0) { free(device->model_name); device->model_name = NULL; } device->width = g_key_file_get_integer(keyfile, DEVICE_GROUP, "Width", NULL); device->height = g_key_file_get_integer(keyfile, DEVICE_GROUP, "Height", NULL); device->integration_flags = WACOM_DEVICE_INTEGRATED_UNSET; string_list = g_key_file_get_string_list(keyfile, DEVICE_GROUP, "IntegratedIn", NULL, NULL); if (string_list) { guint i, n; gboolean found; device->integration_flags = WACOM_DEVICE_INTEGRATED_NONE; for (i = 0; string_list[i]; i++) { found = FALSE; for (n = 0; n < G_N_ELEMENTS (integration_flags); n++) { if (g_str_equal(string_list[i], integration_flags[n].key)) { device->integration_flags |= integration_flags[n].value; found = TRUE; break; } } if (!found) g_warning ("Unrecognized integration flag '%s'", string_list[i]); } g_strfreev (string_list); } layout = g_key_file_get_string(keyfile, DEVICE_GROUP, "Layout", NULL); if (layout) { /* For the layout, we store the full path to the SVG layout */ device->layout = g_build_filename (datadir, "layouts", layout, NULL); g_free (layout); } class = g_key_file_get_string(keyfile, DEVICE_GROUP, "Class", NULL); device->cls = libwacom_class_string_to_enum(class); g_free(class); string_list = g_key_file_get_string_list(keyfile, DEVICE_GROUP, "Styli", NULL, NULL); if (string_list) { libwacom_parse_styli_list(db, device, string_list); g_strfreev (string_list); } else { int fallback_eraser = WACOM_ERASER_FALLBACK_ID; int fallback_stylus = WACOM_STYLUS_FALLBACK_ID; device->styli = g_array_new(FALSE, FALSE, sizeof(int)); g_array_append_val(device->styli, fallback_eraser); g_array_append_val(device->styli, fallback_stylus); } /* Features */ if (g_key_file_get_boolean(keyfile, FEATURES_GROUP, "Stylus", NULL)) device->features |= FEATURE_STYLUS; if (g_key_file_get_boolean(keyfile, FEATURES_GROUP, "Touch", NULL)) device->features |= FEATURE_TOUCH; if (g_key_file_get_boolean(keyfile, FEATURES_GROUP, "Ring", NULL)) device->features |= FEATURE_RING; if (g_key_file_get_boolean(keyfile, FEATURES_GROUP, "Ring2", NULL)) device->features |= FEATURE_RING2; if (g_key_file_get_boolean(keyfile, FEATURES_GROUP, "Reversible", NULL)) device->features |= FEATURE_REVERSIBLE; if (g_key_file_get_boolean(keyfile, FEATURES_GROUP, "TouchSwitch", NULL)) device->features |= FEATURE_TOUCHSWITCH; if (device->integration_flags != WACOM_DEVICE_INTEGRATED_UNSET && device->integration_flags & WACOM_DEVICE_INTEGRATED_DISPLAY && device->features & FEATURE_REVERSIBLE) g_warning ("Tablet '%s' is both reversible and integrated in screen. This is impossible", libwacom_get_match(device)); if (!(device->features & FEATURE_RING) && (device->features & FEATURE_RING2)) g_warning ("Tablet '%s' has Ring2 but no Ring. This is impossible", libwacom_get_match(device)); if (!(device->features & FEATURE_TOUCH) && (device->features & FEATURE_TOUCHSWITCH)) g_warning ("Tablet '%s' has touch switch but no touch tool. This is impossible", libwacom_get_match(device)); device->num_strips = g_key_file_get_integer(keyfile, FEATURES_GROUP, "NumStrips", NULL); num_buttons = g_key_file_get_integer(keyfile, FEATURES_GROUP, "Buttons", &error); if (num_buttons == 0 && g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { g_warning ("Tablet '%s' has no buttons defined, do something!", libwacom_get_match(device)); g_clear_error (&error); } device->buttons = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); if (num_buttons > 0) libwacom_parse_buttons(device, keyfile); device->status_leds = g_array_new (FALSE, FALSE, sizeof(WacomStatusLEDs)); string_list = g_key_file_get_string_list(keyfile, FEATURES_GROUP, "StatusLEDs", NULL, NULL); if (string_list) { guint i, n; for (i = 0; string_list[i]; i++) { for (n = 0; n < G_N_ELEMENTS (supported_leds); n++) { if (g_str_equal(string_list[i], supported_leds[n].key)) { g_array_append_val (device->status_leds, supported_leds[n].value); break; } } } g_strfreev (string_list); } success = TRUE; out: if (path) g_free(path); if (keyfile) g_key_file_free(keyfile); if (error) g_error_free(error); if (!success) device = libwacom_unref(device); return device; } static bool has_suffix(const char *name, const char *suffix) { size_t len = strlen(name); size_t suffix_len = strlen(suffix); if (!name || name[0] == '.') return 0; if (len <= suffix_len) return false; return g_str_equal(&name[len - suffix_len], suffix); } static int is_tablet_file(const struct dirent *entry) { return has_suffix(entry->d_name, TABLET_SUFFIX); } static int is_stylus_file(const struct dirent *entry) { return has_suffix(entry->d_name, STYLUS_SUFFIX); } static bool load_tablet_files(WacomDeviceDatabase *db, const char *datadir) { DIR *dir; struct dirent *file; bool success = false; GHashTable *keyset = NULL; dir = opendir(datadir); if (!dir) return errno == ENOENT; /* non-existing directory is ok */ /* A set of all matches for duplicate detection. We allow duplicates * across data directories, but we don't allow for duplicates * within the same data directory. */ keyset = g_hash_table_new_full (g_str_hash, g_str_equal, free, NULL); if (!keyset) goto out; while ((file = readdir(dir))) { WacomDevice *d; guint idx = 0; if (!is_tablet_file(file)) continue; d = libwacom_parse_tablet_keyfile(db, datadir, file->d_name); if (!d) continue; if (d->matches->len == 0) { g_critical("Device '%s' has no matches defined\n", libwacom_get_name(d)); goto out; } /* Note: we may change the array while iterating over it */ while (idx < d->matches->len) { WacomMatch *match = g_array_index(d->matches, WacomMatch*, idx); const char *matchstr; matchstr = libwacom_match_get_match_string(match); /* no duplicate matches allowed within the same * directory */ if (g_hash_table_contains(keyset, matchstr)) { g_critical("Duplicate match of '%s' on device '%s'.", matchstr, libwacom_get_name(d)); goto out; } g_hash_table_add(keyset, g_strdup(matchstr)); /* We already have an entry for this match in the database, * that takes precedence. Remove the current match * from the new tablet - that's fine because we * haven't exposed the tablet yet. */ if (g_hash_table_lookup(db->device_ht, matchstr)) { libwacom_remove_match(d, match); continue; } g_hash_table_insert(db->device_ht, g_strdup (matchstr), d); libwacom_ref(d); idx++; } libwacom_unref(d); } success = true; out: if (keyset) g_hash_table_destroy(keyset); closedir(dir); return success; } static void stylus_destroy(void *data) { libwacom_stylus_unref((WacomStylus*)data); } static bool load_stylus_files(WacomDeviceDatabase *db, const char *datadir) { DIR *dir; struct dirent *file; dir = opendir(datadir); if (!dir) return errno == ENOENT; /* non-existing directory is ok */ while ((file = readdir(dir))) { char *path; if (!is_stylus_file(file)) continue; path = g_build_filename (datadir, file->d_name, NULL); libwacom_parse_stylus_keyfile(db, path); g_free(path); } closedir(dir); return true; } static WacomDeviceDatabase * database_new_for_paths (size_t npaths, const char **datadirs) { WacomDeviceDatabase *db; size_t n; const char **datadir; db = g_new0 (WacomDeviceDatabase, 1); db->device_ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) libwacom_destroy); db->stylus_ht = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) stylus_destroy); for (datadir = datadirs, n = npaths; n--; datadir++) { if (!load_stylus_files(db, *datadir)) goto error; } for (datadir = datadirs, n = npaths; n--; datadir++) { if (!load_tablet_files(db, *datadir)) goto error; } /* If we couldn't load _anything_ then something's wrong */ if (g_hash_table_size (db->stylus_ht) == 0 || g_hash_table_size (db->device_ht) == 0) goto error; libwacom_setup_paired_attributes(db); return db; error: libwacom_database_destroy(db); return NULL; } LIBWACOM_EXPORT WacomDeviceDatabase * libwacom_database_new_for_path (const char *datadir) { return database_new_for_paths(1, &datadir); } LIBWACOM_EXPORT WacomDeviceDatabase * libwacom_database_new (void) { const char *datadir[] = { ETCDIR, DATADIR, }; return database_new_for_paths (2, datadir); } LIBWACOM_EXPORT void libwacom_database_destroy(WacomDeviceDatabase *db) { if (db->device_ht) g_hash_table_destroy(db->device_ht); if (db->stylus_ht) g_hash_table_destroy(db->stylus_ht); g_free (db); } static gint device_compare(gconstpointer pa, gconstpointer pb) { const WacomDevice *a = pa, *b = pb; int cmp; cmp = libwacom_get_vendor_id(a) - libwacom_get_vendor_id(b); if (cmp == 0) cmp = libwacom_get_product_id(a) - libwacom_get_product_id(b); if (cmp == 0) cmp = g_strcmp0(libwacom_get_name(a), libwacom_get_name(b)); return cmp; } static void ht_copy_key(gpointer key, gpointer value, gpointer user_data) { g_hash_table_add((GHashTable*)user_data, value); } LIBWACOM_EXPORT WacomDevice** libwacom_list_devices_from_database(const WacomDeviceDatabase *db, WacomError *error) { GList *cur, *devices = NULL; WacomDevice **list, **p; GHashTable *ht = NULL; if (!db) { libwacom_error_set(error, WERROR_INVALID_DB, "db is NULL"); return NULL; } /* Devices may be present more than one in the device_ht, so let's * use a temporary hashtable like a set to filter duplicates */ ht = g_hash_table_new (g_direct_hash, g_direct_equal); if (!ht) goto error; g_hash_table_foreach (db->device_ht, ht_copy_key, ht); devices = g_hash_table_get_keys (ht); list = calloc (g_list_length (devices) + 1, sizeof (WacomDevice *)); if (!list) goto error; devices = g_list_sort (devices, device_compare); for (p = list, cur = devices; cur; cur = g_list_next (cur)) *p++ = (WacomDevice *) cur->data; g_list_free (devices); g_hash_table_destroy (ht); return list; error: libwacom_error_set(error, WERROR_BAD_ALLOC, "Memory allocation failed"); if (ht) g_hash_table_destroy (ht); if (devices) g_list_free (devices); return NULL; } /* vim: set noexpandtab tabstop=8 shiftwidth=8: */