gtk2/gtk/gtkcomposetable.c
Naïm Favier 1284a804f1
composetable: invalidate cache based on symlink mtime too
When the compose file is a symbolic link, take the link itself's
modification time into account (in addition to its target's) in
determining whether to invalidate the compose cache.

This is useful e.g. on NixOS systems where the compose file might point
to a store path with an irrelevant modification time, and we want the
cache to expire when the symlink itself changes.

(cherry picked from commit 22f5236943)
2021-11-16 18:30:02 +01:00

1292 lines
34 KiB
C

/* GTK - The GIMP Toolkit
* Copyright (C) 2015 Takao Fujiwara <takao.fujiwara1@gmail.com>
*
* 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 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, see <http://www.gnu.org/licenses/>.
*/
#include <gdk/gdk.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include "gtkcomposetable.h"
#include "gtkimcontextsimple.h"
#define GTK_COMPOSE_TABLE_MAGIC "GtkComposeTable"
#define GTK_COMPOSE_TABLE_VERSION (2)
/* Maximum length of sequences we parse */
#define MAX_COMPOSE_LEN 20
typedef struct {
gunichar *sequence;
char *value;
} GtkComposeData;
static void
gtk_compose_data_free (GtkComposeData *compose_data)
{
g_free (compose_data->sequence);
g_free (compose_data->value);
g_slice_free (GtkComposeData, compose_data);
}
static void
gtk_compose_list_element_free (GtkComposeData *compose_data, gpointer data)
{
gtk_compose_data_free (compose_data);
}
static gboolean
is_codepoint (const char *str)
{
int i;
/* 'U' is not code point but 'U00C0' is code point */
if (str[0] == '\0' || str[0] != 'U' || str[1] == '\0')
return FALSE;
for (i = 1; str[i] != '\0'; i++)
{
if (!g_ascii_isxdigit (str[i]))
return FALSE;
}
return TRUE;
}
static gboolean
parse_compose_value (GtkComposeData *compose_data,
const char *val,
const char *line)
{
const char *p;
GString *value;
gunichar ch;
char *endp;
if (val[0] != '"')
{
g_warning ("Only strings supported after ':': %s: %s", val, line);
goto fail;
}
value = g_string_new ("");
p = val + 1;
while (*p)
{
if (*p == '\0')
{
g_warning ("Missing closing '\"': %s: %s", val, line);
goto fail;
}
else if (*p == '\"')
{
break;
}
else if (*p == '\\')
{
if (p[1] == '"')
{
g_string_append_c (value, '"');
p += 2;
}
else if (p[1] == '\\')
{
g_string_append_c (value, '\\');
p += 2;
}
else if (p[1] >= '0' && p[1] < '8')
{
ch = g_ascii_strtoll (p + 1, &endp, 8);
if (ch == 0)
{
g_warning ("Invalid escape sequence: %s: %s", val, line);
goto fail;
}
g_string_append_unichar (value, ch);
p = endp;
}
else if (p[1] == 'x' || p[1] == 'X')
{
ch = g_ascii_strtoll (p + 2, &endp, 16);
if (ch == 0)
{
g_warning ("Invalid escape sequence: %s: %s", val, line);
goto fail;
}
g_string_append_unichar (value, ch);
p = endp;
}
else
{
g_warning ("Invalid escape sequence: %s: %s", val, line);
goto fail;
}
}
else
{
ch = g_utf8_get_char (p);
g_string_append_unichar (value, ch);
p = g_utf8_next_char (p);
}
}
compose_data->value = g_string_free (value, FALSE);
return TRUE;
fail:
return FALSE;
}
static gboolean
parse_compose_sequence (GtkComposeData *compose_data,
const char *seq,
const char *line)
{
char **words = g_strsplit (seq, "<", -1);
int i;
int n = 0;
if (g_strv_length (words) < 2)
{
g_warning ("key sequence format is <a> <b>...: %s", line);
goto fail;
}
for (i = 1; words[i] != NULL; i++)
{
char *start = words[i];
char *end = strchr (words[i], '>');
char *match;
gunichar codepoint;
if (words[i][0] == '\0')
continue;
if (start == NULL || end == NULL || end <= start)
{
g_warning ("key sequence format is <a> <b>...: %s", line);
goto fail;
}
match = g_strndup (start, end - start);
if (compose_data->sequence == NULL)
compose_data->sequence = g_malloc (sizeof (gunichar) * 2);
else
compose_data->sequence = g_realloc (compose_data->sequence, sizeof (gunichar) * (n + 2));
if (is_codepoint (match))
{
codepoint = (gunichar) g_ascii_strtoll (match + 1, NULL, 16);
compose_data->sequence[n] = codepoint;
compose_data->sequence[n + 1] = 0;
}
else
{
codepoint = (gunichar) gdk_keyval_from_name (match);
compose_data->sequence[n] = codepoint;
compose_data->sequence[n + 1] = 0;
}
if (codepoint == GDK_KEY_VoidSymbol)
g_warning ("Could not get code point of keysym %s", match);
g_free (match);
n++;
}
g_strfreev (words);
if (0 == n || n > MAX_COMPOSE_LEN)
{
g_warning ("Suspicious compose sequence length (%d). Are you sure this is right?: %s",
n, line);
return FALSE;
}
return TRUE;
fail:
g_strfreev (words);
return FALSE;
}
static void
parse_compose_line (GList **compose_list,
const char *line)
{
char **components = NULL;
GtkComposeData *compose_data = NULL;
if (line[0] == '\0' || line[0] == '#')
return;
if (g_str_has_prefix (line, "include "))
return;
components = g_strsplit (line, ":", 2);
if (components[1] == NULL)
{
g_warning ("No delimiter ':': %s", line);
goto fail;
}
compose_data = g_slice_new0 (GtkComposeData);
if (!parse_compose_sequence (compose_data, g_strstrip (components[0]), line))
goto fail;
if (!parse_compose_value (compose_data, g_strstrip (components[1]), line))
goto fail;
g_strfreev (components);
*compose_list = g_list_append (*compose_list, compose_data);
return;
fail:
g_strfreev (components);
if (compose_data)
gtk_compose_data_free (compose_data);
}
extern const GtkComposeTableCompact gtk_compose_table_compact;
static GList *
gtk_compose_list_parse_file (const char *compose_file)
{
char *contents = NULL;
char **lines = NULL;
gsize length = 0;
GError *error = NULL;
GList *compose_list = NULL;
int i;
if (!g_file_get_contents (compose_file, &contents, &length, &error))
{
g_warning ("%s", error->message);
g_error_free (error);
return NULL;
}
lines = g_strsplit (contents, "\n", -1);
g_free (contents);
for (i = 0; lines[i] != NULL; i++)
parse_compose_line (&compose_list, lines[i]);
g_strfreev (lines);
return compose_list;
}
static GList *
gtk_compose_list_check_duplicated (GList *compose_list)
{
GList *list;
GList *removed_list = NULL;
GtkComposeData *compose_data;
for (list = compose_list; list != NULL; list = list->next)
{
static guint16 keysyms[MAX_COMPOSE_LEN + 1];
int i;
int n_compose = 0;
gboolean compose_finish;
gunichar output_char;
char buf[8] = { 0, };
compose_data = list->data;
for (i = 0; i < MAX_COMPOSE_LEN + 1; i++)
keysyms[i] = 0;
for (i = 0; i < MAX_COMPOSE_LEN + 1; i++)
{
gunichar codepoint = compose_data->sequence[i];
keysyms[i] = (guint16) codepoint;
if (codepoint == 0)
break;
n_compose++;
}
if (gtk_compose_table_compact_check (&gtk_compose_table_compact,
keysyms, n_compose,
&compose_finish,
NULL,
&output_char) &&
compose_finish)
{
g_unichar_to_utf8 (output_char, buf);
if (strcmp (compose_data->value, buf) == 0)
removed_list = g_list_prepend (removed_list, compose_data);
}
else if (gtk_check_algorithmically (keysyms, n_compose, &output_char))
{
g_unichar_to_utf8 (output_char, buf);
if (strcmp (compose_data->value, buf) == 0)
removed_list = g_list_prepend (removed_list, compose_data);
}
}
for (list = removed_list; list != NULL; list = list->next)
{
compose_data = list->data;
compose_list = g_list_remove (compose_list, compose_data);
gtk_compose_data_free (compose_data);
}
g_list_free (removed_list);
return compose_list;
}
static GList *
gtk_compose_list_check_uint16 (GList *compose_list)
{
GList *list;
GList *removed_list = NULL;
GtkComposeData *compose_data;
for (list = compose_list; list != NULL; list = list->next)
{
int i;
compose_data = list->data;
for (i = 0; i < MAX_COMPOSE_LEN; i++)
{
gunichar codepoint = compose_data->sequence[i];
if (codepoint == 0)
break;
if (codepoint > 0xffff)
{
removed_list = g_list_prepend (removed_list, compose_data);
break;
}
}
}
for (list = removed_list; list != NULL; list = list->next)
{
compose_data = list->data;
compose_list = g_list_remove (compose_list, compose_data);
gtk_compose_data_free (compose_data);
}
g_list_free (removed_list);
return compose_list;
}
static GList *
gtk_compose_list_format_for_gtk (GList *compose_list,
int *p_max_compose_len,
int *p_n_index_stride)
{
GList *list;
GtkComposeData *compose_data;
int max_compose_len = 0;
int i;
gunichar codepoint;
for (list = compose_list; list != NULL; list = list->next)
{
compose_data = list->data;
for (i = 0; i < MAX_COMPOSE_LEN + 1; i++)
{
codepoint = compose_data->sequence[i];
if (codepoint == 0)
{
if (max_compose_len < i)
max_compose_len = i;
break;
}
}
}
if (p_max_compose_len)
*p_max_compose_len = max_compose_len;
if (p_n_index_stride)
*p_n_index_stride = max_compose_len + 2;
return compose_list;
}
static int
gtk_compose_data_compare (gpointer a,
gpointer b,
gpointer data)
{
GtkComposeData *compose_data_a = a;
GtkComposeData *compose_data_b = b;
int max_compose_len = GPOINTER_TO_INT (data);
int i;
for (i = 0; i < max_compose_len; i++)
{
gunichar code_a = compose_data_a->sequence[i];
gunichar code_b = compose_data_b->sequence[i];
if (code_a != code_b)
return code_a - code_b;
}
return 0;
}
/* Implemented from g_str_hash() */
static guint32
gtk_compose_table_data_hash (gconstpointer v, int length)
{
const guint16 *p, *head;
unsigned char c;
guint32 h = 5381;
for (p = v, head = v; (p - head) < length; p++)
{
c = 0x00ff & (*p >> 8);
h = (h << 5) + h + c;
c = 0x00ff & *p;
h = (h << 5) + h + c;
}
return h;
}
static char *
gtk_compose_hash_get_cache_path (guint32 hash)
{
char *basename = NULL;
char *dir = NULL;
char *path = NULL;
basename = g_strdup_printf ("%08x.cache", hash);
dir = g_build_filename (g_get_user_cache_dir (), "gtk-3.0", "compose", NULL);
path = g_build_filename (dir, basename, NULL);
if (g_mkdir_with_parents (dir, 0755) != 0)
{
g_warning ("Failed to mkdir %s", dir);
g_free (path);
path = NULL;
}
g_free (dir);
g_free (basename);
return path;
}
static char *
gtk_compose_table_serialize (GtkComposeTable *compose_table,
gsize *count)
{
char *p, *contents;
gsize length, total_length;
guint16 bytes;
const char *header = GTK_COMPOSE_TABLE_MAGIC;
const guint16 version = GTK_COMPOSE_TABLE_VERSION;
guint16 max_seq_len = compose_table->max_seq_len;
guint16 index_stride = max_seq_len + 2;
guint16 n_seqs = compose_table->n_seqs;
guint16 n_chars = compose_table->n_chars;
guint32 i;
g_return_val_if_fail (compose_table != NULL, NULL);
g_return_val_if_fail (max_seq_len > 0, NULL);
g_return_val_if_fail (index_stride > 0, NULL);
length = strlen (header);
total_length = length + sizeof (guint16) * (4 + index_stride * n_seqs) + n_chars;
if (count)
*count = total_length;
p = contents = g_malloc (total_length);
memcpy (p, header, length);
p += length;
#define APPEND_GUINT16(elt) \
bytes = GUINT16_TO_BE (elt); \
memcpy (p, &bytes, sizeof (guint16)); \
p += sizeof (guint16);
APPEND_GUINT16 (version);
APPEND_GUINT16 (max_seq_len);
APPEND_GUINT16 (n_seqs);
APPEND_GUINT16 (n_chars);
for (i = 0; i < (guint32) index_stride * n_seqs; i++)
{
APPEND_GUINT16 (compose_table->data[i]);
}
if (compose_table->n_chars > 0)
memcpy (p, compose_table->char_data, compose_table->n_chars);
#undef APPEND_GUINT16
return contents;
}
static int
gtk_compose_table_find (gconstpointer data1,
gconstpointer data2)
{
const GtkComposeTable *compose_table = (const GtkComposeTable *) data1;
guint32 hash = (guint32) GPOINTER_TO_INT (data2);
return compose_table->id != hash;
}
static GtkComposeTable *
gtk_compose_table_load_cache (const char *compose_file)
{
guint32 hash;
char *path = NULL;
char *contents = NULL;
char *p;
GStatBuf original_buf;
GStatBuf cache_buf;
gsize total_length;
GError *error = NULL;
guint16 bytes;
guint16 version;
guint16 max_seq_len;
guint16 index_stride;
guint16 n_seqs;
guint16 n_chars;
guint32 i;
guint16 *gtk_compose_seqs = NULL;
GtkComposeTable *retval;
char *char_data = NULL;
hash = g_str_hash (compose_file);
if ((path = gtk_compose_hash_get_cache_path (hash)) == NULL)
return NULL;
if (!g_file_test (path, G_FILE_TEST_EXISTS))
goto out_load_cache;
g_stat (path, &cache_buf);
g_lstat (compose_file, &original_buf);
if (original_buf.st_mtime > cache_buf.st_mtime)
goto out_load_cache;
g_stat (compose_file, &original_buf);
if (original_buf.st_mtime > cache_buf.st_mtime)
goto out_load_cache;
if (!g_file_get_contents (path, &contents, &total_length, &error))
{
g_warning ("Failed to get cache content %s: %s", path, error->message);
g_error_free (error);
goto out_load_cache;
}
#define GET_GUINT16(elt) \
memcpy (&bytes, p, sizeof (guint16)); \
elt = GUINT16_FROM_BE (bytes); \
p += sizeof (guint16);
p = contents;
if (g_ascii_strncasecmp (p, GTK_COMPOSE_TABLE_MAGIC,
strlen (GTK_COMPOSE_TABLE_MAGIC)) != 0)
{
g_warning ("The file is not a GtkComposeTable cache file %s", path);
goto out_load_cache;
}
p += strlen (GTK_COMPOSE_TABLE_MAGIC);
if (p - contents > total_length)
{
g_warning ("Broken cache content %s at head", path);
goto out_load_cache;
}
GET_GUINT16 (version);
if (version != GTK_COMPOSE_TABLE_VERSION)
{
g_warning ("cache version is different %u != %u",
version, GTK_COMPOSE_TABLE_VERSION);
goto out_load_cache;
}
GET_GUINT16 (max_seq_len);
GET_GUINT16 (n_seqs);
GET_GUINT16 (n_chars);
if (max_seq_len == 0 || n_seqs == 0)
{
g_warning ("cache size is not correct %d %d", max_seq_len, n_seqs);
goto out_load_cache;
}
index_stride = max_seq_len + 2;
gtk_compose_seqs = g_new0 (guint16, n_seqs * index_stride);
for (i = 0; i < (guint32) index_stride * n_seqs; i++)
{
GET_GUINT16 (gtk_compose_seqs[i]);
}
if (n_chars > 0)
{
char_data = g_new (char, n_chars + 1);
memcpy (char_data, p, n_chars);
char_data[n_chars] = '\0';
}
retval = g_new0 (GtkComposeTable, 1);
retval->data = gtk_compose_seqs;
retval->max_seq_len = max_seq_len;
retval->n_seqs = n_seqs;
retval->char_data = char_data;
retval->n_chars = n_chars;
retval->id = hash;
g_free (contents);
g_free (path);
return retval;
#undef GET_GUINT16
out_load_cache:
g_free (gtk_compose_seqs);
g_free (char_data);
g_free (contents);
g_free (path);
return NULL;
}
static void
gtk_compose_table_save_cache (GtkComposeTable *compose_table)
{
char *path = NULL;
char *contents = NULL;
GError *error = NULL;
gsize length = 0;
if ((path = gtk_compose_hash_get_cache_path (compose_table->id)) == NULL)
return;
contents = gtk_compose_table_serialize (compose_table, &length);
if (contents == NULL)
{
g_warning ("Failed to serialize compose table %s", path);
goto out_save_cache;
}
if (!g_file_set_contents (path, contents, length, &error))
{
g_warning ("Failed to save compose table %s: %s", path, error->message);
g_error_free (error);
goto out_save_cache;
}
out_save_cache:
g_free (contents);
g_free (path);
}
static GtkComposeTable *
gtk_compose_table_new_with_list (GList *compose_list,
int max_compose_len,
int n_index_stride,
guint32 hash)
{
guint length;
guint n = 0;
int i, j;
guint16 *gtk_compose_seqs = NULL;
GList *list;
GtkComposeData *compose_data;
GtkComposeTable *retval = NULL;
gunichar codepoint;
GString *char_data;
g_return_val_if_fail (compose_list != NULL, NULL);
length = g_list_length (compose_list);
gtk_compose_seqs = g_new0 (guint16, length * n_index_stride);
char_data = g_string_new ("");
for (list = compose_list; list != NULL; list = list->next)
{
compose_data = list->data;
for (i = 0; i < max_compose_len; i++)
{
if (compose_data->sequence[i] == 0)
{
for (j = i; j < max_compose_len; j++)
gtk_compose_seqs[n++] = 0;
break;
}
gtk_compose_seqs[n++] = (guint16) compose_data->sequence[i];
}
if (g_utf8_strlen (compose_data->value, -1) > 1)
{
if (char_data->len > 0)
g_string_append_c (char_data, 0);
codepoint = char_data->len | (1 << 31);
g_string_append (char_data, compose_data->value);
}
else
{
codepoint = g_utf8_get_char (compose_data->value);
g_assert ((codepoint & (1 << 31)) == 0);
}
gtk_compose_seqs[n++] = (codepoint & 0xffff0000) >> 16;
gtk_compose_seqs[n++] = codepoint & 0xffff;
}
retval = g_new0 (GtkComposeTable, 1);
retval->data = gtk_compose_seqs;
retval->max_seq_len = max_compose_len;
retval->n_seqs = length;
retval->id = hash;
retval->n_chars = char_data->len;
retval->char_data = g_string_free (char_data, FALSE);
return retval;
}
GtkComposeTable *
gtk_compose_table_new_with_file (const char *compose_file)
{
GList *compose_list = NULL;
GtkComposeTable *compose_table;
int max_compose_len = 0;
int n_index_stride = 0;
g_assert (compose_file != NULL);
compose_list = gtk_compose_list_parse_file (compose_file);
if (compose_list == NULL)
return NULL;
compose_list = gtk_compose_list_check_duplicated (compose_list);
compose_list = gtk_compose_list_check_uint16 (compose_list);
compose_list = gtk_compose_list_format_for_gtk (compose_list,
&max_compose_len,
&n_index_stride);
compose_list = g_list_sort_with_data (compose_list,
(GCompareDataFunc) gtk_compose_data_compare,
GINT_TO_POINTER (max_compose_len));
if (compose_list == NULL)
{
g_warning ("compose file %s does not include any keys besides keys in en-us compose file", compose_file);
return NULL;
}
compose_table = gtk_compose_table_new_with_list (compose_list,
max_compose_len,
n_index_stride,
g_str_hash (compose_file));
g_list_free_full (compose_list, (GDestroyNotify) gtk_compose_list_element_free);
return compose_table;
}
GSList *
gtk_compose_table_list_add_array (GSList *compose_tables,
const guint16 *data,
int max_seq_len,
int n_seqs)
{
guint32 hash;
GtkComposeTable *compose_table;
gsize n_index_stride;
gsize length;
int i;
guint16 *gtk_compose_seqs = NULL;
g_return_val_if_fail (data != NULL, compose_tables);
g_return_val_if_fail (max_seq_len >= 0, compose_tables);
g_return_val_if_fail (n_seqs >= 0, compose_tables);
n_index_stride = max_seq_len + 2;
if (!g_size_checked_mul (&length, n_index_stride, n_seqs))
{
g_critical ("Overflow in the compose sequences");
return compose_tables;
}
hash = gtk_compose_table_data_hash (data, length);
if (g_slist_find_custom (compose_tables, GINT_TO_POINTER (hash), gtk_compose_table_find) != NULL)
return compose_tables;
gtk_compose_seqs = g_new0 (guint16, length);
for (i = 0; i < length; i++)
gtk_compose_seqs[i] = data[i];
compose_table = g_new (GtkComposeTable, 1);
compose_table->data = gtk_compose_seqs;
compose_table->max_seq_len = max_seq_len;
compose_table->n_seqs = n_seqs;
compose_table->id = hash;
compose_table->char_data = NULL;
compose_table->n_chars = 0;
return g_slist_prepend (compose_tables, compose_table);
}
GSList *
gtk_compose_table_list_add_file (GSList *compose_tables,
const char *compose_file)
{
guint32 hash;
GtkComposeTable *compose_table;
g_return_val_if_fail (compose_file != NULL, compose_tables);
hash = g_str_hash (compose_file);
if (g_slist_find_custom (compose_tables, GINT_TO_POINTER (hash), gtk_compose_table_find) != NULL)
return compose_tables;
compose_table = gtk_compose_table_load_cache (compose_file);
if (compose_table != NULL)
return g_slist_prepend (compose_tables, compose_table);
if ((compose_table = gtk_compose_table_new_with_file (compose_file)) == NULL)
return compose_tables;
gtk_compose_table_save_cache (compose_table);
return g_slist_prepend (compose_tables, compose_table);
}
static int
compare_seq (const void *key, const void *value)
{
int i = 0;
const guint16 *keysyms = key;
const guint16 *seq = value;
while (keysyms[i])
{
if (keysyms[i] < seq[i])
return -1;
else if (keysyms[i] > seq[i])
return 1;
i++;
}
return 0;
}
/*
* gtk_compose_table_check:
* @table: the table to check
* @compose_buffer: the key vals to match
* @n_compose: number of non-zero key vals in @compose_buffer
* @compose_finish: (out): return location for whether there may be longer matches
* @compose_match: (out): return location for whether there is a match
* @output: (out) (caller-allocates): return location for the match values
*
* Looks for matches for a key sequence in @table.
*
* Returns: %TRUE if there were any matches, %FALSE otherwise
*/
gboolean
gtk_compose_table_check (const GtkComposeTable *table,
const guint16 *compose_buffer,
int n_compose,
gboolean *compose_finish,
gboolean *compose_match,
GString *output)
{
int row_stride = table->max_seq_len + 2;
guint16 *seq;
*compose_finish = FALSE;
*compose_match = FALSE;
g_string_set_size (output, 0);
/* Will never match, if the sequence in the compose buffer is longer
* than the sequences in the table. Further, compare_seq (key, val)
* will overrun val if key is longer than val.
*/
if (n_compose > table->max_seq_len)
return FALSE;
seq = bsearch (compose_buffer,
table->data, table->n_seqs,
sizeof (guint16) * row_stride,
compare_seq);
if (seq)
{
guint16 *prev_seq;
/* Back up to the first sequence that matches to make sure
* we find the exact match if there is one.
*/
while (seq > table->data)
{
prev_seq = seq - row_stride;
if (compare_seq (compose_buffer, prev_seq) != 0)
break;
seq = prev_seq;
}
if (n_compose == table->max_seq_len ||
seq[n_compose] == 0) /* complete sequence */
{
guint16 *next_seq;
gunichar value;
value = (seq[table->max_seq_len] << 16) | seq[table->max_seq_len + 1];
if ((value & (1 << 31)) != 0)
g_string_append (output, &table->char_data[value & ~(1 << 31)]);
else
g_string_append_unichar (output, value);
*compose_match = TRUE;
/* We found a tentative match. See if there are any longer
* sequences containing this subsequence
*/
next_seq = seq + row_stride;
if (next_seq < table->data + row_stride * table->n_seqs)
{
if (compare_seq (compose_buffer, next_seq) == 0)
return TRUE;
}
*compose_finish = TRUE;
return TRUE;
}
return TRUE;
}
return FALSE;
}
static int
compare_seq_index (const void *key, const void *value)
{
const guint16 *keysyms = key;
const guint16 *seq = value;
if (keysyms[0] < seq[0])
return -1;
else if (keysyms[0] > seq[0])
return 1;
return 0;
}
gboolean
gtk_compose_table_compact_check (const GtkComposeTableCompact *table,
const guint16 *compose_buffer,
int n_compose,
gboolean *compose_finish,
gboolean *compose_match,
gunichar *output_char)
{
int row_stride;
guint16 *seq_index;
guint16 *seq;
int i;
gboolean match;
gunichar value;
if (compose_finish)
*compose_finish = FALSE;
if (compose_match)
*compose_match = FALSE;
if (output_char)
*output_char = 0;
/* Will never match, if the sequence in the compose buffer is longer
* than the sequences in the table. Further, compare_seq (key, val)
* will overrun val if key is longer than val.
*/
if (n_compose > table->max_seq_len)
return FALSE;
seq_index = bsearch (compose_buffer,
table->data,
table->n_index_size,
sizeof (guint16) * table->n_index_stride,
compare_seq_index);
if (!seq_index)
return FALSE;
if (n_compose == 1)
return TRUE;
seq = NULL;
match = FALSE;
value = 0;
for (i = n_compose - 1; i < table->max_seq_len; i++)
{
row_stride = i + 1;
if (seq_index[i + 1] - seq_index[i] > 0)
{
seq = bsearch (compose_buffer + 1,
table->data + seq_index[i],
(seq_index[i + 1] - seq_index[i]) / row_stride,
sizeof (guint16) * row_stride,
compare_seq);
if (seq)
{
if (i == n_compose - 1)
{
value = seq[row_stride - 1];
match = TRUE;
}
else
{
if (output_char)
*output_char = value;
if (match)
{
if (compose_match)
*compose_match = TRUE;
}
return TRUE;
}
}
}
}
if (match)
{
if (compose_match)
*compose_match = TRUE;
if (compose_finish)
*compose_finish = TRUE;
if (output_char)
*output_char = value;
return TRUE;
}
return FALSE;
}
/* Checks if a keysym is a dead key.
* Dead key keysym values are defined in ../gdk/gdkkeysyms.h and the
* first is GDK_KEY_dead_grave. As X.Org is updated, more dead keys
* are added and we need to update the upper limit.
*/
#define IS_DEAD_KEY(k) \
((k) >= GDK_KEY_dead_grave && (k) <= GDK_KEY_dead_greek)
/* This function receives a sequence of Unicode characters and tries to
* normalize it (NFC). We check for the case where the resulting string
* has length 1 (single character).
* NFC normalisation normally rearranges diacritic marks, unless these
* belong to the same Canonical Combining Class.
* If they belong to the same canonical combining class, we produce all
* permutations of the diacritic marks, then attempt to normalize.
*/
static gboolean
check_normalize_nfc (gunichar *combination_buffer,
int n_compose)
{
gunichar *combination_buffer_temp;
char *combination_utf8_temp = NULL;
char *nfc_temp = NULL;
int n_combinations;
gunichar temp_swap;
int i;
combination_buffer_temp = g_alloca (n_compose * sizeof (gunichar));
n_combinations = 1;
for (i = 1; i < n_compose; i++)
n_combinations *= i;
/* Xorg reuses dead_tilde for the perispomeni diacritic mark.
* We check if base character belongs to Greek Unicode block,
* and if so, we replace tilde with perispomeni.
*/
if (combination_buffer[0] >= 0x390 && combination_buffer[0] <= 0x3FF)
{
for (i = 1; i < n_compose; i++ )
if (combination_buffer[i] == 0x303)
combination_buffer[i] = 0x342;
}
memcpy (combination_buffer_temp, combination_buffer, n_compose * sizeof (gunichar) );
for (i = 0; i < n_combinations; i++)
{
g_unicode_canonical_ordering (combination_buffer_temp, n_compose);
combination_utf8_temp = g_ucs4_to_utf8 (combination_buffer_temp, n_compose, NULL, NULL, NULL);
nfc_temp = g_utf8_normalize (combination_utf8_temp, -1, G_NORMALIZE_NFC);
if (g_utf8_strlen (nfc_temp, -1) == 1)
{
memcpy (combination_buffer, combination_buffer_temp, n_compose * sizeof (gunichar) );
g_free (combination_utf8_temp);
g_free (nfc_temp);
return TRUE;
}
g_free (combination_utf8_temp);
g_free (nfc_temp);
if (n_compose > 2)
{
temp_swap = combination_buffer_temp[i % (n_compose - 1) + 1];
combination_buffer_temp[i % (n_compose - 1) + 1] = combination_buffer_temp[(i+1) % (n_compose - 1) + 1];
combination_buffer_temp[(i+1) % (n_compose - 1) + 1] = temp_swap;
}
else
break;
}
return FALSE;
}
gboolean
gtk_check_algorithmically (const guint16 *compose_buffer,
int n_compose,
gunichar *output_char)
{
int i;
gunichar *combination_buffer;
char *combination_utf8, *nfc;
combination_buffer = alloca (sizeof (gunichar) * (n_compose + 1));
if (output_char)
*output_char = 0;
for (i = 0; i < n_compose && IS_DEAD_KEY (compose_buffer[i]); i++)
;
/* Allow at most 2 dead keys */
if (i > 2)
return FALSE;
/* Can't combine if there's no base character */
if (i == n_compose)
return TRUE;
if (i > 0 && i == n_compose - 1)
{
combination_buffer[0] = gdk_keyval_to_unicode (compose_buffer[i]);
combination_buffer[n_compose] = 0;
i--;
while (i >= 0)
{
switch (compose_buffer[i])
{
#define CASE(keysym, unicode) \
case GDK_KEY_dead_##keysym: combination_buffer[i+1] = unicode; break
CASE (grave, 0x0300);
CASE (acute, 0x0301);
CASE (circumflex, 0x0302);
CASE (tilde, 0x0303); /* Also used with perispomeni, 0x342. */
CASE (macron, 0x0304);
CASE (breve, 0x0306);
CASE (abovedot, 0x0307);
CASE (diaeresis, 0x0308);
CASE (abovering, 0x30A);
CASE (hook, 0x0309);
CASE (doubleacute, 0x030B);
CASE (caron, 0x030C);
CASE (cedilla, 0x0327);
CASE (ogonek, 0x0328); /* Legacy use for dasia, 0x314.*/
CASE (iota, 0x0345);
CASE (voiced_sound, 0x3099); /* Per Markus Kuhn keysyms.txt file. */
CASE (semivoiced_sound, 0x309A); /* Per Markus Kuhn keysyms.txt file. */
CASE (belowdot, 0x0323);
CASE (horn, 0x031B); /* Legacy use for psili, 0x313 (or 0x343). */
CASE (stroke, 0x335);
CASE (abovecomma, 0x0313); /* Equivalent to psili */
CASE (abovereversedcomma, 0x0314); /* Equivalent to dasia */
CASE (doublegrave, 0x30F);
CASE (belowring, 0x325);
CASE (belowmacron, 0x331);
CASE (belowcircumflex, 0x32D);
CASE (belowtilde, 0x330);
CASE (belowbreve, 0x32e);
CASE (belowdiaeresis, 0x324);
CASE (invertedbreve, 0x32f);
CASE (belowcomma, 0x326);
CASE (lowline, 0x332);
CASE (aboveverticalline, 0x30D);
CASE (belowverticalline, 0x329);
CASE (longsolidusoverlay, 0x338);
CASE (a, 0x363);
CASE (A, 0x363);
CASE (e, 0x364);
CASE (E, 0x364);
CASE (i, 0x365);
CASE (I, 0x365);
CASE (o, 0x366);
CASE (O, 0x366);
CASE (u, 0x367);
CASE (U, 0x367);
CASE (small_schwa, 0x1DEA);
CASE (capital_schwa, 0x1DEA);
#undef CASE
default:
combination_buffer[i+1] = gdk_keyval_to_unicode (compose_buffer[i]);
}
i--;
}
/* If the buffer normalizes to a single character, then modify the order
* of combination_buffer accordingly, if necessary, and return TRUE.
*/
if (check_normalize_nfc (combination_buffer, n_compose))
{
combination_utf8 = g_ucs4_to_utf8 (combination_buffer, -1, NULL, NULL, NULL);
nfc = g_utf8_normalize (combination_utf8, -1, G_NORMALIZE_NFC);
if (output_char)
*output_char = g_utf8_get_char (nfc);
g_free (combination_utf8);
g_free (nfc);
return TRUE;
}
}
return FALSE;
}