/* * Copyright (C) 2011 Daiki Ueno * Copyright (C) 2011 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * SECTION:eek-xml-layout * @short_description: Layout engine which loads layout information from XML */ #include "config.h" #include /* GResource */ #include #include #include "eek-xml-layout.h" #include "eek-keyboard.h" #include "eek-section.h" #include "eek-key.h" #include "eek-keysym.h" #include "eek-text.h" #include "squeekboard-resources.h" enum { PROP_0, PROP_ID, PROP_LAST }; static void initable_iface_init (GInitableIface *initable_iface); typedef struct _EekXmlLayoutPrivate { gchar *id; gchar *keyboards_dir; EekXmlKeyboardDesc *desc; } EekXmlLayoutPrivate; G_DEFINE_TYPE_EXTENDED (EekXmlLayout, eek_xml_layout, EEK_TYPE_LAYOUT, 0, /* GTypeFlags */ G_ADD_PRIVATE(EekXmlLayout) G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)) G_DEFINE_BOXED_TYPE(EekXmlKeyboardDesc, eek_xml_keyboard_desc, eek_xml_keyboard_desc_copy, eek_xml_keyboard_desc_free); #define BUFSIZE 8192 static GList *parse_keyboards (const gchar *path, GError **error); static GList *parse_prerequisites (const gchar *path, GError **error); static gboolean parse_geometry (const gchar *path, EekKeyboard *keyboard, GError **error); static gboolean parse_symbols_with_prerequisites (const gchar *keyboards_dir, const gchar *name, EekKeyboard *keyboard, GList **loaded, GError **error); static gboolean parse_symbols (const gchar *path, EekKeyboard *keyboard, GError **error); static void scale_keyboard (EekKeyboard *keyboard, gdouble width, gdouble height); static gboolean validate (const gchar **valid_path_list, gsize valid_path_list_len, const gchar *element_name, GSList *element_stack, GError **error); static gboolean parse (GMarkupParseContext *pcontext, GInputStream *input, GError **error); static const gchar * get_attribute (const gchar **names, const gchar **values, const gchar *name); static void keyboard_desc_free (EekXmlKeyboardDesc *desc) { g_free (desc->id); g_free (desc->name); g_free (desc->geometry); g_free (desc->symbols); g_free (desc->longname); g_free (desc->language); g_slice_free (EekXmlKeyboardDesc, desc); } struct _KeyboardsParseData { GSList *element_stack; GList *keyboards; }; typedef struct _KeyboardsParseData KeyboardsParseData; static KeyboardsParseData * keyboards_parse_data_new (void) { return g_slice_new0 (KeyboardsParseData); } static void keyboards_parse_data_free (KeyboardsParseData *data) { g_list_free_full (data->keyboards, (GDestroyNotify) keyboard_desc_free); g_slice_free (KeyboardsParseData, data); } static const gchar *keyboards_valid_path_list[] = { "keyboards", "keyboard/keyboards", }; static void keyboards_start_element_callback (GMarkupParseContext *pcontext, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { KeyboardsParseData *data = user_data; if (!validate (keyboards_valid_path_list, G_N_ELEMENTS (keyboards_valid_path_list), element_name, data->element_stack, error)) return; if (g_strcmp0 (element_name, "keyboard") == 0) { EekXmlKeyboardDesc *desc = g_slice_new0 (EekXmlKeyboardDesc); const gchar *attribute; data->keyboards = g_list_prepend (data->keyboards, desc); attribute = get_attribute (attribute_names, attribute_values, "id"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"id\" attribute for \"keyboard\""); return; } desc->id = g_strdup (attribute); attribute = get_attribute (attribute_names, attribute_values, "name"); if (attribute) desc->name = g_strdup (attribute); attribute = get_attribute (attribute_names, attribute_values, "geometry"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"geometry\" attribute for \"keyboard\""); return; } desc->geometry = g_strdup (attribute); attribute = get_attribute (attribute_names, attribute_values, "symbols"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"symbols\" attribute for \"keyboard\""); goto out; } desc->symbols = g_strdup (attribute); attribute = get_attribute (attribute_names, attribute_values, "longname"); if (attribute) desc->longname = g_strdup (attribute); attribute = get_attribute (attribute_names, attribute_values, "language"); if (attribute) desc->language = g_strdup (attribute); } out: data->element_stack = g_slist_prepend (data->element_stack, g_strdup (element_name)); } static void keyboards_end_element_callback (GMarkupParseContext *pcontext, const gchar *element_name, gpointer user_data, GError **error) { KeyboardsParseData *data = user_data; GSList *head = data->element_stack; g_free (head->data); data->element_stack = g_slist_next (data->element_stack); g_slist_free1 (head); } static const GMarkupParser keyboards_parser = { keyboards_start_element_callback, keyboards_end_element_callback, 0, 0, 0 }; struct _GeometryParseData { GSList *element_stack; EekBounds bounds; EekKeyboard *keyboard; EekSection *section; EekKey *key; gint num_columns; gint num_rows; EekOrientation orientation; gdouble corner_radius; GSList *points; gchar *name; EekOutline outline; gchar *oref; GHashTable *key_oref_hash; GHashTable *oref_outline_hash; }; typedef struct _GeometryParseData GeometryParseData; static GeometryParseData * geometry_parse_data_new (EekKeyboard *keyboard) { GeometryParseData *data = g_slice_new0 (GeometryParseData); data->keyboard = g_object_ref (keyboard); data->key_oref_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); data->oref_outline_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)eek_outline_free); return data; } static void geometry_parse_data_free (GeometryParseData *data) { g_object_unref (data->keyboard); g_hash_table_destroy (data->key_oref_hash); g_hash_table_destroy (data->oref_outline_hash); g_slice_free (GeometryParseData, data); } static const gchar *geometry_valid_path_list[] = { "geometry", "bounds/geometry", "section/geometry", "outline/geometry", "bounds/section/geometry", "row/section/geometry", "key/row/section/geometry", "bounds/key/row/section/geometry", "point/outline/geometry", }; static void geometry_start_element_callback (GMarkupParseContext *pcontext, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { GeometryParseData *data = user_data; const gchar *attribute; if (!validate (geometry_valid_path_list, G_N_ELEMENTS (geometry_valid_path_list), element_name, data->element_stack, error)) { return; } if (g_strcmp0 (element_name, "bounds") == 0) { EekBounds bounds; attribute = get_attribute (attribute_names, attribute_values, "x"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"x\" attribute for \"bounds\""); return; } bounds.x = g_strtod (attribute, NULL); attribute = get_attribute (attribute_names, attribute_values, "y"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"y\" attribute for \"bounds\""); return; } bounds.y = g_strtod (attribute, NULL); attribute = get_attribute (attribute_names, attribute_values, "width"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"width\" attribute for \"bounds\""); return; } bounds.width = g_strtod (attribute, NULL); attribute = get_attribute (attribute_names, attribute_values, "height"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"height\" attribute for \"bounds\""); return; } bounds.height = g_strtod (attribute, NULL); if (g_strcmp0 (data->element_stack->data, "geometry") == 0) eek_element_set_bounds (EEK_ELEMENT(data->keyboard), &bounds); goto out; } if (g_strcmp0 (element_name, "section") == 0) { data->section = eek_keyboard_create_section (data->keyboard); attribute = get_attribute (attribute_names, attribute_values, "id"); if (attribute != NULL) eek_element_set_name (EEK_ELEMENT(data->section), attribute); attribute = get_attribute (attribute_names, attribute_values, "angle"); if (attribute != NULL) { gint angle; angle = strtol (attribute, NULL, 10); eek_section_set_angle (data->section, angle); } goto out; } if (g_strcmp0 (element_name, "row") == 0) { attribute = get_attribute (attribute_names, attribute_values, "orientation"); if (attribute != NULL) data->orientation = strtol (attribute, NULL, 10); eek_section_add_row (data->section, data->num_columns, data->orientation); data->num_rows++; goto out; } if (g_strcmp0 (element_name, "key") == 0) { guint keycode; attribute = get_attribute (attribute_names, attribute_values, "keycode"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"keycode\" attribute for \"key\""); return; } keycode = strtoul (attribute, NULL, 10); data->key = eek_section_create_key (data->section, keycode, data->num_columns, data->num_rows - 1); attribute = get_attribute (attribute_names, attribute_values, "name"); if (attribute != NULL) eek_element_set_name (EEK_ELEMENT(data->key), attribute); attribute = get_attribute (attribute_names, attribute_values, "oref"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"oref\" attribute for \"key\""); return; } g_hash_table_insert (data->key_oref_hash, data->key, g_strdup (attribute)); data->num_columns++; goto out; } if (g_strcmp0 (element_name, "outline") == 0) { attribute = get_attribute (attribute_names, attribute_values, "id"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"id\" attribute for \"outline\""); return; } data->oref = g_strdup (attribute); attribute = get_attribute (attribute_names, attribute_values, "corner-radius"); if (attribute != NULL) data->corner_radius = g_strtod (attribute, NULL); goto out; } if (g_strcmp0 (element_name, "point") == 0) { EekPoint *point; gdouble x, y; attribute = get_attribute (attribute_names, attribute_values, "x"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"x\" attribute for \"bounds\""); return; } x = g_strtod (attribute, NULL); attribute = get_attribute (attribute_names, attribute_values, "y"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"y\" attribute for \"bounds\""); return; } y = g_strtod (attribute, NULL); point = g_slice_new (EekPoint); point->x = x; point->y = y; data->points = g_slist_prepend (data->points, point); goto out; } out: data->element_stack = g_slist_prepend (data->element_stack, g_strdup (element_name)); } static void geometry_end_element_callback (GMarkupParseContext *pcontext, const gchar *element_name, gpointer user_data, GError **error) { GeometryParseData *data = user_data; GSList *head = data->element_stack; g_free (head->data); data->element_stack = g_slist_next (data->element_stack); g_slist_free1 (head); if (g_strcmp0 (element_name, "section") == 0) { data->section = NULL; data->num_rows = 0; return; } if (g_strcmp0 (element_name, "key") == 0) { data->key = NULL; return; } if (g_strcmp0 (element_name, "row") == 0) { data->num_columns = 0; data->orientation = EEK_ORIENTATION_HORIZONTAL; return; } if (g_strcmp0 (element_name, "outline") == 0) { EekOutline *outline = g_slice_new (EekOutline); outline->corner_radius = data->corner_radius; data->corner_radius = 0.0; outline->num_points = g_slist_length (data->points); outline->points = g_slice_alloc0 (sizeof (EekPoint) * outline->num_points); guint i; for (i = 0, head = data->points = g_slist_reverse (data->points); head && i < outline->num_points; head = g_slist_next (head), i++) { memcpy (&outline->points[i], head->data, sizeof (EekPoint)); g_slice_free1 (sizeof (EekPoint), head->data); } g_slist_free (data->points); data->points = NULL; g_hash_table_insert (data->oref_outline_hash, g_strdup (data->oref), outline); g_free (data->oref); return; } } static const GMarkupParser geometry_parser = { geometry_start_element_callback, geometry_end_element_callback, 0, 0, 0 }; struct _SymbolsParseData { GSList *element_stack; GString *text; EekKeyboard *keyboard; EekKey *key; GSList *symbols; gchar *label; gchar *icon; gchar *tooltip; EekSymbolCategory category; guint keyval; gint groups; }; typedef struct _SymbolsParseData SymbolsParseData; static SymbolsParseData * symbols_parse_data_new (EekKeyboard *keyboard) { SymbolsParseData *data = g_slice_new0 (SymbolsParseData); data->keyboard = g_object_ref (keyboard); data->text = g_string_sized_new (BUFSIZE); return data; } static void symbols_parse_data_free (SymbolsParseData *data) { g_object_unref (data->keyboard); g_string_free (data->text, TRUE); g_slice_free (SymbolsParseData, data); } static const gchar *symbols_valid_path_list[] = { "symbols", "include/symbols", "key/symbols", "text/key/symbols", "keysym/key/symbols", "symbol/key/symbols", "invalid/key/symbols", }; static void symbols_start_element_callback (GMarkupParseContext *pcontext, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { SymbolsParseData *data = user_data; const gchar *attribute; if (!validate (symbols_valid_path_list, G_N_ELEMENTS (symbols_valid_path_list), element_name, data->element_stack, error)) return; if (g_strcmp0 (element_name, "key") == 0) { guint keycode; attribute = get_attribute (attribute_names, attribute_values, "keycode"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"keycode\" attribute for \"key\""); return; } keycode = strtoul (attribute, NULL, 10); data->key = eek_keyboard_find_key_by_keycode (data->keyboard, keycode); /*if (data->key == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "no such keycode %u", keycode); return; }*/ attribute = get_attribute (attribute_names, attribute_values, "groups"); if (attribute != NULL) data->groups = strtol (attribute, NULL, 10); else data->groups = 1; data->symbols = NULL; goto out; } if (g_strcmp0 (element_name, "keysym") == 0) { attribute = get_attribute (attribute_names, attribute_values, "keyval"); if (attribute == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, "no \"keyval\" attribute for \"keysym\""); return; } data->keyval = strtoul (attribute, NULL, 0); } if (g_strcmp0 (element_name, "symbol") == 0 || g_strcmp0 (element_name, "keysym") == 0 || g_strcmp0 (element_name, "text") == 0) { attribute = get_attribute (attribute_names, attribute_values, "label"); if (attribute != NULL) data->label = g_strdup (attribute); attribute = get_attribute (attribute_names, attribute_values, "icon"); if (attribute != NULL) data->icon = g_strdup (attribute); attribute = get_attribute (attribute_names, attribute_values, "tooltip"); if (attribute != NULL) data->tooltip = g_strdup (attribute); attribute = get_attribute (attribute_names, attribute_values, "category"); if (attribute != NULL) data->category = strtoul (attribute, NULL, 10); else data->category = EEK_SYMBOL_CATEGORY_KEYNAME; } out: data->element_stack = g_slist_prepend (data->element_stack, g_strdup (element_name)); data->text->len = 0; } static void symbols_end_element_callback (GMarkupParseContext *pcontext, const gchar *element_name, gpointer user_data, GError **error) { SymbolsParseData *data = user_data; GSList *head = data->element_stack; gchar *text; gint i; g_free (head->data); data->element_stack = g_slist_next (data->element_stack); g_slist_free1 (head); text = g_strndup (data->text->str, data->text->len); if (g_strcmp0 (element_name, "key") == 0) { if (!data->key) { return; } gint num_symbols = g_slist_length (data->symbols); gint levels = num_symbols / data->groups; EekSymbolMatrix *matrix = eek_symbol_matrix_new (data->groups, levels); head = data->symbols = g_slist_reverse (data->symbols); for (i = 0; i < num_symbols; i++) { if (head && head->data) { matrix->data[i] = head->data; head = g_slist_next (head); } else matrix->data[i] = NULL; } g_slist_free (data->symbols); data->symbols = NULL; eek_key_set_symbol_matrix (data->key, matrix); eek_symbol_matrix_free (matrix); data->key = NULL; goto out; } if (g_strcmp0 (element_name, "symbol") == 0 || g_strcmp0 (element_name, "keysym") == 0 || g_strcmp0 (element_name, "text") == 0) { EekSymbol *symbol; if (g_strcmp0 (element_name, "keysym") == 0) { EekKeysym *keysym; if (data->keyval != EEK_INVALID_KEYSYM) keysym = eek_keysym_new (data->keyval); else keysym = eek_keysym_new_from_name (text); symbol = EEK_SYMBOL(keysym); } else if (g_strcmp0 (element_name, "text") == 0) { symbol = EEK_SYMBOL(eek_text_new (text)); } else { symbol = eek_symbol_new (text); eek_symbol_set_category (symbol, EEK_SYMBOL_CATEGORY_KEYNAME); } if (data->label) { eek_symbol_set_label (symbol, data->label); g_free (data->label); data->label = NULL; } if (data->icon) { eek_symbol_set_icon_name (symbol, data->icon); g_free (data->icon); data->icon = NULL; } if (data->tooltip) { eek_symbol_set_tooltip (symbol, data->tooltip); g_free (data->tooltip); data->tooltip = NULL; } data->symbols = g_slist_prepend (data->symbols, symbol); goto out; } if (g_strcmp0 (element_name, "invalid") == 0) { data->symbols = g_slist_prepend (data->symbols, NULL); goto out; } out: g_free (text); } static void symbols_text_callback (GMarkupParseContext *pcontext, const gchar *text, gsize text_len, gpointer user_data, GError **error) { SymbolsParseData *data = user_data; g_string_append_len (data->text, text, text_len); } static const GMarkupParser symbols_parser = { symbols_start_element_callback, symbols_end_element_callback, symbols_text_callback, 0, 0 }; struct _PrerequisitesParseData { GSList *element_stack; GString *text; GList *prerequisites; }; typedef struct _PrerequisitesParseData PrerequisitesParseData; static PrerequisitesParseData * prerequisites_parse_data_new (void) { PrerequisitesParseData *data = g_slice_new0 (PrerequisitesParseData); data->text = g_string_sized_new (BUFSIZE); return data; } static void prerequisites_parse_data_free (PrerequisitesParseData *data) { g_list_free_full (data->prerequisites, g_free); g_string_free (data->text, TRUE); g_slice_free (PrerequisitesParseData, data); } static void prerequisites_start_element_callback (GMarkupParseContext *pcontext, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { PrerequisitesParseData *data = user_data; if (!validate (symbols_valid_path_list, G_N_ELEMENTS (symbols_valid_path_list), element_name, data->element_stack, error)) return; data->element_stack = g_slist_prepend (data->element_stack, g_strdup (element_name)); data->text->len = 0; } static void prerequisites_end_element_callback (GMarkupParseContext *pcontext, const gchar *element_name, gpointer user_data, GError **error) { PrerequisitesParseData *data = user_data; GSList *head = data->element_stack; g_free (head->data); data->element_stack = g_slist_next (data->element_stack); g_slist_free1 (head); if (g_strcmp0 (element_name, "include") == 0) { data->prerequisites = g_list_append (data->prerequisites, g_strndup (data->text->str, data->text->len)); } } static void prerequisites_text_callback (GMarkupParseContext *pcontext, const gchar *text, gsize text_len, gpointer user_data, GError **error) { PrerequisitesParseData *data = user_data; g_string_append_len (data->text, text, text_len); } static const GMarkupParser prerequisites_parser = { prerequisites_start_element_callback, prerequisites_end_element_callback, prerequisites_text_callback, 0, 0 }; static EekKeyboard * eek_xml_layout_real_create_keyboard (EekboardContextService *manager, EekLayout *self, gdouble initial_width, gdouble initial_height) { EekXmlLayout *layout = EEK_XML_LAYOUT (self); EekXmlLayoutPrivate *priv = eek_xml_layout_get_instance_private (layout); gboolean retval; /* Create an empty keyboard to which geometry and symbols information are applied. */ EekKeyboard *keyboard = g_object_new (EEK_TYPE_KEYBOARD, "layout", layout, NULL); keyboard->manager = manager; /* Read geometry information. */ gchar *filename = g_strdup_printf ("%s.xml", priv->desc->geometry); gchar *path = g_build_filename (priv->keyboards_dir, "geometry", filename, NULL); g_free (filename); GError *error = NULL; retval = parse_geometry (path, keyboard, &error); g_free (path); if (!retval) { g_object_unref (keyboard); g_warning ("can't parse geometry file %s: %s", priv->desc->geometry, error->message); g_error_free (error); return NULL; } /* Read symbols information. */ GList *loaded = NULL; retval = parse_symbols_with_prerequisites (priv->keyboards_dir, priv->desc->symbols, keyboard, &loaded, &error); g_list_free_full (loaded, g_free); if (!retval) { g_object_unref (keyboard); g_warning ("can't parse symbols file %s: %s", priv->desc->symbols, error->message); g_error_free (error); return NULL; } /* Fit keyboard in the given width and hight. */ scale_keyboard (keyboard, initial_width, initial_height); /* Use pre-defined modifier mask here. */ eek_keyboard_set_num_lock_mask (keyboard, EEK_MOD2_MASK); eek_keyboard_set_alt_gr_mask (keyboard, EEK_BUTTON1_MASK); return keyboard; } static void eek_xml_layout_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { EekXmlLayout *layout = EEK_XML_LAYOUT (object); EekXmlLayoutPrivate *priv = eek_xml_layout_get_instance_private (layout); switch (prop_id) { case PROP_ID: g_free (priv->id); priv->id = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void eek_xml_layout_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { EekXmlLayout *layout = EEK_XML_LAYOUT (object); EekXmlLayoutPrivate *priv = eek_xml_layout_get_instance_private (layout); switch (prop_id) { case PROP_ID: g_value_set_string (value, priv->id); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void eek_xml_layout_finalize (GObject *object) { EekXmlLayoutPrivate *priv = eek_xml_layout_get_instance_private ( EEK_XML_LAYOUT (object)); g_free (priv->id); if (priv->desc) keyboard_desc_free (priv->desc); g_free (priv->keyboards_dir); G_OBJECT_CLASS (eek_xml_layout_parent_class)->finalize (object); } static void eek_xml_layout_class_init (EekXmlLayoutClass *klass) { EekLayoutClass *layout_class = EEK_LAYOUT_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; layout_class->create_keyboard = eek_xml_layout_real_create_keyboard; gobject_class->set_property = eek_xml_layout_set_property; gobject_class->get_property = eek_xml_layout_get_property; gobject_class->finalize = eek_xml_layout_finalize; pspec = g_param_spec_string ("id", "ID", "ID", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (gobject_class, PROP_ID, pspec); } static void eek_xml_layout_init (EekXmlLayout *self) { /* void */ } EekLayout * eek_xml_layout_new (const gchar *id, GError **error) { return (EekLayout *) g_initable_new (EEK_TYPE_XML_LAYOUT, NULL, error, "id", id, NULL); } static gboolean initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { EekXmlLayout *layout = EEK_XML_LAYOUT (initable); EekXmlLayoutPrivate *priv = eek_xml_layout_get_instance_private (layout); GList *keyboards, *p; gchar *path; EekXmlKeyboardDesc *desc; priv->keyboards_dir = g_strdup ((gchar *) g_getenv ("EEKBOARD_KEYBOARDSDIR")); if (priv->keyboards_dir == NULL) priv->keyboards_dir = g_strdup ("resource:///sm/puri/squeekboard/keyboards/"); path = g_build_filename (priv->keyboards_dir, "keyboards.xml", NULL); keyboards = parse_keyboards (path, error); g_free (path); if (error && *error) return FALSE; for (p = keyboards; p; p = p->next) { desc = p->data; if (g_strcmp0 (desc->id, priv->id) == 0) break; } if (p == NULL) { g_set_error (error, EEK_ERROR, EEK_ERROR_LAYOUT_ERROR, "no such keyboard %s", priv->id); return FALSE; } keyboards = g_list_remove_link (keyboards, p); priv->desc = p->data; g_list_free_1 (p); g_list_free_full (keyboards, (GDestroyNotify) keyboard_desc_free); return TRUE; } static void initable_iface_init (GInitableIface *initable_iface) { initable_iface->init = initable_init; } /** * eek_xml_list_keyboards: * * List available keyboards. * Returns: (transfer container) (element-type utf8): the list of keyboards */ GList * eek_xml_list_keyboards (void) { const gchar *keyboards_dir; gchar *path; GList *keyboards; keyboards_dir = g_getenv ("EEKBOARD_KEYBOARDSDIR"); if (keyboards_dir == NULL) keyboards_dir = g_strdup ("resource:///sm/puri/squeekboard/keyboards/"); path = g_build_filename (keyboards_dir, "keyboards.xml", NULL); keyboards = parse_keyboards (path, NULL); g_free (path); return keyboards; } EekXmlKeyboardDesc * eek_xml_keyboard_desc_copy (EekXmlKeyboardDesc *desc) { return g_slice_dup (EekXmlKeyboardDesc, desc); } void eek_xml_keyboard_desc_free (EekXmlKeyboardDesc *desc) { g_free (desc->id); g_free (desc->name); g_free (desc->geometry); g_free (desc->symbols); g_free (desc->language); g_free (desc->longname); g_slice_free (EekXmlKeyboardDesc, desc); } struct place_data { double desired_width; double current_offset; EekKeyboard *keyboard; }; const double section_spacing = 7.0; static void section_placer(EekElement *element, gpointer user_data) { struct place_data *data = (struct place_data*)user_data; EekBounds section_bounds = {0}; eek_element_get_bounds(element, §ion_bounds); section_bounds.width = data->desired_width; eek_element_set_bounds(element, §ion_bounds); // Sections are rows now. Gather up all the keys and adjust their bounds. eek_section_place_keys(EEK_SECTION(element), EEK_KEYBOARD(data->keyboard)); eek_element_get_bounds(element, §ion_bounds); section_bounds.y = data->current_offset; eek_element_set_bounds(element, §ion_bounds); data->current_offset += section_bounds.height + section_spacing; } static void section_counter(EekElement *element, gpointer user_data) { double *total_height = user_data; EekBounds section_bounds = {0}; eek_element_get_bounds(element, §ion_bounds); *total_height += section_bounds.height + section_spacing; } static gboolean parse_geometry (const gchar *path, EekKeyboard *keyboard, GError **error) { GeometryParseData *data; GMarkupParseContext *pcontext; GHashTable *oref_hash; GHashTableIter iter; gpointer k, v; GFile *file; GFileInputStream *input; gboolean retval; file = g_str_has_prefix (path, "resource://") ? g_file_new_for_uri (path) : g_file_new_for_path (path); input = g_file_read (file, NULL, error); g_object_unref (file); if (input == NULL) return FALSE; data = geometry_parse_data_new (keyboard); pcontext = g_markup_parse_context_new (&geometry_parser, 0, data, NULL); retval = parse (pcontext, G_INPUT_STREAM (input), error); g_markup_parse_context_free (pcontext); g_object_unref (input); if (!retval) { geometry_parse_data_free (data); return FALSE; } /* Resolve outline references. */ oref_hash = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_iter_init (&iter, data->oref_outline_hash); while (g_hash_table_iter_next (&iter, &k, &v)) { EekOutline *outline = v; gulong oref; oref = eek_keyboard_add_outline (data->keyboard, outline); g_hash_table_insert (oref_hash, k, GUINT_TO_POINTER(oref)); } g_hash_table_iter_init (&iter, data->key_oref_hash); while (g_hash_table_iter_next (&iter, &k, &v)) { gpointer oref; if (g_hash_table_lookup_extended (oref_hash, v, NULL, &oref)) eek_key_set_oref (EEK_KEY(k), GPOINTER_TO_UINT(oref)); } g_hash_table_destroy (oref_hash); /* Order rows */ // This needs to be done after outlines, because outlines define key sizes // TODO: do this only for rows without bounds // The keyboard width is given by the user via screen size. The height will be given dynamically. // TODO: calculate max line width beforehand for button centering. Leave keyboard centering to the renderer later EekBounds keyboard_bounds = {0}; eek_element_get_bounds(EEK_ELEMENT(keyboard), &keyboard_bounds); struct place_data placer_data = { .desired_width = keyboard_bounds.width, .current_offset = 0, .keyboard = keyboard, }; eek_container_foreach_child(EEK_CONTAINER(keyboard), section_placer, &placer_data); double total_height = 0; eek_container_foreach_child(EEK_CONTAINER(keyboard), section_counter, &total_height); keyboard_bounds.height = total_height; eek_element_set_bounds(EEK_ELEMENT(keyboard), &keyboard_bounds); geometry_parse_data_free (data); return TRUE; } static gboolean parse_symbols_with_prerequisites (const gchar *keyboards_dir, const gchar *name, EekKeyboard *keyboard, GList **loaded, GError **error) { gchar *filename, *path; GList *prerequisites, *p; GError *prerequisites_error; gboolean retval; for (p = *loaded; p; p = p->next) { if (g_strcmp0 (p->data, name) == 0) { g_set_error (error, EEK_ERROR, EEK_ERROR_LAYOUT_ERROR, "%s already loaded", name); return FALSE; } } *loaded = g_list_prepend (*loaded, g_strdup (name)); filename = g_strdup_printf ("%s.xml", name); path = g_build_filename (keyboards_dir, "symbols", filename, NULL); g_free (filename); prerequisites_error = NULL; prerequisites = parse_prerequisites (path, &prerequisites_error); if (prerequisites_error != NULL) { g_propagate_error (error, prerequisites_error); return FALSE; } for (p = prerequisites; p; p = p->next) { retval = parse_symbols_with_prerequisites (keyboards_dir, p->data, keyboard, loaded, error); if (!retval) return FALSE; } g_list_free_full (prerequisites, (GDestroyNotify)g_free); retval = parse_symbols (path, keyboard, error); g_free (path); return retval; } static gboolean parse_symbols (const gchar *path, EekKeyboard *keyboard, GError **error) { SymbolsParseData *data; GMarkupParseContext *pcontext; GFile *file; GFileInputStream *input; gboolean retval; file = g_str_has_prefix (path, "resource://") ? g_file_new_for_uri (path) : g_file_new_for_path (path); input = g_file_read (file, NULL, error); g_object_unref (file); if (input == NULL) return FALSE; data = symbols_parse_data_new (keyboard); pcontext = g_markup_parse_context_new (&symbols_parser, 0, data, NULL); retval = parse (pcontext, G_INPUT_STREAM (input), error); g_markup_parse_context_free (pcontext); g_object_unref (input); if (!retval) { symbols_parse_data_free (data); return FALSE; } symbols_parse_data_free (data); return TRUE; } static GList * parse_prerequisites (const gchar *path, GError **error) { PrerequisitesParseData *data; GMarkupParseContext *pcontext; GFile *file; GFileInputStream *input; GList *prerequisites; gboolean retval; file = g_str_has_prefix (path, "resource://") ? g_file_new_for_uri (path) : g_file_new_for_path (path); input = g_file_read (file, NULL, error); g_object_unref (file); if (input == NULL) return FALSE; data = prerequisites_parse_data_new (); pcontext = g_markup_parse_context_new (&prerequisites_parser, 0, data, NULL); retval = parse (pcontext, G_INPUT_STREAM (input), error); g_markup_parse_context_free (pcontext); g_object_unref (input); if (!retval) { prerequisites_parse_data_free (data); return NULL; } prerequisites = data->prerequisites; data->prerequisites = NULL; prerequisites_parse_data_free (data); return prerequisites; } static GList * parse_keyboards (const gchar *path, GError **error) { KeyboardsParseData *data; GMarkupParseContext *pcontext; GFile *file; GFileInputStream *input; GList *keyboards; gboolean retval; file = g_str_has_prefix (path, "resource://") ? g_file_new_for_uri (path) : g_file_new_for_path (path); input = g_file_read (file, NULL, error); g_object_unref (file); if (input == NULL) return NULL; data = keyboards_parse_data_new (); pcontext = g_markup_parse_context_new (&keyboards_parser, 0, data, NULL); retval = parse (pcontext, G_INPUT_STREAM (input), error); g_object_unref (input); g_markup_parse_context_free (pcontext); if (!retval) { keyboards_parse_data_free (data); return NULL; } keyboards = data->keyboards; data->keyboards = NULL; keyboards_parse_data_free (data); return keyboards; } static void scale_bounds_callback (EekElement *element, gpointer user_data); static void scale_bounds (EekElement *element, gdouble scale) { EekBounds bounds; eek_element_get_bounds (element, &bounds); bounds.x *= scale; bounds.y *= scale; bounds.width *= scale; bounds.height *= scale; eek_element_set_bounds (element, &bounds); if (EEK_IS_CONTAINER(element)) eek_container_foreach_child (EEK_CONTAINER(element), scale_bounds_callback, &scale); } static void scale_bounds_callback (EekElement *element, gpointer user_data) { scale_bounds (element, *(gdouble *)user_data); } static void scale_keyboard (EekKeyboard *keyboard, gdouble width, gdouble height) { gdouble scale; EekBounds bounds; gsize n_outlines; guint i; eek_element_get_bounds (EEK_ELEMENT(keyboard), &bounds); if (width * bounds.height < height * bounds.width) scale = width / bounds.width; else scale = height / bounds.height; scale_bounds (EEK_ELEMENT(keyboard), scale); n_outlines = eek_keyboard_get_n_outlines (keyboard); for (i = 0; i < n_outlines; i++) { EekOutline *outline = eek_keyboard_get_outline (keyboard, i); gint j; for (j = 0; j < outline->num_points; j++) { outline->points[j].x *= scale; outline->points[j].y *= scale; } } } static gboolean validate (const gchar **valid_path_list, gsize valid_path_list_len, const gchar *element_name, GSList *element_stack, GError **error) { gint i; gchar *element_path; GSList *head, *p; GString *string; head = g_slist_prepend (element_stack, (gchar *)element_name); string = g_string_sized_new (64); for (p = head; p; p = p->next) { g_string_append (string, p->data); if (g_slist_next (p)) g_string_append (string, "/"); } element_path = g_string_free (string, FALSE); g_slist_free1 (head); for (i = 0; i < valid_path_list_len; i++) if (g_strcmp0 (element_path, valid_path_list[i]) == 0) break; g_free (element_path); if (i == valid_path_list_len) { gchar *reverse_element_path; head = g_slist_reverse (g_slist_copy (element_stack)); string = g_string_sized_new (64); for (p = head; p; p = p->next) { g_string_append (string, p->data); if (g_slist_next (p)) g_string_append (string, "/"); } reverse_element_path = g_string_free (string, FALSE); abort (); g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, "%s cannot appear as %s", element_name, reverse_element_path); g_free (reverse_element_path); return FALSE; } return TRUE; } static gboolean parse (GMarkupParseContext *pcontext, GInputStream *input, GError **error) { gchar buffer[BUFSIZE]; while (1) { gssize nread = g_input_stream_read (input, buffer, sizeof (buffer), NULL, error); if (nread < 0) return FALSE; if (nread == 0) break; if (!g_markup_parse_context_parse (pcontext, buffer, nread, error)) return FALSE; } return g_markup_parse_context_end_parse (pcontext, error); } static const gchar * get_attribute (const gchar **names, const gchar **values, const gchar *name) { for (; *names && *values; names++, values++) { if (g_strcmp0 (*names, name) == 0) return *values; } return NULL; }