Skip to content

Add configurable trackpad haptic feedback - #1461

Merged
Valkirie merged 27 commits into
Valkirie:mainfrom
LavX:feature/steam-deck-trackpad-haptics
Aug 11, 2026
Merged

Add configurable trackpad haptic feedback#1461
Valkirie merged 27 commits into
Valkirie:mainfrom
LavX:feature/steam-deck-trackpad-haptics

Conversation

@LavX

@LavX LavX commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Move touchpad button and axis targets into TouchpadActions.
  • Show Touchpad in layout target lists only when the selected virtual controller advertises compatible targets.
  • Restore trackpad movement feedback while Steam lizard mode is disabled.
  • Add press and release click feedback with Off, Low, Medium, and High levels. High is the default.
  • Support left and right touchpad gyro output in Quick Profiles.

Problem

Disabling Steam lizard mode also removes the firmware-generated trackpad feedback. Touchpad behavior had also become split across mouse and button actions, which made device support and settings difficult to keep consistent.

Implementation

TouchpadActions now owns touchpad button targets, axis targets, coordinate and swipe handling, and touchpad haptics. Layout target lists are built from the capabilities advertised by the selected virtual controller. Existing mappings with unavailable targets are kept intact instead of being silently replaced.

Movement feedback uses the selected controller's normal haptic path. Steam trackpad clicks use native Steam haptic commands for distinct strength levels and correct left and right routing. Per-mapping haptic settings can override the global click setting, and the Controller page reports when an override is active.

Quick Profiles now represents gyro output to either touchpad. These options are capability-driven while still displaying an existing unsupported mapping so loading a profile remains non-destructive.

Validation

  • Built the Debug solution with 0 errors.
  • Tested movement feedback on both Steam Deck trackpads.
  • Tested press and release click feedback and correct left and right routing.
  • Tested global and per-mapping haptic settings, including Off, Low, Medium, and High.
  • Confirmed High is the default.
  • Tested left and right touchpad gyro output in Quick Profiles.
  • Verified the changed XAML and resources parse successfully.
  • Ran git diff --check successfully.

Physical validation was performed on a Steam Deck. Other controller paths require testing on their respective hardware.

Built on #1454.

Closes #590

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Steam trackpad input now supports configurable click and movement haptics. The change adds touchpad actions, coordinate and swipe mapping, DualSense touch sequencing, Steam HID gain support, and related controller and layout settings.

Changes

Trackpad input and mapping

Layer / File(s) Summary
Touchpad actions and layout execution
HandheldCompanion/Actions/*, HandheldCompanion/Managers/LayoutManager.cs, HandheldCompanion/Misc/*, HandheldCompanion/ViewModels/Layout/*
Adds touchpad button, axis, coordinate, and swipe actions. Layout execution prioritizes touch samples and maps touchpad coordinates and axes into controller output.
DualSense touch reports
HandheldCompanion/Targets/DualSenseTarget.cs, HandheldCompanion/Controllers/Dummies/*, HandheldCompanion/Controllers/SDL/*
Sequences click touches and serializes coordinate, physical, center-click, and microphone-mute states.
Steam haptic dispatch
steam-hidapi.net/*, HandheldCompanion/Controllers/Steam/*, HandheldCompanion/Controllers/IController.cs, HandheldCompanion/Actions/MouseActions.cs
Adds Steam lizard-mode detection, click and movement haptic handling, HID gain packets, movement filtering, and rumble cancellation cleanup.
Settings and presentation
HandheldCompanion/Views/*, HandheldCompanion/Properties/*, HandheldCompanion/App.config, HandheldCompanion/Converters/*
Adds trackpad haptic settings, touchpad mapping controls, localized labels, and left/right pad motion outputs.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TrackpadInput
  participant TouchpadActions
  participant LayoutManager
  participant SteamController
  participant SteamHIDAPI
  TrackpadInput->>TouchpadActions: Process touchpad input
  TouchpadActions->>LayoutManager: Return touch sample and haptic state
  LayoutManager->>SteamController: Send trackpad haptic
  SteamController->>SteamHIDAPI: Send gain-enabled haptic packet
Loading

Possibly related PRs

Suggested reviewers: valkirie

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also adds generic touchpad actions, layout UI, DualSense support, and touchpad gyro outputs beyond issue [#590]. Limit this PR to Steam Deck trackpad haptics; move generic touchpad, layout, DualSense, and gyro changes to a separate PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes restore configurable Steam Deck trackpad movement and click haptics while Handheld Companion runs, addressing issue [#590].
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: configurable trackpad haptic feedback.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feature/steam-deck-trackpad-haptics
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@LavX
LavX marked this pull request as ready for review August 6, 2026 00:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@HandheldCompanion/Actions/MouseActions.cs`:
- Around line 284-308: The UpdateTrackpadHaptics method must also require Steam
lizard/Hybrid mode to be disabled before generating replacement haptic pulses.
Add the existing lizard-mode state check alongside the SteamController target
check, returning early when lizard mode is enabled while preserving the current
distance and haptic-step behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c48bdd1-f49e-4f3d-98ac-2ff3f173243a

📥 Commits

Reviewing files that changed from the base of the PR and between 694d27e and 12c0dc5.

📒 Files selected for processing (13)
  • HandheldCompanion/Actions/MouseActions.cs
  • HandheldCompanion/App.config
  • HandheldCompanion/Controllers/Steam/GordonController.cs
  • HandheldCompanion/Controllers/Steam/NeptuneController.cs
  • HandheldCompanion/Controllers/Steam/SteamController.cs
  • HandheldCompanion/Misc/Layout.cs
  • HandheldCompanion/Misc/LayoutTemplate.cs
  • HandheldCompanion/Properties/Settings.Designer.cs
  • HandheldCompanion/Properties/Settings.settings
  • HandheldCompanion/Views/Pages/ControllerPage.xaml
  • HandheldCompanion/Views/Pages/ControllerPage.xaml.cs
  • steam-hidapi.net/Hid/HidEnums.cs
  • steam-hidapi.net/SteamController.cs

Comment thread HandheldCompanion/Actions/MouseActions.cs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12c0dc55ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
}

public override void SetHaptic(ButtonFlags button, bool released)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Suppress duplicate clicks for every action type

When a Steam trackpad click is mapped to a KeyboardActions or ButtonActions binding with haptics enabled, this MouseActions-only override never runs: UpdateTrackpadClickHaptics emits the shared click pulse, and the mapped action then calls the base IActions.SetHaptic, producing a second pulse while the global level is enabled. Move this suppression to a common action/controller path so the controller-level feedback is emitted once regardless of mapping type.

Useful? React with 👍 / 👎.

Comment on lines +295 to +297
float distance = delta.Length();
if (distance < TrackpadHapticJitterThreshold)
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accumulate slow trackpad motion before filtering

For slow or high-report-rate trackpad movement where every coordinate delta is below 128 units, each sample returns here before contributing to trackpadHapticDistance, so even sustained travel never produces movement feedback. The jitter filter needs to preserve persistent sub-threshold movement—such as by accumulating displacement before applying the threshold—rather than discarding every small sample independently.

Useful? React with 👍 / 👎.

Comment on lines +216 to +220
int strength = ManagerFactory.settingsManager.GetInt("SteamTrackpadClickHaptics");
if (strength <= 0)
return;

SendTrackpadClickHaptic(motor, System.Math.Clamp(strength, 1, 3), released: !pressed);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expose click-haptic control for every affected controller

On a non-Steam-Deck host with a Gordon Steam Controller or generic 0x12f0 SteamOS handheld selected, this shared base path still reads the new default value of 3 and sends High click feedback, but ControllerPage hides SteamDeckPanel whenever IDevice.GetCurrent() is not SteamDeck. Those supported controllers therefore cannot select Off or another level through the UI; either limit this behavior to actual Deck hardware or expose the setting whenever an affected Steam controller is available.

Useful? React with 👍 / 👎.

Comment on lines +254 to +256
Vector2 trackpadDelta = outVector - prevVector;
prevVector = outVector;
UpdateTrackpadHaptics(layout.flags, touched, trackpadDelta);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Initialize the haptic baseline for center touches

When a new touch begins exactly at the pad center, ConsumeNewTouch records the transition but the earlier zero-vector return skips the firstTouch branch, leaving prevVector at the previous touch's final position. The first small movement of the new touch is therefore treated here as a potentially large stale delta and can immediately emit a false movement pulse; initialize the baseline before returning for a touched zero vector.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@HandheldCompanion/Actions/MouseActions.cs`:
- Around line 206-214: Update the early-return condition in the touch-processing
flow around isTrackpad so Vector2.Zero positions continue through delta and
haptic processing for trackpads. Return early only when outVector is zero and
the current layout is not a trackpad, preserving the first-touch trackpad
initialization behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e9780f4-2800-44b2-b4f8-b687cf1088db

📥 Commits

Reviewing files that changed from the base of the PR and between 12c0dc5 and 8bdffff.

📒 Files selected for processing (8)
  • HandheldCompanion/Actions/IActions.cs
  • HandheldCompanion/Actions/MouseActions.cs
  • HandheldCompanion/Controllers/Steam/GordonController.cs
  • HandheldCompanion/Controllers/Steam/NeptuneController.cs
  • HandheldCompanion/Controllers/Steam/SteamController.cs
  • HandheldCompanion/Views/Pages/ControllerPage.xaml
  • HandheldCompanion/Views/Pages/ControllerPage.xaml.cs
  • steam-hidapi.net/SteamController.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • HandheldCompanion/Controllers/Steam/GordonController.cs
  • HandheldCompanion/Controllers/Steam/SteamController.cs

Comment thread HandheldCompanion/Actions/MouseActions.cs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8bdffff62b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread HandheldCompanion/Misc/Layout.cs Outdated
Comment on lines +57 to +59
if (action.MouseType is MouseActionsType.Move or MouseActionsType.Scroll &&
action.HapticMode == HapticMode.Off)
action.HapticMode = HapticMode.Down;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve explicit Off settings during layout migration

For every pre-schema saved layout containing a trackpad Move or Scroll action, this migration changes HapticMode.Off to Down, even when the user explicitly selected Off through the existing action settings. Because old files cannot distinguish an omitted default from an intentional Off value, upgrading silently re-enables movement haptics and overwrites the user's configuration; apply the new default only to newly created layouts or use a migration marker that preserves explicit choices.

Useful? React with 👍 / 👎.

return;
}

if (outVector == Vector2.Zero) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset the trackpad baseline while the action is shift-gated

When this action's shift slot is not active, base.Execute replaces outVector with zero and this return leaves prevVector, trackpadHapticJitter, and trackpadHapticDistance untouched. If the finger moves while the action is masked and the shift subsequently becomes active without a new touch edge, the next sample counts all of that inactive travel as a live delta and can emit an immediate spurious movement pulse; update/reset the trackpad baseline while gated before returning.

Useful? React with 👍 / 👎.

@LavX

LavX commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

For visibility, this PR is also related to #336, #357, #1007, and #1096.

It addresses the missing Steam Deck trackpad movement and click haptics mentioned in those reports. The broader issues also cover separate topics such as acceleration, momentum, scrolling behavior, menu navigation, and controller testing, which are outside the scope of this PR.

@Valkirie

Valkirie commented Aug 6, 2026

Copy link
Copy Markdown
Owner

It looks fine overall but I see a few valid comments from the AI. Also, now that we have more devices and controllers shipping with trackpads, it'd be good to make this non Steam Deck specific so Legion Go users and DualSense/DualShock4/Steam Controller(s) owners could also benefit from it.

Also, as part of another pull request discussion, I suggest we create a TouchpadActions class instead of having touchpad related stuff on MouseActions and ButtonActions. Which means we'd have a new UI element on the layout drop downs : Touchpad when targeted controller has touchpad-related Target buttons/TargetsAxis available.

@LavX

LavX commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Retested the latest branch on a Steam Deck. Both trackpads now provide correctly routed movement and click feedback, click feedback occurs on both press and release, Off disables it, Low, Medium, and High are clearly distinct, and High is the default. Slow movement and center touches no longer produce missing or stale feedback. The solution builds locally with 0 errors.

Movement feedback now uses the selected controller's normal haptic path, so it is not limited to Steam controllers. Native click feedback remains on the Steam HID path. I left the broader TouchpadActions and layout UI work to #1454 to avoid duplicating or conflicting with that implementation.

All fixes are pushed in 58c4f44.

@LavX
LavX force-pushed the feature/steam-deck-trackpad-haptics branch from 58c4f44 to 8dc1cbd Compare August 6, 2026 14:05
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@HandheldCompanion/Views/Pages/ActionSettingsPage.xaml`:
- Around line 146-150: Localize the DualSense touchpad UI introduced in
ActionSettingsPage by replacing the hardcoded “DualSense touchpad” heading and
touchpad card headers with resource bindings, and add the corresponding resource
entries. Update MappingViewModel’s TouchpadCoordinateRangeDescription
construction to use localized resources for its generated text, preserving the
existing binding and visibility behavior.
- Around line 153-170: Add IsSnapToTickEnabled="True" to all four coordinate
Slider controls bound to TouchpadX, TouchpadY, TouchpadEndX, and TouchpadEndY,
while preserving their existing TickFrequency and bindings so fractional values
cannot be selected.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 28ca1930-8b51-4799-926d-e3457709232e

📥 Commits

Reviewing files that changed from the base of the PR and between cd0bcef and 8dc1cbd.

📒 Files selected for processing (26)
  • HandheldCompanion/Actions/IActions.cs
  • HandheldCompanion/Actions/MouseActions.cs
  • HandheldCompanion/Actions/TouchpadActions.cs
  • HandheldCompanion/App.config
  • HandheldCompanion/Controllers/Dummies/DummyDualSenseController.cs
  • HandheldCompanion/Controllers/SDL/DualSenseController.cs
  • HandheldCompanion/Controllers/Steam/GordonController.cs
  • HandheldCompanion/Controllers/Steam/NeptuneController.cs
  • HandheldCompanion/Controllers/Steam/SteamController.cs
  • HandheldCompanion/Inputs/ButtonFlags.cs
  • HandheldCompanion/Managers/LayoutManager.cs
  • HandheldCompanion/Misc/DS4Touch.cs
  • HandheldCompanion/Misc/Layout.cs
  • HandheldCompanion/Misc/LayoutTemplate.cs
  • HandheldCompanion/Properties/Settings.Designer.cs
  • HandheldCompanion/Properties/Settings.settings
  • HandheldCompanion/Targets/DualSenseTarget.cs
  • HandheldCompanion/ViewModels/Layout/Mappings/AxisMappingViewModel.cs
  • HandheldCompanion/ViewModels/Layout/Mappings/ButtonMappingViewModel.cs
  • HandheldCompanion/ViewModels/Layout/Mappings/MappingViewModel.cs
  • HandheldCompanion/ViewModels/Layout/Mappings/TriggerMappingViewModel.cs
  • HandheldCompanion/Views/Pages/ActionSettingsPage.xaml
  • HandheldCompanion/Views/Pages/ControllerPage.xaml
  • HandheldCompanion/Views/Pages/ControllerPage.xaml.cs
  • steam-hidapi.net/Hid/HidEnums.cs
  • steam-hidapi.net/SteamController.cs
🚧 Files skipped from review as they are similar to previous changes (13)
  • steam-hidapi.net/Hid/HidEnums.cs
  • HandheldCompanion/Misc/Layout.cs
  • HandheldCompanion/Controllers/Steam/SteamController.cs
  • HandheldCompanion/Properties/Settings.settings
  • HandheldCompanion/Controllers/Steam/GordonController.cs
  • HandheldCompanion/Misc/LayoutTemplate.cs
  • HandheldCompanion/Views/Pages/ControllerPage.xaml
  • HandheldCompanion/Properties/Settings.Designer.cs
  • HandheldCompanion/App.config
  • HandheldCompanion/Controllers/Steam/NeptuneController.cs
  • HandheldCompanion/Views/Pages/ControllerPage.xaml.cs
  • HandheldCompanion/Actions/MouseActions.cs
  • steam-hidapi.net/SteamController.cs

Comment thread HandheldCompanion/Views/Pages/ActionSettingsPage.xaml Outdated
Comment thread HandheldCompanion/Views/Pages/ActionSettingsPage.xaml Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8dc1cbd9d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 555 to 556
ApplyTouchpadCoordinates(touchpadSample);
ProcessGyroActions(controllerState, shiftSlot, deltaMs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply exact touch coordinates after gyro blending

When a DualSense profile maps gyro output to RightPad—a target exposed by DummyDualSenseController—an active coordinate touch, click, or swipe is written here and then immediately modified by ProcessGyroActions, which blends gyro output into the same RightPadX/Y axes. The emitted touch therefore drifts from its configured coordinates and swipes can be distorted or clamped; apply the selected touchpad sample after gyro processing so these exact-position actions retain priority.

Useful? React with 👍 / 👎.

Comment on lines +51 to +53
public override void Execute(ButtonFlags button, bool value, ShiftSlot shiftSlot, float delta)
{
base.Execute(button, value, shiftSlot, delta);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid sending synthetic touch targets to Steam haptics

When an axis or trigger mapping targets one of these touchpad actions and per-action haptics are enabled, inherited ButtonActions.Execute(AxisLayout, ...) calls this method with the synthetic output flag (TouchpadCoordinateClick, TouchpadCoordinateTouch, or TouchpadSwipe) as the haptic button. On a Steam Deck or Gordon controller, SteamController.GetMotorForButton has no case for those flags and throws from SetHaptic, interrupting report processing as soon as the mapping activates; route haptics using a supported physical source/motor or suppress them for these synthetic targets.

Useful? React with 👍 / 👎.

@LavX

LavX commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@Valkirie The Steam Deck trackpad haptics work is ready for review. The current branch builds successfully and I tested the click and movement feedback on my Steam Deck. It remains stacked on #1454 as discussed.

@Valkirie

Valkirie commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Please see my previous message: #1461 (comment). Now that we're bringing that much parameters specific to Touchpad, it's time to migrate from MouseActions/ButtonActions to a single TouchpadActions class.

@LavX LavX changed the title Add Steam Deck trackpad haptic feedback Add configurable trackpad haptic feedback Aug 6, 2026
@LavX

LavX commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Refactored as requested. Touchpad-specific mapping and haptic handling now lives in TouchpadActions, the layout target list is capability-driven, and Quick Profiles can represent left and right pad gyro output without rewriting unsupported mappings. Retested the final build on Steam Deck, and the solution builds with 0 errors. Ready for another look.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 507fa07570

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

private static void WriteTouch(byte[] data, int x, int y)
{
ushort touchX = InputUtils.ClampToUShort(x, 0, DS4Touch.TOUCHPAD_WIDTH - 1);
ushort touchY = InputUtils.ClampToUShort(y, 0, DS4Touch.TOUCHPAD_HEIGHT - 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scale DualSense touch Y to its report range

When emitting any physical or synthetic touch through the DualSense target, this clamps Y to the DS4 range of 0–942 and writes it directly into the VIIPER report. The replaced implementation scaled that range to the DualSense report's 0–1080 range, so touches and swipes can now reach only about 87% of the emulated pad height and coordinates near the physical bottom are reported substantially too high; restore the target-specific Y scaling before writing the report.

Useful? React with 👍 / 👎.

Comment thread HandheldCompanion/Actions/IActions.cs Outdated
// --- Haptics ---
public HapticMode HapticMode = HapticMode.Off;
public HapticStrength HapticStrength = HapticStrength.Low;
public bool? HapticOverride = null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve saved per-click haptic settings

For layouts saved before this field existed, HapticOverride deserializes as null even when a physical trackpad-click action has an explicit HapticMode or strength. The new click resolver honors an action only when this value is exactly true, while the common SetHaptic path now suppresses physical-click haptics, so upgrading silently replaces those saved Down/Up/Off or strength choices with the global setting; infer/migrate the override for legacy click mappings rather than treating every missing value as global.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
HandheldCompanion/Controllers/IController.cs (1)

448-472: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Prevent canceled rumble tasks from writing vibration.

A queued task can start after a later Rumble call cancels and replaces currentCts. It still calls SetVibration before it observes cancellation at Task.Delay. That stale write can overwrite the newer haptic command and remain active because the stale task no longer owns rumbleCts.

Before SetVibration, lock rumbleLock and return unless currentCts is still rumbleCts and its token is not canceled. Keep the vibration write in that protected check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/Controllers/IController.cs` around lines 448 - 472, Update
the rumbleTask body in IController so it locks rumbleLock before calling
SetVibration(LargeMotor, SmallMotor), and only performs the write when
currentCts still references rumbleCts and its token is not canceled; otherwise
return without writing. Keep the existing delay, cleanup, and ownership checks
unchanged.
HandheldCompanion/Actions/IActions.cs (1)

180-188: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve legacy per-action haptics for physical clicks.

Existing saved actions default HapticOverride to null, so TouchpadActions.HasCustomHapticSettings returns false and ResolveClickHapticProfile uses the global TrackpadClickHaptics value instead of the action’s non-zero HapticMode/HapticStrength. When SetHaptic now bypasses physical-click buttons for all IActions subclasses, those legacy settings stop applying unless the mapper re-enables the override or a migration sets HapticOverride to true for non-zero legacy haptics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/Actions/IActions.cs` around lines 180 - 188, Update
SetHaptic in IActions so physical-click buttons are bypassed only when the
action does not have an explicit or legacy per-action haptic configuration.
Preserve non-zero HapticMode/HapticStrength settings when HapticOverride is
null, either by re-enabling the override during mapping or migrating those
actions to HapticOverride=true, while retaining the physical-click bypass for
actions using global haptic settings.
HandheldCompanion/Managers/LayoutManager.cs (1)

715-724: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make touchpad coordinate output coexist with axis/touchpad gyro output.

ProcessButtonActions, ProcessAxisActions, and ProcessGyroActions blend into outputState.AxisState[RightPadX/Y], but ApplyTouchpadCoordinates then assigns those axes directly when a coordinate sample exists. If an AxisLayoutFlags.RightPad touchpad action, axis plan action, or gyro touchpad mapping runs in the same tick, replace the assignment with blending or make the precedence explicit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/Managers/LayoutManager.cs` around lines 715 - 724, Update
ApplyTouchpadCoordinates so coordinate samples blend with, or use explicitly
documented precedence against, RightPadX and RightPadY values produced by
ProcessButtonActions, ProcessAxisActions, and ProcessGyroActions in the same
tick. Preserve the existing coordinate conversion while preventing direct
assignments from overwriting AxisLayoutFlags.RightPad touchpad, axis-plan, or
gyro mappings.
🧹 Nitpick comments (6)
HandheldCompanion/ViewModels/Layout/Mappings/MappingViewModel.cs (2)

365-375: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Three subclasses re-declare the base touchpad visibility properties verbatim. MappingViewModel defines TouchpadSettingsVisibility and TouchpadSwipeSettingsVisibility as virtual with the exact logic that all three subclasses then repeat. The shared root cause is that the base implementation already covers every subclass, so the overrides add no behavior and create four copies to maintain.

  • HandheldCompanion/ViewModels/Layout/Mappings/MappingViewModel.cs#L365-L375: keep these as the single virtual implementation; no change needed here beyond confirming it stays the source of truth.
  • HandheldCompanion/ViewModels/Layout/Mappings/AxisMappingViewModel.cs#L380-L388: delete both overrides and inherit from the base.
  • HandheldCompanion/ViewModels/Layout/Mappings/ButtonMappingViewModel.cs#L418-L426: delete both overrides and inherit from the base.
  • HandheldCompanion/ViewModels/Layout/Mappings/TriggerMappingViewModel.cs#L26-L34: delete both overrides and inherit from the base.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/ViewModels/Layout/Mappings/MappingViewModel.cs` around
lines 365 - 375, Remove the redundant TouchpadSettingsVisibility and
TouchpadSwipeSettingsVisibility overrides from AxisMappingViewModel (lines
380-388), ButtonMappingViewModel (lines 418-426), and TriggerMappingViewModel
(lines 26-34), allowing them to inherit the shared virtual implementations from
MappingViewModel. Keep MappingViewModel (lines 365-375) unchanged as the single
source of truth.

343-363: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider caching the controller lookup in these two visibility getters.

Both getters call ControllerManager.GetDefault(true) on every evaluation, and TouchpadAxisActionTypeVisibility also enumerates TouchpadActions.GetAxisTargets(controller). The mapping view models raise OnPropertyChanged(string.Empty) on every action-type change and every target change, and the layout page holds one view model per input. Each refresh therefore repeats the manager lookup and the LINQ enumeration for every row.

Cache the two results and invalidate them from the existing VirtualManager_ControllerSelected handler, which already triggers ActionTypeChanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/ViewModels/Layout/Mappings/MappingViewModel.cs` around
lines 343 - 363, Cache the controller lookup and computed touchpad target
results used by TouchpadActionTypeVisibility and
TouchpadAxisActionTypeVisibility instead of recalculating them on every getter
evaluation. Invalidate those cached values in the existing
VirtualManager_ControllerSelected handler alongside ActionTypeChanged, ensuring
subsequent property evaluations refresh against the newly selected controller.
HandheldCompanion/Managers/LayoutManager.cs (1)

874-893: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting the gyro blending logic into a shared helper.

Lines 880-888 duplicate the blend computation from the ActionType.Joystick case at lines 852-860. Only the value source differs (aA.GetValue() versus touchpadAction.GetAxisValue()). Extract one private helper that takes the target AxisLayoutFlags, the weight, and the incoming Vector2. This keeps both paths consistent when the blend formula changes.

♻️ Proposed helper extraction
+    private void BlendIntoAxis(AxisLayoutFlags axis, float weight, Vector2 value)
+    {
+        var xyOut = _axisXY[axis];
+        var current = new Vector2(outputState.AxisState[xyOut.X], outputState.AxisState[xyOut.Y]);
+
+        float norm = Math.Clamp(current.Length() / short.MaxValue, 0f, 1f);
+        var blended = current + value * (weight - norm);
+
+        outputState.AxisState[xyOut.X] = ClampShort(blended.X);
+        outputState.AxisState[xyOut.Y] = ClampShort(blended.Y);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/Managers/LayoutManager.cs` around lines 874 - 893, Extract
the duplicated gyro blending and axis assignment logic from the
ActionType.Joystick and ActionType.Touchpad branches into one private helper
accepting the target AxisLayoutFlags, weight, and incoming Vector2. Update both
branches to call the helper while preserving their existing value sources and
touch-button handling.
HandheldCompanion/ViewModels/Layout/Mappings/ButtonMappingViewModel.cs (1)

280-285: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add parentheses to the mixed || / && condition.

C# binds && tighter than ||, so the expression evaluates as Joystick || (Touchpad && Action is TouchpadActions { TargetType: Axis }). That matches the intent, but the grouping is not visible in the source. Add explicit parentheses so a later edit cannot change the meaning silently.

♻️ Proposed clarification
         public override Visibility Button2AxisVisibility =>
-            ActionTypeIndex == (int)ActionType.Joystick ||
-            ActionTypeIndex == (int)ActionType.Touchpad &&
-            Action is TouchpadActions { TargetType: TouchpadTargetType.Axis }
+            ActionTypeIndex == (int)ActionType.Joystick ||
+            (ActionTypeIndex == (int)ActionType.Touchpad &&
+             Action is TouchpadActions { TargetType: TouchpadTargetType.Axis })
                 ? Visibility.Visible
                 : Visibility.Collapsed;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/ViewModels/Layout/Mappings/ButtonMappingViewModel.cs`
around lines 280 - 285, Update Button2AxisVisibility so the mixed || and &&
condition has explicit parentheses around the intended grouped clauses,
preserving the current evaluation of the Joystick case and the Touchpad
axis-action case.
HandheldCompanion/ViewModels/Layout/LayoutItemPageViewModel.cs (1)

114-126: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider deriving this label from the enum and the resource strings.

This switch hardcodes indices and English text. ActionTypeIndexToNameConverter maps the same indices to localized Resources.LayoutPage_ActionType_* strings. The two lists must now be kept in sync by hand, and this one is not localized. MappingViewModel.GetActionTypeDisplayName() already performs the enum-to-resource lookup; call it instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/ViewModels/Layout/LayoutItemPageViewModel.cs` around lines
114 - 126, Replace the local actionType switch in the surrounding method with
MappingViewModel.GetActionTypeDisplayName(), using the current mapping’s action
type value as required by that API. Remove the hardcoded indices and English
labels so the displayed name comes from the shared localized resource lookup.
HandheldCompanion/ViewModels/Pages/ProfilesPageViewModel.cs (1)

3188-3203: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The pruning step depends on the enum declaration order.

The loop places each desired mode at its target index. The trailing while then removes items from the end of the collection. This removes the correct entries only because LeftPad and RightPad are declared last in MotionOutput. If a future value is inserted before them, obsolete entries can remain in the middle and valid entries can be removed from the tail.

Remove obsolete entries explicitly by value before the placement loop.

♻️ Proposed order-independent pruning
             MotionOutput[] desiredModes = Enum.GetValues<MotionOutput>()
                 .Where(availableModes.Contains)
                 .ToArray();
 
+            for (int index = MotionOutputModes.Count - 1; index >= 0; index--)
+            {
+                if (!availableModes.Contains(MotionOutputModes[index].Value))
+                    MotionOutputModes.RemoveAt(index);
+            }
+
             for (int index = 0; index < desiredModes.Length; index++)
             {
                 MotionOutput mode = desiredModes[index];
                 MotionOutputViewModel? existing = MotionOutputModes.FirstOrDefault(item => item.Value == mode);
                 if (existing is null)
                     MotionOutputModes.Insert(index, new MotionOutputViewModel(mode));
                 else if (MotionOutputModes.IndexOf(existing) != index)
                     MotionOutputModes.Move(MotionOutputModes.IndexOf(existing), index);
             }
-
-            while (MotionOutputModes.Count > desiredModes.Length)
-                MotionOutputModes.RemoveAt(MotionOutputModes.Count - 1);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/ViewModels/Pages/ProfilesPageViewModel.cs` around lines
3188 - 3203, Update the MotionOutputModes synchronization logic before the
placement loop to explicitly remove every existing mode whose Value is not
contained in desiredModes, rather than pruning only from the tail afterward.
Keep the existing insertion and move behavior in the loop, and remove the
trailing count-based while removal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@HandheldCompanion/Properties/Settings.settings`:
- Around line 125-127: Update CustomSettingsProvider.GetPropertyValues to
migrate the legacy SteamTrackpadClickHaptics value when loading
TrackpadClickHaptics: if the new setting node is absent, read the old key, use
its value, and persist it under TrackpadClickHaptics instead of defaulting to 3.

In `@HandheldCompanion/ViewModels/Layout/Mappings/GyroMappingViewModel.cs`:
- Around line 435-438: Update the Touchpad branch in the relevant mapping
override to call OnPropertyChanged(string.Empty) immediately after
SetTouchpadTarget, matching AxisMappingViewModel, ButtonMappingViewModel, and
TriggerMappingViewModel so TargetType-dependent visibility bindings refresh.

In `@HandheldCompanion/ViewModels/Layout/Mappings/MappingViewModel.cs`:
- Around line 396-397: Update the mapping migration and display logic around
GetJoystickTargets so persisted ActionType.Joystick mappings targeting LeftPad
or RightPad are converted or relabeled as ActionType.Touchpad mappings. Ensure
these saved actions remain supported and no longer display “Unavailable on
current controller” when those axes are valid touchpad targets.

---

Outside diff comments:
In `@HandheldCompanion/Actions/IActions.cs`:
- Around line 180-188: Update SetHaptic in IActions so physical-click buttons
are bypassed only when the action does not have an explicit or legacy per-action
haptic configuration. Preserve non-zero HapticMode/HapticStrength settings when
HapticOverride is null, either by re-enabling the override during mapping or
migrating those actions to HapticOverride=true, while retaining the
physical-click bypass for actions using global haptic settings.

In `@HandheldCompanion/Controllers/IController.cs`:
- Around line 448-472: Update the rumbleTask body in IController so it locks
rumbleLock before calling SetVibration(LargeMotor, SmallMotor), and only
performs the write when currentCts still references rumbleCts and its token is
not canceled; otherwise return without writing. Keep the existing delay,
cleanup, and ownership checks unchanged.

In `@HandheldCompanion/Managers/LayoutManager.cs`:
- Around line 715-724: Update ApplyTouchpadCoordinates so coordinate samples
blend with, or use explicitly documented precedence against, RightPadX and
RightPadY values produced by ProcessButtonActions, ProcessAxisActions, and
ProcessGyroActions in the same tick. Preserve the existing coordinate conversion
while preventing direct assignments from overwriting AxisLayoutFlags.RightPad
touchpad, axis-plan, or gyro mappings.

---

Nitpick comments:
In `@HandheldCompanion/Managers/LayoutManager.cs`:
- Around line 874-893: Extract the duplicated gyro blending and axis assignment
logic from the ActionType.Joystick and ActionType.Touchpad branches into one
private helper accepting the target AxisLayoutFlags, weight, and incoming
Vector2. Update both branches to call the helper while preserving their existing
value sources and touch-button handling.

In `@HandheldCompanion/ViewModels/Layout/LayoutItemPageViewModel.cs`:
- Around line 114-126: Replace the local actionType switch in the surrounding
method with MappingViewModel.GetActionTypeDisplayName(), using the current
mapping’s action type value as required by that API. Remove the hardcoded
indices and English labels so the displayed name comes from the shared localized
resource lookup.

In `@HandheldCompanion/ViewModels/Layout/Mappings/ButtonMappingViewModel.cs`:
- Around line 280-285: Update Button2AxisVisibility so the mixed || and &&
condition has explicit parentheses around the intended grouped clauses,
preserving the current evaluation of the Joystick case and the Touchpad
axis-action case.

In `@HandheldCompanion/ViewModels/Layout/Mappings/MappingViewModel.cs`:
- Around line 365-375: Remove the redundant TouchpadSettingsVisibility and
TouchpadSwipeSettingsVisibility overrides from AxisMappingViewModel (lines
380-388), ButtonMappingViewModel (lines 418-426), and TriggerMappingViewModel
(lines 26-34), allowing them to inherit the shared virtual implementations from
MappingViewModel. Keep MappingViewModel (lines 365-375) unchanged as the single
source of truth.
- Around line 343-363: Cache the controller lookup and computed touchpad target
results used by TouchpadActionTypeVisibility and
TouchpadAxisActionTypeVisibility instead of recalculating them on every getter
evaluation. Invalidate those cached values in the existing
VirtualManager_ControllerSelected handler alongside ActionTypeChanged, ensuring
subsequent property evaluations refresh against the newly selected controller.

In `@HandheldCompanion/ViewModels/Pages/ProfilesPageViewModel.cs`:
- Around line 3188-3203: Update the MotionOutputModes synchronization logic
before the placement loop to explicitly remove every existing mode whose Value
is not contained in desiredModes, rather than pruning only from the tail
afterward. Keep the existing insertion and move behavior in the loop, and remove
the trailing count-based while removal.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fb138935-7390-4243-8909-837deb145199

📥 Commits

Reviewing files that changed from the base of the PR and between 8dc1cbd and 507fa07.

📒 Files selected for processing (31)
  • HandheldCompanion/Actions/IActions.cs
  • HandheldCompanion/Actions/MouseActions.cs
  • HandheldCompanion/Actions/TouchpadActions.cs
  • HandheldCompanion/App.config
  • HandheldCompanion/Controllers/IController.cs
  • HandheldCompanion/Controllers/Steam/GordonController.cs
  • HandheldCompanion/Controllers/Steam/NeptuneController.cs
  • HandheldCompanion/Controllers/Steam/SteamController.cs
  • HandheldCompanion/Converters/ActionTypeIndexToNameConverter.cs
  • HandheldCompanion/Converters/MotionOutputToNameConverter.cs
  • HandheldCompanion/Extensions/GlyphExtensions.cs
  • HandheldCompanion/Managers/LayoutManager.cs
  • HandheldCompanion/Misc/Layout.cs
  • HandheldCompanion/Misc/LayoutTemplate.cs
  • HandheldCompanion/Properties/Resources.Designer.cs
  • HandheldCompanion/Properties/Resources.resx
  • HandheldCompanion/Properties/Settings.Designer.cs
  • HandheldCompanion/Properties/Settings.settings
  • HandheldCompanion/Utils/InputUtils.cs
  • HandheldCompanion/ViewModels/Layout/LayoutItemPageViewModel.cs
  • HandheldCompanion/ViewModels/Layout/Mappings/AxisMappingViewModel.cs
  • HandheldCompanion/ViewModels/Layout/Mappings/ButtonMappingViewModel.cs
  • HandheldCompanion/ViewModels/Layout/Mappings/GyroMappingViewModel.cs
  • HandheldCompanion/ViewModels/Layout/Mappings/MappingViewModel.cs
  • HandheldCompanion/ViewModels/Layout/Mappings/TriggerMappingViewModel.cs
  • HandheldCompanion/ViewModels/Pages/ProfilesPageViewModel.cs
  • HandheldCompanion/Views/Pages/ActionSettingsPage.xaml
  • HandheldCompanion/Views/Pages/ControllerPage.xaml
  • HandheldCompanion/Views/Pages/ControllerPage.xaml.cs
  • HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml
  • HandheldCompanion/Views/TemplatesDictionary.xaml
💤 Files with no reviewable changes (3)
  • HandheldCompanion/Actions/MouseActions.cs
  • HandheldCompanion/Controllers/Steam/NeptuneController.cs
  • HandheldCompanion/Controllers/Steam/GordonController.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • HandheldCompanion/App.config
  • HandheldCompanion/Views/Pages/ControllerPage.xaml

Comment on lines +125 to +127
<Setting Name="TrackpadClickHaptics" Type="System.Int32" Scope="User">
<Value Profile="(Default)">3</Value>
</Setting>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'SteamTrackpadClickHaptics|TrackpadClickHaptics' \
  -g '*.cs' -g '*.settings' -g '*.config' .

fd -t f -i 'CustomSettingsProvider' . -x rg -n -C 8 \
  'Load|Read|Migrate|Upgrade|SteamTrackpadClickHaptics|TrackpadClickHaptics' {}

Repository: Valkirie/HandheldCompanion

Length of output: 26406


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## settings provider file candidates"
fd -i 'CustomSettingsProvider' . -t f

echo
echo "## relevant Provider source"
files="$(fd -i 'CustomSettingsProvider' . -t f)"
for f in $files; do
  echo "--- $f"
  nl -ba "$f" | sed -n '1,210p'
done

echo
echo "## all references to old names and migration methods"
rg -n -C 3 'SteamTrackpadClickHaptics|TrackpadClickHaptics|PropertyValuesSaved|PropertyValuesLoading|ResetProperties|SettingChanging|PropertyChanged|OnSettingChanged|SettingGetting|SettingChanging|ApplicationSettingsBase' \
  -g '*.cs' .

Repository: Valkirie/HandheldCompanion

Length of output: 367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## relevant Provider source"
for f in ./HandheldCompanion/CustomSettingsProvider.cs; do
  printf '%s\n' "--- $f"
  awk '{printf "%6d\t%s\n", NR, $0}' "$f" | sed -n '1,240p'
done

echo
echo "## all references to old names and migration methods"
rg -n -C 3 'SteamTrackpadClickHaptics|TrackpadClickHaptics|PropertyValuesSaved|PropertyValuesLoading|ResetProperties|SettingChanging|PropertyChanged|OnSettingChanged|SettingGetting|SettingChanging|ApplicationSettingsBase' \
  -g '*.cs' .

Repository: Valkirie/HandheldCompanion

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## CustomSettingsProvider relevant source"
awk '{printf "%6d\t%s\n", NR, $0}' HandheldCompanion/CustomSettingsProvider.cs | sed -n '1,180p'

echo
echo "## focused references in C# files"
rg -n -C 4 'SteamTrackpadClickHaptics|TrackpadClickHaptics' -g '*.cs' .

echo
echo "## focused migration/event references in C# files"
rg -n 'PropertyValuesSaved|PropertyValuesLoading|ResetProperties|SettingChanging|OnSettingChanged|ApplicationSettingsProvider' -g '*.cs' .

Repository: Valkirie/HandheldCompanion

Length of output: 31081


Preserve the old trackpad haptics value during settings load.

Settings.settings and App.config now declare TrackpadClickHaptics, while no persisted setting references SteamTrackpadClickHaptics. CustomSettingsProvider.GetPropertyValues returns the default value 3 when the setting node is missing, so existing user config with SteamTrackpadClickHaptics is not migrated. Check for the old key when loading TrackpadClickHaptics, then write it under the new name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/Properties/Settings.settings` around lines 125 - 127,
Update CustomSettingsProvider.GetPropertyValues to migrate the legacy
SteamTrackpadClickHaptics value when loading TrackpadClickHaptics: if the new
setting node is absent, read the old key, use its value, and persist it under
TrackpadClickHaptics instead of defaulting to 3.

Comment on lines +435 to +438
case ActionType.Touchpad:
if (SelectedTarget.Tag is not null)
SetTouchpadTarget(SelectedTarget.Tag);
break;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

This touchpad case omits the property refresh used by the sibling view models.

AxisMappingViewModel (line 912), ButtonMappingViewModel (line 742) and TriggerMappingViewModel (line 357) all call OnPropertyChanged(string.Empty) after SetTouchpadTarget. This override does not. SetTouchpadTarget changes TargetType, and base visibility properties such as Button2AxisVisibility and AxisSettingsSectionVisibility depend on TargetType. Those bindings therefore do not refresh here.

Add the same refresh, or state in a comment why the gyro mapping does not need it.

🐛 Proposed fix
                 case ActionType.Touchpad:
                     if (SelectedTarget.Tag is not null)
+                    {
                         SetTouchpadTarget(SelectedTarget.Tag);
+                        OnPropertyChanged(string.Empty);
+                    }
                     break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case ActionType.Touchpad:
if (SelectedTarget.Tag is not null)
SetTouchpadTarget(SelectedTarget.Tag);
break;
case ActionType.Touchpad:
if (SelectedTarget.Tag is not null)
{
SetTouchpadTarget(SelectedTarget.Tag);
OnPropertyChanged(string.Empty);
}
break;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HandheldCompanion/ViewModels/Layout/Mappings/GyroMappingViewModel.cs` around
lines 435 - 438, Update the Touchpad branch in the relevant mapping override to
call OnPropertyChanged(string.Empty) immediately after SetTouchpadTarget,
matching AxisMappingViewModel, ButtonMappingViewModel, and
TriggerMappingViewModel so TargetType-dependent visibility bindings refresh.

Comment thread HandheldCompanion/ViewModels/Layout/Mappings/MappingViewModel.cs
Adds per-mapping action type support and updates the action settings UI to disable unsupported options. Also broadens button visibility handling for applicable action types.
Expose the axis-to-button threshold slider for trigger mappings and update related visibility notifications.
Round response curve axis values to one decimal place before formatting, ensuring displayed labels match the intended precision.
@Valkirie

Valkirie commented Aug 10, 2026

Copy link
Copy Markdown
Owner

I'm working on it. I'm getting a hard time figuring out what's going on with TouchpadSettingsVisibility and the conditions to meet. I also believe some of those new options are almost dupes of the current ones. Like Coordinate click and Coordinate touch. I'll simplify some of it.

Adds touchpad coordinate visualization and live property updates, enables coordinate targets for DualShock 4 controllers, reorganizes mapping properties, and updates touchpad labels and designer preview visibility.
Protects concurrent access to profile collections and layout data during mutations, sanitization, and serialization by introducing profile synchronization and locking layout operations.
@Valkirie

Copy link
Copy Markdown
Owner

My open thinking on this PR. We have LeftPadTouch, RightPadTouch, TouchpadCoordinateTouch, LeftPadClick, RightPadClick, TouchpadCoordinateClick.

  • We should remove CenterPadClick.
  • TouchpadCoordinateClick should become TouchpadClick.
  • TouchpadCoordinateTouch should become TouchpadTouch.
  • TouchpadClick and TouchpadTouch should be TargetedButtons only.
  • LeftPadTouch, RightPadTouch, LeftPadClick, RightPadClick should be SourceButtons only.
  • A TouchpadActions should have a targeted finger variable (1,2 for left, right).

Replace coordinate and center-click mappings with unified touchpad click/touch gestures, including finger-aware DualSense output and updated controller targets.
Stop adding touchpad axis targets to button and trigger mapping target lists, including matching logic for axis actions.
@Valkirie

Copy link
Copy Markdown
Owner

We're getting there...
Screenshot 2026-08-10 105319
Screenshot 2026-08-10 105324

Add configurable left/right touchpad fingers for mapped actions and preserve simultaneous touch output across DualShock 4 and DualSense targets.
Use output touch activity to select the click finger, remove synthetic touch coordinates and redundant axis updates, and simplify click sequencing state.
Replace the removed MicrophoneMute flag with B5 in the dummy controller and DualSense target while preserving the mute button bit mapping.
Corrects Steam Deck and trackpad settings panel visibility, safely handles missing controllers, and marks trackpad touch/click flags as UI-only.
Move touchpad frame state and output application into TouchpadActions, simplifying LayoutManager processing while preserving axis accumulation, gesture prioritization, and haptic updates.
Unifies haptic handling across controllers, adds touchpad axis deadzone support, simplifies touchpad output processing, and removes the global trackpad haptics setting in favor of per-action configuration.
Remove per-action global haptic override handling and UI, assign default touchpad click and axis haptic settings, and enable Steam Controller gesture targets.
Allow touchpad click and touch actions to optionally use configured coordinates, expose the setting in the action UI, and enable coordinate-based default mappings.
@Valkirie
Valkirie merged commit 349e165 into Valkirie:main Aug 11, 2026
1 check was pending
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Steam Deck Trackpad Doesnt produce any haptics

2 participants