/* --------------------------------------------------------------------------*/ /* */ /* interface to native HID (Human Interface Devices) API */ /* Written by Hans-Christoph Steiner */ /* Max/MSP port by Olaf Matthes */ /* */ /* Copyright (c) 2004-2006 Hans-Christoph Steiner */ /* */ /* 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. */ /* */ /* See file LICENSE for further informations on licensing terms. */ /* */ /* 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. */ /* */ /* --------------------------------------------------------------------------*/ #ifdef _WIN32 /* any Windows specific includes go in here */ #ifdef PD #include #endif /* PD */ #else #include #include #endif /* _WIN32 */ #include #include #include "hidio.h" /*------------------------------------------------------------------------------ * LOCAL DEFINES */ #define DEBUG(x) //#define DEBUG(x) x unsigned short global_debug_level = 0; /* high numbers means more messages */ /*------------------------------------------------------------------------------ * GLOBAL VARIABLES */ /* count the number of instances of this object so that certain free() * functions can be called only after the final instance is detroyed. */ t_int hidio_instance_count; /* this is used to test for the first instance to execute */ double last_execute_time[MAX_DEVICES]; static t_class *hidio_class; /* mostly for status querying */ unsigned short device_count; /* store element structs to eliminate symbol table lookups, etc. */ t_hid_element *element[MAX_DEVICES][MAX_ELEMENTS]; /* number of active elements per device */ unsigned short element_count[MAX_DEVICES]; /* pre-generated symbols */ t_symbol *ps_open, *ps_device, *ps_poll, *ps_total, *ps_range; t_symbol *ps_absolute, *ps_button, *ps_key, *ps_led, *ps_pid, *ps_relative; t_symbol *absolute_symbols[ABSOLUTE_ARRAY_MAX]; t_symbol *button_symbols[BUTTON_ARRAY_MAX]; t_symbol *key_symbols[KEY_ARRAY_MAX]; t_symbol *led_symbols[LED_ARRAY_MAX]; t_symbol *pid_symbols[PID_ARRAY_MAX]; t_symbol *relative_symbols[RELATIVE_ARRAY_MAX]; /* TODO consider issuing a pd_error if more than one instance is attached to * one given device */ /*------------------------------------------------------------------------------ * FUNCTION PROTOTYPES */ //static void hidio_poll(t_hidio *x, t_float f); static void hidio_open(t_hidio *x, t_symbol *s, int argc, t_atom *argv); //static void hidio_close(t_hidio *x); //static void hidio_float(t_hidio* x, t_floatarg f); /*------------------------------------------------------------------------------ * SUPPORT FUNCTIONS */ void debug_post(t_int message_debug_level, const char *fmt, ...) { if(message_debug_level <= global_debug_level) { char buf[MAXPDSTRING]; va_list ap; //t_int arg[8]; va_start(ap, fmt); vsnprintf(buf, MAXPDSTRING-1, fmt, ap); post(buf); va_end(ap); } } void debug_error(t_hidio *x, t_int message_debug_level, const char *fmt, ...) { if(message_debug_level <= global_debug_level) { char buf[MAXPDSTRING]; va_list ap; //t_int arg[8]; va_start(ap, fmt); vsnprintf(buf, MAXPDSTRING-1, fmt, ap); pd_error(x, buf); va_end(ap); } } static void output_status(t_hidio *x, t_symbol *selector, t_float output_value) { t_atom *output_atom = (t_atom *)getbytes(sizeof(t_atom)); #ifdef PD SETFLOAT(output_atom, output_value); #else /* Max */ atom_setlong(output_atom, (long)output_value); #endif /* PD */ outlet_anything( x->x_status_outlet, selector, 1, output_atom); freebytes(output_atom,sizeof(t_atom)); } static void output_open_status(t_hidio *x) { output_status(x, ps_open, x->x_device_open); } static void output_device_number(t_hidio *x) { output_status(x, ps_device, x->x_device_number); } static void output_poll_time(t_hidio *x) { output_status(x, ps_poll, x->x_delay); } static void output_device_count(t_hidio *x) { output_status(x, ps_total, device_count); } static void output_element_ranges(t_hidio *x) { if( (x->x_device_number > -1) && (x->x_device_open) ) { unsigned int i; t_atom output_data[4]; for(i=0;ix_device_number];++i) { #ifdef PD SETSYMBOL(output_data, element[x->x_device_number][i]->type); SETSYMBOL(output_data + 1, element[x->x_device_number][i]->name); SETFLOAT(output_data + 2, element[x->x_device_number][i]->min); SETFLOAT(output_data + 3, element[x->x_device_number][i]->max); #else atom_setsym(output_data, element[x->x_device_number][i]->type); atom_setsym(output_data + 1, element[x->x_device_number][i]->name); atom_setlong(output_data + 2, element[x->x_device_number][i]->min); atom_setlong(output_data + 3, element[x->x_device_number][i]->max); #endif /* PD */ outlet_anything(x->x_status_outlet, ps_range, 4, output_data); } } } static unsigned int name_to_usage(char *usage_name) { // output usagepage << 16 + usage if(strcmp(usage_name,"pointer") == 0) return 0x00010001; if(strcmp(usage_name,"mouse") == 0) return 0x00010002; if(strcmp(usage_name,"joystick") == 0) return 0x00010004; if(strcmp(usage_name,"gamepad") == 0) return 0x00010005; if(strcmp(usage_name,"keyboard") == 0) return 0x00010006; if(strcmp(usage_name,"keypad") == 0) return 0x00010007; if(strcmp(usage_name,"multiaxiscontroller") == 0) return 0x00010008; return 0; } static t_int convert_symbol_hex_to_int(t_symbol *s) { if(strncmp(s->s_name, "0x", 2) == 0) return (t_int) strtoul(s->s_name, NULL, 16); else return -1; } static short get_device_number_from_arguments(int argc, t_atom *argv) { #ifdef PD short device_number = -1; char device_type_string[MAXPDSTRING] = ""; unsigned short device_type_instance; #else long device_number = -1; char *device_type_string; long device_type_instance; #endif /* PD */ unsigned int usage; unsigned short vendor_id; unsigned short product_id; t_symbol *first_argument; t_symbol *second_argument; if(argc == 1) { #ifdef PD first_argument = atom_getsymbolarg(0,argc,argv); if(first_argument == &s_) #else atom_arg_getsym(&first_argument, 0,argc,argv); if(first_argument == _sym_nothing) #endif /* PD */ { // single float arg means device # #ifdef PD device_number = (short) atom_getfloatarg(0,argc,argv); #else atom_arg_getlong(&device_number, 0, argc, argv); #endif /* PD */ if(device_number < 0) device_number = -1; debug_post(LOG_DEBUG,"[hidio] setting device# to %d",device_number); } else { // single symbol arg means first instance of a device type #ifdef PD atom_string(argv, device_type_string, MAXPDSTRING-1); #else device_type_string = atom_string(argv); // LATER do we have to free this string manually??? #endif /* PD */ usage = name_to_usage(device_type_string); device_number = get_device_number_from_usage(0, usage >> 16, usage & 0xffff); debug_post(LOG_INFO,"[hidio] using 0x%04x 0x%04x for %s", usage >> 16, usage & 0xffff, device_type_string); } } else if(argc == 2) { #ifdef PD first_argument = atom_getsymbolarg(0,argc,argv); second_argument = atom_getsymbolarg(1,argc,argv); if( second_argument == &s_ ) #else atom_arg_getsym(&first_argument, 0,argc,argv); atom_arg_getsym(&second_argument, 1,argc,argv); if( second_argument == _sym_nothing ) #endif /* PD */ { /* a symbol then a float means match on usage */ #ifdef PD atom_string(argv, device_type_string, MAXPDSTRING-1); usage = name_to_usage(device_type_string); device_type_instance = atom_getfloatarg(1,argc,argv); #else device_type_string = atom_string(argv); usage = name_to_usage(device_type_string); atom_arg_getlong(&device_type_instance, 1, argc, argv); #endif /* PD */ debug_post(LOG_DEBUG,"[hidio] looking for %s at #%d", device_type_string, device_type_instance); device_number = get_device_number_from_usage(device_type_instance, usage >> 16, usage & 0xffff); } else { /* two symbols means idVendor and idProduct in hex */ vendor_id = (unsigned short) strtol(first_argument->s_name, NULL, 16); product_id = (unsigned short) strtol(second_argument->s_name, NULL, 16); device_number = get_device_number_by_id(vendor_id,product_id); } } return device_number; } /* output_message[3] is pre-generated by hidio_build_element_list() and * stored in t_hid_element, then just the value is updated. This saves a bit * of CPU time since this is run for every event that is output. */ void hidio_output_event(t_hidio *x, t_hid_element *output_element) { /* debug_post(LOG_DEBUG,"hidio_output_event: instance %d/%d last: %llu", x->x_instance+1, hidio_instance_count, last_execute_time[x->x_device_number]);*/ #ifdef PD SETFLOAT(output_element->output_message + 2, output_element->value); #else /* Max */ atom_setlong(output_element->output_message + 2, (long)output_element->value); #endif /* PD */ outlet_anything(x->x_data_outlet, output_element->type, 3, output_element->output_message); } void hidio_write_event(t_hidio *x, t_symbol *s, int argc, t_atom *argv) { debug_post(LOG_DEBUG,"hidio_write_event"); t_symbol *first_argument; t_symbol *second_argument; t_int first_argument_int; t_int second_argument_int; /* TODO add symbol for value to allow for hex values. This would be useful * for sending values greater than Pd's t_float can handle */ if(argc != 4) { pd_error(x, "[hidio] write message must have exactly 4 atoms"); return; } first_argument = atom_getsymbolarg(0,argc,argv); if(first_argument == &s_) { // first float arg means all float message debug_post(LOG_DEBUG,"first_argument == &s_"); hidio_write_event_ints(x, atom_getintarg(0,argc,argv), atom_getintarg(1,argc,argv), atom_getintarg(2,argc,argv), atom_getintarg(3,argc,argv)); } else { // if the symbol is a hex number, convert it to a t_int first_argument_int = convert_symbol_hex_to_int(first_argument); second_argument = atom_getsymbolarg(1,argc,argv); if((second_argument == &s_) && (first_argument_int > -1)) { // symbol page and float usage debug_post(LOG_DEBUG,"second_argument == &s_"); hidio_write_event_symbol_int(x, first_argument, atom_getintarg(1,argc,argv), atom_getintarg(2,argc,argv), atom_getintarg(3,argc,argv)); } else { // symbol page and usage // if the symbol is a hex number, convert it to a t_int if( (first_argument->s_name[0] == '0') && (first_argument->s_name[1] == 'x') ) first_argument_int = (t_int) strtoul(first_argument->s_name, 0, 16); hidio_write_event_symbols(x, first_argument, second_argument, atom_getintarg(2,argc,argv), atom_getintarg(3,argc,argv)); } } } /* stop polling the device */ static void hidio_stop_poll(t_hidio* x) { debug_post(LOG_DEBUG,"hidio_stop_poll"); if (x->x_started) { clock_unset(x->x_clock); debug_post(LOG_INFO,"[hidio] polling stopped"); x->x_started = 0; } } /*------------------------------------------------------------------------------ * METHODS FOR [hidio]'s MESSAGES */ /* TODO: poll time should be set based on how fast the OS is actually polling * the device, whether that is IOUSBEndpointDescriptor.bInterval, or something * else. */ void hidio_poll(t_hidio* x, t_float f) { debug_post(LOG_DEBUG,"hidio_poll"); /* if the user sets the delay less than 2, set to block size */ if( f > 2 ) x->x_delay = (t_int)f; else if( f > 0 ) //TODO make this the actual time between message processing x->x_delay = 1.54; if(x->x_device_number > -1) { if(!x->x_device_open) { hidio_open(x,ps_open,0,NULL); } if(!x->x_started) { clock_delay(x->x_clock, x->x_delay); debug_post(LOG_DEBUG,"[hidio] polling started"); x->x_started = 1; } } } static void hidio_set_from_float(t_hidio *x, t_floatarg f) { /* values greater than 1 set the polling delay time */ /* 1 and 0 for start/stop so you can use a [tgl] */ if(f > 1) { x->x_delay = (t_int)f; hidio_poll(x,f); } else if(f == 1) { if(! x->x_started) { hidio_poll(x,f); } } else if(f == 0) { hidio_stop_poll(x); } } /* close the device */ static void hidio_close(t_hidio *x) { debug_post(LOG_DEBUG,"hidio_close"); /* just to be safe, stop it first */ hidio_stop_poll(x); if(hidio_close_device(x) != 0) debug_error(x, LOG_ERR,"[hidio] error closing device %d",x->x_device_number); debug_post(LOG_DEBUG,"[hidio] closed device %d",x->x_device_number); x->x_device_open = 0; output_open_status(x); } /* hidio_open behavoir * current state action * --------------------------------------- * closed / same device open * open / same device no action * closed / different device open * open / different device close, open */ static void hidio_open(t_hidio *x, t_symbol *s, int argc, t_atom *argv) { short new_device_number = get_device_number_from_arguments(argc, argv); t_int started = x->x_started; // store state to restore after device is opened debug_post(LOG_DEBUG,"hid_%s",s->s_name); if (new_device_number > -1) { /* check whether we have to close previous device */ if (x->x_device_open && new_device_number != x->x_device_number) { hidio_close(x); } /* no device open, so open one now */ if (!x->x_device_open) { if(hidio_open_device(x, new_device_number) == EXIT_SUCCESS) { x->x_device_open = 1; x->x_device_number = new_device_number; /* restore the polling state so that when I [tgl] is used to * start/stop [hidio], the [tgl]'s state will continue to * accurately reflect [hidio]'s state */ if (started) hidio_set_from_float(x,x->x_delay); // TODO is this useful? debug_post(LOG_DEBUG,"[hidio] set device# to %d",new_device_number); output_device_number(x); } else { x->x_device_number = -1; pd_error(x, "[hidio] can not open device %d",new_device_number); } } } else debug_error(x, LOG_WARNING,"[hidio] device does not exist"); /* always output open result so you can test for success in Pd space */ output_open_status(x); } static void hidio_tick(t_hidio *x) { // debug_post(LOG_DEBUG,"hidio_tick"); t_hid_element *current_element; unsigned int i; double right_now; #ifdef PD right_now = clock_getlogicaltime(); #else /* Max */ clock_getftime(&right_now); #endif /* PD */ // debug_post(LOG_DEBUG,"# %u\tnow: %llu\tlast: %llu", x->x_device_number, // right_now, last_execute_time[x->x_device_number]); if(right_now > last_execute_time[x->x_device_number]) { hidio_get_events(x); last_execute_time[x->x_device_number] = right_now; /* debug_post(LOG_DEBUG,"executing: instance %d/%d at %llu last: %llu", x->x_instance+1, hidio_instance_count, right_now, last_execute_time[x->x_device_number]);*/ } for(i=0; i< element_count[x->x_device_number]; ++i) { /* TODO: since relative events need to be output every time, they need * to be flagged when new relative events arrive. Otherwise, it'll * just spam out relative events no matter if anything new has * arrived */ current_element = element[x->x_device_number][i]; if(current_element->previous_value != current_element->value) { hidio_output_event(x, current_element); if(!current_element->relative) current_element->previous_value = current_element->value; } } if (x->x_started) { clock_delay(x->x_clock, x->x_delay); } } static void hidio_info(t_hidio *x) { output_open_status(x); output_device_number(x); output_device_count(x); output_poll_time(x); output_element_ranges(x); hidio_platform_specific_info(x); } static void hidio_float(t_hidio* x, t_floatarg f) { debug_post(LOG_DEBUG,"hid_float"); hidio_set_from_float(x,f); } #ifndef PD /* Max */ static void hidio_int(t_hidio* x, long l) { debug_post(LOG_DEBUG,"hid_int"); hidio_set_from_float(x, (float)l); } #endif /* NOT PD */ static void hidio_debug(t_hidio *x, t_float f) { debug_post(LOG_INFO,"[hidio] set global debug level to %d", (int)f); global_debug_level = f; } /*------------------------------------------------------------------------------ * system functions */ static void hidio_free(t_hidio* x) { debug_post(LOG_DEBUG,"hidio_free"); hidio_close(x); clock_free(x->x_clock); hidio_instance_count--; hidio_platform_specific_free(x); } /* create a new instance of this class */ static void *hidio_new(t_symbol *s, int argc, t_atom *argv) { unsigned int i; #ifdef PD t_hidio *x = (t_hidio *)pd_new(hidio_class); x->x_clock = clock_new(x, (t_method)hidio_tick); /* create anything outlet used for HID data */ x->x_data_outlet = outlet_new(&x->x_obj, 0); x->x_status_outlet = outlet_new(&x->x_obj, 0); #else /* Max */ t_hidio *x = (t_hidio *)object_alloc(hidio_class); x->x_clock = clock_new(x, (method)hidio_tick); /* create anything outlet used for HID data */ x->x_status_outlet = outlet_new(x, "anything"); x->x_data_outlet = outlet_new(x, "anything"); #endif /* PD */ /* init vars */ x->x_device_open = 0; x->x_started = 0; x->x_delay = DEFAULT_DELAY; for(i=0; ix_hid_device = hidio_platform_specific_new(x); #endif x->x_device_number = get_device_number_from_arguments(argc, argv); x->x_instance = hidio_instance_count; hidio_instance_count++; return x; } #ifdef PD void hidio_setup(void) { hidio_class = class_new(gensym("hidio"), (t_newmethod)hidio_new, (t_method)hidio_free, sizeof(t_hidio), CLASS_DEFAULT, A_GIMME,0); /* add inlet datatype methods */ class_addfloat(hidio_class,(t_method) hidio_float); class_addbang(hidio_class,(t_method) hidio_tick); /* class_addanything(hidio_class,(t_method) hidio_anything); */ /* add inlet message methods */ class_addmethod(hidio_class,(t_method) hidio_debug,gensym("debug"),A_DEFFLOAT,0); class_addmethod(hidio_class,(t_method) hidio_build_device_list,gensym("refresh"),0); /* TODO: [print( should be dumped for [devices( and [elements( messages */ class_addmethod(hidio_class,(t_method) hidio_devices,gensym("devices"),0); class_addmethod(hidio_class,(t_method) hidio_elements,gensym("elements"),0); class_addmethod(hidio_class,(t_method) hidio_info,gensym("info"),0); class_addmethod(hidio_class,(t_method) hidio_open,gensym("open"),A_GIMME,0); class_addmethod(hidio_class,(t_method) hidio_close,gensym("close"),0); class_addmethod(hidio_class,(t_method) hidio_poll,gensym("poll"),A_DEFFLOAT,0); /* test function for output support */ class_addmethod(hidio_class,(t_method) hidio_write_event, gensym("write"), A_GIMME ,0); post("[hidio] %d.%d: © 2004-2008 by Hans-Christoph Steiner & Olaf Matthes", HIDIO_MAJOR_VERSION, HIDIO_MINOR_VERSION); post("\tcompiled on "__DATE__" at "__TIME__ " "); /* pre-generate often used symbols */ ps_open = gensym("open"); ps_device = gensym("device"); ps_poll = gensym("poll"); ps_total = gensym("total"); ps_range = gensym("range"); generate_type_symbols(); generate_event_symbols(); } #else /* Max */ static void hidio_notify(t_hidio *x, t_symbol *s, t_symbol *msg, void *sender, void *data) { if (msg == _sym_free) // this message is sent when a child object is freeing { object_detach(gensym("_obex_hidio"), s, x); object_unregister(sender); } } static void hidio_assist(t_hidio *x, void *b, long m, long a, char *s) { if (m == 2) { switch (a) { case 0: sprintf(s, "(list) Received Events"); break; case 1: sprintf(s, "(list) Status Info"); break; } } else { switch (a) { case 0: sprintf(s, "Control Messages"); break; case 1: sprintf(s, "nothing"); break; } } } int main() { t_class *c; c = class_new("hidio", (method)hidio_new, (method)hidio_free, (short)sizeof(t_hidio), 0L, A_GIMME, 0); /* initialize the common symbols, since we want to use them */ common_symbols_init(); /* register the byte offset of obex with the class */ class_obexoffset_set(c, calcoffset(t_hidio, x_obex)); /* add methods to the class */ class_addmethod(c, (method)hidio_int, "int", A_LONG, 0); class_addmethod(c, (method)hidio_float, "float", A_FLOAT, 0); class_addmethod(c, (method)hidio_tick, "bang", A_GIMME, 0); /* add inlet message methods */ class_addmethod(c, (method)hidio_debug, "debug",A_DEFFLOAT,0); class_addmethod(c, (method)hidio_build_device_list, "refresh",0); /* TODO: [print( should be dumped for [devices( and [elements( messages */ class_addmethod(c, (method)hidio_devices, "devices",0); class_addmethod(c, (method)hidio_elements, "elements",0); class_addmethod(c, (method)hidio_print, "print",0); class_addmethod(c, (method)hidio_info, "info",0); class_addmethod(c, (method)hidio_open, "open",A_GIMME,0); class_addmethod(c, (method)hidio_close, "close",0); class_addmethod(c, (method)hidio_poll, "poll",A_DEFFLOAT,0); /* perfomrance / system stuff */ class_addmethod(c, (method)hidio_assist, "assist", A_CANT, 0); /* add a notify method, so we get notifications from child objects */ class_addmethod(c, (method)hidio_notify, "notify", A_CANT, 0); // add methods for dumpout and quickref class_addmethod(c, (method)object_obex_dumpout, "dumpout", A_CANT, 0); class_addmethod(c, (method)object_obex_quickref, "quickref", A_CANT, 0); /* we want this class to instantiate inside of the Max UI; ergo CLASS_BOX */ class_register(CLASS_BOX, c); hidio_class = c; finder_addclass("Devices", "hidio"); post("hidio %d.%d: © 2004-2008 by Hans-Christoph Steiner & Olaf Matthes", HIDIO_MAJOR_VERSION, HIDIO_MINOR_VERSION); post("hidio: compiled on "__DATE__" at "__TIME__ " "); /* pre-generate often used symbols */ ps_open = gensym("open"); ps_device = gensym("device"); ps_poll = gensym("poll"); ps_total = gensym("total"); ps_range = gensym("range"); generate_type_symbols(); generate_event_symbols(); return EXIT_SUCCESS; } #endif /* PD */