1// SPDX-License-Identifier: MIT
2/*
3 * Copyright © 2014 Red Hat, Inc.
4 */
5
6#include "config.h"
7
8#include <assert.h>
9#include <errno.h>
10#include <fcntl.h>
11#include <getopt.h>
12#include <libgen.h>
13#include <limits.h>
14#include <linux/input.h>
15#include <stdbool.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <sys/stat.h>
20#include <sys/types.h>
21#include <unistd.h>
22
23#include "libevdev/libevdev.h"
24
25static void
26usage(const char *progname)
27{
28	printf("%s --abs <axis> [--min min] [--max max] [--res res] [--fuzz fuzz] [--flat flat] /dev/input/eventXYZ\n"
29	       "\tChange the absinfo struct for the named axis\n"
30	       "%s --resolution res[,yres] /dev/input/eventXYZ\n"
31	       "\tChange the x/y resolution on the given device\n"
32	       "%s --led <led> --on|--off /dev/input/eventXYZ\n"
33	       "\tEnable or disable the named LED\n",
34	       progname,
35	       progname,
36	       progname);
37}
38
39enum mode {
40	MODE_NONE = 0,
41	MODE_ABS,
42	MODE_LED,
43	MODE_RESOLUTION,
44	MODE_HELP,
45};
46
47enum opts {
48	OPT_ABS = 1 << 0,
49	OPT_MIN = 1 << 1,
50	OPT_MAX = 1 << 2,
51	OPT_FUZZ = 1 << 3,
52	OPT_FLAT = 1 << 4,
53	OPT_RES = 1 << 5,
54	OPT_LED = 1 << 6,
55	OPT_ON = 1 << 7,
56	OPT_OFF = 1 << 8,
57	OPT_RESOLUTION = 1 << 9,
58	OPT_HELP = 1 << 10,
59};
60
61static bool
62parse_resolution_argument(const char *arg, int *xres, int *yres)
63{
64	int matched;
65
66	matched = sscanf(arg, "%d,%d", xres, yres);
67
68	switch(matched) {
69		case 2:
70			break;
71		case 1:
72			*yres = *xres;
73			break;
74		default:
75			return false;
76	}
77
78	return true;
79}
80
81static inline bool
82safe_atoi(const char *str, int *val)
83{
84        char *endptr;
85        long v;
86
87        v = strtol(str, &endptr, 10);
88        if (str == endptr)
89                return false;
90        if (*str != '\0' && *endptr != '\0')
91                return false;
92
93        if (v > INT_MAX || v < INT_MIN)
94                return false;
95
96        *val = v;
97        return true;
98}
99
100static int
101parse_event_code(int type, const char *str)
102{
103	int code;
104
105	code = libevdev_event_code_from_name(type, str);
106	if (code != -1)
107		return code;
108
109	if (safe_atoi(str, &code))
110		return code;
111
112	return -1;
113}
114
115static int
116parse_options_abs(int argc, char **argv, unsigned int *changes,
117		  int *axis, struct input_absinfo *absinfo)
118{
119	int rc = 1;
120	int c;
121	int option_index = 0;
122	static struct option opts[] = {
123		{ "abs", 1, 0, OPT_ABS },
124		{ "min", 1, 0, OPT_MIN },
125		{ "max", 1, 0, OPT_MAX },
126		{ "fuzz", 1, 0, OPT_FUZZ },
127		{ "flat", 1, 0, OPT_FLAT },
128		{ "res", 1, 0, OPT_RES },
129		{ NULL, 0, 0, 0 },
130	};
131
132	if (argc < 2)
133		goto error;
134
135	optind = 1;
136	while (1) {
137		c = getopt_long(argc, argv, "h", opts, &option_index);
138		if (c == -1)
139			break;
140
141		switch (c) {
142			case OPT_ABS:
143				*axis = parse_event_code(EV_ABS, optarg);
144				if (*axis == -1)
145					goto error;
146				break;
147			case OPT_MIN:
148				absinfo->minimum = atoi(optarg);
149				break;
150			case OPT_MAX:
151				absinfo->maximum = atoi(optarg);
152				break;
153			case OPT_FUZZ:
154				absinfo->fuzz = atoi(optarg);
155				break;
156			case OPT_FLAT:
157				absinfo->flat = atoi(optarg);
158				break;
159			case OPT_RES:
160				absinfo->resolution = atoi(optarg);
161				break;
162			default:
163				goto error;
164		}
165		*changes |= c;
166	}
167	rc = 0;
168error:
169	return rc;
170}
171
172static int
173parse_options_led(int argc, char **argv, int *led, int *led_state)
174{
175	int rc = 1;
176	int c;
177	int option_index = 0;
178	static struct option opts[] = {
179		{ "led", 1, 0, OPT_LED },
180		{ "on", 0, 0, OPT_ON },
181		{ "off", 0, 0, OPT_OFF },
182		{ NULL, 0, 0, 0 },
183	};
184
185	if (argc < 2)
186		goto error;
187
188	optind = 1;
189	while (1) {
190		c = getopt_long(argc, argv, "h", opts, &option_index);
191		if (c == -1)
192			break;
193
194		switch (c) {
195			case OPT_LED:
196				*led = parse_event_code(EV_LED, optarg);
197				if (*led == -1)
198					goto error;
199				break;
200			case OPT_ON:
201				if (*led_state != -1)
202					goto error;
203				*led_state = 1;
204				break;
205			case OPT_OFF:
206				if (*led_state != -1)
207					goto error;
208				*led_state = 0;
209				break;
210			default:
211				goto error;
212		}
213	}
214
215	rc = 0;
216error:
217	return rc;
218}
219
220static int
221parse_options_resolution(int argc, char **argv, int *xres, int *yres)
222{
223	int rc = 1;
224	int c;
225	int option_index = 0;
226	static struct option opts[] = {
227		{ "resolution", 1, 0, OPT_RESOLUTION },
228		{ NULL, 0, 0, 0 },
229	};
230
231	if (argc < 2)
232		goto error;
233
234	optind = 1;
235	while (1) {
236		c = getopt_long(argc, argv, "h", opts, &option_index);
237		if (c == -1)
238			break;
239
240		switch (c) {
241			case OPT_RESOLUTION:
242				if (!parse_resolution_argument(optarg,
243							       xres, yres))
244					goto error;
245				break;
246			default:
247				goto error;
248		}
249	}
250
251	rc = 0;
252error:
253	return rc;
254}
255
256static enum mode
257parse_options_mode(int argc, char **argv)
258{
259	int c;
260	int option_index = 0;
261	static const struct option opts[] = {
262		{ "abs", 1, 0, OPT_ABS },
263		{ "led", 1, 0, OPT_LED },
264		{ "resolution", 1, 0, OPT_RESOLUTION },
265		{ "help", 0, 0, OPT_HELP },
266		{ NULL, 0, 0, 0 },
267	};
268	enum mode mode = MODE_NONE;
269
270	if (argc < 2)
271		return mode;
272
273	while (mode == MODE_NONE) {
274		c = getopt_long(argc, argv, "h", opts, &option_index);
275		if (c == -1)
276			break;
277
278		switch (c) {
279			case 'h':
280			case OPT_HELP:
281				mode = MODE_HELP;
282				break;
283			case OPT_ABS:
284				mode = MODE_ABS;
285				break;
286			case OPT_LED:
287				mode = MODE_LED;
288				break;
289			case OPT_RESOLUTION:
290				mode = MODE_RESOLUTION;
291				break;
292			default:
293				break;
294		}
295	}
296
297	if (optind >= argc && mode != MODE_HELP)
298		return MODE_NONE;
299
300	return mode;
301}
302
303static void
304set_abs(struct libevdev *dev, unsigned int changes,
305	unsigned int axis, struct input_absinfo *absinfo)
306{
307	int rc;
308	struct input_absinfo abs;
309	const struct input_absinfo *a;
310
311	if ((a = libevdev_get_abs_info(dev, axis)) == NULL) {
312		fprintf(stderr,
313			"Device '%s' doesn't have axis %s\n",
314			libevdev_get_name(dev),
315			libevdev_event_code_get_name(EV_ABS, axis));
316		return;
317	}
318
319	abs = *a;
320	if (changes & OPT_MIN)
321		abs.minimum = absinfo->minimum;
322	if (changes & OPT_MAX)
323		abs.maximum = absinfo->maximum;
324	if (changes & OPT_FUZZ)
325		abs.fuzz = absinfo->fuzz;
326	if (changes & OPT_FLAT)
327		abs.flat = absinfo->flat;
328	if (changes & OPT_RES)
329		abs.resolution = absinfo->resolution;
330
331	rc = libevdev_kernel_set_abs_info(dev, axis, &abs);
332	if (rc != 0)
333		fprintf(stderr,
334			"Failed to set absinfo %s: %s",
335			libevdev_event_code_get_name(EV_ABS, axis),
336			strerror(-rc));
337}
338
339static void
340set_led(struct libevdev *dev, unsigned int led, int led_state)
341{
342	int rc;
343	enum libevdev_led_value state =
344		led_state ? LIBEVDEV_LED_ON : LIBEVDEV_LED_OFF;
345
346	if (!libevdev_has_event_code(dev, EV_LED, led)) {
347		fprintf(stderr,
348			"Device '%s' doesn't have %s\n",
349			libevdev_get_name(dev),
350			libevdev_event_code_get_name(EV_LED, led));
351		return;
352	}
353
354	rc = libevdev_kernel_set_led_value(dev, led, state);
355	if (rc != 0)
356		fprintf(stderr,
357			"Failed to set LED %s: %s",
358			libevdev_event_code_get_name(EV_LED, led),
359			strerror(-rc));
360}
361
362static void
363set_resolution(struct libevdev *dev, int xres, int yres)
364{
365	struct input_absinfo abs;
366
367	abs.resolution = xres;
368	if (libevdev_has_event_code(dev, EV_ABS, ABS_X))
369		set_abs(dev, OPT_RES, ABS_X, &abs);
370	if (libevdev_has_event_code(dev, EV_ABS, ABS_MT_POSITION_X))
371		set_abs(dev, OPT_RES, ABS_MT_POSITION_X, &abs);
372
373	abs.resolution = yres;
374	if (libevdev_has_event_code(dev, EV_ABS, ABS_Y))
375		set_abs(dev, OPT_RES, ABS_Y, &abs);
376	if (libevdev_has_event_code(dev, EV_ABS, ABS_MT_POSITION_Y))
377		set_abs(dev, OPT_RES, ABS_MT_POSITION_Y, &abs);
378}
379
380int
381main(int argc, char **argv)
382{
383	struct libevdev *dev = NULL;
384	int fd = -1;
385	int rc = EXIT_FAILURE;
386	enum mode mode;
387	const char *path;
388	struct input_absinfo absinfo;
389	int axis = -1;
390	int led = -1;
391	int led_state = -1;
392	unsigned int changes = 0; /* bitmask of changes */
393	int xres = 0,
394	    yres = 0;
395
396	mode = parse_options_mode(argc, argv);
397	switch (mode) {
398		case MODE_HELP:
399			rc = EXIT_SUCCESS;
400			/* fallthrough */
401		case MODE_NONE:
402			usage(basename(argv[0]));
403			goto out;
404		case MODE_ABS:
405			rc = parse_options_abs(argc, argv, &changes, &axis,
406					       &absinfo);
407			break;
408		case MODE_LED:
409			rc = parse_options_led(argc, argv, &led, &led_state);
410			break;
411		case MODE_RESOLUTION:
412			rc = parse_options_resolution(argc, argv, &xres,
413						      &yres);
414			break;
415		default:
416			fprintf(stderr,
417				"++?????++ Out of Cheese Error. Redo From Start.\n");
418			goto out;
419	}
420
421	if (rc != EXIT_SUCCESS)
422		goto out;
423
424	if (optind >= argc) {
425		rc = EXIT_FAILURE;
426		usage(basename(argv[0]));
427		goto out;
428	}
429
430	path = argv[optind];
431
432	fd = open(path, O_RDWR);
433	if (fd < 0) {
434		rc = EXIT_FAILURE;
435		perror("Failed to open device");
436		goto out;
437	}
438
439	rc = libevdev_new_from_fd(fd, &dev);
440	if (rc < 0) {
441		fprintf(stderr, "Failed to init libevdev (%s)\n", strerror(-rc));
442		goto out;
443	}
444
445	switch (mode) {
446		case MODE_ABS:
447			set_abs(dev, changes, axis, &absinfo);
448			break;
449		case MODE_LED:
450			set_led(dev, led, led_state);
451			break;
452		case MODE_RESOLUTION:
453			set_resolution(dev, xres, yres);
454			break;
455		default:
456			break;
457	}
458
459out:
460	libevdev_free(dev);
461	if (fd != -1)
462		close(fd);
463
464	return rc;
465}
466