1 /*
2  * Copyright © 2018 Red Hat, Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  */
23 
24 #include "config.h"
25 
26 #include <assert.h>
27 #include <errno.h>
28 #include <getopt.h>
29 #include <stdio.h>
30 #include <stdbool.h>
31 #include <stdlib.h>
32 #include <string.h>
33 
34 #include "xkbcommon/xkbcommon.h"
35 #if ENABLE_PRIVATE_APIS
36 #include "xkbcomp/xkbcomp-priv.h"
37 #include "xkbcomp/rules.h"
38 #endif
39 #include "tools-common.h"
40 
41 #define DEFAULT_INCLUDE_PATH_PLACEHOLDER "__defaults__"
42 
43 static bool verbose = false;
44 static enum output_format {
45     FORMAT_RMLVO,
46     FORMAT_KEYMAP,
47     FORMAT_KCCGST,
48     FORMAT_KEYMAP_FROM_XKB,
49 } output_format = FORMAT_KEYMAP;
50 static const char *includes[64];
51 static size_t num_includes = 0;
52 
53 static void
usage(char ** argv)54 usage(char **argv)
55 {
56     printf("Usage: %s [OPTIONS]\n"
57            "\n"
58            "Compile the given RMLVO to a keymap and print it\n"
59            "\n"
60            "Options:\n"
61            " --verbose\n"
62            "    Enable verbose debugging output\n"
63 #if ENABLE_PRIVATE_APIS
64            " --kccgst\n"
65            "    Print a keymap which only includes the KcCGST component names instead of the full keymap\n"
66 #endif
67            " --rmlvo\n"
68            "    Print the full RMLVO with the defaults filled in for missing elements\n"
69            " --from-xkb\n"
70            "    Load the XKB file from stdin, ignore RMLVO options.\n"
71 #if ENABLE_PRIVATE_APIS
72            "    This option must not be used with --kccgst.\n"
73 #endif
74            " --include\n"
75            "    Add the given path to the include path list. This option is\n"
76            "    order-dependent, include paths given first are searched first.\n"
77            "    If an include path is given, the default include path list is\n"
78            "    not used. Use --include-defaults to add the default include\n"
79            "    paths\n"
80            " --include-defaults\n"
81            "    Add the default set of include directories.\n"
82            "    This option is order-dependent, include paths given first\n"
83            "    are searched first.\n"
84            "\n"
85            "XKB-specific options:\n"
86            " --rules <rules>\n"
87            "    The XKB ruleset (default: '%s')\n"
88            " --model <model>\n"
89            "    The XKB model (default: '%s')\n"
90            " --layout <layout>\n"
91            "    The XKB layout (default: '%s')\n"
92            " --variant <variant>\n"
93            "    The XKB layout variant (default: '%s')\n"
94            " --options <options>\n"
95            "    The XKB options (default: '%s')\n"
96            "\n",
97            argv[0], DEFAULT_XKB_RULES,
98            DEFAULT_XKB_MODEL, DEFAULT_XKB_LAYOUT,
99            DEFAULT_XKB_VARIANT ? DEFAULT_XKB_VARIANT : "<none>",
100            DEFAULT_XKB_OPTIONS ? DEFAULT_XKB_OPTIONS : "<none>");
101 }
102 
103 static bool
parse_options(int argc,char ** argv,struct xkb_rule_names * names)104 parse_options(int argc, char **argv, struct xkb_rule_names *names)
105 {
106     enum options {
107         OPT_VERBOSE,
108         OPT_KCCGST,
109         OPT_RMLVO,
110         OPT_FROM_XKB,
111         OPT_INCLUDE,
112         OPT_INCLUDE_DEFAULTS,
113         OPT_RULES,
114         OPT_MODEL,
115         OPT_LAYOUT,
116         OPT_VARIANT,
117         OPT_OPTION,
118     };
119     static struct option opts[] = {
120         {"help",             no_argument,            0, 'h'},
121         {"verbose",          no_argument,            0, OPT_VERBOSE},
122 #if ENABLE_PRIVATE_APIS
123         {"kccgst",           no_argument,            0, OPT_KCCGST},
124 #endif
125         {"rmlvo",            no_argument,            0, OPT_RMLVO},
126         {"from-xkb",         no_argument,            0, OPT_FROM_XKB},
127         {"include",          required_argument,      0, OPT_INCLUDE},
128         {"include-defaults", no_argument,            0, OPT_INCLUDE_DEFAULTS},
129         {"rules",            required_argument,      0, OPT_RULES},
130         {"model",            required_argument,      0, OPT_MODEL},
131         {"layout",           required_argument,      0, OPT_LAYOUT},
132         {"variant",          required_argument,      0, OPT_VARIANT},
133         {"options",          required_argument,      0, OPT_OPTION},
134         {0, 0, 0, 0},
135     };
136 
137     while (1) {
138         int c;
139         int option_index = 0;
140         c = getopt_long(argc, argv, "h", opts, &option_index);
141         if (c == -1)
142             break;
143 
144         switch (c) {
145         case 'h':
146             usage(argv);
147             exit(0);
148         case OPT_VERBOSE:
149             verbose = true;
150             break;
151         case OPT_KCCGST:
152             output_format = FORMAT_KCCGST;
153             break;
154         case OPT_RMLVO:
155             output_format = FORMAT_RMLVO;
156             break;
157         case OPT_FROM_XKB:
158             output_format = FORMAT_KEYMAP_FROM_XKB;
159             break;
160         case OPT_INCLUDE:
161             if (num_includes >= ARRAY_SIZE(includes)) {
162                 fprintf(stderr, "error: too many includes\n");
163                 exit(EXIT_INVALID_USAGE);
164             }
165             includes[num_includes++] = optarg;
166             break;
167         case OPT_INCLUDE_DEFAULTS:
168             if (num_includes >= ARRAY_SIZE(includes)) {
169                 fprintf(stderr, "error: too many includes\n");
170                 exit(EXIT_INVALID_USAGE);
171             }
172             includes[num_includes++] = DEFAULT_INCLUDE_PATH_PLACEHOLDER;
173             break;
174         case OPT_RULES:
175             names->rules = optarg;
176             break;
177         case OPT_MODEL:
178             names->model = optarg;
179             break;
180         case OPT_LAYOUT:
181             names->layout = optarg;
182             break;
183         case OPT_VARIANT:
184             names->variant = optarg;
185             break;
186         case OPT_OPTION:
187             names->options = optarg;
188             break;
189         default:
190             usage(argv);
191             exit(EXIT_INVALID_USAGE);
192         }
193 
194     }
195 
196     return true;
197 }
198 
199 static bool
print_rmlvo(struct xkb_context * ctx,const struct xkb_rule_names * rmlvo)200 print_rmlvo(struct xkb_context *ctx, const struct xkb_rule_names *rmlvo)
201 {
202     printf("rules: \"%s\"\nmodel: \"%s\"\nlayout: \"%s\"\nvariant: \"%s\"\noptions: \"%s\"\n",
203            rmlvo->rules, rmlvo->model, rmlvo->layout,
204            rmlvo->variant ? rmlvo->variant : "",
205            rmlvo->options ? rmlvo->options : "");
206     return true;
207 }
208 
209 static bool
print_kccgst(struct xkb_context * ctx,const struct xkb_rule_names * rmlvo)210 print_kccgst(struct xkb_context *ctx, const struct xkb_rule_names *rmlvo)
211 {
212 #if ENABLE_PRIVATE_APIS
213         struct xkb_component_names kccgst;
214 
215         if (!xkb_components_from_rules(ctx, rmlvo, &kccgst))
216             return false;
217 
218         printf("xkb_keymap {\n"
219                "  xkb_keycodes { include \"%s\" };\n"
220                "  xkb_types { include \"%s\" };\n"
221                "  xkb_compat { include \"%s\" };\n"
222                "  xkb_symbols { include \"%s\" };\n"
223                "};\n",
224                kccgst.keycodes, kccgst.types, kccgst.compat, kccgst.symbols);
225         free(kccgst.keycodes);
226         free(kccgst.types);
227         free(kccgst.compat);
228         free(kccgst.symbols);
229 
230         return true;
231 #else
232         return false;
233 #endif
234 }
235 
236 static bool
print_keymap(struct xkb_context * ctx,const struct xkb_rule_names * rmlvo)237 print_keymap(struct xkb_context *ctx, const struct xkb_rule_names *rmlvo)
238 {
239     struct xkb_keymap *keymap;
240 
241     keymap = xkb_keymap_new_from_names(ctx, rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
242     if (keymap == NULL)
243         return false;
244 
245     printf("%s\n", xkb_keymap_get_as_string(keymap,
246                                             XKB_KEYMAP_FORMAT_TEXT_V1));
247     xkb_keymap_unref(keymap);
248     return true;
249 }
250 
251 static bool
print_keymap_from_file(struct xkb_context * ctx)252 print_keymap_from_file(struct xkb_context *ctx)
253 {
254     struct xkb_keymap *keymap = NULL;
255     char *keymap_string = NULL;
256     FILE *file = NULL;
257     bool success = false;
258 
259     file = tmpfile();
260     if (!file) {
261         fprintf(stderr, "Failed to create tmpfile\n");
262         goto out;
263     }
264 
265     while (true) {
266         char buf[4096];
267         size_t len;
268 
269         len = fread(buf, 1, sizeof(buf), stdin);
270         if (ferror(stdin)) {
271             fprintf(stderr, "Failed to read from stdin\n");
272             goto out;
273         }
274         if (len > 0) {
275             size_t wlen = fwrite(buf, 1, len, file);
276             if (wlen != len) {
277                 fprintf(stderr, "Failed to write to tmpfile\n");
278                 goto out;
279             }
280         }
281         if (feof(stdin))
282             break;
283     }
284     fseek(file, 0, SEEK_SET);
285     keymap = xkb_keymap_new_from_file(ctx, file,
286                                       XKB_KEYMAP_FORMAT_TEXT_V1, 0);
287     if (!keymap) {
288         fprintf(stderr, "Couldn't create xkb keymap\n");
289         goto out;
290     }
291 
292     keymap_string = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
293     if (!keymap_string) {
294         fprintf(stderr, "Couldn't get the keymap string\n");
295         goto out;
296     }
297 
298     fputs(keymap_string, stdout);
299     success = true;
300 
301 out:
302     if (file)
303         fclose(file);
304     xkb_keymap_unref(keymap);
305     free(keymap_string);
306 
307     return success;
308 }
309 
310 int
main(int argc,char ** argv)311 main(int argc, char **argv)
312 {
313     struct xkb_context *ctx;
314     struct xkb_rule_names names = {
315         .rules = DEFAULT_XKB_RULES,
316         .model = DEFAULT_XKB_MODEL,
317         /* layout and variant are tied together, so we either get user-supplied for
318          * both or default for both, see below */
319         .layout = NULL,
320         .variant = NULL,
321         .options = DEFAULT_XKB_OPTIONS,
322     };
323     int rc = 1;
324 
325     if (argc <= 1) {
326         usage(argv);
327         return EXIT_INVALID_USAGE;
328     }
329 
330     if (!parse_options(argc, argv, &names))
331         return EXIT_INVALID_USAGE;
332 
333     /* Now fill in the layout */
334     if (!names.layout || !*names.layout) {
335         if (names.variant && *names.variant) {
336             fprintf(stderr, "Error: a variant requires a layout\n");
337             return EXIT_INVALID_USAGE;
338         }
339         names.layout = DEFAULT_XKB_LAYOUT;
340         names.variant = DEFAULT_XKB_VARIANT;
341     }
342 
343     ctx = xkb_context_new(XKB_CONTEXT_NO_DEFAULT_INCLUDES);
344     assert(ctx);
345 
346     if (verbose) {
347         xkb_context_set_log_level(ctx, XKB_LOG_LEVEL_DEBUG);
348         xkb_context_set_log_verbosity(ctx, 10);
349     }
350 
351     if (num_includes == 0)
352         includes[num_includes++] = DEFAULT_INCLUDE_PATH_PLACEHOLDER;
353 
354     for (size_t i = 0; i < num_includes; i++) {
355         const char *include = includes[i];
356         if (strcmp(include, DEFAULT_INCLUDE_PATH_PLACEHOLDER) == 0)
357             xkb_context_include_path_append_default(ctx);
358         else
359             xkb_context_include_path_append(ctx, include);
360     }
361 
362     if (output_format == FORMAT_RMLVO) {
363         rc = print_rmlvo(ctx, &names) ? EXIT_SUCCESS : EXIT_FAILURE;
364     } else if (output_format == FORMAT_KEYMAP) {
365         rc = print_keymap(ctx, &names) ? EXIT_SUCCESS : EXIT_FAILURE;
366     } else if (output_format == FORMAT_KCCGST) {
367         rc = print_kccgst(ctx, &names) ? EXIT_SUCCESS : EXIT_FAILURE;
368     } else if (output_format == FORMAT_KEYMAP_FROM_XKB) {
369         rc = print_keymap_from_file(ctx);
370     }
371 
372     xkb_context_unref(ctx);
373 
374     return rc;
375 }
376