-
Notifications
You must be signed in to change notification settings - Fork 6
ViewModels
All ViewModels live in PadForge.App/ViewModels/ under the PadForge.ViewModels namespace. They use CommunityToolkit.Mvvm (ObservableObject, RelayCommand).
File: ViewModelBase.cs
Abstract base class for all PadForge ViewModels. Extends ObservableObject (which provides INotifyPropertyChanged and SetProperty helpers).
public abstract class ViewModelBase : ObservableObject
{
public string Title { get; set; }
}| Property | Type | Description |
|---|---|---|
Title |
string |
Display title for the view. Used by navigation and page headers. |
ViewModelBase subscribes to Strings.CultureChanged in its constructor to support live language switching. When the user changes the UI language at runtime, Strings.ChangeCulture() raises CultureChanged, and every living ViewModel's OnCultureChanged() method is called. Derived ViewModels override OnCultureChanged() to refresh culture-dependent computed properties (status text, display titles, etc.).
Strings.CultureChanged uses a weak event pattern: instance-method subscribers are stored as (WeakReference<Target>, MethodInfo) pairs rather than strong delegates. This means short-lived ViewModels are eligible for garbage collection without needing to explicitly unsubscribe. Dead entries are pruned automatically on each raise. Static-method subscribers use strong references.
protected ViewModelBase()
{
Strings.CultureChanged += OnCultureChanged; // weak — no GC leak
}
protected virtual void OnCultureChanged() { }File: MainViewModel.cs
Root ViewModel for the application. Serves as the DataContext for MainWindow. Manages navigation state, the collection of pad ViewModels, and app-wide status.
public partial class MainViewModel : ViewModelBase| Property | Type | Description |
|---|---|---|
Pads |
ObservableCollection<PadViewModel> |
Virtual controller pad VMs (up to InputManager.MaxPads = 16). |
NavControllerItems |
ObservableCollection<NavControllerItemViewModel> |
Sidebar navigation items for created controller slots. |
Dashboard |
DashboardViewModel |
Dashboard overview VM. |
Devices |
DevicesViewModel |
Devices list VM. |
Settings |
SettingsViewModel |
Application settings VM. |
| Property | Type | Description |
|---|---|---|
SelectedNavTag |
string |
Currently selected nav tag: "Dashboard", "Pad1".."Pad16", "Devices", "Settings", "About", "Profiles". |
IsPadPageSelected |
bool (computed) |
true if a Pad page (Pad1..Pad16) is currently selected. |
SelectedPadIndex |
int (computed) |
Zero-based pad index for the selected Pad page, or -1. |
SelectedPad |
PadViewModel (computed) |
PadViewModel for the selected Pad page, or null. |
| Property | Type | Description |
|---|---|---|
StatusText |
string |
Status bar text (default: "Ready"). |
IsEngineRunning |
bool |
Whether the input engine polling loop is active. Also raises EngineStatusText. |
EngineStatusText |
string (computed) |
"Running" or "Stopped". |
PollingFrequency |
double |
Current input polling frequency in Hz. |
ConnectedDeviceCount |
int |
Number of currently connected input devices. |
IsViGEmInstalled |
bool |
Whether the ViGEmBus driver is available. |
| Command | Type | Execute | CanExecute |
|---|---|---|---|
StartEngineCommand |
RelayCommand |
Raises StartEngineRequested
|
!IsEngineRunning |
StopEngineCommand |
RelayCommand |
Raises StopEngineRequested
|
IsEngineRunning |
| Event | Signature | Description |
|---|---|---|
StartEngineRequested |
EventHandler |
User requests engine start. |
StopEngineRequested |
EventHandler |
User requests engine stop. |
NavControllerItemsRefreshed |
EventHandler |
Raised after RefreshNavControllerItems() completes. |
public void RefreshNavControllerItems()Rebuilds sidebar controller entries from SettingsManager.SlotCreated[]. Computes per-type instance numbering (Xbox #1, DS4 #1, etc.) and updates NavControllerItems, SlotLabel, TypeInstanceLabel, icon keys, and enabled states.
public void ForceNavControllerItemsRefreshed()Forces a NavControllerItemsRefreshed event even when slot count hasn't changed. Used after slot reorder/swap.
public void RefreshCommands()Calls NotifyCanExecuteChanged() on start/stop commands.
public class NavControllerItemViewModel : ObservableObject
{
public NavControllerItemViewModel(int padIndex);
public int PadIndex { get; }
public string Tag { get; } // "Pad1".."Pad16"
public int SlotNumber { get; set; } // 1-based global number
public string InstanceLabel { get; set; } // Per-type instance number
public string IconKey { get; set; } // "XboxControllerIcon"/"DS4ControllerIcon"/"VJoyControllerIcon"/"MidiControllerIcon"/"KeyboardMouseControllerIcon"
public bool IsEnabled { get; set; }
public int ConnectedDeviceCount { get; set; }
}File: DashboardViewModel.cs
ViewModel for the Dashboard page. At-a-glance overview of all controller slots, engine status, device counts, driver status, and DSU settings.
public partial class DashboardViewModel : ViewModelBase| Property | Type | Description |
|---|---|---|
SlotSummaries |
ObservableCollection<SlotSummary> |
Summary cards for active virtual controller slots. |
ShowAddController |
bool |
Whether the "Add Controller" card is visible (any type has capacity). |
| Property | Type | Description |
|---|---|---|
EngineStatus |
string |
Current engine status text. Default: "Stopped". |
PollingFrequency |
double |
Polling frequency in Hz. |
PollingFrequencyText |
string (computed) |
Formatted: "987.3 Hz" or "---". |
| Property | Type | Description |
|---|---|---|
TotalDevices |
int |
Total detected input devices. |
OnlineDevices |
int |
Currently connected devices. |
MappedDevices |
int |
Devices with active slot mappings. |
| Property | Type | Description |
|---|---|---|
IsViGEmInstalled |
bool |
ViGEmBus installed. Also raises ViGEmStatusText. |
ViGEmStatusText |
string (computed) |
"Installed" or "Not Installed". |
ViGEmVersion |
string |
ViGEmBus driver version. |
IsHidHideInstalled |
bool |
HidHide installed. Also raises HidHideStatusText. |
HidHideStatusText |
string (computed) |
"Installed" or "Not Installed". |
IsVJoyInstalled |
bool |
vJoy driver installed. Also raises VJoyStatusText. |
VJoyStatusText |
string (computed) |
"Installed" or "Not Installed". |
| Property | Type | Description |
|---|---|---|
EnableDsuMotionServer |
bool |
Whether DSU server is enabled. |
DsuMotionServerPort |
int |
UDP port (clamped 1024-65535, default 26760). |
DsuServerStatus |
string |
Server status for UI display. |
| Property | Type | Description |
|---|---|---|
EnableWebController |
bool |
Whether the web controller server is enabled |
WebControllerPort |
int |
HTTP/WebSocket port (1024-65535, default 8080) |
WebControllerStatus |
string |
Current server status text |
WebControllerClientCount |
int |
Connected browser clients |
public void RefreshActiveSlots(IList<int> activeSlots, bool canAddMore)Rebuilds SlotSummaries to match active slots. Updates sequential global numbering. Sets ShowAddController.
public class SlotSummary : ObservableObject
{
public SlotSummary(int padIndex);
public int PadIndex { get; }
public string SlotLabel { get; set; }
public string DeviceName { get; set; } // Default: "No device"
public bool IsActive { get; set; }
public bool IsVirtualControllerConnected { get; set; }
public int MappedDeviceCount { get; set; }
public int ConnectedDeviceCount { get; set; }
public string StatusText { get; set; } // "Active"/"Idle"/"No mapping"/"Disabled"
public bool IsEnabled { get; set; }
public int SlotNumber { get; set; } // 1-based global number
public string TypeInstanceLabel { get; set; } // Per-type instance
public VirtualControllerType OutputType { get; set; }
}File: PadViewModel.cs
The most complex VM. Represents a single virtual controller slot (one of 16 pads).
public partial class PadViewModel : ViewModelBase
{
public PadViewModel(int padIndex);
}| Property | Type | Description |
|---|---|---|
PadIndex |
int |
Zero-based pad slot index (0-15). |
SlotNumber |
int |
One-based slot number. Settable property with backing field (_slotNumber), initialized to padIndex + 1 in constructor and updated by RefreshNavControllerItems(). |
SlotLabel |
string |
Display label (e.g., "Virtual Controller 1"). |
TypeInstanceLabel |
string |
Per-type instance number label. |
| Property | Type | Description |
|---|---|---|
OutputType |
VirtualControllerType |
Virtual controller output type. Setting triggers ResetDeadZoneSettings(), RebuildMappings(), RebuildStickConfigs(), RebuildTriggerConfigs(), SyncMacroButtonStyle(). |
OutputTypeIndex |
int |
Int binding for ComboBox (0=Xbox, 1=DS4, 2=VJoy, 3=Midi, 4=KeyboardMouse). Maps directly to VirtualControllerType enum values. |
| Property | Type | Description |
|---|---|---|
VJoyConfig |
VJoySlotConfig |
Per-slot vJoy configuration. Subscribes to PropertyChanged for dynamic rebuilds. |
| Property | Type | Description |
|---|---|---|
MidiConfig |
MidiSlotConfig |
Per-slot MIDI configuration. Always present; meaningful when OutputType == Midi. Subscribes to PropertyChanged to push updates to the engine. |
| Property | Type | Description |
|---|---|---|
MappedDevices |
ObservableCollection<MappedDeviceInfo> |
Physical devices mapped to this slot. |
SelectedMappedDevice |
MappedDeviceInfo |
Currently selected device for configuration. |
HasSelectedDevice |
bool (computed) |
Whether a device is selected. |
MappedDeviceName |
string |
Primary mapped device name. |
MappedDeviceGuid |
Guid |
Primary device GUID. |
IsDeviceOnline |
bool |
Whether the primary device is connected. |
public class MappedDeviceInfo : ObservableObject
{
public string Name { get; set; }
public Guid InstanceGuid { get; set; }
public bool IsOnline { get; set; }
}| Property | Type | Description |
|---|---|---|
ButtonA |
bool |
A button state |
ButtonB |
bool |
B button state |
ButtonX |
bool |
X button state |
ButtonY |
bool |
Y button state |
LeftShoulder |
bool |
Left bumper |
RightShoulder |
bool |
Right bumper |
ButtonBack |
bool |
Back/Select |
ButtonStart |
bool |
Start |
LeftThumbButton |
bool |
Left stick click |
RightThumbButton |
bool |
Right stick click |
ButtonGuide |
bool |
Guide/Home |
DPadUp |
bool |
D-Pad Up |
DPadDown |
bool |
D-Pad Down |
DPadLeft |
bool |
D-Pad Left |
DPadRight |
bool |
D-Pad Right |
LeftTrigger |
double |
Left trigger (0.0-1.0) |
RightTrigger |
double |
Right trigger (0.0-1.0) |
ThumbLX |
double |
Left stick X normalized (0.0-1.0, 0.5=center) |
ThumbLY |
double |
Left stick Y normalized |
ThumbRX |
double |
Right stick X normalized |
ThumbRY |
double |
Right stick Y normalized |
RawThumbLX |
short |
Raw left stick X (-32768..32767) |
RawThumbLY |
short |
Raw left stick Y |
RawThumbRX |
short |
Raw right stick X |
RawThumbRY |
short |
Raw right stick Y |
RawLeftTrigger |
byte |
Raw left trigger (0-255) |
RawRightTrigger |
byte |
Raw right trigger |
Per-device preview values (shown on Sticks/Triggers tabs for selected device only):
DeviceThumbLX, DeviceThumbLY, DeviceThumbRX, DeviceThumbRY (double), DeviceRawThumbLX..DeviceRawThumbRY (short), DeviceLeftTrigger, DeviceRightTrigger (double), DeviceRawLeftTrigger, DeviceRawRightTrigger (byte).
Left Stick: LeftDeadZoneX, LeftDeadZoneY, LeftAntiDeadZoneX, LeftAntiDeadZoneY, LeftLinear (all int, clamped 0-100).
Right Stick: RightDeadZoneX, RightDeadZoneY, RightAntiDeadZoneX, RightAntiDeadZoneY, RightLinear (all int, clamped 0-100).
Triggers: LeftTriggerDeadZone, RightTriggerDeadZone, LeftTriggerAntiDeadZone, RightTriggerAntiDeadZone (int, 0-100), LeftTriggerMaxRange, RightTriggerMaxRange (int, 1-100).
Backward compat shims: LeftDeadZone { set => LeftDeadZoneX = LeftDeadZoneY = value; }, RightDeadZone same pattern.
| Property | Type | Description |
|---|---|---|
ForceOverallGain |
int |
Overall FF gain (0-100, default 100). |
LeftMotorStrength |
int |
Left motor strength (0-100). |
RightMotorStrength |
int |
Right motor strength (0-100). |
SwapMotors |
bool |
Swap L/R motors. |
LeftMotorDisplay |
double |
Live left motor value for visualizer. |
RightMotorDisplay |
double |
Live right motor value for visualizer. |
| Property | Type | Description |
|---|---|---|
AudioRumbleEnabled |
bool |
Enable audio bass rumble for this slot. |
AudioRumbleSensitivity |
int |
Bass detection sensitivity. |
AudioRumbleCutoffHz |
int |
Low-pass cutoff frequency in Hz. |
AudioRumbleLeftMotor |
int |
Left motor strength percentage for audio rumble (0-100). |
AudioRumbleRightMotor |
int |
Right motor strength percentage for audio rumble (0-100). |
AudioRumbleLevelMeter |
double |
Live bass energy level for UI meter display. |
Reset commands: ResetAudioRumbleSensitivityCommand, ResetAudioRumbleCutoffHzCommand, ResetAudioRumbleLeftMotorCommand, ResetAudioRumbleRightMotorCommand.
| Property | Type | Description |
|---|---|---|
Mappings |
ObservableCollection<MappingItem> |
Mapping rows for this slot. |
public void RebuildMappings()Clears and rebuilds Mappings based on OutputType and VJoyConfig. For gamepad presets: 21 standard items (11 buttons + 4 D-Pad + 2 triggers + 4 stick axes). For custom vJoy: dynamic numbered items. Raises MappingsRebuilt.
| Property | Type | Description |
|---|---|---|
StickConfigs |
ObservableCollection<StickConfigItem> |
Dynamic stick config items. |
TriggerConfigs |
ObservableCollection<TriggerConfigItem> |
Dynamic trigger config items. |
public void RebuildStickConfigs()
public void RebuildTriggerConfigs()
public void SyncStickItemFromVm(StickConfigItem item)
public void SyncTriggerItemFromVm(TriggerConfigItem item)
public void SyncAllConfigItemsFromVm()| Property | Type | Description |
|---|---|---|
Macros |
ObservableCollection<MacroItem> |
Configured macros. |
SelectedMacro |
MacroItem |
Selected macro. |
HasSelectedMacro |
bool (computed) |
Whether a macro is selected. |
AddMacroCommand |
RelayCommand |
Creates new macro with derived button style. |
RemoveMacroCommand |
RelayCommand |
Removes selected macro. |
| Property | Type | Description |
|---|---|---|
CurrentRecordingTarget |
string |
TargetSettingName of the mapping being recorded. null when idle. Drives controller-tab flash animation. |
SelectedConfigTab |
int |
Active tab index (0=Controller, 1=Macros, 2=Mappings, 3=Sticks, 4=Triggers, 5=Force Feedback). |
VJoyOutputSnapshot |
VJoyRawState |
Latest vJoy raw output state for schematic view. |
| Event | Signature |
|---|---|
SelectedDeviceChanged |
EventHandler<MappedDeviceInfo> |
MappingsRebuilt |
EventHandler |
TestRumbleRequested |
EventHandler |
TestLeftMotorRequested |
EventHandler |
TestRightMotorRequested |
EventHandler |
File: DevicesViewModel.cs
ViewModel for the Devices page. Shows all detected input devices and raw input state for the selected device.
public partial class DevicesViewModel : ViewModelBase| Property | Type | Description |
|---|---|---|
Devices |
ObservableCollection<DeviceRowViewModel> |
All known devices. |
SelectedDevice |
DeviceRowViewModel |
Currently selected device. |
HasSelectedDevice |
bool (computed) |
Whether a device is selected. |
TotalCount |
int |
Total detected devices. |
OnlineCount |
int |
Connected devices. |
| Property | Type | Description |
|---|---|---|
RawAxes |
ObservableCollection<AxisDisplayItem> |
Axis values for progress bars. |
RawButtons |
ObservableCollection<ButtonDisplayItem> |
Button states for circles. |
RawPovs |
ObservableCollection<PovDisplayItem> |
POV hat values for compass. |
KeyboardKeys |
ObservableCollection<KeyboardKeyItem> |
Keyboard key layout items. |
IsKeyboardDevice |
bool |
Whether selected device is a keyboard. |
SelectedButtonTotal |
int |
Total buttons on selected device. |
HasRawData |
bool |
Whether raw data is available. |
HasGyroData / HasAccelData
|
bool |
Whether gyro/accel data available. |
GyroX / GyroY / GyroZ
|
double |
Gyroscope values. |
AccelX / AccelY / AccelZ
|
double |
Accelerometer values. |
LastRawStateDeviceGuid |
Guid (internal) |
Tracks which device's collections are populated. |
| Property | Type | Description |
|---|---|---|
ActiveSlotItems |
ObservableCollection<SlotButtonItem> |
Dynamic slot assignment buttons. |
ToggleSlotCommand |
RelayCommand<int> |
Toggle device assignment to slot. |
| Command | Type | Execute |
|---|---|---|
RefreshCommand |
RelayCommand |
Raises RefreshRequested. |
AssignToSlotCommand |
RelayCommand<int> |
Raises AssignToSlotRequested. |
HideDeviceCommand |
RelayCommand |
Marks device hidden, raises HideDeviceRequested. |
RemoveDeviceCommand |
RelayCommand |
Removes device row and raises RemoveDeviceRequested. |
| Event | Signature |
|---|---|
RefreshRequested |
EventHandler |
AssignToSlotRequested |
EventHandler<int> |
HideDeviceRequested |
EventHandler<Guid> |
RemoveDeviceRequested |
EventHandler<Guid> |
ToggleSlotRequested |
EventHandler<int> |
internal void RebuildRawStateCollections(int axisCount, int buttonCount, int povCount, bool isKeyboard = false)
internal void ClearRawState()
public void RefreshSlotButtons()
public DeviceRowViewModel FindByGuid(Guid instanceGuid)
public void RefreshCounts()public class AxisDisplayItem : ObservableObject
{
public int Index { get; set; }
public string Name { get; set; }
public double NormalizedValue { get; set; } // 0.0-1.0
public int RawValue { get; set; } // 0-65535
}public class ButtonDisplayItem : ObservableObject
{
public int Index { get; set; }
public bool IsPressed { get; set; }
}public class KeyboardKeyItem : ObservableObject
{
public int VKeyIndex { get; set; }
public string Label { get; set; }
public double X { get; set; }
public double Y { get; set; }
public double KeyWidth { get; set; }
public double KeyHeight { get; set; }
public bool IsPressed { get; set; }
public const double LayoutWidth = 556;
public const double LayoutHeight = 136;
public static ObservableCollection<KeyboardKeyItem> BuildLayout()
public static bool IsVKeyPressed(bool[] buttons, int vk)
}BuildLayout() generates a full ANSI QWERTY keyboard with numpad. Constants: u=24 (key width unit), g=2 (gap), kh=20 (key height), rh=22 (row height). Wrapped in a Viewbox for auto-scaling.
public class PovDisplayItem : ObservableObject
{
public int Index { get; set; }
public int Centidegrees { get; set; } // 0-35900, or -1 for centered
public bool IsCentered { get; } // computed
public double AngleDegrees { get; } // computed (0-359)
}public class SlotButtonItem : ObservableObject
{
public int PadIndex { get; set; } // 0-based slot index
public int SlotNumber { get; set; } // 1-based display number
public bool IsAssigned { get; set; }
}public class DeviceRowViewModel : ObservableObject
{
// Identity
public Guid InstanceGuid { get; set; }
public string DeviceName { get; set; }
public string ProductName { get; set; }
public Guid ProductGuid { get; set; }
public ushort VendorId { get; set; }
public ushort ProductId { get; set; }
public string VendorIdHex { get; } // computed "045E"
public string ProductIdHex { get; } // computed "028E"
// Status
public bool IsOnline { get; set; }
public bool IsEnabled { get; set; }
public bool IsHidden { get; set; }
public string StatusText { get; } // computed "Online"/"Offline"/"Disabled"
// Capabilities
public int AxisCount { get; set; }
public int ButtonCount { get; set; }
public int PovCount { get; set; }
public string DeviceType { get; set; }
public bool HasRumble { get; set; }
public bool HasGyro { get; set; }
public bool HasAccel { get; set; }
// Slot assignment
public List<int> AssignedSlots { get; }
public ObservableCollection<SlotBadge> SlotBadges { get; }
public bool IsUnassigned { get; }
public void SetAssignedSlots(List<int> slots);
// Misc
public string DevicePath { get; set; }
public string CapabilitiesSummary { get; }
public void NotifyDisplayChanged();
}| Property | Type | Description |
|---|---|---|
ForceRawJoystickMode |
bool |
Bypass SDL3 gamepad remapping and read raw joystick indices |
File: SettingsViewModel.cs
ViewModel for the Settings page.
public partial class SettingsViewModel : ViewModelBase| Property | Type | Description |
|---|---|---|
SelectedThemeIndex |
int |
0=System, 1=Light, 2=Dark. Raises ThemeChanged. |
ViGEmBus:
| Property | Type | Description |
|---|---|---|
IsViGEmInstalled |
bool |
Installed flag. |
ViGEmStatusText |
string (computed) |
"Installed" / "Not Installed". |
ViGEmVersion |
string |
Version string. |
InstallViGEmCommand |
RelayCommand |
CanExecute: !IsViGEmInstalled. |
UninstallViGEmCommand |
RelayCommand |
CanExecute: IsViGEmInstalled && !HasAnyViGEmSlots(). |
HidHide:
| Property | Type | Description |
|---|---|---|
IsHidHideInstalled |
bool |
Installed flag. |
HidHideStatusText |
string (computed) |
"Installed" / "Not Installed". |
HidHideVersion |
string |
Version string. |
InstallHidHideCommand |
RelayCommand |
CanExecute: !IsHidHideInstalled. |
UninstallHidHideCommand |
RelayCommand |
CanExecute: IsHidHideInstalled. |
vJoy:
| Property | Type | Description |
|---|---|---|
IsVJoyInstalled |
bool |
Installed flag. |
VJoyStatusText |
string (computed) |
"Installed" / "Not Installed". |
VJoyVersion |
string |
Version string. |
InstallVJoyCommand |
RelayCommand |
CanExecute: !IsVJoyInstalled. |
UninstallVJoyCommand |
RelayCommand |
CanExecute: IsVJoyInstalled && !HasAnyVJoySlots(). |
MIDI Services:
| Property | Type | Description |
|---|---|---|
IsMidiServicesInstalled |
bool |
Installed flag. |
MidiServicesStatusText |
string (computed) |
"Installed" / "Not Installed". |
IsMidiOsSupported |
bool (static) |
True if OS build >= 26100 (Win11 24H2). |
InstallMidiServicesCommand |
RelayCommand |
CanExecute: !IsMidiServicesInstalled && IsMidiOsSupported. |
UninstallMidiServicesCommand |
RelayCommand |
CanExecute: IsMidiServicesInstalled && !HasAnyMidiSlots(). |
HidHide Whitelist:
| Property | Type | Description |
|---|---|---|
HidHideWhitelistPaths |
ObservableCollection<string> |
Application paths whitelisted in HidHide (user-visible paths, not DOS device paths). |
SelectedWhitelistPath |
string |
Currently selected path in the whitelist ListBox. |
AddWhitelistPathCommand |
RelayCommand |
Opens file dialog. CanExecute: IsHidHideInstalled. |
RemoveWhitelistPathCommand |
RelayCommand |
Removes selected path. CanExecute: SelectedWhitelistPath != null. |
Uninstall guards:
internal Func<bool> HasAnyViGEmSlots { get; set; } // set by MainWindow
internal Func<bool> HasAnyVJoySlots { get; set; } // set by MainWindow
internal Func<bool> HasAnyMidiSlots { get; set; } // set by MainWindow
internal Func<bool> HasAnyHidHideDevices { get; set; } // set by MainWindow
public void RefreshDriverGuards()Uninstall buttons are disabled when the corresponding feature is actively in use:
- ViGEm: any created slot uses Xbox 360 or DS4
- vJoy: any created slot uses vJoy
- MIDI: any created slot uses MIDI
- HidHide: any device has HidHide enabled
| Property | Type | Description |
|---|---|---|
AutoStartEngine |
bool |
Auto-start on launch (default true). |
MinimizeToTray |
bool |
Minimize to system tray. |
StartMinimized |
bool |
Start minimized. |
StartAtLogin |
bool |
Auto-start on login. |
EnablePollingOnFocusLoss |
bool |
Continue polling on focus loss (default true). |
PollingRateMs |
int |
Target polling interval (clamped 1-16). |
Use2DControllerView |
bool |
Show 2D view instead of 3D. |
| Property | Type | Description |
|---|---|---|
SettingsFilePath |
string |
Path to current settings file. |
HasUnsavedChanges |
bool |
Unsaved changes flag. |
SaveCommand |
RelayCommand |
Raises SaveRequested. |
ReloadCommand |
RelayCommand |
Raises ReloadRequested. |
ResetCommand |
RelayCommand |
Raises ResetRequested. |
OpenSettingsFolderCommand |
RelayCommand |
Raises OpenSettingsFolderRequested. |
| Property | Type | Description |
|---|---|---|
SdlVersion |
string |
SDL3 library version. |
ApplicationVersion |
string |
App version. |
RuntimeVersion |
string |
.NET runtime version. |
| Property | Type | Description |
|---|---|---|
EnableAutoProfileSwitching |
bool |
Auto-profile switching enabled. |
ProfileItems |
ObservableCollection<ProfileListItem> |
Profile list. |
SelectedProfile |
ProfileListItem |
Selected profile. |
ActiveProfileInfo |
string |
Active profile display text. |
Profile commands: NewProfileCommand, SaveAsProfileCommand, DeleteProfileCommand, EditProfileCommand, LoadProfileCommand.
public class ProfileListItem : ObservableObject
{
public const string DefaultProfileId = "__default__";
public bool IsDefault { get; }
public string Id { get; set; }
public string Name { get; set; }
public string Executables { get; set; }
public string TopologyLabel { get; set; }
public int XboxCount { get; set; }
public int DS4Count { get; set; }
public int VJoyCount { get; set; }
public int MidiCount { get; set; }
}File: MappingItem.cs
Represents a single mapping row linking a physical input source to an output target.
public class MappingItem : ObservableObject
{
public MappingItem(string targetLabel, string targetSettingName, MappingCategory category, string negSettingName = null);
}| Property | Type | Description |
|---|---|---|
TargetLabel |
string |
Human-readable output label (e.g., "A", "Left Stick X"). |
TargetSettingName |
string |
PadSetting property name (e.g., "ButtonA", "LeftThumbAxisX"). |
Category |
MappingCategory |
Grouping category. |
NegSettingName |
string |
Negative direction property (e.g., "LeftThumbAxisXNeg"). Null for non-axis. |
HasNegDirection |
bool (computed) |
Whether negative direction is supported. |
SourceDescriptor |
string |
Mapping descriptor: "Button 0", "Axis 1", "IHAxis 2", "POV 0 Up". Empty=unmapped. |
NegSourceDescriptor |
string |
Negative-direction descriptor. |
SourceDisplayText |
string (computed) |
Human-readable source text. For bidirectional: "neg / pos". |
IsMapped |
bool (computed) |
Whether source is assigned. |
IsRecording |
bool |
Recording mode active. |
RecordButtonText |
string (computed) |
"Record" or "Recording...". |
CurrentValueText |
string |
Live raw value (updated at 30Hz). |
IsInverted |
bool |
Axis inversion. Setting calls RebuildDescriptor(). |
IsHalfAxis |
bool |
Half-axis mode. Setting calls RebuildDescriptor(). |
InputChoices |
ObservableCollection<string> |
Available source input options for dropdown selection. |
SelectedInput |
string |
Currently selected source input from the dropdown. |
public void SyncSelectedInputFromDescriptor()Synchronizes the SelectedInput dropdown selection from the current SourceDescriptor value. Called when descriptors are loaded or changed externally to keep the dropdown in sync.
public void LoadDescriptor(string descriptor) // Parses I/H prefixes, sets flags + descriptor
public void LoadNegDescriptor(string descriptor) // Sets NegSourceDescriptor
public void SetResolvedSourceText(string text) // Human-readable override
public void SetResolvedNegText(string text)| Command | Description |
|---|---|
ToggleRecordCommand |
Toggles recording; raises StartRecordingRequested / StopRecordingRequested. |
ClearCommand |
Clears source, neg source, inversion, half-axis. |
public enum MappingCategory { Buttons, DPad, Triggers, LeftStick, RightStick }File: MacroItem.cs
Represents a single macro with trigger condition and action sequence.
public class MacroItem : ObservableObject| Property | Type | Description |
|---|---|---|
Name |
string |
Macro name (default "New Macro"). |
IsEnabled |
bool |
Active flag. |
| Property | Type | Description |
|---|---|---|
TriggerButtons |
ushort |
Xbox bitmask flags (all must be pressed simultaneously). |
TriggerCustomButtonWords |
uint[4] |
vJoy 128-bit button bitmask. [XmlIgnore]. |
TriggerCustomButtons |
string |
Serializable hex form of TriggerCustomButtonWords. |
UsesCustomTrigger |
bool (computed) |
Any custom trigger button set. |
TriggerSource |
MacroTriggerSource |
InputDevice or OutputController. |
TriggerDisplayText |
string (computed) |
Human-readable trigger combo. |
TriggerDeviceGuid |
Guid |
Raw device trigger source. Guid.Empty = use bitmask. |
TriggerRawButtons |
int[] |
Raw button indices. |
UsesRawTrigger |
bool (computed) |
Uses raw device button trigger path. |
TriggerAxisTargetList |
string |
Comma-separated axis names for combo trigger (e.g., "LeftStickX,LeftTrigger"). |
TriggerAxisThreshold |
int |
Axis threshold percentage (1-100). Normalized axis must exceed this. |
TriggerPovs |
string[] |
POV trigger directions as "povIndex:centidegrees" strings. |
UsesAxisTrigger |
bool (computed) |
True when TriggerAxisTargetList is non-empty. |
UsesPovTrigger |
bool (computed) |
True when TriggerPovs has entries. |
IsRecordingTrigger |
bool |
Recording trigger combo. |
RecordingLiveText |
string |
Live display during recording. [XmlIgnore]. |
ButtonStyle |
MacroButtonStyle |
Display names style. [XmlIgnore]. |
CustomButtonCount |
int |
vJoy button count. [XmlIgnore]. |
| Property | Type | Description |
|---|---|---|
TriggerMode |
MacroTriggerMode |
OnPress, OnRelease, WhileHeld. |
ConsumeTriggerButtons |
bool |
Remove trigger buttons from output (default true). |
| Property | Type | Description |
|---|---|---|
Actions |
ObservableCollection<MacroAction> |
Ordered action sequence. |
SelectedAction |
MacroAction |
Selected action. |
| Property | Type | Description |
|---|---|---|
RepeatMode |
MacroRepeatMode |
Once, FixedCount, UntilRelease. |
RepeatCount |
int |
Repeat count (min 1). |
RepeatDelayMs |
int |
Delay between repeats (min 0). |
| Property | Type | Description |
|---|---|---|
IsExecuting |
bool |
Currently executing. |
CurrentActionIndex |
int |
Position in sequence. |
RemainingRepeats |
int |
Remaining repeats. |
ActionStartTime |
DateTime |
When current action started. |
WasTriggerActive |
bool |
Trigger active on previous frame. |
public class MacroAction : ObservableObject
{
public MacroActionType Type { get; set; }
public bool IsButtonType { get; } // ButtonPress or ButtonRelease
public bool IsKeyType { get; } // KeyPress or KeyRelease
public bool IsDurationType { get; } // ButtonPress, KeyPress, or Delay
public bool IsAxisType { get; } // AxisSet
public MacroButtonStyle ButtonStyle { get; set; }
public int CustomButtonCount { get; set; }
public ushort ButtonFlags { get; set; }
public uint[] CustomButtonWords { get; set; } // 4 x uint = 128 buttons
public string CustomButtons { get; set; } // Serializable hex
public void SetCustomButton(int index, bool pressed);
public bool IsCustomButtonPressed(int index);
public bool HasCustomButtons { get; }
public IReadOnlyList<GamepadButtonOption> ButtonOptions { get; }
public int KeyCode { get; set; }
public VirtualKey SelectedVirtualKey { get; set; }
public string KeyString { get; set; } // "{Control}{Alt}{Delete}" format
public int[] ParsedKeyCodes { get; }
public static int[] ParseKeyString(string keyString);
public static string FormatKeyString(int[] keyCodes);
public VirtualKey SelectedKeyToAdd { get; set; } // Auto-appends to KeyString
public RelayCommand ClearKeyStringCommand { get; }
public int DurationMs { get; set; } // Hold/delay duration
public short AxisValue { get; set; } // -32768..32767
public MacroAxisTarget AxisTarget { get; set; }
public float MouseSensitivity { get; set; } // Mouse/scroll sensitivity (default 10)
public MacroMouseButton MouseButton { get; set; } // Left, Right, Middle, X1, X2
public bool IsMouseButtonType { get; } // Visibility flag for mouse button UI
public MacroAxisSource AxisSource { get; set; } // OutputController or InputDevice
public Guid SourceDeviceGuid { get; set; } // Physical device GUID for InputDevice source
public int SourceDeviceAxisIndex { get; set; } // Axis index on the source device
public double MouseAccumulator { get; set; } // Internal sub-pixel precision accumulator
public string DisplayText { get; } // Computed summary
}public class GamepadButtonOption : ObservableObject
{
public GamepadButtonOption(MacroAction parent, string label, ushort flag); // Xbox/DS4 bitmask mode
public GamepadButtonOption(MacroAction parent, string label, int customIndex); // vJoy custom mode
public string Label { get; internal set; }
public ushort Flag { get; }
public int CustomIndex { get; } // -1 = use Flag
public bool IsChecked { get; set; }
public void Refresh();
}public enum MacroTriggerMode { OnPress, OnRelease, WhileHeld, Always }
public enum MacroTriggerSource { InputDevice, OutputController }
public enum MacroRepeatMode { Once, FixedCount, UntilRelease }
public enum MacroActionType { ButtonPress, ButtonRelease, KeyPress, KeyRelease, Delay, AxisSet,
SystemVolume, AppVolume, MouseMove, MouseButtonPress, MouseButtonRelease, MouseScroll }
public enum MacroAxisTarget { None, LeftStickX, LeftStickY, RightStickX, RightStickY, LeftTrigger, RightTrigger }
public enum MacroButtonStyle { Xbox360, DualShock4, Numbered }
public enum MacroMouseButton { Left, Right, Middle, X1, X2 }
public enum MacroAxisSource { OutputController, InputDevice }public static class MacroButtonNames
{
public static (string Label, ushort Flag)[] GetButtonDefs(MacroButtonStyle style);
public static string FormatButtons(ushort flags, MacroButtonStyle style);
public static string FormatCustomButtons(uint[] words);
public static MacroButtonStyle DeriveStyle(VirtualControllerType outputType, VJoyPreset vJoyPreset = VJoyPreset.Xbox360);
}File: StickConfigItem.cs
Represents one thumbstick section in the dynamic Sticks tab.
public class StickConfigItem : ObservableObject
{
public StickConfigItem(int index, string title, int axisXIndex = -1, int axisYIndex = -1);
public string Title { get; }
public int Index { get; }
public double DeadZoneX { get; set; } // clamped 0-100, has DeadZoneXDigit (int, raw ±32768 units)
public double DeadZoneY { get; set; }
public double AntiDeadZoneX { get; set; }
public double AntiDeadZoneY { get; set; }
public double Linear { get; set; }
public double LiveX { get; set; } // 0.0-1.0 normalized
public double LiveY { get; set; }
public short RawX { get; set; }
public short RawY { get; set; }
public string RawDisplay { get; } // Computed: "X: -1234 (50.0%) Y: 5678 (58.7%)"
public int AxisXIndex { get; } // VJoyRawState axis index (-1 for gamepad)
public int AxisYIndex { get; }
}| Property | Type | Description |
|---|---|---|
DeadZoneShape |
DeadZoneShape (enum) |
Dead zone algorithm. Default ScaledRadial. Setting raises computed booleans below. |
DeadZoneShapeIndex |
int |
Int wrapper for ComboBox SelectedIndex binding. Maps display order (ScaledRadial, Radial, Axial, Hybrid, SlopedScaledAxial, SlopedAxial) to the enum. |
IsAxialShape |
bool (computed) |
True for Axial. |
IsRadialShape |
bool (computed) |
True for Radial or ScaledRadial. |
IsSlopedShape |
bool (computed) |
True for SlopedAxial or SlopedScaledAxial. |
IsHybridShape |
bool (computed) |
True for Hybrid. |
HasSlopedWedges |
bool (computed) |
True for SlopedAxial, SlopedScaledAxial, or Hybrid. Drives dead zone overlay wedge visualization. |
| Property | Type | Description |
|---|---|---|
SensitivityCurveX |
string |
X-axis sensitivity curve. Format: "0,0;0.5,0.2;1,1" (semicolon-separated input,output pairs). Default "0,0;1,1". Setting raises PresetNameX. |
SensitivityCurveY |
string |
Y-axis sensitivity curve. Default "0,0;1,1". Setting raises PresetNameY. |
PresetNameX |
string (computed) |
Matches CurveLut.Presets or "Custom". |
PresetNameY |
string (computed) |
Matches CurveLut.Presets or "Custom". |
CurvePresetNames |
string[] (static) |
["Linear", "Smooth", "Aggressive", "Instant", "S-Curve", "Delay", "Custom"]. |
LiveInputX |
double |
Live input value (normalized 0-1) for CurveEditor binding. |
LiveInputY |
double |
Live input value for Y-axis CurveEditor. |
| Property | Type | Description |
|---|---|---|
CenterOffsetX |
double |
X-axis center offset (-100 to 100%). Corrects stick drift before dead zone. Has CenterOffsetXDigit (int, raw ±32768 units). |
CenterOffsetY |
double |
Y-axis center offset (-100 to 100%). |
IsCalibrating |
bool |
Whether calibration is in progress |
HardwareRawX |
short |
Unprocessed hardware value for calibration |
HardwareRawY |
short |
Unprocessed hardware value for calibration |
StartCalibration() samples HardwareRawX/HardwareRawY over ~0.5s (15 frames at 33ms) and sets offset to negate the average drift.
| Property | Type | Description |
|---|---|---|
MaxRangeX |
double |
X-axis max range (1-100%). Full physical deflection maps to this ceiling. Has MaxRangeXDigit (int). |
MaxRangeY |
double |
Y-axis max range (1-100%). |
| Command | Resets To |
|---|---|
ResetAllCommand |
All properties to defaults (shape, offsets, DZ, ADZ, linear, curves, range) |
ResetDeadZoneShapeCommand |
DeadZoneShape = ScaledRadial |
ResetCenterOffsetXCommand / ResetCenterOffsetYCommand
|
CenterOffset = 0 |
ResetDeadZoneXCommand / ResetDeadZoneYCommand
|
DeadZone = 0 |
ResetAntiDeadZoneXCommand / ResetAntiDeadZoneYCommand
|
AntiDeadZone = 0 |
ResetLinearCommand |
Linear = 0 |
ResetSensitivityXCommand / ResetSensitivityYCommand
|
SensitivityCurve = "0,0;1,1" |
ResetMaxRangeXCommand / ResetMaxRangeYCommand
|
MaxRange = 100 |
internal static double ApplyCurve(double magnitude, string curveString)
internal static PointCollection BuildTriggerCurvePoints(string curveString, double deadZone, double maxRange, int chartSize = 120, int sampleCount = 64)ApplyCurve looks up or builds a spline LUT from the curve string and returns the mapped output. BuildTriggerCurvePoints generates a 0-1 curve with dead zone flattened, used for trigger chart rendering.
File: TriggerConfigItem.cs
Represents one trigger section in the dynamic Triggers tab.
public class TriggerConfigItem : ObservableObject
{
public TriggerConfigItem(int index, string title, int axisIndex = -1);
public string Title { get; }
public int Index { get; }
public double DeadZone { get; set; } // clamped 0-100, has DeadZoneDigit (int, raw 0-65535 units)
public double MaxRange { get; set; } // clamped 1-100, has MaxRangeDigit
public double AntiDeadZone { get; set; } // clamped 0-100, has AntiDeadZoneDigit
public double LiveValue { get; set; } // 0.0-1.0 normalized
public ushort RawValue { get; set; }
public string RawDisplay { get; } // Computed: "32768 (50.0%)"
public int AxisIndex { get; } // VJoyRawState axis index (-1 for gamepad)
}| Property | Type | Description |
|---|---|---|
SensitivityCurve |
string |
Sensitivity curve. Format: "0,0;0.5,0.2;1,1". Default "0,0;1,1". Setting raises PresetName. |
PresetName |
string (computed) |
Matches CurveLut.Presets or "Custom". |
CurvePresetNames |
string[] (static) |
Same as StickConfigItem: ["Linear", "Smooth", "Aggressive", "Instant", "S-Curve", "Delay", "Custom"]. |
LiveInputForCurve |
double |
Live input value (normalized 0-1) for CurveEditor binding. |
| Command | Resets To |
|---|---|
ResetAllCommand |
All properties (DZ, max range, ADZ, curve) |
ResetRangeCommand |
DeadZone = 0, MaxRange = 100 |
ResetAntiDeadZoneCommand |
AntiDeadZone = 0 |
ResetSensitivityCommand |
SensitivityCurve = "0,0;1,1" |
File: VJoySlotConfig.cs
Per-slot vJoy configuration. Drives HID descriptor generation, stick/trigger/POV/button counts, and mapping generation.
public enum VJoyPreset { Xbox360, DualShock4, Custom }
public class VJoySlotConfig : ObservableObject
{
public const int MaxAxes = 8;
public VJoyPreset Preset { get; set; } // Setting calls ApplyPresetDefaults()
public int ThumbstickCount { get; set; } // Clamped: sticks*2 + triggers <= MaxAxes
public int TriggerCount { get; set; } // Clamped: sticks*2 + triggers <= MaxAxes
public int PovCount { get; set; } // Clamped 0-4
public int ButtonCount { get; set; } // Clamped 0-128
public int TotalAxes { get; } // Min(ThumbstickCount*2 + TriggerCount, MaxAxes)
public int MaxThumbsticks { get; } // (MaxAxes - TriggerCount) / 2
public int MaxTriggers { get; } // MaxAxes - ThumbstickCount * 2
public bool IsGamepadPreset { get; } // Preset != Custom
public void ComputeAxisLayout(out int[] stickAxisX, out int[] stickAxisY, out int[] triggerAxis);
public void ApplyPresetDefaults();
}Interleaves sticks and triggers: [StickX, StickY, Trigger] per group.
For min(sticks, triggers) groups:
stickAxisX[g] = g * 3
stickAxisY[g] = g * 3 + 1
triggerAxis[g] = g * 3 + 2
Remaining sticks get consecutive pairs: offset, offset+1
Remaining triggers get consecutive singles: offset++
Example with 3 sticks, 2 triggers:
Axis 0: Stick 1 X Axis 1: Stick 1 Y Axis 2: Trigger 1
Axis 3: Stick 2 X Axis 4: Stick 2 Y Axis 5: Trigger 2
Axis 6: Stick 3 X Axis 7: Stick 3 Y
| Preset | Sticks | Triggers | POVs | Buttons |
|---|---|---|---|---|
| Xbox360 | 2 | 2 | 1 | 11 |
| DualShock4 | 2 | 2 | 1 | 14 |
| Custom | (keep current) |
public class VJoySlotConfigData
{
[XmlAttribute] public int SlotIndex { get; set; }
[XmlAttribute] public VJoyPreset Preset { get; set; }
[XmlAttribute] public int ThumbstickCount { get; set; }
[XmlAttribute] public int TriggerCount { get; set; }
[XmlAttribute] public int PovCount { get; set; }
[XmlAttribute] public int ButtonCount { get; set; }
}File: MidiSlotConfig.cs
Per-slot MIDI configuration. Drives CC number/note number range generation and velocity for MidiVirtualController.
public class MidiSlotConfig : ObservableObject
{
public int Channel { get; set; } // 1-16 (clamped)
public int CcCount { get; set; } // 0-127 (clamped)
public int StartCc { get; set; } // 0-127 (clamped, with interdependent clamping: StartCc + CcCount <= 128)
public int NoteCount { get; set; } // 0-127 (clamped)
public int StartNote { get; set; } // 0-127 (clamped, with interdependent clamping: StartNote + NoteCount <= 128)
public int Velocity { get; set; } // 0-127 (clamped)
}Setters implement interdependent clamping: changing CcCount auto-clamps StartCc so the range stays within 0-127, and vice versa. Same for NoteCount/StartNote.
public class MidiSlotConfigData
{
[XmlAttribute] public int SlotIndex { get; set; }
[XmlAttribute] public int Channel { get; set; } = 1;
[XmlAttribute] public int CcCount { get; set; } = 6;
[XmlAttribute] public int StartCc { get; set; } = 1;
[XmlAttribute] public int NoteCount { get; set; } = 11;
[XmlAttribute] public int StartNote { get; set; } = 60;
[XmlAttribute] public int Velocity { get; set; } = 127;
}