From bd8d77dd8a6fbbc2a8925a2210917bfe0e2d6849 Mon Sep 17 00:00:00 2001 From: "N.N." Date: Tue, 20 Jun 2006 17:20:32 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r5269, which included commits to RCS files with non-trunk default branches. svn path=/trunk/externals/input_noticer/; revision=5270 --- INSTALL.TXT | 27 ++++ Makefile | 33 +++++ README.TXT | 51 +++++++ input_noticer.c | 371 ++++++++++++++++++++++++++++++++++++++++++++++++ input_noticer.h | 58 ++++++++ noticer_test.pd | 12 ++ test_noticer.c | 147 +++++++++++++++++++ test_noticer_compile.sh | 1 + 8 files changed, 700 insertions(+) create mode 100644 INSTALL.TXT create mode 100644 Makefile create mode 100644 README.TXT create mode 100644 input_noticer.c create mode 100644 input_noticer.h create mode 100644 noticer_test.pd create mode 100644 test_noticer.c create mode 100644 test_noticer_compile.sh diff --git a/INSTALL.TXT b/INSTALL.TXT new file mode 100644 index 0000000..f7eefed --- /dev/null +++ b/INSTALL.TXT @@ -0,0 +1,27 @@ +input_noticer install instructions +David Merrill + +In order to make this code compile, I had to install the following libraries +onto my system: + + libhal-dev + libglib2.0-dev + libdbus-glib-1-dev + +In a debian-based system, they can be installed with apt-get, as in: + sudo apt-get install libhal-dev + +To see if you have the right libraries installed in order to compile, you can +try compiling the test_noticer.c file, by running the test_noticer_compile.sh +shell script, as follows: + +source test_noticer_compile.sh + +Then, run test_noticer, and when it's running you should see messages when +you plug, or un-plug a joystick. + +Once you've got your libraries sorted out, to compile and install, you should +first modify the Makefile to suit your system. Then you can type: + +make +make install_noticer diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..12401f6 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +CC=gcc + +pd_linux: input_noticer.pd_linux + +clean: ; rm -f *.pd_linux *.o *~ + +# installs all compiled externals. +# you'll want to change this to match where your externals live +install: ; sudo cp *.pd_linux /usr/lib/pd/extra + +# installs the input_noticer +# you'll want to change this to match where your externals live +install_noticer: ; cp input_noticer.pd_linux /usr/lib/pd/extra + +# ----------------------- LINUX i386 ----------------------- + +.SUFFIXES: .pd_linux + +LINUXLDFLAGS = `pkg-config --libs glib-2.0 hal dbus-glib-1` -lpthread -lgthread-2.0 -lglib-2.0 +# LINUXLDFLAGS = `pkg-config --libs glib-2.0 hal dbus-glib-1 gthread-2.0` + +LINUXCFLAGS = -DUNIX -DPD -O2 -funroll-loops -fomit-frame-pointer \ + -Wall -W -Wshadow -Wstrict-prototypes -Werror \ + -Wno-unused -Wno-parentheses -Wno-switch \ + `pkg-config --cflags --libs glib-2.0 hal dbus-glib-1 gthread-2.0` + +LINUXINCLUDE = -I/usr/local/lib/pd/include -I/usr/lib/pd/src -I/usr/local/include + +.c.pd_linux: + $(CC) $(LINUXCFLAGS) $(LINUXINCLUDE) -o $*.o -c $*.c + ld -export_dynamic -shared -o $*.pd_linux $*.o -lc -lm $(LINUXLDFLAGS) + strip --strip-unneeded $*.pd_linux + rm $*.o diff --git a/README.TXT b/README.TXT new file mode 100644 index 0000000..b92bb67 --- /dev/null +++ b/README.TXT @@ -0,0 +1,51 @@ +title: input_noticer + +author: David Merrill + +desc: Using dbus and the hardware abstraction layer (HAL) in linux, +this external allows pd to find all linux device files for a given device type. +This scanning behavior can happen when the external is set up (via a [bang]), +and will happen automatically when a new device is added to the system. An +example linux device file would be: + +/dev/input/event5 + +The pd user specifies device type as a string - i.e. "SideWinder Dual Strike", and +this external outputs lists containing an index, and the linux device file where +each device of the given type was found. For example + +{0, /dev/input/event5} +{1, /dev/input/event6} + +These lists can be routed in PD with the [route] object - see the help file for +[route] for more details. + +In order to make this code compile, I had to install the following libraries +onto my system: + + libhal-dev + libglib2.0-dev + libdbus-glib-1-dev + +In a debian-based system, they can be installed with apt-get, as in: + sudo apt-get install libhal-dev + +To see if you have the right libraries installed in order to compile, you can +try compiling the test_noticer.c file, by running the test_noticer_compile.sh +shell script, as follows: + +source test_noticer_compile.sh + +Then, run test_noticer, and when it's running you should see messages when +you plug, or un-plug a joystick. + +Thanks to Dan Willmans, Seth Nickell, and David Zeuthen for their +invaluable help with the whole dbus/hal part. Also, thanks to Hans-Christoph +Steiner for his help with (and creation of) the joystick external. + +For good examples and reference on dbus/hal, please see: +NetworkManager.c: http://cvs.gnome.org/viewcvs/NetworkManager/src/NetworkManager.c?rev=1.100&view=markup +libhal.h: http://webcvs.freedesktop.org/hal/hal/libhal/libhal.h?rev=1.32&view=markup + +Parts of this code were pulled from those examples. + diff --git a/input_noticer.c b/input_noticer.c new file mode 100644 index 0000000..ef2c91a --- /dev/null +++ b/input_noticer.c @@ -0,0 +1,371 @@ +/* + * input_noticer - input noticer external for pure-data + * + * David Merrill + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "input_noticer.h" + +static char *version = "$Revision: 1.1.1.1 $"; +#define MAX_INPUT_DEVICES 32 + +/*------------------------------------------------------------------------------ + * CLASS DEF + */ + +typedef struct callback_info { + void (*device_added)(char *device_file); + void (*device_removed)(char *device_file); +} callback_info; + +static t_class *input_noticer_class; + +typedef struct _input_noticer { + t_object x_obj; + GMainContext *gmc; + GMainLoop *gml; + callback_info *cbi; + LibHalContext *lhc; + DBusConnection *connection; + char *capability; + char *product_substring; + int device_idx; + t_outlet *notify_out; + char *last_notification_sent; + GThread *gthread; + int started; +} t_input_noticer; + +int udi_matches_device(LibHalContext *ctx, const char *udi); +static void output_list(t_input_noticer *x, t_symbol *s, int argc, t_atom *argv); + +/*------------------------------------------------------------------------------ + * IMPLEMENTATION + */ + +static void output_inputpath(t_input_noticer *x, int idx, char *path) +{ + t_atom t[2]; + + // set up the output array + SETFLOAT(&(t[0]),idx); + SETSYMBOL(&(t[1]),gensym(path)); + + // output a list + outlet_list(x->notify_out, &s_list, 2, t); +} + +int scmp(const void *sp1, const void *sp2 ) +{ + return( strcmp(*(char **)sp1, *(char **)sp2) ); +} + +void scan_for_devices(LibHalContext *ctx) { + char ** input_devices; + int num_input_devices; + int i,j; + int this_device_idx; + DBusError dbus_error; + char *linux_device_file = NULL; + char *found_devices[MAX_INPUT_DEVICES]; + t_input_noticer *x = libhal_ctx_get_user_data(ctx); + + /// can't do anything here if we don't have a pointer back to our struct + if (x == NULL) return; + + // not really using this, but why not initialize... :) + dbus_error_init (&dbus_error); + + // grab an array of all devices that match the capability we're looking for + input_devices = libhal_find_device_by_capability (ctx, "input", &num_input_devices, &dbus_error); + if (dbus_error_is_set (&dbus_error)) + { + post("could not find existing networking devices: %s\n", dbus_error.message); + dbus_error_free (&dbus_error); + return; + } + + if (input_devices) + { + // we found at least one + this_device_idx = 0; + for (i = 0; i < num_input_devices; i++) + { + if (udi_matches_device(ctx, input_devices[i])) + { + post("found a %s",x->product_substring); + + // get the linux.device_file + linux_device_file = libhal_device_get_property_string(ctx, input_devices[i],"linux.device_file", NULL); + + // store the linux device file + found_devices[this_device_idx] = linux_device_file; + + this_device_idx++; + } + + } + // sort the devices alphabetically, so they will stay in the same order + qsort (found_devices, this_device_idx, sizeof (char *), scmp); + for (i = 0; i < this_device_idx; i++) + { + output_inputpath(x, i, found_devices[i]); + libhal_free_string(found_devices[i]); + } + libhal_free_string_array(input_devices); + } else { + post("no input devices found!"); + } +} + +// this function checks the UDI returned from libhal against the device product string +// that we are looking +int udi_matches_device(LibHalContext *ctx, const char *udi) { + t_input_noticer *x = libhal_ctx_get_user_data(ctx); + int i; + char *capability = malloc (strlen("input") + strlen(x->capability) + 2); + char *temp1; + + sprintf(capability,"input.%s",x->capability); + temp1 = libhal_device_get_property_string (ctx, udi, "info.product", NULL); + if (libhal_device_property_exists(ctx, udi, "info.capabilities", NULL)) + { + char **capabilities = libhal_device_get_property_strlist(ctx, udi, "info.capabilities", NULL); + for (i=0; capabilities[i] != NULL; i++) + { + //post("looking for %s, now checking capability #%d, %s",capability,i,capabilities[i]); + if (!strcmp (capabilities[i], capability)) + { + char *temp = libhal_device_get_property_string (ctx, udi, "info.product", NULL); + if (temp != NULL && strstr(temp, x->product_substring)) // if product string matches up + { + libhal_free_string_array(capabilities); + libhal_free_string (temp); + free(capability); + return 1; + } else { + // product string does not match up + } + + libhal_free_string (temp); + } + } + libhal_free_string_array(capabilities); + } else { + // no capabilities found + } + free(capability); + return 0; +} + +// this callback gets called whenever HAL notices a new device being added +void hal_device_added(LibHalContext *ctx, const char *udi) { + t_input_noticer *x = libhal_ctx_get_user_data(ctx); + + if (x != NULL && x->started) { + if (udi_matches_device(ctx,udi)) { + scan_for_devices(ctx); + } else { + // post("nope"); + } + } +} + +void hal_device_removed(LibHalContext *ctx, const char *udi) { + // post("device removed, udi = %s\n", udi); +} + +// I haven't seen this one get called... +void hal_device_new_capability(LibHalContext *ctx, const char *udi, const char *capability) { + // post("new device capability, udi = %s\n", udi); +} + +void input_noticer_stop(t_input_noticer* x) { + DEBUG(post("input_noticer_stop");); + + /* Signal the HAL listener to stop */ + g_main_loop_quit(x->gml); + + /* Wait until it has actually stopped */ + g_thread_join(x->gthread); +} + +static int input_noticer_close(t_input_noticer *x) { + DEBUG(post("input_noticer_close");); + + input_noticer_stop(x); + + if (x->product_substring) free(x->product_substring); + if (x->capability) free(x->capability); + + return 1; +} + +static int input_noticer_open(t_input_noticer *x, t_symbol *s) { + DEBUG(post("input_noticer_open");) + + // close it down, if running already + input_noticer_close(x); + + return 1; +} + +void input_noticer_start(t_input_noticer* x) { + post("input_noticer: started"); + + x->started = 1; + + // do first scan here (NOT in input_noticer_new, can't generate output from there) + scan_for_devices(x->lhc); +} + +gpointer input_noticer_thread_main(gpointer user_data) { + t_input_noticer *x = (t_input_noticer *) user_data; + + /* Run the main loop. We stay here until g_main_quit() is called */ + g_main_loop_run(x->gml); + + return user_data; +} + +/* teardown functions */ +static void input_noticer_free(t_input_noticer* x) { + DEBUG(post("input_noticer_free");) +} + +// removes double-quotes from a string, and returns a copy of it, otherwise unharmed +static char *remove_quotes(char *input_str) +{ + char *rv, *tp; + unsigned int i; + + post ("removing quotes from %s", input_str); + + if (input_str != NULL) + { + rv = malloc ((strlen(input_str) + 1) * sizeof(char)); + tp = rv; + for (i=0; i < strlen(input_str); i++) { + if (input_str[i] != '"') { + *tp = input_str[i]; + tp++; + } + } + *tp = '\0'; + } else { + return NULL; + } + + post ("returning %s", rv); + return rv; +} + +/* setup functions */ +static void *input_noticer_new(t_symbol *capability, t_symbol *product_substring) { + int i; + t_input_noticer *x = (t_input_noticer *)pd_new(input_noticer_class); + + post("[input_noticer] %s, written by David Merrill ",version); + + /* init vars */ + x->gmc = NULL; + x->cbi = NULL; + x->lhc = NULL; + x->connection = NULL; + x->notify_out = NULL; + x->last_notification_sent = NULL; + x->started = 0; + x->capability = remove_quotes((char *)capability->s_name); + x->product_substring = remove_quotes((char *)product_substring->s_name); + + // create outlet for notifying + x->notify_out = outlet_new(&x->x_obj, 0); // list outlet + + // Setup glib and dbus for threading + g_type_init(); + if (!g_thread_supported ()) + g_thread_init (NULL); + dbus_g_thread_init(); + + // create a context for the callback functions + x->gmc = g_main_context_new(); + x->gml = g_main_loop_new(x->gmc, FALSE); + + // create a libhal context + if ((x->lhc = libhal_ctx_new()) == NULL) { + // complain here (exit) + DEBUG(post("input_noticer_open: error, could not create a libhal context!");) + } + + // get the dbus connection + x->connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + if (x->connection == NULL) { + // complain here (exit) + DEBUG(post("input_noticer_open: error, could not get the DBUS connection!");) + } + + // attaches the main loop to dbus, so that the main loop + // gets dbus events + dbus_connection_setup_with_g_main(x->connection,x->gmc); + + // tells libhal to use our dbus connection, in order to receive + // events from hal + libhal_ctx_set_dbus_connection(x->lhc,x->connection); + + if (!libhal_ctx_init (x->lhc, NULL)) { + DEBUG(post("input_noticer_open: error, could not init libhal!");) + } + + // handing my custom data structure to libhal context, so that the callback functions + // can get to it + libhal_ctx_set_user_data(x->lhc, x); + + libhal_ctx_set_device_added(x->lhc, hal_device_added); + libhal_ctx_set_device_removed(x->lhc, hal_device_removed); + libhal_ctx_set_device_new_capability(x->lhc, hal_device_new_capability); + + /* Create the thread that listens for HAL events */ + x->gthread = g_thread_create(input_noticer_thread_main, x, TRUE, NULL); + + return (void *)x; +} + +void input_noticer_setup(void) { + // DEBUG(post("input_noticer_setup");) + + // define how the object gets instantiated + // example: [input_noticer joystick "SideWinder Dual Strike"] + input_noticer_class = class_new( + gensym("input_noticer"), + (t_newmethod)input_noticer_new, + (t_method)input_noticer_free, + sizeof(t_input_noticer), + CLASS_DEFAULT, + A_DEFSYMBOL, + A_DEFSYMBOL, + 0); + + class_addbang(input_noticer_class, input_noticer_start); +} diff --git a/input_noticer.h b/input_noticer.h new file mode 100644 index 0000000..5bedd24 --- /dev/null +++ b/input_noticer.h @@ -0,0 +1,58 @@ +#ifndef INPUT_NOTICER_H_ +#define INPUT_NOTICER_H_ + +/* + * input_noticer - input noticer external for pure-data + * + * David Merrill + * + * Description: Using dbus and the hardware abstraction layer (HAL) in linux, + * this external allows pd to find all linux device files for a given device type. + * This scanning behavior can happen when the external is set up (via a [bang]), + * and will happen automatically when a new device is added to the system. An + * example linux device file would be: + * + * /dev/input/event5 + * + * The pd user specifies device type as a string - i.e. "SideWinder Dual Strike", and + * this external outputs lists containing an index, and the linux device file where + * each device of the given type was found. For example + * + * {0, /dev/input/event5} + * {1, /dev/input/event6} + * + * These lists can be routed in PD with the [route] object - see the help file for + * more details. + * + * Thanks to Dan Willmans, Seth Nickell, and David Zeuthen for their + * invaluable help with the whole dbus/hal part. Also, thanks to Hans-Christoph + * Steiner for his help with (and creation of) the joystick external. + * + * For good examples and reference on dbus/hal, please see: + * NetworkManager.c: http://cvs.gnome.org/viewcvs/NetworkManager/src/NetworkManager.c?rev=1.100&view=markup + * libhal.h: http://webcvs.freedesktop.org/hal/hal/libhal/libhal.h?rev=1.32&view=markup + * + * Parts of this code were pulled from those examples. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include + +//#define DEBUG(x) +#define DEBUG(x) x + +#endif /*INPUT_NOTICER_H_*/ diff --git a/noticer_test.pd b/noticer_test.pd new file mode 100644 index 0000000..e001bc5 --- /dev/null +++ b/noticer_test.pd @@ -0,0 +1,12 @@ +#N canvas 0 0 450 300 10; +#X obj 108 188 print A; +#X obj 49 45 input_noticer joystick SideWinder; +#X obj 133 137 route 0 1; +#X obj 197 186 print C; +#X obj 149 215 print B; +#X obj 50 22 loadbang; +#X connect 1 0 2 0; +#X connect 2 0 0 0; +#X connect 2 1 4 0; +#X connect 2 2 3 0; +#X connect 5 0 1 0; diff --git a/test_noticer.c b/test_noticer.c new file mode 100644 index 0000000..b963ae3 --- /dev/null +++ b/test_noticer.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include + +typedef struct callback_info { + void (*device_added)(char *device_file); + void (*device_removed)(char *device_file); +} callback_info; + +// callback fns + +// this one actually never gets called in my experience +void hal_device_added(LibHalContext *ctx, const char *udi) { + int i, found_joystick; + char *linux_device_file = NULL; + + printf("device added, udi = %s\n", udi); + + // find out if this device advertises capabilities + if (libhal_device_property_exists(ctx, udi, "info.capabilities", NULL)) { + //printf("***** it's a joystick! *****\n"); + + // get the capabilities strlist + char **capabilities = libhal_device_get_property_strlist(ctx, udi, "info.capabilities", NULL); + + // find out if it's a joystick + found_joystick = 0; + for (i=0; capabilities[i] != NULL; i++) { + if (!strcmp (capabilities[i], "input.joystick")) { + char **linux_device_file_strlist; + found_joystick = 1; + + // printf("found a joystick!\n"); + + // pull out the relevant information (note - in Device Manager, this is reported incorrectly as a strlist, + // whereas it actually returns a string - so we make the correct call here + linux_device_file = libhal_device_get_property_string(ctx, udi, "linux.device_file", NULL); + + if (linux_device_file != NULL) { + // linux_device_file = linux_device_file_strlist[0]; + printf("found the joystick at: %s\n", linux_device_file); + + } else { + // we didn't find the device file, better luck next time + + } + } + // printf("got the following: %s\n", capabilities[i]); + } + } +} + +void hal_device_removed(LibHalContext *ctx, const char *udi) { + printf("device removed, udi = %s\n", udi); +} + +void hal_device_new_capability(LibHalContext *ctx, const char *udi, const char *capability) { + char *device; + callback_info *cbi = (callback_info *)libhal_ctx_get_user_data(ctx); + + printf("device has a new capability, udi = %s, cap = %s\n", udi, capability); + + if (capability && ((strcmp (capability, "input") == 0))) { + // + if (libhal_device_property_exists(ctx, udi, "input.device", NULL)) { + device = libhal_device_get_property_string(ctx, udi, "input.device",NULL); + + // this is the callback into my PD C code + // (*(cbi->device_added))(device); + printf("new capability, testing: %s\n",device); + } + } +} + + + +gpointer hal_thread_main(gpointer user_data) { + callback_info *cbi = (callback_info *)(user_data); + GMainContext *gmc; + GMainLoop *gml; + LibHalContext *lhc; + DBusConnection *connection; + + gmc = g_main_context_new(); + gml = g_main_loop_new(gmc,FALSE); + + if ((lhc = libhal_ctx_new()) == NULL) { + // complain here (exit) + } + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + if (connection == NULL) { + + } + + // attaches the main loop to dbus, so tha the main loop + // gets dbus events + dbus_connection_setup_with_g_main(connection,gmc); + + // tells libhal to use our dbus connection, in order to receive + // events from hal + libhal_ctx_set_dbus_connection(lhc,connection); + + if (!libhal_ctx_init (lhc, NULL)) { + // die + } + + // handing my custom data structure to libhal context + libhal_ctx_set_user_data(lhc, cbi); + + libhal_ctx_set_device_added(lhc, hal_device_added); + libhal_ctx_set_device_removed(lhc, hal_device_removed); + libhal_ctx_set_device_new_capability(lhc, hal_device_new_capability); + + // Get stuck here forever, and ever, and ever.... + g_main_loop_run(gml); +} + +GThread *hal_thread(callback_info *cbi) { + GThread *rv; + + // eventually, we will pass in some user data here (the first NULL) + // this could be a fn pointer, or a structure with a few fn pointers, + // etc, so that + rv = g_thread_create(hal_thread_main, cbi, TRUE, NULL); + return(rv); +} + +int main(int argc, char **argv) { + GThread *gth; + callback_info * cbi = malloc (sizeof (callback_info)); + + // Setup glib + g_type_init(); + if (!g_thread_supported ()) + g_thread_init (NULL); + + dbus_g_thread_init(); + + gth = hal_thread(cbi); + while (1) { + sleep(1); + } +} + diff --git a/test_noticer_compile.sh b/test_noticer_compile.sh new file mode 100644 index 0000000..7adc18a --- /dev/null +++ b/test_noticer_compile.sh @@ -0,0 +1 @@ +gcc `pkg-config --cflags --libs glib-2.0 hal dbus-glib-1 gthread-2.0` -o test_noticer test_noticer.c -- cgit v1.2.1