diff --git a/base/libxkbcommon/afb26e7df9090a0b765eb294b6efff448f763b6f.patch b/base/libxkbcommon/afb26e7df9090a0b765eb294b6efff448f763b6f.patch new file mode 100644 index 0000000..9f537b8 --- /dev/null +++ b/base/libxkbcommon/afb26e7df9090a0b765eb294b6efff448f763b6f.patch @@ -0,0 +1,3461 @@ +From afb26e7df9090a0b765eb294b6efff448f763b6f Mon Sep 17 00:00:00 2001 +From: Peter Hutterer +Date: Tue, 12 May 2020 14:09:50 +1000 +Subject: [PATCH] Add libxkbregistry to query available RMLVO + +This library is the replacement for clients parsing evdev.xml directly. +Instead, they should use the API here so that in the future we may even +be able to swap evdev.xml for a more suitable data format. + +The library parses through evdev.xml (using libxml2) and - if requested - +through evdev.extras.xml as well. The merge approach is optimised for +the default case where we have a system-installed rules XML and another file in +$XDG_CONFIG_DIR that adds a few entries. We load the system file first, then +append any custom ones to that. It's not possible to overwrite the MLVO list +provided by the system files - if you want to do that, get the change upstream. + +XML validation is handled through the DTD itself which means we only need to +check for a nonempty name, everything else the DTD validation should complain +about. + +The logging system is effectively identical to xkbcommon. + +Signed-off-by: Peter Hutterer +--- + meson.build | 58 +- + meson_options.txt | 6 + + src/darray.h | 3 + + src/registry.c | 1193 ++++++++++++++++++++++++++++++++++++ + src/util-list.c | 94 +++ + src/util-list.h | 71 +++ + test/registry.c | 843 +++++++++++++++++++++++++ + tools/registry-list.c | 223 +++++++ + xkbcommon/xkbregistry.h | 782 +++++++++++++++++++++++ + xkbregistry.map | 61 ++ + 15 files changed, 3435 insertions(+), 7 deletions(-) + create mode 100644 src/registry.c + create mode 100644 src/util-list.c + create mode 100644 src/util-list.h + create mode 100644 test/registry.c + create mode 100644 tools/registry-list.c + create mode 100644 xkbcommon/xkbregistry.h + create mode 100644 xkbregistry.map + + diff --git a/meson.build b/meson.build +index 7041d35f8..52aeafd07 100644 +--- a/meson.build ++++ b/meson.build +@@ -296,6 +296,53 @@ You can disable X11 support with -Denable-x11=false.''') + ) + endif + ++# libxkbregistry ++if get_option('enable-xkbregistry') ++ dep_libxml = dependency('libxml-2.0') ++ deps_libxkbregistry = [dep_libxml] ++ libxkbregistry_sources = [ ++ 'src/registry.c', ++ 'src/utils.h', ++ 'src/utils.c', ++ 'src/util-list.h', ++ 'src/util-list.c', ++ ] ++ libxkbregistry_link_args = [] ++ if have_version_script ++ libxkbregistry_link_args += '-Wl,--version-script=' + join_paths(meson.source_root(), 'xkbregistry.map') ++ endif ++ libxkbregistry = library( ++ 'xkbregistry', ++ 'xkbcommon/xkbregistry.h', ++ libxkbregistry_sources, ++ link_args: libxkbregistry_link_args, ++ link_depends: 'xkbregistry.map', ++ dependencies: deps_libxkbregistry, ++ version: '0.0.0', ++ install: true, ++ include_directories: include_directories('src'), ++ ) ++ install_headers( ++ 'xkbcommon/xkbregistry.h', ++ subdir: 'xkbcommon', ++ ) ++ pkgconfig.generate( ++ name: 'xkbregistry', ++ filebase: 'xkbregistry', ++ libraries: libxkbregistry, ++ version: meson.project_version(), ++ description: 'XKB API to query available rules, models, layouts, variants and options', ++ ) ++ ++ dep_libxkbregistry = declare_dependency( ++ include_directories: include_directories('xkbcommon'), ++ link_with: libxkbregistry ++ ) ++ executable('xkbcommon-registry-list', ++ 'tools/registry-list.c', ++ dependencies: dep_libxkbregistry, ++ install: false) ++endif + + # Tests + test_env = environment() +@@ -440,7 +487,15 @@ if get_option('enable-x11') + # See: https://github.com/xkbcommon/libxkbcommon/issues/30 + executable('test-x11comp', 'test/x11comp.c', dependencies: x11_test_dep) + endif +- ++if get_option('enable-xkbregistry') ++ test( ++ 'registry', ++ executable('test-registry', 'test/registry.c', ++ include_directories: include_directories('src'), ++ dependencies: dep_libxkbregistry), ++ env: test_env, ++ ) ++endif + + # Fuzzing target programs. + executable('fuzz-keymap', 'fuzz/keymap/target.c', dependencies: test_dep) +@@ -577,6 +632,7 @@ You can disable the documentation with -Denable-docs=false.''') + 'xkbcommon/xkbcommon-names.h', + 'xkbcommon/xkbcommon-x11.h', + 'xkbcommon/xkbcommon-compose.h', ++ 'xkbcommon/xkbregistry.h', + ] + doxygen_data = configuration_data() + doxygen_data.set('PACKAGE_NAME', meson.project_name()) +diff --git a/meson_options.txt b/meson_options.txt +index 0e166b134..5eaa081db 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -56,3 +56,9 @@ option( + value: true, + description: 'Enable support for Wayland utility programs', + ) ++option( ++ 'enable-xkbregistry', ++ type: 'boolean', ++ value: true, ++ description: 'Enable building libxkbregistry', ++) +diff --git a/src/darray.h b/src/darray.h +index 8e87c942e..de659ccad 100644 +--- a/src/darray.h ++++ b/src/darray.h +@@ -206,4 +206,7 @@ darray_next_alloc(unsigned alloc, unsigned need, unsigned itemSize) + (idx) < (arr).size; \ + (idx)++, (val)++) + ++#define darray_foreach_reverse(i, arr) \ ++ for ((i) = &(arr).item[(arr).size - 1]; (arr).size > 0 && (i) >= &(arr).item[0]; (i)--) ++ + #endif /* CCAN_DARRAY_H */ +diff --git a/src/registry.c b/src/registry.c +new file mode 100644 +index 000000000..19e004ba5 +--- /dev/null ++++ b/src/registry.c +@@ -0,0 +1,1193 @@ ++/* ++ * Copyright © 2020 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. ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "xkbcommon/xkbregistry.h" ++#include "utils.h" ++#include "util-list.h" ++ ++struct rxkb_object; ++ ++typedef void (*destroy_func_t)(struct rxkb_object *object); ++ ++/** ++ * All our objects are refcounted and are linked to iterate through them. ++ * Abstract those bits away into a shared parent class so we can generate ++ * most of the functions through macros. ++ */ ++struct rxkb_object { ++ struct rxkb_object *parent; ++ uint32_t refcount; ++ struct list link; ++ destroy_func_t destroy; ++}; ++ ++struct rxkb_iso639_code { ++ struct rxkb_object base; ++ char *code; ++}; ++ ++struct rxkb_iso3166_code { ++ struct rxkb_object base; ++ char *code; ++}; ++ ++enum context_state { ++ CONTEXT_NEW, ++ CONTEXT_PARSED, ++ CONTEXT_FAILED, ++}; ++ ++struct rxkb_context { ++ struct rxkb_object base; ++ enum context_state context_state; ++ ++ bool load_extra_rules_files; ++ ++ struct list models; /* list of struct rxkb_models */ ++ struct list layouts; /* list of struct rxkb_layouts */ ++ struct list option_groups; /* list of struct rxkb_option_group */ ++ ++ darray(char *) includes; ++ ++ ++ ATTR_PRINTF(3, 0) void (*log_fn)(struct rxkb_context *ctx, ++ enum rxkb_log_level level, ++ const char *fmt, va_list args); ++ enum rxkb_log_level log_level; ++ ++ void *userdata; ++}; ++ ++struct rxkb_model { ++ struct rxkb_object base; ++ ++ char *name; ++ char *vendor; ++ char *description; ++ enum rxkb_popularity popularity; ++}; ++ ++struct rxkb_layout { ++ struct rxkb_object base; ++ ++ char *name; ++ char *brief; ++ char *description; ++ char *variant; ++ enum rxkb_popularity popularity; ++ ++ struct list iso639s; /* list of struct rxkb_iso639_code */ ++ struct list iso3166s; /* list of struct rxkb_iso3166_code */ ++}; ++ ++struct rxkb_option_group { ++ struct rxkb_object base; ++ ++ bool allow_multiple; ++ struct list options; /* list of struct rxkb_options */ ++ char *name; ++ char *description; ++ enum rxkb_popularity popularity; ++}; ++ ++struct rxkb_option { ++ struct rxkb_object base; ++ ++ char *name; ++ char *brief; ++ char *description; ++ enum rxkb_popularity popularity; ++}; ++ ++static bool ++parse(struct rxkb_context *ctx, const char *path, ++ enum rxkb_popularity popularity); ++ ++static void ++rxkb_log(struct rxkb_context *ctx, enum rxkb_log_level level, ++ const char *fmt, ...) ++{ ++ va_list args; ++ ++ if (ctx->log_level < level) ++ return; ++ ++ va_start(args, fmt); ++ ctx->log_fn(ctx, level, fmt, args); ++ va_end(args); ++} ++ ++/* ++ * The format is not part of the argument list in order to avoid the ++ * "ISO C99 requires rest arguments to be used" warning when only the ++ * format is supplied without arguments. Not supplying it would still ++ * result in an error, though. ++ */ ++#define log_dbg(ctx, ...) \ ++ rxkb_log((ctx), RXKB_LOG_LEVEL_DEBUG, __VA_ARGS__) ++#define log_info(ctx, ...) \ ++ rxkb_log((ctx), RXKB_LOG_LEVEL_INFO, __VA_ARGS__) ++#define log_warn(ctx, ...) \ ++ rxkb_log((ctx), RXKB_LOG_LEVEL_WARNING, __VA_ARGS__) ++#define log_err(ctx, ...) \ ++ rxkb_log((ctx), RXKB_LOG_LEVEL_ERROR, __VA_ARGS__) ++#define log_wsgo(ctx, ...) \ ++ rxkb_log((ctx), RXKB_LOG_LEVEL_CRITICAL, __VA_ARGS__) ++ ++ ++#define DECLARE_REF_UNREF_FOR_TYPE(type_) \ ++XKB_EXPORT struct type_ * type_##_ref(struct type_ *object) { \ ++ rxkb_object_ref(&object->base); \ ++ return object; \ ++} \ ++XKB_EXPORT struct type_ * type_##_unref(struct type_ *object) { \ ++ if (!object) return NULL; \ ++ return rxkb_object_unref(&object->base); \ ++} ++ ++#define DECLARE_CREATE_FOR_TYPE(type_) \ ++static inline struct type_ * type_##_create(struct rxkb_object *parent) { \ ++ struct type_ *t = calloc(1, sizeof *t); \ ++ if (t) \ ++ rxkb_object_init(&t->base, parent, (destroy_func_t)type_##_destroy); \ ++ return t; \ ++} ++ ++#define DECLARE_TYPED_GETTER_FOR_TYPE(type_, field_, rtype_) \ ++XKB_EXPORT rtype_ type_##_get_##field_(struct type_ *object) { \ ++ return object->field_; \ ++} ++ ++#define DECLARE_GETTER_FOR_TYPE(type_, field_) \ ++ DECLARE_TYPED_GETTER_FOR_TYPE(type_, field_, const char*) ++ ++#define DECLARE_FIRST_NEXT_FOR_TYPE(type_, parent_type_, parent_field_) \ ++XKB_EXPORT struct type_ * type_##_first(struct parent_type_ *parent) { \ ++ struct type_ *o = NULL; \ ++ if (!list_empty(&parent->parent_field_)) \ ++ o = list_first_entry(&parent->parent_field_, o, base.link); \ ++ return o; \ ++} \ ++XKB_EXPORT struct type_ * \ ++type_##_next(struct type_ *o) \ ++{ \ ++ struct parent_type_ *parent; \ ++ struct type_ *next; \ ++ parent = container_of(o->base.parent, struct parent_type_, base); \ ++ next = list_first_entry(&o->base.link, o, base.link); \ ++ if (list_is_last(&parent->parent_field_, &o->base.link)) \ ++ return NULL; \ ++ return next; \ ++} ++ ++static void ++rxkb_object_init(struct rxkb_object *object, struct rxkb_object *parent, destroy_func_t destroy) ++{ ++ object->refcount = 1; ++ object->destroy = destroy; ++ object->parent = parent; ++ list_init(&object->link); ++} ++ ++static void ++rxkb_object_destroy(struct rxkb_object *object) ++{ ++ if (object->destroy) ++ object->destroy(object); ++ list_remove(&object->link); ++ free(object); ++} ++ ++static void * ++rxkb_object_ref(struct rxkb_object *object) ++{ ++ assert(object->refcount >= 1); ++ ++object->refcount; ++ return object; ++} ++ ++static void * ++rxkb_object_unref(struct rxkb_object *object) ++{ ++ assert(object->refcount >= 1); ++ if (--object->refcount == 0) ++ rxkb_object_destroy(object); ++ return NULL; ++} ++ ++static void ++rxkb_iso639_code_destroy(struct rxkb_iso639_code *code) ++{ ++ free(code->code); ++} ++ ++XKB_EXPORT struct rxkb_iso639_code * ++rxkb_layout_get_iso639_first(struct rxkb_layout *layout) ++{ ++ struct rxkb_iso639_code *code = NULL; ++ ++ if (!list_empty(&layout->iso639s)) ++ code = list_first_entry(&layout->iso639s, code, base.link); ++ ++ return code; ++} ++ ++XKB_EXPORT struct rxkb_iso639_code * ++rxkb_iso639_code_next(struct rxkb_iso639_code *code) ++{ ++ struct rxkb_iso639_code *next = NULL; ++ struct rxkb_layout *layout; ++ ++ layout = container_of(code->base.parent, struct rxkb_layout, base); ++ ++ if (list_is_last(&layout->iso639s, &code->base.link)) ++ return NULL; ++ ++ next = list_first_entry(&code->base.link, code, base.link); ++ ++ return next; ++} ++ ++DECLARE_REF_UNREF_FOR_TYPE(rxkb_iso639_code); ++DECLARE_CREATE_FOR_TYPE(rxkb_iso639_code); ++DECLARE_GETTER_FOR_TYPE(rxkb_iso639_code, code); ++ ++static void ++rxkb_iso3166_code_destroy(struct rxkb_iso3166_code *code) ++{ ++ free(code->code); ++} ++ ++XKB_EXPORT struct rxkb_iso3166_code * ++rxkb_layout_get_iso3166_first(struct rxkb_layout *layout) ++{ ++ struct rxkb_iso3166_code *code = NULL; ++ ++ if (!list_empty(&layout->iso3166s)) ++ code = list_first_entry(&layout->iso3166s, code, base.link); ++ ++ return code; ++} ++ ++XKB_EXPORT struct rxkb_iso3166_code * ++rxkb_iso3166_code_next(struct rxkb_iso3166_code *code) ++{ ++ struct rxkb_iso3166_code *next = NULL; ++ struct rxkb_layout *layout; ++ ++ layout = container_of(code->base.parent, struct rxkb_layout, base); ++ ++ if (list_is_last(&layout->iso3166s, &code->base.link)) ++ return NULL; ++ ++ next = list_first_entry(&code->base.link, code, base.link); ++ ++ return next; ++} ++ ++DECLARE_REF_UNREF_FOR_TYPE(rxkb_iso3166_code); ++DECLARE_CREATE_FOR_TYPE(rxkb_iso3166_code); ++DECLARE_GETTER_FOR_TYPE(rxkb_iso3166_code, code); ++ ++static void ++rxkb_option_destroy(struct rxkb_option *o) ++{ ++ free(o->name); ++ free(o->brief); ++ free(o->description); ++} ++ ++DECLARE_REF_UNREF_FOR_TYPE(rxkb_option); ++DECLARE_CREATE_FOR_TYPE(rxkb_option); ++DECLARE_GETTER_FOR_TYPE(rxkb_option, name); ++DECLARE_GETTER_FOR_TYPE(rxkb_option, brief); ++DECLARE_GETTER_FOR_TYPE(rxkb_option, description); ++DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_option, popularity, enum rxkb_popularity); ++DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_option, rxkb_option_group, options); ++ ++static void ++rxkb_layout_destroy(struct rxkb_layout *l) ++{ ++ struct rxkb_iso639_code *iso639, *tmp_639; ++ struct rxkb_iso3166_code *iso3166, *tmp_3166; ++ ++ free(l->name); ++ free(l->brief); ++ free(l->description); ++ free(l->variant); ++ ++ list_for_each_safe(iso639, tmp_639, &l->iso639s, base.link) { ++ rxkb_iso639_code_unref(iso639); ++ } ++ list_for_each_safe(iso3166, tmp_3166, &l->iso3166s, base.link) { ++ rxkb_iso3166_code_unref(iso3166); ++ } ++} ++ ++DECLARE_REF_UNREF_FOR_TYPE(rxkb_layout); ++DECLARE_CREATE_FOR_TYPE(rxkb_layout); ++DECLARE_GETTER_FOR_TYPE(rxkb_layout, name); ++DECLARE_GETTER_FOR_TYPE(rxkb_layout, brief); ++DECLARE_GETTER_FOR_TYPE(rxkb_layout, description); ++DECLARE_GETTER_FOR_TYPE(rxkb_layout, variant); ++DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_layout, popularity, enum rxkb_popularity); ++DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_layout, rxkb_context, layouts); ++ ++static void ++rxkb_model_destroy(struct rxkb_model *m) ++{ ++ free(m->name); ++ free(m->vendor); ++ free(m->description); ++} ++ ++DECLARE_REF_UNREF_FOR_TYPE(rxkb_model); ++DECLARE_CREATE_FOR_TYPE(rxkb_model); ++DECLARE_GETTER_FOR_TYPE(rxkb_model, name); ++DECLARE_GETTER_FOR_TYPE(rxkb_model, vendor); ++DECLARE_GETTER_FOR_TYPE(rxkb_model, description); ++DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_model, popularity, enum rxkb_popularity); ++DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_model, rxkb_context, models); ++ ++static void ++rxkb_option_group_destroy(struct rxkb_option_group *og) ++{ ++ struct rxkb_option *o, *otmp; ++ ++ free(og->name); ++ free(og->description); ++ ++ list_for_each_safe(o, otmp, &og->options, base.link) { ++ rxkb_option_unref(o); ++ } ++} ++ ++XKB_EXPORT bool ++rxkb_option_group_allows_multiple(struct rxkb_option_group *g) ++{ ++ return g->allow_multiple; ++} ++ ++DECLARE_REF_UNREF_FOR_TYPE(rxkb_option_group); ++DECLARE_CREATE_FOR_TYPE(rxkb_option_group); ++DECLARE_GETTER_FOR_TYPE(rxkb_option_group, name); ++DECLARE_GETTER_FOR_TYPE(rxkb_option_group, description); ++DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_option_group, popularity, enum rxkb_popularity); ++DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_option_group, rxkb_context, option_groups); ++ ++static void ++rxkb_context_destroy(struct rxkb_context *ctx) ++{ ++ struct rxkb_model *m, *mtmp; ++ struct rxkb_layout *l, *ltmp; ++ struct rxkb_option_group *og, *ogtmp; ++ char **path; ++ ++ list_for_each_safe(m, mtmp, &ctx->models, base.link) ++ rxkb_model_unref(m); ++ assert(list_empty(&ctx->models)); ++ ++ list_for_each_safe(l, ltmp, &ctx->layouts, base.link) ++ rxkb_layout_unref(l); ++ assert(list_empty(&ctx->layouts)); ++ ++ list_for_each_safe(og, ogtmp, &ctx->option_groups, base.link) ++ rxkb_option_group_unref(og); ++ assert(list_empty(&ctx->option_groups)); ++ ++ darray_foreach(path, ctx->includes) ++ free(*path); ++ darray_free(ctx->includes); ++ ++ assert(darray_empty(ctx->includes)); ++} ++ ++DECLARE_REF_UNREF_FOR_TYPE(rxkb_context); ++DECLARE_CREATE_FOR_TYPE(rxkb_context); ++DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_context, log_level, enum rxkb_log_level); ++ ++XKB_EXPORT void ++rxkb_context_set_log_level(struct rxkb_context *ctx, ++ enum rxkb_log_level level) ++{ ++ ctx->log_level = level; ++} ++ ++static const char * ++log_level_to_prefix(enum rxkb_log_level level) ++{ ++ switch (level) { ++ case RXKB_LOG_LEVEL_DEBUG: ++ return "xkbregistry: DEBUG: "; ++ case RXKB_LOG_LEVEL_INFO: ++ return "xkbregistry: INFO: "; ++ case RXKB_LOG_LEVEL_WARNING: ++ return "xkbregistry: WARNING: "; ++ case RXKB_LOG_LEVEL_ERROR: ++ return "xkbregistry: ERROR: "; ++ case RXKB_LOG_LEVEL_CRITICAL: ++ return "xkbregistry: CRITICAL: "; ++ default: ++ return NULL; ++ } ++} ++ ++ATTR_PRINTF(3, 0) static void ++default_log_fn(struct rxkb_context *ctx, enum rxkb_log_level level, ++ const char *fmt, va_list args) ++{ ++ const char *prefix = log_level_to_prefix(level); ++ ++ if (prefix) ++ fprintf(stderr, "%s", prefix); ++ vfprintf(stderr, fmt, args); ++} ++ ++static enum rxkb_log_level ++log_level(const char *level) { ++ char *endptr; ++ enum rxkb_log_level lvl; ++ ++ errno = 0; ++ lvl = strtol(level, &endptr, 10); ++ if (errno == 0 && (endptr[0] == '\0' || is_space(endptr[0]))) ++ return lvl; ++ if (istreq_prefix("crit", level)) ++ return RXKB_LOG_LEVEL_CRITICAL; ++ if (istreq_prefix("err", level)) ++ return RXKB_LOG_LEVEL_ERROR; ++ if (istreq_prefix("warn", level)) ++ return RXKB_LOG_LEVEL_WARNING; ++ if (istreq_prefix("info", level)) ++ return RXKB_LOG_LEVEL_INFO; ++ if (istreq_prefix("debug", level) || istreq_prefix("dbg", level)) ++ return RXKB_LOG_LEVEL_DEBUG; ++ ++ return RXKB_LOG_LEVEL_ERROR; ++} ++ ++XKB_EXPORT struct rxkb_context * ++rxkb_context_new(enum rxkb_context_flags flags) ++{ ++ struct rxkb_context *ctx = rxkb_context_create(NULL); ++ const char *env; ++ ++ if (!ctx) ++ return NULL; ++ ++ ctx->context_state = CONTEXT_NEW; ++ ctx->load_extra_rules_files = flags & RXKB_CONTEXT_LOAD_EXOTIC_RULES; ++ ctx->log_fn = default_log_fn; ++ ctx->log_level = RXKB_LOG_LEVEL_ERROR; ++ ++ /* Environment overwrites defaults. */ ++ env = secure_getenv("RXKB_LOG_LEVEL"); ++ if (env) ++ rxkb_context_set_log_level(ctx, log_level(env)); ++ ++ list_init(&ctx->models); ++ list_init(&ctx->layouts); ++ list_init(&ctx->option_groups); ++ ++ if (!(flags & RXKB_CONTEXT_NO_DEFAULT_INCLUDES) && ++ !rxkb_context_include_path_append_default(ctx)) { ++ rxkb_context_unref(ctx); ++ return NULL; ++ } ++ ++ return ctx; ++} ++ ++XKB_EXPORT void ++rxkb_context_set_log_fn(struct rxkb_context *ctx, ++ void (*log_fn)(struct rxkb_context *ctx, ++ enum rxkb_log_level level, ++ const char *fmt, va_list args)) ++{ ++ ctx->log_fn = (log_fn ? log_fn : default_log_fn); ++} ++ ++XKB_EXPORT bool ++rxkb_context_include_path_append(struct rxkb_context *ctx, const char *path) ++{ ++ struct stat stat_buf; ++ int err; ++ char *tmp = NULL; ++ char rules[PATH_MAX]; ++ ++ if (ctx->context_state != CONTEXT_NEW) { ++ log_err(ctx, "include paths can only be appended to a new context\n"); ++ return false; ++ } ++ ++ tmp = strdup(path); ++ if (!tmp) ++ goto err; ++ ++ err = stat(path, &stat_buf); ++ if (err != 0) ++ goto err; ++ if (!S_ISDIR(stat_buf.st_mode)) ++ goto err; ++ ++ if (!check_eaccess(path, R_OK | X_OK)) ++ goto err; ++ ++ /* Pre-filter for the 99.9% case - if we can't assemble the default ruleset ++ * path, complain here instead of during parsing later. The niche cases ++ * where this is the wrong behaviour aren't worth worrying about. ++ */ ++ if (!snprintf_safe(rules, sizeof(rules), "%s/rules/%s.xml", ++ path, DEFAULT_XKB_RULES)) ++ goto err; ++ ++ darray_append(ctx->includes, tmp); ++ ++ return true; ++ ++err: ++ free(tmp); ++ return false; ++} ++ ++XKB_EXPORT bool ++rxkb_context_include_path_append_default(struct rxkb_context *ctx) ++{ ++ const char *home, *xdg, *root; ++ char *user_path; ++ int err; ++ bool ret = false; ++ ++ if (ctx->context_state != CONTEXT_NEW) { ++ log_err(ctx, "include paths can only be appended to a new context\n"); ++ return false; ++ } ++ ++ home = secure_getenv("HOME"); ++ ++ xdg = secure_getenv("XDG_CONFIG_HOME"); ++ if (xdg != NULL) { ++ err = asprintf(&user_path, "%s/xkb", xdg); ++ if (err >= 0) { ++ ret |= rxkb_context_include_path_append(ctx, user_path); ++ free(user_path); ++ } ++ } else if (home != NULL) { ++ /* XDG_CONFIG_HOME fallback is $HOME/.config/ */ ++ err = asprintf(&user_path, "%s/.config/xkb", home); ++ if (err >= 0) { ++ ret |= rxkb_context_include_path_append(ctx, user_path); ++ free(user_path); ++ } ++ } ++ ++ if (home != NULL) { ++ err = asprintf(&user_path, "%s/.xkb", home); ++ if (err >= 0) { ++ ret |= rxkb_context_include_path_append(ctx, user_path); ++ free(user_path); ++ } ++ } ++ ++ root = secure_getenv("XKB_CONFIG_ROOT"); ++ if (root != NULL) ++ ret |= rxkb_context_include_path_append(ctx, root); ++ else ++ ret |= rxkb_context_include_path_append(ctx, DFLT_XKB_CONFIG_ROOT); ++ ++ return ret; ++} ++ ++XKB_EXPORT bool ++rxkb_context_parse_default_ruleset(struct rxkb_context *ctx) ++{ ++ return rxkb_context_parse(ctx, DEFAULT_XKB_RULES); ++} ++ ++XKB_EXPORT bool ++rxkb_context_parse(struct rxkb_context *ctx, const char *ruleset) ++{ ++ char **path; ++ bool success = false; ++ ++ if (ctx->context_state != CONTEXT_NEW) { ++ log_err(ctx, "parse must only be called on a new context\n"); ++ return false; ++ } ++ ++ darray_foreach_reverse(path, ctx->includes) { ++ char rules[PATH_MAX]; ++ ++ if (snprintf_safe(rules, sizeof(rules), "%s/rules/%s.xml", ++ *path, ruleset)) { ++ log_dbg(ctx, "Parsing %s\n", rules); ++ if (parse(ctx, rules, RXKB_POPULARITY_STANDARD)) ++ success = true; ++ } ++ ++ if (ctx->load_extra_rules_files && ++ snprintf_safe(rules, sizeof(rules), "%s/rules/%s.extras.xml", ++ *path, ruleset)) { ++ log_dbg(ctx, "Parsing %s\n", rules); ++ if (parse(ctx, rules, RXKB_POPULARITY_EXOTIC)) ++ success = true; ++ } ++ } ++ ++ ctx->context_state = success ? CONTEXT_PARSED : CONTEXT_FAILED; ++ ++ return success; ++} ++ ++ ++XKB_EXPORT void ++rxkb_context_set_user_data(struct rxkb_context *ctx, void *userdata) ++{ ++ ctx->userdata = userdata; ++} ++ ++XKB_EXPORT void * ++rxkb_context_get_user_data(struct rxkb_context *ctx) ++{ ++ return ctx->userdata; ++} ++ ++static inline bool ++is_node(xmlNode *node, const char *name) ++{ ++ return node->type == XML_ELEMENT_NODE && ++ xmlStrEqual(node->name, (const xmlChar*)name); ++} ++ ++/* return a copy of the text content from the first text node of this node */ ++static char * ++extract_text(xmlNode *node) ++{ ++ xmlNode *n; ++ ++ for (n = node->children; n; n = n->next) { ++ if (n->type == XML_TEXT_NODE) ++ return (char *)xmlStrdup(n->content); ++ } ++ return NULL; ++} ++ ++static bool ++parse_config_item(struct rxkb_context *ctx, ++ xmlNode *parent, ++ char **name, ++ char **description, ++ char **brief, ++ char **vendor) ++{ ++ xmlNode *node = NULL; ++ xmlNode *ci = NULL; ++ ++ for (ci = parent->children; ci; ci = ci->next) { ++ if (is_node(ci, "configItem")) { ++ *name = NULL; ++ *description = NULL; ++ *brief = NULL; ++ *vendor = NULL; ++ ++ for (node = ci->children; node; node = node->next) { ++ if (is_node(node, "name")) ++ *name = extract_text(node); ++ else if (is_node(node, "description")) ++ *description = extract_text(node); ++ else if (is_node(node, "shortDescription")) ++ *brief = extract_text(node); ++ else if (is_node(node, "vendor")) ++ *vendor = extract_text(node); ++ /* Note: the DTD allows for vendor + brief but models only use ++ * vendor and everything else only uses shortDescription */ ++ } ++ ++ if (!*name || !strlen(*name)) { ++ log_err(ctx, "xml:%d: missing required element 'name'\n", ++ ci->line); ++ return false; ++ } ++ ++ return true; /* only one configItem allowed in the dtd */ ++ } ++ } ++ ++ return false; ++} ++ ++static void ++parse_model(struct rxkb_context *ctx, xmlNode *model, ++ enum rxkb_popularity popularity) ++{ ++ char *name, *description, *brief, *vendor; ++ ++ if (parse_config_item(ctx, model, &name, &description, &brief, &vendor)) { ++ struct rxkb_model *m; ++ ++ list_for_each(m, &ctx->models, base.link) { ++ if (streq(m->name, name)) { ++ free(name); ++ free(description); ++ free(brief); ++ free(vendor); ++ return; ++ } ++ } ++ ++ /* new model */ ++ m = rxkb_model_create(&ctx->base); ++ m->name = name; ++ m->description = description; ++ m->vendor = vendor; ++ m->popularity = popularity; ++ list_append(&ctx->models, &m->base.link); ++ } ++} ++ ++static void ++parse_model_list(struct rxkb_context *ctx, xmlNode *model_list, ++ enum rxkb_popularity popularity) ++{ ++ xmlNode *node = NULL; ++ ++ for (node = model_list->children; node; node = node->next) { ++ if (is_node(node, "model")) ++ parse_model(ctx, node, popularity); ++ } ++} ++ ++static void ++parse_language_list(xmlNode *language_list, struct rxkb_layout *layout) ++{ ++ xmlNode *node = NULL; ++ struct rxkb_iso639_code *code; ++ ++ for (node = language_list->children; node; node = node->next) { ++ if (is_node(node, "iso639Id")) { ++ char *str = extract_text(node); ++ struct rxkb_object *parent; ++ ++ parent = &layout->base; ++ code = rxkb_iso639_code_create(parent); ++ code->code = str; ++ list_append(&layout->iso639s, &code->base.link); ++ } ++ } ++} ++ ++static void ++parse_country_list(xmlNode *country_list, struct rxkb_layout *layout) ++{ ++ xmlNode *node = NULL; ++ struct rxkb_iso3166_code *code; ++ ++ for (node = country_list->children; node; node = node->next) { ++ if (is_node(node, "iso3166Id")) { ++ char *str = extract_text(node); ++ struct rxkb_object *parent; ++ ++ parent = &layout->base; ++ code = rxkb_iso3166_code_create(parent); ++ code->code = str; ++ list_append(&layout->iso3166s, &code->base.link); ++ } ++ } ++} ++ ++static void ++parse_variant(struct rxkb_context *ctx, struct rxkb_layout *l, ++ xmlNode *variant, enum rxkb_popularity popularity) ++{ ++ xmlNode *ci; ++ char *name, *description, *brief, *vendor; ++ ++ if (parse_config_item(ctx, variant, &name, &description, &brief, &vendor)) { ++ struct rxkb_layout *v; ++ bool exists = false; ++ ++ list_for_each(v, &ctx->layouts, base.link) { ++ if (streq(v->name, name) && streq(v->name, l->name)) { ++ exists = true; ++ break; ++ } ++ } ++ ++ if (!exists) { ++ v = rxkb_layout_create(&ctx->base); ++ list_init(&v->iso639s); ++ list_init(&v->iso3166s); ++ v->name = strdup(l->name); ++ v->variant = name; ++ v->description = description; ++ v->brief = brief; ++ v->popularity = popularity; ++ list_append(&ctx->layouts, &v->base.link); ++ ++ for (ci = variant->children; ci; ci = ci->next) { ++ xmlNode *node; ++ ++ if (!is_node(ci, "configItem")) ++ continue; ++ ++ for (node = ci->children; node; node = node->next) { ++ if (is_node(node, "languageList")) ++ parse_language_list(node, v); ++ if (is_node(node, "countryList")) ++ parse_country_list(node, v); ++ } ++ } ++ } else { ++ free(name); ++ free(description); ++ free(brief); ++ free(vendor); ++ } ++ } ++} ++ ++static void ++parse_variant_list(struct rxkb_context *ctx, struct rxkb_layout *l, ++ xmlNode *variant_list, enum rxkb_popularity popularity) ++{ ++ xmlNode *node = NULL; ++ ++ for (node = variant_list->children; node; node = node->next) { ++ if (is_node(node, "variant")) ++ parse_variant(ctx, l, node, popularity); ++ } ++} ++ ++static void ++parse_layout(struct rxkb_context *ctx, xmlNode *layout, ++ enum rxkb_popularity popularity) ++{ ++ char *name, *description, *brief, *vendor; ++ struct rxkb_layout *l; ++ xmlNode *node = NULL; ++ bool exists = false; ++ ++ if (!parse_config_item(ctx, layout, &name, &description, &brief, &vendor)) ++ return; ++ ++ list_for_each(l, &ctx->layouts, base.link) { ++ if (streq(l->name, name) && l->variant == NULL) { ++ exists = true; ++ break; ++ } ++ } ++ ++ if (!exists) { ++ l = rxkb_layout_create(&ctx->base); ++ list_init(&l->iso639s); ++ list_init(&l->iso3166s); ++ l->name = name; ++ l->variant = NULL; ++ l->description = description; ++ l->brief = brief; ++ l->popularity = popularity; ++ list_append(&ctx->layouts, &l->base.link); ++ } else { ++ free(name); ++ free(description); ++ free(brief); ++ free(vendor); ++ } ++ ++ for (node = layout->children; node; node = node->next) { ++ if (is_node(node, "variantList")) { ++ parse_variant_list(ctx, l, node, popularity); ++ } ++ if (!exists && is_node(node, "configItem")) { ++ xmlNode *ll; ++ for (ll = node->children; ll; ll = ll->next) { ++ if (is_node(ll, "languageList")) ++ parse_language_list(ll, l); ++ if (is_node(ll, "countryList")) ++ parse_country_list(ll, l); ++ } ++ } ++ } ++} ++ ++static void ++parse_layout_list(struct rxkb_context *ctx, xmlNode *layout_list, ++ enum rxkb_popularity popularity) ++{ ++ xmlNode *node = NULL; ++ ++ for (node = layout_list->children; node; node = node->next) { ++ if (is_node(node, "layout")) ++ parse_layout(ctx, node, popularity); ++ } ++} ++ ++static void ++parse_option(struct rxkb_context *ctx, struct rxkb_option_group *group, ++ xmlNode *option, enum rxkb_popularity popularity) ++{ ++ char *name, *description, *brief, *vendor; ++ ++ if (parse_config_item(ctx, option, &name, &description, &brief, &vendor)) { ++ struct rxkb_option *o; ++ ++ list_for_each(o, &group->options, base.link) { ++ if (streq(o->name, name)) { ++ free(name); ++ free(description); ++ free(brief); ++ free(vendor); ++ return; ++ } ++ } ++ ++ o = rxkb_option_create(&group->base); ++ o->name = name; ++ o->description = description; ++ o->popularity = popularity; ++ list_append(&group->options, &o->base.link); ++ } ++} ++ ++static void ++parse_group(struct rxkb_context *ctx, xmlNode *group, ++ enum rxkb_popularity popularity) ++{ ++ char *name, *description, *brief, *vendor; ++ struct rxkb_option_group *g; ++ xmlNode *node = NULL; ++ xmlChar *multiple; ++ bool exists = false; ++ ++ if (!parse_config_item(ctx, group, &name, &description, &brief, &vendor)) ++ return; ++ ++ list_for_each(g, &ctx->option_groups, base.link) { ++ if (streq(g->name, name)) { ++ exists = true; ++ break; ++ } ++ } ++ ++ if (!exists) { ++ g = rxkb_option_group_create(&ctx->base); ++ g->name = name; ++ g->description = description; ++ g->popularity = popularity; ++ ++ multiple = xmlGetProp(group, (const xmlChar*)"allowMultipleSelection"); ++ if (multiple && xmlStrEqual(multiple, (const xmlChar*)"true")) ++ g->allow_multiple = true; ++ xmlFree(multiple); ++ ++ list_init(&g->options); ++ list_append(&ctx->option_groups, &g->base.link); ++ } else { ++ free(name); ++ free(description); ++ free(brief); ++ free(vendor); ++ } ++ ++ for (node = group->children; node; node = node->next) { ++ if (is_node(node, "option")) ++ parse_option(ctx, g, node, popularity); ++ } ++} ++ ++static void ++parse_option_list(struct rxkb_context *ctx, xmlNode *option_list, ++ enum rxkb_popularity popularity) ++{ ++ xmlNode *node = NULL; ++ ++ for (node = option_list->children; node; node = node->next) { ++ if (is_node(node, "group")) ++ parse_group(ctx, node, popularity); ++ } ++} ++ ++static void ++parse_rules_xml(struct rxkb_context *ctx, xmlNode *root, ++ enum rxkb_popularity popularity) ++{ ++ xmlNode *node = NULL; ++ ++ for (node = root->children; node; node = node->next) { ++ if (is_node(node, "modelList")) ++ parse_model_list(ctx, node, popularity); ++ else if (is_node(node, "layoutList")) ++ parse_layout_list(ctx, node, popularity); ++ else if (is_node(node, "optionList")) ++ parse_option_list(ctx, node, popularity); ++ } ++} ++ ++static void ++ATTR_PRINTF(2, 0) ++xml_error_func(void *ctx, const char *msg, ...) ++{ ++ static char buf[PATH_MAX]; ++ static int slen = 0; ++ va_list args; ++ int rc; ++ ++ /* libxml2 prints IO errors from bad includes paths by ++ * calling the error function once per word. So we get to ++ * re-assemble the message here and print it when we get ++ * the line break. My enthusiasm about this is indescribable. ++ */ ++ va_start(args, msg); ++ rc = vsnprintf(&buf[slen], sizeof(buf) - slen, msg, args); ++ va_end(args); ++ ++ /* This shouldn't really happen */ ++ if (rc < 0) { ++ log_err(ctx, "+++ out of cheese error. redo from start +++\n"); ++ slen = 0; ++ memset(buf, 0, sizeof(buf)); ++ return; ++ } ++ ++ slen += rc; ++ if (slen >= (int)sizeof(buf)) { ++ /* truncated, let's flush this */ ++ buf[sizeof(buf) - 1] = '\n'; ++ slen = sizeof(buf); ++ } ++ ++ /* We're assuming here that the last character is \n. */ ++ if (buf[slen - 1] == '\n') { ++ log_err(ctx, "%s", buf); ++ memset(buf, 0, sizeof(buf)); ++ slen = 0; ++ } ++} ++ ++static bool ++validate(struct rxkb_context *ctx, xmlDoc *doc) ++{ ++ bool success = false; ++ xmlValidCtxt *dtdvalid = NULL; ++ xmlDtd *dtd = NULL; ++ xmlParserInputBufferPtr buf = NULL; ++ /* This is a modified version of the xkeyboard-config xkb.dtd. That one ++ * requires modelList, layoutList and optionList, we ++ * allow for any of those to be missing. ++ */ ++ const char dtdstr[] = ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n" ++ "\n"; ++ ++ /* Note: do not use xmlParserInputBufferCreateStatic, it generates random ++ * DTD validity errors for unknown reasons */ ++ buf = xmlParserInputBufferCreateMem(dtdstr, sizeof(dtdstr), ++ XML_CHAR_ENCODING_UTF8); ++ if (!buf) ++ return false; ++ ++ dtd = xmlIOParseDTD(NULL, buf, XML_CHAR_ENCODING_UTF8); ++ if (!dtd) { ++ log_err(ctx, "Failed to load DTD\n"); ++ return false; ++ } ++ ++ dtdvalid = xmlNewValidCtxt(); ++ if (xmlValidateDtd(dtdvalid, doc, dtd)) ++ success = true; ++ ++ if (dtd) ++ xmlFreeDtd(dtd); ++ if (dtdvalid) ++ xmlFreeValidCtxt(dtdvalid); ++ ++ return success; ++} ++ ++static bool ++parse(struct rxkb_context *ctx, const char *path, ++ enum rxkb_popularity popularity) ++{ ++ bool success = false; ++ xmlDoc *doc = NULL; ++ xmlNode *root = NULL; ++ ++ if (!check_eaccess(path, R_OK)) ++ return false; ++ ++ LIBXML_TEST_VERSION ++ ++ xmlSetGenericErrorFunc(ctx, xml_error_func); ++ ++ doc = xmlParseFile(path); ++ if (!doc) ++ return false; ++ ++ if (!validate(ctx, doc)) { ++ log_err(ctx, "XML error: failed to validate document at %s\n", path); ++ goto error; ++ } ++ ++ root = xmlDocGetRootElement(doc); ++ parse_rules_xml(ctx, root, popularity); ++ ++ success = true; ++error: ++ xmlFreeDoc(doc); ++ xmlCleanupParser(); ++ ++ return success; ++} +diff --git a/src/util-list.c b/src/util-list.c +new file mode 100644 +index 000000000..b6f506930 +--- /dev/null ++++ b/src/util-list.c +@@ -0,0 +1,94 @@ ++/* ++ * Copyright © 2008-2011 Kristian Høgsberg ++ * Copyright © 2011 Intel Corporation ++ * Copyright © 2013-2015 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. ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++ ++#include "util-list.h" ++ ++void ++list_init(struct list *list) ++{ ++ list->prev = list; ++ list->next = list; ++} ++ ++void ++list_insert(struct list *list, struct list *elm) ++{ ++ assert((list->next != NULL && list->prev != NULL) || ++ !"list->next|prev is NULL, possibly missing list_init()"); ++ assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || ++ !"elm->next|prev is not NULL, list node used twice?"); ++ ++ elm->prev = list; ++ elm->next = list->next; ++ list->next = elm; ++ elm->next->prev = elm; ++} ++ ++void ++list_append(struct list *list, struct list *elm) ++{ ++ assert((list->next != NULL && list->prev != NULL) || ++ !"list->next|prev is NULL, possibly missing list_init()"); ++ assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || ++ !"elm->next|prev is not NULL, list node used twice?"); ++ ++ elm->next = list; ++ elm->prev = list->prev; ++ list->prev = elm; ++ elm->prev->next = elm; ++} ++ ++void ++list_remove(struct list *elm) ++{ ++ assert((elm->next != NULL && elm->prev != NULL) || ++ !"list->next|prev is NULL, possibly missing list_init()"); ++ ++ elm->prev->next = elm->next; ++ elm->next->prev = elm->prev; ++ elm->next = NULL; ++ elm->prev = NULL; ++} ++ ++bool ++list_empty(const struct list *list) ++{ ++ assert((list->next != NULL && list->prev != NULL) || ++ !"list->next|prev is NULL, possibly missing list_init()"); ++ ++ return list->next == list; ++} ++ ++bool ++list_is_last(const struct list *list, const struct list *elm) ++{ ++ return elm->next == list; ++} +diff --git a/src/util-list.h b/src/util-list.h +new file mode 100644 +index 000000000..573dff79d +--- /dev/null ++++ b/src/util-list.h +@@ -0,0 +1,71 @@ ++/* ++ * Copyright © 2008-2011 Kristian Høgsberg ++ * Copyright © 2011 Intel Corporation ++ * Copyright © 2013-2015 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 "config.h" ++ ++#include ++#include ++ ++/* ++ * This list data structure is a verbatim copy from wayland-util.h from the ++ * Wayland project; except that wl_ prefix has been removed. ++ */ ++ ++struct list { ++ struct list *prev; ++ struct list *next; ++}; ++ ++void list_init(struct list *list); ++void list_insert(struct list *list, struct list *elm); ++void list_append(struct list *list, struct list *elm); ++void list_remove(struct list *elm); ++bool list_empty(const struct list *list); ++bool list_is_last(const struct list *list, const struct list *elm); ++ ++#define container_of(ptr, type, member) \ ++ (__typeof__(type) *)((char *)(ptr) - \ ++ offsetof(__typeof__(type), member)) ++ ++#define list_first_entry(head, pos, member) \ ++ container_of((head)->next, __typeof__(*pos), member) ++ ++#define list_last_entry(head, pos, member) \ ++ container_of((head)->prev, __typeof__(*pos), member) ++ ++#define list_for_each(pos, head, member) \ ++ for (pos = 0, pos = list_first_entry(head, pos, member); \ ++ &pos->member != (head); \ ++ pos = list_first_entry(&pos->member, pos, member)) ++ ++#define list_for_each_safe(pos, tmp, head, member) \ ++ for (pos = 0, tmp = 0, \ ++ pos = list_first_entry(head, pos, member), \ ++ tmp = list_first_entry(&pos->member, tmp, member); \ ++ &pos->member != (head); \ ++ pos = tmp, \ ++ tmp = list_first_entry(&pos->member, tmp, member)) +diff --git a/test/registry.c b/test/registry.c +new file mode 100644 +index 000000000..68e74d024 +--- /dev/null ++++ b/test/registry.c +@@ -0,0 +1,843 @@ ++/* ++ * Copyright © 2020 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. ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "xkbcommon/xkbregistry.h" ++ ++#include "utils.h" ++ ++#define NO_VARIANT NULL ++ ++enum { ++ MODEL = 78, ++ LAYOUT, ++ VARIANT, ++ OPTION, ++}; ++ ++struct test_model { ++ const char *name; /* required */ ++ const char *vendor; ++ const char *description; ++}; ++ ++struct test_layout { ++ const char *name; /* required */ ++ const char *variant; ++ const char *brief; ++ const char *description; ++}; ++ ++struct test_option { ++ const char *name; ++ const char *description; ++}; ++ ++struct test_option_group { ++ const char *name; ++ const char *description; ++ bool allow_multiple_selection; ++ ++ struct test_option options[10]; ++}; ++ ++static void ++fprint_config_item(FILE *fp, ++ const char *name, ++ const char *vendor, ++ const char *brief, ++ const char *description) ++{ ++ fprintf(fp, " \n" ++ " %s\n", name); ++ if (brief) ++ fprintf(fp, " %s\n", brief); ++ if (description) ++ fprintf(fp, " %s\n", description); ++ if (vendor) ++ fprintf(fp, " %s\n", vendor); ++ fprintf(fp, " \n"); ++} ++ ++/** ++ * Create a directory populated with a rules/.xml that contains the ++ * given items. ++ * ++ * @return the XKB base directory ++ */ ++static char * ++test_create_rules(const char *ruleset, ++ const struct test_model *test_models, ++ const struct test_layout *test_layouts, ++ const struct test_option_group *test_groups) ++{ ++ static int iteration; ++ char *tmpdir; ++ char buf[PATH_MAX]; ++ int rc; ++ FILE *fp; ++ ++ rc = asprintf(&tmpdir, "/tmp/%s.%d.XXXXXX", ruleset, iteration++); ++ assert(rc > 0); ++ assert(mkdtemp(tmpdir) == tmpdir); ++ ++ rc = snprintf_safe(buf, sizeof(buf), "%s/rules", tmpdir); ++ assert(rc); ++ rc = mkdir(buf, 0777); ++ assert(rc == 0); ++ rc = snprintf_safe(buf, sizeof(buf), "%s/rules/%s.xml", tmpdir, ruleset); ++ assert(rc); ++ ++ fp = fopen(buf, "w"); ++ assert(fp); ++ ++ fprintf(fp, ++ "\n" ++ "\n" ++ "\n"); ++ ++ if (test_models) { ++ fprintf(fp, "\n"); ++ ++ for (const struct test_model *m = test_models; m->name; m++) { ++ fprintf(fp, "\n"); ++ fprint_config_item(fp, m->name, m->vendor, NULL, m->description); ++ fprintf(fp, "\n"); ++ } ++ fprintf(fp, "\n"); ++ } ++ ++ if (test_layouts) { ++ const struct test_layout *l, *next; ++ ++ fprintf(fp, "\n"); ++ ++ l = test_layouts; ++ next = l + 1; ++ ++ assert(l->variant == NULL); ++ ++ while (l->name) { ++ fprintf(fp, "\n"); ++ fprint_config_item(fp, l->name, NULL, l->brief, l->description); ++ ++ if (next->name && streq(next->name, l->name)) { ++ fprintf(fp, "\n"); ++ do { ++ fprintf(fp, "\n"); ++ fprint_config_item(fp, next->variant, NULL, next->brief, ++ next->description); ++ fprintf(fp, "\n"); ++ l = next; ++ next++; ++ } while (next->name && streq(next->name, l->name)); ++ fprintf(fp, "\n"); ++ } ++ fprintf(fp, "\n"); ++ l++; ++ } ++ fprintf(fp, "\n"); ++ } ++ ++ if (test_groups) { ++ fprintf(fp, "\n"); ++ ++ for (const struct test_option_group *g = test_groups; g->name; g++) { ++ fprintf(fp, "\n", ++ g->allow_multiple_selection ? "true" : "false"); ++ fprint_config_item(fp, g->name, NULL, NULL, g->description); ++ for (const struct test_option *o = g->options; o->name; o++) { ++ fprintf(fp, " \n"); ++ } ++ fprintf(fp, "\n"); ++ } ++ fprintf(fp, "\n"); ++ } ++ ++ fprintf(fp, "\n"); ++ fclose(fp); ++ ++ return tmpdir; ++} ++ ++static void ++test_remove_rules(char *basedir, const char *ruleset) ++{ ++ char path[PATH_MAX]; ++ int rc; ++ ++ rc = snprintf_safe(path, sizeof(path), "%s/rules/%s.xml", basedir, ++ ruleset); ++ assert(rc); ++ unlink(path); ++ rc = snprintf_safe(path, sizeof(path), "%s/xkb/rules", basedir); ++ assert(rc); ++ rmdir(path); ++ rmdir(basedir); ++ free(basedir); ++} ++ ++static struct rxkb_context * ++test_setup_context_for(const char *ruleset, ++ struct test_model *system_models, ++ struct test_model *user_models, ++ struct test_layout *system_layouts, ++ struct test_layout *user_layouts, ++ struct test_option_group *system_groups, ++ struct test_option_group *user_groups) ++{ ++ char *sysdir = NULL, *userdir = NULL; ++ struct rxkb_context *ctx; ++ ++ sysdir = test_create_rules(ruleset, system_models, system_layouts, ++ system_groups); ++ if (user_models || user_layouts || user_groups) ++ userdir = test_create_rules(ruleset, user_models, user_layouts, ++ user_groups); ++ ++ ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES); ++ assert(ctx); ++ if (userdir) ++ assert(rxkb_context_include_path_append(ctx, userdir)); ++ assert(rxkb_context_include_path_append(ctx, sysdir)); ++ assert(rxkb_context_parse(ctx, ruleset)); ++ ++ test_remove_rules(sysdir, ruleset); ++ if (userdir) ++ test_remove_rules(userdir, ruleset); ++ ++ return ctx; ++} ++ ++static struct rxkb_context * ++test_setup_context(struct test_model *system_models, ++ struct test_model *user_models, ++ struct test_layout *system_layouts, ++ struct test_layout *user_layouts, ++ struct test_option_group *system_groups, ++ struct test_option_group *user_groups) ++{ ++ const char *ruleset = "xkbtests"; ++ return test_setup_context_for(ruleset, system_models, ++ user_models, system_layouts, ++ user_layouts, system_groups, ++ user_groups); ++} ++ ++static struct rxkb_model * ++fetch_model(struct rxkb_context *ctx, const char *model) ++{ ++ struct rxkb_model *m = rxkb_model_first(ctx); ++ while (m) { ++ if (streq(rxkb_model_get_name(m), model)) ++ return rxkb_model_ref(m); ++ m = rxkb_model_next(m); ++ } ++ return NULL; ++} ++ ++static bool ++find_model(struct rxkb_context *ctx, const char *model) ++{ ++ struct rxkb_model *m = fetch_model(ctx, model); ++ rxkb_model_unref(m); ++ return m != NULL; ++} ++ ++static bool ++find_models(struct rxkb_context *ctx, ...) ++{ ++ va_list args; ++ const char *name; ++ int idx = 0; ++ ++ va_start(args, ctx); ++ name = va_arg(args, const char *); ++ while(name) { ++ assert(++idx < 20); /* safety guard */ ++ if (!find_model(ctx, name)) ++ return false; ++ name = va_arg(args, const char *); ++ }; ++ ++ va_end(args); ++ return true; ++} ++ ++static struct rxkb_layout * ++fetch_layout(struct rxkb_context *ctx, const char *layout, const char *variant) ++{ ++ struct rxkb_layout *l = rxkb_layout_first(ctx); ++ while (l) { ++ const char *v = rxkb_layout_get_variant(l); ++ ++ if (streq(rxkb_layout_get_name(l), layout) && ++ ((v == NULL && variant == NULL) || ++ (v != NULL && variant != NULL && streq(v, variant)))) ++ return rxkb_layout_ref(l); ++ l = rxkb_layout_next(l); ++ } ++ return NULL; ++} ++ ++static bool ++find_layout(struct rxkb_context *ctx, const char *layout, const char *variant) ++{ ++ struct rxkb_layout *l = fetch_layout(ctx, layout, variant); ++ rxkb_layout_unref(l); ++ return l != NULL; ++} ++ ++static bool ++find_layouts(struct rxkb_context *ctx, ...) ++{ ++ va_list args; ++ const char *name, *variant; ++ int idx = 0; ++ ++ va_start(args, ctx); ++ name = va_arg(args, const char *); ++ variant = va_arg(args, const char *); ++ while(name) { ++ assert(++idx < 20); /* safety guard */ ++ if (!find_layout(ctx, name, variant)) ++ return false; ++ name = va_arg(args, const char *); ++ if (name) ++ variant = va_arg(args, const char *); ++ }; ++ ++ va_end(args); ++ return true; ++} ++ ++static struct rxkb_option_group * ++fetch_option_group(struct rxkb_context *ctx, const char *grp) ++{ ++ struct rxkb_option_group *g = rxkb_option_group_first(ctx); ++ while (g) { ++ if (streq(grp, rxkb_option_group_get_name(g))) ++ return rxkb_option_group_ref(g); ++ g = rxkb_option_group_next(g); ++ } ++ return NULL; ++} ++ ++static inline bool ++find_option_group(struct rxkb_context *ctx, const char *grp) ++{ ++ struct rxkb_option_group *g = fetch_option_group(ctx, grp); ++ rxkb_option_group_unref(g); ++ return g != NULL; ++} ++ ++static struct rxkb_option * ++fetch_option(struct rxkb_context *ctx, const char *grp, const char *opt) ++{ ++ struct rxkb_option_group *g = rxkb_option_group_first(ctx); ++ while (g) { ++ if (streq(grp, rxkb_option_group_get_name(g))) { ++ struct rxkb_option *o = rxkb_option_first(g); ++ ++ while (o) { ++ if (streq(opt, rxkb_option_get_name(o))) ++ return rxkb_option_ref(o); ++ o = rxkb_option_next(o); ++ } ++ } ++ g = rxkb_option_group_next(g); ++ } ++ return NULL; ++} ++ ++static bool ++find_option(struct rxkb_context *ctx, const char *grp, const char *opt) ++{ ++ struct rxkb_option *o = fetch_option(ctx, grp, opt); ++ rxkb_option_unref(o); ++ return o != NULL; ++} ++ ++static bool ++find_options(struct rxkb_context *ctx, ...) ++{ ++ va_list args; ++ const char *grp, *opt; ++ int idx = 0; ++ ++ va_start(args, ctx); ++ grp = va_arg(args, const char *); ++ opt = va_arg(args, const char *); ++ while(grp) { ++ assert(++idx < 20); /* safety guard */ ++ if (!find_option(ctx, grp, opt)) ++ return false; ++ grp = va_arg(args, const char *); ++ if (grp) ++ opt = va_arg(args, const char *); ++ }; ++ ++ va_end(args); ++ return true; ++} ++ ++static bool ++cmp_models(struct test_model *tm, struct rxkb_model *m) ++{ ++ if (!tm || !m) ++ return false; ++ ++ if (!streq(tm->name, rxkb_model_get_name(m))) ++ return false; ++ ++ if (!streq_null(tm->vendor, rxkb_model_get_vendor(m))) ++ return false; ++ ++ if (!streq_null(tm->description, rxkb_model_get_description(m))) ++ return false; ++ ++ return true; ++} ++ ++static bool ++cmp_layouts(struct test_layout *tl, struct rxkb_layout *l) ++{ ++ if (!tl || !l) ++ return false; ++ ++ if (!streq(tl->name, rxkb_layout_get_name(l))) ++ return false; ++ ++ if (!streq_null(tl->variant, rxkb_layout_get_variant(l))) ++ return false; ++ ++ if (!streq_null(tl->brief, rxkb_layout_get_brief(l))) ++ return false; ++ ++ if (!streq_null(tl->description, rxkb_layout_get_description(l))) ++ return false; ++ ++ return true; ++} ++ ++static bool ++cmp_options(struct test_option *to, struct rxkb_option *o) ++{ ++ if (!to || !o) ++ return false; ++ ++ if (!streq(to->name, rxkb_option_get_name(o))) ++ return false; ++ ++ if (!streq_null(to->description, rxkb_option_get_description(o))) ++ return false; ++ ++ return true; ++} ++ ++enum cmp_type { ++ CMP_EXACT, ++ CMP_MATCHING_ONLY, ++}; ++ ++static bool ++cmp_option_groups(struct test_option_group *tg, struct rxkb_option_group *g, ++ enum cmp_type cmp) ++{ ++ struct rxkb_option *o; ++ struct test_option *to; ++ ++ if (!tg || !g) ++ return false; ++ ++ if (!streq(tg->name, rxkb_option_group_get_name(g))) ++ return false; ++ ++ if (!streq_null(tg->description, rxkb_option_group_get_description(g))) ++ return false; ++ ++ if (tg->allow_multiple_selection != rxkb_option_group_allows_multiple(g)) ++ return false; ++ ++ to = tg->options; ++ o = rxkb_option_first(g); ++ ++ while (o && to->name) { ++ if (!cmp_options(to, o)) ++ return false; ++ to++; ++ o = rxkb_option_next(o); ++ } ++ ++ if (cmp == CMP_EXACT && (o || to->name)) ++ return false; ++ ++ return true; ++} ++ ++static void ++test_load_basic(void) ++{ ++ struct test_model system_models[] = { ++ {"m1"}, ++ {"m2"}, ++ {NULL}, ++ }; ++ struct test_layout system_layouts[] = { ++ {"l1"}, ++ {"l1", "v1"}, ++ {NULL}, ++ }; ++ struct test_option_group system_groups[] = { ++ {"grp1", NULL, true, ++ { {"grp1:1"}, {"grp1:2"} } }, ++ {"grp2", NULL, false, ++ { {"grp2:1"}, {"grp2:2"} } }, ++ { NULL }, ++ }; ++ struct rxkb_context *ctx; ++ ++ ctx = test_setup_context(system_models, NULL, ++ system_layouts, NULL, ++ system_groups, NULL); ++ ++ assert(find_models(ctx, "m1", "m2", NULL)); ++ assert(find_layouts(ctx, "l1", NO_VARIANT, ++ "l1", "v1", NULL)); ++ assert(find_options(ctx, "grp1", "grp1:1", ++ "grp1", "grp1:2", ++ "grp2", "grp2:1", ++ "grp2", "grp2:2", NULL)); ++ rxkb_context_unref(ctx); ++} ++ ++static void ++test_load_full(void) ++{ ++ struct test_model system_models[] = { ++ {"m1", "vendor1", "desc1"}, ++ {"m2", "vendor2", "desc2"}, ++ {NULL}, ++ }; ++ struct test_layout system_layouts[] = { ++ {"l1", NO_VARIANT, "lbrief1", "ldesc1"}, ++ {"l1", "v1", "vbrief1", "vdesc1"}, ++ {NULL}, ++ }; ++ struct test_option_group system_groups[] = { ++ {"grp1", "gdesc1", true, ++ { {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } }, ++ {"grp2", "gdesc2", false, ++ { {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } }, ++ { NULL }, ++ }; ++ struct rxkb_context *ctx; ++ struct rxkb_model *m; ++ struct rxkb_layout *l; ++ struct rxkb_option_group *g; ++ ++ ctx = test_setup_context(system_models, NULL, ++ system_layouts, NULL, ++ system_groups, NULL); ++ ++ m = fetch_model(ctx, "m1"); ++ assert(cmp_models(&system_models[0], m)); ++ rxkb_model_unref(m); ++ ++ m = fetch_model(ctx, "m2"); ++ assert(cmp_models(&system_models[1], m)); ++ rxkb_model_unref(m); ++ ++ l = fetch_layout(ctx, "l1", NO_VARIANT); ++ assert(cmp_layouts(&system_layouts[0], l)); ++ rxkb_layout_unref(l); ++ ++ l = fetch_layout(ctx, "l1", "v1"); ++ assert(cmp_layouts(&system_layouts[1], l)); ++ rxkb_layout_unref(l); ++ ++ g = fetch_option_group(ctx, "grp1"); ++ assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT)); ++ rxkb_option_group_unref(g); ++ ++ g = fetch_option_group(ctx, "grp2"); ++ assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT)); ++ rxkb_option_group_unref(g); ++ ++ rxkb_context_unref(ctx); ++} ++ ++static void ++test_popularity(void) ++{ ++ struct test_layout system_layouts[] = { ++ {"l1", NO_VARIANT }, ++ {"l1", "v1" }, ++ {NULL}, ++ }; ++ struct rxkb_context *ctx; ++ struct rxkb_layout *l; ++ const char *ruleset = "xkbtests.extras"; ++ char *dir = NULL; ++ ++ dir = test_create_rules(ruleset, NULL, system_layouts, NULL); ++ ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES | ++ RXKB_CONTEXT_LOAD_EXOTIC_RULES); ++ assert(ctx); ++ assert(rxkb_context_include_path_append(ctx, dir)); ++ /* Hack: rulest above generates xkbtests.extras.xml, loading "xkbtests" ++ * means the extras file counts as exotic */ ++ assert(rxkb_context_parse(ctx, "xkbtests")); ++ ++ l = fetch_layout(ctx, "l1", NO_VARIANT); ++ assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC); ++ rxkb_layout_unref(l); ++ ++ l = fetch_layout(ctx, "l1", "v1"); ++ assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC); ++ rxkb_layout_unref(l); ++ ++ test_remove_rules(dir, ruleset); ++ rxkb_context_unref(ctx); ++} ++ ++ ++static void ++test_load_merge(void) ++{ ++ struct test_model system_models[] = { ++ {"m1", "vendor1", "desc1"}, ++ {"m2", "vendor2", "desc2"}, ++ {NULL}, ++ }; ++ struct test_model user_models[] = { ++ {"m3", "vendor3", "desc3"}, ++ {"m4", "vendor4", "desc4"}, ++ {NULL}, ++ }; ++ struct test_layout system_layouts[] = { ++ {"l1", NO_VARIANT, "lbrief1", "ldesc1"}, ++ {"l1", "v1", "vbrief1", "vdesc1"}, ++ {NULL}, ++ }; ++ struct test_layout user_layouts[] = { ++ {"l2", NO_VARIANT, "lbrief2", "ldesc2"}, ++ {"l2", "v2", "vbrief2", "vdesc2"}, ++ {NULL}, ++ }; ++ struct test_option_group system_groups[] = { ++ {"grp1", NULL, true, ++ { {"grp1:1"}, {"grp1:2"} } }, ++ {"grp2", NULL, false, ++ { {"grp2:1"}, {"grp2:2"} } }, ++ { NULL }, ++ }; ++ struct test_option_group user_groups[] = { ++ {"grp3", NULL, true, ++ { {"grp3:1"}, {"grp3:2"} } }, ++ {"grp4", NULL, false, ++ { {"grp4:1"}, {"grp4:2"} } }, ++ { NULL }, ++ }; ++ struct rxkb_context *ctx; ++ struct rxkb_model *m; ++ struct rxkb_layout *l; ++ struct rxkb_option_group *g; ++ ++ ctx = test_setup_context(system_models, user_models, ++ system_layouts, user_layouts, ++ system_groups, user_groups); ++ ++ assert(find_models(ctx, "m1", "m2", "m3", "m4", NULL)); ++ assert(find_layouts(ctx, "l1", NO_VARIANT, ++ "l1", "v1", ++ "l2", NO_VARIANT, ++ "l2", "v2", NULL)); ++ ++ m = fetch_model(ctx, "m1"); ++ assert(cmp_models(&system_models[0], m)); ++ rxkb_model_unref(m); ++ ++ m = fetch_model(ctx, "m2"); ++ assert(cmp_models(&system_models[1], m)); ++ rxkb_model_unref(m); ++ ++ m = fetch_model(ctx, "m3"); ++ assert(cmp_models(&user_models[0], m)); ++ rxkb_model_unref(m); ++ ++ m = fetch_model(ctx, "m4"); ++ assert(cmp_models(&user_models[1], m)); ++ rxkb_model_unref(m); ++ ++ l = fetch_layout(ctx, "l1", NO_VARIANT); ++ assert(cmp_layouts(&system_layouts[0], l)); ++ rxkb_layout_unref(l); ++ ++ l = fetch_layout(ctx, "l1", "v1"); ++ assert(cmp_layouts(&system_layouts[1], l)); ++ rxkb_layout_unref(l); ++ ++ l = fetch_layout(ctx, "l2", NO_VARIANT); ++ assert(cmp_layouts(&user_layouts[0], l)); ++ rxkb_layout_unref(l); ++ ++ l = fetch_layout(ctx, "l2", "v2"); ++ assert(cmp_layouts(&user_layouts[1], l)); ++ rxkb_layout_unref(l); ++ ++ g = fetch_option_group(ctx, "grp1"); ++ assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT)); ++ rxkb_option_group_unref(g); ++ ++ g = fetch_option_group(ctx, "grp2"); ++ assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT)); ++ rxkb_option_group_unref(g); ++ ++ g = fetch_option_group(ctx, "grp3"); ++ assert(cmp_option_groups(&user_groups[0], g, CMP_EXACT)); ++ rxkb_option_group_unref(g); ++ ++ g = fetch_option_group(ctx, "grp4"); ++ assert(cmp_option_groups(&user_groups[1], g, CMP_EXACT)); ++ rxkb_option_group_unref(g); ++ ++ rxkb_context_unref(ctx); ++} ++ ++static void ++test_load_merge_no_overwrite(void) ++{ ++ struct test_model system_models[] = { ++ {"m1", "vendor1", "desc1"}, ++ {"m2", "vendor2", "desc2"}, ++ {NULL}, ++ }; ++ struct test_model user_models[] = { ++ {"m1", "vendor3", "desc3"}, /* must not overwrite */ ++ {"m4", "vendor4", "desc4"}, ++ {NULL}, ++ }; ++ struct test_layout system_layouts[] = { ++ {"l1", NO_VARIANT, "lbrief1", "ldesc1"}, ++ {"l1", "v1", "vbrief1", "vdesc1"}, ++ {NULL}, ++ }; ++ struct test_layout user_layouts[] = { ++ {"l2", NO_VARIANT, "lbrief2", "ldesc2"}, ++ {"l2", "v2", "vbrief2", "vdesc2"}, ++ {"l1", NO_VARIANT, "lbrief3", "ldesc3"}, /* must not overwrite */ ++ {"l1", "v2", "vbrief3", "vdesc3"}, /* must not overwrite */ ++ {NULL}, ++ }; ++ struct test_option_group system_groups[] = { ++ {"grp1", "gdesc1", true, ++ { {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } }, ++ {"grp2", "gdesc2", false, ++ { {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } }, ++ { NULL }, ++ }; ++ struct test_option_group user_groups[] = { ++ {"grp1", "XXXXX", false, /* must not overwrite */ ++ { {"grp1:1", "YYYYYYY"}, /* must not overwrite */ ++ {"grp1:3", "ZZZZZZ"} } }, /* append */ ++ {"grp4", "gdesc4", false, ++ { {"grp4:1", "odesc41"}, {"grp4:2", "odesc42"} } }, ++ { NULL }, ++ }; ++ struct rxkb_context *ctx; ++ struct rxkb_model *m; ++ struct rxkb_layout *l; ++ struct rxkb_option_group *g; ++ ++ ctx = test_setup_context(system_models, user_models, ++ system_layouts, user_layouts, ++ system_groups, user_groups); ++ ++ m = fetch_model(ctx, "m1"); ++ assert(cmp_models(&system_models[0], m)); ++ rxkb_model_unref(m); ++ ++ l = fetch_layout(ctx, "l1", NO_VARIANT); ++ assert(cmp_layouts(&system_layouts[0], l)); ++ rxkb_layout_unref(l); ++ ++ l = fetch_layout(ctx, "l1", "v1"); ++ assert(cmp_layouts(&system_layouts[1], l)); ++ rxkb_layout_unref(l); ++ ++ assert(find_option(ctx, "grp1", "grp1:3")); ++ g = fetch_option_group(ctx, "grp1"); ++ assert(cmp_option_groups(&system_groups[0], g, CMP_MATCHING_ONLY)); ++ rxkb_option_group_unref(g); ++ ++ rxkb_context_unref(ctx); ++} ++ ++static void ++test_no_include_paths(void) ++{ ++ struct rxkb_context *ctx; ++ ++ ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES); ++ assert(ctx); ++ assert(!rxkb_context_parse_default_ruleset(ctx)); ++ ++ rxkb_context_unref(ctx); ++} ++ ++static void ++test_invalid_include(void) ++{ ++ struct rxkb_context *ctx; ++ ++ ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES); ++ assert(ctx); ++ assert(!rxkb_context_include_path_append(ctx, "/foo/bar/baz/bat")); ++ assert(!rxkb_context_parse_default_ruleset(ctx)); ++ ++ rxkb_context_unref(ctx); ++} ++ ++int ++main(void) ++{ ++ test_no_include_paths(); ++ test_invalid_include(); ++ test_load_basic(); ++ test_load_full(); ++ test_load_merge(); ++ test_load_merge_no_overwrite(); ++ test_popularity(); ++ ++ return 0; ++} +diff --git a/tools/registry-list.c b/tools/registry-list.c +new file mode 100644 +index 000000000..e51c41fb7 +--- /dev/null ++++ b/tools/registry-list.c +@@ -0,0 +1,223 @@ ++/* ++ * Copyright © 2020 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. ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++ ++#include "xkbcommon/xkbregistry.h" ++ ++static void ++usage(const char *progname) ++{ ++ fprintf(stderr, ++ "Usage: %s [OPTIONS] [/path/to/xkb_base_directory [/path2]...]\n" ++ "\n" ++ "Options:\n" ++ " --verbose, -v .......... Increase verbosity, use multiple times for debugging output\n" ++ " --ruleset=foo .......... Load the 'foo' ruleset\n" ++ " --skip-default-paths ... Do not load the default XKB paths\n" ++ " --load-exotic .......... Load the exotic (extra) rulesets\n" ++ "\n" ++ "Trailing arguments are treated as XKB base directory installations.\n", ++ progname); ++} ++ ++int ++main(int argc, char **argv) ++{ ++ int rc = 1; ++ struct rxkb_context *ctx = NULL; ++ struct rxkb_model *m; ++ struct rxkb_layout *l; ++ struct rxkb_option_group *g; ++ enum rxkb_context_flags flags = RXKB_CONTEXT_NO_FLAGS; ++ bool load_defaults = true; ++ int verbosity = 0; ++ const char *ruleset = DEFAULT_XKB_RULES; ++ ++ static const struct option opts[] = { ++ {"help", no_argument, 0, 'h'}, ++ {"verbose", no_argument, 0, 'v'}, ++ {"load-exotic", no_argument, 0, 'e'}, ++ {"skip-default-paths", no_argument, 0, 'd'}, ++ {"ruleset", required_argument, 0, 'r'}, ++ {0, 0, 0, 0}, ++ }; ++ ++ while (1) { ++ int c; ++ int option_index = 0; ++ ++ c = getopt_long(argc, argv, "hev", opts, &option_index); ++ if (c == -1) ++ break; ++ ++ switch (c) { ++ case 'h': ++ case '?': ++ usage(argv[0]); ++ return 1; ++ case 'd': ++ load_defaults = false; ++ break; ++ case 'e': ++ flags |= RXKB_CONTEXT_LOAD_EXOTIC_RULES; ++ break; ++ case 'r': ++ ruleset = optarg; ++ break; ++ case 'v': ++ verbosity++; ++ break; ++ } ++ } ++ ++ if (optind < argc) ++ flags |= RXKB_CONTEXT_NO_DEFAULT_INCLUDES; ++ ++ ctx = rxkb_context_new(flags); ++ assert(ctx); ++ ++ switch (verbosity) { ++ case 0: ++ rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_ERROR); ++ break; ++ case 1: ++ rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_INFO); ++ break; ++ default: ++ rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_DEBUG); ++ break; ++ } ++ ++ if (optind < argc) { ++ for (int i = optind; i < argc; i++) { ++ if (!rxkb_context_include_path_append(ctx, argv[i])) { ++ fprintf(stderr, "Failed to append include path '%s'\n", ++ argv[i]); ++ goto err; ++ } ++ } ++ ++ if (load_defaults) { ++ if (!rxkb_context_include_path_append_default(ctx)) { ++ fprintf(stderr, "Failed to include default paths.\n"); ++ goto err; ++ } ++ } ++ } ++ if (!rxkb_context_parse(ctx, ruleset)) { ++ fprintf(stderr, "Failed to parse XKB descriptions.\n"); ++ goto err; ++ } ++ ++ printf("Models:\n"); ++ m = rxkb_model_first(ctx); ++ assert(m); /* Empty model list is usually a bug or a bad xml file */ ++ while (m) { ++ printf("- %s:%s:%s\n", ++ rxkb_model_get_name(m), ++ rxkb_model_get_vendor(m), ++ rxkb_model_get_description(m)); ++ m = rxkb_model_next(m); ++ } ++ ++ printf("\n"); ++ printf("Layouts:\n"); ++ l = rxkb_layout_first(ctx); ++ assert(l); /* Empty layout list is usually a bug or a bad xml file */ ++ while (l) { ++ struct rxkb_iso639_code *iso639; ++ struct rxkb_iso3166_code *iso3166; ++ const char *variant = rxkb_layout_get_variant(l); ++ const char *brief = rxkb_layout_get_brief(l); ++ bool first; ++ ++ printf("- %s%s%s%s:%s:%s", ++ rxkb_layout_get_name(l), ++ variant ? "(" : "", ++ variant ? variant : "", ++ variant ? ")" : "", ++ brief ? brief : "", ++ rxkb_layout_get_description(l)); ++ ++ iso639 = rxkb_layout_get_iso639_first(l); ++ if (iso639) ++ printf(":iso639-"); ++ first = true; ++ while (iso639) { ++ printf("%s%s", first ? "" : ",", rxkb_iso639_code_get_code(iso639)); ++ iso639 = rxkb_iso639_code_next(iso639); ++ first = false; ++ } ++ iso3166 = rxkb_layout_get_iso3166_first(l); ++ if (iso3166) ++ printf(":iso3166-"); ++ first = true; ++ while (iso3166) { ++ printf("%s%s", first ? "" : ",", rxkb_iso3166_code_get_code(iso3166)); ++ iso3166 = rxkb_iso3166_code_next(iso3166); ++ first = false; ++ } ++ ++ printf("\n"); ++ l = rxkb_layout_next(l); ++ } ++ printf("\n"); ++ printf("Options:\n"); ++ g = rxkb_option_group_first(ctx); ++ assert(g); /* Empty option goups list is usually a bug or a bad xml file */ ++ while (g) { ++ struct rxkb_option *o; ++ ++ printf("- %s:%s (%s)\n", ++ rxkb_option_group_get_name(g), ++ rxkb_option_group_get_description(g), ++ rxkb_option_group_allows_multiple(g) ? "multiple" : "single"); ++ ++ o = rxkb_option_first(g); ++ assert(o); /* Empty option list is usually a bug or a bad xml file */ ++ while (o) { ++ const char *brief = rxkb_option_get_brief(o); ++ ++ printf(" - %s:%s:%s\n", ++ rxkb_option_get_name(o), ++ brief ? brief : "", ++ rxkb_option_get_description(o)); ++ o = rxkb_option_next(o); ++ } ++ ++ g = rxkb_option_group_next(g); ++ } ++ ++ rc = 0; ++ ++err: ++ if (ctx) ++ rxkb_context_unref(ctx); ++ ++ return rc; ++} +diff --git a/xkbcommon/xkbregistry.h b/xkbcommon/xkbregistry.h +new file mode 100644 +index 000000000..4e7e926c4 +--- /dev/null ++++ b/xkbcommon/xkbregistry.h +@@ -0,0 +1,782 @@ ++/* ++ * Copyright © 2020 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. ++ */ ++ ++ ++#ifndef _XKBREGISTRY_H_ ++#define _XKBREGISTRY_H_ ++ ++#include ++#include ++ ++/** ++ * @file ++ * @brief Query for available RMLVO ++ * ++ */ ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++/** ++ * @defgroup registry Query for available RMLVO ++ * ++ * The libxkbregistry API to query for available rules, models, layouts, ++ * variants and options (RMLVO). libxkbregistry is a separate library to ++ * libxkbcommon. ++ * ++ * This library is the replacement for clients currently parsing evdev.xml ++ * directly. The library is intended to provide easy access to the set of ++ * **possible** MLVO configurations for a given ruleset. It is not a library to ++ * apply these configurations, merely to enumerate them. The intended users of ++ * this library are the configuration UIs that allow a user to select their ++ * keyboard layout of choice. ++ * ++ * @{ ++ */ ++ ++/** ++ * @struct rxkb_context ++ * ++ * Opaque top level library context object. ++ * ++ * The context contains general library state, like include paths and parsed ++ * data. Objects are created in a specific context, and multiple contexts ++ * may coexist simultaneously. Objects from different contexts are ++ * completely separated and do not share any memory or state. ++ */ ++struct rxkb_context; ++ ++/** ++ * @struct rxkb_model ++ * ++ * Opaque struct representing an XKB model. ++ */ ++struct rxkb_model; ++ ++/** ++ * @struct rxkb_layout ++ * ++ * Opaque struct representing an XKB layout, including an optional variant. ++ * Where the variant is NULL, the layout is the base layout. ++ * ++ * For example, "us" is the base layout, "us(intl)" is the "intl" variant of the ++ * layout "us". ++ */ ++struct rxkb_layout; ++ ++/** ++ * @struct rxkb_option_group ++ * ++ * Opaque struct representing an option group. Option groups divide the ++ * individual options into logical groups. Their main purpose is to indicate ++ * whether some options are mutually exclusive or not. ++ */ ++struct rxkb_option_group; ++ ++/** ++ * @struct rxkb_option ++ * ++ * Opaque struct representing an XKB option. Options are grouped inside an @ref ++ * rxkb_option_group. ++ */ ++struct rxkb_option; ++ ++/** ++ * ++ * @struct rxkb_iso639_code ++ * ++ * Opaque struct representing an ISO 639-3 code (e.g. "eng", "fra"). There ++ * is no guarantee that two identical ISO codes share the same struct. You ++ * must not rely on the pointer value of this struct. ++ * ++ * See https://iso639-3.sil.org/code_tables/639/data for a list of codes. ++ */ ++struct rxkb_iso639_code; ++ ++/** ++ * ++ * @struct rxkb_iso3166_code ++ * ++ * Opaque struct representing an ISO 3166 Alpha 2 code (e.g. "US", "FR"). ++ * There is no guarantee that two identical ISO codes share the same struct. ++ * You must not rely on the pointer value of this struct. ++ * ++ * See https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes for a list ++ * of codes. ++ */ ++struct rxkb_iso3166_code; ++ ++/** ++ * Describes the popularity of an item. Historically, some highly specialized or ++ * experimental definitions are excluded from the default list and shipped in ++ * separate files. If these extra definitions are loaded (see @ref ++ * RXKB_CONTEXT_LOAD_EXOTIC_RULES), the popularity of the item is set ++ * accordingly. ++ * ++ * If the exotic items are not loaded, all items will have the standard ++ * popularity. ++ */ ++enum rxkb_popularity { ++ RXKB_POPULARITY_STANDARD = 1, ++ RXKB_POPULARITY_EXOTIC, ++}; ++ ++/** ++ * Flags for context creation. ++ */ ++enum rxkb_context_flags { ++ RXKB_CONTEXT_NO_FLAGS = 0, ++ /** ++ * Skip the default include paths. This requires the caller to call ++ * rxkb_context_include_path_append() or ++ * rxkb_context_include_path_append_default(). ++ */ ++ RXKB_CONTEXT_NO_DEFAULT_INCLUDES = (1 << 0), ++ /** ++ * Load the extra items that are considered too exotic for the default list. ++ * ++ * For historical reasons, xkeyboard-config ships those exotic rules in a ++ * separate file (e.g. `evdev.extras.xml`). Where the exotic rules are ++ * requested, libxkbregistry will look for and load `$ruleset.extras.xml` ++ * in the include paths, see rxkb_context_include_path_append() for details ++ * on the lookup behavior. ++ */ ++ RXKB_CONTEXT_LOAD_EXOTIC_RULES = (1 << 1), ++}; ++ ++/** ++ * Create a new xkb registry context. ++ * ++ * The context has an initial refcount of 1. Use rxkb_context_unref() to release ++ * memory associated with this context. ++ * ++ * Creating a context does not parse the files yet, use ++ * rxkb_context_parse(). ++ * ++ * @param flags Flags affecting context behavior ++ * @return A new xkb registry context or NULL on failure ++ */ ++struct rxkb_context * ++rxkb_context_new(enum rxkb_context_flags flags); ++ ++/** Specifies a logging level. */ ++enum rxkb_log_level { ++ RXKB_LOG_LEVEL_CRITICAL = 10, /**< Log critical internal errors only. */ ++ RXKB_LOG_LEVEL_ERROR = 20, /**< Log all errors. */ ++ RXKB_LOG_LEVEL_WARNING = 30, /**< Log warnings and errors. */ ++ RXKB_LOG_LEVEL_INFO = 40, /**< Log information, warnings, and errors. */ ++ RXKB_LOG_LEVEL_DEBUG = 50 /**< Log everything. */ ++}; ++ ++/** ++ * Set the current logging level. ++ * ++ * @param ctx The context in which to set the logging level. ++ * @param level The logging level to use. Only messages from this level ++ * and below will be logged. ++ * ++ * The default level is RXKB_LOG_LEVEL_ERROR. The environment variable ++ * RXKB_LOG_LEVEL, if set at the time the context was created, overrides the ++ * default value. It may be specified as a level number or name. ++ */ ++void ++rxkb_context_set_log_level(struct rxkb_context *ctx, ++ enum rxkb_log_level level); ++ ++/** ++ * Get the current logging level. ++ */ ++enum rxkb_log_level ++rxkb_context_get_log_level(struct rxkb_context *ctx); ++ ++/** ++ * Set a custom function to handle logging messages. ++ * ++ * @param ctx The context in which to use the set logging function. ++ * @param log_fn The function that will be called for logging messages. ++ * Passing NULL restores the default function, which logs to stderr. ++ * ++ * By default, log messages from this library are printed to stderr. This ++ * function allows you to replace the default behavior with a custom ++ * handler. The handler is only called with messages which match the ++ * current logging level and verbosity settings for the context. ++ * level is the logging level of the message. @a format and @a args are ++ * the same as in the vprintf(3) function. ++ * ++ * You may use rxkb_context_set_user_data() on the context, and then call ++ * rxkb_context_get_user_data() from within the logging function to provide ++ * it with additional private context. ++ */ ++void ++rxkb_context_set_log_fn(struct rxkb_context *ctx, ++ void (*log_fn)(struct rxkb_context *ctx, ++ enum rxkb_log_level level, ++ const char *format, va_list args)); ++ ++ ++/** ++ * Parse the given ruleset. This can only be called once per context and once ++ * parsed the data in the context is considered constant and will never ++ * change. ++ * ++ * This function parses all files with the given ruleset name. See ++ * rxkb_context_include_path_append() for details. ++ * ++ * If this function returns false, libxkbregistry failed to parse the xml files. ++ * This is usually caused by invalid files on the host and should be debugged by ++ * the host's administrator using external tools. Callers should reduce the ++ * include paths to known good paths and/or fall back to a default RMLVO set. ++ * ++ * If this function returns false, the context should be be considered dead and ++ * must be released with rxkb_context_unref(). ++ * ++ * @param ctx The xkb registry context ++ * @param ruleset The ruleset to parse, e.g. "evdev" ++ * @return true on success or false on failure ++ */ ++bool ++rxkb_context_parse(struct rxkb_context *ctx, const char *ruleset); ++ ++/** ++ * Parse the default ruleset as configured at build time. See ++ * rxkb_context_parse() for details. ++ */ ++bool ++rxkb_context_parse_default_ruleset(struct rxkb_context *ctx); ++ ++/** ++ * Increases the refcount of this object by one and returns the object. ++ * ++ * @param ctx The xkb registry context ++ * @return The passed in object ++ */ ++struct rxkb_context* ++rxkb_context_ref(struct rxkb_context *ctx); ++ ++/** ++ * Decreases the refcount of this object by one. Where the refcount of an ++ * object hits zero, associated resources will be freed. ++ * ++ * @param ctx The xkb registry context ++ * @return always NULL ++ */ ++struct rxkb_context* ++rxkb_context_unref(struct rxkb_context *ctx); ++ ++/** ++ * Assign user-specific data. libxkbregistry will not look at or modify the ++ * data, it will merely return the same pointer in ++ * rxkb_context_get_user_data(). ++ * ++ * @param ctx The xkb registry context ++ * @param user_data User-specific data pointer ++ */ ++void ++rxkb_context_set_user_data(struct rxkb_context *ctx, void *user_data); ++ ++/** ++ * Return the pointer passed into rxkb_context_get_user_data(). ++ * ++ * @param ctx The xkb registry context ++ * @return User-specific data pointer ++ */ ++void * ++rxkb_context_get_user_data(struct rxkb_context *ctx); ++ ++/** ++ * Append a new entry to the context's include path. ++ * ++ * The include path handling is optimized for the most common use-case: a set of ++ * system files that provide a complete set of MLVO and some ++ * custom MLVO provided by a user **in addition** to the system set. ++ * ++ * The include paths should be given so that the least complete path is ++ * specified first and the most complete path is appended last. For example: ++ * ++ * @code ++ * ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES); ++ * rxkb_context_include_path_append(ctx, "/home/user/.config/xkb"); ++ * rxkb_context_include_path_append(ctx, "/usr/share/X11/xkb"); ++ * rxkb_context_parse(ctx, "evdev"); ++ * @endcode ++ * ++ * The above example reflects the default behavior unless @ref ++ * RXKB_CONTEXT_NO_DEFAULT_INCLUDES is provided. ++ * ++ * Loading of the files is in **reverse order**, i.e. the last path appended is ++ * loaded first - in this case the ``/usr/share/X11/xkb`` path. ++ * Any models, layouts, variants and options defined in the "evdev" ruleset ++ * are loaded into the context. Then, any RMLVO found in the "evdev" ruleset of ++ * the user's path (``/home/user/.config/xkb`` in this example) are **appended** ++ * to the existing set. ++ * ++ * Note that data from previously loaded include paths is never overwritten, ++ * only appended to. It is not not possible to change the system-provided data, ++ * only to append new models, layouts, variants and options to it. ++ * ++ * In other words, to define a new variant of the "us" layout called "banana", ++ * the following XML is sufficient. ++ * ++ * @verbatim ++ * ++ * ++ * ++ * ++ * us ++ * ++ * ++ * ++ * ++ * banana ++ * English (Banana) ++ * ++ * ++ * ++ * ++ * ++ * @endverbatim ++ * ++ * The list of models, options and all other layouts (including "us" and its ++ * variants) is taken from the system files. The resulting list of layouts will ++ * thus have a "us" keyboard layout with the variant "banana" and all other ++ * system-provided variants (dvorak, colemak, intl, etc.) ++ * ++ * This function must be called before rxkb_context_parse() or ++ * rxkb_context_parse_default_ruleset(). ++ * ++ * @returns true on success, or false if the include path could not be added ++ * or is inaccessible. ++ */ ++bool ++rxkb_context_include_path_append(struct rxkb_context *ctx, const char *path); ++ ++/** ++ * Append the default include paths to the context's include path. ++ * See rxkb_context_include_path_append() for details about the merge order. ++ * ++ * This function must be called before rxkb_context_parse() or ++ * rxkb_context_parse_default_ruleset(). ++ * ++ * @returns true on success, or false if the include path could not be added ++ * or is inaccessible. ++ */ ++bool ++rxkb_context_include_path_append_default(struct rxkb_context *ctx); ++ ++/** ++ * Return the first model for this context. Use this to start iterating over ++ * the models, followed by calls to rxkb_model_next(). Models are not sorted. ++ * ++ * The refcount of the returned model is not increased. Use rxkb_model_ref() if ++ * you need to keep this struct outside the immediate scope. ++ * ++ * @return The first model in the model list. ++ */ ++struct rxkb_model * ++rxkb_model_first(struct rxkb_context *ctx); ++ ++/** ++ * Return the next model for this context. Returns NULL when no more models ++ * are available. ++ * ++ * The refcount of the returned model is not increased. Use rxkb_model_ref() if ++ * you need to keep this struct outside the immediate scope. ++ * ++ * @return the next model or NULL at the end of the list ++ */ ++struct rxkb_model * ++rxkb_model_next(struct rxkb_model *m); ++ ++/** ++ * Increase the refcount of the argument by one. ++ * ++ * @returns The argument passed in to this function. ++ */ ++struct rxkb_model * ++rxkb_model_ref(struct rxkb_model *m); ++ ++/** ++ * Decrease the refcount of the argument by one. When the refcount hits zero, ++ * all memory associated with this struct is freed. ++ * ++ * @returns always NULL ++ */ ++struct rxkb_model * ++rxkb_model_unref(struct rxkb_model *m); ++ ++/** ++ * Return the name of this model. This is the value for M in RMLVO, to be used ++ * with libxkbcommon. ++ */ ++const char * ++rxkb_model_get_name(struct rxkb_model *m); ++ ++/** ++ * Return a human-readable description of this model. This function may return ++ * NULL. ++ */ ++const char * ++rxkb_model_get_description(struct rxkb_model *m); ++ ++/** ++ * Return the vendor name for this model. This function may return NULL. ++ */ ++const char * ++rxkb_model_get_vendor(struct rxkb_model *m); ++ ++/** ++ * Return the popularity for this model. ++ */ ++enum rxkb_popularity ++rxkb_model_get_popularity(struct rxkb_model *m); ++ ++/** ++ * Return the first layout for this context. Use this to start iterating over ++ * the layouts, followed by calls to rxkb_layout_next(). Layouts are not sorted. ++ * ++ * The refcount of the returned layout is not increased. Use rxkb_layout_ref() if ++ * you need to keep this struct outside the immediate scope. ++ * ++ * @return The first layout in the layout list. ++ */ ++struct rxkb_layout * ++rxkb_layout_first(struct rxkb_context *ctx); ++ ++/** ++ * Return the next layout for this context. Returns NULL when no more layouts ++ * are available. ++ * ++ * The refcount of the returned layout is not increased. Use rxkb_layout_ref() ++ * if you need to keep this struct outside the immediate scope. ++ * ++ * @return the next layout or NULL at the end of the list ++ */ ++struct rxkb_layout * ++rxkb_layout_next(struct rxkb_layout *l); ++ ++/** ++ * Increase the refcount of the argument by one. ++ * ++ * @returns The argument passed in to this function. ++ */ ++struct rxkb_layout * ++rxkb_layout_ref(struct rxkb_layout *l); ++ ++/** ++ * Decrease the refcount of the argument by one. When the refcount hits zero, ++ * all memory associated with this struct is freed. ++ * ++ * @returns always NULL ++ */ ++struct rxkb_layout * ++rxkb_layout_unref(struct rxkb_layout *l); ++ ++/** ++ * Return the name of this layout. This is the value for L in RMLVO, to be used ++ * with libxkbcommon. ++ */ ++const char * ++rxkb_layout_get_name(struct rxkb_layout *l); ++ ++/** ++ * Return the variant of this layout. This is the value for V in RMLVO, to be ++ * used with libxkbcommon. ++ * ++ * A variant does not stand on its own, it always depends on the base layout. ++ * e.g. there may be multiple variants called "intl" but there is only one ++ * "us(intl)". ++ * ++ * Where the variant is NULL, the layout is the base layout (e.g. "us"). ++ */ ++const char * ++rxkb_layout_get_variant(struct rxkb_layout *l); ++ ++/** ++ * Return a short (one-word) description of this layout. This function may ++ * return NULL. ++ */ ++const char * ++rxkb_layout_get_brief(struct rxkb_layout *l); ++ ++/** ++ * Return a human-readable description of this layout. This function may return ++ * NULL. ++ */ ++const char * ++rxkb_layout_get_description(struct rxkb_layout *l); ++ ++/** ++ * Return the popularity for this layout. ++ */ ++enum rxkb_popularity ++rxkb_layout_get_popularity(struct rxkb_layout *l); ++ ++/** ++ * Return the first option group for this context. Use this to start iterating ++ * over the option groups, followed by calls to rxkb_option_group_next(). ++ * Option groups are not sorted. ++ * ++ * The refcount of the returned option group is not increased. Use ++ * rxkb_option_group_ref() if you need to keep this struct outside the immediate ++ * scope. ++ * ++ * @return The first option group in the option group list. ++ */ ++struct rxkb_option_group * ++rxkb_option_group_first(struct rxkb_context *ctx); ++ ++/** ++ * Return the next option group for this context. Returns NULL when no more ++ * option groups are available. ++ * ++ * The refcount of the returned option group is not increased. Use ++ * rxkb_option_group_ref() if you need to keep this struct outside the immediate ++ * scope. ++ * ++ * @return the next option group or NULL at the end of the list ++ */ ++struct rxkb_option_group * ++rxkb_option_group_next(struct rxkb_option_group *g); ++ ++/** ++ * Increase the refcount of the argument by one. ++ * ++ * @returns The argument passed in to this function. ++ */ ++struct rxkb_option_group * ++rxkb_option_group_ref(struct rxkb_option_group *g); ++ ++/** ++ * Decrease the refcount of the argument by one. When the refcount hits zero, ++ * all memory associated with this struct is freed. ++ * ++ * @returns always NULL ++ */ ++struct rxkb_option_group * ++rxkb_option_group_unref(struct rxkb_option_group *g); ++ ++/** ++ * Return the name of this option group. This is **not** the value for O in ++ * RMLVO, the name can be used for internal sorting in the caller. This function ++ * may return NULL. ++ */ ++const char * ++rxkb_option_group_get_name(struct rxkb_option_group *m); ++ ++/** ++ * Return a human-readable description of this option group. This function may ++ * return NULL. ++ */ ++const char * ++rxkb_option_group_get_description(struct rxkb_option_group *m); ++ ++/** ++ * @return true if multiple options within this option group can be selected ++ * simultaneously, false if all options within this option group ++ * are mutually exclusive. ++ */ ++bool ++rxkb_option_group_allows_multiple(struct rxkb_option_group *g); ++ ++/** ++ * Return the popularity for this option group. ++ */ ++enum rxkb_popularity ++rxkb_option_group_get_popularity(struct rxkb_option_group *g); ++ ++/** ++ * Return the first option for this option group. Use this to start iterating ++ * over the options, followed by calls to rxkb_option_next(). Options are not ++ * sorted. ++ * ++ * The refcount of the returned option is not increased. Use rxkb_option_ref() ++ * if you need to keep this struct outside the immediate scope. ++ * ++ * @return The first option in the option list. ++ */ ++struct rxkb_option * ++rxkb_option_first(struct rxkb_option_group *group); ++ ++/** ++ * Return the next option for this option group. Returns NULL when no more ++ * options are available. ++ * ++ * The refcount of the returned options is not increased. Use rxkb_option_ref() ++ * if you need to keep this struct outside the immediate scope. ++ * ++ * @returns The next option or NULL at the end of the list ++ */ ++struct rxkb_option * ++rxkb_option_next(struct rxkb_option *o); ++ ++/** ++ * Increase the refcount of the argument by one. ++ * ++ * @returns The argument passed in to this function. ++ */ ++struct rxkb_option * ++rxkb_option_ref(struct rxkb_option *o); ++ ++/** ++ * Decrease the refcount of the argument by one. When the refcount hits zero, ++ * all memory associated with this struct is freed. ++ * ++ * @returns always NULL ++ */ ++struct rxkb_option * ++rxkb_option_unref(struct rxkb_option *o); ++ ++/** ++ * Return the name of this option. This is the value for O in RMLVO, to be used ++ * with libxkbcommon. ++ */ ++const char * ++rxkb_option_get_name(struct rxkb_option *o); ++ ++/** ++ * Return a short (one-word) description of this option. This function may ++ * return NULL. ++ */ ++const char * ++rxkb_option_get_brief(struct rxkb_option *o); ++ ++/** ++ * Return a human-readable description of this option. This function may return ++ * NULL. ++ */ ++const char * ++rxkb_option_get_description(struct rxkb_option *o); ++ ++/** ++ * Return the popularity for this option. ++ */ ++enum rxkb_popularity ++rxkb_option_get_popularity(struct rxkb_option *o); ++ ++/** ++ * Increase the refcount of the argument by one. ++ * ++ * @returns The argument passed in to this function. ++ */ ++struct rxkb_iso639_code * ++rxkb_iso639_code_ref(struct rxkb_iso639_code *iso639); ++ ++/** ++ * Decrease the refcount of the argument by one. When the refcount hits zero, ++ * all memory associated with this struct is freed. ++ * ++ * @returns always NULL ++ */ ++struct rxkb_iso639_code * ++rxkb_iso639_code_unref(struct rxkb_iso639_code *iso639); ++ ++/** ++ * Return the ISO 639-3 code for this code (e.g. "eng", "fra"). ++ */ ++const char * ++rxkb_iso639_code_get_code(struct rxkb_iso639_code *iso639); ++ ++/** ++ * Return the first ISO 639 for this layout. Use this to start iterating over ++ * the codes, followed by calls to rxkb_iso639_code_next(). Codes are not ++ * sorted. ++ * ++ * The refcount of the returned code is not increased. Use rxkb_iso639_code_ref() ++ * if you need to keep this struct outside the immediate scope. ++ * ++ * @return The first code in the code list. ++ */ ++struct rxkb_iso639_code * ++rxkb_layout_get_iso639_first(struct rxkb_layout *layout); ++ ++/** ++ * Return the next code in the list. Returns NULL when no more codes ++ * are available. ++ * ++ * The refcount of the returned codes is not increased. Use ++ * rxkb_iso639_code_ref() if you need to keep this struct outside the immediate ++ * scope. ++ * ++ * @returns The next code or NULL at the end of the list ++ */ ++struct rxkb_iso639_code * ++rxkb_iso639_code_next(struct rxkb_iso639_code *iso639); ++ ++/** ++ * Increase the refcount of the argument by one. ++ * ++ * @returns The argument passed in to this function. ++ */ ++struct rxkb_iso3166_code * ++rxkb_iso3166_code_ref(struct rxkb_iso3166_code *iso3166); ++ ++/** ++ * Decrease the refcount of the argument by one. When the refcount hits zero, ++ * all memory associated with this struct is freed. ++ * ++ * @returns always NULL ++ */ ++struct rxkb_iso3166_code * ++rxkb_iso3166_code_unref(struct rxkb_iso3166_code *iso3166); ++ ++/** ++ * Return the ISO 3166 Alpha 2 code for this code (e.g. "US", "FR"). ++ */ ++const char * ++rxkb_iso3166_code_get_code(struct rxkb_iso3166_code *iso3166); ++ ++/** ++ * Return the first ISO 3166 for this layout. Use this to start iterating over ++ * the codes, followed by calls to rxkb_iso3166_code_next(). Codes are not ++ * sorted. ++ * ++ * The refcount of the returned code is not increased. Use ++ * rxkb_iso3166_code_ref() if you need to keep this struct outside the immediate ++ * scope. ++ * ++ * @return The first code in the code list. ++ */ ++struct rxkb_iso3166_code * ++rxkb_layout_get_iso3166_first(struct rxkb_layout *layout); ++ ++/** ++ * Return the next code in the list. Returns NULL when no more codes ++ * are available. ++ * ++ * The refcount of the returned codes is not increased. Use ++ * rxkb_iso3166_code_ref() if you need to keep this struct outside the immediate ++ * scope. ++ * ++ * @returns The next code or NULL at the end of the list ++ */ ++struct rxkb_iso3166_code * ++rxkb_iso3166_code_next(struct rxkb_iso3166_code *iso3166); ++ ++/** @} */ ++ ++#ifdef __cplusplus ++} /* extern "C" */ ++#endif ++ ++#endif /* _XKBREGISTRY_H_ */ +diff --git a/xkbregistry.map b/xkbregistry.map +new file mode 100644 +index 000000000..6e18b9c35 +--- /dev/null ++++ b/xkbregistry.map +@@ -0,0 +1,61 @@ ++/* versions are kept in sync with libxkbcommon.so */ ++V_0.11.0 { ++global: ++ rxkb_context_new; ++ rxkb_context_parse; ++ rxkb_context_parse_default_ruleset; ++ rxkb_context_ref; ++ rxkb_context_unref; ++ rxkb_context_set_user_data; ++ rxkb_context_get_user_data; ++ rxkb_context_set_log_level; ++ rxkb_context_get_log_level; ++ rxkb_context_set_log_fn; ++ rxkb_context_include_path_append; ++ rxkb_context_include_path_append_default; ++ rxkb_model_first; ++ rxkb_model_next; ++ rxkb_model_ref; ++ rxkb_model_unref; ++ rxkb_model_get_name; ++ rxkb_model_get_description; ++ rxkb_model_get_vendor; ++ rxkb_model_get_popularity; ++ rxkb_layout_first; ++ rxkb_layout_next; ++ rxkb_layout_ref; ++ rxkb_layout_unref; ++ rxkb_layout_get_name; ++ rxkb_layout_get_brief; ++ rxkb_layout_get_description; ++ rxkb_layout_get_variant; ++ rxkb_layout_get_popularity; ++ rxkb_option_group_first; ++ rxkb_option_group_next; ++ rxkb_option_group_ref; ++ rxkb_option_group_unref; ++ rxkb_option_group_get_name; ++ rxkb_option_group_get_description; ++ rxkb_option_group_allows_multiple; ++ rxkb_option_group_get_popularity; ++ rxkb_option_first; ++ rxkb_option_next; ++ rxkb_option_ref; ++ rxkb_option_unref; ++ rxkb_option_get_name; ++ rxkb_option_get_brief; ++ rxkb_option_get_description; ++ rxkb_option_get_popularity; ++ rxkb_layout_get_iso639_first; ++ rxkb_iso639_code_next; ++ rxkb_iso639_code_ref; ++ rxkb_iso639_code_unref; ++ rxkb_iso639_code_get_code; ++ rxkb_layout_get_iso3166_first; ++ rxkb_iso3166_code_next; ++ rxkb_iso3166_code_ref; ++ rxkb_iso3166_code_unref; ++ rxkb_iso3166_code_get_code; ++local: ++ *; ++}; diff --git a/base/libxkbcommon/import-functions-required-for-backport.patch b/base/libxkbcommon/import-functions-required-for-backport.patch new file mode 100644 index 0000000..1375d77 --- /dev/null +++ b/base/libxkbcommon/import-functions-required-for-backport.patch @@ -0,0 +1,114 @@ +diff -Naur a/src/utils.c b/src/utils.c +--- a/src/utils.c 2019-10-21 02:08:47.000000000 +0600 ++++ b/src/utils.c 2024-06-28 11:13:17.092091027 +0600 +@@ -161,3 +161,48 @@ + } + return 0; + } ++ ++#if !(defined(HAVE_ASPRINTF) && HAVE_ASPRINTF) ++int ++asprintf(char **strp, const char *fmt, ...) ++{ ++ int ret; ++ va_list ap; ++ va_start(ap, fmt); ++ ret = vasprintf(strp, fmt, ap); ++ va_end(ap); ++ return ret; ++} ++ ++# if !(defined(HAVE_VASPRINTF) && HAVE_VASPRINTF) ++int ++vasprintf(char **strp, const char *fmt, va_list ap) ++{ ++ int ret; ++ char *buf; ++ va_list ap_copy; ++ ++ /* ++ * The value of the va_list parameter is undefined after the call to ++ * vsnprintf() returns: pass a copy to make sure "ap" remains valid. ++ */ ++ va_copy(ap_copy, ap); ++ ret = vsnprintf(NULL, 0, fmt, ap_copy); ++ va_end(ap_copy); ++ ++ if (ret < 0) ++ return ret; ++ ++ if (!(buf = malloc(ret + 1))) ++ return -1; ++ ++ if ((ret = vsnprintf(buf, ret + 1, fmt, ap)) < 0) { ++ free(buf); ++ return ret; ++ } ++ ++ *strp = buf; ++ return ret; ++} ++# endif /* !HAVE_VASPRINTF */ ++#endif /* !HAVE_ASPRINTF */ +diff -Naur a/src/utils.h b/src/utils.h +--- a/src/utils.h 2019-10-21 02:08:47.000000000 +0600 ++++ b/src/utils.h 2024-06-28 11:12:29.634354358 +0600 +@@ -186,6 +186,29 @@ + return pos; + } + ++static inline bool ++streq_null(const char *s1, const char *s2) ++{ ++ if (s1 == NULL || s2 == NULL) ++ return s1 == s2; ++ return streq(s1, s2); ++} ++ ++ ++static inline bool ++check_eaccess(const char *path, int mode) ++{ ++ #if defined(HAVE_EACCESS) ++ if (eaccess(path, mode) != 0) ++ return false; ++ #elif defined(HAVE_EUIDACCESS) ++ if (euidaccess(path, mode) != 0) ++ return false; ++ #endif ++ ++ return true; ++} ++ + static inline int + one_bit_set(uint32_t x) + { +@@ -240,6 +263,28 @@ + # define ATTR_PRINTF(x,y) + #endif + ++#if !(defined(HAVE_ASPRINTF) && HAVE_ASPRINTF) ++int asprintf(char **strp, const char *fmt, ...) ATTR_PRINTF(2, 3); ++# if !(defined(HAVE_VASPRINTF) && HAVE_VASPRINTF) ++# include ++int vasprintf(char **strp, const char *fmt, va_list ap); ++# endif /* !HAVE_VASPRINTF */ ++#endif /* !HAVE_ASPRINTF */ ++ ++static inline bool ++ATTR_PRINTF(3, 4) ++snprintf_safe(char *buf, size_t sz, const char *format, ...) ++{ ++ va_list ap; ++ int rc; ++ ++ va_start(ap, format); ++ rc = vsnprintf(buf, sz, format, ap); ++ va_end(ap); ++ ++ return rc >= 0 && (size_t)rc < sz; ++} ++ + #if (defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 205)) \ + || (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590)) + # define ATTR_NORETURN __attribute__((__noreturn__)) diff --git a/base/libxkbcommon/libxkbcommon.spec b/base/libxkbcommon/libxkbcommon.spec new file mode 100644 index 0000000..9e77f5f --- /dev/null +++ b/base/libxkbcommon/libxkbcommon.spec @@ -0,0 +1,243 @@ +#global gitdate 20120917 + +Name: libxkbcommon +Version: 0.9.1 +Release: 1%{?gitdate:.%{gitdate}}%{?dist} +Summary: X.Org X11 XKB parsing library +License: MIT +URL: http://www.x.org + +%if 0%{?gitdate} +Source0: %{name}-%{gitdate}.tar.bz2 +%else +Source0: http://xkbcommon.org/download/%{name}-%{version}.tar.xz +%endif +Source1: make-git-snapshot.sh + +# Backport libxkbregistry +Patch0: import-functions-required-for-backport.patch +Patch1: afb26e7df9090a0b765eb294b6efff448f763b6f.patch + +BuildRequires: git meson +BuildRequires: xorg-x11-util-macros byacc flex bison +BuildRequires: xorg-x11-proto-devel libX11-devel +BuildRequires: xkeyboard-config-devel +BuildRequires: pkgconfig(xcb-xkb) >= 1.10 + +Requires: xkeyboard-config + +%description +%{name} is the X.Org library for compiling XKB maps into formats usable by +the X Server or other display servers. + +%package devel +Summary: X.Org X11 XKB parsing development package +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description devel +X.Org X11 XKB parsing development package + +%package x11 +Summary: X.Org X11 XKB keymap creation library +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description x11 +%{name}-x11 is the X.Org library for creating keymaps by querying the X +server. + +%package x11-devel +Summary: X.Org X11 XKB keymap creation library +Requires: %{name}-x11%{?_isa} = %{version}-%{release} + +%description x11-devel +X.Org X11 XKB keymap creation library development package + +%package -n libxkbregistry +Summary: Library for handling xkb descriptions +BuildRequires: pkgconfig(libxml-2.0) + +%description -n libxkbregistry +libxkbregistry is a C library that lists available XKB models, +layouts and variants for a given ruleset. + +%package -n libxkbregistry-devel +Summary: Header files for xkbregistry +Requires: libxkbregistry%{?_isa} = %{version}-%{release} + +%description -n libxkbregistry-devel +This package contains the header and pkg-config files for developing +with libxkbcommon. + +%prep +%autosetup -S git + +%build +%meson -Denable-docs=false \ + -Denable-x11=true \ + -Denable-wayland=false +%meson_build + +%install +%meson_install + +%ldconfig_scriptlets + +%files +%license LICENSE +%{_libdir}/libxkbcommon.so.0.0.0 +%{_libdir}/libxkbcommon.so.0 + +%files devel +%{_libdir}/libxkbcommon.so +%dir %{_includedir}/xkbcommon/ +%{_includedir}/xkbcommon/xkbcommon.h +%{_includedir}/xkbcommon/xkbcommon-compat.h +%{_includedir}/xkbcommon/xkbcommon-compose.h +%{_includedir}/xkbcommon/xkbcommon-keysyms.h +%{_includedir}/xkbcommon/xkbcommon-names.h +%{_libdir}/pkgconfig/xkbcommon.pc + +%ldconfig_scriptlets x11 + +%files x11 +%{_libdir}/libxkbcommon-x11.so.0.0.0 +%{_libdir}/libxkbcommon-x11.so.0 + +%files x11-devel +%{_libdir}/libxkbcommon-x11.so +%{_includedir}/xkbcommon/xkbcommon-x11.h +%{_libdir}/pkgconfig/xkbcommon-x11.pc + +%ldconfig_scriptlets -n libxkbregistry + +%files -n libxkbregistry +%license LICENSE +%{_libdir}/libxkbregistry.so.0.0.0 +%{_libdir}/libxkbregistry.so.0 + +%files -n libxkbregistry-devel +%{_libdir}/libxkbregistry.so +%{_includedir}/xkbcommon/xkbregistry.h +%{_libdir}/pkgconfig/xkbregistry.pc + +%changelog +* Fri Jun 28 2024 Raven - 0.9.1-2 +- backport xkbregistry from 0.11.0 + +* Fri Nov 01 2019 Peter Hutterer 0.9.1-1 +- libxkbcommon 0.9.1 (#1728801) + +* Thu Aug 23 2018 Peter Hutterer 0.8.2-1 +- libxkbcommon 0.8.2 (#1619541) + +* Sat Feb 03 2018 Igor Gnatenko - 0.8.0-2 +- Switch to %%ldconfig_scriptlets + +* Tue Dec 19 2017 Peter Hutterer 0.8.0-1 +- libxkbcommon 0.8.0 + +* Thu Aug 03 2017 Fedora Release Engineering - 0.7.1-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Wed Jul 26 2017 Fedora Release Engineering - 0.7.1-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Fri May 12 2017 Hans de Goede - 0.7.1-3 +- Add patch from upstream adding XF86Keyboard and XF86RFKill keysyms + +* Fri Feb 10 2017 Fedora Release Engineering - 0.7.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Thu Jan 19 2017 Peter Hutterer 0.7.1-1 +- xkbcommon 0.7.1 + +* Mon Nov 14 2016 Peter Hutterer 0.7.0-1 +- xkbcommon 0.7.0 + +* Fri Jun 03 2016 Peter Hutterer 0.6.1-1 +- xkbcommon 0.6.1 + +* Thu Feb 04 2016 Fedora Release Engineering - 0.5.0-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Fri Jan 08 2016 Dan Horák - 0.5.0-3 +- always build the x11 subpackage + +* Wed Jun 17 2015 Fedora Release Engineering - 0.5.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Wed Oct 22 2014 Hans de Goede - 0.5.0-1 +- Update to 0.5.0 (#1154574) + +* Mon Sep 22 2014 Kalev Lember - 0.4.3-2 +- Require xkeyboard-config (#1145260) + +* Wed Aug 20 2014 Kalev Lember - 0.4.3-1 +- Update to 0.4.3 + +* Sun Aug 17 2014 Fedora Release Engineering - 0.4.2-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Sat Jun 07 2014 Fedora Release Engineering - 0.4.2-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Tue May 27 2014 Rex Dieter - 0.4.2-3 +- make -x11 support conditional (f21+, #1000497) +- --disable-silent-rules + +* Fri May 23 2014 Hans de Goede - 0.4.2-2 +- Bump release to 2 to avoid confusion with non official non scratch 0.4.2-1 + +* Thu May 22 2014 Rex Dieter - 0.4.2-1 +- xkbcommon 0.4.2 (#1000497) +- own %%{_includedir}/xkbcommon/ +- -x11: +ldconfig scriptlets +- -devel: don't include xkbcommon-x11.h +- run reautoconf in %%prep (instead of %%build) +- tighten subpkg deps via %%_isa +- .spec cleanup, remove deprecated stuff +- BR: pkgconfig(xcb-xkb) >= 1.10 + +* Wed Feb 05 2014 Peter Hutterer 0.4.0-1 +- xkbcommon 0.4.0 +- Add new xkbcommon-x11 and xkbcommon-x11-devel subpackages + +* Tue Aug 27 2013 Peter Hutterer 0.3.1-1 +- xkbcommon 0.3.1 + +* Sat Aug 03 2013 Fedora Release Engineering - 0.3.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Thu Apr 18 2013 Peter Hutterer 0.3.0-1 +- xkbcommon 0.3.0 + +* Thu Feb 14 2013 Fedora Release Engineering - 0.2.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild + +* Tue Oct 23 2012 Adam Jackson 0.2.0-1 +- xkbcommon 0.2.0 + +* Mon Sep 17 2012 Thorsten Leemhuis 0.1.0-8.20120917 +- Today's git snapshot + +* Thu Jul 19 2012 Fedora Release Engineering - 0.1.0-7.20120306 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Tue Mar 06 2012 Peter Hutterer 0.1.0-6.20120306 +- BuildRequire xkeyboard-config-devel to get the right XKB target path (#799717) + +* Tue Mar 06 2012 Peter Hutterer 0.1.0-5.20120306 +- Today's git snapshot + +* Fri Jan 13 2012 Fedora Release Engineering - 0.1.0-4.20111109 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Wed Nov 09 2011 Adam Jackson 0.1.0-3 +- Today's git snap + +* Tue Feb 08 2011 Fedora Release Engineering - 0.1.0-2.20101110 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Sat Nov 06 2010 Dave Airlie 0.1.0-1.20101110 +- inital import + diff --git a/base/libxkbcommon/make-git-snapshot.sh b/base/libxkbcommon/make-git-snapshot.sh new file mode 100755 index 0000000..507c2a0 --- /dev/null +++ b/base/libxkbcommon/make-git-snapshot.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +DIRNAME=libxkbcommon-$( date +%Y%m%d ) + +rm -rf $DIRNAME +git clone git://anongit.freedesktop.org/git/xorg/lib/libxkbcommon $DIRNAME +cd $DIRNAME +if [ -z "$1" ]; then + git log | head -1 +else + git checkout $1 +fi +git log | head -1 | awk '{ print $2 }' > ../commitid +git repack -a -d +cd .. +tar jcf $DIRNAME.tar.bz2 $DIRNAME +rm -rf $DIRNAME