mirror of
https://github.com/SrIzan10/vdo.ninja.git
synced 2026-05-01 11:05:24 +00:00
696 lines
25 KiB
HTML
696 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Controller Visualizer</title>
|
|
<style>
|
|
body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
font-family: Arial, sans-serif;
|
|
background: #1a1a1a;
|
|
color: #fff;
|
|
}
|
|
#controls {
|
|
margin: 20px;
|
|
padding: 10px;
|
|
background: #333;
|
|
border-radius: 5px;
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
select, button {
|
|
padding: 5px;
|
|
background: #444;
|
|
color: #fff;
|
|
border: 1px solid #555;
|
|
border-radius: 3px;
|
|
}
|
|
button {
|
|
padding: 5px 10px;
|
|
background: #4CAF50;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
.tooltip {
|
|
position: relative;
|
|
display: inline-block;
|
|
}
|
|
|
|
.tooltip:hover::after {
|
|
content: attr(data-tooltip);
|
|
position: absolute;
|
|
bottom: 100%;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
padding: 5px 10px;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
color: white;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
white-space: nowrap;
|
|
z-index: 1000;
|
|
}
|
|
|
|
button:hover { background: #45a049; }
|
|
#gamepad { width: 600px; height: 400px; }
|
|
.button { transition: fill 0.1s ease; }
|
|
.button.pressed { fill: #4CAF50; }
|
|
.stick { transition: transform 0.1s ease; }
|
|
#deviceLog {
|
|
width: 80%;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
background: #333;
|
|
padding: 10px;
|
|
margin: 10px;
|
|
border-radius: 5px;
|
|
font-family: monospace;
|
|
}
|
|
.controller-xbox, .controller-ps { display: none; }
|
|
.controller-xbox.active, .controller-ps.active { display: block; }
|
|
#mappingSelect { margin-left: 10px; }
|
|
|
|
.device-section {
|
|
background: #444;
|
|
padding: 10px;
|
|
margin: 5px 0;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
.device-section h3 {
|
|
margin: 0 0 5px 0;
|
|
font-size: 14px;
|
|
color: #aaa;
|
|
}
|
|
|
|
.device-section.empty {
|
|
color: #888;
|
|
font-style: italic;
|
|
}
|
|
|
|
.device-option {
|
|
padding: 5px;
|
|
margin: 2px 0;
|
|
background: #555;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.device-option:hover {
|
|
background: #666;
|
|
}
|
|
|
|
.device-option.active {
|
|
background: #4CAF50;
|
|
}
|
|
|
|
.device-option {
|
|
position: relative;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.status-indicator {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
margin-left: 8px;
|
|
background: #666;
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
|
|
.status-indicator.connected {
|
|
background: #4CAF50;
|
|
}
|
|
|
|
.status-indicator.disconnected {
|
|
background: #f44336;
|
|
}
|
|
|
|
.device-info {
|
|
font-size: 12px;
|
|
color: #888;
|
|
margin-left: 8px;
|
|
}
|
|
|
|
.pulse {
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4); }
|
|
70% { box-shadow: 0 0 0 10px rgba(76, 175, 80, 0); }
|
|
100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); }
|
|
}
|
|
.empty small {
|
|
display: block;
|
|
margin-top: 4px;
|
|
color: #666;
|
|
}
|
|
|
|
.controller-xbox, .controller-playstation {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.3s ease;
|
|
position: absolute;
|
|
}
|
|
|
|
.controller-xbox.active, .controller-playstation.active {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="controls">
|
|
<div id="deviceList">
|
|
<div id="gamepadSection" class="device-section">
|
|
<h3>Game Controllers</h3>
|
|
<div id="gamepadDevices">
|
|
<div class="empty">
|
|
<div>No gamepads detected</div>
|
|
<small>Connect an Xbox, PlayStation, or other gamepad and press any button.</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="hidSection" class="device-section">
|
|
<h3>Other USB Devices</h3>
|
|
<div id="hidDevices">
|
|
<div class="empty">
|
|
<div>No other devices connected</div>
|
|
<small>Click "Connect Device" to add USB game controllers.</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<select id="mappingSelect" style="display: none;">
|
|
<option value="">Select controller type</option>
|
|
<option value="Xbox">Xbox Controller</option>
|
|
<option value="PlayStation">PlayStation Controller</option>
|
|
</select>
|
|
<button id="connectHID" class="tooltip" data-tooltip="Connect a USB game controller">Connect Device</button>
|
|
<button id="disconnectDevice" class="tooltip" data-tooltip="Disconnect selected device" style="display: none;">Disconnect</button>
|
|
</div>
|
|
<div id="deviceLog"></div>
|
|
<svg id="gamepad" viewBox="0 0 800 500">
|
|
<g class="controller-xbox active">
|
|
<path d="M150 100 Q 300 150, 400 100 T 650 100 C 730 100, 730 250, 730 300 C 730 400, 700 450, 650 450 C 600 450, 450 480, 400 480 C 350 480, 200 450, 150 450 C 100 450, 70 400, 70 300 C 70 250, 70 100, 150 100 Z" fill="#333" stroke="#444" stroke-width="2" class="draggable"/>
|
|
|
|
<path d="M 150 100 C 70 100, 40 130, 40 200 C 40 300, 70 350, 150 400" fill="#333" stroke="#222" stroke-width="3" class="draggable"/>
|
|
|
|
<path d="M 650 100 C 730 100, 760 130, 760 200 C 760 300, 730 350, 650 400" fill="#333" stroke="#222" stroke-width="3" class="draggable"/>
|
|
|
|
<g id="xbox-dpad" transform="translate(250,330)">
|
|
<path id="dpad-disc" d="M -50 -50 L -50 50 L 50 50 L 50 -50 Z" fill="none" class="draggable"/>
|
|
<path d="M -30 -10 L -10 -10 L -10 -30 L 10 -30 L 10 -10 L 30 -10 L 30 10 L 10 10 L 10 30 L -10 30 L -10 10 L -30 10 Z" fill="#222" stroke="#111" stroke-width="2" class="draggable"/>
|
|
<rect id="dpad-up" class="button draggable" x="-15" y="-45" width="30" height="20" rx="3" fill="none" stroke="none"/>
|
|
<rect id="dpad-down" class="button draggable" x="-15" y="25" width="30" height="20" rx="3" fill="none" stroke="none"/>
|
|
<rect id="dpad-left" class="button draggable" x="-45" y="-15" width="20" height="30" rx="3" fill="none" stroke="none"/>
|
|
<rect id="dpad-right" class="button draggable" x="25" y="-15" width="20" height="30" rx="3" fill="none" stroke="none"/>
|
|
</g>
|
|
|
|
<g id="xbox-face" transform="translate(550,240)">
|
|
<circle id="button-a" class="button draggable" cx="0" cy="60" r="25" fill="#0a0" stroke="#070" stroke-width="2"/>
|
|
<circle id="button-b" class="button draggable" cx="60" cy="0" r="25" fill="#a00" stroke="#700" stroke-width="2"/>
|
|
<circle id="button-x" class="button draggable" cx="-60" cy="0" r="25" fill="#00a" stroke="#007" stroke-width="2"/>
|
|
<circle id="button-y" class="button draggable" cx="0" cy="-60" r="25" fill="#aa0" stroke="#770" stroke-width="2"/>
|
|
<text x="0" y="67" fill="#fff" text-anchor="middle" font-size="22" font-weight="bold" font-family="Arial" class="draggable">A</text>
|
|
<text x="60" y="7" fill="#fff" text-anchor="middle" font-size="22" font-weight="bold" font-family="Arial" class="draggable">B</text>
|
|
<text x="-60" y="7" fill="#fff" text-anchor="middle" font-size="22" font-weight="bold" font-family="Arial" class="draggable">X</text>
|
|
<text x="0" y="-53" fill="#fff" text-anchor="middle" font-size="22" font-weight="bold" font-family="Arial" class="draggable">Y</text>
|
|
</g>
|
|
|
|
<circle cx="148.6431121826172" cy="231.15162658691406" r="40" fill="#222" stroke="#111" stroke-width="2" class="draggable"/>
|
|
<circle cx="441.5507507324219" cy="328.3466491699219" r="40" fill="#222" stroke="#111" stroke-width="2" class="draggable"/>
|
|
|
|
<g id="xbox-sticks">
|
|
<circle id="stick-left" class="stick draggable" cx="149.55532836914062" cy="232.06381225585938" r="30" fill="#666" stroke="#444" stroke-width="3"/>
|
|
<circle id="stick-right" class="stick draggable" cx="442.46295166015625" cy="329.25885009765625" r="30" fill="#666" stroke="#444" stroke-width="3"/>
|
|
</g>
|
|
|
|
<g id="xbox-shoulders">
|
|
<rect id="button-lb" class="button draggable" x="95.26795959472656" y="90.06841278076172" width="100" height="25" rx="5" ry="15" fill="#444" stroke="#222" stroke-width="2"/>
|
|
<rect id="button-rb" class="button draggable" x="571.892822265625" y="81.85861206054688" width="100" height="25" rx="5" ry="15" fill="#444" stroke="#222" stroke-width="2"/>
|
|
</g>
|
|
|
|
<circle cx="349.8289794921875" cy="183.11287689208984" r="25" fill="#107c10" stroke="#fff" stroke-width="3" class="draggable"/>
|
|
<text x="348.00457763671875" y="193.84947967529297" fill="#fff" text-anchor="middle" font-size="32" font-weight="bold" font-family="Arial" class="draggable">X</text>
|
|
</g>
|
|
<g class="controller-playstation">
|
|
<!-- PS Base -->
|
|
<path d="M150 100 C150 50, 650 50, 650 100 L650 400 C650 450, 150 450, 150 400 Z" fill="#333" stroke="#444" stroke-width="2" class="draggable"/>
|
|
|
|
<!-- D-pad -->
|
|
<g id="ps-dpad" transform="translate(230,220)">
|
|
<!-- Keep the existing paths but update positions to match Xbox layout -->
|
|
<path id="ps-dpad-up" class="button draggable" d="M-15 -45 L15 -45 L15 -25 L-15 -25 Z" fill="#666"/>
|
|
<path id="ps-dpad-right" class="button draggable" d="M25 -15 L45 -15 L45 15 L25 15 Z" fill="#666"/>
|
|
<path id="ps-dpad-down" class="button draggable" d="M-15 25 L15 25 L15 45 L-15 45 Z" fill="#666"/>
|
|
<path id="ps-dpad-left" class="button draggable" d="M-45 -15 L-25 -15 L-25 15 L-45 15 Z" fill="#666"/>
|
|
</g>
|
|
|
|
<!-- Face buttons -->
|
|
<g id="ps-face" transform="translate(540,230)">
|
|
<!-- Update circle positions to match Xbox spacing -->
|
|
<circle id="ps-cross" class="button draggable" cx="0" cy="60" r="25" fill="#666"/>
|
|
<circle id="ps-circle" class="button draggable" cx="60" cy="0" r="25" fill="#666"/>
|
|
<circle id="ps-square" class="button draggable" cx="-60" cy="0" r="25" fill="#666"/>
|
|
<circle id="ps-triangle" class="button draggable" cx="0" cy="-60" r="25" fill="#666"/>
|
|
<!-- Update symbol positions to match new circle positions -->
|
|
<path d="M-8 60 L8 60 M0 52 L0 68" stroke="#444" stroke-width="3" class="draggable"/>
|
|
<circle cx="60" cy="0" r="15" stroke="#444" stroke-width="3" fill="none" class="draggable"/>
|
|
<rect x="-70" y="-10" width="20" height="20" stroke="#444" stroke-width="3" fill="none" class="draggable"/>
|
|
<path d="M0 -68 L-8 -52 L8 -52 Z" fill="#444" class="draggable"/>
|
|
</g>
|
|
|
|
<!-- Sticks base rings -->
|
|
<circle cx="297.3318176269531" cy="348.8255157470703" r="40" fill="#444" stroke="#555" stroke-width="2" class="draggable"/>
|
|
<circle cx="462.5313720703125" cy="356.6248779296875" r="40" fill="#444" stroke="#555" stroke-width="2" class="draggable"/>
|
|
|
|
<!-- Sticks -->
|
|
<g id="ps-sticks">
|
|
<circle id="ps-stick-left" class="stick draggable" cx="297.3318176269531" cy="348.82554626464844" r="30" fill="#888"/>
|
|
<circle id="ps-stick-right" class="stick draggable" cx="460.70697021484375" cy="357.53704833984375" r="30" fill="#888"/>
|
|
</g>
|
|
|
|
<!-- Shoulder buttons -->
|
|
<g id="ps-shoulders">
|
|
<rect id="ps-l1" class="button draggable" x="200" y="50" width="80" height="30" rx="15" fill="#666"/>
|
|
<rect id="ps-r1" class="button draggable" x="520" y="50" width="80" height="30" rx="15" fill="#666"/>
|
|
</g>
|
|
</g>
|
|
</svg>
|
|
<script>
|
|
const deviceSelect = document.getElementById('deviceSelect');
|
|
const connectButton = document.getElementById('connectHID');
|
|
const disconnectButton = document.getElementById('disconnectDevice');
|
|
const deviceLog = document.getElementById('deviceLog');
|
|
let animationFrame;
|
|
const devices = new Map();
|
|
let lastState = new Uint8Array();
|
|
let selectedDeviceId = null;
|
|
|
|
const mappingSelect = document.getElementById('mappingSelect');
|
|
let currentMapping = null;
|
|
|
|
|
|
function logDevice(message) {
|
|
const line = document.createElement('div');
|
|
line.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
|
|
deviceLog.insertBefore(line, deviceLog.firstChild);
|
|
if (deviceLog.children.length > 50) deviceLog.lastChild.remove();
|
|
}
|
|
|
|
function updateDeviceOption(deviceId, deviceInfo) {
|
|
const deviceEl = document.createElement('div');
|
|
deviceEl.className = 'device-option';
|
|
|
|
const contentWrapper = document.createElement('div');
|
|
contentWrapper.style.flex = 1;
|
|
|
|
const nameEl = document.createElement('div');
|
|
nameEl.textContent = deviceId;
|
|
contentWrapper.appendChild(nameEl);
|
|
|
|
if (deviceInfo.detectedType) {
|
|
const typeEl = document.createElement('div');
|
|
typeEl.className = 'device-info';
|
|
typeEl.textContent = `Type: ${deviceInfo.detectedType}`;
|
|
contentWrapper.appendChild(typeEl);
|
|
}
|
|
|
|
deviceEl.appendChild(contentWrapper);
|
|
|
|
const indicator = document.createElement('div');
|
|
indicator.className = 'status-indicator';
|
|
if (deviceInfo.type === 'gamepad') {
|
|
const gamepad = navigator.getGamepads()[deviceInfo.device.index];
|
|
indicator.classList.toggle('connected', gamepad && gamepad.connected);
|
|
} else {
|
|
indicator.classList.toggle('connected', deviceInfo.device.opened);
|
|
}
|
|
deviceEl.appendChild(indicator);
|
|
|
|
deviceEl.onclick = () => selectDevice(deviceId);
|
|
return deviceEl;
|
|
}
|
|
|
|
function handleHIDInput(e) {
|
|
const data = new Uint8Array(e.data.buffer);
|
|
if (lastState.length) {
|
|
for (let i = 0; i < data.length; i++) {
|
|
if (data[i] !== lastState[i]) {
|
|
logDevice(`Device ${e.device.productName} Button ${i + 1} ${data[i] ? "Pressed" : "Released"}`);
|
|
|
|
const buttonMap = {
|
|
0: 'button-x',
|
|
1: 'button-y',
|
|
2: 'button-b',
|
|
3: 'button-a',
|
|
4: 'dpad-up',
|
|
5: 'dpad-right',
|
|
6: 'dpad-down',
|
|
7: 'dpad-left'
|
|
};
|
|
|
|
const elementId = buttonMap[i];
|
|
if (elementId) {
|
|
const element = document.getElementById(elementId);
|
|
if (element) {
|
|
element.classList.toggle('pressed', Boolean(data[i]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
lastState = data;
|
|
}
|
|
|
|
async function setupHIDDevice(device) {
|
|
try {
|
|
if (!device.opened) {
|
|
await device.open();
|
|
}
|
|
|
|
addDevice(device.productName, device, 'hid');
|
|
device.addEventListener("inputreport", handleHIDInput);
|
|
|
|
logDevice(`Connected to ${device.productName}`);
|
|
disconnectButton.style.display = 'inline-block';
|
|
} catch (error) {
|
|
logDevice(`Error setting up device: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
connectButton.onclick = async () => {
|
|
try {
|
|
const devices = await navigator.hid.requestDevice({
|
|
filters: [] // Let Chrome show all HID devices
|
|
});
|
|
|
|
for (const device of devices) {
|
|
await setupHIDDevice(device);
|
|
}
|
|
} catch (error) {
|
|
logDevice(`Error connecting device: ${error.message}`);
|
|
}
|
|
};
|
|
|
|
const controllerMappings = {
|
|
'Xbox': {
|
|
type: 'gamepad',
|
|
buttons: {
|
|
0: 'button-a',
|
|
1: 'button-b',
|
|
2: 'button-x',
|
|
3: 'button-y',
|
|
4: 'button-lb',
|
|
5: 'button-rb',
|
|
12: 'dpad-up',
|
|
13: 'dpad-down',
|
|
14: 'dpad-left',
|
|
15: 'dpad-right'
|
|
},
|
|
sticks: {
|
|
leftX: 0,
|
|
leftY: 1,
|
|
rightX: 2,
|
|
rightY: 3
|
|
},
|
|
detect: (id) => /xbox|xinput/i.test(id)
|
|
},
|
|
'PlayStation': {
|
|
type: 'gamepad',
|
|
buttons: {
|
|
0: 'ps-cross',
|
|
1: 'ps-circle',
|
|
2: 'ps-square',
|
|
3: 'ps-triangle',
|
|
4: 'ps-l1',
|
|
5: 'ps-r1',
|
|
12: 'ps-dpad-up',
|
|
13: 'ps-dpad-down',
|
|
14: 'ps-dpad-left',
|
|
15: 'ps-dpad-right'
|
|
},
|
|
sticks: {
|
|
leftX: 0,
|
|
leftY: 1,
|
|
rightX: 2,
|
|
rightY: 3
|
|
},
|
|
detect: (id) => /playstation|ps4|ps5|dualshock|dualsense/i.test(id)
|
|
}
|
|
};
|
|
|
|
function setControllerType(type) {
|
|
const xboxController = document.querySelector('.controller-xbox');
|
|
const psController = document.querySelector('.controller-playstation');
|
|
|
|
if (!xboxController || !psController) return;
|
|
|
|
xboxController.classList.remove('active');
|
|
psController.classList.remove('active');
|
|
|
|
if (type) {
|
|
if (type.toLowerCase() === 'xbox') {
|
|
xboxController.classList.add('active');
|
|
} else if (type.toLowerCase() === 'playstation') {
|
|
psController.classList.add('active');
|
|
}
|
|
currentMapping = controllerMappings[type];
|
|
mappingSelect.value = type;
|
|
}
|
|
}
|
|
|
|
function detectControllerType(id) {
|
|
for (const [type, mapping] of Object.entries(controllerMappings)) {
|
|
if (mapping.detect(id)) return type;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function addDevice(id, device, type) {
|
|
const detectedType = detectControllerType(id);
|
|
devices.set(id, { device, type, detectedType });
|
|
|
|
const gamepadDevices = document.getElementById('gamepadDevices');
|
|
const hidDevices = document.getElementById('hidDevices');
|
|
|
|
gamepadDevices.innerHTML = '';
|
|
hidDevices.innerHTML = '';
|
|
|
|
let hasGamepads = false;
|
|
let hasHID = false;
|
|
|
|
// Sort and group devices
|
|
const deviceArray = Array.from(devices.entries()).sort((a, b) => {
|
|
if (a[1].type !== b[1].type) return a[1].type === 'gamepad' ? -1 : 1;
|
|
if (!!a[1].detectedType !== !!b[1].detectedType) return a[1].detectedType ? -1 : 1;
|
|
return a[0].localeCompare(b[0]);
|
|
});
|
|
|
|
deviceArray.forEach(([deviceId, deviceInfo]) => {
|
|
const deviceEl = updateDeviceOption(deviceId, deviceInfo);
|
|
|
|
if (deviceInfo.type === 'gamepad') {
|
|
hasGamepads = true;
|
|
gamepadDevices.appendChild(deviceEl);
|
|
} else {
|
|
hasHID = true;
|
|
hidDevices.appendChild(deviceEl);
|
|
}
|
|
});
|
|
|
|
// Update empty states
|
|
if (!hasGamepads) {
|
|
gamepadDevices.innerHTML = `
|
|
<div class="empty">
|
|
No gamepads detected. Connect a controller and press any button.
|
|
<br><small>Supports Xbox, PlayStation, and other standard gamepads.</small>
|
|
</div>`;
|
|
}
|
|
if (!hasHID) {
|
|
hidDevices.innerHTML = `
|
|
<div class="empty">
|
|
No other devices connected.
|
|
<br><small>Click "Connect Device" to add USB game controllers.</small>
|
|
</div>`;
|
|
}
|
|
|
|
if (!selectedDeviceId && deviceArray.length > 0) {
|
|
const firstGamepad = deviceArray.find(([_, info]) => info.type === 'gamepad');
|
|
if (firstGamepad) selectDevice(firstGamepad[0]);
|
|
}
|
|
|
|
updateConnectButton();
|
|
}
|
|
|
|
function updateButtonVisuals(elementId, isPressed, value = 1) {
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return;
|
|
|
|
element.classList.toggle('pressed', isPressed);
|
|
if (isPressed) {
|
|
// Add visual pressure feedback
|
|
const intensity = Math.min(value * 255, 255);
|
|
element.style.fill = `rgb(${76 + intensity * 0.2}, ${175 + intensity * 0.1}, ${80 + intensity * 0.1})`;
|
|
} else {
|
|
element.style.fill = '#666';
|
|
}
|
|
}
|
|
|
|
function updateConnectButton() {
|
|
const hasDevices = devices.size > 0;
|
|
const anyHIDDevices = Array.from(devices.values()).some(d => d.type === 'hid');
|
|
|
|
connectButton.classList.toggle('pulse', !hasDevices);
|
|
connectButton.textContent = hasDevices ? 'Add Device' : 'Connect Device';
|
|
|
|
if (anyHIDDevices) {
|
|
disconnectButton.style.display = 'inline-block';
|
|
} else {
|
|
disconnectButton.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function selectDevice(deviceId) {
|
|
const deviceInfo = devices.get(deviceId);
|
|
if (!deviceInfo) return;
|
|
|
|
selectedDeviceId = deviceId;
|
|
|
|
// Update UI
|
|
document.querySelectorAll('.device-option').forEach(el =>
|
|
el.classList.toggle('active', el.textContent === deviceId)
|
|
);
|
|
|
|
if (deviceInfo.detectedType) {
|
|
setControllerType(deviceInfo.detectedType);
|
|
mappingSelect.style.display = 'none';
|
|
} else {
|
|
mappingSelect.style.display = 'inline-block';
|
|
}
|
|
|
|
startPolling();
|
|
}
|
|
|
|
function removeDevice(id) {
|
|
devices.delete(id);
|
|
|
|
if (selectedDeviceId === id) {
|
|
selectedDeviceId = null;
|
|
stopPolling();
|
|
}
|
|
}
|
|
|
|
window.addEventListener('gamepadconnected', (e) => {
|
|
logDevice(`Gamepad connected: ${e.gamepad.id}`);
|
|
addDevice(e.gamepad.id, e.gamepad, 'gamepad');
|
|
});
|
|
|
|
window.addEventListener('gamepaddisconnected', (e) => {
|
|
logDevice(`Gamepad disconnected: ${e.gamepad.id}`);
|
|
removeDevice(e.gamepad.id);
|
|
});
|
|
|
|
disconnectButton.onclick = async () => {
|
|
const deviceInfo = devices.get(selectedDeviceId);
|
|
if (deviceInfo && deviceInfo.type === 'hid') {
|
|
await deviceInfo.device.close();
|
|
removeDevice(selectedDeviceId);
|
|
disconnectButton.style.display = 'none';
|
|
logDevice(`Disconnected ${selectedDeviceId}`);
|
|
}
|
|
};
|
|
|
|
function startPolling() {
|
|
if (!animationFrame) {
|
|
animationFrame = requestAnimationFrame(updateVisuals);
|
|
}
|
|
}
|
|
|
|
function stopPolling() {
|
|
if (animationFrame) {
|
|
cancelAnimationFrame(animationFrame);
|
|
animationFrame = null;
|
|
}
|
|
resetVisuals();
|
|
}
|
|
|
|
function updateVisuals() {
|
|
const deviceInfo = devices.get(selectedDeviceId);
|
|
|
|
if (deviceInfo && deviceInfo.type === 'gamepad') {
|
|
updateGamepadVisuals(deviceInfo.device);
|
|
}
|
|
|
|
animationFrame = requestAnimationFrame(updateVisuals);
|
|
}
|
|
|
|
mappingSelect.addEventListener('change', () => {
|
|
if (mappingSelect.value) {
|
|
setControllerType(mappingSelect.value);
|
|
}
|
|
});
|
|
|
|
function updateGamepadVisuals(gamepadId) {
|
|
const gamepad = navigator.getGamepads()[gamepadId.index];
|
|
if (!gamepad || !currentMapping) return;
|
|
|
|
gamepad.buttons.forEach((button, index) => {
|
|
const elementId = currentMapping.buttons[index];
|
|
if (elementId) {
|
|
updateButtonVisuals(elementId, button.pressed, button.value);
|
|
}
|
|
});
|
|
|
|
const stickIds = currentMapping === controllerMappings['PlayStation'] ?
|
|
['ps-stick-left', 'ps-stick-right'] :
|
|
['stick-left', 'stick-right'];
|
|
|
|
updateStick(stickIds[0],
|
|
gamepad.axes[currentMapping.sticks.leftX],
|
|
gamepad.axes[currentMapping.sticks.leftY]
|
|
);
|
|
updateStick(stickIds[1],
|
|
gamepad.axes[currentMapping.sticks.rightX],
|
|
gamepad.axes[currentMapping.sticks.rightY]
|
|
);
|
|
}
|
|
|
|
function updateStick(stickId, x, y) {
|
|
const stick = document.getElementById(stickId);
|
|
const maxOffset = 20;
|
|
const transformX = x * maxOffset;
|
|
const transformY = y * maxOffset;
|
|
stick.style.transform = `translate(${transformX}px, ${transformY}px)`;
|
|
}
|
|
|
|
function resetVisuals() {
|
|
document.querySelectorAll('.button').forEach(button => {
|
|
button.classList.remove('pressed');
|
|
});
|
|
document.querySelectorAll('.stick').forEach(stick => {
|
|
stick.style.transform = 'translate(0, 0)';
|
|
});
|
|
}
|
|
|
|
// Check for any already-connected HID devices
|
|
async function initializeHID() {
|
|
const devices = await navigator.hid.getDevices();
|
|
devices.forEach(device => {
|
|
logDevice(`Found HID: ${device.productName}`);
|
|
if (device.opened) {
|
|
setupHIDDevice(device);
|
|
}
|
|
});
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', initializeHID);
|
|
</script>
|
|
</body>
|
|
</html> |