hid.c 23.8 KB
Newer Older
phil's avatar
phil committed
1
2
3


#include "m_pd.h"
phil's avatar
phil committed
4
5
#include "libusb.h"
#include "hidapi.h"
phil's avatar
phil committed
6
7
8
#include "usbhid_map.h"
//#include "report_item.h"
//#include "report_usage.h"
phil's avatar
phil committed
9

phil's avatar
phil committed
10
#include <stdlib.h>
phil's avatar
phil committed
11
#include <unistd.h>
phil's avatar
phil committed
12
#include <string.h>
phil's avatar
phil committed
13
14
15
#include <wchar.h>
#include <assert.h>
#include <math.h>
phil's avatar
phil committed
16
17
18
#include <signal.h>
#include <time.h>
#include <pthread.h>
phil's avatar
phil committed
19
20
21
22

#define UNUSED(x) (void)(x)

static t_class *hid_class;
phil's avatar
phil committed
23
static int obj_counter;
phil's avatar
phil committed
24
25
26
27
28
29

typedef struct {
    libusb_device * dev;
    struct libusb_device_descriptor desc;
//    struct libusb_config_descriptor *config;
    uint8_t interface_num;
phil's avatar
phil committed
30

phil's avatar
phil committed
31
32
33
34
35
36
37
    int input_endpoint;
    int output_endpoint;
    int input_ep_max_packet_size;

    wchar_t * serial_string;
    wchar_t * manufacturer_string;
    wchar_t * product_string;
phil's avatar
phil committed
38

phil's avatar
phil committed
39
    size_t report_desc_len;
phil's avatar
phil committed
40
    uint8_t report_desc[];
phil's avatar
phil committed
41
42
#define hid_usage_page  report_desc[1]
#define hid_usage       report_desc[3]
phil's avatar
phil committed
43
44
} hid_device_t;

phil's avatar
phil committed
45
46
47
48
typedef struct {
    t_object  x_obj;
    t_outlet *out;

phil's avatar
phil committed
49
    libusb_context * usb_context;
phil's avatar
phil committed
50

phil's avatar
phil committed
51
52
    //// currently open device
    // local type
phil's avatar
phil committed
53
    hid_device_t * hiddev;
phil's avatar
phil committed
54
    // hidapi type
phil's avatar
phil committed
55
    hid_device * handle;
phil's avatar
phil committed
56

phil's avatar
phil committed
57
58
59
60
    // parsed hid report
    usbhid_map_ref_t hid_map;

    // option
phil's avatar
phil committed
61
62
    uint8_t report_id;

phil's avatar
phil committed
63
    // polling option/logic
phil's avatar
phil committed
64
65
    volatile int poll_ms;
    pthread_t polling_thread;
phil's avatar
phil committed
66
67
68
69
70

} hid_t;



phil's avatar
phil committed
71
72
73
74
void hid_setup(void);
static void * hid_new();
static void hid_free(hid_t *hid);

phil's avatar
phil committed
75
//static void hid_anything(hid_t *hid, t_symbol *s, int argc, t_atom *argv);
phil's avatar
phil committed
76

phil's avatar
phil committed
77
static void hid_cmd_list(hid_t *hid, t_symbol *s, int argc, t_atom *argv);
phil's avatar
phil committed
78
79
80
static void hid_cmd_open(hid_t *hid, t_symbol *s, int argc, t_atom *argv);
static void hid_cmd_close(hid_t *hid, t_symbol *s, int argc, t_atom *argv);

phil's avatar
phil committed
81
82
static void hid_cmd_bang(hid_t *hid);
static void hid_cmd_poll(hid_t *hid, t_symbol *s, int argc, t_atom *argv);
phil's avatar
phil committed
83

phil's avatar
phil committed
84
85
static void hid_cmd_report_id(hid_t *hid, t_symbol *s, int argc, t_atom *argv);

phil's avatar
phil committed
86
87
static int hid_get_device_list(hid_t *hid, hid_device_t ***hiddevs, uint16_t vendor, uint16_t product, char * serial, uint8_t usage_page, uint8_t usage, uint8_t max);
static int hid_filter_device_list(libusb_device **devs, ssize_t count, hid_device_t ***hiddevs, uint16_t vendor, uint16_t product, char * serial, uint8_t usage_page, uint8_t usage, uint8_t max);
phil's avatar
phil committed
88
89

static void hid_free_device(hid_device_t * hiddev);
phil's avatar
phil committed
90
91
static void hid_free_device_list(hid_device_t ** hiddevs);

phil's avatar
phil committed
92
static int hid_read_report(hid_t * hid);
phil's avatar
phil committed
93
94
//static char *get_usb_wstring(libusb_device_handle *dev, uint8_t idx);

phil's avatar
phil committed
95

phil's avatar
phil committed
96
static void hid_shutdown(hid_t *hid);
phil's avatar
phil committed
97

phil's avatar
phil committed
98
99
100
101
102
103
104
105
106
107
108
/**
 * define the function-space of the class
 */
void hid_setup(void) {
    hid_class = class_new(gensym("hid"),
                          (t_newmethod)hid_new,
                          (t_method)hid_free,
                          sizeof(hid_t),
                          CLASS_DEFAULT,
                          0);

phil's avatar
phil committed
109
    class_addmethod(hid_class, (t_method)hid_cmd_list, gensym("list"), A_GIMME, 0);
phil's avatar
phil committed
110

phil's avatar
phil committed
111
112
    class_addmethod(hid_class, (t_method)hid_cmd_open, gensym("open"), A_GIMME, 0);
    class_addmethod(hid_class, (t_method)hid_cmd_close, gensym("close"), A_GIMME, 0);
phil's avatar
phil committed
113

phil's avatar
phil committed
114
115
    class_addbang(hid_class, hid_cmd_bang);
    class_addmethod(hid_class, (t_method)hid_cmd_poll, gensym("poll"), A_GIMME, 0);
phil's avatar
phil committed
116
117

    class_addmethod(hid_class, (t_method)hid_cmd_report_id, gensym("report_id"), A_GIMME, 0);
phil's avatar
phil committed
118
119

    obj_counter = 0;
phil's avatar
phil committed
120
121
122
123
124
125
126
}


static void *hid_new()
{
    hid_t *hid = (hid_t *)pd_new(hid_class);

phil's avatar
phil committed
127
    int r = libusb_init(&hid->usb_context);
phil's avatar
phil committed
128
129
130
131
132
    if (r < 0){
        error("failed to init libusb: %d", r);
        return NULL;
    }

phil's avatar
phil committed
133
134
135
136
137
138
139
140
    if (obj_counter == 0){
        if (hid_init()){
            error("failed to init hid");
            return NULL;
        }
    }
    obj_counter++;

phil's avatar
phil committed
141
142
    hid->handle = NULL;

phil's avatar
phil committed
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
    // generic outlet
    hid->out = outlet_new(&hid->x_obj, 0);

//    self->in = inlet_new(&self->x_obj, &self->x_obj.ob_pd,
//              gensym(""), gensym("list"));

    /* create a new outlet for floating-point values */
//    self->byte_out = outlet_new(&self->x_obj, &s_float);
//    self->dbg_out = outlet_new(&self->x_obj, &s_symbol);

//    self->runningStatusEnabled = false;
//    self->runningStatusState = MidiMessage_RunningStatusNotSet;

    return hid;
}

static void hid_free(hid_t *hid)
{
phil's avatar
phil committed
161
162
163
164
165
166
167
168
169
170
    hid_shutdown(hid);

    if (obj_counter){
        obj_counter--;
        if (!obj_counter){
            hid_exit();
        }
    }

    libusb_exit(hid->usb_context);
phil's avatar
phil committed
171
172
}

phil's avatar
phil committed
173
174
175
176
177
178
179
180
181
182
183
184
185
//static void hid_anything(hid_t *hid, t_symbol *s, int argc, t_atom *argv)
//{
//    if (s == gensym("list")){
//        hid_cmd_list(hid, s, argc, argv);
//    } else {
//        error("not a recognized command");
//    }
//}


/* This function returns a newly allocated wide string containing the USB
   device string numbered by the index. The returned string must be freed
   by using free(). */
phil's avatar
phil committed
186
static wchar_t *get_usb_wstring(libusb_device_handle *dev, uint8_t idx)
phil's avatar
phil committed
187
{
phil's avatar
phil committed
188
189
    char buf[512];
    int len;
phil's avatar
phil committed
190
    wchar_t *str = NULL;
phil's avatar
phil committed
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208


    /* Determine which language to use. */
    uint16_t lang = 0;
//    lang = get_usb_code_for_current_locale();
//    if (!is_language_supported(dev, lang))
//        lang = get_first_language(dev);

    /* Get the string from libusb. */
    len = libusb_get_string_descriptor(dev,
                                       idx,
                                       lang,
                                       (unsigned char*)buf,
                                       sizeof(buf));
    if (len < 0)
        return NULL;

	len -= 2;
phil's avatar
phil committed
209
	str = (wchar_t*) malloc((len / 2 + 1) * sizeof(wchar_t));
phil's avatar
phil committed
210
211
	int i;
	for (i = 0; i < len / 2; i++) {
phil's avatar
phil committed
212
		str[i] = buf[i * 2 + 2] | (buf[i * 2 + 3] << 8);
phil's avatar
phil committed
213
214
215
216
217
	}
	str[len / 2] = 0x00000000;


    return str;
phil's avatar
phil committed
218
219
}

phil's avatar
phil committed
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
static int wchar2char(wchar_t * wstr, char * cstr, size_t count)
{
    if (!count--){
        return 0;
    }

    int len = 0;
    for(;*wstr && count--; wstr++, cstr++, len++){
        *cstr = *wstr;
    }
    *cstr = '\0';

    return len;
}

static void hid_shutdown(hid_t *hid)
{
    // first shutdown threads
    if (hid->poll_ms){
        hid->poll_ms = 0;
        pthread_join(hid->polling_thread, NULL);
    }

    // then close handle
    if (hid->handle){
        hid_close(hid->handle);
        hid->handle = NULL;
    }

    // and free store device
    if (hid->hiddev){
        hid_free_device(hid->hiddev);
        hid->hiddev = NULL;
    }
phil's avatar
phil committed
254
255
256
257
258

    if (hid->hid_map){
        usbhid_map_free(hid->hid_map);
        hid->hid_map = NULL;
    }
phil's avatar
phil committed
259
260
}

phil's avatar
phil committed
261

phil's avatar
phil committed
262
static int hid_get_device_list(hid_t *hid, hid_device_t ***hiddevs, uint16_t vendor, uint16_t product, char * serial, uint8_t usage_page, uint8_t usage, uint8_t max)
phil's avatar
phil committed
263
264
265
266
267
{
    libusb_device **devs;
    ssize_t cnt;


phil's avatar
phil committed
268
    cnt = libusb_get_device_list(hid->usb_context, &devs);
phil's avatar
phil committed
269
270
271
272
273
    if (cnt < 0){
        error("Failed to get device list: %zd", cnt);
        return -1;
    }

phil's avatar
phil committed
274
    cnt = hid_filter_device_list(devs, cnt, hiddevs, vendor, product, serial, usage_page, usage, max);
phil's avatar
phil committed
275
276
277
278
279
280
281
282
283
284

    libusb_free_device_list(devs, 1);

    if (cnt < 0){
        error("Error during filter operation: %zd", cnt);
        return -1;
    }

    return cnt;
}
phil's avatar
phil committed
285

phil's avatar
phil committed
286
static int hid_filter_device_list(libusb_device **devs, ssize_t count, hid_device_t ***hiddevs, uint16_t vendor, uint16_t product, char * serial, uint8_t usage_page, uint8_t usage, uint8_t max)
phil's avatar
phil committed
287
288
289
290
{
    libusb_device *dev;
    int i = 0, o = 0;

phil's avatar
phil committed
291
292
293
294
295
296
297
298
    if (count <= 0){
        return count;
    }

    if (max <= 0){
        max = count;
    }

phil's avatar
phil committed
299
    // let's assume that there will be fewer HID devices (interfaces) than original usb devices...
phil's avatar
phil committed
300
    *hiddevs = calloc(1+max,sizeof(hid_device_t));
phil's avatar
phil committed
301
302
303
304
305
    if (!*hiddevs){
        error("failed to allocate memory for HID devices list");
        return -1;
    }

phil's avatar
phil committed
306
    while ((dev = devs[i++]) != NULL && o < max) {
phil's avatar
phil committed
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
        struct libusb_device_descriptor desc;
        int r = libusb_get_device_descriptor(dev, &desc);
        if (r < 0) {
            error("failed to get device descriptor");
            continue;
        }

        // actually this only works for simple devices...
        if (desc.bDeviceClass != 0 || desc.bDeviceSubClass != 0){
            continue;
        }

        // if filter bby vendor/product id
        if ((vendor != 0 && vendor != desc.idVendor) ||
            (product != 0 && product != desc.idProduct)){
            continue;
        }

        struct libusb_config_descriptor *config;
        r = libusb_get_active_config_descriptor(dev, &config);
        if (r < 0){
            libusb_get_config_descriptor(dev, 0, &config);
        }
        if (r < 0){
            error("Failed to get config descriptor: %d", r);
            continue;
        }

phil's avatar
phil committed
335
        for(int k = 0; k < config->bNumInterfaces && o < max; k++) {
phil's avatar
phil committed
336
337
//            printf("\t if %d / num_altsetting = %d\n", k, config->interface[k].num_altsetting);

phil's avatar
phil committed
338
            for (int l = 0; l < config->interface[k].num_altsetting && o < max; l++) {
phil's avatar
phil committed
339

phil's avatar
phil committed
340
                if (config->interface[k].altsetting[l].bInterfaceClass != LIBUSB_CLASS_HID) {
phil's avatar
phil committed
341
342
343
344
345
346
                    continue;
                }

                libusb_device_handle *handle;
                r = libusb_open(dev, &handle);

phil's avatar
phil committed
347
                if (r < 0) {
phil's avatar
phil committed
348
349
350
351
352
353
354
355
                    error("failed to open dev: %d", r);
                    continue;
                }


                uint8_t interface_num = config->interface[k].altsetting[l].bInterfaceNumber;

                uint8_t report_desc[256];
phil's avatar
phil committed
356
357
358
359
                r = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_INTERFACE,
                                            LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8) | interface_num, 0,
                                            report_desc, sizeof(report_desc), 5000);
                if (r < 4) {
phil's avatar
phil committed
360
361
                    error("control_transfer() fail for report descriptor (%04x:%04x): %d", desc.idVendor, desc.idProduct, r);
                } else if (report_desc[0] != 0x05 || report_desc[2] != 0x09) {
phil's avatar
phil committed
362
363
364
365
366
                    error("Notice: report descriptor not starting with usage page and usage bytes: %02x %02x %02x %02x",
                          report_desc[0], report_desc[1], report_desc[2], report_desc[3]);
                } else if ((usage_page != 0 && usage_page != report_desc[1]) ||
                           (usage != 0 && usage != report_desc[3]) ||
                           (serial != NULL && desc.iSerialNumber == 0)) {
phil's avatar
phil committed
367
368
                    // filter out unwanted usages
                    // ie, do nothing
phil's avatar
phil committed
369
370
371
//                } else if (o >= count) {
//                    error("Too many HID interfaces, skipping device %04x:%04x usage %d %d", desc.idVendor,
//                          desc.idProduct, report_desc[1], report_desc[3]);
phil's avatar
phil committed
372
373
                } else {

phil's avatar
phil committed
374
375
376
                    wchar_t * serial_wstring = NULL;
                    char serial_cstring[256];

phil's avatar
phil committed
377

phil's avatar
phil committed
378
379
380
381
                    if (desc.iSerialNumber > 0 && (serial_wstring = get_usb_wstring(handle, desc.iSerialNumber)) == NULL){
                        error("failed to load (required) serial for device %04x:%04x", desc.idVendor, desc.idProduct);
                    } else if (serial != NULL && wchar2char(serial_wstring, serial_cstring, sizeof(serial_cstring)) && strcmp(serial, serial_cstring) != 0){
                        free(serial_wstring);
phil's avatar
phil committed
382
383
                    } else {

phil's avatar
phil committed
384

phil's avatar
phil committed
385
386
387
388
                        hid_device_t * hiddev = calloc(1, sizeof(hid_device_t) + r);

                        hiddev->dev = dev;
                        libusb_ref_device(dev); // increase reference count
phil's avatar
phil committed
389

phil's avatar
phil committed
390
                        memcpy(&hiddev->desc, &desc, sizeof(struct libusb_device_descriptor));
phil's avatar
phil committed
391

phil's avatar
phil committed
392
                        // on macos causes a fault...
phil's avatar
phil committed
393
394
//                    hiddev->config = config;
//                    config = NULL;
phil's avatar
phil committed
395

phil's avatar
phil committed
396
                        hiddev->interface_num = interface_num;
phil's avatar
phil committed
397

phil's avatar
phil committed
398
                        hiddev->report_desc_len = r;
phil's avatar
phil committed
399
                        memcpy(hiddev->report_desc, report_desc, r);
phil's avatar
phil committed
400

phil's avatar
phil committed
401
                        /* Serial Number */
phil's avatar
phil committed
402
                        hiddev->serial_string = serial_wstring;
phil's avatar
phil committed
403

phil's avatar
phil committed
404
405
406
                        /* Manufacturer and Product strings */
                        if (desc.iManufacturer > 0)
                            hiddev->manufacturer_string =
phil's avatar
phil committed
407
                                    get_usb_wstring(handle, desc.iManufacturer);
phil's avatar
phil committed
408
409
                        if (desc.iProduct > 0)
                            hiddev->product_string =
phil's avatar
phil committed
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
                                    get_usb_wstring(handle, desc.iProduct);


                        /* Find the INPUT and OUTPUT endpoints. An
						   OUTPUT endpoint is not required. */
                        for (int e = 0; e < config->interface[k].altsetting[l].bNumEndpoints; e++) {
                            const struct libusb_endpoint_descriptor *ep = &config->interface[k].altsetting[l].endpoint[i];

                            /* Determine the type and direction of this
                               endpoint. */
                            int is_interrupt =
                                    (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
                                    == LIBUSB_TRANSFER_TYPE_INTERRUPT;
                            int is_output =
                                    (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
                                    == LIBUSB_ENDPOINT_OUT;
                            int is_input =
                                    (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
                                    == LIBUSB_ENDPOINT_IN;

                            /* Decide whether to use it for input or output. */
                            if (hiddev->input_endpoint == 0 &&
                                is_interrupt && is_input) {
                                /* Use this endpoint for INPUT */
                                hiddev->input_endpoint = ep->bEndpointAddress;
                                hiddev->input_ep_max_packet_size = ep->wMaxPacketSize;
                            }
                            if (hiddev->output_endpoint == 0 &&
                                is_interrupt && is_output) {
                                /* Use this endpoint for OUTPUT */
                                hiddev->output_endpoint = ep->bEndpointAddress;
                            }
                        }
phil's avatar
phil committed
443

phil's avatar
phil committed
444
                        (*hiddevs)[o++] = hiddev;
phil's avatar
phil committed
445

phil's avatar
phil committed
446
447
//                    post("usage (page) = %d (%d)", report_desc[3], report_desc[1]);
                    }
phil's avatar
phil committed
448
449
450
451
452
453
                }

                libusb_close(handle);
            }
        }

phil's avatar
phil committed
454
455
456
        if (config != NULL){
            libusb_free_config_descriptor(config);
        }
phil's avatar
phil committed
457
458
459
460
461
462
463
464
465
466
467
    }

    // if not found any devices free list;
    if (o == 0){
        free(*hiddevs);
        *hiddevs = NULL;
    }

    return o;
}

phil's avatar
phil committed
468
static void hid_free_device(hid_device_t * hiddev)
phil's avatar
phil committed
469
{
phil's avatar
phil committed
470
    if (!hiddev)
phil's avatar
phil committed
471
472
        return;

phil's avatar
phil committed
473
474
    if (hiddev->serial_string)
        free(hiddev->serial_string);
phil's avatar
phil committed
475

phil's avatar
phil committed
476
477
    if (hiddev->manufacturer_string)
        free(hiddev->manufacturer_string);
phil's avatar
phil committed
478

phil's avatar
phil committed
479
480
    if (hiddev->product_string)
        free(hiddev->product_string);
phil's avatar
phil committed
481
482
//
//        if (dev->config) {
phil's avatar
phil committed
483
484
//            libusb_free_config_descriptor(dev->config);
//        }
phil's avatar
phil committed
485

phil's avatar
phil committed
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
    libusb_unref_device(hiddev->dev);
    free(hiddev);
}

static void hid_free_device_list(hid_device_t ** hiddevs)
{
    if (!hiddevs){
        return;
    }

    hid_device_t *dev;
    int i = 0;

    while( (dev = hiddevs[i++]) != NULL){
        hid_free_device(dev);
phil's avatar
phil committed
501
502
503
    }
}

phil's avatar
phil committed
504
505
506
507
508
509
510
511
512
513
typedef struct {
    uint16_t vendor;
    uint16_t product;
    char serial[256];
    char * serialptr;
    uint8_t usage_page;
    uint8_t usage;
} filter_args_t;

static void get_filter_args(filter_args_t * args, int argc, t_atom *argv)
phil's avatar
phil committed
514
{
phil's avatar
phil committed
515
516
    assert(args);
    assert(argv);
phil's avatar
phil committed
517

phil's avatar
phil committed
518
519
520
521
522
    args->vendor = 0;
    args->product = 0;
    args->serialptr = NULL;
    args->usage_page = 0;
    args->usage = 0;
phil's avatar
phil committed
523

phil's avatar
phil committed
524
525
526
527
    if (argc > 0){
        for(int i = 0, inc; i < argc; i += inc){
            char opt[256];
            inc = 0;
phil's avatar
phil committed
528

phil's avatar
phil committed
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
            atom_string(argv + i, opt, sizeof(opt));

            if (strcmp(opt, "vendorid") == 0){
                if (i+1 >= argc){
                    error("missing argument");
                    return;
                }
                inc = 2;

                t_float f = atom_getfloat(argv + i + 1);

                if (f <= 0.0 || fmodf(f, 1.0) > 0.0){
                    error("vendorid must be integer > 0");
                    return;
                }
phil's avatar
phil committed
544
                args->vendor = f;
phil's avatar
phil committed
545
546
547
548
549
550
551
552
553
554
555
556
557
558

            } else if (strcmp(opt, "productid") == 0){
                if (i+1 >= argc){
                    error("missing argument");
                    return;
                }
                inc = 2;

                t_float f = atom_getfloat(argv + i + 1);

                if (f <= 0.0 || fmodf(f, 1.0) > 0.0){
                    error("productidid must be integer > 0");
                    return;
                }
phil's avatar
phil committed
559
560
                args->product = f;

phil's avatar
phil committed
561
562
563
564
565
566
567
            } else if (strcmp(opt, "serial") == 0){
                if (i+1 >= argc){
                    error("missing argument");
                    return;
                }
                inc = 2;

phil's avatar
phil committed
568
569
570
571
572
                atom_string(argv + i + 1, args->serial, sizeof(args->serial));

                if (strlen(args->serial)){
                    args->serialptr = args->serial;
                }
phil's avatar
phil committed
573
574
575
576
577
578
579
580
581
582
583
584
585
586

            } else if (strcmp(opt, "usage_page") == 0){
                if (i+1 >= argc){
                    error("missing argument");
                    return;
                }
                inc = 2;

                t_float f = atom_getfloat(argv + i + 1);

                if (f <= 0.0 || fmodf(f, 1.0) > 0.0){
                    error("usage_page must be integer > 0");
                    return;
                }
phil's avatar
phil committed
587
                args->usage_page = f;
phil's avatar
phil committed
588
589
590
591
592
593
594
595
596
597
598
599
600
            } else if (strcmp(opt, "usage") == 0){
                if (i+1 >= argc){
                    error("missing argument");
                    return;
                }
                inc = 2;

                t_float f = atom_getfloat(argv + i + 1);

                if (f <= 0.0 || fmodf(f, 1.0) > 0.0){
                    error("usage must be integer > 0");
                    return;
                }
phil's avatar
phil committed
601
                args->usage = f;
phil's avatar
phil committed
602
603
604
            } else if (strcmp(opt, "mouse") == 0) {
                inc = 1 ;

phil's avatar
phil committed
605
606
                args->usage_page = 1;
                args->usage = 2;
phil's avatar
phil committed
607
608
609
610

            } else if (strcmp(opt, "joystick") == 0) {
                inc = 1 ;

phil's avatar
phil committed
611
612
                args->usage_page = 1;
                args->usage = 4;
phil's avatar
phil committed
613
614
615
616
617
618
619
            }  else {
                error("Unrecognized option %s", opt);
                return;
            }

            assert(inc > 0); // so you forgot to set inc(rement)
        }
phil's avatar
phil committed
620
    }
phil's avatar
phil committed
621
622
623
624
625
626
627
628
629
}

static void hid_cmd_list(hid_t *hid, t_symbol *s, int argc, t_atom *argv)
{
    UNUSED(s);

    filter_args_t args;

    get_filter_args(&args, argc, argv);
phil's avatar
phil committed
630
631

    hid_device_t ** hiddevs;
phil's avatar
phil committed
632
    ssize_t cnt = hid_get_device_list(hid, &hiddevs, args.vendor, args.product, args.serialptr, args.usage_page, args.usage, 0);
phil's avatar
phil committed
633
634
635
636
637

    if (cnt < 0){
        return;
    }

phil's avatar
phil committed
638
//    post("list all devices now: %d", cnt);
phil's avatar
phil committed
639
640

    for(int i = 0; i < cnt; i++){
phil's avatar
phil committed
641
642
        t_atom orgv[7];
        int orgc = sizeof(orgv) / sizeof(t_atom);
phil's avatar
phil committed
643

phil's avatar
phil committed
644
645
646
647
648
        SETFLOAT(orgv, hiddevs[i]->hid_usage_page);
        SETFLOAT(orgv+1, hiddevs[i]->hid_usage);
        SETFLOAT(orgv+2, hiddevs[i]->desc.idVendor);
        SETFLOAT(orgv+3, hiddevs[i]->desc.idProduct);

phil's avatar
phil committed
649
650
651
652
        char serial[256];
        char man[256];
        char product[256];

phil's avatar
phil committed
653
        if (hiddevs[i]->serial_string) {
phil's avatar
phil committed
654
655
            wchar2char(hiddevs[i]->serial_string, serial, sizeof(serial));
            SETSYMBOL(orgv + 4, gensym(serial));
phil's avatar
phil committed
656
657
658
659
660
        } else {
            SETSYMBOL(orgv + 4, gensym("-"));
        }

        if (hiddevs[i]->manufacturer_string) {
phil's avatar
phil committed
661
662
            wchar2char(hiddevs[i]->manufacturer_string, man, sizeof(man));
            SETSYMBOL(orgv + 5, gensym(man));
phil's avatar
phil committed
663
664
665
666
667
        } else {
            SETSYMBOL(orgv + 5, gensym("-"));
        }

        if (hiddevs[i]->product_string) {
phil's avatar
phil committed
668
669
            wchar2char(hiddevs[i]->product_string, product, sizeof(product));
            SETSYMBOL(orgv + 6, gensym(product));
phil's avatar
phil committed
670
671
672
673
        } else {
            SETSYMBOL(orgv + 6, gensym("-"));
        }

phil's avatar
phil committed
674
        outlet_anything(hid->out, gensym("device"), orgc, orgv);
phil's avatar
phil committed
675
676
677
    }

    hid_free_device_list(hiddevs);
phil's avatar
phil committed
678
679
680
681
682
}


static void hid_cmd_open(hid_t *hid, t_symbol *s, int argc, t_atom *argv)
{
phil's avatar
phil committed
683
684
685
    UNUSED(s);

    if (hid->handle){
phil's avatar
phil committed
686
        error("already open");
phil's avatar
phil committed
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
        return;
    }

    filter_args_t args;

    get_filter_args(&args, argc, argv);

    hid_device_t ** hiddevs;
    ssize_t cnt = hid_get_device_list(hid, &hiddevs, args.vendor, args.product, args.serialptr, args.usage_page, args.usage, 1);

    if (cnt < 0){
        return;
    }

    if (cnt == 0){
        error("No such device");
        return;
    }


phil's avatar
phil committed
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721


    if (usbhid_map_parse_desc(&hid->hid_map, hiddevs[0]->report_desc, hiddevs[0]->report_desc_len)){
        error("failed to parse HID descriptor");
        hid_free_device_list(hiddevs);
        return;
    }

    // TODO get default / validate report id

    if (usbhid_map_get_report_ids(hid->hid_map, Input(0), &hid->report_id, 1) == 0){
        error("no input reports");
    }

    post("report_id = %d", hid->report_id);
phil's avatar
phil committed
722

phil's avatar
phil committed
723

phil's avatar
phil committed
724
725
726
727
    hid->handle = hid_open(hiddevs[0]->desc.idVendor, hiddevs[0]->desc.idProduct, hiddevs[0]->serial_string);
    if (hid->handle < 0){
        error("failed to open");
        hid_free_device_list(hiddevs);
phil's avatar
phil committed
728
729
730
        return;
    }

phil's avatar
phil committed
731
732
    hid->poll_ms = 0;

phil's avatar
phil committed
733
734
735
736
737
738
739
    hid->hiddev = hiddevs[0];

    // just free list (not found device)
    free(hiddevs);

//    outlet_symbol(hid->out, gensym("opened"));
    outlet_anything(hid->out, gensym("opened"), 0, NULL);
phil's avatar
phil committed
740
741
742
743
}

static void hid_cmd_close(hid_t *hid, t_symbol *s, int argc, t_atom *argv)
{
phil's avatar
phil committed
744
745
746
747
748
    UNUSED(s);
    UNUSED(argc);
    UNUSED(argv);

    if (hid->handle == NULL) {
phil's avatar
phil committed
749
        error("already closed");
phil's avatar
phil committed
750
    } else {
phil's avatar
phil committed
751
        hid_shutdown(hid);
phil's avatar
phil committed
752
753
754
    }

    outlet_anything(hid->out, gensym("closed"), 0, NULL);
phil's avatar
phil committed
755
756
}

phil's avatar
phil committed
757
758
759
760
761
762
763
764
765
766
767
768
769
770
static int hid_read_report(hid_t * hid)
{
    uint8_t data[128];

//    size_t r = hid_read(hid->handle, data, sizeof(data));
    size_t r = hid_read_timeout(hid->handle, data, sizeof(data), hid->poll_ms ? hid->poll_ms : 0);

    if (r > 0){
        post("got report!");
    }

    return r > 0;
}

phil's avatar
phil committed
771
772
773
774
775
776
777
static void hid_cmd_bang(hid_t *hid)
{
    if (!hid->handle){
        error("device not open");
        return;
    }

phil's avatar
phil committed
778
779
780
781
    if (hid->poll_ms){
        error("polling already in process - it's pointless to bang");
        return;
    }
phil's avatar
phil committed
782

phil's avatar
phil committed
783
    hid_set_nonblocking(hid->handle, 1);
phil's avatar
phil committed
784

phil's avatar
phil committed
785
    while( hid_read_report(hid) );
phil's avatar
phil committed
786
787
788
}


phil's avatar
phil committed
789
790
static void * polling_thread_handler(void * ptr)
{
phil's avatar
phil committed
791
792
    hid_t * hid = ptr;

phil's avatar
phil committed
793
    hid_set_nonblocking(hid->handle, 0);
phil's avatar
phil committed
794
795
796

    while(hid->poll_ms){

phil's avatar
phil committed
797
        hid_read_report(hid);
phil's avatar
phil committed
798
799
800
801
802

        usleep(1000*hid->poll_ms);
    }

    return NULL;
phil's avatar
phil committed
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
}

static void hid_cmd_poll(hid_t *hid, t_symbol *s, int argc, t_atom *argv)
{
    UNUSED(s);

    if (!hid->handle){
        error("device not open");
        return;
    }

    if (argc != 1){
        error("requires an argument");
        return;
    }

    t_float p = atom_getfloat(argv);

phil's avatar
phil committed
821
822
823
824
825
826
827
828
829
    if (p < 0.0 || 1000.0 < p || fmod(p, 1.0) != 0.0){
        error("must be integer >= 0 <= 1000");
        return;
    }

    uint32_t poll_ms = p;

    if (hid->poll_ms && poll_ms == 0){
        hid->poll_ms = 0;
phil's avatar
phil committed
830
//        pthread_kill(hid->polling_thread, SIGCONT);
phil's avatar
phil committed
831
832
833
834
835
        pthread_join(hid->polling_thread, NULL);
    }

    // do nothing else if polling should stop
    if (poll_ms == 0){
phil's avatar
phil committed
836
837
838
        return;
    }

phil's avatar
phil committed
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
    hid->poll_ms = poll_ms;

    int r = pthread_create(&hid->polling_thread, NULL, polling_thread_handler, hid);

    if (r){
        error("pthread_create(): %d", r);
        hid->poll_ms = 0;
        return;
    }
}

static void hid_cmd_report_id(hid_t *hid, t_symbol *s, int argc, t_atom *argv)
{
    UNUSED(s);

    if (argc == 0){
        // do nothing
phil's avatar
phil committed
856
857
858
        t_atom atom_report_id;
        SETFLOAT(&atom_report_id, hid->report_id);
        outlet_anything(hid->out, gensym("report_id"), 1, &atom_report_id);
phil's avatar
phil committed
859
860
    } else if (argc == 1){
        hid->report_id = atom_getfloat(argv);
phil's avatar
phil committed
861
862
        //TODO set report
        return;
phil's avatar
phil committed
863
864
    } else {
        error("invalid argument count");
865
        return;
phil's avatar
phil committed
866
    }
phil's avatar
phil committed
867

phil's avatar
phil committed
868
}