changeset 422:5e46b57d1eda trunk

[svn] - added evdev-plug, written-from-scratch plugin that allows to control the player via event devices on linux systems
author giacomo
date Sun, 14 Jan 2007 17:55:24 -0800
parents 1a82af4f13cf
children dea1d9cb7735
files ChangeLog configure.ac src/evdev-plug/Makefile src/evdev-plug/ed.c src/evdev-plug/ed.h src/evdev-plug/ed_actions.h src/evdev-plug/ed_bindings_store.c src/evdev-plug/ed_bindings_store.h src/evdev-plug/ed_common.h src/evdev-plug/ed_internals.c src/evdev-plug/ed_internals.h src/evdev-plug/ed_types.h src/evdev-plug/ed_ui.c src/evdev-plug/ed_ui.h
diffstat 14 files changed, 3306 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sun Jan 14 02:35:15 2007 -0800
+++ b/ChangeLog	Sun Jan 14 17:55:24 2007 -0800
@@ -1,3 +1,13 @@
+2007-01-14 10:35:15 +0000  Michael Farber <01mf02@gmail.com>
+  revision [926]
+  More xmms_create_dirbrowser cleanup
+  trunk/src/flac112/configure.c |    2 -
+  trunk/src/flac113/configure.c |    2 -
+  trunk/src/mpg123/configure.c  |    2 -
+  trunk/src/vorbis/configure.c  |   59 +++++++++---------------------------------
+  4 files changed, 17 insertions(+), 48 deletions(-)
+
+
 2007-01-13 23:22:28 +0000  Michael Farber <01mf02@gmail.com>
   revision [924]
   Replaced xmms_create_dir_browser
--- a/configure.ac	Sun Jan 14 02:35:15 2007 -0800
+++ b/configure.ac	Sun Jan 14 17:55:24 2007 -0800
@@ -354,6 +354,33 @@
 	GENERAL_PLUGINS="$GENERAL_PLUGINS lirc"
 fi
 
+dnl *** EvDev-Plug general plugin (only built on Linux)
+
+AC_ARG_ENABLE(evdevplug,
+    [  --disable-evdevplug     disable Linux evdev plugin (default=enabled)],
+    [enable_evdevplug=$enableval],
+    [enable_evdevplug="yes"]
+)
+
+if test "x$enable_evdevplug" = "xyes"; then
+    case "$target" in
+        *-*-linux*)
+            have_evdevplug="yes"
+        ;;
+        *)
+            AC_MSG_RESULT([*** Linux evdev plugin disabled (host does not run linux) ***])
+            have_evdevplug="no"
+        ;;
+    esac
+else
+    AC_MSG_RESULT([*** Linux evdev plugin disabled per user request ***])
+    have_evdevplug="no"
+fi
+
+if test "x$have_evdevplug" = "xyes"; then
+    GENERAL_PLUGINS="$GENERAL_PLUGINS evdev-plug"
+fi
+
 dnl *** AdPlug requirement (libbinio)
 
 AC_ARG_ENABLE(adplug,
@@ -1148,6 +1175,7 @@
 echo "  -------"
 echo "  Alarm:                                  yes"
 echo "  Song Change:                            yes"
+echo "  Control via event device (evdev-plug):  $have_evdevplug"
 echo "  LIRC:                                   $have_lirc"
 echo "  AudioScrobbler Client:                  $scrobbler"
 echo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/Makefile	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,18 @@
+include ../../mk/rules.mk
+include ../../mk/init.mk
+
+OBJECTIVE_LIBS = libevdev-plug$(SHARED_SUFFIX)
+
+noinst_HEADERS = ed.h ed_ui.h ed_types.h ed_internals.h ed_actions.h ed_bindings_store.h ed_common.h
+
+LIBDIR = $(plugindir)/$(GENERAL_PLUGIN_DIR)
+
+LIBADD = $(GTK_LIBS)
+SOURCES = ed.c ed_ui.c ed_internals.c ed_bindings_store.c
+
+OBJECTS = ${SOURCES:.c=.o}
+
+CFLAGS += $(PICFLAGS) $(GTK_CFLAGS) \
+	-I../../intl -I../.. -I..
+
+include ../../mk/objective.mk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed.c	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,239 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#include "ed.h"
+#include "ed_types.h"
+#include "ed_internals.h"
+#include "ed_actions.h"
+#include "ed_ui.h"
+#include "ed_common.h"
+#include <glib/gi18n.h>
+#include <audacious/beepctrl.h>
+
+
+GList *ed_device_listening_list = NULL;
+gboolean plugin_is_active = FALSE;
+
+/* action callbacks */
+void ed_action_pb_play ( gpointer );
+void ed_action_pb_stop ( gpointer );
+void ed_action_pb_pause ( gpointer );
+void ed_action_pb_prev ( gpointer );
+void ed_action_pb_next ( gpointer );
+void ed_action_pb_eject ( gpointer );
+void ed_action_vol_up5 ( gpointer );
+void ed_action_vol_down5 ( gpointer );
+void ed_action_vol_up10 ( gpointer );
+void ed_action_vol_down10 ( gpointer );
+void ed_action_win_main ( gpointer );
+void ed_action_win_playlist ( gpointer );
+void ed_action_win_equalizer ( gpointer );
+void ed_action_pl_repeat ( gpointer );
+void ed_action_pl_shuffle ( gpointer );
+
+/* map action codes to ed_action_t objects */
+ed_action_t player_actions[] =
+{
+  [ED_ACTION_PB_PLAY] = { N_("Playback->Play") , ed_action_pb_play },
+  [ED_ACTION_PB_STOP] = { N_("Playback->Stop") , ed_action_pb_stop },
+  [ED_ACTION_PB_PAUSE] = { N_("Playback->Pause") , ed_action_pb_pause },
+  [ED_ACTION_PB_PREV] = { N_("Playback->Prev") , ed_action_pb_prev },
+  [ED_ACTION_PB_NEXT] = { N_("Playback->Next") , ed_action_pb_next },
+  [ED_ACTION_PB_EJECT] = { N_("Playback->Eject") , ed_action_pb_eject },
+
+  [ED_ACTION_PL_REPEAT] = { N_("Playlist->Repeat") , ed_action_pl_repeat },
+  [ED_ACTION_PL_SHUFFLE] = { N_("Playlist->Shuffle") , ed_action_pl_shuffle },
+
+  [ED_ACTION_VOL_UP5] = { N_("Volume->Up_5") , ed_action_vol_up5 },
+  [ED_ACTION_VOL_DOWN5] = { N_("Volume->Down_5") , ed_action_vol_down5 },
+  [ED_ACTION_VOL_UP10] = { N_("Volume->Up_10") , ed_action_vol_up10 },
+  [ED_ACTION_VOL_DOWN10] = { N_("Volume->Down_10") , ed_action_vol_down10 },
+
+  [ED_ACTION_WIN_MAIN] = { N_("Window->Main") , ed_action_win_main },
+  [ED_ACTION_WIN_PLAYLIST] = { N_("Window->Playlist") , ed_action_win_playlist },
+  [ED_ACTION_WIN_EQUALIZER] = { N_("Window->Equalizer") , ed_action_win_equalizer }
+};
+
+
+
+/* ***************** */
+/* plug-in functions */
+
+GeneralPlugin *get_gplugin_info()
+{
+   return &ed_gp;
+}
+
+
+void
+ed_init ( void )
+{
+  g_log_set_handler( NULL , G_LOG_LEVEL_WARNING , g_log_default_handler , NULL );
+
+  plugin_is_active = TRUE; /* go! */
+
+  /* read event devices and bindings from user
+     configuration and start listening for active ones */
+  ed_device_start_listening_from_config();
+
+  return;
+}
+
+
+void
+ed_cleanup ( void )
+{
+  /* shut down all devices being listened */
+  ed_device_stop_listening_all( TRUE );
+
+  plugin_is_active = FALSE; /* stop! */
+
+  return;
+}
+
+
+void
+ed_config ( void )
+{
+  ed_ui_config_show();
+}
+
+
+void
+ed_about ( void )
+{
+  ed_ui_about_show();
+}
+
+
+
+/* ************** */
+/* player actions */
+
+void
+ed_action_call ( gint code , gpointer param )
+{
+  DEBUGMSG( "Calling action; code %i ( %s )\n" , code , player_actions[code].desc );
+
+  /* activate callback for requested action */
+  player_actions[code].callback( param );
+}
+
+
+void
+ed_action_pb_play ( gpointer param )
+{
+  xmms_remote_play( ed_gp.xmms_session );
+}
+
+void
+ed_action_pb_stop ( gpointer param )
+{
+  xmms_remote_stop( ed_gp.xmms_session );
+}
+
+void
+ed_action_pb_pause ( gpointer param )
+{
+  xmms_remote_pause( ed_gp.xmms_session );
+}
+
+void
+ed_action_pb_prev ( gpointer param )
+{
+  xmms_remote_playlist_prev( ed_gp.xmms_session );
+}
+
+void
+ed_action_pb_next ( gpointer param )
+{
+  xmms_remote_playlist_next( ed_gp.xmms_session );
+}
+
+void
+ed_action_pb_eject ( gpointer param )
+{
+  xmms_remote_eject( ed_gp.xmms_session );
+}
+
+void
+ed_action_pl_repeat ( gpointer param )
+{
+  xmms_remote_toggle_repeat( ed_gp.xmms_session );
+}
+
+void
+ed_action_pl_shuffle ( gpointer param )
+{
+  xmms_remote_toggle_shuffle( ed_gp.xmms_session );
+}
+
+void
+ed_action_vol_up5 ( gpointer param )
+{
+  gint vl, vr;
+  xmms_remote_get_volume( ed_gp.xmms_session , &vl , &vr );
+  xmms_remote_set_volume( ed_gp.xmms_session , CLAMP(vl + 5, 0, 100) , CLAMP(vr + 5, 0, 100) );
+}
+
+void
+ed_action_vol_down5 ( gpointer param )
+{
+  gint vl, vr;
+  xmms_remote_get_volume( ed_gp.xmms_session , &vl , &vr );
+  xmms_remote_set_volume( ed_gp.xmms_session , CLAMP(vl - 5, 0, 100) , CLAMP(vr - 5, 0, 100) );
+}
+
+void
+ed_action_vol_up10 ( gpointer param )
+{
+  gint vl, vr;
+  xmms_remote_get_volume( ed_gp.xmms_session , &vl , &vr );
+  xmms_remote_set_volume( ed_gp.xmms_session , CLAMP(vl + 10, 0, 100) , CLAMP(vr + 10, 0, 100) );
+}
+
+void
+ed_action_vol_down10 ( gpointer param )
+{
+  gint vl, vr;
+  xmms_remote_get_volume( ed_gp.xmms_session , &vl , &vr );
+  xmms_remote_set_volume( ed_gp.xmms_session , CLAMP(vl - 10, 0, 100) , CLAMP(vr - 10, 0, 100) );
+}
+
+void
+ed_action_win_main ( gpointer param )
+{
+  xmms_remote_main_win_toggle( ed_gp.xmms_session ,
+    !xmms_remote_is_main_win ( ed_gp.xmms_session ) );
+}
+
+void
+ed_action_win_playlist ( gpointer param )
+{
+  xmms_remote_pl_win_toggle( ed_gp.xmms_session ,
+    !xmms_remote_is_pl_win ( ed_gp.xmms_session ) );
+}
+
+void
+ed_action_win_equalizer ( gpointer param )
+{
+  xmms_remote_eq_win_toggle( ed_gp.xmms_session ,
+    !xmms_remote_is_eq_win ( ed_gp.xmms_session ) );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed.h	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,45 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#ifndef _I_ED_H
+#define _I_ED_H 1
+
+#include "ed_common.h"
+#include <glib.h>
+#include <audacious/plugin.h>
+
+void ed_init ( void );
+void ed_cleanup ( void );
+void ed_config ( void );
+void ed_about ( void );
+
+GeneralPlugin ed_gp =
+{
+    NULL,					/* handle */
+    NULL,					/* filename */
+    -1,						/* session */
+    "EvDev-Plug " ED_VERSION_PLUGIN,		/* description */
+    ed_init,					/* init */
+    ed_about,					/* about */
+    ed_config,					/* configure */
+    ed_cleanup					/* cleanup */
+};
+
+#endif /* !_I_ED_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed_actions.h	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,60 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#ifndef _I_ED_ACTIONS_H
+#define _I_ED_ACTIONS_H 1
+
+#include "ed_common.h"
+#include <glib.h>
+
+/* ed_action_t object structure */
+typedef struct
+{
+  const gchar * desc;
+  void (*callback)( gpointer );
+}
+ed_action_t;
+
+/* action codes */
+enum
+{
+  ED_ACTION_PB_PLAY = 0,
+  ED_ACTION_PB_STOP = 1,
+  ED_ACTION_PB_PAUSE = 2,
+  ED_ACTION_PB_PREV = 3,
+  ED_ACTION_PB_NEXT = 4,
+  ED_ACTION_PB_EJECT = 5,
+
+  ED_ACTION_PL_REPEAT = 10,
+  ED_ACTION_PL_SHUFFLE = 11,
+
+  ED_ACTION_VOL_UP5 = 20,
+  ED_ACTION_VOL_DOWN5 = 21,
+  ED_ACTION_VOL_UP10 = 22,
+  ED_ACTION_VOL_DOWN10 = 23,
+
+  ED_ACTION_WIN_MAIN = 30,
+  ED_ACTION_WIN_PLAYLIST = 31,
+  ED_ACTION_WIN_EQUALIZER = 32
+};
+
+void ed_action_call ( gint , gpointer );
+
+#endif /* !_I_ED_ACTIONS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed_bindings_store.c	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,137 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#include "ed_bindings_store.h"
+#include "ed_common.h"
+#include <stdlib.h>
+
+
+/* currently, the bindings store is implemented as a string-based Hash Table;
+   however, a change of implementation would only require to re-write the
+   public API defined in this source file;
+   the rest of the code in EvDev-Plug doesn't care about the
+   implementation at all, and access it in a transparent fashion */
+
+
+gpointer
+ed_bindings_store_new ( void )
+{
+  GHashTable *hashtable = g_hash_table_new_full( g_str_hash , g_str_equal , g_free , NULL );
+  return hashtable;
+}
+
+
+/* associate a triplet ev_type:ev_code:ev_value
+   (identifying an input event) to an action code in our bindings store;
+   bindings store should keep its own copy of ed_inputevent_t objects! */
+gint
+ed_bindings_store_insert ( gpointer hashtable_gp ,
+                           ed_inputevent_t * inputev ,
+                           gint action_code )
+{
+  gchar *input_str;
+  input_str = g_strdup_printf( "%i:%i:%i" , inputev->type , inputev->code , inputev->value );
+  g_hash_table_insert( (GHashTable*)hashtable_gp , input_str , GINT_TO_POINTER(action_code) );
+
+  DEBUGMSG( "storing code %i -> event: %i:%i:%i\n" ,
+    action_code , inputev->type, inputev->code, inputev->value );
+  return 0;
+}
+
+
+typedef struct
+{
+  ed_bindings_store_foreach_func callback;
+  gpointer data1;
+  gpointer data2;
+}
+hashfunc_foreach_container_t;
+
+static void
+hashfunc_foreach( gpointer key , gpointer value , gpointer container_gp )
+{
+  hashfunc_foreach_container_t *container = container_gp;
+  ed_inputevent_t inputev;
+  gchar **toks;
+
+  /* convert key (it's a string in the "type:code:value" form) back to ed_inputevent_t object */
+  toks = g_strsplit( (gchar*)key , ":" , 3 );
+  inputev.type = atoi( toks[0] );
+  inputev.code = atoi( toks[1] );
+  inputev.value = atoi( toks[2] );
+  g_strfreev( toks );
+
+  container->callback( &inputev , GPOINTER_TO_INT(value) , container->data1 , container->data2 );
+  return;
+}
+
+gint
+ed_bindings_store_foreach ( gpointer hashtable_gp ,
+                            ed_bindings_store_foreach_func callback ,
+                            gpointer user_data1 ,
+                            gpointer user_data2 )
+{
+  hashfunc_foreach_container_t *container = g_malloc(sizeof(hashfunc_foreach_container_t));
+  container->callback = callback;
+  container->data1 = user_data1;
+  container->data2 = user_data2;
+  g_hash_table_foreach( (GHashTable*)hashtable_gp , hashfunc_foreach , container );
+  g_free( container );
+}
+
+
+guint
+ed_bindings_store_size ( gpointer hashtable_gp )
+{
+  return g_hash_table_size( (GHashTable*)hashtable_gp );
+}
+
+
+gboolean
+ed_bindings_store_lookup( gpointer hashtable_gp ,
+                          ed_inputevent_t * inputev ,
+                          gint * action_code )
+{
+  gpointer p;
+  gchar *input_str;
+
+  input_str = g_strdup_printf( "%i:%i:%i" , inputev->type , inputev->code , inputev->value );
+
+  if ( g_hash_table_lookup_extended( (GHashTable*)hashtable_gp , input_str , NULL , &p ) == TRUE )
+  {
+    *action_code = GPOINTER_TO_INT(p);
+    g_free( input_str );
+    return TRUE;
+  }
+  else
+  {
+    g_free( input_str );
+    return FALSE;
+  }
+}
+
+
+gint
+ed_bindings_store_delete ( gpointer hashtable_gp )
+{
+  if ( hashtable_gp != NULL )
+    g_hash_table_destroy( hashtable_gp );
+  return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed_bindings_store.h	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,37 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#ifndef _I_ED_BINDINGS_STORE_H
+#define _I_ED_BINDINGS_STORE_H 1
+
+#include "ed_types.h"
+#include "ed_common.h"
+#include <glib.h>
+
+typedef void (*ed_bindings_store_foreach_func)( ed_inputevent_t * , gint , gpointer , gpointer );
+
+gpointer ed_bindings_store_new ( void );
+gint ed_bindings_store_insert ( gpointer , ed_inputevent_t * , gint );
+gint ed_bindings_store_foreach ( gpointer , ed_bindings_store_foreach_func , gpointer , gpointer );
+guint ed_bindings_store_size ( gpointer );
+gboolean ed_bindings_store_lookup( gpointer , ed_inputevent_t * , gint * );
+gint ed_bindings_store_delete ( gpointer );
+
+#endif /* !_I_ED_BINDINGS_STORE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed_common.h	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,41 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#ifndef _I_ED_COMMON_H
+#define _I_ED_COMMON_H 1
+
+#define DEBUG 1
+
+#ifdef DEBUG
+#include <stdio.h>
+#define DEBUGMSG(...) { fprintf(stderr, "evdev-plug(%s:%s:%d): ", __FILE__, __FUNCTION__, (int) __LINE__); fprintf(stderr, __VA_ARGS__); }
+#else
+#define DEBUGMSG(...)
+#endif /* DEBUG */
+
+#include "../../config.h"
+
+#define ED_VERSION_PLUGIN "0.1beta"
+#define ED_VERSION_CONFIG "0"
+
+#define PLAYER_LOCALRC_DIR ".audacious"
+#define PLAYER_LOCALRC_FILE "evdev-plug.conf"
+
+#endif /* !_I_ED_COMMON_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed_internals.c	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,1005 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#include "ed_types.h"
+#include "ed_internals.h"
+#include "ed_actions.h"
+#include "ed_bindings_store.h"
+#include "ed_common.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <linux/input.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <regex.h>
+/* for variadic */
+#include <stdarg.h>
+
+#include <glib/gi18n.h>
+
+
+static gboolean ed_device_giofunc ( GIOChannel * , GIOCondition , gpointer );
+
+static gint ed_util_get_data_from_keyfile( GKeyFile * , gchar * , ... );
+static gpointer ed_util_get_bindings_from_keyfile( GKeyFile * , gchar * );
+
+extern GList *ed_device_listening_list;
+
+
+/* ***************** */
+/*     internals     */
+
+ed_device_info_t *
+ed_device_info_new ( gchar * device_name , gchar * device_filename ,
+                     gchar * device_phys , gint device_is_custom )
+{
+  ed_device_info_t *info = g_malloc(sizeof(ed_device_info_t));
+  info->name = g_strdup(device_name);
+  info->filename = g_strdup(device_filename);
+  info->phys = g_strdup(device_phys);
+  info->is_custom = device_is_custom;
+  info->is_active = FALSE;
+  info->bindings = NULL;
+  info->reg = 0;
+  return info;
+}
+
+
+gint
+ed_device_info_delete ( ed_device_info_t * info )
+{
+  g_free( info->phys );
+  g_free( info->filename );
+  g_free( info->name );
+  g_free( info );
+  return 0;
+}
+
+
+ed_device_t *
+ed_device_new ( gchar * device_name , gchar * device_filename ,
+                gchar * device_phys , gint device_is_custom )
+{
+  ed_device_t *event_device;
+  GIOChannel *iochan;
+  gint fd;
+
+  fd = g_open( device_filename , O_RDONLY , 0 );
+  if ( fd < 0 )
+  {
+    /* an error occurred */
+    g_warning( _("event-device-plugin: unable to open device file %s , skipping this device; check that "
+               "the file exists and that you have read permission for it\n") , device_filename );
+    return NULL;
+  }
+
+  iochan = g_io_channel_unix_new( fd );
+  if ( iochan == NULL )
+  {
+    /* an error occurred */
+    g_warning( _("event-device-plugin: unable to create a io_channel for device file %s ,"
+               "skipping this device\n") , device_filename );
+    close( fd );
+    return NULL;
+  }
+  g_io_channel_set_encoding( iochan , NULL , NULL ); /* binary data */
+
+  event_device = g_malloc(sizeof(ed_device_t));
+  event_device->fd = fd;
+  event_device->iochan = iochan;
+  event_device->is_listening = FALSE;
+  event_device->info = ed_device_info_new(
+    device_name , device_filename , device_phys , device_is_custom );
+
+  return event_device;
+}
+
+
+gint
+ed_device_delete ( ed_device_t * event_device )
+{
+  if ( event_device->is_listening )
+    ed_device_stop_listening( event_device );
+
+  g_io_channel_shutdown( event_device->iochan , TRUE , NULL );
+  g_io_channel_unref( event_device->iochan );
+  close( event_device->fd );
+
+  ed_device_info_delete( event_device->info );
+  g_free( event_device );
+
+  return 0;
+}
+
+
+gboolean
+ed_inputevent_check_equality( ed_inputevent_t *iev1 , ed_inputevent_t *iev2 )
+{
+  if (( iev1 == NULL ) || ( iev2 == NULL ))
+  {
+    if (( iev1 == NULL ) && ( iev2 == NULL ))
+      return TRUE;
+    else
+      return FALSE;
+  }
+
+  if ( ( iev1->code == iev2->code ) &&
+       ( iev1->type == iev2->type ) &&
+       ( iev1->value == iev2->value ) )
+    return TRUE;
+  else
+    return FALSE;
+}
+
+
+gboolean
+ed_device_info_check_equality( ed_device_info_t *info1 , ed_device_info_t *info2 )
+{
+  if (( info1 == NULL ) || ( info2 == NULL ))
+  {
+    if (( info1 == NULL ) && ( info2 == NULL ))
+      return TRUE;
+    else
+      return FALSE;
+  }
+
+  if ( ( strcmp(info1->name,info2->name) == 0 ) &&
+       ( strcmp(info1->filename,info2->filename) == 0 ) &&
+       ( strcmp(info1->phys,info2->phys) == 0 ) &&
+       ( info1->is_custom == info2->is_custom ) )
+    return TRUE;
+  else
+    return FALSE;
+}
+
+
+gint
+ed_device_start_listening ( ed_device_t * event_device )
+{
+  if ( g_list_find( ed_device_listening_list , event_device ) != NULL )
+  {
+    DEBUGMSG( "called start listening for device \"%s\" ( %s - %s ) but device listening is already active!\n" ,
+               event_device->info->name , event_device->info->filename , event_device->info->phys );
+    return -1; /* device listening is already active, do nothing */
+  }
+  else
+  {
+    DEBUGMSG( "start listening for device \"%s\" ( %s - %s )\n" ,
+               event_device->info->name , event_device->info->filename , event_device->info->phys );
+    /* add a watch that checks if there's data to read */
+    event_device->iochan_sid = g_io_add_watch( event_device->iochan , G_IO_IN ,
+      (GIOFunc)ed_device_giofunc , event_device );
+
+    /* add event_device to the list */
+    ed_device_listening_list = g_list_append( ed_device_listening_list , event_device );
+    event_device->is_listening = TRUE;
+    return 0;
+  }
+}
+
+
+gint
+ed_device_stop_listening ( ed_device_t * event_device )
+{
+  if ( ( g_list_find( ed_device_listening_list , event_device ) != NULL ) &&
+       ( event_device->is_listening == TRUE ) )
+  {
+    DEBUGMSG( "stop listening for device \"%s\" ( %s - %s )\n" ,
+               event_device->info->name , event_device->info->filename , event_device->info->phys );
+    g_source_remove( event_device->iochan_sid );
+    ed_device_listening_list = g_list_remove( ed_device_listening_list , event_device );
+    event_device->is_listening = FALSE;
+    return 0;
+  }
+  else
+  {
+    DEBUGMSG( "called stop listening for device \"%s\" ( %s - %s ) but device listening is not active!\n" ,
+               event_device->info->name , event_device->info->filename , event_device->info->phys );
+    return -1;
+  }
+}
+
+
+gint
+ed_device_stop_listening_from_info ( ed_device_info_t * info )
+{
+  GList *list_iter = ed_device_listening_list;
+  while ( list_iter != NULL )
+  {
+    ed_device_t *dev = list_iter->data;
+    if ( ed_device_info_check_equality( dev->info , info ) == TRUE )
+    {
+      ed_device_stop_listening( dev );
+      return 0;
+    }
+    list_iter = g_list_next( list_iter );
+  }
+  return -1;
+}
+
+
+gint
+ed_device_stop_listening_all ( gboolean delete_bindings )
+{
+  /* convenience function that stops listening for all
+     devices and also deletes bindings if requested */
+  GList *list_iter = ed_device_listening_list;
+  while ( list_iter != NULL )
+  {
+    ed_device_t *dev = list_iter->data;
+
+    if (( delete_bindings == TRUE ) && ( dev->info->bindings != NULL ))
+      ed_bindings_store_delete( dev->info->bindings );
+
+    ed_device_delete( dev );
+
+    list_iter = g_list_next( list_iter );
+  }
+}
+
+
+gboolean
+ed_device_check_listening_from_info ( ed_device_info_t * info )
+{
+  /* note: this must not alter the reg parameter of info */
+  GList *list_iter = ed_device_listening_list;
+  while ( list_iter != NULL )
+  {
+    ed_device_t *dev = list_iter->data;
+    if ( ed_device_info_check_equality( dev->info , info ) == TRUE )
+      return TRUE;
+    list_iter = g_list_next( list_iter );
+  }
+  return FALSE;
+}
+
+
+static gboolean
+ed_device_giofunc ( GIOChannel * iochan , GIOCondition cond , gpointer event_device )
+{
+  switch ( cond )
+  {
+    case G_IO_IN:
+    {
+      gsize rb = 0;
+      struct input_event inputev;
+
+      if ( g_io_channel_read_chars( iochan , (gchar*)&inputev ,
+             sizeof(struct input_event) , &rb , NULL ) == G_IO_STATUS_NORMAL )
+      {
+        if ( rb == sizeof(struct input_event) )
+        {
+          gint action_code = -1;
+          ed_device_t *dev = event_device;
+
+          DEBUGMSG( "event (%d,%d,%d) intercepted for device \"%s\" ( %s - %s )\n" ,
+            inputev.type , inputev.code , inputev.value ,
+            dev->info->name , dev->info->filename , dev->info->phys );
+
+          if ( dev->info->bindings != NULL )
+          {
+            ed_inputevent_t ev;
+            ev.type = inputev.type;
+            ev.code = inputev.code;
+            ev.value = inputev.value;
+
+            /* lookup event type/code/value in the binding tree for this device */
+            if ( ed_bindings_store_lookup( dev->info->bindings , &ev , &action_code ) == TRUE )
+            {
+              /* this has been binded to an action, call the corresponding action */
+              DEBUGMSG( "found action code %i for event (%d,%d,%d)\n" ,
+                action_code , inputev.type , inputev.code , inputev.value );
+              ed_action_call( action_code , NULL );
+            }
+          }
+        }
+      }
+      break;
+    }
+  }
+
+  return TRUE;
+}
+
+
+GList *
+ed_device_get_list_from_system ( void )
+{
+  GIOChannel *iochan;
+  gchar *buffer;
+  gsize buffer_len;
+  gint fd = -1;
+
+  fd = g_open( "/proc/bus/input/devices" , O_RDONLY , 0 );
+  if ( fd < 0 )
+  {
+    /* an error occurred */
+    g_warning( _("event-device-plugin: unable to open /proc/bus/input/devices , automatic "
+               "detection of event devices won't work.\n") );
+    return NULL;
+  }
+
+  iochan = g_io_channel_unix_new( fd );
+  if ( iochan == NULL )
+  {
+    /* an error occurred */
+    g_warning( _("event-device-plugin: unable to open a io_channel for /proc/bus/input/devices , "
+               "automatic detection of event devices won't work.\n") );
+    close( fd );
+    return NULL;
+  }
+  g_io_channel_set_encoding( iochan , "UTF-8" , NULL ); /* utf-8 text */
+
+  if ( g_io_channel_read_to_end( iochan , &buffer , &buffer_len , NULL ) != G_IO_STATUS_NORMAL )
+  {
+    /* an error occurred */
+    g_warning( _("event-device-plugin: an error occurred while reading /proc/bus/input/devices , "
+               "automatic detection of event devices won't work.\n") );
+    g_io_channel_shutdown( iochan , TRUE , NULL );
+    g_io_channel_unref( iochan );
+    close( fd );
+    return NULL;
+  }
+  else
+  {
+    regex_t preg;
+    gint search_offset = 0;
+    GList *system_devices_list = NULL;
+
+    /* we don't need these anymore */
+    g_io_channel_shutdown( iochan , TRUE , NULL );
+    g_io_channel_unref( iochan );
+    close( fd );
+
+    /* parse content of /proc/bus/input/devices */
+    regcomp( &preg,
+      "I:[^\n]*\nN: Name=\"([^\n]*)\"\nP: Phys=([^\n]*)\n[^\n]+\nH: Handlers=[^\n]*(event[0-9]+)[^\n]*\n" ,
+      REG_ICASE | REG_EXTENDED );
+
+    while ( search_offset > -1 )
+    {
+      size_t nmatch = 4;
+      regmatch_t submatch[4];
+
+      if ( regexec( &preg , &buffer[search_offset] , nmatch , submatch , 0 ) == 0 )
+      {
+        GString *device_name = NULL;
+        GString *device_phys = NULL;
+        GString *device_file = NULL;
+
+        if ( submatch[1].rm_so != -1 ) /* check validity of name sub-expression */
+        {
+          device_name = g_string_new( "" );
+          g_string_append_len( device_name ,
+            &buffer[(search_offset + submatch[1].rm_so)] ,
+            submatch[1].rm_eo - submatch[1].rm_so );
+        }
+
+        if ( submatch[2].rm_so != -1 ) /* check validity of physicalport sub-expression */
+        {
+          device_phys = g_string_new( "" );
+          g_string_append_len( device_phys ,
+            &buffer[(search_offset + submatch[2].rm_so)] ,
+            submatch[2].rm_eo - submatch[2].rm_so );
+        }
+
+        if ( submatch[3].rm_so != -1 ) /* check validity of filename sub-expression */
+        {
+          device_file = g_string_new( "" );
+          GString *device_test = g_string_new( "" );
+          g_string_append_len( device_file ,
+            &buffer[(search_offset + submatch[3].rm_so)] ,
+            submatch[3].rm_eo - submatch[3].rm_so );
+
+          /* let's check if the filename actually exists in /dev */
+          g_string_printf( device_test , "/dev/input/%s" , device_file->str );
+          if ( !g_file_test( device_test->str , G_FILE_TEST_EXISTS ) )
+          {
+            /* it doesn't exist, mark as invalid device by nullifying device_file*/
+            g_warning( _("event-device-plugin: device %s not found in /dev/input , skipping.\n") , device_file );
+            g_string_free( device_file , TRUE );
+            device_file = NULL;
+          }
+          else
+          {
+            /* it does exist, mark as valid device by using the full path in device_file*/
+            g_string_assign( device_file , device_test->str );
+          }
+          g_string_free( device_test , TRUE );
+        }
+
+        if (( device_name != NULL ) && ( device_phys != NULL ) && ( device_file != NULL ))
+        {
+          /* add item to the list */
+          ed_device_info_t *info = ed_device_info_new(
+            device_name->str , device_file->str , device_phys->str , 0 );
+          info->reg = 0;
+          DEBUGMSG( "device found, name:\"%s\" , file \"%s\" , phys \"%s\"\n" ,
+            info->name , info->filename , info->phys );
+          system_devices_list = g_list_append( system_devices_list , info );
+        }
+
+        if ( device_name != NULL )
+          g_string_free( device_name , TRUE );
+        if ( device_phys != NULL )
+          g_string_free( device_phys , TRUE );
+        if ( device_file != NULL )
+          g_string_free( device_file , TRUE );
+
+        search_offset += submatch[0].rm_eo; /* update offset for further search */
+      }
+      else
+      {
+        /* no more valid devices found */
+        search_offset = -1;
+      }
+    }
+    regfree( &preg );
+    return system_devices_list;
+  }
+}
+
+
+GList *
+ed_device_get_list_from_config ( void )
+{
+  GKeyFile *keyfile = NULL;
+  GList *config_devices_list = NULL;
+  gboolean is_loaded = FALSE;
+  gchar **device_names = NULL;
+  gsize device_names_num = 0;
+  gchar *config_pathfilename = NULL;
+  gint i = 0;
+
+  config_pathfilename = g_strjoin( "" , g_get_home_dir() ,
+    "/" PLAYER_LOCALRC_DIR "/" PLAYER_LOCALRC_FILE , NULL );
+  keyfile = g_key_file_new();
+  is_loaded = g_key_file_load_from_file( keyfile , config_pathfilename , G_KEY_FILE_NONE , NULL );
+  g_free( config_pathfilename );
+
+  if ( is_loaded != TRUE )
+  {
+    g_warning( _("event-device-plugin: unable to load config file %s , default settings will be used.\n") ,
+               PLAYER_LOCALRC_FILE );
+    g_key_file_free( keyfile );
+    return NULL;
+  }
+
+  /* remove ___plugin___ group that contains plugin settings */
+  g_key_file_remove_group( keyfile , "___plugin___" , NULL );
+
+  /* the other groups are devices; check them and run active ones */
+  device_names = g_key_file_get_groups( keyfile , &device_names_num );
+  while ( device_names[i] != NULL )
+  {
+    gint device_is_custom = 0;
+    gchar *device_file = NULL;
+    gchar *device_phys = NULL;
+    gboolean device_is_active = FALSE;
+    gint result = 0;
+
+    result = ed_util_get_data_from_keyfile(
+               keyfile , device_names[i] ,
+               ED_CONFIG_INFO_FILENAME , &device_file ,
+               ED_CONFIG_INFO_PHYS , &device_phys ,
+               ED_CONFIG_INFO_ISCUSTOM , &device_is_custom ,
+               ED_CONFIG_INFO_ISACTIVE , &device_is_active ,
+               ED_CONFIG_INFO_END );
+
+    if ( result == 0 )
+    {
+      /* all information succesfully retrieved from config, create a ed_device_info_t */
+      ed_device_info_t *info;
+
+      info = ed_device_info_new( device_names[i] , device_file ,
+                                 device_phys , device_is_custom );
+
+      /* pick bindings for this device */
+      info->bindings = ed_util_get_bindings_from_keyfile( keyfile , device_names[i] );
+      info->is_active = device_is_active;
+
+      /* add this device to the config list */
+      config_devices_list = g_list_append( config_devices_list , info );
+      /* free information from config file, info has its own copies */
+      g_free( device_file ); g_free( device_phys );
+    }
+    else
+    {
+      g_warning( _("event-device-plugin: incomplete information in config file for device \"%s\""
+                 " , skipping.\n") , device_names[i] );
+    }
+
+    i++; /* on with next */
+  }
+
+  g_strfreev( device_names );
+  g_key_file_free( keyfile );
+  return config_devices_list;
+}
+
+
+void
+ed_device_free_list ( GList * system_devices_list )
+{
+  GList *list_iter = system_devices_list;
+  while ( list_iter != NULL )
+  {
+    ed_device_info_delete( (ed_device_info_t*)list_iter->data );
+    list_iter = g_list_next( list_iter );
+  }
+  g_list_free( system_devices_list );
+  return;
+}
+
+
+void
+ed_device_start_listening_from_config ( void )
+{
+  GKeyFile *keyfile = NULL;
+  gboolean is_loaded = FALSE;
+  gchar **device_names = NULL;
+  gsize device_names_num = 0;
+  gchar *config_pathfilename = NULL;
+  GList *system_devices_list = NULL;
+  gint i = 0;
+
+  config_pathfilename = g_strjoin( "" , g_get_home_dir() ,
+    "/" PLAYER_LOCALRC_DIR "/" PLAYER_LOCALRC_FILE , NULL );
+  keyfile = g_key_file_new();
+  is_loaded = g_key_file_load_from_file( keyfile , config_pathfilename , G_KEY_FILE_NONE , NULL );
+  g_free( config_pathfilename );
+
+  if ( is_loaded != TRUE )
+  {
+    g_warning( _("event-device-plugin: unable to load config file %s , default settings will be used.\n") ,
+               PLAYER_LOCALRC_FILE );
+    g_key_file_free( keyfile );
+    return;
+  }
+
+  system_devices_list = ed_device_get_list_from_system();
+
+  /* remove ___plugin___ group that contains plugin settings */
+  g_key_file_remove_group( keyfile , "___plugin___" , NULL );
+
+  /* check available devices and run active ones */
+  device_names = g_key_file_get_groups( keyfile , &device_names_num );
+  while ( device_names[i] != NULL )
+  {
+    GError *gerr = NULL;
+    gboolean is_active;
+
+    is_active = g_key_file_get_boolean( keyfile , device_names[i] , "is_active" , &gerr );
+    if ( gerr != NULL )
+    {
+      g_warning( _("event-device-plugin: configuration, unable to get is_active value for device \"%s\""
+                 ", skipping it.\n") , device_names[i] );
+      g_clear_error( &gerr );
+    }
+
+    if ( is_active == TRUE ) /* only care about active devices at this time, ignore others */
+    {
+      gint is_custom = 0;
+      gchar *device_file = NULL;
+      gchar *device_phys = NULL;
+      gint result = 0;
+
+      result = ed_util_get_data_from_keyfile(
+                 keyfile , device_names[i] ,
+                 ED_CONFIG_INFO_FILENAME , &device_file ,
+                 ED_CONFIG_INFO_PHYS , &device_phys ,
+                 ED_CONFIG_INFO_ISCUSTOM , &is_custom ,
+                 ED_CONFIG_INFO_END );
+
+      if ( result != 0 )
+      {
+        /* something wrong, skip this device */
+        i++; continue;
+      }
+
+      /* unless this is a custom device, perform a device check */
+      if ( is_custom != 1 )
+      {
+        /* not a custom device, check it against system_devices_list
+           to see if its information should be updated or if it's not plugged at all */
+        gint check_result = ed_device_check(
+          system_devices_list , device_names[i] , &device_file , &device_phys );
+
+        if ( check_result == ED_DEVCHECK_OK )
+        {
+          /* ok, we have an active not-custom device and it has been successfully
+             checked too; create a ed_device_t item for it */
+          ed_device_t *dev = ed_device_new ( device_names[i] , device_file , device_phys , 0 );
+          g_free( device_file ); g_free( device_phys ); /* not needed anymore */
+          if ( dev != NULL )
+          {
+            dev->info->bindings = ed_util_get_bindings_from_keyfile( keyfile , device_names[i] );
+            ed_device_start_listening ( dev );
+          }
+        }
+
+        /* note: if check_result == ED_DEVCHECK_ABSENT, we simply skip this device */
+      }
+      else
+      {
+        /* ok, we have an active custom device; create a ed_device_t item for it */
+        ed_device_t *dev = ed_device_new ( device_names[i] , device_file , device_phys , 1 );
+        g_free( device_file ); g_free( device_phys ); /* not needed anymore */
+        if ( dev != NULL )
+        {
+          dev->info->bindings = ed_util_get_bindings_from_keyfile( keyfile , device_names[i] );
+          ed_device_start_listening ( dev );
+        }
+      }
+    }
+
+    /* on with next device name */
+    i++;
+  }
+
+  g_strfreev( device_names );
+  ed_device_free_list( system_devices_list );
+  g_key_file_free( keyfile );
+  return;
+}
+
+
+/* this function checks that a given event device (with device_name,
+   device_phys and device_file) exists in system_devices_list; device_phys
+   and device_file must be dynamically-allocated string, they could
+   be freed and reallocated if their information needs to be updated;
+   it returns an integer, its value represents the performed operations */
+gint
+ed_device_check ( GList * system_devices_list ,
+                  gchar * device_name ,
+                  gchar ** device_file ,
+                  gchar ** device_phys )
+{
+  /* first, search in the list for a device, named device_name,
+     that has not been found in a previous ed_device_check
+     made with the same system_devices_list (info->reg == 0) */
+  GList *list_iter = system_devices_list;
+
+  while ( list_iter != NULL )
+  {
+    ed_device_info_t *info = list_iter->data;
+
+    if ( ( info->reg == 0 ) && ( strcmp( device_name , info->name ) == 0 ) )
+    {
+      /* found a device, check if it has the same physical address */
+      if ( strcmp( *device_phys , info->phys ) == 0 )
+      {
+        /* good, same device name and same physical
+           address; update device_file if necessary */
+        if ( strcmp( *device_file , info->filename ) != 0 )
+        {
+          g_free( *device_file );
+          *device_file = g_strdup( info->filename );
+        }
+        /* now mark it as "found" so it won't be searched in next
+           ed_device_check made with the same system_devices_list*/
+        info->reg = 1;
+        /* everything done */
+        return ED_DEVCHECK_OK;
+      }
+      else
+      {
+        /* device found, but physical address is not the one from *device_phys; try to
+           search further in system_devices_list for a device with same name and address */
+        GList *list_iter2 = g_list_next(list_iter);
+        while ( list_iter2 != NULL )
+        {
+          ed_device_info_t *info2 = list_iter2->data;
+          if ( ( info2->reg == 0 ) &&
+               ( strcmp( device_name , info2->name ) == 0 ) &&
+               ( strcmp( *device_phys , info2->phys ) == 0 ) )
+          {
+            /* found a device with the same name and address,
+               so let's use it; update device_file if necessary */
+            if ( strcmp( *device_file , info2->filename ) != 0 )
+            {
+              g_free( *device_file );
+              *device_file = g_strdup( info2->filename );
+            }
+            /* now mark it as "found" so it won't be searched in next
+               ed_device_check made with the same system_devices_list */
+            info2->reg = 1;
+            /* everything done */
+            return ED_DEVCHECK_OK;
+          }
+          list_iter2 = g_list_next(list_iter2);
+        }
+
+        /* if we get to this point, it means that there isn't any device named
+           device_name with physical address equal to *device_phys ; there is only
+           one (or more) device named device_name but with different physical
+           address; we'll use the first of those (alas the current content of info) */
+        g_free( *device_phys ); /* free outdated device_phys */
+        *device_phys = g_strdup( info->phys ); /* update it with the new one */
+
+        /* update device_file if necessary */
+        if ( strcmp( *device_file , info->filename ) != 0 )
+        {
+          g_free( *device_file );
+          *device_file = g_strdup( info->filename );
+        }
+
+        /* now mark it as "found" so it won't be searched in next
+           ed_device_check made with the same system_devices_list*/
+        info->reg = 1;
+        /* everything done */
+        return ED_DEVCHECK_OK;
+      }
+    }
+
+    list_iter = g_list_next(list_iter);
+  }
+
+  /* the entire system_devices_list was searched,
+     but no device named device_name was found */
+  return ED_DEVCHECK_ABSENT;
+}
+
+
+
+/* config */
+static void
+ed_config_save_from_list_bindings_foreach ( ed_inputevent_t * iev ,
+                                            gint action_code ,
+                                            gpointer keyfile ,
+                                            gpointer info_gp )
+{
+  gint int_list[4];
+  gchar *keyname;
+  ed_device_info_t *info = info_gp;
+  keyname = g_strdup_printf( "b%i" , info->reg );
+  int_list[0] = action_code;
+  int_list[1] = iev->type;
+  int_list[2] = iev->code;
+  int_list[3] = iev->value;
+  g_key_file_set_integer_list( keyfile , info->name , keyname , int_list , 4 );
+  g_free( keyname );
+  info->reg++;
+  return;
+}
+
+gint
+ed_config_save_from_list ( GList * config_devices_list )
+{
+  GKeyFile *keyfile;
+  GList *iter_list = NULL;
+  gchar *keyfile_str = NULL;
+  gsize keyfile_str_len = 0;
+  GIOChannel *iochan;
+  gchar *config_pathfilename = NULL;
+
+  config_pathfilename = g_strjoin( "" , g_get_home_dir() ,
+    "/" PLAYER_LOCALRC_DIR "/" PLAYER_LOCALRC_FILE , NULL );
+
+  keyfile = g_key_file_new();
+
+  g_key_file_set_string( keyfile , "___plugin___" , "config_ver" , ED_VERSION_CONFIG );
+
+  iter_list = config_devices_list;
+  while ( iter_list != NULL )
+  {
+    ed_device_info_t *info = iter_list->data;
+    g_key_file_set_string( keyfile , info->name , "filename" , info->filename );
+    g_key_file_set_string( keyfile , info->name , "phys" , info->phys );
+    g_key_file_set_boolean( keyfile , info->name , "is_active" , info->is_active );
+    g_key_file_set_integer( keyfile , info->name , "is_custom" , info->is_custom );
+    /* use the info->reg field as a counter to list actions */
+    info->reg = 0; /* init the counter */
+    if ( info->bindings != NULL )
+      ed_bindings_store_foreach( info->bindings ,
+         ed_config_save_from_list_bindings_foreach , keyfile , info );
+    iter_list = g_list_next( iter_list );
+  }
+
+  keyfile_str = g_key_file_to_data( keyfile , &keyfile_str_len , NULL );
+  iochan = g_io_channel_new_file( config_pathfilename , "w" , NULL );
+  g_io_channel_set_encoding( iochan , "UTF-8" , NULL );
+  g_io_channel_write_chars( iochan , keyfile_str , keyfile_str_len , NULL , NULL );
+  g_io_channel_shutdown( iochan , TRUE , NULL );
+  g_io_channel_unref( iochan );
+
+  g_free( keyfile_str );
+  g_key_file_free( keyfile );
+  return 0;
+}
+
+
+/* utils */
+
+
+/* this picks information from a keyfile, using device_name
+   as group name; information must be requested by passing
+   a ed_config_info_t value and a corresponding container;
+   list of requested information must be terminated with
+   ED_CONFIG_INFO_END; returns 0 if everything is found,
+   returns negative values if some information is missing */
+static gint
+ed_util_get_data_from_keyfile( GKeyFile * keyfile , gchar * device_name , ... )
+{
+  GError *gerr = NULL;
+  gboolean is_failed = FALSE;
+  ed_config_info_t info_code = ED_CONFIG_INFO_END;
+  GList *temp_stringstore = NULL;
+  va_list ap;
+
+  /* when we get a string value from g_key_file_get_string, we temporarily
+     store its container in temp_stringstore; if subsequent information
+     requests in the iteraton fails, we free the information in previous
+     container and nullify its content;
+     this way, user will get complete information (return value 0) or
+     absent information (all string containers nullified, return value -1) */
+
+  va_start( ap, device_name );
+
+  while ( ( is_failed == FALSE ) &&
+          ( ( info_code = va_arg( ap , ed_config_info_t ) ) != ED_CONFIG_INFO_END ) )
+  {
+    switch ( info_code )
+    {
+      case ED_CONFIG_INFO_FILENAME:
+      {
+        gchar **device_file = va_arg( ap , gchar ** );
+        *device_file = g_key_file_get_string( keyfile , device_name , "filename" , &gerr );
+        if ( gerr != NULL )
+        {
+           g_clear_error( &gerr ); 
+           g_warning( _("event-device-plugin: configuration, unable to get filename value for device \"%s\""
+                      ", skipping it.\n") , device_name );
+           is_failed = TRUE;
+        }
+        else
+          temp_stringstore = g_list_append( temp_stringstore , device_file );
+        break;
+      }
+
+      case ED_CONFIG_INFO_PHYS:
+      {
+        gchar **device_phys = va_arg( ap , gchar ** );
+        *device_phys = g_key_file_get_string( keyfile , device_name , "phys" , &gerr );
+        if ( gerr != NULL )
+        {
+           g_clear_error( &gerr );
+           g_warning( _("event-device-plugin: configuration, unable to get phys value for device \"%s\""
+                      ", skipping it.\n") , device_name );
+           is_failed = TRUE;
+        }
+        else
+          temp_stringstore = g_list_append( temp_stringstore , device_phys );
+        break;
+      }
+
+      case ED_CONFIG_INFO_ISCUSTOM:
+      {
+        gint *is_custom = va_arg( ap , gint * );
+        *is_custom = g_key_file_get_integer( keyfile , device_name , "is_custom" , &gerr );
+        if ( gerr != NULL )
+        {
+           g_clear_error( &gerr ); 
+           g_warning( _("event-device-plugin: configuration, unable to get is_custom value for device \"%s\""
+                      ", skipping it.\n") , device_name );
+           is_failed = TRUE;
+        }
+        break;
+      }
+
+      case ED_CONFIG_INFO_ISACTIVE:
+      {
+        gboolean *is_active = va_arg( ap , gboolean * );
+        *is_active = g_key_file_get_boolean( keyfile , device_name , "is_active" , &gerr );
+        if ( gerr != NULL )
+        {
+           g_clear_error( &gerr ); 
+           g_warning( _("event-device-plugin: configuration, unable to get is_active value for device \"%s\""
+                      ", skipping it.\n") , device_name );
+           is_failed = TRUE;
+        }
+        break;
+      }
+
+      default:
+      {
+        /* unexpected value in info_code, skipping */
+        g_warning( _("event-device-plugin: configuration, unexpected value for device \"%s\""
+                   ", skipping it.\n") , device_name );
+        is_failed = TRUE;
+      }
+    }
+  }
+
+  va_end( ap );
+
+  if ( is_failed == FALSE )
+  {
+    /* temp_stringstore is not needed anymore,
+       do not change pointed containers */
+    g_list_free( temp_stringstore );
+    return 0;
+  }
+  else
+  {
+    /* temp_stringstore is not needed anymore,
+       nullify pointed containers and free content */
+    GList *list_iter = temp_stringstore;
+    while ( list_iter != NULL )
+    {
+      gchar **container = list_iter->data;
+      g_free( *container );
+      *container = NULL;
+      list_iter = g_list_next( list_iter );
+    }
+    g_list_free( temp_stringstore );
+    return -1;
+  }
+}
+
+
+/* this does just what its name says :) */
+static gpointer
+ed_util_get_bindings_from_keyfile( GKeyFile * keyfile , gchar * device_name )
+{
+  ed_inputevent_t *iev = g_malloc(sizeof(ed_inputevent_t));
+  gpointer bindings = ed_bindings_store_new();
+  gchar **keys;
+  gint j = 0;
+
+  /* now get bindings for this device */
+  keys = g_key_file_get_keys( keyfile , device_name , NULL , NULL );
+  while ( keys[j] != NULL )
+  {
+    /* in the config file, only bindings start with the 'b' character */
+    if ( keys[j][0] == 'b' )
+    {
+      gsize ilist_len = 0;
+      gint *ilist;
+      ilist = g_key_file_get_integer_list( keyfile ,
+                device_name ,  keys[j] , &ilist_len , NULL );
+      if ( ilist_len > 3 )
+      {
+        gint action_code = (gint)ilist[0];
+        iev->type = (guint)ilist[1];
+        iev->code = (guint)ilist[2];
+        iev->value = (gint)ilist[3];
+        ed_bindings_store_insert( bindings , iev , action_code );
+      }
+      g_free( ilist );
+    }
+    j++;
+  }
+
+  g_strfreev( keys );
+  g_free( iev );
+
+  if ( ed_bindings_store_size( bindings ) == 0 )
+  {
+    ed_bindings_store_delete( bindings );
+    bindings = NULL;
+  }
+
+  return bindings;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed_internals.h	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,66 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#ifndef _I_ED_INTERNALS_H
+#define _I_ED_INTERNALS_H 1
+
+#include "ed_types.h"
+#include "ed_common.h"
+#include <glib.h>
+
+#define ED_DEVCHECK_OK		0
+#define ED_DEVCHECK_ABSENT	1
+
+typedef enum
+{
+  ED_CONFIG_INFO_END = -1,
+  ED_CONFIG_INFO_FILENAME = 0,
+  ED_CONFIG_INFO_PHYS,
+  ED_CONFIG_INFO_ISCUSTOM,
+  ED_CONFIG_INFO_ISACTIVE
+}
+ed_config_info_t;
+
+ed_device_t * ed_device_new ( gchar * , gchar * , gchar * , gint );
+gint ed_device_delete ( ed_device_t * );
+
+ed_device_info_t * ed_device_info_new ( gchar * , gchar * , gchar * , gint );
+gint ed_device_info_delete ( ed_device_info_t * );
+
+gint ed_device_start_listening ( ed_device_t * );
+void ed_device_start_listening_from_config ( void );
+gint ed_device_stop_listening ( ed_device_t * );
+gint ed_device_stop_listening_from_info ( ed_device_info_t * );
+gint ed_device_stop_listening_all ( gboolean );
+gboolean ed_device_check_listening_from_info ( ed_device_info_t * );
+
+gboolean ed_inputevent_check_equality( ed_inputevent_t * , ed_inputevent_t * );
+gboolean ed_device_info_check_equality( ed_device_info_t * , ed_device_info_t * );
+
+/* these function return a GList of ed_device_info_t items */
+GList * ed_device_get_list_from_system ( void );
+GList * ed_device_get_list_from_config ( void );
+void ed_device_free_list ( GList * );
+
+gint ed_device_check ( GList * , gchar * , gchar ** , gchar ** );
+
+gint ed_config_save_from_list ( GList * );
+
+#endif /* !_I_ED_INTERNALS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed_types.h	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,62 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#ifndef _I_ED_TYPES_H
+#define _I_ED_TYPES_H 1
+
+#include "ed_common.h"
+#include <glib.h>
+
+typedef struct
+{
+  gchar * name;
+  gchar * filename;
+  gchar * phys;
+
+  gint reg;
+
+  gint is_custom;
+  gboolean is_active;
+
+  gpointer * bindings;
+}
+ed_device_info_t;
+
+typedef struct
+{
+  gint fd;
+
+  GIOChannel * iochan;
+  guint iochan_sid;
+  gboolean is_listening;
+
+  ed_device_info_t * info;
+}
+ed_device_t;
+
+typedef struct
+{
+  guint type;
+  guint code;
+  gint value;
+}
+ed_inputevent_t;
+
+#endif /* !_I_ED_TYPES_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed_ui.c	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,1527 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#include "ed_ui.h"
+#include "ed_types.h"
+#include "ed_internals.h"
+#include "ed_actions.h"
+#include "ed_bindings_store.h"
+#include "ed_common.h"
+#include <stdlib.h>
+#include <linux/input.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+
+static void cfg_ui_bindings_show ( ed_device_t * , GtkTreeRowReference * );
+
+extern ed_action_t player_actions[];
+extern gboolean plugin_is_active;
+
+static GtkWidget *cfg_win = NULL;
+
+enum
+{
+  DEVLIST_COL_ISACTIVE = 0,
+  DEVLIST_COL_NAME,
+  DEVLIST_COL_FILENAME,
+  DEVLIST_COL_PHYS,
+  DEVLIST_COL_ISAVAILABLE,
+  DEVLIST_COL_BINDINGS,
+  DEVLIST_NUMCOLS
+};
+
+enum
+{
+  DEVLIST_ISAVAILABLE_NOTDET = 0,
+  DEVLIST_ISAVAILABLE_DET,
+  DEVLIST_ISAVAILABLE_CUSTOM
+};
+
+
+
+static void
+cfg_device_lv_populate( GtkListStore * store )
+{
+  /* store is logically divided in three families of devices,
+     depending on the ISAVAILABLE value;
+     DEVLIST_ISAVAILABLE_NOTDET -> not detected devices: these are not-custom
+         devices that are present in configuration file but they do not seem
+         to be plugged at the moment, cause they're not in system_devices_list;
+     DEVLIST_ISAVAILABLE_DET -> detected devices: these are not-custom devices that
+         are currently plugged, no matter whether they are in configuration or not;
+     DEVLIST_ISAVAILABLE_CUSTOM -> custom devices: defined by user in configuration
+         file, these are not checked against system_devices_list;
+  */
+
+  GList *system_devices_list = NULL;
+  GList *config_devices_list = NULL;
+  GList *list_iter = NULL;
+
+  system_devices_list = ed_device_get_list_from_system();
+  config_devices_list = ed_device_get_list_from_config();
+
+  /* first, for each not-custom device in config_devices_list,
+     perform a search in system_devices_list */
+  list_iter = config_devices_list;
+  while ( list_iter != NULL )
+  {
+    ed_device_info_t *info_from_cfg = list_iter->data;
+    if ( info_from_cfg->is_custom == 0 ) /* only try to detect not-custom devices */
+    {
+      /* note: if device is found, filename and phys in info_from_cfg could be updated by the
+               check; the found device will be marked as "found" in system_devices_list too */
+      if ( ed_device_check( system_devices_list ,
+           info_from_cfg->name , &(info_from_cfg->filename) , &(info_from_cfg->phys) ) == ED_DEVCHECK_OK )
+      {
+        info_from_cfg->reg = 1; /* mark our device from config as "found" */
+      }
+    }
+    list_iter = g_list_next( list_iter );
+  }
+
+  /* at this point, we have three logical groups:
+     #1 - not-custom devices in config_devices_list, marked as "found" -> DEVLIST_ISAVAILABLE_DET
+     #2 - not-custom devices in config_devices_list, not-marked -> DEVLIST_ISAVAILABLE_NOTDET
+     #3 - custom devices in config_devices_list -> DEVLIST_ISAVAILABLE_CUSTOM
+     #1bis - plus, we pick not-marked devices from system_devices_list, which
+             are plugged devices that are not present in config_devices_list -> DEVLIST_ISAVAILABLE_DET
+  */
+
+  /* let's start to fill the store with items from group #1 (DEVLIST_ISAVAILABLE_DET) */
+  list_iter = config_devices_list;
+  while ( list_iter != NULL )
+  {
+    ed_device_info_t *info_from_cfg = list_iter->data;
+    if (( info_from_cfg->reg == 1 ) && ( info_from_cfg->is_custom == 0 ))
+    {
+      GtkTreeIter iter;
+      gtk_list_store_append( store , &iter );
+      gtk_list_store_set( store , &iter ,
+        DEVLIST_COL_ISACTIVE , info_from_cfg->is_active ,
+        DEVLIST_COL_NAME , info_from_cfg->name ,
+        DEVLIST_COL_FILENAME , info_from_cfg->filename ,
+        DEVLIST_COL_PHYS , info_from_cfg->phys ,
+        DEVLIST_COL_ISAVAILABLE , DEVLIST_ISAVAILABLE_DET ,
+        DEVLIST_COL_BINDINGS , info_from_cfg->bindings ,
+        -1 );
+    }
+    list_iter = g_list_next( list_iter );
+  }
+
+  /* add items from group #1bis (DEVLIST_ISAVAILABLE_DET) */
+  list_iter = system_devices_list;
+  while ( list_iter != NULL )
+  {
+    ed_device_info_t *info_from_sys = list_iter->data;
+    if ( info_from_sys->reg == 0 )
+    {
+      GtkTreeIter iter;
+      gtk_list_store_append( store , &iter );
+      gtk_list_store_set( store , &iter ,
+        DEVLIST_COL_ISACTIVE , info_from_sys->is_active ,
+        DEVLIST_COL_NAME , info_from_sys->name ,
+        DEVLIST_COL_FILENAME , info_from_sys->filename ,
+        DEVLIST_COL_PHYS , info_from_sys->phys ,
+        DEVLIST_COL_ISAVAILABLE , DEVLIST_ISAVAILABLE_DET ,
+        DEVLIST_COL_BINDINGS , info_from_sys->bindings ,
+        -1 );
+    }
+    list_iter = g_list_next( list_iter );
+  }
+
+  /* add items from group #2 (DEVLIST_ISAVAILABLE_NOTDET) */
+  list_iter = config_devices_list;
+  while ( list_iter != NULL )
+  {
+    ed_device_info_t *info_from_cfg = list_iter->data;
+    if (( info_from_cfg->reg == 0 ) && ( info_from_cfg->is_custom == 0 ))
+    {
+      GtkTreeIter iter;
+      gtk_list_store_append( store , &iter );
+      gtk_list_store_set( store , &iter ,
+        DEVLIST_COL_ISACTIVE , info_from_cfg->is_active ,
+        DEVLIST_COL_NAME , info_from_cfg->name ,
+        DEVLIST_COL_FILENAME , info_from_cfg->filename ,
+        DEVLIST_COL_PHYS , info_from_cfg->phys ,
+        DEVLIST_COL_ISAVAILABLE , DEVLIST_ISAVAILABLE_NOTDET ,
+        DEVLIST_COL_BINDINGS , info_from_cfg->bindings ,
+        -1 );
+    }
+    list_iter = g_list_next( list_iter );
+  }
+
+  /* add items from group #3 (DEVLIST_ISAVAILABLE_CUSTOM) */
+  list_iter = config_devices_list;
+  while ( list_iter != NULL )
+  {
+    ed_device_info_t *info_from_cfg = list_iter->data;
+    if ( info_from_cfg->is_custom == 1 )
+    {
+      GtkTreeIter iter;
+      gtk_list_store_append( store , &iter );
+      gtk_list_store_set( store , &iter ,
+        DEVLIST_COL_ISACTIVE , info_from_cfg->is_active ,
+        DEVLIST_COL_NAME , info_from_cfg->name ,
+        DEVLIST_COL_FILENAME , info_from_cfg->filename ,
+        DEVLIST_COL_PHYS , info_from_cfg->phys ,
+        DEVLIST_COL_ISAVAILABLE , DEVLIST_ISAVAILABLE_CUSTOM ,
+        DEVLIST_COL_BINDINGS , info_from_cfg->bindings ,
+        -1 );
+    }
+    list_iter = g_list_next( list_iter );
+  }
+
+  /* delete lists; bindings objects are not deleted,
+     they must be accessible from liststore for customization
+     and they'll be deleted when the config win is destroyed */
+  ed_device_free_list( config_devices_list );
+  ed_device_free_list( system_devices_list );
+  return;
+}
+
+
+static void
+cfg_device_lv_celldatafunc_isavailable( GtkTreeViewColumn * col , GtkCellRenderer * renderer ,
+                                        GtkTreeModel * model , GtkTreeIter * iter , gpointer data )
+{
+  guint is_available = 0;
+  gtk_tree_model_get( model , iter , DEVLIST_COL_ISAVAILABLE , &is_available , -1 );
+  switch (is_available)
+  {
+    case DEVLIST_ISAVAILABLE_DET:
+      g_object_set( renderer , "text" , "Detected" ,
+        "foreground" , "Green" , "foreground-set" , TRUE ,
+        "background" , "Black" , "background-set" , TRUE , NULL );
+      break;
+    case DEVLIST_ISAVAILABLE_CUSTOM:
+      g_object_set( renderer , "text" , "Custom" ,
+        "foreground" , "Yellow" , "foreground-set" , TRUE ,
+        "background" , "Black" , "background-set" , TRUE , NULL );
+      break;
+    case DEVLIST_ISAVAILABLE_NOTDET:
+    default:
+      g_object_set( renderer , "text" , "Not Detected" ,
+        "foreground" , "Orange" , "foreground-set" , TRUE ,
+        "background" , "Black" , "background-set" , TRUE , NULL );
+      break;
+  }
+  return;
+}
+
+
+static void
+cfg_device_lv_changetoggle ( GtkCellRendererToggle * toggle ,
+                             gchar * path_str , gpointer cfg_device_lv )
+{
+  GtkTreeModel *model = gtk_tree_view_get_model( GTK_TREE_VIEW(cfg_device_lv) );
+  GtkTreeIter iter;
+  GtkTreePath *path = gtk_tree_path_new_from_string( path_str );
+  gboolean toggled;
+
+  gtk_tree_model_get_iter( model , &iter , path );
+  gtk_tree_model_get( model , &iter , DEVLIST_COL_ISACTIVE , &toggled , -1 );
+
+  toggled ^= 1;
+  gtk_list_store_set( GTK_LIST_STORE(model), &iter , DEVLIST_COL_ISACTIVE , toggled , -1 );
+
+  gtk_tree_path_free( path );
+  return;
+}
+
+
+static void
+cfg_config_cb_bindings_show ( gpointer cfg_device_lv )
+{
+  GtkTreeSelection *sel;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  /* check if a row is selected */
+  sel = gtk_tree_view_get_selection( GTK_TREE_VIEW(cfg_device_lv) );
+  if ( gtk_tree_selection_get_selected( sel , &model , &iter ) == TRUE )
+  {
+    /* check availability and active status */
+    guint is_available = 0;
+    gboolean is_active = FALSE;
+
+    gtk_tree_model_get( model , &iter ,
+      DEVLIST_COL_ISACTIVE , &is_active ,
+      DEVLIST_COL_ISAVAILABLE , &is_available ,
+      -1 );
+
+    if ( is_available == DEVLIST_ISAVAILABLE_NOTDET )
+    {
+      /* do not open bindings window for a not-detected device */
+      ed_ui_message_show( _("Information") ,
+                          _("Cannot open bindings window for a not-detected device.\n"
+                            "Ensure that the device has been correctly plugged in.") ,
+                          cfg_win );
+      return;
+    }
+    else
+    {
+      /* ok, create a ed_device_t item from selected row information */
+      ed_device_t *dev;
+      gchar *device_name = NULL, *device_file = NULL, *device_phys = NULL;
+      gpointer bindings = NULL;
+
+      gtk_tree_model_get( model , &iter ,
+        DEVLIST_COL_NAME , &device_name ,
+        DEVLIST_COL_FILENAME , &device_file ,
+        DEVLIST_COL_PHYS , &device_phys ,
+        DEVLIST_COL_BINDINGS , &bindings ,
+        -1 );
+
+      dev = ed_device_new(
+               device_name , device_file , device_phys ,
+               ( is_available == DEVLIST_ISAVAILABLE_CUSTOM ? 1 : 0 ) );
+      g_free( device_name ); g_free( device_file ); g_free( device_phys );
+
+      if ( dev != NULL )
+      {
+        GtkTreePath *path;
+        GtkTreeRowReference *rowref;
+
+        dev->info->bindings = bindings;
+
+        path = gtk_tree_model_get_path( model , &iter );
+        rowref = gtk_tree_row_reference_new( model , path );
+        gtk_tree_path_free( path );
+        /* show bindings window for device "dev"; also pass to the bindings window
+           the GtkTreeRowReference (it keeps track of the model too) for selected row;
+           if changes are committed in the bindings window, the row reference will be
+           needed to update the treemodel with the new bindings information */
+        cfg_ui_bindings_show( dev , rowref );
+      }
+      else
+      {
+        /* something went wrong */
+        ed_ui_message_show( _("Error") ,
+                            _("Unable to open selected device.\n"
+                            "Please check read permissions on device file.") ,
+                            cfg_win );
+      }
+    }
+  }
+}
+
+
+static void
+cfg_config_cb_addcustom_show ( gpointer cfg_device_lv )
+{
+  GtkWidget *addc_dlg;
+  GtkWidget *addc_data_frame, *addc_data_vbox;
+  GtkWidget *addc_data_label;
+  GtkWidget *addc_data_table;
+  GtkWidget *addc_data_name_label, *addc_data_name_entry;
+  GtkWidget *addc_data_file_label, *addc_data_file_entry;
+  gint result;
+  gboolean task_done = FALSE;
+
+  addc_dlg = gtk_dialog_new_with_buttons( _("EvDev-Plug - Add custom device") ,
+               GTK_WINDOW(cfg_win) , GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT ,
+               GTK_STOCK_CANCEL , GTK_RESPONSE_REJECT , GTK_STOCK_OK , GTK_RESPONSE_ACCEPT , NULL );
+
+  addc_data_frame = gtk_frame_new( NULL );
+  gtk_container_add( GTK_CONTAINER(GTK_DIALOG(addc_dlg)->vbox) , addc_data_frame );
+  addc_data_vbox = gtk_vbox_new( FALSE , 5 );
+  gtk_container_set_border_width( GTK_CONTAINER(addc_data_vbox) , 5 );
+  gtk_container_add( GTK_CONTAINER(addc_data_frame) , addc_data_vbox );
+
+  addc_data_label = gtk_label_new( "" );
+  gtk_label_set_text( GTK_LABEL(addc_data_label) ,
+                      _("EvDev-Plug tries to automatically detect and update information about\n"
+                        "event devices available on the system.\n"
+                        "However, if auto-detect doesn't work for your system, or you have event\n"
+                        "devices in a non-standard location (currently they're only searched in\n"
+                        "/dev/input/ ), you may want to add a custom device, explicitly specifying\n"
+                        "name and device file.") );
+  gtk_box_pack_start( GTK_BOX(addc_data_vbox) , addc_data_label , FALSE , FALSE , 0 );
+
+  addc_data_name_label = gtk_label_new( _("Device name:") );
+  gtk_misc_set_alignment( GTK_MISC(addc_data_name_label) , 0 , 0.5 );
+  addc_data_name_entry = gtk_entry_new();
+
+  addc_data_file_label = gtk_label_new( _("Device file:") );
+  gtk_misc_set_alignment( GTK_MISC(addc_data_file_label) , 0 , 0.5 );
+  addc_data_file_entry = gtk_entry_new();
+
+  addc_data_table = gtk_table_new( 2 , 2 , FALSE );
+  gtk_table_set_col_spacings( GTK_TABLE(addc_data_table) , 2 );
+  gtk_table_set_row_spacings( GTK_TABLE(addc_data_table) , 2 );
+  gtk_table_attach( GTK_TABLE(addc_data_table) , addc_data_name_label , 0 , 1 , 0 , 1 ,
+                    GTK_FILL , GTK_EXPAND | GTK_FILL , 0 , 0 );
+  gtk_table_attach( GTK_TABLE(addc_data_table) , addc_data_name_entry , 1 , 2 , 0 , 1 ,
+                    GTK_EXPAND | GTK_FILL , GTK_EXPAND | GTK_FILL , 0 , 0 );
+  gtk_table_attach( GTK_TABLE(addc_data_table) , addc_data_file_label , 0 , 1 , 1 , 2 ,
+                    GTK_FILL , GTK_EXPAND | GTK_FILL , 0 , 0 );
+  gtk_table_attach( GTK_TABLE(addc_data_table) , addc_data_file_entry , 1 , 2 , 1 , 2 ,
+                    GTK_EXPAND | GTK_FILL , GTK_EXPAND | GTK_FILL , 0 , 0 );
+  gtk_box_pack_start( GTK_BOX(addc_data_vbox) , addc_data_table , TRUE , TRUE , 0 );
+
+  gtk_widget_show_all( addc_dlg );
+
+  while ( task_done == FALSE )
+  {
+    result = gtk_dialog_run( GTK_DIALOG(addc_dlg) );
+    if ( result == GTK_RESPONSE_ACCEPT )
+    {
+      const gchar *name = gtk_entry_get_text( GTK_ENTRY(addc_data_name_entry) );
+      const gchar *file = gtk_entry_get_text( GTK_ENTRY(addc_data_file_entry) );
+      if ( ( strcmp( name , "" ) != 0 ) &&
+           ( strcmp( name , "___plugin___" ) != 0 ) &&
+           ( strcmp( file , "" ) != 0 ) &&
+           ( file[0] == '/' ) )
+      {
+        GtkTreeModel *model = gtk_tree_view_get_model( GTK_TREE_VIEW(cfg_device_lv) );
+        GtkTreeIter iter;
+
+        gtk_list_store_append( GTK_LIST_STORE(model) , &iter );
+        gtk_list_store_set( GTK_LIST_STORE(model) , &iter ,
+          DEVLIST_COL_ISACTIVE , FALSE ,
+          DEVLIST_COL_NAME , name ,
+          DEVLIST_COL_FILENAME , file ,
+          DEVLIST_COL_PHYS , "(custom)" ,
+          DEVLIST_COL_ISAVAILABLE , DEVLIST_ISAVAILABLE_CUSTOM ,
+          DEVLIST_COL_BINDINGS , NULL , -1 );
+        task_done = TRUE;
+      }
+      else
+      {
+        ed_ui_message_show( _("Information") ,
+                            _("Please specify both name and filename.\n"
+                              "Filename must be specified with absolute path.") ,
+                            addc_dlg );
+      }
+    }
+    else
+      task_done = TRUE;
+  }
+
+  gtk_widget_destroy( addc_dlg );
+}
+
+
+static void
+cfg_config_cb_remove ( gpointer cfg_device_lv )
+{
+  GtkTreeSelection *sel;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  /* check if a row is selected */
+  sel = gtk_tree_view_get_selection( GTK_TREE_VIEW(cfg_device_lv) );
+  if ( gtk_tree_selection_get_selected( sel , &model , &iter ) == TRUE )
+  {
+    guint is_available = 0;
+    gtk_tree_model_get( model , &iter , DEVLIST_COL_ISAVAILABLE , &is_available , -1 );
+    switch ( is_available )
+    {
+      case DEVLIST_ISAVAILABLE_NOTDET:
+      {
+        /* if remove is called on a not-detected device,
+           it allows to remove existing configuration for it */
+        GtkWidget *yesno_dlg = gtk_message_dialog_new( GTK_WINDOW(cfg_win) ,
+          GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT , GTK_MESSAGE_QUESTION ,
+          GTK_BUTTONS_YES_NO ,
+          _("Do you want to remove the existing configuration for selected device?\n") );
+        if ( gtk_dialog_run( GTK_DIALOG(yesno_dlg) ) == GTK_RESPONSE_YES )
+        {
+          gpointer bindings = NULL;
+          gtk_tree_model_get( model , &iter , DEVLIST_COL_BINDINGS , &bindings , -1 );
+          if ( bindings != NULL ) ed_bindings_store_delete( bindings );
+          gtk_list_store_remove( GTK_LIST_STORE(model) , &iter );
+        }
+        gtk_widget_destroy( yesno_dlg );
+        break;
+      }
+
+      case DEVLIST_ISAVAILABLE_CUSTOM:
+      {
+        /* if remove is called on a custom device,
+           it allows to remove it along with its configuration */
+        GtkWidget *yesno_dlg = gtk_message_dialog_new( GTK_WINDOW(cfg_win) ,
+          GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT , GTK_MESSAGE_QUESTION ,
+          GTK_BUTTONS_YES_NO ,
+          _("Do you want to remove the selected custom device?\n") );
+        if ( gtk_dialog_run( GTK_DIALOG(yesno_dlg) ) == GTK_RESPONSE_YES )
+        {
+          gpointer bindings = NULL;
+          gtk_tree_model_get( model , &iter , DEVLIST_COL_BINDINGS , &bindings , -1 );
+          if ( bindings != NULL ) ed_bindings_store_delete( bindings );
+          gtk_list_store_remove( GTK_LIST_STORE(model) , &iter );
+        }
+        gtk_widget_destroy( yesno_dlg );
+        break;
+      }
+
+      case DEVLIST_ISAVAILABLE_DET:
+      default:
+      {
+        /* do nothing for detected devices */
+        break;
+      }
+    }
+  }
+}
+
+
+static gboolean
+cfg_config_cb_bindings_commit_foreach ( GtkTreeModel * model ,
+                                        GtkTreePath * path ,
+                                        GtkTreeIter * iter ,
+                                        gpointer config_device_list_p )
+{
+  gchar *device_name = NULL;
+  gchar *device_file = NULL;
+  gchar *device_phys = NULL;
+  gboolean is_active = FALSE;
+  guint is_available = 0;
+  gpointer bindings = NULL;
+  ed_device_info_t *info;
+  GList **config_device_list = config_device_list_p;
+
+  gtk_tree_model_get( model , iter ,
+    DEVLIST_COL_ISACTIVE , &is_active ,
+    DEVLIST_COL_NAME , &device_name ,
+    DEVLIST_COL_FILENAME , &device_file ,
+    DEVLIST_COL_PHYS , &device_phys ,
+    DEVLIST_COL_ISAVAILABLE , &is_available ,
+    DEVLIST_COL_BINDINGS , &bindings , -1 );
+
+  info = ed_device_info_new( device_name , device_file , device_phys ,
+           ( is_available == DEVLIST_ISAVAILABLE_CUSTOM ? 1 : 0 ) );
+  info->bindings = bindings;
+  info->is_active = is_active;
+
+  *config_device_list = g_list_append( *config_device_list , info );
+  return FALSE;
+}
+
+
+static gboolean
+cfg_config_cb_bindings_delbindings_foreach ( GtkTreeModel * model ,
+                                             GtkTreePath * path ,
+                                             GtkTreeIter * iter ,
+                                             gpointer data )
+{
+  gpointer bindings = NULL;
+  gtk_tree_model_get( model , iter , DEVLIST_COL_BINDINGS , &bindings , -1 );
+  if ( bindings != NULL )
+    ed_bindings_store_delete( bindings );
+  return FALSE;
+}
+
+
+static void
+cfg_config_cb_commit ( gpointer cfg_device_lv )
+{
+  GList *config_device_list = NULL;
+  GList *list_iter;
+  GtkTreeModel *model;
+
+  model = gtk_tree_view_get_model( GTK_TREE_VIEW(cfg_device_lv) );
+  /* fill config_device_list with information from the treeview */
+  gtk_tree_model_foreach( model , cfg_config_cb_bindings_commit_foreach , &config_device_list );
+  /* ok, now we have a list of ed_device_info_t objects */
+
+  /* commit changes in configuration file */
+  ed_config_save_from_list( config_device_list );
+
+  /* not needed anymore */
+  ed_device_free_list( config_device_list );
+
+  /* free bindings stored in the liststore */
+  gtk_tree_model_foreach( model , cfg_config_cb_bindings_delbindings_foreach , NULL );
+
+  if ( plugin_is_active == TRUE )
+  {
+    /* restart device listening according to the (possibly changed) config file */
+    ed_device_start_listening_from_config();
+  }
+
+  gtk_widget_destroy( cfg_win );
+}
+
+
+static void
+cfg_config_cb_cancel ( gpointer cfg_device_lv )
+{
+  GtkTreeModel *model;
+  model = gtk_tree_view_get_model( GTK_TREE_VIEW(cfg_device_lv) );
+
+  /* free bindings stored in the liststore */
+  gtk_tree_model_foreach( model , cfg_config_cb_bindings_delbindings_foreach , NULL );
+
+  if ( plugin_is_active == TRUE )
+  {
+    /* restart device listening according to the (unchanged) config file */
+    ed_device_start_listening_from_config();
+  }
+
+  gtk_widget_destroy( cfg_win );
+}
+
+
+void
+ed_ui_config_show( void )
+{
+  GtkWidget *cfg_vbox;
+  GtkWidget *cfg_device_lv, *cfg_device_lv_frame, *cfg_device_lv_sw;
+  GtkTreeViewColumn *cfg_device_lv_col_name, *cfg_device_lv_col_filename, *cfg_device_lv_col_phys;
+  GtkTreeViewColumn *cfg_device_lv_col_isactive, *cfg_device_lv_col_isavailable;
+  GtkCellRenderer *cfg_device_lv_rndr_text, *cfg_device_lv_rndr_toggle;
+  GtkCellRenderer *cfg_device_lv_rndr_textphys, *cfg_device_lv_rndr_isavailable;
+  GtkListStore *device_store;
+  GtkWidget *cfg_bbar_hbbox;
+  GtkWidget *cfg_bbar_bt_bind, *cfg_bbar_bt_addc, *cfg_bbar_bt_remc;
+  GtkWidget *cfg_bbar_bt_cancel, *cfg_bbar_bt_ok;
+  GdkGeometry cfg_win_hints;
+
+  if ( cfg_win != NULL )
+  {
+    gtk_window_present( GTK_WINDOW(cfg_win) );
+    return;
+  }
+
+  /* IMPORTANT: stop listening for all devices when config window is opened */
+  ed_device_stop_listening_all( TRUE );
+
+  cfg_win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
+  gtk_window_set_type_hint( GTK_WINDOW(cfg_win), GDK_WINDOW_TYPE_HINT_DIALOG );
+  gtk_window_set_position( GTK_WINDOW(cfg_win), GTK_WIN_POS_CENTER );
+  gtk_window_set_title( GTK_WINDOW(cfg_win), _("EvDev-Plug - Configuration") );
+  gtk_container_set_border_width( GTK_CONTAINER(cfg_win), 10 );
+  g_signal_connect( G_OBJECT(cfg_win) , "destroy" ,
+                    G_CALLBACK(gtk_widget_destroyed) , &cfg_win );
+  cfg_win_hints.min_width = -1;
+  cfg_win_hints.min_height = 300;
+  gtk_window_set_geometry_hints( GTK_WINDOW(cfg_win) , GTK_WIDGET(cfg_win) ,
+                                 &cfg_win_hints , GDK_HINT_MIN_SIZE );
+
+  cfg_vbox = gtk_vbox_new( FALSE , 0 );
+  gtk_container_add( GTK_CONTAINER(cfg_win) , cfg_vbox );
+
+  /* current liststore model
+     ----------------------------------------------
+     G_TYPE_BOOLEAN -> device listening on/off
+     G_TYPE_STRING -> device name
+     G_TYPE_STRING -> device filename
+     G_TYPE_STRING -> device physical port
+     G_TYPE_UINT -> device is available
+     G_TYPE_POINTER -> device bindings
+     ----------------------------------------------
+  */
+  device_store = gtk_list_store_new(
+    DEVLIST_NUMCOLS , G_TYPE_BOOLEAN , G_TYPE_STRING ,
+    G_TYPE_STRING , G_TYPE_STRING , G_TYPE_UINT , G_TYPE_POINTER );
+  cfg_device_lv_populate( device_store );
+
+  cfg_device_lv_frame = gtk_frame_new( NULL );
+  cfg_device_lv = gtk_tree_view_new_with_model( GTK_TREE_MODEL(device_store) );
+  g_object_unref( device_store );
+
+  cfg_device_lv_rndr_text = gtk_cell_renderer_text_new();
+  cfg_device_lv_rndr_toggle = gtk_cell_renderer_toggle_new();
+  cfg_device_lv_rndr_isavailable = gtk_cell_renderer_text_new();
+  cfg_device_lv_rndr_textphys = gtk_cell_renderer_text_new();
+  g_object_set( G_OBJECT(cfg_device_lv_rndr_textphys) ,
+                "ellipsize-set" , TRUE , "ellipsize" , PANGO_ELLIPSIZE_END , NULL );
+
+  cfg_device_lv_col_isactive = gtk_tree_view_column_new_with_attributes(
+    _("Active") , cfg_device_lv_rndr_toggle , "active" , DEVLIST_COL_ISACTIVE , NULL );
+  gtk_tree_view_column_set_expand( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_isactive) , FALSE );
+  gtk_tree_view_append_column( GTK_TREE_VIEW(cfg_device_lv), cfg_device_lv_col_isactive );
+  cfg_device_lv_col_isavailable = gtk_tree_view_column_new();
+  gtk_tree_view_column_set_title( cfg_device_lv_col_isavailable , _("Status") );
+  gtk_tree_view_column_pack_start( cfg_device_lv_col_isavailable , cfg_device_lv_rndr_isavailable , TRUE );
+  gtk_tree_view_column_set_cell_data_func(
+    cfg_device_lv_col_isavailable , cfg_device_lv_rndr_isavailable ,
+    (GtkTreeCellDataFunc)cfg_device_lv_celldatafunc_isavailable ,
+    NULL , NULL );
+  gtk_tree_view_column_set_expand( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_isavailable) , FALSE );
+  gtk_tree_view_append_column( GTK_TREE_VIEW(cfg_device_lv), cfg_device_lv_col_isavailable );
+  cfg_device_lv_col_name = gtk_tree_view_column_new_with_attributes(
+    _("Device Name") , cfg_device_lv_rndr_text , "text" , DEVLIST_COL_NAME , NULL );
+  gtk_tree_view_column_set_expand( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_name) , FALSE );
+  gtk_tree_view_append_column( GTK_TREE_VIEW(cfg_device_lv), cfg_device_lv_col_name );
+  cfg_device_lv_col_filename = gtk_tree_view_column_new_with_attributes(
+    _("Device File") , cfg_device_lv_rndr_text , "text" , DEVLIST_COL_FILENAME , NULL );
+  gtk_tree_view_column_set_expand( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_filename) , FALSE );
+  gtk_tree_view_append_column( GTK_TREE_VIEW(cfg_device_lv), cfg_device_lv_col_filename );
+  cfg_device_lv_col_phys = gtk_tree_view_column_new_with_attributes(
+    _("Device Address") , cfg_device_lv_rndr_textphys , "text" , DEVLIST_COL_PHYS , NULL );
+  gtk_tree_view_column_set_expand( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_phys) , TRUE );
+  gtk_tree_view_column_set_resizable( GTK_TREE_VIEW_COLUMN(cfg_device_lv_col_phys) , TRUE );
+  gtk_tree_view_append_column( GTK_TREE_VIEW(cfg_device_lv), cfg_device_lv_col_phys );
+  cfg_device_lv_sw = gtk_scrolled_window_new( NULL , NULL );
+  gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(cfg_device_lv_sw) ,
+                                  GTK_POLICY_NEVER , GTK_POLICY_ALWAYS );
+
+  gtk_container_add( GTK_CONTAINER(cfg_device_lv_sw) , cfg_device_lv );
+  gtk_container_add( GTK_CONTAINER(cfg_device_lv_frame) , cfg_device_lv_sw );
+  gtk_box_pack_start( GTK_BOX(cfg_vbox) , cfg_device_lv_frame , TRUE , TRUE , 0 );
+
+  gtk_box_pack_start( GTK_BOX(cfg_vbox) , gtk_hseparator_new() , FALSE , FALSE , 4 );
+
+  /* button bar */
+  cfg_bbar_hbbox = gtk_hbutton_box_new();
+  gtk_button_box_set_layout( GTK_BUTTON_BOX(cfg_bbar_hbbox) , GTK_BUTTONBOX_START );
+  cfg_bbar_bt_bind = gtk_button_new_with_mnemonic( _("_Bindings") );
+  gtk_button_set_image( GTK_BUTTON(cfg_bbar_bt_bind) ,
+    gtk_image_new_from_stock( GTK_STOCK_PREFERENCES , GTK_ICON_SIZE_BUTTON ) );
+  cfg_bbar_bt_addc = gtk_button_new_from_stock( GTK_STOCK_ADD );
+  cfg_bbar_bt_remc = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
+  /*cfg_bbar_bt_refresh = gtk_button_new_from_stock( GTK_STOCK_REFRESH );*/
+  cfg_bbar_bt_cancel = gtk_button_new_from_stock( GTK_STOCK_CANCEL );
+  cfg_bbar_bt_ok = gtk_button_new_from_stock( GTK_STOCK_OK );
+  gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_bind );
+  gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_addc );
+  gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_remc );
+  /*gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_refresh );*/
+  gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_cancel );
+  gtk_container_add( GTK_CONTAINER(cfg_bbar_hbbox) , cfg_bbar_bt_ok );
+  gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(cfg_bbar_hbbox) , cfg_bbar_bt_cancel , TRUE );
+  gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(cfg_bbar_hbbox) , cfg_bbar_bt_ok , TRUE );
+  gtk_box_pack_start( GTK_BOX(cfg_vbox) , cfg_bbar_hbbox , FALSE , FALSE , 0 );
+
+  g_signal_connect( cfg_device_lv_rndr_toggle , "toggled" ,
+                    G_CALLBACK(cfg_device_lv_changetoggle) , cfg_device_lv );
+  g_signal_connect_swapped( G_OBJECT(cfg_bbar_bt_bind) , "clicked" ,
+                            G_CALLBACK(cfg_config_cb_bindings_show) , cfg_device_lv );
+  g_signal_connect_swapped( G_OBJECT(cfg_bbar_bt_addc) , "clicked" ,
+                            G_CALLBACK(cfg_config_cb_addcustom_show) , cfg_device_lv );
+  g_signal_connect_swapped( G_OBJECT(cfg_bbar_bt_remc) , "clicked" ,
+                            G_CALLBACK(cfg_config_cb_remove) , cfg_device_lv );
+  g_signal_connect_swapped( G_OBJECT(cfg_bbar_bt_cancel) , "clicked" ,
+                            G_CALLBACK(cfg_config_cb_cancel) , cfg_device_lv );
+  g_signal_connect_swapped( G_OBJECT(cfg_bbar_bt_ok) , "clicked" ,
+                            G_CALLBACK(cfg_config_cb_commit) , cfg_device_lv );
+
+  gtk_widget_show_all( cfg_win );
+}
+
+
+
+/* bindings window */
+
+static void cfg_bindbox_new_empty_row( GtkTable * , GtkWidget * , gboolean );
+
+enum
+{
+  BINDLIST_COL_COMBO_ACTION = 0,
+  BINDLIST_COL_BT_ASSIGN,
+  BINDLIST_COL_LABEL_EVCODE,
+  BINDLIST_COL_BT_DELETE,
+  BINDLIST_NUMCOLS
+};
+
+
+static gboolean
+cfg_bindbox_assign_binding_timeout_func ( gpointer bindings_win )
+{
+  GtkWidget *trigger_dlg =  g_object_get_data( G_OBJECT(bindings_win) , "trigger-win" );
+  if ( trigger_dlg != NULL )
+    gtk_dialog_response( GTK_DIALOG(trigger_dlg) , GTK_RESPONSE_CANCEL );
+
+  return FALSE;
+}
+
+
+static gboolean
+cfg_bindbox_assign_binding_input_func ( GIOChannel * iochan ,
+                                        GIOCondition cond ,
+                                        gpointer bindings_win )
+{
+  switch ( cond )
+  {
+    case G_IO_IN:
+    {
+      gsize rb = 0;
+      GError *gerr = NULL;
+      struct input_event inputev;
+      g_io_channel_read_chars( iochan , (gchar*)&inputev ,
+                               sizeof(struct input_event) , &rb , NULL );
+      if ( rb == sizeof(struct input_event) )
+      {
+        GtkWidget *trigger_dlg = g_object_get_data( G_OBJECT(bindings_win) , "trigger-win" );
+        if ( trigger_dlg == NULL )
+        {
+          /* the trigger-dialog window is not open; ignore input */
+          return TRUE;
+        }
+        else
+        {
+          /* the trigger-dialog window is open; record input and
+             store it in a container managed by the trigger-dialog itself */
+          ed_inputevent_t *dinputev = g_malloc(sizeof(ed_inputevent_t*));
+          dinputev->type = inputev.type;
+          dinputev->code = inputev.code;
+          dinputev->value = inputev.value;
+          g_object_set_data( G_OBJECT(trigger_dlg) , "trigger-data" , dinputev );
+          gtk_dialog_response( GTK_DIALOG(trigger_dlg) , GTK_RESPONSE_OK );
+        }
+      }
+    }
+  }
+  return TRUE;
+}
+
+
+static gboolean
+cfg_bindbox_assign_binding_checkdups( GtkWidget * table , ed_inputevent_t * inputev )
+{
+  /* check if inputev is already assigned in table */
+  GList *children = GTK_TABLE(table)->children;
+  for ( children ; children != NULL ; children = g_list_next(children) )
+  {
+    GtkTableChild *child = children->data;
+
+    if ( child->top_attach + 1 == GTK_TABLE(table)->nrows )
+      continue; /* skip last empty row */
+
+    if ( child->left_attach == BINDLIST_COL_LABEL_EVCODE )
+    {
+      /* it's a label, pick its inputevent */
+      ed_inputevent_t * iev = g_object_get_data( G_OBJECT(child->widget) , "inputevent" );
+      if ( ( iev != NULL ) && ( ed_inputevent_check_equality( inputev , iev ) == TRUE ) )
+        return TRUE; /* a duplicate was found */
+    }
+  }
+  return FALSE;
+}
+
+
+static void
+cfg_bindbox_assign_binding ( GtkButton * assign_button , gpointer bindings_win )
+{
+  GtkWidget *trigger_dlg;
+  GtkWidget *trigger_dlg_frame;
+  GtkWidget *trigger_dlg_label;
+  guint timeout_sid = 0;
+  gint res = 0;
+
+  /* create a not-decorated window to inform the user about what's going on */
+  trigger_dlg = gtk_dialog_new();
+  gtk_widget_set_name( trigger_dlg , "trigger_dlg" );
+  trigger_dlg_label = gtk_label_new( _("Press a key of your device to bind it;\n"
+                                    "if no key is pressed in five seconds, this window\n"
+                                    "will close without binding changes.") );
+  gtk_widget_hide( GTK_WIDGET(GTK_DIALOG(trigger_dlg)->action_area) );
+  gtk_misc_set_padding( GTK_MISC(trigger_dlg_label) , 10 , 10 );
+  trigger_dlg_frame = gtk_frame_new( NULL );
+  gtk_container_add( GTK_CONTAINER(trigger_dlg_frame) , trigger_dlg_label );
+  gtk_container_add( GTK_CONTAINER(GTK_DIALOG(trigger_dlg)->vbox) , trigger_dlg_frame );
+  gtk_window_set_position( GTK_WINDOW(trigger_dlg) , GTK_WIN_POS_CENTER );
+  gtk_window_set_decorated( GTK_WINDOW(trigger_dlg) , FALSE );
+  gtk_window_set_transient_for( GTK_WINDOW(trigger_dlg) ,
+    GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(assign_button))) );
+  gtk_dialog_set_has_separator( GTK_DIALOG(trigger_dlg) , FALSE );
+  gtk_widget_show_all( trigger_dlg );
+
+  /* close the trigger-window if no input is received in 5 seconds */
+  timeout_sid = g_timeout_add( 5000 , cfg_bindbox_assign_binding_timeout_func , bindings_win );
+
+  g_object_set_data( G_OBJECT(bindings_win) , "trigger-win" , trigger_dlg ); /* enable trigger-listening */
+  res = gtk_dialog_run( GTK_DIALOG(trigger_dlg) );
+  g_object_set_data( G_OBJECT(bindings_win) , "trigger-win" , NULL ); /* stop trigger-listening */
+  switch ( res )
+  {
+    case GTK_RESPONSE_OK:
+    {
+      ed_inputevent_t *dinputev = g_object_get_data( G_OBJECT(trigger_dlg) , "trigger-data" );
+      GtkWidget *label = g_object_get_data( G_OBJECT(assign_button) , "label" );
+      GtkWidget *table = g_object_get_data( G_OBJECT(bindings_win) , "table" );
+      gchar *input_str;
+
+      /* we got a new input event; ensure that this has not been assigned to an action already */
+      if ( cfg_bindbox_assign_binding_checkdups( table , dinputev ) == TRUE )
+      {
+        /* this input event has been already used */
+        g_free( dinputev );
+        g_source_remove( timeout_sid );
+        ed_ui_message_show( _("Information") ,
+                            _("This input event has been already assigned.\n\n"
+                              "It's not possible to assign multiple actions to the same input event "
+                              "(although it's possible to assign the same action to multiple events).") ,
+                            bindings_win );
+      }
+      else
+      {
+        /* if this is the last row ("Assign" button) , create a new empty row */
+        if ( GPOINTER_TO_INT(g_object_get_data(G_OBJECT(assign_button),"last")) == 1 )
+        {
+          cfg_bindbox_new_empty_row( GTK_TABLE(table) , bindings_win , TRUE );
+          /* so, the current row is not the last one anymore;
+             remove its "last" mark and enable its delete button */
+          g_object_set_data( G_OBJECT(assign_button) , "last" , GINT_TO_POINTER(0) );
+          gtk_widget_set_sensitive( GTK_WIDGET(g_object_get_data(G_OBJECT(assign_button),"delbt")) , TRUE );
+        }
+
+        g_object_set_data_full( G_OBJECT(label) , "inputevent" , dinputev , g_free );
+        input_str = g_strdup_printf( "%i:%i:%i" , dinputev->type , dinputev->code , dinputev->value );
+        gtk_label_set_text( GTK_LABEL(label) , input_str );
+        g_free( input_str );
+        g_source_remove( timeout_sid );
+      }
+
+      break;
+    }
+
+    default:
+    {
+      break; /* nothing should be changed */
+    }
+  }
+
+  gtk_widget_destroy( trigger_dlg );
+}
+
+
+static void
+cfg_bindbox_delete_row( GtkButton * delete_button , gpointer bindings_win )
+{
+  GtkTable *oldtable, *newtable;
+  GtkWidget *table_sw, *table_vp;
+  GList *children = NULL;
+  gint drow = 0;
+
+  /* here comes the drawback of using GtkTable to display
+     a list-like set of widgets :) deletion can be a complex task */
+
+  /* first, get the row number of the row that should be deleted
+     (this can't be the last one cause it has the delete button disabled) */
+  oldtable = g_object_get_data( G_OBJECT(bindings_win) , "table" );
+  children = oldtable->children;
+  while ( children != NULL )
+  {
+    GtkTableChild *child = children->data;
+    if ( child->widget == (GtkWidget*)delete_button )
+      drow = child->top_attach;
+    children = g_list_next( children );
+  }
+
+  /* now, a simple trick: create a new GtkTable and move all
+     of the widgets there, excepting those positioned in row "drow" */
+  newtable = (GtkTable*)gtk_table_new( oldtable->nrows - 1 , oldtable->ncols , FALSE );
+
+  children = oldtable->children;
+  while ( children != NULL )
+  {
+    GtkTableChild *child = children->data;
+    if ( child->top_attach < drow )
+    {
+      GtkWidget *widget = child->widget;
+      guint widget_row = child->top_attach;
+      guint widget_col = child->left_attach;
+      g_object_ref( widget );
+      gtk_container_remove( GTK_CONTAINER(oldtable) , widget );
+      gtk_table_attach( GTK_TABLE(newtable) , widget ,
+        widget_col , widget_col + 1 , widget_row , widget_row + 1 ,
+        (widget_col == BINDLIST_COL_LABEL_EVCODE ? GTK_EXPAND | GTK_FILL : GTK_FILL ) , GTK_FILL , 0 , 0 );
+      /* gtk_container_remove() changed the structure used to iterate; restart */
+      children = oldtable->children;
+    }
+    else if ( child->top_attach > drow )
+    {
+      GtkWidget *widget = child->widget;
+      guint widget_row = child->top_attach;
+      guint widget_col = child->left_attach;
+      g_object_ref( widget );
+      gtk_container_remove( GTK_CONTAINER(oldtable) , widget );
+      gtk_table_attach( GTK_TABLE(newtable) , widget ,
+        widget_col , widget_col + 1 , widget_row - 1 , widget_row ,
+        (widget_col == BINDLIST_COL_LABEL_EVCODE ? GTK_EXPAND | GTK_FILL : GTK_FILL ) , GTK_FILL , 0 , 0 );
+      /* gtk_container_remove() changed the structure used to iterate; restart */
+      children = oldtable->children;
+    }
+    else
+      children = g_list_next(children);
+  }
+
+  /* now, replace the old GtkTable with the new one */
+  table_sw = g_object_get_data( G_OBJECT(bindings_win) , "tablesw" );
+  table_vp = gtk_bin_get_child( GTK_BIN(table_sw) ); /* get the viewport */
+  gtk_widget_destroy( GTK_WIDGET(oldtable) ); /* destroy old GtkTable */
+  gtk_container_add( GTK_CONTAINER(table_vp) , GTK_WIDGET(newtable) );
+  g_object_set_data( G_OBJECT(bindings_win) , "table" , newtable ); /* update table in bindings_win */
+  gtk_widget_show_all( GTK_WIDGET(newtable) );
+  return;
+}
+
+
+/* the three structure types defined below
+   are only used in cfg_bindbox_populate process */
+typedef struct
+{
+  GtkWidget *combobox;
+  gint action_code;
+}
+combosas_helper_t;
+
+typedef struct
+{
+  gint action_code;
+  ed_inputevent_t * inputev;
+}
+popul_binding_t;
+
+typedef struct
+{
+  gint id;
+  popul_binding_t *bindings;
+}
+popul_container_binding_t;
+
+static gboolean
+cfg_bindbox_populate_foreach_combosas ( GtkTreeModel * model ,
+                                        GtkTreePath * path ,
+                                        GtkTreeIter * iter ,
+                                        gpointer combosas_helper_gp )
+{
+  gint action_code = 0;
+  combosas_helper_t *combosas_helper = combosas_helper_gp;
+  gtk_tree_model_get( model , iter , 1 , &action_code , -1 );
+  if ( action_code == combosas_helper->action_code )
+  {
+    gtk_combo_box_set_active_iter( GTK_COMBO_BOX(combosas_helper->combobox) , iter );
+    return TRUE;
+  }
+  return FALSE;
+}
+
+static void
+cfg_bindbox_populate_foreach ( ed_inputevent_t * sinputev ,
+                               gint action_code ,
+                               gpointer popul_container_gp ,
+                               gpointer none )
+{
+  ed_inputevent_t *inputev;
+  popul_container_binding_t *popul_container = popul_container_gp;
+
+  /* make a copy of the ed_inputevent_t object "sinputev" */
+  inputev = g_malloc(sizeof(ed_inputevent_t));
+  inputev->type = sinputev->type;
+  inputev->code = sinputev->code;
+  inputev->value = sinputev->value;
+
+  popul_container->bindings[popul_container->id].inputev = inputev;
+  popul_container->bindings[popul_container->id].action_code = action_code;
+
+  popul_container->id++;
+  return;
+}
+
+static int
+cfg_bindbox_populate_qsortfunc( const void * p_binding_a , const void * p_binding_b )
+{
+  return ((popul_binding_t*)p_binding_a)->action_code - ((popul_binding_t*)p_binding_b)->action_code;
+}
+
+static void
+cfg_bindbox_populate( GtkWidget * bind_table , GtkWidget * bindings_win , gpointer bindings )
+{
+  if ( bindings != NULL )
+  {
+    guint bindings_num = ed_bindings_store_size( bindings );
+    popul_binding_t *popul_binding = (popul_binding_t*)calloc( bindings_num , sizeof(popul_binding_t) );
+    popul_container_binding_t *popul_container = g_malloc(sizeof(popul_container_binding_t));
+    gint i = 0;
+
+    popul_container->bindings = popul_binding;
+    popul_container->id = 0;
+
+    ed_bindings_store_foreach( bindings ,
+      (ed_bindings_store_foreach_func)cfg_bindbox_populate_foreach , popul_container , NULL );
+
+    /* ok, now we have a popul_binding array, reorder it using action_codes */
+    qsort( popul_binding , bindings_num , sizeof(popul_binding_t) , cfg_bindbox_populate_qsortfunc );
+
+    /* attach to table each popul_binding */
+    for ( i = 0 ; i < bindings_num ; i++ )
+    {
+      GList *children = GTK_TABLE(bind_table)->children;
+      for ( children ; children != NULL ; children = g_list_next(children) )
+      {
+        GtkTableChild *child = children->data;
+        if ( ( (child->top_attach + 1) == GTK_TABLE(bind_table)->nrows ) &&
+             ( child->left_attach == BINDLIST_COL_BT_ASSIGN ) &&
+             ( GPOINTER_TO_INT(g_object_get_data(G_OBJECT(child->widget),"last")) == 1 ) )
+        {
+          /* ok, this child->widget is the last assign button */
+          GtkWidget *combobox = g_object_get_data(G_OBJECT(child->widget),"combobox");
+          GtkWidget *label = g_object_get_data(G_OBJECT(child->widget),"label");
+          GtkWidget *delbt = g_object_get_data(G_OBJECT(child->widget),"delbt");
+          GtkTreeModel *combomodel;
+          GtkTreeIter comboiter;
+          combosas_helper_t *combosas_helper;
+          gchar *input_str;
+
+          combomodel = gtk_combo_box_get_model( GTK_COMBO_BOX(combobox) );
+          combosas_helper = g_malloc(sizeof(combosas_helper_t));
+          combosas_helper->combobox = combobox;
+          combosas_helper->action_code = popul_binding[i].action_code;
+          gtk_tree_model_foreach( combomodel , cfg_bindbox_populate_foreach_combosas , combosas_helper );
+          g_free( combosas_helper );
+
+          g_object_set_data_full( G_OBJECT(label) , "inputevent" , popul_binding[i].inputev , g_free );
+          input_str = g_strdup_printf( "%i:%i:%i" ,
+            popul_binding[i].inputev->type ,
+            popul_binding[i].inputev->code ,
+            popul_binding[i].inputev->value );
+          gtk_label_set_text( GTK_LABEL(label) , input_str );
+          g_free( input_str );
+
+          /* so, the current row wont' be the last one anymore;
+             remove its "last" mark and enable its delete button */
+          g_object_set_data( G_OBJECT(child->widget) , "last" , GINT_TO_POINTER(0) );
+          gtk_widget_set_sensitive( delbt , TRUE );
+          cfg_bindbox_new_empty_row( GTK_TABLE(bind_table) , bindings_win , TRUE );
+
+        }
+      }
+      DEBUGMSG( "populating bindings window: code %i -> event %d:%d:%d\n" ,
+        popul_binding[i].action_code , popul_binding[i].inputev->type,
+        popul_binding[i].inputev->code, popul_binding[i].inputev->value );
+    }
+    g_free( popul_binding );
+    g_free( popul_container );
+  }
+  return;
+}
+
+
+static void
+cfg_bindbox_new_empty_row( GtkTable * bind_table , GtkWidget * bindings_win , gboolean resize_table )
+{
+  GtkWidget *action_combobox;
+  GtkCellRenderer *action_combobox_rndr;
+  GtkWidget *assign_button;
+  GtkWidget *assign_label;
+  GtkWidget *delete_button;
+
+  /* add one row to the table */
+  if ( resize_table == TRUE )
+    gtk_table_resize( bind_table , bind_table->nrows + 1 , bind_table->ncols );
+
+  /* action combobox */
+  action_combobox = gtk_combo_box_new_with_model(
+   GTK_TREE_MODEL(g_object_get_data(G_OBJECT(bindings_win),"action_store")) );
+  action_combobox_rndr = gtk_cell_renderer_text_new();
+  gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(action_combobox) , action_combobox_rndr , TRUE );
+  gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(action_combobox) ,
+    action_combobox_rndr , "text" , 0 , NULL );
+  gtk_combo_box_set_active( GTK_COMBO_BOX(action_combobox) , 0 );
+
+  gtk_table_attach( bind_table , action_combobox ,
+    BINDLIST_COL_COMBO_ACTION , BINDLIST_COL_COMBO_ACTION + 1 ,
+    bind_table->nrows - 1 , bind_table->nrows ,
+    GTK_FILL , GTK_FILL , 0 , 0 );
+
+  /* assign button */
+  assign_button = gtk_button_new();
+  gtk_button_set_image( GTK_BUTTON(assign_button) ,
+                        gtk_image_new_from_stock( GTK_STOCK_EXECUTE , GTK_ICON_SIZE_BUTTON ) );
+  g_object_set_data( G_OBJECT(assign_button) , "last" , GINT_TO_POINTER(1) );
+  g_signal_connect( G_OBJECT(assign_button) , "clicked" ,
+                    G_CALLBACK(cfg_bindbox_assign_binding) , bindings_win );
+
+  gtk_table_attach( bind_table , assign_button ,
+    BINDLIST_COL_BT_ASSIGN , BINDLIST_COL_BT_ASSIGN + 1 ,
+    bind_table->nrows - 1 , bind_table->nrows ,
+    GTK_FILL , GTK_FILL , 0 , 0 );
+
+  /* assigned-code label */
+  assign_label = gtk_label_new( "" );
+  gtk_misc_set_alignment( GTK_MISC(assign_label) , 0 , 0.5 );
+  gtk_misc_set_padding( GTK_MISC(assign_label) , 10 , 0 );
+  g_object_set_data_full( G_OBJECT(assign_label) , "inputevent" , NULL , g_free );
+
+  gtk_table_attach( bind_table , assign_label ,
+    BINDLIST_COL_LABEL_EVCODE , BINDLIST_COL_LABEL_EVCODE +1 ,
+    bind_table->nrows - 1 , bind_table->nrows ,
+    GTK_EXPAND | GTK_FILL , GTK_FILL , 0 , 0 );
+
+  /* delete button */
+  delete_button = gtk_button_new();
+  gtk_button_set_image( GTK_BUTTON(delete_button) ,
+                        gtk_image_new_from_stock( GTK_STOCK_DELETE , GTK_ICON_SIZE_BUTTON ) );
+  g_signal_connect( G_OBJECT(delete_button) , "clicked" ,
+                    G_CALLBACK(cfg_bindbox_delete_row) , bindings_win );
+  gtk_widget_set_sensitive( delete_button , FALSE );
+
+  gtk_table_attach( bind_table , delete_button ,
+    BINDLIST_COL_BT_DELETE , BINDLIST_COL_BT_DELETE + 1 ,
+    bind_table->nrows - 1 , bind_table->nrows ,
+    GTK_FILL , GTK_FILL , 0 , 0 );
+
+  /* data for assign button */
+  g_object_set_data( G_OBJECT(assign_button) , "combobox" , action_combobox );
+  g_object_set_data( G_OBJECT(assign_button) , "label" , assign_label );
+  g_object_set_data( G_OBJECT(assign_button) , "delbt" , delete_button );
+
+  gtk_widget_show_all( GTK_WIDGET(bind_table) );
+  return;
+}
+
+
+static void
+cfg_bindings_cb_destroy ( GtkObject * bindings_win , gpointer dev_gp )
+{
+  ed_device_t *dev = dev_gp;
+  /* bindings window is going to be destroyed;
+     deactivate device listening and free the ed_device_t object */
+  g_source_remove( dev->iochan_sid );
+  ed_device_delete( dev );
+
+  /* the rowreference is no longer needed as well */
+  gtk_tree_row_reference_free(
+    (GtkTreeRowReference*)g_object_get_data(G_OBJECT(bindings_win),"rowref") );
+  return;
+}
+
+
+static void
+cfg_bindings_cb_commit ( gpointer bindings_win )
+{
+  GtkTreeRowReference *rowref = g_object_get_data(G_OBJECT(bindings_win),"rowref");
+  if ( gtk_tree_row_reference_valid( rowref ) == TRUE )
+  {
+    GtkTreeModel *model = gtk_tree_row_reference_get_model( rowref );
+    GtkTreePath *path = gtk_tree_row_reference_get_path( rowref );
+    GtkTreeIter iter;
+    GtkTable *table;
+    gpointer new_bindings = NULL, old_bindings = NULL;
+
+    /****************************************************************************/
+    table = g_object_get_data( G_OBJECT(bindings_win) , "table" );
+    if ( table->nrows > 1 )
+    {
+      /* bindings defined */
+      GList *children = table->children;
+      gint *array_actioncode;
+      ed_inputevent_t **array_inputevent;
+      gint i = 0;
+
+      array_actioncode = calloc( table->nrows - 1 , sizeof(gint) );
+      array_inputevent = calloc( table->nrows - 1 , sizeof(ed_inputevent_t*) );
+
+      for ( children ; children != NULL ; children = g_list_next( children ) )
+      {
+        /* pick information from relevant table cells and put them in arrays */
+        GtkTableChild *child = children->data;
+
+        if ( ( child->top_attach + 1 ) == table->nrows )
+          continue; /* skip last empty row */
+
+        if ( child->left_attach == BINDLIST_COL_COMBO_ACTION )
+        {
+          GtkTreeModel *combomodel = gtk_combo_box_get_model( GTK_COMBO_BOX(child->widget) );
+          GtkTreeIter comboiter;
+          gint actioncode = 0;
+
+          gtk_combo_box_get_active_iter( GTK_COMBO_BOX(child->widget) , &comboiter );
+          gtk_tree_model_get( combomodel , &comboiter , 1 , &actioncode , -1 ); /* pick the action code */
+          array_actioncode[child->top_attach] = actioncode;
+        }
+        else if ( child->left_attach == BINDLIST_COL_LABEL_EVCODE )
+        {
+          ed_inputevent_t *inputevent = g_object_get_data( G_OBJECT(child->widget) , "inputevent" );
+          array_inputevent[child->top_attach] = inputevent;
+        }
+      }
+
+      /* ok, now copy data from arrays to the bindings store */
+      new_bindings = ed_bindings_store_new(); /* new bindings store object */
+
+      for ( i = 0 ; i < ( table->nrows - 1 ) ; i++ )
+        ed_bindings_store_insert( new_bindings , array_inputevent[i] , array_actioncode[i] );
+
+      g_free( array_actioncode ); g_free( array_inputevent ); /* not needed anymore */
+    }
+    else
+    {
+      /* no bindings defined */
+      new_bindings = NULL;
+    }
+    /****************************************************************************/
+
+    /* pick old bindings store and delete it */
+    gtk_tree_model_get_iter( model , &iter , path );
+    gtk_tree_model_get( model , &iter, DEVLIST_COL_BINDINGS , &old_bindings , -1 );
+    if ( old_bindings != NULL )
+      ed_bindings_store_delete( old_bindings );
+    /* replace it with new one */
+    gtk_list_store_set( GTK_LIST_STORE(model) , &iter , DEVLIST_COL_BINDINGS , new_bindings , -1 );
+    /* everything done! */
+  }
+  gtk_widget_destroy( GTK_WIDGET(bindings_win) );
+}
+
+
+#define action_store_add(x) { gtk_list_store_append(action_store,&iter); gtk_list_store_set(action_store,&iter,0,player_actions[x].desc,1,x,-1); }
+
+static void
+cfg_ui_bindings_show ( ed_device_t * dev , GtkTreeRowReference * rowref )
+{
+  GtkWidget *bindings_win;
+  GdkGeometry bindings_win_hints;
+  GtkWidget *bindings_vbox;
+  GtkWidget *bindings_info_table, *bindings_info_frame;
+  GtkWidget *bindings_info_label_name_p, *bindings_info_label_name_c;
+  GtkWidget *bindings_info_label_file_p, *bindings_info_label_file_c;
+  GtkWidget *bindings_info_label_phys_p, *bindings_info_label_phys_c;
+  GtkWidget *bindings_bind_frame, *bindings_bind_table, *bindings_bind_table_sw;
+  GtkWidget *bindings_bbar_hbbox, *bindings_bbar_bt_cancel, *bindings_bbar_bt_ok;
+  GtkListStore *action_store;
+  GtkTreeIter iter;
+  static gboolean style_added = FALSE;
+
+  if ( !style_added )
+  {
+    /* this is used by trigger dialogs, calling it once is enough */
+    gtk_rc_parse_string( "style \"noaaborder\" { GtkDialog::action-area-border = 0 }\n"
+                         "widget \"trigger_dlg\" style \"noaaborder\"\n" );
+    style_added = TRUE;
+  }
+
+  bindings_win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
+  gtk_window_set_type_hint( GTK_WINDOW(bindings_win), GDK_WINDOW_TYPE_HINT_DIALOG );
+  gtk_window_set_position( GTK_WINDOW(bindings_win), GTK_WIN_POS_CENTER );
+  gtk_window_set_transient_for( GTK_WINDOW(bindings_win), GTK_WINDOW(cfg_win) );
+  gtk_window_set_modal( GTK_WINDOW(bindings_win) , TRUE );
+  gtk_window_set_title( GTK_WINDOW(bindings_win), _("EvDev-Plug - Bindings Configuration") );
+  gtk_container_set_border_width( GTK_CONTAINER(bindings_win), 10 );
+  bindings_win_hints.min_width = 400;
+  bindings_win_hints.min_height = 400;
+  gtk_window_set_geometry_hints( GTK_WINDOW(bindings_win) , GTK_WIDGET(bindings_win) ,
+                                 &bindings_win_hints , GDK_HINT_MIN_SIZE );
+
+  g_object_set_data( G_OBJECT(bindings_win) , "rowref" , rowref );
+  g_object_set_data( G_OBJECT(bindings_win) , "trigger-win" , NULL );
+
+  bindings_vbox = gtk_vbox_new( FALSE , 4 );
+  gtk_container_add( GTK_CONTAINER(bindings_win) , bindings_vbox );
+
+  /* action combobox model
+     column 0 -> action desc
+     column 1 -> action code */
+  action_store = gtk_list_store_new( 2 , G_TYPE_STRING , G_TYPE_INT );
+  action_store_add( ED_ACTION_PB_PLAY );
+  action_store_add( ED_ACTION_PB_STOP );
+  action_store_add( ED_ACTION_PB_PAUSE );
+  action_store_add( ED_ACTION_PB_PREV );
+  action_store_add( ED_ACTION_PB_NEXT );
+  action_store_add( ED_ACTION_PB_EJECT );
+  action_store_add( ED_ACTION_PL_REPEAT );
+  action_store_add( ED_ACTION_PL_SHUFFLE );
+  action_store_add( ED_ACTION_VOL_UP5 );
+  action_store_add( ED_ACTION_VOL_DOWN5 );
+  action_store_add( ED_ACTION_VOL_UP10 );
+  action_store_add( ED_ACTION_VOL_DOWN10 );
+  action_store_add( ED_ACTION_VOL_UP5 );
+  action_store_add( ED_ACTION_WIN_MAIN );
+  action_store_add( ED_ACTION_WIN_PLAYLIST );
+  action_store_add( ED_ACTION_WIN_EQUALIZER );
+  g_object_set_data_full( G_OBJECT(bindings_win) , "action_store" , action_store , g_object_unref );
+
+  /* info table */
+  bindings_info_table = gtk_table_new( 3 , 2 , FALSE );
+  gtk_container_set_border_width( GTK_CONTAINER(bindings_info_table), 4 );
+  bindings_info_label_name_p = gtk_label_new( "" );
+  gtk_label_set_markup( GTK_LABEL(bindings_info_label_name_p) , _("<b>Name: </b>") );
+  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_name_p) , 0 , 0.5 );
+  bindings_info_label_name_c = gtk_label_new( dev->info->name );
+  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_name_c) , 0 , 0.5 );
+  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_name_p , 0 , 1 , 0 , 1 ,
+                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
+  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_name_c , 1 , 2 , 0 , 1 ,
+                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
+  bindings_info_label_file_p = gtk_label_new( "" );
+  gtk_label_set_markup( GTK_LABEL(bindings_info_label_file_p) , _("<b>Filename: </b>") );
+  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_file_p) , 0 , 0.5 );
+  bindings_info_label_file_c = gtk_label_new( dev->info->filename );
+  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_file_c) , 0 , 0.5 );
+  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_file_p , 0 , 1 , 1 , 2 ,
+                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
+  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_file_c , 1 , 2 , 1 , 2 ,
+                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
+  bindings_info_label_phys_p = gtk_label_new( "" );
+  gtk_label_set_markup( GTK_LABEL(bindings_info_label_phys_p) , _("<b>Phys.Address: </b>") );
+  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_phys_p) , 0 , 0.5 );
+  bindings_info_label_phys_c = gtk_label_new( dev->info->phys );
+  gtk_misc_set_alignment( GTK_MISC(bindings_info_label_phys_c) , 0 , 0.5 );
+  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_phys_p , 0 , 1 , 2 , 3 ,
+                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
+  gtk_table_attach( GTK_TABLE(bindings_info_table) , bindings_info_label_phys_c , 1 , 2 , 2 , 3 ,
+                    GTK_FILL , GTK_FILL | GTK_EXPAND , 0 , 0 );
+  bindings_info_frame = gtk_frame_new( NULL );
+  gtk_container_add( GTK_CONTAINER(bindings_info_frame) , bindings_info_table );
+  gtk_box_pack_start( GTK_BOX(bindings_vbox) , bindings_info_frame , FALSE , FALSE , 0 );
+
+  /* bindings boxlist */
+  bindings_bind_table = gtk_table_new( 1 , BINDLIST_NUMCOLS , FALSE );
+  cfg_bindbox_new_empty_row( GTK_TABLE(bindings_bind_table) , bindings_win , FALSE );
+  cfg_bindbox_populate( bindings_bind_table , bindings_win , dev->info->bindings );
+  bindings_bind_table_sw = gtk_scrolled_window_new( NULL , NULL );
+  gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(bindings_bind_table_sw) ,
+                                  GTK_POLICY_NEVER , GTK_POLICY_ALWAYS );
+  bindings_bind_frame = gtk_frame_new( NULL );
+  gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(bindings_bind_table_sw) ,
+                                         bindings_bind_table );
+  gtk_container_add( GTK_CONTAINER(bindings_bind_frame) , bindings_bind_table_sw );
+  gtk_box_pack_start( GTK_BOX(bindings_vbox) , bindings_bind_frame , TRUE , TRUE , 0 );
+  g_object_set_data( G_OBJECT(bindings_win) , "table" , bindings_bind_table );
+  g_object_set_data( G_OBJECT(bindings_win) , "tablesw" , bindings_bind_table_sw );
+
+  gtk_box_pack_start( GTK_BOX(bindings_vbox) , gtk_hseparator_new() , FALSE , FALSE , 0 );
+
+  /* button bar */
+  bindings_bbar_hbbox = gtk_hbutton_box_new();
+  gtk_button_box_set_layout( GTK_BUTTON_BOX(bindings_bbar_hbbox) , GTK_BUTTONBOX_START );
+  bindings_bbar_bt_cancel = gtk_button_new_from_stock( GTK_STOCK_CANCEL );
+  bindings_bbar_bt_ok = gtk_button_new_from_stock( GTK_STOCK_OK );
+  gtk_container_add( GTK_CONTAINER(bindings_bbar_hbbox) , bindings_bbar_bt_cancel );
+  gtk_container_add( GTK_CONTAINER(bindings_bbar_hbbox) , bindings_bbar_bt_ok );
+  gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(bindings_bbar_hbbox) , bindings_bbar_bt_cancel , TRUE );
+  gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(bindings_bbar_hbbox) , bindings_bbar_bt_ok , TRUE );
+  gtk_box_pack_start( GTK_BOX(bindings_vbox) , bindings_bbar_hbbox , FALSE , FALSE , 0 );
+
+  /* activate device listening */
+  dev->iochan_sid = g_io_add_watch( dev->iochan , G_IO_IN ,
+    (GIOFunc)cfg_bindbox_assign_binding_input_func , bindings_win );
+
+  /* signals */
+  g_signal_connect( G_OBJECT(bindings_win) , "destroy" ,
+                    G_CALLBACK(cfg_bindings_cb_destroy) , dev );
+  g_signal_connect_swapped( G_OBJECT(bindings_bbar_bt_cancel) , "clicked" ,
+                            G_CALLBACK(gtk_widget_destroy) , bindings_win );
+  g_signal_connect_swapped( G_OBJECT(bindings_bbar_bt_ok) , "clicked" ,
+                            G_CALLBACK(cfg_bindings_cb_commit) , bindings_win );
+
+  gtk_widget_show_all( bindings_win );
+}
+
+
+
+/* about box */
+void
+ed_ui_about_show( void )
+{
+  static GtkWidget *about_win = NULL;
+  GtkWidget *about_vbox;
+  GtkWidget *logoandinfo_vbox;
+  GtkWidget *info_tv, *info_tv_sw, *info_tv_frame;
+  GtkWidget *bbar_bbox, *bbar_bt_ok;
+  GtkTextBuffer *info_tb;
+  GdkGeometry abount_win_hints;
+
+  if ( about_win != NULL )
+  {
+    gtk_window_present( GTK_WINDOW(about_win) );
+    return;
+  }
+
+  about_win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
+  gtk_window_set_type_hint( GTK_WINDOW(about_win), GDK_WINDOW_TYPE_HINT_DIALOG );
+  gtk_window_set_position( GTK_WINDOW(about_win), GTK_WIN_POS_CENTER );
+  gtk_window_set_title( GTK_WINDOW(about_win), _("EvDev-Plug - about") );
+  abount_win_hints.min_width = 420;
+  abount_win_hints.min_height = 200;
+  gtk_window_set_geometry_hints( GTK_WINDOW(about_win) , GTK_WIDGET(about_win) ,
+                                 &abount_win_hints , GDK_HINT_MIN_SIZE );
+  /* gtk_window_set_resizable( GTK_WINDOW(about_win) , FALSE ); */
+  gtk_container_set_border_width( GTK_CONTAINER(about_win) , 10 );
+  g_signal_connect( G_OBJECT(about_win) , "destroy" , G_CALLBACK(gtk_widget_destroyed) , &about_win );
+
+  about_vbox = gtk_vbox_new( FALSE , 0 );
+  gtk_container_add( GTK_CONTAINER(about_win) , about_vbox );
+
+  logoandinfo_vbox = gtk_vbox_new( TRUE , 2 );
+
+  /* TODO make a logo or make someone do it! :)
+  logo_pixbuf = gdk_pixbuf_new_from_xpm_data( (const gchar **)evdev_plug_logo_xpm );
+  logo_image = gtk_image_new_from_pixbuf( logo_pixbuf );
+  g_object_unref( logo_pixbuf );
+
+  logo_frame = gtk_frame_new( NULL );
+  gtk_container_add( GTK_CONTAINER(logo_frame) , logo_image );
+  gtk_box_pack_start( GTK_BOX(logoandinfo_vbox) , logo_frame , TRUE , TRUE , 0 );  */
+
+  info_tv = gtk_text_view_new();
+  info_tb = gtk_text_view_get_buffer( GTK_TEXT_VIEW(info_tv) );
+  gtk_text_view_set_editable( GTK_TEXT_VIEW(info_tv) , FALSE );
+  gtk_text_view_set_cursor_visible( GTK_TEXT_VIEW(info_tv) , FALSE );
+  gtk_text_view_set_justification( GTK_TEXT_VIEW(info_tv) , GTK_JUSTIFY_LEFT );
+  gtk_text_view_set_left_margin( GTK_TEXT_VIEW(info_tv) , 10 );
+
+  gtk_text_buffer_set_text( info_tb ,
+                            _("\nEvDev-Plug " ED_VERSION_PLUGIN
+                              "\nplayer remote control via event devices\n"
+                              "http://www.develia.org/projects.php?p=evdevplug\n\n"
+                              "written by Giacomo Lozito\n"
+                              "< james@develia.org >\n\n") , -1 );
+
+  info_tv_sw = gtk_scrolled_window_new( NULL , NULL );
+  gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(info_tv_sw) ,
+                                  GTK_POLICY_NEVER , GTK_POLICY_ALWAYS );
+  gtk_container_add( GTK_CONTAINER(info_tv_sw) , info_tv );
+  info_tv_frame = gtk_frame_new( NULL );
+  gtk_container_add( GTK_CONTAINER(info_tv_frame) , info_tv_sw );
+  gtk_box_pack_start( GTK_BOX(logoandinfo_vbox) , info_tv_frame , TRUE , TRUE , 0 );
+
+  gtk_box_pack_start( GTK_BOX(about_vbox) , logoandinfo_vbox , TRUE , TRUE , 0 );
+
+  /* horizontal separator and buttons */
+  gtk_box_pack_start( GTK_BOX(about_vbox) , gtk_hseparator_new() , FALSE , FALSE , 4 );
+  bbar_bbox = gtk_hbutton_box_new();
+  gtk_button_box_set_layout( GTK_BUTTON_BOX(bbar_bbox) , GTK_BUTTONBOX_END );
+  bbar_bt_ok = gtk_button_new_from_stock( GTK_STOCK_OK );
+  g_signal_connect_swapped( G_OBJECT(bbar_bt_ok) , "clicked" ,
+                            G_CALLBACK(gtk_widget_destroy) , about_win );
+  gtk_container_add( GTK_CONTAINER(bbar_bbox) , bbar_bt_ok );
+  gtk_box_pack_start( GTK_BOX(about_vbox) , bbar_bbox , FALSE , FALSE , 0 );
+
+  gtk_widget_show_all( about_win );
+}
+
+
+
+/* message box */
+void
+ed_ui_message_show ( gchar * title , gchar * message , gpointer parent_win_gp )
+{
+  GtkWidget *message_win;
+  GtkWindow *parent_win = NULL;
+
+  if (( parent_win_gp != NULL ) && ( GTK_WIDGET_TOPLEVEL(GTK_WIDGET(parent_win_gp)) ))
+    parent_win = GTK_WINDOW(parent_win_gp);
+
+  message_win = gtk_message_dialog_new(
+                  parent_win ,
+                  ( parent_win != NULL ? GTK_DIALOG_DESTROY_WITH_PARENT : 0 ) ,
+                  GTK_MESSAGE_INFO , GTK_BUTTONS_CLOSE , message );
+  gtk_window_set_title( GTK_WINDOW(message_win) , title );
+
+  gtk_dialog_run( GTK_DIALOG(message_win) );
+  gtk_widget_destroy( message_win );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/evdev-plug/ed_ui.h	Sun Jan 14 17:55:24 2007 -0800
@@ -0,0 +1,31 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#ifndef _I_ED_UI_H
+#define _I_ED_UI_H 1
+
+#include "ed_common.h"
+#include <glib.h>
+
+void ed_ui_config_show( void );
+void ed_ui_about_show( void );
+void ed_ui_message_show ( gchar * , gchar * , gpointer );
+
+#endif /* !_I_ED_UI_H */