-
Notifications
You must be signed in to change notification settings - Fork 0
/
joy-remap.c
2549 lines (2490 loc) · 81.6 KB
/
joy-remap.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
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
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* LD_PRELOAD shim which remaps axis and button events for gamepad event
* devices. Use jscal to do it for js devices, although I have now added
* enough js suport that it might work for you, as well. Since it's an
* LD_PRELOAD, it doesn't need root, and it doesn't deal with other problems
* using uinput. It's very simplistic, intended to map exactly one
* controller to what one program expects to see. Since it supports multiple
* opens of multiple devices, it should work with multiple controllers and
* in-game hotplugging. Internal support for hot-plugging, or at least
* persistence in the face of temporary disconnects, really requires uinput,
* since every possible use of the file descriptor would otherwise have to be
* intercepted. It is also unable to merge multiple devices into one, such
* as mapping the Dualshock 3+ motion sensors into the main controller.
* It also doesn't support adding autofire and chording. Since it happens
* at the user level, js devices associated with the same gamepad will not
* be affected, unless they use the built-in jsremap feature. I used to say
* to use jscal for that, but jscal has global effect and doesn't support
* e.g. axis-to-button or vice-versa. I also support only overriding the
* name of a js device in case a game uses name-based heuristics. Really,
* given that js devices have never supported force feedback, and likely
* never will, new programs should not be using them, in the first place.
* Of course Linux doesn't exactly make it easy to figure out which event
* device(s) to use, either. Don't even get me started on LEDs.
*
* Some messages are normally printed to stderr. In order to catch them
* even if the program redirects stderr, or if you just want them stored
* elsewhere, set EV_JOY_REMAP_LOG to something else. To hide, just set
* to /dev/null. To see even if redirected, set to /dev/tty.
*
* There are many ways an event device can be accessed. Following the
* open, the only methods supported are read and ioctl. The actual
* method of opening is expected to be open/open64, finished by close.
* The CAP_* defines below can also be used to enable other methods:
* openat/openat64, fopen/fclose, and syscall(open,openat). There are
* also many ways this entire shim can be disabled. For example:
* explicit symbol lookups from libc, forking after unsetting LD_PRELOAD,
* use of an unsupported access method, and explicitly dlopen()ing libc.
* In fact, one game I have does the latter, so I have added a dlopen
* intercept which disallows libc, libpthread, or libdl from being opened.
* If this causes problems, I may add an environment override to disable
* this feature.
*
* This uses a configuration file, with one directive per line. Blank
* lines and lines beginning with # are ignored, as is initial and trailing
* whitespace. The directives are case-insensitive keywords, followed by
* (skipped) whitespace, followed by a parameter, where needed. The file
* name is specified by the environment variable EV_JOY_REMAP_CONFIG. If
* that environment variable is missing or blank, it uses ev_joy_remap.conf
* in the current directory, ~/.config, or /etc, whichever is found first.
* Failure to find any file, or failure to parse the file it found will
* result in an error message and no attempt at device interception.
* If the configuration contains multiple sections, the regular expression
* in EV_JOY_REMAP_ENABLE enables a subset of sections (all are enabled
* if not present or blank). The last section which matches a device
* applies for capture, and disabled devices are the intersection of all
* enabled sections' disabled devices (i.e., if any section enables it,
* it is enabled).
*
* Keywords are:
*
* section <name>
* If present, all configuration prior to this line, if any, belongs to
* the previous section (unnamed if no section keyword given), and any
* subsequent configuration, up to the next section keyword, belongs to
* the section with the given name (which must be non-blank). If this
* name is repeated, the subsequent configuration will override
* previous configuration for the same-named section.
*
* use [<name>]
* Copy config from given (un)named section. Note that future changes
* to the given section are not followed. This must be the first keyword
* in a section, and may only be used once in a section.
*
* match <pattern>
* This mandatory keyword is a pattern to match the device name to
* intercept, as a POSIX case-sensitive extended regular expression
* (regex(7)).
* The pattern is matched against both the device name and a pseudo name
* generated from the device ID (capital 4-digit hex for all fields but the
* last, which is decimal):
* <bus type>:<vendor id>:<product id>:<version>-<event device #>
* All accepted matches are remapped.
*
* filter
* If present, this keyword indicates that all non-matching event devices
* should be hidden from the application. Unlike remapping, any matching
* device is unfiltered.
*
* reject <pattern>
* This regular expression specifies names to reject from "match". If
* either name matches the reject pattern, it is rejected. Otherwise, if
* either name matches the accept pattern, it is allowed. Otherwise, it
* is rejected.
*
* name <name>
* Replace the advertised name of the device. There is no way to change
* the version number right now, as I don't know of any software that
* depends on the version.
*
* jsrename
* If present, the joystick device associated with the filter is renamed.
* This will cause the event device to be opened and closed, so if something
* else is filtering out the event device, it may not work.
*
* id <idcode>
* Replace the advertised ID of the device. The idcode must be of the
* form <bus type>:<vendor id>:<product id>:<version> with each field
* being hexadecimal. If any field is blank, that portion of the ID is
* not replaced. For example, 3::: forces a bus type of 3 (USB). See
* /usr/include/linux/input.h for bus types.
*
* uniq <guid>
* Replace the unique string for the device (usually the UUID).
*
* axes <list>
* This remaps absolute axes. Relative axes are not supported. It is
* a comma-separated list of input axes to map (regardless of whether or
* not the device actually has this axis). Just a plain number*
* or range of numbers (separated by -), optionally preceeded by a -
* to invert the values, maps to the next unmapped output axis number,
* starting with 0. Note that hats (dpads) start at 16. A blank entry
* skips an unmapped output for this auto-assignment. Two numbers
* separated by = (wiith a an optional - after the = for inversion)
* indicate the output axis to the left of equals and the input to the right.
* Ranges are also allowed after the =, in which case the range is
* assigned in sequential order (unlike auto-assignment, which skips
* outputs already mapped). In place of the input axis number
* or range in either form, a button triplet, preceeded by the letter b,
* separated by less-than signs, may be specified: b<but><<but><<but>
* where <but> is the button event for negative, middle, and positive
* values, respectively. Each button may be preceeded by a - to indicate
* "when released". Each button may also be blank (but at least one must
* not be blank). If the positive is blank, then the inverted state of
* the negative is used (if that is not already used by the middle), and
* vice-versa. If the middle is blank, the inverted state of either
* positive or negative is used. If only the middle is non-blank, the
* negative is left unmapped and the positive uses the inverse of the
* middle. Button codes are described below. Finally, a list entry
* consisting of an axis number preceeded by an exclamation point (!)
* disables that axis for input, removing any existing mapping using that
* axis. Note that in all cases, any mapping for an input overrides any
* preceeding (un)mapping. If this keyword is missing, any axes not
* mapped to buttons are passed through as is. Otherwise, any inputs not
* explicitly mapped are (normally) ignored.
* * numbers are C-style: decimal, octal (0 prefix), hexadecimal (0x prefix)
*
* rescale <list>
* Change the absinfo parameters for the given output axes. Multiple
* list entries are separated by commas (or separate keywords). Each
* list entry is an output axis number, followed by an equals sign,
* followed by the minimum, maximum, fuzz, flat, and resolution
* parameters, respectively, separated by colons. Missing parameters
* (either due to too few colons or missing numbers) are set to 0.
* Values are automatically scaled to the new range (before inversion,
* if requested by the axes keyword):
* (v - old_min) * (new_max - new_min) / (old_max - old_min)) + new_min
* It uses integer math with unisgned longs with no checks for overflow.
* This rescaling may not produce desired results, but it's all I've got.
* Note that rescaling must be specified after mapping using the axes
* keyword, or the scaling will be lost.
*
* pass_axes
* Normally, if there are any axes keywords at all, any inputs not
* explicilty mapped are ignored. This passes through any inputs not
* conflicting with outputs.
*
* buttons <list>
* This remaps buttons (key events). It is a comma-separated list of input
* button (key) codes to remap (regardless of whether or not the device
* actually has this button). See /usr/include/linux/input-event-codes.h
* for codes. Just a plain number* or range (separated by -) of
* numbers, optionally preceeded by a - to invert the state, maps to the
* next unmapped output, starting with 0x130 (BTN_A). Just like with axes,
* you can also use = to specify the output. Axes can be mapped
* to buttons, as well. Using ax<num>><num><<num> instead of an input
* button code says that when the axis specified by the first number has
* a value greater than or equal to the second, press the button, and if
* less than or equal to the third, release the button. If the second is
* less than the third, the tests are inverted. One plain and one inverted
* button map per axis is allowed. Finally, a button ID preceeded by an
* exclamation point (!) will explicitly unmap the given input. Here is
* the list of standard gamepad buttons:
* A = SOUTH = 304, B = EAST = 305, C = 306,
* X = NORTH = 306, Y = WEST = 308, Z = 309,
* TL = 310, TR = 311, TL2 = 312, TR2 = 313,
* SELECT = 314, START = 315, MODE = 316, THUMBL = 317, THUMBR = 318
* And here are the standard joystick buttons:
* TRIGGER = 288, THUMB = 289, THUMB2 = 290, TOP = 291, TOP2 = 292,
* PINKIE = 293, BASE = 294, BASE2 = 295, BASE3 = 296, BASE4 = 297,
* BASE5 = 298, BASE6 = 299, DEAD = 303
* Note that most controllers do not have C or Z buttons. Older versions
* of the hid_sony driver issued joystick events (THUMB/THUMB2/TOP/TRIGGER
* instead of A/B/X/Y). Also, some controllers only use axes for TL2 and
* TR2. My other remappers support hex and symbolic names (using the C
* preprocessor with input-event-codes.h), but not this time. As a
* compromise, the above-listed names are supported, case-insensitive
* (without the BTN_ prefix, as shown).
* * numbers are C-style: decimal, octal (0 prefix), hexadecimal (0x prefix)
*
* pass_buttons
* Normally, if there are any buttons keywords at all, any inputs not
* explicilty mapped are ignored. This passes through any inputs not
* conflicting with outputs.
*
* jsremap [remappings]
* If present, do remapping as if the js device was generated from the
* remapped event device in the standard way. This includes renaming,
* so the jsrename is not additionally needed. As with jsrename, this
* will cause the event device to be opened and closed, so if something
* else is filtering out the event device, it may not work. If the
* optional remappings parameter is given, it is interpreted like the
* jscal -u/--set-mappings parameter: a comma-separated list of numbers,
* with the first being the number of axes, followed by axis event codes,
* followed by the number of buttons, followed by button event codes. The
* number of axes or buttons may be zero to skip that remapping. For
* convenience, button codes can be specified in the same way as the
* buttons command above (i.e., not just decimal numbers). Remember
* that this is applied after the event device remapping. Without the
* remappings parameter, axes and buttons are assigned in ascending code
* order. Thus, event device remapping can take care of reordering,
* unless a game reads the G...MAP results and makes decisions based on
* that. This is also a convenient way to replace a "temporary" jscal
* for a game.
*
* syn_drop
* When dropping events, rather than just removing them from the stream,
* send SYN_DROP events.
*
* Note that for button-to-axis and axis-to-button mappings, the button press
* or release event will not occur unless the state changes. All buttons
* are assumed to be initially released, and all axes are assumed to be
* initially at their center value.
*
* Note also that each input axis or button can only do one thing. Again,
* this is only a simple mapper.
*
* Once the config is set, run the command <cmd> with LD_PRELOAD set to the
* path to this compiled shim:
*
* > LD_PRELOAD="<path_to>/joy-remap.so" <cmd>
*
*
* Build with: gcc -s -Wall -O2 -shared -fPIC -o joy-remap.{so,c} -ldl -lpthread
* for debug: gcc -g -Wall -shared -fPIC -o joy-remap.{so,c} -ldl -lpthread
* Use clang instead of gcc if you prefer. Don't bother with debug; gdb
* has a real hard time debugging LD_PRELOADs (or maybe I'm missing some
* special magic). At least crashes can be debugged using the core file.
* Crashing within gdb confuses gdb, and setting breakpoints doesn't work.
* Note that you have to use -lpthread in order to have this use pthread's
* open64 rather than libc's. This code uses pthread code, anyway. Code
* that hard-codes use of the errno var might not work quite right.
*
*****************************************
* Known Bugs:
*
* There are some applications that refuse to work with this. Java in
* particular tends to crash. Some games using Unity3D/Mono dynamically
* load libc, which undoes the libc overrides. Some clear the environment
* using execve(2), losing the LD_PRELOAD. Some are setuid, losing the
* LD_PRELOAD even earlier.
*
* See also the FIXMEs below.
*
*****************************************
* Missing featuers:
*
* It's not possible to report a button or axis that isn't mapped. It would
* generate no events, but allow one to insert dummy inputs for games that
* ignore labels. e.g. [axis=]none or [button=]none
*
* There is no chording support.
*
* It's possible to produce the same output from multiple inputs, but not
* to produce multiple outputs from the same input. This might require
* intercepting poll(2) and select(2).
*
* It's not possible to get inputs from other sources. This definitely
* requires interception of poll(2) and select(2) for event injection.
*
* There is no auto-repeat (auto-fire) mode. This would require event
* injection, definitely requiring interception of poll(2) and select(2).
* The timeouts for those two functions could be used to do the timing, but
* the most reliable would require creating a timer as well, and hopefully
* not having that timer interfere with the parent process in any way (e.g.
* spurious signals).
*
* It's not possible to generate or intercept keyboard events. This requires
* interception of the input stream, which is generally standard input or
* X's event interface (and maybe xkb & such as well). While it is possible
* to receive keyboard events from input devices, it is not possible to
* suppress them for a particular application without intercepting the
* higher level routines. Similarly, it would require a kernel driver to
* inject keyboard events into applications without intercepting the higher
* level routines.
*
* At a higher level, significantly more complex missing features:
*
* Macros: inject A, wait .5 sec, inject X, etc. Similar to auto-repeat.
*
* Dynamic macros: start recording, end recording, save to this key, save as
* config file
*
* Other events: a particular sound played, or a particular GL update was
* made, or a part of the window was updated in a particular way. These
* events would most likely invoke macros rather than individual keypresses,
* or provide a way for looping macros to end. This requires many mmore
* interceptions, as well as a way to configure such events (e.g. start and
* end capture, or pause and select a part of the screen) and detect them
* (e.g. fuzzy matching).
*/
/* Every intercept takes time, albeit usually unnoticabbly little */
/* The only ones known to work are open and open64 */
#ifndef CAP_OPENAT
#define CAP_OPENAT 0 /* this is the syscall used by glibc, but nobody calls this */
#endif
#ifndef CAP_FOPEN
#define CAP_FOPEN 0 /* for c++; assumes use of read() rather than fread() */
#endif
#ifndef CAP_SYSCALL
#define CAP_SYSCALL 0 /* for mono, I guess */
/* note that altough I made changes to the asm to get the SYSCALL code to
* compile on clang, I have not tested the compiled clang version at all */
/* the sysall only intercepts open and openat, not openat2, read, ioctl, etc */
/* glibc also exports numerous "internal" symbols beginning with __ */
/* not going to intercept them, for now. */
/* the only other open is open_to_handle_at, which I don't want to deal with */
/* direct syscalls bypassing libc entirely could work, but won't, ever */
#endif
/* In fact, I may remove all of the above given that nothing I have uses them */
/* for RTLD_NEXT */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <dlfcn.h>
#include <stdlib.h>
#include <ctype.h>
/* fortified glibc makes read() some sort of complex beast that is incompatible */
#define read internal_read
#include <unistd.h>
#undef read
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/joystick.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <regex.h>
#include <dirent.h>
/* why would you be scanning for devices in parallel? Oh well, some
* jackass will try and screw this up, so may as well support it */
#include <pthread.h>
/* These numbers are not exported, and may change in the future */
/* see linux/drivers/input/evdev.c and linux/drivers/input/joydev.c */
#define INPUT_MAJOR 13
#define EVDEV_MINOR0 64
#define EVDEV_NMINOR 32
#define JSDEV_MINOR0 0
#define JSDEV_NMINOR 16
static FILE *logf;
/* macros for accessing bits in arrays of unsigned longs */
/* stupid kernel doesn't export its bitops, so everybody has to reimplement */
/* these probably only work on little-endian, but that's ok for now */
#define ULBITS (sizeof(unsigned long)*8)
#define MINBITS(x) (((x) + ULBITS - 1)/ULBITS)
#define ULSET(bits, bit) do { \
int _b = bit; \
(bits)[_b/ULBITS] |= 1UL << _b % ULBITS; \
} while(0)
#define ULCLR(bits, bit) do { \
int _b = bit; \
(bits)[_b/ULBITS] &= ~(1UL << _b % ULBITS); \
} while(0)
#define ULISSET(bits, bit) ((bits)[(bit)/ULBITS] & (1UL << (bit) % ULBITS))
/* info for mapping an input axis to an axis or key target */
struct axmap {
struct input_absinfo ai; /* for rescaling */
int flags;
int target;
int onthresh, offthresh; /* axis->button */
int ntarget, nonthresh, noffthresh; /* axis->button, 2nd key */
};
#define AXFL_MAP (1<<0) /* does this need processing? */
#define AXFL_BUTTON (1<<1) /* is this a button map? else ax map */
#define AXFL_INVERT (1<<2) /* invert before sending on? */
/* onthresh is min+max if AXFL_INVERT */
#define AXFL_RESCALE (1<<3) /* rescale using ai? */
#define AXFL_NINVERT (1<<4) /* invert ntarget before sending out */
#define AXFL_PRESSED (1<<5) /* is the button currently pressed? */
/* defaults to no, even if axis says otherwise */
#define AXFL_NPRESSED (1<<6) /* is the neg button pressed? */
/* info for mapping an input key to a key or axis target */
struct butmap {
int flags;
int target;
#define onax target /* this and next are for button->axis */
int offax, onval, offval; /* val is -1 0 1 on init */
};
#define BTFL_MAP (1<<0) /* does this need processing? */
#define BTFL_AXIS (1<<1) /* is this an axis map? else bt map */
#define BTFL_INVERT (1<<2) /* invert before sending on? */
/* all config combined into one structure for multiple sections */
static struct evjrconf {
char *name;
struct axmap *ax_map;
struct butmap *bt_map;
char *repl_name, *repl_id, *repl_uniq;
/* since there is no regcopy() or equiv., need strings for KW_USE */
char *match_str, *reject_str;
regex_t match, reject; /* compiled matching regexes */
__u8 *jsaxmap; /* jscal -u-like remapping; 1st element is len */
__u16 *jsbtmap; /* jscal -u-like remapping; 1st element is len */
/* stuff below this is safe to copy on USE */
int bt_low, nbt, nax; /* mapping array valid bounds */
int max_ax, max_bt; /* mapping array sizes */
int auto_ax, auto_bt; /* during parse: current auto-assigned inputs */
char filter_ax, filter_bt; /* flag: pass-through unmapped? */
char filter_dev; /* flag: filter non-matching devs completely? */
char jsrename; /* rename js device associated with event device? */
char jsremap; /* do full js remapping? */
char syn_drop; /* use SYN_DROP instead of deleting drops? */
} *conf;
static int nconf = 0;
/* captured fds and the config that captured them */
/* also other per-device info */
struct js_extra;
static struct evfdcap {
struct evfdcap *next; /* linked list is less thread-unsafe */
const struct evjrconf *conf;
struct js_extra *js_extra; /* only there if jsremap */
unsigned long absout[MINBITS(ABS_MAX)]; /* sent GBITS(EV_ABS) */
int axval[ABS_MAX]; /* value for key-generated axes */
/* FIXME: do I need to support EVIOC[GS]KEYCODE*? */
unsigned long keysout[MINBITS(KEY_MAX)], /* sent GBITS(EV_KEY) */
keystates[MINBITS(KEY_MAX)], /* sent GKEY */
keystates_in[MINBITS(KEY_MAX)]; /* device GKEY */
struct input_id repl_id_val;
int fd;
char ebuf[sizeof(struct input_event)];
char excess_read;
char is_js;
} *ev_fd = NULL, *free_ev_fd = NULL;
struct js_extra {
/* in: index = js-code, value = ev-code */
__u8 in_ax_map[ABS_MAX];
__u16 in_btn_map[KEY_MAX - BTN_MISC + 1];
/* out: index = ev-code, value = js-code (0xff/0xffff == no map) */
__u8 out_ax_map[ABS_MAX];
__u16 out_btn_map[KEY_MAX - BTN_MISC + 1];
};
static char buf[1024]; /* generic large buffer to reduce stack usage */
/* in case of threads; used to just be access lock for buf[] */
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
/* array must be alphabetized */
static const struct bname {
const char *nm;
int code;
} bname[] = {
{ "a", BTN_A },
{ "b", BTN_B },
{ "base", BTN_BASE },
{ "base2", BTN_BASE2 },
{ "base3", BTN_BASE3 },
{ "base4", BTN_BASE4 },
{ "base5", BTN_BASE5 },
{ "base6", BTN_BASE6 },
{ "c", BTN_C },
{ "dead", BTN_DEAD },
{ "east", BTN_EAST },
{ "mode", BTN_MODE },
{ "north", BTN_NORTH },
{ "pinkie", BTN_PINKIE },
{ "select", BTN_SELECT },
{ "south", BTN_SOUTH },
{ "start", BTN_START },
{ "thumb", BTN_THUMB },
{ "thumb2", BTN_THUMB2 },
{ "thumbl", BTN_THUMBL },
{ "thumbr", BTN_THUMBR },
{ "tl", BTN_TL },
{ "tl2", BTN_TL2 },
{ "top", BTN_TOP },
{ "top2", BTN_TOP2 },
{ "tr", BTN_TR },
{ "tr2", BTN_TR2 },
{ "trigger", BTN_TRIGGER },
{ "west", BTN_WEST },
{ "x", BTN_X },
{ "y", BTN_Y },
{ "z", BTN_Z }
};
/* a is always target; b is always bname[] entry */
static int bncmp(const void *_a, const void *_b)
{
const struct bname *a = (const struct bname *)_a;
const struct bname *b = _b;
/* code in a is actually a->nm's length */
int ret = strncasecmp(a->nm, b->nm, a->code);
if(ret)
return ret;
if(b->nm[a->code])
return -1;
return 0;
}
/* interpret string of alnum as button code */
/* advances s if successful; returns -1 otherwise */
/* technically s could be const char **, but then compiler complains too often */
static int bnum(char **s)
{
if(!**s)
return -1;
if(isdigit(**s))
/* FIXME: restore *s and return -1 if value > KEY_MAX */
return strtol(*s, (char **)s, 0);
const char *e;
for(e = *s; isalnum(*e); e++);
struct bname str = { *s, e - *s}; /* code is length */
struct bname *m = bsearch(&str, bname, sizeof(bname)/sizeof(bname[0]),
sizeof(bname[0]), bncmp);
if(m) {
*s += str.code;
return m->code;
}
return -1;
}
/* array and enum must be alphabetized */
static const char * const kws[] = {
"axes",
"buttons",
"filter",
"id",
"jsremap",
"jsrename",
"match",
"name",
"pass_axes",
"pass_buttons",
"reject",
"rescale",
"section",
"syn_drop",
"uniq",
"use"
};
enum kw {
KW_AXES, KW_BUTTONS, KW_FILTER, KW_ID, KW_JSREMAP, KW_JSRENAME, KW_MATCH,
KW_NAME, KW_PASS_AX, KW_PASS_BT, KW_REJECT, KW_RESCALE, KW_SECTION,
KW_SYN_DROP, KW_UNIQ, KW_USE
};
static int kwcmp(const void *_a, const void *_b)
{
const char *a = _a, * const *b = _b;
return strcasecmp(a, *b);
}
static void free_conf(struct evjrconf *sec);
#if CAP_SYSCALL
static long (*real_syscall)(long number, ...);
#endif
static int (*real_open)(const char *pathname, int flags, ...);
static int (*real_open64)(const char *pathname, int flags, ...);
static int (*real_ioctl)(int fd, unsigned long request, ...);
static ssize_t (*real_read)(int, void *, size_t);
static int (*real_close)(int fd);
#if CAP_OPENAT
static int (*real_openat)(int dirfd, const char *pathname, int flags, ...);
static int (*real_openat64)(int dirfd, const char *pathname, int flags, ...);
#else
#define real_openat openat // used even if not capturing
#endif
#if CAP_FOPEN
static FILE * (*real_fopen)(const char *, const char *);
static FILE * (*real_fopen64)(const char *, const char *);
static int (*real_fclose)(FILE *);
#endif
#if 0
static int (*real_dup)(int);
static int (*real_dup2)(int, int);
static void *(*real_dlopen)(const char *, int);
#endif
/* parse config file */
/* is this too early for file I/O? apparently not */
/* dlopen() docs say this must be exported, but again, apparently not */
/* note that this attribute works with clang as well */
__attribute__((constructor))
static void init(void)
{
#if CAP_SYSCALL
real_syscall = dlsym(RTLD_NEXT, "syscall"); /* needs to be first! */
#endif
real_open = dlsym(RTLD_NEXT, "open");
real_open64 = dlsym(RTLD_NEXT, "open64");
real_ioctl = dlsym(RTLD_NEXT, "ioctl");
real_read = dlsym(RTLD_NEXT, "read");
real_close = dlsym(RTLD_NEXT, "close");
#if CAP_OPENAT
real_openat = dlsym(RTLD_NEXT, "openat");
real_openat64 = dlsym(RTLD_NEXT, "openat64");
#endif
#if CAP_FOPEN
real_fopen = dlsym(RTLD_NEXT, "fopen");
real_fopen64 = dlsym(RTLD_NEXT, "fopen64");
real_fclose = dlsym(RTLD_NEXT, "fclose");
#endif
#if 0
real_dup = dlsym(RTLD_NEXT, "dup");
real_dup2 = dlsym(RTLD_NEXT, "dup2");
real_dlopen = dlsym(RTLD_NEXT, "dlopen");
#endif
const char *fname = getenv("EV_JOY_REMAP_CONFIG"),
*logn = getenv("EV_JOY_REMAP_LOG");
FILE *f;
long fsize;
char *cfg;
struct evjrconf *sec;
if(logn) {
logf = fopen(logn, "w");
if(!logf)
perror(logn);
else
setbuf(logf, NULL);
}
if(!logf)
logf = stderr;
if(fname && *fname)
f = fopen(fname, "r");
else if(!(f = fopen((fname = "ev_joy_remap.conf"), "r"))) {
if((fname = getenv("HOME")) && *fname) {
sprintf(buf, "%.200s/.config/ev_joy_remap.conf", fname);
f = fopen((fname = buf), "r");
}
if(!f)
f = fopen("/etc/ev_joy_remap.conf", "r");
}
if(!f) {
fprintf(logf, "%s: %s\n", fname, strerror(errno));
return;
}
/* config should be short enough to fit in memory */
/* this eliminates the need for line read gymnastics */
if(fseek(f, 0, SEEK_END) || (fsize = ftell(f)) < 0 || fseek(f, 0, SEEK_SET) ||
!(cfg = malloc(fsize + 1))) {
fprintf(logf, "%s: %s\n", fname, strerror(errno));
fclose(f);
return;
}
if(fread(cfg, fsize, 1, f) != 1) {
fprintf(logf, "%s: %s\n", fname, strerror(errno));
fclose(f);
free(cfg);
errno = 0;
return;
}
fclose(f);
sec = conf = calloc(sizeof(*conf), (nconf = 1));
if(!conf) {
fprintf(logf, "%s: %s\n", "conf", strerror(errno));
free(cfg);
nconf = 0;
errno = 0;
return;
}
cfg[fsize] = 0;
char *ln = cfg, *e, c;
sec->ax_map = calloc((sec->max_ax = 18), sizeof(*sec->ax_map));
sec->bt_map = calloc((sec->max_bt = 15), sizeof(*sec->bt_map));
if(!sec->ax_map || !sec->bt_map) {
fprintf(logf, "%s: %s\n", "mapping", strerror(errno));
free(cfg);
if(sec->ax_map)
free(sec->ax_map);
if(sec->bt_map)
free(sec->bt_map);
errno = 0;
return;
}
sec->auto_ax = -1;
sec->auto_bt = BTN_A - 1;
int lno = 1;
#define abort_parse(msg) do { \
fprintf(logf, "error parsing map on line %d: " msg "\n", lno); \
goto err; \
} while(0)
#define map_resize(what, sz) do { \
if(sec->max_##what < sz) { \
int old_max = sec->max_##what; \
void *old_map = sec->what##_map; \
if(!old_max) \
sec->max_##what = sz < 18 ? 18 : sz; \
while(sec->max_##what < sz) \
sec->max_##what *= 2; \
sec->what##_map = realloc(sec->what##_map, sec->max_##what * sizeof(*sec->what##_map)); \
if(!sec->what##_map) { \
sec->what##_map = old_map; \
abort_parse(#what " map too large"); \
} \
memset(sec->what##_map + old_max, 0, (sec->max_##what - old_max) * sizeof(*sec->what##_map)); \
} \
} while(0)
int i, ret;
int has_conf = 0;
while(1) {
while(isspace(*ln)) {
if(*ln == '\n')
lno++;
ln++;
}
if(!*ln)
break;
if(*ln == '#') {
while(*++ln && *ln != '\n');
continue;
}
for(e = ln; *e && !isspace(*e); e++);
c = *e;
*e = 0;
const char **kw = bsearch(ln, kws, sizeof(kws)/sizeof(kws[0]),
sizeof(ln), kwcmp);
*e = c;
if(!kw)
abort_parse("unknown keyword");
has_conf++;
for(ln = e; isspace(*ln) && *ln != '\n'; ln++);
for(e = ln; *e && *e != '\n'; e++);
while(e > ln && isspace(e[-1]))
e--;
c = *e;
*e = 0;
switch(kw - kws) {
case KW_SECTION:
/* is it a continuation? */
for(i = 0; i < nconf; i++)
if((!*ln && !conf[i].name) ||
(conf[i].name && !strcmp(ln, conf[i].name)))
break;
if(i < nconf) {
/* yes */
sec = &conf[i];
has_conf = 2; /* don't allow USE */
} else if(i == 1 && !sec->name && has_conf == 1) {
/* no, and it's the first section w/ no unnamed entries */
/* so replace the 1st section */
if(*ln) {
sec->name = strdup(ln);
if(!sec->name) {
fprintf(logf, "%s: %s\n", "sec name", strerror(errno));
goto err;
}
}
} else {
/* no, and it doesn't replace the first unnamed */
/* yeah, one at a time, rather than in blocks. too lazy */
sec = conf;
conf = realloc(conf, ++nconf * sizeof(*conf));
if(!conf) {
conf = sec;
fprintf(logf, "%s: %s\n", "expand conf", strerror(errno));
goto err;
}
sec = &conf[nconf - 1];
memset(sec, 0, sizeof(*sec));
if(*ln) {
sec->name = strdup(ln);
if(!sec->name) {
fprintf(logf, "%s: %s\n", "sec name", strerror(errno));
goto err;
}
}
has_conf = 1;
}
break;
case KW_USE:
if(has_conf != 2)
abort_parse("use must be first in a section");
for(i = 0; i < nconf; i++)
if((!*ln && !conf[i].name) ||
(conf[i].name && !strcmp(ln, conf[i].name)))
break;
if(i == nconf)
abort_parse("unknown section");
if(i == sec - conf)
abort_parse("can't include self");
free(sec->ax_map);
free(sec->bt_map);
memcpy((char *)sec + offsetof(struct evjrconf, bt_low),
(char *)&conf[i] + offsetof(struct evjrconf, bt_low),
sizeof(*sec) - offsetof(struct evjrconf, bt_low));
sec->ax_map = malloc(sec->max_ax * sizeof(*sec->ax_map));
sec->bt_map = malloc(sec->max_bt * sizeof(*sec->bt_map));
if(!sec->ax_map || !sec->bt_map)
abort_parse("no mem");
memcpy(sec->ax_map, conf[i].ax_map, sec->max_ax * sizeof(*sec->ax_map));
memcpy(sec->bt_map, conf[i].bt_map, sec->max_bt * sizeof(*sec->bt_map));
#define cp_jsmap(t) do { \
if(sec->js##t##map) { \
int sz = (sec->js##t##map[0] + 1) * sizeof(sec->js##t##map[0]); \
sec->js##t##map = malloc(sz); \
if(!sec->js##t##map) \
abort_parse("no mem"); \
memcpy(sec->js##t##map, conf[i].js##t##map, sz); \
} \
} while(0)
cp_jsmap(ax);
cp_jsmap(bt);
#define dupstr(s) do { \
if(conf[i].s) { \
sec->s = strdup(conf[i].s); \
if(!sec->s) \
abort_parse("no mem"); \
} \
} while(0)
dupstr(repl_name);
dupstr(repl_id);
dupstr(repl_uniq);
#define comp_regex(s, type) do { \
if((ret = regcomp(&sec->type, s, REG_EXTENDED | REG_NOSUB))) { \
regerror(ret, &sec->type, buf, sizeof(buf)); \
fprintf(logf, #type " pattern error: %.*s\n", (int)sizeof(buf), buf); \
regfree(&sec->type); \
goto err; \
} \
sec->type##_str = strdup(s); \
if(!sec->type##_str) { \
fprintf(logf, "%s: %s\n", s, strerror(errno)); \
regfree(&sec->type); \
goto err; \
} \
} while(0)
#define dupre(r) do { \
if(conf[i].r##_str) \
comp_regex(conf[i].r##_str, r); \
} while(0)
dupre(match);
dupre(reject);
break;
case KW_MATCH:
#define parse_regex(type) do { \
if(sec->type##_str) { \
free(sec->type##_str); \
sec->type##_str = NULL; \
regfree(&sec->type); \
} \
comp_regex(ln, type); \
} while(0)
parse_regex(match);
break;
case KW_REJECT:
parse_regex(reject);
break;
case KW_FILTER:
if(*ln)
abort_parse("filter takes no parameter");
sec->filter_dev = 1;
break;
case KW_NAME:
#define store_repl(w) do { \
if(sec->repl_##w) \
free(sec->repl_##w); \
sec->repl_##w = strdup(ln); \
if(!sec->repl_##w) \
abort_parse(#w); \
} while(0)
store_repl(name);
break;
case KW_JSRENAME:
if(*ln)
abort_parse("jsrename takes no parameter");
sec->jsrename = 1;
break;
case KW_ID:
store_repl(id);
break;
case KW_UNIQ:
store_repl(uniq);
break;
case KW_AXES:
/* I guess duplicates are OK here */
/* blank list just skips an auto */
if(!*ln) {
int t;
#define next_auto_axis do { \
sec->auto_ax++; \
for(t = 0; t < sec->nax; t++) \
if((sec->ax_map[t].flags & (AXFL_MAP | AXFL_BUTTON)) == AXFL_MAP && \
sec->ax_map[t].target == sec->auto_ax) { \
sec->auto_ax++; \
t = -1; \
} \
for(t = 0; t < sec->nbt; t++) \
if((sec->bt_map[t].flags & (BTFL_MAP | BTFL_AXIS)) == (BTFL_MAP | BTFL_AXIS) && \
(sec->bt_map[t].onax == sec->auto_ax || sec->bt_map[t].offax == sec->auto_ax)) { \
sec->auto_ax++; \
t = -1; \
} \
t = -1; \
} while(0)
next_auto_axis;
break;
}
sec->filter_ax |= 1;
while(*ln) {
int a = -1, t = -1;
if(*ln == '!' && isdigit(ln[1])) {
/* FIXME: abort if value > ABS_MAX */
a = strtol(ln + 1, &ln, 0);
if(*ln && *ln != ',')
abort_parse("invalid !");
if(sec->nax <= a)
sec->nax = a + 1;
map_resize(ax, sec->nax);
sec->ax_map[a].flags = AXFL_MAP;
sec->ax_map[a].target = -1;
if(*ln == ',')
ln++;
continue;
}
if(isdigit(*ln))
/* FIXME: abort if value > ABS_MAX */
a = t = strtol(ln, (char **)&ln, 0);
if(*ln != '=') {
/* skip if target already used */
next_auto_axis;
} else {
if(t < 0 || ln[1] == ',')
abort_parse("unexpected =");
ln++;
a = -1;
}
if(a < 0 && t < 0 && *ln == ',') {
++ln;
continue;
}
int invert = a < 0 && *ln == '-';
if(invert) {
ln++;
if(!isdigit(*ln))
abort_parse("unexpected -");
invert = AXFL_INVERT;
}
if(isdigit(*ln))
/* FIXME: abort if value > ABS_MAX */
a = strtol(ln, (char **)&ln, 0);
if(a >= 0) {
if(sec->nax <= a)
sec->nax = a + 1;
map_resize(ax, sec->nax);
memset(sec->ax_map + a, 0, sizeof(*sec->ax_map));
sec->ax_map[a].flags = AXFL_MAP | invert;
sec->ax_map[a].target = t < 0 ? sec->auto_ax : t;
if(*ln == '-' && isdigit(ln[1])) {
/* FIXME: abort if value > ABS_MAX */
int b = strtol(ln + 1, (char **)&ln, 0);
if(b < a)
abort_parse("invalid range");
if(sec->nax <= b)
sec->nax = b + 1;
map_resize(ax, sec->nax);
for(;++a <= b;) {
if(t < 0)
next_auto_axis;
else
t++;
memset(&sec->ax_map[a], 0, sizeof(*sec->ax_map));
sec->ax_map[a].flags = AXFL_MAP | invert;
sec->ax_map[a].target = t < 0 ? sec->auto_ax : t;
}
}
} else if(tolower(*ln) == 'b') {
ln++;
if(t < 0) /* we don't need this flag any more as there are no ranges */
t = sec->auto_ax;
int l, m, h, li, mi, hi;
if((li = *ln == '-'))
ln++;
l = bnum(&ln);