Add libxkbregistry to query available RMLVO

This library is the replacement for clients parsing evdev.xml directly.
Instead, they should use the API here so that in the future we may even
be able to swap evdev.xml for a more suitable data format.

The library parses through evdev.xml (using libxml2) and - if requested -
through evdev.extras.xml as well. The merge approach is optimised for
the default case where we have a system-installed rules XML and another file in
$XDG_CONFIG_DIR that adds a few entries.  We load the system file first, then
append any custom ones to that. It's not possible to overwrite the MLVO list
provided by the system files - if you want to do that, get the change upstream.

XML validation is handled through the DTD itself which means we only need to
check for a nonempty name, everything else the DTD validation should complain
about.

The logging system is effectively identical to xkbcommon.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
master
Peter Hutterer 2020-05-12 14:09:50 +10:00
parent fe88613382
commit afb26e7df9
15 changed files with 3435 additions and 7 deletions

View File

@ -53,7 +53,7 @@ jobs:
HOMEBREW_NO_INSTALL_CLEANUP: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1
- name: Setup - name: Setup
run: | 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 - name: Build
run: | run: |
PATH="/usr/local/opt/bison/bin:${PATH}" meson compile -C build PATH="/usr/local/opt/bison/bin:${PATH}" meson compile -C build
@ -76,7 +76,7 @@ jobs:
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 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: env:
CC: cl CC: cl
- name: Build - name: Build

11
NEWS
View File

@ -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 libxkbcommon 0.10.0 - 2020-01-18
=================== ===================

View File

@ -1,5 +1,6 @@
libxkbcommon consists of two shared libraries, libxkbcommon (the main libxkbcommon consists of three shared libraries, libxkbcommon (the main
library) and libxkbcommon-x11 (an addon library for XCB clients). library), libxkbcommon-x11 (an addon library for XCB clients) and libxkbregistry
(a library to list available RMLVO options).
The files for libxkbcommon-x11 are: The files for libxkbcommon-x11 are:
libxkbcommon-x11.a libxkbcommon-x11.so* xkbcommon/xkbcommon-x11.h 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 libxkbcommon-x11 can be disabled with -Denable-x11=false (see
`meson configure build` for other options/variables). `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: Dependencies for libxkbcommon:
- C compiler, meson, pkg-config, libc, bash, grep, sed. - C compiler, meson, pkg-config, libc, bash, grep, sed.
- (build) bison (preferred), win_bison or byacc>=20141006. - (build) bison (preferred), win_bison or byacc>=20141006.
byacc must be configured with --enable-btyacc. 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 During build, for automatically detecting the value of
-Dxkb-config-root instead of guessing (/usr/share/X11/xkb). -Dxkb-config-root instead of guessing (/usr/share/X11/xkb).
During runtime, not strictly needed, but most users of the library During runtime, not strictly needed, but most users of the library
@ -41,6 +49,16 @@ Dependencies for libxkbcommon-x11 tests:
- xkbcomp, Xvfb. - xkbcomp, Xvfb.
If they are not available, the relevant tests are skipped. 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: Dependencies for Wayland tests:
- wayland-client>=1.2.0, wayland-scanner, wayland-protocols>=1.0. - wayland-client>=1.2.0, wayland-scanner, wayland-protocols>=1.0.
To disable, use -Denable-wayland=false. 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, that libxkbcommon and libxkbcommon-x11 be split into separate packages,
such that the main library does not depend on libxcb. This avoids a such that the main library does not depend on libxcb. This avoids a
transitive dependency of Wayland clients on X libraries. 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.

View File

@ -3,7 +3,7 @@
libxkbcommon is a keyboard keymap compiler and support library which libxkbcommon is a keyboard keymap compiler and support library which
processes a reduced subset of keymaps as defined by the XKB (X Keyboard processes a reduced subset of keymaps as defined by the XKB (X Keyboard
Extension) specification. It also contains a module for handling Compose 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 ## Quick Guide

View File

@ -154,3 +154,66 @@ xkb_symbols "baz" {
With these in place, a user may select any layout/variant together with With these in place, a user may select any layout/variant together with
the "custom:foo" and/or "custom:baz" options. 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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xkbConfigRegistry SYSTEM "xkb.dtd">
<xkbConfigRegistry version="1.1">
<layoutList>
<layout>
<configItem>
<name>banana</name>
<shortDescription>ban</shortDescription>
<description>Banana</description>
</configItem>
<variantList>
<variant>
<configItem>
<name>orange</name>
<shortDescription>or</shortDescription>
<description>Orange (Banana)</description>
</variant>
</variantList>
</layoutList>
<optionList>
<group allowMultipleSelection="true">
<configItem>
<name>custom</name>
<description>Custom options</description>
</configItem>
<option>
<configItem>
<name>custom:foo</name>
<description>Map Tilde to nothing</description>
</configItem>
</option>
<option>
<configItem>
<name>custom:baz</name>
<description>Map Z to K</description>
</configItem>
</option>
</group>
</optionList>
</xkbConfigRegistry>
```
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 `<datadir>/X11/xkb/rules/xkb.dtd`
and the system-provided XML files in `<datadir>/X11/xkb/rulies/xkb.dtd`.

View File

@ -296,6 +296,53 @@ You can disable X11 support with -Denable-x11=false.''')
) )
endif 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 # Tests
test_env = environment() test_env = environment()
@ -440,7 +487,15 @@ if get_option('enable-x11')
# See: https://github.com/xkbcommon/libxkbcommon/issues/30 # See: https://github.com/xkbcommon/libxkbcommon/issues/30
executable('test-x11comp', 'test/x11comp.c', dependencies: x11_test_dep) executable('test-x11comp', 'test/x11comp.c', dependencies: x11_test_dep)
endif 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. # Fuzzing target programs.
executable('fuzz-keymap', 'fuzz/keymap/target.c', dependencies: test_dep) 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-names.h',
'xkbcommon/xkbcommon-x11.h', 'xkbcommon/xkbcommon-x11.h',
'xkbcommon/xkbcommon-compose.h', 'xkbcommon/xkbcommon-compose.h',
'xkbcommon/xkbregistry.h',
] ]
doxygen_data = configuration_data() doxygen_data = configuration_data()
doxygen_data.set('PACKAGE_NAME', meson.project_name()) doxygen_data.set('PACKAGE_NAME', meson.project_name())

View File

@ -56,3 +56,9 @@ option(
value: true, value: true,
description: 'Enable support for Wayland utility programs', description: 'Enable support for Wayland utility programs',
) )
option(
'enable-xkbregistry',
type: 'boolean',
value: true,
description: 'Enable building libxkbregistry',
)

View File

@ -206,4 +206,7 @@ darray_next_alloc(unsigned alloc, unsigned need, unsigned itemSize)
(idx) < (arr).size; \ (idx) < (arr).size; \
(idx)++, (val)++) (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 */ #endif /* CCAN_DARRAY_H */

1193
src/registry.c Normal file

File diff suppressed because it is too large Load Diff

94
src/util-list.c Normal file
View File

@ -0,0 +1,94 @@
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2013-2015 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <assert.h>
#include <stddef.h>
#include <stdbool.h>
#include "util-list.h"
void
list_init(struct list *list)
{
list->prev = list;
list->next = list;
}
void
list_insert(struct list *list, struct list *elm)
{
assert((list->next != NULL && list->prev != NULL) ||
!"list->next|prev is NULL, possibly missing list_init()");
assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) ||
!"elm->next|prev is not NULL, list node used twice?");
elm->prev = list;
elm->next = list->next;
list->next = elm;
elm->next->prev = elm;
}
void
list_append(struct list *list, struct list *elm)
{
assert((list->next != NULL && list->prev != NULL) ||
!"list->next|prev is NULL, possibly missing list_init()");
assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) ||
!"elm->next|prev is not NULL, list node used twice?");
elm->next = list;
elm->prev = list->prev;
list->prev = elm;
elm->prev->next = elm;
}
void
list_remove(struct list *elm)
{
assert((elm->next != NULL && elm->prev != NULL) ||
!"list->next|prev is NULL, possibly missing list_init()");
elm->prev->next = elm->next;
elm->next->prev = elm->prev;
elm->next = NULL;
elm->prev = NULL;
}
bool
list_empty(const struct list *list)
{
assert((list->next != NULL && list->prev != NULL) ||
!"list->next|prev is NULL, possibly missing list_init()");
return list->next == list;
}
bool
list_is_last(const struct list *list, const struct list *elm)
{
return elm->next == list;
}

71
src/util-list.h Normal file
View File

@ -0,0 +1,71 @@
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2013-2015 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "config.h"
#include <stdbool.h>
#include <stddef.h>
/*
* This list data structure is a verbatim copy from wayland-util.h from the
* Wayland project; except that wl_ prefix has been removed.
*/
struct list {
struct list *prev;
struct list *next;
};
void list_init(struct list *list);
void list_insert(struct list *list, struct list *elm);
void list_append(struct list *list, struct list *elm);
void list_remove(struct list *elm);
bool list_empty(const struct list *list);
bool list_is_last(const struct list *list, const struct list *elm);
#define container_of(ptr, type, member) \
(__typeof__(type) *)((char *)(ptr) - \
offsetof(__typeof__(type), member))
#define list_first_entry(head, pos, member) \
container_of((head)->next, __typeof__(*pos), member)
#define list_last_entry(head, pos, member) \
container_of((head)->prev, __typeof__(*pos), member)
#define list_for_each(pos, head, member) \
for (pos = 0, pos = list_first_entry(head, pos, member); \
&pos->member != (head); \
pos = list_first_entry(&pos->member, pos, member))
#define list_for_each_safe(pos, tmp, head, member) \
for (pos = 0, tmp = 0, \
pos = list_first_entry(head, pos, member), \
tmp = list_first_entry(&pos->member, tmp, member); \
&pos->member != (head); \
pos = tmp, \
tmp = list_first_entry(&pos->member, tmp, member))

843
test/registry.c Normal file
View File

@ -0,0 +1,843 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "xkbcommon/xkbregistry.h"
#include "utils.h"
#define NO_VARIANT NULL
enum {
MODEL = 78,
LAYOUT,
VARIANT,
OPTION,
};
struct test_model {
const char *name; /* required */
const char *vendor;
const char *description;
};
struct test_layout {
const char *name; /* required */
const char *variant;
const char *brief;
const char *description;
};
struct test_option {
const char *name;
const char *description;
};
struct test_option_group {
const char *name;
const char *description;
bool allow_multiple_selection;
struct test_option options[10];
};
static void
fprint_config_item(FILE *fp,
const char *name,
const char *vendor,
const char *brief,
const char *description)
{
fprintf(fp, " <configItem>\n"
" <name>%s</name>\n", name);
if (brief)
fprintf(fp, " <shortDescription>%s</shortDescription>\n", brief);
if (description)
fprintf(fp, " <description>%s</description>\n", description);
if (vendor)
fprintf(fp, " <vendor>%s</vendor>\n", vendor);
fprintf(fp, " </configItem>\n");
}
/**
* Create a directory populated with a rules/<ruleset>.xml that contains the
* given items.
*
* @return the XKB base directory
*/
static char *
test_create_rules(const char *ruleset,
const struct test_model *test_models,
const struct test_layout *test_layouts,
const struct test_option_group *test_groups)
{
static int iteration;
char *tmpdir;
char buf[PATH_MAX];
int rc;
FILE *fp;
rc = asprintf(&tmpdir, "/tmp/%s.%d.XXXXXX", ruleset, iteration++);
assert(rc > 0);
assert(mkdtemp(tmpdir) == tmpdir);
rc = snprintf_safe(buf, sizeof(buf), "%s/rules", tmpdir);
assert(rc);
rc = mkdir(buf, 0777);
assert(rc == 0);
rc = snprintf_safe(buf, sizeof(buf), "%s/rules/%s.xml", tmpdir, ruleset);
assert(rc);
fp = fopen(buf, "w");
assert(fp);
fprintf(fp,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE xkbConfigRegistry SYSTEM \"xkb.dtd\">\n"
"<xkbConfigRegistry version=\"1.1\">\n");
if (test_models) {
fprintf(fp, "<modelList>\n");
for (const struct test_model *m = test_models; m->name; m++) {
fprintf(fp, "<model>\n");
fprint_config_item(fp, m->name, m->vendor, NULL, m->description);
fprintf(fp, "</model>\n");
}
fprintf(fp, "</modelList>\n");
}
if (test_layouts) {
const struct test_layout *l, *next;
fprintf(fp, "<layoutList>\n");
l = test_layouts;
next = l + 1;
assert(l->variant == NULL);
while (l->name) {
fprintf(fp, "<layout>\n");
fprint_config_item(fp, l->name, NULL, l->brief, l->description);
if (next->name && streq(next->name, l->name)) {
fprintf(fp, "<variantList>\n");
do {
fprintf(fp, "<variant>\n");
fprint_config_item(fp, next->variant, NULL, next->brief,
next->description);
fprintf(fp, "</variant>\n");
l = next;
next++;
} while (next->name && streq(next->name, l->name));
fprintf(fp, "</variantList>\n");
}
fprintf(fp, "</layout>\n");
l++;
}
fprintf(fp, "</layoutList>\n");
}
if (test_groups) {
fprintf(fp, "<optionList>\n");
for (const struct test_option_group *g = test_groups; g->name; g++) {
fprintf(fp, "<group allowMultipleSelection=\"%s\">\n",
g->allow_multiple_selection ? "true" : "false");
fprint_config_item(fp, g->name, NULL, NULL, g->description);
for (const struct test_option *o = g->options; o->name; o++) {
fprintf(fp, " <option>\n");
fprint_config_item(fp, o->name, NULL, NULL, o->description);
fprintf(fp, "</option>\n");
}
fprintf(fp, "</group>\n");
}
fprintf(fp, "</optionList>\n");
}
fprintf(fp, "</xkbConfigRegistry>\n");
fclose(fp);
return tmpdir;
}
static void
test_remove_rules(char *basedir, const char *ruleset)
{
char path[PATH_MAX];
int rc;
rc = snprintf_safe(path, sizeof(path), "%s/rules/%s.xml", basedir,
ruleset);
assert(rc);
unlink(path);
rc = snprintf_safe(path, sizeof(path), "%s/xkb/rules", basedir);
assert(rc);
rmdir(path);
rmdir(basedir);
free(basedir);
}
static struct rxkb_context *
test_setup_context_for(const char *ruleset,
struct test_model *system_models,
struct test_model *user_models,
struct test_layout *system_layouts,
struct test_layout *user_layouts,
struct test_option_group *system_groups,
struct test_option_group *user_groups)
{
char *sysdir = NULL, *userdir = NULL;
struct rxkb_context *ctx;
sysdir = test_create_rules(ruleset, system_models, system_layouts,
system_groups);
if (user_models || user_layouts || user_groups)
userdir = test_create_rules(ruleset, user_models, user_layouts,
user_groups);
ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
assert(ctx);
if (userdir)
assert(rxkb_context_include_path_append(ctx, userdir));
assert(rxkb_context_include_path_append(ctx, sysdir));
assert(rxkb_context_parse(ctx, ruleset));
test_remove_rules(sysdir, ruleset);
if (userdir)
test_remove_rules(userdir, ruleset);
return ctx;
}
static struct rxkb_context *
test_setup_context(struct test_model *system_models,
struct test_model *user_models,
struct test_layout *system_layouts,
struct test_layout *user_layouts,
struct test_option_group *system_groups,
struct test_option_group *user_groups)
{
const char *ruleset = "xkbtests";
return test_setup_context_for(ruleset, system_models,
user_models, system_layouts,
user_layouts, system_groups,
user_groups);
}
static struct rxkb_model *
fetch_model(struct rxkb_context *ctx, const char *model)
{
struct rxkb_model *m = rxkb_model_first(ctx);
while (m) {
if (streq(rxkb_model_get_name(m), model))
return rxkb_model_ref(m);
m = rxkb_model_next(m);
}
return NULL;
}
static bool
find_model(struct rxkb_context *ctx, const char *model)
{
struct rxkb_model *m = fetch_model(ctx, model);
rxkb_model_unref(m);
return m != NULL;
}
static bool
find_models(struct rxkb_context *ctx, ...)
{
va_list args;
const char *name;
int idx = 0;
va_start(args, ctx);
name = va_arg(args, const char *);
while(name) {
assert(++idx < 20); /* safety guard */
if (!find_model(ctx, name))
return false;
name = va_arg(args, const char *);
};
va_end(args);
return true;
}
static struct rxkb_layout *
fetch_layout(struct rxkb_context *ctx, const char *layout, const char *variant)
{
struct rxkb_layout *l = rxkb_layout_first(ctx);
while (l) {
const char *v = rxkb_layout_get_variant(l);
if (streq(rxkb_layout_get_name(l), layout) &&
((v == NULL && variant == NULL) ||
(v != NULL && variant != NULL && streq(v, variant))))
return rxkb_layout_ref(l);
l = rxkb_layout_next(l);
}
return NULL;
}
static bool
find_layout(struct rxkb_context *ctx, const char *layout, const char *variant)
{
struct rxkb_layout *l = fetch_layout(ctx, layout, variant);
rxkb_layout_unref(l);
return l != NULL;
}
static bool
find_layouts(struct rxkb_context *ctx, ...)
{
va_list args;
const char *name, *variant;
int idx = 0;
va_start(args, ctx);
name = va_arg(args, const char *);
variant = va_arg(args, const char *);
while(name) {
assert(++idx < 20); /* safety guard */
if (!find_layout(ctx, name, variant))
return false;
name = va_arg(args, const char *);
if (name)
variant = va_arg(args, const char *);
};
va_end(args);
return true;
}
static struct rxkb_option_group *
fetch_option_group(struct rxkb_context *ctx, const char *grp)
{
struct rxkb_option_group *g = rxkb_option_group_first(ctx);
while (g) {
if (streq(grp, rxkb_option_group_get_name(g)))
return rxkb_option_group_ref(g);
g = rxkb_option_group_next(g);
}
return NULL;
}
static inline bool
find_option_group(struct rxkb_context *ctx, const char *grp)
{
struct rxkb_option_group *g = fetch_option_group(ctx, grp);
rxkb_option_group_unref(g);
return g != NULL;
}
static struct rxkb_option *
fetch_option(struct rxkb_context *ctx, const char *grp, const char *opt)
{
struct rxkb_option_group *g = rxkb_option_group_first(ctx);
while (g) {
if (streq(grp, rxkb_option_group_get_name(g))) {
struct rxkb_option *o = rxkb_option_first(g);
while (o) {
if (streq(opt, rxkb_option_get_name(o)))
return rxkb_option_ref(o);
o = rxkb_option_next(o);
}
}
g = rxkb_option_group_next(g);
}
return NULL;
}
static bool
find_option(struct rxkb_context *ctx, const char *grp, const char *opt)
{
struct rxkb_option *o = fetch_option(ctx, grp, opt);
rxkb_option_unref(o);
return o != NULL;
}
static bool
find_options(struct rxkb_context *ctx, ...)
{
va_list args;
const char *grp, *opt;
int idx = 0;
va_start(args, ctx);
grp = va_arg(args, const char *);
opt = va_arg(args, const char *);
while(grp) {
assert(++idx < 20); /* safety guard */
if (!find_option(ctx, grp, opt))
return false;
grp = va_arg(args, const char *);
if (grp)
opt = va_arg(args, const char *);
};
va_end(args);
return true;
}
static bool
cmp_models(struct test_model *tm, struct rxkb_model *m)
{
if (!tm || !m)
return false;
if (!streq(tm->name, rxkb_model_get_name(m)))
return false;
if (!streq_null(tm->vendor, rxkb_model_get_vendor(m)))
return false;
if (!streq_null(tm->description, rxkb_model_get_description(m)))
return false;
return true;
}
static bool
cmp_layouts(struct test_layout *tl, struct rxkb_layout *l)
{
if (!tl || !l)
return false;
if (!streq(tl->name, rxkb_layout_get_name(l)))
return false;
if (!streq_null(tl->variant, rxkb_layout_get_variant(l)))
return false;
if (!streq_null(tl->brief, rxkb_layout_get_brief(l)))
return false;
if (!streq_null(tl->description, rxkb_layout_get_description(l)))
return false;
return true;
}
static bool
cmp_options(struct test_option *to, struct rxkb_option *o)
{
if (!to || !o)
return false;
if (!streq(to->name, rxkb_option_get_name(o)))
return false;
if (!streq_null(to->description, rxkb_option_get_description(o)))
return false;
return true;
}
enum cmp_type {
CMP_EXACT,
CMP_MATCHING_ONLY,
};
static bool
cmp_option_groups(struct test_option_group *tg, struct rxkb_option_group *g,
enum cmp_type cmp)
{
struct rxkb_option *o;
struct test_option *to;
if (!tg || !g)
return false;
if (!streq(tg->name, rxkb_option_group_get_name(g)))
return false;
if (!streq_null(tg->description, rxkb_option_group_get_description(g)))
return false;
if (tg->allow_multiple_selection != rxkb_option_group_allows_multiple(g))
return false;
to = tg->options;
o = rxkb_option_first(g);
while (o && to->name) {
if (!cmp_options(to, o))
return false;
to++;
o = rxkb_option_next(o);
}
if (cmp == CMP_EXACT && (o || to->name))
return false;
return true;
}
static void
test_load_basic(void)
{
struct test_model system_models[] = {
{"m1"},
{"m2"},
{NULL},
};
struct test_layout system_layouts[] = {
{"l1"},
{"l1", "v1"},
{NULL},
};
struct test_option_group system_groups[] = {
{"grp1", NULL, true,
{ {"grp1:1"}, {"grp1:2"} } },
{"grp2", NULL, false,
{ {"grp2:1"}, {"grp2:2"} } },
{ NULL },
};
struct rxkb_context *ctx;
ctx = test_setup_context(system_models, NULL,
system_layouts, NULL,
system_groups, NULL);
assert(find_models(ctx, "m1", "m2", NULL));
assert(find_layouts(ctx, "l1", NO_VARIANT,
"l1", "v1", NULL));
assert(find_options(ctx, "grp1", "grp1:1",
"grp1", "grp1:2",
"grp2", "grp2:1",
"grp2", "grp2:2", NULL));
rxkb_context_unref(ctx);
}
static void
test_load_full(void)
{
struct test_model system_models[] = {
{"m1", "vendor1", "desc1"},
{"m2", "vendor2", "desc2"},
{NULL},
};
struct test_layout system_layouts[] = {
{"l1", NO_VARIANT, "lbrief1", "ldesc1"},
{"l1", "v1", "vbrief1", "vdesc1"},
{NULL},
};
struct test_option_group system_groups[] = {
{"grp1", "gdesc1", true,
{ {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } },
{"grp2", "gdesc2", false,
{ {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } },
{ NULL },
};
struct rxkb_context *ctx;
struct rxkb_model *m;
struct rxkb_layout *l;
struct rxkb_option_group *g;
ctx = test_setup_context(system_models, NULL,
system_layouts, NULL,
system_groups, NULL);
m = fetch_model(ctx, "m1");
assert(cmp_models(&system_models[0], m));
rxkb_model_unref(m);
m = fetch_model(ctx, "m2");
assert(cmp_models(&system_models[1], m));
rxkb_model_unref(m);
l = fetch_layout(ctx, "l1", NO_VARIANT);
assert(cmp_layouts(&system_layouts[0], l));
rxkb_layout_unref(l);
l = fetch_layout(ctx, "l1", "v1");
assert(cmp_layouts(&system_layouts[1], l));
rxkb_layout_unref(l);
g = fetch_option_group(ctx, "grp1");
assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT));
rxkb_option_group_unref(g);
g = fetch_option_group(ctx, "grp2");
assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT));
rxkb_option_group_unref(g);
rxkb_context_unref(ctx);
}
static void
test_popularity(void)
{
struct test_layout system_layouts[] = {
{"l1", NO_VARIANT },
{"l1", "v1" },
{NULL},
};
struct rxkb_context *ctx;
struct rxkb_layout *l;
const char *ruleset = "xkbtests.extras";
char *dir = NULL;
dir = test_create_rules(ruleset, NULL, system_layouts, NULL);
ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES |
RXKB_CONTEXT_LOAD_EXOTIC_RULES);
assert(ctx);
assert(rxkb_context_include_path_append(ctx, dir));
/* Hack: rulest above generates xkbtests.extras.xml, loading "xkbtests"
* means the extras file counts as exotic */
assert(rxkb_context_parse(ctx, "xkbtests"));
l = fetch_layout(ctx, "l1", NO_VARIANT);
assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC);
rxkb_layout_unref(l);
l = fetch_layout(ctx, "l1", "v1");
assert(rxkb_layout_get_popularity(l) == RXKB_POPULARITY_EXOTIC);
rxkb_layout_unref(l);
test_remove_rules(dir, ruleset);
rxkb_context_unref(ctx);
}
static void
test_load_merge(void)
{
struct test_model system_models[] = {
{"m1", "vendor1", "desc1"},
{"m2", "vendor2", "desc2"},
{NULL},
};
struct test_model user_models[] = {
{"m3", "vendor3", "desc3"},
{"m4", "vendor4", "desc4"},
{NULL},
};
struct test_layout system_layouts[] = {
{"l1", NO_VARIANT, "lbrief1", "ldesc1"},
{"l1", "v1", "vbrief1", "vdesc1"},
{NULL},
};
struct test_layout user_layouts[] = {
{"l2", NO_VARIANT, "lbrief2", "ldesc2"},
{"l2", "v2", "vbrief2", "vdesc2"},
{NULL},
};
struct test_option_group system_groups[] = {
{"grp1", NULL, true,
{ {"grp1:1"}, {"grp1:2"} } },
{"grp2", NULL, false,
{ {"grp2:1"}, {"grp2:2"} } },
{ NULL },
};
struct test_option_group user_groups[] = {
{"grp3", NULL, true,
{ {"grp3:1"}, {"grp3:2"} } },
{"grp4", NULL, false,
{ {"grp4:1"}, {"grp4:2"} } },
{ NULL },
};
struct rxkb_context *ctx;
struct rxkb_model *m;
struct rxkb_layout *l;
struct rxkb_option_group *g;
ctx = test_setup_context(system_models, user_models,
system_layouts, user_layouts,
system_groups, user_groups);
assert(find_models(ctx, "m1", "m2", "m3", "m4", NULL));
assert(find_layouts(ctx, "l1", NO_VARIANT,
"l1", "v1",
"l2", NO_VARIANT,
"l2", "v2", NULL));
m = fetch_model(ctx, "m1");
assert(cmp_models(&system_models[0], m));
rxkb_model_unref(m);
m = fetch_model(ctx, "m2");
assert(cmp_models(&system_models[1], m));
rxkb_model_unref(m);
m = fetch_model(ctx, "m3");
assert(cmp_models(&user_models[0], m));
rxkb_model_unref(m);
m = fetch_model(ctx, "m4");
assert(cmp_models(&user_models[1], m));
rxkb_model_unref(m);
l = fetch_layout(ctx, "l1", NO_VARIANT);
assert(cmp_layouts(&system_layouts[0], l));
rxkb_layout_unref(l);
l = fetch_layout(ctx, "l1", "v1");
assert(cmp_layouts(&system_layouts[1], l));
rxkb_layout_unref(l);
l = fetch_layout(ctx, "l2", NO_VARIANT);
assert(cmp_layouts(&user_layouts[0], l));
rxkb_layout_unref(l);
l = fetch_layout(ctx, "l2", "v2");
assert(cmp_layouts(&user_layouts[1], l));
rxkb_layout_unref(l);
g = fetch_option_group(ctx, "grp1");
assert(cmp_option_groups(&system_groups[0], g, CMP_EXACT));
rxkb_option_group_unref(g);
g = fetch_option_group(ctx, "grp2");
assert(cmp_option_groups(&system_groups[1], g, CMP_EXACT));
rxkb_option_group_unref(g);
g = fetch_option_group(ctx, "grp3");
assert(cmp_option_groups(&user_groups[0], g, CMP_EXACT));
rxkb_option_group_unref(g);
g = fetch_option_group(ctx, "grp4");
assert(cmp_option_groups(&user_groups[1], g, CMP_EXACT));
rxkb_option_group_unref(g);
rxkb_context_unref(ctx);
}
static void
test_load_merge_no_overwrite(void)
{
struct test_model system_models[] = {
{"m1", "vendor1", "desc1"},
{"m2", "vendor2", "desc2"},
{NULL},
};
struct test_model user_models[] = {
{"m1", "vendor3", "desc3"}, /* must not overwrite */
{"m4", "vendor4", "desc4"},
{NULL},
};
struct test_layout system_layouts[] = {
{"l1", NO_VARIANT, "lbrief1", "ldesc1"},
{"l1", "v1", "vbrief1", "vdesc1"},
{NULL},
};
struct test_layout user_layouts[] = {
{"l2", NO_VARIANT, "lbrief2", "ldesc2"},
{"l2", "v2", "vbrief2", "vdesc2"},
{"l1", NO_VARIANT, "lbrief3", "ldesc3"}, /* must not overwrite */
{"l1", "v2", "vbrief3", "vdesc3"}, /* must not overwrite */
{NULL},
};
struct test_option_group system_groups[] = {
{"grp1", "gdesc1", true,
{ {"grp1:1", "odesc11"}, {"grp1:2", "odesc12"} } },
{"grp2", "gdesc2", false,
{ {"grp2:1", "odesc21"}, {"grp2:2", "odesc22"} } },
{ NULL },
};
struct test_option_group user_groups[] = {
{"grp1", "XXXXX", false, /* must not overwrite */
{ {"grp1:1", "YYYYYYY"}, /* must not overwrite */
{"grp1:3", "ZZZZZZ"} } }, /* append */
{"grp4", "gdesc4", false,
{ {"grp4:1", "odesc41"}, {"grp4:2", "odesc42"} } },
{ NULL },
};
struct rxkb_context *ctx;
struct rxkb_model *m;
struct rxkb_layout *l;
struct rxkb_option_group *g;
ctx = test_setup_context(system_models, user_models,
system_layouts, user_layouts,
system_groups, user_groups);
m = fetch_model(ctx, "m1");
assert(cmp_models(&system_models[0], m));
rxkb_model_unref(m);
l = fetch_layout(ctx, "l1", NO_VARIANT);
assert(cmp_layouts(&system_layouts[0], l));
rxkb_layout_unref(l);
l = fetch_layout(ctx, "l1", "v1");
assert(cmp_layouts(&system_layouts[1], l));
rxkb_layout_unref(l);
assert(find_option(ctx, "grp1", "grp1:3"));
g = fetch_option_group(ctx, "grp1");
assert(cmp_option_groups(&system_groups[0], g, CMP_MATCHING_ONLY));
rxkb_option_group_unref(g);
rxkb_context_unref(ctx);
}
static void
test_no_include_paths(void)
{
struct rxkb_context *ctx;
ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
assert(ctx);
assert(!rxkb_context_parse_default_ruleset(ctx));
rxkb_context_unref(ctx);
}
static void
test_invalid_include(void)
{
struct rxkb_context *ctx;
ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
assert(ctx);
assert(!rxkb_context_include_path_append(ctx, "/foo/bar/baz/bat"));
assert(!rxkb_context_parse_default_ruleset(ctx));
rxkb_context_unref(ctx);
}
int
main(void)
{
test_no_include_paths();
test_invalid_include();
test_load_basic();
test_load_full();
test_load_merge();
test_load_merge_no_overwrite();
test_popularity();
return 0;
}

223
tools/registry-list.c Normal file
View File

@ -0,0 +1,223 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <assert.h>
#include <stdio.h>
#include <getopt.h>
#include "xkbcommon/xkbregistry.h"
static void
usage(const char *progname)
{
fprintf(stderr,
"Usage: %s [OPTIONS] [/path/to/xkb_base_directory [/path2]...]\n"
"\n"
"Options:\n"
" --verbose, -v .......... Increase verbosity, use multiple times for debugging output\n"
" --ruleset=foo .......... Load the 'foo' ruleset\n"
" --skip-default-paths ... Do not load the default XKB paths\n"
" --load-exotic .......... Load the exotic (extra) rulesets\n"
"\n"
"Trailing arguments are treated as XKB base directory installations.\n",
progname);
}
int
main(int argc, char **argv)
{
int rc = 1;
struct rxkb_context *ctx = NULL;
struct rxkb_model *m;
struct rxkb_layout *l;
struct rxkb_option_group *g;
enum rxkb_context_flags flags = RXKB_CONTEXT_NO_FLAGS;
bool load_defaults = true;
int verbosity = 0;
const char *ruleset = DEFAULT_XKB_RULES;
static const struct option opts[] = {
{"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'},
{"load-exotic", no_argument, 0, 'e'},
{"skip-default-paths", no_argument, 0, 'd'},
{"ruleset", required_argument, 0, 'r'},
{0, 0, 0, 0},
};
while (1) {
int c;
int option_index = 0;
c = getopt_long(argc, argv, "hev", opts, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
case '?':
usage(argv[0]);
return 1;
case 'd':
load_defaults = false;
break;
case 'e':
flags |= RXKB_CONTEXT_LOAD_EXOTIC_RULES;
break;
case 'r':
ruleset = optarg;
break;
case 'v':
verbosity++;
break;
}
}
if (optind < argc)
flags |= RXKB_CONTEXT_NO_DEFAULT_INCLUDES;
ctx = rxkb_context_new(flags);
assert(ctx);
switch (verbosity) {
case 0:
rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_ERROR);
break;
case 1:
rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_INFO);
break;
default:
rxkb_context_set_log_level(ctx, RXKB_LOG_LEVEL_DEBUG);
break;
}
if (optind < argc) {
for (int i = optind; i < argc; i++) {
if (!rxkb_context_include_path_append(ctx, argv[i])) {
fprintf(stderr, "Failed to append include path '%s'\n",
argv[i]);
goto err;
}
}
if (load_defaults) {
if (!rxkb_context_include_path_append_default(ctx)) {
fprintf(stderr, "Failed to include default paths.\n");
goto err;
}
}
}
if (!rxkb_context_parse(ctx, ruleset)) {
fprintf(stderr, "Failed to parse XKB descriptions.\n");
goto err;
}
printf("Models:\n");
m = rxkb_model_first(ctx);
assert(m); /* Empty model list is usually a bug or a bad xml file */
while (m) {
printf("- %s:%s:%s\n",
rxkb_model_get_name(m),
rxkb_model_get_vendor(m),
rxkb_model_get_description(m));
m = rxkb_model_next(m);
}
printf("\n");
printf("Layouts:\n");
l = rxkb_layout_first(ctx);
assert(l); /* Empty layout list is usually a bug or a bad xml file */
while (l) {
struct rxkb_iso639_code *iso639;
struct rxkb_iso3166_code *iso3166;
const char *variant = rxkb_layout_get_variant(l);
const char *brief = rxkb_layout_get_brief(l);
bool first;
printf("- %s%s%s%s:%s:%s",
rxkb_layout_get_name(l),
variant ? "(" : "",
variant ? variant : "",
variant ? ")" : "",
brief ? brief : "",
rxkb_layout_get_description(l));
iso639 = rxkb_layout_get_iso639_first(l);
if (iso639)
printf(":iso639-");
first = true;
while (iso639) {
printf("%s%s", first ? "" : ",", rxkb_iso639_code_get_code(iso639));
iso639 = rxkb_iso639_code_next(iso639);
first = false;
}
iso3166 = rxkb_layout_get_iso3166_first(l);
if (iso3166)
printf(":iso3166-");
first = true;
while (iso3166) {
printf("%s%s", first ? "" : ",", rxkb_iso3166_code_get_code(iso3166));
iso3166 = rxkb_iso3166_code_next(iso3166);
first = false;
}
printf("\n");
l = rxkb_layout_next(l);
}
printf("\n");
printf("Options:\n");
g = rxkb_option_group_first(ctx);
assert(g); /* Empty option goups list is usually a bug or a bad xml file */
while (g) {
struct rxkb_option *o;
printf("- %s:%s (%s)\n",
rxkb_option_group_get_name(g),
rxkb_option_group_get_description(g),
rxkb_option_group_allows_multiple(g) ? "multiple" : "single");
o = rxkb_option_first(g);
assert(o); /* Empty option list is usually a bug or a bad xml file */
while (o) {
const char *brief = rxkb_option_get_brief(o);
printf(" - %s:%s:%s\n",
rxkb_option_get_name(o),
brief ? brief : "",
rxkb_option_get_description(o));
o = rxkb_option_next(o);
}
g = rxkb_option_group_next(g);
}
rc = 0;
err:
if (ctx)
rxkb_context_unref(ctx);
return rc;
}

782
xkbcommon/xkbregistry.h Normal file
View File

@ -0,0 +1,782 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef _XKBREGISTRY_H_
#define _XKBREGISTRY_H_
#include <stdarg.h>
#include <stdbool.h>
/**
* @file
* @brief Query for available RMLVO
*
*/
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup registry Query for available RMLVO
*
* The libxkbregistry API to query for available rules, models, layouts,
* variants and options (RMLVO). libxkbregistry is a separate library to
* libxkbcommon.
*
* This library is the replacement for clients currently parsing evdev.xml
* directly. The library is intended to provide easy access to the set of
* **possible** MLVO configurations for a given ruleset. It is not a library to
* apply these configurations, merely to enumerate them. The intended users of
* this library are the configuration UIs that allow a user to select their
* keyboard layout of choice.
*
* @{
*/
/**
* @struct rxkb_context
*
* Opaque top level library context object.
*
* The context contains general library state, like include paths and parsed
* data. Objects are created in a specific context, and multiple contexts
* may coexist simultaneously. Objects from different contexts are
* completely separated and do not share any memory or state.
*/
struct rxkb_context;
/**
* @struct rxkb_model
*
* Opaque struct representing an XKB model.
*/
struct rxkb_model;
/**
* @struct rxkb_layout
*
* Opaque struct representing an XKB layout, including an optional variant.
* Where the variant is NULL, the layout is the base layout.
*
* For example, "us" is the base layout, "us(intl)" is the "intl" variant of the
* layout "us".
*/
struct rxkb_layout;
/**
* @struct rxkb_option_group
*
* Opaque struct representing an option group. Option groups divide the
* individual options into logical groups. Their main purpose is to indicate
* whether some options are mutually exclusive or not.
*/
struct rxkb_option_group;
/**
* @struct rxkb_option
*
* Opaque struct representing an XKB option. Options are grouped inside an @ref
* rxkb_option_group.
*/
struct rxkb_option;
/**
*
* @struct rxkb_iso639_code
*
* Opaque struct representing an ISO 639-3 code (e.g. "eng", "fra"). There
* is no guarantee that two identical ISO codes share the same struct. You
* must not rely on the pointer value of this struct.
*
* See https://iso639-3.sil.org/code_tables/639/data for a list of codes.
*/
struct rxkb_iso639_code;
/**
*
* @struct rxkb_iso3166_code
*
* Opaque struct representing an ISO 3166 Alpha 2 code (e.g. "US", "FR").
* There is no guarantee that two identical ISO codes share the same struct.
* You must not rely on the pointer value of this struct.
*
* See https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes for a list
* of codes.
*/
struct rxkb_iso3166_code;
/**
* Describes the popularity of an item. Historically, some highly specialized or
* experimental definitions are excluded from the default list and shipped in
* separate files. If these extra definitions are loaded (see @ref
* RXKB_CONTEXT_LOAD_EXOTIC_RULES), the popularity of the item is set
* accordingly.
*
* If the exotic items are not loaded, all items will have the standard
* popularity.
*/
enum rxkb_popularity {
RXKB_POPULARITY_STANDARD = 1,
RXKB_POPULARITY_EXOTIC,
};
/**
* Flags for context creation.
*/
enum rxkb_context_flags {
RXKB_CONTEXT_NO_FLAGS = 0,
/**
* Skip the default include paths. This requires the caller to call
* rxkb_context_include_path_append() or
* rxkb_context_include_path_append_default().
*/
RXKB_CONTEXT_NO_DEFAULT_INCLUDES = (1 << 0),
/**
* Load the extra items that are considered too exotic for the default list.
*
* For historical reasons, xkeyboard-config ships those exotic rules in a
* separate file (e.g. `evdev.extras.xml`). Where the exotic rules are
* requested, libxkbregistry will look for and load `$ruleset.extras.xml`
* in the include paths, see rxkb_context_include_path_append() for details
* on the lookup behavior.
*/
RXKB_CONTEXT_LOAD_EXOTIC_RULES = (1 << 1),
};
/**
* Create a new xkb registry context.
*
* The context has an initial refcount of 1. Use rxkb_context_unref() to release
* memory associated with this context.
*
* Creating a context does not parse the files yet, use
* rxkb_context_parse().
*
* @param flags Flags affecting context behavior
* @return A new xkb registry context or NULL on failure
*/
struct rxkb_context *
rxkb_context_new(enum rxkb_context_flags flags);
/** Specifies a logging level. */
enum rxkb_log_level {
RXKB_LOG_LEVEL_CRITICAL = 10, /**< Log critical internal errors only. */
RXKB_LOG_LEVEL_ERROR = 20, /**< Log all errors. */
RXKB_LOG_LEVEL_WARNING = 30, /**< Log warnings and errors. */
RXKB_LOG_LEVEL_INFO = 40, /**< Log information, warnings, and errors. */
RXKB_LOG_LEVEL_DEBUG = 50 /**< Log everything. */
};
/**
* Set the current logging level.
*
* @param ctx The context in which to set the logging level.
* @param level The logging level to use. Only messages from this level
* and below will be logged.
*
* The default level is RXKB_LOG_LEVEL_ERROR. The environment variable
* RXKB_LOG_LEVEL, if set at the time the context was created, overrides the
* default value. It may be specified as a level number or name.
*/
void
rxkb_context_set_log_level(struct rxkb_context *ctx,
enum rxkb_log_level level);
/**
* Get the current logging level.
*/
enum rxkb_log_level
rxkb_context_get_log_level(struct rxkb_context *ctx);
/**
* Set a custom function to handle logging messages.
*
* @param ctx The context in which to use the set logging function.
* @param log_fn The function that will be called for logging messages.
* Passing NULL restores the default function, which logs to stderr.
*
* By default, log messages from this library are printed to stderr. This
* function allows you to replace the default behavior with a custom
* handler. The handler is only called with messages which match the
* current logging level and verbosity settings for the context.
* level is the logging level of the message. @a format and @a args are
* the same as in the vprintf(3) function.
*
* You may use rxkb_context_set_user_data() on the context, and then call
* rxkb_context_get_user_data() from within the logging function to provide
* it with additional private context.
*/
void
rxkb_context_set_log_fn(struct rxkb_context *ctx,
void (*log_fn)(struct rxkb_context *ctx,
enum rxkb_log_level level,
const char *format, va_list args));
/**
* Parse the given ruleset. This can only be called once per context and once
* parsed the data in the context is considered constant and will never
* change.
*
* This function parses all files with the given ruleset name. See
* rxkb_context_include_path_append() for details.
*
* If this function returns false, libxkbregistry failed to parse the xml files.
* This is usually caused by invalid files on the host and should be debugged by
* the host's administrator using external tools. Callers should reduce the
* include paths to known good paths and/or fall back to a default RMLVO set.
*
* If this function returns false, the context should be be considered dead and
* must be released with rxkb_context_unref().
*
* @param ctx The xkb registry context
* @param ruleset The ruleset to parse, e.g. "evdev"
* @return true on success or false on failure
*/
bool
rxkb_context_parse(struct rxkb_context *ctx, const char *ruleset);
/**
* Parse the default ruleset as configured at build time. See
* rxkb_context_parse() for details.
*/
bool
rxkb_context_parse_default_ruleset(struct rxkb_context *ctx);
/**
* Increases the refcount of this object by one and returns the object.
*
* @param ctx The xkb registry context
* @return The passed in object
*/
struct rxkb_context*
rxkb_context_ref(struct rxkb_context *ctx);
/**
* Decreases the refcount of this object by one. Where the refcount of an
* object hits zero, associated resources will be freed.
*
* @param ctx The xkb registry context
* @return always NULL
*/
struct rxkb_context*
rxkb_context_unref(struct rxkb_context *ctx);
/**
* Assign user-specific data. libxkbregistry will not look at or modify the
* data, it will merely return the same pointer in
* rxkb_context_get_user_data().
*
* @param ctx The xkb registry context
* @param user_data User-specific data pointer
*/
void
rxkb_context_set_user_data(struct rxkb_context *ctx, void *user_data);
/**
* Return the pointer passed into rxkb_context_get_user_data().
*
* @param ctx The xkb registry context
* @return User-specific data pointer
*/
void *
rxkb_context_get_user_data(struct rxkb_context *ctx);
/**
* Append a new entry to the context's include path.
*
* The include path handling is optimized for the most common use-case: a set of
* system files that provide a complete set of MLVO and some
* custom MLVO provided by a user **in addition** to the system set.
*
* The include paths should be given so that the least complete path is
* specified first and the most complete path is appended last. For example:
*
* @code
* ctx = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
* rxkb_context_include_path_append(ctx, "/home/user/.config/xkb");
* rxkb_context_include_path_append(ctx, "/usr/share/X11/xkb");
* rxkb_context_parse(ctx, "evdev");
* @endcode
*
* The above example reflects the default behavior unless @ref
* RXKB_CONTEXT_NO_DEFAULT_INCLUDES is provided.
*
* Loading of the files is in **reverse order**, i.e. the last path appended is
* loaded first - in this case the ``/usr/share/X11/xkb`` path.
* Any models, layouts, variants and options defined in the "evdev" ruleset
* are loaded into the context. Then, any RMLVO found in the "evdev" ruleset of
* the user's path (``/home/user/.config/xkb`` in this example) are **appended**
* to the existing set.
*
* Note that data from previously loaded include paths is never overwritten,
* only appended to. It is not not possible to change the system-provided data,
* only to append new models, layouts, variants and options to it.
*
* In other words, to define a new variant of the "us" layout called "banana",
* the following XML is sufficient.
*
* @verbatim
* <xkbConfigRegistry version="1.1">
* <layoutList>
* <layout>
* <configItem>
* <name>us</name>
* </configItem>
* <variantList>
* <variant>
* <configItem>
* <name>banana</name>
* <description>English (Banana)</description>
* </configItem>
* </variant>
* </layout>
* </layoutList>
* </xkbConfigRegistry>
* @endverbatim
*
* The list of models, options and all other layouts (including "us" and its
* variants) is taken from the system files. The resulting list of layouts will
* thus have a "us" keyboard layout with the variant "banana" and all other
* system-provided variants (dvorak, colemak, intl, etc.)
*
* This function must be called before rxkb_context_parse() or
* rxkb_context_parse_default_ruleset().
*
* @returns true on success, or false if the include path could not be added
* or is inaccessible.
*/
bool
rxkb_context_include_path_append(struct rxkb_context *ctx, const char *path);
/**
* Append the default include paths to the context's include path.
* See rxkb_context_include_path_append() for details about the merge order.
*
* This function must be called before rxkb_context_parse() or
* rxkb_context_parse_default_ruleset().
*
* @returns true on success, or false if the include path could not be added
* or is inaccessible.
*/
bool
rxkb_context_include_path_append_default(struct rxkb_context *ctx);
/**
* Return the first model for this context. Use this to start iterating over
* the models, followed by calls to rxkb_model_next(). Models are not sorted.
*
* The refcount of the returned model is not increased. Use rxkb_model_ref() if
* you need to keep this struct outside the immediate scope.
*
* @return The first model in the model list.
*/
struct rxkb_model *
rxkb_model_first(struct rxkb_context *ctx);
/**
* Return the next model for this context. Returns NULL when no more models
* are available.
*
* The refcount of the returned model is not increased. Use rxkb_model_ref() if
* you need to keep this struct outside the immediate scope.
*
* @return the next model or NULL at the end of the list
*/
struct rxkb_model *
rxkb_model_next(struct rxkb_model *m);
/**
* Increase the refcount of the argument by one.
*
* @returns The argument passed in to this function.
*/
struct rxkb_model *
rxkb_model_ref(struct rxkb_model *m);
/**
* Decrease the refcount of the argument by one. When the refcount hits zero,
* all memory associated with this struct is freed.
*
* @returns always NULL
*/
struct rxkb_model *
rxkb_model_unref(struct rxkb_model *m);
/**
* Return the name of this model. This is the value for M in RMLVO, to be used
* with libxkbcommon.
*/
const char *
rxkb_model_get_name(struct rxkb_model *m);
/**
* Return a human-readable description of this model. This function may return
* NULL.
*/
const char *
rxkb_model_get_description(struct rxkb_model *m);
/**
* Return the vendor name for this model. This function may return NULL.
*/
const char *
rxkb_model_get_vendor(struct rxkb_model *m);
/**
* Return the popularity for this model.
*/
enum rxkb_popularity
rxkb_model_get_popularity(struct rxkb_model *m);
/**
* Return the first layout for this context. Use this to start iterating over
* the layouts, followed by calls to rxkb_layout_next(). Layouts are not sorted.
*
* The refcount of the returned layout is not increased. Use rxkb_layout_ref() if
* you need to keep this struct outside the immediate scope.
*
* @return The first layout in the layout list.
*/
struct rxkb_layout *
rxkb_layout_first(struct rxkb_context *ctx);
/**
* Return the next layout for this context. Returns NULL when no more layouts
* are available.
*
* The refcount of the returned layout is not increased. Use rxkb_layout_ref()
* if you need to keep this struct outside the immediate scope.
*
* @return the next layout or NULL at the end of the list
*/
struct rxkb_layout *
rxkb_layout_next(struct rxkb_layout *l);
/**
* Increase the refcount of the argument by one.
*
* @returns The argument passed in to this function.
*/
struct rxkb_layout *
rxkb_layout_ref(struct rxkb_layout *l);
/**
* Decrease the refcount of the argument by one. When the refcount hits zero,
* all memory associated with this struct is freed.
*
* @returns always NULL
*/
struct rxkb_layout *
rxkb_layout_unref(struct rxkb_layout *l);
/**
* Return the name of this layout. This is the value for L in RMLVO, to be used
* with libxkbcommon.
*/
const char *
rxkb_layout_get_name(struct rxkb_layout *l);
/**
* Return the variant of this layout. This is the value for V in RMLVO, to be
* used with libxkbcommon.
*
* A variant does not stand on its own, it always depends on the base layout.
* e.g. there may be multiple variants called "intl" but there is only one
* "us(intl)".
*
* Where the variant is NULL, the layout is the base layout (e.g. "us").
*/
const char *
rxkb_layout_get_variant(struct rxkb_layout *l);
/**
* Return a short (one-word) description of this layout. This function may
* return NULL.
*/
const char *
rxkb_layout_get_brief(struct rxkb_layout *l);
/**
* Return a human-readable description of this layout. This function may return
* NULL.
*/
const char *
rxkb_layout_get_description(struct rxkb_layout *l);
/**
* Return the popularity for this layout.
*/
enum rxkb_popularity
rxkb_layout_get_popularity(struct rxkb_layout *l);
/**
* Return the first option group for this context. Use this to start iterating
* over the option groups, followed by calls to rxkb_option_group_next().
* Option groups are not sorted.
*
* The refcount of the returned option group is not increased. Use
* rxkb_option_group_ref() if you need to keep this struct outside the immediate
* scope.
*
* @return The first option group in the option group list.
*/
struct rxkb_option_group *
rxkb_option_group_first(struct rxkb_context *ctx);
/**
* Return the next option group for this context. Returns NULL when no more
* option groups are available.
*
* The refcount of the returned option group is not increased. Use
* rxkb_option_group_ref() if you need to keep this struct outside the immediate
* scope.
*
* @return the next option group or NULL at the end of the list
*/
struct rxkb_option_group *
rxkb_option_group_next(struct rxkb_option_group *g);
/**
* Increase the refcount of the argument by one.
*
* @returns The argument passed in to this function.
*/
struct rxkb_option_group *
rxkb_option_group_ref(struct rxkb_option_group *g);
/**
* Decrease the refcount of the argument by one. When the refcount hits zero,
* all memory associated with this struct is freed.
*
* @returns always NULL
*/
struct rxkb_option_group *
rxkb_option_group_unref(struct rxkb_option_group *g);
/**
* Return the name of this option group. This is **not** the value for O in
* RMLVO, the name can be used for internal sorting in the caller. This function
* may return NULL.
*/
const char *
rxkb_option_group_get_name(struct rxkb_option_group *m);
/**
* Return a human-readable description of this option group. This function may
* return NULL.
*/
const char *
rxkb_option_group_get_description(struct rxkb_option_group *m);
/**
* @return true if multiple options within this option group can be selected
* simultaneously, false if all options within this option group
* are mutually exclusive.
*/
bool
rxkb_option_group_allows_multiple(struct rxkb_option_group *g);
/**
* Return the popularity for this option group.
*/
enum rxkb_popularity
rxkb_option_group_get_popularity(struct rxkb_option_group *g);
/**
* Return the first option for this option group. Use this to start iterating
* over the options, followed by calls to rxkb_option_next(). Options are not
* sorted.
*
* The refcount of the returned option is not increased. Use rxkb_option_ref()
* if you need to keep this struct outside the immediate scope.
*
* @return The first option in the option list.
*/
struct rxkb_option *
rxkb_option_first(struct rxkb_option_group *group);
/**
* Return the next option for this option group. Returns NULL when no more
* options are available.
*
* The refcount of the returned options is not increased. Use rxkb_option_ref()
* if you need to keep this struct outside the immediate scope.
*
* @returns The next option or NULL at the end of the list
*/
struct rxkb_option *
rxkb_option_next(struct rxkb_option *o);
/**
* Increase the refcount of the argument by one.
*
* @returns The argument passed in to this function.
*/
struct rxkb_option *
rxkb_option_ref(struct rxkb_option *o);
/**
* Decrease the refcount of the argument by one. When the refcount hits zero,
* all memory associated with this struct is freed.
*
* @returns always NULL
*/
struct rxkb_option *
rxkb_option_unref(struct rxkb_option *o);
/**
* Return the name of this option. This is the value for O in RMLVO, to be used
* with libxkbcommon.
*/
const char *
rxkb_option_get_name(struct rxkb_option *o);
/**
* Return a short (one-word) description of this option. This function may
* return NULL.
*/
const char *
rxkb_option_get_brief(struct rxkb_option *o);
/**
* Return a human-readable description of this option. This function may return
* NULL.
*/
const char *
rxkb_option_get_description(struct rxkb_option *o);
/**
* Return the popularity for this option.
*/
enum rxkb_popularity
rxkb_option_get_popularity(struct rxkb_option *o);
/**
* Increase the refcount of the argument by one.
*
* @returns The argument passed in to this function.
*/
struct rxkb_iso639_code *
rxkb_iso639_code_ref(struct rxkb_iso639_code *iso639);
/**
* Decrease the refcount of the argument by one. When the refcount hits zero,
* all memory associated with this struct is freed.
*
* @returns always NULL
*/
struct rxkb_iso639_code *
rxkb_iso639_code_unref(struct rxkb_iso639_code *iso639);
/**
* Return the ISO 639-3 code for this code (e.g. "eng", "fra").
*/
const char *
rxkb_iso639_code_get_code(struct rxkb_iso639_code *iso639);
/**
* Return the first ISO 639 for this layout. Use this to start iterating over
* the codes, followed by calls to rxkb_iso639_code_next(). Codes are not
* sorted.
*
* The refcount of the returned code is not increased. Use rxkb_iso639_code_ref()
* if you need to keep this struct outside the immediate scope.
*
* @return The first code in the code list.
*/
struct rxkb_iso639_code *
rxkb_layout_get_iso639_first(struct rxkb_layout *layout);
/**
* Return the next code in the list. Returns NULL when no more codes
* are available.
*
* The refcount of the returned codes is not increased. Use
* rxkb_iso639_code_ref() if you need to keep this struct outside the immediate
* scope.
*
* @returns The next code or NULL at the end of the list
*/
struct rxkb_iso639_code *
rxkb_iso639_code_next(struct rxkb_iso639_code *iso639);
/**
* Increase the refcount of the argument by one.
*
* @returns The argument passed in to this function.
*/
struct rxkb_iso3166_code *
rxkb_iso3166_code_ref(struct rxkb_iso3166_code *iso3166);
/**
* Decrease the refcount of the argument by one. When the refcount hits zero,
* all memory associated with this struct is freed.
*
* @returns always NULL
*/
struct rxkb_iso3166_code *
rxkb_iso3166_code_unref(struct rxkb_iso3166_code *iso3166);
/**
* Return the ISO 3166 Alpha 2 code for this code (e.g. "US", "FR").
*/
const char *
rxkb_iso3166_code_get_code(struct rxkb_iso3166_code *iso3166);
/**
* Return the first ISO 3166 for this layout. Use this to start iterating over
* the codes, followed by calls to rxkb_iso3166_code_next(). Codes are not
* sorted.
*
* The refcount of the returned code is not increased. Use
* rxkb_iso3166_code_ref() if you need to keep this struct outside the immediate
* scope.
*
* @return The first code in the code list.
*/
struct rxkb_iso3166_code *
rxkb_layout_get_iso3166_first(struct rxkb_layout *layout);
/**
* Return the next code in the list. Returns NULL when no more codes
* are available.
*
* The refcount of the returned codes is not increased. Use
* rxkb_iso3166_code_ref() if you need to keep this struct outside the immediate
* scope.
*
* @returns The next code or NULL at the end of the list
*/
struct rxkb_iso3166_code *
rxkb_iso3166_code_next(struct rxkb_iso3166_code *iso3166);
/** @} */
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* _XKBREGISTRY_H_ */

61
xkbregistry.map Normal file
View File

@ -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:
*;
};