Mercurial > audlegacy-plugins
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 */