top of page
loading_reduced.jpg

3.3.0 / 27-05-2026

No ratings yet

Performance

No ratings yet

Features

No ratings yet

PadForge

Accuracy

competition

Modern fork of x360ce built with SDL3, ViGEmBus, vJoy, Windows MIDI Services, HelixToolkit, .NET 10 WPF, and Fluent Design. Turn any input device into any virtual controller:
Gamepads, joysticks, keyboards, mice, and touchscreens — mapped to Xbox 360, DualShock 4, DirectInput, MIDI, or Keyboard+Mouse output that games treat as real hardware.


Most Recent Changes

--3.3.0--

The new Touchpad tab is the headline. A full Pad-page surface for tuning touchpad behavior per slot, on any source that carries a touchpad surface (DualSense, DualSense Edge, DualShock 4, Web Controller in DS4 / touchpad mode, the on-screen Touchpad Overlay, or a Windows Precision Touchpad).


Touchpad tab

Five cards:

  1. Stick / D-Pad Output. Turn a touchpad finger into a virtual analog stick (anchor-relative) and a wedge-thresholded D-pad. Max radius, inner deadzone, 4-way / 8-way D-pad mode, D-pad activation threshold.

  2. Mouse Output. Per-axis sensitivity (0.05x to 10x) and per-axis invert for touchpad-finger → mouse X/Y on a Keyboard / Mouse virtual controller. Settings save per slot, so two slots sharing one physical touchpad each carry their own sensitivity and invert.

  3. Gesture Detection. Master enable, recognize mode (in-box / custom / both), cooldown between fires.

  4. In-Box Gestures. Three tiers. Single-finger: 4-way / 8-way swipes, radial zones, tap / double-tap / triple-tap, long-press. Multi-finger: two-finger swipes, pinch, rotate, three / four / five-finger gestures. Shape templates: trace a Circle (clockwise and counter-clockwise are separate bindings), Square, Triangle, Z, or Checkmark on the pad. Adjustable match threshold tunes strictness against false positives.

  5. Custom Gestures. Profile-scoped recorder. Trace a shape on the live touchpad, save it, bind the descriptor.

Every toggle starts disabled. Numeric thresholds (cooldown, swipe distance, tap time window, longpress duration, deadzones) keep tuned defaults so a feature works correctly the moment its toggle flips.

The gesture engine fans out per slot. A DualSense in two slots with different toggles gets two independent recognizers. Fires from slot 0 don't bleed into slot 1's mapping rows.


Other 3.3 work

  • Gyro Motion Passthrough card. A slot-wide toggle controls whether the Gyro tab's discretionary tuning (deadzone, sensitivity, smoothing, response curve, invert) reaches the motion the virtual controller hands to the game and to the DSU motion server. Off by default, so a fresh profile sends a clean, calibrated reading and lets the game apply its own feel. Calibration drift correction always applies either way.

  • Per-device Copy / Paste / Copy From. Copy now snapshots every assigned device's full PadSetting (gyro tuning, touchpad settings, FFB, impulse triggers, adaptive triggers, lighting), not just the device selected at the time. Paste matches each entry to a target-slot device by InstanceGuid first, then ProductGuid as a fallback so the same controller model on a different physical unit still picks up the tuning. Devices without a match are left alone.

  • Keyboard / Mouse virtual controller. Added Print Screen, Scroll Lock, Pause, and Num Lock to the key picker. Useful for game launchers and capture overlays that bind to these.

  • Pad page label column. Widened to 270 DIP so every locale's longest setting label fits without truncation. Spanish "Ventana de tiempo del deslizamiento (ms)" was the worst case at 259 px. Every tab from Sticks through Touchpad now aligns its dropdown / slider column at the same X.

  • Localization. Touchpad-tab strings localized into all 9 non-English locales: de, es, fr, it, ja, ko, nl, pt-BR, zh-Hans. Brazilian Portuguese touchpad strings recased to match the Title Case convention used by every other Pad-page tab.

  • Gyro live readouts. Per-axis fields renamed Pitch / Yaw / Roll (was X / Y / Z) on the gyro rate strip so the abbreviations match what the rest of the tab uses.


PTP touchpad reliability

The Windows Precision Touchpad reader handles four spec-mandatory behaviors that were missing or incomplete. The user-visible impact: three-, four-, and five-finger taps on laptop trackpads now fire on the first try instead of needing several attempts.

  • Tip-switch (digitizer usage 0x42). Per the PTP spec, a lifting contact sends one final report with tip-switch = 0. The reader now skips those entries. Lifted contacts no longer inflate the apparent contact count.

  • Multi-report frame assembly. Certified PTP hardware caps each HID report at 2 contacts and signals the frame's total contact count on the first report. The reader accumulates contacts across fragments and commits only when the buffer reaches the expected total.

  • HID-contact-id-stable slot assignment. Each contact's HID ID keeps its engine-side slot across frames. A low-slot finger lifting no longer shifts the remaining contacts down in buffer order and corrupts the engine's per-slot paths with the wrong finger's coordinates.

  • Picker fallback. The mapping picker for PTP devices defaults MaxFingers to 5 (the PTP-spec maximum) rather than the legacy 2-finger assumption, so three-, four-, and five-finger gesture descriptors surface in the dropdown even before any live touch state is read.


Raw Input mouse

The mouse reader skips events where RAWMOUSE.usFlags has MOUSE_MOVE_ABSOLUTE set. Absolute-mode pointing devices (RDP virtual mouse, Wacom tablets in absolute mode, some KVMs) deliver lLastX / lLastY as 0..65535 absolute coordinates rather than relative deltas. Treating them as deltas would inject huge spurious motion into the gamepad-mapping aim and scroll paths. Matches the policy SDL3 and XInput use.


Carrying forward from 3.2

  • Rebuilt mapping engine: multi-source rows, shift layers, cross-device chords, drag-and-drop formula editor.

  • Gyro at Steam Input parity (calibration, sensitivity, response shaping, aim-engage button + mode).

  • Impulse Triggers tab (per-trigger gain, Constant Trigger Force, Audio Bass Trigger Rumble).

  • 2026 Steam Controller support through the bundled SDL3 fork.

  • HIDMaestro user-mode UMDF2 driver upgrades, OpenXInput XInput filter, HidHide masking, GameControllerDB bundled.


Compatibility

  • Windows 10 1809 (build 17763) or later. Windows 11 recommended.

  • The MIDI virtual controller requires Windows 11 24H2 (build 26100) for the Windows MIDI Services SDK.

  • .NET 10 self-contained. No separate runtime install.

  • Tested with Xbox 360 / One / Elite / Series, DualShock 3 / 4, DualSense / Edge, Switch Pro, 2026 Steam Controller, generic DirectInput joysticks / wheels / HOTAS, Windows Precision Touchpads.



--3.2.4--

PadForge 3.2.4 adds Toggle mode to the Gyro engage button field and a SetGyroEngaged macro action.


Engage button gains a Toggle mode

The Gyro tab's engage button has been hold-only. Press to engage, release to disengage. Fine for FPS aim where the engaged state should follow a trigger; awkward for global on/off where pressing once and walking away from the keypress is the natural interaction.

A new dropdown next to the engage button picker lets you choose:

  • Hold (default, prior behavior): gyro fires while the engage button is held.

  • Toggle: each press of the engage button flips a sticky engaged bit. Release does nothing. Press again to disengage.

Empty engage button stays always-on in Hold mode (same as before). Empty engage button in Toggle mode leaves the bit untouched so a macro can drive it instead.


SetGyroEngaged macro action

A new macro action in the macro editor. Three modes:

  • Toggle: flip the engaged bit

  • On: set engaged

  • Off: clear engaged

Any input that can fire a macro can now drive gyro engagement: a button release, an axis past threshold, a sequenced multi-step trigger, a multi-source combine row, a shift-layer transition. The dedicated engage field handles the simple case; the macro action handles everything composite or time-based.


Both sources are OR-combined

The dedicated engage field and the macro action each own an independent per-slot engaged bit. The gyro evaluator reads the OR. Either source can engage; neither can disengage what the other engaged. If you want exclusive control through one path, leave the other unbound. Easy Aim continues to AND-compose on top, unchanged.


What this does not replace

The shift-layer system already covers the broader case: cross-device activators, Toggle / Hold / Custom / Cycle / Sticky modes, chord activation, named layers gating any mapping rows you like. If you want a button to toggle gyro and several other mappings at the same time, that is still a shift-layer job. This patch is the friction shortcut for users who configure engagement from the Gyro tab and want toggle behavior without setting up a layer.


Translations

Twelve new strings shipped in all ten PadForge locales (German, Spanish, French, Italian, Japanese, Korean, Dutch, Brazilian Portuguese, Simplified Chinese, plus English): the Engage Mode label, its tooltip, Hold and Toggle dropdown values, Reset tooltip, the macro action name with its three mode values, and the macro action description.



--3.2.3--

PadForge 3.2.3 moves motion passthrough into the mapping table, adds an accelerometer line and a live preview to the Gyro tab, and surfaces per-row Invert on both sensor sub-channels.


Motion passthrough is a mapping row

Source selection for the virtual controller's gyro and accelerometer report used to be hidden inside the engine: first online assigned device with sensors wins, no way to choose. Now each Sony-class slot has two rows in the Mappings tab — Motion Gyro and Motion Accel — and the device named in each row is the source. Multi-device setups work without surprise: assign a Switch Pro and a DualSense to the same slot, turn one off and the other on, and the engine picks up the live one on the next polling tick.

Auto-created on assignment for every gyro and accel-capable device, deletable to opt out, and editable from the standard source picker — the same dropdown the rest of the mapping table uses.

The Gyro tab stays exactly where it was. The Apply Gyro Tuning to Motion Passthrough toggle keeps the same scope and semantics.


Live readout on the Gyro tab Calibration card

The Calibration card now shows both sensors live:

Live gyroscope        Pitch +0.0°/s   Yaw +0.0°/s   Roll +0.0°/s
Live accelerometer    X +0.01g        Y -0.99g      Z +0.02g

Same font and size as the motor previews on the Controller tab. Axis names spelled out. The gyro line is the calibrated rate (bias subtracted); the accel line is the raw sensor scaled to g-units.


Live preview in the Motion row Value column

The Value column on the Motion Gyro and Motion Accel mapping rows reads the post-tuning sensor — same bytes the engine writes to the virtual DualSense HID report and broadcasts over DSU. Flip Invert Pitch on the Gyro tab or check the row's Invert box, and the reading flips live.


Per-row Invert for both sensor sub-channels

The Invert checkbox on a Motion Gyro source now flips the three gyro axes. Same checkbox on a Motion Accel source flips the three accel axes. Stacks with the Gyro tab's Invert Pitch and Invert Yaw / Roll toggles — both checked equals no net flip, same composition rule Invert follows everywhere else.


Slot type changes refresh the mapping table live

Switching a slot from Xbox to PlayStation through the sidebar or the dashboard adds Motion rows to the slot's mapping table immediately. No save and reload needed.


Translations

All new strings landed in the nine PadForge locales (German, Spanish, French, Italian, Japanese, Korean, Dutch, Brazilian Portuguese, Simplified Chinese). Motion mapping labels, live sensor labels, and the parallel sensor-name forms (Live gyroscope / Live accelerometer) are translated consistently.



--3.2.2--

PadForge 3.2.2 is a gyro release. The Gyro tab tuning now reaches the motion that PadForge passes through to games, the HID reports it ships to virtual controllers stay in their native sensor frame, and a Bluetooth DualSense bug that swapped the right stick with the triggers is fixed.


Gyro tab tuning now drives motion passthrough

Sensitivity, deadzone, smoothing, invert, real-world calibration, and the gyro space projection apply to the motion PadForge passes through to games. Calibration bias is always subtracted, even when the rest of the chain is bypassed. A new toggle on the Gyro tab — Apply Gyro Tuning to Motion Passthrough — lets you keep raw motion if a game needs the device's native feel.


At-rest drift on virtual DualSense

The HID reports PadForge writes to virtual DualSense controllers (and the DSU broadcast on slot motion) now ship in SDL's native sensor frame instead of being point-inverted on the way out. The point-inversion was incompatible with the gyro and accel sign conventions the consumer's sensor fusion expects, which kept its filter from converging. Several degrees of yaw drift while the pad sat still. Scale constants now match SDL3's PS5 driver (gyro 1024 per deg/sec, accel 8192 per g) and the sensor timestamp ships at the 0.33µs tick rate the DualSense report uses.


Bluetooth DualSense stick/trigger swap

A Bluetooth-paired DualSense now reports its right stick and triggers on the correct axes. Before this release, moving the right stick changed the trigger values and pressing a trigger moved the right stick. Verified against ds.daidr.me. The fix ships as HIDMaestro v1.3.13.


Motion Passthrough card

The passthrough toggle lives in its own Motion Passthrough card at the top of the Gyro tab, with the same look as the Calibration and Tuning cards below it, plus a Reset All button.


Naming

The Invert Yaw checkbox is now labeled Invert Yaw / Roll (X). It always flipped both axes. The label says so now. Yaw + Roll is what the Steam Input convention calls "horizontal" — the same axis the Horizontal Sensitivity slider scales.


Translations

All gyro-related strings are translated into the nine PadForge locales (German, Spanish, French, Italian, Japanese, Korean, Dutch, Brazilian Portuguese, Simplified Chinese).



--3.2.1--

PadForge 3.2.1 is a bug-fix release for the 3.2 mapping engine. It fixes touchpad-to-mouse aiming and mapping-table column sizing, plus a few smaller mapping-tab issues.


Touchpad to mouse

A touchpad axis mapped to mouse X/Y now moves the cursor the way a laptop trackpad does. Before this release the cursor moved inverted on the Y axis, ran at full speed no matter how far the finger moved, and pinned to a screen edge when the finger lifted. The reader now tracks finger motion as a relative delta, and a full swipe across the pad moves the cursor about one screen width.

Mapping a touchpad axis to a non-mouse target still reads absolute finger position. A DualSense touchpad mapped to a virtual touchpad, or to a stick, passes the finger's location straight through.


Mapping table column widths

Every column in the Mappings table sizes to its own content. The Options column no longer renders far wider than the checkboxes inside it. The Output and Source dropdowns reserve room for the widest entry their list can show, instead of shrinking to whatever is selected, or to nothing when the row is empty. Column widths recompute on load, so they fit each language's label lengths.


Smaller fixes

  • Half checkbox. The Half option is disabled when the mapped source is not an axis. A button, POV direction, or touchpad click has no half-axis, so the checkbox is grayed out for those sources.

  • Xbox Share auto-map. Any controller that reports a Misc1 button auto-maps it to the Xbox virtual controller's Share output. That covers the DualSense Mic button, the Switch Pro Capture button, and the Xbox Series Share button. Controllers without the button are left unbound instead of carrying a dead binding.

  • Extended controller schematic. Clicking a trigger image to record now flashes the trigger outline, the same as the buttons, sticks, and POV hats on that schematic.



--3.2.0--

PadForge 3.2.0 is the release where the mapping engine grew up. One row in the Mappings table can now read from many physical inputs at once. Shift layers turn your face buttons into a Pit Stop menu when you hold a paddle. A drag-and-drop formula editor lets you bind "fire only when this axis is past halfway AND that other button is held." Behind it sits a gyro pass that closes the gap with Steam Input, and a dedicated Impulse Triggers tab so Forza shakes your real Xbox pad.


The mapping engine, rebuilt (Issue #61)

The Mappings tab is now per-virtual-controller, not per-device. One row can take input from any number of physical sources, on any device. Add extra sources from a cascading device-and-input picker. Sources are letter-tagged a, b, c, ... in row order. A row that says "a + b, where a is the wheel's pedal and b is the keyboard's W key" works the way you would expect.

Six combine modes ship in the box: Strongest, Combined, Average, Either, Both, Only one. A seventh, Custom, opens the formula editor. The drag-and-drop operator palette covers arithmetic (+, −, ×, ÷), comparisons (<, >, ≤, ≥, =, ≠), logic (&&, ||, !), if-then-else, and math (abs, min, max, clamp, sign, lerp, round, sqrt). Live parse status. Ten starter recipes ship in the box: Half scale, Quarter scale, Reverse a, Cap to ±1, Weighted blend, Difference, A unless idle, Threshold gate, Both pressed, Stronger wins.

Each source can be Direct, Incremental (rate-of-change via Up / Down buttons with sticky-vs-snap and clamp range), or Invert On Hold (flip the row while a modifier is held). Bipolar-axis source rows show → + and ← − direction badges so the sign on each half is obvious. Bidirectional half-axis (formerly "Either") fires past the deadzone in either direction. Adding a device to a slot extends existing rows with the new sources via the configured combine mode. Your old mappings stay where they were.


Shift layers (Issue #61 Phase 6)

Each slot can carry extra mapping tables that activate while a button, chord, or axis is engaged. Think of it as a keyboard's Shift or Caps Lock for your controller. Hold a paddle to turn the face buttons into a Pit Stop menu, then let go and you are back to driving.

Five activation modes (Hold, Toggle, Sticky one-shot, Cycle through an ordered list, Custom jump-to-named-layer) and three activator kinds (Button, cross-device Chord, Axis past a threshold). Per-layer color picker and emoji icon. A Win11-style banner at the bottom of the screen confirms the active layer and auto-dismisses after two seconds. A wraparound tab strip sits above the mapping grid.

InheritUnmapped controls whether the layer overlays Base with fallthrough or replaces it outright. Per-row NoInherit blocks fallthrough for specific targets. Cross-VC activator scanning means any slot can engage any other slot's layer. Last engaged wins on conflicts. PostponeMapping lets the activator input also fire its own row alongside the layer change. DelayMs debounces the activator.


Gyro at Steam Input parity

The Gyro tab moves out of Devices and gets its own Pad-page tab on any slot whose pad has a motion sensor. Calibration, Sensitivity, Response Shaping, Engage, and Easy Aim each live in a section card with its own Reset All. Local, Player, and World reference frames are all there. A cross-device Aim Engage picker lets you hold a paddle on the wheel to wake the gyro on the handheld. The device-name subtitle under the picker mirrors the mapping-row pattern.

Dual-threshold smoothing (deadzone plus a speed-based smoothing curve). Real-world calibration with at-rest drift zeroing on a flat surface. Per-(device, slot) persistence so each physical pad keeps its own gyro tuning per slot. Per-axis invert (X, Y, Z independently). A per-source gyro sensitivity multiplier sits on the mapping row that reads from a Gyro source so you can tune sensitivity at the row you are authoring. Gyro Pitch / Yaw / Roll bind in the Mappings table as first-class sources. Gyro-to-virtual-stick is rate-direct, not integrated. The stick recenters on engage release. Multiplier and Degrees-per-screen-turn unit modes. Response curves: Linear, Aggressive, Relaxed, Wide, Extra wide.


Impulse Triggers tab

A new Pad-page tab for trigger-motor effects, separate from the body-rumble Force Feedback tab. Three sections sit on it.

Game-driven passthrough is automatic. Forza, Gears, Halo, and any game that drives Xbox impulse trigger motors reaches the trigger motors on the assigned physical Xbox One, Elite, or Series pad. The same trigger data also routes to assigned DualSense pads as Adaptive Trigger Vibration. No setup.

Constant Trigger Force is a per-device toggle plus Left / Right sliders that apply a continuous force to each trigger motor until you turn it off. Game-driven force overrides while active and resumes when the game stops sending. Audio Bass Trigger Rumble drives the trigger motors from system audio with separate sensitivity, bass cutoff, and per-trigger level. It runs alongside the existing main-motor audio rumble. Swap-left-and-right toggle, Test Left / Right Trigger buttons, motor activity meter round out the tab.

Xbox One+ rumble now writes raw HID directly instead of routing through SDL. PadForge is the sole writer on those pads. Fixes inconsistent rumble across XInput, WGI, and GameInput backends.


Custom Expression macros

A new macro trigger mode that fires on the rising edge when a formula crosses 0.5. Bind as many variables as the formula needs (a, b, c, ...) to an Input Device (a button, POV, or axis on any physical device) or an Output Controller (a channel on the slot's combined virtual output). Per-variable Record and Clear buttons. The trigger formula accepts + − × ÷ && || ?: and math functions (abs, min, max, clamp, sign, lerp), with the same drag-and-drop operator palette and starter recipes as the mapping formula editor. Multi-device combos are supported, axes included. Per-entry axis options for macro axis triggers (Invert, Half-axis, Deadzone) match the merge-mapping options.

Starter recipes: "a alone", "a and b" (classic chord), "a or b", "a but not b", "Axis past 50%".


Profiles and shortcuts

A new Profile Shortcut mode toggles every created virtual controller on or off with one combo press. A flyout pops at the bottom of the screen with a Fluent success-green or critical-red icon to confirm the new state. Useful for stepping away without unplugging anything.

Profile Shortcuts dropdowns no longer clear their selection when you change the app language.


Lighting

Lighting picks up two new modes for DualSense and DualShock 4. Strobe is a hard square-wave flash at the period you set. Battery paints the lightbar with your charge level: red at low, yellow at mid, green at full. Push-driven refresh, no animation timer.


Devices and platform

The 2026 Steam Controller works through the bundled SDL3 fork once Valve's mainline change merged. It shows up on the Devices page and maps like any other gamepad. TouchpadClick is a first-class button now (Buttons[16] = SDL_GAMEPAD_BUTTON_TOUCHPAD). Web Controller and overlays use the canonical channel. The Xbox virtual controller labels Misc 1 as Share and auto-maps it. SDL3 and OpenXInput forks are rebuilt for a 16-slot XInput layout, and SDL3 surfaces the Share button through OpenXInput's XInputGetSystemButtons.

The KBM virtual controller uses a sub-pixel accumulator on mouse delta and scroll, which fixes the gyro-to-mouse truncation that issue #90 reported. The App and Engine assemblies share a single SharedVersion.cs file linked into both csproj, so the two versions cannot drift.


Localization

Spanish, Brazilian Portuguese, and French normalize "virtual controller" terminology across the app. The Impulse Triggers tab strings are translated across nine locales. The Mappings tab, Combine UI, Custom formula editor, and Shift activator dialog are fully localized. Profile Shortcuts and Assigned-devices dropdowns re-translate on language change without losing their selection.


Copy / Paste

The whole-slot MappingSet snapshot preserves multi-device contributions. The snapshot round-trips Lighting, Adaptive Triggers, Mic LED, Player LED, Extended layout, MIDI config, Bidirectional flag, and per-entry macro persistence. Copy From is a full slot copy now, not a single-device slice. It lands on the matching device, not the selected one.


Fixed

  • DualSense Adaptive Trigger linger window for inter-pulse continuity.

  • HM HID output parser reads impulse trigger motors from the correct bytes.

  • Per-device test target honored on the impulse-to-AT auto-route.

  • Per-device gyro persistence: CloneDeep and checksum cover all gyro fields. Aim Engage no longer clobbers TwoWay.

  • Gyro tab settings persist on edit. Stick recenters on engage release.

  • Mapping table: Bidirectional row visible. Per-source gyro on primary mapping.

  • Profile cycle no longer wipes per-VC MappingSets. Snapshot/restore round-trips them.

  • Deep-clone SlotMappingSets stops cross-profile mapping bleed.

  • Adding a device no longer clobbers existing slot mappings.

  • Slot reorder does not rebuild active VCs when an inactive neighbor moves above them.

  • Slot delete skips bubble-down cascade when the removed slot had no active VC.

  • Pad indices compact after delete, on load, and on profile apply (no gaps).

  • Sony dispatcher timer keeps impulse triggers, constant trigger force, and audio-trigger rumble alive when no other source is active.

  • Centered-axis-to-opposing-buttons no longer double-inverts.


Architecture invariants worth knowing

Xbox One+ rumble and impulse triggers: raw HID is the sole writer. SDL_RumbleGamepad is not called on those pads. Sony rumble, lightbar, AT, and mic LED: the dispatcher is the sole writer. SDL is skipped on Sony pads. TouchpadClick lives at Buttons[16]. SDL3 defines one canonical slot.



--3.1.4--

Highlights

  • Per-profile 3D and 2D renders. Xbox 360, Xbox One / Elite / Adaptive, Xbox Series, DualShock 4, and DualSense each have their own native artwork. Assigning a profile swaps the model and overlays to match.

  • Xbox Series Share button. Clickable on the 3D mesh, in the 2D overlay, and in the mapping grid (also recorded by Map All). Non-Series Xbox profiles use the same Series mesh but leave the Share region inert so it stays visually accurate without firing on press.

  • DS4 lightbar parity with DualSense. Audio modes, base modes, and macro override all now route through the DS4 effect synthesizer the same way they do on DualSense.

  • DS4 force feedback fix. Gates the rumble-skip on active DualSense passthrough so DS4 virtuals always rumble.

  • SideWinder force feedback hotfix. Per-RID magnitude scale, descriptor-aware Set Effect / Set Periodic decoding, and a gate that stops Microsoft-VID Xbox-rumble branches from stealing PID FFB.

  • HIDMaestro v1.3.5 → v1.3.12. Custom Extended trigger classifier fix, HMGamepadState.Axes dict migration, layout-derived per-row axis map, plus the SideWinder profile + FFB hotfix chain.

  • DualSense 3D touchpad finger preview is now cropped to the real ~52x32 mm touch surface. Previously the finger sphere drifted past the visual edges and stopped well above the bottom because the inset constants were tuned for the smaller DS4 Screen mesh.

  • Web controller trigger z-order fixed. The trigger-base silhouette now sits behind the body PNG and the active press-fill draws in front, matching the desktop ControllerModel2DView. Xbox 360 and DualShock 4 web layouts now show the trigger bulges above the bumpers and the press fill animates correctly.

  • WPF-UI 4.3.0, click-outside clears keyboard focus at the window level, and diagnostic logs stripped across HM/FFB/DS5/Sony so only crash.log remains in normal operation.


Visualization

  • Per-profile 3D and 2D renders: DualSense, Xbox One S, and Xbox Series X (white) staged alongside the existing Xbox 360 and DualShock 4 artwork (a409b30).

  • Per-profile 2D/3D asset routing wires the new artwork into the model picker (3a4c159).

  • Xbox One/Series bumper position fix + DualSense touchpad preview added to the 2D layout (0298dd3).

  • Xbox One/Series press-overlay sizing tuned + synthesized DualSense touchpad mesh (fb14a79).

  • Visual analysis pass for Xbox 2D layout sizing + real DualSense touchpad mesh (729aa70).

  • Xbox Series LB/RB and d-pad tuned, DualSense bbox-fit + 3D scale, white touchpad (784fa00).

  • DualSense 3D scale moved to the parent level + Xbox Series d-pad edge anchoring (8eaf6fd).

  • 3D left-drag rotation restored, stick quadrants now use the mesh centroid (b52bcfa).

  • Quadrant wedge, flash arrow, and ring overlay also use the mesh centroid for consistent hit-testing (29e5874).

  • Start/Back/Bumpers snapped to actual rendered positions on each base PNG (074c6dd).

  • Xbox 360 triggers clipped above bumpers, Xbox One bumper highlight widened (d5e13de).

  • Xbox 360 triggers flushed to bumper top, Xbox One bumpers widened further (a2abd4d).

  • Visual analysis fix pass: Xbox One bumper width 0.27, drop the Xbox 360 trigger clip that was over-cropping (781baca).

  • Xbox One bumper width tuned + Xbox 360 trigger / stick fits (419ee9f).

  • 2D overlay polish: per-controller trigger and stick canonical positions (83f4971).


Xbox Series Share button

  • Share button wired end-to-end: HMButton.Share dispatch, gamepad source field, mapping grid row gated to xbox-series-* profiles, Map All opt-in, and 2D + 3D visual feedback. Non-Series Xbox profiles render the same mesh but leave the Share region inert (f2ffc32).


Adaptive TriggersLighting

DualSense / DS4 lightbar and rumble

  • HIDMaestro v1.3.5 migration brings new lightbar and mic-LED features; Sony rumble fix lands in the same wave (1eb3327).

  • Sony dispatcher fixes + profile-filter tightening + diag capture path (58b465a).

  • HIDMaestro v1.3.5 final release build integration (bf0ab5c).

  • Dead lightbar reset commands and strings removed after the per-channel UI rework (4c83af5).

  • DS4 virtual FFB gates the rumble-skip on active DS5 passthrough so DS4 virtuals always rumble (9d4142d).


SideWinder / PID force feedback

  • PID FFB detection fixed for descriptors with non-adjacent Usage Page (148bb45).

  • Microsoft-VID Xbox-rumble branches no longer steal SideWinder PID FFB (a4e9898, 8acf41b).

  • SideWinder FFB diagnostic: per-packet dispatch tag + Connect-time ffb-decoder log (dc27d75).

  • Decode SideWinder's short Set Effect / Set Periodic / no-Set-Effect Start path (8fd564e).

  • DecodeSetEffect: only read Gain/Direction at canonical offsets when len >= 21 (400e9c4).

  • PID FFB decoder parses descriptor for per-report magnitude scale (b068de9).

  • Per-RID magnitude format for periodic-style FFB reports (b165cce).

  • FfbTest expanded: Square / Triangle / Saw / conditions / Ramp coverage (a17c1a3).

  • FfbTest condition crash fixed + decoder ramp-on-periodic-RID misroute fixed (1605a7e).


HIDMaestro integrations (1.3.5 → 1.3.12)

  • v1.3.6: Custom-shaped trigger classifier fix (a85a5b9).

  • v1.3.7 (cfcd35d).

  • v1.3.8 (bef47e5).

  • v1.3.9: HMGamepadState.Axes dict migration on the PadForge side (b57d938).

  • v1.3.10: SideWinder profile hotfix (ad10b43).

  • v1.3.11: SideWinder force feedback hotfix (cf7c8cc).

  • v1.3.12: SideWinder force feedback hotfix (d982604).


Custom Extended layout

  • Use HMProfile.StickCount / TriggerCount for the Extended row count (75885b7).

  • Layout-aware Extended row count prefers HMProfile.Layout over the classifier (7b1c6f8).

  • Layout-derived per-row axis map for Custom Extended writes (34fb4fc).

  • Revert layout-wrapper helpers; use HMProfile.Sticks/Triggers directly (c349a1b).

  • Custom Extended triggers: pressed-wins merge + released-rest default (02945f6).


UI / framework

  • WPF-UI bumped to 4.3.0 (1edfa0f).

  • Click-outside clears keyboard focus at the window level (11f7a8d).

  • PadPage: sectional Reset All + consistent tooltip casing (ac5d097).


Diagnostics

  • Diagnostic logs stripped across HM/FFB/DS5/Sony; only crash.log remains in normal operation (82534cb).


Release plumbing

  • v3.1.4: bump version, refresh screenshots, README per-profile renders blurb. Adds capture_v3_1_4 + capture_v3_1_4_fix tooling for additive screenshot recapture (c99ba7e).



--3.1.3--

Per-device constant force, rumble macro actions, and a touchpad click bar. New continuous-force override on the Force Feedback tab with a 2D X / Y grid, two new rumble actions for the macro editor, a hold-to-click strip on the in-app touchpad overlay, and a stack of bug fixes spanning web controllers, FFB pass-through, and the test-rumble dispatch path.


Force Feedback — Constant Force per device (#29)

The Force Feedback tab now has a Constant Force card under the rumble card. A toggle plus a 200×200 X / Y grid drive a continuous force on the assigned physical device. Click or drag inside the grid to set the direction and strength of the force vector — center is no force, the edges are full strength, the angle of the dot from center sets the polar direction.

The force runs while the toggle is on, except when a game or program is sending its own non-zero force to that device-slot pair — game force always wins while it's active, then the constant force resumes the moment the game returns to silence (override-with-resume). Macro rumble layers via max() ahead of constant force, so a macro pulse behaves the same as game force for resume purposes.


Routing per device class:

  • FFB-capable devices (wheels, joysticks) receive a real DICONSTANTFORCE through the existing SetDirectionalHapticForces pipeline. Single-axis devices (wheels) project the angle onto the steering axis exactly like the DXSDK FFConst sample.

  • Rumble-only devices (Xbox-style pads, generic gamepads) get a quadrant motor mapping: |Y| drives the heavy / low-frequency motor, |X| drives the light / high-freq motor, with a half-bleed across so diagonals engage both.

  • Sony pads (DualShock 4 / DualSense / DualSense Edge) route through the UserEffectsDispatcher per-device rumble pump — the dispatcher remains the sole writer of effect packets to Sony pads, so this layers on top of game rumble and audio rumble without reintroducing any second writer.


Persists across PadForge restarts, per-device per-slot. The Motor Activity meter on the FFB tab reflects the constant force when it's the active source — what you see on the meter is what the device receives. Solves the wheel-mapped-to-virtual-Xbox-needs-centering case that's been tracked since the v2 era.


Macros — Rumble and Stop Rumble actions (#62)

Two new action types in the macro editor, parallel to the existing Lightbar Color / Lightbar Color Clear pair:

  • Rumble. Drives the slot's physical device rumble from a macro. Per-motor strength sliders (0–100% each, set one to zero to fire one motor in isolation) plus two hold modes: Reactive runs at full strength across the configured Hold window then linearly fades to zero across the Fade window (good for shoot / hit / confirm pulses), or Sticky holds at full strength until a Stop Rumble action runs (good for armed / engaged states).

  • Stop Rumble. Releases any active rumble override on the slot. Pair with a Sticky Rumble action to release a held rumble from another macro.

Layers over game-driven rumble via max() so user-driven feedback always reaches the motors. Sony slots route through the same dispatcher that drives game rumble (sole-writer model preserved); non-Sony slots take the SDL haptic / rumble path. Localized across all 10 shipped locales.


Touchpad overlay — hold-to-click bar

The in-app touchpad overlay (toggled by the ToggleTouchpadOverlay macro action) now has a 32 DIP click strip along the bottom of the window. Mouse-press or touch-hold sets state.TouchpadClick = true for as long as you hold, so click-drag patterns work — the overlay's existing surface double-tap pulse is momentary and could not. The strip has no label so it doesn't obscure whatever the overlay is sitting on; the semi-transparent tint is the affordance, brighter while held.

The overlay's legacy click path also moves off state.Buttons[20] (a v2-era click-as-numbered-button artifact) onto the canonical state.TouchpadClick field, so the overlay's click now auto-maps to PlayStation slots via the same "Touchpad 0 Click" descriptor the rest of the app uses.


Web controllers — layout-specific identity, touchpad routing, dropdown dedup

Several fixes for the browser-driven controller surfaces:

  • Xbox 360 and DualShock 4 web controllers no longer overwrite each other in the Devices list. The "BT reconnect" fallback in FindOrCreateUserDevice gated only on ProductGuid + offline state. Both web layouts shipped with the same static ProductGuid, so disconnecting one and connecting the other migrated the offline row's slot mapping onto the new device. ProductGuid is now derived from the layout key (xbox360 / ds4 / touchpad) so each layout reads as a distinct product.

  • DS4 web controller touchpad click auto-maps to PlayStation slots. The touchpad overlay zone in the controller page sent kind: button, code: 11 (the legacy click-as-numbered-button shape), so a DS4 web controller assigned to a PlayStation virtual controller never auto-mapped the click. Now sends type: touchpad, click: true/false so the click lands in state.TouchpadClick and the canonical "Touchpad 0 Click" descriptor resolves at runtime.

  • Mapping dropdown no longer shows touchpad inputs twice. WebControllerDevice.GetDeviceObjects() was publishing the touchpad finger axes (as bogus Axis 18..23) and a Touchpad Click button alongside the canonical "Touchpad 0 Finger N X / Y / Down" and "Touchpad 0 Click" descriptors. The list now exposes the gamepad-shaped surface only; touchpad inputs come from the canonical block.

  • Button count reads as 11, not 12. NumButtons and RawButtonCount no longer add +1 for HasTouchpad since the click rides its own state field.


The standalone touchpad page also gains a dedicated Click button beneath the touch surface so press / release pairs work for click-and-hold.


Bug fixes

  • Extended FFB toggle now works on customized catalog profiles. Most HIDMaestro catalog profiles ship without a PID FFB block. The customize-rebuild path was only rebuilding the descriptor when disabling FFB, so enabling FFB on a non-Custom profile left the descriptor FFB-less. The PID FFB packet decoder was also gated on _profile.VendorId == 0xBEEF (the synthetic Custom profile's VID), so customized catalog profiles couldn't run the decoder even after a rebuild. Both fixed: the rebuild always carries the FFB block when the toggle is on, and the decoder gates on whether the descriptor itself contains the canonical PID FFB block signature instead of on VID.

  • Tab visibility refreshes after in-place device-list swap. With multiple devices on a slot, unassigning the selected one slid the next device into the same MappedDeviceInfo object via in-place mutation, so SelectedMappedDevice PropertyChanged didn't fire and the FFB / Lighting / Adaptive Triggers tabs stayed pinned to the previous device's capabilities until the user manually toggled the dropdown.

  • Test rumble on DS5 / DS4 stops at 500 ms instead of 6–8 s. The dispatcher's polling-thread stop path in UpdateAnimTimer was calling StopAnimTimer() without first dispatching a final snapshot. When the test-rumble clear timer zeroed the motors, the controller never received a "rumble = 0" packet and kept rumbling until something else triggered a dispatch. Now mirrors the OnAnimTick early-exit's final-snapshot behavior on the polling-thread stop path.

  • PlayStation 3D touchpad finger map raised at the bottom edge. zBottomInsetFrac bumped from 0.08 to 0.12 (about 3.9 mm above the mesh's bottom edge instead of 2.6 mm) so a touch at normY = 1 lands where a real DS4 v2 finger sits at the bottom of the touch surface. Top boundary unchanged.

  • PadPage tab and card icons refreshed. Force Feedback tab + Rumble card now use Segoe MDL2 E877 (Vibrate). Constant Force card uses F0AD. Lighting tab uses E781 (Lightbulb). Indicator Lights card uses EF31. Replaces the custom inline Path data that used to ship for those tabs.


Localization

All new strings (Constant Force card on the FFB tab, rumble macro action editor) localized across the 10 shipped locales: de, es, fr, it, ja, ko, nl, pt-BR, zh-Hans. The rumble hold-mode dropdown values mirror the lightbar Reactive / Sticky wording per-locale so the two action families read consistently.



--3.1.2--

Adaptive Triggers — Load GameCube preset (#78)

When the active mode on a DualSense / DualSense Edge slot is set to Weapon, a row labeled "Load GameCube preset" appears below the Mode dropdown. Clicking it loads:

  • Start = 0x90 (≈ 56 % of trigger pull)

  • End = 0xA0 (≈ 63 % of trigger pull)

  • Strength = 0xFF (max force)


The byte values match the GameCube presets shipped by Mxater's DualSenseSupport and DualSenseY-v2. The synthesizer's Weapon branch passes the slider values directly through to bytes 1/2/3 of the per-trigger effect block, so what the firmware applies matches the verified click-feel of a real GameCube trigger: smooth pull through ~56 % of travel, resistance ramp from 56 % to 63 %, hard wall past that with maximum force.


It's a one-click loader, not a lock — the sliders stay editable afterwards, so you can tweak any of the three values to taste.

For a full hybrid analog-then-digital trigger workflow (firmware click resistance from this preset, plus a digital button activation past the click point), pair this with the trigger ceiling control on the Triggers tab and an axis-to-button-deadzone mapping on the Mappings tab.


Localized across all 10 shipped locales (de / es / fr / it / ja / ko / nl / pt-BR / zh-Hans).



--3.1.1--

Lighting tab — per-(slot, device)

Two physical Sony pads on the same virtual controller slot can now have different lightbar modes, different palettes, different Input Reactive variants. The Lighting tab follows whichever device is selected in the assigned-devices dropdown, exactly the way the Mappings, Sticks, Triggers, and Force Feedback tabs already worked.

Macro lightbar actions stay slot-level — a macro that switches the lightbar to Rainbow fans out across every Sony pad on the slot, each rendering Rainbow with its own configured period and brightness.


Input Reactive overlay

The three Input Reactive variants (random hue, palette cycle, fixed color) are now a separate overlay dropdown rather than base lightbar modes. Pick any base — Static / Rainbow / AudioPulse / Off — and layer Input Reactive on top. Each button press flashes the reactive color at full intensity and lerps back to the base across the configured Hold + Decay window.

The fixed-color variant has its own RGB picker, separate from the base color, so a Static-blue base + white per-press flash is one of many combinations that previously couldn't coexist.

Migration is automatic: old LightbarMode = InputReactive* saves migrate to LightbarMode = Off + the corresponding overlay variant; the visual result is unchanged.


Macro lightbar actions (#63)

Four macro action types for lightbar control:

  • LightbarColor — push an RGB triple as a temporary override on every Sony pad on the slot. Two hold modes:Reactive — flash at full intensity, decay over the configured fade-out.
    Sticky — hold the color until the next clear or another macro override.

  • LightbarColorClear — release any active macro override, restoring the configured base mode.

  • LightbarModeSet — set the slot's base lightbar mode (with Input Reactive variants auto-translating into the new overlay).

  • LightbarModeCycle — step through a configured list of base modes per macro fire.


Macro overrides beat both the base mode and the Input Reactive overlay; game-driven writes still win at packet level via the existing passthrough path.


DualShock 4 audio lightbar (#55)

DS4 picks up the same audio-driven lightbar logic DualSense had: Audio Pulse (static / random / rainbow), Audio Bands (hard / smooth / crossfade), and the Input Reactive overlay all run on DS4 too, with full USB Report 0x05 + BT Report 0x11 + CRC32 packet generation through the dispatcher's raw-HID path.


Audio rumble — sole-writer architecture

The audio-rumble + animated-lightbar interaction in v3.1.0 produced a 30 Hz motor stutter that users perceived as weak rumble. The cause was two simultaneous writers (PadForge's effect-packet dispatcher and SDL3's SDL_RumbleJoystick) competing through separate HID handles on an asynchronously-sampled audio peak — they always disagreed at sample time, and the firmware applied "whichever WriteFile lands most recently."

v3.1.1 replumbs DS5/DS4 rumble around a single writer. InputManager.Step2.ApplyForceFeedback now skips Sony VID 0x054C / DS5 / DS4 PIDs entirely; UserEffectsDispatcher writes the entire effect packet (rumble + lightbar + adaptive triggers + mic LED) every 33 ms, with rumble bytes computed per-device via the same audio-mix + gain math SDL used to do. One writer, no race.


A polling-thread "rumble poke" keeps the dispatcher's animation timer alive whenever audio rumble is enabled or game rumble is in flight, even with a static / off lightbar — so audio rumble works the same with or without an animated mode running concurrently.


Per-(slot, device) Force Feedback

Different physical Sony pads on the same slot can now carry different gain settings — the FFB tab follows the selected device, same as Lighting. Test rumble routes only to the selected device instead of fanning out across every Sony pad on the slot.


Keep devices cloaked between launches (#75)

New Settings toggle under the existing "Hide devices from games" master switch. Off by default (PadForge clears its HidHide cloaks at shutdown). On: cloaks persist between PadForge sessions, so processes scanning for controllers while PadForge is closed (Steam launching after PadForge exits, for instance) still see the physicals as cloaked. The runtime master toggle is unchanged — flipping it off mid-session still decloaks immediately.


Other fixes and polish

  • Slot delete now clears the per-device Lighting configs in the deleted slot's PadViewModel, so re-mapping the same physical device to a freshly-created slot starts from defaults instead of resurrecting the old slot's lightbar mode.

  • Settings save reliability: per-device Lighting settings used to bleed across devices on the slot due to a load-time fan-out; v3.1.1 only fans out for genuine pre-v3.1 saves with no per-device entries. Lighting tab edits now persist correctly across app restarts.

  • Touchpad click participates in Input Reactive — pressing the DS5 touchpad now fires a reactive pulse, alongside the standard button presses.

  • Inactivity timeout label cleaned up — was "Inactivity destroy timeout", now just "Inactivity timeout"; trailing colons stripped from Polling interval / Inactivity timeout in all 10 locales (matches the existing pad-config-tab style).

  • Localization pass: 10 new strings translated across de / es / fr / it / ja / ko / nl / pt-BR / zh-Hans for the Input Reactive overlay, the per-press flash color, and the cloaks toggle.

  • Code audit: 40 build warnings cleared (CS0168 / CS0108 / CS0420 / CS9191 / CA2014). No behavioral changes — exception-variable cleanup, private-const renames that stop shadowing inherited members, redundant volatile keyword drops on fields that already use Volatile.Read/Write, an in-arg modifier fix on a Marshal.QueryInterface call, and a stackalloc hoisted out of a per-slot loop.



--3.1.0--

DualSense, fully wired. 

Two new tabs on PlayStation-class slots with a DualSense or DualSense Edge assigned: Adaptive Triggers (seven effect modes with a live profile-preview graph) and Lighting (thirteen lightbar modes plus a separate Indicator LEDs card for the player row, mute LED, and brightness). Game-driven trigger and rumble effects forward through automatically; user settings apply when no game is writing. Plus a HIDMaestro bump, a tab UI pass that retints with light/dark, and 128 new strings localized across all nine non-English locales.


Adaptive Triggers tab

Visible only on slots with a DualSense or DualSense Edge assigned. Each trigger gets a mode dropdown, a live effect-profile graph that draws the resistance or amplitude shape across the trigger pull, Range / Strength / Frequency sliders, and per-parameter reset buttons.

Seven modes, mapping directly to the firmware's ScePadTriggerEffectParam types:

ModeWire opcodeShapeOff0x00Empty trackFeedback0x01Solid bar from start position to fully pressedWeapon0x02Resistance through the soft zone, click at endVibration0x06Continuous sine wave from start onwardMulti-Position Feedback0x21Alternating bumps inside [start, end]Slope Feedback0x26Triangular ramp from start to end, held past endMulti-Position Vibration0x25Stuttering sine bursts inside [start, end]

The 0x21 / 0x25 / 0x26 opcodes use Sony's official 10-zone bitmap encoding. The preview graph re-renders as you drag the sliders, so what you see is what the firmware will receive.

Game-driven trigger effects pass through unchanged via a separate dispatch path. The user-settings layer fires only when no game is driving the trigger.


Lighting tab

Visible only on slots with a DualSense or DualSense Edge assigned. Thirteen lightbar modes:

ModeBehaviorOff (Game Owns the Lightbar)Default. Only game writes drive the lightbar.Static ColorSingle user color.BreathingSingle color, sinusoidal fade.Rainbow CycleHSV sweep across the full hue wheel.Color CycleSteps through the configured palette; smooth-blend toggle.Audio Pulse — Static ColorBrightness modulates with system audio peak.Audio Pulse — Random Color per BeatNew hue on each beat detected.Audio Pulse — Rainbow CycleRainbow with audio-driven brightness.Audio Bands — Three Colors, Hard TransitionsQuiet / Medium / Loud, instant switch at thresholds.Audio Bands — Three Colors, Smooth GradientLinear interpolation between thresholds.Audio Bands — Three Colors, Crossfade at BoundariesConfigurable crossfade width.Input Reactive — Random Color per PressEach button press flashes a fresh hue, decaying linearly.Input Reactive — Cycle Through PaletteEach press steps to the next palette color.

Variable-length palette with per-entry color picker (hex + RGB sliders + reset), per-band thresholds, crossfade width, sensitivity, pulse decay. A separate Indicator LEDs card covers the player-pattern row, mute LED mode, and brightness. Every dropdown in the tab has a one-click reset button to its default.

Lightbar packets ship via raw HID using OpenRGB's packet shape, not SDL3's joystick rumble path. Bit 5 of byte 43 (the no-fade flag) is what makes the firmware skip its boot/connect fade and apply the requested state immediately. Without it, late-connect packets visually lose to the firmware's default state.


Game-driven passthrough

Two paths cover the DualSense / DualSense Edge surface:

  • Game → physical device. Trigger effects, rumble, and lightbar writes from the running game forward to the assigned source device exactly as the game intended, with no interpretation or remap.

  • User settings → physical device. Adaptive Triggers and Lighting tab settings apply only when the game isn't writing to that specific surface (per-trigger and per-lightbar). User and game layers don't fight.

Player pattern, mute LED, and brightness write independent of the lightbar, so a "lightbar off but show me the player number" config works.


HIDMaestro 1.3.4

Bumped from 1.3.2 (shipped with v3.0.7). Picks up upstream catalog updates and minor PnP-walk hardening. No PadForge-side behavior change required.


Tab UI overhaul

Page-header + bordered sub-section style for Sticks / Triggers / Force Feedback / Adaptive Triggers / Lighting, matching the Settings and Dashboard surfaces. Tab-strip glyphs replaced with DrawingImage SVG paths so the icons retint cleanly on a light/dark switch. New labeled-stick / labeled-trigger icons by Zacksly (CC BY 4.0, attribution in Resources/ZackslyIcons/ATTRIBUTION.txt).

The Extended schematic preview now uses SetResourceReference for its brushes, so a theme switch retints the schematic instead of leaving it stuck on whatever theme was active when the slot was created.


Localization

128 new Adaptive Triggers + Lighting strings, fully translated across de, es, fr, it, ja, ko, nl, pt-BR, zh-Hans. Each locale matches its native convention rather than mirroring English Title Case verbatim: German keeps noun capitalization, French / Italian / Spanish / Dutch / Brazilian Portuguese drop to sentence case for body labels, Japanese / Korean / Simplified Chinese have no case to apply.

Brazilian Portuguese also got a Title Case pass on its setting titles to match the rest of the app: Brilho do LED:, Padrão do Jogador:, Modo do LED de Mudo:. Gatilhos Adaptativos (tab name + page header) and Force Feedback (tab vs. page header consistency) corrected too.


Other fixes and polish

  • Slot delete clears macros. Previously, deleting a virtual controller and creating a new one in the same slot left the deleted slot's macros attached to the new VC. Macros are bound to the slot, not the physical device, so they now clear on delete.

  • Devices page ordering. Device badges and the slot selector follow the type-group order (Xbox group → PlayStation → Extended → KbM → MIDI), not slot-creation order, so reorders don't shuffle the visible numbering.

  • Audio capture gating. WASAPI capture is gated on LightbarMode rather than the legacy "audio rumble enabled" bool, so audio modes start and stop cleanly without depending on Force Feedback state.

  • PlayStation config persistence. AT and Lighting settings persist across slot teardown, so reconnecting the same physical DualSense doesn't lose the configuration.

  • DS5 reconnect. Per-device retry burst extended to 15 s to cover the BT LED reset window on initial pairing.



--3.0.7--

Xbox slot teardown — 5,700 ms → 135 ms (HIDMaestro 1.3.1)

DeviceManager.RemoveDevice previously tore down HID children before closing the SwD parent. Children of an SwD parent can't fully unwind while the parent's HSWDEVICE refcount is held, so each WaitForDeviceRemoval call hit its 2,000 ms timeout and the cumulative budget for an Xbox 360 wired teardown landed near 5.7 s.


HM 1.3.1 closes the SwD parent first and blocks on CM_NOTIFY_ACTION_DEVICEINSTANCEREMOVED for the parent. The parent close takes ~55 ms and cascades the children automatically — net ~135 ms. Phantom-only registry residue (entries with no live device) skips the hmswd.exe round-trip entirely, saving ~50-75 ms per stale entry and preventing creep across same-process recreations.

This change carried through PadForge v3.0.6 and is the foundation the v3.0.7 create-side fix builds on.


Xbox slot-claim wait cap — 15 s → 500 ms (HIDMaestro 1.3.2)

The XInput slot-claim wait inside HM's SetupController was budgeted at 15 s. That sounds defensive until you realize the slot-claim distribution is bimodal:

  • Working case: the new slot publishes in under 100 ms. Typical: ~65 ms for Xbox 360 wired, ~3-10 ms for Xbox Series BT.

  • Stuck case: kernel state issue, prior-session residue, or xinputhid recovering from a recent unbind. The slot never publishes, and the old budget sat through the full 15 s before logging the failure.


So the budget had no working-case payoff and only ran to completion on stuck-allocator failures. PadForge users observed 13-14 s freezes on a single Xbox Series BT create when this hit.

HM 1.3.2 caps the wait at 500 ms — about 5× the slowest healthy claim observed. Slow-but-working creates still finish well inside their headroom; the stuck case degrades to a near-imperceptible pause that gets logged and falls through to ordinary creation. Validated empirically against an Atom x5-Z8350 + eMMC stick PC running Windows 10 IoT Enterprise LTSC — both the worst hardware we have on hand and realistically the worst we're likely to ever test against, since anything slower than the Z8350 generation predates the platform's minimum hardware requirements. Xbox VC create, swap, and bubble-down cascade-recreate all feel instantaneous now.


Bubble-down cascade now uniform across Xbox / PlayStation / Extended

Earlier v3.x builds limited the cascade to Xbox slots only and only fired it on full slot deletion or on the HM inactivity timeout (default 60 s). Two gaps:

  • PlayStation and Extended observe creation order too. DirectInput, SDL, and raw HID all enumerate devices in creation order. A PS or Extended slot at position 1 going non-active without a cascade leaves survivors at positions 2+ holding their original kernel slots — games that bound to "controller N" by enumeration index see the wrong device. Same shape as Xbox via xinputhid, just a different external observer. The cascade now fires uniformly across all three HM subgroups.

  • Sidebar disable and "all devices unassigned" weren't triggering it. Toggling a slot off via the sidebar power dot, or clearing the slot's mapping panel, would destroy the live VC immediately (no grace period — these are deliberate user actions) but skip the cascade. Surviving slots at higher positions held their kernel slots until the user manually reordered or restarted. Both paths now fire the cascade synchronously alongside the destroy.

  • Bonus: a position-vs-raw-index bug in the OnSlotDeleted cascade loop is fixed. Previously, after a like-to-like swap that reshuffled the group's pad-index ↔ position correspondence, deleting a slot could over-destroy lower-position-but-higher-pad-index siblings and miss higher-position-but-lower-pad-index slots that needed to bubble.


The architecture rule (j) added to InputManager.Step5.VirtualDevices.cs documents the all-HM-subgroups cascade scope as the source of truth.


Extended config bar refreshes on profile switch (#73)

ApplyProfile writes the active slot's ExtendedSlotConfig.Customize, OemNameOverride, and ProductString directly when the user reloads a saved profile. The Extended config bar's CheckBox / TextBox controls used an imperative two-way sync that only re-read the model on the slot-load, slot-switch, and OutputType-change paths — none of which fire when an active Extended slot's config gets mutated under it by a profile reload. Engine state was correct (the game saw the right Product String per profile); only the UI display went stale.

PadPage now subscribes to ExtendedSlotConfig.PropertyChanged on the active config instance and re-runs the imperative sync when Customize, OemNameOverride, or ProductString mutates externally. The _syncingExtendedConfig guard prevents the sync from cycling back through the UI events. Count fields (sticks, triggers, POVs, buttons) keep their existing sync — they pull from the active HM profile rather than ExtendedConfig directly and refresh through the ProfileId PropertyChanged trigger.



--3.0.6--

Pulls in HIDMaestro 1.3.0's perf and durability work atop v1.2.2's locale-stable pnputil parsing. No PadForge-side code changes — straight DLL swap and version bump.


What HIDMaestro 1.3.0 changes

  • Filesystem fast-path on IsHidMaestroDriverInstalled — skips the full pnputil enumeration when the driver INFs are already in the store.

  • Parallelized HMContext constructor prewarm tasks.

  • Skip the GIP-buffer pack/copy in SubmitState and SubmitRawReport for non-Xbox profiles. PlayStation and Extended don't use that surface, so the work was wasted on every frame.

  • Persistent diag and timing-log StreamWriters (was File.AppendAllText per call).

  • Thread-local reusable buffers in GetHidChildId and GetDeviceId to drop per-call allocations.

  • Tighter SwdDeviceFactory.Remove hmswd timeout (30 s → 8 s).

  • Per-hwId DeviceOverrides write dedup in CreateDeviceNode.

  • WriteBits byte-aligned fast path; button-mask bit-pop loop replaces a 32-iteration scan.

  • TimeoutScale.Apply scaling for devcon WaitForExit budgets.

  • Driver INF version bump.


The result on PadForge's side is faster slot create/destroy and lower CPU during steady-state polling, particularly when many HM virtuals are active simultaneously.



--3.0.5--

"Start at login" toggle now actually works

Since v3.0.0 began requiring elevation (auto-elevation for vJoy / HIDMaestro driver lifecycle), the Settings → "Start at login" toggle silently no-op'd. The prior implementation wrote HKCU\Software\Microsoft\Windows\CurrentVersion\Run, which Windows can't use to launch elevated apps — there's no way to show a UAC prompt at the login screen, so the entry just gets dropped.


PadForge now registers a Task Scheduler task with /SC ONLOGON /RL HIGHEST. Task Scheduler is a trusted launcher so it can start elevated apps at logon without user interaction. Driving schtasks.exe strictly by exit code — no parsing of human-readable output — keeps it locale-independent (same lesson as the v3.0.4 hotfix).


Existing users who had the (broken) HKCU\Run toggle on under v3.0.0 through v3.0.4 get auto-migrated on first launch of v3.0.5: PadForge spots the legacy registry entry, creates the working scheduled task in its place, and removes the old entry. No re-toggle needed.


Two same-model Xbox Series Bluetooth pads no longer merge into one card

Pairing two Xbox Series BT controllers at the same time previously collapsed them to a single Devices-page card with the live preview alternating between both physical inputs every couple of seconds.


Root cause was identity collision in BuildInstanceGuid's XInput-path branch. StableXInputInstance.Find returned the first non-HM HID instance for the given VID/PID, so both pads got the same physical path, the same MD5, and the same InstanceGuid. The polling loop then alternately overwrote that single Pads slot from each SDL device.


The XInput-path branch now retrieves every matching non-HM HID instance (sorted lexicographically for determinism across reboots / SetupDi enumeration changes), parses the slot index from SDL's XInput#N path, and picks the candidate at that index — appending :slot{slot} to the identifier when more than one candidate exists so two pads can never hash to the same GUID. Single-pad users see no change to their existing InstanceGuid.



--3.0.4--

Hotfix: PadForge v3.x.x failing to launch on non-English Windows (issue #69)


The v3.0.0 → v3.0.3 builds did not survive a relaunch on French / German / Japanese / etc. Windows installs. After a clean first run that successfully created a virtual controller, subsequent launches would either show the process briefly in Task Manager and exit, or sit running with no GUI.

Root cause was upstream in HIDMaestro (its issue #17, reported by @d3xMachina on Win 11 24H2 French). The HM SDK parsed pnputil.exe output by matching English-literal strings (Published Name, Driver package added successfully). Localized Windows installs print the same fields in the system locale, so PnputilHelper.IsHidMaestroDriverInstalled returned false even when the driver was already in the store. SetupController then ran FullDeploy per controller, the second deploy hit the localized already installed path the English greps couldn't recognize, and HM v1.2.0's throw-on-failure converted that into a hard crash before PadForge's UI could come up.

HIDMaestro 1.2.2 ships locale-stable parsing: pnputil /enum-drivers /format xml for enumeration (XML element names are pnputil's own identifiers, locale-stable), and a post-condition store check after /add-driver instead of grepping its rc/output. PadForge v3.0.4 picks up the new HM Core DLL.

If you hit issue #69 on v3.0.3, this build fixes it without any settings or registry cleanup on your end.


Other changes since v3.0.3

Touchpad click: first-class Touchpad 0 Click descriptor

The DualSense / DualShock 4 touchpad click is now mapped through a dedicated Touchpad 0 Click descriptor instead of being shoved into a numeric Button N slot. Sits parallel to the existing Touchpad 0 Finger M X/Y/Down descriptors that already covered the touchpad surface coordinates.

  • Auto-mapping picks it up out of the box on DualSense / DualShock 4.

  • MapToButtonPressedSingle recognizes the prefix and routes to state.TouchpadClick (single bool — Sony hardware ships one touchpad per controller, so the N is always 0).

  • Mapping table shows live state for the descriptor.

  • Devices preview tints the touchpad surface border on press so you can see when the click registers.

  • Click-to-remap on a mapping cell now detects a touchpad press and writes Touchpad 0 Click directly. Previously did nothing because the recorder only scanned the numeric buttons array.


DualSense BT: touchpad click + HidHide

Two BT-side regressions cleaned up:

  • HidHide blocking now matches BT Classic DualSense paths. The validator was rejecting VID&0002XXXX interface paths because it required a literal VID_ prefix; relaxed to accept the VID& form too.

  • Touchpad click on the BT profile now actually fires descriptor button 14. The USB DualSense path drives byte 9 of Sony Report 0x01 directly through SonyReportPackers, but the BT profile has no raw packer registered — so touchpad click silently dropped. Step 5 now ORs Gamepad.TOUCHPAD into the gamepad-state mask for PlayStation slots when the touchpad is clicked, and HMaestroVirtualController.MapButtons translates that to HMButton.Touchpad so HM's buttonMap lands it on descriptor button 14 for both BT and USB DualSense / DS4 profiles.


DS5 USB packer: counter no longer bleeds into byte 9 button bits

Byte 9 of the DualSense USB input report (rgucButtonsAndHat[2]) is entirely button bits per SDL_hidapi_ps5.c: bit 0 PS, bit 1 Touchpad click, bit 2 Mute, bits 4-7 DualSense Edge function / paddle buttons. The packer was OR'ing (frameCounter >> 8) & 0x3F into the low six bits, so the PS and touchpad-click flags flickered as the counter rolled through the report at the polling rate (PS at ~4 Hz, buttons 14/15 cycling 00→10→01→11 at ~2 Hz). DS4 has its own counter packed into bits 2-7 of its byte 7; that does not apply to DS5.


Standardized button positions extended to 21 (Misc 1, paddles, Misc 2-6)

PadForge button positions 0-10 stay as the standard XInput layout (A/B/X/Y, LB/RB, Back/Start, LS/RS, Guide). Positions 11-20 are now wired to SDL3's extended gamepad button enum in Xbox Elite paddle order (P1=R-upper, P2=R-lower, P3=L-upper, P4=L-lower).


Mapping table shows the localized name for each (Misc 1, Right Paddle 1, etc.) across all nine languages. Touchpad click stays on its own Touchpad 0 Click descriptor — not a numeric Button slot — to keep parity with the existing touchpad coordinate descriptors.


Devices preview only shows buttons the device actually has

Every SDL3-recognized gamepad previously reported NumButtons = 21, so the Devices preview lit up 21 button circles for plain Xbox 360 / DualShock 4 controllers and the row summary always read 6 axes, 21 buttons, 1 POV(s). The extended slots only exist on Xbox Elite / DualSense Edge / etc.

ISdlInputDevice.SupportedButtonIndices now exposes the sparse list of button positions a device actually has — SDL_GamepadHasButton for positions 11-20, plus any raw-passthrough indices ≥ 21 not already consumed by the gamepad mapping. The preview circles, the per-frame IsPressed update, and the row's button-count column all read from this list. ForceRawJoystickMode bypasses the gate so you still see every native HID button when you flip that toggle.


HM 1.2.1 PXN VD6 profile

PXN VD6 wheel profile bundled with HM 1.2.1 (separate from the locale-stable parsing fix in 1.2.2 above).



--3.0.3--

Force Feedback tab on Keyboard+Mouse and MIDI slots (issue #54)

The Force Feedback tab is now shown for every VC type, not just Xbox / PlayStation / Extended. K+M and MIDI virtual controllers don't emit FFB upstream to games (no HID PID endpoint), but the tab is still meaningful: audio bass rumble feeds into the same combined-vibration buffer Step 2 sends to the physical mapped device, and ForceFeedbackState.SetDeviceForces applies the tab's overall gain, per-motor strength, and swap-motors scaling on its way out. Test Rumble fires the physical device's haptic surface directly, so it works regardless of VC type. The full tab makes sense as the layer audio rumble passes through on every slot type, not just the FFB-capable ones.

The Sticks tab still hides for MIDI; the Triggers tab still hides for K+M and MIDI — those map onto axes the K+M and MIDI mapping surfaces don't have.


Extended config bar: Rumble → Force Feedback

The "Rumble" checkbox in the Extended config bar is now labeled "Force Feedback" across all nine locales. The previous label undersold what the checkbox actually controls — the HID PID 1.0 force-feedback descriptor block, which covers constant, ramp, periodic (sine/square/triangle/sawtooth), and condition (spring/damper/friction/inertia) effects. "Rumble" is a strict subset.


The checkbox is now functional: toggling it actually drops or restores the PID descriptor block on the live device. Games that probe for PID FFB stop seeing the device's FFB capability when the checkbox is off. Useful for steering wheels whose own hardware drives FFB and you don't want games trying to drive both surfaces simultaneously. Customize-gated, same shape as the OEM Name Override and Product String overrides above it: the stored value persists across Customize toggle off/on so flipping Customize back on restores the user's setting; while Customize is off, the engine sees the catalog default regardless of any sticky non-default value.

Behavior table: Customizestored FFBresult offanycatalog profile as-is, PID block includedontruecatalog profile as-is, PID block includedonfalsecustom descriptor, PID block dropped


Settings persistence: three round-trip fixes

A codebase audit found three places where state was being silently dropped during settings save / profile switch / file load. All three are one-line patches in the persistence layer.


FFB toggle dropped from default profile on save. SettingsService.BuildAppSettings builds the AppSettings snapshot for the default profile inline, separate from the named-profile path. The inline initializer was missing ForceFeedbackEnabled = cfg.ForceFeedbackEnabled. Result: any user who set Customize=true, ForceFeedbackEnabled=false on the default profile would have FFB silently flipped back to true on the next save+reload, because ExtendedSlotConfigData.ForceFeedbackEnabled defaults to true on XML deserialization. Named profiles were unaffected — only the default profile took the broken path.


Touchpad overlay state dropped on profile switch. InputService.SnapshotCurrentProfile and the field-copy block in SaveActiveProfileState both omitted all seven EnableTouchpadOverlay / TouchpadOverlayOpacity / TouchpadOverlayMonitor / TouchpadOverlayLeft / Top / Width / Height fields, while ApplyProfile reads them. Symptom: switching profiles wiped overlay enable, position, opacity, and size to ProfileData defaults regardless of the live state. The on-disk XML round-trip via UpdateActiveProfileSnapshot was already correct; only the runtime profile-switch path was leaking the values.


UserSettings.Items.RemoveAll outside the SyncRoot lock. SettingsService.LoadFromFile purges orphaned MapTo<0 entries left by old PadForge.xml files. The purge ran outside the lock that the polling thread takes for FindByInstanceGuid / FindByPadIndex. Reload() from the UI menu can fire while the engine is running, putting List<T>.RemoveAll and a concurrent foreach on the same backing list — undefined behavior. Wrapped in the existing SyncRoot lock.


Notes on the audit

The audit also confirmed several categories are clean:

  • No bare locale-sensitive .ToString() calls on doubles in the settings serialization path (issue #50 sweep is holding).

  • All XAML DataTrigger Value= strings use the v3 enum identifiers (Xbox / PlayStation / Extended); no leftover Microsoft / Sony from the rename pass.

  • ControllerModel{2D,3D}View / MidiPreviewView / KBMPreviewView correctly pair Bind() / Unbind() for both CompositionTarget.Rendering and PropertyChanged subscriptions; no event-handler leaks.

  • DriverInstaller.UninstallViGEmBus and UninstallVJoy are kept on purpose for the v2→v3 migration wizard; not dead code.

  • No live // TODO / // HACK / // FIXME markers across the codebase.



--3.0.2--

Profile switching: HM identity per slot

v3.0.1 carried two latent bugs in the runtime profile-switch path that together broke per-slot HIDMaestro identity tracking across high-level profiles.


HM profile slug stuck across profile switches. InputService.ApplyProfile re-set SlotCreated, SlotControllerTypes, Extended/MIDI configs, slot orders, and DSU/web/overlay state — but never touched the per-slot HM profile slug. Switching from a profile with xbox-one-s-bt to a profile saved with xbox-series-xs-bt left the slot stuck on xbox-one-s-bt. The save side compounded it: SnapshotCurrentProfile and SaveActiveProfileState both stripped SlotProfileIds out before storing, so each switch wiped the saved slug from the outgoing profile too. v3.0.2 wires both paths end to end. Step 5's per-slot diff at InputManager.Step5.VirtualDevices.cs:514-527 already destroys + recreates only the slots whose desired slug differs from the live HMaestroVirtualController.ProfileId — pointer-survival on slots that share a slug across profiles is automatic.


Spurious teardowns on profile switch. 

Device-assignment apply used a reset-then-rebuild shape: every us.MapTo = -1 in one phase, reapply from profile.Entries in the next. The lock spans both phases but Step 5's IsSlotActive / HasAnyDeviceMapped reads don't take that lock. The polling thread could observe the reset window and fall into the immediate-destroy path at Step 5:590-600 — slots whose mapping is unchanged across profiles still got destroyed and recreated, including kernel-slot reallocation and the Xbox bubble-up cascade. v3.0.2 transitions each UserSetting directly old → new in a single pass, so identical-mapping slots ride through the switch with zero teardown.


One-time migration note. Profiles saved by v3.0.0 / v3.0.1 may have null SlotProfileIds arrays on disk. The first time you switch away from such a profile after upgrading, the new save path populates the array correctly. To force a populate on a specific profile: switch to it, set the HM slug you want, switch away. Round-trip once and the array is permanent.


Other

  • Wiki updated for the corrected ApplyProfile shape and SlotProfileIds round-trip.

  • Stragglers from the v2→v3 enum rename caught earlier this cycle (Xbox type-switch button highlight, Pads-page Xbox icon) ship in v3.0.1 — listed here for completeness.



--3.0.1--

The original v3.0.0 build wiped a virtual controller's slot configuration from PadForge.xml when its mapped devices stayed offline past the HM inactivity destroy timeout (default 60 s). Returning to PadForge after a long away-from-keyboard found the affected slots gone from the file. v3.0.0 has been pulled. v3.0.1 fixes the timeout to tear down the live HM controller only — the slot, mappings, profile, and per-group ordering are all preserved end-to-end, and the VC recreates automatically when devices return online. The sidebar power dot also stays green during the grace window now (it was flipping yellow the moment a device went offline). Two stragglers from the v2→v3 rename pass were caught in the same commit: an Xbox type-switch button highlight and an Xbox icon on the pad page that both still tested OutputType=="Microsoft" after the enum was renamed to Xbox. v3.0.0 release notes follow unchanged below.


Powered by HIDMaestro

Until v3, every Windows virtual-controller stack picked a side. ViGEmBus handled Xbox and DualShock 4 outputs. vJoy handled generic DirectInput sticks. Anything in between — a real Thrustmaster T300 with the actual T300 HID descriptor, an 8BitDo dance pad with the actual 8BitDo descriptor, a custom 8-axis 128-button rig — needed a third tool, or wasn't possible at all.

HIDMaestro takes both sides at once. 225+ device profiles, every one a real HID descriptor captured from real hardware. Xbox 360, Xbox One, Xbox Series, Elite, Adaptive. DualShock 3, DualShock 4, DualSense, DualSense Edge. Logitech wheels. Thrustmaster wheels and HOTAS rigs. Fanatec ClubSport. Hori fight sticks. 8BitDo gamepads. Dance pads. Racing wheels. Plus a synthetic Custom profile for fully custom HID descriptors with VID:PID, product string, and OEM name table override.


One driver. Every shape. User-mode UMDF2, installs without a reboot. The HIDMaestro SDK ships embedded inside PadForge.exe; the driver itself self-installs on first virtual-controller creation. Nothing to download, nothing to run manually.


This is the largest architectural change in PadForge's history. Every virtual-controller code path was rewritten on top of HIDMaestro's SDK. Two drivers became one. The "vJoy phantom controller" bug class is gone. The driver-install dialog and uninstall guards for the virtual-controller backend are gone — HIDMaestro is just there.


HIDMaestro is open source under MIT. Even if I close my IDE for the last time tomorrow, get hit by the proverbial bus, or otherwise meet an untimely demise, the community never has to fall back to ViGEmBus or vJoy again. HIDMaestro alone covers everything.


If you're upgrading from PadForge v2, the legacy driver cleanup dialog handles ViGEmBus and vJoy uninstall for you on first launch. Your PadForge.xml loads unchanged: the v2 on-disk enum strings (Microsoft, Sony, VJoy) round-trip to the v3 in-code identifiers (Xbox, PlayStation, Extended) via XmlEnum attributes.


One driver, every kind of controller

The Add Controller popup gives you five categories: Xbox, PlayStation, Extended, Keyboard+Mouse, MIDI. The first three route through HIDMaestro. Keyboard+Mouse uses Win32 SendInput and ships built in; MIDI uses Windows MIDI Services.


Per slot, a profile picker lets you choose any of HIDMaestro's 225+ profiles. The Xbox category surfaces Microsoft-vendor profiles. The PlayStation category surfaces Sony-vendor profiles. The Extended category gets everything else, plus the synthetic Custom profile for from-scratch HID descriptors.


Up to 16 simultaneous virtual controllers, mixed types. The XInput visibility cap of four is a Microsoft API limit (XInputGetState addresses user-indexes 0–3 only), not a backend ceiling. Slots beyond four still work; DirectInput, SDL, and raw HID see all sixteen. PlayStation, Extended, MIDI, and Keyboard+Mouse slots are unaffected by the cap entirely.


Three smaller features round out the engine:

  • HMOemNameOverride: Per-slot OEM name override surfaces through the Windows DirectInput OEM name table for games that key on the OEM friendly name.

  • HM inactivity destroy timeout: Idle HM virtuals destroy themselves after a configurable timeout (default 60 s, 0 = never), reclaiming kernel slots for active controllers.

Bubble-up cascade on slot deletion: When an HM slot is destroyed mid-stack, surviving Xbox HM VCs at higher pad indices shift down so xinputhid re-binds them to lower kernel slots, matching the natural disconnect/reconnect shape XInput does for real controllers.


Capture profiles from real devices

The Imported Profiles dialog in the Extended config bar reads the HID descriptor off any connected device. Plug in a controller, click Import, and the captured profile lands in the Extended dropdown with a (User Generated) suffix. Use it on every Extended slot going forward, export to JSON, share with anyone, or open a profile-contribution issue against HIDMaestro for the next release. No standalone tool, no extra downloads, no admin during capture.


Authentic PlayStation passthrough

PadForge is now the first remapper that passes a physical DualShock 4 or DualSense's full Sony Report 0x01 stream straight through to games. Gyro, accelerometer, touchpad fingers, and battery level all forward when the slot's HM profile is one of the DS4 or DualSense variants. Games that read the standard DualShock raw report shape see real motion, real touch, real battery — not a synthesized approximation.


The Sony Report 0x01 packer covers DS4 Type 1 and DualSense USB layouts. Profile-based dispatch by HM profile slug. The tools/Ds4InputDump/ utility ships a small raw-HID reader for verifying the byte layout frame by frame.


The touchpad surface itself can be driven by any source: a physical DS4 or DualSense touchpad, a Precision Touchpad, the on-screen touchpad overlay, or the web controller's touch zone. Routing happens through the Mappings tab.


Live touchpad on the virtual surface

The 3D and 2D PlayStation views render a live touchpad with finger-contact spheres (3D) and finger dots (2D). You see in real time which fingers are down, where on the surface, at what coordinates.


The touchpad surface itself is a click target. Click anywhere on the touchpad in either preview to record the TouchpadClick mapping — no need to find a button buried in a list. Map All on PlayStation outputs ends with TouchpadClick as the final recorded button. The 3D preview's touchpad dimensions are pinned to the real DS4 v2 touch surface (~52 × 23 mm).


Drag-reorder that just works

Slots within a type group can be drag-reordered on the Dashboard. Per-group order persists across restarts. The reorder model in v3 is repoint, not rebuild: kernel slots are anchored to visual position; data identities (mappings, devices, profile selection) are repointed to the kernel VC at their new visual position.


For same-profile reorders, this is a pure pointer swap in _virtualControllers[] plus a FeedbackPadIndex update on each moved VC. Zero kernel teardown. Zero game-side disconnection. Reordering three Xbox 360 slots takes effectively no engine time.

For different-profile reorders, only the specific positions whose profile actually changed get destroy + recreate. Matching positions in the same reorder still pointer-swap. Cross-group moves (changing a slot's type) go through type-change detection in Pass 1 and are handled separately.


HID PID 1.0 force feedback

Extended profiles that include the FFB descriptor block decode the full HID PID 1.0 effect set. Constant. Ramp. Periodic (sine, square, triangle, sawtooth). Condition (spring, damper, friction, inertia). All routed to the physical wheel or joystick driving the slot, with directional pass-through.


HMaestroFfbDecoder does the decode; HMaestroFfbDescriptor appends the PID descriptor block to Extended profiles that opt in. The condition-effect reader is now bit-aware, fixing several incorrect "approximate" comments on byte-aligned reads that were silently truncating values.


On-screen touchpad overlay

The on-screen touchpad overlay accumulated a long list of small bugs in v2. v3 cleared them:

  • Drift-proof touch counter, reset on visibility change.

  • px↔DIP conversion at the Win32 boundary so drag and resize deltas scale cleanly with display DPI.

  • Self-heal off-screen saves: if the saved position is outside the current monitor configuration, it relocates back into a visible area on next launch.

  • Reset Position button restores the overlay to a default visible spot.

  • Press-and-hold-to-right-click gesture suppression.

  • Title bar's WPF touch promotion suppressed so double-clicks fire on the title bar.

  • Surface alpha floored at 1 so low-opacity overlays stay touch-a..



--2.4.1--

Add Controller Popup
Theme-aware — background and icons follow dark/light theme
Live theme updates — popup background updates instantly when theme changes while open
Dismiss on outside click — clicking anywhere outside the popup closes it
Cleaner shadow — separate shadow element pattern eliminates corner artifacts

Touchpad Overlay
Opacity 0% — slider and number field now allow fully transparent
Reset buttons added next to DSU port, Web port, and Opacity fields
Wider port fields (140 → 160px) so 5-character port numbers don't get clipped
Updated description — "On-screen touch surface for DualShock 4 and DualSense-compatible touchpad emulation" (10 languages)

DSU Motion Server
Updated terminology — "cemuhook" → "CemuHook Motion Provider protocol" (10 languages)

Code Quality
WebControllerDevice.RawButtonCount now respects touchpad-only device flag
Removed redundant null check in DevicesViewModel
Removed redundant empty-string check in InputManager.Step3.ParseDescriptor


--2.4.0--
Profile Shortcuts
Switch profiles via controller button combos — assign Next, Previous, Specific, or Toggle Window actions to any button/axis combination
Cross-device combos — combine buttons from different controllers (e.g., Guide on gamepad + pedal on flight stick)
Axis direction triggers — use stick/trigger directions as shortcut inputs (e.g., Guide + LX left = Previous, Guide + LX right = Next)
5-second recording with live preview and countdown — press Record, hold your combo, auto-commits on timeout
Toggle Window shortcut — minimize/restore PadForge from a controller without touching the keyboard; forces to foreground even over fullscreen apps
Device filtering — trigger from Any Device or a specific controller

Profile Switch Flyout
Win11-style notification above the taskbar when profiles switch — pixel-matched to the native volume OSD
Four-stage flow: Profile Name → Initializing (flashing icon) → Active (accent checkmark) → auto-dismiss
Offline warning: "One or more controllers offline" with amber icon when devices are missing
Slide animation — slides up from the taskbar edge and down on dismiss
Theme-aware — follows system dark/light theme

DS4 Touchpad Support
Multi-source touchpad input — SDL3 gamepad touchpad, Precision Touchpad (PTP), touchscreen overlay, and web controller all feed into DS4 virtual controller via DS4_REPORT_EX
Touchscreen overlay — on-screen touchpad surface for touch devices, draggable and resizable
Web controller touchpad — touch zone on the web controller page for remote touchpad input
Devices page touchpad preview — live finger position and contact visualization
Precision Touchpad reader — captures PTP trackpad input via WM_INPUT for touchpad-to-DS4 passthrough

WPF UI Migration
ModernWPF 0.9.6 → WPF UI 4.2.0 — full migration to actively maintained Fluent Design framework
App branding bar with custom fullscreen button and native Win32 tooltip
Compact sidebar — 48px items, 15px font, Windows 11 design language
Dashboard card polish — drag stability at high DPI, power button hover, visual parity with sidebar

SDL GUID in Mapping Submission
Devices page shows SDL GUID — needed for gamecontrollerdb mapping submissions (VID/PID alone can't build the CRC-based GUID)
Submission workflow includes SDL GUID in the generated issue template

Force Feedback Fixes
Fix directional data loss — Step2 was dropping FFB direction data, routing all effects to scalar rumble
Motor split direction convention — polar direction correctly maps to left/right motor split
FfbTest tool — standalone build, .NET 10, interactive polar direction control

Map All Toggle
Single toggle button — "Map All" switches to "Stop" while active, replacing the separate stop button
Both tabs — works on Preview and Mappings tabs

Bug Fixes
Fix fullscreen state not restoring from tray or minimize
Fix fullscreen button foreground stuck on light theme when starting minimized to tray
Fix overlay crash on shutdown — stop timers before closing window
Fix KBM mappings missing from HasAnyMapping, ClearMappingDescriptors, GetAllMappingDescriptors (KBM-only slots excluded from Copy From dialog)
Fix window position/size/maximized state not persisting across restarts
Fix DS4 touchpad macro ignored (#51)
Fix mouse handle mismatch after PTP registration
Fix 2D controller view crash — pack URI scheme failure
Fix device identity confusion in same XInput slot
Fix WPF GPU device loss on sleep/wake
Spelling & Localization
Standardize "deadzone" — updated from "dead zone" to match Microsoft's official gaming terminology across all code, UI strings, documentation, and wiki (10 languages)
Rename "Awaiting Controllers" → "Awaiting Devices" — technically accurate since assigned inputs may not be controllers
Code Quality
Remove duplicate FormatExePaths method
Fix misplaced XML doc on SwapSlots/MoveSlot
Rename QueueProfileSwitch → HandleGlobalMacroAction
Remove unused DashboardViewModel.ViGEmVersion
Clean up stale fields and missing null cleanup

Show Previous Changes

...More to be added

bottom of page