Implement touch, motion, and battery for DS4

This commit is contained in:
Cameron Gutman
2023-07-08 18:08:35 -05:00
parent 309fcc4142
commit 2ed2dd5e0e

View File

@@ -42,6 +42,12 @@ namespace platf {
DS4_LIGHTBAR_COLOR /* led_color */,
void *userdata);
struct gp_touch_context_t {
uint8_t pointerIndex;
uint16_t x;
uint16_t y;
};
struct gamepad_context_t {
target_t gp;
feedback_queue_t feedback_queue;
@@ -51,6 +57,10 @@ namespace platf {
DS4_REPORT_EX ds4;
} report;
// Map from pointer ID to pointer index
std::map<uint32_t, uint8_t> pointer_id_map;
uint8_t available_pointers;
gamepad_feedback_msg_t last_rumble;
gamepad_feedback_msg_t last_rgb_led;
};
@@ -81,7 +91,7 @@ namespace platf {
.bTriggerL = 0,
.bTriggerR = 0,
.wTimestamp = 0,
.bBatteryLvl = 99,
.bBatteryLvl = 0xFF,
.wGyroX = 0,
.wGyroY = 0,
.wGyroZ = 0,
@@ -89,9 +99,9 @@ namespace platf {
.wAccelY = 0,
.wAccelZ = 0,
._bUnknown1 = { 0x00, 0x00, 0x00, 0x00, 0x00 },
.bBatteryLvlSpecial = 0x10, // Wired
.bBatteryLvlSpecial = 0x1A, // Wired - Full battery
._bUnknown2 = { 0x00, 0x00 },
.bTouchPacketsN = 0,
.bTouchPacketsN = 1,
.sCurrentTouch = ds4_touch_unused,
.sPreviousTouch = { ds4_touch_unused, ds4_touch_unused } } }
};
@@ -206,6 +216,13 @@ namespace platf {
// Set initial accelerometer and gyro state
ds4_update_motion(gamepad, LI_MOTION_TYPE_ACCEL, 0.0f, EARTH_G, 0.0f);
ds4_update_motion(gamepad, LI_MOTION_TYPE_GYRO, 0.0f, 0.0f, 0.0f);
// Request motion events from the client at 100 Hz
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(nr, LI_MOTION_TYPE_ACCEL, 100));
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(nr, LI_MOTION_TYPE_GYRO, 100));
// We support pointer index 0 and 1
gamepad.available_pointers = 0x3;
}
auto status = vigem_target_add(client.get(), gamepad.gp.get());
@@ -828,6 +845,9 @@ namespace platf {
}
auto &gamepad = vigem->gamepads[nr];
if (!gamepad.gp) {
return;
}
VIGEM_ERROR status;
@@ -852,7 +872,97 @@ namespace platf {
*/
void
gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
// Unimplemented feature - platform_caps::controller_touch
auto vigem = ((input_raw_t *) input.get())->vigem;
// If there is no gamepad support
if (!vigem) {
return;
}
auto &gamepad = vigem->gamepads[touch.gamepadNumber];
if (!gamepad.gp) {
return;
}
// Touch is only supported on DualShock 4 controllers
if (vigem_target_get_type(gamepad.gp.get()) != DualShock4Wired) {
return;
}
auto &report = gamepad.report.ds4.Report;
uint8_t pointerIndex;
if (touch.eventType == LI_TOUCH_EVENT_DOWN) {
if (gamepad.available_pointers & 0x1) {
// Reserve pointer index 0 for this touch
gamepad.pointer_id_map[touch.pointerId] = pointerIndex = 0;
gamepad.available_pointers &= ~(1 << pointerIndex);
// Set pointer 0 down
report.sCurrentTouch.bIsUpTrackingNum1 &= ~0x80;
report.sCurrentTouch.bIsUpTrackingNum1++;
}
else if (gamepad.available_pointers & 0x2) {
// Reserve pointer index 1 for this touch
gamepad.pointer_id_map[touch.pointerId] = pointerIndex = 1;
gamepad.available_pointers &= ~(1 << pointerIndex);
// Set pointer 1 down
report.sCurrentTouch.bIsUpTrackingNum2 &= ~0x80;
report.sCurrentTouch.bIsUpTrackingNum2++;
}
else {
BOOST_LOG(warning) << "No more free pointer indices! Did the client miss an touch up event?"sv;
return;
}
}
else {
auto i = gamepad.pointer_id_map.find(touch.pointerId);
if (i == gamepad.pointer_id_map.end()) {
BOOST_LOG(warning) << "Pointer ID not found! Did the client miss a touch down event?"sv;
return;
}
pointerIndex = (*i).second;
if (touch.eventType == LI_TOUCH_EVENT_UP) {
// Remove the pointer index mapping
gamepad.pointer_id_map.erase(i);
// Set pointer up
if (pointerIndex == 0) {
report.sCurrentTouch.bIsUpTrackingNum1 |= 0x80;
}
else {
report.sCurrentTouch.bIsUpTrackingNum2 |= 0x80;
}
// Free the pointer index
gamepad.available_pointers |= (1 << pointerIndex);
}
}
// Touchpad is 1920x943 according to ViGEm
uint16_t x = touch.x * 1920;
uint16_t y = touch.y * 943;
uint8_t touchData[] = {
(uint8_t) (x & 0xFF), // Low 8 bits of X
(uint8_t) (((x >> 8) & 0x0F) | ((y & 0x0F) << 4)), // High 4 bits of X and low 4 bits of Y
(uint8_t) (((y >> 4) & 0xFF)) // High 8 bits of Y
};
report.sCurrentTouch.bPacketCounter++;
if (pointerIndex == 0) {
memcpy(report.sCurrentTouch.bTouchData1, touchData, sizeof(touchData));
}
else {
memcpy(report.sCurrentTouch.bTouchData2, touchData, sizeof(touchData));
}
auto status = vigem_target_ds4_update_ex(vigem->client.get(), gamepad.gp.get(), gamepad.report.ds4);
if (!VIGEM_SUCCESS(status)) {
BOOST_LOG(warning) << "Couldn't send gamepad touch input to ViGEm ["sv << util::hex(status).to_string_view() << ']';
}
}
/**
@@ -862,7 +972,29 @@ namespace platf {
*/
void
gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
// Unimplemented
auto vigem = ((input_raw_t *) input.get())->vigem;
// If there is no gamepad support
if (!vigem) {
return;
}
auto &gamepad = vigem->gamepads[motion.gamepadNumber];
if (!gamepad.gp) {
return;
}
// Motion is only supported on DualShock 4 controllers
if (vigem_target_get_type(gamepad.gp.get()) != DualShock4Wired) {
return;
}
ds4_update_motion(gamepad, motion.motionType, motion.x, motion.y, motion.z);
auto status = vigem_target_ds4_update_ex(vigem->client.get(), gamepad.gp.get(), gamepad.report.ds4);
if (!VIGEM_SUCCESS(status)) {
BOOST_LOG(warning) << "Couldn't send gamepad motion input to ViGEm ["sv << util::hex(status).to_string_view() << ']';
}
}
/**
@@ -872,7 +1004,75 @@ namespace platf {
*/
void
gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
// Unimplemented
auto vigem = ((input_raw_t *) input.get())->vigem;
// If there is no gamepad support
if (!vigem) {
return;
}
auto &gamepad = vigem->gamepads[battery.gamepadNumber];
if (!gamepad.gp) {
return;
}
// Battery is only supported on DualShock 4 controllers
if (vigem_target_get_type(gamepad.gp.get()) != DualShock4Wired) {
return;
}
// For details on the report format of these battery level fields, see:
// https://github.com/torvalds/linux/blob/946c6b59c56dc6e7d8364a8959cb36bf6d10bc37/drivers/hid/hid-playstation.c#L2305-L2314
auto &report = gamepad.report.ds4.Report;
// Update the battery state if it is known
switch (battery.state) {
case LI_BATTERY_STATE_CHARGING:
case LI_BATTERY_STATE_DISCHARGING:
if (battery.state == LI_BATTERY_STATE_CHARGING) {
report.bBatteryLvlSpecial |= 0x10; // Connected via USB
}
else {
report.bBatteryLvlSpecial &= ~0x10; // Not connected via USB
}
// If there was a special battery status set before, clear that and
// initialize the battery level to 50%. It will be overwritten below
// if the actual percentage is known.
if ((report.bBatteryLvlSpecial & 0xF) > 0xA) {
report.bBatteryLvlSpecial = (report.bBatteryLvlSpecial & ~0xF) | 0x5;
}
break;
case LI_BATTERY_STATE_FULL:
report.bBatteryLvlSpecial = 0x1B; // USB + Battery Full
report.bBatteryLvl = 0xFF;
break;
case LI_BATTERY_STATE_NOT_PRESENT:
case LI_BATTERY_STATE_NOT_CHARGING:
report.bBatteryLvlSpecial = 0x1F; // USB + Charging Error
break;
default:
break;
}
// Update the battery level if it is known
if (battery.percentage != LI_BATTERY_PERCENTAGE_UNKNOWN) {
report.bBatteryLvl = battery.percentage * 255 / 100;
// Don't overwrite low nibble if there's a special status there (see above)
if ((report.bBatteryLvlSpecial & 0x10) && (report.bBatteryLvlSpecial & 0xF) <= 0xA) {
report.bBatteryLvlSpecial = (report.bBatteryLvlSpecial & ~0xF) | ((battery.percentage + 5) / 10);
}
}
auto status = vigem_target_ds4_update_ex(vigem->client.get(), gamepad.gp.get(), gamepad.report.ds4);
if (!VIGEM_SUCCESS(status)) {
BOOST_LOG(warning) << "Couldn't send gamepad battery input to ViGEm ["sv << util::hex(status).to_string_view() << ']';
}
}
void
@@ -902,6 +1102,13 @@ namespace platf {
*/
platform_caps::caps_t
get_capabilities() {
return 0;
platform_caps::caps_t caps = 0;
// We support controller touchpad input as long as we're not emulating X360
if (config::input.gamepad != "x360"sv) {
caps |= platform_caps::controller_touch;
}
return caps;
}
} // namespace platf