ViewModels Reference
Every ViewModel class in PadForge: what it backs, what it exposes, and how state flows between the UI and the services layer.
v4 (2026-07-12): Refreshed for v4. Remote Link (DashboardViewModel + SettingsViewModel, and the RemoteLinkTrustedPeer / RemoteLinkNearbyPeer rows), the per-(slot, device) DeviceSlotConfig (renamed from PlayStationSlotConfig), KbmSlotConfig (SOCD / Snap Tap), multi-source mapping rows (MappingSourceItem), the mouse-gesture partial, and the Ember dashboard vocabulary all landed since the v3 rewrite. The HIDMaestro SDK surface, OpenXInput shim, thread-pool lifecycle, and bubble-up cascade live on HIDMaestro Deep Dive. If anything here drifts from the live source, the live source wins.
v4.1 (2026-07-30): The Nintendo output type (#215) joins the family. PadViewModel gains the Bass Shakers tab (#236), the slot SOCD card (#240), the flick rotation offset, gyro Acceleration, and the macro Layer dropdown. MacroItem carries the #238 / #253 fire-mode wave and per-macro layer scope (#254). Mapping rows and sources gain Flip Output, per-source Acceleration, and the #251 axis-latch macro actions. The Touchpad partial grows the libinput pointer Response, momentum, and pointer-region cards. DeviceSlotConfig adds synthetic touch pressure (#239) and the Switch home-LED lane (#226).
v4.2: The VR output type (#49) lands as VirtualControllerType.Vr (6), with a VrControllerIcon sidebar entry, a VrOutputSnapshot, and a SteamVR row in both driver-status surfaces (DashboardViewModel.IsSteamVrInstalled, plus the install / uninstall pair on SettingsViewModel). The pad page grows two slot-tier tabs: Bass Shakers at index 16 and Output at index 17 (SOCD plus Keep Controller Awake). DeviceSlotConfig gains headphone-jack volume, the DualSense audio-output path, and persona haptics, with MacroActionType.HeadphoneVolumeUp / HeadphoneVolumeDown (52 / 53) driving the volume. Sony headset head trackers get their own device class (HeadsetMotion, #188). RawHidState replaced the old ExtendedRawState, so the Extended snapshot is now RawHidOutputSnapshot.
v4.3: The mapping picker gains one shared per-slot choice list plus a search box and device-visibility filter (#322 / discussion #302), both on PadViewModel. Voice macros (#317) add the "Microphone" device class, the Voice Macros preview on DevicesViewModel, DeviceRowViewModel.ShowManageVoicePhrases, and MacroActionType.VoiceListenWhileHeld (54). MacroAction grows the pressure-scaled turbo block (#290). StickConfigItem grows stick trackball momentum (#291), the touchpad partial grows the shared momentum knobs, and PadViewModel grows the Gyro Tilt envelope (#292). The Dashboard's web-controller card gains a URL and QR (#296), its SteamVR row goes tiered (#287), and Settings gains the low-battery notification trio (#293).
v4.4: Head tracking moves from SettingsViewModel to DashboardViewModel, which also gains the Razer Chroma (#373), Logitech LIGHTSYNC (#382) and Razer Sensa (#374) enable-plus-status pairs. The Dashboard's driver-status strip is gone, leaving three bare installed flags and the full cards on Settings. DeviceRowViewModel gains ShowQuickCharge (#372), and IsInternalVirtual grows the handheld://, sensor:// and headtrack:// schemes. PadViewModel gains the ExtendedConfig reseed on a profile switch, the Grip option list and IsLoadingPadSetting (#392), the grouped MacroTypeCatalog picker, menu macro cells and .pficons icon packages (#390). The Profiles page gains external control (#366) and the per-profile polling override (#365).
Peripheral outputs (#494): DashboardViewModel loses the Razer Chroma, Logitech LIGHTSYNC and Razer Sensa enable-plus-status pairs. Those outputs are vendor rows on the Devices page now, assigned to a virtual controller like any device, and each row's tab names where its output goes. DeviceSlotConfig gains PeripheralLightingEnabled, the Lighting tab's Control This Device’s Lighting switch for a mouse or keyboard. DeviceRowViewModel gains the "PeripheralLighting" and "PeripheralHaptics" type keys. MacroActionType.SetChromaColor paints the Razer devices assigned to its macro's virtual controller.
All ViewModels live in PadForge.App/ViewModels/ (PadForge.ViewModels namespace). Built on CommunityToolkit.Mvvm (ObservableObject, RelayCommand).
ViewModelBase
File: ViewModelBase.cs
Abstract base class for all ViewModels. Extends ObservableObject (INotifyPropertyChanged, SetProperty).
public abstract class ViewModelBase : ObservableObject
{
private string _title = string.Empty;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
}
| Property |
Type |
Description |
Title |
string |
Display title for the view. Used by navigation and page headers. |
Culture / Localization
The constructor subscribes to Strings.CultureChanged for live language switching. When the UI language changes, every ViewModel's OnCultureChanged() runs, letting derived classes refresh culture-dependent text.
Strings.CultureChanged uses a weak event pattern. Instance-method subscribers are stored as (WeakReference<Target>, MethodInfo) pairs, so short-lived ViewModels can be garbage-collected without unsubscribing. Dead entries are pruned on each raise. Static-method subscribers use strong references.
protected ViewModelBase()
{
Strings.CultureChanged += OnCultureChanged; // weak. No GC leak
}
protected virtual void OnCultureChanged() { }
Per-Setting Reset Commands
File: SettingResetCommands.cs
A partial of sixteen classes that adds one generic ResetSettingCommand (RelayCommand<string>) to each: SettingsViewModel, DashboardViewModel, DeviceRowViewModel, PadViewModel, DeviceSlotConfig, MacroItem, MacroItem.TriggerInputEntry, MacroAction, MacroExpressionVariable, MappingItem, MappingSourceItem, EqBandVm, MenuEditorItem, MenuCellItem, ProfileShortcutViewModel, and RemoteLinkTrustedPeer. The parameter is a property name. CanResetSetting accepts only the names its class lists, and ResetSetting writes that property's default, then raises the property's change notification even when the value was already the default. The SettingResetButton control (PadForge.App/Controls/) binds it with CommandParameter set to the property name. The dedicated Reset…Command members named on this page sit beside it.
NavControllerItemViewModel
File: MainViewModel.cs (defined alongside MainViewModel)
Sidebar entry for one virtual controller. Shows controller type icon, slot number, and per-type instance label.
| Property |
Type |
Description |
PadIndex |
int |
Zero-based pad slot index (0–15). Read-only. |
Tag |
string |
Navigation tag ("Pad1"–"Pad16"). Computed as $"Pad{PadIndex + 1}". |
SlotNumber |
int |
1-based controller number among active slots. |
InstanceLabel |
string |
Per-type instance label (e.g., "1", "2"). |
IconKey |
string |
Resource key for controller type icon (e.g., "XboxControllerIcon", "DS4ControllerIcon", "NintendoControllerIcon", "ExtendedControllerIcon", "MidiControllerIcon", "KeyboardMouseControllerIcon", "VrControllerIcon"). |
IsEnabled |
bool |
Virtual controller enabled. |
ConnectedDeviceCount |
int |
Mapped physical devices connected. |
MappedDeviceCount |
int |
Physical devices assigned to this slot in config, connected or not. Lets the mini card split "assigned but awaiting" (idle) from "nothing assigned" (cold), the same split the Dashboard card makes. Added by commit 2006ac6b. |
IsInitializing |
bool |
Virtual controller initializing. |
IsVirtualControllerConnected |
bool |
Live virtual controller is alive (created + reporting IsConnected). Drives the sidebar green-vs-yellow indicator so the slot stays green through the HM-inactivity grace window and only turns yellow once the timeout tears the VC down. |
IsCreateFailed |
bool |
The slot's latest virtual-controller create attempt failed (engine createFailed latch). Distinct from awaiting devices, so the flame tooltip does not blame absent devices for a failed create. |
Sidebar wiring: MainWindow code-behind rebuilds one wpf-ui NavigationViewItem per NavControllerItems entry on NavControllerItemsRefreshed (RebuildControllerSection / CreateControllerNavItem), and subscribes each item's PropertyChanged so SlotNumber, InstanceLabel, IconKey (matched as a plain string in UpdateControllerNavItemContent), IsEnabled, and the flame-state flags update the card in place.
MainViewModel
File: MainViewModel.cs
DataContext for: MainWindow
Root ViewModel. Manages navigation state, 16 pad ViewModels, sidebar controller entries, and app-wide status.
Child ViewModels
| Property |
Type |
Description |
Pads |
ObservableCollection<PadViewModel> |
16 pad ViewModels (one per slot). Created in constructor. |
NavControllerItems |
ObservableCollection<NavControllerItemViewModel> |
Sidebar items for created slots. Rebuilt by RefreshNavControllerItems(). |
Dashboard |
DashboardViewModel |
Dashboard overview ViewModel. |
Devices |
DevicesViewModel |
Devices list ViewModel. |
Settings |
SettingsViewModel |
Application settings ViewModel. |
Navigation Properties
| Property |
Type |
Description |
SelectedNavTag |
string |
Current nav tag: "Dashboard", "Pad1"–"Pad16", "Profiles", "Devices", "Settings", "About". Also notifies IsPadPageSelected and SelectedPadIndex. |
IsPadPageSelected |
bool |
True if a Pad page (Pad1–Pad16) is selected. Computed. |
SelectedPadIndex |
int |
Zero-based pad index for the selected Pad page, or -1. Computed. |
SelectedPad |
PadViewModel |
PadViewModel for the selected Pad page, or null. Computed. |
App-wide Status Properties
| Property |
Type |
Default |
Description |
StatusText |
string |
"Ready" |
Status bar text. Plain writes are info-class and decay after ~5 s (StatusDecayMs). Every write restamps the decay window. |
IsEngineRunning |
bool |
false |
Polling loop active. Calls RefreshEngineStatus(). |
HasActiveSlots |
bool |
false |
Any virtual controller slots exist. Calls RefreshEngineStatus(). |
EngineStatusText |
string |
- |
Computed localized text: Engine_Forging ("Forging", engine running with active slots) / Common_Idle ("Idle", running with no created slots) / Common_Stopped ("Stopped"). Note: this is the localized display text. DashboardViewModel.EngineStateKey carries the non-localized "Running" / "Idle" / "Stopped" key for XAML DataTriggers. |
EngineStatusBrush |
Brush |
- |
Computed: Red (#F44336) = Stopped, Amber (#FFB300) = Idle (engine running, no created slots), Green (#4CAF50) = running with active slots (the Forging state). Frozen (thread-safe). |
PollingFrequency |
double |
0 |
Current polling frequency in Hz. |
ConnectedDeviceCount |
int |
0 |
Connected input device count. |
Commands
| Command |
CanExecute |
Description |
StartEngineCommand |
!IsEngineRunning |
Raises StartEngineRequested. Wired by MainWindow code-behind. |
StopEngineCommand |
IsEngineRunning |
Raises StopEngineRequested. |
Events
| Event |
Args |
Description |
StartEngineRequested |
EventArgs |
Start engine. |
StopEngineRequested |
EventArgs |
Stop engine. |
NavControllerItemsRefreshed |
EventArgs |
Raised after RefreshNavControllerItems() completes. MainWindow uses this instead of CollectionChanged to avoid rapid sidebar rebuilds. |
Key Methods
| Method |
Description |
RefreshNavControllerItems() |
Rebuilds sidebar entries from SettingsManager.SlotCreated[]. Computes per-type instance numbers and updates each pad's Title, SlotLabel, TypeInstanceLabel. Raises NavControllerItemsRefreshed only when the active-slot set changed (add / remove / reorder). Property-only changes (icon, label, enabled) update in place without the event. |
RefreshEngineStatus() |
Notifies EngineStatusText and EngineStatusBrush. |
RefreshCommands() |
Notifies CanExecuteChanged on start/stop commands. Call after IsEngineRunning changes. |
SetStatus(string, bool persist = false) |
Writes the status bar with an explicit class. persist: true (failures, prompts a later write always terminates) exempts the message from the decay sweep until the next write. |
ClearDecayedStatus() |
Clears a decayed info message. Re-checks IsStatusDecayDue first, so a write that lands mid-sweep wins. Called from MainWindow's 5 s driver-status timer. |
DashboardViewModel
File: DashboardViewModel.cs
DataContext for: Dashboard page
Overview of all controller slots, engine status, connected devices, and driver status.
Slot Summaries
| Property |
Type |
Description |
SlotSummaries |
ObservableCollection<SlotSummary> |
Cards for created slots. Rebuilt by RefreshActiveSlots(). |
ShowAddController |
bool |
"Add Controller" visible (any type has capacity). |
Engine Status
| Property |
Type |
Default |
Description |
EngineStatus |
string |
"Stopped" |
Localized engine status. |
EngineStateKey |
string |
"Stopped" |
Non-localized state key ("Running", "Stopped", "Idle") for XAML DataTriggers. |
PollingFrequency |
double |
0 |
Polling frequency in Hz. Also notifies PollingFrequencyText. |
PollingFrequencyText |
string |
- |
Computed: formatted frequency (e.g., "987.3 Hz") or dash when zero. |
Device Counts
| Property |
Type |
Description |
TotalDevices |
int |
Total detected devices (online + offline). |
OnlineDevices |
int |
Currently connected devices. |
MappedDevices |
int |
Devices with an active slot mapping. |
Driver Status
HIDMaestro ships inside the executable as a managed SDK, so it never had an install row here. The Dashboard's driver-status strip was removed on 2026-09-02. The full HidHide, Windows MIDI Services, and SteamVR cards live on Settings. What remains on DashboardViewModel are three bare flags with no status text of their own. MainWindow's periodic driver-status refresh writes all three.
| Property |
Type |
Description |
IsHidHideInstalled |
bool |
HidHide installed. Written by the status refresh. No Dashboard surface currently reads it. |
IsMidiAvailable |
bool |
A MIDI API started, Windows MIDI Services or the legacy one, the same answer the Settings card shows. A slot card's MIDI type button shows a "no" cursor and the Main_MIDI_NotRunning tooltip when false. |
IsSteamVrInstalled |
bool |
SteamVR installed, which gates the VR slot type (#49). A slot card's VR type button shows a "no" cursor and the Main_VR_RequiresSteamVR tooltip when false. The tiered SteamVR status row (#287) lives on the Settings card, which reads the live statics itself. |
DSU Motion Server
| Property |
Type |
Default |
Description |
EnableDsuMotionServer |
bool |
false |
DSU (cemuhook) motion server enabled. |
DsuMotionServerPort |
int |
26760 |
UDP port. Clamped to 1024–65535. |
DsuServerStatus |
string |
"Stopped" |
DSU server status. |
IsDsuServerRunning |
bool |
false |
Serving truth for the DSU card flame (#175): the actual server lifecycle, not the enable checkbox. Set by InputService at the same points that write DsuServerStatus. |
| Command |
Description |
ResetDsuPortCommand |
Resets DsuMotionServerPort to 26760. |
Game Mods (#529)
The server for DSX game mods. All three settings ride profiles. See Game Mods Internals.
| Property |
Type |
Default |
Description |
EnableGameModServer |
bool |
false |
The server listens while this is on and the engine runs. |
GameModServerPort |
int |
6969 |
UDP port. Clamped to 1024–65535. |
GameModDsxStandIn |
bool |
false |
Run the DSX.exe stand-in while the server listens. |
GameModServerStatus |
string |
"Stopped" |
The card's line: listening, who sends to which virtual controller, the port in use, or the bind that failed. Set by InputService from GameModServer.FormatStatus(). |
IsGameModServerRunning |
bool |
false |
Serving truth for the card's flame: the server listens, whatever the switch says. |
| Command |
Description |
ResetGameModPortCommand |
Resets GameModServerPort to 6969. |
Game Telemetry (#531)
The listener behind the Game Telemetry row. Each setter mirrors into the static GameTelemetryRuntime, the head tracking card's shape, so a write lands whatever its source. The switch, the port, the game, the forward port and Full Speed ride profiles. The custom formats are one list for every profile. See Game Telemetry Internals.
| Property |
Type |
Default |
Description |
EnableGameTelemetry |
bool |
false |
The row listens while this is on and the engine runs. Mirrors to GameTelemetryRuntime.Enabled. |
GameTelemetryPort |
int |
20777 |
The UDP port the games send to. Clamped to 1024–65535. Mirrors to GameTelemetryRuntime.Port. |
GameTelemetryFormat |
GameTelemetryFormat |
Auto |
The game picked, or Auto-Detect. Mirrors to GameTelemetryRuntime.Format. |
GameTelemetryCustomFormat |
string |
"" |
The custom format read while the game is Custom. Null writes empty. Mirrors to GameTelemetryRuntime.CustomFormatName. |
GameTelemetryOptions |
ObservableCollection<GameTelemetryOption> |
- |
The picker: Auto, each game family, then each custom format with a valid name. A GameTelemetryOption is the pair of a GameTelemetryFormat and a custom format's name. Its Display is renamed in place on a language change. |
SelectedGameTelemetryOption |
GameTelemetryOption |
Auto |
The picker's selection, read from the game and the custom name, or the first option. Picking Project CARS 2 moves the port to 5606 and the ETS2 plugin to 49001, the two senders whose port cannot change. Picking another game afterward, or the reset, puts 20777 back while the port is still that sender's (PickGameTelemetry). A load or a profile applies its saved port as saved. A null write is ignored. |
GameTelemetryReadsUdp |
bool |
true |
Whether the picked game is read over UDP (TelemetryReader.FormatReadsUdp). False while Assetto Corsa is picked, which grays out Port and Forward to Port. Raised with GameTelemetryFormat. |
GameTelemetryForwardPort |
int |
0 |
The port every packet is copied to on 127.0.0.1, 0 for none. Clamped to 0–65535. Mirrors to GameTelemetryRuntime.ForwardPort. |
GameTelemetryFullSpeed |
int |
300 |
The speed, in km/h, that moves the Speed axis all the way. Clamped to 10–1000. Mirrors to GameTelemetryRuntime.FullSpeedKmh. |
GameTelemetryFormats |
List<CustomTelemetryFormat> |
empty |
The custom formats. A set mirrors them to GameTelemetryRuntime.Formats, which reopens the row, writes each variable field's axis slot back with WriteAxes so the settings file saves it, refreshes the expression variables' TelemetryChannelOptions and rebuilds GameTelemetryOptions. A picked custom format that is gone sends the picker back to Auto-Detect. The Dashboard's Formats button sets it from CustomTelemetryFormatsDialog.Result. |
GameTelemetryStatus |
string |
"Stopped" |
The card's line: the game it hears, or why it hears nothing. Set by InputService.UpdateGameTelemetryStatus. Null coalesces back to "Stopped". |
IsGameTelemetryRunning |
bool |
false |
Serving truth for the card's flame: the row is open, whatever the switch says. False while the port is in use. |
| Command |
Description |
ResetGameTelemetryPortCommand |
Resets the port to 20777. |
ResetGameTelemetryGameCommand |
Back to Auto-Detect, with no custom format. |
ResetGameTelemetryForwardPortCommand |
Sets the forward port to 0, forwarding nothing. |
ResetGameTelemetryFullSpeedCommand |
Resets Full Speed to 300. |
| Method |
Description |
ApplyGameTelemetrySettings(GameTelemetryFormat format, string customName, int port, int forwardPort, int fullSpeed) |
Applies a saved game, port, forward port and Full Speed, from the settings file or a profile. The switch is excluded, and its caller sets it last. An undefined format, or a custom one whose format is gone, reads as Auto-Detect. An out-of-range port, forward port or Full Speed keeps the current value. |
Head Tracking (#355, #403)
Three independent inputs feed the Head Tracker device row: a UDP listener on OpenTrack's port, a FreeTrack 2.0 shared-memory reader, and an OpenXR headset reader (#403) that also adds the two VR controller rows. The row exists while any of the three is on (HeadTrackingRuntime.AnyEnabled). Each setter mirrors into the static PadForge.Common.Input.HeadTrackingRuntime that the poll thread's device sweep reads, so a write lands whatever its source (global load, profile apply, the user). The UDP and FreeTrack toggles ride profiles as nullable legs (ProfileData.EnableHeadTracking, ProfileData.EnableHeadTrackingFreeTrack). The port, the OpenXR toggle and runtime, and the ranges are global. These properties moved here from SettingsViewModel on 2026-09-02. MainWindow's Dashboard dirty-gate allowlist moved with them, so a head-tracking edit still marks the settings file dirty. See Head Tracking: Internals and OpenXR Input Internals.
| Property |
Type |
Default |
Description |
HeadTrackingEnabled |
bool |
false |
The OpenTrack UDP input. Mirrors to HeadTrackingRuntime.Enabled. |
HeadTrackingUdpPort |
int |
4242 |
Port OpenTrack's "UDP over network" output sends to. Clamped to 1–65535. Mirrors to HeadTrackingRuntime.UdpPort. |
HeadTrackingFreeTrack |
bool |
false |
The FreeTrack 2.0 shared-memory input, independent of UDP. Mirrors to HeadTrackingRuntime.FreeTrackEnabled. |
HeadTrackingOpenXr |
bool |
false |
Read the headset through an OpenXR runtime, independent of the other two inputs and of SteamVR. Mirrors to HeadTrackingRuntime.OpenXrEnabled. Turning it on calls RefreshOpenXrRuntimes(). |
OpenXrRuntimes |
ObservableCollection<OpenXrRuntimeChoice> |
- |
Runtime picker items (ManifestPath, and Display, observable so an entry kept across a refresh takes a new caption): the system default (empty path) first, then each installed runtime OpenXrRuntimeCatalog.Discover() finds, read through the internal test seam DiscoverOpenXrRuntimes. Built on first read, never rebuilt from the getter. A saved runtime the registry no longer names stays listed under its manifest's file name. RefreshOpenXrRuntimes() rebuilds it. |
SelectedOpenXrRuntime |
OpenXrRuntimeChoice |
system default |
Reads and writes HeadTrackingRuntime.OpenXrRuntimeManifest. It changes the runtime this process uses, never the machine's active runtime. A null, which WPF writes when the box's entry leaves the list, and any write during a refresh are ignored. |
HeadTrackingRotationRange |
int |
90 |
Degrees of head rotation at full deflection for the rotation axes. Clamped to 1–180. |
HeadTrackingTranslationRange |
int |
30 |
Centimeters of head travel at full deflection for the translation axes. Clamped to 1–500. |
HeadTrackingRangeYaw / Pitch / Roll / X / Y / Z |
int |
0 |
Per-axis range pins (#403) over HeadTrackingRuntime.SetAxisRange. 0 follows the axis family's range. Rotation axes clamp to 180, translation axes to 500. |
HeadTrackingStatus |
string |
"Stopped" |
The Head Tracker row's source line (which source is live, or why none is). Pushed by InputService on the dashboard tick whenever the device's StatusVersion moves. Null coalesces back to "Stopped". |
| Command |
Description |
ResetHeadTrackingEnabledCommand / ResetHeadTrackingFreeTrackCommand / ResetHeadTrackingOpenXrCommand |
Turn the matching input off. |
ResetHeadTrackingOpenXrRuntimeCommand |
Back to the system default runtime. |
ResetHeadTrackingPortCommand |
Resets the port to 4242. |
ResetHeadTrackingRotationRangeCommand |
Resets the rotation range to 90. |
ResetHeadTrackingTranslationRangeCommand |
Resets the translation range to 30. |
ResetHeadTrackingRangeYawCommand … ResetHeadTrackingRangeZCommand |
Set one axis back to 0, so it follows its family again. |
HeadTrackingRecenterCommand |
Calls HeadTrackingRuntime.Recenter(). Only the OpenXR source has a neutral to move. OpenTrack and FreeTrack keep the zero their own application set. |
| Method |
Description |
RefreshOpenXrRuntimes() |
Rebuilds the runtime list in place: an entry whose manifest path is still wanted keeps its instance and takes the fresh caption, so the bound selection rides through. The saved runtime stays listed. Runs when HeadTrackingOpenXr turns on, from NotifyHeadTrackingRangesChanged(), and on a language switch once the list exists. |
NotifyHeadTrackingRangesChanged() |
Re-raises the six per-axis ranges and rebuilds the runtime list after a settings load or Reset to Defaults writes the statics they read. The rebuild lists a saved runtime the registry no longer names, which a list built before the load lacks. |
Web Controller Server
| Property |
Type |
Default |
Description |
EnableWebController |
bool |
false |
Web controller server enabled. |
WebControllerPort |
int |
8080 |
HTTP/WebSocket port. Clamped to 1024–65535. |
WebControllerStatus |
string |
"Stopped" |
Web controller server status. |
IsWebControllerRunning |
bool |
false |
Serving truth for the Web Controller card flame (#175): actual server lifecycle, not the enable checkbox. Set by InputService alongside WebControllerStatus. |
WebControllerClientCount |
int |
0 |
Connected web controller clients. |
WebControllerUrl |
string |
null |
The live server URL, https:// when the secure lane bound (#296). Empty when stopped. |
WebControllerQr |
ImageSource |
null |
QR of WebControllerUrl so a phone opens the controller by scanning (#296). Null when stopped, which hides the image. |
HasWebControllerQr |
bool |
false |
Whether a QR is available. Drives its visibility. |
WebControllerHttpsWarning |
string |
null |
Why the main address fell back to plain HTTP and phone motion is off. Null while it serves HTTPS. |
EnableWebControllerPlainHttp |
bool |
false |
Also serve the plain HTTP address. Machine setting, never in a profile. |
WebControllerPlainHttpPort |
int |
8081 |
The plain address's port. Clamped to 1024–65535. |
WebControllerPlainHttpLocalOnly |
bool |
false |
Admit only this PC on the plain address. |
WebControllerAccessCode |
string |
fresh code |
The plain address's access code. Starts as a freshly generated code, and the setter ignores anything that is not a valid code, so it is never empty. |
IsWebControllerPlainRunning |
bool |
false |
Serving truth for the plain address's flame. |
WebControllerPlainStatus |
string |
null |
Where the plain address runs, or why it does not. Null while stopped or off. |
WebControllerPlainUrl |
string |
null |
The plain address with its ?code=. Null when not served. |
WebControllerPlainQr / HasWebControllerPlainQr |
ImageSource / bool |
null / false |
QR of the plain address. None in This PC Only mode. |
| Command |
Description |
ResetWebPortCommand |
Resets WebControllerPort to 8080. |
CopyWebControllerUrlCommand |
Copies WebControllerUrl to the clipboard. No-op when empty. |
ResetWebPlainPortCommand |
Resets WebControllerPlainHttpPort to 8081. |
NewWebAccessCodeCommand |
Replaces WebControllerAccessCode with a fresh code. |
CopyWebControllerPlainUrlCommand |
Copies WebControllerPlainUrl to the clipboard. No-op when empty. |
ResetSettingCommand also resets EnableWebControllerPlainHttp and WebControllerPlainHttpLocalOnly to off.
Remote Link (#138)
Peer-to-peer controller relay over the LAN. The Dashboard hosts the server toggles and the outbound-connect box. The paired-peer manager and identity-protection state live on SettingsViewModel and are surfaced here through the RemoteLink reference below.
| Property |
Type |
Default |
Description |
RemoteLink |
SettingsViewModel |
- |
The Settings VM, set once at startup. Holds TrustedPeers, NearbyUnpaired, and the identity-protection block so the Dashboard's Remote Link section can show paired peers, identity mode, and nearby PCs in one place. |
EnableRemoteLink |
bool |
false |
Whether the Remote Link server is listening for paired peers. |
AutoReconnect |
bool |
true |
When a paired PC appears on the LAN, establish the link without a click. |
RemoteLinkPort |
int |
27500 |
TCP control + UDP data port. Clamped to 1024–65535. |
RemoteLinkStatus |
string |
"Stopped" |
Remote Link server status text. |
IsRemoteLinkRunning |
bool |
false |
Serving truth for the Remote Link card flame (#175): true only while the link server object is live. The status text can carry identity-unlock errors while the server never started, so the flame keys on this, not the text or the enable checkbox. |
RemoteLinkConnectHost |
string |
"" |
Host (or host:port) the user types to initiate an outbound pairing. |
RemoteLinkMyCode |
string |
"" |
This PC's connection code (#294): a self-contained code embedding the public and private endpoints, minted while Remote Link runs. Empty until the STUN probe returns. Set by InputService. Notifies HasRemoteLinkMyCode. |
HasRemoteLinkMyCode |
bool |
- |
Computed: a shareable code is available to show or copy. |
RemoteLinkNatWarning |
string |
"" |
Network warning (#294): set when the STUN probe found no public address (UDP blocked) or an endpoint-dependent NAT direct connections cannot traverse. Empty when the network looks punchable. Notifies HasRemoteLinkNatWarning. |
HasRemoteLinkNatWarning |
bool |
- |
Computed: RemoteLinkNatWarning is non-empty. |
| Command |
Description |
ResetRemoteLinkPortCommand |
Resets RemoteLinkPort to 27500. |
ConnectToPeerCommand |
Raises ConnectToPeerRequested with the trimmed RemoteLinkConnectHost when non-empty. |
CopyRemoteLinkCodeCommand |
Copies RemoteLinkMyCode to the clipboard. No-op when empty. |
| Event |
Args |
Description |
ConnectToPeerRequested |
string |
User asked to connect/pair with a typed host[:port]. |
Touchpad Overlay (3.2)
On-screen transparent touchpad window that drives the DS4 / DualSense touchpad on the assigned PlayStation slot.
| Property |
Type |
Default |
Description |
EnableTouchpadOverlay |
bool |
false |
Overlay visible. |
EnableMenuOverlay |
bool |
true |
Radial / touch menu overlay (#9): render the MenuOverlayWindow HUD while a menu is engaged. When off, menus still hover and commit blind (the runtime never depends on the window). |
EnableShiftLayerFlyout |
bool |
true |
Show the shift-layer flyout while a slot sits on a non-Base layer. Purely a display of engagement state the poll thread already computes, so turning it off changes nothing about which layer is active. |
EnableProfileOverlay |
bool |
true |
Show the profile-switch overlay when a profile changes. The switch still happens when off, this only suppresses the announcement. |
TouchpadOverlayOpacity |
double |
0.25 |
Surface opacity 0.0–1.0. Notifies TouchpadOverlayOpacityPercent. |
TouchpadOverlayOpacityPercent |
int |
25 |
0–100 integer view of opacity for NumberBox binding. |
TouchpadOverlayMonitor |
int |
0 |
Monitor index the overlay is pinned to (0 = primary). |
TouchpadOverlayLeft / Top |
double |
-1 |
Window position. -1 = centered on the chosen monitor. |
TouchpadOverlayWidth / Height |
double |
500 / 250 |
Window size. Clamped to >= 150 and >= 80 respectively. |
IsTouchpadOverlayRunning |
bool |
false |
Overlay window currently shown. Drives TouchpadOverlayStatus. |
TouchpadOverlayStatus |
string |
- |
Localized status string ("Running" / "Stopped"). |
| Command |
Description |
ResetOpacityCommand |
Resets opacity to default 0.25. |
ResetTouchpadOverlayPositionCommand |
Raises ResetTouchpadOverlayPositionRequested. InputService recenters the live overlay (or seeds defaults if not open) and clears persisted Left / Top. |
| Event |
Description |
ResetTouchpadOverlayPositionRequested |
Recenter request from the UI. |
Key Methods
| Method |
Description |
RefreshActiveSlots(IList<int>, bool) |
Rebuilds SlotSummaries for active slots. Updates display labels, re-applies the remembered focus selection, sets ShowAddController. Called by InputService and MainWindow. |
SetSelectedPad(int) |
Marks the slot whose pad page is in focus so its card wears the selection glow. -1 clears it. The index is remembered, so a later RefreshActiveSlots rebuild re-applies the flag to fresh summaries. |
SlotSummary
File: DashboardViewModel.cs (nested class)
Summary card for one virtual controller slot on the Dashboard.
| Property |
Type |
Default |
Description |
PadIndex |
int |
- |
Zero-based slot index. Read-only. |
SlotLabel |
string |
- |
Display label (e.g., "Virtual Controller 1"). |
MappedDevices |
ObservableCollection<PadViewModel.MappedDeviceInfo> |
null |
Live reference to the pad's mapped-device list, so the crucible card renders a per-device roster with battery glyphs (#175). Set by InputService. Same-reference sets are no-ops. |
DeviceName |
string |
"No device" |
Primary mapped device name. |
BatteryText |
string |
"" |
Battery of the first mapped device reporting one, e.g. "78%". Empty otherwise (#175, issue #167 lane). |
IsActive |
bool |
false |
Has at least one online mapped device. |
IsSelected |
bool |
false |
Whether this slot's pad page is the one currently in focus. Drives the persistent selection glow on the card. Set by DashboardViewModel.SetSelectedPad, not the same as IsActive. |
IsVirtualControllerConnected |
bool |
false |
Virtual controller connected. Notifies StatusText. |
IsInitializing |
bool |
false |
Virtual controller initializing. Notifies StatusText. |
IsCreateFailed |
bool |
false |
The slot's latest create attempt failed (engine createFailed latch). Its own status: a failed slot with online devices is neither forging nor awaiting devices. Notifies StatusText. |
MappedDeviceCount |
int |
0 |
Devices mapped to this slot. Notifies HasMappedDevices and StatusText. |
HasMappedDevices |
bool |
- |
Computed: MappedDeviceCount > 0. Ember heat gating (#175): cards with zero mappings stay cold (no ember rim, no glow, steel seg tile) even when enabled. |
ConnectedDeviceCount |
int |
0 |
Mapped devices connected. |
StatusText |
string |
"Idle" |
Ember status vocabulary (#175). For an enabled, non-initializing slot the getter checks three branches in order. A failed create returns Main_VcFailed first. Then zero mappings returns Dashboard_StatusCold ("Cold"). It returns Main_AwaitingDevices ("Awaiting devices") for a mapped slot with nothing connected once the live VC is gone. While the VC survives the inactivity grace (60 s default), a device dropout must not flap the card to awaiting. Every other case passes through the engine-assigned setter value: Main_Active ("Forging") when a device is online, Common_Idle ("Idle"), Common_Disabled ("Disabled"), or Main_Initializing ("Initializing"). InputService still assigns Status_NoMapping ("No mapping") to an unmapped slot, and the Cold branch intercepts it. |
IsEnabled |
bool |
true |
Slot enabled for output. |
SlotNumber |
int |
1 |
1-based controller number among active slots. |
TypeInstanceLabel |
string |
"1" |
Per-type instance label. |
OutputType |
VirtualControllerType |
Xbox |
Virtual controller output type. (XML on-disk name is "Microsoft" via [XmlEnum] for v2/early-v3 back-compat. In-code identifier is Xbox.) |
StageLedger |
ObservableCollection<SlotStageInfo> |
empty |
Pipeline stage ledger (#175 item 10): one entry per configuration stage the slot's assigned devices actually have (sticks / triggers / gyro / lighting / touchpad / audio), in that order. Rebuilt per slot by InputService.RefreshSlotStageLedger on the dashboard refresh's 1 s slow lane. Entries mutate in place when membership is unchanged so the card doesn't re-template. |
SlotStageInfo
File: DashboardViewModel.cs
One stage entry in a slot card's pipeline heat ledger (#175 item 10).
| Property |
Type |
Description |
Kind |
string |
Stable stage identity ("Sticks".."Audio"). Membership comparisons key on this so in-place updates are possible. Read-only. |
Glyph |
string |
Segoe font glyph for the font stages (same characters as the matching tab page headers). Empty for shape stages. Read-only. |
IsStickShape |
bool |
True for the Sticks entry. Rendered with the Zacksly stick ring + disc geometries instead of a font glyph. Read-only. |
IsTriggerShape |
bool |
True for the Triggers entry. Rendered with the Zacksly trigger body geometry. Read-only. |
IsHot |
bool |
True when the stage carries a non-default configuration on any assigned device. Ember when hot, ashen steel when inert. |
Summary |
string |
Composite change key for the tooltip readout (all lines joined). Empty when inert, which disables the tooltip. Notifies HasSummary. |
HasSummary |
bool |
Computed: tooltip gate, no tooltip on inert stages. |
SummaryLines |
ObservableCollection<StageSummaryLine> |
Per-device readout lines for the hover tooltip, in binding order. Devices still at defaults read STOCK. |
StageSummaryLine
File: DashboardViewModel.cs
One tooltip line of a stage's readout: device-class glyph, device name, then mono value tokens.
| Property |
Type |
Description |
DeviceGlyph |
string |
Device-class glyph. Empty for slot-level lines. |
DeviceName |
string |
Body-face device name, carrying its trailing " · " separator. Empty for slot-level lines like the audio master volume. |
Tokens |
string |
Mono value tokens. |
SettingsViewModel
File: SettingsViewModel.cs
DataContext for: Settings page
Application-level settings: theme, language, drivers, profiles, and engine configuration.
Theme
| Property |
Type |
Default |
Description |
SelectedThemeIndex |
int |
0 |
0 = System, 1 = Light, 2 = Dark. Raises ThemeChanged. |
| Event |
Args |
Description |
ThemeChanged |
int |
Raised when theme selection changes. Arg = theme index. |
Language
| Property |
Type |
Description |
AvailableLanguages |
IReadOnlyList<LanguageInfo> |
LanguageCatalog.All: one row for each language in Resources/Strings/Languages.tsv, sorted by own name. Each LanguageInfo carries Tag, EnglishName, NativeName, IsRightToLeft, IsComplete, Culture, and EnglishNameIfDifferent, the English name the picker shows beside the own name, empty when the two match. |
SelectedLanguage |
CultureInfo |
Current UI language. Applies immediately via Strings.ChangeCulture(). With no explicit selection it reads the row LanguageCatalog.Resolve finds for the current UI culture: its own, its nearest parent's or an alias's (de-DE shows de, en-AU shows en-GB), else American English. |
| Member |
Type |
Description |
LanguageCode |
string |
Persisted language code for serialization. Empty when no language was picked. |
SetLanguageFromCode(string) |
method |
Sets the language from the persisted code, found through LanguageCatalog.Find, so a code saved before American and British English each had a row (en) reads en-US. When it differs from the current UI culture it goes through Strings.ChangeCulture(), which raises CultureChanged, so a reload to another saved language updates the live window. An empty or unknown code is a no-op. |
ResetLanguageToSystemDefault() |
method |
Clears the explicit selection (LanguageCode saves "") and switches back to the culture the process started under, read through its row of the language table (LanguageCatalog.InterfaceCulture) as startup reads it. Used by Reset to Defaults and by the language row's reset button. |
Driver Status: HIDMaestro
HIDMaestro is shipped as an embedded managed SDK (HIDMaestro.Core, bundled at Resources/HIDMaestro/HIDMaestro.Core.dll). The user-mode driver still needs to be registered with Windows once. That happens inside the engine via HMContext.InstallDriver() (called from InputManager.Step5.VirtualDevices.cs:EnsureHMaestroContext), not through DriverInstaller. The SettingsViewModel exposes a read-only version string read from the bundled assembly at startup.
| Property |
Type |
Description |
HIDMaestroVersion |
string |
HIDMaestro SDK version. Initialized from the embedded HIDMaestro.Core assembly via GetEmbeddedHidMaestroVersion(). |
IsHIDMaestroInstalled |
bool |
HIDMaestro's driver is in the driver store. Set by MainWindow.RefreshHIDMaestroStatus from HIDMaestroRemoval.IsDriverInstalled() at startup, on the driver status timer and after a removal. |
HIDMaestroStatusText |
string |
Computed: Common_Installed or Common_NotInstalled. Raised again on a language change. |
| Command |
CanExecute |
Description |
UninstallHIDMaestroCommand |
Always |
Raises UninstallHIDMaestroRequested, which MainWindow.UninstallHIDMaestroAsync handles. Available with the driver gone, since HIDMaestro can leave other things behind. |
Driver Status: BthPS3
| Property |
Type |
Description |
BthPS3Version |
string |
The BthPS3 version PadForge carries, from the DriverVer of its embedded BthPS3.inf (Ds3DriverInstaller.BundledBthPs3Version). |
IsBthPS3Installed |
bool |
Either BthPS3 driver is installed as a service. Set by MainWindow.RefreshBthPS3Status from Ds3DriverInstaller.IsAnyBthPs3DriverInstalled() at startup, on the driver status timer and after a removal. |
BthPS3StatusText |
string |
Computed: Common_Installed or Common_NotInstalled. Raised again on a language change. |
| Command |
CanExecute |
Description |
UninstallBthPS3Command |
Always |
Raises UninstallBthPS3Requested, which MainWindow.UninstallBthPS3Async handles. Available with the drivers gone, since PadForge's WinUSB package and certificate can remain. |
Driver Status: HidHide
| Property |
Type |
Description |
IsHidHideInstalled |
bool |
HidHide installed. |
HidHideStatusText |
string |
Computed: Common_Installed ("Installed") or Common_NotInstalled ("Not Installed"). On a machine that is neither x64 nor ARM64 (PlatformSupport.HidHideAvailable false) it reads Common_NotAvailableOnArm64 instead. |
HidHideVersion |
string |
HidHide version. |
HidHideWhitelistPaths |
ObservableCollection<string> |
Whitelisted application paths. |
ICadePads |
ObservableCollection<string> |
Pads in iCade mode marked on the Devices page, as 0xVVVV/0xPPPP pairs (hifihedgehog/SDL#33 Part 16). MainWindow's SetICadeMode edits it. |
DjiRemoteHosts |
ObservableCollection<string> |
DJI remotes added by address in the pairing dialog, as a.b.c.d:port keys (hifihedgehog/SDL#33 Part 6). DjiRemoteHostsChanged fires on each add or remove, never on load. |
UsioLayout |
string |
The Namco USIO's layout, "taiko" or "tekken" (hifihedgehog/SDL#33 Part 14). MainWindow's SetUsioLayout sets it. |
SelectedWhitelistPath |
string |
Selected whitelist path. Refreshes RemoveWhitelistPathCommand. |
| Command |
CanExecute |
Description |
InstallHidHideCommand |
!IsHidHideInstalled && PlatformSupport.HidHideAvailable |
Raises InstallHidHideRequested. |
UninstallHidHideCommand |
IsHidHideInstalled && !HasAnyHidHideDevices() |
Raises UninstallHidHideRequested. |
AddWhitelistPathCommand |
IsHidHideInstalled |
Raises AddWhitelistPathRequested. |
RemoveWhitelistPathCommand |
SelectedWhitelistPath != null |
Removes selected path, raises WhitelistChanged. |
| Event |
Description |
InstallHidHideRequested |
Install HidHide. |
UninstallHidHideRequested |
Uninstall HidHide. |
AddWhitelistPathRequested |
Add whitelist path (opens file dialog). |
WhitelistChanged |
Whitelist was modified (add or remove). |
Driver Status: Windows MIDI Services
The card names the API PadForge drives (MidiApiSelection), offers PadForge's build of the App SDK runtime where it would give MIDI slots ports of their own, and removes an installed runtime, Microsoft's or PadForge's. MainWindow's status refresh, on the 5-second driver timer, writes the inputs from MidiApiSelection.ForCard, MidiApiSelection.CanOfferRuntimeInstall and DriverInstaller. See Driver Installation Internals.
| Property |
Type |
Description |
ActiveMidiApi |
MidiApiKind (internal) |
InBox, AppSdk, Legacy or None. Notifies IsMidiAvailable and the card text. |
CanInstallMidiRuntime |
bool |
The card offers the runtime (MidiApiSelection.CanOfferRuntimeInstall). Shows the Install button. |
IsMidiRuntimeInstalled |
bool |
The App SDK runtime is installed, Microsoft's or PadForge's (DriverInstaller.IsMidiRuntimeInstalled). Shows the Uninstall button. |
MidiRuntimeVersion |
string |
The runtime's version, from its DLL (DriverInstaller.GetMidiRuntimeVersion). |
MidiApiNotStarted |
bool |
The probe started no API, the legacy one included, or timed out on a stuck service. Also true while MIDI is held released for the runtime's uninstall. |
MidiNotRunningReason |
MidiNotRunningReason (internal) |
Why nothing runs while MidiApiNotStarted holds: ServiceDidNotRespond, DriverDidNotRespond (a classic MIDI driver, which no service restart reaches) or Uninstalling. Written from MidiVirtualController.NotRunningReason. Notifies the card text. |
MidiInBoxFailure |
int (internal) |
The HRESULT of an in-box API that Windows registers and that failed to activate, or 0. The legacy API runs in its place. Written from MidiVirtualController.InBoxActivationFailure. Notifies the card text. |
MidiUninstallerRunning |
bool (internal) |
A runtime uninstaller PadForge started has not exited (MidiRuntimeSwitch.UninstallerRunning). Re-evaluates UninstallMidiServicesCommand. |
IsMidiAvailable |
bool |
Computed: ActiveMidiApi != None. Lights the card's flame. |
MidiServicesStatusText |
string |
Computed: Settings_MidiStatusInBox ("Built into Windows"), Settings_MidiStatusRuntime ("App SDK Runtime"), Settings_MidiStatusLegacy ("Legacy MIDI API"), Settings_MidiStatusNotRunning ("Not Running") or Settings_MidiStatusUnavailable ("Not Available"). |
MidiServicesDetailText |
string |
Computed: under the in-box API, empty, or Settings_MidiOlderRuntime_Format when a runtime is also installed. Under the runtime, its version. Under the legacy API, Settings_MidiLegacy, or Settings_MidiInBoxFailed_Format with the HRESULT when MidiInBoxFailure is set. Otherwise empty unless MidiApiNotStarted, and then by MidiNotRunningReason: Settings_MidiNotRunning, Settings_MidiNotRunningDriver or Settings_MidiUninstallRunning. |
MidiDetailIsVersion |
bool |
Computed: the detail line is the runtime's version, which the card sets in the telemetry face. |
| Command |
CanExecute |
Description |
InstallMidiRuntimeCommand |
CanInstallMidiRuntime |
Raises InstallMidiRuntimeRequested. |
UninstallMidiServicesCommand |
IsMidiRuntimeInstalled && !MidiUninstallerRunning && !(ActiveMidiApi == AppSdk && HasAnyMidiSlots()) |
Raises UninstallMidiServicesRequested. Under the in-box or the legacy API, MIDI slots do not hold the runtime. Unavailable while an uninstaller PadForge started still runs. |
| Event |
Description |
InstallMidiRuntimeRequested |
Download and install PadForge's build of the runtime (DriverInstaller.InstallMidiRuntime). |
UninstallMidiServicesRequested |
Uninstall the installed runtime (DriverInstaller.UninstallMidiRuntime). MainWindow asks first when the runtime is Microsoft's bundle. |
Driver Status: SteamVR (#49)
The VR slot type needs SteamVR present. PadForge can install it Steam-free through steamcmd, and it only offers to remove the copy it created itself.
| Property |
Type |
Description |
IsSteamVrInstalled |
bool |
A SteamVR install of either shape is present (Steam client, or the Steam-free steamcmd one). Notifies SteamVrStatusText and ShowSteamVrUninstall. |
SteamVrStatusText |
string |
Computed, tiered (#287), deepest true state wins: "Not Installed", then controllers-live / driver-connected / running read off HMaestroVRController.GlobalDriverStatus() and OpenVrConsumerService.ServerConnected, falling back to "Installed". This is where the SteamVR status reads now that the Dashboard's driver strip is gone. |
SteamVrInstallDir |
string |
Where the Steam-free install will land (seeded from DriverInstaller.SteamVrInstallDir). Not persisted by PadForge: after a successful install the HIDMaestro path hint is the durable record. |
IsSteamVrOwned |
bool |
The present install is the Steam-free one PadForge created. A Steam-client install is never PadForge's to remove. |
ShowSteamVrUninstall |
bool |
Computed: IsSteamVrInstalled && IsSteamVrOwned. |
| Command |
CanExecute |
Description |
InstallSteamVrCommand |
!IsSteamVrInstalled |
Raises InstallSteamVrRequested. |
UninstallSteamVrCommand |
IsSteamVrInstalled && IsSteamVrOwned && !HasAnyVrSlots() |
Raises UninstallSteamVrRequested. |
| Event |
Description |
InstallSteamVrRequested |
Install SteamVR Steam-free. |
UninstallSteamVrRequested |
Remove the PadForge-owned install. |
| Method |
Description |
RefreshSteamVrStatus() |
Raises the SteamVR row's change notification. Called by MainWindow's periodic status refresh, since SteamVrStatusText reads live statics rather than stored fields. |
Driver Uninstall Guards
| Member |
Type |
Description |
HasAnyMidiSlots |
Func<bool> |
Set by MainWindow. True if any slot uses MIDI. Blocks UninstallMidiServicesCommand while the App SDK runtime is the API in use. |
HasAnyVrSlots |
Func<bool> |
Set by MainWindow. True if any created slot is a VR slot. Blocks UninstallSteamVrCommand. |
HasAnyHidHideDevices |
Func<bool> |
Set by MainWindow. True if any device has HidHide enabled. Blocks UninstallHidHideCommand. |
RefreshDriverGuards() |
method |
Re-evaluates uninstall CanExecute for HidHide, MIDI Services, and SteamVR. Call after slot creation/deletion/type changes. |
Engine Settings
| Property |
Type |
Default |
Description |
AutoStartEngine |
bool |
true |
Auto-start engine on launch. |
MinimizeToTray |
bool |
false |
Minimize to system tray instead of taskbar. |
CloseToTray |
bool |
false |
Closing the window keeps PadForge running in the system tray. Has its own ResetCloseToTrayCommand. |
AlwaysShowTrayIcon |
bool |
false |
Keep the tray icon up while the window is open (#439), so the tray menu's Exit never needs the window closed first. |
BatteryNotifyEnabled |
bool |
true |
Low-battery notification master toggle (#293). |
BatteryNotifyThreshold |
int |
15 |
Percent at or below which the edge-triggered notification fires. Clamped 5–50. |
BatteryNotifyVibrate |
bool |
false |
Also buzz the device on the low-battery edge, using the identify pulse train. |
AssignOfferNewDevice |
bool |
true |
Assignment Prompts: offer a device PadForge has never seen before to the virtual controller whose page is open when it connects. |
AssignOfferEmptySlot |
bool |
true |
Assignment Prompts: offer any connecting device, new or already known, to the open virtual controller when that slot has no devices assigned. |
HandheldButtonsEnabled |
bool |
false |
Handheld PC Buttons (#343): adds the per-machine Hidden Buttons row and the System Motion row. The setter mirrors into HandheldButtonRegistry.FeatureEnabled, which the engine sweep reads. |
StartMinimized |
bool |
false |
Start minimized. |
DiagnosticsLoggingEnabled |
bool |
false |
Diagnostics logging (#303): mirrors the engine's in-memory event ring to diagnostics.log continuously. Persisted, so it survives restarts and covers auto-started sessions where a launch flag cannot. |
DiagnosticsFolderPath |
string |
- |
Read-only forwarder over DiagnosticsLogControl.Folder: where the log and snapshots land. Shown on the Diagnostics card. |
StartAtLogin |
bool |
false |
Auto-start at login. |
EnablePollingOnFocusLoss |
bool |
true |
Continue polling on focus loss. |
FlydigiEnhancedProtocol |
bool |
true |
SDL's Flydigi HIDAPI driver switch (#395), applied through InputManager.ApplyFlydigiEnhancedProtocol. On, SDL drives a Flydigi pad's vendor interface (M1 to M4, C, Z, LM, RM, motion). Off leaves the pad on its XInput view so Flydigi Space Station keeps access. |
GKeysEnabled |
bool |
false |
Read Logitech G-keys through the G-key SDK (#454). The setter mirrors into LogitechGKeysRuntime.Enabled. See Logitech G-Keys Internals. |
GKeysStatus |
string |
"" |
Which of seven states the G-key source is in, pushed by InputService. Empty while the feature is off, which collapses the line. |
PollingOverrideNote |
string |
null |
Why the global polling knob is not in charge (#365 follow-up). While the active profile carries a polling override, this names the profile and the rate it imposes, and the Settings page shows it under the knob. Null or empty collapses the note. Written by InputService.ApplyEffectivePollingRate, the same owner that writes the live rate, so note and loop cannot disagree. |
PollingRateMs |
int |
1 |
Polling interval in ms. Clamped to 1–16. |
HmInactivityDestroyTimeoutSeconds |
int |
60 |
Seconds the engine waits for any mapped device to return online before tearing down the live HM virtual controller and freeing its kernel slot. Clamped to 0–3600. 0 disables the timeout. The slot's configuration (mappings, profile, position, enabled state) is preserved end-to-end. Only the live VC is destroyed. Once mapped devices come back online, the VC recreates automatically at the same visual position. Surviving Xbox HM VCs at higher visual positions bubble down to keep xinputhid indices contiguous after the teardown. |
EnableInputHiding |
bool |
true |
Master switch for device hiding (HidHide + input hooks). |
KeepHidHideCloaksBetweenLaunches |
bool |
false |
When true, HidHide cloaks stay asserted after PadForge exits so other apps still see the physical devices hidden. When false (default), cloaks clear on shutdown. |
Updates (#457)
The Settings > Updates card. UpdateController (Services/UpdateController.cs) subscribes to the three request events and to the three switches, and writes the status line, the progress and the busy state. The three switches persist as PadForge.xml AppSettings elements of the same names. The user-facing behavior is on Updates, the mechanics on Updates Internals.
| Property |
Type |
Default |
Description |
CheckForUpdatesAutomatically |
bool |
true |
Check GitHub 20 seconds after launch and every 12 hours after that. |
InstallUpdatesAutomatically |
bool |
false |
Download what the automatic check finds and install it at the next launch. The card shows it only while CheckForUpdatesAutomatically is on. |
IncludePreReleaseUpdates |
bool |
false |
Offer the rolling dev build instead of releases. A change cancels the running operation, drops the current offer and clears the status line, then checks again when automatic checks are on. |
UpdateStatusText |
string |
"" |
The card's status line, empty until a check has run. Built on every read from the function SetUpdateStatus(Func<string>) stored, so a language change reaches it (OnCultureChanged raises it). |
IsUpdateAvailable |
bool |
false |
A newer build was found. Shows Install and Restart and Release Notes, and turns the status line ember. |
IsUpdateBusy |
bool |
false |
A check or a download is running. Disables Check Now and Install and Restart. |
IsUpdateDownloading |
bool |
false |
Shows the progress bar. |
UpdateProgress |
int |
0 |
Download progress, 0 to 100. |
| Command |
CanExecute |
Description |
CheckForUpdatesNowCommand |
!IsUpdateBusy |
Check Now. Raises CheckForUpdatesNowRequested. |
InstallUpdateCommand |
IsUpdateAvailable && !IsUpdateBusy |
Install and Restart. Raises InstallUpdateRequested. |
OpenReleaseNotesCommand |
always |
Release Notes. Raises OpenReleaseNotesRequested. |
Settings File
| Property |
Type |
Description |
SettingsFilePath |
string |
Path to the loaded settings file. |
HasUnsavedChanges |
bool |
Unsaved changes exist. |
| Command |
Description |
SaveCommand |
Raises SaveRequested. |
ReloadCommand |
Raises ReloadRequested. |
ResetCommand |
Raises ResetRequested. |
OpenSettingsFolderCommand |
Raises OpenSettingsFolderRequested. |
| Event |
Description |
SaveRequested |
Save settings. |
ReloadRequested |
Reload from disk. |
ResetRequested |
Reset settings. |
OpenSettingsFolderRequested |
Open settings folder. |
Diagnostic Info
| Property |
Type |
Description |
SdlVersion |
string |
SDL3 version from SDL_Linked_Version(), formatted "SDL {major}.{minor}.{patch}". |
ApplicationVersion |
string |
BuildIdentity.Display, which adds the commit count and short hash to a build made from a git checkout, for example "4.5.3 (r3682@176208e)". Set by MainWindow on load. |
RuntimeVersion |
string |
.NET runtime version (Environment.Version). |
Display Preferences
| Property |
Type |
Default |
Description |
Use2DControllerView |
bool |
false |
Show 2D controller view instead of 3D. |
| Member |
Type |
Default |
Description |
EnableCommunityConfigLookup |
bool |
false |
Master opt-in for the Steam Workshop clients. Persisted, dirty-gate allowlisted. |
ShowLegacyWorkshopConfigs |
bool |
false |
Legacy sub-toggle. Persisted, dirty-gate allowlisted. |
ClearWorkshopCacheCommand |
RelayCommand |
|
Raises ClearWorkshopCacheRequested, handled by MainWindow (empties SteamWorkshopCache). |
CheckWorkshopUpdatesCommand |
RelayCommand |
|
Raises CheckWorkshopUpdatesRequested, handled by MainWindow (batched provenance check). |
BrowseCommunityConfigsCommand |
RelayCommand |
|
The Profiles-page entry. No CanExecute gate: with the opt-in off the dialog opens on its cold state and offers the enable step. |
BrowseStarterProfilesCommand |
RelayCommand |
|
Opens the starter-profile gallery (#256) through BrowseStarterProfilesRequested. Always enabled: the catalog ships in the box, so there is no opt-in and no network. |
Remote Link Peer Manager (#138)
The trusted-peer list and identity-protection mode live here. The Dashboard's Remote Link section binds them through DashboardViewModel.RemoteLink.
| Property |
Type |
Description |
TrustedPeers |
ObservableCollection<RemoteLinkTrustedPeer> |
Trusted paired PCs, listed under Paired PCs on the Dashboard's Remote Link card. |
NearbyUnpaired |
ObservableCollection<RemoteLinkNearbyPeer> |
PadForge PCs discovered on the LAN that aren't paired yet. |
HasNearbyUnpaired |
bool |
Computed: NearbyUnpaired.Count > 0. |
IdentityProtectionModes |
IReadOnlyList<string> |
Dropdown options in index order: 0 Secure, 1 password-portable, 2 open-portable. |
IdentityProtectionModeIndex |
int |
Selected protection mode (0/1/2). User changes raise IdentityProtectionModeChangeRequested. Programmatic reverts use SetIdentityProtectionModeSilently so no event re-fires. Notifies IdentityProtectionHint. |
IdentityProtectionHint |
string |
Computed: one-line guidance under the dropdown for the selected mode. |
| Command |
Description |
RevokeAllPeersCommand |
Raises PeerRevokeAllRequested. |
| Event |
Args |
Description |
PeerRevokeRequested |
string |
Revoke one peer by fingerprint. |
PeerRevokeAllRequested |
- |
Revoke every peer. |
PeerRenameRequested |
string, string |
Rename a peer (fingerprint, new name), persisted. |
PeerConnectRequested |
string |
Connect to a paired-but-offline peer (host:port). |
PeerAssignmentPermissionChanged |
string, bool |
A peer's Allow Remote Assignment Changes switch changed (fingerprint, allowed). InputService writes it to the trust store. |
PeerAssignmentsRequested |
string |
Assign Shared Devices was clicked for a peer (fingerprint). InputService opens RemoteAssignmentsDialog over that peer's assignment channel. |
IdentityProtectionModeChangeRequested |
int |
User picked a different protection mode (new index). |
| Method |
Description |
RefreshTrustedPeers(IEnumerable<PeerTrust>, IReadOnlyCollection<string> connectedFingerprints = null) |
Rebuilds TrustedPeers from the trust store. Called on pair / revoke / rename, not for online refresh. |
UpdatePeerOnlineStatus(IReadOnlyCollection<string>) |
Updates each peer's online dot in place from the live connection set. |
UpdatePeerReachability(IReadOnlyDictionary<string,string>) |
Updates each paired peer's reachable host:port in place from LAN discovery. |
SetNearbyUnpaired(IEnumerable<RemoteLinkNearbyPeer>) |
Replaces the nearby-unpaired list. |
SetIdentityProtectionModeSilently(int) |
Sets the mode without raising the change request (init from settings, or revert a canceled switch). |
First-Run, Window, and Foreground State
| Property |
Type |
Default |
Description |
ForegroundExeName |
string |
"-" |
Foreground exe filename the auto-switch monitor last saw (#175 item 8). Runtime-only, fed at 1 Hz by InputService's UI timer, never persisted. |
IsForegroundMatched |
bool |
false |
True while the foreground exe matches a profile. Runtime-only. |
NoProfileHasExecutables |
bool |
- |
Computed: no profile carries an executable match rule (#175 item 8). |
LegacyDriverCleanupOffered |
bool |
false |
True after the v3 first-run cleanup wizard has been shown. Persists so the dialog appears once. |
FirstRunTourCompleted |
bool |
false |
True once the first-run welcome tour is completed or skipped. Replaces the pre-v4 beside-exe marker file. |
MainWindowLeft / Top |
double |
-1 |
Main window position (profile-independent). |
MainWindowWidth / Height |
double |
1100 / 720 |
Main window size. |
MainWindowState |
int |
0 |
Persisted window state (normal / maximized). |
MainWindowFullScreen |
bool |
false |
Full-screen flag. |
Profiles
| Property |
Type |
Default |
Description |
EnableAutoProfileSwitching |
bool |
false |
Auto-profile switching enabled. |
EnableExternalControl |
bool |
false |
Serve the external-control named pipe (#366) so a launcher or script can activate and deactivate profiles from outside PadForge. InputService watches this property and starts or stops the pipe live. The Profiles page carries the checkbox beside the shortcut combo and the foreground match, because this is a third way a profile activates rather than a global engine service. |
ProfileItems |
ObservableCollection<ProfileListItem> |
empty |
Profile entries for UI list. |
ProfileShortcuts |
ObservableCollection<ProfileShortcutViewModel> |
empty |
Profile-switch shortcut rows for the ProfilesPage shortcuts card. |
SelectedProfile |
ProfileListItem |
null |
Selected profile. Refreshes delete/edit/load/export commands. |
ActiveProfileInfo |
string |
"Default" |
Active profile display text. |
| Command |
CanExecute |
Description |
NewProfileCommand |
always |
Raises NewProfileRequested. |
SaveAsProfileCommand |
always |
Raises SaveAsProfileRequested. |
DeleteProfileCommand |
SelectedProfile != null && !IsDefault |
Raises DeleteProfileRequested. |
EditProfileCommand |
SelectedProfile != null && !IsDefault |
Raises EditProfileRequested. |
LoadProfileCommand |
SelectedProfile != null |
Raises LoadProfileRequested or RevertToDefaultRequested if default. |
ExportProfileCommand |
SelectedProfile != null |
Raises ExportProfileRequested. Exports the selected profile as a .pfprofile. The Default entry exports a snapshot of the current settings. |
ImportProfileCommand |
always |
Raises ImportProfileRequested. Imports a .pfprofile. |
| Event |
Description |
RevertToDefaultRequested |
Revert to default profile. |
NewProfileRequested |
Create new empty profile. |
SaveAsProfileRequested |
Save current settings as profile. |
DeleteProfileRequested |
Delete selected profile. |
EditProfileRequested |
Edit selected profile metadata. |
LoadProfileRequested |
Load selected profile. |
ExportProfileRequested |
Export selected profile to a .pfprofile. |
ImportProfileRequested |
Import a .pfprofile. |
The delete / edit / load / export commands refresh their own CanExecute inline in the SelectedProfile setter. There is no separate refresh method.
ProfileListItem
File: SettingsViewModel.cs (nested class)
Display item for a profile in the Settings list.
| Property |
Type |
Description |
Id |
string |
Profile identifier. |
IsActive |
bool |
Whether this profile is the currently active one. Drives the lit flame in the Profiles list (#175). |
Name |
string |
Display name. |
Executables |
string |
Comma-separated exe names for auto-switch (display form). Notifies HasExecutables, AutoSwitchRuleSummary. |
ExecutablePaths |
string |
Pipe-separated full executable paths (the ForegroundMonitorService match source). Feeds the card's exe icon and mono exe line (#175). |
FirstExecutablePath |
string |
Computed: full path of the first executable, or null. The card's icon converter File.Exists-gates this. |
FirstExecutableName |
string |
Computed: file name of the first executable, or null. |
SecondExecutableName |
string |
Computed: file name of the second executable, or null. The card face shows up to two names before the +N marker. |
ExtraExecutablesSuffix |
string |
Computed: locale-neutral "+N" marker for rules beyond the two shown names, or null. |
HasExecutables |
bool |
Computed: the profile carries any executable match rule. Half of the auto-switch chip gate (#175). |
AutoSwitchRuleSummary |
string |
Computed: exe-match rule summary for the cold auto-switch chip, the display exe list under a localized prefix. |
TopologyLabel |
string |
Slot topology summary. |
HasNoSlots |
bool |
Computed: all type counts are zero. |
XboxCount |
int |
Number of Xbox slots in this profile. |
PlayStationCount |
int |
Number of PlayStation slots. |
NintendoCount |
int |
Number of Nintendo slots (#215). |
ExtendedCount |
int |
Number of Extended slots. |
MidiCount |
int |
Number of MIDI slots. |
KbmCount |
int |
Number of Keyboard+Mouse slots. |
VrCount |
int |
Number of VR slots (#49). |
IsDefault |
bool |
Computed: true if Id == "__default__". |
| Constant |
Value |
Description |
DefaultProfileId |
"__default__" |
Sentinel ID for the Default profile. |
DevicesViewModel
File: DevicesViewModel.cs
DataContext for: Devices page
All detected input devices (online and offline) with raw input state for the selected device.
Device List
| Property |
Type |
Description |
Devices |
ObservableCollection<DeviceRowViewModel> |
All known devices. Updated by InputService on change. |
SelectedDevice |
DeviceRowViewModel |
Selected device. Notifies HasSelectedDevice, refreshes slot buttons and command state. |
HasSelectedDevice |
bool |
Computed: SelectedDevice != null. |
Device Counts
| Property |
Type |
Description |
TotalCount |
int |
Total detected devices. |
OnlineCount |
int |
Connected devices. |
Type Facet Filter (#175)
Chip row above the device list that filters by device class. The chips bind their labels to the count properties. Clicking one sets SelectedFacet.
| Property |
Type |
Default |
Description |
SelectedFacet |
string |
"ALL" |
Active facet token: ALL / GAMEPAD / JOYSTICK / WHEEL / KEYBOARD / MOUSE / OTHER. Locale-neutral literals the chip DataTriggers key off. The setter routes through SetFacet. |
FacetCountGamepad |
int |
0 |
Rows behind the GAMEPAD chip (the stick classes: Gamepad / FirstPerson / Supplemental). |
FacetCountJoystick |
int |
0 |
Rows behind the JOYSTICK chip (joysticks + flight sticks). |
FacetCountWheel |
int |
0 |
Rows behind the WHEEL chip. |
FacetCountKeyboard |
int |
0 |
Rows behind the KEYBOARD chip. |
FacetCountMouse |
int |
0 |
Rows behind the MOUSE chip. |
FacetCountOther |
int |
0 |
Rows behind the OTHER chip (touchpads, MIDI, NFC, microphones, consumer collections, unclassified). |
| Method |
Description |
SetFacet(string) |
Applies the facet filter to the default collection view the ListBox binds through, so the XAML keeps a plain ItemsSource="{Binding Devices}". ALL clears the filter. No-op when the token is unchanged or empty. |
The ALL chip binds TotalCount. RefreshCounts() recomputes every facet count and re-runs the active filter so rows whose type key changed in place re-bucket.
| Property |
Type |
Description |
RawAxes |
ObservableCollection<AxisDisplayItem> |
Axis values (progress bars). |
RawButtons |
ObservableCollection<ButtonDisplayItem> |
Button states (circles). |
RawPovs |
ObservableCollection<PovDisplayItem> |
POV hat values (compass). |
KeyboardKeys |
ObservableCollection<KeyboardKeyItem> |
Keyboard layout items. |
IsKeyboardDevice |
bool |
Selected device is a keyboard. |
IsMouseDevice |
bool |
Selected device is a mouse. |
IsTouchpadDevice |
bool |
Selected device is itself a touchpad (CapType Touchpad) or a drawing tablet. |
IsMidiDevice |
bool |
Selected device is a MIDI input (#128). Drives the live piano / CC preview off LiveMidi. |
IsNfcDevice |
bool |
Selected device is an NFC reader (#150). Populates NfcTags. |
IsMicrophoneDevice |
bool |
Selected device is a standalone microphone (#317). Hides the numbered button grid, since every button it has is a named phrase. |
ShowVoicePhrases |
bool |
Selected device carries voice phrases (a microphone row, or a Bluetooth DualSense whose embedded mic is not a system device). Drives the Voice Macros section, the voice twin of the NFC Tags section. |
VoicePhrases |
ObservableCollection<NfcTagDisplayItem> |
Named phrase rows for the live preview (#317): "Any Phrase" plus each registered phrase. Reuses the NFC row item. Rebuilt by RebuildVoicePhrases(int). |
VoiceButtonBase |
int |
Raw-button base the current device's phrase buttons sit at (0 on a microphone row, the reserved pad range on a DualSense). -1 when none. Private setter. |
IsConsumerDevice |
bool |
Selected device is a Consumer Control collection (#168). Populates ConsumerButtons. |
IsHeadsetMotionDevice |
bool |
Selected device is a Sony headset head tracker (#188). Collapses the raw Axes / Buttons sections, since a motion-only source has neither and the gyro readout is its whole preview. |
IsHandheldDevice |
bool |
Selected device is the handheld hidden-buttons row (#343). Drives the named button list in place of the numbered grid, the same treatment NFC gets. |
IsSystemMotionDevice |
bool |
Selected device is the machine's own motion sensor row (#343). Motion only, so the raw Axes / Buttons sections collapse like the headset tracker's. |
IsHeadTrackerDevice |
bool |
Selected device is the head tracker row (#355): six named axes, no buttons, plus a source line. |
HeadTrackerStatus |
string |
Which head-tracking source is live, for the details pane (#355). Written by InputService.UpdateDevicePanelLines, which runs on the Devices page tick, on a selection change while the engine is off, and once after an engine stop, so a retired device's line clears. Null coalesces to empty. |
HeadTrackerStatusVersion |
int |
Internal. The device StatusVersion the current HeadTrackerStatus was built from, -1 for none. |
HeadTrackerStatusDevice |
object |
Internal. The device instance that version belongs to. A reopen (port change, FreeTrack toggle) builds a new device whose StatusVersion restarts at 0 and collides with the cached 0, so the cache compares instance as well as version. |
IsGameTelemetryDevice |
bool |
Selected device is the Game Telemetry row (#531): named axes, the Driving, Reverse and Neutral buttons, a button per gear, plus the listener's line. |
GameTelemetryStatus |
string |
What the listener hears, for the details pane (#531), the same line as the Dashboard card's. Written by InputService.UpdateDevicePanelLines like HeadTrackerStatus. Null coalesces to empty. |
GameTelemetryStatusVersion / GameTelemetryStatusDevice |
int / object |
Internal. The cache pair, the head tracker's shape: the row's StatusVersion and the row instance it belongs to. |
HandheldButtons |
ObservableCollection<NfcTagDisplayItem> |
Named button chips, each lighting while its button is down. Reuses the NFC row item. For the handheld row (#343) RebuildHandheldButtons() fills it from the registry, with Uid carrying the delivery description. For a VR controller or Logitech G-Keys row RebuildNamedButtons(IReadOnlyList<DeviceObjectItem>) fills it from the device's own button objects. |
ShowNamedButtons |
bool |
The preview shows the named chips instead of the numbered grid: the handheld, VR controller, and Logitech G-Keys rows. |
NfcTags |
ObservableCollection<NfcTagDisplayItem> |
Registered-tag rows for the NFC preview. Rebuilt by RebuildNfcTags(). |
ConsumerButtons |
ObservableCollection<ConsumerButtonDisplayItem> |
Named media-key chips for the Consumer Control preview. |
LiveMidi |
MidiInputState |
Latest MIDI input snapshot the piano / CC preview polls. Plain property, no change notification. |
HasTouchpadData |
bool |
The selected device has a touchpad to preview (UserDevice.HasTouchpad, or the device is itself a touchpad or tablet). Set when the selection changes. |
TouchpadX0–TouchpadX4 / TouchpadY0–TouchpadY4 |
double |
Per-finger normalized position (0.0–1.0) on the first pad. Five contacts, not two. |
TouchpadDown0–TouchpadDown4 |
bool |
Per-finger contact state. |
HasSecondTouchpadData |
bool |
The device exposes a second pad (Steam Controller / Steam Deck). Switches the labels to TouchpadLabel / Touchpad2Label. |
TouchpadLabel / Touchpad2Label |
string |
Computed pad captions. Touchpad2Label is always the localized touchpad word plus " 2". TouchpadLabel picks up its own " 1" only when a second pad exists. |
Pad2X0–Pad2X4 / Pad2Y0–Pad2Y4 / Pad2Down0–Pad2Down4 |
double / bool |
Second-pad contacts, same five-finger shape. |
MouseMotionX |
double |
Mouse X motion. |
MouseMotionY |
double |
Mouse Y motion. |
MouseScrollIntensity |
double |
Normalized scroll intensity (−1 to 1). |
SelectedButtonTotal |
int |
Button count on selected device. |
HasRawData |
bool |
Raw state data available. |
Gyroscope / Accelerometer
| Property |
Type |
Description |
HasGyroData |
bool |
Gyroscope data available. |
HasAccelData |
bool |
Accelerometer data available. |
GyroX |
double |
Gyroscope X value. |
GyroY |
double |
Gyroscope Y value. |
GyroZ |
double |
Gyroscope Z value. |
AccelX |
double |
Accelerometer X value. |
AccelY |
double |
Accelerometer Y value. |
AccelZ |
double |
Accelerometer Z value. |
HasAccelAuxData |
bool |
Aux (left-side) accelerometer data available (#199). True for a Nunchuk behind a MotionPlus or a left Joy-Con, where the device carries a second accelerometer alongside the primary one. |
AccelAuxX |
double |
Aux accelerometer X value. |
AccelAuxY |
double |
Aux accelerometer Y value. |
AccelAuxZ |
double |
Aux accelerometer Z value. |
HasGyroAuxData |
bool |
Aux (left-side) gyro data available (#252): the left Joy-Con of a combined pair. Paired with the aux accel readout so the user can see which half a reading comes from before binding it. Cleared by ClearRawState(). |
GyroAuxX |
double |
Aux gyro X value. |
GyroAuxY |
double |
Aux gyro Y value. |
GyroAuxZ |
double |
Aux gyro Z value. |
| Property |
Type |
Description |
ActiveSlotItems |
ObservableCollection<SlotButtonItem> |
Slot toggle buttons. Only includes created/active slots. |
Commands
| Command |
CanExecute |
Description |
RefreshCommand |
always |
Raises RefreshRequested. Force-refreshes device list. |
PairCommand |
always |
Raises PairRequested. Opens the Bluetooth pairing flow for controllers that need an in-app pairing ceremony (Wii controllers, #116). |
AssignToSlotCommand |
HasSelectedDevice |
RelayCommand<int>. Raises AssignToSlotRequested with slot index (0–15). |
ToggleSlotCommand |
HasSelectedDevice |
RelayCommand<int>. Toggles slot assignment. Raises ToggleSlotRequested. |
HideDeviceCommand |
HasSelectedDevice |
Raises HideDeviceRequested with the selected device's InstanceGuid. The row itself stays in the collection until the service refreshes the list. |
RemoveDeviceCommand |
HasSelectedDevice |
Removes the row from Devices, clears SelectedDevice, raises RemoveDeviceRequested, then RefreshCounts(). |
ClearModEffects() |
method |
Ends what DSX game mods hold on every virtual controller the selected device feeds (GameModEffects.ClearDevice, #529) and clears its GameModLine. Called by Clear Mod Effects on the panel. |
ResetSelectedDeviceSettingCommand |
HasSelectedDevice and a name DeviceRowViewModel.CanResetSetting accepts |
RelayCommand<string> (DevicesViewModel.SettingResets.cs). Runs the selected row's ResetSettingCommand for the named option, then clears LastRawStateDeviceGuid and raises DeviceHidingChanged so the option persists. Skips HidHideEnabled while HidHide is unavailable. |
Events
| Event |
Args |
Description |
RefreshRequested |
EventArgs |
Refresh requested. |
PairRequested |
EventArgs |
User opened the Bluetooth pairing flow (#116). |
AssignToSlotRequested |
int |
Slot index. |
ToggleSlotRequested |
int |
Slot index. |
HideDeviceRequested |
Guid |
Instance GUID. |
RemoveDeviceRequested |
Guid |
Instance GUID. |
DeviceHidingChanged |
Guid |
HidHide or ConsumeInput toggle changed. |
Key Methods
| Method |
Description |
RebuildRawStateCollections(IReadOnlyList<int> axisIndices, IReadOnlyList<int> buttonIndices, int povCount, bool isKeyboard, bool isMouse, bool isTouchpad, bool isMidi, bool isNfc, IReadOnlyList<ConsumerButtonDisplayItem> consumerButtons, bool isHeadsetMotion, int voiceButtonBase, bool isMicrophone, bool isHandheld, bool isSystemMotion, bool isHeadTracker, IReadOnlyList<DeviceObjectItem> namedObjects) |
Internal. Rebuilds axis/button/POV collections for a new device. axisIndices is the sparse list of axis slots the device actually exposes (from SupportedAxisIndices / CapAxisIndices through InputService.ResolveAxisIndices), so each AxisDisplayItem.Index is the real state.Axis[] slot and its label reads Axis {slot}, gaps included. buttonIndices is the sparse list of button positions the device actually exposes (each item's value is stored verbatim as both Index and DisplayNumber). namedObjects, passed for VR controller and Logitech G-Keys rows, fills the named chips in place of the numbered grid. The class-flag booleans default to false, consumerButtons and namedObjects to null, and voiceButtonBase to -1. Handles keyboard, mouse, touchpad, MIDI, NFC, Consumer Control, headset-motion, handheld, system-motion, head-tracker, named-button, and voice-phrase cases. |
ClearRawState() |
Internal. Clears all raw state display data, including every class flag, the head-tracker status cache, and the NFC / voice / handheld / Consumer collections. ShowNamedButtons keeps its value until the next rebuild. |
RebuildNfcTags() |
Repopulates NfcTags from NfcTagRegistry: "Any NFC Tag" first at button 0, then each registered tag at its stable button index. |
RebuildHandheldButtons() |
Repopulates HandheldButtons from HandheldButtonRegistry.Entries (#343). Called on selection and whenever the registry changes. |
RebuildNamedButtons(IReadOnlyList<DeviceObjectItem>) |
Repopulates HandheldButtons from a device's own button objects, one chip per button with the name the device publishes. |
RebuildVoicePhrases(int) |
Repopulates VoicePhrases from VoicePhraseRegistry at the passed button base. A negative base clears the list. |
RefreshSlotButtons() |
Rebuilds ActiveSlotItems from created slots and selected device assignments. |
RefreshAllSlotBadges() |
Re-derives every row's SlotBadges after a slot set changes. |
FindByGuid(Guid) |
Finds a DeviceRowViewModel by instance GUID. |
RefreshCounts() |
Updates TotalCount and OnlineCount, recomputes every facet count, and re-runs the active filter. |
NotifyDeviceHidingChanged(Guid) |
Raises DeviceHidingChanged. |
Internal State
| Member |
Type |
Description |
LastRawStateDeviceGuid |
Guid |
Tracks which device's collections are populated. Prevents unnecessary rebuilds. |
HeadTrackerAxisName(int slot) |
static string |
Localized name of a head-tracker axis slot (#355): Yaw, Pitch, Roll, X, Y, Z for slots 0–5. Null past the six. |
File: DevicesViewModel.cs
Single slot toggle button on the Devices page.
| Property |
Type |
Description |
PadIndex |
int |
Zero-based slot index (0–15). |
SlotNumber |
int |
1-based number among active slots. |
IsAssigned |
bool |
Selected device is assigned to this slot. |
VcType |
VirtualControllerType |
The slot's output type. Notifies the two icon properties below. |
TypeGeometry |
Geometry |
Branded type icon path from SlotTypeIconMap.GeometryFor. Null for the glyph-drawn types. |
TypeGlyph |
string |
Segoe glyph from SlotTypeIconMap.GlyphFor for the types with no path geometry (MIDI E8D6, Keyboard+Mouse E961, VR F119). |
AxisDisplayItem
File: DevicesViewModel.cs
Display item for a single axis value.
| Property |
Type |
Description |
Index |
int |
The real state.Axis[] slot (sparse, gaps preserved). |
Name |
string |
Display name from that slot (e.g., "Axis 0"). |
NormalizedValue |
double |
Value normalized to 0.0–1.0. Bound to ProgressBar. |
RawValue |
int |
Raw value (0–65535). |
File: DevicesViewModel.cs
Display item for a single button state.
| Property |
Type |
Description |
Index |
int |
Raw button slot (sparse, e.g. 16 for touchpad-click). |
DisplayNumber |
int |
The number shown to the user. Equals Index, so the preview agrees with the mapping picker's Button N names, gaps included. The XAML binds DisplayNumber. |
IsPressed |
bool |
Currently pressed. Bound to circle fill color. |
File: DevicesViewModel.cs
One named chip in the Consumer Control live preview (#168): a canonical media key that lights while held.
| Property |
Type |
Description |
Index |
int |
Slot into state.Buttons[] (the persistence-stable ConsumerUsageTable index). |
Name |
string |
Localized button name. |
IsPressed |
bool |
Currently held. Bound to chip background. |
NfcTagDisplayItem
File: DevicesViewModel.cs
One row in the NFC reader's live tag preview (#150): a registered tag (or "Any NFC Tag") that lights while its button pulses.
| Property |
Type |
Description |
Name |
string |
Tag display name. |
Uid |
string |
Tag UID (empty for "Any NFC Tag"). |
Button |
int |
Stable raw-button index (0 = Any NFC Tag). |
LastActiveTick |
long |
TickCount64 of the last tap. Held past the ~175 ms button pulse so the row does not flicker. |
IsActive |
bool |
Lit while recently tapped. Bound to row background. |
KeyboardKeyItem
File: DevicesViewModel.cs
Display item for a keyboard key with position data. Rendered in a Canvas inside a Viewbox for auto-scaling.
| Property |
Type |
Description |
VKeyIndex |
int |
Windows Virtual Key code. |
Label |
string |
Key label. |
X |
double |
Canvas X position (px). |
Y |
double |
Canvas Y position (px). |
KeyWidth |
double |
Key width (px). |
KeyHeight |
double |
Key height (px). |
IsPressed |
bool |
Currently pressed. |
| Constant |
Value |
Description |
LayoutWidth |
556 |
Canvas width for the keyboard layout. |
LayoutHeight |
136 |
Canvas height for the keyboard layout. |
| Static Method |
Description |
BuildLayout() |
Builds full ANSI QWERTY layout with numpad. Each key mapped to its VK code. |
IsVKeyPressed(bool[], int) |
Checks if a VKey is pressed in a button array. |
PovDisplayItem
File: DevicesViewModel.cs
Display item for a single POV hat switch.
| Property |
Type |
Default |
Description |
Index |
int |
- |
POV hat index. |
Centidegrees |
int |
-1 |
Value in centidegrees (0–35900), or −1 for centered. Notifies IsCentered and AngleDegrees. |
IsCentered |
bool |
- |
Computed: Centidegrees < 0. |
AngleDegrees |
double |
- |
Computed: direction in degrees (0–359) for RotateTransform. |
DeviceRowViewModel
File: DeviceRowViewModel.cs
Single device row on the Devices page. Shows identification, status, capabilities, and input hiding controls.
Identity
| Property |
Type |
Description |
InstanceGuid |
Guid |
Unique instance GUID. |
SdlGuid |
string |
SDL device GUID string used by gamecontrollerdb mappings. |
DeviceName |
string |
Display name. |
ProductName |
string |
Product name. |
ProductGuid |
Guid |
Product GUID in PIDVID format. |
USB Identification
| Property |
Type |
Description |
VendorId |
ushort |
USB Vendor ID. Notifies VendorIdHex. |
ProductId |
ushort |
USB Product ID. Notifies ProductIdHex. |
VendorIdHex |
string |
Computed: hex (e.g., "045E"). |
ProductIdHex |
string |
Computed: hex (e.g., "028E"). |
Status
| Property |
Type |
Default |
Description |
IsOnline |
bool |
false |
Device connected. Notifies StatusText. |
IsEnabled |
bool |
true |
Device enabled for mapping. Notifies StatusText. |
StatusText |
string |
- |
Computed: "Disabled", "Online", or "Offline". |
SerialNumber |
string |
"" |
Device serial, where the transport reports one. |
HasVidPid |
bool |
- |
Computed: either VendorId or ProductId is non-zero. Gates the VID:PID line. |
GameModLine |
string |
"" |
(#529) What DSX game mods set on the virtual controllers this device feeds, as this device carries it, from InputService.GameModDeviceLine. Empty hides the line and Clear Mod Effects. |
Battery (#167)
Battery indicator on the device row. Sourced from SDL by InputService's slow lane.
| Property |
Type |
Default |
Description |
BatteryPercent |
int |
-1 |
Battery percentage. -1 when the device reports none (wired, unknown, or offline). Notifies HasBattery, BatteryText, BatteryGlyph. |
BatteryCharging |
bool |
false |
True while the device reports charging or charged. Notifies BatteryGlyph. |
HasBattery |
bool |
- |
Computed: BatteryPercent >= 0. Gates whether the indicator renders. |
BatteryText |
string |
- |
Computed: "78%", or empty when BatteryPercent < 0. |
BatteryGlyph |
string |
- |
Computed: Segoe MDL2 Assets battery glyph bucketed to the nearest tenth. Discharging: Battery0–Battery9 = U+E850–U+E859, Battery10 = U+E83F. Charging: BatteryCharging0–8 = U+E85A–U+E862, BatteryCharging9 = U+E83E, BatteryCharging10 = U+EA93. |
Power: Idle Disconnect (#162) and Quick Charge (#372)
| Property |
Type |
Default |
Description |
IdleDisconnectMinutes |
int |
0 |
Idle countdown in minutes, 0 = off. Clamped >= 0. Persisted to UserDevice in seconds through the hiding-toggle channel. |
ShowIdleDisconnect |
bool |
false |
Whether the idle-disconnect control applies (Bluetooth path detected). Set by the service, not computed on the row. Notifies ShowPowerSection and ShowRawInputDivider. |
QuickChargeEnabled |
bool |
false |
Quick Charge (#372): drop this pad's Bluetooth link when its USB twin comes online. Persisted on UserDevice through the same channel as the hiding toggles. |
ShowQuickCharge |
bool |
false |
Whether the Quick Charge checkbox applies. Deliberately wider than ShowIdleDisconnect: the USB cable rebinds a Sony record to the USB path (identity keys on the shared MAC serial, so no twin record exists), a path the disconnect predicate rejects, which made the checkbox vanish exactly while the pad was plugged in. Seeded by ComputeShowQuickCharge. Notifies ShowPowerSection and ShowRawInputDivider. |
ShowPowerSection |
bool |
- |
Computed: ShowIdleDisconnect \|\| ShowQuickCharge. The Power section draws when either control in it does. |
Light gun, iCade and USIO rows (hifihedgehog/SDL#33)
| Property |
Type |
Default |
Description |
GunCalibration |
string |
"" |
A GunCon 2's or a Wii Remote's aim range as UserDevice.GunCalibration keeps it. Persisted through the hiding-toggle channel, which also hands it to a connected gun or remote. |
ShowGunCalibration |
bool |
false |
The row is a GunCon 2 (UserDevice.IsGunCon2) or a Wii Remote with an IR camera (UserDevice.HasIrCamera, #485), online or offline. A Remote Link peer's row has none, the rule the other owner-only controls follow (#248): ComputeShowGunCalibration, which the row fill calls. Notifies ShowGunCalibrationDivider and ShowRawInputDivider. |
GunIsWiiRemote |
bool |
false |
The light gun is a Wii Remote, not a GunCon 2 (#485). Notifies GunCalibrationStatus and GunCalibrateTooltip. |
GunConnectedHere |
bool |
false |
The gun's or remote's live device is this PC's own SdlDeviceWrapper. A relayed one is calibrated on its owner's PC. |
CanCalibrateGun |
bool |
- |
Computed: GunConnectedHere && IsOnline. Enables Calibrate. |
ShowGunCalibrationDivider |
bool |
- |
Computed: ShowGunCalibration && (ShowPowerSection \|\| !ShowInputModeOrHidingSection), the rule above the Light Gun section. |
GunCalibrationStatus |
string |
- |
Computed: the aim range, formatted with Devices_GunWindowDefault or Devices_GunWindowCalibrated. A Wii Remote's line says only whether a window is stored (Devices_WiiAimCalibrated or Devices_WiiAimDefault), since its pointer counts mean nothing to a reader. |
GunCalibrateTooltip |
string |
- |
Computed: what Calibrate does for this gun, Devices_WiiCalibrateTooltip for a Wii Remote, else Devices_GunCalibrateTooltip. |
ShowReadAsICade |
bool |
false |
A Bluetooth keyboard with IDs, not the ION iCade cabinet, whose pair ICadePads does not list (ICadePads.CanMark). |
ShowReadAsKeyboard |
bool |
false |
A listed pair's iCade Controller joystick, or its keyboard record while the pad is away. |
ShowUsioTekken / ShowUsioTaiko |
bool |
false |
A Namco USIO drum or stick joystick, by the names the fork gives each layout. Each offers the other layout. |
| Method |
Description |
ComputeShowQuickCharge(string devicePath, ushort vendorId, ushort productId, string serial) |
Internal static, so tests need no view model. True for a BluetoothLinkHelper.IsDisconnectTarget shape, or for a record whose serial parses as a non-zero Bluetooth address. The second leg is exactly the gate the engine's wired path fires on, with no vendor gate: the feature fires for any pad that reports its radio's MAC as the serial on both transports. |
Device-Class Flags
| Property |
Type |
Description |
IsMidiDevice |
bool |
Computed: DeviceTypeKey == "Midi". Drives the live piano / CC preview (#128). |
IsHeadsetMotionDevice |
bool |
Computed: DeviceTypeKey == "HeadsetMotion". True for Sony headset head trackers (#188). Collapses the raw Axes / Buttons preview, which a motion-only source has neither of, and gates the repair action. |
IsHandheldDevice |
bool |
Computed: DeviceTypeKey == "HandheldButtons". The handheld hidden-buttons row (#343). |
IsSystemMotionDevice |
bool |
Computed: DeviceTypeKey == "SystemMotion". The machine's own motion sensor row (#343). |
IsHeadTrackerDevice |
bool |
Computed: DeviceTypeKey == "HeadTracker". The head tracker row (#355). |
IsGameTelemetryDevice |
bool |
Computed: DeviceTypeKey == "GameTelemetry". The Game Telemetry row (#531). |
ShowLearnHandheldButton |
bool |
Computed: IsHandheldDevice and the path is not peer://. A peer row's buttons are learned on the machine that has them. |
HandheldDaemonRunning |
string |
Comma-joined names of vendor daemons the sweep found running (#343), empty when none. Written by the device list sync. Notifies the two below. |
HasHandheldDaemonWarning |
bool |
Computed: IsHandheldDevice and HandheldDaemonRunning is non-empty. |
HandheldDaemonWarning |
string |
Computed: the localized warning naming the running daemons, empty when there is nothing to warn about. |
ShowRegisterNfcTag |
bool |
Computed: shows the Register / Manage NFC Tags button (#150). True for a standalone PC/SC reader (DeviceTypeKey == "Nfc") and for a Switch right Joy-Con / combined pair / Pro Controller (VID 057E, PID 2007 / 2008 / 2009) on a Bluetooth link, whose own reader registers through the same dialog (#241). Always false on a peer:// row: the tap event never crosses the link, so registration belongs to the owner. |
ShowManageVoicePhrases |
bool |
Computed: shows the Manage Voice Macros button (#317). True for a "Microphone" row, and for a DualSense (VID 054C, PID 0CE6 / 0DF2) on a Bluetooth link, where the embedded mic is not a system device. Same peer:// exclusion as the NFC button. |
HasNfcCapabilityChip |
bool |
Computed: the device is a Switch controller carrying an NFC reader (VID 057E, PID 2007 / 2008 / 2009). A hardware fact, not the transport-gated arming state, so it reads true over USB too. A standalone PC/SC reader is excluded, its device type already says NFC. |
IsBluetoothLink |
bool |
Computed by DeviceTransport.IsBluetooth from the device path and VID:PID. |
FlydigiServiceRunning |
string |
Flydigi Space Station processes the device list sync found running (#395), empty when none. Notifies the two below. |
IsFlydigiDevice |
bool |
Computed: FlydigiServiceWatch.IsFlydigiDevice(VendorId, ProductId). |
HasFlydigiServiceWarning / FlydigiServiceWarning |
bool / string |
Computed: a Flydigi row while Flydigi software is running, and the localized warning that names it (Flydigi_ServiceWarning_Format). |
IsTabletDevice |
bool |
Computed: DeviceTypeKey == "Tablet", a drawing tablet. |
ShowTabletCaptureStatus |
bool |
Computed: IsTabletDevice && !IsInternalVirtual. Shows the capture line. |
TabletCaptureState |
TabletCaptureState |
Shared, Switching, WaitingForInput, Captured, Failed, or Offline. Notifies TabletInputStatus. |
TabletInputStatus |
string |
Computed: the localized line for TabletCaptureState ("Shared Input", "Switching Input", "Waiting For Tablet Input", "Captured By PadForge", "Tablet Input Unavailable"), or StatusText while offline. |
Capabilities
| Property |
Type |
Description |
AxisCount |
int |
Number of axes. |
ButtonCount |
int |
Number of buttons. |
PovCount |
int |
Number of POV hat switches. |
DeviceTypeKey |
string |
English type key: "Gamepad", "Joystick", "Wheel", "FlightStick", "FirstPerson", "Supplemental", "Mouse", "Keyboard", "Touchpad", "Tablet", "Midi", "Nfc", "Microphone", "ConsumerControl", "HeadsetMotion", "HandheldButtons", "SystemMotion", "HeadTracker", "VrController", "LogitechGKeys", "AnalogKeyboard", "WebMenus", and the vendor rows' "PeripheralLighting" and "PeripheralHaptics" (#494, labeled "Lighting" and "Haptics"). Anything else falls back to the generic "Device" label. The setter notifies every derived property that reads the key. A row is constructed before the key is known, so one missing from that list evaluates once against the empty string and its binding never updates (#315). |
DeviceType |
string |
Computed: localized type from DeviceTypeKey. |
HasRumble |
bool |
Supports rumble. |
HasGyro |
bool |
Has gyroscope. |
HasAccel |
bool |
Has accelerometer. |
HasTouchpad |
bool |
Exposes a touchpad surface. |
ShowTouchpadCapability |
bool |
Computed: HasTouchpad and the device is not itself a touchpad, so a touchpad row does not advertise itself twice. |
HasCapabilityIcons |
bool |
Computed: HasRumble \|\| HasGyro \|\| ShowTouchpadCapability. Gates the icon strip. |
CapabilitiesSummary |
string |
Computed: e.g., "6 axes, 11 buttons, 1 POV(s), Rumble, Gyro". A touchpad-type row skips the counts. |
Slot Assignment
| Property |
Type |
Description |
AssignedSlots |
List<int> |
Assigned pad slot indices (0–15). Set via SetAssignedSlots(). |
SlotBadges |
ObservableCollection<SlotBadge> |
Slot badges for XAML (icon + number). Rebuilt by SetAssignedSlots(). Absence of badges encodes "unassigned". The old IsUnassigned flag and its gray fallback pill were removed in #175 (phase 2 item 9). |
| Method |
Description |
SetAssignedSlots(List<int>) |
Replaces assigned slots, rebuilds SlotBadges with sequential numbering, notifies UI. |
| Property |
Type |
Default |
Description |
HidHideEnabled |
bool |
false |
Hidden via HidHide (driver-level). |
ConsumeInputEnabled |
bool |
false |
Mapped inputs consumed via low-level hooks. |
ForceRawJoystickMode |
bool |
false |
Bypass SDL gamepad remapping. Read raw joystick indices. |
IsHidHideAvailable |
bool |
false |
HidHide installed (controls toggle IsEnabled). |
ShowConsumeToggle |
bool |
- |
Computed: real keyboards and mice only ((Keyboard or Mouse) && !IsInternalVirtual). Consumption suppresses the source at the raw/descriptor layer, which only exists for a real Windows HID device. |
IsInternalVirtual |
bool |
- |
Computed: true for PadForge-internal virtual sources, identified by a URI-scheme DevicePath instead of a real Windows HID path. Twenty schemes: web://, overlay://, midi://, peer://, nfc://, mic://, handheld://, sensor://, headtrack://, openxr://, logigkeys://, keyboardscreen://, falcon://, telemetry://, analogkb://, razerchroma://, logilightsync://, steelseriesgg://, razersensa://, lightunit://. Those cover web controllers, the touchpad overlay, MIDI-input devices, Remote Link peers, PC/SC readers, WASAPI microphone endpoints, the handheld hidden-buttons row, the machine's own sensor row, the head tracker, the OpenXR hand-controller rows, the Logitech G-Keys row, the keyboard screen rows (#519), the Novint Falcon rows (#521), the Game Telemetry row (#531), the analog keyboard rows, the Razer Chroma, Logitech LIGHTSYNC, SteelSeries GG and Razer Sensa rows, and the unit rows of lit devices no mouse or keyboard row reaches (#494). DeviceDetailDividerTests reads every scheme the source assigns and checks each one here. HidHide cannot blacklist a non-HID path, so the "Hide from games" toggle hides itself for these. |
IsAggregate |
bool |
- |
Computed: an aggregate:// merged row (All Keyboards, All Mice, All Touchpads, All Consumer Controls). It has no HID instance of its own. |
ShowHidHideToggle |
bool |
- |
Computed: !IsInternalVirtual && !IsAggregate. Gates the "Hide from games" checkbox. |
ShowInputHidingSection |
bool |
- |
Computed: ShowHidHideToggle \|\| ShowConsumeToggle. Drives the Input Hiding section visibility, heading included. |
ShowInputModeSection |
bool |
- |
Computed: _forceRawJoystickMode \|\| (IsGamepad && !IsInternalVirtual). Drives the Input Mode (Force Raw Joystick) section visibility. A row whose flag is on shows the section whatever its type or path, so the checkbox can turn it off. |
ShowInputModeOrHidingSection |
bool |
- |
Computed: union of the two above. Gates the separator between Slot Assignment and the hiding/mode sections so a virtual device doesn't leave a dangling divider. |
ShowRawInputDivider |
bool |
- |
Computed: ShowPowerSection \|\| ShowGunCalibration \|\| !ShowInputModeOrHidingSection. Two dividers bracket the Power section, so exactly one of them has to survive whatever the device has. Left unconditional, the lower one doubled up on any device with an Input Mode or Hiding section but no Power section. Made to follow the Power section alone, an NFC reader or a microphone would get no rule at all. |
Device Path
| Property |
Type |
Description |
DevicePath |
string |
Device path (diagnostics). Notifies DossierConnectionPath and the IsInternalVirtual family. |
HidHideInstancePath |
string |
Resolved HID instance path for HidHide blacklisting. Notifies DossierConnectionPath. |
DossierConnectionPath |
string |
Computed: the connection path the dossier shows when no HidHide instance path occupies the PATH row. Covers bridged devices like the DS3, whose real transport path (BthPS3 PDO / WinUSB interface) arrives through SdlDeviceWrapper.ExternalDevicePathProvider even though SDL reports no path. Empty for internal virtual sources and when a HidHide path is already shown. |
Display Helpers
| Property |
Type |
Description |
IsGamepad |
bool |
Computed: true if DeviceTypeKey == "Gamepad". |
ShowSubmitMapping |
bool |
Computed: the device can have a community mapping submitted. True for everything except "Gamepad", "Mouse", "Keyboard", "Touchpad", "Tablet", "Midi", "Nfc", "HeadsetMotion", "Microphone", "ConsumerControl", "HandheldButtons", "SystemMotion", "HeadTracker", "VrController", "LogitechGKeys", "AnalogKeyboard", "WebMenus", "PeripheralLighting", "PeripheralHaptics", "KeyboardScreenKeys", "NovintFalcon", and "GameTelemetry". |
| Method |
Description |
NotifyDisplayChanged() |
Refreshes computed properties (StatusText, CapabilitiesSummary, VendorIdHex, ProductIdHex, HasVidPid). |
SlotBadge
File: DeviceRowViewModel.cs
Slot assignment badge (icon + number).
| Property |
Type |
Description |
SlotIndex |
int |
Zero-based pad slot index. |
SlotNumber |
int |
Sequential global number (1-based among created slots). |
VcType |
VirtualControllerType |
The slot's output type, which picks the badge art below. |
TypeGeometry / TypeGlyph |
Geometry / string |
Branded icon path, or the Segoe glyph for the types with no path (SlotTypeIconMap). |
PadViewModel
Files: PadViewModel.cs, PadViewModel.Touchpad.cs (partial added in v3.3 for Touchpad-tab properties), PadViewModel.Mouse.cs (partial added for the #200 mouse-gesture tab), PadViewModel.AudioDsp.cs (partial added in 4.3.2 for the #347 Audio DSP chain)
DataContext for: Pad page (one per virtual controller slot)
The largest ViewModel. One per virtual controller slot (16 total). Handles output type, multi-device selection, mapping grid, deadzone/sensitivity, macros, force feedback, Touchpad-tab settings, and live state display.
Identity and Type
| Property |
Type |
Default |
Description |
PadIndex |
int |
- |
Zero-based slot index (0–15). Read-only. |
SlotNumber |
int |
PadIndex + 1 |
1-based number among active slots. |
SlotLabel |
string |
- |
Display label (e.g., "Virtual Controller 1"). |
OutputType |
VirtualControllerType |
Xbox |
Output type. Resets deadzones, rebuilds mappings/stick/trigger configs, syncs macro button style. |
TypeInstanceLabel |
string |
"1" |
Per-type instance label. Set by RefreshNavControllerItems. |
OutputTypeIndex |
int |
- |
ComboBox SelectedIndex, a plain cast over OutputType: 0=Xbox, 1=PlayStation, 2=Extended, 3=Midi, 4=KeyboardMouse, 5=Nintendo, 6=Vr. The setter ignores any value Enum.IsDefined rejects. |
OutputTypeDisplayName |
string |
- |
Computed: localized type name for the tier-1 header. Re-raised on OutputType and culture changes. |
ConfigItemDirtyCallback |
Action |
null |
Called on config item change. Wired to SettingsService.MarkDirty(). |
HIDMaestro Profile
The slot's HID descriptor. Picking a different profile rewires the slot, so the setter does the work of a small migration before anything rebinds.
| Property |
Type |
Description |
ProfileId |
string |
HIDMaestro profile slug ("xbox-360-wired", "dualsense", "logitech-g920"). Null or empty falls back to the active category's default profile in CreateVirtualController. |
AvailableProfiles |
IReadOnlyList<HMProfile> |
Computed: the catalog list for the current category (Xbox / PlayStation / Nintendo / Extended). Empty for MIDI, Keyboard+Mouse, and VR slots, which use no HIDMaestro profile. |
HasHMaestroProfileBar |
bool |
Computed: the slot's category uses an HIDMaestro profile. Gates the profile picker bar on the pad page. True for Xbox, PlayStation, Nintendo, and Extended. |
The ProfileId setter, in order. Steps 1 and 2 run on a live re-target only: the setter reads SettingsManager.GetWireStamp before translating, and restore, apply, and import paths stamp the incoming wire first, so they no-op through both. An unknown stamp adopts the incoming profile and moves nothing, since the data was saved with it, so the next change on the slot reads as the live change it is. The OutputType setter stamps the new category's default before it assigns the profile, so a type change never reads the incoming set as a re-target from the outgoing wire.
- Translates existing raw bindings by role, with the SOCD pairs and each device's per-mapping deadzone and Bidirectional entries, on a Nintendo slot, or on any slot where both the outgoing and incoming wires are lettered (
NintendoPreviewMap.IsLettered). Every other live re-target only re-stamps the wire (StampNintendoWire). Raw targets are wire-relative and the lettered families share almost no indices, so without the translation every binding keeps its index and silently changes meaning.
- On an Extended or Nintendo slot, fills empty auto-mappings for what the new profile added (
DeviceService.FillEmptyAutoMappingsForSlot, then SettingsService.RefreshMappingSetsFromLegacy), merged into the slot's MappingSet before the rebuild, since the grid rebuilds from the set and the save pipeline regenerates settings from the grid. Fill is additive, so a binding the user authored or deliberately cleared survives.
- Reseeds
ExtendedConfig from the profile (SyncExtendedConfigFromProfile): ThumbstickCount and TriggerCount from HMProfile.StickCount / TriggerCount, PovCount from HasHat, ButtonCount from HMProfile.ButtonCount, or on a Nintendo lettered profile from its own lettered count. That lettered count is authoritative rather than a Min against the SDK-reported one: taking a Min let a low reported count truncate the surface, which is how Capture, GR, GL, and C went missing from the Switch 2 Pro grid. A Valve lettered profile takes all four counts from NintendoPreviewMap instead, because those descriptors declare almost nothing and the real shape lives in the extended report. Picking the synthetic Custom entry also forces ExtendedConfig.Customize on.
- Rebuilds mappings, stick configs, and trigger configs, re-derives macro and menu button lettering (
SyncMacroButtonStyle), and re-gates the Bass Shakers tab.
Xbox and PlayStation slots have fixed layouts, so they skip the reseed. They rebuild mappings and re-derive the macro and menu lettering (SyncMacroButtonStyle, which also stamps each macro's CarriesPressure, #530). On a PlayStation slot, a live preset change first fills, from the slot's pads, what the new preset's default maps and the outgoing one's does not (DeviceService.FillEmptyPresetMappingsForSlot): the Full preset's pressure rows, and the touchpad, Mic Mute and Edge rows a DualShock 3 preset withheld. A field the outgoing default mapped and the user cleared on that wire stays cleared. Profile-gated rows exist inside those fixed layouts too (Xbox Series adds Share, the DualSense family adds Mic Mute, the Edge adds its paddle and Fn pairs, the Full preset adds the ten pressure rows, and both DualShock 3 presets drop the touchpad rows and label Back and Start as Select and Start), so a profile change within the category still has to rebuild the row list. The lettering follows for the same reason: MacroButtonNames.DeriveStyle returns DualShock3 for a PlayStation slot on either DualShock 3 preset (HMaestroProfileCatalog.IsDualShock3).
Extended Configuration
| Property |
Type |
Description |
ExtendedConfig |
ExtendedSlotConfig |
Per-slot Extended config (preset, axis/button/POV counts). Drives the HID descriptor handed to HIDMaestro for Extended slots. Only meaningful when OutputType == Extended. Subscribes to PropertyChanged for dynamic rebuilds. |
MIDI Configuration
| Property |
Type |
Description |
MidiConfig |
MidiSlotConfig |
Per-slot MIDI config (channel, CC/note mappings). Only meaningful when OutputType == Midi. |
IsLegacyMidi |
bool |
True while the legacy MIDI API runs MIDI slots. Shows the MIDI bar's Output Port picker. MainWindow sets it on every pad from the Settings card's refresh. |
Per-(Slot, Device) Lighting / Adaptive Trigger Configuration
Drives the Adaptive Triggers and Lighting tabs. Keyed per physical device, so two devices on one slot each carry their own lighting/trigger config. A lit mouse, keyboard, unit row or vendor row (#494) gets its own config here too, whose PeripheralLightingEnabled decides whether this slot lights a mouse or keyboard at all and whose PeripheralClaimLevel says how firmly. See DeviceSlotConfig.
| Property |
Type |
Description |
DeviceConfig |
DeviceSlotConfig |
The Adaptive Triggers / Lighting tab's currently-bound config, following SelectedMappedDevice. The setter re-attaches its PropertyChanged forwarder (ActiveDeviceConfigPropertyChanged) on swap. |
PerDeviceSlotConfigs |
IReadOnlyDictionary<Guid, DeviceSlotConfig> |
Per-device configs keyed by InstanceGuid. The empty-Guid key holds a fallback used before any device is mapped. |
| Method |
Description |
GetOrCreateDeviceConfig(Guid) |
Returns the per-device config, creating a fresh default entry if none exists. Empty Guid returns the anchor config. |
EnumerateDeviceSlotConfigs() |
Snapshot of every per-device config on the slot. Used by macro fan-out. |
EnsureDeviceSlotConfigsForMappedDevices() |
Adds a default config entry for every currently-mapped device. |
Three lines name a DSX game mod (#529) while it holds something on the slot. InputService.SetGameModPadLines sets them, and each is empty otherwise:
| Property |
Type |
Shown on |
GameModTriggersLine |
string |
The Adaptive Triggers tab, while the mod holds a trigger program |
GameModImpulseLine |
string |
The Impulse Triggers tab, while the mod holds a trigger program |
GameModLightsLine |
string |
The Lighting tab, while the mod holds the lightbar, the player lights or the mute light |
Keyboard + Mouse Configuration (#205)
| Property |
Type |
Description |
KbmConfig |
KbmSlotConfig |
Per-slot surface mode and SOCD / Snap Tap config. Always present, only meaningful when OutputType == KeyboardMouse. See KbmSlotConfig. |
KbmKeyboardDevice / KbmMouseDevice |
string |
Forwarders over KbmConfig.KeyboardDevice / MouseDevice for the device bar's pickers, each with a reset button. A null write is ignored, and a change calls NotifyKbmSurfacesChanged(), which re-reads the pickers, their visibility, the capability tags and the mapping table's rows. |
KbmKeyboardDeviceVisible / KbmMouseDeviceVisible |
bool |
A picker shows while the surface mode drives its half. |
KbmKeyboardCapabilities / KbmMouseCapabilities |
IReadOnlyList<string> |
The device bar's tags for the picked model: Keys at Once (or Any), Media Keys and Lock Lights for a keyboard, and Buttons, Wheel and Tilt Wheel for a mouse. Empty on Windows input. |
KbmKeyboardLeds |
int |
The lock lights Windows last set on the slot's virtual keyboard (bit 0 Num Lock, 1 Caps Lock, 2 Scroll Lock), -1 without one. The poll fills it beside KbmOutputSnapshot, and the preview reads it per frame. |
KbmSurfaces |
string |
Forwarder over KbmConfig.Surfaces for the surface dropdown (#408). A null or empty write is ignored. NotifyKbmSurfacesChanged() re-reads it after a load, paste, or profile apply and re-filters the mapping table. |
MappingSurfaceScope |
string |
The mapping table's keyboard / mouse scope: All, Keyboard, or Mouse. Session state. Visible only through KbmSurfaceScopeVisible (a Keyboard + Mouse slot in Both mode), and reset to All when the slot type changes. |
Multi-Device Selection
| Property |
Type |
Description |
MappedDevices |
ObservableCollection<MappedDeviceInfo> |
Physical devices mapped to this slot. |
SelectedMappedDevice |
MappedDeviceInfo |
Selected device for configuration. Raises SelectedDeviceChanged, notifies HasSelectedDevice. |
HasSelectedDevice |
bool |
Computed: SelectedMappedDevice != null. |
MappedDeviceName |
string |
Mapped device name. Default: "No device mapped". |
MappedDeviceGuid |
Guid |
Mapped device GUID. |
IsDeviceOnline |
bool |
Mapped device is online. |
| Event |
Args |
Description |
SelectedDeviceChanged |
MappedDeviceInfo |
Different device selected. InputService reloads PadSetting. |
MappedDeviceInfo (nested class)
| Property |
Type |
Description |
Name |
string |
Device display name. |
InstanceGuid |
Guid |
Device instance GUID. |
IsOnline |
bool |
Device connected. Refreshed every dashboard tick so an offline roster line dims instead of rendering cold (#175 phase 2 item 14). |
BatteryText |
string |
"78%" while the device reports a battery, else empty (#167). Shown per item in the Pad-page device dropdown. |
BatteryGlyph |
string |
Segoe MDL2 battery glyph for the crucible card roster (#175). Same bucketing as DeviceRowViewModel.BatteryGlyph. Notifies HasBattery. |
HasBattery |
bool |
Computed: BatteryGlyph is non-empty. |
TransportGlyph |
string |
Segoe MDL2 transport glyph for the roster (#175): Bluetooth links carry E702, wired stays unmarked. Notifies HasTransportGlyph. |
HasTransportGlyph |
bool |
Computed: TransportGlyph is non-empty. |
TypeGlyph |
string |
Segoe MDL2 device-class glyph (#175), sourced from DeviceTypeGlyph.For. Defaults to the controller glyph (U+E7FC) until InputService resolves the device. |
Assignment Offer (Settings > Assignment Prompts)
The pad page's assign-offer banner. InputService writes it when a device connects while this slot's page is open and one of the two Assignment Prompts settings applies. UI-only state, never persisted.
| Member |
Type |
Description |
AssignOfferGuid |
Guid |
The device on offer, or Guid.Empty. |
HasAssignOffer |
bool |
Computed: AssignOfferGuid != Guid.Empty. |
AssignOfferText |
string |
The banner sentence (Pad_AssignOffer_Format: "{0} just connected. Assign it to this virtual controller?"). |
SetAssignOffer(Guid, string) / ClearAssignOffer() |
methods |
Raise or clear the offer. An empty guid clears it. |
AcceptAssignOfferCommand |
RelayCommand |
Clears the offer and raises AssignOfferAccepted with the guid. MainWindow routes it to DeviceService.AssignDeviceToSlot, the drag-and-drop entry point. |
DismissAssignOfferCommand |
RelayCommand |
Not Now. Clears the offer and raises AssignOfferDismissed, so InputService skips that device for this slot for the rest of the session. |
Combined slot output values, updated at 30 Hz by UpdateFromEngineState(). Bound to the 2D/3D controller visualizer. On a slot whose preview rides the raw surface, UpdateNintendoPreviewFromRaw owns the button and stick properties instead, and UpdateFromEngineState writes only the vibration bars.
Buttons (all bool):
ButtonA, ButtonB, ButtonX, ButtonY, LeftShoulder, RightShoulder, ButtonBack, ButtonStart, LeftThumbButton, RightThumbButton, ButtonGuide, DPadUp, DPadDown, DPadLeft, DPadRight
Family extras on the same lane, each driving the 2D overlay and 3D mesh accent for the pads that carry the button: ButtonShare (Xbox Series), ButtonMute (DualSense mic mute), LeftFunction / RightFunction (DualSense Edge Fn), ButtonC / LeftPaddle / RightPaddle (Switch 2 Pro C, GL and GR, with UpdateFromEngineState writing the paddle pair on PlayStation slots too, so both surfaces share it), and the Valve set ButtonQuickAccess, Paddle1-Paddle4 (translator handedness: R4, L4, R5, L5), LeftGrip / RightGrip (the 2015 pad's rear grips), and LeftTouchpadClick / RightTouchpadClick.
On a Nintendo or Valve slot these come from UpdateNintendoPreviewFromRaw, which walks Models2D.NintendoPreviewMap.ButtonTable(ProfileId) and assigns by name. It is table-driven off the same wire table the mapping grid and the click-to-record path use, so the two directions cannot disagree. The hardcoded index list it replaced was the original Pro Controller's, and it lit the wrong art for eleven of the Switch 2 Pro's twenty-one buttons. Two details fall out of the table: on the 2015 Steam Controller the right pad click is also the right stick button, so RightThumbButton follows RightTouchpadClick, and a profile that spends real buttons on the D-pad (Switch 2 Pro) skips the hat read entirely.
Axes (combined slot values):
| Property |
Type |
Description |
LeftTrigger |
double |
Left trigger (0.0–1.0). |
RightTrigger |
double |
Right trigger (0.0–1.0). |
ThumbLX |
double |
Left stick X (0.0–1.0, 0.5 = center). |
ThumbLY |
double |
Left stick Y (0.0–1.0, 0.5 = center, Y inverted). |
ThumbRX |
double |
Right stick X (0.0–1.0, 0.5 = center). |
ThumbRY |
double |
Right stick Y (0.0–1.0, 0.5 = center, Y inverted). |
Raw values:
| Property |
Type |
Description |
RawThumbLX |
short |
Raw left stick X (−32768 to 32767). |
RawThumbLY |
short |
Raw left stick Y. |
RawThumbRX |
short |
Raw right stick X. |
RawThumbRY |
short |
Raw right stick Y. |
RawLeftTrigger |
ushort |
Raw left trigger (0–65535). |
RawRightTrigger |
ushort |
Raw right trigger (0–65535). |
Per-device values (selected device only, for stick/trigger tab previews):
| Property |
Type |
Description |
DeviceThumbLX |
double |
Selected device left stick X (0.0–1.0). |
DeviceThumbLY |
double |
Selected device left stick Y. |
DeviceThumbRX |
double |
Selected device right stick X. |
DeviceThumbRY |
double |
Selected device right stick Y. |
DeviceRawThumbLX |
short |
Selected device raw left stick X. |
DeviceRawThumbLY |
short |
Selected device raw left stick Y. |
DeviceRawThumbRX |
short |
Selected device raw right stick X. |
DeviceRawThumbRY |
short |
Selected device raw right stick Y. |
DeviceLeftTrigger |
double |
Selected device left trigger (0.0–1.0). |
DeviceRightTrigger |
double |
Selected device right trigger. |
DeviceRawLeftTrigger |
ushort |
Selected device raw left trigger. |
DeviceRawRightTrigger |
ushort |
Selected device raw right trigger. |
Mapping Rows
| Property |
Type |
Description |
Mappings |
ObservableCollection<MappingItem> |
Rows linking physical inputs to output targets. Rebuilt by RebuildMappings(). |
ActiveLayerMask |
string |
The layer being authored on the Mappings tab (the selected layer tab). Defaults to Base. A change raises LayerChanging first, then LayerActivated, so the grid reloads that layer's rows. |
LayerTabs |
ObservableCollection<ShiftLayerInfo> |
Tab strip entries above the mapping grid, one per layer (Base + each shift layer). Populated by RebuildLayerTabs from the slot's ShiftActivators. |
HasShiftLayers |
bool |
True when at least one shift activator is authored (i.e. at least one tab beyond Base). |
| Event |
Description |
MappingsRebuilt |
Raised after RebuildMappings() completes so InputService can reload descriptors. |
LayerChanging |
Raised before ActiveLayerMask changes, while the grid still belongs to the old layer. |
LayerActivated |
Raised when ActiveLayerMask changes. Subscribers reload per-row source data so the DataGrid reflects the active layer's rows. |
The slot's MappingSet (multi-source rows, shift layers and their activators, plus the menus, SOCD, Bass Shakers, and Keep Controller Awake configs) is not a PadViewModel property. It lives in SettingsManager.SlotMappingSets[PadIndex], and the view model reads it through a private accessor. See Button and Axis Mappings and Shift Layers.
Slot-Level Picker Lists and Mapping Picker Filter (#322, discussion #302)
Slot-wide cross-device choice lists, plus the one search box and device-visibility set applied as the filter of the shared views every picker on the slot binds. ApplyMappingPickerFilter filters all four lists, and RebuildPickerDeviceFilterEntries builds the popup from the device groups of the first three, so a device the Motion rows alone offer still has a checkbox. The key list holds no device the full list lacks. A view filter changes what a dropdown offers, never what a row's binding holds, so saved selections survive filtering.
| Property |
Type |
Description |
SlotAvailableInputs |
ObservableCollection<InputChoice> |
Slot-level cross-device choice list, so the Gyro tab's Aim Engage picker and any other slot-wide picker binds without proxy-walking Mappings. Populated by InputService.PopulateAvailableInputs. Every choice except the four bundled motion sources (#475). |
SlotMotionGyroInputs |
ObservableCollection<InputChoice> |
(#475) The Motion Gyro row's choices: Motion Gyro and Motion Gyro L per device, the one kind that row reads. |
SlotMotionAccelInputs |
ObservableCollection<InputChoice> |
(#475) The Motion Accelerometer row's choices: Motion Accel and Motion Accel L per device. |
SlotKeyInputs |
ObservableCollection<InputChoice> |
The Up and Down pickers' list: the entries of SlotAvailableInputs an Incremental or Ramp source reads as a key (SourceKindRuntime.ReadsAsKey), as the same instances. A stick, a trigger or another axis is in the full list and not in this one. |
SlotAvailableInputsView |
ICollectionView |
Grouped view over the list, keyed on InputChoice.DeviceLabel for the picker's GroupStyle header. |
SlotMacroTriggerChoices |
ObservableCollection<InputChoice> |
The subset of SlotAvailableInputs that converts to a MacroItem.TriggerInputEntry (raw buttons, POV directions, gamepad-layout axes, the touchpad click and finger reads, gestures, gyro axes, stick rings, and the on/off sources: capsense, NFC tags, voice phrases, analog keys, menu cells, MIDI notes, CCs and encoder detents, and IR Offscreen), for the macro trigger dropdown (#177). |
SlotMacroTriggerChoicesView |
ICollectionView |
Grouped view over the macro-trigger list, same DeviceLabel grouping. |
MappingInputSearch |
string |
Find-as-you-type over the picker list. Session state, never persisted. Setting it re-applies the filter. |
PickerDeviceFilterEntries |
ObservableCollection<PickerDeviceFilterEntry> |
The device-filter popup's rows, one per device group currently in the picker. |
MappingPickerFilterActive |
bool |
Computed: any hidden device, a non-empty search, or a MappingSurfaceScope other than All. Lets the funnel button read as engaged. |
| Method / Event |
Description |
RebuildPickerDeviceFilterEntries() |
Rebuilds the popup rows from the shared list's current device groups, preserving each group's shown / hidden state. Called after every picker repopulation. |
SetHiddenPickerDeviceKeys(IEnumerable<string>) |
Restores the persisted hidden set on settings load. |
GetHiddenPickerDeviceKeysJoined() |
The persisted form: hidden keys, semicolon-joined. Stored per slot in the settings root, not in profiles. |
PickerDeviceFilterChanged |
Raised when the hidden set changes so the settings service can mark dirty and persist. |
The search filters both sides: the dropdown's offered choices, and the mapping grid's own rows. A row matches when its target label or its selected source's display name carries the text. Rows are outputs, so the device-visibility set never hides them, only typed text does. On a Keyboard + Mouse slot the row filter also drops rows of a half the surface mode turns off, then applies MappingSurfaceScope (#408).
PickerDeviceFilterEntry is one popup row: Key (device guid, or "any" for the device-agnostic group), Label, and a two-way IsShown whose setter toggles the owner's hidden set.
Per-Device Test Routing (3.2)
Not a ViewModel property. InputManager.TestRumbleTargetGuid is a Guid[MaxPads] array holding, per slot, the InstanceGuid of the device currently visible in the per-device tabs (Impulse Triggers, Force Feedback, Adaptive Triggers, Lighting). InputService's test-send methods (SendTestImpulseTrigger and its siblings) set the slot's entry to the device GUID the pad page passed, and clear it back to Guid.Empty when the pulse ends. Test / Probe actions on those tabs gate on target == Empty || ud.InstanceGuid == target so only the selected physical pad fires, and UserEffectsDispatcher.TestRumbleTargetGuidProvider carries the same gate into the dispatcher branches that emit physical effects from those tabs (for example auto-routed impulse-to-AT vibration).
Touchpad State (3.2 canonical slot)
| Property |
Type |
Description |
TouchpadClickPressed |
bool |
True while the touchpad-click button is held (state.Buttons[16] = SDL_GAMEPAD_BUTTON_TOUCHPAD upstream). Drives the 2D/3D model click highlight and the per-press lightbar overlay synth. |
TouchpadFinger0X / TouchpadFinger0Y / TouchpadFinger0Down |
float / float / bool |
First finger position (0..1, normalized to pad width / height) and contact state on the source touchpad. |
TouchpadFinger1X / TouchpadFinger1Y / TouchpadFinger1Down |
float / float / bool |
Second finger position and contact state. |
All seven are written by UpdateFromTouchpadState(in TouchpadState), which mirrors the per-slot combined touchpad state so the 2D / 3D / web previews can render finger dots and the click highlight. Only meaningful on PlayStation slots, harmless elsewhere.
| Method |
Description |
RebuildMappings() |
Rebuilds Mappings from OutputType and Extended config. Dispatches to type-specific initializers. |
Touchpad Gestures (v3.3)
File: PadViewModel.Touchpad.cs
Per-(active device, pad-index) touchpad-tab settings. Same Load / Sync / guard rhythm as the gyro tuning partial: Load* reads PadSetting.TouchpadSettings[] into VM fields under the _loadingTouchpadGestures guard, setters push back through PushIfNotLoading, and InputService calls the sync method on the live polling rhythm so the gesture engine sees changes at once. Setter clamps in parentheses below.
Active pad pivot (devices with two pads, Steam Controller / Deck):
| Property |
Type |
Default |
Description |
SelectedTouchpadIndex |
int |
0 |
Which pad on the active device the tab edits (0..MaxTouchpadIndex-1). Changing it reloads VM fields from the matching TouchpadSettings entry. |
MaxTouchpadIndex |
int |
1 |
Touchpad count on the active device (0 when none). Private setter. |
HasMultipleTouchpads |
bool |
- |
Computed: MaxTouchpadIndex > 1. Shows the pad pivot. |
TouchpadIndexOptions |
IEnumerable<int> |
- |
0..MaxTouchpadIndex-1 for the pivot ComboBox. |
Detection card:
| Property |
Type |
Default |
Description |
TouchpadGesturesEnabled |
bool |
false |
Master switch for the tab. |
TouchpadGestureMode |
string |
"Both" |
"InBoxOnly" / "CustomOnly" / "Both". |
TouchpadInBoxSectionEnabled |
bool |
- |
Computed: master on and mode isn't CustomOnly. Grays the In-Box card otherwise (#177/#178). |
TouchpadCustomSectionEnabled |
bool |
- |
Computed: master on and mode isn't InBoxOnly. |
TouchpadCooldownMs |
int |
100 |
Minimum gap between recognized gestures (0–5000). |
In-box gestures card:
| Property |
Type |
Default |
Description |
TouchpadSwipeDistanceThreshold |
double |
0.15 |
Swipe distance, fraction of pad (0.01–1.0). |
TouchpadSwipeTimeWindowMs |
int |
500 |
Swipe time window (50–5000). |
TouchpadEnableFourWaySwipes |
bool |
false |
4-direction swipes. |
TouchpadEnableEightWaySwipes |
bool |
false |
8-direction swipes. |
TouchpadEnableRadialZones |
bool |
false |
Radial-menu zones. |
TouchpadRadialZoneCount |
int |
8 |
Zone count, snapped to one of 4 / 6 / 8 / 12. |
TouchpadRadialZoneCountOptions |
IReadOnlyList<int> |
{4,6,8,12} |
Static dropdown choices. |
TouchpadRadialCenterDeadzone |
double |
0.30 |
Center dead radius (0.0–0.9). |
TouchpadEnableTouchSpots |
bool |
false |
Fixed touch-spot regions. |
TouchpadEnableTaps |
bool |
false |
Tap / multi-tap recognition. |
TouchpadTapTimeWindowMs |
int |
350 |
Single-tap window (30–1000). |
TouchpadMultiTapGapMs |
int |
300 |
Gap between taps in a multi-tap (50–2000). |
TouchpadEnableLongPress |
bool |
false |
Long-press recognition. |
TouchpadLongPressTimeWindowMs |
int |
500 |
Long-press hold time (100–5000). |
TouchpadEnableTwoFingerSwipes |
bool |
false |
Two-finger swipes. |
TouchpadEnablePinchSpread |
bool |
false |
Pinch / spread. |
TouchpadEnableRotate |
bool |
false |
Two-finger rotate. |
TouchpadEnableThreeFingerGestures |
bool |
false |
Three-finger gestures. |
TouchpadEnableFourFingerGestures |
bool |
false |
Four-finger gestures. |
TouchpadEnableFiveFingerGestures |
bool |
false |
Five-finger gestures. |
TouchpadEnableShapeGestures |
bool |
false |
Shape-recognizer gestures. |
TouchpadGestureMatchThreshold |
double |
3.0 |
Shape-recognizer match threshold, lower = stricter (0.1–10.0). $Q scale. |
Joystick / D-pad output card:
| Property |
Type |
Default |
Description |
TouchpadEnableJoystickOutput |
bool |
false |
Drive a stick / D-pad from finger position. |
TouchpadJoystickMaxRadius |
double |
0.30 |
Finger travel mapped to full deflection (0.05–0.5). |
TouchpadJoystickInnerDeadzone |
double |
0.02 |
Center dead radius (0.0–0.10). |
TouchpadJoystickDPadMode |
string |
"FourWay" |
"Off" / "FourWay" / "EightWay". |
TouchpadJoystickDPadActivationThreshold |
double |
0.15 |
Travel before a D-pad direction fires (0.05–0.5). |
Mouse output card (touchpad-as-mouse tuning):
| Property |
Type |
Default |
Description |
TouchpadMouseSensitivityX |
double |
1.0 |
X sensitivity (0.05–10.0). |
TouchpadMouseSensitivityY |
double |
1.0 |
Y sensitivity (0.05–10.0). |
TouchpadMouseInvertX |
bool |
false |
Invert X. |
TouchpadMouseInvertY |
bool |
false |
Invert Y. |
TouchpadMouseMomentum |
bool |
false |
Keep the cursor gliding after the finger lifts. |
TouchpadMouseMomentumDecay |
double |
0.90 |
Per-tick momentum decay (0.80–1.00). Higher glides longer. |
TouchpadMomentumMaxSpeed |
double |
0 |
Fling launch ceiling in pad widths per second (0–30). 0 = no ceiling. |
TouchpadMomentumMinLift |
double |
0.286 |
Lift-velocity gate below which no fling starts (0–2). The old hardcoded value. |
TouchpadMomentumFlingGain |
double |
1.0 |
Launch-speed multiplier (0.1–5.0). Leaves drag speed alone. |
TouchpadMomentumStacking |
bool |
false |
Whether a new fling adds to an in-flight coast instead of replacing it. |
TouchpadPointerResponse |
string |
"Simple" |
"Simple" (flat multiplier) or "Trackpad" (the libinput pointer-acceleration port). Notifies TouchpadPointerResponseIsSimple / TouchpadPointerResponseIsTrackpad, which gate the two halves of the card. |
TouchpadTrackpadThreshold |
double |
130 |
Trackpad-curve speed threshold in mm/s (20–600). |
TouchpadTrackpadPadWidthMm |
double |
69 |
Physical pad width in mm the curve normalizes against (20–150). |
TouchpadMouseAcceleration |
double |
0 |
Simple-response acceleration exponent (0.0–5.0). 0 is linear. |
TouchpadMouseJitterReduction |
bool |
true |
Suppress sub-pixel jitter at rest. |
Recognizer tolerances:
| Property |
Type |
Default |
Description |
TouchpadTapMaxMotion |
double |
0.04 |
Travel a contact may drift and still count as a tap, as a fraction of the pad (0.01–0.30). |
TouchpadLongPressMaxMotion |
double |
0.05 |
Same guard for a long press (0.01–0.30). |
TouchpadTwoFingerSwipeAngularTolerance |
double |
25 |
How far apart, in degrees, two fingers may travel and still read as one swipe (5–90). |
TouchpadPinchThreshold |
double |
0.25 |
Spread change that fires a pinch (0.05–1.00). |
TouchpadRotateThresholdDegrees |
double |
20 |
Rotation that fires a two-finger rotate (5–90). |
Pointer region card (#9): the screen rectangle the "Touchpad N Pointer X/Y" absolute cursor sources map onto. The four values are authored together: touching any of them latches the region as user-authored, so a later default sweep leaves it alone.
| Property |
Type |
Default |
Description |
TouchpadPointerRegionSizeX |
double |
1.0 |
Region width as a fraction of the work area (0.05–3.0). 1.0 is Steam's 1:1 pad-to-screen map. |
TouchpadPointerRegionSizeY |
double |
1.0 |
Region height (0.05–3.0). |
TouchpadPointerRegionCenterX |
double |
0.5 |
Region center X in work-area fractions (0.0–1.0). |
TouchpadPointerRegionCenterY |
double |
0.5 |
Region center Y (0.0–1.0). |
Swipe haptics card (#219):
| Property |
Type |
Default |
Description |
TouchpadSwipeHapticsEnabled |
bool |
false |
Short haptic ticks as the finger travels across this pad. |
TouchpadSwipeHapticsIntensity |
double |
0.5 |
Tick intensity (0.0–1.0). |
Custom gestures card:
| Member |
Type |
Description |
CustomTouchpadGestures |
ObservableCollection<TouchpadCustomGestureItem> |
Profile-scoped custom gestures filtered by the active device's class. |
HasNoCustomTouchpadGestures |
bool |
Computed: the collection is empty. Drives the placeholder text. |
RecordTouchpadGestureCommand |
RelayCommand |
Raises RecordTouchpadGestureRequested with the active (device, pad) so the recorder dialog mirrors the right pad. |
DeleteTouchpadGestureCommand |
RelayCommand<TouchpadCustomGestureItem> |
Raises DeleteTouchpadGestureRequested for the row. |
| Event |
Args |
Description |
RecordTouchpadGestureRequested |
RecordTouchpadGestureArgs |
Open the recorder dialog for (device, pad). |
DeleteTouchpadGestureRequested |
TouchpadCustomGestureItem |
Delete a saved custom gesture. |
Reset commands: every setting row carries a Reset<Property>Command that restores its default above, plus per-card ResetTouchpadDetectionCardCommand, ResetTouchpadInBoxCardCommand, ResetTouchpadJoystickCardCommand, ResetTouchpadMouseCardCommand, ResetTouchpadPointerCardCommand, ResetTouchpadSwipeHapticsCardCommand, and ResetTouchpadSyntheticPressureCardCommand. Defaults mirror TouchpadGestureSettings.Default(), so Reset to Defaults round-trips the tab by construction.
| Method |
Description |
LoadTouchpadGestureSettingsForActiveDevice() |
Reads the per-(device, pad) entry into VM fields under the load guard so setters don't ping-pong back. |
SyncTouchpadGestureSettingsToActiveDevice() |
Writes VM fields into the entry, creating it on first touch. No-op while loading, which stops an external PropertyChanged caller from clobbering not-yet-loaded fields. |
RecomputeTouchpadCountForActiveDevice(int) |
Sets MaxTouchpadIndex from the active device. 0 hides the tab. |
RefreshCustomTouchpadGestures(IEnumerable<TouchpadCustomGesture>) |
Repopulates CustomTouchpadGestures after a profile load / switch. Pass null to clear. |
RecordTouchpadGestureArgs (device GUID, name, pad index) and TouchpadCustomGestureItem (display wrapper over a TouchpadCustomGesture with Name, FingerCount, Summary) are declared alongside the partial.
Mouse Gestures (#200)
File: PadViewModel.Mouse.cs
Per-(slot, device) mouse-gesture settings. Twin of the Touchpad partial's Load/Sync/guard pattern. Persistence rides setter-time writes into the live PadSetting plus MainWindow's PropertyChanged hook. The 30 Hz VM flush does not carry these fields.
| Property |
Type |
Default |
Description |
MouseGesturesEnabled |
bool |
false |
Mouse-gesture recognizer armed for the active mouse device. |
MouseGestureButtons |
int |
8 (1 << 3, X1) |
Bitmask of buttons arming the recognizer: bit 0 Left, bit 1 Middle, bit 2 Right, bit 3 X1, bit 4 X2, bit 5 Custom (#216, armed by the recorded cross-device input below instead of a mouse button). Masked to 0x3F. |
MouseGestureButtonLeft / Middle / Right / X1 / X2 / Custom |
bool |
- |
Per-bit wrappers over MouseGestureButtons for the checkboxes. |
MouseGestureFlickThreshold |
int |
150 |
Flick distance threshold in mouse counts. Clamped 10–5000. |
MouseGestureCooldownMs |
int |
100 |
Minimum gap between recognized gestures (ms). Clamped 0–5000. |
Custom activation input (#216). The recorded cross-device descriptor + owning device GUID that arm the Custom gesture session while held. Same pair shape, picker projection, and record flow as the Gyro tab's Aim Engage cluster, scoped per (slot, mouse device) with the rest of the card.
| Member |
Type |
Default |
Description |
MouseGestureCustomEngageButton |
string |
"" |
Recorded input descriptor. |
MouseGestureCustomEngageDeviceGuid |
string |
"" |
Owning device GUID for the recorded input. |
MouseGestureCustomEngageSelectedInput |
InputChoice |
null |
Picker projection over the (descriptor, device) pair. Setting it writes both. OnMouseGestureCustomEngageSelectedInputRefresh() re-raises it after the choice list repopulates. |
MouseGestureCustomEngageRecording |
bool |
false |
Record in progress. Swaps MouseGestureCustomEngageRecordButtonIcon / MouseGestureCustomEngageRecordButtonText. |
MouseGestureCustomEngageRecordCommand |
RelayCommand |
- |
Raises MouseGestureCustomEngageRecordRequested so MainWindow runs the freeform recorder. |
Reset commands: ResetMouseGesturesEnabledCommand, ResetMouseGestureButtonCommand, ResetMouseGestureFlickThresholdCommand, ResetMouseGestureCooldownCommand, ResetMouseGestureCustomEngageCommand (clears the recorded pair), ResetMouseGesturesCardCommand.
| Method |
Description |
LoadMouseGestureSettingsForActiveDevice() |
Loads the active mouse device's gesture settings into the VM. Missing settings load as defaults, so Reset to Defaults resets this tab by construction. |
SyncMouseGestureSettingsToActiveDevice() |
Writes the VM fields into the active mouse device's entry, creating it on first touch. No-op while loading. |
Force Feedback / Rumble
| Property |
Type |
Default |
Range |
Description |
ForceOverallGain |
int |
100 |
0–100 |
Overall FFB gain %. |
LeftMotorStrength |
int |
100 |
0–100 |
Left motor strength %. |
RightMotorStrength |
int |
100 |
0–100 |
Right motor strength %. |
SwapMotors |
bool |
false |
- |
Swap left/right motor assignment. |
TriggerRumbleFold |
bool |
false |
- |
Fold the game's trigger-motor channels into the body motors on devices without trigger motors (#271 item 2). |
LeftMotorDisplay |
double |
0 |
0–1 |
Live left motor level (post-scaling). |
RightMotorDisplay |
double |
0 |
0–1 |
Live right motor level. |
Wheel (per-device):
| Property |
Type |
Default |
Range |
Description |
WheelRotationRangeEnabled |
bool |
true |
- |
Set Rotation Range. False leaves the range to the wheel and its own software, and no range is sent (#512). Persisted in PadSetting.RotationRangeEnabled, reset by its row and by the Wheel card's Reset All. |
WheelRotationRange |
int |
900 |
WheelRangeMinimum–WheelRangeMaximum |
Native wheel rotation range in degrees. The setter clamps to the bounds. |
WheelRangeMinimum |
int |
40 |
- |
The least range the loaded wheel takes. Read-only, set by LoadWheelRotationRange. |
WheelRangeMaximum |
int |
2520 |
- |
The greatest range the loaded wheel takes. Read-only, set by LoadWheelRotationRange. |
WheelAutoCenter |
int |
0 |
0–100 |
Auto-center strength %. 0 is off. |
WheelRpmLeds |
bool |
false |
- |
Drive the wheel's RPM / shift LEDs from game telemetry. |
LoadWheelRotationRange(int degrees, (int Min, int Max)? limits) loads a device's stored range with that device's wheel bounds (WheelRangeLimits.For), or 40–2520 when no native writer drives it (#512). The bounds change only here, with the values they belong to. See Wheel Force Feedback Internals.
Audio Bass Rumble (per-device):
| Property |
Type |
Default |
Range |
Description |
AudioRumbleEnabled |
bool |
false |
- |
Audio-driven rumble from system bass frequencies. |
AudioRumbleSensitivity |
double |
4.0 |
1–20 |
Bass detection sensitivity multiplier. |
AudioRumbleCutoffHz |
double |
80.0 |
20–200 |
Low-pass cutoff (Hz) for bass extraction. |
AudioRumbleLeftMotor |
int |
100 |
0–100 |
Left motor strength for audio rumble. |
AudioRumbleRightMotor |
int |
100 |
0–100 |
Right motor strength for audio rumble. |
AudioRumbleLevelMeter |
double |
0 |
- |
Live audio bass level meter. |
Reset commands: ResetForceAllCommand, ResetOverallGainCommand, ResetLeftMotorCommand, ResetRightMotorCommand, ResetAudioRumbleAllCommand, ResetAudioSensitivityCommand, ResetAudioCutoffCommand, ResetAudioLeftMotorCommand, ResetAudioRightMotorCommand.
Steering Angle Rumble (per-device):
| Property |
Type |
Default |
Range |
Description |
SteeringAngleRumbleEnabled |
bool |
false |
- |
Turns the selected virtual stick axis into continuous rumble. |
SteeringAngleRumbleAxis |
int |
0 |
0–3 |
Left Stick X, Left Stick Y, Right Stick X, Right Stick Y. |
SteeringAngleRumbleMotor |
int |
0 |
0–2 |
Split by Direction, Left Motor, Right Motor (#510). The setter reads any other value as 0, as the engine does, so the picker shows what plays. |
SteeringAngleRumbleStrength |
int |
50 |
0–100 |
Rumble at full travel, %. |
SteeringAngleRumbleDeadzone |
int |
2 |
0–25 |
Travel around center that adds no rumble, %. |
Reset commands: ResetSteeringAngleRumbleCommand (the card's Reset All, also run by ResetForceAllCommand and ResetAllSettings), ResetSteeringAngleRumbleEnabledCommand, ResetSteeringAngleRumbleAxisCommand, ResetSteeringAngleRumbleMotorCommand, ResetSteeringAngleRumbleStrengthCommand, ResetSteeringAngleRumbleDeadzoneCommand.
Constant Force (per-device, FFB-capable + scalar-rumble pads):
| Property |
Type |
Default |
Range |
Description |
ConstantForceEnabled |
bool |
false |
- |
Apply a continuous force vector until toggled off. Resumes after game/macro forces stop. |
ConstantForceX |
double |
0 |
−1.0–1.0 |
X component of the force vector. |
ConstantForceY |
double |
0 |
−1.0–1.0 |
Y component (+ = up in the UI grid). |
Audio DSP Chain (#347)
Audio-tab surface over the per-device crossfeed / EQ / limiter fields on DeviceSlotConfig (see the DeviceSlotConfig section below). Lives in PadViewModel.AudioDsp.cs. The cards are gated on SelectedDeviceHasDspChain.
| Member |
Type |
Description |
SelectedDeviceHasDspChain |
bool (computed, PadViewModel.cs) |
True when the selected device is a Sony pad whose audio rides an AudioPassthroughService sink, the only place the chain runs: DualSense family, Sony's DS4 USB wireless adapter (PID 0BA0), a second-model DualShock 4 (PID 09CC) on a cable, or any DualShock 4 over Bluetooth (AudioPassthroughService.Ds4HasAudio). |
EqBands |
ObservableCollection<EqBandVm> |
Grid rows decoded from DeviceSlotConfig.AudioEqBands. Every row edit re-encodes the whole list back into the config (PushEqBands). |
RefreshEqBands() |
method |
Rebuilds the rows from the selected device's config on every device switch and clears the import status. |
AddEqBandCommand / RemoveEqBandCommand / ClearEqBandsCommand |
RelayCommand (RemoveEqBandCommand takes the EqBandVm row) |
Add a default band, remove one row, or clear the list. Each pushes the encoded list. |
ImportAutoEqFileCommand |
RelayCommand |
Open-file dialog for an AutoEq ParametricEQ.txt / FixedBandEQ.txt and apply its Filter lines plus preamp. |
ImportAutoEqCommand |
RelayCommand |
Same parse over the clipboard text. |
EqImportStatus / HasEqImportStatus |
string / bool |
What the last import did, shown under the buttons. A Graphic EQ file with no Filter lines says so instead of silently doing nothing. |
ResetCrossfeedCommand |
RelayCommand |
AudioCrossfeedLevel back to 0. |
ResetCrossfeedCutCommand / ResetCrossfeedFeedCommand |
RelayCommand |
Custom knobs back to libbs2b's own defaults, 700 Hz and 4.5 dB. |
ResetEqPreampCommand |
RelayCommand |
AudioEqPreampDb back to 0. |
ResetEqCommand |
RelayCommand |
EQ off, preamp 0, bands cleared. |
ResetLimiterCommand / ResetLimiterCeilingCommand |
RelayCommand |
Limiter back on, ceiling back to 98. |
EqBandVm (one editable row, ObservableObject): Enabled, Type (EqBandType: Peaking, LowShelf, HighShelf, HighPass, LowPass, Notch), FrequencyHz (clamped 10 to EqBand.MaxFrequencyHz()), GainDb (clamped −30..30), Q (clamped 0.05..20). Clamps match the engine's so the grid never accepts a band the DSP would silently reject.
Impulse Triggers Tab (3.2)
Per-pad-per-slot per-trigger-motor effects. Tab visible only when the selected device reports HasRumbleTriggers: SDL's SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN or a Microsoft impulse-trigger PID (XboxControllerIdentity.IsImpulseTriggerDevice). Xbox One and later pads.
| Property |
Type |
Default |
Range |
Description |
ImpulseOverallGain |
int |
100 |
0–100 |
Master scale for the trigger-motor pipeline. |
ImpulseLeftStrength |
int |
100 |
0–100 |
Left trigger motor scale. |
ImpulseRightStrength |
int |
100 |
0–100 |
Right trigger motor scale. |
ImpulseSwapTriggers |
bool |
false |
- |
Swap left/right trigger motor assignment. |
AtVibrationToImpulse |
bool |
false |
- |
Render vibration-class DualSense adaptive-trigger programs on the impulse motors (#271 item 3). Resistance-class programs are ignored. |
Constant Trigger Force:
| Property |
Type |
Default |
Range |
Description |
ConstantTriggerForceEnabled |
bool |
false |
- |
Override-with-resume continuous force on the trigger motors. |
ConstantTriggerForceLeft |
double |
0 |
0.0–1.0 |
Left trigger steady force. |
ConstantTriggerForceRight |
double |
0 |
0.0–1.0 |
Right trigger steady force. |
Audio Bass Trigger Rumble:
| Property |
Type |
Default |
Range |
Description |
AudioRumbleTriggersEnabled |
bool |
false |
- |
Drive trigger motors from system audio bass. Runs alongside the body-motor audio rumble. |
AudioRumbleTriggersSensitivity |
double |
4.0 |
1.0–20.0 |
Bass intensity multiplier (trigger channel). |
AudioRumbleTriggersCutoffHz |
double |
80.0 |
20–200 |
Low-pass cutoff (trigger channel). |
AudioRumbleLeftTrigger |
int |
100 |
0–100 |
Audio-driven left-trigger motor scale. |
AudioRumbleRightTrigger |
int |
100 |
0–100 |
Audio-driven right-trigger motor scale. |
AudioRumbleTriggersLevelMeter |
double |
0 |
- |
Live trigger-channel level. |
| Event |
Args |
Description |
TestLeftImpulseTriggerRequested |
EventArgs |
Per-device test pulse on the left trigger motor. |
TestRightImpulseTriggerRequested |
EventArgs |
Per-device test pulse on the right trigger motor. |
Gyro Engage Stick Gate (#120)
Per-direction stick gate for Easy-Aim gyro engagement. Both persist per device and reset to their defaults when the value is empty, so old profiles keep their original right-stick-radial behavior.
| Property |
Type |
Default |
Values |
Description |
GyroEngageStickSide |
string |
"Right" |
Right, Left, Either |
Which stick's deflection drives the Easy-Aim threshold gate. Either uses the larger of the two. Empty collapses to "Right". |
GyroEngageStickDirection |
string |
"Full" |
Full, X, Y, XNeg, XPos, YNeg, YPos |
Which component of the engage stick(s) drives the gate. Full is radial. Empty collapses to "Full". |
| Command |
Description |
ResetGyroEngageStickSideCommand |
Resets GyroEngageStickSide to "Right". |
ResetGyroEngageStickDirectionCommand |
Resets GyroEngageStickDirection to "Full". |
Motion Space and Grip (#392)
Both store the canonical English identifier, not the localized label, so PadForge.xml round-trips across a language change. Both are per (slot, device).
| Property |
Type |
Default |
Values |
Description |
GyroSpace |
string |
"Local" |
Local, Player, World |
Gyro reference frame. Empty collapses to "Local". |
GyroSpaceOptions |
IReadOnlyList<GyroLabeledOption> |
- |
- |
Display-label / stored-value pairs for the space dropdown. |
MotionGrip |
string |
"Pointing" |
Pointing, Sideways, WiiWheel, Upright |
How the controller is held. Empty collapses to "Pointing". |
MotionGripOptions |
IReadOnlyList<GyroLabeledOption> |
- |
- |
Display-label / stored-value pairs for the grip dropdown, in that order. |
IsLoadingPadSetting |
bool |
false |
- |
True while InputService.LoadPadSettingIntoViewModel mirrors a PadSetting into this VM. A plain field with no change notification: it is a scope marker, not a bound property. |
Reset commands: ResetGyroSpaceCommand (back to "Local"), ResetMotionGripCommand (back to "Pointing").
Changing the grip re-references the motion state of the one device the page is editing: its gravity estimate, shake envelope, and lean and tilt neutrals are in the old frame. MainWindow's MotionGrip PropertyChanged handler gates that on InputService.ShouldRecenterOnGripChange, which is !IsLoadingPadSetting. A grip write arriving through a PadSetting load is a mirror of stored state, not a change of hold, so a device selection or a profile switch must not drop every device's estimate on the slot. The recenter itself is InputService.RecenterMotionForSelectedDevice. The Gyro Recenter macro's pad-wide drop is a separate path (InputManager.GyroRecenterApply).
Pitch and Roll Simulation (#474)
Per (slot, device). The card shows only for a DualShock 3 with both sensors.
| Property |
Type |
Default |
Values |
Description |
GyroSimulation |
bool |
false |
- |
Fills pitch and roll from the accelerometer. |
GyroSimulationSmoothingMs |
double |
100 |
20 to 250 |
The smoothing time constant. The setter clamps, and a value that is not finite (NaN or infinity) becomes 100. |
Reset commands: ResetGyroSimulationCommand (off), ResetGyroSimulationSmoothingCommand (back to 100), ResetGyroSimulationCardCommand (both).
Gyro Tilt Envelope (#292)
Per-(slot, device) lens over the "Gyro Tilt X/Y" sources, the same shape as Flick Stick below. The save pipeline stamps these onto every tilt source (ApplyGyroTiltParamsToRow, the Motion Steering push pattern) and persists them in the PadSetting extended-mapping bag under GyroTilt* keys.
| Property |
Type |
Default |
Range |
Description |
GyroTiltRangeDeg |
double |
25 |
1–90 |
Physical tilt in degrees that reaches full deflection. 25 is the modal Steam-corpus maximum. The cap is 90 because the angle comes from an arc sine of one gravity component, so a wider range never reaches full deflection. |
GyroTiltInnerDz |
double |
0 |
0–89 |
Inner tilt deadzone in degrees. |
Reset commands: ResetGyroTiltRangeCommand, ResetGyroTiltInnerCommand, and the card-level ResetGyroTiltAllCommand.
Flick Stick (#225)
Per-(slot, device) lens over the "Flick Stick ..." sources in the slot's KBM mapping set. The save pipeline stamps these onto every flick source (ApplyFlickStickParamsToRow, the Motion Steering push pattern) and persists them in the PadSetting extended-mapping bag (FlickStick* keys). Defaults mirror the MappingSource ParamFlick* defaults (JSM-grounded).
| Property |
Type |
Default |
Range |
Description |
FlickCountsPer360 |
double |
14400 |
100–100000 |
Mouse counts per full 360° camera turn (Steam "Dots Per 360°"). |
FlickTime |
double |
0.1 |
0.01–2.0 |
Flick easing duration in seconds for a full 180° flick. |
FlickThreshold |
double |
0.9 |
0.1–1.0 |
Stick deflection (raw magnitude) at which a flick engages. |
FlickSnapMode |
string |
"None" |
None / Forward / Half / Four / Sixths / Eight |
Snap interval, Tag-backed combo string (the GyroEngageStickSide pattern). Value set = SourceKindRuntime.FlickSnapIntervalRad's. |
FlickSnapStrength |
double |
1.0 |
0.0–1.0 |
Snap lerp strength. 1.0 = full snap to the interval. |
FlickForwardDeadzone |
double |
0 |
0–180 |
Forward angle deadzone in degrees: a flick within this of dead-ahead reads as 0°. |
FlickSmoothing |
double |
-1 |
−1.0 to 0.5 |
Rotation smoothing threshold in rad/tick. Negative = automatic tiered window, 0 = off, positive = explicit lower threshold. |
FlickRotationOffset |
double |
0 |
−180 to 180 |
Steam's flick-stick rotation in degrees: the whole input map turns, so the offset lands on the flick angle before snapping. |
FlickOnEngage |
bool |
false |
- |
Fire a flick immediately when evaluation starts with the stick already past threshold (the shift-layer engage case). Off arms at the current angle and only tracks rotation. |
Reset commands: one per row (ResetFlickCountsPer360Command, ResetFlickTimeCommand, ResetFlickThresholdCommand, ResetFlickSnapModeCommand, ResetFlickSnapStrengthCommand, ResetFlickForwardDeadzoneCommand, ResetFlickSmoothingCommand, ResetFlickRotationOffsetCommand, ResetFlickOnEngageCommand) plus the card-level ResetFlickStickCardCommand.
Deadzone Settings
Left Stick:
| Property |
Type |
Default |
Range |
Description |
LeftDeadZoneShape |
int |
2 (ScaledRadial) |
0–5 |
Deadzone shape index. |
LeftDeadZoneX |
double |
0 |
0–100 |
X deadzone %. |
LeftDeadZoneY |
double |
0 |
0–100 |
Y deadzone %. |
LeftAntiDeadZoneX |
double |
0 |
0–100 |
X anti-deadzone %. |
LeftAntiDeadZoneY |
double |
0 |
0–100 |
Y anti-deadzone %. |
LeftLinear |
double |
0 |
0–100 |
Linear interpolation factor. |
LeftMaxRangeX |
double |
100 |
1–100 |
X max range (positive). |
LeftMaxRangeY |
double |
100 |
1–100 |
Y max range (positive). |
LeftMaxRangeXNeg |
double |
100 |
1–100 |
X max range (negative). |
LeftMaxRangeYNeg |
double |
100 |
1–100 |
Y max range (negative). |
LeftCenterOffsetX |
double |
0 |
−100 to 100 |
X center offset %. |
LeftCenterOffsetY |
double |
0 |
−100 to 100 |
Y center offset %. |
Right Stick: Same pattern with Right prefix: RightDeadZoneShape, RightDeadZoneX, RightDeadZoneY, RightAntiDeadZoneX, RightAntiDeadZoneY, RightLinear, RightMaxRangeX, RightMaxRangeY, RightMaxRangeXNeg, RightMaxRangeYNeg, RightCenterOffsetX, RightCenterOffsetY.
Backward compat shims: LeftDeadZone (get=LeftDeadZoneX, set=both X+Y), RightDeadZone (same pattern).
Speed and momentum mirrors: LeftStickSensitivity / RightStickSensitivity (1.0, clamped 0.1–5.0) carry the flat speed multiplier the KBM pointer sticks use, and KbmMouseMomentum (false) / KbmMouseMomentumGlide (0.90, clamped 0.80–1.00) carry the stick trackball (#291) for the KBM mouse stick. SyncStickItemFromVm pushes them into the matching StickConfigItem.
Sensitivity Curves (serialized control point strings, "x,y;x,y;..." format):
| Property |
Default |
Description |
LeftSensitivityCurveX |
"0,0;1,1" |
Left stick X sensitivity curve. |
LeftSensitivityCurveY |
"0,0;1,1" |
Left stick Y sensitivity curve. |
RightSensitivityCurveX |
"0,0;1,1" |
Right stick X sensitivity curve. |
RightSensitivityCurveY |
"0,0;1,1" |
Right stick Y sensitivity curve. |
LeftTriggerSensitivityCurve |
"0,0;1,1" |
Left trigger sensitivity curve. |
RightTriggerSensitivityCurve |
"0,0;1,1" |
Right trigger sensitivity curve. |
Triggers:
| Property |
Type |
Default |
Range |
Description |
LeftTriggerDeadZone |
double |
0 |
0–100 |
Left trigger deadzone %. |
RightTriggerDeadZone |
double |
0 |
0–100 |
Right trigger deadzone %. |
LeftTriggerAntiDeadZone |
double |
0 |
0–100 |
Left trigger anti-deadzone %. |
RightTriggerAntiDeadZone |
double |
0 |
0–100 |
Right trigger anti-deadzone %. |
LeftTriggerMaxRange |
double |
100 |
1–100 |
Left trigger max range %. |
RightTriggerMaxRange |
double |
100 |
1–100 |
Right trigger max range %. |
Dynamic Stick/Trigger Config Items
Drive the ItemsControl-based Sticks and Triggers tabs. Gamepad presets: 2 sticks, 2 triggers. Custom Extended: N sticks, M triggers. KBM: 2 stick items (Mouse Movement, Scroll Wheel) and no triggers. VR: none of either.
| Property |
Type |
Description |
StickConfigs |
ObservableCollection<StickConfigItem> |
Stick config items. Rebuilt by RebuildStickConfigs(). |
TriggerConfigs |
ObservableCollection<TriggerConfigItem> |
Trigger config items. Rebuilt by RebuildTriggerConfigs(). |
| Method |
Description |
RebuildStickConfigs() |
Rebuilds stick configs for current output type. A VR slot gets none. |
RebuildTriggerConfigs() |
Rebuilds trigger configs. KBM and VR have none. |
SyncStickItemFromVm(StickConfigItem) |
Pushes VM deadzone properties into a stick item. |
SyncTriggerItemFromVm(TriggerConfigItem) |
Pushes VM trigger properties into a trigger item. |
SyncAllConfigItemsFromVm() |
Syncs all items from VM. Called after settings load/paste. |
ResetAllSettings() |
Resets per-slot settings to defaults. Called on slot deletion and by Reset to Defaults. |
Macros
| Property |
Type |
Description |
Macros |
ObservableCollection<MacroItem> |
Macros for this slot. |
SelectedMacro |
MacroItem |
Selected macro. Notifies HasSelectedMacro, refreshes remove command. |
HasSelectedMacro |
bool |
Computed: SelectedMacro != null. |
| Command |
CanExecute |
Description |
AddMacroCommand |
always |
Adds a new macro named "Macro N", with the slot's derived ButtonStyle, RawProfileId and CarriesPressure (#530), and selects it. |
RemoveMacroCommand |
HasSelectedMacro |
Removes the selected macro, then selects the last remaining one. |
DuplicateMacroCommand |
HasSelectedMacro |
Clones the selected macro within the slot (#112) by round-tripping it through the macro DTO, so it is a deep copy and the runtime-only execution state drops away. A macro mid-fire keeps firing. The clone starts cold. |
CopyMacroCommand |
HasSelectedMacro |
Raises CopyMacroRequested. MainWindow owns the clipboard. |
PasteMacroCommand |
always |
Raises PasteMacroRequested. |
CopyMacroFromCommand |
always |
Raises CopyMacroFromRequested: pull macros from another virtual controller, mirroring the Mappings-tab Copy From. MainWindow shows the picker and does the copy. |
Action-type picker. The editor's type dropdown binds MacroTypeCatalog.View, a grouped ICollectionView over MacroTypeCatalog.Choices keyed on MacroTypeChoice.Category. Each MacroTypeChoice carries the MacroActionType, a localized Label, the localized Category header, and an optional Tooltip. The picker was fifty-six flat entries in enum-history order. The catalog orders them by what they act on, under eleven headers: Virtual Buttons, Virtual Axes & Wheel, Keyboard & Text, Mouse, Timing & Flow, Rumble, Lightbar & LEDs, Sound & Volume, Motion & Pointer, Layers & Overlays, and System & Apps. A census test pins that every enum member appears exactly once, so a new action type cannot ship without choosing its shelf. The catalog holds one list instance for its lifetime and refills it in place on Strings.CultureChanged, then refreshes the view: a fresh list per culture would strand the x:Static-bound view on the old one.
Bass Shakers (#236)
The per-slot Bass Shakers tab surface, MappingSet-backed like Menus and persisted through the same dirty callback. RumbleAudioTabVisible gates the tab on slot type: Xbox, PlayStation, and Nintendo always, plus Extended slots whose surface carries force feedback. With Customize on, the ForceFeedbackEnabled checkbox decides. With Customize off, the catalog profile descriptor must carry a PID FFB block, except the steam-deck-composite persona, whose vendor feedback PadForge decodes without one. The card binds RumbleAudioEnabled, RumbleAudioEndpointId (output device picker), RumbleAudioChannelMode (Mono / Controller Stereo), RumbleAudioMasterGain, and RumbleAudioVoices, an ObservableCollection of the four per-channel voice rows (Low Motor, High Motor, Left Trigger, Right Trigger: enable, 20–120 Hz frequency, gain, per-voice Test). The frequency sweep and Stop live beside the voices. Everything writes into MappingSet.RumbleAudio (RumbleAudioConfig), so the routing travels with profiles and Copy / Paste.
Output tab: Slot SOCD (#245) and Keep Controller Awake
Both cards live on the Output tab (index 17). OutputTabVisible is a slot-type gate: every type except MIDI and VR, the two with no output-behavior surface at all.
Controller-button SOCD is distinct from KbmSlotConfig's key SOCD (#205). SocdCardVisible and KbmSocdCardVisible are mutually exclusive by slot type. On a Keyboard + Mouse slot, KbmSocdEditorVisible also hides the key-pair editor while the slot's keyboard half is off (#408). The card binds SocdMode over MappingSet.SocdMode (AvailableSlotSocdModes, in dropdown order: Off / Last Wins / Neutral / First Wins), and SocdPairItems (SlotSocdPairItem) edits the opposing button pairs from SocdButtonOptions. SocdUsesRawIndices says which pair grammar the slot stores: Extended and Nintendo slots write flat raw indices ("12:13"), Xbox and PlayStation slots write the WriteBoolTarget names, mirroring the engine's own raw-surface gate. The engine applies the cleaning to the combined output right before submit, on both the Gamepad bitmap and the raw-HID button words.
Keep Controller Awake holds a stick off-center so a console-style pad never idles out. Like the Bass Shakers config, it lives on the slot's MappingSet and every setter fires ConfigItemDirtyCallback.
| Property |
Type |
Default |
Description |
KeepAwakeCardVisible |
bool |
- |
Computed: Xbox and PlayStation slots only. |
KeepAwakeEnabled |
bool |
false |
Card master switch, over MappingSet.KeepAwakeEnabled. |
KeepAwakeMotion |
bool |
false |
Sweep the hold instead of parking it, over MappingSet.KeepAwakeMotion. For titles that gate vibration on the stick moving rather than on where it sits. |
KeepAwakeAxis |
string |
"LX" |
Held axis, locale-stable: LX, LY, RX, RY. The default is stored as an empty string, so the getter substitutes "LX". |
AvailableKeepAwakeAxes |
IReadOnlyList<GyroLabeledOption> |
- |
Axis picker options, reusing the stick-axis strings the mapping grid already localizes. |
KeepAwakeDeflection |
int |
25 |
Held deflection percent, clamped 1–90. A persisted 0 means unset and reads as the engine default 25, so the card always shows the effective number. |
| Command |
Description |
ResetKeepAwakeEnabledCommand |
Card switch back off. |
ResetKeepAwakeMotionCommand |
Sweep back off. |
ResetKeepAwakeAxisCommand |
Back to "LX". |
ResetKeepAwakeDeflectionCommand |
Back to 25. |
ResetKeepAwakeCardCommand |
Disabled, sweep off, axis and deflection back to their unset defaults, then ReloadKeepAwake(). |
| Method |
Description |
ReloadKeepAwake() |
Re-raises the whole card off the live MappingSet. Called on profile apply and output-type change. |
Radial / touch menus for this slot. Slot-level like Macros: the collection wraps the LIVE MenuDefinitionEntry list on the slot's MappingSet (write-through), and every edit fires ConfigItemDirtyCallback so the autosave path persists the set. Rows are MenuEditorItem instances.
| Property |
Type |
Description |
Menus |
ObservableCollection<MenuEditorItem> |
Menus configured for this pad slot. |
SelectedMenu |
MenuEditorItem |
Selected menu. Notifies HasSelectedMenu, refreshes the remove / duplicate commands. |
HasSelectedMenu |
bool |
Computed: SelectedMenu != null. |
| Command |
CanExecute |
Description |
AddMenuCommand |
always |
Adds a new entry to the slot set with the next free MenuId and an auto-numbered name. |
RemoveMenuCommand |
HasSelectedMenu |
Removes the selected menu from the slot set. |
DuplicateMenuCommand |
HasSelectedMenu |
Clones the selected entry under the next free MenuId with a "(Copy)"-suffixed name. |
MenuHostRecordCommand |
always |
Calls SelectedMenu.PrepareRecord(Host), then raises MenuHostRecordRequested. |
MenuRecordTargetCommand |
always |
RelayCommand<string>. The same freeform recorder aimed by parameter for the Custom opener's steer axes and the Click input: "CustomX", "CustomY", "Click". |
| Callback |
Description |
MenusStructureChanged |
Action, wired by MainWindow to InputService.RefreshAvailableInputsForSlot. Fired on a structural edit (add / remove / duplicate, kind, cell count, center cell, enabled) that changes which "Menu N Item K" descriptors exist, so the mapping and macro pickers stop offering stale entries. Label and name typing must not fire it. |
| Event |
Description |
MenuHostRecordRequested |
Record button on the menu host picker. MainWindow runs the freeform recorder and folds the recorded descriptor onto a host choice (MenuEditorItem.TryApplyRecordedHost), the Aim Engage record shape. |
| Method |
Description |
ReloadMenus() |
Rebuilds Menus from the slot's live MappingSet and selects the first entry. Called from RebuildMappings(), so profile applies, Workshop imports, output-type changes, and Reset to Defaults all refresh the tab. |
Slot context stamped onto every row. ApplyMenuButtonStyle runs on each item at reload, add, duplicate, and whenever the slot's macro button style is re-derived. It sets ButtonStyle from MacroButtonNames.DeriveStyle(OutputType), RawButtonCount (the Extended / Nintendo ExtendedConfig.ButtonCount, else 11), RawProfileId, and SupportsControllerButtons, true on Xbox, PlayStation, Nintendo and Extended only. A MIDI or Keyboard+Mouse output cannot press a controller button, so its cells omit the choice rather than offering it with a warning. It also hands the row five providers: DescriptorDisplayProvider, InputChoicesProvider (the slot's SlotAvailableInputs), RowBoundProvider, LayerChoicesProvider (the slot's MacroLayerChoices, for the #413 layer gate), and, for the #390 macro cells, MacroNamesProvider, which lists this slot's macro names live. Two callbacks ride along: StructureChanged fires MenusStructureChanged, and RuntimeEdit routes an edit through InputService.TryEditMenuConfiguration.
Macro renames follow their cells. MacroItem.Renamed is a static hook each pad VM subscribes to once. A rename on this slot retags every menu cell that referenced the old name, matched case-insensitively, which is the comparer the menu runtime itself uses.
Active Config Tab
| Property |
Type |
Default |
Description |
SelectedConfigTab |
int |
0 |
Always-visible: 0=Preview (the TabController RadioButton, Tag="0", binds Pad_Tab_Preview = "Preview"), 1=Macros, 2=Mappings, 3=Sticks, 4=Triggers. Visibility-gated on the active source device's capability flags: 5=Force Feedback (a stick-class device, or a haptic mouse, keyboard or the Razer Sensa row, #494), 6=Adaptive Triggers (HasAdaptiveTriggers), 7=Lighting (a lightbar, a Guide or HOME LED, or a lit mouse, keyboard or vendor row, #494), 8=Gyro (HasGyro), 9=Impulse Triggers (HasRumbleTriggers), 10=Touchpad (HasTouchpad), 11=Wheel, 12=Audio (AudioTabIndex), 13=Pointer (IR-capable Wii Remote, #146), 14=Mouse (source device is a mouse, #200). Slot-scope like tabs 0–2: 15=Menus (#9, always visible), 16=Bass Shakers (BassShakersTabIndex, gated on RumbleAudioTabVisible), 17=Output (OutputTabIndex, gated on OutputTabVisible). Tag values match the RadioButton.Tag strings on the PadPage tab strip. Entering 12 re-derives the sound-macro list and the mirror endpoints. Entering 16 re-enumerates render endpoints and re-seeds the voice rows. A gate that closes under the current selection evicts it back to tab 0. |
Commands
| Command |
CanExecute |
Description |
TestRumbleCommand |
IsDeviceOnline |
Raises TestRumbleRequested. |
ClearMappingsCommand |
always |
Stops any Map All walk, then clears every row's whole surface: primary and negative descriptors, the option flags, the per-row sensitivities and acceleration, NoInherit, the primary kind (back to Direct), the deadzone (back to 50), the device tag, the extra sources, the combine mode and formula, and the trim settings. |
CopySettingsCommand |
HasSelectedDevice, or the slot is created |
Raises CopySettingsRequested. |
PasteSettingsCommand |
HasSelectedDevice, or the slot is created |
Raises PasteSettingsRequested. |
CopyFromCommand |
HasSelectedDevice, or the slot is created |
Raises CopyFromRequested. |
MapAllCommand |
HasSelectedDevice && !IsMapAllActive && SelectedMappedDevice.IsOnline |
Starts sequential "Map All" recording. |
StopMapAllCommand |
IsMapAllActive |
Raises MapAllCancelRequested, ending a sweep in progress. The Map All button swaps its own caption and tooltip through MapAllButtonText / MapAllButtonTooltip while a sweep runs. |
Map All System
| Property |
Type |
Description |
IsMapAllActive |
bool |
Map All recording in progress. |
MapAllCurrentIndex |
int |
Current mapping row index during Map All. |
MapAllCurrentTarget |
string |
Target setting name being recorded. |
MapAllPromptText |
string |
Descriptive text shown on the Preview tab during Map All (e.g., "Map: A (1/21)"). |
CurrentRecordingTarget |
string |
TargetSettingName being recorded. Drives the preview's flashing. |
MapAllRecordingNeg |
bool |
Internal. Recording negative direction of a bidirectional axis. |
| Event |
Args |
Description |
TestRumbleRequested |
EventArgs |
Test rumble requested. |
TestLeftMotorRequested |
EventArgs |
Test left motor only. Fired via FireTestLeftMotor(). |
TestRightMotorRequested |
EventArgs |
Test right motor only. Fired via FireTestRightMotor(). |
CopySettingsRequested |
EventArgs |
Copy settings to clipboard. |
PasteSettingsRequested |
EventArgs |
Paste settings from clipboard. |
CopyFromRequested |
EventArgs |
Copy from another slot (MainWindow opens CopyFromDialog). |
MapAllRecordRequested |
MappingItem |
Request recording for the current Map All item. |
MapAllCancelRequested |
EventArgs |
Cancel an in-progress Map All recording. |
State Update Methods
| Method |
Description |
UpdateFromEngineState(Gamepad, Vibration, Vibration selectedDeviceVibration = null) |
Updates combined slot output at 30 Hz. The optional third argument drives the selected device's own motor bars. |
UpdateDeviceState(Gamepad) |
Updates per-device stick/trigger values for tab previews. |
UpdateFromTouchpadState(in TouchpadState) |
Mirrors the combined touchpad state onto the TouchpadFinger* / TouchpadClickPressed preview properties. |
UpdateFromRawHidState(RawHidState) |
Publishes the combined raw-HID output for the Extended schematic. Skips the notification when axes, hardware axes, buttons, and POVs all match the last snapshot, so an idle slot does not re-arm a repaint at 30 Hz. On a slot whose preview rides the raw surface (PreviewRidesRawSurface: any Nintendo slot, and an Extended slot on a Valve lettered profile) it also projects the raw state onto the Gamepad-shaped preview properties, and UpdateFromEngineState skips writing them so the two paths cannot fight. |
UpdateFromMidiRawState(MidiRawState) |
Updates the MIDI preview snapshot. Skips the notification when the CC values and notes match the last snapshot. |
OnMapAllItemCompleted() |
Advances Map All to next item after 500 ms delay. |
StopMapAll() |
Stops Map All, clears state, raises MapAllCancelRequested. |
RefreshCommands() |
Refreshes CanExecute for all commands. |
Output Snapshots (for custom previews)
| Property |
Type |
Description |
RawHidOutputSnapshot |
RawHidState |
Latest raw-HID state for the Extended / Nintendo schematic. Updated at 30 Hz by UpdateFromRawHidState, which raises PropertyChanged for it. Private setter. |
KbmOutputSnapshot |
KbmRawState |
Latest KBM raw state. Updated at 30 Hz. Plain auto-property, no change notification: the view polls it. |
VrOutputSnapshot |
VrRawState |
Latest VR raw state for the VR preview (#49). Same plain auto-property contract as the KBM twin. |
MidiOutputSnapshot |
MidiRawState |
Latest MIDI raw state snapshot. |
MappingItem
File: MappingItem.cs
Single mapping row linking a physical input to an output target in the Pad page mapping grid.
Target (Output)
| Property |
Type |
Description |
TargetLabel |
string |
Display label (e.g., "A", "Left Stick X", "CC 1"). Read-only. |
TargetSettingName |
string |
PadSetting property name (e.g., "ButtonA", "LeftThumbAxisX"). Read-only. |
Category |
MappingCategory |
Grouping: Buttons, DPad, Triggers, LeftStick, RightStick, Touchpad, Motion. |
NegSettingName |
string |
Negative-direction PadSetting property. Null for non-axis targets. |
HasNegDirection |
bool |
Computed: NegSettingName != null. |
| Property |
Type |
Default |
Description |
SourceDescriptor |
string |
"" |
Physical input descriptor. Format: "{MapType} {Index}", "IH{MapType} {Index}", or "POV {Index} {Direction}". Empty = unmapped. Notifies SourceDisplayText and IsMapped. |
NegSourceDescriptor |
string |
"" |
Negative-direction descriptor for stick axes. |
SourceDisplayText |
string |
- |
Computed: readable text. Bidirectional axes show "neg / pos". Falls back to "Not mapped". |
IsMapped |
bool |
- |
Computed: true if either SourceDescriptor or NegSourceDescriptor is non-empty. |
| Method |
Description |
SetResolvedSourceText(string) |
Sets display text (e.g., "A" instead of "Button 65"). Called by InputService. |
SetResolvedNegText(string) |
Sets resolved text for negative direction. |
LoadDescriptor(string) |
Sets source descriptor, syncs IsInverted/IsHalfAxis from prefix flags. |
LoadNegDescriptor(string) |
Loads negative-direction descriptor. |
SyncSelectedInputFromDescriptor() |
Syncs SelectedInput to current SourceDescriptor without re-triggering update. |
| Property |
Type |
Description |
AvailableInputs |
ObservableCollection<InputChoice> |
Source dropdown choices, spanning every device assigned to the slot. Populated by InputService once per VC slot, not per device-dropdown change. Since #322 every row on a slot points at a shared list (UseSharedAvailableInputs), so a keyboard-and-mouse tab switch no longer rebuilds a private copy and a grouped view per row. Since #475 the Motion Gyro and Motion Accelerometer rows point at the slot's motion lists and every other row at SlotAvailableInputs (InputService.PickerListForRow). |
ParamInputs / ParamInputsView |
ObservableCollection<InputChoice> / ICollectionView |
(#475) The slot's list, SlotAvailableInputs, for the modifier picker and for MappingSourceItem.ResolveParamChoice, which resolves the modifier and both keys. It differs from AvailableInputs only on the Motion Gyro and Motion Accelerometer rows, where an Invert On Hold modifier still needs a button. Set by UseSharedParamInputs, which collapses back to AvailableInputs when the two are the same list. |
KeyInputs / KeyInputsView |
ObservableCollection<InputChoice> / ICollectionView |
The Up and Down pickers' list, PadViewModel.SlotKeyInputs, set by UseSharedKeyInputs. A row no slot has filled holds an empty list of its own. A key saved before the pickers were narrowed, a stick or a trigger, resolves from ParamInputs to an entry this list lacks, so its picker shows no selection. The saved descriptor stays: the picker's null write-back is ignored (#160), and a pick or the reset button replaces it. |
AvailableInputsView |
ICollectionView |
The grouped view the XAML ComboBox binds to. GroupDescriptions carries one PropertyGroupDescription on InputChoice.DeviceLabel, so the picker renders one dropdown with device-name headers. The default view is one object per collection, so on the shared list every row lands on the same view and the grouping is configured once. |
SelectedInput |
InputChoice |
Selected dropdown input. Updates SourceDescriptor. Empty sentinel triggers ClearCommand. Suppression flag prevents re-entrancy, and is held across the shared-list rebuild (BeginSharedListRebuild / EndSharedListRebuild) so a live ComboBox cannot write its own selection back through the TwoWay binding. |
| Event |
Description |
InputSelectedFromDropdown |
Input selected from dropdown (for display text resolution). |
Recording
| Property |
Type |
Default |
Description |
IsRecording |
bool |
false |
Recording mode active. Notifies RecordButtonText and RecordButtonIcon. |
RecordButtonText |
string |
- |
Computed: "Record" or "Recording...". |
Live Value Display
| Property |
Type |
Description |
CurrentValueText |
string |
Source input raw value. Updated at 30 Hz when visible. |
Per-Mapping Deadzone
| Property |
Type |
Default |
Description |
MappingDeadZone |
int |
50 |
Axis activation threshold, clamped 1–100%. Input below this percentage is ignored for this mapping row. 0 is disallowed because it used to read as "unset" and silently reverted to 50. |
IsDeadZoneApplicable |
bool |
- |
Computed: true for axis-based mappings where a per-mapping deadzone is meaningful, and on a gamepad trigger row whose primary is in Rapid Trigger, where it is the actuation point (#482). |
| Command |
Description |
ResetDeadZoneCommand |
Resets MappingDeadZone to 50. |
PadViewModel.ClearAllMappings resets all mapping deadzones to 50.
Options
| Property |
Type |
Default |
Description |
IsInverted |
bool |
false |
Axis inverted. Calls RebuildDescriptor() to toggle "I" prefix. |
IsHalfAxis |
bool |
false |
Upper half of axis range only. Calls RebuildDescriptor() to toggle "H" prefix. |
IsBidirectional |
bool |
false |
With IsHalfAxis, fires on absolute deflection past the deadzone, either side of center. Persisted through PadSetting.MappingBidirectional, not a descriptor prefix, because the I/H prefixes are already spoken for. |
IsInvertApplicable |
bool |
- |
Computed: !(IsHalfAxis && IsBidirectional). Mirroring around center already covers both directions. |
InvertOutput |
bool |
false |
Flip Output: invert what the row writes, for the half-axis reads where Invert is consumed as the half selector. Round-trips to MappingSource.InvertOutput. |
IsInvertOutputApplicable |
bool |
- |
Computed by the engine's own InvertConsumedByHalfAxisRead predicate against the prefix-stripped primary. Gates the Flip Output checkbox. |
ParamAccel |
double |
0 |
Per-row acceleration exponent, clamped 0.0–5.0. Has ResetParamAccelCommand. Gated by IsParamAccelApplicable, which excludes the gravity-tilt family: those are position reads whose engine path applies only the curve / range channel (#292). |
GyroSensitivity |
double |
1.0 |
Primary-source gyro multiplier, clamped 0.1–10. Gated by IsGyroSource, which excludes the gravity-tilt family. |
IsGyroLeanSource |
bool |
- |
Computed: the primary is a gravity-lean read ("Gyro Lean X" / "Gyro Lean Y", #292). Gates the lean row's dial, which binds Sensitivity. |
MouseCursorSensitivity |
double |
1.0 |
Primary-source cursor sensitivity, clamped 0.1–5. Gated by IsMouseCursorSource. |
IrPointerSensitivity |
double |
1.0 |
Primary-source Wii IR-pointer sensitivity, clamped 0.1–5. Gated by IsIrPointerSource. |
Sensitivity |
double |
1.0 |
Clamped 0.1–5. Its only mapping-grid surface is the Gyro Lean dial, gated by IsGyroLeanSource. The generic per-source slider was removed on 2026-07-27 and the knob moved to the Sticks tab as a per-stick multiplier, so IsGenericSensitivitySource exists but no XAML binds it. |
Commands
| Command |
Description |
ToggleRecordCommand |
Toggles recording. Raises StartRecordingRequested or StopRecordingRequested. |
ClearCommand |
Clears the source and neg descriptor, resets IsInverted / IsHalfAxis / IsBidirectional, puts MappingDeadZone back to 50, blanks PrimarySourceDeviceGuid and PrimarySourceDeviceLabel, wipes the primary kind holder's ParamUp / ParamDown / ParamModifier and forces its Kind to "Direct" and blanks its DeviceGuid and DeviceLabel, then re-syncs the selected input from the descriptor. |
ResetGyroSensitivityCommand, ResetMouseCursorSensitivityCommand, ResetIrPointerSensitivityCommand, ResetSensitivityCommand, ResetParamAccelCommand |
Per-control resets for the primary-source knobs above. |
| Event |
Description |
StartRecordingRequested |
Start recording. |
StopRecordingRequested |
Recording should stop. |
A row's primary source stays on SourceDescriptor. Additional sources live in ExtraSources, each a MappingSourceItem. CombineMode applies when the row has more than one source. The engine's CombineHelper / MappingExpression consumes it in Step 3.
| Property |
Type |
Default |
Description |
ExtraSources |
ObservableCollection<MappingSourceItem> |
empty |
Sources beyond the primary. |
IsPrimaryDirect / IsPrimaryDescriptor |
bool |
- |
Computed. IsPrimaryDirect is PrimaryKindSource.Kind == "Direct". IsPrimaryDescriptor is true for Direct, Toggle and Rapid Trigger (SourceEvaluator.IsDescriptorKind), the kinds whose input lives in SourceDescriptor. It gates the Source picker, its device label and the kind card in the row, and the Record button's target (#461, #482). |
IsGamepadTriggerTarget / IsRapidTriggerTarget / IsRapidTriggerOffered |
bool |
- |
Computed (#482). IsGamepadTriggerTarget is the LeftTrigger and RightTrigger rows, narrower than IsTriggerTarget. IsRapidTriggerTarget adds the button-class rows (IsTargetDiscrete) apart from the touchpad X/Y rows (IsTouchpadAxisTarget), which read every source as a position. IsRapidTriggerOffered also needs the primary's input to have press depth (SourceCoercion.IsRapidTriggerSource) or to be empty. PrimaryKindOptions lists Rapid Trigger only while it holds, and EnforcePrimaryKindGate, run from the SourceDescriptor setter and EndLoadRow, sets a Rapid Trigger primary back to Direct when it stops holding. |
IsMultiSource |
bool |
- |
Computed: ExtraSources.Count > 0 \|\| !IsPrimaryDescriptor, so a lone Toggle primary shows no Combine picker. Not the same test as IsTrivialDirect, which requires IsPrimaryDirect and additionally a primary descriptor, no neg descriptor, no extras, no invert / half / bidirectional, and no custom formula. |
VariableCount |
int |
- |
Computed: PositionalSourceCount, the number of letters a Custom formula may reference. Drives the formula chip palette's visibility. It can run smaller than the primary plus ExtraSources.Count: the positional walk skips the bipolar Neg pair and any InvertOnHold modifier source. |
CombineMode |
string |
"" |
Per-row combine mode. Empty = per-target-type default (MaxAbs for axes, OR for buttons). Named modes: MaxAbs, Sum, Average, OR, AND, XOR, StickTrim, Custom. On a Motion target the getter reads MaxAbs for any stored mode other than empty, MaxAbs, Sum, Average, or Custom. |
CombineExpression |
string |
"" |
Custom formula, only meaningful when CombineMode == "Custom". |
IsCustomCombine |
bool |
- |
Computed: CombineMode == "Custom". |
ShouldShowCustomExpression |
bool |
- |
Computed: IsCustomCombine && (IsMultiSource \|\| IsTouchpadAxisTarget). A touchpad axis row evaluates a Custom formula at any source count, so its editor shows with one source. |
IsBipolarAxisTarget |
bool |
- |
Computed: target is a bipolar stick axis (LeftThumbAxisX/Y, RightThumbAxisX/Y). Drives per-source direction-badge visibility. |
IsTouchpadAxisTarget |
bool |
- |
Computed: target is a touchpad X/Y axis (TouchpadX1/Y1/X2/Y2). |
PrimarySourceDeviceGuid |
string |
"" |
DeviceGuid of the primary source on the per-VC MappingSet row. Empty = first available device on this VC. |
PrimarySourceDeviceLabel |
string |
"" |
Friendly device name for the primary source. |
NoInherit |
bool |
false |
Shift-layer "do not inherit from Base" flag. Round-trips to MappingRow.NoInherit. Visible only when authoring a non-Base layer. |
ShouldShowEmptyDirectionHint |
bool |
- |
Computed: a bipolar-axis row with exactly one non-inverted button-class primary source, nudging the user to map the opposite direction. |
| Command |
Description |
AddExtraSourceCommand |
Appends a blank MappingSourceItem, seeding CombineMode to the per-target default. |
RemoveExtraSourceCommand |
RelayCommand<MappingSourceItem>. Removes the passed source. |
AddOppositeDirectionCommand |
Adds an extra source mirroring the primary descriptor with Invert=true, filling a single-button bipolar-axis row's negative direction in one click. |
Stick Trim (#155)
Trigger-only combine mode that lets a "winding" stick axis trim a trigger output up/down.
| Property |
Type |
Default |
Description |
IsStickTrimCombine |
bool |
- |
Computed: CombineMode == "StickTrim". |
IsTriggerTarget |
bool |
- |
Computed: target is LeftTrigger / RightTrigger or an Extended axis created in the Triggers category. The only class the engine's StickTrim combine intercepts. |
ShouldShowTrimSettings |
bool |
- |
Computed: IsMultiSource && IsStickTrimCombine && IsTriggerTarget. Gates the trim strip. |
TrimDeadzone |
int |
25 |
Trim-axis deflection below this percentage is ignored (steering wobble guard). Clamped 0–95. |
TrimRate |
int |
100 |
Full-deflection adjust speed, percent of the trigger range per second. Clamped 1–1000. |
TrimResetOnRelease |
bool |
true |
Releasing the gate snaps the stored level back to 100% when true. False keeps it. |
Reset commands: ResetTrimDeadzoneCommand, ResetTrimRateCommand, ResetTrimResetOnReleaseCommand.
Motion Pitch, Yaw and Roll (#475)
File: MappingItem.MotionRows.cs. The three rows that turn stick, trigger, button and key input into the virtual controller's motion.
| Property |
Type |
Default |
Description |
IsMotionAxisRow |
bool |
- |
Computed: the target is MotionPitch, MotionYaw or MotionRoll. Shows the motion panel. |
CanUseMotionAngle |
bool |
- |
Computed: pitch or roll. Yaw has no Angle response, so the Motion Mode dropdown hides on it. |
MotionResponse |
string |
"" |
"" for Speed, "Angle" for Angle. Any other value stores as "". |
IsMotionAngle |
bool |
- |
Computed: CanUseMotionAngle and MotionResponse == "Angle". |
ShowMotionSpeedSettings / ShowMotionAngleSettings |
bool |
- |
Computed: which half of the panel shows. Raised with MotionResponse. |
MotionSpeed |
int |
360 |
Top Speed, degrees per second. Clamped 1 to 1600. |
MotionMinSpeed |
int |
0 |
Start Speed, degrees per second. Clamped 0 to 1600. |
MotionAngle |
int |
85 |
Lean Angle, degrees. Clamped 1 to 90. |
MotionDeadzone |
int |
20 |
Motion Deadzone, percent. Clamped 0 to 90. |
MotionResponseOptions |
IReadOnlyList<CombineModeOption> |
- |
Speed and Angle with their descriptions, culture-cached like the combine modes. |
PresetCarriesNoMotion |
bool (init) |
false |
Set by the grid builder on all five Motion rows of the DualShock 3 (SIXAXIS), plain Switch 2 Pro and plain Steam Deck presets (HMaestroProfileCatalog.ReportCarriesNoMotion). |
MotionRowNote / ShowMotionRowNote |
string / bool |
null / false |
The note under the row. On a Motion Gyro or Motion Accelerometer row, a source whose descriptor is not a Motion one: Pad_Mapping_MotionRowUnreadNote. On any other row, a bundled motion source: Pad_Mapping_MotionSourceElsewhereNote. Otherwise, on a preset without motion, Pad_Mapping_MotionPresetNote on the Motion rows, and on the DualShock 3 (SIXAXIS): Full, whose report carries no pitch or roll rate (PresetCarriesNoPitchRollRate), Pad_Mapping_MotionPitchRollRateNote on Motion Pitch and Motion Roll in Speed mode. InvertOnHold modifiers are skipped. Raised when the primary, an extra source or MotionResponse changes. |
The five settings reset through ResetSettingCommand with their property names. The grid builder adds the three rows after Motion Gyro and Motion Accelerometer on the PlayStation, Nintendo and Valve grids (PadViewModel.AddMotionRows), each with NegSettingName <Target>Neg and all five out of Map All. Clear All resets the five settings.
File: MappingItem.cs
Input choice in the source dropdown.
| Property |
Type |
Description |
Descriptor |
string |
Mapping descriptor (e.g., "Button 0", "Axis 1", "POV 0 Up"). Empty string = "clear" sentinel. |
DisplayName |
string |
Human-readable display name (e.g., "A", "Left Stick X"). ToString() returns it. |
DeviceGuid |
string |
Owning device for the cross-device picker. Empty = the "(Any device)" group. |
DeviceLabel |
string |
Friendly device name for that group heading. |
MappingCategory (enum)
Values: Buttons, DPad, Triggers, LeftStick, RightStick, Touchpad, Motion. Motion covers the bundled motion-passthrough rows (MotionGyro / MotionAccel targets) and the Motion Pitch, Yaw and Roll rows (#475). The passthrough rows are auto-created on Sony-class slots for every assigned gyro or accel-capable device and rendered as one combined Motion row when both sub-channels come from the same source. Touchpad rows are the one category IsRecordable excludes.
MappingSourceItem
File: MappingSourceItem.cs
One source row within a multi-source MappingItem (#61). Represents a single Engine.Data.MappingSource, bound by the RowDetailsTemplate inside the Mappings DataGrid.
Kind
| Property |
Type |
Default |
Description |
Kind |
string |
"Direct" |
Source kind: Direct, Toggle, RapidTrigger, Incremental, InvertOnHold, Ramped. Notifies the Is*Kind computed flags. A change to Incremental or Ramp outside a load drops Invert, unless it comes from the other of those two kinds, and puts the source on the controller its Up and Down keys share (FollowKeysDevice). A change to InvertOnHold outside a load puts it on the controller its modifier names, and a change to a kind that reads the Descriptor puts it back on the Descriptor's (RestoreDescriptorController). |
IsIncrementalKind / IsInvertOnHoldKind / IsRampedKind / IsToggleKind / IsRapidTriggerKind |
bool |
- |
Computed per-kind flags. |
DescriptorKind |
string (internal) |
- |
Computed: the kind a row's primary saves as, Toggle or RapidTrigger when set, else Direct. Every path that rebuilds a primary into a source reads it. |
UsesUpDownKeys |
bool |
- |
Computed: Incremental or Ramped (authored via an Up/Down key pair). |
IsKindDescriptorless |
bool |
- |
Computed: the kinds that leave the source's Descriptor, HalfAxis and DeadZone unread (Incremental, InvertOnHold, Ramped). The grid collapses those controls and Invert with them. Invert is not unread: the engine applies it to an Incremental source and to a Ramp source on an axis, so a source that comes to one of those two kinds from any other kind in the editor drops it, while a switch between Incremental and Ramp keeps it, and one saved with it on shows it (IsKeyInvertShown). |
IsKeyInvertShown |
bool |
- |
Computed: UsesUpDownKeys && Invert. An Incremental or Ramp source saved with Invert on shows a checkbox for it, the control that turns it off. Unchecked, the checkbox goes with it. A row's primary shows the same checkbox in its own strip, bound to the primary kind holder's Invert. |
HasAnyBoundFeed |
bool |
- |
Computed: whether any input feeds this source, per kind. Mirrors the engine's SourceEvaluator dispatch. |
KindOptions (static) is the culture-cached list of KindChoice { Value, Name } for the Kind dropdown, keeping backend identifiers out of the UI. KindChoices, the extra-source chip's instance list, drops Rapid Trigger while IsRapidTriggerOffered is false: the source's input has no press depth, or its row cannot take the mode (ParentTargetTakesRapidTrigger, #482). Picking an input without depth sets a Rapid Trigger source back to Direct, and EnforceRapidTriggerGate does the same when the parent row pushes a target that cannot take it.
Source
| Property |
Type |
Default |
Description |
DeviceGuid |
string |
"" |
Device this source reads from. Empty = the picker's "(Any device)" group. |
DeviceLabel |
string |
"" |
Friendly device name, shown inline below the per-source picker. |
DisplayDeviceLabel |
string |
- |
Computed device label honoring the "(Any device)" contract: a concrete-guid source prefers SelectedInput.DeviceLabel (recovers the friendly name when the stored label wasn't hydrated), an empty-guid source keeps its own label and never borrows a concrete device from a descriptor-only picker fallback. Read by the grid subtitle, the annotation overlays, and the pipeline-chip tooltip. |
Descriptor |
string |
"" |
Input descriptor. Notifies the source-class flags below. |
DescriptorDeviceGuid |
string |
- |
Computed: what ToDomain saves as the controller the kept Descriptor was picked on, the domain field of the same name. The remembered controller while the kind leaves the Descriptor unread and the source sits on another device, null otherwise. FromDomain takes a saved one back. |
SelectedInput |
InputChoice |
null |
Cross-device picker selection. Setting it writes DeviceGuid, DeviceLabel, and Descriptor in one shot. |
IsGyroSource / IsMouseCursorSource / IsIrPointerSource / IsMouseMotionSource |
bool |
- |
Computed from the descriptor prefix. Gate the matching per-source sensitivity sliders. IsGyroSource excludes the gravity-tilt family (#292). |
IsGyroLeanSource |
bool |
- |
Computed: the gravity-lean pair ("Gyro Lean X" / "Gyro Lean Y", #292). Gates the lean row's dial, which binds Sensitivity, the field ReadGyroLean scales by. |
IsParamAccelApplicable |
bool |
- |
Computed: gates the per-source Acceleration slider. The continuous-source family minus the gravity-tilt pairs, whose engine path never applies ParamAccel (#292). |
IsButtonClassDescriptor |
bool |
- |
Computed: the descriptor yields a bool. Gamepad aliases resolve first, then button, POV, touchpad-click, NFC tag (#241), and voice phrase (#317). |
DirectionBadge |
string |
- |
Computed: "→ +" / "← −" for button-class sources, per Invert. Empty otherwise. |
Options and per-source deadzone
| Property |
Type |
Default |
Description |
Invert |
bool |
false |
Axis / direction inverted. |
HalfAxis |
bool |
false |
Upper half of the range only. |
IsHalfAxisApplicable |
bool |
- |
Computed: Half applies to continuous-range sources (Axis, Slider, Touchpad X/Y/Pressure, Gyro, Mouse Motion). |
Bidirectional |
bool |
false |
With HalfAxis, fires on absolute deflection past the deadzone (either side of center). |
DeadZone |
int |
50 |
Per-source axis-to-button threshold. Clamped 1–100 (0 is disallowed because it read as "unset"). |
IsDeadZoneApplicable |
bool |
- |
Computed: the source is an axis/slider (or an engine-owned continuous family) AND the parent target is a discrete output, or a gamepad trigger row with this source in Rapid Trigger (#482). |
ParentTargetIsDiscrete / ParentTargetIsTrigger |
bool |
false |
Set by the parent MappingItem so IsDeadZoneApplicable knows the target class. ParentTargetIsTrigger is MappingItem.IsGamepadTriggerTarget. |
ParentTargetTakesRapidTrigger |
bool |
false |
Set by the parent MappingItem from IsRapidTriggerTarget, so an extra source offers Rapid Trigger on exactly the rows whose primary does (#482). |
Kind parameters
| Property |
Type |
Default |
Description |
ParamUp / ParamDown |
string |
"" |
Up / Down key descriptors for Incremental and Ramped. |
ParamRapidTriggerDistance |
int |
10 |
Rapid Trigger distance in percent of full travel, clamped 1 to 50 (#482). The Distance row in the detail strip and on the extra-source chip, each with a reset button. LoadPrimaryKind(null) and the row's Clear reset it. |
ParamRate |
double |
0.5 |
Incremental step rate. |
ParamSticky |
bool |
true |
Incremental sticky hold. |
ParamMin / ParamMax |
double |
0 / 1 |
Incremental output bounds. |
ParamModifier |
string |
"" |
InvertOnHold modifier key descriptor. |
ParamUpDeviceGuid / ParamDownDeviceGuid / ParamModifierDeviceGuid |
string |
null |
The controller each key reads, the domain fields of the same names. A pick or a recording sets it with the key (SetParamKey), and ToDomain saves none for an empty key. ParamUpReadDevice and its siblings answer the controller the engine reads, the source's own for a key with none. |
ParamAttackTime |
double |
0.30 |
Ramped attack seconds (#111). Clamped 0–5. |
ParamReleaseTime |
double |
0.30 |
Ramped release seconds (#111). Clamped 0–5. |
ParamAutocenter |
bool |
true |
Ramped: release ramps back to zero (#111). |
ParamReverseMultiplier |
double |
4.0 |
Ramped reverse speed-up (#111). Clamped 1–10. |
GyroSensitivity |
double |
1.0 |
Per-source gyro multiplier (Gyro descriptors). Clamped 0.1–10. |
MouseCursorSensitivity |
double |
1.0 |
Per-source cursor sensitivity (#107, Mouse Position). Clamped 0.1–5. |
IrPointerSensitivity |
double |
1.0 |
Per-source Wii IR-pointer sensitivity (#146). Clamped 0.1–5. |
Sensitivity |
double |
1.0 |
Per-source multiplier the Gyro Lean dial binds (ReadGyroLean scales by it). Clamped 0.1–5. |
ParamAccel |
double |
0 |
Per-source acceleration exponent, clamped 0.0–5.0. Gated by IsParamAccelApplicable. |
InvertOutput |
bool |
false |
Flip Output: invert what this source writes. Gated by IsInvertOutputApplicable, the engine's InvertConsumedByHalfAxisRead predicate. |
Per-source recording
| Property |
Type |
Default |
Description |
IsRecording |
bool |
false |
Recording mode active. Notifies the record text/icon and the per-Param recording flags. |
RecordButtonText / RecordButtonIcon |
string |
- |
Computed record-button label / Segoe MDL2 glyph. |
IsRecordingParamUp / Down / Modifier |
bool |
- |
Computed: which Param field's record is armed, so only that button swaps to its Stop glyph. |
| Command |
Description |
ToggleRecordCommand |
Toggles primary-source recording (raises StartRecordingRequested / StopRecordingRequested). |
RecordParamUpCommand / RecordParamDownCommand / RecordParamModifierCommand |
Toggle recording for a specific Param field (raise StartParamRecordingRequested with a ParamRecordTarget). |
ClearCommand |
Clears the descriptor, DeviceGuid, DeviceLabel, and the backing selected-input field, and resets the flags and deadzone to their defaults, keeping the row in ExtraSources. It leaves Kind, the Param* fields, the sensitivities, ParamAccel, and InvertOutput alone. |
ResetDeadZoneCommand, ResetGyroSensitivityCommand, ResetMouseCursorSensitivityCommand, ResetIrPointerSensitivityCommand, ResetParamAccelCommand, ResetSensitivityCommand |
Per-control resets. |
| Method |
Description |
ToDomain() |
Builds a domain Engine.Data.MappingSource from the VM. |
FromDomain(MappingSource) |
Static: populates a new VM from a domain source. |
SyncSelectedInputFromState(IEnumerable<InputChoice>) |
Syncs the picker selection from the current (DeviceGuid, Descriptor) pair. |
SetParamKey(ParamRecordTarget, string, string) |
Internal: lands a key and its controller together, a cleared key keeping none, then lets the source follow the keys its kind reads (FollowKeysDevice: for Incremental or Ramp the controller its Up and Down keys share, or none, for InvertOnHold the controller its modifier names). A key saved without a controller, the modifier included, is first given the source's. The source remembers the controller and name its Descriptor was picked on when it first follows its keys away, and a switch back to a kind that reads the Descriptor restores them (RestoreDescriptorController). FollowKeysDevice has two more callers: AdoptLegacyKeyDevices, and the Kind setter when the kind becomes Incremental or Ramp with keys already in. |
AdoptLegacyKeyDevices() |
Internal: gives each key saved before keys carried a controller the controller of the entry its picker shows, and only from a list that carries the source's own controller (ListCarriesOwnController). The parent row calls it from RefreshExtraSourceInputs, outside a row load (BeginLoadRow to EndLoadRow), so the list rebuilt after the rows decides. MappingItem.LoadPrimaryKind holds it off (LoadingKeys) until the whole source is in, because the source's device lands before its keys. LoadingKeys holds the Kind setter's follow off too: LoadPrimaryKind sets the kind last, and the source stays on the device it was saved on. |
ParamRecordTarget (enum): Up, Down, Modifier.
ShiftLayerInfo
File: ShiftLayerInfo.cs
VM wrapper around a single Engine.Data.ShiftActivator. Populates the nested tab strip on the Mappings tab (PadViewModel.LayerTabs) and the per-layer context-menu commands.
| Property |
Type |
Default |
Description |
LayerMask |
string |
"Base" |
Engine-side layer identity (matches MappingRow.LayerMask). |
LayerName |
string |
"Base" |
User-visible display name. |
Color |
string |
"" |
v2 per-layer color hint, "#AARRGGBB" or empty. |
IsActive |
bool |
false |
True when this layer's tab is selected (the one being authored). |
IsBase |
bool |
- |
Computed: LayerMask == "Base". Two consumers: the tab chip's color-dot visibility, and the SHIFT pipeline chip's idle summary, which lists only non-Base layer names. It does not gate the tab context menu, whose Base behavior lives in the handlers. Base does show Rename (edits the base appearance), Configure (its flyout and tab appearance), and Delete (the legacy-Base heal path). |
MacroItem
File: MacroItem.cs
A trigger combination of inputs that produces a sequence of output actions. Evaluated in Step 4b, after Step 4 combines the slot output and before Step 5 writes the virtual devices.
Identity
| Property |
Type |
Default |
Description |
Name |
string |
Macro_NewMacro |
User-facing name, seeded from the localized resource (English "New Macro"). The setter is not plain: any post-construction change fires the static Renamed event, which is how #390 retags menu cells that referenced the old name. |
IsEnabled |
bool |
true |
Macro active. |
| Property |
Type |
Default |
Description |
TriggerButtons |
ushort |
0 |
Gamepad button bitmask (e.g., Gamepad.A \| Gamepad.B). All must be pressed. |
TriggerCustomButtonWords |
uint[] |
new uint[4] |
Wide bitmask for custom Extended slots (128 buttons, 4x32-bit). Not serialized. |
TriggerCustomButtons |
string |
null |
Serializable hex form (e.g., "00000003,00000000,00000000,00000000"). |
UsesCustomTrigger |
bool |
- |
Computed: any custom trigger button set. |
TriggerSource |
MacroTriggerSource |
InputDevice |
Record from physical device or virtual controller output. |
TriggerDisplayText |
string |
- |
Computed: readable trigger combo with device name. |
ButtonStyle |
MacroButtonStyle |
Xbox360 |
Display name style. Not serialized. |
CustomButtonCount |
int |
11 |
Custom Extended button count. Not serialized. |
CarriesPressure |
bool |
false |
(#530) True on a PlayStation slot whose preset HMaestroProfileCatalog.ReportCarriesPressure names, the DualShock 3 (SIXAXIS): Full. Stamped with ButtonStyle (PadViewModel.SyncMacroButtonStyle, the add commands, SettingsService.LoadMacroFromData) and handed to every action. Not serialized. |
| Property |
Type |
Default |
Description |
TriggerDeviceGuid |
Guid |
Guid.Empty |
Trigger source device. Empty = legacy Xbox bitmask. |
TriggerRawButtons |
int[] |
[] |
Raw button indices. All must be pressed. |
UsesRawTrigger |
bool |
- |
Computed: any TriggerInputs entry has RawButton >= 0, or the legacy pair TriggerDeviceGuid != Empty && TriggerRawButtons.Length > 0. The legacy half alone reads false for every entry-list macro. |
Trigger Condition (POV Hat)
| Property |
Type |
Default |
Description |
TriggerPovs |
string[] |
[] |
POV hat triggers as "povIndex:centidegrees" (e.g., "0:0" for POV 0 Up). |
UsesPovTrigger |
bool |
- |
Computed: any TriggerInputs entry carries a non-empty Pov, or TriggerPovs.Length > 0. |
Trigger Condition (Axis)
| Property |
Type |
Default |
Description |
TriggerAxisTargets |
MacroAxisTarget[] |
[] |
Axes that must all exceed threshold. Not serialized. |
TriggerAxisTargetList |
string |
null |
Serializable comma-separated form. |
TriggerAxisThreshold |
int |
50 |
Threshold % (1–100). |
UsesAxisTrigger |
bool |
- |
Computed: TriggerAxisTargets.Length > 0, or any TriggerInputs entry has AxisTarget != MacroAxisTarget.None. |
TriggerAxisDirections |
MacroAxisDirection[] |
[] |
Direction filter per axis. Parallel array. Not serialized. |
TriggerAxisDirectionList |
string |
null |
Serializable comma-separated form. |
TriggerAxisDirectionIndex |
int |
- |
UI index for direction (0=Any, 1=Positive, 2=Negative). Sets all uniformly. |
Trigger Condition (Descriptor / Device-Free) (#9)
Descriptor entries carry the trigger shapes that have no raw-entry form: engine-read descriptors ("Gyro Pitch", "Touchpad 0 Finger 0 Down", "Menu {id} Item {k}") evaluated through SourceCoercion's button read with the same per-(device, slot) tuning a mapping row gets. Entry-list only, picked from the trigger dropdown, never recorded.
| Member |
Type |
Default |
Description |
TriggerInputEntry.SourceDescriptor |
string |
null |
The engine-read descriptor on a trigger-entry row. Serialized through the pipe-joined TriggerInputs spec. The plain form is in:{guid}:sd:{descriptor} (same tail escaping as gestures). When the entry carries a stamp (HalfAxis, Invert, Bidirectional, or DescriptorDeadZone > 0) it writes the extended form instead: in:{guid}:sdh:{half}:{invert}:{bidi}:{deadzone}:{descriptor}. |
TriggerInputEntry.DescriptorSource |
MappingSource |
- |
Cached wrapper for the descriptor so the 1 kHz trigger evaluation never allocates. Null when the entry isn't a descriptor entry. |
UsesDescriptorTrigger |
bool |
- |
Computed: any trigger entry carries a SourceDescriptor. |
An entry with DeviceGuid == Guid.Empty is device-free: the evaluator resolves it against whatever device sits on the macro's slot, and TriggerDisplayText renders it under the localized "(Any device)" sentinel (the same group the mapping picker uses).
Trigger Options
| Property |
Type |
Default |
Description |
TriggerMode |
MacroTriggerMode |
OnPress |
Twelve modes, ordinals pinned: OnPress (0), OnRelease (1), WhileHeld (2), Always (3), CustomExpression (4, v3.2), HoldForMs (5, On Long Press), DoublePress (6), TriplePress (7), SinglePress (8), Toggle (9), Turbo (10), ShortPress (11, On Short Press, #253). |
TriggerHoldMs |
int |
500 |
Hold threshold shared by HoldForMs and ShortPress, the tap-vs-hold pair (#9 wave 1b). Clamped 50-10000. ResetTriggerHoldMsCommand puts it back to 500. |
TriggerDoublePressMs |
int |
442 |
Multi-press window for the Single / Double / Triple modes. Clamped 50-5000. The default is the double-tap window Valve's own shipped controller_base templates author (basicui.vdf / basicui_neptune.vdf, "double_tap_time" "442"). ResetTriggerDoublePressMsCommand puts it back to 442. |
LayerMask |
string |
"" |
Per-macro shift-layer scope (#254). Empty fires on any layer. "Base" opens only while Base is effectively engaged, honoring the engaged layer's InheritUnmapped exactly like a Base row does. Any other value opens only while that layer is engaged, scoped to the macro's own slot. When the macro's own slot does not declare the mask at all, any slot's engagement still opens it. A null write is ignored: the editor's Layer ComboBox coerces SelectedValue to null when the selected choice disappears, which silently downgraded a scoped macro to "any layer". HasLayerScope and ShowsLayerRow both read LayerMask non-empty. |
IsNotAlwaysMode |
bool |
- |
Computed: TriggerMode != Always. Controls trigger UI visibility. |
ConsumeTriggerButtons |
bool |
true |
Remove trigger buttons from Gamepad state on fire. |
Recording
| Property |
Type |
Default |
Description |
IsRecordingTrigger |
bool |
false |
Recording trigger combo. Notifies RecordTriggerButtonText. |
RecordTriggerButtonText |
string |
- |
Computed: "Stop" or "Record Trigger". |
RecordingLiveText |
string |
"" |
Live display of buttons pressed during recording. Not serialized. |
Actions
| Property |
Type |
Description |
Actions |
ObservableCollection<MacroAction> |
Ordered sequence of actions to execute. |
SelectedAction |
MacroAction |
Selected action. Refreshes remove command. |
Repeat Settings
| Property |
Type |
Default |
Description |
RepeatMode |
MacroRepeatMode |
Once |
How actions repeat: Once, FixedCount, UntilRelease. |
RepeatCount |
int |
1 |
Repeat count for FixedCount mode. Min 1. |
RepeatDelayMs |
int |
100 |
Delay between repeats (ms). Min 0. |
Runtime State (not serialized)
| Property |
Type |
Description |
IsExecuting |
bool |
Executing action sequence. |
CurrentActionIndex |
int |
Position in action sequence. |
RemainingRepeats |
int |
Remaining repeat count. |
ActionStartTime |
DateTime |
When current action/delay started. |
WasTriggerActive |
bool |
Trigger active on previous frame. |
Commands
| Command |
Description |
RecordTriggerCommand |
Toggles IsRecordingTrigger and raises RecordTriggerRequested. |
ClearTriggerCommand |
Stops a trigger recording in progress, then clears every trigger form: the button masks, the raw buttons and their device, the axis targets and directions, the POVs, and the trigger-entry list. |
AddActionCommand |
Adds a new MacroAction with default type ButtonPress and this macro's button style, count, and profile. |
DuplicateActionCommand |
Clones the selected action through the action DTO and inserts the copy right after it (#112). CanExecute: SelectedAction != null. |
RemoveActionCommand |
Removes the selected action, then selects the last remaining one. CanExecute: SelectedAction != null. |
| Event |
Description |
RecordTriggerRequested |
Trigger recording toggled. |
MacroAction
File: MacroItem.cs (a separate top-level class in the same file, not nested inside MacroItem)
Single action in a macro's sequence.
Core Properties
| Property |
Type |
Default |
Description |
Type |
MacroActionType |
ButtonPress |
Action type. Notifies all Is*Type properties. |
DurationMs |
int |
50 |
Hold/delay duration (ms). Min 0. |
DisplayText |
string |
- |
Computed: readable action summary. |
Type-Check Properties (all computed, not serialized)
One Is*Type flag per action family, plus the rolled-up families the editor gates whole panels on (IsAnyRumbleType, IsAnyAxisValueType, IsAnyVcButtonType, IsAnyKeyType, and the rest). A representative set: IsButtonType, IsKeyType, IsDurationType, IsAxisType, IsSystemVolumeType, IsAppVolumeType, IsMouseMoveType, IsMouseButtonType, IsContinuousAxisType, IsDeviceAxisSource, IsOutputAxisSource.
| Property |
Type |
Default |
Description |
ButtonFlags |
ushort |
0 |
Xbox bitmask for gamepad presets. |
ButtonStyle |
MacroButtonStyle |
Xbox360 |
Display name style. Not serialized. |
CustomButtonCount |
int |
11 |
Numbered style button count. Not serialized. |
CarriesPressure |
bool |
false |
(#530) The owning macro's flag, set by Add Action and Duplicate Action too. The axis pickers show the ten pressure targets only while it is true. Not serialized. |
CustomButtonWords |
uint[] |
new uint[4] |
Wide bitmask for Extended slots (128 buttons). Not serialized. |
CustomButtons |
string |
null |
Serializable hex form. |
ButtonOptions |
IReadOnlyList<GamepadButtonOption> |
- |
Computed: checkbox-bindable list. Lazy-built from style and count. |
| Method |
Description |
SetCustomButton(int, bool) |
Sets/clears a custom Extended button by 0-based index. |
IsCustomButtonPressed(int) |
Returns whether a custom button is pressed. |
HasCustomButtons |
Computed: any custom button is set. |
Key Action Properties
| Property |
Type |
Default |
Description |
KeyCode |
int |
0 |
Win32 VK_ code. Notifies SelectedVirtualKey. |
SelectedVirtualKey |
VirtualKey |
None |
Enum wrapper for ComboBox. Not serialized. |
KeyString |
string |
"" |
Multi-key combo in x360ce format ("{Control}{Alt}{Delete}"). |
ParsedKeyCodes |
int[] |
- |
Computed: VK codes from KeyString, falling back to KeyCode. |
SelectedKeyToAdd |
VirtualKey |
None |
Key picker binding. Auto-appends to KeyString and resets. |
| Command |
Description |
ClearKeyStringCommand |
Clears the KeyString. |
| Static |
Description |
VirtualKeyValues |
List<KeyDisplayItem> with localized display names. Rebuilt on culture change. |
ParseKeyString(string) |
Parses "{Key1}{Key2}..." into int[] of VK codes. |
Axis Action Properties
| Property |
Type |
Default |
Description |
AxisValue |
short |
0 |
AxisSet: signed value (−32768 to 32767). |
AxisTarget |
MacroAxisTarget |
None |
Which axis to set/read. |
InvertAxis |
bool |
false |
Invert axis value. |
Volume Action Properties
| Property |
Type |
Default |
Description |
ShowVolumeOsd |
bool |
true |
Trigger Windows volume OSD. |
ProcessName |
string |
"" |
AppVolume: target process name. |
AudioProcessNames |
ObservableCollection<string> |
empty |
Processes with active audio sessions (suggestions). |
VolumeLimit |
int |
100 |
Max volume % (1–100). |
| Command |
Description |
RefreshAudioProcessesCommand |
Refreshes active audio session process list. |
Mouse Action Properties
| Property |
Type |
Default |
Description |
MouseSensitivity |
float |
10f |
Pixels/scroll units per frame at full deflection (1–100). |
MouseButton |
MacroMouseButton |
Left |
Mouse button for press/release. |
MouseAccumulator |
float |
0 |
Internal [XmlIgnore] field, not a property: the fractional pixel accumulator that keeps sub-pixel precision across ticks. |
Axis Source Properties
| Property |
Type |
Default |
Description |
AxisSource |
MacroAxisSource |
OutputController |
Axis value source. |
SourceDeviceGuid |
Guid |
Guid.Empty |
InputDevice source: physical device GUID. |
SourceDeviceAxisIndex |
int |
-1 |
InputDevice source: axis index in InputState.Axis[]. |
Pressure-Scaled Turbo (#290, #393)
Applies to the repeat-interval action family (RepeatKeyWhileHeld, RepeatVcButtonWhileHeld, the toggles pulsing under PulseWhileLatched, and the rest of the IsRepeatIntervalType set). While the gate is on, the repeat rate follows a physical analog source (DualShock 3 button pressure, any trigger) instead of the fixed IntervalMs. IntervalMs becomes the fast end (full press) and SlowIntervalMs the slow end, interpolated in rate space so a light press is a defined slow rate.
Pressure direction comes from the trigger (#393). The read used to be the raw axis position, raw / 65535, so on a stick's lower half a harder push read as falling pressure and a gentle push repeated faster than a hard one. A macro that fires on a stick half already says which half, so the engine takes the direction from there rather than adding a knob. InputManager.ResolveTurboPressure01 resolves the 0..1 pressure in three steps:
- A
TriggerInputs entry on the pressure source's axis, on the same device or device-free (Guid.Empty, the picker's "(Any device)" form). An entry pinned to the source device outranks a device-free one on the same axis. A half-axis entry reads deflection from center into the half it selects: Invert takes the lower half, Bidirectional takes both sides. A full-axis entry is trigger-style and reads raw / 65535, flipped by Invert.
- Otherwise a legacy
TriggerAxisTargets slot target on the corresponding gamepad axis: Positive and Negative read deflection from center into that half, Any reads raw / 65535.
- Otherwise
raw / 65535, byte-identical to the #290 read. A pressure source that is not the macro's trigger keeps exactly what it shipped with.
Every direction now ramps 0 at rest to 1 at full push, which is what the ramp endpoints, the curve, and the rate blend were written against. The volume and mouse actions keep ReadAxisFromDevice, where absolute position is the meaning.
| Property |
Type |
Default |
Description |
PressureScaledRate |
bool |
false |
Master gate. Off keeps every existing macro on the legacy fixed-rate path. |
SlowIntervalMs |
int |
500 |
Light-press period in ms. Clamped 10–2000 here, and floored at IntervalMs at evaluation time so slow can never outrun fast. |
TurboRateCurve |
string |
"Linear" |
Curve applied to the 0..1 pressure before the rate interpolation. Same vocabulary as the mapping rows' output curves: Linear, Aggressive, Relaxed, Wide, ExtraWide. Empty collapses to Linear. |
TurboRateCurveOptions |
IReadOnlyList<GyroLabeledOption> |
- |
Live-language curve choices for the dropdown. |
PressureStartPercent |
int |
0 |
Pressure at which the ramp leaves the slow rate. Below it the rate stays at SlowIntervalMs. Clamped 0–99. The remap runs before the curve. |
PressureEndPercent |
int |
100 |
Pressure at which the ramp reaches the full-press rate. Clamped 1–100. The engine floors the span at one percent. |
ShowsPressureTurboRows |
bool |
- |
Computed: IsRepeatIntervalType && PressureScaledRate. Gates the card's extra rows. |
Reset commands: ResetIntervalMsCommand (100 ms), ResetSlowIntervalMsCommand (500 ms), ResetPressureStartCommand (0), ResetPressureEndCommand (100).
Disconnect Action Properties (#162)
For MacroActionType.DisconnectController. The target is set on the action, not the trigger, so a macro can turn off device X from a chord on device Y.
| Property |
Type |
Default |
Description |
DisconnectTarget |
MacroDisconnectTarget |
TriggeringDevice |
Which device(s) the disconnect targets. Notifies DisplayText and IsDisconnectSpecificDevice. |
DisconnectDeviceGuid |
Guid |
Guid.Empty |
Victim device for SpecificDevice mode. Notifies DisplayText. |
IsDisconnectSpecificDevice |
bool |
- |
Computed: IsDisconnectControllerType && DisconnectTarget == SpecificDevice. Drives the specific-device picker's visibility. |
DisconnectDeviceOptions |
List<MacroDisconnectDeviceOption> |
- |
Computed on read ([XmlIgnore]). Every known device on a Bluetooth path (BluetoothLinkHelper.IsDisconnectTarget). If the saved DisconnectDeviceGuid is offline or forgotten it is appended as a synthetic option, so the selection stays visible in the picker. Populates the Specific-device picker. |
Adaptive Trigger Effect Properties (#531)
For MacroActionType.TriggerEffect (IsTriggerEffectType) and MacroActionType.TriggerEffectStop (IsTriggerEffectStopType). The effects are the DSX trigger modes, encoded by the game mods' encoder (GameModProtocol.TryEncodeTrigger, #529). TriggerEffectCatalog (PadForge.App/ViewModels/TriggerEffectCatalog.cs) lists the 20 kinds, each with its mode number, label and parameters. How a held effect reaches the triggers is on Game Telemetry Internals.
| Property |
Type |
Default |
Description |
TriggerEffectSide |
MacroTriggerEffectSide |
Right |
The trigger the effect plays on, or that Stop ends: Right, Left or Both. |
TriggerEffectMode |
int |
13 |
The DSX trigger mode. 13 is the one the editor calls Feedback. A change of effect starts the values at the new effect's defaults, except while a settings file loads them (LoadTriggerEffect). |
TriggerEffectKind |
TriggerEffectKind |
- |
Computed ([XmlIgnore]): the picker's selection, the catalog entry for TriggerEffectMode. |
TriggerEffectParameters |
string |
the effect's defaults |
The values in the encoder's order, comma-separated, as the settings file stores them. A list shorter than the effect's takes the effect's defaults for the rest. |
TriggerEffectRows |
ObservableCollection<TriggerEffectParameterValue> |
- |
Computed ([XmlIgnore]): the editor's rows, one per parameter of the effect, rebuilt when the effect changes. |
TriggerEffectIsValid |
bool |
- |
Computed ([XmlIgnore]): false when the encoder refuses the values, an end zone not past its start or a second foot not past the first. The action then plays nothing and the editor says why. |
TriggerEffectHold |
MacroTriggerEffectHold |
UntilRelease |
Duration (for TriggerEffectMs), UntilRelease (until the macro's trigger is let go) or UntilStopped (until a Stop Adaptive Trigger Effect action). |
IsTriggerEffectTimed |
bool |
- |
Computed ([XmlIgnore]): TriggerEffectHold == Duration. Shows the time field. |
TriggerEffectMs |
int |
500 |
The effect's length for Duration. Clamped to 1–60,000 ms. |
MacroTriggerEffectSide and MacroTriggerEffectHold live in TriggerEffectCatalog.cs. Both are saved by name and append-only.
Supporting Enums (MacroItem.cs)
MacroTriggerMode (ordinals pinned): OnPress (0), OnRelease (1), WhileHeld (2), Always (3), CustomExpression (4, v3.2), HoldForMs (5, On Long Press), DoublePress (6), TriplePress (7), SinglePress (8, deferred single), Toggle (9), Turbo (10), ShortPress (11, On Short Press, #253. Shares TriggerHoldMs with HoldForMs to compose tap-vs-hold on one button)
MacroTriggerSource: InputDevice, OutputController, GameTelemetry (#531). The last one is offered for Custom Expression variables alone, which then read a game's telemetry in its own units. Appended, since the clipboard writes this enum as a number.
MacroRepeatMode: Once, FixedCount, UntilRelease
MacroActionType (append-only, since the clipboard serializes these as ints, so members are never reordered): ButtonPress, ButtonRelease, KeyPress, KeyRelease, Delay, AxisSet, SystemVolume, AppVolume, MouseMove, MouseButtonPress, MouseButtonRelease, MouseScroll, ToggleTouchpadOverlay (v3.2), LightbarColor / LightbarColorClear / LightbarModeSet / LightbarModeCycle (v3.1+), SetGyroEngaged (#120), Rumble / RumbleStop (v3.1+), RumbleTrigger / RumbleTriggerStop (#102), PlaySound / SoundStop (#83), MouseRecenter (#108), MouseFixPosition (#109), MouseLimitRegion (#110), DisconnectController (#162), RunProgram (launch an external program/file), TextBlock (#201, Unicode text injection), PointerModeCycle / PointerModeSet (#203, Wii pointer mode), GuideLedBrightness (#209), MoveMouseToScreenPosition (33, #9), RepeatKeyWhileHeld (34), RepeatVcButtonWhileHeld (35), ToggleVcButton (36), ToggleKey (37), GyroRecenter (38), AxisHold (39), MouseWheelTap (40), MouseNudge (41), CycleTapList (42), ToggleMouseButton (43), ToggleVcAxis (44), RepeatVcAxisWhileHeld (45), ToggleWheel (46), AxisAdd (47, #237), ComboBreak (48, #237), AxisSetLatched (49, #251), AxisLatchRelease (50, #251), AxisScale (51, #251), HeadphoneVolumeUp (52), HeadphoneVolumeDown (53), VoiceListenWhileHeld (54, #317), SwitchLayer (55, #377), SetChromaColor (56, #468), ShowDreamcastScreen (57, #469), ShowKeyboardScreen (58, #519), KeyboardScreenMode (59, #519), Stopwatch (60, #519), PanKeyboardScreen (61, #519), TriggerEffect (62, #531), TriggerEffectStop (63, #531). TriggerEffect plays a DSX trigger mode on the slot's DualSense triggers, and TriggerEffectStop ends one (see Adaptive Trigger Effect Properties). The volume pair steps DeviceSlotConfig.HeadphoneVolume by 10% and clamps at the ends, persisting like any other Audio-tab edit. SwitchLayer is one-shot: it writes the shift runtime's CustomLayer override under the Latch activator's own lock-and-version discipline, so the layer stays engaged until another switch, a Latch or Cycle transition, or a profile switch. Its parameter is MacroAction.SwitchLayerMask (default "Base") and its gate is IsSwitchLayerType. SetChromaColor has the AxisHold duration shape: Step 4b asserts its color (LightbarR, LightbarG, LightbarB) for the macro's slot on every frame the action is current, through PeripheralOutputs.AssertChromaMacro (#494). The Chroma worker paints the categories of the Razer devices assigned to that slot, every category when the Razer Chroma row is, over every Lighting tab, and lets go within 120 ms of the action ending. Two macros current in one frame resolve to the last one evaluated. Its gate is IsSetChromaColorType. The append-only rule is stated in code beside the enum.
The editor's type picker is grouped, not a flat list of these members. See MacroTypeCatalog under PadViewModel's Macros section.
MacroDisconnectTarget (#162): TriggeringDevice (0), SpecificDevice (1), SlotDevices (2), AllDevices (3). Picks the disconnect victim: the trigger's device(s), one picked device, every Bluetooth device on the pad's slot, or every Bluetooth device PadForge knows.
MacroLightbarHoldMode: Reactive, Sticky (v3.1)
MacroLightbarColorSource: Fixed, RandomHue, PaletteStep (v3.1)
MacroRumbleHoldMode: Reactive, Sticky (v3.1)
MacroMouseButton: Left, Right, Middle, X1, X2
MacroAxisTarget: None, LeftStickX, LeftStickY, RightStickX, RightStickY, LeftTrigger, RightTrigger, then the virtual DualShock 3's ten button pressures (#530, values 7 to 16, in ButtonPressureState order): PressureButtonA, PressureButtonB, PressureButtonX, PressureButtonY, PressureLeftShoulder, PressureRightShoulder, PressureDPadUp, PressureDPadDown, PressureDPadLeft, PressureDPadRight. Append-only. MacroAxisTargetNames.PressureIndex gives a target's pressure index or -1, and DisplayName gives a pressure target the pressure mapping row's label (PressureName). MacroPressureLabels.Instance serves the same labels to the axis pickers, raised again on a language change.
MacroAxisDirection: Any, Positive, Negative
MacroAxisSource: OutputController, InputDevice
MacroButtonStyle: Xbox360, DualShock4, Numbered, DualShock3 (the DualShock 4 lettering with Select and Start on Back and Start, and no Touchpad button)
File: MacroItem.cs
Checkbox for a single gamepad button. Reads/writes bits from the parent MacroAction's ButtonFlags (or CustomButtonWords for Extended slots).
| Property |
Type |
Description |
Label |
string |
Button display name. |
Flag |
ushort |
Xbox/PlayStation bitmask flag (0 for custom). |
CustomIndex |
int |
Extended button index (0-based). −1 = use Flag. |
IsChecked |
bool |
Button selected. Reads/writes parent's flags. |
| Method |
Description |
Refresh() |
Re-evaluates IsChecked after external state change. |
KeyDisplayItem
File: MacroItem.cs
Wraps a VirtualKey with a localized display name for ComboBox binding.
| Property |
Type |
Description |
Key |
VirtualKey |
The virtual key value. |
DisplayName |
string |
Localized display name. |
MacroDisconnectDeviceOption
File: MacroItem.cs
One row in the DisconnectController action's Specific-device picker (#162): a known Bluetooth-pathed device by GUID and display name.
| Property |
Type |
Description |
Guid |
Guid |
Device instance GUID. |
Name |
string |
Resolved device name. |
File: MenuEditorItem.cs
Editor VM for one radial / touch menu (#9), backing a row of PadViewModel.Menus. Wraps the LIVE MenuDefinitionEntry stored on the slot's MappingSet (write-through, like the mapping grid edits its live rows). Every mutation raises Changed, which PadViewModel forwards to ConfigItemDirtyCallback so the settings go dirty. Cells materialize lazily: the editor shows every cell position for the current shape, and a cell's entry exists in MenuDefinitionEntry.Items only once it carries a label or a binding.
Slot Context
Set on every row by PadViewModel.ApplyMenuButtonStyle. The five providers and the two callbacks are internal delegates, not bound properties.
| Member |
Type |
Description |
ButtonStyle |
MacroButtonStyle |
Button lettering, derived from the slot's output type and, on a PlayStation slot, its preset. Same derivation the macro editor uses. |
RawButtonCount |
int |
Raw button count for Extended and Nintendo slots, from ExtendedConfig.ButtonCount. Defaults to 11. |
RawProfileId |
string |
The slot's HIDMaestro profile slug, so lettered wires label their buttons correctly. |
SupportsControllerButtons |
bool |
Xbox, PlayStation, Nintendo, and Extended only. A MIDI or Keyboard+Mouse output cannot press a controller button, so its cells omit the choice rather than offering it with a warning. |
DescriptorDisplayProvider |
Func<string,string> |
Resolves a descriptor to its display name for the Custom host's subtitles. |
InputChoicesProvider |
Func<IEnumerable<InputChoice>> |
The slot's SlotAvailableInputs, for the Custom host's three pickers. |
RowBoundProvider |
Func<int,int,bool> |
Whether a cell is already bound through a mapping row, which is what the read-only RowBoundKind binding kind reports. |
MacroNamesProvider |
Func<IReadOnlyList<string>> |
The slot's macro names, live, for the #390 macro cells. |
LayerChoicesProvider |
Func<IEnumerable<ShiftLayerInfo>> |
The slot's layer choices (Any Layer, Base, every named layer, every cycle stop) for the #413 layer picker. |
StructureChanged |
Action |
Raised on a structural edit (enabled, kind, cell count, center cell). The owner forwards it to MenusStructureChanged. |
RuntimeEdit |
Func<Action, bool> |
Routes the Enabled write through InputService.TryEditMenuConfiguration. A refused edit leaves the value unchanged. |
Identity and shape
| Property |
Type |
Description |
Name |
string |
User-facing name. |
Enabled |
bool |
Menu active. |
KindIndex |
int |
0 = Radial, 1 = Grid (combo index = enum value). Switching to Grid forces HasCenter = false, both rebuild Cells. |
IsRadial |
bool |
Computed: Kind == MenuKind.Radial. Gates the Center Cell checkbox. |
IsGrid |
bool |
Computed: Kind == MenuKind.Grid (#471). Raised with IsRadial when KindIndex changes. |
ShowWebControllerRow |
bool |
Computed: IsGrid || ShowOnWebController (#471). Shows the Show on Web Controller row on a Touch Grid, and on any menu while the flag is set, so a menu switched to Radial Ring can still clear it, the layer-hold row's rule. |
ShowOnWebController |
bool |
(#471) Offer this Touch Grid menu to Web Menus phones on the slot. Write-through to the definition, reset to off through ResetSettingCommand. |
KindOptions |
IReadOnlyList<MenuIntOption> |
Style combo items (Menu_Style_* resx). Instance accessor over a static backing field on purpose: WPF {Binding} resolves against the DataContext instance and never finds static properties. |
CellCount |
int |
Cell count, clamped 1–20. Rebuilds Cells. |
HasCenter |
bool |
Radial only: adds the center cell (index 0, Steam's "Radial Menu Center Button"), selected while inside the deadzone. Rebuilds Cells. |
FireTypeIndex |
int |
Fire mode, clamped 0–3 over FireOptions (Menu_Fire_* resx): 0 On Click, 1 On Click Release, 2 On Touch Release, 3 While Hovered. A negative write (a Selector losing its items) is ignored. FireOptions swaps to the stay-open explanations while EffectiveLayerHoldsOpen is true. SelectedFireDescription is the selected option's longer explanation, or the note that a button-pair grid (IsButtonPairGrid: a grid on the Gamepad DPad or Gamepad Diamond host) ignores Fire Mode. |
EngageDeadzonePercent |
int |
Engage deadzone, clamped 1–95. |
Layer gate and stay-open (#413)
| Member |
Type |
Description |
LayerMask |
string |
The shift layer the menu is gated to. Empty and "Base" behave alike at runtime and both spellings are kept as written. A null write is ignored. Setting it to empty or "Base" also clears LayerHoldsOpen. |
LayerChoices |
ObservableCollection<ShiftLayerInfo> |
The Layer picker items from LayerChoicesProvider, plus a marked "(not listed on this slot)" entry for an authored mask the slot lacks. Reconciled in place by mask. LayerChoicesVersion bumps after each reconcile. |
HasLayerScope / HasNamedLayer |
bool |
Computed: any mask at all, and a mask that names a real layer (not "Base"). |
ShowsLayerRow / ShowLayerHold |
bool |
Computed visibility of the Layer row and the stay-open checkbox. Each also shows while LayerHoldsOpen is set, so a malformed loaded file stays visible and clearable. |
LayerHoldsOpen |
bool |
Layer Holds the Menu Open: the named layer keeps the menu open and the host surface only steers. A true write is refused without a named layer. EffectiveLayerHoldsOpen is what the runtime applies. |
HostInputLabel / HostInputTip / HostInputCaption |
string |
The host row's text: "Opens With" normally, "Steer With" in stay-open mode. |
ResetLayerCommand / ResetLayerHoldsOpenCommand |
RelayCommand |
Clear the gate (and the stay-open flag), or only the flag. |
Host surface
| Property |
Type |
Description |
HostOptions |
IReadOnlyList<MenuHostOption> |
The five built-in host surfaces: Gamepad LeftStick, Gamepad RightStick, Touchpad 0, Touchpad 1 (labels from Menu_Host_* resx, touchpads displayed 1-based as "Touchpad 1" / "Touchpad 2"), and Custom for the devices the named surfaces cannot describe (joysticks, wheels, anything not detected as a gamepad). An authored descriptor outside that grammar is appended as its own entry so the selection never silently lies. Never gated on assignment or online state, because imported profiles land on slots with nothing assigned yet. |
SelectedHost |
MenuHostOption |
Selected host. A new menu starts on the right stick (the MenuDefinitionEntry.HostDescriptor default). An empty stored descriptor reads as the first option, the left stick. Selecting a non-touchpad host resets HostHalf to 0. |
HostIsTouchpad |
bool |
Computed from SelectedHost. Shows the Pad Half row. |
IsCustomHost |
bool |
Computed: the Custom host is selected, which swaps in the CustomX / CustomY / Click pickers (CustomXChoices, CustomXSelected, CustomXSubtitle, and their Y and click twins) and their own record buttons. |
HostHalfOptions / HostHalfIndex |
list / int |
Pad Half picker for touchpad hosts: 0 Whole Pad, 1 Left Half, 2 Right Half. Clamped 0–2. |
HostRecording |
bool |
Freeform-recorder state. |
HostRecordIcon / CustomXRecordIcon / CustomYRecordIcon / ClickRecordIcon |
string |
Record-button glyphs: Stop (E71A) on the target currently recording, Record (E7C8) otherwise, mirroring the Aim Engage record button. |
PendingRecordTarget |
MenuRecordTarget |
Which of Host, CustomX, CustomY, Click the next recorded descriptor lands on. Private setter. PrepareRecord(MenuRecordTarget) sets it. |
| Method |
Description |
TryApplyRecordedHost(string) |
Folds a freeform-recorded descriptor onto a host choice: stick axis and click reads pick the stick, touchpad-family reads pick the pad. Returns false when the recorded input has no host surface (buttons, gyro, keys). |
TryApplyRecorded(MenuRecordTarget, string) |
The same fold aimed at any of the four record targets. |
PrepareRecord(MenuRecordTarget) |
Aims the next recording at one target. |
Overlay geometry
| Property |
Type |
Description |
ShowLabels |
bool |
Draw cell labels on the overlay. |
PosXPercent / PosYPercent |
int |
Overlay center as work-area percents, clamped 0–100 (50/50 = centered). |
ScalePercent |
int |
Overlay scale, clamped 10–400. |
OpacityPercent |
int |
Overlay opacity, clamped 5–100. |
Cells
| Member |
Type |
Description |
Cells |
ObservableCollection<MenuCellItem> |
One row per visible cell position: grid 0..N−1, radial (optional center 0 +) ring 1..N. |
RebuildCells() |
internal |
Rebuilds the rows for the current shape. Existing item entries keep their data. Out-of-range entries (a shrunken ring's tail, a removed center) are pruned from the definition so exports and the overlay agree with the editor. |
EnsureItem(int) / DropItemIfEmpty(...) |
internal |
Write-through for a cell edit: materializes the MenuItemDefinition on first use, drops it again when the label and both bindings are cleared. |
Per-row resets (canon: every setting row has one): ResetHostCommand (right stick, whole pad, custom descriptors cleared), ResetHostHalfCommand (whole pad), ResetStyleCommand (Radial), ResetFireCommand (On Click), ResetCellsCommand (4 cells, no center), ResetGeometryCommand (50/50, scale 100%, opacity 90%, labels on), ResetDeadzoneCommand (25), plus ResetCustomXCommand / ResetCustomYCommand / ResetClickCommand for the Custom host's three inputs.
File: MenuEditorItem.cs
One cell row in the menu editor: a label, an optional icon, and one direct binding. The backing MenuItemDefinition reference is null until the cell has content.
| Property |
Type |
Description |
Index / IsCenter |
int / bool |
Cell position. Header renders "Center" or the localized "Cell {n}". |
Label |
string |
Cell label. Setting it materializes the item entry, clearing it drops an otherwise-empty entry. |
HasIcon / IconName / IconImage / ShowIconGlyph / IsGlyphIcon |
bool / string / ImageSource / bool / bool |
Cell icon: the stored reference, the resolved image, the picture-glyph fallback used when a picture reference resolves to nothing, and (#471) whether the icon is an emoji, which the row shows as itself and never resolves as an image. Written through the internal SetIcon(string), which the shared icon picker calls. An empty reference clears the icon and prunes an otherwise-empty item. |
IconScalePercent |
int |
Per-cell icon size as a percent of the menu's normal icon box (#413), clamped 25–200. Reads 100 and ignores writes while the cell has no icon. Reset through ResetSettingCommand. |
BindingKind |
int |
Derived from which field is set: 0 none, 1 keyboard key, 2 VC button (Xbox mask on Xbox and PlayStation slots, 1-based raw button number on Extended slots, per the owner's button style), 3 RowBoundKind (no direct binding but bound through mapping rows, a read-only sentinel, and the cell still fires as a menu-item source), 4 MacroKind (#390). Selecting a kind seeds a default and zeroes the others. |
BindingKindOptions |
IReadOnlyList<MenuIntOption> |
Built per read, dynamic per slot type. None and Keyboard Key everywhere. Controller Button only where the slot's output can press one, or as a marked "unsupported" entry when a slot-type switch left a stale button binding, so the selection never lies. Macro only while the slot has macros, or when the cell already carries a name. RowBoundKind appears only while that is the current kind. |
ShowKeyPicker / ShowButtonPicker / ShowMacroPicker |
bool |
Computed from BindingKind (1 / 2 / 4). |
SelectedKeyVk |
int |
Virtual-key code. KeyOptions offers the full VirtualKey vocabulary the macro editor's Key Press offers, with the same localized labels. VirtualKey.None is dropped: a cell with no key is an empty cell, cleared by its reset button, never by a "None" pick. |
SelectedButtonFlag |
int |
The Xbox button mask on Xbox and PlayStation slots, or the 1-based raw button number on a numbered-style slot. Labels come from the macro editor's MacroButtonNames tables. After a slot-type switch the stored value in the other space is shown through the shared mask-to-number mapping. |
SelectedMacroName |
string |
The macro this cell fires, by name (#390). |
MacroOptions |
IReadOnlyList<MenuHostOption> |
The slot's macro names from MacroNamesProvider. A name the slot no longer declares stays in the list under a "missing" label rather than vanishing, and the runtime treats the stale name as an inert no-op. |
ResetCellCommand |
RelayCommand |
Clears the icon (and its size), the label, and the binding, which drops the item entry. |
Cell Icons and Icon Packages (#390)
A cell's Icon is one reference string, resolved by PadForge.Common.MenuIconResolver.Resolve into a frozen ImageSource. Three shapes, tried in that order, with misses cached as null so a menu rebuild never re-probes the disk for a known-absent file:
pficon://Package/entry, an entry inside a registered icon package.
- A loose image path, exe-relative or absolute. The shape test wants a directory separator or a drive colon plus one of the pack image extensions. Existence is the loader's problem.
- A bare Steam binding-icon name, resolved under the Steam install.
PadForge.Common.IconPackageManager owns the packages. A package is a .pficons file: a zip carrying the images and an optional manifest.json display name. Recognized image formats are .png, .jpg, .jpeg, .bmp, .gif, one list shared by the pack probe, the entry lister, and the editor's loose-file gate, so every surface agrees on what an icon is.
| Member |
Description |
Scheme / FileExtension |
"pficon://" and ".pficons". |
Packages |
The registry: PackageRef rows of Name and Path. The stored path is relative to the exe directory when the pack lives under it, absolute otherwise. |
RegistryChanged |
Raised on any registry change. MenuIconResolver subscribes and drops its whole cache, since a registration, rename, or removal changes what pficon:// references resolve to and misses are cached. |
Register(string filePath) / Register(string, out string probedName) |
Adds a pack, returning its registered name. |
Unregister(string name) |
Removes a pack. |
LoadRegistry(...) / SaveRegistry() |
Settings round-trip. |
IsPackageRef / MakeRef / TryParseRef / DisplayName |
Reference-string grammar. |
ResolvePackageFile / ResolvePath / MakeStoredPath |
Path resolution against the exe directory. |
TryReadIcon(string iconRef) |
The bytes behind one reference. |
ListIcons(string packageName) / ListIconsInFile(string filePath) |
Entry names, for the picker. |
ExportPackage(string destFilePath, string displayName, IEnumerable<string> imageFiles) |
Builds a pack from loose images. Entry names are the source file names, with a " (2)" suffix on same-named files from different folders. It writes to a temp file and moves it into place, so a mid-write failure never leaves a truncated pack at the destination. |
File: MenuEditorItem.cs
MenuHostOption is one pickable host surface (Descriptor, Label, IsTouchpad), and it is also reused for the macro picker's rows. MenuIntOption is a generic labeled int option used by the style, half, fire, binding-kind, and button combos (Value, Label, Description). The key picker reuses SocdKeyOption from KbmSlotConfig instead. Both option types override ToString() with the label.
ProfileShortcutViewModel
File: ProfileShortcutViewModel.cs
Wraps a GlobalMacroData instance for data binding in the ProfilesPage shortcuts card. One ViewModel per shortcut row.
Constructor
public ProfileShortcutViewModel(
GlobalMacroData data,
Action<ProfileShortcutViewModel> deleteCallback,
Action<ProfileShortcutViewModel> saveCallback)
deleteCallback removes the row. saveCallback triggers a settings save via SettingsService.MarkDirty().
Switch Mode
| Property |
Type |
Description |
SwitchMode |
SwitchProfileMode |
Two-way bound to mode ComboBox. Setter writes through to Data.SwitchMode and raises IsSpecificMode. |
IsSpecificMode |
bool (computed) |
true when SwitchMode == Specific. Controls visibility of the target profile dropdown. |
SwitchModes |
ObservableCollection<SwitchProfileModeItem> |
Dropdown items: Next, Previous, Specific, ToggleWindow, ToggleVCsDisabled (v3.2). |
SwitchProfileModeItem is a class extending ObservableObject with a read-only Mode and a mutable DisplayName that updates in place on culture change. ToString() returns DisplayName so ComboBox display refreshes on language switch without rebuilding the list.
Target Profile (Specific mode)
Selection binds to a stable ID, not a display name. The previous name-keyed properties were removed: rebuilding a fresh ObservableCollection<string> on every culture refresh replaced the ComboBox ItemsSource instance, which cleared SelectedItem and wrote null back through the binding, wiping Data.TargetProfileId / TriggerDeviceGuid / SwitchMode. The persistent-collection + stable-ID-selection shape fixes that.
| Member |
Type |
Description |
TargetProfileId |
string |
Two-way selection target. Empty string is the localized "Default" sentinel. Any other value is a ProfileEntry.Id. Getter reads Data.TargetProfileId ?? "". Setter writes through (empty maps to null) and invokes the save callback. |
ProfileChoices |
ObservableCollection<ProfileChoice> |
Persistent dropdown items: the "Default" sentinel plus one per SettingsManager.Profiles entry. |
RebuildProfileChoices() |
method |
Rebuilds ProfileChoices in place from SettingsManager.Profiles. The page's DropDownOpened handler calls it so newly-saved / deleted profiles surface without tearing down the shortcut row. |
Trigger Device
| Member |
Type |
Description |
TriggerDeviceGuid |
Guid |
Two-way selection target. Guid.Empty is the localized "Any Connected Device" sentinel (Profiles_ShortcutDevice_Any). Getter/setter proxy Data.TriggerDeviceGuid and invoke the save callback on change. |
DeviceChoices |
ObservableCollection<DeviceChoice> |
Persistent dropdown items: the "Any Connected Device" sentinel plus one per online, named SettingsManager.UserDevices entry. |
RebuildDeviceChoices() |
method |
Rebuilds DeviceChoices in place from SettingsManager.UserDevices. Called from the page's DropDownOpened handler so newly-connected / disconnected devices surface without a row teardown. |
ProfileChoice and DeviceChoice are the same mutable-wrapper pattern as SwitchProfileModeItem: a stable key (ProfileId / DeviceGuid) plus a DisplayName that updates in place on culture change, with ToString() returning DisplayName. Selection is unaffected by a language switch because no item is added or removed.
| Property |
Type |
Description |
ButtonComboDisplay |
string (computed) |
Human-readable trigger combo. Joins entries with " + ", resolving each to a friendly name (e.g., "A (Xbox Controller) + LT+ (Xbox Controller)"). During recording, appends countdown. |
Name resolution helpers:
- ResolveButtonName(int index, Guid deviceGuid). Gamepad-type devices use A, B, X, Y, then the localized Left Shoulder, Right Shoulder, Back, Start, Left Stick Button, Right Stick Button, and Guide for indices 0–10. Keyboard devices resolve via the VirtualKey enum. Others fall back to Macro_Btn_Format, "Btn N" with N one-based.
- ResolveAxisName(int index, Guid deviceGuid, AxisTriggerDirection direction). Gamepad axes use LX/LY/LT/RX/RY/RT names with +/– suffix. Others use "Axis N+/–".
- ResolveDeviceName(Guid deviceGuid). Returns ResolvedName from UserDevices, or null for Guid.Empty.
Recording State
| Property |
Type |
Description |
IsRecording |
bool |
true during Learn mode. Raises LearnButtonText and LearnButtonIcon. |
RecordingCountdown |
int |
Seconds remaining. Raises ButtonComboDisplay on change. |
LearnButtonText |
string (computed) |
"Recording..." (Profiles_ShortcutLearning) or "Record" (Profiles_ShortcutLearn) |
LearnButtonIcon |
string (computed) |
\uE71A (Stop) or \uE7C8 (Record) |
| Method |
Description |
SetLearnedButtons(TriggerButtonEntry[] entries) |
Saves captured entries to Data.TriggerEntries, clears recording state, invokes save callback. |
CancelRecording() |
Clears IsRecording without saving. |
NotifyComboChanged() |
Raises ButtonComboDisplay for live display during recording. |
Commands
| Command |
Action |
DeleteCommand |
Invokes _deleteCallback. Removes this shortcut from the list. |
ClearCommand |
Sets Data.TriggerEntries = null, updates display, saves. |
StickConfigItem
File: StickConfigItem.cs
One thumbstick section in the Sticks tab. Gamepad presets: 0 = Left, 1 = Right. Custom Extended: 0..N per ThumbstickCount.
Identity
| Property |
Type |
Description |
Title |
string |
Display title (e.g., "Left Thumbstick", "Stick 1"). Read-only. |
Index |
int |
Stick index (0-based). Read-only. |
AxisXIndex |
int |
Raw axis index for X in RawHidState.Axes (custom Extended only, -1 for gamepad). Read-only. |
AxisYIndex |
int |
Raw axis index for Y in RawHidState.Axes (custom Extended only, -1 for gamepad). Read-only. |
IsPointerStick |
bool |
Set at construction. True on a KBM slot, where stick 0 is mouse movement and stick 1 the scroll wheel. Those are rate outputs, so the speed multiplier row applies there and only there. |
IsMouseStick |
bool |
Set at construction. True only for the KBM slot's stick 0. Gates the Momentum rows, since a coasting scroll wheel is kinetic scrolling and out of #291's scope. |
Speed and Momentum
| Property |
Type |
Default |
Range |
Description |
Sensitivity |
double |
1.0 |
0.1–5.0 |
Flat speed multiplier. Live for the pointer sticks above only. On a gamepad stick the per-axis response curves own the shaping. Reset: ResetStickSensitivityCommand. |
MomentumEnabled |
bool |
false |
- |
Stick trackball (#291): flick the mouse stick and release, and the cursor keeps traveling, coasting to a stop on the same constant-deceleration physics the touchpad's Momentum uses. |
MomentumGlide |
double |
0.90 |
0.80–1.00 |
The coast's friction band, identical to the touchpad's Momentum Glide. 0.80 full friction, 1.00 frictionless. |
Reset commands: ResetMomentumCommand, ResetMomentumGlideCommand.
Deadzone Configuration
All percentage properties clamped to 0–100. Each has a *Digit companion for digit-based binding (signed 16-bit: +/−32768).
| Property (%) |
Digit Property |
Default |
Description |
DeadZoneX |
DeadZoneXDigit |
0 |
X deadzone percentage. |
DeadZoneY |
DeadZoneYDigit |
0 |
Y deadzone percentage. |
AntiDeadZoneX |
AntiDeadZoneXDigit |
0 |
X anti-deadzone percentage. |
AntiDeadZoneY |
AntiDeadZoneYDigit |
0 |
Y anti-deadzone percentage. |
MaxRangeX |
MaxRangeXDigit |
100 |
X max range (positive), 1–100. |
MaxRangeY |
MaxRangeYDigit |
100 |
Y max range (positive), 1–100. |
MaxRangeXNeg |
MaxRangeXNegDigit |
100 |
X max range (negative), 1–100. |
MaxRangeYNeg |
MaxRangeYNegDigit |
100 |
Y max range (negative), 1–100. |
CenterOffsetX |
CenterOffsetXDigit |
0 |
X center offset, −100 to 100. |
CenterOffsetY |
CenterOffsetYDigit |
0 |
Y center offset, −100 to 100. |
Linear |
- |
0 |
Linear interpolation factor (0–100). |
Deadzone Shape
| Property |
Type |
Default |
Description |
DeadZoneShape |
DeadZoneShape |
ScaledRadial |
Shape enum. Notifies DeadZoneShapeIndex and Is*Shape properties. |
DeadZoneShapeIndex |
int |
0 |
ComboBox SelectedIndex. Maps display order to enum. |
IsAxialShape |
bool |
- |
Computed. |
IsRadialShape |
bool |
- |
Computed: Radial or ScaledRadial. |
IsSlopedShape |
bool |
- |
Computed: SlopedAxial or SlopedScaledAxial. |
IsHybridShape |
bool |
- |
Computed. |
HasSlopedWedges |
bool |
- |
Computed: SlopedAxial, SlopedScaledAxial, or Hybrid. |
Display order: ScaledRadial(0), Radial(1), Axial(2), Hybrid(3), SlopedScaledAxial(4), SlopedAxial(5).
Sensitivity Curves
| Property |
Type |
Default |
Description |
SensitivityCurveX |
string |
"0,0;1,1" |
X-axis sensitivity curve (control point string). |
SensitivityCurveY |
string |
"0,0;1,1" |
Y-axis sensitivity curve. |
PresetNameX |
string |
- |
Computed: matched preset name for X curve. |
PresetNameY |
string |
- |
Computed: matched preset name for Y curve. |
| Static |
Description |
CurvePresetNames |
string[] of available curve preset display names. Rebuilt on culture change. CurvePresetChoices is its instance accessor for XAML. |
Calibration
| Property |
Type |
Default |
Description |
IsCalibrating |
bool |
false |
Center calibration in progress. |
HardwareRawX |
short |
- |
Unprocessed hardware X value (not affected by offset/deadzone). |
HardwareRawY |
short |
- |
Unprocessed hardware Y value. |
| Method |
Description |
StartCalibration() |
Samples RawX/RawY over ~0.5 s (15 frames at 33 ms). Sets CenterOffsetX/Y to negate drift. |
Boundary Calibration (#174)
Measures the stick's real physical boundary by sweeping the rim, then reshapes it toward a circle at runtime (Step 3 warp). Offered only for the two primary physical thumbsticks the warp covers.
| Property |
Type |
Default |
Description |
BoundaryMap |
string |
"" |
Serialized measured boundary (StickBoundary format). Empty = uncalibrated, no reshaping. The setter rebuilds the radar overlay and circularity readout. |
HasBoundaryCalibration |
bool |
- |
Computed: BoundaryMap is non-empty. |
SupportsBoundaryCalibration |
bool |
- |
Set at construction. True for the Xbox/PlayStation two-stick grid and Extended primary sticks 0/1. False for KBM pseudo-sticks and Extended custom sticks 2+. |
IsCalibratingBoundary |
bool |
false |
A boundary sweep is in progress. Notifies BoundaryButtonText. |
BoundarySectorsRemaining |
int |
0 |
Live count of rim sectors still uncovered during a sweep. Notifies BoundaryButtonText. |
BoundaryButtonText |
string |
- |
Computed button caption: Calibrate / Recalibrate idle prompt, the "N sectors left" countdown, or the second-lap prompt. |
BoundaryPolygonPoints |
PointCollection |
empty |
Measured boundary as a convex polygon in the 200×200 radar plot. |
BoundaryCircularityText |
string |
"" |
Computed circularity readout for the plot. |
| Command |
Description |
CalibrateBoundaryCommand |
Starts (or, if sweeping, commits) a boundary sweep. |
ResetBoundaryCommand |
Cancels any sweep and clears BoundaryMap. |
| Method |
Description |
StartBoundaryCalibration() |
Runs a coverage-driven ~60 Hz rim sweep off HardwareRawX/Y, auto-completing once every sector is covered and at least two full rotations have swept (30 s cap). Clicking again while sweeping commits early. |
Live Preview
| Property |
Type |
Default |
Description |
LiveX |
double |
0.5 |
Live X (0.0–1.0 for Canvas). |
LiveY |
double |
0.5 |
Live Y. |
RawX |
short |
0 |
Processed raw X. Notifies RawDisplay. |
RawY |
short |
0 |
Processed raw Y. |
RawDisplay |
string |
- |
Computed: "X: -1234 (50.0%) Y: 5678 (58.7%)". |
LiveInputX |
double |
0 |
CurveEditor X input (0–1). |
LiveInputY |
double |
0 |
CurveEditor Y input. |
RawPosX / RawPosY |
double |
0.5 |
Raw-stage dot position (0–1, before the pipeline) for the two-stage plot (#175). LiveX / LiveY hold the processed stage. Notifies InDisplay. |
InDisplay |
string |
- |
Computed: the raw stage in the RawDisplay format. |
Steering Mode (#94)
Per-stick steering source kind and its tunables. The kind feeds the matching Param* fields on the stick's MappingSet rows at build time.
| Property |
Type |
Default |
Description |
SteeringModeIndex |
int |
0 |
0 Linear (Direct), 1 WindingStick, 2 AngleToAxisX, 3 AngleToAxisY. Clamped 0–3. SteeringKind is the matching MappingSource.Kind, and SetSteeringKind(string) sets the index from one on load (an unknown kind falls back to Linear). |
IsSteeringActive / IsWindingMode / IsAngleMode |
bool |
- |
Computed from the index. |
WindRangeDeg |
double |
900 |
Winding range in degrees, clamped 90–2520. |
WindPower |
double |
1 |
Winding power, clamped 0–4. |
WindUnwindRate |
double |
1800 |
Unwind rate, clamped 0–10000. |
AngleInnerDz / AngleOuterDz |
double |
0 / 10 |
Angle-mode inner and outer deadzones in degrees, clamped 0–89. |
Reset Commands
ResetAllCommand (shape, offsets, deadzones, anti-deadzones, linear, speed, curves, ranges, and steering back to their defaults, the boundary map cleared, and any running center or boundary calibration stopped. Momentum keeps its values), ResetDeadZoneShapeCommand, ResetCenterOffsetXCommand, ResetCenterOffsetYCommand, ResetDeadZoneXCommand, ResetDeadZoneYCommand, ResetAntiDeadZoneXCommand, ResetAntiDeadZoneYCommand, ResetLinearCommand, ResetStickSensitivityCommand, ResetSensitivityXCommand, ResetSensitivityYCommand, ResetMaxRangeXCommand, ResetMaxRangeYCommand, ResetMaxRangeXNegCommand, ResetMaxRangeYNegCommand, ResetSteeringModeCommand, ResetWindRangeCommand, ResetWindPowerCommand, ResetWindUnwindRateCommand, ResetAngleInnerDzCommand, ResetAngleOuterDzCommand
Static Methods
| Method |
Description |
ApplyCurve(double, string) |
Internal. Applies the spline LUT curve to a magnitude. Used by the preview and Extended processing. |
TriggerConfigItem
File: TriggerConfigItem.cs
One trigger section in the Triggers tab. Gamepad presets: 0 = Left, 1 = Right. Custom Extended: 0..N per TriggerCount.
Identity
| Property |
Type |
Description |
Title |
string |
Display title (e.g., "Left Trigger", "Trigger 1"). Read-only. |
Index |
int |
Trigger index (0-based). Read-only. |
AxisIndex |
int |
Raw axis index in RawHidState.Axes (custom Extended only, -1 for gamepad). Read-only. |
Configuration
All percentage properties clamped to 0–100 (except MaxRange: 1–100). Each has a *Digit companion for digit-based binding (unsigned 16-bit: 0–65535).
| Property (%) |
Digit Property |
Default |
Description |
DeadZone |
DeadZoneDigit |
0 |
Deadzone %. |
MaxRange |
MaxRangeDigit |
100 |
Max range % (1–100). |
AntiDeadZone |
AntiDeadZoneDigit |
0 |
Anti-deadzone %. |
Sensitivity Curve
| Property |
Type |
Default |
Description |
SensitivityCurve |
string |
"0,0;1,1" |
Sensitivity curve (control point string). |
PresetName |
string |
- |
Computed: matched preset name for the curve. |
| Static |
Description |
CurvePresetNames |
string[] of available curve preset display names. Rebuilt on culture change. CurvePresetChoices is its instance accessor for XAML. |
Live Preview
| Property |
Type |
Default |
Description |
LiveValue |
double |
0 |
Processed trigger value (0.0–1.0). |
RawValue |
ushort |
0 |
Processed raw value. Notifies RawDisplay. |
RawDisplay |
string |
- |
Computed: formatted display "32768 (50.0%)". |
LiveInputForCurve |
double |
0 |
Live input for CurveEditor binding. |
RawNorm |
double |
0 |
Raw-stage value (0–1, before the pipeline) for the two-stage bars (#175). |
OutDisplay |
string |
- |
Computed: LiveValue in the RawDisplay format, for the OUT readout. |
Reset Commands
ResetAllCommand, ResetRangeCommand (resets DeadZone+MaxRange), ResetAntiDeadZoneCommand, ResetSensitivityCommand
ExtendedSlotConfig
File: ExtendedSlotConfig.cs
Per-slot Extended-controller configuration. Drives stick/trigger/POV/button counts, the HID descriptor handed to HIDMaestro for Extended slots, and mapping generation.
| Constant |
Value |
Description |
MaxAxes |
8 |
DirectInput max axis count (shared between sticks and triggers). |
| Property |
Type |
Default |
Range |
Description |
Customize |
bool |
false |
- |
Master toggle for the override fields. When false, the VC is built from the catalog HM profile with no customizations (Product String, layout counts, OEM-name override are ignored even if they hold values). When true, each sub-field is applied on top of the catalog profile via HMProfileBuilder. |
ThumbstickCount |
int |
2 |
0–(MaxAxes - TriggerCount) / 2 |
Thumbsticks (2 axes each). Applied only when Customize is true. |
TriggerCount |
int |
2 |
0–MaxAxes - ThumbstickCount * 2 |
Triggers (1 axis each). Applied only when Customize is true. |
PovCount |
int |
1 |
0–4 |
POV hat switches. Applied only when Customize is true. |
ButtonCount |
int |
11 |
0–128 |
Buttons. Applied only when Customize is true. |
TotalAxes |
int |
- |
- |
Computed: ThumbstickCount * 2 + TriggerCount (max 8). |
MaxThumbsticks |
int |
- |
- |
Computed: max sticks given current triggers. |
MaxTriggers |
int |
- |
- |
Computed: max triggers given current sticks. |
OemNameOverride |
bool |
false |
- |
Whether this slot claims the Windows DirectInput OEM-name table entry for its profile's VID:PID at create time, using ProductString as the label. Customize-gated. |
ProductString |
string |
"" |
- |
Custom product string applied to the HID descriptor when Customize is true. |
VendorId |
int |
0 |
- |
VID override. 0 means "use the active profile's VID" (the box then shows the profile value). Customize-gated. Applied at profile-build time via HMProfileBuilder.Vid. |
ProductId |
int |
0 |
- |
PID override. 0 means "use the active profile's PID". Customize-gated. Applied via HMProfileBuilder.Pid. |
ForceFeedbackEnabled |
bool |
true |
- |
Whether the HID PID 1.0 force-feedback block is appended to the descriptor. Customize-gated. Toggling on a live VC forces a destroy + recreate because HIDMaestro bakes the descriptor at create time. |
| Method |
Description |
ComputeAxisLayout(out int[], out int[], out int[]) |
Computes interleaved axis indices per group. |
ResetToDefaults() |
Resets every field to its fresh-install default in place. Never replaces the instance (MainWindow's autosave hook binds to this object's PropertyChanged). Triggers drop to 0 first so the stick default isn't clamped away by the shared-axis budget. |
The v2 ExtendedPreset enum (Xbox360 / DualShock4 / Custom) and the ApplyPresetDefaults() method that paired with it were dropped in v3 (commit 824b4224). v3 picks layouts from HIDMaestro.Core's embedded catalog instead. HIDMaestro 2.1.0 ships 337 profiles in 59 vendor folders, and PadForge's pickers offer 239 of the 240 that carry a captured HID descriptor (HMaestroProfileCatalog keeps only IsDeployable profiles and withholds the GameCube adapter's Full persona). Customize is the single boolean that gates user overrides on top of the catalog profile.
ExtendedSlotConfigData
Serializable DTO for persisting in PadForge.xml. All properties have [XmlAttribute].
| Property |
Type |
Default |
SlotIndex |
int |
0 |
Customize |
bool |
false |
ThumbstickCount |
int |
2 |
TriggerCount |
int |
2 |
PovCount |
int |
1 |
ButtonCount |
int |
11 |
OemNameOverride |
bool |
false |
ProductString |
string |
"" |
VendorId |
int |
0 |
ProductId |
int |
0 |
ForceFeedbackEnabled |
bool |
true |
VendorId / ProductId default to 0, meaning "use the active profile's VID/PID" (no override).
ForceFeedbackEnabled defaults to true so v3.0.0/v3.0.1/v3.0.2 PadForge.xml files (which never wrote this attribute) deserialize with FFB enabled. Customize-gated like the layout overrides: only honored when Customize == true.
Older PadForge.xml files written by v2 contained a Preset attribute (Xbox360 / DualShock4 / Custom). v3 ignores it on read since the ExtendedPreset enum was removed in commit d57a725. Equivalent layouts in v3 come from the active HIDMaestro profile.
MidiSlotConfig
File: MidiSlotConfig.cs
Per-slot MIDI output configuration: CC/note counts, starting numbers, channel, velocity, and the legacy API's output port.
| Property |
Type |
Default |
Range |
Description |
Channel |
int |
1 |
1–16 |
MIDI channel (1-based). |
CcCount |
int |
6 |
0–128 - StartCc |
CC output count. |
StartCc |
int |
1 |
0–127 |
Starting CC number. Re-clamps CcCount. |
NoteCount |
int |
11 |
0–128 - StartNote |
Note output count. |
StartNote |
int |
60 |
0–127 |
Starting note number. Re-clamps NoteCount. |
Velocity |
byte |
127 |
1–127 |
Note velocity for button presses. A 0 reads as 1, because a Note On with velocity 0 is a Note Off. |
OutputPort |
string |
"" |
A port name |
The output port the slot sends to under the legacy MIDI API, by the name the slot's port picker shows. Empty for None, and until the user picks one. The Windows MIDI Services APIs create the slot's own port and ignore it. |
OutputPortChosen |
bool |
false |
- |
Whether OutputPort, None included, is a choice for the profile in use: picked or reset in the slot's port picker, or applied from a snapshot that carries one. |
OutputPortSnapshot |
string |
- |
- |
Computed: the port as a snapshot records it. OutputPort while OutputPortChosen, else null. |
| Method |
Description |
GetCcNumbers() |
Returns int[] of sequential CC numbers from StartCc. |
GetNoteNumbers() |
Returns int[] of sequential note numbers from StartNote. |
ChooseOutputPort(string) |
Makes a port the slot's port as the user's choice: sets OutputPort, empty for None, and OutputPortChosen. |
ResetToDefaults() |
Resets every field in place, starts before counts so the count clamps see the default starts. Clears OutputPort and OutputPortChosen. |
MidiSlotConfigData
Serializable DTO. All properties have [XmlAttribute].
| Property |
Type |
Default |
SlotIndex |
int |
0 |
Channel |
int |
1 |
CcCount |
int |
6 |
StartCc |
int |
1 |
NoteCount |
int |
11 |
StartNote |
int |
60 |
Velocity |
byte |
127 |
OutputPort |
string |
null |
OutputPort is written whenever the snapshot records a choice, None as an empty attribute, and left out when it records none (ShouldSerializeOutputPort). ApplyOutputPortTo(MidiSlotConfig) applies a recorded choice through ChooseOutputPort. A snapshot with no choice keeps the slot's current port and clears OutputPortChosen, so a snapshot saved before a port was picked keeps the slot's port.
DeviceSlotConfig
File: DeviceSlotConfig.cs
Per-(slot, device) output configuration. Renamed from PlayStationSlotConfig in commit 3fd97c89. It is not PlayStation-only. Drives the Adaptive Triggers and Lighting tabs, the Lighting tab of a lit mouse, keyboard or vendor row included (#494). Held per physical device on a slot (PadViewModel.PerDeviceSlotConfigs), so two devices on one slot each carry their own config. Parallel to ExtendedSlotConfig and MidiSlotConfig: ObservableObject with a paired [XmlAttribute] data record.
Adaptive Triggers (per trigger)
| Property |
Type |
Default |
Description |
LeftTriggerMode / RightTriggerMode |
AdaptiveTriggerMode |
Off |
Effect mode. Off reverts to the standard linear response. |
LeftStartPosition / RightStartPosition |
byte |
0 |
Start of the pull range the effect targets. |
LeftEndPosition / RightEndPosition |
byte |
255 |
End of the pull range (full travel). |
LeftStrength / RightStrength |
byte |
200 |
Effect force (0–255). |
LeftFrequency / RightFrequency |
byte |
10 |
Vibration frequency for the Vibration modes. |
Lightbar base color
| Property |
Type |
Default |
Description |
LightbarRed / LightbarGreen |
byte |
0 |
Base RGB red / green channel. |
LightbarBlue |
byte |
0xFF |
Base blue channel. Fresh slot lights blue (Sony player-1 color). |
LightbarEnabled |
bool |
false |
Legacy toggle kept for XML round-trip. Nothing reads it at runtime. On a save with LightingRev 0 whose LightbarMode is Off and whose AudioLightbarEnabled is false, true migrates to LightbarMode.Static on load. |
Audio mirror / speaker passthrough (#83)
| Property |
Type |
Default |
Description |
AudioPassthroughEnabled |
bool |
false |
Mirror the system audio to this pad's built-in speaker (per device). |
AudioMirrorSourceId |
string |
"" |
MMDevice ID of the render endpoint to capture. Empty = the system default. |
Synthetic touchpad pressure (#239)
| Property |
Type |
Default |
Description |
TouchpadSyntheticPressure |
bool |
false |
Synthesize a pressure reading on pads whose hardware reports none (DualShock 4, DualSense, Steam Controller 2015). No touch reads 0, a resting touch reads TouchpadSyntheticTouchPercent, a pad click reads 100%. Valve pads with true analog pressure keep their raw readings while this is off. |
TouchpadSyntheticTouchPercent |
int |
50 |
The touch stop of that curve, percent of full pressure a resting unclicked touch reads. Clamped 0-100. |
Haptic mirror engage gate (#185)
Applies to haptic-tone sinks only (Joy-Con, Switch Pro, Steam family). Sony/Wii speaker mirrors stay ungated.
| Property |
Type |
Default |
Description |
AudioMirrorEngageMode |
string |
"Always" |
Always, Input (while an input is held), or Rumble (while game vibration is active). |
AudioMirrorEngageDeviceGuid |
string |
"" |
Device carrying the engage input for Input mode. |
AudioMirrorEngageButton |
string |
"" |
Input descriptor held to engage in Input mode. |
AudioMirrorEngageReleaseMs |
int |
500 |
How long the mirror keeps playing after the engage source drops (ms). |
High-tone filter (#202)
Filters the single (pitch, amplitude) pair the haptic-tone sinks reduce everything to, upstream of every family encoder.
| Property |
Type |
Default |
Description |
AudioToneFilterMode |
string |
"Off" |
Off, Cut (silence above the limit), or Fold (octave-halve into the pass band). |
AudioToneLimitHz |
int |
800 |
Ceiling for Cut / Fold in Hz. |
Persona haptics (#271 item 1)
| Property |
Type |
Default |
Description |
AudioPersonaHapticsEnabled |
bool |
false |
Render the virtual DualSense's authored haptic audio (persona UAC channels 3/4) on this device's actuators through the haptic-tone chain. Off by default because the derived tones only approximate the designer's track. While it is off the device takes the track as rumble instead (PersonaHapticRumble, #522). |
AudioPersonaHapticsGain |
int |
100 |
Input gain percent (25–300) applied before the tone reducer, so a quiet authored track can still reach the actuators. |
Reset commands: ResetPersonaHapticsCommand, ResetPersonaHapticsGainCommand (both on PadViewModel, alongside the Audio tab's other row resets).
Steam Controller 2026 PCM haptics (#381)
| Property |
Type |
Default |
Description |
AudioTritonLowPassHz |
int |
250 |
Low-pass cutoff in Hz for the Steam Controller 2026 PCM haptic stream, clamped 60-1000. Past roughly 250 Hz the pad's actuators start behaving like small speakers. 250 is one requester's hardware-measured threshold on their own unit, not a device specification, which is why it stays tunable. |
Audio DSP chain (#347)
Per (slot, device) like the rest of the bag, never gated on OutputType. The chain runs on the AudioPassthroughService sink only, upstream of the Opus encoder. Edited from the PadViewModel.AudioDsp.cs cards.
| Property |
Type |
Default |
Description |
AudioCrossfeedLevel |
int |
0 |
bs2b crossfeed level, clamped 0–9: 0 off, 1–3 crossfeed, 4–6 easy crossfeed, 7 Jan Meier, 8 the bs2b default, 9 custom. Skipped on the speaker paths, which carry a mono downmix. |
AudioCrossfeedIsCustom |
bool |
- |
Computed: level 9. Shows the two custom knobs. |
AudioCrossfeedCutHz |
int |
700 |
Custom crossover cutoff, clamped 300–2000 (libbs2b's BS2B_MINFCUT / BS2B_MAXFCUT). |
AudioCrossfeedFeedDb |
double |
4.5 |
Custom feed level in dB, clamped 1.0–15.0 (libbs2b's BS2B_MINFEED / BS2B_MAXFEED in tenths). |
AudioEqEnabled |
bool |
false |
Master switch for the parametric EQ, separate from an empty band list so a tuned EQ can be muted without being lost. |
AudioEqBands |
string |
"" |
The band list as one attribute encoded by EqBandCodec, bridged to grid rows by PadViewModel.AudioDsp.cs. |
AudioEqPreampDb |
double |
0 |
Preamp in dB applied before the EQ, clamped −30..12. AutoEq imports carry their negative preamp through here. |
AudioLimiterEnabled |
bool |
true |
On by default, since a positive EQ band without a limiter clips the Opus encoder. |
AudioLimiterCeiling |
int |
98 |
Ceiling as a percent of full scale, clamped 5–100. |
Headphone jack and audio path
| Property |
Type |
Default |
Description |
HeadphoneVolume |
int |
100 |
Headphone-jack hardware volume percent, clamped 0–100. Maps onto the DS5 output report's VolumeHeadphones byte over Sony's own scePad window. 0% writes 0x00. Owned by the Audio-tab card and the HeadphoneVolumeUp / HeadphoneVolumeDown macro actions. On a Bluetooth DualShock 4 (#528) the same percent maps onto 0x00 to 0x4F for its left and right headphone bytes. |
AudioOutputPath |
AudioOutputPath |
Automatic |
Where the DualSense plays its audio (output report byte 7 bits 4–5). On a Bluetooth DualShock 4 (#528) it picks the stream's target, 0x24 for the two headphone routes and 0x02 otherwise, and which output's volume the route mutes. |
Ds5AudioBufferLength |
int |
48 |
DualSense Bluetooth audio buffer length (#314): the packet 0x11 header byte governing the transport that carries speaker audio, haptics, and microphone capture together. Clamped 16–255, defaulting to AudioPassthroughService.Ds5AudioBufferLengthDefault. Lower is less delay and more dropout risk. |
| Command |
Description |
ResetHeadphoneVolumeCommand |
Back to 100. |
ResetDs5AudioBufferLengthCommand |
Back to the 48 default. |
ResetAudioOutputPathCommand |
Back to Automatic. |
Macro lightbar override (#63)
Transient runtime state set by MacroActionType.LightbarColor. Not persisted ([XmlIgnore]).
| Member |
Type |
Default |
Description |
MacroOverrideR / MacroOverrideG / MacroOverrideB |
byte |
0 |
Override RGB. |
MacroOverrideStartUtc / MacroOverrideHoldEndUtc / MacroOverrideExpiresAtUtc |
DateTime |
MinValue |
Hold window bounds. Full intensity over [Start, HoldEnd], linear fade to 0 over [HoldEnd, Expires]. |
MacroOverrideHoldMode |
MacroLightbarHoldMode |
Reactive |
Reactive (decay-fade) or Sticky (held until cleared). |
HasActiveMacroLightbarOverride |
bool |
- |
Computed: UtcNow < ExpiresAtUtc. |
ComputeMacroOverrideIntensity() |
method |
- |
0..1 scalar for the override RGB. 1.0 for Sticky. Ramps for Reactive. |
ClearMacroOverride() |
method |
- |
Releases the active override, Sticky or Reactive. Drives the LightbarColorClear macro action. |
Mic LED
| Property |
Type |
Default |
Description |
MicLedMode |
MicLedMode |
Off |
Mic-mute LED state. Notifies IsMicLedFollowDevice. |
MicLedFollowDeviceId |
string |
"" |
CoreAudio endpoint id polled by FollowDeviceMute. The only name the class's ResetSettingCommand accepts. |
IsMicLedFollowDevice |
bool |
- |
Computed: mode is FollowDeviceMute. |
MicLightOn |
bool |
- |
Legacy XML shim over MicLedMode (true = Solid, false = Off). |
MicLedAvailableDevices |
List<MicLedDeviceItem> |
- |
Endpoints for the follow-device dropdown. RefreshMicLedDevices() re-enumerates. |
Player LEDs (#191)
| Property |
Type |
Default |
Description |
PlayerLedMode |
PlayerLedMode |
PlayerNumber |
Bottom-row player pips. PlayerNumber idles on the virtual controller's number. |
PlayerLedBrightness |
PlayerLedBrightness |
High |
Pip brightness (byte 42). |
Guide LED (#209)
| Property |
Type |
Default |
Description |
GuideLedMode |
GuideLedMode |
DeviceDefault |
DeviceDefault never writes, Fixed holds GuideLedBrightness, Battery re-maps battery percent to brightness on a slow cadence with a floor of 10 so a low battery stays visible. Xbox One and later pads take the GIP LED command over \\.\XboxGIP (USB only, XboxGipGuideLedWriter). The 2015 Steam Controller takes SDL's process-global home-LED hint (SteamHomeLedSetter). Switch Pro Controllers, right Joy-Cons, the combined pair, and the charging grip take per-device SDL_SetJoystickLED (SwitchHomeLedSetter, #226). Notifies IsGuideLedFixed. |
IsGuideLedFixed |
bool |
- |
Computed: mode is Fixed. Gates the brightness slider. |
GuideLedBrightness |
int |
100 |
Fixed-mode brightness percent. Clamped 0–100. Writers scale it onto each device's own range: 0-47 for GIP per MS-GIPUSB, 0..1 for the SDL hint, and a 4-bit subcommand 0x38 intensity for the Switch home LED. |
Lightbar mode and animation
| Property |
Type |
Default |
Description |
LightbarMode |
LightbarMode |
PlayerNumber |
Base lightbar effect. Off, Static, PlayerNumber, and Battery leave the dispatcher's 33 ms animation timer parked, and Battery repaints when the battery percent changes. The animated modes run the timer, as does any input-reactive overlay or Reactive macro override. |
InputReactiveMode |
InputReactiveMode |
Off |
Per-press flash overlay on top of the base mode. Notifies IsInputReactiveActive, ShowPaletteForBase, ShowPaletteForOverlay, IsInputReactiveFixed. |
InputReactiveR / G / B |
byte |
0xFF |
Per-press flash color for the Fixed overlay variant. |
LightbarPeriodMs |
int |
3000 |
Animation period for time-based modes. Clamped 250–10000. |
LightbarColorCycleSmooth |
bool |
true |
ColorCycle blends between palette entries when true. |
LightbarRainbowBrightness |
int |
100 |
Rainbow output scale. Clamped 0–100. |
LightbarBatteryLowR/G/B |
byte |
red |
Battery-mode 0% endpoint color (default red). |
LightbarBatteryHighR/G/B |
byte |
green |
Battery-mode 100% endpoint color (default green). |
LightbarInputHoldMs |
int |
0 |
Input-reactive full-intensity hold before decay. |
LightbarInputDecayMs |
int |
600 |
Input-reactive decay length. |
LightbarPalette |
ObservableCollection<LightbarPaletteEntry> |
- |
ColorCycle palette. Add / Remove / Reset commands. |
LightbarInputReactivePalette |
ObservableCollection<LightbarPaletteEntry> |
- |
Dedicated palette for the InputReactive = Cycle overlay. |
Peripheral lighting (#494)
| Property |
Type |
Default |
Description |
PeripheralLightingEnabled |
bool |
false |
The Lighting tab's Control This Device’s Lighting switch (DeviceSlotConfig.cs line 1003). On, this virtual controller lights a mouse or keyboard with the modes above, and the slot's effects dispatcher holds a lighting claim on the device. Off, the dispatcher releases this slot's claim, so the color comes from another virtual controller that lights the device, a vendor row, another device of its kind on a shared path, or the device's own software. A Set Chroma Color macro on this virtual controller still paints a Razer device. Off by default, so assigning a keyboard for its keys never repaints it. The vendor lighting rows and the unit rows ignore it, since lighting is all they carry. A change re-checks the dispatcher's animation timer. |
PeripheralClaimLevel |
PeripheralClaimLevel |
Colors |
The Lighting tab's Lighting Hold under that switch, for the same (virtual controller, device) pair: how firmly this controller may hold the device while it lights it. Colors (0) takes the colors alone, Power (1) the device's lighting power too, so it stops dimming its lights by itself, and HostMode (2) its on-board memory mode as well, so the profile saved on the device does not apply while it is lit. The number is the hold a lighting claim carries (PeripheralOutputs.SetLighting) and the option's place in the picker, so a member is appended and never renumbered. The saved form is the name. A value the enum does not name leaves the property as it was. Only a device that offers a hold shows the picker, and a worker holds a device at no more than it offers. A vendor row and a unit row ask for colors alone |
ResetPeripheralLightingEnabledCommand turns the switch back off, and ResetPeripheralClaimLevelCommand puts the hold back at Colors. PeripheralLightingClaim(out level) reads the switch and the hold together from one field, for the dispatcher, whose pass can run on its timer's thread while a load writes the two one after the other.
Audio-to-lightbar (#55)
| Property |
Type |
Default |
Description |
AudioLightbarEnabled |
bool |
false |
Legacy toggle (migrated into LightbarMode on load). |
AudioLightbarSensitivity |
double |
4.0 |
Pre-clamp gain on the audio peak. Clamped 1–20. |
AudioLightbarMode |
AudioLightbarMode |
Pulse |
Pulse / Thresholds / Gradient / CrossFade behavior. |
AudioLowR/G/B |
byte |
green |
Quiet-band color. |
AudioMidR/G/B |
byte |
yellow |
Mid-band color. |
AudioHighR/G/B |
byte |
red |
Loud-band color. |
AudioLowToMidPercent |
double |
33 |
Low→Mid threshold. Clamped 0–100. |
AudioMidToHighPercent |
double |
66 |
Mid→High threshold. Clamped 0–100. |
AudioCrossFadePercent |
double |
5.0 |
Half-width of the crossfade window around each threshold. Clamped 0–50. |
Each control has a matching Reset…Command (mirroring the Sticks / Triggers per-row reset pattern), plus the palette add/remove commands AddPaletteColorCommand, RemovePaletteColorCommand, AddInputReactivePaletteColorCommand, RemoveInputReactivePaletteColorCommand, ResetInputReactivePaletteCommand, ResetPaletteCommand.
Enums and helper classes (DeviceSlotConfig.cs)
AdaptiveTriggerMode: Off(0), Feedback(1), Weapon(2), Vibration(3), MultiplePositionFeedback(4), SlopeFeedback(5), MultiplePositionVibration(6). Sony's seven PS5 SDK effect modes.
AudioOutputPath: Automatic(0), StereoHeadset(1), MonoHeadset(2), HeadsetAndSpeaker(3), SpeakerOnly(4), FollowHeadphoneJack(5). Values 1–4 map to firmware paths 0–3, the four names in duaLib's scePad surface. Automatic writes nothing and keeps the #83 behavior: firmware routing, with PadForge forcing the speaker only while it plays sounds there. FollowHeadphoneJack tracks the pad's own jack detect. The jack bit is read by AudioPassthroughService's sink-owned JackWatch on either transport (USB input report 0x01 or Bluetooth 0x31, the duaLib PluggedHeadphones status bit), and on a Bluetooth DualShock 4 from bit 5 of the status byte at offset 32 (#528), independent of any persona lane. Plugged resolves to StereoHeadset, unplugged to SpeakerOnly, and no reading to the default. Persisted numerically, so the members are append-only.
MicLedMode: Off(0), Solid(1), Pulse(2), FollowDeviceMute(3).
PlayerLedMode: Off(0), Player1(1), Player2(2), Player3(3), Player4(4), All(5), PlayerNumber(6, default). Sequential to map 1:1 with the dropdown.
PlayerLedBrightness: High(0), Medium(1), Low(2).
GuideLedMode (#209): DeviceDefault(0), Fixed(1), Battery(2).
LightbarMode: Off(0), Static(1), Breathing(2), Rainbow(3), ColorCycle(4), AudioPulse(5), AudioPulseRandom(6), AudioPulseRainbow(7), AudioThresholds(8), AudioGradient(9), AudioCrossFade(10), InputReactive(11, legacy), InputReactiveCycle(12, legacy), InputReactiveFixed(13, legacy), Battery(14), Strobe(15), PlayerNumber(16, default). Legacy 11–13 stay in the enum for XML round-trip and are migrated to InputReactiveMode on load.
InputReactiveMode: Off(0), Random(1), Cycle(2), Fixed(3).
AudioLightbarMode: Pulse(0), Thresholds(1), Gradient(2), CrossFade(3).
MicLedDeviceItem: Id (CoreAudio endpoint string), Display (label, prefixed [In] / [Out]).
LightbarPaletteEntry (ObservableObject): R, G, B (byte), Hex (two-way "RRGGBB" shim, [XmlIgnore]).
DeviceSlotConfigData
Serializable DTO. All scalar properties are [XmlAttribute]. The two palettes are [XmlArray]. Key defaults: LeftEndPosition/RightEndPosition = 255, LeftStrength/RightStrength = 200, LeftFrequency/RightFrequency = 10, LightbarBlue = 0xFF, AudioMirrorEngageMode = "Always", AudioMirrorEngageReleaseMs = 500, AudioToneFilterMode = "Off", AudioToneLimitHz = 800, TouchpadSyntheticPressure = false, TouchpadSyntheticTouchPercent = 50, AudioPersonaHapticsEnabled = false, AudioPersonaHapticsGain = 100, AudioTritonLowPassHz = 250, HeadphoneVolume = 100 (a missing attribute on legacy XML keeps the initializer, so old configs load at full volume, the pre-feature effective behavior), Ds5AudioBufferLength = 48, AudioOutputPath = Automatic, AudioCrossfeedLevel = 0, AudioCrossfeedCutHz = 700, AudioCrossfeedFeedDb = 4.5, AudioEqEnabled = false, AudioEqBands = "", AudioEqPreampDb = 0, AudioLimiterEnabled = true, AudioLimiterCeiling = 98, GuideLedMode = DeviceDefault, GuideLedBrightness = 100, PeripheralLightingEnabled = false (#494: a config saved before the switch leaves every mouse and keyboard to its own software), LightbarMode = Off (the DTO default, migrated to PlayerNumber via LightingRev), PlayerLedMode = Off (likewise). It also carries a DeviceGuid (per-device key, empty = a legacy slot-level entry the loader fans out) and LightingRev (schema revision: 0 predates the PlayerNumber default and triggers the Off→PlayerNumber lift on load). LightbarPaletteEntryData is the palette element (R, G, B byte attributes).
KbmSlotConfig
File: KbmSlotConfig.cs
Per-slot keyboard + mouse output config (discussion #205, SOCD / Snap Tap, plus the #408 surface mode). Same per-slot lane as MidiSlotConfig: lives on PadViewModel.KbmConfig, referenced into the engine by InputService, persisted as KbmSlotConfigData.
| Property |
Type |
Default |
Description |
Surfaces |
string |
"Both" |
Which halves of the slot are live (#408): Both, KeyboardOnly, or MouseOnly. Any other value coerces to Both. Turning a half off hides its mapping rows and stops the slot's own keyboard or mouse output for them. The mappings stay on the PadSetting. Macros are not gated. Notifies KeyboardEnabled and MouseEnabled. |
KeyboardEnabled / MouseEnabled |
bool |
- |
Computed: the half is live (Surfaces != "MouseOnly" / Surfaces != "KeyboardOnly"). Allows(KbmSurfaceKind) answers the same question for a row's half. |
KeyboardDevice / MouseDevice |
string |
"" (WindowsInput) |
The HIDMaestro keyboard or mouse profile each half sends through, or the empty string for Windows input. When one changes, a half that rode the old shared device follows to the new one if it carries that half, and goes back to Windows input if it does not. A null write is a picker artifact and is ignored. SetDevices(keyboard, mouse) sets both as one change, as a load, a paste or a profile apply does, and ResetToDefaults() puts both back on Windows input. See Virtual keyboards and mice. |
AvailableKeyboardDevices / AvailableMouseDevices |
IReadOnlyList<KbmDeviceOption> |
- |
The two dropdowns: Windows Input, then the other half's device when it carries this half (Same Device as the Mouse / Keyboard), then every keyboard or mouse profile by name. KbmDeviceOption compares by value, so a rebuilt list keeps its selection. |
UsesDevices |
bool |
- |
True when either half has a model. DeviceCanSend(settingName) answers whether the picked model can send a row's target, and the mapping table hides a row it cannot and keeps the binding. |
AvailableKbmSurfaces |
IReadOnlyList<KbmSurfaceOption> |
- |
Culture-cached surface dropdown items, in order: "Keyboard + Mouse" (Both), "Mouse Only", "Keyboard Only". |
SocdMode |
string |
"Off" |
Resolution mode: Off, LastWins, Neutral, FirstWins. Stored locale-stable. |
SocdPairs |
string |
"87:83\|65:68\|38:40\|37:39" |
Pipe-separated "vkA:vkB" decimal pairs (the DefaultSocdPairs constant: W/S, A/D, Up/Down, Left/Right). |
SocdPairItems |
ObservableCollection<SocdPairItem> |
- |
Editable projection of SocdPairs for the pair editor. |
AvailableSocdModes |
IReadOnlyList<SocdModeOption> |
- |
Culture-cached dropdown items. |
| Constant |
Value |
DefaultSocdPairs |
"87:83\|65:68\|38:40\|37:39" |
DefaultSurfaces |
"Both" |
| Command |
Description |
AddSocdPairCommand |
Appends a fresh W/S pair. |
RemoveSocdPairCommand |
RelayCommand<SocdPairItem>. Removes the passed pair. |
ResetSocdCommand |
The SOCD card's Reset All (ResetSocdToDefaults()): SocdMode back to Off and SocdPairs to DefaultSocdPairs in place. It leaves Surfaces alone. |
ResetSocdModeCommand |
SocdMode back to Off, pairs untouched. |
| Method |
Description |
ResetToDefaults() |
The whole-slot reset: Surfaces back to Both, then the SOCD reset. Keeps the instance. |
| Static Method |
Description |
GetKeyOptions() |
Culture-cached SocdKeyOption list, the same key set and labels as the KBM mapping targets. |
Companion classes:
- SocdModeOption carries Value (engine-stable), Name, and Description. Localized dropdown entry.
- KbmSurfaceOption carries Value and Name. One surface-mode entry, with no description.
- KbmSurfaceKind (enum): Keyboard, Mouse. The half a mapping row drives, classified by PadViewModel.KbmSurfaceOf.
- SocdKeyOption carries Vk (int) and Label. One pickable key.
- SocdPairItem (ObservableObject) carries VkA and VkB. Edits reserialize the owner's SocdPairs. KeyOptions returns GetKeyOptions().
- KbmSlotConfigData is the DTO with [XmlAttribute] SlotIndex, Surfaces (default "Both", so files written before #408 read as Both), SocdMode (default "Off"), SocdPairs (default DefaultSocdPairs), and KeyboardDevice and MouseDevice, written only when set, so a file written before the device pickers reads as Windows input.
RemoteLinkTrustedPeer
File: RemoteLinkTrustedPeer.cs
One trusted peer in the Paired PCs list on the Dashboard's Remote Link card (#138), held by SettingsViewModel.TrustedPeers. The name is editable (committed on focus loss, persisted to the trust store). The online dot refreshes in place so editing isn't disrupted.
| Property |
Type |
Description |
Name |
string |
Friendly name. Setting it persists the rename via the callback. Empty falls back to the host name. |
HostName |
string |
The peer's machine name. HasHostName gates showing it. |
IsOnline |
bool |
Live-session state. Notifies OnlineText, CanConnect. |
OnlineText |
string |
Computed localized "Online" / "Offline". |
ReachableHostPort |
string |
Where the peer is reachable now (host:port from discovery), or null. Notifies CanConnect. |
CanConnect |
bool |
Computed: on the LAN but not connected. Shows the Connect button. |
FingerprintHex |
string |
Full key fingerprint. |
FingerprintDisplay |
string |
Computed: short grouped fingerprint, with a gamepad-only suffix when applicable. |
PairedUtc |
string |
Pairing timestamp. |
GamepadOnly |
bool |
Whether the peer is restricted to gamepad input. |
AllowRemoteAssignments |
bool |
Allow Remote Assignment Changes: this peer may assign the devices it shares to this PC's existing virtual controllers. A change raises the permission callback (SettingsViewModel.PeerAssignmentPermissionChanged). Turning it off keeps existing assignments. |
| Command |
Description |
ConnectCommand |
Reconnect to this known peer (no SAS prompt). |
RevokeCommand |
Revoke trust (raises the revoke callback with the fingerprint). |
AssignmentsCommand |
Assign Shared Devices, shown while the peer is online. Raises the assignments callback with the fingerprint (SettingsViewModel.PeerAssignmentsRequested). |
RemoteLinkNearbyPeer
File: RemoteLinkNearbyPeer.cs
One PadForge PC discovered on the LAN (#138), shown in the "Nearby PCs" list. Immutable. Clicking Pair initiates pairing without IP typing.
| Property |
Type |
Description |
Name |
string |
Discovered machine name. |
HostPort |
string |
Host:port to pair against. |
FingerprintHex |
string |
The peer's key fingerprint. |
IsPaired |
bool |
Already in the trust store. |
IsConnected |
bool |
A live session already exists. |
DisplayName |
string |
Computed: name with a Connected / Paired state suffix. |
ButtonLabel |
string |
Computed: Connected (disabled) / Connect (paired) / Pair (new). |
CanPair |
bool |
Computed: the action button is enabled unless already connected. |
| Command |
Description |
PairCommand |
Initiates pairing with HostPort. |
See Also
- Architecture Overview: MVVM design philosophy,
CommunityToolkit.Mvvm usage
- XAML Views: View counterparts:
DashboardPage, PadPage, DevicesPage, SettingsPage
- Services Layer:
InputService, SettingsService, DeviceService consumed by ViewModels
- Engine Library:
Gamepad, RawHidState, KbmRawState, VrRawState, MidiRawState, PadSetting
- Settings and Serialization:
SettingsManager data synced to/from ViewModels by SettingsService
- Input Pipeline:
InputManager state reflected in PadViewModel output snapshots
- 2D Overlay System:
ControllerModel2DView, ControllerSchematicView bound to PadViewModel
- 3D Model System:
ControllerModelView bound to PadViewModel
Last updated for PadForge 5.2.0.