/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
/* vim:set et sts=4: */
/* IBus - The Input Bus
* Copyright (C) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
* Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
* Copyright (C) 2008-2018 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <glib/gstdio.h>
#include "ibuskeys.h"
#include "ibuskeysyms.h"
#include "ibuskeymap.h"
typedef guint KEYMAP[256][7];
/* functions prototype */
static void ibus_keymap_destroy (IBusKeymap *keymap);
static gboolean ibus_keymap_load (const gchar *name,
KEYMAP keymap);
static GHashTable *keymaps = NULL;
G_DEFINE_TYPE (IBusKeymap, ibus_keymap, IBUS_TYPE_OBJECT)
static void
ibus_keymap_class_init (IBusKeymapClass *class)
{
IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class);
object_class->destroy = (IBusObjectDestroyFunc) ibus_keymap_destroy;
}
static void
ibus_keymap_init (IBusKeymap *keymap)
{
gint i;
keymap->name = NULL;
for (i = 0; i < sizeof (keymap->keymap) / sizeof (guint); i++) {
((guint *)keymap->keymap)[i] = IBUS_KEY_VoidSymbol;
}
}
static void
ibus_keymap_destroy (IBusKeymap *keymap)
{
if (keymap->name != NULL) {
g_free (keymap->name);
keymap->name = NULL;
}
IBUS_OBJECT_CLASS (ibus_keymap_parent_class)->destroy ((IBusObject *)keymap);
}
#define SKIP_SPACE(p) \
while (*p == ' ') p++;
static gboolean
ibus_keymap_parse_line (gchar *str,
KEYMAP keymap)
{
gchar *p1, *p2, ch;
gint i;
guint keycode;
guint keysym;
const struct {
const gchar *prefix;
const gint len;
} prefix [] = {
{ "keycode ", sizeof ("keycode ") - 1 },
{ "shift keycode ", sizeof ("shift keycode ") - 1 },
{ "capslock keycode ", sizeof ("capslock keycode ") - 1 },
{ "shift capslock keycode ", sizeof ("shift capslock keycode ") - 1 },
{ "altgr keycode ", sizeof ("altgr keycode ") - 1},
{ "shift altgr keycode ", sizeof ("shift altgr keycode ") - 1},
{ "numlock keycode ", sizeof ("numlock keycode ") - 1},
};
p1 = str;
SKIP_SPACE(p1);
if (*p1 == '#')
return TRUE;
if (strncmp (p1, "include ", sizeof ("include ") - 1) == 0) {
p1 += sizeof ("include ") - 1;
for (p2 = p1; *p2 != '\n'; p2++);
*p2 = '\0';
return ibus_keymap_load (p1, keymap);
}
for (i = 0; i < sizeof (prefix) / sizeof (prefix[0]); i++) {
if (strncmp (p1, prefix[i].prefix, prefix[i].len) == 0) {
p1 += prefix[i].len;
break;
}
}
if (i >= sizeof (prefix) / sizeof (prefix[0]))
return FALSE;
keycode = (guint) strtoul (p1, &p2, 10);
if (keycode == 0 && p1 == p2)
return FALSE;
if ((int) keycode < 0 || keycode > 255)
return FALSE;
p1 = p2;
if (*p1++ != ' ')
return FALSE;
if (*p1++ != '=')
return FALSE;
if (*p1++ != ' ')
return FALSE;
for (p2 = p1; *p2 != '\n' && *p2 != ' '; p2++);
*p2 = '\0'; p2++;
keysym = ibus_keyval_from_name (p1);
if (keysym == IBUS_KEY_VoidSymbol)
return FALSE;
/* Do not assign *p1 to g_ascii_isalpha() directly for the syntax check */
if (i == 0 &&
strncmp (p2, "addupper", sizeof ("addupper") - 1) == 0 &&
(ch = *p1) && (ch >= 0) && g_ascii_isalpha (ch)) {
gchar buf[] = "a";
buf[0] = g_ascii_toupper(ch);
keymap[keycode][0] = keymap[keycode][3] = keysym;
keymap[keycode][1] = keymap[keycode][2] = ibus_keyval_from_name (buf);
}
else {
keymap[keycode][i] = keysym;
}
return TRUE;
}
static gboolean
ibus_keymap_load (const gchar *name,
KEYMAP keymap)
{
const gchar *envstr;
gchar *fname;
FILE *pf;
gchar buf[256];
gint lineno;
if ((envstr = g_getenv ("IBUS_KEYMAP_PATH")) != NULL)
fname = g_build_filename (envstr, name, NULL);
else
fname = g_build_filename (IBUS_DATA_DIR, "keymaps", name, NULL);
if (fname == NULL) {
return FALSE;
}
pf = g_fopen (fname, "r");
g_free (fname);
if (pf == NULL) {
return FALSE;
}
lineno = 0;
while (fgets (buf, sizeof (buf), pf) != NULL) {
lineno ++;
if (!ibus_keymap_parse_line (buf, keymap)) {
g_warning ("parse %s failed on %d line", name, lineno);
lineno = -1;
break;
}
}
fclose (pf);
if (lineno == -1) {
return FALSE;
}
return TRUE;
}
void
ibus_keymap_fill (KEYMAP keymap)
{
gint i;
for (i = 0; i < 256; i++) {
/* fill shift */
if (keymap[i][1] == IBUS_KEY_VoidSymbol)
keymap[i][1] = keymap[i][0];
/* fill capslock */
if (keymap[i][2] == IBUS_KEY_VoidSymbol)
keymap[i][2] = keymap[i][0];
/* fill shift capslock */
if (keymap[i][3] == IBUS_KEY_VoidSymbol)
keymap[i][3] = keymap[i][1];
/* fill altgr */
if (keymap[i][4] == IBUS_KEY_VoidSymbol)
keymap[i][4] = keymap[i][0];
/* fill shift altgr */
if (keymap[i][5] == IBUS_KEY_VoidSymbol)
keymap[i][5] = keymap[i][1];
}
}
static void
_keymap_destroy_cb (IBusKeymap *keymap,
gpointer user_data)
{
g_hash_table_remove (keymaps, keymap->name);
g_object_unref (keymap);
}
IBusKeymap *
ibus_keymap_new (const gchar *name)
{
return ibus_keymap_get (name);
}
IBusKeymap *
ibus_keymap_get (const gchar *name)
{
g_assert (name != NULL);
IBusKeymap *keymap;
if (keymaps == NULL) {
keymaps = g_hash_table_new_full (g_str_hash,
g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_object_unref);
}
keymap = (IBusKeymap *) g_hash_table_lookup (keymaps, name);
if (keymap == NULL) {
keymap = g_object_new (IBUS_TYPE_KEYMAP, NULL);
g_object_ref_sink (keymap);
if (ibus_keymap_load (name, keymap->keymap)) {
ibus_keymap_fill (keymap->keymap);
keymap->name = g_strdup (name);
g_hash_table_insert (keymaps, g_strdup (keymap->name), keymap);
g_signal_connect (keymap, "destroy", G_CALLBACK (_keymap_destroy_cb), NULL);
}
else {
g_object_unref (keymap);
keymap = NULL;
}
}
if (keymap != NULL)
g_object_ref_sink (keymap);
return keymap;
}
guint32
ibus_keymap_lookup_keysym (IBusKeymap *keymap,
guint16 keycode,
guint32 state)
{
g_assert (IBUS_IS_KEYMAP (keymap));
if (keycode < 256) {
/* numlock */
if ((state & IBUS_MOD2_MASK) &&
(keymap->keymap[keycode][6] != IBUS_KEY_VoidSymbol)) {
return keymap->keymap[keycode][6];
}
state &= IBUS_SHIFT_MASK | IBUS_LOCK_MASK | IBUS_MOD5_MASK;
switch (state) {
case 0:
return keymap->keymap[keycode][0];
case IBUS_SHIFT_MASK:
return keymap->keymap[keycode][1];
case IBUS_LOCK_MASK:
return keymap->keymap[keycode][2];
case IBUS_SHIFT_MASK | IBUS_LOCK_MASK:
return keymap->keymap[keycode][3];
case IBUS_MOD5_MASK:
case IBUS_MOD5_MASK | IBUS_LOCK_MASK:
return keymap->keymap[keycode][4];
case IBUS_MOD5_MASK | IBUS_SHIFT_MASK:
case IBUS_MOD5_MASK | IBUS_LOCK_MASK | IBUS_SHIFT_MASK:
return keymap->keymap[keycode][5];
default:
break;
}
}
return IBUS_KEY_VoidSymbol;
}