Commit d7b39f6ffbe9b46181c5597b0d0e7373eb2e9070

Peter Hutterer 2020-07-10T08:50:02

Add /etc/xkb as extra lookup path for system data files This completes the usual triplet of configuration locations available for most processes: - vendor-provided data files in /usr/share/X11/xkb - system-specific data files in /etc/xkb - user-specific data files in $XDG_CONFIG_HOME/xkb The default lookup order user, system, vendor, just like everything else that uses these conventions. For include directives in rules files, the '%E' resolves to that path. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>

diff --git a/doc/rules-format.md b/doc/rules-format.md
index 96acbbf..f9fe0d9 100644
--- a/doc/rules-format.md
+++ b/doc/rules-format.md
@@ -73,6 +73,9 @@ Notes:
   %H:
     The value of the HOME environment variable.
 
+  %E:
+    The extra lookup path for system-wide XKB data (usually /etc/xkb/rules).
+
   %S:
     The system-installed rules directory (usually /usr/share/X11/xkb/rules).
 ```
diff --git a/doc/user-configuration.md b/doc/user-configuration.md
index 17e7179..b8c4d30 100644
--- a/doc/user-configuration.md
+++ b/doc/user-configuration.md
@@ -11,6 +11,8 @@ libxkbcommon searches the following paths for XKB configuration files:
 - `$XDG_CONFIG_HOME/xkb/`, or `$HOME/.config/xkb/` if the `$XDG_CONFIG_HOME`
   environment variable is not defined
 - `$HOME/.xkb/`
+- `$XKB_CONFIG_EXTRA_PATH` if set, otherswise `<sysconfdir>/xkb` (on most
+  distributions this is `/etc/xkb`)
 - `$XKB_CONFIG_ROOT` if set, otherwise `<datadir>/X11/xkb/` (path defined by the
   `xkeyboard-config` package, on most distributions this is
   `/usr/share/X11/xkb`)
diff --git a/meson.build b/meson.build
index e7d75aa..c08b40b 100644
--- a/meson.build
+++ b/meson.build
@@ -47,6 +47,10 @@ if XKBCONFIGROOT == ''
   endif
 endif
 
+XKBCONFIGEXTRAPATH = get_option('xkb-config-extra-path')
+if XKBCONFIGEXTRAPATH == ''
+    XKBCONFIGEXTRAPATH = join_paths(get_option('prefix'), get_option('sysconfdir'), 'xkb')
+endif
 
 # The X locale directory for compose.
 XLOCALEDIR = get_option('x-locale-root')
@@ -70,6 +74,7 @@ endif
 configh_data.set(system_extensions, 1)
 system_ext_define = '#define ' + system_extensions
 configh_data.set_quoted('DFLT_XKB_CONFIG_ROOT', XKBCONFIGROOT)
+configh_data.set_quoted('DFLT_XKB_CONFIG_EXTRA_PATH', XKBCONFIGEXTRAPATH)
 configh_data.set_quoted('XLOCALEDIR', XLOCALEDIR)
 configh_data.set_quoted('DEFAULT_XKB_RULES', get_option('default-rules'))
 configh_data.set_quoted('DEFAULT_XKB_MODEL', get_option('default-model'))
diff --git a/meson_options.txt b/meson_options.txt
index 5eaa081..04982c6 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -4,6 +4,11 @@ option(
     description: 'The XKB config root [default=xkeyboard-config install path]',
 )
 option(
+    'xkb-config-extra-path',
+    type: 'string',
+    description: 'Extra lookup path for system-wide XKB data [default=$sysconfdir/xkb]',
+)
+option(
     'x-locale-root',
     type: 'string',
     description: 'The X locale root [default=$datadir/X11/locale]',
diff --git a/src/context.c b/src/context.c
index ef9b774..2abaa9b 100644
--- a/src/context.c
+++ b/src/context.c
@@ -84,6 +84,13 @@ err:
 }
 
 const char *
+xkb_context_include_path_get_extra_path(struct xkb_context *ctx)
+{
+    const char *extra = secure_getenv("XKB_CONFIG_EXTRA_PATH");
+    return extra ? extra : DFLT_XKB_CONFIG_EXTRA_PATH;
+}
+
+const char *
 xkb_context_include_path_get_system_path(struct xkb_context *ctx)
 {
     const char *root = secure_getenv("XKB_CONFIG_ROOT");
@@ -96,7 +103,7 @@ xkb_context_include_path_get_system_path(struct xkb_context *ctx)
 XKB_EXPORT int
 xkb_context_include_path_append_default(struct xkb_context *ctx)
 {
-    const char *home, *xdg, *root;
+    const char *home, *xdg, *root, *extra;
     char *user_path;
     int ret = 0;
 
@@ -126,6 +133,8 @@ xkb_context_include_path_append_default(struct xkb_context *ctx)
         }
     }
 
+    extra = xkb_context_include_path_get_extra_path(ctx);
+    ret |= xkb_context_include_path_append(ctx, extra);
     root = xkb_context_include_path_get_system_path(ctx);
     ret |= xkb_context_include_path_append(ctx, root);
 
diff --git a/src/context.h b/src/context.h
index 9584dbc..ead2508 100644
--- a/src/context.h
+++ b/src/context.h
@@ -60,6 +60,9 @@ xkb_context_failed_include_path_get(struct xkb_context *ctx,
                                     unsigned int idx);
 
 const char *
+xkb_context_include_path_get_extra_path(struct xkb_context *ctx);
+
+const char *
 xkb_context_include_path_get_system_path(struct xkb_context *ctx);
 
 /*
diff --git a/src/registry.c b/src/registry.c
index 956d850..d3d95f5 100644
--- a/src/registry.c
+++ b/src/registry.c
@@ -583,7 +583,7 @@ err:
 XKB_EXPORT bool
 rxkb_context_include_path_append_default(struct rxkb_context *ctx)
 {
-    const char *home, *xdg, *root;
+    const char *home, *xdg, *root, *extra;
     char *user_path;
     bool ret = false;
 
@@ -618,6 +618,12 @@ rxkb_context_include_path_append_default(struct rxkb_context *ctx)
         }
     }
 
+    extra = secure_getenv("XKB_CONFIG_EXTRA_PATH");
+    if (extra != NULL)
+        ret |= rxkb_context_include_path_append(ctx, extra);
+    else
+        ret |= rxkb_context_include_path_append(ctx, DFLT_XKB_CONFIG_EXTRA_PATH);
+
     root = secure_getenv("XKB_CONFIG_ROOT");
     if (root != NULL)
         ret |= rxkb_context_include_path_append(ctx, root);
diff --git a/src/xkbcomp/rules.c b/src/xkbcomp/rules.c
index 3359552..099500a 100644
--- a/src/xkbcomp/rules.c
+++ b/src/xkbcomp/rules.c
@@ -399,6 +399,13 @@ matcher_include(struct matcher *m, struct scanner *parent_scanner,
                     return;
                 }
             }
+            else if (chr(&s, 'E')) {
+                const char *default_root = xkb_context_include_path_get_extra_path(m->ctx);
+                if (!buf_appends(&s, default_root) || !buf_appends(&s, "/rules")) {
+                    scanner_err(&s, "include path after expanding %%E is too long");
+                    return;
+                }
+            }
             else {
                 scanner_err(&s, "unknown %% format (%c) in include statement", peek(&s));
                 return;
diff --git a/test/tool-option-parsing.py b/test/tool-option-parsing.py
index c503176..de638d1 100755
--- a/test/tool-option-parsing.py
+++ b/test/tool-option-parsing.py
@@ -306,5 +306,7 @@ if __name__ == '__main__':
         # to override it with a known (empty) directory. Otherwise our test
         # behavior depends on the system the test is run on.
         os.environ['XDG_CONFIG_HOME'] = tmpdir
+        # This needs to be separated if we do specific extra path testing
+        os.environ['XKB_CONFIG_EXTRA_PATH'] = tmpdir
 
         sys.exit(pytest.main(args=[__file__]))
diff --git a/xkbcommon/xkbcommon.h b/xkbcommon/xkbcommon.h
index c091e5d..5fabc34 100644
--- a/xkbcommon/xkbcommon.h
+++ b/xkbcommon/xkbcommon.h
@@ -553,7 +553,7 @@ xkb_keysym_to_lower(xkb_keysym_t ks);
  *
  * The user may set some environment variables which affect the library:
  *
- * - `XKB_CONFIG_ROOT`, `XDG_CONFIG_DIR`, `HOME` - see @ref include-path.
+ * - `XKB_CONFIG_ROOT`, `XKB_EXTRA_PATH`, `XDG_CONFIG_DIR`, `HOME` - see @ref include-path.
  * - `XKB_LOG_LEVEL` - see xkb_context_set_log_level().
  * - `XKB_LOG_VERBOSITY` - see xkb_context_set_log_verbosity().
  * - `XKB_DEFAULT_RULES`, `XKB_DEFAULT_MODEL`, `XKB_DEFAULT_LAYOUT`,
@@ -644,6 +644,9 @@ xkb_context_get_user_data(struct xkb_context *context);
  *   fallback to `$HOME/.config/` if unset.
  * - The path `$HOME/.xkb`, where $HOME is the value of the environment
  *   variable `HOME`.
+ * - The `XKB_EXTRA_PATH` environment variable, if defined, otherwise the
+ *   system configuration directory, defined at library configuration time
+ *   (usually `/etc/xkb`).
  * - The `XKB_CONFIG_ROOT` environment variable, if defined, otherwise
  *   the system XKB root, defined at library configuration time.
  *