DualSense Controller
The DualSense virtual gamepad emulates a complete PlayStation 5 DualSense controller connected via USB. The DualSense Edge variant is also supported and uses the same wire/state model.
It supports sticks, triggers, D-pad, face/shoulder buttons, PS button, touchpad click, back paddles/function buttons (Edge variant), mic mute button, IMU (gyro + accelerometer), and touchpad finger coordinates.
Use dualsense as the default device type when adding a device via the API
or client libraries.
Use dualsenseedge when you want the Edge variant.
Client Library Support
The wire protocol is abstracted by client libraries.
The Go client includes built-in types (/device/dualsense),
and generated client libraries provide equivalent structures
with proper packing.
You don't need to manually construct packets, just use the provided types and send/receive them via the device control and feedback stream.
See: API Reference
(RAW) Streaming protocol
The device stream is a bidirectional, raw TCP connection with fixed-size packets.
Input State
- 33-byte packets, little-endian layout:
- Sticks: StickLX, StickLY, StickRX, StickRY: int8 each (4 bytes) -128 to 127 per axis (-128=min, 0=center, 127=max)
- Buttons: uint32 (4 bytes, bitfield)
- DPad: uint8 (1 byte, bitfield)
- Triggers: TriggerL2, TriggerR2: uint8, uint8 (2 bytes) 0-255 (0=not pressed, 255=fully pressed)
- Touch1: Touch1X, Touch1Y: uint16 each, Touch1Active: bool (5 bytes)
- Touch2: Touch2X, Touch2Y: uint16 each, Touch2Active: bool (5 bytes)
- Gyroscope: GyroX, GyroY, GyroZ: int16 each (6 bytes, raw report values)
- Accelerometer: AccelX, AccelY, AccelZ: int16 each (6 bytes, raw report values)
See /device/dualsense/state.go for details.
Feedback (Rumble & LED)
- 6-byte packets:
- RumbleSmall: uint8, RumbleLarge: uint8 (2 bytes), 0-255 intensity values
- LED Color: LedRed, LedGreen, LedBlue: uint8 each (3 bytes), 0-255 per channel
- PlayerLeds: uint8 (1 byte), host-controlled player indicator LED mask
See /device/dualsense/state.go for the OutputState wire definition.
Reference
Button Constants
| Button | Hex Value |
|---|---|
| Square button | 0x00000010 |
| Cross (X) button | 0x00000020 |
| Circle button | 0x00000040 |
| Triangle button | 0x00000080 |
| L1 (Left bumper) | 0x00000100 |
| R1 (Right bumper) | 0x00000200 |
| L2 button | 0x00000400 |
| R2 button | 0x00000800 |
| Create button | 0x00001000 |
| Options button | 0x00002000 |
| L3 (Left stick button) | 0x00004000 |
| R3 (Right stick button) | 0x00008000 |
| PS button | 0x00010000 |
| Touchpad click | 0x00020000 |
| Mic mute button | 0x00040000 |
| Edge Variant only | --- |
| RFn button | 0x00200000 |
| LFn button | 0x00100000 |
| R4 back paddle | 0x00800000 |
| L4 back paddle | 0x00400000 |
D-Pad Constants
| D-Pad Direction | Hex Value |
|---|---|
| Up | 0x01 |
| Down | 0x02 |
| Left | 0x04 |
| Right | 0x08 |
Touchpad Coordinates
Touch coordinates are sent as Touch{1,2}X: uint16 and Touch{1,2}Y: uint16
plus an explicit boolean Touch{1,2}Active.
VIIPER clamps touch coordinates to the DualSense range:
- X: 0..1920
- Y: 0..1080
See /device/dualsense/const.go.
IMU (Gyro + Accelerometer)
VIIPER exposes DualSense IMU values as raw report-space int16 values,
while helper conversions use fixed scale factors.
Constants (see /device/dualsense/const.go):
GyroCountsPerDps = 16.384AccelCountsPerMS2 = 835.07
Gyro (degrees/second):
raw_gyro = round(gyro_dps * GyroCountsPerDps)
gyro_dps = raw_gyro / GyroCountsPerDps
Accelerometer (m/s2):
raw_accel = round(accel_ms2 * AccelCountsPerMS2)
accel_ms2 = raw_accel / AccelCountsPerMS2
On device creation, VIIPER initializes the accelerometer to a controller
lying flat with gravity downwards (AccelZ = -8192, i.e. roughly -1g).
Helpers are in /device/dualsense/helpers.go.
API
| Function | Description |
|---|---|
CreateDualSenseDevice(...) |
Create a virtual DualSense device |
CreateDualSenseEdgeDevice(...) |
Create a virtual DualSense Edge |
SetDualSenseDeviceState(handle, state) |
Push input state |
SetDualSenseOutputCallback(handle, cb) |
Register output callback |
RemoveDualSenseDevice(handle) |
Remove the device |
Input state
typedef struct {
int8_t LX;
int8_t LY;
int8_t RX;
int8_t RY;
uint32_t Buttons;
uint8_t DPad;
uint8_t L2;
uint8_t R2;
uint16_t Touch1X;
uint16_t Touch1Y;
uint8_t Touch1Active;
uint16_t Touch2X;
uint16_t Touch2Y;
uint8_t Touch2Active;
int16_t GyroX;
int16_t GyroY;
int16_t GyroZ;
int16_t AccelX;
int16_t AccelY;
int16_t AccelZ;
} DSDeviceState;
Meta state
Optional metadata can be provided during CreateDualSenseDevice
and CreateDualSenseEdgeDevice.
typedef struct {
const char* SerialNumber; // NULL = use default
const char* MACAddress; // NULL = use default
const char* Board; // NULL = use default
uint8_t BatteryStatus; // 0 = use default
double TemperatureCelsius; // 0 = use default
double BatteryVoltage; // 0 = use default
const char* ShellColor; // NULL = use default (e.g. "00", "Z1")
} DSMetaState;
Output callback
Called when the host sends rumble or LED commands to the device.
typedef void (*DSOutputCallback)(
DSDeviceHandle handle,
uint8_t rumbleSmall,
uint8_t rumbleLarge,
uint8_t ledRed,
uint8_t ledGreen,
uint8_t ledBlue,
uint8_t playerLeds
);
Pass NULL to SetDualSenseOutputCallback to clear
a previously registered callback.