1 /***
2 Copyright (C) 2016-2018 Denis Arnst (Sapd) <https://github.com/Sapd>
3
4 This file is part of HeadsetControl.
5
6 HeadsetControl is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 HeadsetControl is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with HeadsetControl. If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include "dev.h"
21 #include "device_registry.h"
22 #include "hid_utility.h"
23 #include "utility.h"
24
25 #include <hidapi.h>
26
27 #include <assert.h>
28 #include <getopt.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33
34 // 0=false; 1=true
35 static int short_output = 0;
36
37 /// printf only when short output not specified
38 #define PRINT_INFO(...) \
39 { \
40 if (!short_output) { \
41 printf(__VA_ARGS__); \
42 } \
43 }
44
45 /**
46 * This function iterates through all HID devices.
47 *
48 * @return 0 when a supported device is found
49 */
find_device(struct device * device_found)50 static int find_device(struct device* device_found)
51 {
52 struct hid_device_info* devs;
53 struct hid_device_info* cur_dev;
54 int found = -1;
55 devs = hid_enumerate(0x0, 0x0);
56 cur_dev = devs;
57 while (cur_dev) {
58 found = get_device(device_found, cur_dev->vendor_id, cur_dev->product_id);
59
60 if (found == 0) {
61 PRINT_INFO("Found %s!\n", device_found->device_name);
62 break;
63 }
64
65 cur_dev = cur_dev->next;
66 }
67 hid_free_enumeration(devs);
68
69 return found;
70 }
71
72 /**
73 * @brief Generates udev rules, and prints them to STDOUT
74 *
75 * Goes through the implementation of all devices, and generates udev rules for use by Linux systems
76 */
print_udevrules()77 static void print_udevrules()
78 {
79 int i = 0;
80 struct device* device_found;
81
82 printf("ACTION!=\"add|change\", GOTO=\"headset_end\"\n");
83 printf("\n");
84
85 while (iterate_devices(i++, &device_found) == 0) {
86 printf("# %s\n", device_found->device_name);
87
88 for (int i = 0; i < device_found->numIdProducts; i++)
89 printf("KERNEL==\"hidraw*\", SUBSYSTEM==\"hidraw\", ATTRS{idVendor}==\"%04x\", ATTRS{idProduct}==\"%04x\", TAG+=\"uaccess\"\n",
90 (unsigned int)device_found->idVendor, (unsigned int)device_found->idProductsSupported[i]);
91
92 printf("\n");
93 }
94
95 printf("LABEL=\"headset_end\"\n");
96 }
97
98 /**
99 * @brief Checks if an existing connection exists, and either uses it, or closes it and creates a new one
100 *
101 * A device - depending on the feature - needs differend Endpoints/Connections
102 * Instead of opening and keeping track of multiple connections, we close and open connections no demand
103 *
104 *
105 *
106 * @param existing_hid_path an existing connection path or NULL if none yet
107 * @param device_handle an existing device handle or NULL if none yet
108 * @param device headsetcontrol struct, containing vendor and productid
109 * @param cap which capability to use, to determine interfaceid and usageids
110 * @return 0 if successfull, or hid error code
111 */
dynamic_connect(char ** existing_hid_path,hid_device * device_handle,struct device * device,enum capabilities cap)112 static hid_device* dynamic_connect(char** existing_hid_path, hid_device* device_handle,
113 struct device* device, enum capabilities cap)
114 {
115 // Generate the path which is needed
116 char* hid_path = get_hid_path(device->idVendor, device->idProduct,
117 device->capability_details[cap].interface, device->capability_details[cap].usagepage, device->capability_details[cap].usageid);
118
119 if (!hid_path) {
120 fprintf(stderr, "Requested/supported HID device not found or system error.\n");
121 fprintf(stderr, " HID Error: %S\n", hid_error(NULL));
122 return NULL;
123 }
124
125 // A connection already exists
126 if (device_handle != NULL) {
127 // The connection which exists is the same we need, so simply return it
128 if (strcmp(*existing_hid_path, hid_path) == 0) {
129 return device_handle;
130 } else { // if its not the same, and already one connection is open, close it so we can make a new one
131 hid_close(device_handle);
132 }
133 }
134
135 free(*existing_hid_path);
136
137 device_handle = hid_open_path(hid_path);
138 if (device_handle == NULL) {
139 fprintf(stderr, "Failed to open requested device.\n");
140 fprintf(stderr, " HID Error: %S\n", hid_error(NULL));
141
142 *existing_hid_path = NULL;
143 return NULL;
144 }
145
146 *existing_hid_path = hid_path;
147 return device_handle;
148 }
149
150 /**
151 * @brief Handle a requested feature
152 *
153 * @param device_found the headset to use
154 * @param device_handle points to an already open device_handle (if connection already exists) or points to null
155 * @param hid_path points to an already used path used to connect, or points to null
156 * @param cap requested feature
157 * @param param first parameter of the feature
158 * @return int 0 on success, some other number otherwise
159 */
handle_feature(struct device * device_found,hid_device ** device_handle,char ** hid_path,enum capabilities cap,int param)160 static int handle_feature(struct device* device_found, hid_device** device_handle, char** hid_path, enum capabilities cap, int param)
161 {
162 // Check if the headset implements the requested feature
163 if ((device_found->capabilities & B(cap)) == 0) {
164 fprintf(stderr, "Error: This headset doesn't support %s\n", capabilities_str[cap]);
165 return 1;
166 }
167
168 *device_handle = dynamic_connect(hid_path, *device_handle,
169 device_found, cap);
170
171 if (!device_handle | !(*device_handle))
172 return 1;
173
174 int ret;
175
176 switch (cap) {
177 case CAP_SIDETONE:
178 ret = device_found->send_sidetone(*device_handle, param);
179 break;
180
181 case CAP_BATTERY_STATUS:
182 ret = device_found->request_battery(*device_handle);
183
184 if (ret < 0)
185 break;
186 else if (ret == BATTERY_CHARGING)
187 short_output ? printf("-1") : printf("Battery: Charging\n");
188 else if (ret == BATTERY_UNAVAILABLE)
189 short_output ? printf("-2") : printf("Battery: Unavailable\n");
190 else
191 short_output ? printf("%d", ret) : printf("Battery: %d%%\n", ret);
192
193 break;
194
195 case CAP_NOTIFICATION_SOUND:
196 ret = device_found->notifcation_sound(*device_handle, param);
197 break;
198
199 case CAP_LIGHTS:
200 ret = device_found->switch_lights(*device_handle, param);
201 break;
202
203 case CAP_INACTIVE_TIME:
204 ret = device_found->send_inactive_time(*device_handle, param);
205
206 if (ret < 0)
207 break;
208
209 PRINT_INFO("Successfully set inactive time to %d minutes!\n", param);
210 break;
211
212 case CAP_CHATMIX_STATUS:
213 ret = device_found->request_chatmix(*device_handle);
214
215 if (ret < 0)
216 break;
217
218 short_output ? printf("%d", ret) : printf("Chat-Mix: %d\n", ret);
219 break;
220
221 case CAP_VOICE_PROMPTS:
222 ret = device_found->switch_voice_prompts(*device_handle, param);
223 break;
224
225 case CAP_ROTATE_TO_MUTE:
226 ret = device_found->switch_rotate_to_mute(*device_handle, param);
227 break;
228
229 case NUM_CAPABILITIES:
230 ret = -99; // silence warning
231
232 assert(0);
233 break;
234 }
235
236 if (ret < 0) {
237 fprintf(stderr, "Failed to set %s. Error: %d: %ls\n", capabilities_str[cap], ret, hid_error(*device_handle));
238 return 1;
239 }
240
241 PRINT_INFO("Success!\n");
242 return 0;
243 }
244
main(int argc,char * argv[])245 int main(int argc, char* argv[])
246 {
247 int c;
248
249 int sidetone_loudness = -1;
250 int request_battery = 0;
251 int request_chatmix = 0;
252 int notification_sound = -1;
253 int lights = -1;
254 int inactive_time = -1;
255 int voice_prompts = -1;
256 int rotate_to_mute = -1;
257 int print_capabilities = -1;
258 int dev_mode = 0;
259
260 struct option opts[] = {
261 { "battery", no_argument, NULL, 'b' },
262 { "capabilities", no_argument, NULL, '?' },
263 { "chatmix", no_argument, NULL, 'm' },
264 { "dev", no_argument, NULL, 0 },
265 { "help", no_argument, NULL, 'h' },
266 { "inactive-time", required_argument, NULL, 'i' },
267 { "light", required_argument, NULL, 'l' },
268 { "notificate", required_argument, NULL, 'n' },
269 { "rotate-to-mute", required_argument, NULL, 'r' },
270 { "sidetone", required_argument, NULL, 's' },
271 { "short-output", no_argument, NULL, 'c' },
272 { "voice-prompt", required_argument, NULL, 'v' },
273 { 0, 0, 0, 0 }
274 };
275
276 int option_index = 0;
277
278 // Init all information of supported devices
279 init_devices();
280
281 while ((c = getopt_long(argc, argv, "bchi:l:mn:r:s:uv:?", opts, &option_index)) != -1) {
282 switch (c) {
283 case 'b':
284 request_battery = 1;
285 break;
286 case 'c':
287 short_output = 1;
288 break;
289 case 'i':
290 inactive_time = strtol(optarg, NULL, 10);
291
292 if (inactive_time > 90 || inactive_time < 0) {
293 printf("Usage: %s -s 0-90, 0 is off\n", argv[0]);
294 return 1;
295 }
296 break;
297 case 'l':
298 lights = strtol(optarg, NULL, 10);
299 if (lights < 0 || lights > 1) {
300 printf("Usage: %s -l 0|1\n", argv[0]);
301 return 1;
302 }
303 break;
304 case 'm':
305 request_chatmix = 1;
306 break;
307 case 'n': // todo
308 notification_sound = strtol(optarg, NULL, 10);
309
310 if (notification_sound < 0 || notification_sound > 1) {
311 printf("Usage: %s -n 0|1\n", argv[0]);
312 return 1;
313 }
314 break;
315 case 'r':
316 rotate_to_mute = strtol(optarg, NULL, 10);
317 if (rotate_to_mute < 0 || rotate_to_mute > 1) {
318 printf("Usage: %s -r 0|1\n", argv[0]);
319 return 1;
320 }
321 break;
322 case 's':
323 sidetone_loudness = strtol(optarg, NULL, 10);
324
325 if (sidetone_loudness > 128 || sidetone_loudness < 0) {
326 printf("Usage: %s -s 0-128\n", argv[0]);
327 return 1;
328 }
329 break;
330 case 'u':
331 fprintf(stderr, "Generating udev rules..\n\n");
332 print_udevrules();
333 return 0;
334 case 'v':
335 voice_prompts = strtol(optarg, NULL, 10);
336 if (voice_prompts < 0 || voice_prompts > 1) {
337 printf("Usage: %s -v 0|1\n", argv[0]);
338 return 1;
339 }
340 break;
341 case '?':
342 print_capabilities = 1;
343 break;
344 case 'h':
345 printf("Headsetcontrol written by Sapd (Denis Arnst)\n\thttps://github.com/Sapd\n\n");
346 printf("Parameters\n");
347 printf(" -s, --sidetone level\t\tSets sidetone, level must be between 0 and 128\n");
348 printf(" -b, --battery\t\t\tChecks the battery level\n");
349 printf(" -n, --notificate soundid\tMakes the headset play a notifiation\n");
350 printf(" -l, --light 0|1\t\tSwitch lights (0 = off, 1 = on)\n");
351 printf(" -c, --short-output\t\tUse more machine-friendly output \n");
352 printf(" -i, --inactive-time time\tSets inactive time in minutes, time must be between 0 and 90, 0 disables the feature.\n");
353 printf(" -m, --chatmix\t\t\tRetrieves the current chat-mix-dial level setting between 0 and 128. Below 64 is the game side and above is the chat side.\n");
354 printf(" -v, --voice-prompt 0|1\tTurn voice prompts on or off (0 = off, 1 = on)\n");
355 printf(" -r, --rotate-to-mute 0|1\tTurn rotate to mute feature on or off (0 = off, 1 = on)\n");
356 printf(" -?, --capabilities\t\tPrint every feature headsetcontrol supports of the connected headset\n");
357 printf("\n");
358 printf(" -u\tOutputs udev rules to stdout/console\n");
359
360 printf("\n");
361 return 0;
362 case 0:
363 if (strcmp(opts[option_index].name, "dev") == 0) {
364 dev_mode = 1;
365 break;
366 }
367 default:
368 printf("Invalid argument %c\n", c);
369 return 1;
370 }
371 }
372
373 if (dev_mode) {
374 // use +1 to make sure the first parameter is some previous argument (which normally would be the name of the program)
375 return dev_main(argc - optind + 1, &argv[optind - 1]);
376 } else {
377 for (int index = optind; index < argc; index++)
378 fprintf(stderr, "Non-option argument %s\n", argv[index]);
379 }
380
381 // describes the headsetcontrol device, when a headset was found
382 static struct device device_found;
383
384 // Look for a supported device
385 int headset_available = find_device(&device_found);
386 if (headset_available != 0) {
387 fprintf(stderr, "No supported headset found\n");
388 return 1;
389 }
390
391 // We open connection to HID devices on demand
392 hid_device* device_handle = NULL;
393 char* hid_path = NULL;
394
395 // Set all features the user wants us to set
396
397 if (sidetone_loudness != -1) {
398 if (handle_feature(&device_found, &device_handle, &hid_path, CAP_SIDETONE, sidetone_loudness) != 0)
399 goto error;
400 }
401
402 if (lights != -1) {
403 if (handle_feature(&device_found, &device_handle, &hid_path, CAP_LIGHTS, lights) != 0)
404 goto error;
405 }
406
407 if (notification_sound != -1) {
408 if (handle_feature(&device_found, &device_handle, &hid_path, CAP_NOTIFICATION_SOUND, notification_sound) != 0)
409 goto error;
410 }
411
412 if (request_battery == 1) {
413 if (handle_feature(&device_found, &device_handle, &hid_path, CAP_BATTERY_STATUS, request_battery) != 0)
414 goto error;
415 }
416
417 if (inactive_time != -1) {
418 if (handle_feature(&device_found, &device_handle, &hid_path, CAP_INACTIVE_TIME, inactive_time) != 0)
419 goto error;
420 }
421
422 if (request_chatmix == 1) {
423 if (handle_feature(&device_found, &device_handle, &hid_path, CAP_CHATMIX_STATUS, request_chatmix) != 0)
424 goto error;
425 }
426
427 if (voice_prompts != -1) {
428 if (handle_feature(&device_found, &device_handle, &hid_path, CAP_VOICE_PROMPTS, voice_prompts) != 0)
429 goto error;
430 }
431
432 if (rotate_to_mute != -1) {
433 if (handle_feature(&device_found, &device_handle, &hid_path, CAP_ROTATE_TO_MUTE, rotate_to_mute) != 0)
434 goto error;
435 }
436
437 if (print_capabilities != -1) {
438 PRINT_INFO("Supported capabilities:\n\n");
439
440 // go through all enum capabilities
441 for (int i = 0; i < NUM_CAPABILITIES; i++) {
442 // When the capability i is included in .capabilities
443 if ((device_found.capabilities & B(i)) == B(i)) {
444 if (short_output) {
445 printf("%c", capabilities_str_short[i]);
446 } else {
447 printf("* %s\n", capabilities_str[i]);
448 }
449 }
450 }
451 }
452
453 if (argc <= 1) {
454 printf("You didn't set any arguments, so nothing happened.\nType %s -h for help.\n", argv[0]);
455 }
456
457 terminate_hid(&device_handle, &hid_path);
458
459 return 0;
460
461 error:
462 terminate_hid(&device_handle, &hid_path);
463 return 1;
464 }
465