@gjsify/gamepad 0.3.13 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/axis-mapping.js +35 -29
- package/lib/esm/button-mapping.js +70 -86
- package/lib/esm/gamepad-button.js +20 -15
- package/lib/esm/gamepad-event.js +18 -12
- package/lib/esm/gamepad-manager.js +213 -218
- package/lib/esm/gamepad.js +31 -25
- package/lib/esm/haptic-actuator.js +34 -26
- package/lib/esm/index.js +6 -20
- package/lib/esm/register.js +6 -2
- package/package.json +7 -7
package/lib/esm/axis-mapping.js
CHANGED
|
@@ -1,35 +1,41 @@
|
|
|
1
|
+
//#region src/axis-mapping.ts
|
|
2
|
+
/**
|
|
3
|
+
* SDL logical axis indices as reported by Manette.Event.get_absolute().
|
|
4
|
+
* These are the SDL gamepad mapping indices, NOT Linux ABS_* hardware codes.
|
|
5
|
+
*/
|
|
1
6
|
const ManetteAxis = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
RIGHT_Y: 3,
|
|
9
|
-
// SDL righty
|
|
10
|
-
LEFT_TRIGGER: 4,
|
|
11
|
-
// SDL lefttrigger
|
|
12
|
-
RIGHT_TRIGGER: 5
|
|
13
|
-
// SDL righttrigger
|
|
7
|
+
LEFT_X: 0,
|
|
8
|
+
LEFT_Y: 1,
|
|
9
|
+
RIGHT_X: 2,
|
|
10
|
+
RIGHT_Y: 3,
|
|
11
|
+
LEFT_TRIGGER: 4,
|
|
12
|
+
RIGHT_TRIGGER: 5
|
|
14
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* W3C standard gamepad axis indices.
|
|
16
|
+
* https://w3c.github.io/gamepad/#remapping
|
|
17
|
+
*/
|
|
15
18
|
const W3CAxis = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
LEFT_STICK_X: 0,
|
|
20
|
+
LEFT_STICK_Y: 1,
|
|
21
|
+
RIGHT_STICK_X: 2,
|
|
22
|
+
RIGHT_STICK_Y: 3
|
|
20
23
|
};
|
|
24
|
+
/** Total number of axes in the W3C standard mapping. */
|
|
21
25
|
const W3C_AXIS_COUNT = 4;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Maps SDL logical axis → W3C axis index.
|
|
28
|
+
* Stick axes (0–3) map 1:1. Trigger axes (4–5) are NOT in this map —
|
|
29
|
+
* they are handled separately in gamepad-manager as buttons[6]/buttons[7].
|
|
30
|
+
*/
|
|
31
|
+
const MANETTE_TO_W3C_AXIS = new Map([
|
|
32
|
+
[ManetteAxis.LEFT_X, W3CAxis.LEFT_STICK_X],
|
|
33
|
+
[ManetteAxis.LEFT_Y, W3CAxis.LEFT_STICK_Y],
|
|
34
|
+
[ManetteAxis.RIGHT_X, W3CAxis.RIGHT_STICK_X],
|
|
35
|
+
[ManetteAxis.RIGHT_Y, W3CAxis.RIGHT_STICK_Y]
|
|
27
36
|
]);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
W3CAxis,
|
|
34
|
-
W3C_AXIS_COUNT
|
|
35
|
-
};
|
|
37
|
+
/** Threshold above which an analog trigger is considered "pressed". */
|
|
38
|
+
const TRIGGER_PRESS_THRESHOLD = .5;
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { MANETTE_TO_W3C_AXIS, ManetteAxis, TRIGGER_PRESS_THRESHOLD, W3CAxis, W3C_AXIS_COUNT };
|
|
@@ -1,92 +1,76 @@
|
|
|
1
|
+
//#region src/button-mapping.ts
|
|
2
|
+
/**
|
|
3
|
+
* Linux BTN_* input event codes as reported by Manette.Event.get_button().
|
|
4
|
+
* These are the actual values libmanette 0.2 passes in button-press/release signals.
|
|
5
|
+
*/
|
|
1
6
|
const LinuxButton = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// 0x139 — Right trigger (RT, R2)
|
|
22
|
-
BTN_SELECT: 314,
|
|
23
|
-
// 0x13a — Select / Back / Share
|
|
24
|
-
BTN_START: 315,
|
|
25
|
-
// 0x13b — Start / Menu / Options
|
|
26
|
-
BTN_MODE: 316,
|
|
27
|
-
// 0x13c — Home / Guide / PS
|
|
28
|
-
BTN_THUMBL: 317,
|
|
29
|
-
// 0x13d — Left stick click (L3)
|
|
30
|
-
BTN_THUMBR: 318,
|
|
31
|
-
// 0x13e — Right stick click (R3)
|
|
32
|
-
BTN_DPAD_UP: 544,
|
|
33
|
-
// 0x220
|
|
34
|
-
BTN_DPAD_DOWN: 545,
|
|
35
|
-
// 0x221
|
|
36
|
-
BTN_DPAD_LEFT: 546,
|
|
37
|
-
// 0x222
|
|
38
|
-
BTN_DPAD_RIGHT: 547
|
|
39
|
-
// 0x223
|
|
7
|
+
BTN_SOUTH: 304,
|
|
8
|
+
BTN_EAST: 305,
|
|
9
|
+
BTN_C: 306,
|
|
10
|
+
BTN_NORTH: 307,
|
|
11
|
+
BTN_WEST: 308,
|
|
12
|
+
BTN_Z: 309,
|
|
13
|
+
BTN_TL: 310,
|
|
14
|
+
BTN_TR: 311,
|
|
15
|
+
BTN_TL2: 312,
|
|
16
|
+
BTN_TR2: 313,
|
|
17
|
+
BTN_SELECT: 314,
|
|
18
|
+
BTN_START: 315,
|
|
19
|
+
BTN_MODE: 316,
|
|
20
|
+
BTN_THUMBL: 317,
|
|
21
|
+
BTN_THUMBR: 318,
|
|
22
|
+
BTN_DPAD_UP: 544,
|
|
23
|
+
BTN_DPAD_DOWN: 545,
|
|
24
|
+
BTN_DPAD_LEFT: 546,
|
|
25
|
+
BTN_DPAD_RIGHT: 547
|
|
40
26
|
};
|
|
27
|
+
/**
|
|
28
|
+
* W3C standard gamepad button indices.
|
|
29
|
+
* https://w3c.github.io/gamepad/#remapping
|
|
30
|
+
*/
|
|
41
31
|
const W3CButton = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
RIGHT_STICK: 11,
|
|
60
|
-
DPAD_UP: 12,
|
|
61
|
-
DPAD_DOWN: 13,
|
|
62
|
-
DPAD_LEFT: 14,
|
|
63
|
-
DPAD_RIGHT: 15,
|
|
64
|
-
HOME: 16
|
|
32
|
+
FACE_1: 0,
|
|
33
|
+
FACE_2: 1,
|
|
34
|
+
FACE_3: 2,
|
|
35
|
+
FACE_4: 3,
|
|
36
|
+
LEFT_BUMPER: 4,
|
|
37
|
+
RIGHT_BUMPER: 5,
|
|
38
|
+
LEFT_TRIGGER: 6,
|
|
39
|
+
RIGHT_TRIGGER: 7,
|
|
40
|
+
SELECT: 8,
|
|
41
|
+
START: 9,
|
|
42
|
+
LEFT_STICK: 10,
|
|
43
|
+
RIGHT_STICK: 11,
|
|
44
|
+
DPAD_UP: 12,
|
|
45
|
+
DPAD_DOWN: 13,
|
|
46
|
+
DPAD_LEFT: 14,
|
|
47
|
+
DPAD_RIGHT: 15,
|
|
48
|
+
HOME: 16
|
|
65
49
|
};
|
|
50
|
+
/** Total number of buttons in the W3C standard mapping. */
|
|
66
51
|
const W3C_BUTTON_COUNT = 17;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Maps Linux BTN_* code → W3C standard button index.
|
|
54
|
+
*/
|
|
55
|
+
const MANETTE_TO_W3C_BUTTON = new Map([
|
|
56
|
+
[LinuxButton.BTN_SOUTH, W3CButton.FACE_1],
|
|
57
|
+
[LinuxButton.BTN_EAST, W3CButton.FACE_2],
|
|
58
|
+
[LinuxButton.BTN_WEST, W3CButton.FACE_3],
|
|
59
|
+
[LinuxButton.BTN_NORTH, W3CButton.FACE_4],
|
|
60
|
+
[LinuxButton.BTN_TL, W3CButton.LEFT_BUMPER],
|
|
61
|
+
[LinuxButton.BTN_TR, W3CButton.RIGHT_BUMPER],
|
|
62
|
+
[LinuxButton.BTN_TL2, W3CButton.LEFT_TRIGGER],
|
|
63
|
+
[LinuxButton.BTN_TR2, W3CButton.RIGHT_TRIGGER],
|
|
64
|
+
[LinuxButton.BTN_SELECT, W3CButton.SELECT],
|
|
65
|
+
[LinuxButton.BTN_START, W3CButton.START],
|
|
66
|
+
[LinuxButton.BTN_THUMBL, W3CButton.LEFT_STICK],
|
|
67
|
+
[LinuxButton.BTN_THUMBR, W3CButton.RIGHT_STICK],
|
|
68
|
+
[LinuxButton.BTN_DPAD_UP, W3CButton.DPAD_UP],
|
|
69
|
+
[LinuxButton.BTN_DPAD_DOWN, W3CButton.DPAD_DOWN],
|
|
70
|
+
[LinuxButton.BTN_DPAD_LEFT, W3CButton.DPAD_LEFT],
|
|
71
|
+
[LinuxButton.BTN_DPAD_RIGHT, W3CButton.DPAD_RIGHT],
|
|
72
|
+
[LinuxButton.BTN_MODE, W3CButton.HOME]
|
|
85
73
|
]);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
LinuxButton as ManetteButton,
|
|
90
|
-
W3CButton,
|
|
91
|
-
W3C_BUTTON_COUNT
|
|
92
|
-
};
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { LinuxButton, LinuxButton as ManetteButton, MANETTE_TO_W3C_BUTTON, W3CButton, W3C_BUTTON_COUNT };
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
//#region src/gamepad-button.ts
|
|
2
|
+
/**
|
|
3
|
+
* Represents the state of a single button on a gamepad.
|
|
4
|
+
* https://w3c.github.io/gamepad/#dom-gamepadbutton
|
|
5
|
+
*/
|
|
6
|
+
var GamepadButton = class {
|
|
7
|
+
pressed;
|
|
8
|
+
touched;
|
|
9
|
+
value;
|
|
10
|
+
constructor(pressed = false, touched = false, value = 0) {
|
|
11
|
+
this.pressed = pressed;
|
|
12
|
+
this.touched = touched;
|
|
13
|
+
this.value = value;
|
|
14
|
+
}
|
|
15
|
+
get [Symbol.toStringTag]() {
|
|
16
|
+
return "GamepadButton";
|
|
17
|
+
}
|
|
16
18
|
};
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
export { GamepadButton };
|
package/lib/esm/gamepad-event.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { Event } from "@gjsify/dom-events";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
|
|
3
|
+
//#region src/gamepad-event.ts
|
|
4
|
+
/**
|
|
5
|
+
* Fired on the Window when a gamepad is connected or disconnected.
|
|
6
|
+
* https://w3c.github.io/gamepad/#dom-gamepadevent
|
|
7
|
+
*/
|
|
8
|
+
var GamepadEvent = class extends Event {
|
|
9
|
+
gamepad;
|
|
10
|
+
constructor(type, eventInitDict) {
|
|
11
|
+
super(type, eventInitDict);
|
|
12
|
+
this.gamepad = eventInitDict.gamepad;
|
|
13
|
+
}
|
|
14
|
+
get [Symbol.toStringTag]() {
|
|
15
|
+
return "GamepadEvent";
|
|
16
|
+
}
|
|
14
17
|
};
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { GamepadEvent };
|
|
@@ -1,223 +1,218 @@
|
|
|
1
|
+
import { MANETTE_TO_W3C_AXIS, ManetteAxis, TRIGGER_PRESS_THRESHOLD, W3C_AXIS_COUNT } from "./axis-mapping.js";
|
|
2
|
+
import { MANETTE_TO_W3C_BUTTON, W3CButton, W3C_BUTTON_COUNT } from "./button-mapping.js";
|
|
1
3
|
import { GamepadButton } from "./gamepad-button.js";
|
|
2
|
-
import { Gamepad } from "./gamepad.js";
|
|
3
4
|
import { GamepadEvent } from "./gamepad-event.js";
|
|
5
|
+
import { Gamepad } from "./gamepad.js";
|
|
4
6
|
import { ManetteHapticActuator } from "./haptic-actuator.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { W3CButton } from "./button-mapping.js";
|
|
7
|
+
|
|
8
|
+
//#region src/gamepad-manager.ts
|
|
8
9
|
const MAX_GAMEPADS = 4;
|
|
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
|
-
this._monitorSignalIds = [];
|
|
215
|
-
this._monitor = null;
|
|
216
|
-
}
|
|
217
|
-
this._initialized = false;
|
|
218
|
-
this._initPromise = null;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
export {
|
|
222
|
-
GamepadManager
|
|
10
|
+
/**
|
|
11
|
+
* Singleton manager that wraps Manette.Monitor and maintains gamepad state.
|
|
12
|
+
*
|
|
13
|
+
* libmanette fires GObject signals on button/axis changes. This manager
|
|
14
|
+
* caches the latest state so that `getGamepads()` can return a snapshot
|
|
15
|
+
* matching the W3C Gamepad API's polling model.
|
|
16
|
+
*/
|
|
17
|
+
var GamepadManager = class {
|
|
18
|
+
_monitor = null;
|
|
19
|
+
_slots = new Array(MAX_GAMEPADS).fill(null);
|
|
20
|
+
_monitorSignalIds = [];
|
|
21
|
+
_ManetteModule = null;
|
|
22
|
+
_initPromise = null;
|
|
23
|
+
_initialized = false;
|
|
24
|
+
/**
|
|
25
|
+
* Lazily initialize the Manette.Monitor.
|
|
26
|
+
* Called on first `getGamepads()` invocation.
|
|
27
|
+
*/
|
|
28
|
+
_ensureInit() {
|
|
29
|
+
if (this._initialized) return;
|
|
30
|
+
if (this._initPromise) return;
|
|
31
|
+
this._initPromise = this._init();
|
|
32
|
+
}
|
|
33
|
+
async _init() {
|
|
34
|
+
try {
|
|
35
|
+
const mod = await import("gi://Manette");
|
|
36
|
+
this._ManetteModule = mod.default;
|
|
37
|
+
} catch {
|
|
38
|
+
this._initialized = true;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const monitor = new this._ManetteModule.Monitor();
|
|
42
|
+
this._monitor = monitor;
|
|
43
|
+
const iter = monitor.iterate();
|
|
44
|
+
let result = iter.next();
|
|
45
|
+
while (result[0]) {
|
|
46
|
+
const device = result[1];
|
|
47
|
+
if (device) {
|
|
48
|
+
this._onDeviceConnected(device);
|
|
49
|
+
}
|
|
50
|
+
result = iter.next();
|
|
51
|
+
}
|
|
52
|
+
this._monitorSignalIds.push(monitor.connect("device-connected", (_monitor, device) => {
|
|
53
|
+
this._onDeviceConnected(device);
|
|
54
|
+
}), monitor.connect("device-disconnected", (_monitor, device) => {
|
|
55
|
+
this._onDeviceDisconnected(device);
|
|
56
|
+
}));
|
|
57
|
+
this._initialized = true;
|
|
58
|
+
}
|
|
59
|
+
_onDeviceConnected(device) {
|
|
60
|
+
let slotIndex = -1;
|
|
61
|
+
for (let i = 0; i < MAX_GAMEPADS; i++) {
|
|
62
|
+
if (this._slots[i] === null) {
|
|
63
|
+
slotIndex = i;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (slotIndex === -1) return;
|
|
68
|
+
const state = {
|
|
69
|
+
device,
|
|
70
|
+
index: slotIndex,
|
|
71
|
+
connected: true,
|
|
72
|
+
timestamp: performance.now(),
|
|
73
|
+
buttons: new Float64Array(17),
|
|
74
|
+
buttonsPressed: new Array(17).fill(false),
|
|
75
|
+
axes: new Float64Array(4),
|
|
76
|
+
hapticActuator: new ManetteHapticActuator(device),
|
|
77
|
+
signalIds: []
|
|
78
|
+
};
|
|
79
|
+
state.signalIds.push(device.connect("button-press-event", (_device, event) => {
|
|
80
|
+
this._onButtonPress(state, event);
|
|
81
|
+
}), device.connect("button-release-event", (_device, event) => {
|
|
82
|
+
this._onButtonRelease(state, event);
|
|
83
|
+
}), device.connect("absolute-axis-event", (_device, event) => {
|
|
84
|
+
this._onAxisChange(state, event);
|
|
85
|
+
}), device.connect("hat-axis-event", (_device, event) => {
|
|
86
|
+
this._onHatChange(state, event);
|
|
87
|
+
}), device.connect("disconnected", () => {
|
|
88
|
+
this._onDeviceDisconnected(device);
|
|
89
|
+
}));
|
|
90
|
+
this._slots[slotIndex] = state;
|
|
91
|
+
const snapshot = this._createSnapshot(state);
|
|
92
|
+
if (snapshot) {
|
|
93
|
+
globalThis.dispatchEvent?.(new GamepadEvent("gamepadconnected", { gamepad: snapshot }));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
_onDeviceDisconnected(device) {
|
|
97
|
+
const state = this._findStateByDevice(device);
|
|
98
|
+
if (!state) return;
|
|
99
|
+
for (const id of state.signalIds) {
|
|
100
|
+
device.disconnect(id);
|
|
101
|
+
}
|
|
102
|
+
state.connected = false;
|
|
103
|
+
const snapshot = this._createSnapshot(state);
|
|
104
|
+
this._slots[state.index] = null;
|
|
105
|
+
if (snapshot) {
|
|
106
|
+
globalThis.dispatchEvent?.(new GamepadEvent("gamepaddisconnected", { gamepad: snapshot }));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
_onButtonPress(state, event) {
|
|
110
|
+
const [ok, button] = event.get_button();
|
|
111
|
+
if (!ok) return;
|
|
112
|
+
const w3cIdx = MANETTE_TO_W3C_BUTTON.get(button);
|
|
113
|
+
if (w3cIdx === undefined) return;
|
|
114
|
+
state.buttons[w3cIdx] = 1;
|
|
115
|
+
state.buttonsPressed[w3cIdx] = true;
|
|
116
|
+
state.timestamp = performance.now();
|
|
117
|
+
}
|
|
118
|
+
_onButtonRelease(state, event) {
|
|
119
|
+
const [ok, button] = event.get_button();
|
|
120
|
+
if (!ok) return;
|
|
121
|
+
const w3cIdx = MANETTE_TO_W3C_BUTTON.get(button);
|
|
122
|
+
if (w3cIdx === undefined) return;
|
|
123
|
+
state.buttons[w3cIdx] = 0;
|
|
124
|
+
state.buttonsPressed[w3cIdx] = false;
|
|
125
|
+
state.timestamp = performance.now();
|
|
126
|
+
}
|
|
127
|
+
_onAxisChange(state, event) {
|
|
128
|
+
const [ok, axis, value] = event.get_absolute();
|
|
129
|
+
if (!ok) return;
|
|
130
|
+
const w3cAxisIdx = MANETTE_TO_W3C_AXIS.get(axis);
|
|
131
|
+
if (w3cAxisIdx !== undefined) {
|
|
132
|
+
state.axes[w3cAxisIdx] = value;
|
|
133
|
+
} else if (axis === ManetteAxis.LEFT_TRIGGER) {
|
|
134
|
+
const normalized = (value + 1) / 2;
|
|
135
|
+
state.buttons[W3CButton.LEFT_TRIGGER] = normalized;
|
|
136
|
+
state.buttonsPressed[W3CButton.LEFT_TRIGGER] = normalized > TRIGGER_PRESS_THRESHOLD;
|
|
137
|
+
} else if (axis === ManetteAxis.RIGHT_TRIGGER) {
|
|
138
|
+
const normalized = (value + 1) / 2;
|
|
139
|
+
state.buttons[W3CButton.RIGHT_TRIGGER] = normalized;
|
|
140
|
+
state.buttonsPressed[W3CButton.RIGHT_TRIGGER] = normalized > TRIGGER_PRESS_THRESHOLD;
|
|
141
|
+
}
|
|
142
|
+
state.timestamp = performance.now();
|
|
143
|
+
}
|
|
144
|
+
_onHatChange(state, event) {
|
|
145
|
+
const [ok, hatAxis, hatValue] = event.get_hat();
|
|
146
|
+
if (!ok) return;
|
|
147
|
+
if (hatAxis === 0) {
|
|
148
|
+
state.buttonsPressed[W3CButton.DPAD_LEFT] = hatValue < 0;
|
|
149
|
+
state.buttons[W3CButton.DPAD_LEFT] = hatValue < 0 ? 1 : 0;
|
|
150
|
+
state.buttonsPressed[W3CButton.DPAD_RIGHT] = hatValue > 0;
|
|
151
|
+
state.buttons[W3CButton.DPAD_RIGHT] = hatValue > 0 ? 1 : 0;
|
|
152
|
+
} else if (hatAxis === 1) {
|
|
153
|
+
state.buttonsPressed[W3CButton.DPAD_UP] = hatValue < 0;
|
|
154
|
+
state.buttons[W3CButton.DPAD_UP] = hatValue < 0 ? 1 : 0;
|
|
155
|
+
state.buttonsPressed[W3CButton.DPAD_DOWN] = hatValue > 0;
|
|
156
|
+
state.buttons[W3CButton.DPAD_DOWN] = hatValue > 0 ? 1 : 0;
|
|
157
|
+
}
|
|
158
|
+
state.timestamp = performance.now();
|
|
159
|
+
}
|
|
160
|
+
_findStateByDevice(device) {
|
|
161
|
+
for (const state of this._slots) {
|
|
162
|
+
if (state && state.device === device) return state;
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
_createSnapshot(state) {
|
|
167
|
+
const buttons = [];
|
|
168
|
+
for (let i = 0; i < 17; i++) {
|
|
169
|
+
buttons.push(new GamepadButton(state.buttonsPressed[i], state.buttonsPressed[i] || state.buttons[i] > 0, state.buttons[i]));
|
|
170
|
+
}
|
|
171
|
+
return new Gamepad({
|
|
172
|
+
id: state.device.get_name() ?? `Gamepad (${state.device.get_guid()})`,
|
|
173
|
+
index: state.index,
|
|
174
|
+
connected: state.connected,
|
|
175
|
+
timestamp: state.timestamp,
|
|
176
|
+
mapping: "standard",
|
|
177
|
+
axes: Array.from(state.axes),
|
|
178
|
+
buttons,
|
|
179
|
+
vibrationActuator: state.hapticActuator
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Returns a snapshot array matching the W3C `navigator.getGamepads()` contract.
|
|
184
|
+
* Each non-null entry is a frozen Gamepad object with current state.
|
|
185
|
+
*/
|
|
186
|
+
getGamepads() {
|
|
187
|
+
this._ensureInit();
|
|
188
|
+
const result = [];
|
|
189
|
+
for (let i = 0; i < MAX_GAMEPADS; i++) {
|
|
190
|
+
const state = this._slots[i];
|
|
191
|
+
result.push(state ? this._createSnapshot(state) : null);
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
/** Cleanup — disconnect all signal handlers. */
|
|
196
|
+
dispose() {
|
|
197
|
+
for (const state of this._slots) {
|
|
198
|
+
if (state) {
|
|
199
|
+
for (const id of state.signalIds) {
|
|
200
|
+
state.device.disconnect(id);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
this._slots.fill(null);
|
|
205
|
+
if (this._monitor) {
|
|
206
|
+
for (const id of this._monitorSignalIds) {
|
|
207
|
+
this._monitor.disconnect(id);
|
|
208
|
+
}
|
|
209
|
+
this._monitorSignalIds = [];
|
|
210
|
+
this._monitor = null;
|
|
211
|
+
}
|
|
212
|
+
this._initialized = false;
|
|
213
|
+
this._initPromise = null;
|
|
214
|
+
}
|
|
223
215
|
};
|
|
216
|
+
|
|
217
|
+
//#endregion
|
|
218
|
+
export { GamepadManager };
|
package/lib/esm/gamepad.js
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
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
|
-
|
|
1
|
+
//#region src/gamepad.ts
|
|
2
|
+
/**
|
|
3
|
+
* Represents a single gamepad device.
|
|
4
|
+
* Instances are snapshots — they are not live-updating.
|
|
5
|
+
* https://w3c.github.io/gamepad/#dom-gamepad
|
|
6
|
+
*/
|
|
7
|
+
var Gamepad = class {
|
|
8
|
+
id;
|
|
9
|
+
index;
|
|
10
|
+
connected;
|
|
11
|
+
timestamp;
|
|
12
|
+
mapping;
|
|
13
|
+
axes;
|
|
14
|
+
buttons;
|
|
15
|
+
vibrationActuator;
|
|
16
|
+
constructor(init) {
|
|
17
|
+
this.id = init.id;
|
|
18
|
+
this.index = init.index;
|
|
19
|
+
this.connected = init.connected;
|
|
20
|
+
this.timestamp = init.timestamp;
|
|
21
|
+
this.mapping = init.mapping;
|
|
22
|
+
this.axes = Object.freeze([...init.axes]);
|
|
23
|
+
this.buttons = Object.freeze(init.buttons);
|
|
24
|
+
this.vibrationActuator = init.vibrationActuator ?? null;
|
|
25
|
+
}
|
|
26
|
+
get [Symbol.toStringTag]() {
|
|
27
|
+
return "Gamepad";
|
|
28
|
+
}
|
|
26
29
|
};
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { Gamepad };
|
|
@@ -1,27 +1,35 @@
|
|
|
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
|
-
|
|
1
|
+
//#region src/haptic-actuator.ts
|
|
2
|
+
/**
|
|
3
|
+
* Wraps libmanette's rumble support as a W3C GamepadHapticActuator.
|
|
4
|
+
*
|
|
5
|
+
* libmanette supports dual-rumble with strong/weak magnitude control
|
|
6
|
+
* via `Device.rumble(strong_magnitude, weak_magnitude, milliseconds)`.
|
|
7
|
+
* Magnitudes are in the range 0–65535 (uint16).
|
|
8
|
+
*/
|
|
9
|
+
var ManetteHapticActuator = class {
|
|
10
|
+
effects;
|
|
11
|
+
_device;
|
|
12
|
+
constructor(device) {
|
|
13
|
+
this._device = device;
|
|
14
|
+
this.effects = device.has_rumble() ? ["dual-rumble"] : [];
|
|
15
|
+
}
|
|
16
|
+
playEffect(type, params) {
|
|
17
|
+
if (type !== "dual-rumble" || !this._device.has_rumble()) {
|
|
18
|
+
return Promise.resolve("complete");
|
|
19
|
+
}
|
|
20
|
+
const duration = params?.duration ?? 200;
|
|
21
|
+
const strong = Math.round((params?.strongMagnitude ?? 1) * 65535);
|
|
22
|
+
const weak = Math.round((params?.weakMagnitude ?? 1) * 65535);
|
|
23
|
+
this._device.rumble(strong, weak, Math.min(duration, 32767));
|
|
24
|
+
return Promise.resolve("complete");
|
|
25
|
+
}
|
|
26
|
+
reset() {
|
|
27
|
+
if (this._device.has_rumble()) {
|
|
28
|
+
this._device.rumble(0, 0, 0);
|
|
29
|
+
}
|
|
30
|
+
return Promise.resolve("complete");
|
|
31
|
+
}
|
|
27
32
|
};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { ManetteHapticActuator };
|
package/lib/esm/index.js
CHANGED
|
@@ -1,23 +1,9 @@
|
|
|
1
|
+
import { MANETTE_TO_W3C_AXIS, ManetteAxis, TRIGGER_PRESS_THRESHOLD, W3CAxis, W3C_AXIS_COUNT } from "./axis-mapping.js";
|
|
2
|
+
import { LinuxButton, MANETTE_TO_W3C_BUTTON, W3CButton, W3C_BUTTON_COUNT } from "./button-mapping.js";
|
|
1
3
|
import { GamepadButton } from "./gamepad-button.js";
|
|
2
|
-
import { Gamepad } from "./gamepad.js";
|
|
3
4
|
import { GamepadEvent } from "./gamepad-event.js";
|
|
4
|
-
import {
|
|
5
|
+
import { Gamepad } from "./gamepad.js";
|
|
5
6
|
import { ManetteHapticActuator } from "./haptic-actuator.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
Gamepad,
|
|
10
|
-
GamepadButton,
|
|
11
|
-
GamepadEvent,
|
|
12
|
-
GamepadManager,
|
|
13
|
-
MANETTE_TO_W3C_AXIS,
|
|
14
|
-
MANETTE_TO_W3C_BUTTON,
|
|
15
|
-
ManetteAxis,
|
|
16
|
-
ManetteButton,
|
|
17
|
-
ManetteHapticActuator,
|
|
18
|
-
TRIGGER_PRESS_THRESHOLD,
|
|
19
|
-
W3CAxis,
|
|
20
|
-
W3CButton,
|
|
21
|
-
W3C_AXIS_COUNT,
|
|
22
|
-
W3C_BUTTON_COUNT
|
|
23
|
-
};
|
|
7
|
+
import { GamepadManager } from "./gamepad-manager.js";
|
|
8
|
+
|
|
9
|
+
export { Gamepad, GamepadButton, GamepadEvent, GamepadManager, MANETTE_TO_W3C_AXIS, MANETTE_TO_W3C_BUTTON, ManetteAxis, LinuxButton as ManetteButton, ManetteHapticActuator, TRIGGER_PRESS_THRESHOLD, W3CAxis, W3CButton, W3C_AXIS_COUNT, W3C_BUTTON_COUNT };
|
package/lib/esm/register.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { GamepadEvent } from "./gamepad-event.js";
|
|
2
2
|
import { GamepadManager } from "./gamepad-manager.js";
|
|
3
|
+
|
|
4
|
+
//#region src/register.ts
|
|
3
5
|
const manager = new GamepadManager();
|
|
4
6
|
if (typeof globalThis.navigator === "undefined") {
|
|
5
|
-
|
|
7
|
+
globalThis.navigator = {};
|
|
6
8
|
}
|
|
7
9
|
globalThis.navigator.getGamepads = () => manager.getGamepads();
|
|
8
10
|
if (typeof globalThis.GamepadEvent === "undefined") {
|
|
9
|
-
|
|
11
|
+
globalThis.GamepadEvent = GamepadEvent;
|
|
10
12
|
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/gamepad",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Gamepad Web API for GJS using libmanette as backend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"controller"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@gjsify/dom-events": "^0.3.
|
|
39
|
+
"@gjsify/dom-events": "^0.3.14"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@girs/gjs": "
|
|
43
|
-
"@girs/glib-2.0": "
|
|
44
|
-
"@girs/manette-0.2": "
|
|
45
|
-
"@gjsify/cli": "^0.3.
|
|
46
|
-
"@gjsify/unit": "^0.3.
|
|
42
|
+
"@girs/gjs": "4.0.0-rc.9",
|
|
43
|
+
"@girs/glib-2.0": "2.88.0-4.0.0-rc.9",
|
|
44
|
+
"@girs/manette-0.2": "0.2.13-4.0.0-rc.9",
|
|
45
|
+
"@gjsify/cli": "^0.3.14",
|
|
46
|
+
"@gjsify/unit": "^0.3.14",
|
|
47
47
|
"@types/node": "^25.6.0",
|
|
48
48
|
"typescript": "^6.0.3"
|
|
49
49
|
}
|