Merge branch 'im-context-work' into 'master'

Some im context work

Closes #1004, #186, and #3521

See merge request GNOME/gtk!3143
This commit is contained in:
Matthias Clasen 2021-02-02 17:29:14 +00:00
commit e9b06b6346
23 changed files with 1084 additions and 691 deletions

View File

@ -1831,7 +1831,6 @@ GtkIMContextSimple
gtk_im_context_simple_new
gtk_im_context_simple_add_table
gtk_im_context_simple_add_compose_file
GTK_MAX_COMPOSE_LEN
<SUBSECTION Standard>
GTK_IM_CONTEXT_SIMPLE
GTK_IS_IM_CONTEXT_SIMPLE

View File

@ -26,16 +26,17 @@
#include "gtkcomposetable.h"
#include "gtkimcontextsimple.h"
#include "gtkimcontextsimpleprivate.h"
#define GTK_COMPOSE_TABLE_MAGIC "GtkComposeTable"
#define GTK_COMPOSE_TABLE_VERSION (1)
#define GTK_COMPOSE_TABLE_VERSION (2)
/* Maximum length of sequences we parse */
#define MAX_COMPOSE_LEN 20
typedef struct {
gunichar *sequence;
gunichar value[2];
char *comment;
gunichar *sequence;
char *value;
} GtkComposeData;
@ -43,7 +44,7 @@ static void
gtk_compose_data_free (GtkComposeData *compose_data)
{
g_free (compose_data->sequence);
g_free (compose_data->comment);
g_free (compose_data->value);
g_slice_free (GtkComposeData, compose_data);
}
@ -76,58 +77,82 @@ parse_compose_value (GtkComposeData *compose_data,
const char *val,
const char *line)
{
char **words = g_strsplit (val, "\"", 3);
gunichar uch;
char *word;
const char *p;
gsize len;
GString *value;
gunichar ch;
char *endp;
if (g_strv_length (words) < 3)
len = strlen (val);
if (val[0] != '"' || val[len - 1] != '"')
{
g_warning ("Need to double-quote the value: %s: %s", val, line);
goto fail;
}
uch = g_utf8_get_char (words[1]);
word = g_strndup (val + 1, len - 2);
if (uch == 0)
{
g_warning ("Invalid value: %s: %s", val, line);
goto fail;
}
else if (uch == '\\')
{
uch = words[1][1];
value = g_string_new ("");
/* The escaped string "\"" is separated with '\\' and '"'. */
if (uch == '\0' && words[2][0] == '"')
uch = '"';
/* The escaped octal */
else if (uch >= '0' && uch <= '8')
uch = g_ascii_strtoll(words[1] + 1, NULL, 8);
/* If we need to handle other escape sequences. */
else if (uch != '\\')
p = word;
while (*p)
{
if (*p == '\\')
{
g_warning ("Invalid escape sequence: %s: %s", val, line);
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);
}
}
if (g_utf8_get_char (g_utf8_next_char (words[1])) > 0)
{
g_warning ("GTK supports to output one char only: %s: %s", val, line);
goto fail;
}
compose_data->value = g_string_free (value, FALSE);
compose_data->value[1] = uch;
if (uch == '"')
compose_data->comment = g_strdup (g_strstrip (words[2] + 1));
else
compose_data->comment = g_strdup (g_strstrip (words[2]));
g_strfreev (words);
g_free (word);
return TRUE;
fail:
g_strfreev (words);
return FALSE;
}
@ -189,10 +214,10 @@ parse_compose_sequence (GtkComposeData *compose_data,
}
g_strfreev (words);
if (0 == n || n > GTK_MAX_COMPOSE_LEN)
if (0 == n || n > MAX_COMPOSE_LEN)
{
g_warning ("The max length of compose sequences is %d: %s",
GTK_MAX_COMPOSE_LEN, line);
g_warning ("Suspicious compose sequence length (%d). Are you sure this is right?: %s",
n, line);
return FALSE;
}
@ -214,7 +239,10 @@ parse_compose_line (GList **compose_list,
return;
if (g_str_has_prefix (line, "include "))
return;
{
g_warning ("include in Compose files not supported: %s", line);
return;
}
components = g_strsplit (line, ":", 2);
@ -244,6 +272,8 @@ fail:
gtk_compose_data_free (compose_data);
}
extern const GtkComposeTableCompact gtk_compose_table_compact;
static GList *
gtk_compose_list_parse_file (const char *compose_file)
{
@ -279,18 +309,19 @@ gtk_compose_list_check_duplicated (GList *compose_list)
for (list = compose_list; list != NULL; list = list->next)
{
static guint16 keysyms[GTK_MAX_COMPOSE_LEN + 1];
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 < GTK_MAX_COMPOSE_LEN + 1; i++)
for (i = 0; i < MAX_COMPOSE_LEN + 1; i++)
keysyms[i] = 0;
for (i = 0; i < GTK_MAX_COMPOSE_LEN + 1; i++)
for (i = 0; i < MAX_COMPOSE_LEN + 1; i++)
{
gunichar codepoint = compose_data->sequence[i];
keysyms[i] = (guint16) codepoint;
@ -301,20 +332,21 @@ gtk_compose_list_check_duplicated (GList *compose_list)
n_compose++;
}
if (gtk_check_compact_table (&gtk_compose_table_compact,
keysyms,
n_compose,
&compose_finish,
NULL,
&output_char) &&
if (gtk_compose_table_compact_check (&gtk_compose_table_compact,
keysyms, n_compose,
&compose_finish,
NULL,
&output_char) &&
compose_finish)
{
if (compose_data->value[1] == 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);
}
else if (gtk_check_algorithmically (keysyms, n_compose, &output_char))
{
if (compose_data->value[1] == 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);
}
}
@ -343,7 +375,7 @@ gtk_compose_list_check_uint16 (GList *compose_list)
int i;
compose_data = list->data;
for (i = 0; i < GTK_MAX_COMPOSE_LEN; i++)
for (i = 0; i < MAX_COMPOSE_LEN; i++)
{
gunichar codepoint = compose_data->sequence[i];
@ -384,7 +416,7 @@ gtk_compose_list_format_for_gtk (GList *compose_list,
for (list = compose_list; list != NULL; list = list->next)
{
compose_data = list->data;
for (i = 0; i < GTK_MAX_COMPOSE_LEN + 1; i++)
for (i = 0; i < MAX_COMPOSE_LEN + 1; i++)
{
codepoint = compose_data->sequence[i];
if (codepoint == 0)
@ -401,17 +433,6 @@ gtk_compose_list_format_for_gtk (GList *compose_list,
if (p_n_index_stride)
*p_n_index_stride = max_compose_len + 2;
for (list = compose_list; list != NULL; list = list->next)
{
compose_data = list->data;
codepoint = compose_data->value[1];
if (codepoint > 0xffff)
{
compose_data->value[0] = codepoint / 0x10000;
compose_data->value[1] = codepoint - codepoint / 0x10000 * 0x10000;
}
}
return compose_list;
}
@ -436,61 +457,6 @@ gtk_compose_data_compare (gpointer a,
return 0;
}
static void
gtk_compose_list_print (GList *compose_list,
int max_compose_len,
int n_index_stride)
{
GList *list;
int i, j;
GtkComposeData *compose_data;
int total_size = 0;
gunichar upper;
gunichar lower;
const char *comment;
const char *keyval;
for (list = compose_list; list != NULL; list = list->next)
{
compose_data = list->data;
g_printf (" ");
for (i = 0; i < max_compose_len; i++)
{
if (compose_data->sequence[i] == 0)
{
for (j = i; j < max_compose_len; j++)
{
if (j == max_compose_len - 1)
g_printf ("0,\n");
else
g_printf ("0, ");
}
break;
}
keyval = gdk_keyval_name (compose_data->sequence[i]);
if (i == max_compose_len - 1)
g_printf ("%s,\n", keyval ? keyval : "(null)");
else
g_printf ("%s, ", keyval ? keyval : "(null)");
}
upper = compose_data->value[0];
lower = compose_data->value[1];
comment = compose_data->comment;
if (list == g_list_last (compose_list))
g_printf (" %#06X, %#06X /* %s */\n", upper, lower, comment);
else
g_printf (" %#06X, %#06X, /* %s */\n", upper, lower, comment);
total_size += n_index_stride;
}
g_printerr ("TOTAL_SIZE: %d\nMAX_COMPOSE_LEN: %d\nN_INDEX_STRIDE: %d\n",
total_size, max_compose_len, n_index_stride);
}
/* Implemented from g_str_hash() */
static guint32
gtk_compose_table_data_hash (gconstpointer v, int length)
@ -546,6 +512,7 @@ gtk_compose_table_serialize (GtkComposeTable *compose_table,
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);
@ -553,44 +520,34 @@ gtk_compose_table_serialize (GtkComposeTable *compose_table,
g_return_val_if_fail (index_stride > 0, NULL);
length = strlen (header);
total_length = length + sizeof (guint16) * (3 + index_stride * n_seqs);
total_length = length + sizeof (guint16) * (4 + index_stride * n_seqs) + n_chars;
if (count)
*count = total_length;
p = contents = g_slice_alloc (total_length);
p = contents = g_malloc (total_length);
memcpy (p, header, length);
p += length;
/* Copy by byte for endian */
#define BYTE_COPY_FROM_BUF(element) \
bytes = GUINT16_TO_BE ((element)); \
memcpy (p, &bytes, length); \
p += length; \
if (p - contents > total_length) \
{ \
g_warning ("data size %lld is bigger than %" G_GSIZE_FORMAT, \
(long long) (p - contents), total_length); \
g_free (contents); \
if (count) \
{ \
*count = 0; \
} \
return NULL; \
}
#define APPEND_GUINT16(elt) \
bytes = GUINT16_TO_BE (elt); \
memcpy (p, &bytes, sizeof (guint16)); \
p += sizeof (guint16);
length = sizeof (guint16);
BYTE_COPY_FROM_BUF (version);
BYTE_COPY_FROM_BUF (max_seq_len);
BYTE_COPY_FROM_BUF (n_seqs);
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++)
{
BYTE_COPY_FROM_BUF (compose_table->data[i]);
APPEND_GUINT16 (compose_table->data[i]);
}
#undef BYTE_COPY_FROM_BUF
if (compose_table->n_chars > 0)
memcpy (p, compose_table->char_data, compose_table->n_chars);
#undef APPEND_GUINT16
return contents;
}
@ -614,16 +571,17 @@ gtk_compose_table_load_cache (const char *compose_file)
GStatBuf original_buf;
GStatBuf cache_buf;
gsize total_length;
gsize 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)
@ -642,16 +600,10 @@ gtk_compose_table_load_cache (const char *compose_file)
goto out_load_cache;
}
/* Copy by byte for endian */
#define BYTE_COPY_TO_BUF(element) \
memcpy (&bytes, p, length); \
element = GUINT16_FROM_BE (bytes); \
p += length; \
if (p - contents > total_length) \
{ \
g_warning ("Broken cache content %s in %s", path, #element); \
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,
@ -660,6 +612,7 @@ gtk_compose_table_load_cache (const char *compose_file)
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)
{
@ -667,9 +620,7 @@ gtk_compose_table_load_cache (const char *compose_file)
goto out_load_cache;
}
length = sizeof (guint16);
BYTE_COPY_TO_BUF (version);
GET_GUINT16 (version);
if (version != GTK_COMPOSE_TABLE_VERSION)
{
g_warning ("cache version is different %u != %u",
@ -677,8 +628,9 @@ gtk_compose_table_load_cache (const char *compose_file)
goto out_load_cache;
}
BYTE_COPY_TO_BUF (max_seq_len);
BYTE_COPY_TO_BUF (n_seqs);
GET_GUINT16 (max_seq_len);
GET_GUINT16 (n_seqs);
GET_GUINT16 (n_chars);
if (max_seq_len == 0 || n_seqs == 0)
{
@ -691,13 +643,22 @@ gtk_compose_table_load_cache (const char *compose_file)
for (i = 0; i < (guint32) index_stride * n_seqs; i++)
{
BYTE_COPY_TO_BUF (gtk_compose_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);
@ -705,10 +666,11 @@ gtk_compose_table_load_cache (const char *compose_file)
return retval;
#undef BYTE_COPY_TO_BUF
#undef GET_GUINT16
out_load_cache:
g_free (gtk_compose_seqs);
g_free (char_data);
g_free (contents);
g_free (path);
return NULL;
@ -739,7 +701,7 @@ gtk_compose_table_save_cache (GtkComposeTable *compose_table)
}
out_save_cache:
g_slice_free1 (length, contents);
g_free (contents);
g_free (path);
}
@ -756,6 +718,8 @@ gtk_compose_table_new_with_list (GList *compose_list,
GList *list;
GtkComposeData *compose_data;
GtkComposeTable *retval = NULL;
gunichar codepoint;
GString *char_data;
g_return_val_if_fail (compose_list != NULL, NULL);
@ -763,6 +727,8 @@ gtk_compose_table_new_with_list (GList *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;
@ -776,8 +742,24 @@ gtk_compose_table_new_with_list (GList *compose_list,
}
gtk_compose_seqs[n++] = (guint16) compose_data->sequence[i];
}
gtk_compose_seqs[n++] = (guint16) compose_data->value[0];
gtk_compose_seqs[n++] = (guint16) compose_data->value[1];
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);
@ -785,6 +767,8 @@ gtk_compose_table_new_with_list (GList *compose_list,
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;
}
@ -816,9 +800,6 @@ gtk_compose_table_new_with_file (const char *compose_file)
return NULL;
}
if (g_getenv ("GTK_COMPOSE_TABLE_PRINT") != NULL)
gtk_compose_list_print (compose_list, max_compose_len, n_index_stride);
compose_table = gtk_compose_table_new_with_list (compose_list,
max_compose_len,
n_index_stride,
@ -841,9 +822,10 @@ gtk_compose_table_list_add_array (GSList *compose_tables,
guint16 *gtk_compose_seqs = NULL;
g_return_val_if_fail (data != NULL, compose_tables);
g_return_val_if_fail (max_seq_len <= GTK_MAX_COMPOSE_LEN, 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 = MIN (max_seq_len, GTK_MAX_COMPOSE_LEN) + 2;
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");
@ -864,12 +846,14 @@ gtk_compose_table_list_add_array (GSList *compose_tables,
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,
gtk_compose_table_list_add_file (GSList *compose_tables,
const char *compose_file)
{
guint32 hash;
@ -891,3 +875,409 @@ gtk_compose_table_list_add_file (GSList *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 (seq_index && 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++)
;
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;
}

View File

@ -29,8 +29,10 @@ typedef struct _GtkComposeTableCompact GtkComposeTableCompact;
struct _GtkComposeTable
{
guint16 *data;
char *char_data;
int max_seq_len;
int n_seqs;
int n_chars;
guint32 id;
};
@ -42,13 +44,32 @@ struct _GtkComposeTableCompact
int n_index_stride;
};
GtkComposeTable * gtk_compose_table_new_with_file (const char *compose_file);
GSList *gtk_compose_table_list_add_array (GSList *compose_tables,
const guint16 *data,
int max_seq_len,
int n_seqs);
GSList *gtk_compose_table_list_add_file (GSList *compose_tables,
const char *compose_file);
GtkComposeTable * gtk_compose_table_new_with_file (const char *compose_file);
GSList * gtk_compose_table_list_add_array (GSList *compose_tables,
const guint16 *data,
int max_seq_len,
int n_seqs);
GSList * gtk_compose_table_list_add_file (GSList *compose_tables,
const char *compose_file);
gboolean gtk_compose_table_check (const GtkComposeTable *table,
const guint16 *compose_buffer,
int n_compose,
gboolean *compose_finish,
gboolean *compose_match,
GString *output);
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);
gboolean gtk_check_algorithmically (const guint16 *compose_buffer,
int n_compose,
gunichar *output);
G_END_DECLS

View File

@ -32,7 +32,6 @@
#include "gtkcomposetable.h"
#include "gtkimmoduleprivate.h"
#include "gtkimcontextsimpleprivate.h"
#include "gtkimcontextsimpleseqs.h"
#include "gdk/gdkprofilerprivate.h"
@ -61,8 +60,9 @@
struct _GtkIMContextSimplePrivate
{
guint16 compose_buffer[GTK_MAX_COMPOSE_LEN + 1];
gunichar tentative_match;
guint16 *compose_buffer;
int compose_buffer_len;
GString *tentative_match;
int tentative_match_len;
guint in_hex_sequence : 1;
@ -174,8 +174,7 @@ gtk_im_context_simple_init_compose_table (void)
g_free (path);
return;
}
g_free (path);
path = NULL;
g_clear_pointer (&path, g_free);
home = g_get_home_dir ();
if (home == NULL)
@ -190,8 +189,7 @@ gtk_im_context_simple_init_compose_table (void)
g_free (path);
return;
}
g_free (path);
path = NULL;
g_clear_pointer (&path, g_free);
locale = g_getenv ("LC_CTYPE");
if (locale == NULL)
@ -224,8 +222,7 @@ gtk_im_context_simple_init_compose_table (void)
if (g_file_test (path, G_FILE_TEST_EXISTS))
break;
g_free (path);
path = NULL;
g_clear_pointer (&path, g_free);
}
g_free (x11_compose_file_dir);
@ -237,8 +234,7 @@ gtk_im_context_simple_init_compose_table (void)
global_tables = gtk_compose_table_list_add_file (global_tables, path);
G_UNLOCK (global_tables);
}
g_free (path);
path = NULL;
g_clear_pointer (&path, g_free);
}
static void
@ -271,14 +267,27 @@ init_compose_table_async (GCancellable *cancellable,
}
static void
gtk_im_context_simple_init (GtkIMContextSimple *im_context_simple)
gtk_im_context_simple_init (GtkIMContextSimple *context_simple)
{
im_context_simple->priv = gtk_im_context_simple_get_instance_private (im_context_simple);
GtkIMContextSimplePrivate *priv;
priv = context_simple->priv = gtk_im_context_simple_get_instance_private (context_simple);
priv->compose_buffer_len = gtk_compose_table_compact.max_seq_len + 1;
priv->compose_buffer = g_new0 (guint16, priv->compose_buffer_len);
priv->tentative_match = g_string_new ("");
priv->tentative_match_len = 0;
}
static void
gtk_im_context_simple_finalize (GObject *obj)
{
GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (obj);
GtkIMContextSimplePrivate *priv = context_simple->priv;;
g_free (priv->compose_buffer);
g_string_free (priv->tentative_match, TRUE);
G_OBJECT_CLASS (gtk_im_context_simple_parent_class)->finalize (obj);
}
@ -296,413 +305,29 @@ gtk_im_context_simple_new (void)
}
static void
gtk_im_context_simple_commit_char (GtkIMContext *context,
gunichar ch)
gtk_im_context_simple_commit_string (GtkIMContextSimple *context_simple,
const char *str)
{
GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context);
GtkIMContextSimplePrivate *priv = context_simple->priv;
char buf[10];
int len;
g_return_if_fail (g_unichar_validate (ch));
len = g_unichar_to_utf8 (ch, buf);
buf[len] = '\0';
priv->in_hex_sequence = FALSE;
priv->tentative_match = 0;
g_string_set_size (priv->tentative_match, 0);
priv->tentative_match_len = 0;
priv->compose_buffer[0] = 0;
g_signal_emit_by_name (context, "preedit-changed");
g_signal_emit_by_name (context, "preedit-end");
g_signal_emit_by_name (context, "commit", &buf);
g_signal_emit_by_name (context_simple, "preedit-changed");
g_signal_emit_by_name (context_simple, "preedit-end");
g_signal_emit_by_name (context_simple, "commit", str);
}
static int
compare_seq_index (const void *key, const void *value)
static void
gtk_im_context_simple_commit_char (GtkIMContextSimple *context_simple,
gunichar ch)
{
const guint16 *keysyms = key;
const guint16 *seq = value;
char buf[8] = { 0, };
if (keysyms[0] < seq[0])
return -1;
else if (keysyms[0] > seq[0])
return 1;
g_unichar_to_utf8 (ch, buf);
return 0;
}
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;
}
static gboolean
check_table (GtkIMContextSimple *context_simple,
const GtkComposeTable *table,
int n_compose)
{
GtkIMContextSimplePrivate *priv = context_simple->priv;
int row_stride = table->max_seq_len + 2;
guint16 *seq;
/* 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 (priv->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 (priv->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 =
0x10000 * seq[table->max_seq_len] + seq[table->max_seq_len + 1];
/* 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 (priv->compose_buffer, next_seq) == 0)
{
priv->tentative_match = value;
priv->tentative_match_len = n_compose;
g_signal_emit_by_name (context_simple, "preedit-changed");
return TRUE;
}
}
gtk_im_context_simple_commit_char (GTK_IM_CONTEXT (context_simple), value);
return TRUE;
}
g_signal_emit_by_name (context_simple, "preedit-changed");
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)
gboolean
gtk_check_compact_table (const GtkComposeTableCompact *table,
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 (seq_index && 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;
}
/* 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[GTK_MAX_COMPOSE_LEN];
char *combination_utf8_temp = NULL;
char *nfc_temp = NULL;
int n_combinations;
gunichar temp_swap;
int i;
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, GTK_MAX_COMPOSE_LEN * 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, -1, 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, GTK_MAX_COMPOSE_LEN * 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[GTK_MAX_COMPOSE_LEN];
char *combination_utf8, *nfc;
if (output_char)
*output_char = 0;
if (n_compose >= GTK_MAX_COMPOSE_LEN)
return FALSE;
for (i = 0; i < n_compose && IS_DEAD_KEY (compose_buffer[i]); i++)
;
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;
gtk_im_context_simple_commit_string (context_simple, buf);
}
/* In addition to the table-driven sequences, we allow Unicode hex
@ -733,7 +358,7 @@ check_hex (GtkIMContextSimple *context_simple,
char *nptr = NULL;
char buf[7];
priv->tentative_match = 0;
g_string_set_size (priv->tentative_match, 0);
priv->tentative_match_len = 0;
str = g_string_new (NULL);
@ -773,7 +398,8 @@ check_hex (GtkIMContextSimple *context_simple,
if (g_unichar_validate (n))
{
priv->tentative_match = n;
g_string_set_size (priv->tentative_match, 0);
g_string_append_unichar (priv->tentative_match, n);
priv->tentative_match_len = n_compose;
}
@ -809,18 +435,23 @@ no_sequence_matches (GtkIMContextSimple *context_simple,
/* No compose sequences found, check first if we have a partial
* match pending.
*/
if (priv->tentative_match)
if (priv->tentative_match_len > 0)
{
int len = priv->tentative_match_len;
int i;
guint16 compose_buffer[GTK_MAX_COMPOSE_LEN + 1];
guint16 *compose_buffer;
char *str;
memcpy (compose_buffer, priv->compose_buffer, sizeof (compose_buffer));
compose_buffer = alloca (sizeof (guint16) * priv->compose_buffer_len);
memcpy (compose_buffer, priv->compose_buffer, sizeof (guint16) * priv->compose_buffer_len);
str = g_strdup (priv->tentative_match->str);
gtk_im_context_simple_commit_string (context_simple, str);
g_free (str);
gtk_im_context_simple_commit_char (context, priv->tentative_match);
for (i = 0; i < n_compose - len - 1; i++)
{
{
GdkTranslatedKey translated;
translated.keyval = compose_buffer[len + i];
translated.consumed = 0;
@ -858,7 +489,7 @@ no_sequence_matches (GtkIMContextSimple *context_simple,
ch = gdk_keyval_to_unicode (keyval);
if (ch != 0 && !g_unichar_iscntrl (ch))
{
gtk_im_context_simple_commit_char (context, ch);
gtk_im_context_simple_commit_char (context_simple, ch);
return TRUE;
}
else
@ -942,7 +573,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
gunichar output_char;
guint keyval, state;
while (priv->compose_buffer[n_compose] != 0)
while (priv->compose_buffer[n_compose] != 0 && n_compose < priv->compose_buffer_len)
n_compose++;
keyval = gdk_key_event_get_keyval (event);
@ -954,10 +585,11 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
(keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R ||
keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R))
{
if (priv->tentative_match &&
g_unichar_validate (priv->tentative_match))
if (priv->tentative_match->len > 0)
{
gtk_im_context_simple_commit_char (context, priv->tentative_match);
char *str = g_strdup (priv->tentative_match->str);
gtk_im_context_simple_commit_string (context_simple, str);
g_free (str);
return TRUE;
}
@ -972,7 +604,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
/* invalid hex sequence */
beep_surface (surface);
priv->tentative_match = 0;
g_string_set_size (priv->tentative_match, 0);
priv->in_hex_sequence = FALSE;
priv->compose_buffer[0] = 0;
@ -1072,10 +704,11 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
/* Check for hex sequence restart */
if (priv->in_hex_sequence && have_hex_mods && is_hex_start)
{
if (priv->tentative_match &&
g_unichar_validate (priv->tentative_match))
if (priv->tentative_match->len > 0)
{
gtk_im_context_simple_commit_char (context, priv->tentative_match);
char *str = g_strdup (priv->tentative_match->str);
gtk_im_context_simple_commit_string (context_simple, str);
g_free (str);
}
else
{
@ -1083,7 +716,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
if (n_compose > 0)
beep_surface (surface);
priv->tentative_match = 0;
g_string_set_size (priv->tentative_match, 0);
priv->in_hex_sequence = FALSE;
priv->compose_buffer[0] = 0;
}
@ -1095,7 +728,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
priv->compose_buffer[0] = 0;
priv->in_hex_sequence = TRUE;
priv->modifiers_dropped = FALSE;
priv->tentative_match = 0;
g_string_set_size (priv->tentative_match, 0);
g_signal_emit_by_name (context_simple, "preedit-start");
g_signal_emit_by_name (context_simple, "preedit-changed");
@ -1106,7 +739,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
/* Then, check for compose sequences */
if (priv->in_hex_sequence)
{
if (hex_keyval)
if (hex_keyval && n_compose < 6)
priv->compose_buffer[n_compose++] = hex_keyval;
else if (is_escape)
{
@ -1115,13 +748,21 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
}
else if (!is_hex_end)
{
/* non-hex character in hex sequence */
/* non-hex character in hex sequence, or sequence too long */
beep_surface (surface);
return TRUE;
}
}
else
priv->compose_buffer[n_compose++] = keyval;
{
if (n_compose + 1 == priv->compose_buffer_len)
{
priv->compose_buffer_len += 1;
priv->compose_buffer = g_renew (guint16, priv->compose_buffer, priv->compose_buffer_len);
}
priv->compose_buffer[n_compose++] = keyval;
}
priv->compose_buffer[n_compose] = 0;
@ -1133,17 +774,18 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
/* space or return ends the sequence, and we eat the key */
if (n_compose > 0 && is_hex_end)
{
if (priv->tentative_match &&
g_unichar_validate (priv->tentative_match))
if (priv->tentative_match->len > 0)
{
gtk_im_context_simple_commit_char (context, priv->tentative_match);
char *str = g_strdup (priv->tentative_match->str);
gtk_im_context_simple_commit_string (context_simple, str);
g_free (str);
}
else
{
/* invalid hex sequence */
beep_surface (surface);
priv->tentative_match = 0;
g_string_set_size (priv->tentative_match, 0);
priv->in_hex_sequence = FALSE;
priv->compose_buffer[0] = 0;
}
@ -1162,43 +804,65 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
else
{
gboolean success = FALSE;
GString *output;
output = g_string_new ("");
G_LOCK (global_tables);
tmp_list = global_tables;
while (tmp_list)
{
if (check_table (context_simple, tmp_list->data, n_compose))
if (gtk_compose_table_check ((GtkComposeTable *)tmp_list->data,
priv->compose_buffer, n_compose,
&compose_finish, &compose_match,
output))
{
if (compose_finish)
{
if (compose_match)
gtk_im_context_simple_commit_string (context_simple, output->str);
}
else
{
if (compose_match)
{
g_string_assign (priv->tentative_match, output->str);
priv->tentative_match_len = n_compose;
}
g_signal_emit_by_name (context_simple, "preedit-changed");
}
success = TRUE;
break;
}
tmp_list = tmp_list->next;
}
G_UNLOCK (global_tables);
g_string_free (output, TRUE);
if (success)
return TRUE;
if (gtk_check_compact_table (&gtk_compose_table_compact,
priv->compose_buffer,
n_compose, &compose_finish,
&compose_match, &output_char))
if (gtk_compose_table_compact_check (&gtk_compose_table_compact,
priv->compose_buffer, n_compose,
&compose_finish, &compose_match,
&output_char))
{
if (compose_finish)
{
if (compose_match)
{
gtk_im_context_simple_commit_char (GTK_IM_CONTEXT (context_simple),
output_char);
}
gtk_im_context_simple_commit_char (context_simple, output_char);
}
else
{
if (compose_match)
{
priv->tentative_match = output_char;
g_string_set_size (priv->tentative_match, 0);
g_string_append_unichar (priv->tentative_match, output_char);
priv->tentative_match_len = n_compose;
}
g_signal_emit_by_name (context_simple, "preedit-changed");
@ -1206,18 +870,15 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
return TRUE;
}
if (gtk_check_algorithmically (priv->compose_buffer, n_compose, &output_char))
{
if (output_char)
{
gtk_im_context_simple_commit_char (GTK_IM_CONTEXT (context_simple),
output_char);
}
return TRUE;
gtk_im_context_simple_commit_char (context_simple, output_char);
return TRUE;
}
}
/* The current compose_buffer doesn't match anything */
return no_sequence_matches (context_simple, n_compose, (GdkEvent *)event);
}
@ -1230,10 +891,10 @@ gtk_im_context_simple_reset (GtkIMContext *context)
priv->compose_buffer[0] = 0;
if (priv->tentative_match || priv->in_hex_sequence)
if (priv->tentative_match->len > 0 || priv->in_hex_sequence)
{
priv->in_hex_sequence = FALSE;
priv->tentative_match = 0;
g_string_set_size (priv->tentative_match, 0);
priv->tentative_match_len = 0;
g_signal_emit_by_name (context_simple, "preedit-changed");
g_signal_emit_by_name (context_simple, "preedit-end");
@ -1260,9 +921,9 @@ gtk_im_context_simple_get_preedit_string (GtkIMContext *context,
for (i = 0; priv->compose_buffer[i]; i++)
g_string_append_unichar (s, gdk_keyval_to_unicode (priv->compose_buffer[i]));
}
else if (priv->tentative_match && priv->compose_buffer[0] != 0)
else if (priv->tentative_match->len > 0 && priv->compose_buffer[0] != 0)
{
g_string_append_unichar (s, priv->tentative_match);
g_string_append (s, priv->tentative_match->str);
}
else
{
@ -1300,7 +961,6 @@ gtk_im_context_simple_get_preedit_string (GtkIMContext *context,
* @context_simple: A #GtkIMContextSimple
* @data: (array): the table
* @max_seq_len: Maximum length of a sequence in the table
* (cannot be greater than #GTK_MAX_COMPOSE_LEN)
* @n_seqs: number of sequences in the table
*
* Adds an additional table to search to the input context.
@ -1320,7 +980,6 @@ gtk_im_context_simple_add_table (GtkIMContextSimple *context_simple,
int n_seqs)
{
g_return_if_fail (GTK_IS_IM_CONTEXT_SIMPLE (context_simple));
g_return_if_fail (max_seq_len <= GTK_MAX_COMPOSE_LEN);
G_LOCK (global_tables);
@ -1345,8 +1004,7 @@ gtk_im_context_simple_add_compose_file (GtkIMContextSimple *context_simple,
G_LOCK (global_tables);
global_tables = gtk_compose_table_list_add_file (global_tables,
compose_file);
global_tables = gtk_compose_table_list_add_file (global_tables, compose_file);
G_UNLOCK (global_tables);
}

View File

@ -27,10 +27,9 @@
G_BEGIN_DECLS
/**
* GTK_MAX_COMPOSE_LEN:
*
* The maximum length of sequences in compose tables.
/*
* No longer used by GTK, just left here on the off chance that some
* 3rd party code used this define.
*/
#define GTK_MAX_COMPOSE_LEN 7

View File

@ -1,42 +0,0 @@
/* GTK - The GIMP Toolkit
* Copyright (C) 2000 Red Hat Software
*
* 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/>.
*/
#ifndef __GTK_IM_CONTEXT_SIMPLE_PRIVATE_H__
#define __GTK_IM_CONTEXT_SIMPLE_PRIVATE_H__
#include <glib.h>
#include "gdk/gdkkeysyms.h"
G_BEGIN_DECLS
extern const GtkComposeTableCompact gtk_compose_table_compact;
gboolean gtk_check_algorithmically (const guint16 *compose_buffer,
int n_compose,
gunichar *output);
gboolean gtk_check_compact_table (const GtkComposeTableCompact *table,
guint16 *compose_buffer,
int n_compose,
gboolean *compose_finish,
gboolean *compose_match,
gunichar *output_char);
G_END_DECLS
#endif /* __GTK_IM_CONTEXT_SIMPLE_PRIVATE_H__ */

View File

@ -0,0 +1 @@
<Multi_key> <s> <e> <q> : "!"

View File

@ -0,0 +1,3 @@
# n_seqs: 1
# max_seq_len: 4
<Uff20> <U73> <U65> <U71> : "!" # U21

View File

@ -0,0 +1 @@
<Multi_key> <U73> <U6F> <U7a> : "!"

View File

@ -0,0 +1,3 @@
# n_seqs: 1
# max_seq_len: 4
<Uff20> <U73> <U6f> <U7a> : "!" # U21

View File

@ -0,0 +1 @@
<Multi_key> <s> <e> <q> : "\x23fe\X23F3"

View File

@ -0,0 +1,3 @@
# n_seqs: 1
# max_seq_len: 4
<Uff20> <U73> <U65> <U71> : "⏾⏳"

View File

@ -0,0 +1 @@
<Multi_key> <e> <m> <m> <e> <n> <t> <a> <l> <e> <r> : "🧀"

View File

@ -0,0 +1,3 @@
# n_seqs: 1
# max_seq_len: 11
<Uff20> <U65> <U6d> <U6d> <U65> <U6e> <U74> <U61> <U6c> <U65> <U72> : "🧀" # U1f9c0

View File

@ -0,0 +1,3 @@
<Multi_key> <s> <e> <q> : "!"
<Multi_key> <s> <e> <q> <u> : "?"
<Multi_key> <z> <w> <i> <n> <e> <s> : "🥂"

View File

@ -0,0 +1,3 @@
<Multi_key> <s> <e> <q> : "!"
<Multi_key> <u> <b> <2> <3> : "/"
<Multi_key> <s> <a> <s> : "_"

View File

@ -0,0 +1,5 @@
# n_seqs: 3
# max_seq_len: 5
<Uff20> <U73> <U61> <U73> <U0> : "_" # U5f
<Uff20> <U73> <U65> <U71> <U0> : "!" # U21
<Uff20> <U75> <U62> <U32> <U33> : "/" # U2f

View File

@ -0,0 +1 @@
<Multi_key> <s> <e> <q> : "\041"

View File

@ -0,0 +1,3 @@
# n_seqs: 1
# max_seq_len: 4
<Uff20> <U73> <U65> <U71> : "!" # U21

View File

@ -0,0 +1,4 @@
<Multi_key> <s> <e> <q> : "!a"
<Multi_key> <s> <e> <q> <u> : "?"
<Multi_key> <u> <b> <2> <3> : "\121\122"
<Multi_key> <s> <a> <s> : "\"\\"

View File

@ -0,0 +1,6 @@
# n_seqs: 4
# max_seq_len: 5
<Uff20> <U73> <U61> <U73> <U0> : "\"\\"
<Uff20> <U73> <U65> <U71> <U0> : "!a"
<Uff20> <U73> <U65> <U71> <U75> : "?" # U3f
<Uff20> <U75> <U62> <U32> <U33> : "QR"

View File

@ -0,0 +1,320 @@
#include <gtk/gtk.h>
#include <locale.h>
#include "../gtk/gtkcomposetable.h"
#include "../gtk/gtkimcontextsimpleseqs.h"
#include "testsuite/testutils.h"
static void
append_escaped (GString *str,
const char *s)
{
for (const char *p = s; *p; p = g_utf8_next_char (p))
{
gunichar ch = g_utf8_get_char (p);
if (ch == '"')
g_string_append (str, "\\\"");
else if (ch == '\\')
g_string_append (str, "\\\\");
else if (g_unichar_isprint (ch))
g_string_append_unichar (str, ch);
else
{
guint n[8] = { 0, };
int i = 0;
while (ch != 0)
{
n[i++] = ch & 7;
ch = ch >> 3;
}
for (; i >= 0; i--)
g_string_append_printf (str, "\\%o", n[i]);
}
}
}
static char *
gtk_compose_table_print (GtkComposeTable *table)
{
int i, j;
guint16 *seq;
GString *str;
str = g_string_new ("");
g_string_append_printf (str, "# n_seqs: %d\n# max_seq_len: %d\n",
table->n_seqs,
table->max_seq_len);
for (i = 0, seq = table->data; i < table->n_seqs; i++, seq += table->max_seq_len + 2)
{
gunichar value;
char buf[8] = { 0 };
for (j = 0; j < table->max_seq_len; j++)
g_string_append_printf (str, "<U%x> ", seq[j]);
value = (seq[table->max_seq_len] << 16) | seq[table->max_seq_len + 1];
if ((value & (1 << 31)) != 0)
{
const char *out = &table->char_data[value & ~(1 << 31)];
g_string_append (str, ": \"");
append_escaped (str, out);
g_string_append (str, "\"\n");
}
else
{
g_unichar_to_utf8 (value, buf);
g_string_append_printf (str, ": \"%s\" # U%x\n", buf, value);
}
}
return g_string_free (str, FALSE);
}
static void
generate_output (const char *file)
{
GSList *tables = NULL;
GtkComposeTable *table;
char *output;
tables = gtk_compose_table_list_add_file (tables, file);
table = tables->data;
output = gtk_compose_table_print (table);
g_print ("%s", output);
}
static void
compose_table_compare (gconstpointer data)
{
const char *basename = data;
GSList *tables = NULL;
GtkComposeTable *table;
char *file;
char *expected;
char *output;
char *diff;
GError *error = NULL;
file = g_build_filename (g_test_get_dir (G_TEST_DIST), "compose", basename, NULL);
expected = g_strconcat (file, ".expected", NULL);
tables = gtk_compose_table_list_add_file (tables, file);
g_assert_true (g_slist_length (tables) == 1);
table = tables->data;
output = gtk_compose_table_print (table);
diff = diff_with_file (expected, output, -1, &error);
g_assert_no_error (error);
if (diff && diff[0])
{
g_print ("Resulting output doesn't match reference:\n%s", diff);
g_test_fail ();
}
g_free (output);
g_free (file);
g_free (expected);
}
/* Check matching against a small table */
static void
compose_table_match (void)
{
GSList *tables = NULL;
GtkComposeTable *table;
char *file;
guint16 buffer[8] = { 0, };
gboolean finish, match, ret;
GString *output;
output = g_string_new ("");
file = g_build_filename (g_test_get_dir (G_TEST_DIST), "compose", "match", NULL);
tables = gtk_compose_table_list_add_file (tables, file);
g_assert_true (g_slist_length (tables) == 1);
table = tables->data;
buffer[0] = GDK_KEY_Multi_key;
buffer[1] = 0;
ret = gtk_compose_table_check (table, buffer, 1, &finish, &match, output);
g_assert_true (ret);
g_assert_false (finish);
g_assert_false (match);
g_assert_true (output->len == 0);
buffer[0] = GDK_KEY_a;
buffer[1] = 0;
ret = gtk_compose_table_check (table, buffer, 1, &finish, &match, output);
g_assert_false (ret);
g_assert_false (finish);
g_assert_false (match);
g_assert_true (output->len == 0);
buffer[0] = GDK_KEY_Multi_key;
buffer[1] = GDK_KEY_s;
buffer[2] = GDK_KEY_e;
ret = gtk_compose_table_check (table, buffer, 3, &finish, &match, output);
g_assert_true (ret);
g_assert_false (finish);
g_assert_false (match);
g_assert_true (output->len == 0);
buffer[0] = GDK_KEY_Multi_key;
buffer[1] = GDK_KEY_s;
buffer[2] = GDK_KEY_e;
buffer[3] = GDK_KEY_q;
ret = gtk_compose_table_check (table, buffer, 4, &finish, &match, output);
g_assert_true (ret);
g_assert_false (finish);
g_assert_true (match);
g_assert_cmpstr (output->str, ==, "!");
g_string_set_size (output, 0);
buffer[0] = GDK_KEY_Multi_key;
buffer[1] = GDK_KEY_s;
buffer[2] = GDK_KEY_e;
buffer[3] = GDK_KEY_q;
buffer[4] = GDK_KEY_u;
ret = gtk_compose_table_check (table, buffer, 5, &finish, &match, output);
g_assert_true (ret);
g_assert_true (finish);
g_assert_true (match);
g_assert_cmpstr (output->str, ==, "?");
g_string_free (output, TRUE);
g_free (file);
}
/* just check some random sequences */
static void
compose_table_match_compact (void)
{
const GtkComposeTableCompact table = {
gtk_compose_seqs_compact,
5,
30,
6
};
guint16 buffer[8] = { 0, };
gboolean finish, match, ret;
gunichar ch;
buffer[0] = GDK_KEY_Multi_key;
ret = gtk_compose_table_compact_check (&table, buffer, 1, &finish, &match, &ch);
g_assert_true (ret);
g_assert_false (finish);
g_assert_false (match);
g_assert_true (ch == 0);
buffer[0] = GDK_KEY_a;
buffer[1] = GDK_KEY_b;
buffer[2] = GDK_KEY_c;
ret = gtk_compose_table_compact_check (&table, buffer, 3, &finish, &match, &ch);
g_assert_false (ret);
g_assert_false (finish);
g_assert_false (match);
g_assert_true (ch == 0);
buffer[0] = GDK_KEY_Multi_key;
buffer[1] = GDK_KEY_parenleft;
buffer[2] = GDK_KEY_j;
buffer[3] = GDK_KEY_parenright;
ret = gtk_compose_table_compact_check (&table, buffer, 4, &finish, &match, &ch);
g_assert_true (ret);
g_assert_true (finish);
g_assert_true (match);
g_assert_true (ch == 0x24d9); /* CIRCLED LATIN SMALL LETTER J */
}
static void
match_algorithmic (void)
{
guint16 buffer[8] = { 0, };
gboolean ret;
gunichar ch;
buffer[0] = GDK_KEY_a;
buffer[1] = GDK_KEY_b;
ret = gtk_check_algorithmically (buffer, 2, &ch);
g_assert_false (ret);
g_assert_true (ch == 0);
buffer[0] = GDK_KEY_dead_abovering;
buffer[1] = GDK_KEY_A;
ret = gtk_check_algorithmically (buffer, 2, &ch);
g_assert_true (ret);
g_assert_true (ch == 0xc5);
buffer[0] = GDK_KEY_A;
buffer[1] = GDK_KEY_dead_abovering;
ret = gtk_check_algorithmically (buffer, 2, &ch);
g_assert_false (ret);
g_assert_true (ch == 0);
buffer[0] = GDK_KEY_dead_dasia;
buffer[1] = GDK_KEY_dead_perispomeni;
buffer[2] = GDK_KEY_Greek_alpha;
ret = gtk_check_algorithmically (buffer, 3, &ch);
g_assert_true (ret);
g_assert_true (ch == 0x1f07);
buffer[0] = GDK_KEY_dead_perispomeni;
buffer[1] = GDK_KEY_dead_dasia;
buffer[2] = GDK_KEY_Greek_alpha;
ret = gtk_check_algorithmically (buffer, 3, &ch);
g_assert_true (ret);
g_assert_true (ch == 0x1f07);
}
int
main (int argc, char *argv[])
{
char *dir;
dir = g_dir_make_tmp ("composetableXXXXXX", NULL);
g_setenv ("XDG_CACHE_HOME", dir, TRUE);
g_free (dir);
if (argc == 3 && strcmp (argv[1], "--generate") == 0)
{
setlocale (LC_ALL, "");
generate_output (argv[2]);
return 0;
}
gtk_test_init (&argc, &argv, NULL);
g_test_add_data_func ("/compose-table/basic", "basic", compose_table_compare);
g_test_add_data_func ("/compose-table/long", "long", compose_table_compare);
g_test_add_data_func ("/compose-table/octal", "octal", compose_table_compare);
g_test_add_data_func ("/compose-table/hex", "hex", compose_table_compare);
g_test_add_data_func ("/compose-table/codepoint", "codepoint", compose_table_compare);
g_test_add_data_func ("/compose-table/multi", "multi", compose_table_compare);
g_test_add_data_func ("/compose-table/strings", "strings", compose_table_compare);
g_test_add_func ("/compose-table/match", compose_table_match);
g_test_add_func ("/compose-table/match-compact", compose_table_match_compact);
g_test_add_func ("/compose-table/match-algorithmic", match_algorithmic);
return g_test_run ();
}

View File

@ -103,6 +103,13 @@ tests = [
# Tests that test private apis and therefore are linked against libgtk-4.a
internal_tests = [
{ 'name': 'bitmask' },
{
'name': 'composetable',
'sources': [
'composetable.c',
'../testutils.c'
],
},
{ 'name': 'constraint-solver' },
{ 'name': 'rbtree-crash' },
{ 'name': 'propertylookuplistmodel' },