1 #include "dev.h"
2 
3 #include "hid_utility.h"
4 
5 #include <hidapi.h>
6 
7 #include <getopt.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12 
13 /**
14  * @brief Print information of devices
15  *
16  * When vendorid and productid are 0, then all are listed.
17  * If they are specified, information about all usagepages and interfaces of the respective device are listed
18  *
19  * @param vendorid
20  * @param productid
21  */
print_devices(unsigned short vendorid,unsigned short productid)22 static void print_devices(unsigned short vendorid, unsigned short productid)
23 {
24     struct hid_device_info *devs, *cur_dev;
25 
26     devs    = hid_enumerate(vendorid, productid);
27     cur_dev = devs;
28     while (cur_dev) {
29         printf("Device Found\n  VendorID: %#06hx\n ProductID: %#06hx\n  path: %s\n  serial_number: %ls",
30             cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
31         printf("\n");
32         printf("  Manufacturer: %ls\n", cur_dev->manufacturer_string);
33         printf("  Product:      %ls\n", cur_dev->product_string);
34         printf("  Interface:    %d\n", cur_dev->interface_number);
35         printf("  Usage-Page: 0x%hx Usageid: 0x%hx\n", cur_dev->usage_page, cur_dev->usage);
36         printf("\n");
37         cur_dev = cur_dev->next;
38     }
39     hid_free_enumeration(devs);
40 }
41 
42 /**
43  * @brief Accepts textual input and converts them to a sendable buffer
44  *
45  * Parses data like "0xff, 123, 0xb" and converts them to an array of len 3
46  *
47  * @param input string
48  * @param dest destination array
49  * @param len max dest length
50  * @return int amount of data converted
51  */
get_data_from_parameter(char * input,char * dest,size_t len)52 static int get_data_from_parameter(char* input, char* dest, size_t len)
53 {
54     const char* delim = " ,{}\n\r";
55 
56     size_t sz = strlen(input);
57     char* str = (char*)malloc(sz + 1);
58     strcpy(str, input);
59 
60     // For each token in the string, parse and store in buf[].
61     char* token = strtok(input, delim);
62     int i       = 0;
63     while (token) {
64         char* endptr;
65         long int val = strtol(token, &endptr, 0);
66 
67         if (i >= len)
68             return -1;
69 
70         dest[i++] = val;
71         token     = strtok(NULL, delim);
72     }
73 
74     free(str);
75     return i;
76 }
77 
78 /**
79  * @brief check if number inside range
80  *
81  * @param number to check
82  * @param low lower bound
83  * @param high upper bound
84  * @return int 0 if in range; 1 if too high; -1 if too low
85  */
86 
check_range(int number,int low,int high)87 int check_range(int number, int low, int high)
88 {
89     if (number > high)
90         return 1;
91 
92     if (number < low)
93         return -1;
94 
95     return 0;
96 }
97 
98 /**
99  * @brief Accept an string input like 123:456 and splits them into two ids
100  *
101  * @param input input string
102  * @param id1 the first (left) id
103  * @param id2 the secound it
104  * @return int 0 if successfull, or 1 if not two ids provided
105  */
get_two_ids(char * input,int * id1,int * id2)106 static int get_two_ids(char* input, int* id1, int* id2)
107 {
108     const char* delim = " :.,";
109 
110     size_t sz = strlen(input);
111     char* str = (char*)malloc(sz + 1);
112     strcpy(str, input);
113 
114     char* token = strtok(input, delim);
115     int i       = 0;
116     while (token) {
117         char* endptr;
118         long int val = strtol(token, &endptr, 0);
119 
120         if (i == 0)
121             *id1 = val;
122         else if (i == 1)
123             *id2 = val;
124 
125         i++;
126         token = strtok(NULL, delim);
127     }
128 
129     free(str);
130 
131     if (i != 2) // not exactly supplied two ids
132         return 1;
133 
134     return 0;
135 }
136 
print_help()137 static void print_help()
138 {
139     printf("HeadsetControl Developer menu. Take caution.\n\n");
140 
141     printf("Usage\n");
142     printf("  headsetcontrol --dev -- PARAMETERS\n");
143     printf("\n");
144 
145     printf("Parameters\n");
146     printf("  --list\n"
147            "\tLists all HID devices when used without --device\n"
148            "\tWhen used with --device, searches for a specific device, and prints all interfaces and usageids\n");
149     printf("  --device VENDORID:PRODUCTID\n"
150            "\te.g. --device 1234:5678 or --device 0x4D2:0x162E\n"
151            "\tRequired for most parameters\n");
152     printf("  --interface INTERFACEID\n"
153            "\tWhich interface of the device to use. 0 is default\n");
154     printf("  --usage USAGEPAGE:USAGEID\n"
155            "\tSpecifies an usage-page and usageid. 0:0 is default\n"
156            "\tImportant for Windows. Ignored on Mac/Linux\n");
157     printf("\n");
158 
159     printf("  --send DATA\n"
160            "\tSend data to specified device\n");
161     printf("  --send-report DATA\n"
162            "\tSend a feature report to the device. The first byte is the reportid\n");
163     printf("  --sleep microsecounds\n"
164            "\tSleep for x microsecounds after sending\n");
165     printf("  --receive\n"
166            "\tTry to receive data from device. Can be combined with --timeout\n");
167     printf("  --timeout\n"
168            "\t--timeout in millisecounds for --receive\n");
169     printf("  --receive-report [REPORTID]\n"
170            "\tTry to receive a report for REPORTID. 0 is the default id\n");
171     printf("\n");
172 
173     printf("  --dev-help\n"
174            "\tThis menu\n");
175     printf("\n");
176 
177     printf("HINTS\n");
178     printf("\tsend and receive can be combined\n");
179     printf("\t--send does not return anything on success and is always executed first\n");
180     printf("\tDATA can be specified as single bytes, either as decimal or hex, seperated by spaces or commas or newlines\n");
181     printf("\n");
182 
183     printf("EXAMPLEs\n");
184     printf("  headsetcontrol --dev -- --list\n");
185     printf("  headsetcontrol --dev -- --list --device 0x1b1c:0x1b27\n");
186     printf("  headsetcontrol --dev -- --device 0x1b1c:0x1b27 --send \"0xC9, 0x64\" --receive --timeout 10\n");
187 }
188 
dev_main(int argc,char * argv[])189 int dev_main(int argc, char* argv[])
190 {
191     int vendorid    = 0;
192     int productid   = 0;
193     int interfaceid = 0;
194     int usagepage   = 0;
195     int usageid     = 0;
196 
197     int send         = 0;
198     int send_feature = 0;
199 
200     int sleep = -1;
201 
202     int receive       = 0;
203     int receivereport = 0;
204 
205     int timeout = 0;
206 
207     int print_deviceinfo = 0;
208 
209 #define BUFFERLENGTH 1024
210     char* sendbuffer       = calloc(BUFFERLENGTH, sizeof(char));
211     char* sendreportbuffer = calloc(BUFFERLENGTH, sizeof(char));
212 
213     unsigned char* receivebuffer       = malloc(sizeof(char) * BUFFERLENGTH);
214     unsigned char* receivereportbuffer = malloc(sizeof(char) * BUFFERLENGTH);
215 
216     struct option opts[] = {
217         { "device", required_argument, NULL, 'd' },
218         { "interface", required_argument, NULL, 'i' },
219         { "usage", required_argument, NULL, 'u' },
220         { "list", no_argument, NULL, 'l' },
221         { "send", required_argument, NULL, 's' },
222         { "send-feature", required_argument, NULL, 'f' },
223         { "sleep", required_argument, NULL, 'm' },
224         { "receive", no_argument, NULL, 'r' },
225         { "receive-feature", optional_argument, NULL, 'g' },
226         { "timeout", required_argument, NULL, 't' },
227         { "dev-help", no_argument, NULL, 'h' },
228         { 0, 0, 0, 0 }
229     };
230 
231     int option_index = 0;
232 
233     optind = 1; // getopt_long requires resetting this variable, we already used it in main()
234 
235     int c;
236     while ((c = getopt_long(argc, argv, "d:i:lu:s:m:f:rgth", opts, &option_index)) != -1) {
237         switch (c) {
238         case 'd': { // --device vendorid:productid
239             int ret = get_two_ids(optarg, &vendorid, &productid);
240             if (ret) {
241                 fprintf(stderr, "You must supply a vendorid:productid pair in the --device / d parameter.\n\tE.g. --device 1234:5678 or --device 0x4D2:0x162E\n");
242                 return 1;
243             }
244 
245             if (check_range(vendorid, 1, 65535) != 0 || check_range(productid, 1, 65535) != 0) {
246                 fprintf(stderr, "Vendor and Productid must be between 1 and 65535 or 0x1 and 0xffff\n");
247                 return 1;
248             }
249 
250             break;
251         }
252         case 'i': { // --interface interfaceid
253             interfaceid = strtol(optarg, NULL, 10);
254 
255             if (interfaceid < 0) {
256                 fprintf(stderr, "The interfaceid you supplied is invalid\n");
257                 return 1;
258             }
259 
260             break;
261         }
262         case 'u': { // --usage usagepage:usageid
263             int ret = get_two_ids(optarg, &usagepage, &usageid);
264             if (ret) {
265                 fprintf(stderr, "You must supply a usagepage:usageid pair in the --usage / u parameter.\n\tE.g. --usage 1234:5678 or --usage 0x4D2:0x162E\n");
266                 return 1;
267             }
268 
269             if (check_range(vendorid, 1, 65535) != 0 || check_range(productid, 1, 65535) != 0) {
270                 fprintf(stderr, "Usagepage and Usageid must be between 1 and 65535 or 0x1 and 0xffff\n");
271                 return 1;
272             }
273 
274             break;
275         }
276         case 's': { // --send string
277             int size = get_data_from_parameter(optarg, sendbuffer, BUFFERLENGTH);
278 
279             if (size < 0) {
280                 fprintf(stderr, "Data to send larger than %d\n", BUFFERLENGTH);
281                 return 1;
282             }
283 
284             if (size == 0) {
285                 fprintf(stderr, "No data specified to --send\n");
286                 return 1;
287             }
288 
289             send = size;
290 
291             break;
292         }
293         case 'f': { // --send-feature string
294             int size = get_data_from_parameter(optarg, sendreportbuffer, BUFFERLENGTH);
295 
296             if (size < 0) {
297                 fprintf(stderr, "Data to send for feature report larger than %d\n", BUFFERLENGTH);
298                 return 1;
299             }
300 
301             if (size == 0) {
302                 fprintf(stderr, "No data specified to --send-feature\n");
303                 return 1;
304             }
305 
306             send_feature = size;
307 
308             break;
309         }
310         case 'm': { // --sleep
311             sleep = strtol(optarg, NULL, 10);
312 
313             if (sleep < 0) {
314                 fprintf(stderr, "--sleep must be positive\n");
315                 return 1;
316             }
317 
318             break;
319         }
320         case 'r': { // --receive
321             receive = 1;
322             break;
323         }
324         case 'g': { // --receive-feature [reportid]
325             int reportid = 0;
326             reportid     = strtol(optarg, NULL, 10);
327 
328             if (reportid > 255 || reportid < 0) {
329                 fprintf(stderr, "The reportid for --receive-feature must be smaller than 255\n");
330                 return 1;
331             }
332 
333             // the first byte of the receivereport buffer must be set for hidapi
334             receivereportbuffer[0] = reportid;
335 
336             break;
337         }
338         case 't': { // --timeout timeout
339             timeout = strtol(optarg, NULL, 10);
340 
341             if (timeout < 0) {
342                 fprintf(stderr, "--timeout cannot be smaller than 0\n");
343                 return 1;
344             }
345             break;
346         }
347         case 'l': { // --list [vendorid:productid]
348             print_deviceinfo = 1;
349             break;
350         }
351         case 'h': {
352             print_help();
353             break;
354         }
355         default:
356             printf("Invalid argument %c\n", c);
357         }
358     }
359 
360     if (argc <= 1)
361         print_help();
362 
363     if (print_deviceinfo)
364         print_devices(vendorid, productid);
365 
366     if (!(send || send_feature || receive || receivereport))
367         goto cleanup;
368 
369     if (!vendorid || !productid) {
370         fprintf(stderr, "You must supply a vendor/productid pair via the parameter --device\n");
371         return 1;
372     }
373 
374     char* hid_path            = get_hid_path(vendorid, productid, interfaceid, usagepage, usageid);
375     hid_device* device_handle = NULL;
376 
377     if (hid_path == NULL) {
378         fprintf(stderr, "Could not find a device with this parameters:\n");
379         fprintf(stderr, "\t Vendor (%#x) Product (%#x) Interface (%#x) UsagePage (%#x) UsageID (%#x)\n",
380             vendorid, productid, interfaceid, usagepage, usageid);
381     }
382 
383     if (send && send_feature) {
384         fprintf(stderr, "Warning: --send and --send-feature specified at the same time\n");
385     }
386 
387     if (receive && receivereport) {
388         fprintf(stderr, "Warning: --receive and --receive-feature specified at the same time\n");
389     }
390 
391     device_handle = hid_open_path(hid_path);
392     if (device_handle == NULL) {
393         fprintf(stderr, "Couldn't open device.\n");
394         terminate_hid(&device_handle, &hid_path);
395         return 1;
396     }
397 
398     if (send) {
399         int ret = hid_write(device_handle, (const unsigned char*)sendbuffer, send);
400 
401         if (ret < 0) {
402             fprintf(stderr, "Failed to send data. Error: %d: %ls\n", ret, hid_error(device_handle));
403             terminate_hid(&device_handle, &hid_path);
404             return 1;
405         }
406     }
407 
408     if (send_feature) {
409         int ret = hid_send_feature_report(device_handle, (const unsigned char*)sendreportbuffer, send_feature);
410 
411         if (ret < 0) {
412             fprintf(stderr, "Failed to send data. Error: %d: %ls\n", ret, hid_error(device_handle));
413             terminate_hid(&device_handle, &hid_path);
414             return 1;
415         }
416     }
417 
418     if (sleep >= 0) { // also allow 0 as a minimum sleep
419         usleep(sleep * 1000);
420     }
421 
422     if (receive) {
423         int read = hid_read_timeout(device_handle, receivebuffer, BUFFERLENGTH, timeout);
424 
425         if (read < 0) {
426             fprintf(stderr, "Failed to read. Error: %d: %ls\n", read, hid_error(device_handle));
427             terminate_hid(&device_handle, &hid_path);
428             return 1;
429         } else if (read == 0) {
430             fprintf(stderr, "No data to read\n");
431         } else {
432             for (int i = 0; i < read; i++) {
433                 printf("%#04x ", receivebuffer[i]);
434             }
435             printf("\n");
436         }
437     }
438 
439     if (receivereport) {
440         int read = hid_get_feature_report(device_handle, receivereportbuffer, BUFFERLENGTH);
441 
442         if (read < 0) {
443             fprintf(stderr, "Failed to read. Error: %d: %ls\n", read, hid_error(device_handle));
444             terminate_hid(&device_handle, &hid_path);
445             return 1;
446         } else if (read == 0) {
447             fprintf(stderr, "No data to read\n");
448         } else {
449             for (int i = 0; i < read; i++) {
450                 printf("%#04x ", receivereportbuffer[i]);
451             }
452             printf("\n");
453         }
454     }
455 
456     terminate_hid(&device_handle, &hid_path);
457 
458 cleanup:
459 
460     free(receivereportbuffer);
461     free(receivebuffer);
462     free(sendreportbuffer);
463     free(sendbuffer);
464 
465     return 0;
466 }