@gjsify/gamepad 0.1.9
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 -0
- package/lib/esm/button-mapping.js +92 -0
- package/lib/esm/gamepad-button.js +16 -0
- package/lib/esm/gamepad-event.js +14 -0
- package/lib/esm/gamepad-manager.js +223 -0
- package/lib/esm/gamepad.js +26 -0
- package/lib/esm/haptic-actuator.js +27 -0
- package/lib/esm/index.js +23 -0
- package/lib/esm/register.js +10 -0
- package/lib/types/axis-mapping.d.ts +32 -0
- package/lib/types/button-mapping.d.ts +55 -0
- package/lib/types/gamepad-button.d.ts +11 -0
- package/lib/types/gamepad-event.d.ts +17 -0
- package/lib/types/gamepad-manager.d.ts +37 -0
- package/lib/types/gamepad.d.ts +45 -0
- package/lib/types/gamepad.spec.d.ts +2 -0
- package/lib/types/haptic-actuator.d.ts +16 -0
- package/lib/types/index.d.ts +9 -0
- package/lib/types/register.d.ts +1 -0
- package/package.json +50 -0
- package/src/axis-mapping.ts +50 -0
- package/src/button-mapping.ts +87 -0
- package/src/gamepad-button.ts +22 -0
- package/src/gamepad-event.ts +29 -0
- package/src/gamepad-manager.ts +299 -0
- package/src/gamepad.spec.ts +191 -0
- package/src/gamepad.ts +66 -0
- package/src/haptic-actuator.ts +49 -0
- package/src/index.ts +18 -0
- package/src/register.ts +21 -0
- package/src/test.mts +5 -0
- package/tsconfig.json +33 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const ManetteAxis = {
|
|
2
|
+
LEFT_X: 0,
|
|
3
|
+
// SDL leftx
|
|
4
|
+
LEFT_Y: 1,
|
|
5
|
+
// SDL lefty
|
|
6
|
+
RIGHT_X: 2,
|
|
7
|
+
// SDL rightx
|
|
8
|
+
RIGHT_Y: 3,
|
|
9
|
+
// SDL righty
|
|
10
|
+
LEFT_TRIGGER: 4,
|
|
11
|
+
// SDL lefttrigger
|
|
12
|
+
RIGHT_TRIGGER: 5
|
|
13
|
+
// SDL righttrigger
|
|
14
|
+
};
|
|
15
|
+
const W3CAxis = {
|
|
16
|
+
LEFT_STICK_X: 0,
|
|
17
|
+
LEFT_STICK_Y: 1,
|
|
18
|
+
RIGHT_STICK_X: 2,
|
|
19
|
+
RIGHT_STICK_Y: 3
|
|
20
|
+
};
|
|
21
|
+
const W3C_AXIS_COUNT = 4;
|
|
22
|
+
const MANETTE_TO_W3C_AXIS = /* @__PURE__ */ new Map([
|
|
23
|
+
[ManetteAxis.LEFT_X, W3CAxis.LEFT_STICK_X],
|
|
24
|
+
[ManetteAxis.LEFT_Y, W3CAxis.LEFT_STICK_Y],
|
|
25
|
+
[ManetteAxis.RIGHT_X, W3CAxis.RIGHT_STICK_X],
|
|
26
|
+
[ManetteAxis.RIGHT_Y, W3CAxis.RIGHT_STICK_Y]
|
|
27
|
+
]);
|
|
28
|
+
const TRIGGER_PRESS_THRESHOLD = 0.5;
|
|
29
|
+
export {
|
|
30
|
+
MANETTE_TO_W3C_AXIS,
|
|
31
|
+
ManetteAxis,
|
|
32
|
+
TRIGGER_PRESS_THRESHOLD,
|
|
33
|
+
W3CAxis,
|
|
34
|
+
W3C_AXIS_COUNT
|
|
35
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const LinuxButton = {
|
|
2
|
+
BTN_SOUTH: 304,
|
|
3
|
+
// 0x130 — A (Xbox), B (Nintendo), Cross (PlayStation)
|
|
4
|
+
BTN_EAST: 305,
|
|
5
|
+
// 0x131 — B (Xbox), A (Nintendo), Circle (PlayStation)
|
|
6
|
+
BTN_C: 306,
|
|
7
|
+
// 0x132
|
|
8
|
+
BTN_NORTH: 307,
|
|
9
|
+
// 0x133 — Y (Xbox), X (Nintendo), Triangle (PlayStation)
|
|
10
|
+
BTN_WEST: 308,
|
|
11
|
+
// 0x134 — X (Xbox), Y (Nintendo), Square (PlayStation)
|
|
12
|
+
BTN_Z: 309,
|
|
13
|
+
// 0x135
|
|
14
|
+
BTN_TL: 310,
|
|
15
|
+
// 0x136 — Left shoulder (L, LB)
|
|
16
|
+
BTN_TR: 311,
|
|
17
|
+
// 0x137 — Right shoulder (R, RB)
|
|
18
|
+
BTN_TL2: 312,
|
|
19
|
+
// 0x138 — Left trigger (LT, L2)
|
|
20
|
+
BTN_TR2: 313,
|
|
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
|
|
40
|
+
};
|
|
41
|
+
const W3CButton = {
|
|
42
|
+
FACE_1: 0,
|
|
43
|
+
// A (Xbox), B (Nintendo), Cross (PlayStation)
|
|
44
|
+
FACE_2: 1,
|
|
45
|
+
// B (Xbox), A (Nintendo), Circle (PlayStation)
|
|
46
|
+
FACE_3: 2,
|
|
47
|
+
// X (Xbox), Y (Nintendo), Square (PlayStation)
|
|
48
|
+
FACE_4: 3,
|
|
49
|
+
// Y (Xbox), X (Nintendo), Triangle (PlayStation)
|
|
50
|
+
LEFT_BUMPER: 4,
|
|
51
|
+
RIGHT_BUMPER: 5,
|
|
52
|
+
LEFT_TRIGGER: 6,
|
|
53
|
+
// Analog trigger — also populated from axis events
|
|
54
|
+
RIGHT_TRIGGER: 7,
|
|
55
|
+
// Analog trigger — also populated from axis events
|
|
56
|
+
SELECT: 8,
|
|
57
|
+
START: 9,
|
|
58
|
+
LEFT_STICK: 10,
|
|
59
|
+
RIGHT_STICK: 11,
|
|
60
|
+
DPAD_UP: 12,
|
|
61
|
+
DPAD_DOWN: 13,
|
|
62
|
+
DPAD_LEFT: 14,
|
|
63
|
+
DPAD_RIGHT: 15,
|
|
64
|
+
HOME: 16
|
|
65
|
+
};
|
|
66
|
+
const W3C_BUTTON_COUNT = 17;
|
|
67
|
+
const MANETTE_TO_W3C_BUTTON = /* @__PURE__ */ new Map([
|
|
68
|
+
[LinuxButton.BTN_SOUTH, W3CButton.FACE_1],
|
|
69
|
+
[LinuxButton.BTN_EAST, W3CButton.FACE_2],
|
|
70
|
+
[LinuxButton.BTN_WEST, W3CButton.FACE_3],
|
|
71
|
+
[LinuxButton.BTN_NORTH, W3CButton.FACE_4],
|
|
72
|
+
[LinuxButton.BTN_TL, W3CButton.LEFT_BUMPER],
|
|
73
|
+
[LinuxButton.BTN_TR, W3CButton.RIGHT_BUMPER],
|
|
74
|
+
[LinuxButton.BTN_TL2, W3CButton.LEFT_TRIGGER],
|
|
75
|
+
[LinuxButton.BTN_TR2, W3CButton.RIGHT_TRIGGER],
|
|
76
|
+
[LinuxButton.BTN_SELECT, W3CButton.SELECT],
|
|
77
|
+
[LinuxButton.BTN_START, W3CButton.START],
|
|
78
|
+
[LinuxButton.BTN_THUMBL, W3CButton.LEFT_STICK],
|
|
79
|
+
[LinuxButton.BTN_THUMBR, W3CButton.RIGHT_STICK],
|
|
80
|
+
[LinuxButton.BTN_DPAD_UP, W3CButton.DPAD_UP],
|
|
81
|
+
[LinuxButton.BTN_DPAD_DOWN, W3CButton.DPAD_DOWN],
|
|
82
|
+
[LinuxButton.BTN_DPAD_LEFT, W3CButton.DPAD_LEFT],
|
|
83
|
+
[LinuxButton.BTN_DPAD_RIGHT, W3CButton.DPAD_RIGHT],
|
|
84
|
+
[LinuxButton.BTN_MODE, W3CButton.HOME]
|
|
85
|
+
]);
|
|
86
|
+
export {
|
|
87
|
+
LinuxButton,
|
|
88
|
+
MANETTE_TO_W3C_BUTTON,
|
|
89
|
+
LinuxButton as ManetteButton,
|
|
90
|
+
W3CButton,
|
|
91
|
+
W3C_BUTTON_COUNT
|
|
92
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class GamepadButton {
|
|
2
|
+
pressed;
|
|
3
|
+
touched;
|
|
4
|
+
value;
|
|
5
|
+
constructor(pressed = false, touched = false, value = 0) {
|
|
6
|
+
this.pressed = pressed;
|
|
7
|
+
this.touched = touched;
|
|
8
|
+
this.value = value;
|
|
9
|
+
}
|
|
10
|
+
get [Symbol.toStringTag]() {
|
|
11
|
+
return "GamepadButton";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
GamepadButton
|
|
16
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Event } from "@gjsify/dom-events";
|
|
2
|
+
class GamepadEvent extends Event {
|
|
3
|
+
gamepad;
|
|
4
|
+
constructor(type, eventInitDict) {
|
|
5
|
+
super(type, eventInitDict);
|
|
6
|
+
this.gamepad = eventInitDict.gamepad;
|
|
7
|
+
}
|
|
8
|
+
get [Symbol.toStringTag]() {
|
|
9
|
+
return "GamepadEvent";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
GamepadEvent
|
|
14
|
+
};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { GamepadButton } from "./gamepad-button.js";
|
|
2
|
+
import { Gamepad } from "./gamepad.js";
|
|
3
|
+
import { GamepadEvent } from "./gamepad-event.js";
|
|
4
|
+
import { ManetteHapticActuator } from "./haptic-actuator.js";
|
|
5
|
+
import { MANETTE_TO_W3C_BUTTON, W3C_BUTTON_COUNT } from "./button-mapping.js";
|
|
6
|
+
import { MANETTE_TO_W3C_AXIS, ManetteAxis, W3C_AXIS_COUNT, TRIGGER_PRESS_THRESHOLD } from "./axis-mapping.js";
|
|
7
|
+
import { W3CButton } from "./button-mapping.js";
|
|
8
|
+
const MAX_GAMEPADS = 4;
|
|
9
|
+
class GamepadManager {
|
|
10
|
+
_monitor = null;
|
|
11
|
+
_slots = new Array(MAX_GAMEPADS).fill(null);
|
|
12
|
+
_monitorSignalIds = [];
|
|
13
|
+
_ManetteModule = null;
|
|
14
|
+
_initPromise = null;
|
|
15
|
+
_initialized = false;
|
|
16
|
+
/**
|
|
17
|
+
* Lazily initialize the Manette.Monitor.
|
|
18
|
+
* Called on first `getGamepads()` invocation.
|
|
19
|
+
*/
|
|
20
|
+
_ensureInit() {
|
|
21
|
+
if (this._initialized) return;
|
|
22
|
+
if (this._initPromise) return;
|
|
23
|
+
this._initPromise = this._init();
|
|
24
|
+
}
|
|
25
|
+
async _init() {
|
|
26
|
+
try {
|
|
27
|
+
const mod = await import("gi://Manette");
|
|
28
|
+
this._ManetteModule = mod.default;
|
|
29
|
+
} catch {
|
|
30
|
+
this._initialized = true;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const monitor = new this._ManetteModule.Monitor();
|
|
34
|
+
this._monitor = monitor;
|
|
35
|
+
const iter = monitor.iterate();
|
|
36
|
+
let result = iter.next();
|
|
37
|
+
while (result[0]) {
|
|
38
|
+
const device = result[1];
|
|
39
|
+
if (device) {
|
|
40
|
+
this._onDeviceConnected(device);
|
|
41
|
+
}
|
|
42
|
+
result = iter.next();
|
|
43
|
+
}
|
|
44
|
+
this._monitorSignalIds.push(
|
|
45
|
+
monitor.connect("device-connected", (_monitor, device) => {
|
|
46
|
+
this._onDeviceConnected(device);
|
|
47
|
+
}),
|
|
48
|
+
monitor.connect("device-disconnected", (_monitor, device) => {
|
|
49
|
+
this._onDeviceDisconnected(device);
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
this._initialized = true;
|
|
53
|
+
}
|
|
54
|
+
_onDeviceConnected(device) {
|
|
55
|
+
let slotIndex = -1;
|
|
56
|
+
for (let i = 0; i < MAX_GAMEPADS; i++) {
|
|
57
|
+
if (this._slots[i] === null) {
|
|
58
|
+
slotIndex = i;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (slotIndex === -1) return;
|
|
63
|
+
const state = {
|
|
64
|
+
device,
|
|
65
|
+
index: slotIndex,
|
|
66
|
+
connected: true,
|
|
67
|
+
timestamp: performance.now(),
|
|
68
|
+
buttons: new Float64Array(W3C_BUTTON_COUNT),
|
|
69
|
+
buttonsPressed: new Array(W3C_BUTTON_COUNT).fill(false),
|
|
70
|
+
axes: new Float64Array(W3C_AXIS_COUNT),
|
|
71
|
+
hapticActuator: new ManetteHapticActuator(device),
|
|
72
|
+
signalIds: []
|
|
73
|
+
};
|
|
74
|
+
state.signalIds.push(
|
|
75
|
+
device.connect("button-press-event", (_device, event) => {
|
|
76
|
+
this._onButtonPress(state, event);
|
|
77
|
+
}),
|
|
78
|
+
device.connect("button-release-event", (_device, event) => {
|
|
79
|
+
this._onButtonRelease(state, event);
|
|
80
|
+
}),
|
|
81
|
+
device.connect("absolute-axis-event", (_device, event) => {
|
|
82
|
+
this._onAxisChange(state, event);
|
|
83
|
+
}),
|
|
84
|
+
device.connect("hat-axis-event", (_device, event) => {
|
|
85
|
+
this._onHatChange(state, event);
|
|
86
|
+
}),
|
|
87
|
+
device.connect("disconnected", () => {
|
|
88
|
+
this._onDeviceDisconnected(device);
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
this._slots[slotIndex] = state;
|
|
92
|
+
const snapshot = this._createSnapshot(state);
|
|
93
|
+
if (snapshot) {
|
|
94
|
+
globalThis.dispatchEvent?.(new GamepadEvent("gamepadconnected", { gamepad: snapshot }));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
_onDeviceDisconnected(device) {
|
|
98
|
+
const state = this._findStateByDevice(device);
|
|
99
|
+
if (!state) return;
|
|
100
|
+
for (const id of state.signalIds) {
|
|
101
|
+
device.disconnect(id);
|
|
102
|
+
}
|
|
103
|
+
state.connected = false;
|
|
104
|
+
const snapshot = this._createSnapshot(state);
|
|
105
|
+
this._slots[state.index] = null;
|
|
106
|
+
if (snapshot) {
|
|
107
|
+
globalThis.dispatchEvent?.(new GamepadEvent("gamepaddisconnected", { gamepad: snapshot }));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
_onButtonPress(state, event) {
|
|
111
|
+
const [ok, button] = event.get_button();
|
|
112
|
+
if (!ok) return;
|
|
113
|
+
const w3cIdx = MANETTE_TO_W3C_BUTTON.get(button);
|
|
114
|
+
if (w3cIdx === void 0) return;
|
|
115
|
+
state.buttons[w3cIdx] = 1;
|
|
116
|
+
state.buttonsPressed[w3cIdx] = true;
|
|
117
|
+
state.timestamp = performance.now();
|
|
118
|
+
}
|
|
119
|
+
_onButtonRelease(state, event) {
|
|
120
|
+
const [ok, button] = event.get_button();
|
|
121
|
+
if (!ok) return;
|
|
122
|
+
const w3cIdx = MANETTE_TO_W3C_BUTTON.get(button);
|
|
123
|
+
if (w3cIdx === void 0) return;
|
|
124
|
+
state.buttons[w3cIdx] = 0;
|
|
125
|
+
state.buttonsPressed[w3cIdx] = false;
|
|
126
|
+
state.timestamp = performance.now();
|
|
127
|
+
}
|
|
128
|
+
_onAxisChange(state, event) {
|
|
129
|
+
const [ok, axis, value] = event.get_absolute();
|
|
130
|
+
if (!ok) return;
|
|
131
|
+
const w3cAxisIdx = MANETTE_TO_W3C_AXIS.get(axis);
|
|
132
|
+
if (w3cAxisIdx !== void 0) {
|
|
133
|
+
state.axes[w3cAxisIdx] = value;
|
|
134
|
+
} else if (axis === ManetteAxis.LEFT_TRIGGER) {
|
|
135
|
+
const normalized = (value + 1) / 2;
|
|
136
|
+
state.buttons[W3CButton.LEFT_TRIGGER] = normalized;
|
|
137
|
+
state.buttonsPressed[W3CButton.LEFT_TRIGGER] = normalized > TRIGGER_PRESS_THRESHOLD;
|
|
138
|
+
} else if (axis === ManetteAxis.RIGHT_TRIGGER) {
|
|
139
|
+
const normalized = (value + 1) / 2;
|
|
140
|
+
state.buttons[W3CButton.RIGHT_TRIGGER] = normalized;
|
|
141
|
+
state.buttonsPressed[W3CButton.RIGHT_TRIGGER] = normalized > TRIGGER_PRESS_THRESHOLD;
|
|
142
|
+
}
|
|
143
|
+
state.timestamp = performance.now();
|
|
144
|
+
}
|
|
145
|
+
_onHatChange(state, event) {
|
|
146
|
+
const [ok, hatAxis, hatValue] = event.get_hat();
|
|
147
|
+
if (!ok) return;
|
|
148
|
+
if (hatAxis === 0) {
|
|
149
|
+
state.buttonsPressed[W3CButton.DPAD_LEFT] = hatValue < 0;
|
|
150
|
+
state.buttons[W3CButton.DPAD_LEFT] = hatValue < 0 ? 1 : 0;
|
|
151
|
+
state.buttonsPressed[W3CButton.DPAD_RIGHT] = hatValue > 0;
|
|
152
|
+
state.buttons[W3CButton.DPAD_RIGHT] = hatValue > 0 ? 1 : 0;
|
|
153
|
+
} else if (hatAxis === 1) {
|
|
154
|
+
state.buttonsPressed[W3CButton.DPAD_UP] = hatValue < 0;
|
|
155
|
+
state.buttons[W3CButton.DPAD_UP] = hatValue < 0 ? 1 : 0;
|
|
156
|
+
state.buttonsPressed[W3CButton.DPAD_DOWN] = hatValue > 0;
|
|
157
|
+
state.buttons[W3CButton.DPAD_DOWN] = hatValue > 0 ? 1 : 0;
|
|
158
|
+
}
|
|
159
|
+
state.timestamp = performance.now();
|
|
160
|
+
}
|
|
161
|
+
_findStateByDevice(device) {
|
|
162
|
+
for (const state of this._slots) {
|
|
163
|
+
if (state && state.device === device) return state;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
_createSnapshot(state) {
|
|
168
|
+
const buttons = [];
|
|
169
|
+
for (let i = 0; i < W3C_BUTTON_COUNT; i++) {
|
|
170
|
+
buttons.push(new GamepadButton(
|
|
171
|
+
state.buttonsPressed[i],
|
|
172
|
+
state.buttonsPressed[i] || state.buttons[i] > 0,
|
|
173
|
+
state.buttons[i]
|
|
174
|
+
));
|
|
175
|
+
}
|
|
176
|
+
return new Gamepad({
|
|
177
|
+
id: state.device.get_name() ?? `Gamepad (${state.device.get_guid()})`,
|
|
178
|
+
index: state.index,
|
|
179
|
+
connected: state.connected,
|
|
180
|
+
timestamp: state.timestamp,
|
|
181
|
+
mapping: "standard",
|
|
182
|
+
axes: Array.from(state.axes),
|
|
183
|
+
buttons,
|
|
184
|
+
vibrationActuator: state.hapticActuator
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Returns a snapshot array matching the W3C `navigator.getGamepads()` contract.
|
|
189
|
+
* Each non-null entry is a frozen Gamepad object with current state.
|
|
190
|
+
*/
|
|
191
|
+
getGamepads() {
|
|
192
|
+
this._ensureInit();
|
|
193
|
+
const result = [];
|
|
194
|
+
for (let i = 0; i < MAX_GAMEPADS; i++) {
|
|
195
|
+
const state = this._slots[i];
|
|
196
|
+
result.push(state ? this._createSnapshot(state) : null);
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
/** Cleanup — disconnect all signal handlers. */
|
|
201
|
+
dispose() {
|
|
202
|
+
for (const state of this._slots) {
|
|
203
|
+
if (state) {
|
|
204
|
+
for (const id of state.signalIds) {
|
|
205
|
+
state.device.disconnect(id);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this._slots.fill(null);
|
|
210
|
+
if (this._monitor) {
|
|
211
|
+
for (const id of this._monitorSignalIds) {
|
|
212
|
+
this._monitor.disconnect(id);
|
|
213
|
+
}
|
|
214
|
+
this._monitorSignalIds = [];
|
|
215
|
+
this._monitor = null;
|
|
216
|
+
}
|
|
217
|
+
this._initialized = false;
|
|
218
|
+
this._initPromise = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export {
|
|
222
|
+
GamepadManager
|
|
223
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class Gamepad {
|
|
2
|
+
id;
|
|
3
|
+
index;
|
|
4
|
+
connected;
|
|
5
|
+
timestamp;
|
|
6
|
+
mapping;
|
|
7
|
+
axes;
|
|
8
|
+
buttons;
|
|
9
|
+
vibrationActuator;
|
|
10
|
+
constructor(init) {
|
|
11
|
+
this.id = init.id;
|
|
12
|
+
this.index = init.index;
|
|
13
|
+
this.connected = init.connected;
|
|
14
|
+
this.timestamp = init.timestamp;
|
|
15
|
+
this.mapping = init.mapping;
|
|
16
|
+
this.axes = Object.freeze([...init.axes]);
|
|
17
|
+
this.buttons = Object.freeze(init.buttons);
|
|
18
|
+
this.vibrationActuator = init.vibrationActuator ?? null;
|
|
19
|
+
}
|
|
20
|
+
get [Symbol.toStringTag]() {
|
|
21
|
+
return "Gamepad";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
Gamepad
|
|
26
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class ManetteHapticActuator {
|
|
2
|
+
effects;
|
|
3
|
+
_device;
|
|
4
|
+
constructor(device) {
|
|
5
|
+
this._device = device;
|
|
6
|
+
this.effects = device.has_rumble() ? ["dual-rumble"] : [];
|
|
7
|
+
}
|
|
8
|
+
playEffect(type, params) {
|
|
9
|
+
if (type !== "dual-rumble" || !this._device.has_rumble()) {
|
|
10
|
+
return Promise.resolve("complete");
|
|
11
|
+
}
|
|
12
|
+
const duration = params?.duration ?? 200;
|
|
13
|
+
const strong = Math.round((params?.strongMagnitude ?? 1) * 65535);
|
|
14
|
+
const weak = Math.round((params?.weakMagnitude ?? 1) * 65535);
|
|
15
|
+
this._device.rumble(strong, weak, Math.min(duration, 32767));
|
|
16
|
+
return Promise.resolve("complete");
|
|
17
|
+
}
|
|
18
|
+
reset() {
|
|
19
|
+
if (this._device.has_rumble()) {
|
|
20
|
+
this._device.rumble(0, 0, 0);
|
|
21
|
+
}
|
|
22
|
+
return Promise.resolve("complete");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
ManetteHapticActuator
|
|
27
|
+
};
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { GamepadButton } from "./gamepad-button.js";
|
|
2
|
+
import { Gamepad } from "./gamepad.js";
|
|
3
|
+
import { GamepadEvent } from "./gamepad-event.js";
|
|
4
|
+
import { GamepadManager } from "./gamepad-manager.js";
|
|
5
|
+
import { ManetteHapticActuator } from "./haptic-actuator.js";
|
|
6
|
+
import { MANETTE_TO_W3C_BUTTON, ManetteButton, W3CButton, W3C_BUTTON_COUNT } from "./button-mapping.js";
|
|
7
|
+
import { MANETTE_TO_W3C_AXIS, ManetteAxis, W3CAxis, W3C_AXIS_COUNT, TRIGGER_PRESS_THRESHOLD } from "./axis-mapping.js";
|
|
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
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GamepadEvent } from "./gamepad-event.js";
|
|
2
|
+
import { GamepadManager } from "./gamepad-manager.js";
|
|
3
|
+
const manager = new GamepadManager();
|
|
4
|
+
if (typeof globalThis.navigator === "undefined") {
|
|
5
|
+
globalThis.navigator = {};
|
|
6
|
+
}
|
|
7
|
+
globalThis.navigator.getGamepads = () => manager.getGamepads();
|
|
8
|
+
if (typeof globalThis.GamepadEvent === "undefined") {
|
|
9
|
+
globalThis.GamepadEvent = GamepadEvent;
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDL logical axis indices as reported by Manette.Event.get_absolute().
|
|
3
|
+
* These are the SDL gamepad mapping indices, NOT Linux ABS_* hardware codes.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ManetteAxis: {
|
|
6
|
+
readonly LEFT_X: 0;
|
|
7
|
+
readonly LEFT_Y: 1;
|
|
8
|
+
readonly RIGHT_X: 2;
|
|
9
|
+
readonly RIGHT_Y: 3;
|
|
10
|
+
readonly LEFT_TRIGGER: 4;
|
|
11
|
+
readonly RIGHT_TRIGGER: 5;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* W3C standard gamepad axis indices.
|
|
15
|
+
* https://w3c.github.io/gamepad/#remapping
|
|
16
|
+
*/
|
|
17
|
+
export declare const W3CAxis: {
|
|
18
|
+
readonly LEFT_STICK_X: 0;
|
|
19
|
+
readonly LEFT_STICK_Y: 1;
|
|
20
|
+
readonly RIGHT_STICK_X: 2;
|
|
21
|
+
readonly RIGHT_STICK_Y: 3;
|
|
22
|
+
};
|
|
23
|
+
/** Total number of axes in the W3C standard mapping. */
|
|
24
|
+
export declare const W3C_AXIS_COUNT = 4;
|
|
25
|
+
/**
|
|
26
|
+
* Maps SDL logical axis → W3C axis index.
|
|
27
|
+
* Stick axes (0–3) map 1:1. Trigger axes (4–5) are NOT in this map —
|
|
28
|
+
* they are handled separately in gamepad-manager as buttons[6]/buttons[7].
|
|
29
|
+
*/
|
|
30
|
+
export declare const MANETTE_TO_W3C_AXIS: ReadonlyMap<number, number>;
|
|
31
|
+
/** Threshold above which an analog trigger is considered "pressed". */
|
|
32
|
+
export declare const TRIGGER_PRESS_THRESHOLD = 0.5;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linux BTN_* input event codes as reported by Manette.Event.get_button().
|
|
3
|
+
* These are the actual values libmanette 0.2 passes in button-press/release signals.
|
|
4
|
+
*/
|
|
5
|
+
export declare const LinuxButton: {
|
|
6
|
+
readonly BTN_SOUTH: 304;
|
|
7
|
+
readonly BTN_EAST: 305;
|
|
8
|
+
readonly BTN_C: 306;
|
|
9
|
+
readonly BTN_NORTH: 307;
|
|
10
|
+
readonly BTN_WEST: 308;
|
|
11
|
+
readonly BTN_Z: 309;
|
|
12
|
+
readonly BTN_TL: 310;
|
|
13
|
+
readonly BTN_TR: 311;
|
|
14
|
+
readonly BTN_TL2: 312;
|
|
15
|
+
readonly BTN_TR2: 313;
|
|
16
|
+
readonly BTN_SELECT: 314;
|
|
17
|
+
readonly BTN_START: 315;
|
|
18
|
+
readonly BTN_MODE: 316;
|
|
19
|
+
readonly BTN_THUMBL: 317;
|
|
20
|
+
readonly BTN_THUMBR: 318;
|
|
21
|
+
readonly BTN_DPAD_UP: 544;
|
|
22
|
+
readonly BTN_DPAD_DOWN: 545;
|
|
23
|
+
readonly BTN_DPAD_LEFT: 546;
|
|
24
|
+
readonly BTN_DPAD_RIGHT: 547;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* W3C standard gamepad button indices.
|
|
28
|
+
* https://w3c.github.io/gamepad/#remapping
|
|
29
|
+
*/
|
|
30
|
+
export declare const W3CButton: {
|
|
31
|
+
readonly FACE_1: 0;
|
|
32
|
+
readonly FACE_2: 1;
|
|
33
|
+
readonly FACE_3: 2;
|
|
34
|
+
readonly FACE_4: 3;
|
|
35
|
+
readonly LEFT_BUMPER: 4;
|
|
36
|
+
readonly RIGHT_BUMPER: 5;
|
|
37
|
+
readonly LEFT_TRIGGER: 6;
|
|
38
|
+
readonly RIGHT_TRIGGER: 7;
|
|
39
|
+
readonly SELECT: 8;
|
|
40
|
+
readonly START: 9;
|
|
41
|
+
readonly LEFT_STICK: 10;
|
|
42
|
+
readonly RIGHT_STICK: 11;
|
|
43
|
+
readonly DPAD_UP: 12;
|
|
44
|
+
readonly DPAD_DOWN: 13;
|
|
45
|
+
readonly DPAD_LEFT: 14;
|
|
46
|
+
readonly DPAD_RIGHT: 15;
|
|
47
|
+
readonly HOME: 16;
|
|
48
|
+
};
|
|
49
|
+
/** Total number of buttons in the W3C standard mapping. */
|
|
50
|
+
export declare const W3C_BUTTON_COUNT = 17;
|
|
51
|
+
/**
|
|
52
|
+
* Maps Linux BTN_* code → W3C standard button index.
|
|
53
|
+
*/
|
|
54
|
+
export declare const MANETTE_TO_W3C_BUTTON: ReadonlyMap<number, number>;
|
|
55
|
+
export { LinuxButton as ManetteButton };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the state of a single button on a gamepad.
|
|
3
|
+
* https://w3c.github.io/gamepad/#dom-gamepadbutton
|
|
4
|
+
*/
|
|
5
|
+
export declare class GamepadButton {
|
|
6
|
+
pressed: boolean;
|
|
7
|
+
touched: boolean;
|
|
8
|
+
value: number;
|
|
9
|
+
constructor(pressed?: boolean, touched?: boolean, value?: number);
|
|
10
|
+
get [Symbol.toStringTag](): string;
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Event } from '@gjsify/dom-events';
|
|
2
|
+
import type { Gamepad } from './gamepad.js';
|
|
3
|
+
export interface GamepadEventInit {
|
|
4
|
+
gamepad: Gamepad;
|
|
5
|
+
bubbles?: boolean;
|
|
6
|
+
cancelable?: boolean;
|
|
7
|
+
composed?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Fired on the Window when a gamepad is connected or disconnected.
|
|
11
|
+
* https://w3c.github.io/gamepad/#dom-gamepadevent
|
|
12
|
+
*/
|
|
13
|
+
export declare class GamepadEvent extends Event {
|
|
14
|
+
readonly gamepad: Gamepad;
|
|
15
|
+
constructor(type: string, eventInitDict: GamepadEventInit);
|
|
16
|
+
get [Symbol.toStringTag](): string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Gamepad } from './gamepad.js';
|
|
2
|
+
/**
|
|
3
|
+
* Singleton manager that wraps Manette.Monitor and maintains gamepad state.
|
|
4
|
+
*
|
|
5
|
+
* libmanette fires GObject signals on button/axis changes. This manager
|
|
6
|
+
* caches the latest state so that `getGamepads()` can return a snapshot
|
|
7
|
+
* matching the W3C Gamepad API's polling model.
|
|
8
|
+
*/
|
|
9
|
+
export declare class GamepadManager {
|
|
10
|
+
private _monitor;
|
|
11
|
+
private _slots;
|
|
12
|
+
private _monitorSignalIds;
|
|
13
|
+
private _ManetteModule;
|
|
14
|
+
private _initPromise;
|
|
15
|
+
private _initialized;
|
|
16
|
+
/**
|
|
17
|
+
* Lazily initialize the Manette.Monitor.
|
|
18
|
+
* Called on first `getGamepads()` invocation.
|
|
19
|
+
*/
|
|
20
|
+
private _ensureInit;
|
|
21
|
+
private _init;
|
|
22
|
+
private _onDeviceConnected;
|
|
23
|
+
private _onDeviceDisconnected;
|
|
24
|
+
private _onButtonPress;
|
|
25
|
+
private _onButtonRelease;
|
|
26
|
+
private _onAxisChange;
|
|
27
|
+
private _onHatChange;
|
|
28
|
+
private _findStateByDevice;
|
|
29
|
+
private _createSnapshot;
|
|
30
|
+
/**
|
|
31
|
+
* Returns a snapshot array matching the W3C `navigator.getGamepads()` contract.
|
|
32
|
+
* Each non-null entry is a frozen Gamepad object with current state.
|
|
33
|
+
*/
|
|
34
|
+
getGamepads(): (Gamepad | null)[];
|
|
35
|
+
/** Cleanup — disconnect all signal handlers. */
|
|
36
|
+
dispose(): void;
|
|
37
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { GamepadButton } from './gamepad-button.js';
|
|
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
|
+
export declare class Gamepad {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly index: number;
|
|
10
|
+
readonly connected: boolean;
|
|
11
|
+
readonly timestamp: number;
|
|
12
|
+
readonly mapping: GamepadMappingType;
|
|
13
|
+
readonly axes: readonly number[];
|
|
14
|
+
readonly buttons: readonly GamepadButton[];
|
|
15
|
+
readonly vibrationActuator: GamepadHapticActuator | null;
|
|
16
|
+
constructor(init: {
|
|
17
|
+
id: string;
|
|
18
|
+
index: number;
|
|
19
|
+
connected: boolean;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
mapping: GamepadMappingType;
|
|
22
|
+
axes: number[];
|
|
23
|
+
buttons: GamepadButton[];
|
|
24
|
+
vibrationActuator?: GamepadHapticActuator | null;
|
|
25
|
+
});
|
|
26
|
+
get [Symbol.toStringTag](): string;
|
|
27
|
+
}
|
|
28
|
+
export type GamepadMappingType = '' | 'standard' | 'xr-standard';
|
|
29
|
+
/**
|
|
30
|
+
* Provides access to haptic feedback (rumble) on the gamepad.
|
|
31
|
+
* https://w3c.github.io/gamepad/#dom-gamepadhapticactuator
|
|
32
|
+
*/
|
|
33
|
+
export interface GamepadHapticActuator {
|
|
34
|
+
readonly effects: readonly GamepadHapticEffectType[];
|
|
35
|
+
playEffect(type: GamepadHapticEffectType, params?: GamepadEffectParameters): Promise<GamepadHapticsResult>;
|
|
36
|
+
reset(): Promise<GamepadHapticsResult>;
|
|
37
|
+
}
|
|
38
|
+
export type GamepadHapticEffectType = 'dual-rumble' | 'trigger-rumble';
|
|
39
|
+
export type GamepadHapticsResult = 'complete' | 'preempted';
|
|
40
|
+
export interface GamepadEffectParameters {
|
|
41
|
+
duration?: number;
|
|
42
|
+
startDelay?: number;
|
|
43
|
+
strongMagnitude?: number;
|
|
44
|
+
weakMagnitude?: number;
|
|
45
|
+
}
|