diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1645afe..4f51763 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,7 +53,7 @@ jobs: HOMEBREW_NO_INSTALL_CLEANUP: 1 - name: Setup run: | - PATH="/usr/local/opt/bison/bin:${PATH}" meson setup -Denable-wayland=false -Denable-x11=false build + PATH="/usr/local/opt/bison/bin:${PATH}" meson setup -Denable-wayland=false -Denable-x11=false -Denable-xkbregistry=false build - name: Build run: | PATH="/usr/local/opt/bison/bin:${PATH}" meson compile -C build @@ -76,7 +76,7 @@ jobs: shell: cmd run: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 - meson setup -Denable-wayland=false -Denable-x11=false -Denable-docs=false build + meson setup -Denable-wayland=false -Denable-x11=false -Denable-docs=false -Denable-xkbregistry=false build env: CC: cl - name: Build diff --git a/NEWS b/NEWS index d35e543..a3acd4e 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,14 @@ +libxkbcommon 0.11.0 +=================== + +- Add libxkbregistry as configure-time optional library. libxkbregistry is a C + library that lists available XKB models, layouts and variants for a given + ruleset. This is a separate library (libxkbregistry.so, pkgconfig file + xkbregistry.pc) and aimed at tools that provide a listing of available + keyboard layouts to the user. See the Documentation for details on the API. + + Contributed by Peter Hutterer <@who-t.net>. + libxkbcommon 0.10.0 - 2020-01-18 =================== diff --git a/PACKAGING b/PACKAGING index 85488e9..a47be77 100644 --- a/PACKAGING +++ b/PACKAGING @@ -1,5 +1,6 @@ -libxkbcommon consists of two shared libraries, libxkbcommon (the main -library) and libxkbcommon-x11 (an addon library for XCB clients). +libxkbcommon consists of three shared libraries, libxkbcommon (the main +library), libxkbcommon-x11 (an addon library for XCB clients) and libxkbregistry +(a library to list available RMLVO options). The files for libxkbcommon-x11 are: libxkbcommon-x11.a libxkbcommon-x11.so* xkbcommon/xkbcommon-x11.h @@ -8,13 +9,20 @@ The files for libxkbcommon-x11 are: libxkbcommon-x11 can be disabled with -Denable-x11=false (see `meson configure build` for other options/variables). +The files for libxkbregistry are: + libxkbregistry.a libxkbregistry.so* xkbcommon/xkbregistry.h + xkbregistry.map xkbregistry.pc + +libxkbregistry can be disabled with -Denable-xkbregistry=false (see +`meson configure build` for other options/variables). + Dependencies for libxkbcommon: - C compiler, meson, pkg-config, libc, bash, grep, sed. - (build) bison (preferred), win_bison or byacc>=20141006. byacc must be configured with --enable-btyacc. -- (build optional, runtime) xkeyboard-config. +- (build optional, runtime required) xkeyboard-config. During build, for automatically detecting the value of -Dxkb-config-root instead of guessing (/usr/share/X11/xkb). During runtime, not strictly needed, but most users of the library @@ -41,6 +49,16 @@ Dependencies for libxkbcommon-x11 tests: - xkbcomp, Xvfb. If they are not available, the relevant tests are skipped. +Dependencies for libxkbregistry: +- libxkbregistry is a sublibrary of libxkbcommon and cannot be built without + building libxbkcommon. The files produced are otherwise independent. + +- libxml2 + +- (build optional, runtime requirement) xkeyboard-config + During build, for automatically detecting the value of + -Dxkb-config-root instead of guessing (/usr/share/X11/xkb). + Dependencies for Wayland tests: - wayland-client>=1.2.0, wayland-scanner, wayland-protocols>=1.0. To disable, use -Denable-wayland=false. @@ -49,3 +67,7 @@ Unless libxcb is always available as part of the system, it is preferred that libxkbcommon and libxkbcommon-x11 be split into separate packages, such that the main library does not depend on libxcb. This avoids a transitive dependency of Wayland clients on X libraries. + +It is perferred that libxkbregistry be split into a separate packages as most +clients that require libxkbcommon do not require libxkbregistry and clients +requiring libxkbregistry may not need libxkbcommon. diff --git a/README.md b/README.md index cfbef53..74d1d27 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ libxkbcommon is a keyboard keymap compiler and support library which processes a reduced subset of keymaps as defined by the XKB (X Keyboard Extension) specification. It also contains a module for handling Compose -and dead keys. +and dead keys and a separate library for listing available keyboard layouts. ## Quick Guide diff --git a/doc/user-configuration.md b/doc/user-configuration.md index 886950a..17e7179 100644 --- a/doc/user-configuration.md +++ b/doc/user-configuration.md @@ -154,3 +154,66 @@ xkb_symbols "baz" { With these in place, a user may select any layout/variant together with the "custom:foo" and/or "custom:baz" options. + +## Discoverable layouts + +**The below requires libxkbregistry as XKB lookup tool and does not work where +clients parse the XML file directly**. + +The above sections apply only to the data files and require that the user knows +about the existence of the new entries. To make custom entries discoverable by +the configuration tools (e.g. the GNOME Control Center), the new entries must +also be added to the XML file that is parsed by libxkbregistry. In most cases, +this is the `evdev.xml` file in the rules directory. The example below shows the +XML file that would add the custom layout and custom options as outlined above +to the XKB registry: + +``` +$ cat $XDG_CONFIG_HOME/xkb/rules/evdev.xml + + + + + + + banana + ban + Banana + + + + + orange + or + Orange (Banana) + + + + + + + custom + Custom options + + + + + + +``` + +The default behavior of libxkbregistry ensures that the new layout and options +are added to the system-provided layouts and options. + +For details on the XML format, see DTD in `/X11/xkb/rules/xkb.dtd` +and the system-provided XML files in `/X11/xkb/rulies/xkb.dtd`. diff --git a/meson.build b/meson.build index 7041d35..52aeafd 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 0e166b1..5eaa081 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 8e87c94..de659cc 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 0000000..19e004b --- /dev/null +++ b/src/registry.c @@ -0,0 +1,1193 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "xkbcommon/xkbregistry.h" +#include "utils.h" +#include "util-list.h" + +struct rxkb_object; + +typedef void (*destroy_func_t)(struct rxkb_object *object); + +/** + * All our objects are refcounted and are linked to iterate through them. + * Abstract those bits away into a shared parent class so we can generate + * most of the functions through macros. + */ +struct rxkb_object { + struct rxkb_object *parent; + uint32_t refcount; + struct list link; + destroy_func_t destroy; +}; + +struct rxkb_iso639_code { + struct rxkb_object base; + char *code; +}; + +struct rxkb_iso3166_code { + struct rxkb_object base; + char *code; +}; + +enum context_state { + CONTEXT_NEW, + CONTEXT_PARSED, + CONTEXT_FAILED, +}; + +struct rxkb_context { + struct rxkb_object base; + enum context_state context_state; + + bool load_extra_rules_files; + + struct list models; /* list of struct rxkb_models */ + struct list layouts; /* list of struct rxkb_layouts */ + struct list option_groups; /* list of struct rxkb_option_group */ + + darray(char *) includes; + + + ATTR_PRINTF(3, 0) void (*log_fn)(struct rxkb_context *ctx, + enum rxkb_log_level level, + const char *fmt, va_list args); + enum rxkb_log_level log_level; + + void *userdata; +}; + +struct rxkb_model { + struct rxkb_object base; + + char *name; + char *vendor; + char *description; + enum rxkb_popularity popularity; +}; + +struct rxkb_layout { + struct rxkb_object base; + + char *name; + char *brief; + char *description; + char *variant; + enum rxkb_popularity popularity; + + struct list iso639s; /* list of struct rxkb_iso639_code */ + struct list iso3166s; /* list of struct rxkb_iso3166_code */ +}; + +struct rxkb_option_group { + struct rxkb_object base; + + bool allow_multiple; + struct list options; /* list of struct rxkb_options */ + char *name; + char *description; + enum rxkb_popularity popularity; +}; + +struct rxkb_option { + struct rxkb_object base; + + char *name; + char *brief; + char *description; + enum rxkb_popularity popularity; +}; + +static bool +parse(struct rxkb_context *ctx, const char *path, + enum rxkb_popularity popularity); + +static void +rxkb_log(struct rxkb_context *ctx, enum rxkb_log_level level, + const char *fmt, ...) +{ + va_list args; + + if (ctx->log_level < level) + return; + + va_start(args, fmt); + ctx->log_fn(ctx, level, fmt, args); + va_end(args); +} + +/* + * The format is not part of the argument list in order to avoid the + * "ISO C99 requires rest arguments to be used" warning when only the + * format is supplied without arguments. Not supplying it would still + * result in an error, though. + */ +#define log_dbg(ctx, ...) \ + rxkb_log((ctx), RXKB_LOG_LEVEL_DEBUG, __VA_ARGS__) +#define log_info(ctx, ...) \ + rxkb_log((ctx), RXKB_LOG_LEVEL_INFO, __VA_ARGS__) +#define log_warn(ctx, ...) \ + rxkb_log((ctx), RXKB_LOG_LEVEL_WARNING, __VA_ARGS__) +#define log_err(ctx, ...) \ + rxkb_log((ctx), RXKB_LOG_LEVEL_ERROR, __VA_ARGS__) +#define log_wsgo(ctx, ...) \ + rxkb_log((ctx), RXKB_LOG_LEVEL_CRITICAL, __VA_ARGS__) + + +#define DECLARE_REF_UNREF_FOR_TYPE(type_) \ +XKB_EXPORT struct type_ * type_##_ref(struct type_ *object) { \ + rxkb_object_ref(&object->base); \ + return object; \ +} \ +XKB_EXPORT struct type_ * type_##_unref(struct type_ *object) { \ + if (!object) return NULL; \ + return rxkb_object_unref(&object->base); \ +} + +#define DECLARE_CREATE_FOR_TYPE(type_) \ +static inline struct type_ * type_##_create(struct rxkb_object *parent) { \ + struct type_ *t = calloc(1, sizeof *t); \ + if (t) \ + rxkb_object_init(&t->base, parent, (destroy_func_t)type_##_destroy); \ + return t; \ +} + +#define DECLARE_TYPED_GETTER_FOR_TYPE(type_, field_, rtype_) \ +XKB_EXPORT rtype_ type_##_get_##field_(struct type_ *object) { \ + return object->field_; \ +} + +#define DECLARE_GETTER_FOR_TYPE(type_, field_) \ + DECLARE_TYPED_GETTER_FOR_TYPE(type_, field_, const char*) + +#define DECLARE_FIRST_NEXT_FOR_TYPE(type_, parent_type_, parent_field_) \ +XKB_EXPORT struct type_ * type_##_first(struct parent_type_ *parent) { \ + struct type_ *o = NULL; \ + if (!list_empty(&parent->parent_field_)) \ + o = list_first_entry(&parent->parent_field_, o, base.link); \ + return o; \ +} \ +XKB_EXPORT struct type_ * \ +type_##_next(struct type_ *o) \ +{ \ + struct parent_type_ *parent; \ + struct type_ *next; \ + parent = container_of(o->base.parent, struct parent_type_, base); \ + next = list_first_entry(&o->base.link, o, base.link); \ + if (list_is_last(&parent->parent_field_, &o->base.link)) \ + return NULL; \ + return next; \ +} + +static void +rxkb_object_init(struct rxkb_object *object, struct rxkb_object *parent, destroy_func_t destroy) +{ + object->refcount = 1; + object->destroy = destroy; + object->parent = parent; + list_init(&object->link); +} + +static void +rxkb_object_destroy(struct rxkb_object *object) +{ + if (object->destroy) + object->destroy(object); + list_remove(&object->link); + free(object); +} + +static void * +rxkb_object_ref(struct rxkb_object *object) +{ + assert(object->refcount >= 1); + ++object->refcount; + return object; +} + +static void * +rxkb_object_unref(struct rxkb_object *object) +{ + assert(object->refcount >= 1); + if (--object->refcount == 0) + rxkb_object_destroy(object); + return NULL; +} + +static void +rxkb_iso639_code_destroy(struct rxkb_iso639_code *code) +{ + free(code->code); +} + +XKB_EXPORT struct rxkb_iso639_code * +rxkb_layout_get_iso639_first(struct rxkb_layout *layout) +{ + struct rxkb_iso639_code *code = NULL; + + if (!list_empty(&layout->iso639s)) + code = list_first_entry(&layout->iso639s, code, base.link); + + return code; +} + +XKB_EXPORT struct rxkb_iso639_code * +rxkb_iso639_code_next(struct rxkb_iso639_code *code) +{ + struct rxkb_iso639_code *next = NULL; + struct rxkb_layout *layout; + + layout = container_of(code->base.parent, struct rxkb_layout, base); + + if (list_is_last(&layout->iso639s, &code->base.link)) + return NULL; + + next = list_first_entry(&code->base.link, code, base.link); + + return next; +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_iso639_code); +DECLARE_CREATE_FOR_TYPE(rxkb_iso639_code); +DECLARE_GETTER_FOR_TYPE(rxkb_iso639_code, code); + +static void +rxkb_iso3166_code_destroy(struct rxkb_iso3166_code *code) +{ + free(code->code); +} + +XKB_EXPORT struct rxkb_iso3166_code * +rxkb_layout_get_iso3166_first(struct rxkb_layout *layout) +{ + struct rxkb_iso3166_code *code = NULL; + + if (!list_empty(&layout->iso3166s)) + code = list_first_entry(&layout->iso3166s, code, base.link); + + return code; +} + +XKB_EXPORT struct rxkb_iso3166_code * +rxkb_iso3166_code_next(struct rxkb_iso3166_code *code) +{ + struct rxkb_iso3166_code *next = NULL; + struct rxkb_layout *layout; + + layout = container_of(code->base.parent, struct rxkb_layout, base); + + if (list_is_last(&layout->iso3166s, &code->base.link)) + return NULL; + + next = list_first_entry(&code->base.link, code, base.link); + + return next; +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_iso3166_code); +DECLARE_CREATE_FOR_TYPE(rxkb_iso3166_code); +DECLARE_GETTER_FOR_TYPE(rxkb_iso3166_code, code); + +static void +rxkb_option_destroy(struct rxkb_option *o) +{ + free(o->name); + free(o->brief); + free(o->description); +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_option); +DECLARE_CREATE_FOR_TYPE(rxkb_option); +DECLARE_GETTER_FOR_TYPE(rxkb_option, name); +DECLARE_GETTER_FOR_TYPE(rxkb_option, brief); +DECLARE_GETTER_FOR_TYPE(rxkb_option, description); +DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_option, popularity, enum rxkb_popularity); +DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_option, rxkb_option_group, options); + +static void +rxkb_layout_destroy(struct rxkb_layout *l) +{ + struct rxkb_iso639_code *iso639, *tmp_639; + struct rxkb_iso3166_code *iso3166, *tmp_3166; + + free(l->name); + free(l->brief); + free(l->description); + free(l->variant); + + list_for_each_safe(iso639, tmp_639, &l->iso639s, base.link) { + rxkb_iso639_code_unref(iso639); + } + list_for_each_safe(iso3166, tmp_3166, &l->iso3166s, base.link) { + rxkb_iso3166_code_unref(iso3166); + } +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_layout); +DECLARE_CREATE_FOR_TYPE(rxkb_layout); +DECLARE_GETTER_FOR_TYPE(rxkb_layout, name); +DECLARE_GETTER_FOR_TYPE(rxkb_layout, brief); +DECLARE_GETTER_FOR_TYPE(rxkb_layout, description); +DECLARE_GETTER_FOR_TYPE(rxkb_layout, variant); +DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_layout, popularity, enum rxkb_popularity); +DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_layout, rxkb_context, layouts); + +static void +rxkb_model_destroy(struct rxkb_model *m) +{ + free(m->name); + free(m->vendor); + free(m->description); +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_model); +DECLARE_CREATE_FOR_TYPE(rxkb_model); +DECLARE_GETTER_FOR_TYPE(rxkb_model, name); +DECLARE_GETTER_FOR_TYPE(rxkb_model, vendor); +DECLARE_GETTER_FOR_TYPE(rxkb_model, description); +DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_model, popularity, enum rxkb_popularity); +DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_model, rxkb_context, models); + +static void +rxkb_option_group_destroy(struct rxkb_option_group *og) +{ + struct rxkb_option *o, *otmp; + + free(og->name); + free(og->description); + + list_for_each_safe(o, otmp, &og->options, base.link) { + rxkb_option_unref(o); + } +} + +XKB_EXPORT bool +rxkb_option_group_allows_multiple(struct rxkb_option_group *g) +{ + return g->allow_multiple; +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_option_group); +DECLARE_CREATE_FOR_TYPE(rxkb_option_group); +DECLARE_GETTER_FOR_TYPE(rxkb_option_group, name); +DECLARE_GETTER_FOR_TYPE(rxkb_option_group, description); +DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_option_group, popularity, enum rxkb_popularity); +DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_option_group, rxkb_context, option_groups); + +static void +rxkb_context_destroy(struct rxkb_context *ctx) +{ + struct rxkb_model *m, *mtmp; + struct rxkb_layout *l, *ltmp; + struct rxkb_option_group *og, *ogtmp; + char **path; + + list_for_each_safe(m, mtmp, &ctx->models, base.link) + rxkb_model_unref(m); + assert(list_empty(&ctx->models)); + + list_for_each_safe(l, ltmp, &ctx->layouts, base.link) + rxkb_layout_unref(l); + assert(list_empty(&ctx->layouts)); + + list_for_each_safe(og, ogtmp, &ctx->option_groups, base.link) + rxkb_option_group_unref(og); + assert(list_empty(&ctx->option_groups)); + + darray_foreach(path, ctx->includes) + free(*path); + darray_free(ctx->includes); + + assert(darray_empty(ctx->includes)); +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_context); +DECLARE_CREATE_FOR_TYPE(rxkb_context); +DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_context, log_level, enum rxkb_log_level); + +XKB_EXPORT void +rxkb_context_set_log_level(struct rxkb_context *ctx, + enum rxkb_log_level level) +{ + ctx->log_level = level; +} + +static const char * +log_level_to_prefix(enum rxkb_log_level level) +{ + switch (level) { + case RXKB_LOG_LEVEL_DEBUG: + return "xkbregistry: DEBUG: "; + case RXKB_LOG_LEVEL_INFO: + return "xkbregistry: INFO: "; + case RXKB_LOG_LEVEL_WARNING: + return "xkbregistry: WARNING: "; + case RXKB_LOG_LEVEL_ERROR: + return "xkbregistry: ERROR: "; + case RXKB_LOG_LEVEL_CRITICAL: + return "xkbregistry: CRITICAL: "; + default: + return NULL; + } +} + +ATTR_PRINTF(3, 0) static void +default_log_fn(struct rxkb_context *ctx, enum rxkb_log_level level, + const char *fmt, va_list args) +{ + const char *prefix = log_level_to_prefix(level); + + if (prefix) + fprintf(stderr, "%s", prefix); + vfprintf(stderr, fmt, args); +} + +static enum rxkb_log_level +log_level(const char *level) { + char *endptr; + enum rxkb_log_level lvl; + + errno = 0; + lvl = strtol(level, &endptr, 10); + if (errno == 0 && (endptr[0] == '\0' || is_space(endptr[0]))) + return lvl; + if (istreq_prefix("crit", level)) + return RXKB_LOG_LEVEL_CRITICAL; + if (istreq_prefix("err", level)) + return RXKB_LOG_LEVEL_ERROR; + if (istreq_prefix("warn", level)) + return RXKB_LOG_LEVEL_WARNING; + if (istreq_prefix("info", level)) + return RXKB_LOG_LEVEL_INFO; + if (istreq_prefix("debug", level) || istreq_prefix("dbg", level)) + return RXKB_LOG_LEVEL_DEBUG; + + return RXKB_LOG_LEVEL_ERROR; +} + +XKB_EXPORT struct rxkb_context * +rxkb_context_new(enum rxkb_context_flags flags) +{ + struct rxkb_context *ctx = rxkb_context_create(NULL); + const char *env; + + if (!ctx) + return NULL; + + ctx->context_state = CONTEXT_NEW; + ctx->load_extra_rules_files = flags & RXKB_CONTEXT_LOAD_EXOTIC_RULES; + ctx->log_fn = default_log_fn; + ctx->log_level = RXKB_LOG_LEVEL_ERROR; + + /* Environment overwrites defaults. */ + env = secure_getenv("RXKB_LOG_LEVEL"); + if (env) + rxkb_context_set_log_level(ctx, log_level(env)); + + list_init(&ctx->models); + list_init(&ctx->layouts); + list_init(&ctx->option_groups); + + if (!(flags & RXKB_CONTEXT_NO_DEFAULT_INCLUDES) && + !rxkb_context_include_path_append_default(ctx)) { + rxkb_context_unref(ctx); + return NULL; + } + + return ctx; +} + +XKB_EXPORT void +rxkb_context_set_log_fn(struct rxkb_context *ctx, + void (*log_fn)(struct rxkb_context *ctx, + enum rxkb_log_level level, + const char *fmt, va_list args)) +{ + ctx->log_fn = (log_fn ? log_fn : default_log_fn); +} + +XKB_EXPORT bool +rxkb_context_include_path_append(struct rxkb_context *ctx, const char *path) +{ + struct stat stat_buf; + int err; + char *tmp = NULL; + char rules[PATH_MAX]; + + if (ctx->context_state != CONTEXT_NEW) { + log_err(ctx, "include paths can only be appended to a new context\n"); + return false; + } + + tmp = strdup(path); + if (!tmp) + goto err; + + err = stat(path, &stat_buf); + if (err != 0) + goto err; + if (!S_ISDIR(stat_buf.st_mode)) + goto err; + + if (!check_eaccess(path, R_OK | X_OK)) + goto err; + + /* Pre-filter for the 99.9% case - if we can't assemble the default ruleset + * path, complain here instead of during parsing later. The niche cases + * where this is the wrong behaviour aren't worth worrying about. + */ + if (!snprintf_safe(rules, sizeof(rules), "%s/rules/%s.xml", + path, DEFAULT_XKB_RULES)) + goto err; + + darray_append(ctx->includes, tmp); + + return true; + +err: + free(tmp); + return false; +} + +XKB_EXPORT bool +rxkb_context_include_path_append_default(struct rxkb_context *ctx) +{ + const char *home, *xdg, *root; + char *user_path; + int err; + bool ret = false; + + if (ctx->context_state != CONTEXT_NEW) { + log_err(ctx, "include paths can only be appended to a new context\n"); + return false; + } + + home = secure_getenv("HOME"); + + xdg = secure_getenv("XDG_CONFIG_HOME"); + if (xdg != NULL) { + err = asprintf(&user_path, "%s/xkb", xdg); + if (err >= 0) { + ret |= rxkb_context_include_path_append(ctx, user_path); + free(user_path); + } + } else if (home != NULL) { + /* XDG_CONFIG_HOME fallback is $HOME/.config/ */ + err = asprintf(&user_path, "%s/.config/xkb", home); + if (err >= 0) { + ret |= rxkb_context_include_path_append(ctx, user_path); + free(user_path); + } + } + + if (home != NULL) { + err = asprintf(&user_path, "%s/.xkb", home); + if (err >= 0) { + ret |= rxkb_context_include_path_append(ctx, user_path); + free(user_path); + } + } + + root = secure_getenv("XKB_CONFIG_ROOT"); + if (root != NULL) + ret |= rxkb_context_include_path_append(ctx, root); + else + ret |= rxkb_context_include_path_append(ctx, DFLT_XKB_CONFIG_ROOT); + + return ret; +} + +XKB_EXPORT bool +rxkb_context_parse_default_ruleset(struct rxkb_context *ctx) +{ + return rxkb_context_parse(ctx, DEFAULT_XKB_RULES); +} + +XKB_EXPORT bool +rxkb_context_parse(struct rxkb_context *ctx, const char *ruleset) +{ + char **path; + bool success = false; + + if (ctx->context_state != CONTEXT_NEW) { + log_err(ctx, "parse must only be called on a new context\n"); + return false; + } + + darray_foreach_reverse(path, ctx->includes) { + char rules[PATH_MAX]; + + if (snprintf_safe(rules, sizeof(rules), "%s/rules/%s.xml", + *path, ruleset)) { + log_dbg(ctx, "Parsing %s\n", rules); + if (parse(ctx, rules, RXKB_POPULARITY_STANDARD)) + success = true; + } + + if (ctx->load_extra_rules_files && + snprintf_safe(rules, sizeof(rules), "%s/rules/%s.extras.xml", + *path, ruleset)) { + log_dbg(ctx, "Parsing %s\n", rules); + if (parse(ctx, rules, RXKB_POPULARITY_EXOTIC)) + success = true; + } + } + + ctx->context_state = success ? CONTEXT_PARSED : CONTEXT_FAILED; + + return success; +} + + +XKB_EXPORT void +rxkb_context_set_user_data(struct rxkb_context *ctx, void *userdata) +{ + ctx->userdata = userdata; +} + +XKB_EXPORT void * +rxkb_context_get_user_data(struct rxkb_context *ctx) +{ + return ctx->userdata; +} + +static inline bool +is_node(xmlNode *node, const char *name) +{ + return node->type == XML_ELEMENT_NODE && + xmlStrEqual(node->name, (const xmlChar*)name); +} + +/* return a copy of the text content from the first text node of this node */ +static char * +extract_text(xmlNode *node) +{ + xmlNode *n; + + for (n = node->children; n; n = n->next) { + if (n->type == XML_TEXT_NODE) + return (char *)xmlStrdup(n->content); + } + return NULL; +} + +static bool +parse_config_item(struct rxkb_context *ctx, + xmlNode *parent, + char **name, + char **description, + char **brief, + char **vendor) +{ + xmlNode *node = NULL; + xmlNode *ci = NULL; + + for (ci = parent->children; ci; ci = ci->next) { + if (is_node(ci, "configItem")) { + *name = NULL; + *description = NULL; + *brief = NULL; + *vendor = NULL; + + for (node = ci->children; node; node = node->next) { + if (is_node(node, "name")) + *name = extract_text(node); + else if (is_node(node, "description")) + *description = extract_text(node); + else if (is_node(node, "shortDescription")) + *brief = extract_text(node); + else if (is_node(node, "vendor")) + *vendor = extract_text(node); + /* Note: the DTD allows for vendor + brief but models only use + * vendor and everything else only uses shortDescription */ + } + + if (!*name || !strlen(*name)) { + log_err(ctx, "xml:%d: missing required element 'name'\n", + ci->line); + return false; + } + + return true; /* only one configItem allowed in the dtd */ + } + } + + return false; +} + +static void +parse_model(struct rxkb_context *ctx, xmlNode *model, + enum rxkb_popularity popularity) +{ + char *name, *description, *brief, *vendor; + + if (parse_config_item(ctx, model, &name, &description, &brief, &vendor)) { + struct rxkb_model *m; + + list_for_each(m, &ctx->models, base.link) { + if (streq(m->name, name)) { + free(name); + free(description); + free(brief); + free(vendor); + return; + } + } + + /* new model */ + m = rxkb_model_create(&ctx->base); + m->name = name; + m->description = description; + m->vendor = vendor; + m->popularity = popularity; + list_append(&ctx->models, &m->base.link); + } +} + +static void +parse_model_list(struct rxkb_context *ctx, xmlNode *model_list, + enum rxkb_popularity popularity) +{ + xmlNode *node = NULL; + + for (node = model_list->children; node; node = node->next) { + if (is_node(node, "model")) + parse_model(ctx, node, popularity); + } +} + +static void +parse_language_list(xmlNode *language_list, struct rxkb_layout *layout) +{ + xmlNode *node = NULL; + struct rxkb_iso639_code *code; + + for (node = language_list->children; node; node = node->next) { + if (is_node(node, "iso639Id")) { + char *str = extract_text(node); + struct rxkb_object *parent; + + parent = &layout->base; + code = rxkb_iso639_code_create(parent); + code->code = str; + list_append(&layout->iso639s, &code->base.link); + } + } +} + +static void +parse_country_list(xmlNode *country_list, struct rxkb_layout *layout) +{ + xmlNode *node = NULL; + struct rxkb_iso3166_code *code; + + for (node = country_list->children; node; node = node->next) { + if (is_node(node, "iso3166Id")) { + char *str = extract_text(node); + struct rxkb_object *parent; + + parent = &layout->base; + code = rxkb_iso3166_code_create(parent); + code->code = str; + list_append(&layout->iso3166s, &code->base.link); + } + } +} + +static void +parse_variant(struct rxkb_context *ctx, struct rxkb_layout *l, + xmlNode *variant, enum rxkb_popularity popularity) +{ + xmlNode *ci; + char *name, *description, *brief, *vendor; + + if (parse_config_item(ctx, variant, &name, &description, &brief, &vendor)) { + struct rxkb_layout *v; + bool exists = false; + + list_for_each(v, &ctx->layouts, base.link) { + if (streq(v->name, name) && streq(v->name, l->name)) { + exists = true; + break; + } + } + + if (!exists) { + v = rxkb_layout_create(&ctx->base); + list_init(&v->iso639s); + list_init(&v->iso3166s); + v->name = strdup(l->name); + v->variant = name; + v->description = description; + v->brief = brief; + v->popularity = popularity; + list_append(&ctx->layouts, &v->base.link); + + for (ci = variant->children; ci; ci = ci->next) { + xmlNode *node; + + if (!is_node(ci, "configItem")) + continue; + + for (node = ci->children; node; node = node->next) { + if (is_node(node, "languageList")) + parse_language_list(node, v); + if (is_node(node, "countryList")) + parse_country_list(node, v); + } + } + } else { + free(name); + free(description); + free(brief); + free(vendor); + } + } +} + +static void +parse_variant_list(struct rxkb_context *ctx, struct rxkb_layout *l, + xmlNode *variant_list, enum rxkb_popularity popularity) +{ + xmlNode *node = NULL; + + for (node = variant_list->children; node; node = node->next) { + if (is_node(node, "variant")) + parse_variant(ctx, l, node, popularity); + } +} + +static void +parse_layout(struct rxkb_context *ctx, xmlNode *layout, + enum rxkb_popularity popularity) +{ + char *name, *description, *brief, *vendor; + struct rxkb_layout *l; + xmlNode *node = NULL; + bool exists = false; + + if (!parse_config_item(ctx, layout, &name, &description, &brief, &vendor)) + return; + + list_for_each(l, &ctx->layouts, base.link) { + if (streq(l->name, name) && l->variant == NULL) { + exists = true; + break; + } + } + + if (!exists) { + l = rxkb_layout_create(&ctx->base); + list_init(&l->iso639s); + list_init(&l->iso3166s); + l->name = name; + l->variant = NULL; + l->description = description; + l->brief = brief; + l->popularity = popularity; + list_append(&ctx->layouts, &l->base.link); + } else { + free(name); + free(description); + free(brief); + free(vendor); + } + + for (node = layout->children; node; node = node->next) { + if (is_node(node, "variantList")) { + parse_variant_list(ctx, l, node, popularity); + } + if (!exists && is_node(node, "configItem")) { + xmlNode *ll; + for (ll = node->children; ll; ll = ll->next) { + if (is_node(ll, "languageList")) + parse_language_list(ll, l); + if (is_node(ll, "countryList")) + parse_country_list(ll, l); + } + } + } +} + +static void +parse_layout_list(struct rxkb_context *ctx, xmlNode *layout_list, + enum rxkb_popularity popularity) +{ + xmlNode *node = NULL; + + for (node = layout_list->children; node; node = node->next) { + if (is_node(node, "layout")) + parse_layout(ctx, node, popularity); + } +} + +static void +parse_option(struct rxkb_context *ctx, struct rxkb_option_group *group, + xmlNode *option, enum rxkb_popularity popularity) +{ + char *name, *description, *brief, *vendor; + + if (parse_config_item(ctx, option, &name, &description, &brief, &vendor)) { + struct rxkb_option *o; + + list_for_each(o, &group->options, base.link) { + if (streq(o->name, name)) { + free(name); + free(description); + free(brief); + free(vendor); + return; + } + } + + o = rxkb_option_create(&group->base); + o->name = name; + o->description = description; + o->popularity = popularity; + list_append(&group->options, &o->base.link); + } +} + +static void +parse_group(struct rxkb_context *ctx, xmlNode *group, + enum rxkb_popularity popularity) +{ + char *name, *description, *brief, *vendor; + struct rxkb_option_group *g; + xmlNode *node = NULL; + xmlChar *multiple; + bool exists = false; + + if (!parse_config_item(ctx, group, &name, &description, &brief, &vendor)) + return; + + list_for_each(g, &ctx->option_groups, base.link) { + if (streq(g->name, name)) { + exists = true; + break; + } + } + + if (!exists) { + g = rxkb_option_group_create(&ctx->base); + g->name = name; + g->description = description; + g->popularity = popularity; + + multiple = xmlGetProp(group, (const xmlChar*)"allowMultipleSelection"); + if (multiple && xmlStrEqual(multiple, (const xmlChar*)"true")) + g->allow_multiple = true; + xmlFree(multiple); + + list_init(&g->options); + list_append(&ctx->option_groups, &g->base.link); + } else { + free(name); + free(description); + free(brief); + free(vendor); + } + + for (node = group->children; node; node = node->next) { + if (is_node(node, "option")) + parse_option(ctx, g, node, popularity); + } +} + +static void +parse_option_list(struct rxkb_context *ctx, xmlNode *option_list, + enum rxkb_popularity popularity) +{ + xmlNode *node = NULL; + + for (node = option_list->children; node; node = node->next) { + if (is_node(node, "group")) + parse_group(ctx, node, popularity); + } +} + +static void +parse_rules_xml(struct rxkb_context *ctx, xmlNode *root, + enum rxkb_popularity popularity) +{ + xmlNode *node = NULL; + + for (node = root->children; node; node = node->next) { + if (is_node(node, "modelList")) + parse_model_list(ctx, node, popularity); + else if (is_node(node, "layoutList")) + parse_layout_list(ctx, node, popularity); + else if (is_node(node, "optionList")) + parse_option_list(ctx, node, popularity); + } +} + +static void +ATTR_PRINTF(2, 0) +xml_error_func(void *ctx, const char *msg, ...) +{ + static char buf[PATH_MAX]; + static int slen = 0; + va_list args; + int rc; + + /* libxml2 prints IO errors from bad includes paths by + * calling the error function once per word. So we get to + * re-assemble the message here and print it when we get + * the line break. My enthusiasm about this is indescribable. + */ + va_start(args, msg); + rc = vsnprintf(&buf[slen], sizeof(buf) - slen, msg, args); + va_end(args); + + /* This shouldn't really happen */ + if (rc < 0) { + log_err(ctx, "+++ out of cheese error. redo from start +++\n"); + slen = 0; + memset(buf, 0, sizeof(buf)); + return; + } + + slen += rc; + if (slen >= (int)sizeof(buf)) { + /* truncated, let's flush this */ + buf[sizeof(buf) - 1] = '\n'; + slen = sizeof(buf); + } + + /* We're assuming here that the last character is \n. */ + if (buf[slen - 1] == '\n') { + log_err(ctx, "%s", buf); + memset(buf, 0, sizeof(buf)); + slen = 0; + } +} + +static bool +validate(struct rxkb_context *ctx, xmlDoc *doc) +{ + bool success = false; + xmlValidCtxt *dtdvalid = NULL; + xmlDtd *dtd = NULL; + xmlParserInputBufferPtr buf = NULL; + /* This is a modified version of the xkeyboard-config xkb.dtd. That one + * requires modelList, layoutList and optionList, we + * allow for any of those to be missing. + */ + const char dtdstr[] = + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n"; + + /* Note: do not use xmlParserInputBufferCreateStatic, it generates random + * DTD validity errors for unknown reasons */ + buf = xmlParserInputBufferCreateMem(dtdstr, sizeof(dtdstr), + XML_CHAR_ENCODING_UTF8); + if (!buf) + return false; + + dtd = xmlIOParseDTD(NULL, buf, XML_CHAR_ENCODING_UTF8); + if (!dtd) { + log_err(ctx, "Failed to load DTD\n"); + return false; + } + + dtdvalid = xmlNewValidCtxt(); + if (xmlValidateDtd(dtdvalid, doc, dtd)) + success = true; + + if (dtd) + xmlFreeDtd(dtd); + if (dtdvalid) + xmlFreeValidCtxt(dtdvalid); + + return success; +} + +static bool +parse(struct rxkb_context *ctx, const char *path, + enum rxkb_popularity popularity) +{ + bool success = false; + xmlDoc *doc = NULL; + xmlNode *root = NULL; + + if (!check_eaccess(path, R_OK)) + return false; + + LIBXML_TEST_VERSION + + xmlSetGenericErrorFunc(ctx, xml_error_func); + + doc = xmlParseFile(path); + if (!doc) + return false; + + if (!validate(ctx, doc)) { + log_err(ctx, "XML error: failed to validate document at %s\n", path); + goto error; + } + + root = xmlDocGetRootElement(doc); + parse_rules_xml(ctx, root, popularity); + + success = true; +error: + xmlFreeDoc(doc); + xmlCleanupParser(); + + return success; +} diff --git a/src/util-list.c b/src/util-list.c new file mode 100644 index 0000000..b6f5069 --- /dev/null +++ b/src/util-list.c @@ -0,0 +1,94 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "util-list.h" + +void +list_init(struct list *list) +{ + list->prev = list; + list->next = list; +} + +void +list_insert(struct list *list, struct list *elm) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || + !"elm->next|prev is not NULL, list node used twice?"); + + elm->prev = list; + elm->next = list->next; + list->next = elm; + elm->next->prev = elm; +} + +void +list_append(struct list *list, struct list *elm) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || + !"elm->next|prev is not NULL, list node used twice?"); + + elm->next = list; + elm->prev = list->prev; + list->prev = elm; + elm->prev->next = elm; +} + +void +list_remove(struct list *elm) +{ + assert((elm->next != NULL && elm->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + + elm->prev->next = elm->next; + elm->next->prev = elm->prev; + elm->next = NULL; + elm->prev = NULL; +} + +bool +list_empty(const struct list *list) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + + return list->next == list; +} + +bool +list_is_last(const struct list *list, const struct list *elm) +{ + return elm->next == list; +} diff --git a/src/util-list.h b/src/util-list.h new file mode 100644 index 0000000..573dff7 --- /dev/null +++ b/src/util-list.h @@ -0,0 +1,71 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include +#include + +/* + * This list data structure is a verbatim copy from wayland-util.h from the + * Wayland project; except that wl_ prefix has been removed. + */ + +struct list { + struct list *prev; + struct list *next; +}; + +void list_init(struct list *list); +void list_insert(struct list *list, struct list *elm); +void list_append(struct list *list, struct list *elm); +void list_remove(struct list *elm); +bool list_empty(const struct list *list); +bool list_is_last(const struct list *list, const struct list *elm); + +#define container_of(ptr, type, member) \ + (__typeof__(type) *)((char *)(ptr) - \ + offsetof(__typeof__(type), member)) + +#define list_first_entry(head, pos, member) \ + container_of((head)->next, __typeof__(*pos), member) + +#define list_last_entry(head, pos, member) \ + container_of((head)->prev, __typeof__(*pos), member) + +#define list_for_each(pos, head, member) \ + for (pos = 0, pos = list_first_entry(head, pos, member); \ + &pos->member != (head); \ + pos = list_first_entry(&pos->member, pos, member)) + +#define list_for_each_safe(pos, tmp, head, member) \ + for (pos = 0, tmp = 0, \ + pos = list_first_entry(head, pos, member), \ + tmp = list_first_entry(&pos->member, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = list_first_entry(&pos->member, tmp, member)) diff --git a/test/registry.c b/test/registry.c new file mode 100644 index 0000000..68e74d0 --- /dev/null +++ b/test/registry.c @@ -0,0 +1,843 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "xkbcommon/xkbregistry.h" + +#include "utils.h" + +#define NO_VARIANT NULL + +enum { + MODEL = 78, + LAYOUT, + VARIANT, + OPTION, +}; + +struct test_model { + const char *name; /* required */ + const char *vendor; + const char *description; +}; + +struct test_layout { + const char *name; /* required */ + const char *variant; + const char *brief; + const char *description; +}; + +struct test_option { + const char *name; + const char *description; +}; + +struct test_option_group { + const char *name; + const char *description; + bool allow_multiple_selection; + + struct test_option options[10]; +}; + +static void +fprint_config_item(FILE *fp, + const char *name, + const char *vendor, + const char *brief, + const char *description) +{ + fprintf(fp, " \n" + " %s\n", name); + if (brief) + fprintf(fp, " %s\n", brief); + if (description) + fprintf(fp, " %s\n", description); + if (vendor) + fprintf(fp, " %s\n", vendor); + fprintf(fp, " \n"); +} + +/** + * Create a directory populated with a rules/.xml that contains the + * given items. + * + * @return the XKB base directory + */ +static char * +test_create_rules(const char *ruleset, + const struct test_model *test_models, + const struct test_layout *test_layouts, + const struct test_option_group *test_groups) +{ + static int iteration; + char *tmpdir; + char buf[PATH_MAX]; + int rc; + FILE *fp; + + rc = asprintf(&tmpdir, "/tmp/%s.%d.XXXXXX", ruleset, iteration++); + assert(rc > 0); + assert(mkdtemp(tmpdir) == tmpdir); + + rc = snprintf_safe(buf, sizeof(buf), "%s/rules", tmpdir); + assert(rc); + rc = mkdir(buf, 0777); + assert(rc == 0); + rc = snprintf_safe(buf, sizeof(buf), "%s/rules/%s.xml", tmpdir, ruleset); + assert(rc); + + fp = fopen(buf, "w"); + assert(fp); + + fprintf(fp, + "\n" + "\n" + "\n"); + + if (test_models) { + fprintf(fp, "\n"); + + for (const struct test_model *m = test_models; m->name; m++) { + fprintf(fp, "\n"); + fprint_config_item(fp, m->name, m->vendor, NULL, m->description); + fprintf(fp, "\n"); + } + fprintf(fp, "\n"); + } + + if (test_layouts) { + const struct test_layout *l, *next; + + fprintf(fp, "\n"); + + l = test_layouts; + next = l + 1; + + assert(l->variant == NULL); + + while (l->name) { + fprintf(fp, "\n"); + fprint_config_item(fp, l->name, NULL, l->brief, l->description); + + if (next->name && streq(next->name, l->name)) { + fprintf(fp, "\n"); + do { + fprintf(fp, "\n"); + fprint_config_item(fp, next->variant, NULL, next->brief, + next->description); + fprintf(fp, "\n"); + l = next; + next++; + } while (next->name && streq(next->name, l->name)); + fprintf(fp, "\n"); + } + fprintf(fp, "\n"); + l++; + } + fprintf(fp, "\n"); + } + + if (test_groups) { + fprintf(fp, "\n"); + + for (const struct test_option_group *g = test_groups; g->name; g++) { + fprintf(fp, "\n", + g->allow_multiple_selection ? "true" : "false"); + fprint_config_item(fp, g->name, NULL, NULL, g->description); + for (const struct test_option *o = g->options; o->name; o++) { + fprintf(fp, " \n"); + } + fprintf(fp, "\n"); + } + fprintf(fp, "\n"); + } + + fprintf(fp, "\n"); + fclose(fp); + + return tmpdir; +} + +static void +test_remove_rules(char *basedir, const char *ruleset) +{ + char path[PATH_MAX]; + int rc; + + rc = snprintf_safe(path, sizeof(path), "%s/rules/%s.xml", basedir, + ruleset); + assert(rc); + unlink(path); + rc = snprintf_safe(path, sizeof(path), "%s/xkb/rules", basedir); + assert(rc); + rmdir(path); + rmdir(basedir); + free(basedir); +} + +static struct rxkb_context * +test_setup_context_for(const char *ruleset, + struct test_model *system_models, + struct test_model *user_models, + struct test_layout *system_layouts, + struct test_layout *user_layouts, + struct test_option_group *system_groups, + struct test_option_group *user_groups) +{ + char *sysdir = NULL, *userdir = NULL; + struct rxkb_context *ctx; + + sysdir = test_create_rules(ruleset, system_models, system_layouts, + system_groups); + if (user_models || user_layouts || user_groups) + userdir = test_create_rules(ruleset, user_models, user_layouts, + user_groups); + + ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES); + assert(ctx); + if (userdir) + assert(rxkb_context_include_path_append(ctx, userdir)); + assert(rxkb_context_include_path_append(ctx, sysdir)); + assert(rxkb_context_parse(ctx, ruleset)); + + test_remove_rules(sysdir, ruleset); + if (userdir) + test_remove_rules(userdir, ruleset); + + return ctx; +} + +static struct rxkb_context * +test_setup_context(struct test_model *system_models, + struct test_model *user_models, + struct test_layout *system_layouts, + struct test_layout *user_layouts, + struct test_option_group *system_groups, + struct test_option_group *user_groups) +{ + const char *ruleset = "xkbtests"; + return test_setup_context_for(ruleset, system_models, + user_models, system_layouts, + user_layouts, system_groups, + user_groups); +} + +static struct rxkb_model * +fetch_model(struct rxkb_context *ctx, const char *model) +{ + struct rxkb_model *m = rxkb_model_first(ctx); + while (m) { + if (streq(rxkb_model_get_name(m), model)) + return rxkb_model_ref(m); + m = rxkb_model_next(m); + } + return NULL; +} + +static bool +find_model(struct rxkb_context *ctx, const char *model) +{ + struct rxkb_model *m = fetch_model(ctx, model); + rxkb_model_unref(m); + return m != NULL; +} + +static bool +find_models(struct rxkb_context *ctx, ...) +{ + va_list args; + const char *name; + int idx = 0; + + va_start(args, ctx); + name = va_arg(args, const char *); + while(name) { + assert(++idx < 20); /* safety guard */ + if (!find_model(ctx, name)) + return false; + name = va_arg(args, const char *); + }; + + va_end(args); + return true; +} + +static struct rxkb_layout * +fetch_layout(struct rxkb_context *ctx, const char *layout, const char *variant) +{ + struct rxkb_layout *l = rxkb_layout_first(ctx); + while (l) { + const char *v = rxkb_layout_get_variant(l); + + if (streq(rxkb_layout_get_name(l), layout) && + ((v == NULL && variant == NULL) || + (v != NULL && variant != NULL && streq(v, variant)))) + return rxkb_layout_ref(l); + l = rxkb_layout_next(l); + } + return NULL; +} + +static bool +find_layout(struct rxkb_context *ctx, const char *layout, const char *variant) +{ + struct rxkb_layout *l = fetch_layout(ctx, layout, variant); + rxkb_layout_unref(l); + return l != NULL; +} + +static bool +find_layouts(struct rxkb_context *ctx, ...) +{ + va_list args; + const char *name, *variant; + int idx = 0; + + va_start(args, ctx); + name = va_arg(args, const char *); + variant = va_arg(args, const char *); + while(name) { + assert(++idx < 20); /* safety guard */ + if (!find_layout(ctx, name, variant)) + return false; + name = va_arg(args, const char *); + if (name) + variant = va_arg(args, const char *); + }; + + va_end(args); + return true; +} + +static struct rxkb_option_group * +fetch_option_group(struct rxkb_context *ctx, const char *grp) +{ + struct rxkb_option_group *g = rxkb_option_group_first(ctx); + while (g) { + if (streq(grp, rxkb_option_group_get_name(g))) + return rxkb_option_group_ref(g); + g = rxkb_option_group_next(g); + } + return NULL; +} + +static inline bool +find_option_group(struct rxkb_context *ctx, const char *grp) +{ + struct rxkb_option_group *g = fetch_option_group(ctx, grp); + rxkb_option_group_unref(g); + return g != NULL; +} + +static struct rxkb_option * +fetch_option(struct rxkb_context *ctx, const char *grp, const char *opt) +{ + struct rxkb_option_group *g = rxkb_option_group_first(ctx); + while (g) { + if (streq(grp, rxkb_option_group_get_name(g))) { + struct rxkb_option *o = rxkb_option_first(g); + + while (o) { + if (streq(opt, rxkb_option_get_name(o))) + return rxkb_option_ref(o); + o = rxkb_option_next(o); + } + } + g = rxkb_option_group_next(g); + } + return NULL; +} + +static bool +find_option(struct rxkb_context *ctx, const char *grp, const char *opt) +{ + struct rxkb_option *o = fetch_option(ctx, grp, opt); + rxkb_option_unref(o); + return o != NULL; +} + +static bool +find_options(struct rxkb_context *ctx, ...) +{ + va_list args; + const char *grp, *opt; + int idx = 0; + + va_start(args, ctx); + grp = va_arg(args, const char *); + opt = va_arg(args, const char *); + while(grp) { + assert(++idx < 20); /* safety guard */ + if (!find_option(ctx, grp, opt)) + return false; + grp = va_arg(args, const char *); + if (grp) + opt = va_arg(args, const char *); + }; + + va_end(args); + return true; +} + +static bool +cmp_models(struct test_model *tm, struct rxkb_model *m) +{ + if (!tm || !m) + return false; + + if (!streq(tm->name, rxkb_model_get_name(m))) + return false; + + if (!streq_null(tm->vendor, rxkb_model_get_vendor(m))) + return false; + + if (!streq_null(tm->description, rxkb_model_get_description(m))) + return false; + + return true; +} + +static bool +cmp_layouts(struct test_layout *tl, struct rxkb_layout *l) +{ + if (!tl || !l) + return false; + + if (!streq(tl->name, rxkb_layout_get_name(l))) + return false; + + if (!streq_null(tl->variant, rxkb_layout_get_variant(l))) + return false; + + if (!streq_null(tl->brief, rxkb_layout_get_brief(l))) + return false; + + if (!streq_null(tl->description, rxkb_layout_get_description(l))) + return false; + + return true; +} + +static bool +cmp_options(struct test_option *to, struct rxkb_option *o) +{ + if (!to || !o) + return false; + + if (!streq(to->name, rxkb_option_get_name(o))) + return false; + + if (!streq_null(to->description, rxkb_option_get_description(o))) + return false; + + return true; +} + +enum cmp_type { + CMP_EXACT, + CMP_MATCHING_ONLY, +}; + +static bool +cmp_option_groups(struct test_option_group *tg, struct rxkb_option_group *g, + enum cmp_type cmp) +{ + struct rxkb_option *o; + struct test_option *to; + + if (!tg || !g) + return false; + + if (!streq(tg->name, rxkb_option_group_get_name(g))) + return false; + + if (!streq_null(tg->description, rxkb_option_group_get_description(g))) + return false; + + if (tg->allow_multiple_selection != rxkb_option_group_allows_multiple(g)) + return false; + + to = tg->options; + o = rxkb_option_first(g); + + while (o && to->name) { + if (!cmp_options(to, o)) + return false; + to++; + o = rxkb_option_next(o); + } + + if (cmp == CMP_EXACT && (o || to->name)) + return false; + + return true; +} + +static void +test_load_basic(void) +{ + struct test_model system_models[] = { + {"m1"}, + {"m2"}, + {NULL}, + }; + struct test_layout system_layouts[] = { + {"l1"}, + {"l1", "v1"}, + {NULL}, + }; + struct test_option_group system_groups[] = { + {"grp1", NULL, true, + { {"grp1:1"}, {"grp1:2"} } }, + {"grp2", NULL, false, + { {"grp2:1"}, {"grp2:2"} } }, + { NULL }, + }; + struct rxkb_context *ctx; + + ctx = test_setup_context(system_models, NULL, + system_layouts, NULL, + system_groups, NULL); + + assert(find_models(ctx, "m1", "m2", NULL)); + assert(find_layouts(ctx, "l1", NO_VARIANT, + "l1", "v1", NULL)); + assert(find_options(ctx, "grp1", "grp1:1", + "grp1", "grp1:2", + "grp2", "grp2:1", + "grp2", "grp2:2", NULL)); + rxkb_context_unref(ctx); +} + +static void +test_load_full(void) +{ + struct test_model system_models[] = { + {"m1", "vendor1", "desc1"}, + {"m2", "vendor2", "desc2"}, + {NULL}, + }; + struct test_layout system_layouts[] = { + {"l1", NO_VARIANT, "lbrief1", "ldesc1"}, + {"l1", "v1", "vbrief1", "vdesc1"}, + {NULL}, + }; + struct test_option_group system_groups[] = { + {"grp1", "gdesc1", true, + { {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } }, + {"grp2", "gdesc2", false, + { {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } }, + { NULL }, + }; + struct rxkb_context *ctx; + struct rxkb_model *m; + struct rxkb_layout *l; + struct rxkb_option_group *g; + + ctx = test_setup_context(system_models, NULL, + system_layouts, NULL, + system_groups, NULL); + + m = fetch_model(ctx, "m1"); + assert(cmp_models(&system_models[0], m)); + rxkb_model_unref(m); + + m = fetch_model(ctx, "m2"); + assert(cmp_models(&system_models[1], m)); + rxkb_model_unref(m); + + l = fetch_layout(ctx, "l1", NO_VARIANT); + assert(cmp_layouts(&system_layouts[0], l)); + rxkb_layout_unref(l); + + l = fetch_layout(ctx, "l1", "v1"); + assert(cmp_layouts(&system_layouts[1], l)); + rxkb_layout_unref(l); + + g = fetch_option_group(ctx, "grp1"); + assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT)); + rxkb_option_group_unref(g); + + g = fetch_option_group(ctx, "grp2"); + assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT)); + rxkb_option_group_unref(g); + + rxkb_context_unref(ctx); +} + +static void +test_popularity(void) +{ + struct test_layout system_layouts[] = { + {"l1", NO_VARIANT }, + {"l1", "v1" }, + {NULL}, + }; + struct rxkb_context *ctx; + struct rxkb_layout *l; + const char *ruleset = "xkbtests.extras"; + char *dir = NULL; + + dir = test_create_rules(ruleset, NULL, system_layouts, NULL); + ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES | + RXKB_CONTEXT_LOAD_EXOTIC_RULES); + assert(ctx); + assert(rxkb_context_include_path_append(ctx, dir)); + /* Hack: rulest above generates xkbtests.extras.xml, loading "xkbtests" + * means the extras file counts as exotic */ + assert(rxkb_context_parse(ctx, "xkbtests")); + + l = fetch_layout(ctx, "l1", NO_VARIANT); + assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC); + rxkb_layout_unref(l); + + l = fetch_layout(ctx, "l1", "v1"); + assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC); + rxkb_layout_unref(l); + + test_remove_rules(dir, ruleset); + rxkb_context_unref(ctx); +} + + +static void +test_load_merge(void) +{ + struct test_model system_models[] = { + {"m1", "vendor1", "desc1"}, + {"m2", "vendor2", "desc2"}, + {NULL}, + }; + struct test_model user_models[] = { + {"m3", "vendor3", "desc3"}, + {"m4", "vendor4", "desc4"}, + {NULL}, + }; + struct test_layout system_layouts[] = { + {"l1", NO_VARIANT, "lbrief1", "ldesc1"}, + {"l1", "v1", "vbrief1", "vdesc1"}, + {NULL}, + }; + struct test_layout user_layouts[] = { + {"l2", NO_VARIANT, "lbrief2", "ldesc2"}, + {"l2", "v2", "vbrief2", "vdesc2"}, + {NULL}, + }; + struct test_option_group system_groups[] = { + {"grp1", NULL, true, + { {"grp1:1"}, {"grp1:2"} } }, + {"grp2", NULL, false, + { {"grp2:1"}, {"grp2:2"} } }, + { NULL }, + }; + struct test_option_group user_groups[] = { + {"grp3", NULL, true, + { {"grp3:1"}, {"grp3:2"} } }, + {"grp4", NULL, false, + { {"grp4:1"}, {"grp4:2"} } }, + { NULL }, + }; + struct rxkb_context *ctx; + struct rxkb_model *m; + struct rxkb_layout *l; + struct rxkb_option_group *g; + + ctx = test_setup_context(system_models, user_models, + system_layouts, user_layouts, + system_groups, user_groups); + + assert(find_models(ctx, "m1", "m2", "m3", "m4", NULL)); + assert(find_layouts(ctx, "l1", NO_VARIANT, + "l1", "v1", + "l2", NO_VARIANT, + "l2", "v2", NULL)); + + m = fetch_model(ctx, "m1"); + assert(cmp_models(&system_models[0], m)); + rxkb_model_unref(m); + + m = fetch_model(ctx, "m2"); + assert(cmp_models(&system_models[1], m)); + rxkb_model_unref(m); + + m = fetch_model(ctx, "m3"); + assert(cmp_models(&user_models[0], m)); + rxkb_model_unref(m); + + m = fetch_model(ctx, "m4"); + assert(cmp_models(&user_models[1], m)); + rxkb_model_unref(m); + + l = fetch_layout(ctx, "l1", NO_VARIANT); + assert(cmp_layouts(&system_layouts[0], l)); + rxkb_layout_unref(l); + + l = fetch_layout(ctx, "l1", "v1"); + assert(cmp_layouts(&system_layouts[1], l)); + rxkb_layout_unref(l); + + l = fetch_layout(ctx, "l2", NO_VARIANT); + assert(cmp_layouts(&user_layouts[0], l)); + rxkb_layout_unref(l); + + l = fetch_layout(ctx, "l2", "v2"); + assert(cmp_layouts(&user_layouts[1], l)); + rxkb_layout_unref(l); + + g = fetch_option_group(ctx, "grp1"); + assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT)); + rxkb_option_group_unref(g); + + g = fetch_option_group(ctx, "grp2"); + assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT)); + rxkb_option_group_unref(g); + + g = fetch_option_group(ctx, "grp3"); + assert(cmp_option_groups(&user_groups[0], g, CMP_EXACT)); + rxkb_option_group_unref(g); + + g = fetch_option_group(ctx, "grp4"); + assert(cmp_option_groups(&user_groups[1], g, CMP_EXACT)); + rxkb_option_group_unref(g); + + rxkb_context_unref(ctx); +} + +static void +test_load_merge_no_overwrite(void) +{ + struct test_model system_models[] = { + {"m1", "vendor1", "desc1"}, + {"m2", "vendor2", "desc2"}, + {NULL}, + }; + struct test_model user_models[] = { + {"m1", "vendor3", "desc3"}, /* must not overwrite */ + {"m4", "vendor4", "desc4"}, + {NULL}, + }; + struct test_layout system_layouts[] = { + {"l1", NO_VARIANT, "lbrief1", "ldesc1"}, + {"l1", "v1", "vbrief1", "vdesc1"}, + {NULL}, + }; + struct test_layout user_layouts[] = { + {"l2", NO_VARIANT, "lbrief2", "ldesc2"}, + {"l2", "v2", "vbrief2", "vdesc2"}, + {"l1", NO_VARIANT, "lbrief3", "ldesc3"}, /* must not overwrite */ + {"l1", "v2", "vbrief3", "vdesc3"}, /* must not overwrite */ + {NULL}, + }; + struct test_option_group system_groups[] = { + {"grp1", "gdesc1", true, + { {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } }, + {"grp2", "gdesc2", false, + { {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } }, + { NULL }, + }; + struct test_option_group user_groups[] = { + {"grp1", "XXXXX", false, /* must not overwrite */ + { {"grp1:1", "YYYYYYY"}, /* must not overwrite */ + {"grp1:3", "ZZZZZZ"} } }, /* append */ + {"grp4", "gdesc4", false, + { {"grp4:1", "odesc41"}, {"grp4:2", "odesc42"} } }, + { NULL }, + }; + struct rxkb_context *ctx; + struct rxkb_model *m; + struct rxkb_layout *l; + struct rxkb_option_group *g; + + ctx = test_setup_context(system_models, user_models, + system_layouts, user_layouts, + system_groups, user_groups); + + m = fetch_model(ctx, "m1"); + assert(cmp_models(&system_models[0], m)); + rxkb_model_unref(m); + + l = fetch_layout(ctx, "l1", NO_VARIANT); + assert(cmp_layouts(&system_layouts[0], l)); + rxkb_layout_unref(l); + + l = fetch_layout(ctx, "l1", "v1"); + assert(cmp_layouts(&system_layouts[1], l)); + rxkb_layout_unref(l); + + assert(find_option(ctx, "grp1", "grp1:3")); + g = fetch_option_group(ctx, "grp1"); + assert(cmp_option_groups(&system_groups[0], g, CMP_MATCHING_ONLY)); + rxkb_option_group_unref(g); + + rxkb_context_unref(ctx); +} + +static void +test_no_include_paths(void) +{ + struct rxkb_context *ctx; + + ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES); + assert(ctx); + assert(!rxkb_context_parse_default_ruleset(ctx)); + + rxkb_context_unref(ctx); +} + +static void +test_invalid_include(void) +{ + struct rxkb_context *ctx; + + ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES); + assert(ctx); + assert(!rxkb_context_include_path_append(ctx, "/foo/bar/baz/bat")); + assert(!rxkb_context_parse_default_ruleset(ctx)); + + rxkb_context_unref(ctx); +} + +int +main(void) +{ + test_no_include_paths(); + test_invalid_include(); + test_load_basic(); + test_load_full(); + test_load_merge(); + test_load_merge_no_overwrite(); + test_popularity(); + + return 0; +} diff --git a/tools/registry-list.c b/tools/registry-list.c new file mode 100644 index 0000000..e51c41f --- /dev/null +++ b/tools/registry-list.c @@ -0,0 +1,223 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "xkbcommon/xkbregistry.h" + +static void +usage(const char *progname) +{ + fprintf(stderr, + "Usage: %s [OPTIONS] [/path/to/xkb_base_directory [/path2]...]\n" + "\n" + "Options:\n" + " --verbose, -v .......... Increase verbosity, use multiple times for debugging output\n" + " --ruleset=foo .......... Load the 'foo' ruleset\n" + " --skip-default-paths ... Do not load the default XKB paths\n" + " --load-exotic .......... Load the exotic (extra) rulesets\n" + "\n" + "Trailing arguments are treated as XKB base directory installations.\n", + progname); +} + +int +main(int argc, char **argv) +{ + int rc = 1; + struct rxkb_context *ctx = NULL; + struct rxkb_model *m; + struct rxkb_layout *l; + struct rxkb_option_group *g; + enum rxkb_context_flags flags = RXKB_CONTEXT_NO_FLAGS; + bool load_defaults = true; + int verbosity = 0; + const char *ruleset = DEFAULT_XKB_RULES; + + static const struct option opts[] = { + {"help", no_argument, 0, 'h'}, + {"verbose", no_argument, 0, 'v'}, + {"load-exotic", no_argument, 0, 'e'}, + {"skip-default-paths", no_argument, 0, 'd'}, + {"ruleset", required_argument, 0, 'r'}, + {0, 0, 0, 0}, + }; + + while (1) { + int c; + int option_index = 0; + + c = getopt_long(argc, argv, "hev", opts, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + case '?': + usage(argv[0]); + return 1; + case 'd': + load_defaults = false; + break; + case 'e': + flags |= RXKB_CONTEXT_LOAD_EXOTIC_RULES; + break; + case 'r': + ruleset = optarg; + break; + case 'v': + verbosity++; + break; + } + } + + if (optind < argc) + flags |= RXKB_CONTEXT_NO_DEFAULT_INCLUDES; + + ctx = rxkb_context_new(flags); + assert(ctx); + + switch (verbosity) { + case 0: + rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_ERROR); + break; + case 1: + rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_INFO); + break; + default: + rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_DEBUG); + break; + } + + if (optind < argc) { + for (int i = optind; i < argc; i++) { + if (!rxkb_context_include_path_append(ctx, argv[i])) { + fprintf(stderr, "Failed to append include path '%s'\n", + argv[i]); + goto err; + } + } + + if (load_defaults) { + if (!rxkb_context_include_path_append_default(ctx)) { + fprintf(stderr, "Failed to include default paths.\n"); + goto err; + } + } + } + if (!rxkb_context_parse(ctx, ruleset)) { + fprintf(stderr, "Failed to parse XKB descriptions.\n"); + goto err; + } + + printf("Models:\n"); + m = rxkb_model_first(ctx); + assert(m); /* Empty model list is usually a bug or a bad xml file */ + while (m) { + printf("- %s:%s:%s\n", + rxkb_model_get_name(m), + rxkb_model_get_vendor(m), + rxkb_model_get_description(m)); + m = rxkb_model_next(m); + } + + printf("\n"); + printf("Layouts:\n"); + l = rxkb_layout_first(ctx); + assert(l); /* Empty layout list is usually a bug or a bad xml file */ + while (l) { + struct rxkb_iso639_code *iso639; + struct rxkb_iso3166_code *iso3166; + const char *variant = rxkb_layout_get_variant(l); + const char *brief = rxkb_layout_get_brief(l); + bool first; + + printf("- %s%s%s%s:%s:%s", + rxkb_layout_get_name(l), + variant ? "(" : "", + variant ? variant : "", + variant ? ")" : "", + brief ? brief : "", + rxkb_layout_get_description(l)); + + iso639 = rxkb_layout_get_iso639_first(l); + if (iso639) + printf(":iso639-"); + first = true; + while (iso639) { + printf("%s%s", first ? "" : ",", rxkb_iso639_code_get_code(iso639)); + iso639 = rxkb_iso639_code_next(iso639); + first = false; + } + iso3166 = rxkb_layout_get_iso3166_first(l); + if (iso3166) + printf(":iso3166-"); + first = true; + while (iso3166) { + printf("%s%s", first ? "" : ",", rxkb_iso3166_code_get_code(iso3166)); + iso3166 = rxkb_iso3166_code_next(iso3166); + first = false; + } + + printf("\n"); + l = rxkb_layout_next(l); + } + printf("\n"); + printf("Options:\n"); + g = rxkb_option_group_first(ctx); + assert(g); /* Empty option goups list is usually a bug or a bad xml file */ + while (g) { + struct rxkb_option *o; + + printf("- %s:%s (%s)\n", + rxkb_option_group_get_name(g), + rxkb_option_group_get_description(g), + rxkb_option_group_allows_multiple(g) ? "multiple" : "single"); + + o = rxkb_option_first(g); + assert(o); /* Empty option list is usually a bug or a bad xml file */ + while (o) { + const char *brief = rxkb_option_get_brief(o); + + printf(" - %s:%s:%s\n", + rxkb_option_get_name(o), + brief ? brief : "", + rxkb_option_get_description(o)); + o = rxkb_option_next(o); + } + + g = rxkb_option_group_next(g); + } + + rc = 0; + +err: + if (ctx) + rxkb_context_unref(ctx); + + return rc; +} diff --git a/xkbcommon/xkbregistry.h b/xkbcommon/xkbregistry.h new file mode 100644 index 0000000..4e7e926 --- /dev/null +++ b/xkbcommon/xkbregistry.h @@ -0,0 +1,782 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + + +#ifndef _XKBREGISTRY_H_ +#define _XKBREGISTRY_H_ + +#include +#include + +/** + * @file + * @brief Query for available RMLVO + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup registry Query for available RMLVO + * + * The libxkbregistry API to query for available rules, models, layouts, + * variants and options (RMLVO). libxkbregistry is a separate library to + * libxkbcommon. + * + * This library is the replacement for clients currently parsing evdev.xml + * directly. The library is intended to provide easy access to the set of + * **possible** MLVO configurations for a given ruleset. It is not a library to + * apply these configurations, merely to enumerate them. The intended users of + * this library are the configuration UIs that allow a user to select their + * keyboard layout of choice. + * + * @{ + */ + +/** + * @struct rxkb_context + * + * Opaque top level library context object. + * + * The context contains general library state, like include paths and parsed + * data. Objects are created in a specific context, and multiple contexts + * may coexist simultaneously. Objects from different contexts are + * completely separated and do not share any memory or state. + */ +struct rxkb_context; + +/** + * @struct rxkb_model + * + * Opaque struct representing an XKB model. + */ +struct rxkb_model; + +/** + * @struct rxkb_layout + * + * Opaque struct representing an XKB layout, including an optional variant. + * Where the variant is NULL, the layout is the base layout. + * + * For example, "us" is the base layout, "us(intl)" is the "intl" variant of the + * layout "us". + */ +struct rxkb_layout; + +/** + * @struct rxkb_option_group + * + * Opaque struct representing an option group. Option groups divide the + * individual options into logical groups. Their main purpose is to indicate + * whether some options are mutually exclusive or not. + */ +struct rxkb_option_group; + +/** + * @struct rxkb_option + * + * Opaque struct representing an XKB option. Options are grouped inside an @ref + * rxkb_option_group. + */ +struct rxkb_option; + +/** + * + * @struct rxkb_iso639_code + * + * Opaque struct representing an ISO 639-3 code (e.g. "eng", "fra"). There + * is no guarantee that two identical ISO codes share the same struct. You + * must not rely on the pointer value of this struct. + * + * See https://iso639-3.sil.org/code_tables/639/data for a list of codes. + */ +struct rxkb_iso639_code; + +/** + * + * @struct rxkb_iso3166_code + * + * Opaque struct representing an ISO 3166 Alpha 2 code (e.g. "US", "FR"). + * There is no guarantee that two identical ISO codes share the same struct. + * You must not rely on the pointer value of this struct. + * + * See https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes for a list + * of codes. + */ +struct rxkb_iso3166_code; + +/** + * Describes the popularity of an item. Historically, some highly specialized or + * experimental definitions are excluded from the default list and shipped in + * separate files. If these extra definitions are loaded (see @ref + * RXKB_CONTEXT_LOAD_EXOTIC_RULES), the popularity of the item is set + * accordingly. + * + * If the exotic items are not loaded, all items will have the standard + * popularity. + */ +enum rxkb_popularity { + RXKB_POPULARITY_STANDARD = 1, + RXKB_POPULARITY_EXOTIC, +}; + +/** + * Flags for context creation. + */ +enum rxkb_context_flags { + RXKB_CONTEXT_NO_FLAGS = 0, + /** + * Skip the default include paths. This requires the caller to call + * rxkb_context_include_path_append() or + * rxkb_context_include_path_append_default(). + */ + RXKB_CONTEXT_NO_DEFAULT_INCLUDES = (1 << 0), + /** + * Load the extra items that are considered too exotic for the default list. + * + * For historical reasons, xkeyboard-config ships those exotic rules in a + * separate file (e.g. `evdev.extras.xml`). Where the exotic rules are + * requested, libxkbregistry will look for and load `$ruleset.extras.xml` + * in the include paths, see rxkb_context_include_path_append() for details + * on the lookup behavior. + */ + RXKB_CONTEXT_LOAD_EXOTIC_RULES = (1 << 1), +}; + +/** + * Create a new xkb registry context. + * + * The context has an initial refcount of 1. Use rxkb_context_unref() to release + * memory associated with this context. + * + * Creating a context does not parse the files yet, use + * rxkb_context_parse(). + * + * @param flags Flags affecting context behavior + * @return A new xkb registry context or NULL on failure + */ +struct rxkb_context * +rxkb_context_new(enum rxkb_context_flags flags); + +/** Specifies a logging level. */ +enum rxkb_log_level { + RXKB_LOG_LEVEL_CRITICAL = 10, /**< Log critical internal errors only. */ + RXKB_LOG_LEVEL_ERROR = 20, /**< Log all errors. */ + RXKB_LOG_LEVEL_WARNING = 30, /**< Log warnings and errors. */ + RXKB_LOG_LEVEL_INFO = 40, /**< Log information, warnings, and errors. */ + RXKB_LOG_LEVEL_DEBUG = 50 /**< Log everything. */ +}; + +/** + * Set the current logging level. + * + * @param ctx The context in which to set the logging level. + * @param level The logging level to use. Only messages from this level + * and below will be logged. + * + * The default level is RXKB_LOG_LEVEL_ERROR. The environment variable + * RXKB_LOG_LEVEL, if set at the time the context was created, overrides the + * default value. It may be specified as a level number or name. + */ +void +rxkb_context_set_log_level(struct rxkb_context *ctx, + enum rxkb_log_level level); + +/** + * Get the current logging level. + */ +enum rxkb_log_level +rxkb_context_get_log_level(struct rxkb_context *ctx); + +/** + * Set a custom function to handle logging messages. + * + * @param ctx The context in which to use the set logging function. + * @param log_fn The function that will be called for logging messages. + * Passing NULL restores the default function, which logs to stderr. + * + * By default, log messages from this library are printed to stderr. This + * function allows you to replace the default behavior with a custom + * handler. The handler is only called with messages which match the + * current logging level and verbosity settings for the context. + * level is the logging level of the message. @a format and @a args are + * the same as in the vprintf(3) function. + * + * You may use rxkb_context_set_user_data() on the context, and then call + * rxkb_context_get_user_data() from within the logging function to provide + * it with additional private context. + */ +void +rxkb_context_set_log_fn(struct rxkb_context *ctx, + void (*log_fn)(struct rxkb_context *ctx, + enum rxkb_log_level level, + const char *format, va_list args)); + + +/** + * Parse the given ruleset. This can only be called once per context and once + * parsed the data in the context is considered constant and will never + * change. + * + * This function parses all files with the given ruleset name. See + * rxkb_context_include_path_append() for details. + * + * If this function returns false, libxkbregistry failed to parse the xml files. + * This is usually caused by invalid files on the host and should be debugged by + * the host's administrator using external tools. Callers should reduce the + * include paths to known good paths and/or fall back to a default RMLVO set. + * + * If this function returns false, the context should be be considered dead and + * must be released with rxkb_context_unref(). + * + * @param ctx The xkb registry context + * @param ruleset The ruleset to parse, e.g. "evdev" + * @return true on success or false on failure + */ +bool +rxkb_context_parse(struct rxkb_context *ctx, const char *ruleset); + +/** + * Parse the default ruleset as configured at build time. See + * rxkb_context_parse() for details. + */ +bool +rxkb_context_parse_default_ruleset(struct rxkb_context *ctx); + +/** + * Increases the refcount of this object by one and returns the object. + * + * @param ctx The xkb registry context + * @return The passed in object + */ +struct rxkb_context* +rxkb_context_ref(struct rxkb_context *ctx); + +/** + * Decreases the refcount of this object by one. Where the refcount of an + * object hits zero, associated resources will be freed. + * + * @param ctx The xkb registry context + * @return always NULL + */ +struct rxkb_context* +rxkb_context_unref(struct rxkb_context *ctx); + +/** + * Assign user-specific data. libxkbregistry will not look at or modify the + * data, it will merely return the same pointer in + * rxkb_context_get_user_data(). + * + * @param ctx The xkb registry context + * @param user_data User-specific data pointer + */ +void +rxkb_context_set_user_data(struct rxkb_context *ctx, void *user_data); + +/** + * Return the pointer passed into rxkb_context_get_user_data(). + * + * @param ctx The xkb registry context + * @return User-specific data pointer + */ +void * +rxkb_context_get_user_data(struct rxkb_context *ctx); + +/** + * Append a new entry to the context's include path. + * + * The include path handling is optimized for the most common use-case: a set of + * system files that provide a complete set of MLVO and some + * custom MLVO provided by a user **in addition** to the system set. + * + * The include paths should be given so that the least complete path is + * specified first and the most complete path is appended last. For example: + * + * @code + * ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES); + * rxkb_context_include_path_append(ctx, "/home/user/.config/xkb"); + * rxkb_context_include_path_append(ctx, "/usr/share/X11/xkb"); + * rxkb_context_parse(ctx, "evdev"); + * @endcode + * + * The above example reflects the default behavior unless @ref + * RXKB_CONTEXT_NO_DEFAULT_INCLUDES is provided. + * + * Loading of the files is in **reverse order**, i.e. the last path appended is + * loaded first - in this case the ``/usr/share/X11/xkb`` path. + * Any models, layouts, variants and options defined in the "evdev" ruleset + * are loaded into the context. Then, any RMLVO found in the "evdev" ruleset of + * the user's path (``/home/user/.config/xkb`` in this example) are **appended** + * to the existing set. + * + * Note that data from previously loaded include paths is never overwritten, + * only appended to. It is not not possible to change the system-provided data, + * only to append new models, layouts, variants and options to it. + * + * In other words, to define a new variant of the "us" layout called "banana", + * the following XML is sufficient. + * + * @verbatim + * + * + * + * + * us + * + * + * + * + * banana + * English (Banana) + * + * + * + * + * + * @endverbatim + * + * The list of models, options and all other layouts (including "us" and its + * variants) is taken from the system files. The resulting list of layouts will + * thus have a "us" keyboard layout with the variant "banana" and all other + * system-provided variants (dvorak, colemak, intl, etc.) + * + * This function must be called before rxkb_context_parse() or + * rxkb_context_parse_default_ruleset(). + * + * @returns true on success, or false if the include path could not be added + * or is inaccessible. + */ +bool +rxkb_context_include_path_append(struct rxkb_context *ctx, const char *path); + +/** + * Append the default include paths to the context's include path. + * See rxkb_context_include_path_append() for details about the merge order. + * + * This function must be called before rxkb_context_parse() or + * rxkb_context_parse_default_ruleset(). + * + * @returns true on success, or false if the include path could not be added + * or is inaccessible. + */ +bool +rxkb_context_include_path_append_default(struct rxkb_context *ctx); + +/** + * Return the first model for this context. Use this to start iterating over + * the models, followed by calls to rxkb_model_next(). Models are not sorted. + * + * The refcount of the returned model is not increased. Use rxkb_model_ref() if + * you need to keep this struct outside the immediate scope. + * + * @return The first model in the model list. + */ +struct rxkb_model * +rxkb_model_first(struct rxkb_context *ctx); + +/** + * Return the next model for this context. Returns NULL when no more models + * are available. + * + * The refcount of the returned model is not increased. Use rxkb_model_ref() if + * you need to keep this struct outside the immediate scope. + * + * @return the next model or NULL at the end of the list + */ +struct rxkb_model * +rxkb_model_next(struct rxkb_model *m); + +/** + * Increase the refcount of the argument by one. + * + * @returns The argument passed in to this function. + */ +struct rxkb_model * +rxkb_model_ref(struct rxkb_model *m); + +/** + * Decrease the refcount of the argument by one. When the refcount hits zero, + * all memory associated with this struct is freed. + * + * @returns always NULL + */ +struct rxkb_model * +rxkb_model_unref(struct rxkb_model *m); + +/** + * Return the name of this model. This is the value for M in RMLVO, to be used + * with libxkbcommon. + */ +const char * +rxkb_model_get_name(struct rxkb_model *m); + +/** + * Return a human-readable description of this model. This function may return + * NULL. + */ +const char * +rxkb_model_get_description(struct rxkb_model *m); + +/** + * Return the vendor name for this model. This function may return NULL. + */ +const char * +rxkb_model_get_vendor(struct rxkb_model *m); + +/** + * Return the popularity for this model. + */ +enum rxkb_popularity +rxkb_model_get_popularity(struct rxkb_model *m); + +/** + * Return the first layout for this context. Use this to start iterating over + * the layouts, followed by calls to rxkb_layout_next(). Layouts are not sorted. + * + * The refcount of the returned layout is not increased. Use rxkb_layout_ref() if + * you need to keep this struct outside the immediate scope. + * + * @return The first layout in the layout list. + */ +struct rxkb_layout * +rxkb_layout_first(struct rxkb_context *ctx); + +/** + * Return the next layout for this context. Returns NULL when no more layouts + * are available. + * + * The refcount of the returned layout is not increased. Use rxkb_layout_ref() + * if you need to keep this struct outside the immediate scope. + * + * @return the next layout or NULL at the end of the list + */ +struct rxkb_layout * +rxkb_layout_next(struct rxkb_layout *l); + +/** + * Increase the refcount of the argument by one. + * + * @returns The argument passed in to this function. + */ +struct rxkb_layout * +rxkb_layout_ref(struct rxkb_layout *l); + +/** + * Decrease the refcount of the argument by one. When the refcount hits zero, + * all memory associated with this struct is freed. + * + * @returns always NULL + */ +struct rxkb_layout * +rxkb_layout_unref(struct rxkb_layout *l); + +/** + * Return the name of this layout. This is the value for L in RMLVO, to be used + * with libxkbcommon. + */ +const char * +rxkb_layout_get_name(struct rxkb_layout *l); + +/** + * Return the variant of this layout. This is the value for V in RMLVO, to be + * used with libxkbcommon. + * + * A variant does not stand on its own, it always depends on the base layout. + * e.g. there may be multiple variants called "intl" but there is only one + * "us(intl)". + * + * Where the variant is NULL, the layout is the base layout (e.g. "us"). + */ +const char * +rxkb_layout_get_variant(struct rxkb_layout *l); + +/** + * Return a short (one-word) description of this layout. This function may + * return NULL. + */ +const char * +rxkb_layout_get_brief(struct rxkb_layout *l); + +/** + * Return a human-readable description of this layout. This function may return + * NULL. + */ +const char * +rxkb_layout_get_description(struct rxkb_layout *l); + +/** + * Return the popularity for this layout. + */ +enum rxkb_popularity +rxkb_layout_get_popularity(struct rxkb_layout *l); + +/** + * Return the first option group for this context. Use this to start iterating + * over the option groups, followed by calls to rxkb_option_group_next(). + * Option groups are not sorted. + * + * The refcount of the returned option group is not increased. Use + * rxkb_option_group_ref() if you need to keep this struct outside the immediate + * scope. + * + * @return The first option group in the option group list. + */ +struct rxkb_option_group * +rxkb_option_group_first(struct rxkb_context *ctx); + +/** + * Return the next option group for this context. Returns NULL when no more + * option groups are available. + * + * The refcount of the returned option group is not increased. Use + * rxkb_option_group_ref() if you need to keep this struct outside the immediate + * scope. + * + * @return the next option group or NULL at the end of the list + */ +struct rxkb_option_group * +rxkb_option_group_next(struct rxkb_option_group *g); + +/** + * Increase the refcount of the argument by one. + * + * @returns The argument passed in to this function. + */ +struct rxkb_option_group * +rxkb_option_group_ref(struct rxkb_option_group *g); + +/** + * Decrease the refcount of the argument by one. When the refcount hits zero, + * all memory associated with this struct is freed. + * + * @returns always NULL + */ +struct rxkb_option_group * +rxkb_option_group_unref(struct rxkb_option_group *g); + +/** + * Return the name of this option group. This is **not** the value for O in + * RMLVO, the name can be used for internal sorting in the caller. This function + * may return NULL. + */ +const char * +rxkb_option_group_get_name(struct rxkb_option_group *m); + +/** + * Return a human-readable description of this option group. This function may + * return NULL. + */ +const char * +rxkb_option_group_get_description(struct rxkb_option_group *m); + +/** + * @return true if multiple options within this option group can be selected + * simultaneously, false if all options within this option group + * are mutually exclusive. + */ +bool +rxkb_option_group_allows_multiple(struct rxkb_option_group *g); + +/** + * Return the popularity for this option group. + */ +enum rxkb_popularity +rxkb_option_group_get_popularity(struct rxkb_option_group *g); + +/** + * Return the first option for this option group. Use this to start iterating + * over the options, followed by calls to rxkb_option_next(). Options are not + * sorted. + * + * The refcount of the returned option is not increased. Use rxkb_option_ref() + * if you need to keep this struct outside the immediate scope. + * + * @return The first option in the option list. + */ +struct rxkb_option * +rxkb_option_first(struct rxkb_option_group *group); + +/** + * Return the next option for this option group. Returns NULL when no more + * options are available. + * + * The refcount of the returned options is not increased. Use rxkb_option_ref() + * if you need to keep this struct outside the immediate scope. + * + * @returns The next option or NULL at the end of the list + */ +struct rxkb_option * +rxkb_option_next(struct rxkb_option *o); + +/** + * Increase the refcount of the argument by one. + * + * @returns The argument passed in to this function. + */ +struct rxkb_option * +rxkb_option_ref(struct rxkb_option *o); + +/** + * Decrease the refcount of the argument by one. When the refcount hits zero, + * all memory associated with this struct is freed. + * + * @returns always NULL + */ +struct rxkb_option * +rxkb_option_unref(struct rxkb_option *o); + +/** + * Return the name of this option. This is the value for O in RMLVO, to be used + * with libxkbcommon. + */ +const char * +rxkb_option_get_name(struct rxkb_option *o); + +/** + * Return a short (one-word) description of this option. This function may + * return NULL. + */ +const char * +rxkb_option_get_brief(struct rxkb_option *o); + +/** + * Return a human-readable description of this option. This function may return + * NULL. + */ +const char * +rxkb_option_get_description(struct rxkb_option *o); + +/** + * Return the popularity for this option. + */ +enum rxkb_popularity +rxkb_option_get_popularity(struct rxkb_option *o); + +/** + * Increase the refcount of the argument by one. + * + * @returns The argument passed in to this function. + */ +struct rxkb_iso639_code * +rxkb_iso639_code_ref(struct rxkb_iso639_code *iso639); + +/** + * Decrease the refcount of the argument by one. When the refcount hits zero, + * all memory associated with this struct is freed. + * + * @returns always NULL + */ +struct rxkb_iso639_code * +rxkb_iso639_code_unref(struct rxkb_iso639_code *iso639); + +/** + * Return the ISO 639-3 code for this code (e.g. "eng", "fra"). + */ +const char * +rxkb_iso639_code_get_code(struct rxkb_iso639_code *iso639); + +/** + * Return the first ISO 639 for this layout. Use this to start iterating over + * the codes, followed by calls to rxkb_iso639_code_next(). Codes are not + * sorted. + * + * The refcount of the returned code is not increased. Use rxkb_iso639_code_ref() + * if you need to keep this struct outside the immediate scope. + * + * @return The first code in the code list. + */ +struct rxkb_iso639_code * +rxkb_layout_get_iso639_first(struct rxkb_layout *layout); + +/** + * Return the next code in the list. Returns NULL when no more codes + * are available. + * + * The refcount of the returned codes is not increased. Use + * rxkb_iso639_code_ref() if you need to keep this struct outside the immediate + * scope. + * + * @returns The next code or NULL at the end of the list + */ +struct rxkb_iso639_code * +rxkb_iso639_code_next(struct rxkb_iso639_code *iso639); + +/** + * Increase the refcount of the argument by one. + * + * @returns The argument passed in to this function. + */ +struct rxkb_iso3166_code * +rxkb_iso3166_code_ref(struct rxkb_iso3166_code *iso3166); + +/** + * Decrease the refcount of the argument by one. When the refcount hits zero, + * all memory associated with this struct is freed. + * + * @returns always NULL + */ +struct rxkb_iso3166_code * +rxkb_iso3166_code_unref(struct rxkb_iso3166_code *iso3166); + +/** + * Return the ISO 3166 Alpha 2 code for this code (e.g. "US", "FR"). + */ +const char * +rxkb_iso3166_code_get_code(struct rxkb_iso3166_code *iso3166); + +/** + * Return the first ISO 3166 for this layout. Use this to start iterating over + * the codes, followed by calls to rxkb_iso3166_code_next(). Codes are not + * sorted. + * + * The refcount of the returned code is not increased. Use + * rxkb_iso3166_code_ref() if you need to keep this struct outside the immediate + * scope. + * + * @return The first code in the code list. + */ +struct rxkb_iso3166_code * +rxkb_layout_get_iso3166_first(struct rxkb_layout *layout); + +/** + * Return the next code in the list. Returns NULL when no more codes + * are available. + * + * The refcount of the returned codes is not increased. Use + * rxkb_iso3166_code_ref() if you need to keep this struct outside the immediate + * scope. + * + * @returns The next code or NULL at the end of the list + */ +struct rxkb_iso3166_code * +rxkb_iso3166_code_next(struct rxkb_iso3166_code *iso3166); + +/** @} */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* _XKBREGISTRY_H_ */ diff --git a/xkbregistry.map b/xkbregistry.map new file mode 100644 index 0000000..6e18b9c --- /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: + *; +};