Files
SteamlessController/src/app/VirtualController.h
T
Dylan DeverillandClaude Opus 5 255f4f9cad Ask the system which virtual gamepad buses exist instead of guessing
vigem_connect returns VIGEM_ERROR_BUS_NOT_FOUND both when no bus driver is
installed and when one is present and did not answer, and the client never
records which product's bus it tried. The app inferred "driver missing" from
that error, so a driverMissing=1 that happened to be right was
indistinguishable from one that was wrong — which is most of why #65 took as
long as it did to pin down.

ViGEmBusInfo enumerates the interface GUID directly. The failure line is now
followed by what the machine actually has, and driverMissing is set from an
empty enumeration rather than inferred from an error code with several
causes.

The GUID comes from the vendored client's own header rather than a copy, so
updating third_party cannot leave a stale value behind here. That matters
because the header instructs forks to change it, and several products ship
one: measured on the #68 reporter's machine, which carries both the Oculus
and Virtual Desktop forks, neither publishes this GUID and only the Nefarius
bus appears. An empty result therefore does mean no usable bus, which is
what makes it safe to drive the "go install the driver" advice from. It also
retires the theory that the client had been connecting to a fork.

A disabled bus reads the same as an absent one — the devnode stays present
and reports a problem, but its interface is withdrawn entirely — so that
case belongs in the empty-list wording rather than in a state of its own.

ViGEmBusProbe prints the same inventory, since "vigem_connect failed" is not
a question a reporter's log can answer on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 22:17:28 -07:00

80 lines
3.0 KiB
C++

#pragma once
#include <cstdint>
#include <cstddef>
#include <atomic>
#include <functional>
#include <string>
#include <thread>
#include "BackButtonConfig.h"
#include "ControllerPlatform.h"
#include "TrackpadConfig.h"
class VirtualController {
public:
using RumbleCallback = std::function<void(uint8_t largeMotor, uint8_t smallMotor)>;
explicit VirtualController(ControllerPlatform platform, RumbleCallback rumbleCallback = {});
~VirtualController();
VirtualController(const VirtualController&) = delete;
VirtualController& operator=(const VirtualController&) = delete;
bool IsValid() const { return m_valid; }
// Established by asking the system what buses exist, not inferred from an
// error code — VIGEM_ERROR_BUS_NOT_FOUND is returned for causes other than
// an absent driver, and reporting those as "driver missing" is what sent
// the reporter in #65 looking for a driver they already had.
bool IsDriverMissing() const { return m_driverMissing; }
// Why construction failed. Only meaningful when !IsValid(); FailStage() names
// the ViGEm call that failed and LastError() carries its VIGEM_ERROR code
// (0 when the failing call returns no code, e.g. the allocators).
const char* FailStage() const { return m_failStage; }
uint32_t LastError() const { return m_lastError; }
// What buses were on the machine when construction failed, for the log and
// for choosing what to tell the user. Empty unless a connect failed.
const std::wstring& BusReport() const { return m_busReport; }
void Update(const uint8_t* buf, size_t n, const ControllerProfile& profile);
void OnRumble(uint8_t largeMotor, uint8_t smallMotor);
// DS4-only: update battery level shown in the DS4 report.
void SetBatteryState(uint8_t levelPercent, uint8_t chargeState);
private:
void StartDs4OutputThread();
void StopDs4OutputThread();
void Ds4OutputLoop();
ControllerPlatform m_platform;
void* m_client = nullptr;
void* m_target = nullptr;
RumbleCallback m_rumbleCallback;
bool m_valid = false;
bool m_driverMissing = false;
const char* m_failStage = "none";
uint32_t m_lastError = 0;
std::wstring m_busReport;
// DS4 touchpad tracking. Which pads feed it is read from the profile
// handed to Update every frame, so none of that is duplicated here.
uint8_t m_touchPacketCounter = 0;
uint8_t m_rightTracking = 0;
uint8_t m_leftTracking = 0;
bool m_wasRightTouching = false;
bool m_wasLeftTouching = false;
// DS4 IMU timestamp tracking
uint16_t m_ds4Timestamp = 0;
uint32_t m_lastImuTimestamp = 0;
bool m_hasLastImuTimestamp = false;
// DS4 battery
uint8_t m_ds4BatteryLevel = 0x0B; // full / unknown
uint8_t m_ds4BatterySpecial = 0x1B;
// DS4 output thread (polls for rumble/lightbar output reports)
std::atomic<bool> m_ds4OutputRunning{false};
std::thread m_ds4OutputThread;
};