This commit is contained in:
Rim 2025-04-23 20:21:18 -04:00
parent d1d3336913
commit 25d8621ad9
26 changed files with 1004 additions and 652 deletions

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017-2020 Michaël "e7d" Ferrand
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,10 +1,28 @@
# gamepad-viewer # gamepad-viewer
Displays live status about gaming devices connected to your computer. Displays live status about gaming devices connected to your computer.
## Demo ## Demo
[gamepad.e7d.io](https://gamepad.e7d.io/) [gamepad.e7d.io](https://gamepad.e7d.io/)
## Adding to OBS
> [!IMPORTANT]
> For OBS to display real-time controller inputs when the window isn't focused, OBS needs to be started with the command line flag `--disable-features=EnableWindowsGamingInputDataFetcher`
1. Start a listening server for gamepad-viewer with `node app.js`
2. Add a new browser source and paste the url (e.g. `http://localhost:8080/?color=white&triggers=meter&type=dualsense`)
3. Uncheck the following options
- Shutdown source when not visible
4. Set **_Page permissions_** to **_Full access to OBS (Start/Stop streaming without warning, etc.)_**
5. Click **_Refresh cache of current page_**
6. Press a button on your gamepad
If done correctly, the controller should vibrate and you should see your gamepad's inputs being displayed in real-time!
## Shortcuts ## Shortcuts
- `+`: Zoom in - `+`: Zoom in
- `-`: Zoom out - `-`: Zoom out
- `0`: Reset zoom to 100% - `0`: Reset zoom to 100%
@ -18,12 +36,12 @@ Displays live status about gaming devices connected to your computer.
## Supported gamepads ## Supported gamepads
- Sony DualSense* - Sony DualSense\*
- Sony DualShock 4* - Sony DualShock 4\*
- Microsoft Xbox Series Controller* - Microsoft Xbox Series Controller\*
- Microsoft Xbox One Controller* - Microsoft Xbox One Controller\*
- Microsoft Xbox 360 Controller - Microsoft Xbox 360 Controller
- Nintendo Switch Pro Controller* - Nintendo Switch Pro Controller\*
- Xinput compatible gamepads - Xinput compatible gamepads
## Parameters ## Parameters
@ -49,18 +67,21 @@ The `telemetry` mode requires for you to determine which axes or buttons will be
To help you do so, you need to access the Gamepad Viewer with the `debug` skin. To do so, you can go to https://gamepad.e7d.io/?type=debug, and press any button for detection. To help you do so, you need to access the Gamepad Viewer with the `debug` skin. To do so, you can go to https://gamepad.e7d.io/?type=debug, and press any button for detection.
For each of the axes or buttons you want to use, activate them one by one, and proceed as following: For each of the axes or buttons you want to use, activate them one by one, and proceed as following:
- observe the changing value in the `Axes` and `Buttons` sections to determine the correct entry - observe the changing value in the `Axes` and `Buttons` sections to determine the correct entry
- note down its type (axis or button) and its index. - note down its type (axis or button) and its index.
- for the clutch, brake and throttle, note down the minimum (when released) and maximum (when pressed) values - for the clutch, brake and throttle, note down the minimum (when released) and maximum (when pressed) values
- for the direction, note down the minimum (left) and maximum (right) values - for the direction, note down the minimum (left) and maximum (right) values
For example with a **Thrustmaster T818**, you should get the following values: For example with a **Thrustmaster T818**, you should get the following values:
- clutch: axis 6, min 1, max -1 - clutch: axis 6, min 1, max -1
- brake: axis 1, min 1, max -1 - brake: axis 1, min 1, max -1
- throttle: axis 5, min 1, max -1 - throttle: axis 5, min 1, max -1
- direction: axis 0, min -1, max 1 - direction: axis 0, min -1, max 1
Another example with a **Xbox one controller** with the A button used as clutch, the triggers as brake and throttle, and the left stick as direction. You should get the following values: Another example with a **Xbox one controller** with the A button used as clutch, the triggers as brake and throttle, and the left stick as direction. You should get the following values:
- clutch: button 0, min 0, max 1 - clutch: button 0, min 0, max 1
- brake: button 6, min 0, max 1 - brake: button 6, min 0, max 1
- throttle: button 7, min 0, max 1 - throttle: button 7, min 0, max 1
@ -94,12 +115,15 @@ All the parameters are set in the URL.
### Examples ### Examples
Some working examples: Some working examples:
- Thrustmaster T818, with 900 degrees direction: http://gamepad.e7d.io/?gamepad=044f-b696&type=telemetry&clutchIndex=6&clutchMin=1&clutchMax=-1&brakeIndex=1&brakeMin=1&brakeMax=-1&throttleIndex=5&throttleMin=1&throttleMax=-1&directionIndex=0&directionDegrees=900 - Thrustmaster T818, with 900 degrees direction: http://gamepad.e7d.io/?gamepad=044f-b696&type=telemetry&clutchIndex=6&clutchMin=1&clutchMax=-1&brakeIndex=1&brakeMin=1&brakeMax=-1&throttleIndex=5&throttleMin=1&throttleMax=-1&directionIndex=0&directionDegrees=900
- DualShock 4 or Xbox Controller, with 180 degrees direction representation: http://gamepad.e7d.io/?gamepad=045e-0b00&type=telemetry&brakeType=button&brakeIndex=6&brakeMin=0&brakeMax=1&throttleType=button&throttleIndex=7&throttleMin=0&throttleMax=1&directionIndex=0&directionDegrees=180 - DualShock 4 or Xbox Controller, with 180 degrees direction representation: http://gamepad.e7d.io/?gamepad=045e-0b00&type=telemetry&brakeType=button&brakeIndex=6&brakeMin=0&brakeMax=1&throttleType=button&throttleIndex=7&throttleMin=0&throttleMax=1&directionIndex=0&directionDegrees=180
- DualShock 4 or Xbox Controller, with clutch as A or Cross button, and 180 degrees direction representation: http://gamepad.e7d.io/?gamepad=045e-0b00&type=telemetry&clutchType=button&clutchIndex=0&clutchMin=0&clutchMax=1&brakeType=button&brakeIndex=6&brakeMin=0&brakeMax=1&throttleType=button&throttleIndex=7&throttleMin=0&throttleMax=1&directionIndex=0&directionDegrees=180 - DualShock 4 or Xbox Controller, with clutch as A or Cross button, and 180 degrees direction representation: http://gamepad.e7d.io/?gamepad=045e-0b00&type=telemetry&clutchType=button&clutchIndex=0&clutchMin=0&clutchMax=1&brakeType=button&brakeIndex=6&brakeMin=0&brakeMax=1&throttleType=button&throttleIndex=7&throttleMin=0&throttleMax=1&directionIndex=0&directionDegrees=180
## How to use with OBS Studio ## How to use with OBS Studio
Please read below: Please read below:
- Open [gamepad.e7d.io](https://gamepad.e7d.io/) in your browser - Open [gamepad.e7d.io](https://gamepad.e7d.io/) in your browser
- Activate the controller you want to display in OBS by long pressing one of its buttons - Activate the controller you want to display in OBS by long pressing one of its buttons
- Configure it as desired: change skin, controller color, gamepad color, zoom... - Configure it as desired: change skin, controller color, gamepad color, zoom...
@ -111,8 +135,9 @@ Please read below:
- Press OK - Press OK
- Adjust position and size of the source as you will - Adjust position and size of the source as you will
*: These gamepads work both wired and wireless via Bluetooth \*: These gamepads work both wired and wireless via Bluetooth
## Credits ## Credits
DualShock 4 and Xbox One skins from [gamepadviewer.com](https://gamepadviewer.com/) DualShock 4 and Xbox One skins from [gamepadviewer.com](https://gamepadviewer.com/)
DualSense skin from [justEhCupcake](https://github.com/justEhCupcake/justEhCupcake.github.io/tree/main/PS5_Display_Pics) DualSense skin from [justEhCupcake](https://github.com/justEhCupcake/justEhCupcake.github.io/tree/main/PS5_Display_Pics)

43
app.js Normal file
View File

@ -0,0 +1,43 @@
// app.js
const http = require("http");
const fs = require("fs");
const path = require("path");
const url = require("url");
const port = 8080;
const host = "0.0.0.0";
const mimeTypes = {
".html": "text/html",
".css": "text/css",
".js": "application/javascript",
".svg": "image/svg+xml",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".ico": "image/x-icon",
".json": "application/json",
".txt": "text/plain",
};
http.createServer((req, res) => {
// Parse the URL and extract just the pathname, ignoring query parameters
const parsedUrl = url.parse(req.url);
let filePath = "." + decodeURIComponent(parsedUrl.pathname);
if (filePath === "./") filePath = "./index.html";
const ext = path.extname(filePath).toLowerCase();
const contentType = mimeTypes[ext] || "application/octet-stream";
fs.readFile(filePath, (err, content) => {
if (err) {
res.writeHead(404);
res.end("404 Not Found");
} else {
res.writeHead(200, { "Content-Type": contentType });
res.end(content);
}
});
}).listen(port, host, () => {
console.log(`Server running at http://${host}:${port}/`);
});

View File

@ -2,7 +2,7 @@
<browserconfig> <browserconfig>
<msapplication> <msapplication>
<tile> <tile>
<square150x150logo src="/mstile-150x150.png"/> <square150x150logo src="img/mstile-150x150.png"/>
<TileColor>#666666</TileColor> <TileColor>#666666</TileColor>
</tile> </tile>
</msapplication> </msapplication>

View File

@ -261,21 +261,29 @@ kbd[title],
} }
kbd { kbd {
-moz-box-shadow: inset 0 0 1px rgb(150, 150, 150), -moz-box-shadow:
inset 0 -0.05em 0.4em rgb(80, 80, 80), 0 0.1em 0 rgb(30, 30, 30), inset 0 0 1px rgb(150, 150, 150),
inset 0 -0.05em 0.4em rgb(80, 80, 80),
0 0.1em 0 rgb(30, 30, 30),
0 0.1em 0.1em rgba(0, 0, 0, 0.3); 0 0.1em 0.1em rgba(0, 0, 0, 0.3);
-webkit-box-shadow: inset 0 0 1px rgb(150, 150, 150), -webkit-box-shadow:
inset 0 -0.05em 0.4em rgb(80, 80, 80), 0 0.1em 0 rgb(30, 30, 30), inset 0 0 1px rgb(150, 150, 150),
inset 0 -0.05em 0.4em rgb(80, 80, 80),
0 0.1em 0 rgb(30, 30, 30),
0 0.1em 0.1em rgba(0, 0, 0, 0.3); 0 0.1em 0.1em rgba(0, 0, 0, 0.3);
background: -moz-linear-gradient(top, rgb(60, 60, 60), rgb(80, 80, 80)); background: -moz-linear-gradient(top, rgb(60, 60, 60), rgb(80, 80, 80));
background: -webkit-gradient(linear, background: -webkit-gradient(
linear,
left top, left top,
left bottom, left bottom,
from(rgb(60, 60, 60)), from(rgb(60, 60, 60)),
to(rgb(80, 80, 80))); to(rgb(80, 80, 80))
);
background: rgb(80, 80, 80); background: rgb(80, 80, 80);
box-shadow: inset 0 0 1px rgb(150, 150, 150), box-shadow:
inset 0 -0.05em 0.4em rgb(80, 80, 80), 0 0.1em 0 rgb(30, 30, 30), inset 0 0 1px rgb(150, 150, 150),
inset 0 -0.05em 0.4em rgb(80, 80, 80),
0 0.1em 0 rgb(30, 30, 30),
0 0.1em 0.1em rgba(0, 0, 0, 0.3); 0 0.1em 0.1em rgba(0, 0, 0, 0.3);
color: rgb(250, 250, 250); color: rgb(250, 250, 250);
text-shadow: -1px -1px 0 rgb(70, 70, 70); text-shadow: -1px -1px 0 rgb(70, 70, 70);

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 517 B

View File

Before

Width:  |  Height:  |  Size: 780 B

After

Width:  |  Height:  |  Size: 780 B

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,33 +1,62 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Gamepad Viewer</title> <title>Gamepad Viewer</title>
<meta name="description" content="Displays live status about gaming devices connected to your computer."> <meta
<meta name="viewport" content="width=device-width, initial-scale=1.0"> name="description"
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png"> content="Displays live status about gaming devices connected to your computer."
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png"> />
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="manifest" href="site.webmanifest"> <link
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#333333"> rel="apple-touch-icon"
<meta name="apple-mobile-web-app-title" content="Gamepad Viewer"> sizes="180x180"
<meta name="application-name" content="Gamepad Viewer"> href="img/apple-touch-icon.png"
<meta name="msapplication-TileColor" content="#666666"> />
<meta name="theme-color" content="#ffffff"> <link
rel="icon"
type="image/png"
sizes="32x32"
href="img/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="img/favicon-16x16.png"
/>
<link rel="manifest" href="site.webmanifest" />
<link rel="mask-icon" href="img/safari-pinned-tab.svg" color="#333333" />
<meta name="apple-mobile-web-app-title" content="Gamepad Viewer" />
<meta name="application-name" content="Gamepad Viewer" />
<meta name="msapplication-TileColor" content="#666666" />
<meta name="theme-color" content="#ffffff" />
<link rel="stylesheet" href="css/gamepad.css"> <link rel="stylesheet" href="css/gamepad.css" />
</head> </head>
<body> <body>
<div id="unsupported">Sorry, but your browser does not support the <a target="_blank" <div id="unsupported">
href="https://developer.mozilla.org/docs/Web/API/Gamepad_API">Gamepad API</a>.</div> Sorry, but your browser does not support the
<a
target="_blank"
href="https://developer.mozilla.org/docs/Web/API/Gamepad_API"
>Gamepad API</a
>.
</div>
<div id="placeholder"> <div id="placeholder">
<svg version="1.1" viewBox="0 0 549.3125 367.98749" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"> <svg
version="1.1"
viewBox="0 0 549.3125 367.98749"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
>
<defs> <defs>
<clipPath id="a"> <clipPath id="a">
<path d="m0 294.39h439.45v-294.39h-439.45v294.39z"></path> <path
d="m0 294.39h439.45v-294.39h-439.45v294.39z"
></path>
</clipPath> </clipPath>
</defs> </defs>
<g transform="matrix(1.25 0 0 -1.25 0 367.99)"> <g transform="matrix(1.25 0 0 -1.25 0 367.99)">
@ -35,63 +64,78 @@
<g transform="translate(291.61 74.1)"> <g transform="translate(291.61 74.1)">
<path <path
d="m0 0c12.566-4e-3 24.723-4.474 34.3-12.61 0.226-0.192 0.443-0.396 0.649-0.61l49.151-51.259c0.15-0.151 0.299-0.301 0.46-0.451 15.29-14 39.54-11.75 51.669 5.11 30.55 42.46-4.5 145.09-43.609 235.94-0.845 1.916-2.417 3.417-4.37 4.17l-2.521 1c-2.458 0.96-4.272 3.089-4.83 5.67-1.569 7.229-6.358 13.343-13 16.6-40.389 19.86-65.7 21.27-79.109 8.13-1.479-1.499-3.485-2.36-5.591-2.4h-110.17c-2.127 0.035-4.154 0.908-5.64 2.43-13.4 13.14-38.71 11.73-79.1-8.13-6.642-3.257-11.431-9.371-13-16.6-0.542-2.585-2.345-4.726-4.8-5.7l-2.52-1c-1.963-0.765-3.537-2.285-4.37-4.22-39.1-90.78-74.15-193.43-43.6-235.89 12.13-16.89 36.39-19.09 51.669-5.11 0.161 0.14 0.31 0.29 0.461 0.451l49.15 51.259c0.206 0.214 0.423 0.418 0.65 0.61 9.576 8.136 21.733 12.606 34.299 12.61h143.77z" d="m0 0c12.566-4e-3 24.723-4.474 34.3-12.61 0.226-0.192 0.443-0.396 0.649-0.61l49.151-51.259c0.15-0.151 0.299-0.301 0.46-0.451 15.29-14 39.54-11.75 51.669 5.11 30.55 42.46-4.5 145.09-43.609 235.94-0.845 1.916-2.417 3.417-4.37 4.17l-2.521 1c-2.458 0.96-4.272 3.089-4.83 5.67-1.569 7.229-6.358 13.343-13 16.6-40.389 19.86-65.7 21.27-79.109 8.13-1.479-1.499-3.485-2.36-5.591-2.4h-110.17c-2.127 0.035-4.154 0.908-5.64 2.43-13.4 13.14-38.71 11.73-79.1-8.13-6.642-3.257-11.431-9.371-13-16.6-0.542-2.585-2.345-4.726-4.8-5.7l-2.52-1c-1.963-0.765-3.537-2.285-4.37-4.22-39.1-90.78-74.15-193.43-43.6-235.89 12.13-16.89 36.39-19.09 51.669-5.11 0.161 0.14 0.31 0.29 0.461 0.451l49.15 51.259c0.206 0.214 0.423 0.418 0.65 0.61 9.576 8.136 21.733 12.606 34.299 12.61h143.77z"
fill="#1f1f21"></path> fill="#1f1f21"
></path>
</g> </g>
<g transform="translate(278.88 161.81)"> <g transform="translate(278.88 161.81)">
<path <path
d="m0 0c13.481 0 24.41-10.929 24.41-24.41 0-13.482-10.929-24.41-24.41-24.41s-24.41 10.928-24.41 24.41c0 13.481 10.929 24.41 24.41 24.41" d="m0 0c13.481 0 24.41-10.929 24.41-24.41 0-13.482-10.929-24.41-24.41-24.41s-24.41 10.928-24.41 24.41c0 13.481 10.929 24.41 24.41 24.41"
fill="#383838"></path> fill="#383838"
></path>
</g> </g>
<g transform="translate(278.88 112.99)"> <g transform="translate(278.88 112.99)">
<path <path
d="m0 0c-13.481 0-24.41 10.929-24.41 24.41 0 13.482 10.929 24.41 24.41 24.41s24.41-10.928 24.41-24.41v-0.01c-6e-3 -13.477-10.932-24.4-24.41-24.4m0 51.92c-15.193 0-27.51-12.316-27.51-27.51 0-15.193 12.317-27.51 27.51-27.51 15.189 0 27.505 12.311 27.51 27.5 6e-3 15.194-12.307 27.515-27.5 27.52h-0.01z" d="m0 0c-13.481 0-24.41 10.929-24.41 24.41 0 13.482 10.929 24.41 24.41 24.41s24.41-10.928 24.41-24.41v-0.01c-6e-3 -13.477-10.932-24.4-24.41-24.4m0 51.92c-15.193 0-27.51-12.316-27.51-27.51 0-15.193 12.317-27.51 27.51-27.51 15.189 0 27.505 12.311 27.51 27.5 6e-3 15.194-12.307 27.515-27.5 27.52h-0.01z"
fill="#191919"></path> fill="#191919"
></path>
</g> </g>
<g transform="translate(188.51 145.71)"> <g transform="translate(188.51 145.71)">
<path <path
d="m0 0h-11.88v11.87c0 3.314-2.686 6-6 6h-8.79c-3.313 0-6-2.686-6-6v-11.87h-11.84c-3.313 0-6-2.687-6-6v-8.79c0-3.314 2.687-6 6-6h11.87v-11.87c0-3.314 2.686-6 6-6h8.79c3.314 0 6 2.686 6 6v11.899h11.87c3.314 0 6 2.687 6 6v8.79c-0.016 3.311-2.71 5.982-6.02 5.971" d="m0 0h-11.88v11.87c0 3.314-2.686 6-6 6h-8.79c-3.313 0-6-2.686-6-6v-11.87h-11.84c-3.313 0-6-2.687-6-6v-8.79c0-3.314 2.687-6 6-6h11.87v-11.87c0-3.314 2.686-6 6-6h8.79c3.314 0 6 2.686 6 6v11.899h11.87c3.314 0 6 2.687 6 6v8.79c-0.016 3.311-2.71 5.982-6.02 5.971"
fill="#898989"></path> fill="#898989"
></path>
</g> </g>
<g transform="translate(111.34 230.94)"> <g transform="translate(111.34 230.94)">
<path <path
d="m0 0c14.735 0 26.68-11.945 26.68-26.68s-11.945-26.68-26.68-26.68-26.68 11.945-26.68 26.68 11.945 26.68 26.68 26.68" d="m0 0c14.735 0 26.68-11.945 26.68-26.68s-11.945-26.68-26.68-26.68-26.68 11.945-26.68 26.68 11.945 26.68 26.68 26.68"
fill="#191919"></path> fill="#191919"
></path>
</g> </g>
<g transform="translate(111.34 228.31)"> <g transform="translate(111.34 228.31)">
<path <path
d="m0 0c13.283 0 24.05-10.768 24.05-24.05 0-13.283-10.767-24.05-24.05-24.05-13.282 0-24.05 10.767-24.05 24.05 0 13.282 10.768 24.05 24.05 24.05" d="m0 0c13.283 0 24.05-10.768 24.05-24.05 0-13.283-10.767-24.05-24.05-24.05-13.282 0-24.05 10.767-24.05 24.05 0 13.282 10.768 24.05 24.05 24.05"
fill="#383838"></path> fill="#383838"
></path>
</g> </g>
<g transform="translate(306.39 219)"> <g transform="translate(306.39 219)">
<path <path
d="m0 0c8.141 0 14.74-6.599 14.74-14.74 0-8.14-6.599-14.74-14.74-14.74s-14.739 6.6-14.739 14.74c0 8.141 6.598 14.74 14.739 14.74" d="m0 0c8.141 0 14.74-6.599 14.74-14.74 0-8.14-6.599-14.74-14.74-14.74s-14.739 6.6-14.739 14.74c0 8.141 6.598 14.74 14.739 14.74"
fill="#898989"></path> fill="#898989"
></path>
</g> </g>
<g id="a-button" transform="translate(335.87 189.37)"> <g id="a-button" transform="translate(335.87 189.37)">
<path <path
d="m0 0c8.141 0 14.74-6.599 14.74-14.74s-6.599-14.74-14.74-14.74-14.74 6.599-14.74 14.74 6.599 14.74 14.74 14.74" d="m0 0c8.141 0 14.74-6.599 14.74-14.74s-6.599-14.74-14.74-14.74-14.74 6.599-14.74 14.74 6.599 14.74 14.74 14.74"
fill="#898989"></path> fill="#898989"
></path>
</g> </g>
<g transform="translate(335.87 248.63)"> <g transform="translate(335.87 248.63)">
<path <path
d="m0 0c8.141 0 14.74-6.6 14.74-14.74 0-8.141-6.599-14.74-14.74-14.74s-14.74 6.599-14.74 14.74c0 8.14 6.599 14.74 14.74 14.74" d="m0 0c8.141 0 14.74-6.6 14.74-14.74 0-8.141-6.599-14.74-14.74-14.74s-14.74 6.599-14.74 14.74c0 8.14 6.599 14.74 14.74 14.74"
fill="#898989"></path> fill="#898989"
></path>
</g> </g>
<g transform="translate(365.35 219)"> <g transform="translate(365.35 219)">
<path <path
d="m0 0c8.141 0 14.74-6.599 14.74-14.74 0-8.14-6.599-14.74-14.74-14.74s-14.739 6.6-14.739 14.74c0 8.141 6.598 14.74 14.739 14.74" d="m0 0c8.141 0 14.74-6.599 14.74-14.74 0-8.14-6.599-14.74-14.74-14.74s-14.739 6.6-14.739 14.74c0 8.141 6.598 14.74 14.739 14.74"
fill="#898989"></path> fill="#898989"
></path>
</g> </g>
</g> </g>
</g> </g>
</svg> </svg>
<div id="placeholder-instructions">Press and hold any button to start.</div> <div id="placeholder-instructions">
Press and hold any button to start.
</div>
</div> </div>
<div id="gamepad"></div> <div id="gamepad"></div>
<div id="instructions" style="display: none;"> <div id="instructions" style="display: none">
<p>Press <kbd>H</kbd> or <a href="#">click here</a> to read instructions.</p> <p>
Press <kbd>H</kbd> or <a href="#">click here</a> to read
instructions.
</p>
</div> </div>
<div id="overlay" style="display: none;"> <div id="overlay" style="display: none">
<span id="gamepad-id"> <span id="gamepad-id">
<label for="gamepad-id-select">Gamepad</label> <label for="gamepad-id-select">Gamepad</label>
<select id="gamepad-id-select" name="gamepad-id"> <select id="gamepad-id-select" name="gamepad-id">
@ -121,13 +165,13 @@
<option value="magenta">Magenta</option> <option value="magenta">Magenta</option>
</select> </select>
</span> </span>
<span id="color" style="display: none;"> <span id="color" style="display: none">
<label for="color-select">Color</label> <label for="color-select">Color</label>
<select id="color-select" name="color"> <select id="color-select" name="color">
<option value="">Default</option> <option value="">Default</option>
</select> </select>
</span> </span>
<span id="triggers" style="display: none;"> <span id="triggers" style="display: none">
<label for="triggers-select">Triggers</label> <label for="triggers-select">Triggers</label>
<select id="triggers-select" name="triggers"> <select id="triggers-select" name="triggers">
<option value="opacity">Opacity</option> <option value="opacity">Opacity</option>
@ -142,8 +186,9 @@
<h2>Help</h2> <h2>Help</h2>
<h3>Instructions</h3> <h3>Instructions</h3>
<p>Press and hold any of your gamepad buttons for at least 1 second. If your gamepad is supported, it shows <p>
up. Press and hold any of your gamepad buttons for at least 1
second. If your gamepad is supported, it shows up.
</p> </p>
<h3>Detected Controllers</h3> <h3>Detected Controllers</h3>
@ -214,14 +259,23 @@
</table> </table>
<h3>Credits</h3> <h3>Credits</h3>
<p>All information and source code can be found on GitHub at <a target="_blank" <p>
href="https://github.com/e7d/gamepad-viewer">e7d/gamepad-viewer</a>.</p> All information and source code can be found on GitHub at
<p>DualShock 4 and Xbox One skins from <a target="_blank" <a
href="https://gamepadviewer.com/">gamepadviewer.com</a>.</p> target="_blank"
href="https://github.com/e7d/gamepad-viewer"
>e7d/gamepad-viewer</a
>.
</p>
<p>
DualShock 4 and Xbox One skins from
<a target="_blank" href="https://gamepadviewer.com/"
>gamepadviewer.com</a
>.
</p>
</div> </div>
</div> </div>
<script async src="js/gamepad.js"></script> <script async src="js/gamepad.js"></script>
</body> </body>
</html> </html>

View File

@ -6,44 +6,58 @@
class Gamepad { class Gamepad {
REGEX = { REGEX = {
CHROME: /^(?<name>.*) \((?:.*?Vendor: (?<vendor>[0-9a-f]{4}) Product: (?<product>[0-9a-f]{4})|(?<id>.*))\)$/i, CHROME: /^(?<name>.*) \((?:.*?Vendor: (?<vendor>[0-9a-f]{4}) Product: (?<product>[0-9a-f]{4})|(?<id>.*))\)$/i,
FIREFOX: /^((?<vendor>[0-9a-f]{4})-(?<product>[0-9a-f]{4})-(?<name>.*))$/i, FIREFOX:
/^((?<vendor>[0-9a-f]{4})-(?<product>[0-9a-f]{4})-(?<name>.*))$/i,
OTHER: /^(?<name>.*)$/i, OTHER: /^(?<name>.*)$/i,
} };
/** /**
* Creates an instance of Gamepad. * Creates an instance of Gamepad.
*/ */
constructor() { constructor() {
// cached DOM references // cached DOM references
this.$body = document.querySelector('body'); this.$body = document.querySelector("body");
this.$instructions = document.querySelector('#instructions'); this.$instructions = document.querySelector("#instructions");
this.$instructionsLink = this.$instructions.querySelector('a'); this.$instructionsLink = this.$instructions.querySelector("a");
this.$placeholder = document.querySelector('#placeholder'); this.$placeholder = document.querySelector("#placeholder");
this.$gamepad = document.querySelector('#gamepad'); this.$gamepad = document.querySelector("#gamepad");
this.$overlay = document.querySelector('#overlay'); this.$overlay = document.querySelector("#overlay");
this.$gamepadSelect = document.querySelector('select[name=gamepad-id]'); this.$gamepadSelect = document.querySelector("select[name=gamepad-id]");
this.$skinSelect = document.querySelector('select[name=skin]'); this.$skinSelect = document.querySelector("select[name=skin]");
this.$backgroundSelect = document.querySelector('select[name=background]'); this.$backgroundSelect = document.querySelector(
this.$colorOverlay = this.$overlay.querySelector('#color'); "select[name=background]",
this.$colorSelect = this.$colorOverlay.querySelector('select[name=color]'); );
this.$triggersOverlay = this.$overlay.querySelector('#triggers'); this.$colorOverlay = this.$overlay.querySelector("#color");
this.$triggersSelect = this.$triggersOverlay.querySelector('select[name=triggers]'); this.$colorSelect =
this.$helpPopout = document.querySelector('#help.popout'); this.$colorOverlay.querySelector("select[name=color]");
this.$helpPopoutClose = this.$helpPopout.querySelector('.close'); this.$triggersOverlay = this.$overlay.querySelector("#triggers");
this.$gamepadList = document.querySelector('#gamepad-list'); this.$triggersSelect = this.$triggersOverlay.querySelector(
"select[name=triggers]",
);
this.$helpPopout = document.querySelector("#help.popout");
this.$helpPopoutClose = this.$helpPopout.querySelector(".close");
this.$gamepadList = document.querySelector("#gamepad-list");
// ensure the GamePad API is available on this browser // ensure the GamePad API is available on this browser
this.assertGamepadAPI(); this.assertGamepadAPI();
// overlay selectors // overlay selectors
this.backgrounds = [ this.backgrounds = [
{ name: 'transparent', backgroundColor: 'transparent', textColor: 'black' }, {
{ name: 'checkered', backgroundColor: 'url(css/transparent-bg.png)', textColor: 'black' }, name: "transparent",
{ name: 'dimgrey', backgroundColor: 'dimgrey', textColor: 'black' }, backgroundColor: "transparent",
{ name: 'black', backgroundColor: 'black', textColor: 'white' }, textColor: "black",
{ name: 'white', backgroundColor: 'white', textColor: 'black' }, },
{ name: 'lime', backgroundColor: 'lime', textColor: 'black' }, {
{ name: 'magenta', backgroundColor: 'magenta', textColor: 'black' }, name: "checkered",
backgroundColor: "url(css/transparent-bg.png)",
textColor: "black",
},
{ name: "dimgrey", backgroundColor: "dimgrey", textColor: "black" },
{ name: "black", backgroundColor: "black", textColor: "white" },
{ name: "white", backgroundColor: "white", textColor: "black" },
{ name: "lime", backgroundColor: "lime", textColor: "black" },
{ name: "magenta", backgroundColor: "magenta", textColor: "black" },
]; ];
this.initOverlaySelectors(); this.initOverlaySelectors();
@ -53,19 +67,19 @@ class Gamepad {
// See: https://html5gamepad.com/codes // See: https://html5gamepad.com/codes
debug: { debug: {
id: /debug/, id: /debug/,
name: 'Debug', name: "Debug",
}, },
ds4: { ds4: {
id: /05c4|09cc|0104|046d|0810|2563/, // 05c4,09cc,0104 = DS4 controllers product codes, 046d,0810,2563 = PS-like controllers vendor codes id: /05c4|09cc|0104|046d|0810|2563/, // 05c4,09cc,0104 = DS4 controllers product codes, 046d,0810,2563 = PS-like controllers vendor codes
name: 'DualShock 4', name: "DualShock 4",
colors: ['black', 'white', 'red', 'blue'], colors: ["black", "white", "red", "blue"],
triggers: true, triggers: true,
zoom: true, zoom: true,
}, },
dualsense: { dualsense: {
id: /0ce6/, // 0ce6 = DualSense controller product code id: /0ce6/, // 0ce6 = DualSense controller product code
name: 'DualSense', name: "DualSense",
colors: ['white', 'black'], colors: ["white", "black"],
triggers: true, triggers: true,
zoom: true, zoom: true,
}, },
@ -91,13 +105,13 @@ class Gamepad {
// }, // },
telemetry: { telemetry: {
id: /telemetry/, id: /telemetry/,
name: 'Telemetry', name: "Telemetry",
zoom: true zoom: true,
}, },
'xbox-one': { "xbox-one": {
id: /045e|xinput|XInput/, // 045e = Microsoft vendor code, xinput = standard Windows controller id: /045e|xinput|XInput/, // 045e = Microsoft vendor code, xinput = standard Windows controller
name: 'Xbox One', name: "Xbox One",
colors: ['black', 'white'], colors: ["black", "white"],
triggers: true, triggers: true,
zoom: true, zoom: true,
}, },
@ -124,7 +138,7 @@ class Gamepad {
this.colorIndex = null; this.colorIndex = null;
this.colorName = null; this.colorName = null;
this.useMeterTriggers = false; this.useMeterTriggers = false;
this.zoomMode = 'auto'; this.zoomMode = "auto";
this.zoomLevel = 1; this.zoomLevel = 1;
this.mapping = { this.mapping = {
buttons: [], buttons: [],
@ -132,44 +146,50 @@ class Gamepad {
}; };
// listen for gamepad related events // listen for gamepad related events
this.haveEvents = 'GamepadEvent' in window; this.haveEvents = "GamepadEvent" in window;
if (this.haveEvents) { if (this.haveEvents) {
window.addEventListener( window.addEventListener(
'gamepadconnected', "gamepadconnected",
this.onGamepadConnect.bind(this) this.onGamepadConnect.bind(this),
); );
window.addEventListener( window.addEventListener(
'gamepaddisconnected', "gamepaddisconnected",
this.onGamepadDisconnect.bind(this) this.onGamepadDisconnect.bind(this),
); );
} }
// listen for mouse move events // listen for mouse move events
window.addEventListener('mousemove', this.onMouseMove.bind(this)); window.addEventListener("mousemove", this.onMouseMove.bind(this));
// listen for keyboard events // listen for keyboard events
window.addEventListener('keydown', this.onKeyDown.bind(this)); window.addEventListener("keydown", this.onKeyDown.bind(this));
// listen for keyboard events // listen for keyboard events
window.addEventListener('resize', this.onResize.bind(this)); window.addEventListener("resize", this.onResize.bind(this));
// bind a gamepads scan // bind a gamepads scan
window.setInterval(this.scan.bind(this), this.scanDelay); window.setInterval(this.scan.bind(this), this.scanDelay);
// change the type if specified // change the type if specified
const skin = this.getUrlParam('type'); const skin = this.getUrlParam("type");
if (skin) { if (skin) {
this.changeSkin(skin); this.changeSkin(skin);
} }
// change the background if specified // change the background if specified
const background = this.getUrlParam('background'); const background = this.getUrlParam("background");
if (background) this.changeBackground(background); if (background) this.changeBackground(background);
// by default, enqueue a delayed display of the placeholder animation // by default, enqueue a delayed display of the placeholder animation
this.displayPlaceholder(); this.displayPlaceholder();
// listen for click events // listen for click events
this.$instructionsLink.addEventListener('click', this.toggleHelp.bind(this)); this.$instructionsLink.addEventListener(
this.$helpPopoutClose.addEventListener('click', this.toggleHelp.bind(this)); "click",
this.toggleHelp.bind(this),
);
this.$helpPopoutClose.addEventListener(
"click",
this.toggleHelp.bind(this),
);
} }
/** /**
@ -182,8 +202,8 @@ class Gamepad {
? () => navigator.webkitGetGamepads() ? () => navigator.webkitGetGamepads()
: null; : null;
if (!getGamepadsFn) { if (!getGamepadsFn) {
this.$body.classList.add('unsupported'); this.$body.classList.add("unsupported");
throw new Error('Unsupported gamepad API'); throw new Error("Unsupported gamepad API");
} }
this.getNavigatorGamepads = getGamepadsFn; this.getNavigatorGamepads = getGamepadsFn;
} }
@ -192,20 +212,20 @@ class Gamepad {
* Initialises the overlay selectors * Initialises the overlay selectors
*/ */
initOverlaySelectors() { initOverlaySelectors() {
this.$gamepadSelect.addEventListener('change', () => this.$gamepadSelect.addEventListener("change", () =>
this.changeGamepad(this.$gamepadSelect.value) this.changeGamepad(this.$gamepadSelect.value),
); );
this.$skinSelect.addEventListener('change', () => this.$skinSelect.addEventListener("change", () =>
this.changeSkin(this.$skinSelect.value) this.changeSkin(this.$skinSelect.value),
); );
this.$backgroundSelect.addEventListener('change', () => this.$backgroundSelect.addEventListener("change", () =>
this.changeBackground(this.$backgroundSelect.value) this.changeBackground(this.$backgroundSelect.value),
); );
this.$colorSelect.addEventListener('change', () => this.$colorSelect.addEventListener("change", () =>
this.changeGamepadColor(this.$colorSelect.value) this.changeGamepadColor(this.$colorSelect.value),
); );
this.$triggersSelect.addEventListener('change', () => this.$triggersSelect.addEventListener("change", () =>
this.toggleTriggers(this.$triggersSelect.value === 'meter') this.toggleTriggers(this.$triggersSelect.value === "meter"),
); );
} }
@ -215,8 +235,8 @@ class Gamepad {
* @param {HTMLElement} $element * @param {HTMLElement} $element
*/ */
show($element) { show($element) {
$element.style.removeProperty('display'); $element.style.removeProperty("display");
$element.classList.remove('fadeIn', 'fadeOut'); $element.classList.remove("fadeIn", "fadeOut");
} }
/** /**
@ -225,8 +245,8 @@ class Gamepad {
* @param {HTMLElement} $element * @param {HTMLElement} $element
*/ */
hide($element) { hide($element) {
$element.style.setProperty('display', 'none'); $element.style.setProperty("display", "none");
$element.classList.remove('fadeIn', 'fadeOut'); $element.classList.remove("fadeIn", "fadeOut");
} }
/** /**
@ -235,9 +255,9 @@ class Gamepad {
* @param {HTMLElement} $element * @param {HTMLElement} $element
*/ */
fadeIn($element) { fadeIn($element) {
$element.style.removeProperty('display'); $element.style.removeProperty("display");
$element.classList.remove('fadeOut'); $element.classList.remove("fadeOut");
$element.classList.add('fadeIn'); $element.classList.add("fadeIn");
} }
/** /**
@ -246,9 +266,9 @@ class Gamepad {
* @param {HTMLElement} $element * @param {HTMLElement} $element
*/ */
fadeOut($element) { fadeOut($element) {
$element.style.removeProperty('display'); $element.style.removeProperty("display");
$element.classList.remove('fadeIn'); $element.classList.remove("fadeIn");
$element.classList.add('fadeOut'); $element.classList.add("fadeOut");
} }
/** /**
@ -283,7 +303,7 @@ class Gamepad {
// hide instructions animation if no gamepad is active after X ms // hide instructions animation if no gamepad is active after X ms
this.instructionsTimeout = window.setTimeout( this.instructionsTimeout = window.setTimeout(
() => this.fadeOut(this.$instructions), () => this.fadeOut(this.$instructions),
this.instructionsDelay this.instructionsDelay,
); );
} }
@ -319,7 +339,7 @@ class Gamepad {
// hide placeholder animation if no gamepad is active after X ms // hide placeholder animation if no gamepad is active after X ms
this.placeholderTimeout = window.setTimeout( this.placeholderTimeout = window.setTimeout(
() => this.fadeOut(this.$placeholder), () => this.fadeOut(this.$placeholder),
this.placeholderDelay this.placeholderDelay,
); );
} }
@ -352,7 +372,7 @@ class Gamepad {
// hide overlay animation if no gamepad is active after X ms // hide overlay animation if no gamepad is active after X ms
this.overlayTimeout = window.setTimeout( this.overlayTimeout = window.setTimeout(
() => this.fadeOut(this.$overlay), () => this.fadeOut(this.$overlay),
this.overlayDelay this.overlayDelay,
); );
} }
@ -369,14 +389,14 @@ class Gamepad {
if (firefoxResults) return firefoxResults.groups; if (firefoxResults) return firefoxResults.groups;
const otherResults = this.REGEX.OTHER.exec(id); const otherResults = this.REGEX.OTHER.exec(id);
if (otherResults) return otherResults.groups; if (otherResults) return otherResults.groups;
return { name: id, vendor: '', product: '' }; return { name: id, vendor: "", product: "" };
} }
/** /**
* Updates the list of connected gamepads in the overlay * Updates the list of connected gamepads in the overlay
*/ */
updateGamepadList() { updateGamepadList() {
for (const $entry of this.$gamepadSelect.querySelectorAll('.entry')) { for (const $entry of this.$gamepadSelect.querySelectorAll(".entry")) {
$entry.remove(); $entry.remove();
} }
const $options = []; const $options = [];
@ -385,10 +405,10 @@ class Gamepad {
if (!gamepad) continue; if (!gamepad) continue;
const { name } = this.toGamepadInfo(gamepad.id); const { name } = this.toGamepadInfo(gamepad.id);
$options.push( $options.push(
`<option class='entry' value='${gamepad.id}'>${name}</option>` `<option class='entry' value='${gamepad.id}'>${name}</option>`,
); );
} }
this.$gamepadSelect.innerHTML += $options.join(''); this.$gamepadSelect.innerHTML += $options.join("");
} }
/** /**
@ -407,9 +427,10 @@ class Gamepad {
} }
const colorOptions = colors.map( const colorOptions = colors.map(
(color) => `<option value='${color}'>${color.charAt(0).toUpperCase()}${color.slice(1)}</option>` (color) =>
`<option value='${color}'>${color.charAt(0).toUpperCase()}${color.slice(1)}</option>`,
); );
this.$colorSelect.innerHTML = colorOptions.join(''); this.$colorSelect.innerHTML = colorOptions.join("");
this.show(this.$colorOverlay); this.show(this.$colorOverlay);
} }
@ -461,7 +482,7 @@ class Gamepad {
if (e.gamepad.index === this.index) { if (e.gamepad.index === this.index) {
// display a disconnection indicator // display a disconnection indicator
this.$gamepad.classList.add('disconnected'); this.$gamepad.classList.add("disconnected");
this.disconnectedIndex = e.gamepad.index; this.disconnectedIndex = e.gamepad.index;
// refresh gamepad list on help, if displayed // refresh gamepad list on help, if displayed
@ -485,43 +506,43 @@ class Gamepad {
*/ */
onKeyDown(e) { onKeyDown(e) {
switch (e.code) { switch (e.code) {
case 'Delete': case "Delete":
case 'Escape': case "Escape":
this.clear(); this.clear();
this.displayPlaceholder(); this.displayPlaceholder();
break; break;
case 'KeyB': case "KeyB":
this.changeBackground(); this.changeBackground();
break; break;
case 'KeyC': case "KeyC":
this.changeGamepadColor(); this.changeGamepadColor();
break; break;
case 'KeyD': case "KeyD":
this.toggleDebug(); this.toggleDebug();
break; break;
case 'KeyG': case "KeyG":
this.toggleGamepadType(); this.toggleGamepadType();
break; break;
case 'KeyH': case "KeyH":
this.toggleHelp(); this.toggleHelp();
break; break;
case 'KeyT': case "KeyT":
this.toggleTriggers(); this.toggleTriggers();
break; break;
case 'NumpadAdd': case "NumpadAdd":
case 'Equal': case "Equal":
this.changeZoom('+'); this.changeZoom("+");
break; break;
case 'NumpadSubtract': case "NumpadSubtract":
case 'Minus': case "Minus":
this.changeZoom('-'); this.changeZoom("-");
break; break;
case 'Numpad5': case "Numpad5":
case 'Digit5': case "Digit5":
this.changeZoom('auto'); this.changeZoom("auto");
break; break;
case 'Numpad0': case "Numpad0":
case 'Digit0': case "Digit0":
this.changeZoom(0); this.changeZoom(0);
break; break;
} }
@ -531,7 +552,7 @@ class Gamepad {
* Handles the keyboard 'keydown' event * Handles the keyboard 'keydown' event
*/ */
onResize() { onResize() {
if (this.zoomMode === 'auto') this.changeZoom('auto'); if (this.zoomMode === "auto") this.changeZoom("auto");
} }
/** /**
@ -554,15 +575,18 @@ class Gamepad {
for (let key = 0; key < this.gamepads.length; key++) { for (let key = 0; key < this.gamepads.length; key++) {
const gamepad = this.gamepads[key]; const gamepad = this.gamepads[key];
if (!gamepad) continue; if (!gamepad) continue;
$tbody.push(`<tr><td>${gamepad.index}</td><td>${gamepad.id}</td></tr>`); $tbody.push(
`<tr><td>${gamepad.index}</td><td>${gamepad.id}</td></tr>`,
);
} }
if ($tbody.length === 0) { if ($tbody.length === 0) {
this.$gamepadList.innerHTML = '<tr><td colspan="2">No gamepad detected.</td></tr>'; this.$gamepadList.innerHTML =
'<tr><td colspan="2">No gamepad detected.</td></tr>';
return; return;
} }
this.$gamepadList.innerHTML = $tbody.join(''); this.$gamepadList.innerHTML = $tbody.join("");
} }
/** /**
@ -581,12 +605,12 @@ class Gamepad {
* @returns {string} * @returns {string}
*/ */
getType(gamepad) { getType(gamepad) {
const type = this.getUrlParam('type'); const type = this.getUrlParam("type");
// if the debug option is active, use the associated template // if the debug option is active, use the associated template
if (type === 'debug') this.debug = true; if (type === "debug") this.debug = true;
if (this.debug) { if (this.debug) {
return 'debug'; return "debug";
} }
// if the gamepad type is set through params, apply it // if the gamepad type is set through params, apply it
@ -601,7 +625,7 @@ class Gamepad {
} }
} }
return 'debug'; return "debug";
} }
/** /**
@ -624,13 +648,14 @@ class Gamepad {
if ( if (
null !== this.disconnectedIndex && null !== this.disconnectedIndex &&
index !== this.disconnectedIndex index !== this.disconnectedIndex
) continue; )
continue;
const gamepad = this.gamepads[index]; const gamepad = this.gamepads[index];
if (!gamepad) continue; if (!gamepad) continue;
// check the parameters for a selected gamepad // check the parameters for a selected gamepad
const gamepadId = this.getUrlParam('gamepad'); const gamepadId = this.getUrlParam("gamepad");
if (gamepadId === gamepad.id) { if (gamepadId === gamepad.id) {
this.map(gamepad.index); this.map(gamepad.index);
return; return;
@ -658,7 +683,7 @@ class Gamepad {
strongMagnitude: 0.2, strongMagnitude: 0.2,
weakMagnitude: 1, weakMagnitude: 1,
startDelay: 0, startDelay: 0,
} },
); );
} }
@ -675,7 +700,7 @@ class Gamepad {
*/ */
map(index) { map(index) {
// ensure a gamepad need to be mapped // ensure a gamepad need to be mapped
if ('undefined' === typeof index) return; if ("undefined" === typeof index) return;
// hide the help messages // hide the help messages
this.hideInstructions(true); this.hideInstructions(true);
@ -684,7 +709,7 @@ class Gamepad {
// update local references // update local references
this.index = index; this.index = index;
this.disconnectedIndex = null; this.disconnectedIndex = null;
this.$gamepad.classList.remove('disconnected'); this.$gamepad.classList.remove("disconnected");
const gamepad = this.getActive(); const gamepad = this.getActive();
// ensure that a gamepad was actually found for this index // ensure that a gamepad was actually found for this index
@ -722,8 +747,12 @@ class Gamepad {
*/ */
async toHash(value) { async toHash(value) {
return crypto.subtle return crypto.subtle
.digest('SHA-1', new TextEncoder().encode(value)) .digest("SHA-1", new TextEncoder().encode(value))
.then(ab => encodeURIComponent(String.fromCharCode.apply(null, new Uint8Array(ab)))); .then((ab) =>
encodeURIComponent(
String.fromCharCode.apply(null, new Uint8Array(ab)),
),
);
} }
/** /**
@ -746,9 +775,9 @@ class Gamepad {
this.colorIndex = null; this.colorIndex = null;
this.colorName = null; this.colorName = null;
this.zoomLevel = 1; this.zoomLevel = 1;
this.$gamepad.innerHTML = ''; this.$gamepad.innerHTML = "";
this.$gamepad.classList.remove('fadeIn'); this.$gamepad.classList.remove("fadeIn");
this.$gamepadSelect.value = 'auto'; this.$gamepadSelect.value = "auto";
this.updateColors(); this.updateColors();
this.updateTriggers(); this.updateTriggers();
this.clearUrlParams(); this.clearUrlParams();
@ -758,12 +787,12 @@ class Gamepad {
* Loads the template script and stylesheet * Loads the template script and stylesheet
*/ */
loadTemplateAssets() { loadTemplateAssets() {
const link = document.createElement('link'); const link = document.createElement("link");
link.rel = 'stylesheet'; link.rel = "stylesheet";
link.href = `templates/${this.type}/template.css`; link.href = `templates/${this.type}/template.css`;
this.$gamepad.appendChild(link); this.$gamepad.appendChild(link);
const script = document.createElement('script'); const script = document.createElement("script");
script.async = true; script.async = true;
script.src = `templates/${this.type}/template.js`; script.src = `templates/${this.type}/template.js`;
script.onload = () => { script.onload = () => {
@ -772,7 +801,7 @@ class Gamepad {
// enqueue the initial display refresh // enqueue the initial display refresh
this.startTemplate(); this.startTemplate();
} };
this.$gamepad.appendChild(script); this.$gamepad.appendChild(script);
} }
@ -781,7 +810,7 @@ class Gamepad {
*/ */
loadTemplate() { loadTemplate() {
// hide the gamepad while we prepare it // hide the gamepad while we prepare it
this.$gamepad.style.setProperty('display', 'none'); this.$gamepad.style.setProperty("display", "none");
fetch(`templates/${this.type}/template.html`) fetch(`templates/${this.type}/template.html`)
.then((response) => response.text()) .then((response) => response.text())
@ -794,13 +823,15 @@ class Gamepad {
const identifier = this.identifiers[this.type]; const identifier = this.identifiers[this.type];
// - color // - color
if (identifier.colors) { if (identifier.colors) {
this.changeGamepadColor(this.getUrlParam('color')); this.changeGamepadColor(this.getUrlParam("color"));
} else { } else {
this.updateUrlParams({ color: undefined }); this.updateUrlParams({ color: undefined });
} }
// - triggers mode // - triggers mode
if (identifier.triggers) { if (identifier.triggers) {
this.toggleTriggers(this.getUrlParam('triggers') === 'meter'); this.toggleTriggers(
this.getUrlParam("triggers") === "meter",
);
} else { } else {
this.updateUrlParams({ triggers: undefined }); this.updateUrlParams({ triggers: undefined });
} }
@ -808,19 +839,19 @@ class Gamepad {
if (identifier.zoom) { if (identifier.zoom) {
window.setTimeout(() => window.setTimeout(() =>
this.changeZoom( this.changeZoom(
this.type === 'debug' this.type === "debug"
? 'auto' ? "auto"
: this.getUrlParam('zoom') || 'auto' : this.getUrlParam("zoom") || "auto",
) ),
); );
} else { } else {
this.updateUrlParams({ zoom: undefined }); this.updateUrlParams({ zoom: undefined });
} }
// once fully loaded, display the gamepad // once fully loaded, display the gamepad
this.$gamepad.style.removeProperty('display'); this.$gamepad.style.removeProperty("display");
this.$gamepad.classList.remove('fadeOut'); this.$gamepad.classList.remove("fadeOut");
this.$gamepad.classList.add('fadeIn'); this.$gamepad.classList.add("fadeIn");
}); });
} }
@ -834,14 +865,23 @@ class Gamepad {
// save the buttons mapping of this template // save the buttons mapping of this template
this.mapping.buttons = activeGamepad.buttons.map((_, index) => { this.mapping.buttons = activeGamepad.buttons.map((_, index) => {
const $button = document.querySelector(`[data-button='${index}']`); const $button = document.querySelector(`[data-button='${index}']`);
return { $button, button: { pressed: null, touched: null, value: null } }; return {
$button,
button: { pressed: null, touched: null, value: null },
};
}); });
// save the axes mapping of this template // save the axes mapping of this template
this.mapping.axes = activeGamepad.axes.map((_, index) => { this.mapping.axes = activeGamepad.axes.map((_, index) => {
const { $axis, attribute } = ['data-axis', 'data-axis-x', 'data-axis-y'].reduce((acc, attribute) => { const { $axis, attribute } = [
"data-axis",
"data-axis-x",
"data-axis-y",
].reduce((acc, attribute) => {
if (acc.$axis) return acc; if (acc.$axis) return acc;
const $axis = document.querySelector(`[${attribute}='${index}']`); const $axis = document.querySelector(
`[${attribute}='${index}']`,
);
return $axis ? { $axis, attribute, axis: null } : acc; return $axis ? { $axis, attribute, axis: null } : acc;
}, {}); }, {});
return { $axis, attribute }; return { $axis, attribute };
@ -870,7 +910,8 @@ class Gamepad {
*/ */
pollStatus(force = false) { pollStatus(force = false) {
// ensure that a gamepad is currently active // ensure that a gamepad is currently active
if (this.index === null || this.index === this.disconnectedIndex) return; if (this.index === null || this.index === this.disconnectedIndex)
return;
// enqueue the next refresh // enqueue the next refresh
window.requestAnimationFrame(this.pollStatus.bind(this)); window.requestAnimationFrame(this.pollStatus.bind(this));
@ -907,12 +948,13 @@ class Gamepad {
updatedButton.touched !== button.touched || updatedButton.touched !== button.touched ||
updatedButton.value !== button.value updatedButton.value !== button.value
) { ) {
$button.setAttribute('data-pressed', updatedButton.pressed); $button.setAttribute("data-pressed", updatedButton.pressed);
$button.setAttribute('data-touched', updatedButton.touched); $button.setAttribute("data-touched", updatedButton.touched);
$button.setAttribute('data-value', updatedButton.value); $button.setAttribute("data-value", updatedButton.value);
// ensure we have a button updater callback and hook the template defined button update method // ensure we have a button updater callback and hook the template defined button update method
if ('function' === typeof this.updateButton) this.updateButton($button, updatedButton); if ("function" === typeof this.updateButton)
this.updateButton($button, updatedButton);
} }
// save the updated button // save the updated button
@ -935,10 +977,14 @@ class Gamepad {
// update the display value // update the display value
if (updatedAxis !== axis) { if (updatedAxis !== axis) {
$axis.setAttribute(attribute.replace('-axis', '-value'), updatedAxis); $axis.setAttribute(
attribute.replace("-axis", "-value"),
updatedAxis,
);
// ensure we have an axis updater callback and hook the template defined axis update method // ensure we have an axis updater callback and hook the template defined axis update method
if ('function' === typeof this.updateAxis) this.updateAxis($axis, attribute, updatedAxis); if ("function" === typeof this.updateAxis)
this.updateAxis($axis, attribute, updatedAxis);
} }
// save the updated button // save the updated button
@ -953,10 +999,10 @@ class Gamepad {
*/ */
changeGamepad(id) { changeGamepad(id) {
// get the index corresponding to the identifier of the gamepad // get the index corresponding to the identifier of the gamepad
const index = this.gamepads.findIndex(g => g && id === g.id); const index = this.gamepads.findIndex((g) => g && id === g.id);
// set the selected gamepad // set the selected gamepad
this.updateUrlParams({ gamepad: id !== 'auto' ? id : undefined }); this.updateUrlParams({ gamepad: id !== "auto" ? id : undefined });
index === -1 ? this.clear() : this.map(index); index === -1 ? this.clear() : this.map(index);
} }
@ -973,8 +1019,8 @@ class Gamepad {
this.$skinSelect.value = skin; this.$skinSelect.value = skin;
// set the selected skin // set the selected skin
this.debug = skin === 'debug'; this.debug = skin === "debug";
this.updateUrlParams({ type: skin !== 'auto' ? skin : undefined }); this.updateUrlParams({ type: skin !== "auto" ? skin : undefined });
this.map(this.index); this.map(this.index);
} }
@ -984,20 +1030,23 @@ class Gamepad {
* @param {string|number|undefined} indexOrName * @param {string|number|undefined} indexOrName
*/ */
changeBackground(indexOrName) { changeBackground(indexOrName) {
if ('undefined' === typeof indexOrName) { if ("undefined" === typeof indexOrName) {
this.backgroundIndex++; this.backgroundIndex++;
if (this.backgroundIndex > this.backgrounds.length - 1) { if (this.backgroundIndex > this.backgrounds.length - 1) {
this.backgroundIndex = 0; this.backgroundIndex = 0;
} }
} else if ('string' === typeof indexOrName) { } else if ("string" === typeof indexOrName) {
this.backgroundIndex = this.backgrounds.findIndex(({ name }) => name === indexOrName); this.backgroundIndex = this.backgrounds.findIndex(
({ name }) => name === indexOrName,
);
} else { } else {
this.backgroundIndex = indexOrName; this.backgroundIndex = indexOrName;
} }
const { name, backgroundColor, textColor } = this.backgrounds[this.backgroundIndex]; const { name, backgroundColor, textColor } =
this.backgrounds[this.backgroundIndex];
this.$body.style.setProperty('background', backgroundColor); this.$body.style.setProperty("background", backgroundColor);
this.$body.style.setProperty('color', textColor); this.$body.style.setProperty("color", textColor);
// update current settings // update current settings
this.updateUrlParams({ background: name }); this.updateUrlParams({ background: name });
@ -1013,15 +1062,15 @@ class Gamepad {
// ensure that a gamepad is currently active // ensure that a gamepad is currently active
if (this.index === null) return; if (this.index === null) return;
if ('undefined' === typeof color) { if ("undefined" === typeof color) {
// no color was specified, load the next one in list // no color was specified, load the next one in list
this.colorIndex++; this.colorIndex++;
if (this.colorIndex > this.identifier.colors.length - 1) { if (this.colorIndex > this.identifier.colors.length - 1) {
this.colorIndex = 0; this.colorIndex = 0;
} }
} else if ('string' === typeof style) { } else if ("string" === typeof style) {
this.colorIndex = this.identifier.colors.findIndex( this.colorIndex = this.identifier.colors.findIndex(
(c) => c === color (c) => c === color,
); );
} else { } else {
if (!Number.isNaN(Number.parseInt(color))) { if (!Number.isNaN(Number.parseInt(color))) {
@ -1043,7 +1092,7 @@ class Gamepad {
: null; : null;
// update the DOM with the color value // update the DOM with the color value
this.$gamepad.setAttribute('data-color', this.colorName); this.$gamepad.setAttribute("data-color", this.colorName);
// update current settings // update current settings
this.updateUrlParams({ color: this.colorName }); this.updateUrlParams({ color: this.colorName });
@ -1060,25 +1109,25 @@ class Gamepad {
if (this.index === null) return; if (this.index === null) return;
// ensure we have some data to process // ensure we have some data to process
if (typeof level === 'undefined') return; if (typeof level === "undefined") return;
this.zoomMode = level === 'auto' ? 'auto' : 'manual'; this.zoomMode = level === "auto" ? "auto" : "manual";
if (this.zoomMode === 'auto') { if (this.zoomMode === "auto") {
// 'auto' means a 'contained in window' zoom, with a max zoom of 1 // 'auto' means a 'contained in window' zoom, with a max zoom of 1
const { width, height } = this.$gamepad.getBoundingClientRect(); const { width, height } = this.$gamepad.getBoundingClientRect();
this.zoomLevel = Math.min( this.zoomLevel = Math.min(
window.innerWidth / width, window.innerWidth / width,
window.innerHeight / height, window.innerHeight / height,
1 1,
); );
} else if (level === 0) { } else if (level === 0) {
// 0 means a zoom reset // 0 means a zoom reset
this.zoomLevel = 1; this.zoomLevel = 1;
} else if (level === '+' && this.zoomLevel < 4) { } else if (level === "+" && this.zoomLevel < 4) {
// '+' means a zoom in if we still can // '+' means a zoom in if we still can
this.zoomLevel += 0.1; this.zoomLevel += 0.1;
} else if (level === '-' && this.zoomLevel > 0.1) { } else if (level === "-" && this.zoomLevel > 0.1) {
// '-' means a zoom out if we still can // '-' means a zoom out if we still can
this.zoomLevel -= 0.1; this.zoomLevel -= 0.1;
} else if (!Number.isNaN(+level)) { } else if (!Number.isNaN(+level)) {
@ -1091,13 +1140,13 @@ class Gamepad {
// update the DOM with the zoom value // update the DOM with the zoom value
this.$gamepad.style.setProperty( this.$gamepad.style.setProperty(
'transform', "transform",
`scale(${this.zoomLevel}, ${this.zoomLevel})` `scale(${this.zoomLevel}, ${this.zoomLevel})`,
); );
// update current settings // update current settings
this.updateUrlParams({ this.updateUrlParams({
zoom: this.zoomMode === 'auto' ? undefined : this.zoomLevel, zoom: this.zoomMode === "auto" ? undefined : this.zoomLevel,
}); });
} }
@ -1113,7 +1162,7 @@ class Gamepad {
// compute next type // compute next type
const types = Object.keys(this.identifiers).filter( const types = Object.keys(this.identifiers).filter(
(i) => i !== 'debug' (i) => i !== "debug",
); );
let typeIndex = types.reduce((typeIndex, type, index) => { let typeIndex = types.reduce((typeIndex, type, index) => {
return type === this.type ? index : typeIndex; return type === this.type ? index : typeIndex;
@ -1140,7 +1189,7 @@ class Gamepad {
this.debug = debug !== undefined ? debug : !this.debug; this.debug = debug !== undefined ? debug : !this.debug;
// update current settings // update current settings
this.changeSkin(this.debug ? 'debug' : 'auto') this.changeSkin(this.debug ? "debug" : "auto");
} }
/** /**
@ -1148,8 +1197,8 @@ class Gamepad {
*/ */
toggleHelp() { toggleHelp() {
// display the help popout // display the help popout
this.$helpPopout.classList.toggle('active'); this.$helpPopout.classList.toggle("active");
this.helpVisible = this.$helpPopout.classList.contains('active'); this.helpVisible = this.$helpPopout.classList.contains("active");
} }
/** /**
@ -1162,9 +1211,12 @@ class Gamepad {
if (this.index === null) return; if (this.index === null) return;
// update current settings // update current settings
this.useMeterTriggers = useMeter !== undefined ? useMeter : !this.useMeterTriggers; this.useMeterTriggers =
this.$gamepad.classList[this.useMeterTriggers ? 'add' : 'remove']('triggers-meter'); useMeter !== undefined ? useMeter : !this.useMeterTriggers;
const triggers = this.useMeterTriggers ? 'meter' : 'opacity'; this.$gamepad.classList[this.useMeterTriggers ? "add" : "remove"](
"triggers-meter",
);
const triggers = this.useMeterTriggers ? "meter" : "opacity";
this.updateUrlParams({ triggers }); this.updateUrlParams({ triggers });
this.$triggersSelect.value = triggers; this.$triggersSelect.value = triggers;
} }
@ -1177,7 +1229,7 @@ class Gamepad {
*/ */
getUrlParam(name) { getUrlParam(name) {
const matches = new RegExp(`[?&]${name}(=([^&#]*))?`).exec( const matches = new RegExp(`[?&]${name}(=([^&#]*))?`).exec(
window.location.search window.location.search,
); );
return matches ? decodeURIComponent(matches[2] || true) || true : null; return matches ? decodeURIComponent(matches[2] || true) || true : null;
} }
@ -1189,14 +1241,14 @@ class Gamepad {
*/ */
getUrlParams() { getUrlParams() {
const settingsArr = window.location.search const settingsArr = window.location.search
.replace('?', '') .replace("?", "")
.split('&') .split("&")
.map((param) => param.split('=')); .map((param) => param.split("="));
const settings = {}; const settings = {};
for (const key of Object.keys(settingsArr)) { for (const key of Object.keys(settingsArr)) {
const [k, v] = settingsArr[key]; const [k, v] = settingsArr[key];
settings[k] = v; settings[k] = v;
}; }
return settings; return settings;
} }
@ -1224,7 +1276,7 @@ class Gamepad {
const query = Object.entries(params) const query = Object.entries(params)
.filter(([, value]) => value !== undefined && value !== null) .filter(([, value]) => value !== undefined && value !== null)
.map(([key, value]) => `${key}=${value}`) .map(([key, value]) => `${key}=${value}`)
.join('&'); .join("&");
window.history.replaceState({}, document.title, `/?${query}`); window.history.replaceState({}, document.title, `/?${query}`);
} }
} }

1
obs-commandline.txt Normal file
View File

@ -0,0 +1 @@
--disable-features=EnableWindowsGamingInputDataFetcher

View File

@ -5,8 +5,10 @@ window.gamepad.TemplateClass = class DebugTemplate {
constructor() { constructor() {
this.gamepad = window.gamepad; this.gamepad = window.gamepad;
this.init(); this.init();
this.gamepad.updateButton = ($button, { value }) => this.updateElem($button, value); this.gamepad.updateButton = ($button, { value }) =>
this.gamepad.updateAxis = ($axis, _, axis) => this.updateElem($axis, axis, 6); this.updateElem($button, value);
this.gamepad.updateAxis = ($axis, _, axis) =>
this.updateElem($axis, axis, 6);
} }
/** /**
@ -21,35 +23,37 @@ window.gamepad.TemplateClass = class DebugTemplate {
* Initializes the template * Initializes the template
*/ */
init() { init() {
this.$name = document.querySelector('#info-name .value'); this.$name = document.querySelector("#info-name .value");
this.$vendor = document.querySelector('#info-vendor'); this.$vendor = document.querySelector("#info-vendor");
this.$product = document.querySelector('#info-product'); this.$product = document.querySelector("#info-product");
this.$id = document.querySelector('#info-id'); this.$id = document.querySelector("#info-id");
this.$timestamp = document.querySelector('#info-timestamp .value'); this.$timestamp = document.querySelector("#info-timestamp .value");
this.$index = document.querySelector('#info-index .value'); this.$index = document.querySelector("#info-index .value");
this.$mapping = document.querySelector('#info-mapping .value'); this.$mapping = document.querySelector("#info-mapping .value");
this.$rumble = document.querySelector('#info-rumble .value'); this.$rumble = document.querySelector("#info-rumble .value");
this.$axes = document.querySelector('.axes .container'); this.$axes = document.querySelector(".axes .container");
this.$buttons = document.querySelector('.buttons .container'); this.$buttons = document.querySelector(".buttons .container");
const activeGamepad = this.gamepad.getActive(); const activeGamepad = this.gamepad.getActive();
const { name, vendor, product, id } = this.gamepad.toGamepadInfo(activeGamepad.id); const { name, vendor, product, id } = this.gamepad.toGamepadInfo(
activeGamepad.id,
);
this.$name.innerHTML = name; this.$name.innerHTML = name;
this.$name.setAttribute('title', activeGamepad.id); this.$name.setAttribute("title", activeGamepad.id);
if (vendor && product) { if (vendor && product) {
this.$vendor.querySelector('.value').innerHTML = vendor; this.$vendor.querySelector(".value").innerHTML = vendor;
this.$product.querySelector('.value').innerHTML = product; this.$product.querySelector(".value").innerHTML = product;
this.$vendor.style.setProperty('display', 'block'); this.$vendor.style.setProperty("display", "block");
this.$product.style.setProperty('display', 'block'); this.$product.style.setProperty("display", "block");
} else { } else {
this.$id.querySelector('.value').innerHTML = id; this.$id.querySelector(".value").innerHTML = id;
this.$id.style.setProperty('display', 'block'); this.$id.style.setProperty("display", "block");
} }
this.updateTimestamp(); this.updateTimestamp();
this.$index.innerHTML = this.activeGamepad.index; this.$index.innerHTML = this.activeGamepad.index;
this.$mapping.innerHTML = this.activeGamepad.mapping || 'N/A'; this.$mapping.innerHTML = this.activeGamepad.mapping || "N/A";
this.$rumble.innerHTML = this.activeGamepad.vibrationActuator this.$rumble.innerHTML = this.activeGamepad.vibrationActuator
? this.activeGamepad.vibrationActuator.type ? this.activeGamepad.vibrationActuator.type
: 'N/A'; : "N/A";
this.initAxes(); this.initAxes();
this.initButtons(); this.initButtons();
} }
@ -104,7 +108,7 @@ window.gamepad.TemplateClass = class DebugTemplate {
this.updateTimestamp(); this.updateTimestamp();
$elem.innerHTML = value.toFixed(precision); $elem.innerHTML = value.toFixed(precision);
const color = Math.floor(255 * 0.3 + 255 * 0.7 * Math.abs(value)); const color = Math.floor(255 * 0.3 + 255 * 0.7 * Math.abs(value));
$elem.style.setProperty('color', `rgb(${color}, ${color}, ${color})`); $elem.style.setProperty("color", `rgb(${color}, ${color}, ${color})`);
} }
/** /**
@ -115,6 +119,8 @@ window.gamepad.TemplateClass = class DebugTemplate {
if (!this.activeGamepad) { if (!this.activeGamepad) {
return; return;
} }
this.$timestamp.innerHTML = Number.parseFloat(this.activeGamepad.timestamp).toFixed(3); this.$timestamp.innerHTML = Number.parseFloat(
this.activeGamepad.timestamp,
).toFixed(3);
} }
}; };

View File

@ -23,8 +23,18 @@
<span class="button y" data-button="3"></span> <span class="button y" data-button="3"></span>
</div> </div>
<div class="sticks"> <div class="sticks">
<span class="stick left" data-button="10" data-axis-x="0" data-axis-y="1"></span> <span
<span class="stick right" data-button="11" data-axis-x="2" data-axis-y="3"></span> class="stick left"
data-button="10"
data-axis-x="0"
data-axis-y="1"
></span>
<span
class="stick right"
data-button="11"
data-axis-x="2"
data-axis-y="3"
></span>
</div> </div>
<div class="dpad"> <div class="dpad">
<span class="face up" data-button="12"></span> <span class="face up" data-button="12"></span>

View File

@ -19,26 +19,41 @@ window.gamepad.TemplateClass = class DualShock4Template {
} }
updateButton($button, button) { updateButton($button, button) {
if (!$button.matches('.trigger') || !button) return; if (!$button.matches(".trigger") || !button) return;
$button.style.setProperty('opacity', this.gamepad.useMeterTriggers ? 1 : `${button.value * 100}%`); $button.style.setProperty(
$button.style.setProperty('clip-path', this.gamepad.useMeterTriggers ? `inset(${100 - button.value * 100}% 0px 0px 0pc)` : 'none'); "opacity",
this.gamepad.useMeterTriggers ? 1 : `${button.value * 100}%`,
);
$button.style.setProperty(
"clip-path",
this.gamepad.useMeterTriggers
? `inset(${100 - button.value * 100}% 0px 0px 0pc)`
: "none",
);
} }
updateAxis($axis, attribute, axis) { updateAxis($axis, attribute, axis) {
if (!$axis.matches('.stick')) return; if (!$axis.matches(".stick")) return;
if (attribute === 'data-axis-x') { if (attribute === "data-axis-x") {
$axis.style.setProperty('margin-left', `${axis * 25}px`); $axis.style.setProperty("margin-left", `${axis * 25}px`);
this.updateRotate($axis); this.updateRotate($axis);
} }
if (attribute === 'data-axis-y') { if (attribute === "data-axis-y") {
$axis.style.setProperty('margin-top', `${axis * 25}px`); $axis.style.setProperty("margin-top", `${axis * 25}px`);
this.updateRotate($axis); this.updateRotate($axis);
} }
} }
updateRotate($axis) { updateRotate($axis) {
const rotateX = Number.parseFloat($axis.getAttribute('data-value-y') * 30); const rotateX = Number.parseFloat(
const rotateY = -Number.parseFloat($axis.getAttribute('data-value-x') * 30); $axis.getAttribute("data-value-y") * 30,
$axis.style.setProperty('transform', `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`); );
const rotateY = -Number.parseFloat(
$axis.getAttribute("data-value-x") * 30,
);
$axis.style.setProperty(
"transform",
`rotateX(${rotateX}deg) rotateY(${rotateY}deg)`,
);
} }
}; };

View File

@ -23,8 +23,18 @@
<span class="button y" data-button="3"></span> <span class="button y" data-button="3"></span>
</div> </div>
<div class="sticks"> <div class="sticks">
<span class="stick left" data-button="10" data-axis-x="0" data-axis-y="1"></span> <span
<span class="stick right" data-button="11" data-axis-x="2" data-axis-y="3"></span> class="stick left"
data-button="10"
data-axis-x="0"
data-axis-y="1"
></span>
<span
class="stick right"
data-button="11"
data-axis-x="2"
data-axis-y="3"
></span>
</div> </div>
<div class="dpad"> <div class="dpad">
<span class="face up" data-button="12"></span> <span class="face up" data-button="12"></span>

View File

@ -19,26 +19,41 @@ window.gamepad.TemplateClass = class DualSenseTemplate {
} }
updateButton($button, button) { updateButton($button, button) {
if (!$button.matches('.trigger') || !button) return; if (!$button.matches(".trigger") || !button) return;
$button.style.setProperty('opacity', this.gamepad.useMeterTriggers ? 1 : `${button.value * 100}%`); $button.style.setProperty(
$button.style.setProperty('clip-path', this.gamepad.useMeterTriggers ? `inset(${100 - button.value * 100}% 0px 0px 0pc)` : 'none'); "opacity",
this.gamepad.useMeterTriggers ? 1 : `${button.value * 100}%`,
);
$button.style.setProperty(
"clip-path",
this.gamepad.useMeterTriggers
? `inset(${100 - button.value * 100}% 0px 0px 0pc)`
: "none",
);
} }
updateAxis($axis, attribute, axis) { updateAxis($axis, attribute, axis) {
if (!$axis.matches('.stick')) return; if (!$axis.matches(".stick")) return;
if (attribute === 'data-axis-x') { if (attribute === "data-axis-x") {
$axis.style.setProperty('margin-left', `${axis * 25}px`); $axis.style.setProperty("margin-left", `${axis * 25}px`);
this.updateRotate($axis); this.updateRotate($axis);
} }
if (attribute === 'data-axis-y') { if (attribute === "data-axis-y") {
$axis.style.setProperty('margin-top', `${axis * 25}px`); $axis.style.setProperty("margin-top", `${axis * 25}px`);
this.updateRotate($axis); this.updateRotate($axis);
} }
} }
updateRotate($axis) { updateRotate($axis) {
const rotateX = Number.parseFloat($axis.getAttribute('data-value-y') * 30); const rotateX = Number.parseFloat(
const rotateY = -Number.parseFloat($axis.getAttribute('data-value-x') * 30); $axis.getAttribute("data-value-y") * 30,
$axis.style.setProperty('transform', `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`); );
const rotateY = -Number.parseFloat(
$axis.getAttribute("data-value-x") * 30,
);
$axis.style.setProperty(
"transform",
`rotateX(${rotateX}deg) rotateY(${rotateY}deg)`,
);
} }
}; };

View File

@ -11,9 +11,9 @@
--white-color: white; --white-color: white;
--main-bg-color: black; --main-bg-color: black;
--main-component-color: #333; --main-component-color: #333;
--clutch-color: #2D64B9; --clutch-color: #2d64b9;
--brake-color: #A52725; --brake-color: #a52725;
--throttle-color: #0CA818; --throttle-color: #0ca818;
} }
#gamepad #telemetry:has(#steering:not([style*="display: none"])) { #gamepad #telemetry:has(#steering:not([style*="display: none"])) {
@ -133,7 +133,6 @@
text-align: center; text-align: center;
} }
#wizard .wizard-options { #wizard .wizard-options {
display: flex; display: flex;
margin: auto; margin: auto;

View File

@ -3,7 +3,7 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
* Instanciates a new telemetry template * Instanciates a new telemetry template
*/ */
constructor() { constructor() {
this.AXES = ['clutch', 'brake', 'throttle', 'steering']; this.AXES = ["clutch", "brake", "throttle", "steering"];
this.gamepad = window.gamepad; this.gamepad = window.gamepad;
this.loadSelectors(); this.loadSelectors();
@ -33,7 +33,10 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
* @returns {number} * @returns {number}
*/ */
toPercentage(value, min, max) { toPercentage(value, min, max) {
return Math.max(0, Math.min(100, Math.round((value - min) * (100 / (max - min))))); return Math.max(
0,
Math.min(100, Math.round((value - min) * (100 / (max - min)))),
);
} }
/** /**
@ -46,7 +49,7 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
*/ */
toDegrees(value, min, max) { toDegrees(value, min, max) {
const percentage = this.toPercentage(value, min, max); const percentage = this.toPercentage(value, min, max);
return (this.angle) * (percentage - 50) / 100; return (this.angle * (percentage - 50)) / 100;
} }
/** /**
@ -59,10 +62,11 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
toAxisValue(gamepad, axis) { toAxisValue(gamepad, axis) {
const { type, index, min, max } = this[axis]; const { type, index, min, max } = this[axis];
if (!type || !index) return null; if (!type || !index) return null;
const value = type === 'button' const value =
type === "button"
? gamepad.buttons[index].value ? gamepad.buttons[index].value
: gamepad.axes[index]; : gamepad.axes[index];
return axis === 'steering' return axis === "steering"
? this.toDegrees(value, min, max) ? this.toDegrees(value, min, max)
: this.toPercentage(value, min, max); : this.toPercentage(value, min, max);
} }
@ -71,49 +75,53 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
* Loads the DOM selectors * Loads the DOM selectors
*/ */
loadSelectors() { loadSelectors() {
this.$telemetry = document.querySelector('#telemetry'); this.$telemetry = document.querySelector("#telemetry");
this.$chart = this.$telemetry.querySelector('#chart'); this.$chart = this.$telemetry.querySelector("#chart");
this.chartContext = this.$chart.getContext('2d'); this.chartContext = this.$chart.getContext("2d");
this.$meters = this.$telemetry.querySelector('#meters'); this.$meters = this.$telemetry.querySelector("#meters");
this.$clutch = this.$telemetry.querySelector('#clutch'); this.$clutch = this.$telemetry.querySelector("#clutch");
this.$clutchBar = this.$clutch.querySelector('.bar'); this.$clutchBar = this.$clutch.querySelector(".bar");
this.$clutchValue = this.$clutch.querySelector('.value'); this.$clutchValue = this.$clutch.querySelector(".value");
this.$brake = this.$telemetry.querySelector('#brake'); this.$brake = this.$telemetry.querySelector("#brake");
this.$brakeBar = this.$brake.querySelector('.bar'); this.$brakeBar = this.$brake.querySelector(".bar");
this.$brakeValue = this.$brake.querySelector('.value'); this.$brakeValue = this.$brake.querySelector(".value");
this.$throttle = this.$telemetry.querySelector('#throttle'); this.$throttle = this.$telemetry.querySelector("#throttle");
this.$throttleBar = this.$throttle.querySelector('.bar'); this.$throttleBar = this.$throttle.querySelector(".bar");
this.$throttleValue = this.$throttle.querySelector('.value'); this.$throttleValue = this.$throttle.querySelector(".value");
this.$steering = this.$telemetry.querySelector('#steering'); this.$steering = this.$telemetry.querySelector("#steering");
this.$steeringIndicator = this.$steering.querySelector('.indicator'); this.$steeringIndicator = this.$steering.querySelector(".indicator");
this.$wizard = document.querySelector('#wizard'); this.$wizard = document.querySelector("#wizard");
this.$wizardPreview = this.$wizard.querySelector('#wizard-preview'); this.$wizardPreview = this.$wizard.querySelector("#wizard-preview");
this.$wizardInstructions = this.$wizard.querySelector('#wizard-instructions'); this.$wizardInstructions = this.$wizard.querySelector(
"#wizard-instructions",
);
} }
/** /**
* Loads the params from the URL * Loads the params from the URL
*/ */
loadParams() { loadParams() {
this.withChart = gamepad.getUrlParam('chart') !== 'false'; this.withChart = gamepad.getUrlParam("chart") !== "false";
this.chartColors = { this.chartColors = {
clutch: '#2D64B9', clutch: "#2D64B9",
brake: '#A52725', brake: "#A52725",
throttle: '#0CA818', throttle: "#0CA818",
} };
this.historyLength = +(gamepad.getUrlParam('history') || 5000); this.historyLength = +(gamepad.getUrlParam("history") || 5000);
this.withMeters = gamepad.getUrlParam('meters') !== 'false'; this.withMeters = gamepad.getUrlParam("meters") !== "false";
this.withSteering = gamepad.getUrlParam('steeringIndex') !== null; this.withSteering = gamepad.getUrlParam("steeringIndex") !== null;
this.angle = gamepad.getUrlParam('angle') || 360; this.angle = gamepad.getUrlParam("angle") || 360;
this.frequency = gamepad.getUrlParam('fps') || 60; this.frequency = gamepad.getUrlParam("fps") || 60;
for (const axis of this.AXES) { for (const axis of this.AXES) {
this[axis] = { this[axis] = {
type: (gamepad.getUrlParam(`${axis}Type`) || 'axis').replace('axis', 'axe'), type: (gamepad.getUrlParam(`${axis}Type`) || "axis").replace(
"axis",
"axe",
),
index: gamepad.getUrlParam(`${axis}Index`), index: gamepad.getUrlParam(`${axis}Index`),
min: gamepad.getUrlParam(`${axis}Min`) || -1, min: gamepad.getUrlParam(`${axis}Min`) || -1,
max: gamepad.getUrlParam(`${axis}Max`) || 1, max: gamepad.getUrlParam(`${axis}Max`) || 1,
}; };
} }
} }
@ -131,7 +139,7 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
if (!this.steering.index) { if (!this.steering.index) {
this.$steering.remove(); this.$steering.remove();
} else { } else {
this.$telemetry.classList.add('with-steering'); this.$telemetry.classList.add("with-steering");
} }
} }
@ -160,9 +168,16 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
this.scaleChart(); this.scaleChart();
const now = Date.now(); const now = Date.now();
this.chartData = []; this.chartData = [];
for (let index = 0; index < this.historyLength / this.interval; index++) { for (
const timestamp = now - (this.historyLength - index * this.interval); let index = 0;
const data = demo ? this.getDemoData() : { clutch: 0, brake: 0, throttle: 0 }; index < this.historyLength / this.interval;
index++
) {
const timestamp =
now - (this.historyLength - index * this.interval);
const data = demo
? this.getDemoData()
: { clutch: 0, brake: 0, throttle: 0 };
this.chartData.push({ timestamp, ...data }); this.chartData.push({ timestamp, ...data });
} }
} }
@ -187,7 +202,9 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
if (!this.running) return; if (!this.running) return;
const gamepad = this.gamepad.getActive(); const gamepad = this.gamepad.getActive();
const [clutch, brake, throttle, steering] = this.AXES.map((axis) => this.toAxisValue(gamepad, axis)); const [clutch, brake, throttle, steering] = this.AXES.map((axis) =>
this.toAxisValue(gamepad, axis),
);
if (this.withChart) this.drawChart(clutch, brake, throttle); if (this.withChart) this.drawChart(clutch, brake, throttle);
if (this.withMeters) this.updateMeters(clutch, brake, throttle); if (this.withMeters) this.updateMeters(clutch, brake, throttle);
if (this.withSteering) this.updateSteering(steering); if (this.withSteering) this.updateSteering(steering);
@ -202,10 +219,38 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
* @returns {object} * @returns {object}
*/ */
getDemoData() { getDemoData() {
const clutch = this.withClutch ? Math.max(0, Math.round(Math.sin(this.demoIndex / (1000 / this.interval)) * 100)) : 0; const clutch = this.withClutch
const brake = this.withBrake ? Math.max(0, Math.round(Math.sin(this.demoIndex / (1000 / this.interval) + 1.5) * 100)) : 0; ? Math.max(
const throttle = this.withThrottle ? Math.max(0, Math.round(Math.sin(this.demoIndex / (1000 / this.interval) + 3) * 100)) : 0; 0,
const steering = this.withSteering ? Math.round(Math.sin(this.demoIndex / (1000 / this.interval) + 3) * this.angle) : 0; Math.round(
Math.sin(this.demoIndex / (1000 / this.interval)) * 100,
),
)
: 0;
const brake = this.withBrake
? Math.max(
0,
Math.round(
Math.sin(this.demoIndex / (1000 / this.interval) + 1.5) *
100,
),
)
: 0;
const throttle = this.withThrottle
? Math.max(
0,
Math.round(
Math.sin(this.demoIndex / (1000 / this.interval) + 3) *
100,
),
)
: 0;
const steering = this.withSteering
? Math.round(
Math.sin(this.demoIndex / (1000 / this.interval) + 3) *
this.angle,
)
: 0;
if (this.demoIndex++ > 10000) this.demoIndex = 0; if (this.demoIndex++ > 10000) this.demoIndex = 0;
return { clutch, brake, throttle, steering }; return { clutch, brake, throttle, steering };
} }
@ -256,14 +301,17 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
chartContext.clearRect(0, 0, width, height); chartContext.clearRect(0, 0, width, height);
for (const axis of this.AXES) { for (const axis of this.AXES) {
if (axis === 'steering') continue; if (axis === "steering") continue;
chartContext.beginPath(); chartContext.beginPath();
for (let index = 0; index < this.chartData.length; index++) { for (let index = 0; index < this.chartData.length; index++) {
const entry = this.chartData[index]; const entry = this.chartData[index];
const x = (entry.timestamp + this.historyLength - now) / this.historyLength * width; const x =
const y = (101 - (entry[axis] || 0)) * height / 100; ((entry.timestamp + this.historyLength - now) /
chartContext[index === 0 ? 'moveTo' : 'lineTo'](x, y); this.historyLength) *
width;
const y = ((101 - (entry[axis] || 0)) * height) / 100;
chartContext[index === 0 ? "moveTo" : "lineTo"](x, y);
} }
chartContext.strokeStyle = this.chartColors[axis]; chartContext.strokeStyle = this.chartColors[axis];
@ -280,10 +328,15 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
* @param {number} steering * @param {number} steering
*/ */
updateMeters(clutch, brake, throttle) { updateMeters(clutch, brake, throttle) {
for (const [axis, value] of Object.entries({ clutch, brake, throttle })) { for (const [axis, value] of Object.entries({
clutch,
brake,
throttle,
})) {
if (value === null) continue; if (value === null) continue;
this[`$${axis}Value`].innerHTML = value; this[`$${axis}Value`].innerHTML = value;
this[`$${axis}Value`].style.opacity = `${Math.round(33 + (value / 1.5))}%`; this[`$${axis}Value`].style.opacity =
`${Math.round(33 + value / 1.5)}%`;
this[`$${axis}Bar`].style.height = `${value}%`; this[`$${axis}Bar`].style.height = `${value}%`;
} }
} }
@ -302,7 +355,9 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
*/ */
setupWizard() { setupWizard() {
this.interval = 1000 / 60; this.interval = 1000 / 60;
this.$wizardInstructions.querySelector('#wizard-preview').appendChild(this.$telemetry); this.$wizardInstructions
.querySelector("#wizard-preview")
.appendChild(this.$telemetry);
this.withChart = true; this.withChart = true;
this.withMeters = true; this.withMeters = true;
@ -316,84 +371,107 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
this.updateDemo(); this.updateDemo();
const $inputs = { const $inputs = {
$clutchOption: this.$wizardInstructions.querySelector('#clutch-option'), $clutchOption:
$brakeOption: this.$wizardInstructions.querySelector('#brake-option'), this.$wizardInstructions.querySelector("#clutch-option"),
$throttleOption: this.$wizardInstructions.querySelector('#throttle-option'), $brakeOption:
$steeringOption: this.$wizardInstructions.querySelector('#steering-option') this.$wizardInstructions.querySelector("#brake-option"),
$throttleOption:
this.$wizardInstructions.querySelector("#throttle-option"),
$steeringOption:
this.$wizardInstructions.querySelector("#steering-option"),
}; };
const $chartOption = this.$wizardInstructions.querySelector('#chart-option'); const $chartOption =
const $historyOption = this.$wizardInstructions.querySelector('#history-option'); this.$wizardInstructions.querySelector("#chart-option");
const $metersOption = this.$wizardInstructions.querySelector('#meters-option'); const $historyOption =
const $steeringAngleOption = this.$wizardInstructions.querySelector('#steering-angle-option'); this.$wizardInstructions.querySelector("#history-option");
const $fpsOption = this.$wizardInstructions.querySelector('[name=fps-option]'); const $metersOption =
this.$wizardInstructions.querySelector("#meters-option");
const $steeringAngleOption = this.$wizardInstructions.querySelector(
"#steering-angle-option",
);
const $fpsOption =
this.$wizardInstructions.querySelector("[name=fps-option]");
$inputs.$clutchOption.addEventListener('change', () => { $inputs.$clutchOption.addEventListener("change", () => {
this.withClutch = $inputs.$clutchOption.checked; this.withClutch = $inputs.$clutchOption.checked;
if (this.withClutch) { if (this.withClutch) {
this.$clutch.style.display = ''; this.$clutch.style.display = "";
} else { } else {
this.$clutch.style.display = 'none'; this.$clutch.style.display = "none";
for (const data of this.chartData) { data.clutch = 0; } for (const data of this.chartData) {
data.clutch = 0;
}
} }
}); });
$inputs.$brakeOption.addEventListener('change', () => { $inputs.$brakeOption.addEventListener("change", () => {
this.withBrake = $inputs.$brakeOption.checked; this.withBrake = $inputs.$brakeOption.checked;
if (this.withBrake) { if (this.withBrake) {
this.$brake.style.display = ''; this.$brake.style.display = "";
} else { } else {
this.$brake.style.display = 'none'; this.$brake.style.display = "none";
for (const data of this.chartData) { data.brake = 0; } for (const data of this.chartData) {
data.brake = 0;
}
} }
}); });
$inputs.$throttleOption.addEventListener('change', () => { $inputs.$throttleOption.addEventListener("change", () => {
this.withThrottle = $inputs.$throttleOption.checked; this.withThrottle = $inputs.$throttleOption.checked;
if (this.withThrottle) { if (this.withThrottle) {
this.$throttle.style.display = ''; this.$throttle.style.display = "";
} else { } else {
this.$throttle.style.display = 'none'; this.$throttle.style.display = "none";
for (const data of this.chartData) { data.throttle = 0; } for (const data of this.chartData) {
data.throttle = 0;
}
} }
}); });
$inputs.$steeringOption.addEventListener('change', () => { $inputs.$steeringOption.addEventListener("change", () => {
this.withSteering = $inputs.$steeringOption.checked; this.withSteering = $inputs.$steeringOption.checked;
if (this.withSteering) { if (this.withSteering) {
this.$steering.style.display = ''; this.$steering.style.display = "";
} else { } else {
this.$steering.style.display = 'none'; this.$steering.style.display = "none";
} }
}); });
$chartOption.addEventListener('change', () => { $chartOption.addEventListener("change", () => {
this.withChart = $chartOption.checked; this.withChart = $chartOption.checked;
if (this.withChart) { if (this.withChart) {
this.$chart.style.display = ''; this.$chart.style.display = "";
} else { } else {
this.$chart.style.display = 'none'; this.$chart.style.display = "none";
} }
this.setupChart(true); this.setupChart(true);
}); });
$historyOption.addEventListener('change', () => { $historyOption.addEventListener("change", () => {
this.historyLength = Number.parseInt($historyOption.value) * 1000; this.historyLength = Number.parseInt($historyOption.value) * 1000;
this.setupChart(true); this.setupChart(true);
}); });
$metersOption.addEventListener('change', () => { $metersOption.addEventListener("change", () => {
this.withMeters = $metersOption.checked; this.withMeters = $metersOption.checked;
if (this.withMeters) { if (this.withMeters) {
this.$meters.style.display = ''; this.$meters.style.display = "";
} else { } else {
this.$meters.style.display = 'none'; this.$meters.style.display = "none";
} }
this.setupChart(true); this.setupChart(true);
}); });
$steeringAngleOption.addEventListener('change', () => { $steeringAngleOption.addEventListener("change", () => {
this.angle = $steeringAngleOption.value; this.angle = $steeringAngleOption.value;
}); });
$fpsOption.addEventListener('change', () => { $fpsOption.addEventListener("change", () => {
this.interval = 1000 / Number.parseInt($fpsOption.value); this.interval = 1000 / Number.parseInt($fpsOption.value);
console.log(this.interval); console.log(this.interval);
this.setupChart(true); this.setupChart(true);
}); });
return { $inputs, $chartOption, $historyOption, $metersOption, $steeringAngleOption, $fpsOption }; return {
$inputs,
$chartOption,
$historyOption,
$metersOption,
$steeringAngleOption,
$fpsOption,
};
} }
/** /**
@ -406,7 +484,8 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
return new Promise((resolve) => { return new Promise((resolve) => {
const interval = window.setInterval(() => { const interval = window.setInterval(() => {
const gamepad = this.gamepad.getActive(); const gamepad = this.gamepad.getActive();
const pressedButton = index !== undefined const pressedButton =
index !== undefined
? gamepad.buttons[index].pressed ? gamepad.buttons[index].pressed
: gamepad.buttons.some((button) => button.pressed); : gamepad.buttons.some((button) => button.pressed);
if (pressedButton) return; if (pressedButton) return;
@ -425,7 +504,9 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
return new Promise((resolve) => { return new Promise((resolve) => {
const pressInterval = window.setInterval(() => { const pressInterval = window.setInterval(() => {
const gamepad = this.gamepad.getActive(); const gamepad = this.gamepad.getActive();
const index = gamepad.buttons.findIndex((button) => button.pressed); const index = gamepad.buttons.findIndex(
(button) => button.pressed,
);
if (index === -1) return; if (index === -1) return;
window.clearInterval(pressInterval); window.clearInterval(pressInterval);
const releaseInterval = window.setInterval(() => { const releaseInterval = window.setInterval(() => {
@ -457,7 +538,10 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
value = gamepad.axes[index]; value = gamepad.axes[index];
return; return;
} }
if (referenceValue !== undefined && Math.abs(gamepad.axes[index] - referenceValue) < 0.5) { if (
referenceValue !== undefined &&
Math.abs(gamepad.axes[index] - referenceValue) < 0.5
) {
return; return;
} }
if (Math.abs(gamepad.axes[index] - value) > 0.05) { if (Math.abs(gamepad.axes[index] - value) > 0.05) {
@ -556,21 +640,25 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
$historyOption, $historyOption,
$metersOption, $metersOption,
$steeringAngleOption, $steeringAngleOption,
$fpsOption $fpsOption,
} = this.setupWizard(); } = this.setupWizard();
await this.waitButtonRelease(); await this.waitButtonRelease();
await this.waitButtonClick(); await this.waitButtonClick();
return { return {
...this.AXES.reduce((options, axis) => Object.assign( ...this.AXES.reduce(
options, (options, axis) =>
{ [`with${this.capitalize(axis)}`]: $inputs[`$${axis}Option`].checked } Object.assign(options, {
), {}), [`with${this.capitalize(axis)}`]:
$inputs[`$${axis}Option`].checked,
}),
{},
),
chart: $chartOption.checked, chart: $chartOption.checked,
history: Number.parseInt($historyOption.value) * 1000, history: Number.parseInt($historyOption.value) * 1000,
meters: $metersOption.checked, meters: $metersOption.checked,
angle: $steeringAngleOption.value, angle: $steeringAngleOption.value,
fps: Number.parseInt($fpsOption.value) fps: Number.parseInt($fpsOption.value),
}; };
} }
@ -586,16 +674,25 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
return new Promise((resolve) => { return new Promise((resolve) => {
const interval = window.setInterval(async () => { const interval = window.setInterval(async () => {
const gamepad = this.gamepad.getActive(); const gamepad = this.gamepad.getActive();
const buttonIndex = ['button', undefined].includes(type) const buttonIndex = ["button", undefined].includes(type)
? gamepad.buttons.findIndex((button, index) => button.pressed && Math.abs(button.value - before.buttons[index].value) > distance) ? gamepad.buttons.findIndex(
(button, index) =>
button.pressed &&
Math.abs(
button.value - before.buttons[index].value,
) > distance,
)
: -1; : -1;
const axisIndex = ['axis', undefined].includes(type) const axisIndex = ["axis", undefined].includes(type)
? gamepad.axes.findIndex((axis, index) => Math.abs(axis - before.axes[index]) > distance) ? gamepad.axes.findIndex(
(axis, index) =>
Math.abs(axis - before.axes[index]) > distance,
)
: -1; : -1;
if (buttonIndex === -1 && axisIndex === -1) return; if (buttonIndex === -1 && axisIndex === -1) return;
window.clearInterval(interval); window.clearInterval(interval);
resolve({ resolve({
type: buttonIndex === -1 ? 'axis' : 'button', type: buttonIndex === -1 ? "axis" : "button",
index: buttonIndex === -1 ? axisIndex : buttonIndex, index: buttonIndex === -1 ? axisIndex : buttonIndex,
}); });
}, 100); }, 100);
@ -613,7 +710,7 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
<p>Waiting for <strong>${name}</strong> activity.</p> <p>Waiting for <strong>${name}</strong> activity.</p>
`; `;
const { type, index } = await this.detectActivity(); const { type, index } = await this.detectActivity();
if (type === 'button') { if (type === "button") {
this.$wizardInstructions.innerHTML = ` this.$wizardInstructions.innerHTML = `
<p>Release the <strong>${name}</strong> button.</p> <p>Release the <strong>${name}</strong> button.</p>
`; `;
@ -643,7 +740,7 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
this.$wizardInstructions.innerHTML = ` this.$wizardInstructions.innerHTML = `
<p>Turn the <strong>steering</strong> axis all the way to the <strong>left</strong>.</p> <p>Turn the <strong>steering</strong> axis all the way to the <strong>left</strong>.</p>
`; `;
const { index } = await this.detectActivity('axis', 0.2); const { index } = await this.detectActivity("axis", 0.2);
const leftValue = await this.getAxisPush(index, 0); const leftValue = await this.getAxisPush(index, 0);
this.$wizardInstructions.innerHTML = ` this.$wizardInstructions.innerHTML = `
@ -664,7 +761,7 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
return Object.entries(options) return Object.entries(options)
.filter(([, value]) => value !== undefined && value !== null) .filter(([, value]) => value !== undefined && value !== null)
.map(([key, value]) => `${key}=${value}`) .map(([key, value]) => `${key}=${value}`)
.join('&'); .join("&");
} }
/** /**
@ -694,22 +791,35 @@ window.gamepad.TemplateClass = class TelemetryTemplate {
* @returns {Promise} * @returns {Promise}
*/ */
async wizard() { async wizard() {
this.$wizard.classList.add('active'); this.$wizard.classList.add("active");
const gamepad = this.gamepad.getActive(); const gamepad = this.gamepad.getActive();
const { withClutch, withBrake, withThrottle, withSteering, chart, history, meters, angle, fps } = await this.askForOptions(); const {
const clutch = withClutch && await this.calibratePedal('clutch'); withClutch,
const brake = withBrake && await this.calibratePedal('brake'); withBrake,
const throttle = withThrottle && await this.calibratePedal('throttle'); withThrottle,
const steering = withSteering && await this.calibrateSteering(); withSteering,
chart,
history,
meters,
angle,
fps,
} = await this.askForOptions();
const clutch = withClutch && (await this.calibratePedal("clutch"));
const brake = withBrake && (await this.calibratePedal("brake"));
const throttle =
withThrottle && (await this.calibratePedal("throttle"));
const steering = withSteering && (await this.calibrateSteering());
window.location.href = [ window.location.href = [
`?gamepad=${gamepad.id}&type=telemetry`, `?gamepad=${gamepad.id}&type=telemetry`,
this.toOptionsParams({ chart, history, meters, angle, fps }), this.toOptionsParams({ chart, history, meters, angle, fps }),
withClutch ? this.toPedalParams('clutch', clutch) : null, withClutch ? this.toPedalParams("clutch", clutch) : null,
withBrake ? this.toPedalParams('brake', brake) : null, withBrake ? this.toPedalParams("brake", brake) : null,
withThrottle ? this.toPedalParams('throttle', throttle) : null, withThrottle ? this.toPedalParams("throttle", throttle) : null,
withSteering ? this.toSteeringParams(steering) : null withSteering ? this.toSteeringParams(steering) : null,
].filter(e => e !== null).join('&'); ]
.filter((e) => e !== null)
.join("&");
} }
}; };

View File

@ -22,8 +22,18 @@
<span class="button y" data-button="3"></span> <span class="button y" data-button="3"></span>
</div> </div>
<div class="sticks"> <div class="sticks">
<span class="stick left" data-button="10" data-axis-x="0" data-axis-y="1"></span> <span
<span class="stick right" data-button="11" data-axis-x="2" data-axis-y="3"></span> class="stick left"
data-button="10"
data-axis-x="0"
data-axis-y="1"
></span>
<span
class="stick right"
data-button="11"
data-axis-x="2"
data-axis-y="3"
></span>
</div> </div>
<div class="dpad"> <div class="dpad">
<span class="face up" data-button="12"></span> <span class="face up" data-button="12"></span>

View File

@ -17,26 +17,41 @@ window.gamepad.TemplateClass = class XboxOneTemplate {
} }
updateButton($button, button) { updateButton($button, button) {
if (!$button.matches('.trigger') || !button) return; if (!$button.matches(".trigger") || !button) return;
$button.style.setProperty('opacity', this.gamepad.useMeterTriggers ? 1 : `${button.value * 100}%`); $button.style.setProperty(
$button.style.setProperty('clip-path', this.gamepad.useMeterTriggers ? `inset(${100 - button.value * 100}% 0px 0px 0pc)` : 'none'); "opacity",
this.gamepad.useMeterTriggers ? 1 : `${button.value * 100}%`,
);
$button.style.setProperty(
"clip-path",
this.gamepad.useMeterTriggers
? `inset(${100 - button.value * 100}% 0px 0px 0pc)`
: "none",
);
} }
updateAxis($axis, attribute, axis) { updateAxis($axis, attribute, axis) {
if (!$axis.matches('.stick')) return; if (!$axis.matches(".stick")) return;
if (attribute === 'data-axis-x') { if (attribute === "data-axis-x") {
$axis.style.setProperty('margin-left', `${axis * 25}px`); $axis.style.setProperty("margin-left", `${axis * 25}px`);
this.updateRotate($axis); this.updateRotate($axis);
} }
if (attribute === 'data-axis-y') { if (attribute === "data-axis-y") {
$axis.style.setProperty('margin-top', `${axis * 25}px`); $axis.style.setProperty("margin-top", `${axis * 25}px`);
this.updateRotate($axis); this.updateRotate($axis);
} }
} }
updateRotate($axis) { updateRotate($axis) {
const rotateX = Number.parseFloat($axis.getAttribute('data-value-y') * 30); const rotateX = Number.parseFloat(
const rotateY = -Number.parseFloat($axis.getAttribute('data-value-x') * 30); $axis.getAttribute("data-value-y") * 30,
$axis.style.setProperty('transform', `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`); );
const rotateY = -Number.parseFloat(
$axis.getAttribute("data-value-x") * 30,
);
$axis.style.setProperty(
"transform",
`rotateX(${rotateX}deg) rotateY(${rotateY}deg)`,
);
} }
}; };