aboutsummaryrefslogtreecommitdiff
path: root/console-client/xkb/compose.c
diff options
context:
space:
mode:
Diffstat (limited to 'console-client/xkb/compose.c')
-rw-r--r--console-client/xkb/compose.c593
1 files changed, 593 insertions, 0 deletions
diff --git a/console-client/xkb/compose.c b/console-client/xkb/compose.c
new file mode 100644
index 00000000..65854e14
--- /dev/null
+++ b/console-client/xkb/compose.c
@@ -0,0 +1,593 @@
+/* compose.c -- Keysym composing
+
+ Copyright (C) 2003 Marco Gerards
+
+ Written by Marco Gerards <metgerards@student.han.nl>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <X11/keysymdef.h>
+#include "xkb.h"
+#include <ctype.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <locale.h>
+#include <assert.h>
+
+/* Tokens that can be recognised by the scanner. */
+enum tokentype
+ {
+ UNKNOWN,
+ EOL,
+ REQKS,
+ SEMICOL,
+ STR,
+ PRODKS,
+ END
+ };
+
+/* The current token. */
+struct token
+{
+ enum tokentype toktype;
+ char value[50];
+} tok;
+
+/* The linenumber of the line currently parsed, used for returning
+ errors and warnings. */
+static int linenum;
+
+/* Read a token from the file CF. */
+static void
+read_token (FILE *cf)
+{
+ int c = fgetc (cf);
+ int pos = 0;
+
+ /* Remove whitespaces. */
+ while (c == ' ' || c == '\t')
+ c = fgetc (cf);
+
+ /* Comment, remove until end of line and return a EOL token. */
+ if (c == '#')
+ {
+ while (c != '\n')
+ c = fgetc (cf);
+ tok.toktype = EOL;
+ linenum++;
+ return;
+ }
+
+ /* End of file. */
+ if (c == EOF)
+ {
+ tok.toktype = END;
+ return;
+ }
+
+ /* Semicolon. */
+ if (c == ':')
+ {
+ tok.toktype = SEMICOL;
+ return;
+ }
+
+ /* End of line. */
+ if (c == '\n')
+ {
+ linenum++;
+ tok.toktype = EOL;
+ return;
+ }
+
+
+ /* Required keysym. */
+ if (c == '<')
+ {
+ while ((c = fgetc (cf)) != '>')
+ tok.value[pos++] = c;
+ tok.value[pos] = '\0';
+ tok.toktype = REQKS;
+ return;
+ }
+
+ /* Character string. */
+ if (c == '"')
+ {
+ while ((c = fgetc (cf)) != '"')
+ {
+ if (c == '\\')
+ c = fgetc (cf);
+
+ tok.value[pos++] = c;
+ }
+ tok.value[pos] = '\0';
+ tok.toktype = STR;
+ return;
+ }
+
+ /* Produced keysym. */
+ if (isalpha (c))
+ {
+ tok.value[pos++] = c;
+ while (isgraph (c = fgetc (cf)))
+ tok.value[pos++] = c;
+ tok.value[pos] = '\0';
+ tok.toktype = PRODKS;
+ ungetc (c, cf);
+ return;
+ }
+
+ /* Unknown token. */
+ tok.toktype = UNKNOWN;
+ return;
+}
+
+/* Compose sequence. */
+struct compose
+{
+ struct compose *left;
+ struct compose *right;
+ symbol *expected;
+ symbol produced;
+} *compose_tree;
+
+
+/* Compare symbol sequence s1 to symbol sequence s1. This function
+ works just like the strcmp function. */
+static int
+symbolscmp (symbol *s1, symbol *s2)
+{
+ while (*s1 && *s2 && (*s1 == *s2))
+ {
+ s1++;s2++;
+ }
+ if (*s1 < *s2)
+ return -1;
+ if (*s1 > *s2)
+ return 1;
+ return 0;
+}
+
+/* Compare symbol sequence s1 to symbol sequence s1, compare a maximum
+ of N symbols. This function works just like the strcmp function.
+ */
+static int
+symbolsncmp (symbol *s1, symbol *s2, int n)
+{
+ int cnt = 0;
+ while (*s1 && *s2 && (*s1 == *s2))
+ {
+ if (++cnt == n)
+ break;
+ s1++;s2++;
+ }
+
+ if (*s1 < *s2)
+ return -1;
+ if (*s1 > *s2)
+ return 1;
+ return 0;
+}
+
+
+/* Add the compose sequence EXP to the binary tree, store RESULT as
+ the keysym produced by EXP. */
+static struct compose *
+composetree_add (struct compose *tree, symbol *exp, symbol result)
+{
+ int cmp;
+
+ if (tree == NULL)
+ {
+ tree = malloc (sizeof (struct compose));
+ tree->expected = exp;
+ tree->produced = result;
+ tree->left = tree->right = NULL;
+
+ return tree;
+ }
+
+ cmp = symbolscmp (exp, tree->expected);
+ if (cmp == 0)
+ {
+ printf ("Warning: line %d: Double sequence.\n", linenum);
+ free (exp);
+ }
+ else if (cmp < 0)
+ tree->left = composetree_add (tree->left, exp, result);
+ else
+ tree->right = composetree_add (tree->right, exp, result);
+ return tree;
+}
+
+/* Parse the composefile CF and put all sequences in the binary tree
+ COMPOSE_TREE. This function may never fail because of syntactical or
+ lexalical errors, generate a warning instead. */
+static error_t
+parse_composefile (FILE *cf)
+{
+ void skip_line (void)
+ {
+ while (tok.toktype != EOL && tok.toktype != END)
+ read_token (cf);
+ }
+
+ for (;;)
+ {
+ /* Expected keysyms. */
+ symbol exp[50];
+ symbol *exps;
+ size_t expcnt = 0;
+ symbol sym;
+
+ read_token (cf);
+ /* Blank line. */
+ if (tok.toktype == EOL)
+ continue;
+
+ /* End of file, done parsing. */
+ if (tok.toktype == END)
+ return 0;
+
+ if (tok.toktype != REQKS)
+ {
+ printf ("Warning: line %d: Keysym expected on beginning of line.\n",
+ linenum);
+ skip_line ();
+ continue;
+ }
+
+ /* Keysym was recognised, add it. */
+ sym = XStringToKeysym (tok.value);
+ if (!sym)
+ {
+ printf ("Warning: line %d: Unknown keysym \"%s\".\n", linenum,
+ tok.value);
+ skip_line ();
+ continue;
+ }
+ exp[expcnt++] = sym;
+
+ do
+ {
+ read_token (cf);
+ /* If another required keysym is recognised, add it. */
+ if (tok.toktype == REQKS)
+ {
+ sym = XStringToKeysym (tok.value);
+ if (!sym)
+ {
+ printf ("Warning: line %d: Unknown keysym \"%s\".\n",
+ linenum, tok.value);
+ skip_line ();
+ continue;
+ }
+ exp[expcnt++] = sym;
+ }
+ } while (tok.toktype == REQKS);
+
+ if (tok.toktype != SEMICOL)
+ {
+ printf ("Warning: line %d: Semicolon expected.\n", linenum);
+ skip_line ();
+ continue;
+ }
+
+ read_token (cf);
+ /* Force token and ignore it. */
+ if (tok.toktype != STR)
+ {
+ printf ("Warning: line %d: string expected.\n", linenum);
+ skip_line ();
+ continue;
+ }
+
+ read_token (cf);
+ if (tok.toktype != PRODKS)
+ {
+ printf ("Warning: line %d: keysym expected.\n", linenum);
+ skip_line ();
+ continue;
+ }
+ sym = XStringToKeysym (tok.value);
+ if (!sym)
+ {
+ printf ("Warning: line %d: Unknown keysym \"%s\".\n", linenum,
+ tok.value);
+ skip_line ();
+ continue;
+ }
+
+ read_token (cf);
+ if (tok.toktype != EOL && tok.toktype != END)
+ {
+ printf ("Warning: line %d: end of line or end of file expected.\n",
+ linenum);
+ skip_line ();
+ continue;
+ }
+
+ /* Add the production rule. */
+ exp[expcnt++] = 0;
+ exps = malloc (sizeof (symbol) * expcnt);
+ memcpy (exps, exp, sizeof (symbol) * expcnt);
+ compose_tree = composetree_add (compose_tree, exps, sym);
+ }
+ return 0;
+}
+
+/* Read keysyms passed to this function by S until a keysym can be
+ composed. If the first keysym cannot start a compose sequence return
+ the keysym. */
+symbol
+compose_symbols (symbol s)
+{
+ /* Current position in the compose tree. */
+ static struct compose *treepos = NULL;
+ /* Current compose sequence. */
+ static symbol syms[100];
+ /* Current position in the compose sequence. */
+ static int pos = 0;
+ int cmp;
+
+ if (!treepos)
+ treepos = compose_tree;
+
+ /* Maximum sequence length reached. Some idiot typed this many
+ symbols and now we throw it all away, wheee!!! */
+ if (pos == 99)
+ {
+ treepos = compose_tree;
+ pos = 0;
+ }
+
+ /* Put the keysym in the compose sequence array. */
+ syms[pos++] = s;
+ syms[pos] = 0;
+
+ /* Search the tree for a keysym sequence that can match the current one. */
+ while (treepos)
+ {
+ cmp = symbolsncmp (syms, treepos->expected, pos);
+ if (cmp == 0)
+ {
+ /* The keysym sequence was partially recognised, check if it
+ can completely match. */
+ if (!symbolscmp (syms, treepos->expected))
+ {
+ symbol ret = treepos->produced;
+ treepos = compose_tree;
+ pos = 0;
+ return ret;
+ }
+
+ /* The sequence was partially recognised. */
+ return -1;
+ }
+
+ if (cmp < 0)
+ treepos = treepos->left;
+ else
+ treepos = treepos->right;
+ }
+
+ /* Nothing can be found. */
+ treepos = compose_tree;
+
+ /* This ks should've started a sequence but couldn't be found,
+ just return it. */
+ if (pos == 1)
+ {
+ pos = 0;
+ return s;
+ }
+
+ debug_printf ("Invalid\n");
+ /* Invalid keysym sequence. */
+ pos = 0;
+ return -1;
+}
+
+struct map_entry
+{
+ const char *left;
+ const char *right;
+};
+
+enum callback_result
+ {
+ NEXT,
+ DONE
+ };
+
+typedef enum callback_result (*map_callback) (void *context, struct map_entry *entry);
+
+static error_t
+map_iterate(const char *map_path, map_callback action, void *context)
+{
+ FILE *map;
+ char *buffer = NULL;
+ size_t buffer_size = 0;
+ size_t line_length = 0;
+
+ assert (map_path != NULL);
+ assert (action != NULL);
+
+ map = fopen (map_path, "r");
+
+ if (map == NULL)
+ return errno;
+
+ while ( (line_length = getline (&buffer, &buffer_size, map)) != -1)
+ {
+ /* skips empty lines and comments */
+ if (line_length < 1 || buffer[0] == '#')
+ continue;
+ else
+ {
+ struct map_entry entry = {NULL, NULL};
+ char *end = buffer + line_length;
+ char *p = buffer;
+
+ while (p != end && isspace(*p)) p++;
+
+ if (p == end)
+ continue;
+
+ entry.left = p;
+
+ while (p != end && !isspace(*p)) p++;
+
+ if (p != end)
+ {
+ *(p++) = 0;
+ while (p != end && isspace(*p)) p++;
+
+ if (p != end)
+ {
+ entry.right = p;
+ while (p != end && !isspace(*p)) p++;
+ if (p != end)
+ *p = 0;
+ }
+ }
+
+ if (action (context, &entry) == DONE)
+ break;
+ }
+ }
+ free (buffer);
+ fclose (map);
+ return 0;
+}
+
+struct matcher_context
+{
+ char *value;
+ char *result;
+};
+
+static enum callback_result
+match_left_set_right (void *context, struct map_entry *entry)
+{
+ struct matcher_context *ctx = (struct matcher_context *) context;
+
+ if (strcmp (ctx->value, entry->left) == 0)
+ {
+ ctx->result = strdup (entry->right);
+ return DONE;
+ }
+ return NEXT;
+}
+
+static enum callback_result
+match_right_set_left (void *context, struct map_entry *entry)
+{
+ struct matcher_context *ctx = (struct matcher_context *) context;
+
+ if (strcmp (ctx->value, entry->right) == 0)
+ {
+ ctx->result = strdup (entry->left);
+ return DONE;
+ }
+ return NEXT;
+}
+
+/* Search for a compose file.
+
+ According to Compose(5) man page the compose file searched in the
+ following locations:
+ - XCOMPOSEFILE variable.
+ - .XCompose at $HOME.
+ - System wide compose file for the current locale. */
+static char *
+get_compose_file_for_locale()
+{
+ struct matcher_context context = { NULL };
+ char *xcomposefile;
+ char *to_be_freed;
+ char *home;
+ int err;
+
+ xcomposefile = getenv ("XCOMPOSEFILE");
+ if (xcomposefile != NULL)
+ return strdup (xcomposefile);
+
+ home = getenv ("HOME");
+ if (home != NULL)
+ {
+ err = asprintf (&xcomposefile, "%s/.XCompose", home);
+ if (err != -1)
+ {
+ if (faccessat(AT_FDCWD, xcomposefile, R_OK, AT_EACCESS) == 0)
+ return xcomposefile;
+ else
+ {
+ free (xcomposefile);
+ /* TODO: check and report whether the compose file doesn't exist or
+ read permission was not granted to us. */
+ }
+ }
+ }
+
+ context.value = setlocale (LC_ALL, NULL);
+ map_iterate (DATADIR "/X11/locale/locale.alias", match_left_set_right, &context);
+ to_be_freed = context.result;
+
+ if (context.result != NULL)
+ {
+ /* Current locale is an alias. Use the real name to index the database. */
+ context.value = context.result;
+ }
+ context.result = NULL;
+ map_iterate (DATADIR "/X11/locale/compose.dir", match_right_set_left, &context);
+ free (to_be_freed);
+
+ /* compose.dir contains relative paths to compose files. */
+ to_be_freed = context.result;
+ err = asprintf (&context.result, DATADIR "/X11/locale/%s", context.result);
+ if (err == -1)
+ context.result = NULL;
+
+ free (to_be_freed);
+ return context.result;
+}
+
+/* Read a Compose file. */
+error_t
+read_composefile (char *composefn)
+{
+ FILE *cf;
+
+ error_t err;
+
+ if (composefn == NULL)
+ composefn = get_compose_file_for_locale ();
+
+ cf = fopen (composefn, "r");
+ if (cf == NULL)
+ return errno;
+
+ err = parse_composefile (cf);
+ if (err)
+ fclose (cf);
+
+ return err;
+}