2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2002 Jonathan Belson <jon@witchspace.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 #include <sys/types.h>
30 #include <sys/queue.h>
31 #include <sys/sysctl.h>
34 #include <bsddialog.h>
41 #include <stringlist.h>
47 static const char *lang_default
= DEFAULT_LANG
;
48 static const char *font
;
49 static const char *lang
;
50 static const char *program
;
51 static const char *keymapdir
= DEFAULT_VT_KEYMAP_DIR
;
52 static const char *fontdir
= DEFAULT_VT_FONT_DIR
;
53 static const char *font_default
= DEFAULT_VT_FONT
;
54 static const char *sysconfig
= DEFAULT_SYSCONFIG
;
55 static const char *font_current
;
56 static const char *dir
;
57 static const char *menu
= "";
58 static const char *title
= "Keyboard Menu";
71 SLIST_ENTRY(keymap
) entries
;
73 static SLIST_HEAD(slisthead
, keymap
) head
= SLIST_HEAD_INITIALIZER(head
);
77 * Get keymap entry for 'key', or NULL of not found
79 static struct keymap
*
80 get_keymap(const char *key
)
84 SLIST_FOREACH(km
, &head
, entries
)
85 if (!strcmp(km
->keym
, key
))
92 * Count the number of keymaps we found
100 SLIST_FOREACH(km
, &head
, entries
)
107 * Remove any keymap with given keym
110 remove_keymap(const char *keym
)
114 SLIST_FOREACH(km
, &head
, entries
) {
115 if (!strcmp(keym
, km
->keym
)) {
116 SLIST_REMOVE(&head
, km
, keymap
, entries
);
124 * Add to hash with 'key'
127 add_keymap(const char *desc
, int mark
, const char *keym
)
129 struct keymap
*km
, *km_new
;
131 /* Is there already an entry with this key? */
132 SLIST_FOREACH(km
, &head
, entries
) {
133 if (!strcmp(km
->keym
, keym
)) {
134 /* Reuse this entry */
136 km
->desc
= strdup(desc
);
142 km_new
= (struct keymap
*) malloc (sizeof(struct keymap
));
143 km_new
->desc
= strdup(desc
);
144 km_new
->keym
= strdup(keym
);
147 /* Add to keymap list */
148 SLIST_INSERT_HEAD(&head
, km_new
, entries
);
152 * Return 0 if syscons is in use (to select legacy defaults).
161 if (sysctlbyname("kern.vty", &term
, &len
, NULL
, 0) != 0 ||
162 strcmp(term
, "vt") != 0)
168 * Figure out the default language to use.
175 if ((locale
= getenv("LC_ALL")) == NULL
&&
176 (locale
= getenv("LC_CTYPE")) == NULL
&&
177 (locale
= getenv("LANG")) == NULL
)
178 locale
= lang_default
;
180 /* Check for alias */
181 if (!strcmp(locale
, "C"))
182 locale
= DEFAULT_LANG
;
188 * Extract filename part
191 extract_name(const char *name
)
195 p
= strrchr(name
, '/');
196 if (p
!= NULL
&& p
[1] != '\0')
203 * Return file extension or NULL
206 get_extension(const char *name
)
210 p
= strrchr(name
, '.');
212 if (p
!= NULL
&& p
[1] != '\0')
219 * Read font from /etc/rc.conf else return default.
220 * Freeing the memory is the caller's responsibility.
225 char line
[256], buf
[21];
228 FILE *fp
= fopen(sysconfig
, "r");
230 while (fgets(line
, sizeof(line
), fp
)) {
236 matches
= sscanf(line
,
237 " font%dx%d = \"%20[-.0-9a-zA-Z_]",
240 if (strcmp(buf
, "NO")) {
249 fprintf(stderr
, "Could not open %s for reading\n", sysconfig
);
255 * Set a font using 'vidcontrol'
258 vidcontrol(const char *fnt
)
260 char *tmp
, *p
, *q
, *cmd
;
264 /* syscons test failed */
269 asprintf(&cmd
, "vidcontrol -f %s", fnt
);
277 /* Extract font size */
278 p
= strrchr(tmp
, '-');
279 if (p
&& p
[1] != '\0') {
281 /* Remove any '.fnt' extension */
282 if ((q
= strstr(p
, ".fnt")))
286 * Check font size is valid, with no trailing characters
287 * ('&ch' should not be matched)
289 if (sscanf(p
, "%dx%d%c", &i
, &i
, &ch
) != 2)
290 fprintf(stderr
, "Which font size? %s\n", fnt
);
292 asprintf(&cmd
, "vidcontrol -f %s %s", p
, fnt
);
294 fprintf(stderr
, "%s\n", cmd
);
299 fprintf(stderr
, "Which font size? %s\n", fnt
);
305 * Execute 'kbdcontrol' with the appropriate arguments
308 do_kbdcontrol(struct keymap
*km
)
311 asprintf(&kbd_cmd
, "kbdcontrol -l %s/%s", dir
, km
->keym
);
316 fprintf(stderr
, "keymap=\"%s\"\n", km
->keym
);
321 * Call 'vidcontrol' with the appropriate arguments
324 do_vidfont(struct keymap
*km
)
326 char *vid_cmd
, *tmp
, *p
, *q
;
328 asprintf(&vid_cmd
, "%s/%s", dir
, km
->keym
);
332 tmp
= strdup(km
->keym
);
333 p
= strrchr(tmp
, '-');
334 if (p
&& p
[1]!='\0') {
336 q
= get_extension(p
);
339 printf("font%s=%s\n", p
, km
->keym
);
346 * Display dialog from 'keymaps[]'
349 show_dialog(struct keymap
**km_sorted
, int num_keymaps
)
351 struct bsddialog_conf conf
;
352 struct bsddialog_menuitem
*listitems
;
355 if (bsddialog_init() == BSDDIALOG_ERROR
) {
356 fprintf(stderr
, "Error bsddialog: %s\n", bsddialog_geterror());
359 conf
.title
= __DECONST(char *, title
);
361 listitems
= calloc(num_keymaps
+ 1, sizeof(struct bsddialog_menuitem
));
362 if (listitems
== NULL
) {
363 fprintf(stderr
, "Failed to allocate memory in show_dialog");
368 /* start right font, assume that current font is equal
369 * to default font in /etc/rc.conf
371 * $font is the font which require the language $lang; e.g.
372 * russian *need* a koi8 font
373 * $font_current is the current font from /etc/rc.conf
375 if (font
&& strcmp(font
, font_current
))
378 /* Build up the menu */
379 for (i
=0; i
<num_keymaps
; i
++) {
380 listitems
[i
].prefix
= "";
381 listitems
[i
].depth
= 0;
382 listitems
[i
].bottomdesc
= "";
383 listitems
[i
].on
= false;
384 listitems
[i
].name
= km_sorted
[i
]->desc
;
385 listitems
[i
].desc
= "";
387 bsddialog_initconf(&conf
);
390 conf
.key
.enable_esc
= true;
391 result
= bsddialog_menu(&conf
, menu
, 0, 0, 0, num_keymaps
, listitems
,
393 if (result
== BSDDIALOG_ERROR
)
394 fprintf(stderr
, "Error bsddialog: %s\n", bsddialog_geterror());
399 for (i
= 0; i
< num_keymaps
; i
++) {
400 if (listitems
[i
].on
) {
401 if (!strcmp(program
, "kbdmap"))
402 do_kbdcontrol(km_sorted
[i
]);
404 do_vidfont(km_sorted
[i
]);
410 if (font
!= NULL
&& strcmp(font
, font_current
))
411 /* Cancelled, restore old font */
412 vidcontrol(font_current
);
418 * Search for 'token' in comma delimited array 'buffer'.
419 * Return true for found, false for not found.
422 find_token(const char *buffer
, const char *token
)
424 char *buffer_tmp
, *buffer_copy
, *inputstring
;
428 buffer_copy
= strdup(buffer
);
429 buffer_tmp
= buffer_copy
;
430 inputstring
= buffer_copy
;
435 while ((*ap
= strsep(&inputstring
, ",")) != NULL
) {
436 if (strcmp(buffer_tmp
, token
) == 0) {
448 * Compare function for qsort
451 compare_keymap(const void *a
, const void *b
)
454 /* We've been passed pointers to pointers, so: */
455 const struct keymap
*km1
= *((const struct keymap
* const *) a
);
456 const struct keymap
*km2
= *((const struct keymap
* const *) b
);
458 return strcmp(km1
->desc
, km2
->desc
);
462 * Compare function for qsort
465 compare_lang(const void *a
, const void *b
)
467 const char *l1
= *((const char * const *) a
);
468 const char *l2
= *((const char * const *) b
);
470 return strcmp(l1
, l2
);
474 * Change '8x8' to '8x08' so qsort will put it before eg. '8x14'
477 kludge_desc(struct keymap
**km_sorted
, int num_keymaps
)
481 for (i
=0; i
<num_keymaps
; i
++) {
483 char *km
= km_sorted
[i
]->desc
;
484 if ((p
= strstr(km
, "8x8")) != NULL
) {
491 /* Make enough space for the extra '0' */
493 km
= realloc(km
, len
+ 2);
495 for (j
=len
; j
!=offset
+1; j
--)
500 km_sorted
[i
]->desc
= km
;
506 * Reverse 'kludge_desc()' - change '8x08' back to '8x8'
509 unkludge_desc(struct keymap
**km_sorted
, int num_keymaps
)
513 for (i
=0; i
<num_keymaps
; i
++) {
515 char *km
= km_sorted
[i
]->desc
;
516 if ((p
= strstr(km
, "8x08")) != NULL
) {
521 km
= realloc(km
, p
- km
- 1);
522 km_sorted
[i
]->desc
= km
;
528 * Return 0 if file exists and is readable, else -1
531 check_file(const char *keym
)
535 if (access(keym
, R_OK
) == -1) {
537 asprintf(&fn
, "%s/%s", dir
, keym
);
538 if (access(fn
, R_OK
) == -1) {
540 fprintf(stderr
, "%s not found!\n", fn
);
546 fprintf(stderr
, "No read permission for %s!\n", keym
);
554 * Read options from the relevant configuration file, then
562 int mark
, num_keymaps
, items
, i
;
563 char buffer
[256], filename
[PATH_MAX
];
564 char keym
[65], lng
[65], desc
[257];
565 char dialect
[64], lang_abk
[64];
567 struct keymap
**km_sorted
;
569 StringList
*lang_list
;
573 lang_list
= sl_init();
575 sprintf(filename
, "%s/INDEX.%s", dir
, extract_name(dir
));
577 /* en_US.ISO8859-1 -> en_..\.ISO8859-1 */
578 strlcpy(dialect
, lang
, sizeof(dialect
));
579 if (strlen(dialect
) >= 6 && dialect
[2] == '_') {
585 /* en_US.ISO8859-1 -> en */
586 strlcpy(lang_abk
, lang
, sizeof(lang_abk
));
587 if (strlen(lang_abk
) >= 3 && lang_abk
[2] == '_')
590 fprintf(stderr
, "lang_default = %s\n", lang_default
);
591 fprintf(stderr
, "dialect = %s\n", dialect
);
592 fprintf(stderr
, "lang_abk = %s\n", lang_abk
);
594 fp
= fopen(filename
, "r");
597 while (fgets(buffer
, sizeof(buffer
), fp
)) {
608 /* Parse input, removing newline */
609 matches
= sscanf(p
, "%64[^:]:%64[^:]:%256[^:\n]",
612 if (strcmp(keym
, "FONT") != 0 &&
613 strcmp(keym
, "MENU") != 0 &&
614 strcmp(keym
, "TITLE") != 0) {
615 /* Check file exists & is readable */
616 if (check_file(keym
) == -1)
623 * Take note of supported languages, which
624 * might be in a comma-delimited list
626 char *tmp
= strdup(lng
);
629 for (delim
= tmp
; ; ) {
631 if (ch
== ',' || ch
== '\0') {
633 if (!sl_find(lang_list
, tmp
))
634 sl_add(lang_list
, tmp
);
641 /* Set empty language to default language */
648 /* 4) Your choice if it exists
649 * 3) Long match eg. en_GB.ISO8859-1 is equal to
651 * 2) short match 'de'
652 * 1) default langlist 'en'
655 * Language may be a comma separated list
656 * A higher match overwrites a lower
657 * A later entry overwrites a previous if it exists
658 * twice in the database
661 /* Check for favoured language */
662 km
= get_keymap(keym
);
663 mark
= (km
) ? km
->mark
: 0;
665 if (find_token(lg
, lang
))
666 add_keymap(desc
, 4, keym
);
667 else if (mark
<= 3 && find_token(lg
, dialect
))
668 add_keymap(desc
, 3, keym
);
669 else if (mark
<= 2 && find_token(lg
, lang_abk
))
670 add_keymap(desc
, 2, keym
);
671 else if (mark
<= 1 && find_token(lg
, lang_default
))
672 add_keymap(desc
, 1, keym
);
674 add_keymap(desc
, 0, keym
);
679 fprintf(stderr
, "Could not open %s for reading\n", filename
);
682 qsort(lang_list
->sl_str
, lang_list
->sl_cur
, sizeof(char*),
684 printf("Currently supported languages: ");
685 for (i
=0; i
< (int) lang_list
->sl_cur
; i
++)
686 printf("%s ", lang_list
->sl_str
[i
]);
691 km
= get_keymap("TITLE");
693 /* Take note of dialog title */
694 title
= strdup(km
->desc
);
695 km
= get_keymap("MENU");
697 /* Take note of menu title */
698 menu
= strdup(km
->desc
);
699 km
= get_keymap("FONT");
701 /* Take note of language font */
702 font
= strdup(km
->desc
);
704 /* Remove unwanted items from list */
705 remove_keymap("FONT");
706 remove_keymap("MENU");
707 remove_keymap("TITLE");
709 /* Look for keymaps not in database */
712 while ((dp
= readdir(dirp
)) != NULL
) {
713 const char *ext
= get_extension(dp
->d_name
);
715 if ((!strcmp(ext
, ".fnt") ||
716 !strcmp(ext
, ".kbd")) &&
717 !get_keymap(dp
->d_name
)) {
720 /* Remove any .fnt or .kbd extension */
721 q
= strdup(dp
->d_name
);
722 *(get_extension(q
)) = '\0';
723 add_keymap(q
, 0, dp
->d_name
);
728 "'%s' not in database\n",
735 fprintf(stderr
, "Could not open directory '%s'\n", dir
);
737 /* Sort items in keymap */
738 num_keymaps
= get_num_keymaps();
740 km_sorted
= (struct keymap
**)
741 malloc(num_keymaps
*sizeof(struct keymap
*));
743 /* Make array of pointers to items in hash */
745 SLIST_FOREACH(km
, &head
, entries
)
746 km_sorted
[items
++] = km
;
748 /* Change '8x8' to '8x08' so sort works as we might expect... */
749 kludge_desc(km_sorted
, num_keymaps
);
751 qsort(km_sorted
, num_keymaps
, sizeof(struct keymap
*), compare_keymap
);
753 /* ...change back again */
754 unkludge_desc(km_sorted
, num_keymaps
);
757 for (i
=0; i
<num_keymaps
; i
++)
758 printf("%s\n", km_sorted
[i
]->desc
);
762 show_dialog(km_sorted
, num_keymaps
);
768 * Display usage information and exit
774 fprintf(stderr
, "usage: %s\t[-K] [-V] [-d|-default] [-h|-help] "
775 "[-l|-lang language]\n\t\t[-p|-print] [-r|-restore] [-s|-show] "
776 "[-v|-verbose]\n", program
);
781 parse_args(int argc
, char **argv
)
785 for (i
=1; i
<argc
; i
++) {
786 if (argv
[i
][0] != '-')
788 else if (!strcmp(argv
[i
], "-help") || !strcmp(argv
[i
], "-h"))
790 else if (!strcmp(argv
[i
], "-verbose") || !strcmp(argv
[i
], "-v"))
792 else if (!strcmp(argv
[i
], "-lang") || !strcmp(argv
[i
], "-l"))
797 else if (!strcmp(argv
[i
], "-default") || !strcmp(argv
[i
], "-d"))
799 else if (!strcmp(argv
[i
], "-show") || !strcmp(argv
[i
], "-s"))
801 else if (!strcmp(argv
[i
], "-print") || !strcmp(argv
[i
], "-p"))
803 else if (!strcmp(argv
[i
], "-restore") ||
804 !strcmp(argv
[i
], "-r")) {
805 vidcontrol(font_current
);
807 } else if (!strcmp(argv
[i
], "-K"))
809 else if (!strcmp(argv
[i
], "-V"))
817 * A front-end for the 'vidfont' and 'kbdmap' programs.
820 main(int argc
, char **argv
)
823 x11
= system("kbdcontrol -d >/dev/null");
826 fprintf(stderr
, "You are not on a virtual console - "
827 "expect certain strange side-effects\n");
831 using_vt
= check_vt();
833 keymapdir
= DEFAULT_SC_KEYMAP_DIR
;
834 fontdir
= DEFAULT_SC_FONT_DIR
;
835 font_default
= DEFAULT_SC_FONT
;
842 program
= extract_name(argv
[0]);
844 font_current
= get_font();
845 if (font_current
== NULL
)
846 font_current
= font_default
;
848 if (strcmp(program
, "kbdmap"))
853 /* Parse command line arguments */
854 parse_args(argc
, argv
);
856 /* Read and display options */