/* * Copyright (C) 2002-2006 Sergey V. Udaltsov * * 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, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "xklavier_private.h" #include "xklavier_private_xkb.h" #ifdef LIBXKBFILE_PRESENT #include #include #endif #define XKBCOMP ( XKB_BIN_BASE "/xkbcomp" ) #define XK_XKB_KEYS #include #ifdef LIBXKBFILE_PRESENT static XkbRF_RulesPtr xkl_rules; static XkbRF_RulesPtr xkl_rules_set_load(XklEngine * engine) { XkbRF_RulesPtr rules_set = NULL; char file_name[MAXPATHLEN] = ""; char *rf = xkl_engine_get_ruleset_name(engine, XKB_DEFAULT_RULESET); char *locale = NULL; if (rf == NULL) { xkl_last_error_message = "Could not find the XKB rules set"; return NULL; } locale = setlocale(LC_ALL, NULL); g_snprintf(file_name, sizeof file_name, XKB_BASE "/rules/%s", rf); xkl_debug(160, "Loading rules from [%s]\n", file_name); rules_set = XkbRF_Load(file_name, locale, True, True); if (rules_set == NULL) { xkl_last_error_message = "Could not load rules"; return NULL; } return rules_set; } static void xkl_rules_set_free(void) { if (xkl_rules) XkbRF_Free(xkl_rules, True); xkl_rules = NULL; } #endif void xkl_xkb_init_config_registry(XklConfigRegistry * config) { #ifdef LIBXKBFILE_PRESENT XkbInitAtoms(NULL); #endif } gboolean xkl_xkb_load_config_registry(XklConfigRegistry * config, gboolean if_extras_needed) { return xkl_config_registry_load_helper(config, XKB_DEFAULT_RULESET, XKB_BASE "/rules", if_extras_needed); } #ifdef LIBXKBFILE_PRESENT gboolean xkl_xkb_config_native_prepare(XklEngine * engine, const XklConfigRec * data, XkbComponentNamesPtr component_names_ptr) { XkbRF_VarDefsRec xkl_var_defs; gboolean got_components; memset(&xkl_var_defs, 0, sizeof(xkl_var_defs)); xkl_rules = xkl_rules_set_load(engine); if (!xkl_rules) { return FALSE; } xkl_var_defs.model = (char *) data->model; if (data->layouts != NULL) xkl_var_defs.layout = xkl_config_rec_merge_layouts(data); if (data->variants != NULL) xkl_var_defs.variant = xkl_config_rec_merge_variants(data); if (data->options != NULL) xkl_var_defs.options = xkl_config_rec_merge_options(data); got_components = XkbRF_GetComponents(xkl_rules, &xkl_var_defs, component_names_ptr); g_free(xkl_var_defs.layout); g_free(xkl_var_defs.variant); g_free(xkl_var_defs.options); if (!got_components) { xkl_last_error_message = "Could not translate rules into components"; /* Just cleanup the stuff in case of failure */ xkl_xkb_config_native_cleanup(engine, component_names_ptr); return FALSE; } if (xkl_debug_level >= 200) { xkl_debug(200, "keymap: %s\n", component_names_ptr->keymap); xkl_debug(200, "keycodes: %s\n", component_names_ptr->keycodes); xkl_debug(200, "compat: %s\n", component_names_ptr->compat); xkl_debug(200, "types: %s\n", component_names_ptr->types); xkl_debug(200, "symbols: %s\n", component_names_ptr->symbols); xkl_debug(200, "geometry: %s\n", component_names_ptr->geometry); } return TRUE; } void xkl_xkb_config_native_cleanup(XklEngine * engine, XkbComponentNamesPtr component_names_ptr) { xkl_rules_set_free(); g_free(component_names_ptr->keymap); g_free(component_names_ptr->keycodes); g_free(component_names_ptr->compat); g_free(component_names_ptr->types); g_free(component_names_ptr->symbols); g_free(component_names_ptr->geometry); } static gchar * xkl_config_get_current_group_description(XklEngine * engine) { XklState state; xkl_xkb_get_server_state(engine, &state); int group = state.group; if ((group < 0) || (group >= xkl_engine_backend(engine, XklXkb, cached_desc)->ctrls->num_groups)) return NULL; return g_strdup(xkl_engine_backend(engine, XklXkb, group_names) [group]); } static void xkl_config_set_group_by_description(XklEngine * engine, gchar * descr) { int group, n_groups; gchar **group_names; if (descr == NULL) return; // perhaps could be made mode lightweight? xkl_engine_reset_all_info(engine, FALSE, "Direct reload on activation"); n_groups = xkl_engine_backend(engine, XklXkb, cached_desc)->ctrls->num_groups; group_names = xkl_engine_backend(engine, XklXkb, group_names); for (group = 0; group < n_groups; group++, group_names++) { if (!g_ascii_strcasecmp(descr, *group_names)) { xkl_debug(150, "Found the group with the same description, %d: [%s]\n", group, *group_names); xkl_engine_lock_group(engine, group); break; } } g_free(descr); } static XkbDescPtr xkl_config_get_keyboard(XklEngine * engine, XkbComponentNamesPtr component_names_ptr, gboolean activate) { XkbDescPtr xkb = NULL; char xkm_fn[L_tmpnam]; char xkb_fn[L_tmpnam]; FILE *tmpxkm; XkbFileInfo result; int xkmloadres; Display *display = xkl_engine_get_display(engine); gchar *preactivation_group_description = activate ? xkl_config_get_current_group_description(engine) : NULL; if (tmpnam(xkm_fn) != NULL && tmpnam(xkb_fn) != NULL) { pid_t cpid, pid; int status = 0; FILE *tmpxkb; xkl_debug(150, "tmp XKB/XKM file names: [%s]/[%s]\n", xkb_fn, xkm_fn); if ((tmpxkb = fopen(xkb_fn, "w")) != NULL) { fprintf(tmpxkb, "xkb_keymap {\n"); fprintf(tmpxkb, " xkb_keycodes { include \"%s\" };\n", component_names_ptr->keycodes); fprintf(tmpxkb, " xkb_types { include \"%s\" };\n", component_names_ptr->types); fprintf(tmpxkb, " xkb_compat { include \"%s\" };\n", component_names_ptr->compat); fprintf(tmpxkb, " xkb_symbols { include \"%s\" };\n", component_names_ptr->symbols); fprintf(tmpxkb, " xkb_geometry { include \"%s\" };\n", component_names_ptr->geometry); fprintf(tmpxkb, "};\n"); fclose(tmpxkb); xkl_debug(150, "xkb_keymap {\n" " xkb_keycodes { include \"%s\" };\n" " xkb_types { include \"%s\" };\n" " xkb_compat { include \"%s\" };\n" " xkb_symbols { include \"%s\" };\n" " xkb_geometry { include \"%s\" };\n};\n", component_names_ptr->keycodes, component_names_ptr->types, component_names_ptr->compat, component_names_ptr->symbols, component_names_ptr->geometry); XSync(display, False); /* From this point, ALL errors should be intercepted only by libxklavier */ xkl_engine_priv(engine, critical_section) = TRUE; cpid = fork(); switch (cpid) { case -1: xkl_debug(0, "Could not fork: %d\n", errno); break; case 0: /* child */ xkl_debug(160, "Executing %s\n", XKBCOMP); xkl_debug(160, "%s %s %s %s %s %s %s %s\n", XKBCOMP, XKBCOMP, "-w0", "-I", "-I" XKB_BASE, "-xkm", xkb_fn, xkm_fn); execl(XKBCOMP, XKBCOMP, "-w0", "-I", "-I" XKB_BASE, "-xkm", xkb_fn, xkm_fn, NULL); xkl_debug(0, "Could not exec %s: %d\n", XKBCOMP, errno); exit(1); default: /* parent */ pid = waitpid(cpid, &status, 0); xkl_debug(150, "Return status of %d (well, started %d): %d\n", pid, cpid, status); memset((char *) &result, 0, sizeof(result)); result.xkb = XkbAllocKeyboard(); if (Success == XkbChangeKbdDisplay(display, &result)) { xkl_debug(150, "Hacked the kbddesc - set the display...\n"); if ((tmpxkm = fopen(xkm_fn, "r")) != NULL) { xkmloadres = XkmReadFile(tmpxkm, XkmKeymapLegal, XkmKeymapLegal, &result); xkl_debug(150, "Loaded %s output as XKM file, got %d (comparing to %d)\n", XKBCOMP, (int) xkmloadres, (int) XkmKeymapLegal); if ((int) xkmloadres != (int) XkmKeymapLegal) { xkl_debug(150, "Loaded legal keymap\n"); if (activate) { xkl_debug (150, "Activating it...\n"); if (XkbWriteToServer(&result)) { xkl_debug (150, "Updating the keyboard...\n"); xkb = result.xkb; } else { xkl_debug (0, "Could not write keyboard description to the server\n"); } } else /* no activate, just load */ xkb = result.xkb; } else { /* could not load properly */ xkl_debug(0, "Could not load %s output as XKM file, got %d (asked %d)\n", XKBCOMP, (int) xkmloadres, (int) XkmKeymapLegal); } fclose(tmpxkm); xkl_debug(160, "Unlinking the temporary xkm file %s\n", xkm_fn); if (xkl_debug_level < 500) { /* don't remove on high debug levels! */ if (remove(xkm_fn) == -1) xkl_debug (0, "Could not unlink the temporary xkm file %s: %d\n", xkm_fn, errno); } else xkl_debug(500, "Well, not really - the debug level is too high: %d\n", xkl_debug_level); } else { /* could not open the file */ xkl_debug(0, "Could not open the temporary xkm file %s\n", xkm_fn); } } else { /* could not assign to display */ xkl_debug(0, "Could not change the keyboard description to display\n"); } if (xkb == NULL) XkbFreeKeyboard(result.xkb, XkbAllComponentsMask, True); break; } XSync(display, False); /* Return to normal X error processing */ xkl_engine_priv(engine, critical_section) = FALSE; if (activate) xkl_config_set_group_by_description(engine, preactivation_group_description); xkl_debug(160, "Unlinking the temporary xkb file %s\n", xkb_fn); if (xkl_debug_level < 500) { /* don't remove on high debug levels! */ if (remove(xkb_fn) == -1) xkl_debug(0, "Could not unlink the temporary xkb file %s: %d\n", xkb_fn, errno); } else xkl_debug(500, "Well, not really - the debug level is too high: %d\n", xkl_debug_level); } else { /* could not open input tmp file */ xkl_debug(0, "Could not open tmp XKB file [%s]: %d\n", xkb_fn, errno); } } else { xkl_debug(0, "Could not get tmp names\n"); } return xkb; } #else /* no XKB headers */ gboolean xkl_xkb_config_native_prepare(XklEngine * engine, const XklConfigRec * data, gpointer componentNamesPtr) { return FALSE; } void xkl_xkb_config_native_cleanup(XklEngine * engine, gpointer component_names_ptr) { } #endif /* check only client side support */ gboolean xkl_xkb_multiple_layouts_supported(XklEngine * engine) { enum { NON_SUPPORTED, SUPPORTED, UNCHECKED }; static int support_state = UNCHECKED; if (support_state == UNCHECKED) { XklConfigRec *data = xkl_config_rec_new(); #ifdef LIBXKBFILE_PRESENT XkbComponentNamesRec component_names; memset(&component_names, 0, sizeof(component_names)); #endif data->model = g_strdup("pc105"); data->layouts = g_strsplit_set("us:de", ":", -1); data->variants = g_strsplit_set(":", ":", -1); data->options = NULL; xkl_debug(100, "!!! Checking multiple layouts support\n"); support_state = NON_SUPPORTED; #ifdef LIBXKBFILE_PRESENT if (xkl_xkb_config_native_prepare (engine, data, &component_names)) { xkl_debug(100, "!!! Multiple layouts ARE supported\n"); support_state = SUPPORTED; xkl_xkb_config_native_cleanup(engine, &component_names); } else { xkl_debug(100, "!!! Multiple layouts ARE NOT supported\n"); } #endif g_object_unref(G_OBJECT(data)); } return support_state == SUPPORTED; } gboolean xkl_xkb_activate_config_rec(XklEngine * engine, const XklConfigRec * data) { gboolean rv = FALSE; #if 0 { int i; xkl_debug(150, "New model: [%s]\n", data->model); xkl_debug(150, "New layouts: %p\n", data->layouts); for (i = 0; i < g_strv_length(data->layouts); i++) xkl_debug(150, "New layout[%d]: [%s]\n", i, data->layouts[i]); xkl_debug(150, "New variants: %p\n", data->variants); for (i = 0; i < g_strv_length(data->variants); i++) xkl_debug(150, "New variant[%d]: [%s]\n", i, data->variants[i]); xkl_debug(150, "New options: %p\n", data->options); for (i = 0; i < g_strv_length(data->options); i++) xkl_debug(150, "New option[%d]: [%s]\n", i, data->options[i]); } #endif #ifdef LIBXKBFILE_PRESENT XkbComponentNamesRec component_names; memset(&component_names, 0, sizeof(component_names)); if (xkl_xkb_config_native_prepare(engine, data, &component_names)) { XkbDescPtr xkb; xkb = xkl_config_get_keyboard(engine, &component_names, TRUE); if (xkb != NULL) { if (xkl_config_rec_set_to_root_window_property (data, xkl_engine_priv(engine, base_config_atom), xkl_engine_get_ruleset_name(engine, XKB_DEFAULT_RULESET), engine)) /* We do not need to check the result of _XklGetRulesSetName - because PrepareBeforeKbd did it for us */ rv = TRUE; else xkl_last_error_message = "Could not set names property"; XkbFreeKeyboard(xkb, XkbAllComponentsMask, True); } else { xkl_last_error_message = "Could not load keyboard description"; } xkl_xkb_config_native_cleanup(engine, &component_names); } #endif return rv; } gboolean xkl_xkb_write_config_rec_to_file(XklEngine * engine, const char *file_name, const XklConfigRec * data, const gboolean binary) { gboolean rv = FALSE; #ifdef LIBXKBFILE_PRESENT XkbComponentNamesRec component_names; FILE *output = fopen(file_name, "w"); XkbFileInfo dump_info; if (output == NULL) { xkl_last_error_message = "Could not open the XKB file"; return FALSE; } memset(&component_names, 0, sizeof(component_names)); if (xkl_xkb_config_native_prepare(engine, data, &component_names)) { XkbDescPtr xkb; xkb = xkl_config_get_keyboard(engine, &component_names, FALSE); if (xkb != NULL) { dump_info.defined = 0; dump_info.xkb = xkb; dump_info.type = XkmKeymapFile; if (binary) rv = XkbWriteXKMFile(output, &dump_info); else rv = XkbWriteXKBFile(output, &dump_info, True, NULL, NULL); XkbFreeKeyboard(xkb, XkbGBN_AllComponentsMask, True); } else xkl_last_error_message = "Could not load keyboard description"; xkl_xkb_config_native_cleanup(engine, &component_names); } fclose(output); #endif return rv; }