/*
 * Copyright (C) 2002-2006 Sergey V. Udaltsov <svu@gnome.org>
 *
 * 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 <string.h>
#include <time.h>

#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xmd.h>

#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));
	}
}
