diff --git a/src/renderer/views/Mapping.jsx b/src/renderer/views/Mapping.jsx index fec9d6f..e006904 100644 --- a/src/renderer/views/Mapping.jsx +++ b/src/renderer/views/Mapping.jsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import { Gamepad2, Play, Square, Plus, X, ArrowLeftRight, Save, Trash2, SlidersHorizontal } from 'lucide-react'; +import { Gamepad2, Play, Square, Plus, X, ArrowLeftRight, Save, Trash2, SlidersHorizontal, Activity } from 'lucide-react'; import Card from '../components/Card'; import Badge from '../components/Badge'; import Select from '../components/Select'; @@ -79,6 +79,10 @@ export default function Mapping() { // needing Windows' own Game Controllers properties panel open to see it. const [activeOutputs, setActiveOutputs] = useState({}); const activeOutputsRef = useRef({}); + // The exact {axes, buttons} payload fed to vJoy this tick, per mapping + // slot id, so Live Mapping's own card can show it directly, see tick(). + const [liveOutputs, setLiveOutputs] = useState({}); + const liveOutputsRef = useRef({}); const mappingsRef = useRef([]); const axisSettingsRef = useRef({}); const autoRestoredRef = useRef(false); @@ -149,6 +153,10 @@ export default function Mapping() { // the previous tick's snapshot below so the Advanced Mapping editor // only re-renders when what's live actually changes. const tickActiveOutputs = {}; + // The exact {axes, buttons} payload fed to vJoy this tick, per mapping + // slot, so Live Mapping can show what's actually reaching vJoy without + // needing Windows' own Game Controllers properties panel open. + const tickLiveOutputs = {}; for (const slot of mappingsRef.current) { if (!slot.isLive || !slot.targetDeviceId) continue; @@ -353,6 +361,7 @@ export default function Mapping() { const buttons = Array.from({ length: buttonsOut.length }, (_, i) => buttonsOut[i] ?? false); mim.mapping.feed({ deviceId: Number(slot.targetDeviceId), axes, buttons }); + tickLiveOutputs[slot.id] = { axes, buttons }; } if (runtime && pendingModeChangeRef.current) { @@ -370,6 +379,20 @@ export default function Mapping() { activeOutputsRef.current = tickActiveOutputs; setActiveOutputs(tickActiveOutputs); } + + const prevLiveKeys = Object.keys(liveOutputsRef.current); + const nextLiveKeys = Object.keys(tickLiveOutputs); + const liveChanged = + prevLiveKeys.length !== nextLiveKeys.length || + nextLiveKeys.some((k) => { + const prev = liveOutputsRef.current[k]; + const next = tickLiveOutputs[k]; + return !prev || prev.axes.join() !== next.axes.join() || prev.buttons.join() !== next.buttons.join(); + }); + if (liveChanged) { + liveOutputsRef.current = tickLiveOutputs; + setLiveOutputs(tickLiveOutputs); + } } const interval = setInterval(tick, TICK_INTERVAL_MS); return () => clearInterval(interval); @@ -774,6 +797,7 @@ export default function Mapping() { onSwapWithNext={() => swapTargets(index, index + 1)} axisSettingsMap={axisSettingsMap} onSetAxisSetting={setAxisSetting} + liveOutput={liveOutputs[slot.id]} /> ))} @@ -814,9 +838,11 @@ function MappingSlotCard({ onRemove, onSwapWithNext, axisSettingsMap, - onSetAxisSetting + onSetAxisSetting, + liveOutput }) { const [expandedAxis, setExpandedAxis] = useState(null); + const [showLiveOutput, setShowLiveOutput] = useState(false); const selectedDevices = slot.selectedIds.map((id) => devices.find((d) => d.id === id)).filter(Boolean); const totalAxes = selectedDevices.reduce((sum, d) => sum + d.axes.length, 0); const targetOptions = vjoyDevices @@ -874,6 +900,18 @@ function MappingSlotCard({ Live )} + {slot.isLive && ( + + )} + + {showLiveOutput && slot.isLive && liveOutput && ( + +
+

+ vJoy Device {slot.targetDeviceId}, live output +

+ {liveOutput.axes.length > 0 && ( +
+ {liveOutput.axes.map((value, i) => ( +
+ {AXIS_NAMES[i] ?? `A${i}`} +
+ +
+ {value.toFixed(2)} +
+ ))} +
+ )} +
+ {liveOutput.buttons.map((pressed, i) => ( + + {i + 1} + + ))} +
+
+
+ )} +
+ {slot.liveError && (
{slot.liveError}