3462 lines
103 KiB
Diff
3462 lines
103 KiB
Diff
From afb26e7df9090a0b765eb294b6efff448f763b6f Mon Sep 17 00:00:00 2001
|
|
From: Peter Hutterer <peter.hutterer@who-t.net>
|
|
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 <peter.hutterer@who-t.net>
|
|
---
|
|
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 <sys/stat.h>
|
|
+#include <fcntl.h>
|
|
+#include <stdbool.h>
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <stdint.h>
|
|
+#include <libxml/parser.h>
|
|
+
|
|
+#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[] =
|
|
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
+ "<!ELEMENT xkbConfigRegistry (modelList?, layoutList?, optionList?)>\n"
|
|
+ "<!ATTLIST xkbConfigRegistry version CDATA \"1.1\">\n"
|
|
+ "<!ELEMENT modelList (model*)>\n"
|
|
+ "<!ELEMENT model (configItem)>\n"
|
|
+ "<!ELEMENT layoutList (layout*)>\n"
|
|
+ "<!ELEMENT layout (configItem, variantList?)>\n"
|
|
+ "<!ELEMENT optionList (group*)>\n"
|
|
+ "<!ELEMENT variantList (variant*)>\n"
|
|
+ "<!ELEMENT variant (configItem)>\n"
|
|
+ "<!ELEMENT group (configItem, option*)>\n"
|
|
+ "<!ATTLIST group allowMultipleSelection (true|false) \"false\">\n"
|
|
+ "<!ELEMENT option (configItem)>\n"
|
|
+ "<!ELEMENT configItem (name, shortDescription?, description?, vendor?, countryList?, languageList?, hwList?)>\n"
|
|
+ "<!ATTLIST configItem popularity (standard|exotic) \"standard\">\n"
|
|
+ "<!ELEMENT name (#PCDATA)>\n"
|
|
+ "<!ELEMENT shortDescription (#PCDATA)>\n"
|
|
+ "<!ELEMENT description (#PCDATA)>\n"
|
|
+ "<!ELEMENT vendor (#PCDATA)>\n"
|
|
+ "<!ELEMENT countryList (iso3166Id+)>\n"
|
|
+ "<!ELEMENT iso3166Id (#PCDATA)>\n"
|
|
+ "<!ELEMENT languageList (iso639Id+)>\n"
|
|
+ "<!ELEMENT iso639Id (#PCDATA)>\n"
|
|
+ "<!ELEMENT hwList (hwId+)>\n"
|
|
+ "<!ELEMENT hwId (#PCDATA)>\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 <assert.h>
|
|
+#include <stddef.h>
|
|
+#include <stdbool.h>
|
|
+
|
|
+#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 <stdbool.h>
|
|
+#include <stddef.h>
|
|
+
|
|
+/*
|
|
+ * 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 <assert.h>
|
|
+#include <stdarg.h>
|
|
+#include <stdio.h>
|
|
+#include <sys/stat.h>
|
|
+#include <sys/types.h>
|
|
+
|
|
+#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, " <configItem>\n"
|
|
+ " <name>%s</name>\n", name);
|
|
+ if (brief)
|
|
+ fprintf(fp, " <shortDescription>%s</shortDescription>\n", brief);
|
|
+ if (description)
|
|
+ fprintf(fp, " <description>%s</description>\n", description);
|
|
+ if (vendor)
|
|
+ fprintf(fp, " <vendor>%s</vendor>\n", vendor);
|
|
+ fprintf(fp, " </configItem>\n");
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Create a directory populated with a rules/<ruleset>.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,
|
|
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
+ "<!DOCTYPE xkbConfigRegistry SYSTEM \"xkb.dtd\">\n"
|
|
+ "<xkbConfigRegistry version=\"1.1\">\n");
|
|
+
|
|
+ if (test_models) {
|
|
+ fprintf(fp, "<modelList>\n");
|
|
+
|
|
+ for (const struct test_model *m = test_models; m->name; m++) {
|
|
+ fprintf(fp, "<model>\n");
|
|
+ fprint_config_item(fp, m->name, m->vendor, NULL, m->description);
|
|
+ fprintf(fp, "</model>\n");
|
|
+ }
|
|
+ fprintf(fp, "</modelList>\n");
|
|
+ }
|
|
+
|
|
+ if (test_layouts) {
|
|
+ const struct test_layout *l, *next;
|
|
+
|
|
+ fprintf(fp, "<layoutList>\n");
|
|
+
|
|
+ l = test_layouts;
|
|
+ next = l + 1;
|
|
+
|
|
+ assert(l->variant == NULL);
|
|
+
|
|
+ while (l->name) {
|
|
+ fprintf(fp, "<layout>\n");
|
|
+ fprint_config_item(fp, l->name, NULL, l->brief, l->description);
|
|
+
|
|
+ if (next->name && streq(next->name, l->name)) {
|
|
+ fprintf(fp, "<variantList>\n");
|
|
+ do {
|
|
+ fprintf(fp, "<variant>\n");
|
|
+ fprint_config_item(fp, next->variant, NULL, next->brief,
|
|
+ next->description);
|
|
+ fprintf(fp, "</variant>\n");
|
|
+ l = next;
|
|
+ next++;
|
|
+ } while (next->name && streq(next->name, l->name));
|
|
+ fprintf(fp, "</variantList>\n");
|
|
+ }
|
|
+ fprintf(fp, "</layout>\n");
|
|
+ l++;
|
|
+ }
|
|
+ fprintf(fp, "</layoutList>\n");
|
|
+ }
|
|
+
|
|
+ if (test_groups) {
|
|
+ fprintf(fp, "<optionList>\n");
|
|
+
|
|
+ for (const struct test_option_group *g = test_groups; g->name; g++) {
|
|
+ fprintf(fp, "<group allowMultipleSelection=\"%s\">\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, " <option>\n");
|
|
+ fprint_config_item(fp, o->name, NULL, NULL, o->description);
|
|
+ fprintf(fp, "</option>\n");
|
|
+ }
|
|
+ fprintf(fp, "</group>\n");
|
|
+ }
|
|
+ fprintf(fp, "</optionList>\n");
|
|
+ }
|
|
+
|
|
+ fprintf(fp, "</xkbConfigRegistry>\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 <assert.h>
|
|
+#include <stdio.h>
|
|
+#include <getopt.h>
|
|
+
|
|
+#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 <stdarg.h>
|
|
+#include <stdbool.h>
|
|
+
|
|
+/**
|
|
+ * @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
|
|
+ * <xkbConfigRegistry version="1.1">
|
|
+ * <layoutList>
|
|
+ * <layout>
|
|
+ * <configItem>
|
|
+ * <name>us</name>
|
|
+ * </configItem>
|
|
+ * <variantList>
|
|
+ * <variant>
|
|
+ * <configItem>
|
|
+ * <name>banana</name>
|
|
+ * <description>English (Banana)</description>
|
|
+ * </configItem>
|
|
+ * </variant>
|
|
+ * </layout>
|
|
+ * </layoutList>
|
|
+ * </xkbConfigRegistry>
|
|
+ * @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:
|
|
+ *;
|
|
+};
|