/* Keyboard Accessibility Status Applet
 * Copyright 2003, 2004 Sun Microsystems Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <config.h>

#include <stdlib.h>
#include <string.h>

#include <glib/gi18n.h>
#include <glib-object.h>
#include <gtk/gtk.h>
#include <gio/gio.h>
#include <gio/gdesktopappinfo.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
#include <mate-panel-applet.h>
#include <X11/XKBlib.h>

#define XK_MISCELLANY
#define XK_XKB_KEYS

#include <X11/keysymdef.h>
#include "applet.h"

static int xkb_base_event_type = 0;

static GtkIconSize icon_size_spec;

#define ALT_GRAPH_LED_MASK (0x10)

typedef struct {
	char* stock_id;
	char* name;
	GtkStateType state;
	gboolean wildcarded;
} AppletStockIcon;

static AppletStockIcon stock_icons[] = {
	{ACCESSX_APPLET, "mate-ax-applet.png", GTK_STATE_NORMAL, True},
	{ACCESSX_BASE_ICON, "mate-ax-key-base.png", GTK_STATE_NORMAL, True},
	{ACCESSX_BASE_ICON, "mate-ax-key-none.png", GTK_STATE_INSENSITIVE, False},
	{ACCESSX_BASE_ICON, "mate-ax-key-inverse.png", GTK_STATE_SELECTED, False},
	{ACCESSX_ACCEPT_BASE, "mate-ax-key-yes.png", GTK_STATE_NORMAL, True},
	{ACCESSX_REJECT_BASE, "mate-ax-key-no.png", GTK_STATE_NORMAL, True},
	{MOUSEKEYS_BASE_ICON, "mate-mousekeys-base.png", GTK_STATE_NORMAL, True},
	{MOUSEKEYS_BUTTON_LEFT, "mate-mousekeys-pressed-left.png", GTK_STATE_NORMAL, True},
	{MOUSEKEYS_BUTTON_MIDDLE, "mate-mousekeys-pressed-middle.png", GTK_STATE_NORMAL, True},
	{MOUSEKEYS_BUTTON_RIGHT, "mate-mousekeys-pressed-right.png", GTK_STATE_NORMAL, True},
	{MOUSEKEYS_DOT_LEFT, "mate-mousekeys-default-left.png", GTK_STATE_NORMAL, True},
	{MOUSEKEYS_DOT_MIDDLE, "mate-mousekeys-default-middle.png", GTK_STATE_NORMAL, True},
	{MOUSEKEYS_DOT_RIGHT, "mate-mousekeys-default-right.png", GTK_STATE_NORMAL, True},
	{SHIFT_KEY_ICON, "mate-sticky-shift-latched.png", GTK_STATE_NORMAL, False},
	{SHIFT_KEY_ICON, "mate-sticky-shift-locked.png", GTK_STATE_SELECTED, False},
	{SHIFT_KEY_ICON, "mate-sticky-shift-none.png", GTK_STATE_INSENSITIVE, True},
	{CONTROL_KEY_ICON, "mate-sticky-ctrl-latched.png", GTK_STATE_NORMAL, False},
	{CONTROL_KEY_ICON, "mate-sticky-ctrl-locked.png", GTK_STATE_SELECTED, False},
	{CONTROL_KEY_ICON, "mate-sticky-ctrl-none.png", GTK_STATE_INSENSITIVE, True},
	{ALT_KEY_ICON, "mate-sticky-alt-latched.png", GTK_STATE_NORMAL, False},
	{ALT_KEY_ICON, "mate-sticky-alt-locked.png", GTK_STATE_SELECTED, False},
	{ALT_KEY_ICON, "mate-sticky-alt-none.png", GTK_STATE_INSENSITIVE, True},
	{META_KEY_ICON, "mate-sticky-meta-latched.png", GTK_STATE_NORMAL, False},
	{META_KEY_ICON, "mate-sticky-meta-locked.png", GTK_STATE_SELECTED, False},
	{META_KEY_ICON, "mate-sticky-meta-none.png", GTK_STATE_INSENSITIVE, True},
	{SUPER_KEY_ICON, "mate-sticky-super-latched.png", GTK_STATE_NORMAL, False},
	{SUPER_KEY_ICON, "mate-sticky-super-locked.png", GTK_STATE_SELECTED, False},
	{SUPER_KEY_ICON, "mate-sticky-super-none.png", GTK_STATE_INSENSITIVE, True},
	{HYPER_KEY_ICON, "mate-sticky-hyper-latched.png", GTK_STATE_NORMAL, False},
	{HYPER_KEY_ICON, "mate-sticky-hyper-locked.png", GTK_STATE_SELECTED, False},
	{HYPER_KEY_ICON, "mate-sticky-hyper-none.png", GTK_STATE_INSENSITIVE, True},
	{SLOWKEYS_IDLE_ICON, "mate-ax-slowkeys.png", GTK_STATE_NORMAL, True},
	{SLOWKEYS_PENDING_ICON, "mate-ax-slowkeys-pending.png", GTK_STATE_NORMAL, True},
	{SLOWKEYS_ACCEPT_ICON, "mate-ax-slowkeys-yes.png", GTK_STATE_NORMAL, True},
	{SLOWKEYS_REJECT_ICON, "mate-ax-slowkeys-no.png", GTK_STATE_NORMAL, True},
	{BOUNCEKEYS_ICON, "mate-ax-bouncekeys.png", GTK_STATE_NORMAL, True}
};

typedef struct {
	unsigned int mask;
	GtkWidget* indicator;
} ModifierStruct;

static ModifierStruct modifiers[] = {
	{ShiftMask, NULL},
	{ControlMask, NULL},
	{Mod1Mask, NULL},
	{Mod2Mask, NULL},
	{Mod3Mask, NULL},
	{Mod4Mask, NULL},
	{Mod5Mask, NULL}
};

typedef struct {
	unsigned int mask;
	gchar* stock_id;
} ButtonIconStruct;

static ButtonIconStruct button_icons[] = {
	{Button1Mask, MOUSEKEYS_BUTTON_LEFT},
	{Button2Mask, MOUSEKEYS_BUTTON_MIDDLE},
	{Button3Mask, MOUSEKEYS_BUTTON_RIGHT}
};

static void popup_error_dialog(AccessxStatusApplet* sapplet);

/* cribbed from geyes */
static void about_cb(GtkAction* action, AccessxStatusApplet* sapplet)
{
	static const gchar* authors[] = {
		"Calum Benson <calum.benson@sun.com>",
		"Bill Haneman <bill.haneman@sun.com>",
		NULL
	};

	const gchar* documenters[] = {
		"Bill Haneman <bill.haneman@sun.com>",
		"Sun GNOME Documentation Team <gdocteam@sun.com>",
		NULL
	};

	char copyright[] = \
		"Copyright \xc2\xa9 2012-2016 MATE developers\n"
		"Copyright \xc2\xa9 2003 Sun Microsystems";

	gtk_show_about_dialog(NULL,
		"version", VERSION,
		"comments", _("Shows the state of AccessX features such as latched modifiers"),
		"copyright", copyright,
		"authors", authors,
		"documenters", documenters,
		"translator-credits", _("translator-credits"),
		"logo-icon-name", "mate-ax-applet",
		NULL);
}

static void help_cb(GtkAction* action, AccessxStatusApplet* sapplet)
{
	GError* error = NULL;
	GdkScreen* screen = gtk_widget_get_screen(GTK_WIDGET(sapplet->applet));

#if GTK_CHECK_VERSION (3, 22, 0)
	gtk_show_uri_on_window(NULL,
	                       "help:mate-accessx-status",
	                       gtk_get_current_event_time(),
	                       &error);
#else
	gtk_show_uri(screen, "help:mate-accessx-status", gtk_get_current_event_time(), &error);
#endif

	if (error)
	{
		GtkWidget* parent = gtk_widget_get_parent(GTK_WIDGET(sapplet->applet));

		GtkWidget* dialog = gtk_message_dialog_new(GTK_WINDOW(parent), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("There was an error launching the help viewer: %s"), error->message);

		g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);

		gtk_window_set_screen(GTK_WINDOW(dialog), screen);
		gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);

		gtk_widget_show(dialog);
		g_error_free(error);
	}
}

static void dialog_cb(GtkAction* action, AccessxStatusApplet* sapplet)
{
	GError* error = NULL;
	GdkScreen *screen;
	GdkAppLaunchContext *launch_context;
	GAppInfo *appinfo;

	if (sapplet->error_type != ACCESSX_STATUS_ERROR_NONE)
	{
		popup_error_dialog(sapplet);
		return;
	}


	screen = gtk_widget_get_screen (GTK_WIDGET (sapplet->applet));
	appinfo = g_app_info_create_from_commandline ("mate-keyboard-properties --a11y",
						      _("Open the keyboard preferences dialog"),
						      G_APP_INFO_CREATE_NONE,
						      &error);

	if (!error) {
		launch_context = gdk_display_get_app_launch_context (
			               gtk_widget_get_display (GTK_WIDGET (screen)));
		gdk_app_launch_context_set_screen (launch_context, screen);
		g_app_info_launch (appinfo, NULL, G_APP_LAUNCH_CONTEXT (launch_context), &error);

		g_object_unref (launch_context);
	}

	if (error != NULL)
	{
		GtkWidget* dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("There was an error launching the keyboard preferences dialog: %s"), error->message);

		g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);

		gtk_window_set_screen(GTK_WINDOW(dialog), screen);
		gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);

		gtk_widget_show(dialog);
		g_error_free(error);
	}

	g_object_unref (appinfo);
}

static const GtkActionEntry accessx_status_applet_menu_actions[] = {
	{"Dialog", GTK_STOCK_PROPERTIES, N_("_Keyboard Accessibility Preferences"), NULL, NULL, G_CALLBACK(dialog_cb)},
	{"Help", GTK_STOCK_HELP, N_("_Help"), NULL, NULL, G_CALLBACK(help_cb)},
	{"About", GTK_STOCK_ABOUT, N_("_About"), NULL, NULL, G_CALLBACK(about_cb)}
};

static XkbDescPtr accessx_status_applet_get_xkb_desc(AccessxStatusApplet* sapplet)
{
	Display* display;

	if (sapplet->xkb == NULL)
	{
		int ir, reason_return;
		char* display_name = getenv("DISPLAY");
		display = XkbOpenDisplay(display_name, &xkb_base_event_type, &ir, NULL, NULL, &reason_return);
		g_assert(display); /* TODO: change error message below to something user-viewable */

		if (display == NULL)
		{
			g_warning("Xkb extension could not be initialized! (error code %x)", reason_return);
		}
		else
		{
			sapplet->xkb = XkbGetMap(display, XkbAllComponentsMask, XkbUseCoreKbd);
		}

		g_assert(sapplet->xkb);

		if (sapplet->xkb == NULL)
		{
			g_warning("Xkb keyboard description not available!");
		}

		sapplet->xkb_display = display;
	}
	return sapplet->xkb;
}

static gboolean accessx_status_applet_xkb_select(AccessxStatusApplet* sapplet)
{
	int opcode_rtn, error_rtn;
	gboolean retval = FALSE;
	GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sapplet->applet));

	g_assert(sapplet && sapplet->applet && window);

	Display* display = GDK_WINDOW_XDISPLAY(window);

	g_assert(display);

	retval = XkbQueryExtension(display, &opcode_rtn, &xkb_base_event_type, &error_rtn, NULL, NULL);

	if (retval)
	{
		retval = XkbSelectEvents(display, XkbUseCoreKbd, XkbAllEventsMask, XkbAllEventsMask);
		sapplet->xkb = accessx_status_applet_get_xkb_desc(sapplet);
	}
	else
	{
		sapplet->error_type = ACCESSX_STATUS_ERROR_XKB_DISABLED;
	}

	return retval;
}

static void accessx_status_applet_init_modifiers(AccessxStatusApplet* sapplet)
{
	int i;
	unsigned int hyper_mask, super_mask, alt_gr_mask;

	unsigned int alt_mask = XkbKeysymToModifiers(sapplet->xkb_display, XK_Alt_L);
	unsigned int meta_mask = XkbKeysymToModifiers(sapplet->xkb_display, XK_Meta_L);

	g_assert(sapplet->meta_indicator);

	if (meta_mask && (meta_mask != alt_mask))
	{
		gtk_widget_show(sapplet->meta_indicator);
	}
	else
	{
		gtk_widget_hide(sapplet->meta_indicator);
	}

	hyper_mask = XkbKeysymToModifiers(sapplet->xkb_display, XK_Hyper_L);

	if (hyper_mask)
	{
		gtk_widget_show(sapplet->hyper_indicator);
	}
	else
	{
		gtk_widget_hide(sapplet->hyper_indicator);
	}

	super_mask = XkbKeysymToModifiers(sapplet->xkb_display, XK_Super_L);

	if (super_mask)
	{
		gtk_widget_show(sapplet->super_indicator);
	}
	else
	{
		gtk_widget_hide(sapplet->super_indicator);
	}

	alt_gr_mask = XkbKeysymToModifiers(sapplet->xkb_display, XK_Mode_switch) |
		XkbKeysymToModifiers(sapplet->xkb_display, XK_ISO_Level3_Shift) |
		XkbKeysymToModifiers(sapplet->xkb_display, XK_ISO_Level3_Latch) |
		XkbKeysymToModifiers(sapplet->xkb_display, XK_ISO_Level3_Lock);

	if (alt_gr_mask)
	{
		gtk_widget_show(sapplet->alt_graph_indicator);
	}
	else
	{
		gtk_widget_hide(sapplet->alt_graph_indicator);
	}

	for (i = 0; i < G_N_ELEMENTS(modifiers); ++i)
	{
		if (modifiers[i].mask == ShiftMask)
		{
			modifiers[i].indicator = sapplet->shift_indicator;
		}
		else if (modifiers[i].mask == ControlMask)
		{
			modifiers[i].indicator = sapplet->ctrl_indicator;
		}
		else if (modifiers[i].mask == alt_mask)
		{
			modifiers[i].indicator = sapplet->alt_indicator;
		}
		else if (modifiers[i].mask == meta_mask)
		{
			modifiers[i].indicator = sapplet->meta_indicator;
		}
		else if (modifiers[i].mask == hyper_mask)
		{
			modifiers[i].indicator = sapplet->hyper_indicator;
		}
		else if (modifiers[i].mask == super_mask)
		{
			modifiers[i].indicator = sapplet->super_indicator;
		}
		else if (modifiers[i].mask == alt_gr_mask)
		{
			modifiers[i].indicator = sapplet->alt_graph_indicator;
		}
	}
}

static guint _sk_timeout = 0;
static guint _bk_timeout = 0;

static gboolean timer_reset_slowkeys_image(gpointer user_data)
{
	GdkPixbuf* pixbuf = gtk_widget_render_icon_pixbuf(GTK_WIDGET(user_data), SLOWKEYS_IDLE_ICON, icon_size_spec);

	gtk_image_set_from_pixbuf(GTK_IMAGE(user_data), pixbuf);
	g_object_unref(pixbuf);

	return FALSE;
}

static gboolean timer_reset_bouncekeys_image(gpointer user_data)
{
	GdkPixbuf* pixbuf = gtk_widget_render_icon_pixbuf(GTK_WIDGET(user_data), BOUNCEKEYS_ICON, icon_size_spec);

	gtk_image_set_from_pixbuf(GTK_IMAGE(user_data), pixbuf);
	g_object_unref(pixbuf);

	return FALSE;
}

static GdkPixbuf* accessx_status_applet_get_glyph_pixbuf(AccessxStatusApplet* sapplet, GtkWidget* widget, GdkPixbuf* base, GdkColor* fg, GdkColor* bg, gchar* glyphstring)
{
	GdkPixbuf* glyph_pixbuf;
	cairo_surface_t *surface;
	PangoLayout* layout;
	PangoRectangle ink, logic;
	PangoContext* pango_context;
	gint w = gdk_pixbuf_get_width(base);
	gint h = gdk_pixbuf_get_height(base);
	cairo_t *cr;

	surface = gdk_window_create_similar_surface (gdk_get_default_root_window (), CAIRO_CONTENT_COLOR_ALPHA, w, h);
	pango_context = gtk_widget_get_pango_context(widget);
	layout = pango_layout_new(pango_context);
	pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
	pango_layout_set_text(layout, glyphstring, -1);

	cr = cairo_create (surface);
	gdk_cairo_set_source_color (cr, bg);
	cairo_paint (cr);
	gdk_cairo_set_source_color (cr, fg);

	pango_layout_get_pixel_extents(layout, &ink, &logic);

	cairo_move_to (cr, (w - ink.x - ink.width)/2, (h - ink.y - ink.height)/2);
	pango_cairo_show_layout (cr, layout);
	cairo_destroy (cr);

	g_object_unref(layout);
	glyph_pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, w, h);
	cairo_surface_destroy (surface);

	return glyph_pixbuf;
}

static GdkPixbuf* accessx_status_applet_slowkeys_image(AccessxStatusApplet* sapplet, XkbAccessXNotifyEvent* event)
{
	GdkPixbuf* ret_pixbuf;
	GdkWindow* window;
	gboolean is_idle = TRUE;
	gchar* stock_id = SLOWKEYS_IDLE_ICON;
	GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(sapplet->applet));
	GdkColor bg = style->bg[GTK_STATE_NORMAL];

	if (event != NULL)
	{
		is_idle = FALSE;

		switch (event->detail)
		{
			case XkbAXN_SKPress:
				stock_id = ACCESSX_BASE_ICON;
				if (_sk_timeout)
				{
					g_source_remove(_sk_timeout);
					_sk_timeout = 0;
				}
				break;
			case XkbAXN_SKAccept:
				stock_id = ACCESSX_ACCEPT_BASE;
				gdk_color_parse("#009900", &bg);
				break;
			case XkbAXN_SKReject:
				stock_id = ACCESSX_REJECT_BASE;
				gdk_color_parse("#990000", &bg);
				_sk_timeout = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, MAX(event->sk_delay, 150), timer_reset_slowkeys_image, sapplet->slowfoo, NULL);
				break;
			case XkbAXN_SKRelease:
			default:
				stock_id = SLOWKEYS_IDLE_ICON;
				is_idle = TRUE;
				break;
		}
	}

	ret_pixbuf = gtk_widget_render_icon_pixbuf(GTK_WIDGET(sapplet->applet), stock_id, icon_size_spec);

	if (!is_idle)
	{
		GdkPixbuf* glyph_pixbuf;
		GdkPixbuf* tmp_pixbuf;
		GdkColor  fg;
		gchar* glyphstring = N_("a");
		tmp_pixbuf = ret_pixbuf;
		ret_pixbuf = gdk_pixbuf_copy(tmp_pixbuf);
		g_object_unref(tmp_pixbuf);

		window = gtk_widget_get_window(GTK_WIDGET(sapplet->applet));

		if (event && window)
		{
			KeySym keysym = XKeycodeToKeysym(GDK_WINDOW_XDISPLAY(window), event->keycode, 0);
			glyphstring = XKeysymToString(keysym);

			if ((!g_utf8_validate(glyphstring, -1, NULL)) || (g_utf8_strlen(glyphstring, -1) > 1))
			{
				glyphstring = "";
			}
		}

		fg = style->fg[gtk_widget_get_state_flags (GTK_WIDGET (sapplet->applet))];
		glyph_pixbuf = accessx_status_applet_get_glyph_pixbuf(sapplet, GTK_WIDGET(sapplet->applet), ret_pixbuf, &fg, &bg, glyphstring);
		gdk_pixbuf_composite(glyph_pixbuf, ret_pixbuf, 0, 0, gdk_pixbuf_get_width(glyph_pixbuf), gdk_pixbuf_get_height(glyph_pixbuf), 0., 0., 1.0, 1.0, GDK_INTERP_NEAREST, 255);
		g_object_unref(glyph_pixbuf);
	}

	return ret_pixbuf;
}

static GdkPixbuf* accessx_status_applet_bouncekeys_image(AccessxStatusApplet* sapplet, XkbAccessXNotifyEvent* event)
{
	GtkStyle* style;
	GdkColor fg, bg;
	GdkPixbuf* icon_base = NULL;
	GdkPixbuf* tmp_pixbuf;
	/* Note to translators: the first letter of the alphabet, not the indefinite article */
	gchar* glyphstring = N_("a");
	gchar* stock_id = ACCESSX_BASE_ICON;

	g_assert(sapplet->applet);
	style = gtk_widget_get_style(GTK_WIDGET(sapplet->applet));
	fg = style->text[gtk_widget_get_state_flags (GTK_WIDGET (sapplet->applet))];
	bg = style->base[GTK_STATE_NORMAL];

	if (event != NULL)
	{
		switch (event->detail)
		{
			case XkbAXN_BKReject:
				stock_id = SLOWKEYS_REJECT_ICON;
				_bk_timeout = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, MAX(event->debounce_delay, 150), timer_reset_bouncekeys_image, sapplet->bouncefoo, NULL);
				break;
			default:
				stock_id = ACCESSX_BASE_ICON;
				break;
		}
	}
	tmp_pixbuf = gtk_widget_render_icon_pixbuf(GTK_WIDGET(sapplet->applet), stock_id, icon_size_spec);

	if (tmp_pixbuf)
	{
		GdkPixbuf* glyph_pixbuf;
		icon_base = gdk_pixbuf_copy(tmp_pixbuf);
		g_object_unref(tmp_pixbuf);
		glyph_pixbuf = accessx_status_applet_get_glyph_pixbuf(sapplet, GTK_WIDGET(sapplet->applet), icon_base, &fg, &bg, glyphstring);
		gdk_pixbuf_composite(glyph_pixbuf, icon_base, 2, 2, gdk_pixbuf_get_width(glyph_pixbuf) - 2, gdk_pixbuf_get_height(glyph_pixbuf) - 2, -2., -2., 1.0, 1.0, GDK_INTERP_NEAREST, 96);
		gdk_pixbuf_composite(glyph_pixbuf, icon_base, 1, 1, gdk_pixbuf_get_width(glyph_pixbuf) - 1, gdk_pixbuf_get_height(glyph_pixbuf) - 1, 1., 1., 1.0, 1.0, GDK_INTERP_NEAREST, 255);

		g_object_unref(glyph_pixbuf);
	}
	return icon_base;
}

static GdkPixbuf* accessx_status_applet_mousekeys_image(AccessxStatusApplet* sapplet, XkbStateNotifyEvent* event)
{
	GdkPixbuf* mouse_pixbuf = NULL, *button_pixbuf, *dot_pixbuf, *tmp_pixbuf;
	gchar* which_dot = MOUSEKEYS_DOT_LEFT;
	tmp_pixbuf = gtk_widget_render_icon_pixbuf(GTK_WIDGET(sapplet->applet), MOUSEKEYS_BASE_ICON, icon_size_spec);
	mouse_pixbuf = gdk_pixbuf_copy(tmp_pixbuf);
	g_object_unref(tmp_pixbuf);
	/* composite in the buttons */
	if (mouse_pixbuf && event && event->ptr_buttons)
	{
		gint i;

		for (i = 0; i < G_N_ELEMENTS(button_icons); ++i)
		{
			if (event->ptr_buttons & button_icons[i].mask)
			{
				button_pixbuf = gtk_widget_render_icon_pixbuf(GTK_WIDGET(sapplet->applet), button_icons[i].stock_id, icon_size_spec);
				gdk_pixbuf_composite(button_pixbuf, mouse_pixbuf, 0, 0, gdk_pixbuf_get_width(button_pixbuf), gdk_pixbuf_get_height(button_pixbuf), 0.0, 0.0, 1.0, 1.0, GDK_INTERP_NEAREST, 255);
			}
		}
	}

	if (event)
	{
		switch (sapplet->xkb->ctrls->mk_dflt_btn)
		{
			case Button2:
				which_dot = MOUSEKEYS_DOT_MIDDLE;
				break;
			case Button3:
				which_dot = MOUSEKEYS_DOT_RIGHT;
				break;
			case Button1:
			default:
				which_dot = MOUSEKEYS_DOT_LEFT;
				break;
		}
	}
	dot_pixbuf = gtk_widget_render_icon_pixbuf(GTK_WIDGET(sapplet->applet), which_dot, icon_size_spec);

	gdk_pixbuf_composite(dot_pixbuf, mouse_pixbuf, 0, 0, gdk_pixbuf_get_width(dot_pixbuf), gdk_pixbuf_get_height(dot_pixbuf), 0.0, 0.0, 1.0, 1.0, GDK_INTERP_NEAREST, 255);

	return mouse_pixbuf;
}


static void accessx_status_applet_update(AccessxStatusApplet* sapplet, AccessxStatusNotifyType notify_type, XkbEvent* event)
{
	GdkWindow* window;
	gint i;

	window = gtk_widget_get_window(GTK_WIDGET(sapplet->applet));

	if (notify_type & ACCESSX_STATUS_MODIFIERS)
	{
		unsigned int locked_mods = 0, latched_mods = 0;

		if (event != NULL)
		{
			locked_mods = event->state.locked_mods;
			latched_mods = event->state.latched_mods;
		}
		else if (sapplet->applet && window)
		{
			XkbStateRec state;
			XkbGetState(GDK_WINDOW_XDISPLAY(window), XkbUseCoreKbd, &state);
			locked_mods = state.locked_mods;
			latched_mods = state.latched_mods;
		}
		/* determine which modifiers are locked, and set state accordingly */
		for (i = 0; i < G_N_ELEMENTS(modifiers); ++i)
		{
			if (modifiers[i].indicator != NULL && modifiers[i].mask)
			{
				if (locked_mods & modifiers[i].mask)
				{
					gtk_widget_set_sensitive(modifiers[i].indicator, TRUE);
					gtk_widget_set_state_flags (modifiers[i].indicator, GTK_STATE_FLAG_SELECTED, TRUE);
				}
				else if (latched_mods & modifiers[i].mask)
				{
					gtk_widget_set_sensitive(modifiers[i].indicator, TRUE);
					gtk_widget_set_state_flags (modifiers[i].indicator, GTK_STATE_FLAG_NORMAL, TRUE);
				}
				else
				{
					gtk_widget_set_sensitive(modifiers[i].indicator, FALSE);
				}
			}
		}
	}

	if ((notify_type & ACCESSX_STATUS_SLOWKEYS) && (event != NULL))
	{
		GdkPixbuf* pixbuf = accessx_status_applet_slowkeys_image(sapplet, &event->accessx);
		gtk_image_set_from_pixbuf(GTK_IMAGE(sapplet->slowfoo), pixbuf);
		g_object_unref(pixbuf);
	}

	if ((notify_type & ACCESSX_STATUS_BOUNCEKEYS) && (event != NULL))
	{
		GdkPixbuf* pixbuf = accessx_status_applet_bouncekeys_image(sapplet, &event->accessx);
		gtk_image_set_from_pixbuf(GTK_IMAGE(sapplet->bouncefoo), pixbuf);
		g_object_unref(pixbuf);
	}

	if ((notify_type & ACCESSX_STATUS_MOUSEKEYS) && (event != NULL))
	{
		GdkPixbuf* pixbuf = accessx_status_applet_mousekeys_image(sapplet, &event->state);
		gtk_image_set_from_pixbuf(GTK_IMAGE(sapplet->mousefoo),  pixbuf);
		g_object_unref(pixbuf);
	}

	if (notify_type & ACCESSX_STATUS_ENABLED)
	{
		/* Update the visibility of widgets in the box */
		/* XkbMouseKeysMask | XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask */
		XkbGetControls(GDK_WINDOW_XDISPLAY(window), XkbAllControlsMask, sapplet->xkb);

		if (!(sapplet->xkb->ctrls->enabled_ctrls & (XkbMouseKeysMask | XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask)))
		{
			gtk_widget_show(sapplet->idlefoo);
		}
		else
		{
			gtk_widget_hide(sapplet->idlefoo);
		}

		if (sapplet->xkb->ctrls->enabled_ctrls & XkbMouseKeysMask)
		{
			gtk_widget_show(sapplet->mousefoo);
		}
		else
		{
			gtk_widget_hide(sapplet->mousefoo);
		}

		if (sapplet->xkb->ctrls->enabled_ctrls & XkbStickyKeysMask)
		{
			gtk_widget_show(sapplet->stickyfoo);
		}
		else
		{
			gtk_widget_hide(sapplet->stickyfoo);
		}

		if (sapplet->xkb->ctrls->enabled_ctrls & XkbSlowKeysMask)
		{
			gtk_widget_show(sapplet->slowfoo);
		}
		else
		{
			gtk_widget_hide(sapplet->slowfoo);
		}

		if (sapplet->xkb->ctrls->enabled_ctrls & XkbBounceKeysMask)
		{
			gtk_widget_show(sapplet->bouncefoo);
		}
		else
		{
			gtk_widget_hide(sapplet->bouncefoo);
		}
	}

	return;
}

static void accessx_status_applet_notify_xkb_ax(AccessxStatusApplet* sapplet, XkbAccessXNotifyEvent* event)
{
	AccessxStatusNotifyType notify_mask = 0;

	switch (event->detail)
	{
		case XkbAXN_SKPress:
		case XkbAXN_SKAccept:
		case XkbAXN_SKRelease:
		case XkbAXN_SKReject:
			notify_mask |= ACCESSX_STATUS_SLOWKEYS;
			break;
		case XkbAXN_BKAccept:
		case XkbAXN_BKReject:
			notify_mask |= ACCESSX_STATUS_BOUNCEKEYS;
			break;
		case XkbAXN_AXKWarning:
			break;
		default:
			break;
	}

	accessx_status_applet_update(sapplet, notify_mask, (XkbEvent*) event);
}

static void accessx_status_applet_notify_xkb_state(AccessxStatusApplet* sapplet, XkbStateNotifyEvent* event)
{
	AccessxStatusNotifyType notify_mask = 0;

	if (event->changed & XkbPointerButtonMask)
	{
		notify_mask |= ACCESSX_STATUS_MOUSEKEYS;
	}

	if (event->changed & (XkbModifierLatchMask | XkbModifierLockMask))
	{
		notify_mask |= ACCESSX_STATUS_MODIFIERS;
	}

	accessx_status_applet_update(sapplet, notify_mask, (XkbEvent*) event);
}

static void accessx_status_applet_notify_xkb_device(AccessxStatusApplet* sapplet, XkbExtensionDeviceNotifyEvent* event)
{
	if (event->reason == XkbXI_IndicatorStateMask)
	{
		if (event->led_state &= ALT_GRAPH_LED_MASK)
		{
			gtk_widget_set_sensitive(sapplet->alt_graph_indicator, TRUE);
			gtk_widget_set_state_flags (sapplet->alt_graph_indicator, GTK_STATE_FLAG_NORMAL, TRUE);
		}
		else
		{
			gtk_widget_set_sensitive(sapplet->alt_graph_indicator, FALSE);
		}
	}
}

static void accessx_status_applet_notify_xkb_controls(AccessxStatusApplet* sapplet, XkbControlsNotifyEvent* event)
{
	unsigned int mask = XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbMouseKeysMask;
	unsigned int notify_mask = 0;

	XkbGetControls(sapplet->xkb_display, XkbMouseKeysMask, sapplet->xkb);

	if (event->enabled_ctrl_changes & mask)
	{
		notify_mask = ACCESSX_STATUS_ENABLED;
	}

	if (event->changed_ctrls & XkbMouseKeysMask)
	{
		notify_mask |= ACCESSX_STATUS_MOUSEKEYS;
	}

	if (notify_mask)
	{
		accessx_status_applet_update(sapplet, notify_mask, (XkbEvent*) event);
	}
}

static void accessx_status_applet_notify_xkb_event(AccessxStatusApplet* sapplet, XkbEvent* event)
{
	switch (event->any.xkb_type)
	{
		case XkbStateNotify:
			accessx_status_applet_notify_xkb_state(sapplet, &event->state);
			break;
		case XkbAccessXNotify:
			accessx_status_applet_notify_xkb_ax(sapplet, &event->accessx);
			break;
		case XkbControlsNotify:
			accessx_status_applet_notify_xkb_controls(sapplet, &event->ctrls);
			break;
		case XkbExtensionDeviceNotify:
			/* This is a hack around the fact that XFree86's XKB doesn't give AltGr notifications */
			accessx_status_applet_notify_xkb_device(sapplet, &event->device);
			break;
		default:
			break;
	}
}

static GdkFilterReturn accessx_status_xkb_filter(GdkXEvent* gdk_xevent, GdkEvent* event, gpointer user_data)
{
	AccessxStatusApplet* sapplet = user_data;
	XkbEvent* xevent = gdk_xevent;

	if (xevent->any.type == xkb_base_event_type)
	{
		accessx_status_applet_notify_xkb_event(sapplet, xevent);
	}

	return GDK_FILTER_CONTINUE;
}

static GtkIconSet* accessx_status_applet_altgraph_icon_set(AccessxStatusApplet* sapplet, GtkWidget* widget)
{
	GtkIconSet* icon_set = gtk_icon_set_new();
	gint i;
	GtkStateType states[3] = {GTK_STATE_NORMAL, GTK_STATE_INSENSITIVE, GTK_STATE_SELECTED};
	GtkStyle* style = gtk_widget_get_style(widget);
	GdkPixbuf* icon_base;

	gtk_widget_set_sensitive(widget, TRUE);

	for (i = 0; i < 3; ++i)
	{
		int alpha;
		GdkColor* fg;
		GdkColor* bg;
		GtkIconSource* source = gtk_icon_source_new();
		GdkPixbuf* pixbuf;
		GdkPixbuf* glyph_pixbuf;
		gboolean wildcarded = FALSE;
		fg = &style->text[states[i]];
		bg = &style->white;
		switch (states[i])
		{
			case GTK_STATE_NORMAL:
				alpha = 255;
				gtk_widget_set_sensitive(widget, TRUE);
				gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_NORMAL, TRUE);
				break;
			case GTK_STATE_SELECTED:
				/* FIXME: should use text/base here, for selected ? */
				fg = &style->white;
				bg = &style->black;
				alpha = 255;
				gtk_widget_set_sensitive(widget, TRUE);
				gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_SELECTED, TRUE);
				break;
			case GTK_STATE_INSENSITIVE:
			default:
				alpha = 63;
				gtk_widget_set_sensitive(widget, FALSE);
				wildcarded = TRUE;
				break;
		}

		icon_base = gtk_widget_render_icon_pixbuf(widget, ACCESSX_BASE_ICON, icon_size_spec);
		pixbuf = gdk_pixbuf_copy(icon_base);
		g_object_unref(icon_base);
		/*
		* should be N_("ae"));
		* need en_ locale for this.
		*/
		/*
		* Translators: substitute an easily-recognized single glyph
		* from Level 2, i.e. an AltGraph character from a common keyboard
		* in your locale.
		*/
		glyph_pixbuf = accessx_status_applet_get_glyph_pixbuf(sapplet, widget, pixbuf, fg, bg, ("æ"));
		gdk_pixbuf_composite(glyph_pixbuf, pixbuf, 0, 0, gdk_pixbuf_get_width(glyph_pixbuf), gdk_pixbuf_get_height(glyph_pixbuf), 0., 0., 1.0, 1.0, GDK_INTERP_NEAREST, alpha);
		g_object_unref(glyph_pixbuf);
		gtk_icon_source_set_pixbuf(source, pixbuf);
		gtk_icon_source_set_state(source, states[i]);
		gtk_icon_source_set_state_wildcarded(source, wildcarded);
		gtk_icon_set_add_source(icon_set, source);
		gtk_icon_source_free(source);
	}
	/* we mucked about with the box's state to create the icons; restore it to normal */
	gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_NORMAL, TRUE);
	gtk_widget_set_sensitive(widget, TRUE);

	return icon_set;
}

static void accessx_applet_add_stock_icons(AccessxStatusApplet* sapplet, GtkWidget* widget)
{
	GtkIconFactory* factory = gtk_icon_factory_new();
	gint i = 0;
	GtkIconSet* icon_set;

	gtk_icon_factory_add_default(factory);

	while (i <  G_N_ELEMENTS(stock_icons))
	{
		gchar* set_name = stock_icons[i].stock_id;
		icon_set = gtk_icon_set_new();

		do {
			char* filename;
			GtkIconSource* source = gtk_icon_source_new();
			filename = g_build_filename(ACCESSX_PIXMAPS_DIR, stock_icons[i].name, NULL);

			if (g_file_test(filename, G_FILE_TEST_EXISTS) && g_file_test(filename, G_FILE_TEST_IS_REGULAR))
			{
				gtk_icon_source_set_filename(source, filename);
			}
			else
			{
				GtkIconSet* default_set = gtk_icon_factory_lookup_default(GTK_STOCK_MISSING_IMAGE);
				gtk_icon_source_set_pixbuf(source, gtk_icon_set_render_icon(default_set, gtk_widget_get_style(widget), GTK_TEXT_DIR_NONE, GTK_STATE_NORMAL, icon_size_spec, widget, NULL));
			}
			g_free(filename);
			gtk_icon_source_set_state(source, stock_icons[i].state);
			gtk_icon_source_set_state_wildcarded(source, stock_icons[i].wildcarded);
			gtk_icon_set_add_source(icon_set, source);
			gtk_icon_source_free(source);
			++i;
		} while (set_name == stock_icons[i].stock_id);
		gtk_icon_factory_add(factory, set_name, icon_set);
		gtk_icon_set_unref(icon_set);
	}
	/* now create the stock icons for AltGr, which are internationalized */
	icon_set = accessx_status_applet_altgraph_icon_set (sapplet, widget);
	gtk_icon_factory_add(factory, ALTGRAPH_KEY_ICON, icon_set);
	gtk_icon_set_unref(icon_set);
	sapplet->icon_factory = factory;
}

static void accessx_status_applet_reparent_widget(GtkWidget* widget, GtkContainer* container)
{
	if (widget)
	{
		if (gtk_widget_get_parent(widget))
		{
			g_object_ref(G_OBJECT(widget));
			gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(widget)), widget);
		}

		gtk_container_add(container, widget);
	}
}

static void accessx_status_applet_layout_box(AccessxStatusApplet* sapplet, GtkWidget* box, GtkWidget* stickyfoo)
{
	AtkObject* atko;

	accessx_status_applet_reparent_widget(sapplet->shift_indicator, GTK_CONTAINER(stickyfoo));
	accessx_status_applet_reparent_widget(sapplet->ctrl_indicator, GTK_CONTAINER(stickyfoo));
	accessx_status_applet_reparent_widget(sapplet->alt_indicator, GTK_CONTAINER(stickyfoo));
	accessx_status_applet_reparent_widget(sapplet->meta_indicator, GTK_CONTAINER(stickyfoo));
	accessx_status_applet_reparent_widget(sapplet->hyper_indicator, GTK_CONTAINER(stickyfoo));
	accessx_status_applet_reparent_widget(sapplet->super_indicator, GTK_CONTAINER(stickyfoo));
	accessx_status_applet_reparent_widget(sapplet->alt_graph_indicator, GTK_CONTAINER(stickyfoo));
	accessx_status_applet_reparent_widget(sapplet->idlefoo, GTK_CONTAINER(box));
	accessx_status_applet_reparent_widget(sapplet->mousefoo, GTK_CONTAINER(box));
	accessx_status_applet_reparent_widget(stickyfoo, GTK_CONTAINER(box));
	accessx_status_applet_reparent_widget(sapplet->slowfoo, GTK_CONTAINER(box));
	accessx_status_applet_reparent_widget(sapplet->bouncefoo, GTK_CONTAINER(box));

	if (sapplet->stickyfoo)
	{
		gtk_widget_destroy(sapplet->stickyfoo);
	}

	if (sapplet->box)
	{
		gtk_container_remove(GTK_CONTAINER(sapplet->applet), sapplet->box);
	}

	gtk_container_add(GTK_CONTAINER(sapplet->applet), box);
	sapplet->stickyfoo = stickyfoo;
	sapplet->box = box;

	atko = gtk_widget_get_accessible(sapplet->box);
	atk_object_set_name(atko, _("AccessX Status"));
	atk_object_set_description(atko, _("Shows keyboard status when accessibility features are used."));

	gtk_widget_show(sapplet->box);
	gtk_widget_show(GTK_WIDGET(sapplet->applet));

	if (gtk_widget_get_realized(sapplet->box) && sapplet->initialized)
	{
		accessx_status_applet_update(sapplet, ACCESSX_STATUS_ALL, NULL);
	}
}

static void disable_applet(AccessxStatusApplet* sapplet)
{
	gtk_widget_hide(sapplet->meta_indicator);
	gtk_widget_hide(sapplet->hyper_indicator);
	gtk_widget_hide(sapplet->super_indicator);
	gtk_widget_hide(sapplet->alt_graph_indicator);
	gtk_widget_hide(sapplet->shift_indicator);
	gtk_widget_hide(sapplet->ctrl_indicator);
	gtk_widget_hide(sapplet->alt_indicator);
	gtk_widget_hide(sapplet->meta_indicator);
	gtk_widget_hide(sapplet->mousefoo);
	gtk_widget_hide(sapplet->stickyfoo);
	gtk_widget_hide(sapplet->slowfoo);
	gtk_widget_hide(sapplet->bouncefoo);
}

static void popup_error_dialog(AccessxStatusApplet* sapplet)
{
	GtkWidget* dialog;
	gchar* error_txt;

	switch (sapplet->error_type)
	{
		case ACCESSX_STATUS_ERROR_XKB_DISABLED:
			error_txt = g_strdup(_("XKB Extension is not enabled"));
			break;

		case ACCESSX_STATUS_ERROR_UNKNOWN:

		default: error_txt = g_strdup(_("Unknown error"));
			break;
	}

	dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("Error: %s"), error_txt);

	g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);

	gtk_window_set_screen(GTK_WINDOW(dialog), gtk_widget_get_screen(GTK_WIDGET(sapplet->applet)));

	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);

	gtk_widget_show(dialog);
	g_free(error_txt);
}

static AccessxStatusApplet* create_applet(MatePanelApplet* applet)
{
	AccessxStatusApplet* sapplet = g_new0(AccessxStatusApplet, 1);
	GtkWidget* box;
	GtkWidget* stickyfoo;
	AtkObject* atko;
	GdkPixbuf* pixbuf;
	gint large_toolbar_pixels;

	g_set_application_name(_("AccessX Status"));

	sapplet->xkb = NULL;
	sapplet->xkb_display = NULL;
	sapplet->box = NULL;
	sapplet->initialized = False; /* there must be a better way */
	sapplet->error_type = ACCESSX_STATUS_ERROR_NONE;
	sapplet->applet = applet;
	mate_panel_applet_set_flags(applet, MATE_PANEL_APPLET_EXPAND_MINOR);
	sapplet->orient = mate_panel_applet_get_orient(applet);

	if (sapplet->orient == MATE_PANEL_APPLET_ORIENT_LEFT || sapplet->orient == MATE_PANEL_APPLET_ORIENT_RIGHT)
	{
		box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
		stickyfoo = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
	}
	else
	{
		box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
		stickyfoo = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
	}

	gtk_box_set_homogeneous (GTK_BOX (stickyfoo), TRUE);
	large_toolbar_pixels = 24; /* FIXME */

	if (mate_panel_applet_get_size(sapplet->applet) >= large_toolbar_pixels)
	{
		icon_size_spec = GTK_ICON_SIZE_LARGE_TOOLBAR;
	}
	else
	{
		icon_size_spec = GTK_ICON_SIZE_SMALL_TOOLBAR;
	}

	accessx_applet_add_stock_icons(sapplet, box);
	pixbuf = accessx_status_applet_mousekeys_image(sapplet, NULL);
	sapplet->mousefoo = gtk_image_new_from_pixbuf(pixbuf);
	g_object_unref(pixbuf);
	gtk_widget_hide(sapplet->mousefoo);

	sapplet->shift_indicator = gtk_image_new_from_stock(SHIFT_KEY_ICON, icon_size_spec);
	sapplet->ctrl_indicator = gtk_image_new_from_stock(CONTROL_KEY_ICON, icon_size_spec);
	sapplet->alt_indicator = gtk_image_new_from_stock(ALT_KEY_ICON, icon_size_spec);
	sapplet->meta_indicator = gtk_image_new_from_stock(META_KEY_ICON, icon_size_spec);
	gtk_widget_set_sensitive(sapplet->meta_indicator, FALSE);
	gtk_widget_hide(sapplet->meta_indicator);
	sapplet->hyper_indicator = gtk_image_new_from_stock(HYPER_KEY_ICON, icon_size_spec);
	gtk_widget_set_sensitive(sapplet->hyper_indicator, FALSE);
	gtk_widget_hide(sapplet->hyper_indicator);
	sapplet->super_indicator = gtk_image_new_from_stock(SUPER_KEY_ICON, icon_size_spec);
	gtk_widget_set_sensitive(sapplet->super_indicator, FALSE);
	gtk_widget_hide(sapplet->super_indicator);
	sapplet->alt_graph_indicator = gtk_image_new_from_stock(ALTGRAPH_KEY_ICON, icon_size_spec);
	gtk_widget_set_sensitive(sapplet->alt_graph_indicator, FALSE);

	pixbuf = accessx_status_applet_slowkeys_image(sapplet, NULL);
	sapplet->slowfoo = gtk_image_new_from_pixbuf(pixbuf);
	g_object_unref(pixbuf);
	gtk_widget_hide(sapplet->slowfoo);

	pixbuf = accessx_status_applet_bouncekeys_image(sapplet, NULL);
	sapplet->bouncefoo = gtk_image_new_from_pixbuf(pixbuf);
	g_object_unref(pixbuf);
	gtk_widget_hide(sapplet->bouncefoo);

	sapplet->idlefoo = gtk_image_new_from_stock(ACCESSX_APPLET, icon_size_spec);
	gtk_widget_show(sapplet->slowfoo);

	accessx_status_applet_layout_box(sapplet, box, stickyfoo);
	atko = gtk_widget_get_accessible(GTK_WIDGET(sapplet->applet));
	atk_object_set_name(atko, _("AccessX Status"));
	atk_object_set_description(atko, _("Shows keyboard status when accessibility features are used."));
	return sapplet;
}

static void accessx_status_applet_destroy(GtkWidget* widget, gpointer user_data)
{
	AccessxStatusApplet* sapplet = user_data;
	/* do we need to free the icon factory ? */

	gdk_window_remove_filter(NULL, accessx_status_xkb_filter, sapplet);

	if (sapplet->xkb)
	{
		XkbFreeKeyboard(sapplet->xkb, 0, True);
	}

	if (sapplet->xkb_display)
	{
		XCloseDisplay(sapplet->xkb_display);
	}
}

static void accessx_status_applet_reorient(GtkWidget* widget, MatePanelAppletOrient o, gpointer user_data)
{
	AccessxStatusApplet* sapplet = user_data;
	GtkWidget* box;
	GtkWidget* stickyfoo;

	sapplet->orient = o;

	if (o == MATE_PANEL_APPLET_ORIENT_LEFT || o == MATE_PANEL_APPLET_ORIENT_RIGHT)
	{
		box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
		stickyfoo = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
	}
	else
	{
		box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
		stickyfoo = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
	}
	gtk_box_set_homogeneous (GTK_BOX (stickyfoo), TRUE);
	accessx_status_applet_layout_box(sapplet, box, stickyfoo);
}

static void accessx_status_applet_resize(GtkWidget* widget, int size, gpointer user_data)
{
	/* TODO: either rescale icons to fit panel, or tile them when possible */
}

static gboolean button_press_cb(GtkWidget* widget, GdkEventButton* event, AccessxStatusApplet* sapplet)
{
	if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
	{
		dialog_cb(NULL, sapplet);
	}

	return FALSE;
}

static gboolean key_press_cb(GtkWidget* widget, GdkEventKey* event, AccessxStatusApplet* sapplet)
{
	switch (event->keyval)
	{
		case GDK_KEY_KP_Enter:
		case GDK_KEY_ISO_Enter:
		case GDK_KEY_3270_Enter:
		case GDK_KEY_Return:
		case GDK_KEY_space:
		case GDK_KEY_KP_Space:
			dialog_cb(NULL, sapplet);
			return TRUE;

		default:
			break;
	}

	return FALSE;
}

static gboolean accessx_status_applet_reset(gpointer user_data)
{
	AccessxStatusApplet* sapplet = user_data;
	g_assert(sapplet->applet);
	accessx_status_applet_reorient(GTK_WIDGET(sapplet->applet), mate_panel_applet_get_orient(sapplet->applet), sapplet);

	return FALSE;
}

static gboolean accessx_status_applet_initialize(AccessxStatusApplet* sapplet)
{
	if (!sapplet->initialized)
	{
		sapplet->initialized = True;

		if (!accessx_status_applet_xkb_select(sapplet))
		{
			disable_applet(sapplet);
			popup_error_dialog(sapplet);
			return FALSE ;
		}

		gdk_window_add_filter(NULL, accessx_status_xkb_filter, sapplet);
	}

	accessx_status_applet_init_modifiers(sapplet);
	accessx_status_applet_update(sapplet, ACCESSX_STATUS_ALL, NULL);

	return TRUE;
}

static void accessx_status_applet_realize(GtkWidget* widget, gpointer user_data)
{
	AccessxStatusApplet* sapplet = user_data;

	if (!accessx_status_applet_initialize(sapplet))
	{
		return;
	}

	g_idle_add(accessx_status_applet_reset, sapplet);

	return;
}

static gboolean accessx_status_applet_fill(MatePanelApplet* applet)
{
	AccessxStatusApplet* sapplet;
	AtkObject* atk_object;
	GtkActionGroup* action_group;
	gchar* ui_path;
	gboolean was_realized = FALSE;

	sapplet = create_applet(applet);

	if (!gtk_widget_get_realized(sapplet->box))
	{
		g_signal_connect_after(G_OBJECT(sapplet->box), "realize", G_CALLBACK(accessx_status_applet_realize), sapplet);
	}
	else
	{
		accessx_status_applet_initialize(sapplet);
		was_realized = TRUE;
	}

	g_object_connect(sapplet->applet,
		"signal::destroy", accessx_status_applet_destroy, sapplet,
		"signal::change_orient", accessx_status_applet_reorient, sapplet,
		"signal::change_size", accessx_status_applet_resize, sapplet,
		NULL);

	g_signal_connect(sapplet->applet, "button_press_event", G_CALLBACK(button_press_cb), sapplet);
	g_signal_connect(sapplet->applet, "key_press_event", G_CALLBACK(key_press_cb), sapplet);

	action_group = gtk_action_group_new("Accessx Applet Actions");
	gtk_action_group_set_translation_domain(action_group, GETTEXT_PACKAGE);
	gtk_action_group_add_actions(action_group, accessx_status_applet_menu_actions, G_N_ELEMENTS(accessx_status_applet_menu_actions), sapplet);
	ui_path = g_build_filename(ACCESSX_MENU_UI_DIR, "accessx-status-applet-menu.xml", NULL);
	mate_panel_applet_setup_menu_from_file(sapplet->applet, ui_path, action_group);
	g_free(ui_path);

	if (mate_panel_applet_get_locked_down(sapplet->applet))
	{
		GtkAction* action = gtk_action_group_get_action(action_group, "Dialog");
		gtk_action_set_visible(action, FALSE);
	}

	g_object_unref(action_group);

	gtk_widget_set_tooltip_text(GTK_WIDGET(sapplet->applet), _("Keyboard Accessibility Status"));

	atk_object = gtk_widget_get_accessible(GTK_WIDGET(sapplet->applet));
	atk_object_set_name(atk_object, _("AccessX Status"));
	atk_object_set_description(atk_object, _("Displays current state of keyboard accessibility features"));
	gtk_widget_show_all(GTK_WIDGET(sapplet->applet));

	if (was_realized)
	{
		accessx_status_applet_reset(sapplet);
	}

	mate_panel_applet_set_background_widget (sapplet->applet, GTK_WIDGET (sapplet->applet));

	return TRUE;
}

static gboolean accessx_status_applet_factory(MatePanelApplet* applet, const gchar* iid, gpointer data)
{
	gboolean retval = FALSE;

	if (!strcmp(iid, "AccessxStatusApplet"))
	{
		retval = accessx_status_applet_fill(applet);
	}

	return retval;
}

MATE_PANEL_APPLET_OUT_PROCESS_FACTORY("AccessxStatusAppletFactory", PANEL_TYPE_APPLET, "accessx-status", accessx_status_applet_factory, NULL)

