-
Notifications
You must be signed in to change notification settings - Fork 6
Engine Library
v3 (2026-04-26): This page has been rewritten for v3. Implementation detail describes the HIDMaestro SDK surface, OpenXInput shim, thread-pool lifecycle, and bubble-up cascade. The deeper architecture rationale lives in HIDMaestro Deep Dive. If something cited here drifts from the live source, the live source wins.
Shared class library of data types, interfaces, and enums used by both the Engine (input pipeline) and App (UI/ViewModel) assemblies. No UI dependencies. Targets net10.0-windows.
graph TB
subgraph "Data Models. PadForge.Engine.Data"
PS[PadSetting<br/>mapping config · deadzones · curves]
US[UserSetting<br/>device-to-slot linkage]
UD[UserDevice<br/>physical device record]
MT[MappingTranslation<br/>cross-layout Copy From]
VJM[ExtendedMappingEntry<br/>custom axis/button/POV maps]
end
subgraph "Output State Types. PadForge.Engine"
GP[Gamepad<br/>XInput-layout struct]
VRS[ExtendedRawState<br/>arbitrary axes · 128 buttons · 4 POVs]
KRS[KbmRawState<br/>256 VK codes · mouse deltas]
MRS[MidiRawState<br/>128 notes · 128 CCs]
end
subgraph "Device Wrappers. PadForge.Engine.Common"
ISDI[ISdlInputDevice<br/>common interface]
SDW[SdlDeviceWrapper<br/>joystick/gamepad · rumble · haptic · sensors]
SKW[SdlKeyboardWrapper<br/>per-device keyboard]
SMW[SdlMouseWrapper<br/>per-device mouse]
WCD[WebControllerDevice<br/>browser gamepad]
end
subgraph "Force Feedback"
FFS[ForceFeedbackState<br/>per-device FFB tracking]
VIB[Vibration<br/>left + right motor]
end
subgraph "Interfaces"
IVC[IVirtualController<br/>Create · Submit · Destroy]
end
US -->|references| PS
US -->|references| UD
PS -->|contains| VJM
PS -->|uses| MT
UD -->|runtime: Device| ISDI
SDW -.->|implements| ISDI
SKW -.->|implements| ISDI
SMW -.->|implements| ISDI
WCD -.->|implements| ISDI
IVC -->|accepts| GP
IVC -->|accepts| VRS
IVC -->|accepts| KRS
IVC -->|accepts| MRS
FFS -->|outputs| VIB
style GP fill:#e1f5fe
style PS fill:#e8f5e9
style SDW fill:#f3e5f5
style IVC fill:#fff3e0
style FFS fill:#fce4ec
Project file: PadForge.Engine/PadForge.Engine.csproj
| Namespace | Contents |
|---|---|
PadForge.Engine |
Common types |
PadForge.Engine.Data |
Data models |
PadForge.Engine.Common |
InputHookManager |
SDL3 |
P/Invoke |
- Gamepad (GamepadTypes.cs)
- ExtendedRawState (GamepadTypes.cs)
- KbmRawState (GamepadTypes.cs)
- MidiRawState (GamepadTypes.cs)
- VirtualControllerType (VirtualControllerTypes.cs)
- IVirtualController (VirtualControllerTypes.cs)
- CustomInputState (CustomInputState.cs)
- CustomInputUpdate (CustomInputUpdate.cs)
- CustomInputHelper (CustomInputHelper.cs)
- ISdlInputDevice (ISdlInputDevice.cs)
- SdlDeviceWrapper (SdlDeviceWrapper.cs)
- HapticEffectStrategy (SdlDeviceWrapper.cs)
- SdlKeyboardWrapper (SdlKeyboardWrapper.cs)
- SdlMouseWrapper (SdlMouseWrapper.cs)
- WebControllerDevice (WebControllerDevice.cs)
- DeviceObjectItem (DeviceObjectItem.cs)
- DeviceEffectItem (DeviceEffectItem.cs)
- InputTypes (InputTypes.cs)
- ForceFeedbackState (ForceFeedbackState.cs)
- FfbEffectTypes (ForceFeedbackState.cs)
- Vibration (ForceFeedbackState.cs)
- ConditionAxisData (ForceFeedbackState.cs)
- RumbleLogger (RumbleLogger.cs)
- InputHookManager (InputHookManager.cs)
- RawInputListener (RawInputListener.cs)
- PadSetting (Data/PadSetting.cs)
- ExtendedMappingEntry (Data/PadSetting.cs)
- UserSetting (Data/UserSetting.cs)
- UserDevice (Data/UserDevice.cs)
- DeadZoneShape (Data/DeadZoneShape.cs)
- MappingTranslation (Data/MappingTranslation.cs)
- SDL3 P/Invoke (SDL3Minimal.cs)
File: PadForge.Engine/Common/GamepadTypes.cs
Namespace: PadForge.Engine
Minimal struct matching the XInput XINPUT_GAMEPAD layout. Output of the mapping pipeline (Step 3 → Step 4 → Step 5).
public struct Gamepad
{
// Fields
public ushort Buttons; // Bitmask of button flags
public ushort LeftTrigger; // 0-65535
public ushort RightTrigger; // 0-65535
public short ThumbLX; // -32768 to 32767
public short ThumbLY; // -32768 to 32767
public short ThumbRX; // -32768 to 32767
public short ThumbRY; // -32768 to 32767
// Methods
public bool IsButtonPressed(ushort flag);
public void SetButton(ushort flag, bool pressed);
public void Clear();
}| Constant | Value | Description |
|---|---|---|
DPAD_UP |
0x0001 |
D-pad up |
DPAD_DOWN |
0x0002 |
D-pad down |
DPAD_LEFT |
0x0004 |
D-pad left |
DPAD_RIGHT |
0x0008 |
D-pad right |
START |
0x0010 |
Start button |
BACK |
0x0020 |
Back button |
LEFT_THUMB |
0x0040 |
Left stick click |
RIGHT_THUMB |
0x0080 |
Right stick click |
LEFT_SHOULDER |
0x0100 |
Left bumper |
RIGHT_SHOULDER |
0x0200 |
Right bumper |
GUIDE |
0x0400 |
Guide/home button |
A |
0x1000 |
A button |
B |
0x2000 |
B button |
X |
0x4000 |
X button |
Y |
0x8000 |
Y button |
| Method | Signature | Description |
|---|---|---|
IsButtonPressed |
bool IsButtonPressed(ushort flag) |
true if the button flag bit is set in Buttons
|
SetButton |
void SetButton(ushort flag, bool pressed) |
Sets or clears a button flag bit via bitwise OR/AND |
Clear |
void Clear() |
Resets all fields to zero |
File: PadForge.Engine/Common/GamepadTypes.cs
Namespace: PadForge.Engine
Raw output state for Extended-category virtual controllers and custom HID descriptors. Bypasses the fixed Gamepad struct to support arbitrary axis, button, and POV counts. Step 5 forwards this directly to HIDMaestro via HMaestroVirtualController.SubmitExtendedRawState.
public struct ExtendedRawState
{
public short[] Axes; // Up to 8 axes (signed short range -32768..32767)
public uint[] Buttons; // Button state as 4 x 32-bit words = 128 buttons max
public int[] Povs; // Up to 4 POV hat switches (-1=centered, 0-35900=direction)
public static ExtendedRawState Create(int nAxes, int nButtons, int nPovs);
public void SetButton(int index, bool pressed);
public bool IsButtonPressed(int index);
public void Clear();
}| Method | Signature | Description |
|---|---|---|
Create |
static ExtendedRawState Create(int nAxes, int nButtons, int nPovs) |
Factory. Clamps axes to 8, buttons to 128 (stored as (N+31)/32 uint words), POVs to 4. All zeroed. |
SetButton |
void SetButton(int index, bool pressed) |
Sets button by 0-based index (word = index/32, bit = index%32). No-op if out of range. |
IsButtonPressed |
bool IsButtonPressed(int index) |
true if button at index is set. false if out of range. |
Clear |
void Clear() |
Resets axes to 0, buttons to 0, POVs to −1 (centered). |
Buttons use a 128-bit bitmask stored as uint[4] (32 buttons per word).
Hundredths of degrees: 0=N, 4500=NE, 9000=E, 13500=SE, 18000=S, 22500=SW, 27000=W, 31500=NW, 0xFFFFFFFF (−1) = centered.
File: PadForge.Engine/Common/GamepadTypes.cs
Namespace: PadForge.Engine
Raw keyboard + mouse output state for KeyboardMouseVirtualController. Key states packed into 4 × 64-bit words covering 256 Windows VK codes. Mouse axes are signed shorts (delta per frame).
public struct KbmRawState
{
// Key state (256 VK codes packed into 4 ulongs)
public ulong Keys0; // VK 0-63
public ulong Keys1; // VK 64-127
public ulong Keys2; // VK 128-191
public ulong Keys3; // VK 192-255
// Mouse output
public short MouseDeltaX; // Mouse X delta (signed, pixels per frame)
public short MouseDeltaY; // Mouse Y delta (signed, pixels per frame)
public short ScrollDelta; // Mouse scroll delta (positive = up)
public byte MouseButtons; // Bit 0=LMB, 1=RMB, 2=MMB, 3=X1, 4=X2
// Pre-deadzone values (for UI stick/trigger preview)
public short PreDzMouseDeltaX; // Mouse X before center offset + deadzone
public short PreDzMouseDeltaY; // Mouse Y before center offset + deadzone
public short PreDzScrollDelta; // Scroll before deadzone
// Methods
public bool GetKey(byte vk);
public void SetKey(byte vk, bool pressed);
public bool GetMouseButton(int index);
public void SetMouseButton(int index, bool pressed);
public void Clear();
public static KbmRawState Combine(KbmRawState a, KbmRawState b);
}| Method | Signature | Description |
|---|---|---|
GetKey |
bool GetKey(byte vk) |
true if VK code bit is set (word = vk/64, bit = vk%64). |
SetKey |
void SetKey(byte vk, bool pressed) |
Sets or clears a VK code bit. |
GetMouseButton |
bool GetMouseButton(int index) |
true if mouse button bit is set (0=LMB, 1=RMB, 2=MMB, 3=X1, 4=X2). |
SetMouseButton |
void SetMouseButton(int index, bool pressed) |
Sets or clears a mouse button bit. |
Clear |
void Clear() |
Zeros all keys, mouse deltas, scroll, mouse buttons, and pre-deadzone fields. |
Combine |
static KbmRawState Combine(KbmRawState a, KbmRawState b) |
Merges two KBM states. Keys and mouse buttons OR'd. Deltas and scroll use largest absolute magnitude. |
File: PadForge.Engine/Common/GamepadTypes.cs
Namespace: PadForge.Engine
Dynamic-sized MIDI output state for MidiVirtualController. CC values: 0–127 (MIDI range). Notes: boolean (on/off).
public struct MidiRawState
{
public byte[] CcValues; // CC values 0-127 per CC slot
public bool[] Notes; // Note on/off per note slot
public static MidiRawState Create(int ccCount, int noteCount);
public void Clear();
public static MidiRawState Combine(MidiRawState a, MidiRawState b);
}| Method | Signature | Description |
|---|---|---|
Create |
static MidiRawState Create(int ccCount, int noteCount) |
Allocates arrays. CC values initialized to 0. |
Clear |
void Clear() |
Resets CCs to 64 (center), notes to false. |
Combine |
static MidiRawState Combine(MidiRawState a, MidiRawState b) |
Merges two states. CCs take the value furthest from center (64); notes OR'd. |
File: PadForge.Engine/Common/VirtualControllerTypes.cs
Namespace: PadForge.Engine
public enum VirtualControllerType
{
Microsoft = 0,
[XmlEnum("Sony")] PlayStation = 1,
Extended = 2,
Midi = 3,
KeyboardMouse = 4
}Numeric values are preserved from v2 (Xbox360 → Microsoft, DualShock4 → PlayStation, VJoy → Extended) so v2 PadForge.xml files load unchanged. The XmlEnum("Sony") attribute keeps the on-disk name "Sony" for PlayStation.
File: PadForge.Engine/Common/VirtualControllerTypes.cs
Namespace: PadForge.Engine
Abstraction for virtual controller operations. v3 collapses Microsoft / PlayStation / Extended onto a single concrete class backed by HIDMaestro; MIDI and KB+M remain separate.
| Class | Backend |
|---|---|
HMaestroVirtualController |
HIDMaestro SDK (HMContext, HMProfile, HMController). Handles Microsoft, PlayStation, and Extended categories. Profile selected at construction. |
MidiVirtualController |
Windows MIDI Services |
KeyboardMouseVirtualController |
Win32 SendInput
|
HMaestroVirtualController.Type reports the user-facing category (Microsoft / PlayStation / Extended) so per-type counting in InputService keeps working without inspecting profile metadata.
public interface IVirtualController : IDisposable
{
VirtualControllerType Type { get; }
bool IsConnected { get; }
int FeedbackPadIndex { get; set; }
void Connect();
void Disconnect();
void SubmitGamepadState(Gamepad gp);
void RegisterFeedbackCallback(int padIndex, Vibration[] vibrationStates);
}| Member | Type | Description |
|---|---|---|
Type |
VirtualControllerType |
Virtual controller type |
IsConnected |
bool |
Whether the VC is connected |
FeedbackPadIndex |
int |
Slot index for feedback callbacks into VibrationStates[] (updated on SwapSlotData) |
Connect() |
void |
Creates and plugs in the VC |
Disconnect() |
void |
Unplugs and destroys the VC |
SubmitGamepadState(Gamepad) |
void |
Sends gamepad state to the VC |
RegisterFeedbackCallback(int, Vibration[]) |
void |
Registers a callback writing rumble to VibrationStates[] at the given index |
File: PadForge.Engine/Common/CustomInputState.cs
Namespace: PadForge.Engine
API-agnostic snapshot of a device's full input state at one point in time.
public class CustomInputState
{
// Constants
public const int MaxAxis = 24;
public const int MaxSliders = 8;
public const int MaxPovs = 4;
public const int MaxButtons = 256;
// Fields
public int[] Axis; // 0-65535, center = 32767
public int[] Sliders; // 0-65535
public int[] Povs; // centidegrees 0-35900, or -1 for centered
public bool[] Buttons; // true = pressed
public float[] Gyro; // [X, Y, Z] radians per second
public float[] Accel; // [X, Y, Z] meters per second squared
// Constructors
public CustomInputState();
public CustomInputState(int[] axes, int[] sliders, int[] povs, bool[] buttons);
// Methods
public CustomInputState Clone();
public static void GetAxisMask(DeviceObjectItem[] items, int numAxes,
out int axisMask, out int actuatorMask, out int actuatorCount);
}| Constructor | Description |
|---|---|
CustomInputState() |
Zeroed arrays at default sizes. POVs init to −1 (centered). Gyro/Accel are float[3]. |
CustomInputState(int[], int[], int[], bool[]) |
Copies arrays up to max lengths (snapshot isolation). POVs init to −1 before copy. |
| Method | Signature | Description |
|---|---|---|
Clone |
CustomInputState Clone() |
Deep copy of all arrays (including Gyro and Accel). |
GetAxisMask |
static void GetAxisMask(DeviceObjectItem[], int, out int, out int, out int) |
Scans device objects to build axis and FFB actuator bitmasks. Bit N = axis/actuator N exists. |
| Array | Range | Center | Description |
|---|---|---|---|
Axis |
0–65535 | 32767 | 0–5 = X, Y, Z, Rx, Ry, Rz; 6–23 = additional |
Sliders |
0–65535 | 32767 | Overflow or dedicated slider controls |
Povs |
0–35900 or −1 | −1 | Centidegrees. −1 = centered |
Buttons |
bool | false | 256 max (covers full Windows VK range) |
Gyro |
float[3] | 0.0 | Radians/s. Gyro-capable devices only |
Accel |
float[3] | 0.0 | m/s². Accelerometer-capable devices only |
File: PadForge.Engine/Common/CustomInputUpdate.cs
Namespace: PadForge.Engine
Single buffered input change between two CustomInputState snapshots. Used by the input recorder and update pipeline.
public struct CustomInputUpdate
{
public MapType Type; // Axis, Button, Slider, or POV
public int Index; // Zero-based index within the type's array
public int Value; // New value after the change
public override string ToString(); // Returns "Type Index = Value"
}| Field | Type | Description |
|---|---|---|
Type |
MapType |
Input source type that changed |
Index |
int |
Zero-based index in the type's array (e.g., Axis[0], Buttons[5]) |
Value |
int |
New value. Axes/sliders: 0–65535. POVs: centidegrees or −1. Buttons: 1/0. |
File: PadForge.Engine/Common/CustomInputHelper.cs
Namespace: PadForge.Engine
Static constants and utility methods for CustomInputState.
| Constant | Type | Value | Description |
|---|---|---|---|
MaxAxis |
int |
24 | Mirrors CustomInputState.MaxAxis
|
MaxSliders |
int |
8 | Mirrors CustomInputState.MaxSliders
|
MaxPovs |
int |
4 | Mirrors CustomInputState.MaxPovs
|
MaxButtons |
int |
256 | Mirrors CustomInputState.MaxButtons
|
AxisCenter |
int |
32767 | Unsigned axis center value |
AxisMin |
int |
0 | Unsigned axis minimum |
AxisMax |
int |
65535 | Unsigned axis maximum |
PovCentered |
int |
-1 | POV centered value (no direction pressed) |
| Method | Signature | Description |
|---|---|---|
GetUpdates |
static CustomInputUpdate[] GetUpdates(CustomInputState oldState, CustomInputState newState) |
Returns all differences between two states (axes, sliders, POVs, buttons). Null states treated as all-zero. Empty array if unchanged. |
File: PadForge.Engine/Common/ISdlInputDevice.cs
Namespace: PadForge.Engine
Common interface for all SDL-based input device wrappers (joystick/gamepad, keyboard, mouse, web controller). Lets the pipeline (Steps 2–5) read state from any device type uniformly.
public interface ISdlInputDevice : IDisposable
{
// Identity
uint SdlInstanceId { get; }
string Name { get; }
Guid InstanceGuid { get; }
Guid ProductGuid { get; }
string DevicePath { get; }
string SerialNumber { get; }
ushort VendorId { get; }
ushort ProductId { get; }
// Capabilities
int NumAxes { get; }
int NumButtons { get; }
int RawButtonCount { get; }
int NumHats { get; }
bool HasRumble { get; }
bool HasHaptic { get; }
bool HasGyro { get; }
bool HasAccel { get; }
bool IsAttached { get; }
// Haptic
HapticEffectStrategy HapticStrategy { get; }
IntPtr HapticHandle { get; }
uint HapticFeatures { get; }
int NumHapticAxes { get; }
// State reading
CustomInputState GetCurrentState(bool forceRaw = false);
DeviceObjectItem[] GetDeviceObjects();
int GetInputDeviceType();
// Force feedback
bool SetRumble(ushort low, ushort high, uint durationMs = uint.MaxValue);
bool StopRumble();
}| Property | Type | Description |
|---|---|---|
SdlInstanceId |
uint |
SDL instance ID (unique per connection session; 0 = invalid) |
Name |
string |
Human-readable device name |
InstanceGuid |
Guid |
Deterministic GUID for settings matching (from path/serial/VID+PID) |
ProductGuid |
Guid |
Product GUID from VID/PID for device family identification |
DevicePath |
string |
Device path (may be empty) |
SerialNumber |
string |
Serial number, e.g., Bluetooth MAC (may be empty) |
VendorId |
ushort |
USB Vendor ID |
ProductId |
ushort |
USB Product ID |
NumAxes |
int |
Axis count (6 for gamepads) |
NumButtons |
int |
Button count (11 for gamepads) |
RawButtonCount |
int |
Raw joystick button count before gamepad remapping; may exceed NumButtons
|
NumHats |
int |
POV hat count (1 for gamepads) |
HasRumble |
bool |
Supports simple rumble |
HasHaptic |
bool |
Has an SDL haptic handle open |
HasGyro |
bool |
Has gyroscope sensor |
HasAccel |
bool |
Has accelerometer sensor |
IsAttached |
bool |
Handle still valid and connected |
HapticStrategy |
HapticEffectStrategy |
Best haptic strategy chosen at open time |
HapticHandle |
IntPtr |
SDL haptic handle (IntPtr.Zero if none) |
HapticFeatures |
uint |
Bitmask of SDL_HAPTIC_* flags |
NumHapticAxes |
int |
Haptic axes (1 = wheel, 2+ = joystick) |
| Method | Signature | Description |
|---|---|---|
GetCurrentState |
CustomInputState GetCurrentState(bool forceRaw = false) |
Reads input state. forceRaw=true bypasses gamepad remapping. |
GetDeviceObjects |
DeviceObjectItem[] GetDeviceObjects() |
Returns metadata for each axis, hat, and button. Button count uses Math.Max(NumButtons, RawButtonCount). |
GetInputDeviceType |
int GetInputDeviceType() |
Returns an InputDeviceType constant. |
SetRumble |
bool SetRumble(ushort low, ushort high, uint durationMs) |
Sends rumble. Default duration uint.MaxValue (~49 days). |
StopRumble |
bool StopRumble() |
Stops all rumble (SetRumble(0, 0, 0)). |
File: PadForge.Engine/Common/SdlDeviceWrapper.cs
Namespace: PadForge.Engine
Wraps an SDL joystick (and optionally its Gamepad overlay) for unified device access: open/close, state polling, rumble, GUID construction, and object enumeration. Implements ISdlInputDevice.
| Property | Type | Default | Description |
|---|---|---|---|
Joystick |
IntPtr |
IntPtr.Zero |
Raw SDL joystick handle. Always valid when open. |
GameController |
IntPtr |
IntPtr.Zero |
SDL Gamepad handle. Zero if not a gamepad. |
Haptic |
IntPtr |
IntPtr.Zero |
SDL haptic handle. Non-zero when haptic FFB available. |
ProductVersion |
ushort |
0 | USB Product Version |
JoystickType |
SDL_JoystickType |
UNKNOWN |
SDL joystick type classification |
IsGameController |
bool |
(computed) |
true if opened as an SDL Gamepad |
| Method | Signature | Description |
|---|---|---|
Open |
bool Open(uint instanceId) |
Opens SDL device. Tries Gamepad first, falls back to Joystick. Populates all properties. |
GetCurrentState |
CustomInputState GetCurrentState(bool forceRaw = false) |
Routes to GetGamepadState() (remapped) or GetJoystickState() (raw) based on device type and forceRaw. |
GetDeviceObjects |
DeviceObjectItem[] GetDeviceObjects() |
Builds DeviceObjectItem[] for each axis, hat, button. Uses Math.Max(NumButtons, RawButtonCount) for button count so extra raw buttons (beyond gamepad 11) are included with generic "Button N" names. First 6 axes use standard GUIDs; extras use Slider. |
GetInputDeviceType |
int GetInputDeviceType() |
Maps SDL_JoystickType to InputDeviceType. |
SetRumble |
bool SetRumble(ushort lowFreq, ushort highFreq, uint durationMs) |
Sends rumble via SDL_RumbleJoystick. false if unsupported. |
StopRumble |
bool StopRumble() |
SetRumble(0, 0, 0). |
| Method | Signature | Description |
|---|---|---|
BuildProductGuid |
static Guid BuildProductGuid(ushort vid, ushort pid) |
Synthetic GUID from VID+PID. bytes[0–1]=VID LE, [2–3]=PID LE, [4–15]=0x00. |
BuildInstanceGuid |
static Guid BuildInstanceGuid(string devicePath, ushort vid, ushort pid, uint instanceId, string serial = null) |
Deterministic GUID via MD5. Priority: VID+PID+Serial (stable), device path (wired), VID+PID+SDL ID (session-only). |
HatToCentidegrees |
static int HatToCentidegrees(byte hat) |
SDL hat bitmask to centidegrees (−1 for centered). |
DpadToCentidegrees |
static int DpadToCentidegrees(bool up, bool down, bool left, bool right) |
4 D-pad booleans to centidegrees (supports 8-way diagonals). |
GetGamepadState() reads through SDL's gamecontrollerdb mapping layer, producing a standardized layout:
| Output | Indices |
|---|---|
| Axes | [0]=LX, [1]=LY, [2]=LT, [3]=RX, [4]=RY, [5]=RT |
| Buttons | [0]=A, [1]=B, [2]=X, [3]=Y, [4]=LB, [5]=RB, [6]=Back, [7]=Start, [8]=LS, [9]=RS, [10]=Guide |
| POV[0] | Synthesized from gamepad D-pad buttons |
| Sensors | Gyro and Accel populated if available |
Guide suppression: When Back+Start+Guide are all pressed, Guide is suppressed (Windows/XInput synthesizes Guide from this combo).
Extra raw buttons: Raw joystick buttons beyond index 10 are appended (e.g., DualSense touchpad click), excluding indices consumed by the gamepad mapping (ParseMappedButtonIndices()).
GetJoystickState() reads raw joystick input (no gamepad remapping):
-
Axes: SDL signed (−32768..32767) converted to unsigned (0..65535) via
- short.MinValue. FirstMaxAxisgo toAxis[], overflow toSliders[]. -
Hats: SDL bitmask to centidegrees via
HatToCentidegrees. -
Buttons: Uses
RawButtonCount(notNumButtons) for full raw coverage.
SDL3 may return a raw VID/PID string (e.g., "0x16c0/0x05e1") for unknown devices. IsRawVidPidName() detects this; TryGetHidProductString() queries the HID product string via CreateFile + HidD_GetProductString P/Invoke.
OpenHaptic() opens SDL_OpenHapticFromJoystick and selects the best strategy:
- LeftRight. Best for dual-motor
- Sine. Periodic fallback
- Constant. Last resort
Devices with both simple rumble and LeftRight haptic prefer simple rumble (more reliable for gamepads). Gain set to 100 if SDL_HAPTIC_GAIN is supported.
File: PadForge.Engine/Common/SdlDeviceWrapper.cs
Namespace: PadForge.Engine
public enum HapticEffectStrategy
{
None, // No haptic support
LeftRight, // Best: SDL_HAPTIC_LEFTRIGHT (dual-motor)
Sine, // Periodic effect (period varies by motor)
Constant // Fallback: constant level from dominant motor
}File: PadForge.Engine/Common/SdlKeyboardWrapper.cs
Namespace: PadForge.Engine
Wraps a keyboard device for unified input via ISdlInputDevice. State read from Raw Input (per-device) via RawInputListener.
| Property | Type | Value/Description |
|---|---|---|
NumAxes |
int |
0 |
NumButtons |
int |
Up to 256 (min of 256 and MaxButtons) |
RawButtonCount |
int |
0 |
NumHats |
int |
0 |
HasRumble |
bool |
false |
HasHaptic |
bool |
false |
HasGyro |
bool |
false |
HasAccel |
bool |
false |
RawInputHandle |
IntPtr |
The Raw Input device handle for per-device state reading |
| Method | Signature | Description |
|---|---|---|
Open |
bool Open(RawInputListener.DeviceInfo deviceInfo) |
Opens from Raw Input enumeration. Builds GUID from device path; path hash as pseudo SDL instance ID. |
GetCurrentState |
CustomInputState GetCurrentState(bool forceRaw) |
Reads from RawInputListener.GetKeyboardState, merges hooked state via InputHookManager.MergeHookedKeyState (suppressed keys bypass Raw Input). |
GetDeviceObjects |
DeviceObjectItem[] |
256 button items with ObjectGuid.Key GUIDs. Names from SDL.VirtualKeyName. |
GetInputDeviceType |
int |
InputDeviceType.Keyboard (19). |
SetRumble / StopRumble
|
Always false. |
File: PadForge.Engine/Common/SdlMouseWrapper.cs
Namespace: PadForge.Engine
Wraps a mouse device for unified input via ISdlInputDevice. State read from Raw Input (per-device) via RawInputListener.
| Constant | Value | Description |
|---|---|---|
MouseButtons |
5 | Left, Middle, Right, X1, X2 |
MouseAxes |
3 | X Motion, Y Motion, Scroll |
AxisCenter |
32767 | Center value for mouse axis output |
MotionScale |
2048f | Multiplier for mouse delta to axis value |
ScrollScale |
128f | Multiplier for scroll delta to axis value |
| Property | Type | Value/Description |
|---|---|---|
NumAxes |
int |
3 (X Motion, Y Motion, Scroll) |
NumButtons |
int |
5 (Left, Middle, Right, X1, X2) |
RawButtonCount |
int |
0 |
NumHats |
int |
0 |
HasRumble |
bool |
false |
RawInputHandle |
IntPtr |
The Raw Input device handle |
| Method | Signature | Description |
|---|---|---|
Open |
bool Open(RawInputListener.DeviceInfo deviceInfo) |
Opens from Raw Input enumeration. |
GetCurrentState |
CustomInputState GetCurrentState(bool forceRaw) |
Reads deltas via ConsumeMouseDelta, scroll via ConsumeMouseScroll, buttons via GetMouseButtons + MergeHookedMouseState. Axes = AxisCenter + (delta * Scale) clamped to 0–65535. |
GetDeviceObjects |
DeviceObjectItem[] |
3 RelativeAxis (X, Y, Scroll) + 5 PushButton (L, M, R, X1, X2). |
GetInputDeviceType |
int |
InputDeviceType.Mouse (18). |
File: PadForge.Engine/Common/WebControllerDevice.cs
Namespace: PadForge.Engine
Virtual input device for a browser-connected gamepad. Implements ISdlInputDevice for standard pipeline integration. State written by WebSocket thread, read by polling thread via volatile reference swaps.
| Constant | Value | Description |
|---|---|---|
WebVendorId |
0xBEEF |
Distinctive VID to avoid HIDMaestro filter false positives |
WebProductId |
0xCA7E |
Distinctive PID |
WebProductGuid |
{BEBC0000-...} |
Fixed ProductGuid for all web controller instances |
| Property | Value |
|---|---|
| Axes | 6 (LX, LY, LT, RX, RY, RT. 0–65535 range) |
| Buttons | 11 (standard Xbox layout: A, B, X, Y, LB, RB, Back, Start, LS, RS, Guide) |
| POV Hats | 1 |
| HasRumble |
true (via browser Vibration API) |
| HasHaptic | false |
| HasGyro | false |
| HasAccel | false |
public WebControllerDevice(string clientId, string displayName)Creates a web controller. clientId is a unique browser localStorage identifier. InstanceGuid derived from client ID via MD5. SdlInstanceId is the client ID hash code. Stick axes init to center (32767), trigger axes to 0.
| Event | Signature | Description |
|---|---|---|
RumbleRequested |
Action<ushort, ushort> |
Fired on SetRumble. Parameters: (lowFreq, highFreq), 0–65535. |
| Method | Signature | Description |
|---|---|---|
UpdateAxis |
void UpdateAxis(int code, int value) |
Sets axis (0=LX, 1=LY, 2=LT, 3=RX, 4=RY, 5=RT). Thread-safe. |
UpdateButton |
void UpdateButton(int code, bool pressed) |
Sets button (0=A through 10=Guide). Thread-safe. |
UpdatePov |
void UpdatePov(int value) |
Sets POV hat (centidegrees or −1). Thread-safe. |
SetConnected |
void SetConnected(bool connected) |
Sets connection state (volatile write). |
File: PadForge.Engine/Common/DeviceObjectItem.cs
Namespace: PadForge.Engine
Describes a single input object (axis, button, hat, slider) on a device. Used by mapping UI and pipeline.
public class DeviceObjectItem
{
// Identity
public string Name { get; set; } // Default: ""
public Guid ObjectTypeGuid { get; set; } // Default: Guid.Empty
public DeviceObjectTypeFlags ObjectType { get; set; } // Default: All
// Position
public int InputIndex { get; set; } // Default: 0
public int Offset { get; set; } // Default: 0
// Aspect
public ObjectAspect Aspect { get; set; } // Default: Position
// Computed helpers (read-only)
public bool IsForceActuator { get; }
public bool IsAxis { get; }
public bool IsButton { get; }
public bool IsPov { get; }
public bool IsSlider { get; }
public override string ToString(); // "{Name} ({TypeLabel}, Index {InputIndex})"
}| Property | Type | Default | Description |
|---|---|---|---|
Name |
string |
"" |
Display name (e.g., "X Axis", "Button 3") |
ObjectTypeGuid |
Guid |
Guid.Empty |
Well-known GUID from ObjectGuid
|
ObjectType |
DeviceObjectTypeFlags |
All |
Classification flags |
InputIndex |
int |
0 |
Zero-based index into CustomInputState arrays |
Offset |
int |
0 |
Byte offset (synthetic for SDL, mapping compatibility) |
Aspect |
ObjectAspect |
Position |
Object aspect |
| Property | Logic |
|---|---|
IsForceActuator |
(ObjectType & ForceFeedbackActuator) != 0 |
IsAxis |
(ObjectType & Axis) != 0 |
IsButton |
(ObjectType & Button) != 0 |
IsPov |
(ObjectType & PointOfViewController) != 0 |
IsSlider |
ObjectTypeGuid == ObjectGuid.Slider |
File: PadForge.Engine/Common/DeviceEffectItem.cs
Namespace: PadForge.Engine
Describes a force feedback effect supported by a device. Under SDL3, rumble is the primary type. Kept for UI display and settings compatibility.
| Field | GUID | Description |
|---|---|---|
ConstantForce |
{13541C20-8E33-11D0-9AD0-00A0C9A06E35} |
GUID_ConstantForce (DirectInput) |
Square |
{13541C22-...} |
GUID_Square |
Sine |
{13541C23-...} |
GUID_Sine |
Triangle |
{13541C24-...} |
GUID_Triangle |
Spring |
{13541C27-...} |
GUID_Spring |
Damper |
{13541C28-...} |
GUID_Damper |
SdlRumble |
{53444C52-554D-424C-...} |
Synthetic GUID for SDL rumble ("SDLRUMBL") |
| Property | Type | Default | Description |
|---|---|---|---|
EffectGuid |
Guid |
Guid.Empty |
GUID identifying the effect type |
Name |
string |
"" |
Human-readable name (e.g., "Constant Force", "Rumble") |
Parameters |
EffectParameterFlags |
None |
Which parameters the effect supports |
| Method | Signature | Description |
|---|---|---|
CreateRumbleEffect |
static DeviceEffectItem CreateRumbleEffect() |
Creates an SDL rumble item (SdlRumble GUID, name "Rumble"). |
File: PadForge.Engine/Common/InputTypes.cs
Namespace: PadForge.Engine
[Flags]
public enum DeviceObjectTypeFlags : int
{
All = 0,
RelativeAxis = 1,
AbsoluteAxis = 2,
Axis = 3, // RelativeAxis | AbsoluteAxis
PushButton = 4,
ToggleButton = 8,
Button = 12, // PushButton | ToggleButton
PointOfViewController = 16,
Collection = 64,
NoData = 128,
ForceFeedbackActuator = 0x01000000,
ForceFeedbackEffectTrigger = 0x02000000
}[Flags]
public enum ObjectAspect : int
{
Position = 0x100
}[Flags]
public enum EffectParameterFlags : int
{
None = 0
}Well-known GUIDs for device object types, matching DirectInput GUID constants.
| Field | GUID | Description |
|---|---|---|
XAxis |
{A36D02E0-C9F3-11CF-BFC7-444553540000} |
GUID_XAxis |
YAxis |
{A36D02E1-C9F3-11CF-BFC7-444553540000} |
GUID_YAxis |
ZAxis |
{A36D02E2-C9F3-11CF-BFC7-444553540000} |
GUID_ZAxis |
RxAxis |
{A36D02F4-C9F3-11CF-BFC7-444553540000} |
GUID_RxAxis |
RyAxis |
{A36D02F5-C9F3-11CF-BFC7-444553540000} |
GUID_RyAxis |
RzAxis |
{A36D02E3-C9F3-11CF-BFC7-444553540000} |
GUID_RzAxis |
Slider |
{A36D02E4-C9F3-11CF-BFC7-444553540000} |
GUID_Slider |
Button |
{A36D02F0-C9F3-11CF-BFC7-444553540000} |
GUID_Button |
Key |
{55728220-D33C-11CF-BFC7-444553540000} |
GUID_Key |
PovController |
{A36D02F2-C9F3-11CF-BFC7-444553540000} |
GUID_POV |
Unknown |
Guid.Empty |
GUID_Unknown |
Integer constants matching DirectInput device type values. Used in UserDevice.CapType.
| Constant | Value | Description |
|---|---|---|
Device |
17 | Generic device |
Mouse |
18 | Mouse |
Keyboard |
19 | Keyboard |
Joystick |
20 | Joystick |
Gamepad |
21 | Gamepad |
Driving |
22 | Steering wheel |
Flight |
23 | Flight stick |
FirstPerson |
24 | First-person device |
Supplemental |
25 | Supplemental device (guitar, drum, dance pad) |
public enum MapType : int
{
None = 0,
Axis = 1,
Button = 2,
Slider = 3,
POV = 4
}File: PadForge.Engine/Common/ForceFeedbackState.cs
Namespace: PadForge.Engine
Per-device force feedback (rumble) state with change detection. Only sends to hardware when motor values differ. Uses uint.MaxValue duration (~49 days) to mimic XInput's "set and forget" model.
| Property | Type | Description |
|---|---|---|
LeftMotorSpeed |
ushort |
Last sent left (low-freq) motor speed, 0–65535. Read-only. |
RightMotorSpeed |
ushort |
Last sent right (high-freq) motor speed, 0–65535. Read-only. |
IsActive |
bool |
Whether FFB is active on the device. Read-only. |
| Field | Type | Description |
|---|---|---|
_cachedLeftMotorSpeed |
ushort |
Last sent left motor speed |
_cachedRightMotorSpeed |
ushort |
Last sent right motor speed |
_hapticEffectId |
int |
SDL haptic effect ID (-1 = none) |
_hapticEffectCreated |
bool |
Whether a haptic effect has been created |
_cachedEffectType |
uint |
Last sent FFB effect type |
_cachedSignedMag |
short |
Last sent signed magnitude |
_cachedDirection |
ushort |
Last sent polar direction |
_cachedPeriod |
uint |
Last sent period |
_cachedHasCondition |
bool |
Last sent condition data flag |
_cachedHasDirectional |
bool |
Last sent directional data flag |
| Method | Signature | Description |
|---|---|---|
SetDeviceForces |
void SetDeviceForces(UserDevice ud, ISdlInputDevice device, PadSetting ps, Vibration v) |
Main entry. Reads gain from PadSetting. Routes to directional haptic when HasDirectionalData or HasConditionData and device supports haptic, or scalar rumble otherwise. Only sends when values change. |
StopDeviceForces |
void StopDeviceForces(ISdlInputDevice device) |
Stops all rumble/haptic and resets cached state. |
| Method | Description |
|---|---|
SetDirectionalHapticForces(device, v, overallGain) |
Directional constant/periodic force. Single-axis (wheels): projects via sin(angle). Multi-axis: full 2D polar. Falls back to scalar if unsupported. |
SetConditionHapticForces(device, v, overallGain) |
Condition effects (spring/damper/friction/inertia) with per-axis coefficients. Scales HID (−10000..+10000) to SDL (−32767..+32767). |
SetHapticForces(device, left, right) |
Scalar haptic fallback. Translates dual-motor to SDL effect per HapticEffectStrategy. |
ApplyHapticEffect(device, ref effect) |
Creates on first call, updates in-place after. Avoids create/destroy churn. |
StopAndDestroyHapticEffect(device) |
Stops and destroys active haptic effect. Resets effect state. |
| Strategy | SDL Effect | Large Motor | Small Motor |
|---|---|---|---|
| LeftRight | SDL_HAPTIC_LEFTRIGHT |
large_magnitude = left |
small_magnitude = right |
| Sine | SDL_HAPTIC_SINE |
magnitude = max/2, period = 120
|
period = 40 |
| Constant | SDL_HAPTIC_CONSTANT |
level = max/2 |
N/A |
File: PadForge.Engine/Common/ForceFeedbackState.cs
Namespace: PadForge.Engine
FFB effect type constants matching the HID PID effect-type values used in HIDMaestro's PID descriptor path. Defined in Engine so both Engine and App can reference them.
| Constant | Value | Description |
|---|---|---|
None |
0 | No effect |
Const |
1 | Constant force |
Ramp |
2 | Ramp force |
Square |
3 | Square wave periodic |
Sine |
4 | Sine wave periodic |
Triangle |
5 | Triangle wave periodic |
SawUp |
6 | Sawtooth up periodic |
SawDown |
7 | Sawtooth down periodic |
Spring |
8 | Spring condition |
Damper |
9 | Damper condition |
Inertia |
10 | Inertia condition |
Friction |
11 | Friction condition |
File: PadForge.Engine/Common/ForceFeedbackState.cs
Namespace: PadForge.Engine
Vibration/FFB state for a virtual controller slot. Carries scalar motor speeds (rumble) and directional FFB data (haptic joysticks/wheels).
public class Vibration
{
// Scalar fields (HIDMaestro XInput / HID rumble callback path)
public ushort LeftMotorSpeed { get; set; } // 0-65535, low-frequency heavy rumble
public ushort RightMotorSpeed { get; set; } // 0-65535, high-frequency light buzz
// Directional FFB fields (HIDMaestro PID/FFB callback for haptic devices)
public bool HasDirectionalData { get; set; }
public uint EffectType { get; set; } // FfbEffectTypes constant
public short SignedMagnitude { get; set; } // -10000 to +10000
public ushort Direction { get; set; } // Polar 0-32767 (0=North)
public uint Period { get; set; } // ms, for periodic effects
public byte DeviceGain { get; set; } = 255; // 0-255, device-level gain
// Condition effect fields (spring/damper/friction/inertia)
public bool HasConditionData { get; set; }
public ConditionAxisData[] ConditionAxes { get; set; }
public int ConditionAxisCount { get; set; } // 1 for wheels, 2 for joysticks
// Constructors
public Vibration();
public Vibration(ushort leftMotor, ushort rightMotor);
}| Field | Type | Default | Description |
|---|---|---|---|
LeftMotorSpeed |
ushort |
0 | Left (low-freq) motor speed. Set by HIDMaestro OutputReceived callback. |
RightMotorSpeed |
ushort |
0 | Right (high-freq) motor speed. Set by HIDMaestro OutputReceived callback. |
HasDirectionalData |
bool |
false |
Directional FFB data available (HIDMaestro PID descriptor path) |
EffectType |
uint |
0 |
FfbEffectTypes constant |
SignedMagnitude |
short |
0 | −10000 to +10000. Negative = opposite direction. |
Direction |
ushort |
0 | Polar HID units 0–32767 (0=N, ~8192=E, ~16384=S, ~24576=W) |
Period |
uint |
0 | Period in ms (periodic effects) |
DeviceGain |
byte |
255 | Device-level gain 0–255, on top of per-effect gain |
HasConditionData |
bool |
false |
Per-axis condition data available |
ConditionAxes |
ConditionAxisData[] |
null |
Per-axis coefficients (0=X, 1=Y) |
ConditionAxisCount |
int |
0 | Valid entries (1 = wheel, 2 = joystick) |
File: PadForge.Engine/Common/ForceFeedbackState.cs
Namespace: PadForge.Engine
Per-axis condition parameters for spring/damper/friction/inertia effects.
public struct ConditionAxisData
{
public short PositiveCoefficient; // 0–10000, force when displacement > center
public short NegativeCoefficient; // 0–10000, force when displacement < center
public short Offset; // -10000 to +10000, center offset
public uint DeadBand; // 0–10000, dead band around center
public uint PositiveSaturation; // 0–10000
public uint NegativeSaturation; // 0–10000
}File: PadForge.Engine/Common/RumbleLogger.cs
Namespace: PadForge.Engine
Diagnostic logger for FFB debugging. Disabled by default. Thread-safe.
public static class RumbleLogger
{
public static bool Enabled { get; set; } // Default: false
public static void Log(string message);
public static void Close();
}| Member | Description |
|---|---|
Enabled |
Set true in InputService.Start() to activate. |
Log(string) |
Writes timestamped message to rumble_log.txt beside the executable. Stopwatch timing, lock for thread safety. Creates file on first call. |
Close() |
Closes the log StreamWriter. Call on shutdown. |
File: PadForge.Engine/Common/InputHookManager.cs
Namespace: PadForge.Engine.Common
Manages WH_KEYBOARD_LL and WH_MOUSE_LL low-level hooks to suppress mapped keyboard/mouse inputs. Only suppresses inputs in the active suppression sets.
public class InputHookManager : IDisposable
{
void Start();
void Stop();
void SetSuppressedKeys(HashSet<int> vkCodes);
void SetSuppressedMouseButtons(HashSet<int> buttons);
bool HasAnySuppression { get; }
static void MergeHookedKeyState(bool[] dest, int count);
static void MergeHookedMouseState(bool[] dest, int count);
}| Method | Signature | Description |
|---|---|---|
Start |
void Start() |
Creates background thread with GetMessage loop, installs both hooks. Blocks until installed (5s timeout). |
Stop |
void Stop() |
Posts WM_QUIT to hook thread, joins (2s timeout), clears state. |
SetSuppressedKeys |
void SetSuppressedKeys(HashSet<int> vkCodes) |
Updates VK codes to suppress. Clears state for removed keys. Volatile reference swap. |
SetSuppressedMouseButtons |
void SetSuppressedMouseButtons(HashSet<int> buttons) |
Updates mouse button IDs to suppress (0=L, 1=R, 2=M, 3=X1, 4=X2). Volatile reference swap. |
HasAnySuppression |
bool (property) |
true if any keys or mouse buttons suppressed. |
MergeHookedKeyState |
static void MergeHookedKeyState(bool[] dest, int count) |
Merges suppressed-key state into dest (hook state is authoritative). Called by SdlKeyboardWrapper. |
MergeHookedMouseState |
static void MergeHookedMouseState(bool[] dest, int count) |
Same for mouse buttons. Called by SdlMouseWrapper. |
-
Keyboard: Intercepts
WM_KEYDOWN/UP,WM_SYSKEYDOWN/UP. Returns(IntPtr)1to suppress,CallNextHookExto pass through. Captures state into_hookedKeyState[]before suppressing (LL hook runs beforeWM_INPUT). -
Mouse: Intercepts button messages (
WM_[LR/M/X]BUTTONDOWN/UP). Converts viaMouseMessageToButtonId(). Captures into_hookedMouseState[].
| Mouse Message | Button ID |
|---|---|
WM_LBUTTONDOWN/UP |
0 (Left) |
WM_RBUTTONDOWN/UP |
1 (Right) |
WM_MBUTTONDOWN/UP |
2 (Middle) |
WM_XBUTTONDOWN/UP (XBUTTON1) |
3 |
WM_XBUTTONDOWN/UP (XBUTTON2) |
4 |
| Other (move, wheel) | -1 (pass through) |
| Function | DLL | Purpose |
|---|---|---|
SetWindowsHookExW |
user32.dll | Install low-level hook |
UnhookWindowsHookEx |
user32.dll | Remove hook |
CallNextHookEx |
user32.dll | Pass input to next hook |
GetModuleHandleW |
kernel32.dll | Get module handle for hook registration |
GetMessageW |
user32.dll | Message pump loop |
PostThreadMessageW |
user32.dll | Post WM_QUIT to hook thread |
GetCurrentThreadId |
kernel32.dll | Get hook thread ID |
File: PadForge.Engine/Common/RawInputListener.cs
Namespace: PadForge.Engine
Receives keyboard and mouse input via Windows Raw Input API, even when unfocused (RIDEV_INPUTSINK). Creates a hidden message-only window (HWND_MESSAGE) on a background thread. State tracked per-device via RAWINPUT.header.hDevice for multi-device isolation.
public struct DeviceInfo
{
public IntPtr Handle; // Raw Input device handle
public string Name; // Device display name
public string DevicePath; // Device interface path
public ushort VendorId; // USB VID
public ushort ProductId; // USB PID
}| Field | Type | Description |
|---|---|---|
AggregateKeyboardHandle |
IntPtr |
Sentinel new IntPtr(-99). Aggregates all keyboards. |
AggregateMouseHandle |
IntPtr |
Sentinel new IntPtr(-98). Aggregates all mice. |
| Method | Signature | Description |
|---|---|---|
Start |
static void Start() |
Creates message-pump thread, registers Raw Input. Blocks until window created. |
Stop |
static void Stop() |
Posts WM_QUIT, joins thread. |
EnumerateKeyboards |
static DeviceInfo[] EnumerateKeyboards() |
All connected keyboards via GetRawInputDeviceList. |
EnumerateMice |
static DeviceInfo[] EnumerateMice() |
All connected mice. |
GetKeyboardState |
static void GetKeyboardState(IntPtr hDevice, bool[] dest, int count) |
Copies per-device key states. Aggregate handle for combined output. |
ConsumeMouseDelta |
static void ConsumeMouseDelta(IntPtr hDevice, out int dx, out int dy) |
Returns and resets accumulated mouse delta. |
ConsumeMouseScroll |
static int ConsumeMouseScroll(IntPtr hDevice) |
Returns and resets scroll delta. |
GetMouseButtons |
static void GetMouseButtons(IntPtr hDevice, bool[] dest) |
Copies per-device button states (5: L, M, R, X1, X2). |
-
Keyboard (
RIM_TYPEKEYBOARD): ReadsRAWKEYBOARD.VKey, handlesRI_KEY_E0extended keys (right Ctrl/Alt/Shift, NumLock, Insert, Home, etc.). Per-device state inConcurrentDictionary<IntPtr, bool[]>. -
Mouse (
RIM_TYPEMOUSE): AccumulateslLastX/lLastYdeltas. Tracks buttons viausButtonFlags. Scroll viaRI_MOUSE_WHEEL. -
Scroll:
usButtonDatais a signedshort. Accumulated per-device, consumed byConsumeMouseScroll.
File: PadForge.Engine/Data/PadSetting.cs
Namespace: PadForge.Engine.Data
Complete mapping configuration for a device-to-slot assignment. All mapping properties are string descriptors: "Button N", "Axis N", "IHAxis N", "POV N Dir", "Slider N", or "" (unmapped). Declared partial.
Stored separately from UserSettings, linked via PadSettingChecksum. Multiple UserSettings can share one PadSetting. Numeric settings stored as strings for XML consistency.
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
PadSettingChecksum |
string |
[XmlElement] |
"" |
Checksum from all mapping/setting properties. Links to UserSettings. |
| Property | Type | Serialization | Default |
|---|---|---|---|
ButtonA |
string |
[XmlElement] |
"" |
ButtonB |
string |
[XmlElement] |
"" |
ButtonX |
string |
[XmlElement] |
"" |
ButtonY |
string |
[XmlElement] |
"" |
LeftShoulder |
string |
[XmlElement] |
"" |
RightShoulder |
string |
[XmlElement] |
"" |
ButtonBack |
string |
[XmlElement] |
"" |
ButtonStart |
string |
[XmlElement] |
"" |
ButtonGuide |
string |
[XmlElement] |
"" |
LeftThumbButton |
string |
[XmlElement] |
"" |
RightThumbButton |
string |
[XmlElement] |
"" |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
DPad |
string |
[XmlElement] |
"" |
Combined D-Pad mapping. POV descriptor auto-extracts all 4 directions. Individual overrides take priority. |
DPadUp |
string |
[XmlElement] |
"" |
|
DPadDown |
string |
[XmlElement] |
"" |
|
DPadLeft |
string |
[XmlElement] |
"" |
|
DPadRight |
string |
[XmlElement] |
"" |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
LeftTrigger |
string |
[XmlElement] |
"" |
Mapping descriptor |
RightTrigger |
string |
[XmlElement] |
"" |
Mapping descriptor |
LeftTriggerDeadZone |
string |
[XmlElement] |
"0" |
0–100% |
RightTriggerDeadZone |
string |
[XmlElement] |
"0" |
0–100% |
LeftTriggerAntiDeadZone |
string |
[XmlElement] |
"0" |
0–100% |
RightTriggerAntiDeadZone |
string |
[XmlElement] |
"0" |
0–100% |
LeftTriggerMaxRange |
string |
[XmlElement] |
"100" |
1–100% |
RightTriggerMaxRange |
string |
[XmlElement] |
"100" |
1–100% |
LeftTriggerSensitivityCurve |
string |
[XmlElement] |
"0" |
−100 to 100 (0=linear, +100=exp, −100=log) |
RightTriggerSensitivityCurve |
string |
[XmlElement] |
"0" |
−100 to 100 |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
LeftThumbAxisX |
string |
[XmlElement] |
"" |
|
LeftThumbAxisY |
string |
[XmlElement] |
"" |
|
RightThumbAxisX |
string |
[XmlElement] |
"" |
|
RightThumbAxisY |
string |
[XmlElement] |
"" |
|
LeftThumbAxisXNeg |
string |
[XmlElement] |
"" |
Negative direction (buttons mapped to bidirectional axes) |
LeftThumbAxisYNeg |
string |
[XmlElement] |
"" |
|
RightThumbAxisXNeg |
string |
[XmlElement] |
"" |
|
RightThumbAxisYNeg |
string |
[XmlElement] |
"" |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
LeftThumbDeadZoneX |
string |
[XmlElement] |
"0" |
Left stick deadzone X (0–100%) |
LeftThumbDeadZoneY |
string |
[XmlElement] |
"0" |
Left stick deadzone Y |
RightThumbDeadZoneX |
string |
[XmlElement] |
"0" |
Right stick deadzone X |
RightThumbDeadZoneY |
string |
[XmlElement] |
"0" |
Right stick deadzone Y |
LeftThumbDeadZoneShape |
string |
[XmlElement] |
"2" |
DeadZoneShape enum value. 2 = ScaledRadial. |
RightThumbDeadZoneShape |
string |
[XmlElement] |
"2" |
DeadZoneShape enum value |
LeftThumbAntiDeadZone |
string |
[XmlElement] |
"0" |
Legacy unified (0–100%). Prefer per-axis X/Y. |
RightThumbAntiDeadZone |
string |
[XmlElement] |
"0" |
Legacy unified |
LeftThumbAntiDeadZoneX |
string |
[XmlElement] |
"0" |
Left stick anti-deadzone X (0–100%) |
LeftThumbAntiDeadZoneY |
string |
[XmlElement] |
"0" |
Left stick anti-deadzone Y |
RightThumbAntiDeadZoneX |
string |
[XmlElement] |
"0" |
Right stick anti-deadzone X |
RightThumbAntiDeadZoneY |
string |
[XmlElement] |
"0" |
Right stick anti-deadzone Y |
LeftThumbLinear |
string |
[XmlElement] |
"0" |
Response curve (0–100%). 0=default, 100=fully linear. |
RightThumbLinear |
string |
[XmlElement] |
"0" |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
LeftThumbSensitivityCurveX |
string |
[XmlElement] |
"0" |
−100 to 100 (0=linear, +100=exp, −100=log) |
LeftThumbSensitivityCurveY |
string |
[XmlElement] |
"0" |
|
RightThumbSensitivityCurveX |
string |
[XmlElement] |
"0" |
|
RightThumbSensitivityCurveY |
string |
[XmlElement] |
"0" |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
LeftThumbMaxRangeX |
string |
[XmlElement] |
"100" |
Left stick X max range (1–100%). Symmetric/positive direction. |
LeftThumbMaxRangeY |
string |
[XmlElement] |
"100" |
|
RightThumbMaxRangeX |
string |
[XmlElement] |
"100" |
|
RightThumbMaxRangeY |
string |
[XmlElement] |
"100" |
|
LeftThumbMaxRangeXNeg |
string |
[XmlElement] |
null |
Left stick X negative (left). Null = inherit symmetric. |
LeftThumbMaxRangeYNeg |
string |
[XmlElement] |
null |
Left stick Y negative (down) direction |
RightThumbMaxRangeXNeg |
string |
[XmlElement] |
null |
|
RightThumbMaxRangeYNeg |
string |
[XmlElement] |
null |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
LeftThumbCenterOffsetX |
string |
[XmlElement] |
"0" |
−100 to 100%. Corrects stick drift before deadzone. |
LeftThumbCenterOffsetY |
string |
[XmlElement] |
"0" |
|
RightThumbCenterOffsetX |
string |
[XmlElement] |
"0" |
|
RightThumbCenterOffsetY |
string |
[XmlElement] |
"0" |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
ForceType |
string |
[XmlElement] |
"1" |
0=Off, 1=SDL Rumble |
ForceOverall |
string |
[XmlElement] |
"100" |
Overall strength 0–100%. Multiplier for both motors. |
ForceSwapMotor |
string |
[XmlElement] |
"0" |
"0"=no swap, "1"=swap left/right motors |
LeftMotorStrength |
string |
[XmlElement] |
"100" |
Left (low-freq) motor strength 0–100% |
RightMotorStrength |
string |
[XmlElement] |
"100" |
Right (high-freq) motor strength 0–100% |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
AudioRumbleEnabled |
string |
[XmlElement] |
"0" |
Enable audio bass rumble. "0"=off, "1"=on. |
AudioRumbleSensitivity |
string |
[XmlElement] |
"4" |
Bass detection sensitivity (1–20) |
AudioRumbleCutoffHz |
string |
[XmlElement] |
"80" |
Low-pass cutoff Hz (40–200) |
AudioRumbleLeftMotor |
string |
[XmlElement] |
"100" |
Left motor strength for audio rumble (0–100%) |
AudioRumbleRightMotor |
string |
[XmlElement] |
"100" |
Right motor strength for audio rumble (0–100%) |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
AxisToButtonThreshold |
string |
[XmlElement] |
"50" |
Threshold 0–100% for axis-as-button |
MappingDeadZoneEntries |
ExtendedMappingEntry[] |
[XmlArray("MappingDeadZones")] [XmlArrayItem("Map")] |
null |
Per-mapping axis-to-button thresholds. Keys = target names, values = 0–100%. |
LeftThumbAxisXInvert |
string |
[XmlElement] |
"0" |
Invert left stick X. "0" or "1". |
LeftThumbAxisYInvert |
string |
[XmlElement] |
"0" |
|
RightThumbAxisXInvert |
string |
[XmlElement] |
"0" |
|
RightThumbAxisYInvert |
string |
[XmlElement] |
"0" |
For Extended slots with custom HID descriptors (arbitrary axis/button/POV counts). Keys: "ExtendedAxis0", "ExtendedAxis0Neg", "ExtendedBtn0", "ExtendedPov0Up". Values: mapping descriptors.
| Property | Type | Serialization | Description |
|---|---|---|---|
ExtendedMappingEntries |
ExtendedMappingEntry[] |
[XmlArray("ExtendedMappings")] [XmlArrayItem("Map")] |
Serializable array for XML persistence |
| Method | Signature | Description |
|---|---|---|
GetExtendedMapping |
string GetExtendedMapping(string key) |
Gets an Extended mapping value by key. Returns "" if not found. |
SetExtendedMapping |
void SetExtendedMapping(string key, string value) |
Sets an Extended mapping value. Empty/null removes the key. |
FlushExtendedMappings |
void FlushExtendedMappings() |
Flushes in-memory dictionary back to serializable array. |
Same pattern as Extended. Keys: "MidiCC0", "MidiCC0Neg", "MidiNote0", etc.
| Property | Type | Serialization | Description |
|---|---|---|---|
MidiMappingEntries |
ExtendedMappingEntry[] |
[XmlArray("MidiMappings")] [XmlArrayItem("Map")] |
Serializable array |
| Method | Signature | Description |
|---|---|---|
GetMidiMapping |
string GetMidiMapping(string key) |
Gets a MIDI mapping value. |
SetMidiMapping |
void SetMidiMapping(string key, string value) |
Sets a MIDI mapping value. |
FlushMidiMappings |
void FlushMidiMappings() |
Flushes dictionary to array. |
Keys: "KbmKey41" (VK_A), "KbmMouseX", "KbmMouseXNeg", "KbmMBtn0", "KbmScroll", etc.
| Property | Type | Serialization | Description |
|---|---|---|---|
KbmMappingEntries |
ExtendedMappingEntry[] |
[XmlArray("KbmMappings")] [XmlArrayItem("Map")] |
Serializable array |
| Method | Signature | Description |
|---|---|---|
GetKbmMapping |
string GetKbmMapping(string key) |
Gets a KBM mapping value. |
SetKbmMapping |
void SetKbmMapping(string key, string value) |
Sets a KBM mapping value. |
FlushKbmMappings |
void FlushKbmMappings() |
Flushes dictionary to array. |
Same pattern as Extended/MIDI/KBM mappings. Keys = target mapping names (e.g. "LeftThumbAxisX"), values = 0–100% threshold for axis-to-button activation. Default removal values: "0" or "50".
| Method | Signature | Description |
|---|---|---|
GetMappingDeadZone |
string GetMappingDeadZone(string key) |
Gets deadzone for a target. Returns "" if not found. |
SetMappingDeadZone |
void SetMappingDeadZone(string key, string value) |
Sets or removes a deadzone entry. Removes at "0" or "50". |
FlushMappingDeadZones |
void FlushMappingDeadZones() |
Syncs in-memory dictionary to MappingDeadZoneEntries array for serialization. |
| Property | Type | Serialization | Description |
|---|---|---|---|
HasAnyMapping |
bool |
[XmlIgnore] |
true if any mapping property has a non-empty descriptor. |
| Method | Signature | Description |
|---|---|---|
MigrateAntiDeadZones |
void MigrateAntiDeadZones() |
Migrates legacy unified anti-deadzone to per-axis X/Y. Call after deserialization. |
MigrateMaxRangeDirections |
void MigrateMaxRangeDirections() |
Copies symmetric max range to null/empty negative-direction properties. |
ComputeChecksum |
string ComputeChecksum() |
8-char hex checksum (first 4 bytes of MD5) from all properties. Keys sorted for determinism. |
UpdateChecksum |
void UpdateChecksum() |
Computes and stores checksum in PadSettingChecksum. |
ClearMappingDescriptors |
void ClearMappingDescriptors() |
Clears all mapping descriptors. Preserves deadzone and FFB settings. |
GetAllMappingDescriptors |
List<string> GetAllMappingDescriptors() |
All non-empty mapping descriptor strings. |
ToJson |
string ToJson(VirtualControllerType outputType, bool isExtended) |
JSON for clipboard. Embeds __OutputType, __IsExtended metadata and mapping arrays. |
FromJson |
static PadSetting FromJson(string json) |
Deserializes JSON. Returns null on invalid. |
FromJson |
static PadSetting FromJson(string json, out VirtualControllerType, out bool) |
Same, also extracts source layout metadata. |
CopyFrom |
void CopyFrom(PadSetting source) |
Copies all properties. Deep-copies mapping arrays. |
CopyFromTranslated |
void CopyFromTranslated(PadSetting source, VirtualControllerType srcType, bool srcIsExtended, VirtualControllerType tgtType, bool tgtIsExtended) |
Cross-layout copy via MappingTranslation. Translates mapping properties by canonical position. |
CloneDeep |
PadSetting CloneDeep() |
Deep copy including checksum. |
File: PadForge.Engine/Data/PadSetting.cs
Namespace: PadForge.Engine.Data
Key-value entry for Extended/MIDI/KBM mapping and per-mapping deadzone XML persistence. Shared by all four dictionary-based systems.
public class ExtendedMappingEntry
{
[XmlAttribute] public string Key { get; set; } = "";
[XmlAttribute] public string Value { get; set; } = "";
}File: PadForge.Engine/Data/UserSetting.cs
Namespace: PadForge.Engine.Data
Links a physical device to a virtual controller slot and mapping. One per device-to-slot assignment. Implements INotifyPropertyChanged.
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
InstanceGuid |
Guid |
[XmlElement] |
Physical device instance GUID | |
InstanceName |
string |
[XmlElement] |
"" |
Instance name (for offline display) |
ProductGuid |
Guid |
[XmlElement] |
Product GUID for matching across sessions | |
ProductName |
string |
[XmlElement] |
"" |
Product name |
MapTo |
int |
[XmlElement] |
-1 |
VC slot index (0–15). −1 = unmapped. Raises PropertyChanged. |
PadSettingChecksum |
string |
[XmlElement] |
"" |
Links to a PadSetting
|
IsEnabled |
bool |
[XmlElement] |
true |
Whether this mapping is enabled. Disabled = skipped in pipeline. |
DateCreated |
DateTime |
[XmlElement] |
DateTime.Now |
Creation timestamp |
DateUpdated |
DateTime |
[XmlElement] |
DateTime.Now |
Last modification timestamp |
| Property | Type | Serialization | Description |
|---|---|---|---|
OutputState |
Gamepad |
[XmlIgnore] |
Mapped output from Step 3. Written by background thread. |
RawMappedState |
Gamepad |
[XmlIgnore] |
Pre-processing state (axis-selected, Y-negated, before DZ/ADZ/linear/range). For UI preview. |
ExtendedRawOutputState |
ExtendedRawState |
[XmlIgnore] |
Mapped raw output for Extended slots. Forwarded to HIDMaestro via HMaestroVirtualController.SubmitExtendedRawState. |
MidiRawOutputState |
MidiRawState |
[XmlIgnore] |
Mapped MIDI raw output for MIDI slots. |
KbmRawOutputState |
KbmRawState |
[XmlIgnore] |
Mapped KBM raw output for KeyboardMouse slots. |
_cachedPadSetting |
PadSetting |
[XmlIgnore] (internal) |
Cached PadSetting reference set by SettingsManager. |
| Method | Signature | Description |
|---|---|---|
GetPadSetting |
PadSetting GetPadSetting() |
Returns cached PadSetting. |
SetPadSetting |
void SetPadSetting(PadSetting ps) |
Sets cached PadSetting. Called by SettingsManager on load/sync. |
File: PadForge.Engine/Data/UserDevice.cs
Namespace: PadForge.Engine.Data
Data model for a physical input device. Serializable properties (settings-persisted) and runtime-only fields (pipeline). Partial class. Implements INotifyPropertyChanged.
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
InstanceGuid |
Guid |
[XmlElement] |
Deterministic GUID from device path | |
InstanceName |
string |
[XmlElement] |
"" |
Instance name (e.g., "Xbox Controller") |
ProductGuid |
Guid |
[XmlElement] |
Product GUID (PIDVID format) | |
ProductName |
string |
[XmlElement] |
"" |
Product name |
VendorId |
ushort |
[XmlElement] |
0 | USB Vendor ID |
ProdId |
ushort |
[XmlElement] |
0 | USB Product ID |
DevRevision |
ushort |
[XmlElement] |
0 | USB Product Version / Revision |
DevicePath |
string |
[XmlElement] |
"" |
Device file system path |
SerialNumber |
string |
[XmlElement] |
"" |
Device serial number (e.g., Bluetooth MAC) |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
CapAxeCount |
int |
[XmlElement] |
0 | Number of axes |
CapButtonCount |
int |
[XmlElement] |
0 | Button count (gamepad-mapped for gamepads) |
RawButtonCount |
int |
[XmlElement] |
0 | Raw button count before gamepad remapping |
CapPovCount |
int |
[XmlElement] |
0 | Number of POV hat switches |
CapType |
int |
[XmlElement] |
0 |
InputDeviceType constant |
HasGyro |
bool |
[XmlElement] |
false |
Gyroscope support |
HasAccel |
bool |
[XmlElement] |
false |
Accelerometer support |
| Property | Type | Serialization | Default | Description |
|---|---|---|---|---|
DateCreated |
DateTime |
[XmlElement] |
DateTime.Now |
First creation timestamp. Vestigial. Serialized but never read by any consumer. |
DateUpdated |
DateTime |
[XmlElement] |
DateTime.Now |
Last update timestamp. Vestigial. Serialized but never read by any consumer. |
IsEnabled |
bool |
[XmlElement] |
true |
Whether device is enabled for mapping |
IsHidden |
bool |
[XmlElement] |
false |
Whether device is hidden from UI |
DisplayName |
string |
[XmlElement] |
"" |
User-assigned name (overrides InstanceName) |
HidHideEnabled |
bool |
[XmlElement] |
false |
Hide device from games via HidHide when assigned |
ConsumeInputEnabled |
bool |
[XmlElement] |
false |
Suppress mapped KB/mouse inputs via hooks |
ForceRawJoystickMode |
bool |
[XmlElement] |
false |
Bypass SDL gamepad remapping |
HidHideInstanceIds |
List<string> |
[XmlArray] [XmlArrayItem("Id")] |
new() |
Cached HID instance IDs for HidHide (persisted for offline devices) |
DeviceObjects |
DeviceObjectItem[] |
[XmlElement] |
null |
Axis, hat, and button metadata. Populated in Step 1. Serialized for offline dropdown persistence so mapping UI can show source descriptors when the device is disconnected. |
| Property | Type | Serialization | Description |
|---|---|---|---|
Device |
ISdlInputDevice |
[XmlIgnore] |
Live device handle. Set in Step 1. |
IsOnline |
bool |
[XmlIgnore] |
Connected and opened. |
InputState |
CustomInputState |
[XmlIgnore] |
Current state snapshot (Step 2, atomic ref). |
InputUpdates |
CustomInputUpdate[] |
[XmlIgnore] |
Buffered changes since last poll. |
InputStateTime |
DateTime |
[XmlIgnore] |
Timestamp of InputState. |
OldInputState |
CustomInputState |
[XmlIgnore] |
Previous state for change detection. |
OldInputUpdates |
CustomInputUpdate[] |
[XmlIgnore] |
Previous buffered updates. |
OldInputStateTime |
DateTime |
[XmlIgnore] |
Timestamp of OldInputState. |
ActuatorCount |
int |
[XmlIgnore] |
FFB actuator axis count. |
ForceFeedbackState |
ForceFeedbackState |
[XmlIgnore] |
Per-device FFB state. |
| Property | Type | Serialization | Description |
|---|---|---|---|
IsMouse |
bool |
[XmlIgnore] |
CapType == InputDeviceType.Mouse |
IsKeyboard |
bool |
[XmlIgnore] |
CapType == InputDeviceType.Keyboard |
HasForceFeedback |
bool |
[XmlIgnore] |
`ActuatorCount > 0 |
ResolvedName |
string |
[XmlIgnore] |
DisplayName if set, then InstanceName, then ProductName, then "(Unknown Device)" |
StatusText |
string |
[XmlIgnore] |
"Disabled", "Online", or "Offline" |
| Method | Signature | Description |
|---|---|---|
LoadInstance |
void LoadInstance(...) |
Sets identity properties. |
LoadCapabilities |
void LoadCapabilities(...) |
Sets capability properties. |
LoadFromSdlDevice |
void LoadFromSdlDevice(SdlDeviceWrapper) |
Loads from SDL wrapper + DevRevision. |
LoadFromKeyboardDevice |
void LoadFromKeyboardDevice(SdlKeyboardWrapper) |
Loads from keyboard wrapper. |
LoadFromMouseDevice |
void LoadFromMouseDevice(SdlMouseWrapper) |
Loads from mouse wrapper. |
LoadFromWebDevice |
void LoadFromWebDevice(WebControllerDevice) |
Loads from web controller. |
ClearRuntimeState |
void ClearRuntimeState() |
Clears runtime fields. Preserves serialized properties. |
NotifyStateChanged |
void NotifyStateChanged() |
Raises PropertyChanged for IsOnline, StatusText, InputState. |
ToString |
string |
Returns "{ResolvedName} [{InstanceGuid:N}]". |
File: PadForge.Engine/Data/DeadZoneShape.cs
Namespace: PadForge.Engine.Data
Deadzone algorithm for thumbstick axes.
public enum DeadZoneShape
{
Axial = 0, // Independent per-axis (square/cross shape). Legacy behavior.
Radial = 1, // Circular/elliptical magnitude check, no output rescaling.
ScaledRadial = 2, // Circular/elliptical with output rescaling (industry standard). DEFAULT.
SlopedAxial = 3, // Axis-dependent thresholds: DZ grows as other axis increases.
SlopedScaledAxial = 4, // Sloped axis-dependent with output rescaling.
Hybrid = 5, // Scaled Radial followed by Sloped Scaled Axial (best hybrid).
}File: PadForge.Engine/Data/MappingTranslation.cs
Namespace: PadForge.Engine.Data
Translates mapping property names between virtual controller layouts using positional equivalence.
public enum ControlCategory { Button, Axis, AxisNeg, DPad }
public record MappingSlot(ControlCategory Category, int Position);MappingSlot represents a canonical position (e.g., "3rd button", "1st axis negative"). Translation converts source property name to MappingSlot, then to the target layout's property name.
| Method | Signature | Description |
|---|---|---|
GetPosition |
static MappingSlot GetPosition(string propertyName, VirtualControllerType type, bool isExtended) |
Property name to canonical MappingSlot
|
GetPropertyName |
static string GetPropertyName(MappingSlot slot, VirtualControllerType type, bool isExtended) |
Canonical MappingSlot to property name |
IsSameLayout |
static bool IsSameLayout(VirtualControllerType srcType, bool srcIsExtended, VirtualControllerType tgtType, bool tgtIsExtended) |
true if source and target share property names |
GetLayoutLabel |
static string GetLayoutLabel(VirtualControllerType type, bool isExtended) |
Display label (e.g., "Xbox 360", "Extended", "MIDI", "KB+M") |
| Layout | Property Name Examples | Notes |
|---|---|---|
| Gamepad (Microsoft / PlayStation / Extended gamepad preset) |
ButtonA, LeftThumbAxisX, DPadUp
|
Microsoft and PlayStation share property names. Buttons: A=0..Guide=10. Axes: LX=0..RT=5. |
| Extended Custom |
ExtendedBtn0, ExtendedAxis2, ExtendedAxis2Neg, ExtendedPov0Up
|
Indexed by position. POV 0 only maps to D-Pad. |
| MIDI |
MidiNote0, MidiCC3, MidiCC3Neg
|
No D-Pad support (returns null). |
| KB+M |
KbmMBtn0, KbmMouseX, KbmMouseXNeg, KbmKey20, KbmScroll
|
Mouse buttons 0–4, VK codes, 3 mouse axes. D-Pad mapped to arrow keys. |
private enum LayoutKind { Gamepad, Extended, Midi, Kbm }- Microsoft, PlayStation, and Extended gamepad preset all resolve to
LayoutKind.Gamepad. - Extended with
isExtended=true(custom HID descriptor) resolves toLayoutKind.Extended. -
IsSameLayoutcompares resolvedLayoutKindvalues.
File: PadForge.Engine/Common/SDL3Minimal.cs
Namespace: SDL3
Minimal SDL3 P/Invoke declarations for joystick, gamepad, keyboard, mouse, and haptic. Only functions used by PadForge are declared. Native library: "SDL3".
| Constant | Value | Description |
|---|---|---|
SDL_INIT_VIDEO |
0x00000020 |
Required for keyboard/mouse |
SDL_INIT_JOYSTICK |
0x00000200 |
Joystick subsystem |
SDL_INIT_HAPTIC |
0x00001000 |
Haptic subsystem |
SDL_INIT_GAMEPAD |
0x00002000 |
Gamepad subsystem (was SDL_INIT_GAMECONTROLLER) |
| Constant | Value |
|---|---|
SDL_HAT_CENTERED |
0x00 |
SDL_HAT_UP |
0x01 |
SDL_HAT_RIGHT |
0x02 |
SDL_HAT_DOWN |
0x04 |
SDL_HAT_LEFT |
0x08 |
SDL_HAT_RIGHTUP |
0x03 |
SDL_HAT_RIGHTDOWN |
0x06 |
SDL_HAT_LEFTUP |
0x09 |
SDL_HAT_LEFTDOWN |
0x0C |
| Constant | Value | Description |
|---|---|---|
SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS |
"SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS" |
Allow events when app not focused |
SDL_HINT_JOYSTICK_RAWINPUT |
"SDL_JOYSTICK_RAWINPUT" |
Do NOT set (conflicts with XInput enumeration) |
SDL_HINT_JOYSTICK_XINPUT |
"SDL_JOYSTICK_XINPUT" |
Enables Xbox controller enumeration |
SDL_HINT_JOYSTICK_HIDAPI_SWITCH2 |
"SDL_JOYSTICK_HIDAPI_SWITCH2" |
Switch 2 controller support |
SDL_HINT_VIDEO_ALLOW_SCREENSAVER |
"SDL_VIDEO_ALLOW_SCREENSAVER" |
Allow screensaver |
SDL_JoystickType:
| Value | Name |
|---|---|
| 0 | SDL_JOYSTICK_TYPE_UNKNOWN |
| 1 | SDL_JOYSTICK_TYPE_GAMEPAD |
| 2 | SDL_JOYSTICK_TYPE_WHEEL |
| 3 | SDL_JOYSTICK_TYPE_ARCADE_STICK |
| 4 | SDL_JOYSTICK_TYPE_FLIGHT_STICK |
| 5 | SDL_JOYSTICK_TYPE_DANCE_PAD |
| 6 | SDL_JOYSTICK_TYPE_GUITAR |
| 7 | SDL_JOYSTICK_TYPE_DRUM_KIT |
| 8 | SDL_JOYSTICK_TYPE_ARCADE_PAD |
| 9 | SDL_JOYSTICK_TYPE_THROTTLE |
| 10 | SDL_JOYSTICK_TYPE_COUNT |
SDL_PowerState:
| Value | Name |
|---|---|
| -1 | SDL_POWERSTATE_ERROR |
| 0 | SDL_POWERSTATE_UNKNOWN |
| 1 | SDL_POWERSTATE_ON_BATTERY |
| 2 | SDL_POWERSTATE_NO_BATTERY |
| 3 | SDL_POWERSTATE_CHARGING |
| 4 | SDL_POWERSTATE_CHARGED |
SDL_GUID (16 bytes): data0 through data15. Methods: ToGuid() (converts to .NET Guid), ToByteArray().
SDL_HapticDirection (16 bytes): type (byte), dir0, dir1, dir2 (int).
SDL_HapticLeftRight (12 bytes): type, length, large_magnitude, small_magnitude.
SDL_HapticConstant (40 bytes): type, direction, length, delay, button, interval, level, attack_length, attack_level, fade_length, fade_level.
SDL_HapticPeriodic (44 bytes): type, direction, length, delay, button, interval, period, magnitude, offset, phase, attack_length, attack_level, fade_length, fade_level.
SDL_HapticCondition (68 bytes): type, direction, length, delay, button, interval, per-axis arrays (3 axes): right_sat[0-2], left_sat[0-2], right_coeff[0-2], left_coeff[0-2], deadband[0-2], center[0-2].
SDL_HapticRamp (44 bytes): type, direction, length, delay, button, interval, start, end, attack_length, attack_level, fade_length, fade_level.
SDL_HapticEffect (72 bytes, explicit layout): Union overlaying type, leftright, constant, periodic, condition, ramp all at FieldOffset(0).
| Constant | Value | Description |
|---|---|---|
SDL_HAPTIC_CONSTANT |
1 << 0 |
Constant force |
SDL_HAPTIC_SINE |
1 << 1 |
Sine wave |
SDL_HAPTIC_SQUARE |
1 << 2 |
Square wave |
SDL_HAPTIC_TRIANGLE |
1 << 3 |
Triangle wave |
SDL_HAPTIC_SAWTOOTHUP |
1 << 4 |
Sawtooth up |
SDL_HAPTIC_SAWTOOTHDOWN |
1 << 5 |
Sawtooth down |
SDL_HAPTIC_RAMP |
1 << 6 |
Ramp |
SDL_HAPTIC_SPRING |
1 << 7 |
Spring condition |
SDL_HAPTIC_DAMPER |
1 << 8 |
Damper condition |
SDL_HAPTIC_INERTIA |
1 << 9 |
Inertia condition |
SDL_HAPTIC_FRICTION |
1 << 10 |
Friction condition |
SDL_HAPTIC_LEFTRIGHT |
1 << 11 |
Left/right dual-motor |
SDL_HAPTIC_CUSTOM |
1 << 15 |
Custom effect |
SDL_HAPTIC_GAIN |
1 << 16 |
Gain control supported |
SDL_HAPTIC_AUTOCENTER |
1 << 17 |
Auto-center supported |
SDL_HAPTIC_INFINITY |
0xFFFFFFFF |
Infinite duration |
SDL_HAPTIC_POLAR |
0 (byte) | Polar direction type |
SDL_HAPTIC_CARTESIAN |
1 (byte) | Cartesian direction type |
SDL_HAPTIC_SPHERICAL |
2 (byte) | Spherical direction type |
SDL_HAPTIC_STEERING_AXIS |
3 (byte) | Steering axis direction type |
| Constant | Value | Description |
|---|---|---|
SDL_GAMEPAD_AXIS_LEFTX |
0 | Left stick X |
SDL_GAMEPAD_AXIS_LEFTY |
1 | Left stick Y |
SDL_GAMEPAD_AXIS_RIGHTX |
2 | Right stick X |
SDL_GAMEPAD_AXIS_RIGHTY |
3 | Right stick Y |
SDL_GAMEPAD_AXIS_LEFT_TRIGGER |
4 | Left trigger |
SDL_GAMEPAD_AXIS_RIGHT_TRIGGER |
5 | Right trigger |
SDL_GAMEPAD_AXIS_COUNT |
6 | Total axis count |
| Constant | Value | Description |
|---|---|---|
SDL_GAMEPAD_BUTTON_SOUTH |
0 | A |
SDL_GAMEPAD_BUTTON_EAST |
1 | B |
SDL_GAMEPAD_BUTTON_WEST |
2 | X |
SDL_GAMEPAD_BUTTON_NORTH |
3 | Y |
SDL_GAMEPAD_BUTTON_BACK |
4 | Back/Select |
SDL_GAMEPAD_BUTTON_GUIDE |
5 | Guide/Home |
SDL_GAMEPAD_BUTTON_START |
6 | Start |
SDL_GAMEPAD_BUTTON_LEFT_STICK |
7 | Left stick click |
SDL_GAMEPAD_BUTTON_RIGHT_STICK |
8 | Right stick click |
SDL_GAMEPAD_BUTTON_LEFT_SHOULDER |
9 | Left bumper |
SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER |
10 | Right bumper |
SDL_GAMEPAD_BUTTON_DPAD_UP |
11 | D-pad up |
SDL_GAMEPAD_BUTTON_DPAD_DOWN |
12 | D-pad down |
SDL_GAMEPAD_BUTTON_DPAD_LEFT |
13 | D-pad left |
SDL_GAMEPAD_BUTTON_DPAD_RIGHT |
14 | D-pad right |
SDL_GAMEPAD_BUTTON_COUNT |
21 | Total button count |
| Constant | Value | Description |
|---|---|---|
SDL_SENSOR_ACCEL |
1 | Accelerometer |
SDL_SENSOR_GYRO |
2 | Gyroscope |
SDL_SENSOR_ACCEL_L |
3 | Left accelerometer |
SDL_SENSOR_GYRO_L |
4 | Left gyroscope |
SDL_SENSOR_ACCEL_R |
5 | Right accelerometer |
SDL_SENSOR_GYRO_R |
6 | Right gyroscope |
| Constant | Value | Description |
|---|---|---|
SDL_BUTTON_LMASK |
1 << 0 |
Left button |
SDL_BUTTON_MMASK |
1 << 1 |
Middle button |
SDL_BUTTON_RMASK |
1 << 2 |
Right button |
SDL_BUTTON_X1MASK |
1 << 3 |
X1 button |
SDL_BUTTON_X2MASK |
1 << 4 |
X2 button |
string[256] array of human-readable Windows VK code names. Built by BuildVirtualKeyNames(). Covers standard keys, modifiers, F1–F24, numpad, OEM keys. Used by SdlKeyboardWrapper.GetDeviceObjects() for button naming.
Lifecycle: SDL_Init, SDL_Quit, SDL_EnableScreenSaver, SDL_GetError, SDL_SetHint, SDL_free
Joystick Enumeration: SDL_GetJoysticks, SDL_GetJoystickGUIDForID, SDL_GetJoystickVendorForID, SDL_GetJoystickProductForID, SDL_GetJoystickProductVersionForID, SDL_GetJoystickTypeForID, SDL_GetJoystickNameForID, SDL_GetJoystickPathForID, SDL_IsGamepad
Gamepad Mappings: SDL_AddGamepadMappingsFromFile, SDL_AddGamepadMapping, GetGamepadMapping
Joystick Instance: SDL_OpenJoystick, SDL_CloseJoystick, SDL_GetJoystickID, SDL_JoystickConnected
Gamepad Instance: SDL_OpenGamepad, SDL_CloseGamepad, SDL_GetGamepadJoystick
Gamepad State: SDL_GetGamepadAxis, SDL_GetGamepadButton
Joystick State: SDL_UpdateJoysticks, SDL_PumpEvents, SDL_GetJoystickAxis, SDL_GetJoystickButton, SDL_GetJoystickHat, SDL_GetNumJoystickAxes, SDL_GetNumJoystickButtons, SDL_GetNumJoystickHats
Joystick Properties: SDL_GetJoystickName, SDL_GetJoystickVendor, SDL_GetJoystickProduct, SDL_GetJoystickProductVersion, SDL_GetJoystickType, SDL_GetJoystickPath, SDL_GetJoystickSerial, SDL_GetJoystickGUID, SDL_GetJoystickProperties, SDL_GetBooleanProperty, SDL_GetJoystickPowerInfo
Sensors: SDL_GamepadHasSensor, SDL_SetGamepadSensorEnabled, SDL_GetGamepadSensorData
Rumble: SDL_RumbleJoystick
Haptic: SDL_OpenHapticFromJoystick, SDL_CloseHaptic, SDL_GetHapticFeatures, SDL_CreateHapticEffect, SDL_UpdateHapticEffect, SDL_RunHapticEffect, SDL_StopHapticEffect, SDL_DestroyHapticEffect, SDL_SetHapticGain, SDL_GetNumHapticAxes
Keyboard: SDL_GetKeyboards, SDL_GetKeyboardNameForID, SDL_GetKeyboardState
Mouse: SDL_GetMice, SDL_GetMouseNameForID, SDL_GetMouseState, SDL_GetRelativeMouseState
Version: SDL_GetVersion, SDL_Linked_Version (returns (major, minor, patch) tuple)
- Architecture Overview: Solution structure, how Engine and App assemblies relate
-
Input Pipeline: 6-step pipeline consuming
CustomInputState,Gamepad,PadSetting -
SDL3 Integration: SDL3 P/Invoke details,
SdlDeviceWrapperusage, haptic strategies -
Virtual Controllers:
IVirtualControllerimplementations consumingGamepad,ExtendedRawState,KbmRawState,MidiRawState -
Settings and Serialization:
PadSettingXML persistence,UserDevice/UserSettingserialization -
HIDMaestro Deep Dive:
HMaestroVirtualControllerlifecycle, FFB through HM PID descriptors, OpenXInput shim