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 }