/* * 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 "xklavier_private.h" #include "xkl_engine_marshal.h" static GObjectClass *parent_class = NULL; static XklEngine *the_engine = NULL; gint xkl_debug_level = 0; static XklLogAppender log_appender = xkl_default_log_appender; const gchar *xkl_last_error_message; enum { PROP_0, PROP_DISPLAY, PROP_BACKEND_NAME, PROP_FEATURES, PROP_MAX_NUM_GROUPS, PROP_NUM_GROUPS, PROP_DEFAULT_GROUP, PROP_SECONDARY_GROUPS_MASK, PROP_INDICATORS_HANDLING, }; void xkl_engine_set_indicators_handling(XklEngine * engine, gboolean whether_handle) { xkl_engine_priv(engine, handle_indicators) = whether_handle; } gboolean xkl_engine_get_indicators_handling(XklEngine * engine) { return xkl_engine_priv(engine, handle_indicators); } void xkl_set_debug_level(int level) { xkl_debug_level = level; } void xkl_engine_set_group_per_toplevel_window(XklEngine * engine, gboolean is_set) { xkl_engine_priv(engine, group_per_toplevel_window) = is_set; } gboolean xkl_engine_is_group_per_toplevel_window(XklEngine * engine) { return xkl_engine_priv(engine, group_per_toplevel_window); } static void xkl_engine_set_switch_to_secondary_group(XklEngine * engine, gboolean val) { gulong propval = (gulong) val == TRUE; Display *dpy = xkl_engine_get_display(engine); XChangeProperty(dpy, xkl_engine_priv(engine, root_window), xkl_engine_priv(engine, atoms)[XKLAVIER_ALLOW_SECONDARY], XA_INTEGER, 32, PropModeReplace, (unsigned char *) &propval, 1); XSync(dpy, False); } void xkl_engine_allow_one_switch_to_secondary_group(XklEngine * engine) { xkl_debug(150, "Setting allow_one_switch_to_secondary_group flag\n"); xkl_engine_set_switch_to_secondary_group(engine, TRUE); } gboolean xkl_engine_is_one_switch_to_secondary_group_allowed(XklEngine * engine) { gboolean rv = FALSE; unsigned char *propval = NULL; Atom actual_type; int actual_format; unsigned long bytes_remaining; unsigned long actual_items; int result; result = XGetWindowProperty(xkl_engine_get_display(engine), xkl_engine_priv(engine, root_window), xkl_engine_priv(engine, atoms) [XKLAVIER_ALLOW_SECONDARY], 0L, 1L, False, XA_INTEGER, &actual_type, &actual_format, &actual_items, &bytes_remaining, &propval); if (Success == result) { if (actual_format == 32 && actual_items == 1) { rv = (gboolean) * (Bool *) propval; } XFree(propval); } return rv; } void xkl_engine_one_switch_to_secondary_group_performed(XklEngine * engine) { xkl_debug(150, "Resetting allow_one_switch_to_secondary_group flag\n"); xkl_engine_set_switch_to_secondary_group(engine, FALSE); } void xkl_engine_set_default_group(XklEngine * engine, gint group) { xkl_engine_priv(engine, default_group) = group; } gint xkl_engine_get_default_group(XklEngine * engine) { return xkl_engine_priv(engine, default_group); } void xkl_engine_set_secondary_groups_mask(XklEngine * engine, guint mask) { xkl_engine_priv(engine, secondary_groups_mask) = mask; } guint xkl_engine_get_secondary_groups_mask(XklEngine * engine) { return xkl_engine_priv(engine, secondary_groups_mask); } void xkl_set_log_appender(XklLogAppender func) { log_appender = func; } gint xkl_engine_start_listen(XklEngine * engine, guint what) { int i; guchar *cntr = xkl_engine_priv(engine, listener_type_counters); for (i = 0; i < XKLL_NUMBER_OF_LISTEN_MODES; i++, cntr++) if (what & (1 << i)) (*cntr)++; if (! (xkl_engine_priv(engine, features) & XKLF_REQUIRES_MANUAL_LAYOUT_MANAGEMENT) && (what & XKLL_MANAGE_LAYOUTS)) xkl_debug(0, "The backend does not require manual layout management - " "but it is provided by the application\n"); xkl_engine_resume_listen(engine); xkl_engine_load_window_tree(engine); XFlush(xkl_engine_get_display(engine)); return 0; } gint xkl_engine_stop_listen(XklEngine * engine, guint what) { int i; gboolean no_flags_remains = TRUE; guchar *cntr = xkl_engine_priv(engine, listener_type_counters); for (i = 0; i < XKLL_NUMBER_OF_LISTEN_MODES; i++, cntr++) { int mask = 1 << i; if (what & mask) (*cntr)--; if (*cntr) no_flags_remains = FALSE; } if (no_flags_remains) xkl_engine_pause_listen(engine); return 0; } XklEngine * xkl_engine_get_instance(Display * display) { if (the_engine != NULL) { g_object_ref(G_OBJECT(the_engine)); return the_engine; } if (!display) { xkl_debug(10, "xkl_init : display is NULL ?\n"); return NULL; } the_engine = XKL_ENGINE(g_object_new (xkl_engine_get_type(), "display", display, NULL)); return the_engine; } gboolean xkl_engine_grab_key(XklEngine * engine, gint keycode, guint modifiers) { gboolean ret_code; gchar *keyname; Display *dpy = xkl_engine_get_display(engine); if (xkl_debug_level >= 100) { keyname = XKeysymToString(XKeycodeToKeysym(dpy, keycode, 0)); xkl_debug(100, "Listen to the key %d/(%s)/%d\n", keycode, keyname, modifiers); } if (0 == keycode) return FALSE; xkl_engine_priv(engine, last_error_code) = Success; ret_code = XGrabKey(dpy, keycode, modifiers, xkl_engine_priv(engine, root_window), TRUE, GrabModeAsync, GrabModeAsync); XSync(dpy, False); xkl_debug(100, "XGrabKey recode %d/error %d\n", ret_code, xkl_engine_priv(engine, last_error_code)); ret_code = (xkl_engine_priv(engine, last_error_code) == Success); if (!ret_code) xkl_last_error_message = "Could not grab the key"; return ret_code; } gboolean xkl_engine_ungrab_key(XklEngine * engine, gint keycode, guint modifiers) { if (0 == keycode) return FALSE; return Success == XUngrabKey(xkl_engine_get_display(engine), keycode, 0, xkl_engine_priv(engine, root_window)); } gint xkl_engine_get_next_group(XklEngine * engine) { gint n = xkl_engine_get_num_groups(engine); return (xkl_engine_priv(engine, curr_state).group + 1) % n; } gint xkl_engine_get_prev_group(XklEngine * engine) { gint n = xkl_engine_get_num_groups(engine); return (xkl_engine_priv(engine, curr_state).group + n - 1) % n; } gint xkl_engine_get_current_window_group(XklEngine * engine) { XklState state; if (xkl_engine_priv(engine, curr_toplvl_win) == (Window) NULL) { xkl_debug(150, "cannot restore without current client\n"); } else if (xkl_engine_get_toplevel_window_state (engine, xkl_engine_priv(engine, curr_toplvl_win), &state)) { return state.group; } else xkl_debug(150, "Unbelievable: current client " WINID_FORMAT ", '%s' has no group\n", xkl_engine_priv(engine, curr_toplvl_win), xkl_get_debug_window_title(engine, xkl_engine_priv (engine, curr_toplvl_win))); return 0; } void xkl_engine_set_window_transparent(XklEngine * engine, Window win, gboolean transparent) { Window toplevel_win; xkl_debug(150, "setting transparent flag %d for " WINID_FORMAT "\n", transparent, win); if (!xkl_engine_find_toplevel_window(engine, win, &toplevel_win)) { xkl_debug(150, "No toplevel window!\n"); /* toplevel_win = win; */ return; } xkl_engine_set_toplevel_window_transparent(engine, toplevel_win, transparent); } gboolean xkl_engine_is_window_transparent(XklEngine * engine, Window win) { Window toplevel_win; if (!xkl_engine_find_toplevel_window(engine, win, &toplevel_win)) return FALSE; return xkl_engine_is_toplevel_window_transparent(engine, toplevel_win); } /* * Loads the tree recursively. */ gboolean xkl_engine_load_window_tree(XklEngine * engine) { Window focused; int revert; gboolean retval = TRUE, have_toplevel_win; if (xkl_engine_is_listening_for(engine, XKLL_MANAGE_WINDOW_STATES)) retval = xkl_engine_load_subtree(engine, xkl_engine_priv(engine, root_window), 0, &xkl_engine_priv(engine, curr_state)); XGetInputFocus(xkl_engine_get_display(engine), &focused, &revert); xkl_debug(160, "initially focused: " WINID_FORMAT ", '%s'\n", focused, xkl_get_debug_window_title(engine, focused)); have_toplevel_win = xkl_engine_find_toplevel_window(engine, focused, &xkl_engine_priv(engine, curr_toplvl_win)); if (have_toplevel_win) { XklState old_state; old_state = xkl_engine_priv (engine, curr_state); gboolean have_state = xkl_engine_get_toplevel_window_state(engine, xkl_engine_priv (engine, curr_toplvl_win), &xkl_engine_priv (engine, curr_state)); xkl_debug(160, "initial toplevel: " WINID_FORMAT ", '%s' %s state %d/%X\n", xkl_engine_priv(engine, curr_toplvl_win), xkl_get_debug_window_title(engine, xkl_engine_priv (engine, curr_toplvl_win)), (have_state ? "with" : "without"), (have_state ? xkl_engine_priv(engine, curr_state).group : -1), (have_state ? xkl_engine_priv(engine, curr_state).indicators : -1)); if (old_state.group != xkl_engine_priv (engine, curr_state).group) { xkl_engine_lock_group (engine, xkl_engine_priv (engine, curr_state).group); } } else { xkl_debug(160, "Could not find initial app. " "Probably, focus belongs to some WM service window. " "Will try to survive:)"); } return retval; } void _xkl_debug(const gchar file[], const gchar function[], gint level, const gchar format[], ...) { va_list lst; if (level > xkl_debug_level) return; va_start(lst, format); if (log_appender != NULL) (*log_appender) (file, function, level, format, lst); va_end(lst); } void xkl_default_log_appender(const gchar file[], const gchar function[], gint level, const gchar format[], va_list args) { time_t now = time(NULL); fprintf(stdout, "[%08ld,%03d,%s:%s/] \t", (long) now, level, file, function); vfprintf(stdout, format, args); } /* * Just selects some events from the window. */ void xkl_engine_select_input(XklEngine * engine, Window win, gulong mask) { if (xkl_engine_priv(engine, root_window) == win) xkl_debug(160, "Someone is looking for %lx on root window ***\n", mask); XSelectInput(xkl_engine_get_display(engine), win, mask); } void xkl_engine_select_input_merging(XklEngine * engine, Window win, gulong mask) { XWindowAttributes attrs; gulong oldmask = 0L, newmask; memset(&attrs, 0, sizeof(attrs)); if (XGetWindowAttributes (xkl_engine_get_display(engine), win, &attrs)) oldmask = attrs.your_event_mask; newmask = oldmask | mask; if (newmask != oldmask) xkl_engine_select_input(engine, win, newmask); } void xkl_engine_try_call_state_func(XklEngine * engine, XklEngineStateChange change_type, XklState * old_state) { gint group = xkl_engine_priv(engine, curr_state).group; gboolean restore = old_state->group == group; xkl_debug(150, "change_type: %d, group: %d, secondary_group_mask: %X, allowsecondary: %d\n", change_type, group, xkl_engine_priv(engine, secondary_groups_mask), xkl_engine_is_one_switch_to_secondary_group_allowed (engine)); if (change_type == GROUP_CHANGED) { if (!restore) { if ((xkl_engine_priv(engine, secondary_groups_mask) & (1 << group)) != 0 && !xkl_engine_is_one_switch_to_secondary_group_allowed (engine)) { xkl_debug(150, "secondary -> go next\n"); group = xkl_engine_get_next_group(engine); xkl_engine_lock_group(engine, group); return; /* we do not need to revalidate */ } } xkl_engine_one_switch_to_secondary_group_performed(engine); } g_signal_emit_by_name(engine, "X-state-changed", change_type, xkl_engine_priv(engine, curr_state).group, restore); } void xkl_engine_ensure_vtable_inited(XklEngine * engine) { char *p; if (xkl_engine_priv(engine, backend_id) == NULL) { xkl_debug(0, "ERROR: XKL VTable is NOT initialized.\n"); /* force the crash! */ p = NULL; *p = '\0'; } } const gchar * xkl_engine_get_backend_name(XklEngine * engine) { return xkl_engine_priv(engine, backend_id); } guint xkl_engine_get_features(XklEngine * engine) { return xkl_engine_priv(engine, features); } void xkl_engine_reset_all_info(XklEngine * engine, gboolean force, const gchar reason[]) { xkl_debug(150, "Resetting all the cached info, reason: [%s]\n", reason); xkl_engine_ensure_vtable_inited(engine); if (force || !xkl_engine_vcall(engine, if_cached_info_equals_actual) (engine)) { xkl_engine_vcall(engine, free_all_info) (engine); xkl_engine_vcall(engine, load_all_info) (engine); } else xkl_debug(100, "NOT Resetting the cache: same configuration\n"); } /* * Calling through vtable */ const gchar ** xkl_engine_get_groups_names(XklEngine * engine) { xkl_engine_ensure_vtable_inited(engine); return xkl_engine_vcall(engine, get_groups_names) (engine); } const gchar ** xkl_engine_get_indicators_names(XklEngine * engine) { xkl_engine_ensure_vtable_inited(engine); return xkl_engine_vcall(engine, get_indicators_names) (engine); } guint xkl_engine_get_num_groups(XklEngine * engine) { xkl_engine_ensure_vtable_inited(engine); return xkl_engine_vcall(engine, get_num_groups) (engine); } void xkl_engine_lock_group(XklEngine * engine, int group) { xkl_engine_ensure_vtable_inited(engine); xkl_engine_vcall(engine, lock_group) (engine, group); } gint xkl_engine_pause_listen(XklEngine * engine) { xkl_debug(150, "Pause listening\n"); xkl_engine_ensure_vtable_inited(engine); return xkl_engine_vcall(engine, pause_listen) (engine); } gint xkl_engine_resume_listen(XklEngine * engine) { xkl_engine_ensure_vtable_inited(engine); guchar *listener_type_counters = xkl_engine_priv(engine, listener_type_counters); xkl_debug(150, "Resume listening, listenerType: (%s%s%s)\n", (listener_type_counters [XKLL_MANAGE_WINDOW_STATES_OFFSET]) ? "XKLL_MANAGE_WINDOW_STATES " : "", (listener_type_counters [XKLL_TRACK_KEYBOARD_STATE_OFFSET]) ? "XKLL_TRACK_KEYBOARD_STATE " : "", (listener_type_counters[XKLL_MANAGE_LAYOUTS_OFFSET]) ? "XKLL_MANAGE_LAYOUTS " : ""); if (xkl_engine_vcall(engine, resume_listen) (engine)) return 1; xkl_engine_select_input_merging(engine, xkl_engine_priv(engine, root_window), SubstructureNotifyMask | PropertyChangeMask); xkl_engine_vcall(engine, get_server_state) (engine, &xkl_engine_priv(engine, curr_state)); return 0; } guint xkl_engine_get_max_num_groups(XklEngine * engine) { xkl_engine_ensure_vtable_inited(engine); return xkl_engine_vcall(engine, get_max_num_groups) (engine); } XklEngine * xkl_get_the_engine() { return the_engine; } G_DEFINE_TYPE(XklEngine, xkl_engine, G_TYPE_OBJECT) static GObject * xkl_engine_constructor(GType type, guint n_construct_properties, GObjectConstructParam * construct_properties) { GObject *obj; XklEngine *engine; Display *display; int scr; gint rv; { /* Invoke parent constructor. */ g_type_class_peek(XKL_TYPE_ENGINE); obj = parent_class->constructor(type, n_construct_properties, construct_properties); } engine = XKL_ENGINE(obj); display = (Display *) g_value_peek_pointer(construct_properties[0].value); xkl_engine_priv(engine, display) = display; xkl_engine_priv(engine, default_error_handler) = XSetErrorHandler(xkl_process_error); scr = DefaultScreen(display); xkl_engine_priv(engine, root_window) = RootWindow(display, scr); xkl_engine_priv(engine, skip_one_restore) = FALSE; xkl_engine_priv(engine, default_group) = -1; xkl_engine_priv(engine, secondary_groups_mask) = 0L; xkl_engine_priv(engine, prev_toplvl_win) = 0; xkl_engine_priv(engine, atoms)[WM_NAME] = XInternAtom(display, "WM_NAME", False); xkl_engine_priv(engine, atoms)[WM_STATE] = XInternAtom(display, "WM_STATE", False); xkl_engine_priv(engine, atoms)[XKLAVIER_STATE] = XInternAtom(display, "XKLAVIER_STATE", False); xkl_engine_priv(engine, atoms)[XKLAVIER_TRANSPARENT] = XInternAtom(display, "XKLAVIER_TRANSPARENT", False); xkl_engine_priv(engine, atoms)[XKLAVIER_ALLOW_SECONDARY] = XInternAtom(display, "XKLAVIER_ALLOW_SECONDARY", False); xkl_engine_one_switch_to_secondary_group_performed(engine); rv = -1; xkl_debug(150, "Trying all backends:\n"); #ifdef ENABLE_XKB_SUPPORT xkl_debug(150, "Trying XKB backend\n"); rv = xkl_xkb_init(engine); #endif #ifdef ENABLE_XMODMAP_SUPPORT if (rv != 0) { xkl_debug(150, "Trying xmodmap backend\n"); rv = xkl_xmm_init(engine); } #endif if (rv == 0) { xkl_debug(150, "Actual backend: %s\n", xkl_engine_get_backend_name(engine)); } else { xkl_debug(0, "All backends failed, last result: %d\n", rv); XSetErrorHandler(xkl_engine_priv (engine, default_error_handler)); xkl_engine_priv(engine, display) = NULL; g_object_unref(G_OBJECT(engine)); return NULL; } xkl_engine_ensure_vtable_inited(engine); if (!xkl_engine_vcall(engine, load_all_info) (engine)) { g_object_unref(G_OBJECT(engine)); return NULL; } return obj; } static void xkl_engine_init(XklEngine * engine) { engine->priv = g_new0(XklEnginePrivate, 1); } static void xkl_engine_set_property(GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { } static void xkl_engine_get_property(GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { XklEngine *engine = XKL_ENGINE(object); switch (property_id) { case PROP_DISPLAY: g_value_set_pointer(value, xkl_engine_get_display(engine)); break; case PROP_BACKEND_NAME: g_value_set_string(value, xkl_engine_priv(engine, backend_id)); break; case PROP_FEATURES: g_value_set_flags(value, xkl_engine_priv(engine, features)); break; case PROP_MAX_NUM_GROUPS: g_value_set_uint(value, xkl_engine_vcall(engine, get_max_num_groups) (engine)); break; case PROP_NUM_GROUPS: g_value_set_uint(value, xkl_engine_vcall(engine, get_num_groups) (engine)); break; case PROP_DEFAULT_GROUP: g_value_set_uint(value, xkl_engine_priv(engine, default_group)); break; case PROP_SECONDARY_GROUPS_MASK: g_value_set_uint(value, xkl_engine_priv(engine, secondary_groups_mask)); break; case PROP_INDICATORS_HANDLING: g_value_set_boolean(value, xkl_engine_priv(engine, handle_indicators)); break; } } static void xkl_engine_finalize(GObject * obj) { XklEngine *engine = (XklEngine *) obj; gpointer backend; XSetErrorHandler((XErrorHandler) xkl_engine_priv(engine, default_error_handler)); xkl_engine_ensure_vtable_inited(engine); xkl_engine_vcall(engine, free_all_info) (engine); xkl_engine_vcall(engine, finalize) (engine); backend = xkl_engine_priv(engine, backend); if (backend != NULL) g_free(backend); g_free(engine->priv); G_OBJECT_CLASS(parent_class)->finalize(obj); } static void xkl_engine_class_init(XklEngineClass * klass) { GObjectClass *object_class; GParamSpec *display_param_spec; GParamSpec *backend_name_param_spec; GParamSpec *features_param_spec; GParamSpec *max_num_groups_param_spec; GParamSpec *num_groups_param_spec; GParamSpec *default_group_param_spec; GParamSpec *secondary_groups_mask_param_spec; GParamSpec *indicators_handling_param_spec; const gchar *sdl; object_class = (GObjectClass *) klass; parent_class = g_type_class_peek_parent(object_class); object_class->constructor = xkl_engine_constructor; object_class->finalize = xkl_engine_finalize; object_class->set_property = xkl_engine_set_property; object_class->get_property = xkl_engine_get_property; display_param_spec = g_param_spec_pointer("display", "Display", "X Display pointer", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); backend_name_param_spec = g_param_spec_string("backendName", "backendName", "Backend name", NULL, G_PARAM_READABLE); features_param_spec = g_param_spec_flags("features", "Features", "Backend features", XKL_TYPE_ENGINE_FEATURES, 0, G_PARAM_READABLE); max_num_groups_param_spec = g_param_spec_uint("max-num-groups", "maxNumGroups", "Max number of groups", 0, 0x100, 0, G_PARAM_READABLE); num_groups_param_spec = g_param_spec_uint("num-groups", "numGroups", "Current number of groups", 0, 0x100, 0, G_PARAM_READABLE); default_group_param_spec = g_param_spec_uint("default-group", "defaultGroup", "Default group", 0, 0x100, 0, G_PARAM_READABLE); secondary_groups_mask_param_spec = g_param_spec_uint("secondary-groups-mask", "secondaryGroupsMask", "Secondary groups mask", 0, 0x100, 0, G_PARAM_READABLE); indicators_handling_param_spec = g_param_spec_boolean("indicators-handling", "indicatorsHandling", "Whether engine should handle indicators", FALSE, G_PARAM_READABLE); g_object_class_install_property(object_class, PROP_DISPLAY, display_param_spec); g_object_class_install_property(object_class, PROP_BACKEND_NAME, backend_name_param_spec); g_object_class_install_property(object_class, PROP_FEATURES, features_param_spec); g_object_class_install_property(object_class, PROP_MAX_NUM_GROUPS, max_num_groups_param_spec); g_object_class_install_property(object_class, PROP_NUM_GROUPS, num_groups_param_spec); g_object_class_install_property(object_class, PROP_DEFAULT_GROUP, default_group_param_spec); g_object_class_install_property(object_class, PROP_SECONDARY_GROUPS_MASK, secondary_groups_mask_param_spec); g_object_class_install_property(object_class, PROP_INDICATORS_HANDLING, indicators_handling_param_spec); g_signal_new("X-config-changed", XKL_TYPE_ENGINE, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(XklEngineClass, config_notify), NULL, NULL, xkl_engine_VOID__VOID, G_TYPE_NONE, 0); g_signal_new("X-new-device", XKL_TYPE_ENGINE, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(XklEngineClass, new_device_notify), NULL, NULL, xkl_engine_VOID__VOID, G_TYPE_NONE, 0); g_signal_new("new-toplevel-window", XKL_TYPE_ENGINE, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(XklEngineClass, new_window_notify), NULL, NULL, xkl_engine_INT__LONG_LONG, G_TYPE_INT, 2, G_TYPE_LONG, G_TYPE_LONG); g_signal_new("X-state-changed", XKL_TYPE_ENGINE, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(XklEngineClass, state_notify), NULL, NULL, xkl_engine_VOID__ENUM_INT_BOOLEAN, G_TYPE_NONE, 3, XKL_TYPE_ENGINE_STATE_CHANGE, G_TYPE_INT, G_TYPE_BOOLEAN); /* static stuff initialized */ sdl = g_getenv("XKL_DEBUG"); if (sdl != NULL) { xkl_set_debug_level(atoi(sdl)); } }