2 * Copyright (c) 2002 Jonathan Belson <jon@witchspace.com>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * $FreeBSD: head/usr.sbin/kbdmap/kbdmap.c 237257 2012-06-19 06:10:31Z eadler $
29 #include <sys/types.h>
30 #include <sys/queue.h>
39 #include <stringlist.h>
45 static const char *lang_default
= DEFAULT_LANG
;
46 static const char *font
;
47 static const char *lang
;
48 static const char *program
;
49 static const char *keymapdir
= DEFAULT_KEYMAP_DIR
;
50 static const char *fontdir
= DEFAULT_FONT_DIR
;
51 static const char *sysconfig
= DEFAULT_SYSCONFIG
;
52 static const char *font_default
= DEFAULT_FONT
;
53 static const char *font_current
;
54 static const char *dir
;
55 static const char *menu
= "";
67 SLIST_ENTRY(keymap
) entries
;
69 static SLIST_HEAD(slisthead
, keymap
) head
= SLIST_HEAD_INITIALIZER(head
);
73 * Get keymap entry for 'key', or NULL of not found
75 static struct keymap
*
76 get_keymap(const char *key
)
80 SLIST_FOREACH(km
, &head
, entries
)
81 if (!strcmp(km
->keym
, key
))
88 * Count the number of keymaps we found
96 SLIST_FOREACH(km
, &head
, entries
)
103 * Remove any keymap with given keym
106 remove_keymap(const char *keym
)
110 SLIST_FOREACH(km
, &head
, entries
) {
111 if (!strcmp(keym
, km
->keym
)) {
112 SLIST_REMOVE(&head
, km
, keymap
, entries
);
120 * Add to hash with 'key'
123 add_keymap(const char *desc
, int mark
, const char *keym
)
125 struct keymap
*km
, *km_new
;
127 /* Is there already an entry with this key? */
128 SLIST_FOREACH(km
, &head
, entries
) {
129 if (!strcmp(km
->keym
, keym
)) {
130 /* Reuse this entry */
132 km
->desc
= strdup(desc
);
138 km_new
= (struct keymap
*) malloc (sizeof(struct keymap
));
139 km_new
->desc
= strdup(desc
);
140 km_new
->keym
= strdup(keym
);
143 /* Add to keymap list */
144 SLIST_INSERT_HEAD(&head
, km_new
, entries
);
148 * Figure out the default language to use.
155 if ((locale
= getenv("LC_ALL")) == NULL
&&
156 (locale
= getenv("LC_CTYPE")) == NULL
&&
157 (locale
= getenv("LANG")) == NULL
)
158 locale
= lang_default
;
160 /* Check for alias */
161 if (!strcmp(locale
, "C"))
162 locale
= DEFAULT_LANG
;
168 * Extract filename part
171 extract_name(const char *name
)
175 p
= strrchr(name
, '/');
176 if (p
!= NULL
&& p
[1] != '\0')
183 * Return file extension or NULL
186 get_extension(const char *name
)
190 p
= strrchr(name
, '.');
192 if (p
!= NULL
&& p
[1] != '\0')
199 * Read font from /etc/rc.conf else return default.
200 * Freeing the memory is the caller's responsibility.
205 char line
[256], buf
[20];
208 FILE *fp
= fopen(sysconfig
, "r");
210 while (fgets(line
, sizeof(line
), fp
)) {
216 matches
= sscanf(line
,
217 " font%dx%d = \"%20[-.0-9a-zA-Z_]",
220 if (strcmp(buf
, "NO")) {
223 fnt
= (char *) malloc(strlen(buf
) + 1);
230 fprintf(stderr
, "Could not open %s for reading\n", sysconfig
);
236 * Set a font using 'vidcontrol'
239 vidcontrol(const char *fnt
)
245 /* syscons test failed */
251 /* Extract font size */
252 p
= strrchr(tmp
, '-');
253 if (p
&& p
[1] != '\0') {
255 /* Remove any '.fnt' extension */
256 if ((q
= strstr(p
, ".fnt")))
260 * Check font size is valid, with no trailing characters
261 * ('&ch' should not be matched)
263 if (sscanf(p
, "%dx%d%c", &i
, &i
, &ch
) != 2)
264 fprintf(stderr
, "Which font size? %s\n", fnt
);
267 asprintf(&cmd
, "vidcontrol -f %s %s", p
, fnt
);
269 fprintf(stderr
, "%s\n", cmd
);
274 fprintf(stderr
, "Which font size? %s\n", fnt
);
280 * Execute 'kbdcontrol' with the appropriate arguments
283 do_kbdcontrol(struct keymap
*km
)
286 asprintf(&kbd_cmd
, "kbdcontrol -l %s/%s", dir
, km
->keym
);
291 fprintf(stderr
, "keymap=\"%s\"\n", km
->keym
);
296 * Call 'vidcontrol' with the appropriate arguments
299 do_vidfont(struct keymap
*km
)
301 char *vid_cmd
, *tmp
, *p
, *q
;
303 asprintf(&vid_cmd
, "%s/%s", dir
, km
->keym
);
307 tmp
= strdup(km
->keym
);
308 p
= strrchr(tmp
, '-');
309 if (p
&& p
[1]!='\0') {
311 q
= get_extension(p
);
314 printf("font%s=%s\n", p
, km
->keym
);
321 * Display dialog from 'keymaps[]'
324 show_dialog(struct keymap
**km_sorted
, int num_keymaps
)
328 char tmp_name
[] = "/tmp/_kbd_lang.XXXX";
331 fd
= mkstemp(tmp_name
);
333 fprintf(stderr
, "Could not open temporary file \"%s\"\n",
337 asprintf(&dialog
, "/usr/bin/dialog --clear --title \"Keyboard Menu\" "
338 "--menu \"%s\" 0 0 0", menu
);
340 /* start right font, assume that current font is equal
341 * to default font in /etc/rc.conf
343 * $font is the font which require the language $lang; e.g.
344 * russian *need* a koi8 font
345 * $font_current is the current font from /etc/rc.conf
347 if (font
&& strcmp(font
, font_current
))
350 /* Build up the command */
352 for (i
=0; i
<num_keymaps
; i
++) {
354 * Each 'font' is passed as ' "font" ""', so allow the
357 size
+= strlen(km_sorted
[i
]->desc
) + 6;
360 /* Allow the space for '2> tmpfilename' redirection */
361 size
+= strlen(tmp_name
) + 3;
363 cmd
= (char *) malloc(strlen(dialog
) + size
+ 1);
366 for (i
=0; i
<num_keymaps
; i
++) {
368 strcat(cmd
, km_sorted
[i
]->desc
);
370 strcat(cmd
, " \"\"");
374 strcat(cmd
, tmp_name
);
376 /* Show the dialog.. */
379 fp
= fopen(tmp_name
, "r");
382 if (fgets(choice
, sizeof(choice
), fp
) != NULL
) {
383 /* Find key for desc */
384 for (i
=0; i
<num_keymaps
; i
++) {
385 if (!strcmp(choice
, km_sorted
[i
]->desc
)) {
386 if (!strcmp(program
, "kbdmap"))
387 do_kbdcontrol(km_sorted
[i
]);
389 do_vidfont(km_sorted
[i
]);
394 if (font
!= NULL
&& strcmp(font
, font_current
))
395 /* Cancelled, restore old font */
396 vidcontrol(font_current
);
400 fprintf(stderr
, "Failed to open temporary file");
410 * Search for 'token' in comma delimited array 'buffer'.
411 * Return true for found, false for not found.
414 find_token(const char *buffer
, const char *token
)
416 char *buffer_tmp
, *buffer_copy
, *inputstring
;
420 buffer_copy
= strdup(buffer
);
421 buffer_tmp
= buffer_copy
;
422 inputstring
= buffer_copy
;
427 while ((*ap
= strsep(&inputstring
, ",")) != NULL
) {
428 if (strcmp(buffer_tmp
, token
) == 0) {
440 * Compare function for qsort
443 compare_keymap(const void *a
, const void *b
)
446 /* We've been passed pointers to pointers, so: */
447 const struct keymap
*km1
= *((const struct keymap
* const *) a
);
448 const struct keymap
*km2
= *((const struct keymap
* const *) b
);
450 return strcmp(km1
->desc
, km2
->desc
);
454 * Compare function for qsort
457 compare_lang(const void *a
, const void *b
)
459 const char *l1
= *((const char * const *) a
);
460 const char *l2
= *((const char * const *) b
);
462 return strcmp(l1
, l2
);
466 * Change '8x8' to '8x08' so qsort will put it before eg. '8x14'
469 kludge_desc(struct keymap
**km_sorted
, int num_keymaps
)
473 for (i
=0; i
<num_keymaps
; i
++) {
475 char *km
= km_sorted
[i
]->desc
;
476 if ((p
= strstr(km
, "8x8")) != NULL
) {
483 /* Make enough space for the extra '0' */
485 km
= realloc(km
, len
+ 2);
487 for (j
=len
; j
!=offset
+1; j
--)
492 km_sorted
[i
]->desc
= km
;
498 * Reverse 'kludge_desc()' - change '8x08' back to '8x8'
501 unkludge_desc(struct keymap
**km_sorted
, int num_keymaps
)
505 for (i
=0; i
<num_keymaps
; i
++) {
507 char *km
= km_sorted
[i
]->desc
;
508 if ((p
= strstr(km
, "8x08")) != NULL
) {
513 km
= realloc(km
, p
- km
- 1);
514 km_sorted
[i
]->desc
= km
;
520 * Return 0 if file exists and is readable, else -1
523 check_file(const char *keym
)
527 if (access(keym
, R_OK
) == -1) {
529 asprintf(&fn
, "%s/%s", dir
, keym
);
530 if (access(fn
, R_OK
) == -1) {
532 fprintf(stderr
, "%s not found!\n", fn
);
538 fprintf(stderr
, "No read permission for %s!\n", keym
);
546 * Read options from the relevant configuration file, then
554 int mark
, num_keymaps
, items
, i
;
555 char buffer
[256], filename
[PATH_MAX
];
556 char keym
[64], lng
[64], desc
[64];
557 char dialect
[64], lang_abk
[64];
559 struct keymap
**km_sorted
;
561 StringList
*lang_list
;
565 lang_list
= sl_init();
567 sprintf(filename
, "%s/INDEX.%s", dir
, extract_name(dir
));
569 /* en_US.ISO8859-1 -> en_..\.ISO8859-1 */
570 strlcpy(dialect
, lang
, sizeof(dialect
));
571 if (strlen(dialect
) >= 6 && dialect
[2] == '_') {
577 /* en_US.ISO8859-1 -> en */
578 strlcpy(lang_abk
, lang
, sizeof(lang_abk
));
579 if (strlen(lang_abk
) >= 3 && lang_abk
[2] == '_')
582 fprintf(stderr
, "lang_default = %s\n", lang_default
);
583 fprintf(stderr
, "dialect = %s\n", dialect
);
584 fprintf(stderr
, "lang_abk = %s\n", lang_abk
);
586 fp
= fopen(filename
, "r");
589 while (fgets(buffer
, sizeof(buffer
), fp
)) {
600 /* Parse input, removing newline */
601 matches
= sscanf(p
, "%64[^:]:%64[^:]:%64[^:\n]",
604 if (strcmp(keym
, "FONT")
605 && strcmp(keym
, "MENU")) {
606 /* Check file exists & is readable */
607 if (check_file(keym
) == -1)
614 * Take note of supported languages, which
615 * might be in a comma-delimited list
617 char *tmp
= strdup(lng
);
620 for (delim
= tmp
; ; ) {
622 if (ch
== ',' || ch
== '\0') {
624 if (!sl_find(lang_list
, tmp
))
625 sl_add(lang_list
, tmp
);
632 /* Set empty language to default language */
639 /* 4) Your choice if it exists
640 * 3) Long match eg. en_GB.ISO8859-1 is equal to
642 * 2) short match 'de'
643 * 1) default langlist 'en'
646 * Language may be a comma separated list
647 * A higher match overwrites a lower
648 * A later entry overwrites a previous if it exists
649 * twice in the database
652 /* Check for favoured language */
653 km
= get_keymap(keym
);
654 mark
= (km
) ? km
->mark
: 0;
656 if (find_token(lg
, lang
))
657 add_keymap(desc
, 4, keym
);
658 else if (mark
<= 3 && find_token(lg
, dialect
))
659 add_keymap(desc
, 3, keym
);
660 else if (mark
<= 2 && find_token(lg
, lang_abk
))
661 add_keymap(desc
, 2, keym
);
662 else if (mark
<= 1 && find_token(lg
, lang_default
))
663 add_keymap(desc
, 1, keym
);
665 add_keymap(desc
, 0, keym
);
670 printf("Could not open file\n");
673 qsort(lang_list
->sl_str
, lang_list
->sl_cur
, sizeof(char*),
675 printf("Currently supported languages: ");
676 for (i
=0; i
< (int) lang_list
->sl_cur
; i
++)
677 printf("%s ", lang_list
->sl_str
[i
]);
682 km
= get_keymap("MENU");
684 /* Take note of menu title */
685 menu
= strdup(km
->desc
);
686 km
= get_keymap("FONT");
688 /* Take note of language font */
689 font
= strdup(km
->desc
);
691 /* Remove unwanted items from list */
692 remove_keymap("MENU");
693 remove_keymap("FONT");
695 /* Look for keymaps not in database */
698 while ((dp
= readdir(dirp
)) != NULL
) {
699 const char *ext
= get_extension(dp
->d_name
);
701 if ((!strcmp(ext
, ".fnt") ||
702 !strcmp(ext
, ".kbd")) &&
703 !get_keymap(dp
->d_name
)) {
706 /* Remove any .fnt or .kbd extension */
707 q
= strdup(dp
->d_name
);
708 *(get_extension(q
)) = '\0';
709 add_keymap(q
, 0, dp
->d_name
);
714 "'%s' not in database\n",
721 fprintf(stderr
, "Could not open directory '%s'\n", dir
);
723 /* Sort items in keymap */
724 num_keymaps
= get_num_keymaps();
726 km_sorted
= (struct keymap
**)
727 malloc(num_keymaps
*sizeof(struct keymap
*));
729 /* Make array of pointers to items in hash */
731 SLIST_FOREACH(km
, &head
, entries
)
732 km_sorted
[items
++] = km
;
734 /* Change '8x8' to '8x08' so sort works as we might expect... */
735 kludge_desc(km_sorted
, num_keymaps
);
737 qsort(km_sorted
, num_keymaps
, sizeof(struct keymap
*), compare_keymap
);
739 /* ...change back again */
740 unkludge_desc(km_sorted
, num_keymaps
);
743 for (i
=0; i
<num_keymaps
; i
++)
744 printf("%s\n", km_sorted
[i
]->desc
);
748 show_dialog(km_sorted
, num_keymaps
);
754 * Display usage information and exit
760 fprintf(stderr
, "usage: %s\t[-K] [-V] [-d|-default] [-h|-help] "
761 "[-l|-lang language]\n\t\t[-p|-print] [-r|-restore] [-s|-show] "
762 "[-v|-verbose]\n", program
);
767 parse_args(int argc
, char **argv
)
771 for (i
=1; i
<argc
; i
++) {
772 if (argv
[i
][0] != '-')
774 else if (!strcmp(argv
[i
], "-help") || !strcmp(argv
[i
], "-h"))
776 else if (!strcmp(argv
[i
], "-verbose") || !strcmp(argv
[i
], "-v"))
778 else if (!strcmp(argv
[i
], "-lang") || !strcmp(argv
[i
], "-l"))
783 else if (!strcmp(argv
[i
], "-default") || !strcmp(argv
[i
], "-d"))
785 else if (!strcmp(argv
[i
], "-show") || !strcmp(argv
[i
], "-s"))
787 else if (!strcmp(argv
[i
], "-print") || !strcmp(argv
[i
], "-p"))
789 else if (!strcmp(argv
[i
], "-restore") ||
790 !strcmp(argv
[i
], "-r")) {
791 vidcontrol(font_current
);
793 } else if (!strcmp(argv
[i
], "-K"))
795 else if (!strcmp(argv
[i
], "-V"))
803 * A front-end for the 'vidfont' and 'kbdmap' programs.
806 main(int argc
, char **argv
)
809 x11
= system("kbdcontrol -d >/dev/null");
812 fprintf(stderr
, "You are not on a virtual console - "
813 "expect certain strange side-effects\n");
821 program
= extract_name(argv
[0]);
823 font_current
= get_font();
824 if (font_current
== NULL
)
825 font_current
= font_default
;
827 if (strcmp(program
, "kbdmap"))
832 /* Parse command line arguments */
833 parse_args(argc
, argv
);
835 /* Read and display options */