XInput.java
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13: package xeij;
14:
15: import java.lang.foreign.*;
16: import java.lang.invoke.*;
17: import java.util.*;
18:
19:
20:
21: public final class XInput extends Thread {
22:
23:
24:
25:
26:
27:
28: public static final int UP_BIT = 0;
29: public static final int DOWN_BIT = 1;
30: public static final int LEFT_BIT = 2;
31: public static final int RIGHT_BIT = 3;
32: public static final int START_BIT = 4;
33: public static final int BACK_BIT = 5;
34: public static final int LSTICK_BIT = 6;
35: public static final int RSTICK_BIT = 7;
36: public static final int LB_BIT = 8;
37: public static final int RB_BIT = 9;
38: public static final int A_BIT = 12;
39: public static final int B_BIT = 13;
40: public static final int X_BIT = 14;
41: public static final int Y_BIT = 15;
42: public static final int LSUP_BIT = 16;
43: public static final int LSDOWN_BIT = 17;
44: public static final int LSLEFT_BIT = 18;
45: public static final int LSRIGHT_BIT = 19;
46: public static final int RSUP_BIT = 20;
47: public static final int RSDOWN_BIT = 21;
48: public static final int RSLEFT_BIT = 22;
49: public static final int RSRIGHT_BIT = 23;
50: public static final int LTRIGGER_BIT = 24;
51: public static final int RTRIGGER_BIT = 25;
52: public static final int BUTTONS = 26;
53:
54:
55: public static final int UP_MASK = 1 << UP_BIT;
56: public static final int DOWN_MASK = 1 << DOWN_BIT;
57: public static final int LEFT_MASK = 1 << LEFT_BIT;
58: public static final int RIGHT_MASK = 1 << RIGHT_BIT;
59: public static final int START_MASK = 1 << START_BIT;
60: public static final int BACK_MASK = 1 << BACK_BIT;
61: public static final int LSTICK_MASK = 1 << LSTICK_BIT;
62: public static final int RSTICK_MASK = 1 << RSTICK_BIT;
63: public static final int LB_MASK = 1 << LB_BIT;
64: public static final int RB_MASK = 1 << RB_BIT;
65: public static final int A_MASK = 1 << A_BIT;
66: public static final int B_MASK = 1 << B_BIT;
67: public static final int X_MASK = 1 << X_BIT;
68: public static final int Y_MASK = 1 << Y_BIT;
69: public static final int LSUP_MASK = 1 << LSUP_BIT;
70: public static final int LSDOWN_MASK = 1 << LSDOWN_BIT;
71: public static final int LSLEFT_MASK = 1 << LSLEFT_BIT;
72: public static final int LSRIGHT_MASK = 1 << LSRIGHT_BIT;
73: public static final int RSUP_MASK = 1 << RSUP_BIT;
74: public static final int RSDOWN_MASK = 1 << RSDOWN_BIT;
75: public static final int RSLEFT_MASK = 1 << RSLEFT_BIT;
76: public static final int RSRIGHT_MASK = 1 << RSRIGHT_BIT;
77: public static final int LTRIGGER_MASK = 1 << LTRIGGER_BIT;
78: public static final int RTRIGGER_MASK = 1 << RTRIGGER_BIT;
79:
80:
81: public static final String BIT_TO_TEXT[] = new String[] {
82: "UP", "DOWN", "LEFT", "RIGHT", "START", "BACK", "LSTICK", "RSTICK",
83: "LB", "RB", "(10)", "(11)", "A", "B", "X", "Y",
84: "LSUP", "LSDOWN", "LSLEFT", "LSRIGHT", "RSUP", "RSDOWN", "RSLEFT", "RSRIGHT",
85: "LTRIGGER", "RTRIGGER",
86: };
87:
88:
89:
90:
91:
92:
93: protected static final ValueLayout.OfInt DWORD = ValueLayout.JAVA_INT;
94: protected static final ValueLayout.OfShort WORD = ValueLayout.JAVA_SHORT;
95: protected static final ValueLayout.OfByte BYTE = ValueLayout.JAVA_BYTE;
96: protected static final ValueLayout.OfShort SHORT = ValueLayout.JAVA_SHORT;
97:
98:
99: protected static final int ERROR_SUCCESS = 0;
100:
101:
102:
103:
104:
105: protected static final MemoryLayout GAMEPAD_LAYOUT = MemoryLayout.structLayout (
106: WORD.withName ("wButtons"),
107: BYTE.withName ("bLeftTrigger"),
108: BYTE.withName ("bRightTrigger"),
109: SHORT.withName ("sThumbLX"),
110: SHORT.withName ("sThumbLY"),
111: SHORT.withName ("sThumbRX"),
112: SHORT.withName ("sThumbRY"));
113: protected static final long OFFSET_wButtons = GAMEPAD_LAYOUT.byteOffset (MemoryLayout.PathElement.groupElement ("wButtons"));
114: protected static final long OFFSET_bLeftTrigger = GAMEPAD_LAYOUT.byteOffset (MemoryLayout.PathElement.groupElement ("bLeftTrigger"));
115: protected static final long OFFSET_bRightTrigger = GAMEPAD_LAYOUT.byteOffset (MemoryLayout.PathElement.groupElement ("bRightTrigger"));
116: protected static final long OFFSET_sThumbLX = GAMEPAD_LAYOUT.byteOffset (MemoryLayout.PathElement.groupElement ("sThumbLX"));
117: protected static final long OFFSET_sThumbLY = GAMEPAD_LAYOUT.byteOffset (MemoryLayout.PathElement.groupElement ("sThumbLY"));
118: protected static final long OFFSET_sThumbRX = GAMEPAD_LAYOUT.byteOffset (MemoryLayout.PathElement.groupElement ("sThumbRX"));
119: protected static final long OFFSET_sThumbRY = GAMEPAD_LAYOUT.byteOffset (MemoryLayout.PathElement.groupElement ("sThumbRY"));
120:
121:
122: protected static final MemoryLayout STATE_LAYOUT = MemoryLayout.structLayout (
123: DWORD.withName ("dwPacketNumber"),
124: GAMEPAD_LAYOUT.withName ("Gamepad"));
125: protected static final long OFFSET_dwPacketNumber = STATE_LAYOUT.byteOffset (MemoryLayout.PathElement.groupElement ("dwPacketNumber"));
126:
127:
128:
129:
130:
131:
132: protected Gamepad[] gamepads;
133:
134:
135: protected ArrayList<GamepadListener> gamepadListeners;
136:
137:
138: protected boolean polling;
139:
140:
141: protected MethodHandle XInputGetState;
142:
143:
144: protected MemorySegment state;
145: protected MemorySegment gamepad;
146:
147:
148:
149:
150:
151:
152:
153: public XInput () {
154:
155: gamepads = new Gamepad[4];
156: for (int index = 0; index < 4; index++) {
157: gamepads[index] = new Gamepad (index);
158: }
159:
160: gamepadListeners = new ArrayList<GamepadListener> ();
161:
162: polling = true;
163: this.start ();
164: }
165:
166:
167:
168:
169:
170:
171: @Override public void run () {
172:
173:
174: try (Arena arena = Arena.ofConfined ()) {
175:
176: SymbolLookup xinputLibrary = SymbolLookup.libraryLookup ("XInput1_4.dll", arena);
177:
178: Linker linker = Linker.nativeLinker ();
179:
180:
181: XInputGetState = linker.downcallHandle (
182: xinputLibrary.findOrThrow ("XInputGetState"),
183: FunctionDescriptor.of (
184: DWORD,
185: DWORD,
186: ValueLayout.ADDRESS));
187:
188: state = arena.allocate (STATE_LAYOUT);
189: gamepad = state.asSlice (STATE_LAYOUT.byteOffset (MemoryLayout.PathElement.groupElement ("Gamepad")));
190:
191:
192: while (polling) {
193: boolean available = false;
194: int counter = 0;
195: for (int index = 0; index < 4; index++) {
196: Gamepad gamepad = gamepads[index];
197: if (counter == 0 || gamepad.isAvailable ()) {
198: available = available || gamepad.isAvailable ();
199: if (gamepad.getState () &&
200: gamepadListeners.size () != 0) {
201: if (gamepad.isConnected ()) {
202: for (GamepadListener listener : gamepadListeners) {
203: listener.connected (gamepad);
204: }
205: } else if (gamepad.isDisconnected ()) {
206: for (GamepadListener listener : gamepadListeners) {
207: listener.disconnected (gamepad);
208: }
209: }
210: if (gamepad.isAvailable ()) {
211: int buttonMasks = gamepad.getPressedButtonMasks ();
212: if (buttonMasks != 0) {
213: for (GamepadListener listener : gamepadListeners) {
214: listener.buttonPressed (gamepad, buttonMasks);
215: }
216: }
217: buttonMasks = gamepad.getReleasedButtonMasks ();
218: if (buttonMasks != 0) {
219: for (GamepadListener listener : gamepadListeners) {
220: listener.buttonReleased (gamepad, buttonMasks);
221: }
222: }
223: if (gamepad.isLeftStickMovedX ()) {
224: for (GamepadListener listener : gamepadListeners) {
225: listener.leftStickMovedX (gamepad);
226: }
227: }
228: if (gamepad.isLeftStickMovedY ()) {
229: for (GamepadListener listener : gamepadListeners) {
230: listener.leftStickMovedY (gamepad);
231: }
232: }
233: if (gamepad.isLeftTriggerMoved ()) {
234: for (GamepadListener listener : gamepadListeners) {
235: listener.leftTriggerMoved (gamepad);
236: }
237: }
238: if (gamepad.isRightStickMovedX ()) {
239: for (GamepadListener listener : gamepadListeners) {
240: listener.rightStickMovedX (gamepad);
241: }
242: }
243: if (gamepad.isRightStickMovedY ()) {
244: for (GamepadListener listener : gamepadListeners) {
245: listener.rightStickMovedY (gamepad);
246: }
247: }
248: if (gamepad.isRightTriggerMoved ()) {
249: for (GamepadListener listener : gamepadListeners) {
250: listener.rightTriggerMoved (gamepad);
251: }
252: }
253: }
254: }
255: }
256: }
257: try {
258: if (available) {
259: counter = (counter + 1) & 31;
260: Thread.sleep (10L);
261: } else {
262: counter = 0;
263: Thread.sleep (320L);
264: }
265: } catch (InterruptedException ie) {
266: return;
267: }
268: }
269: }
270: }
271:
272:
273:
274:
275:
276:
277:
278: public void end () {
279:
280: if (polling) {
281: polling = false;
282: if (this.isAlive ()) {
283: this.interrupt ();
284: try {
285: this.join ();
286: } catch (InterruptedException ie) {
287: }
288: }
289: }
290:
291: for (int index = 0; index < 4; index++) {
292: gamepads[index].close ();
293: }
294: }
295:
296:
297:
298:
299:
300: public int getButtonMasks (int index) {
301: return gamepads[index].getButtonMasks ();
302: }
303:
304:
305:
306:
307:
308: public int getLeftStickX (int index) {
309: return gamepads[index].getLeftStickX ();
310: }
311:
312:
313:
314:
315:
316: public int getLeftStickY (int index) {
317: return gamepads[index].getLeftStickY ();
318: }
319:
320:
321:
322:
323:
324: public int getLeftTrigger (int index) {
325: return gamepads[index].getLeftTrigger ();
326: }
327:
328:
329:
330:
331:
332: public int getRightStickX (int index) {
333: return gamepads[index].getRightStickX ();
334: }
335:
336:
337:
338:
339:
340: public int getRightStickY (int index) {
341: return gamepads[index].getRightStickY ();
342: }
343:
344:
345:
346:
347:
348: public int getRightTrigger (int index) {
349: return gamepads[index].getRightTrigger ();
350: }
351:
352:
353:
354:
355:
356: public boolean isAvailable (int index) {
357: return gamepads[index].isAvailable ();
358: }
359:
360:
361:
362:
363:
364: public void setThresholdOfLeftStick (int index, int thresholdOfLeftStick) {
365: gamepads[index].setThresholdOfLeftStick (thresholdOfLeftStick);
366: }
367:
368:
369:
370:
371:
372: public void setThresholdOfRightStick (int index, int thresholdOfRightStick) {
373: gamepads[index].setThresholdOfRightStick (thresholdOfRightStick);
374: }
375:
376:
377:
378:
379:
380: public void setThresholdOfLeftTrigger (int index, int thresholdOfLeftTrigger) {
381: gamepads[index].setThresholdOfLeftTrigger (thresholdOfLeftTrigger);
382: }
383:
384:
385:
386:
387:
388: public void setThresholdOfRightTrigger (int index, int thresholdOfRightTrigger) {
389: gamepads[index].setThresholdOfRightTrigger (thresholdOfRightTrigger);
390: }
391:
392:
393:
394:
395:
396:
397:
398: public interface GamepadListener {
399: public void connected (Gamepad gamepad);
400: public void disconnected (Gamepad gamepad);
401: public void buttonPressed (Gamepad gamepad, int buttonMasks);
402: public void buttonReleased (Gamepad gamepad, int buttonMasks);
403: public void leftStickMovedX (Gamepad gamepad);
404: public void leftStickMovedY (Gamepad gamepad);
405: public void leftTriggerMoved (Gamepad gamepad);
406: public void rightStickMovedX (Gamepad gamepad);
407: public void rightStickMovedY (Gamepad gamepad);
408: public void rightTriggerMoved (Gamepad gamepad);
409: }
410:
411:
412:
413:
414: public void addGamepadListener (GamepadListener listener) {
415: if (listener != null && !gamepadListeners.contains (listener)) {
416: gamepadListeners.add (listener);
417: }
418: }
419:
420:
421:
422:
423: public void removeGamepadListener (GamepadListener listener) {
424: gamepadListeners.remove (listener);
425: }
426:
427:
428:
429: public void removeGamepadListeners () {
430: gamepadListeners.clear ();
431: }
432:
433:
434:
435: public GamepadListener[] getGamepadListeners () {
436: return gamepadListeners.toArray (new GamepadListener[gamepadListeners.size ()]);
437: }
438:
439:
440:
441:
442:
443:
444:
445: public static class GamepadAdapter implements GamepadListener {
446: @Override public void connected (Gamepad gamepad) {
447: }
448: @Override public void disconnected (Gamepad gamepad) {
449: }
450: @Override public void buttonPressed (Gamepad gamepad, int buttonMasks) {
451: }
452: @Override public void buttonReleased (Gamepad gamepad, int buttonMasks) {
453: }
454: @Override public void leftStickMovedX (Gamepad gamepad) {
455: }
456: @Override public void leftStickMovedY (Gamepad gamepad) {
457: }
458: @Override public void leftTriggerMoved (Gamepad gamepad) {
459: }
460: @Override public void rightStickMovedX (Gamepad gamepad) {
461: }
462: @Override public void rightStickMovedY (Gamepad gamepad) {
463: }
464: @Override public void rightTriggerMoved (Gamepad gamepad) {
465: }
466: }
467:
468:
469:
470:
471:
472:
473:
474: public class Gamepad implements AutoCloseable {
475:
476:
477:
478:
479: static final int THRESHOLD_OF_STICK = (32768 / 2);
480: static final int THRESHOLD_OF_TRIGGER = (256 / 4);
481:
482:
483: int index;
484:
485: boolean available;
486: int dwPacketNumber;
487: int wButtons;
488: int bLeftTrigger;
489: int bRightTrigger;
490: int sThumbLX;
491: int sThumbLY;
492: int sThumbRX;
493: int sThumbRY;
494: int buttonMasks;
495:
496: boolean previous_available;
497: int previous_dwPacketNumber;
498: int previous_wButtons;
499: int previous_bLeftTrigger;
500: int previous_bRightTrigger;
501: int previous_sThumbLX;
502: int previous_sThumbLY;
503: int previous_sThumbRX;
504: int previous_sThumbRY;
505: int previous_buttonMasks;
506:
507: long thresholdOfLeftStick2;
508: long thresholdOfRightStick2;
509: int thresholdOfLeftTrigger;
510: int thresholdOfRightTrigger;
511:
512:
513:
514:
515: public Gamepad (int index) {
516: open (index);
517: }
518:
519:
520:
521: @Override public void close () {
522: available = false;
523: dwPacketNumber = 0;
524: wButtons = 0;
525: bLeftTrigger = 0;
526: bRightTrigger = 0;
527: sThumbLX = 0;
528: sThumbLY = 0;
529: sThumbRX = 0;
530: sThumbRY = 0;
531: buttonMasks = 0;
532: previous_available = false;
533: previous_dwPacketNumber = 0;
534: previous_wButtons = 0;
535: previous_bLeftTrigger = 0;
536: previous_bRightTrigger = 0;
537: previous_sThumbLX = 0;
538: previous_sThumbLY = 0;
539: previous_sThumbRX = 0;
540: previous_sThumbRY = 0;
541: previous_buttonMasks = 0;
542: }
543:
544:
545:
546:
547: public int getButtonMasks () {
548: return buttonMasks;
549: }
550:
551:
552:
553:
554: public int getIndex () {
555: return index;
556: }
557:
558:
559:
560:
561: public int getLeftStickX () {
562: return sThumbLX;
563: }
564:
565:
566:
567:
568: public int getLeftStickY () {
569: return sThumbLY;
570: }
571:
572:
573:
574:
575: public int getLeftTrigger () {
576: return bLeftTrigger;
577: }
578:
579:
580:
581:
582: public int getPressedButtonMasks () {
583: return buttonMasks & ~previous_buttonMasks;
584: }
585:
586:
587:
588:
589: public int getReleasedButtonMasks () {
590: return ~buttonMasks & previous_buttonMasks;
591: }
592:
593:
594:
595:
596: public int getRightStickX () {
597: return sThumbRX;
598: }
599:
600:
601:
602:
603: public int getRightStickY () {
604: return sThumbRY;
605: }
606:
607:
608:
609:
610: public int getRightTrigger () {
611: return bRightTrigger;
612: }
613:
614:
615:
616:
617: public boolean getState () {
618:
619: previous_available = available;
620: previous_wButtons = wButtons;
621: previous_bLeftTrigger = bLeftTrigger;
622: previous_bRightTrigger = bRightTrigger;
623: previous_sThumbLX = sThumbLX;
624: previous_sThumbLY = sThumbLY;
625: previous_sThumbRX = sThumbRX;
626: previous_sThumbRY = sThumbRY;
627: previous_buttonMasks = buttonMasks;
628:
629: available = XInputGetState != null;
630: if (available) {
631: try {
632: available = (int) XInputGetState.invoke (index, state) == ERROR_SUCCESS;
633: } catch (Throwable e) {
634:
635: }
636: }
637: if (available) {
638: dwPacketNumber = state.get (DWORD, OFFSET_dwPacketNumber);
639: wButtons = (char) gamepad.get (WORD, OFFSET_wButtons);
640: bLeftTrigger = 0xff & gamepad.get (BYTE, OFFSET_bLeftTrigger);
641: bRightTrigger = 0xff & gamepad.get (BYTE, OFFSET_bRightTrigger);
642: sThumbLX = gamepad.get (SHORT, OFFSET_sThumbLX);
643: sThumbLY = gamepad.get (SHORT, OFFSET_sThumbLY);
644: sThumbRX = gamepad.get (SHORT, OFFSET_sThumbRX);
645: sThumbRY = gamepad.get (SHORT, OFFSET_sThumbRY);
646: } else {
647: dwPacketNumber = 0;
648: wButtons = 0;
649: bLeftTrigger = 0;
650: bRightTrigger = 0;
651: sThumbLX = 0;
652: sThumbLY = 0;
653: sThumbRX = 0;
654: sThumbRY = 0;
655: }
656:
657: int masks = wButtons;
658:
659: {
660: int x = sThumbLX;
661: int y = sThumbLY;
662: if (thresholdOfLeftStick2 < ((long) x * (long) x + (long) y * (long) y)) {
663: int px = 5741 * x - 2378 * y;
664: int py = 2378 * x + 5741 * y;
665: int qx = 5741 * x + 2378 * y;
666: int qy = -2378 * x + 5741 * y;
667: masks |= ((0 <= (-px | -qx) ? LSLEFT_MASK : 0 <= (px | qx) ? LSRIGHT_MASK : 0) |
668: (0 <= (-py | -qy) ? LSDOWN_MASK : 0 <= (py | qy) ? LSUP_MASK : 0));
669: }
670: }
671:
672: {
673: int x = sThumbRX;
674: int y = sThumbRY;
675: if (thresholdOfRightStick2 < ((long) x * (long) x + (long) y * (long) y)) {
676: int px = 5741 * x - 2378 * y;
677: int py = 2378 * x + 5741 * y;
678: int qx = 5741 * x + 2378 * y;
679: int qy = -2378 * x + 5741 * y;
680: masks |= ((0 <= (-px | -qx) ? RSLEFT_MASK : 0 <= (px | qx) ? RSRIGHT_MASK : 0) |
681: (0 <= (-py | -qy) ? RSDOWN_MASK : 0 <= (py | qy) ? RSUP_MASK : 0));
682: }
683: }
684:
685: if (thresholdOfLeftTrigger < bLeftTrigger) {
686: masks |= LTRIGGER_MASK;
687: }
688:
689: if (thresholdOfRightTrigger < bRightTrigger) {
690: masks |= RTRIGGER_MASK;
691: }
692: buttonMasks = masks;
693:
694: return ((available != previous_available) ||
695: (available &&
696: dwPacketNumber != previous_dwPacketNumber));
697: }
698:
699:
700:
701:
702: public boolean isAvailable () {
703: return available;
704: }
705:
706:
707:
708:
709: public boolean isConnected () {
710: return available && !previous_available;
711: }
712:
713:
714:
715:
716: public boolean isDisconnected () {
717: return !available && previous_available;
718: }
719:
720:
721:
722:
723: public boolean isLeftStickMovedX () {
724: return sThumbLX != previous_sThumbLX;
725: }
726:
727:
728:
729:
730: public boolean isLeftStickMovedY () {
731: return sThumbLY != previous_sThumbLY;
732: }
733:
734:
735:
736:
737: public boolean isLeftTriggerMoved () {
738: return bLeftTrigger != previous_bLeftTrigger;
739: }
740:
741:
742:
743:
744: public boolean isRightStickMovedX () {
745: return sThumbRX != previous_sThumbRX;
746: }
747:
748:
749:
750:
751: public boolean isRightStickMovedY () {
752: return sThumbRY != previous_sThumbRY;
753: }
754:
755:
756:
757:
758: public boolean isRightTriggerMoved () {
759: return bRightTrigger != previous_bRightTrigger;
760: }
761:
762:
763:
764:
765: private void open (int index) {
766:
767: if (index < 0 || 3 < index) {
768: throw new IllegalArgumentException (String.format ("Controller %d cannot be specified", index));
769: }
770:
771: this.index = index;
772: available = false;
773: dwPacketNumber = 0;
774: wButtons = 0;
775: bLeftTrigger = 0;
776: bRightTrigger = 0;
777: sThumbLX = 0;
778: sThumbLY = 0;
779: sThumbRX = 0;
780: sThumbRY = 0;
781: buttonMasks = 0;
782: previous_available = false;
783: previous_dwPacketNumber = 0;
784: previous_wButtons = 0;
785: previous_bLeftTrigger = 0;
786: previous_bRightTrigger = 0;
787: previous_sThumbLX = 0;
788: previous_sThumbLY = 0;
789: previous_sThumbRX = 0;
790: previous_sThumbRY = 0;
791: previous_buttonMasks = 0;
792: thresholdOfLeftStick2 = (long) THRESHOLD_OF_STICK * (long) THRESHOLD_OF_STICK;
793: thresholdOfRightStick2 = (long) THRESHOLD_OF_STICK * (long) THRESHOLD_OF_STICK;
794: thresholdOfLeftTrigger = THRESHOLD_OF_TRIGGER;
795: thresholdOfRightTrigger = THRESHOLD_OF_TRIGGER;
796: }
797:
798:
799:
800:
801: public void setThresholdOfLeftStick (int thresholdOfLeftStick) {
802: thresholdOfLeftStick2 = (long) thresholdOfLeftStick * (long) thresholdOfLeftStick;
803: }
804:
805:
806:
807:
808: public void setThresholdOfRightStick (int thresholdOfRightStick) {
809: thresholdOfRightStick2 = (long) thresholdOfRightStick * (long) thresholdOfRightStick;
810: }
811:
812:
813:
814:
815: public void setThresholdOfLeftTrigger (int thresholdOfLeftTrigger) {
816: this.thresholdOfLeftTrigger = thresholdOfLeftTrigger;
817: }
818:
819:
820:
821:
822: public void setThresholdOfRightTrigger (int thresholdOfRightTrigger) {
823: this.thresholdOfRightTrigger = thresholdOfRightTrigger;
824: }
825:
826: }
827:
828:
829:
830: }