Skip to content

Commit cf62637

Browse files
endriftslouken
authored andcommitted
joystick: Add support for Xbox One Chatpad attachment
1 parent 83cbf7f commit cf62637

File tree

1 file changed

+131
-6
lines changed

1 file changed

+131
-6
lines changed

src/joystick/hidapi/SDL_hidapi_gip.c

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#ifdef SDL_JOYSTICK_HIDAPI
2424

25+
#include "../../events/SDL_keyboard_c.h"
2526
#include "../SDL_sysjoystick.h"
2627
#include "SDL_hidapijoystick_c.h"
2728
#include "SDL_hidapi_rumble.h"
@@ -427,6 +428,7 @@ typedef struct GIP_Attachment
427428
struct GIP_Device *device;
428429
Uint8 attachment_index;
429430
SDL_JoystickID joystick;
431+
SDL_KeyboardID keyboard;
430432

431433
Uint8 fragment_message;
432434
Uint16 total_length;
@@ -461,6 +463,12 @@ typedef struct GIP_Attachment
461463

462464
Uint8 last_input[64];
463465

466+
Uint8 last_modifiers;
467+
bool capslock;
468+
SDL_Keycode last_key;
469+
Uint32 altcode;
470+
int altcode_digit;
471+
464472
GIP_AttachmentType attachment_type;
465473
GIP_PaddleFormat paddle_format;
466474
Uint32 features;
@@ -769,6 +777,11 @@ static bool GIP_SendVendorMessage(
769777
NULL);
770778
}
771779

780+
static bool GIP_AttachmentIsController(GIP_Attachment *attachment)
781+
{
782+
return attachment->attachment_type != GIP_TYPE_CHATPAD;
783+
}
784+
772785
static void GIP_MetadataFree(GIP_Metadata *metadata)
773786
{
774787
if (metadata->device.audio_formats) {
@@ -933,6 +946,9 @@ static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes,
933946
}
934947
device->hid_descriptor = SDL_malloc(device->hid_descriptor_size);
935948
SDL_memcpy(device->hid_descriptor, &bytes[buffer_offset + 1], device->hid_descriptor_size);
949+
#ifdef DEBUG_XBOX_PROTOCOL
950+
HIDAPI_DumpPacket("GIP received HID descriptor: size = %d", device->hid_descriptor, device->hid_descriptor_size);
951+
#endif
936952
}
937953
}
938954

@@ -1197,9 +1213,13 @@ static bool GIP_SendInitSequence(GIP_Attachment *attachment)
11971213
GIP_SendVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, 0, NULL, 0);
11981214
}
11991215

1200-
if (!attachment->joystick) {
1216+
if ((!attachment->attachment_index || GIP_AttachmentIsController(attachment)) && !attachment->joystick) {
12011217
return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick);
12021218
}
1219+
if (attachment->attachment_type == GIP_TYPE_CHATPAD && !attachment->keyboard) {
1220+
attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment;
1221+
SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad", true);
1222+
}
12031223
return true;
12041224
}
12051225

@@ -1424,7 +1444,7 @@ static bool GIP_HandleCommandMetadataRespose(
14241444
{
14251445
GIP_Metadata metadata = {0};
14261446
const GUID *expected_guid = NULL;
1427-
bool found_expected_guid = false;
1447+
bool found_expected_guid;
14281448
bool found_controller_guid = false;
14291449
int i;
14301450

@@ -1494,6 +1514,7 @@ static bool GIP_HandleCommandMetadataRespose(
14941514
}
14951515
}
14961516

1517+
found_expected_guid = !expected_guid;
14971518
for (i = 0; i < metadata.device.num_supported_interfaces; i++) {
14981519
const GUID* guid = &metadata.device.supported_interfaces[i];
14991520
#ifdef DEBUG_XBOX_PROTOCOL
@@ -1539,7 +1560,7 @@ static bool GIP_HandleCommandMetadataRespose(
15391560
}
15401561
}
15411562

1542-
if (!found_expected_guid || !found_controller_guid) {
1563+
if (!found_expected_guid || (GIP_AttachmentIsController(attachment) && !found_controller_guid)) {
15431564
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
15441565
"GIP: Controller was missing expected GUID. This controller probably won't work on an actual Xbox.");
15451566
}
@@ -1701,9 +1722,95 @@ static bool GIP_HandleCommandHidReport(
17011722
const Uint8 *bytes,
17021723
int num_bytes)
17031724
{
1704-
// TODO
1705-
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented HID Report message");
1706-
return false;
1725+
Uint64 timestamp = SDL_GetTicksNS();
1726+
// SDL doesn't have HID descriptor parsing, so we have to hardcode for the Chatpad descriptor instead.
1727+
// I don't know of any other devices that emit HID reports, so this should be safe.
1728+
if (attachment->attachment_type != GIP_TYPE_CHATPAD || !attachment->keyboard || num_bytes != 8) {
1729+
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented HID Report message");
1730+
return false;
1731+
}
1732+
1733+
Uint8 modifiers = bytes[0];
1734+
Uint8 changed_modifiers = modifiers ^ attachment->last_modifiers;
1735+
if (changed_modifiers & 0x02) {
1736+
if (modifiers & 0x02) {
1737+
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, true);
1738+
} else {
1739+
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, false);
1740+
}
1741+
}
1742+
// The chatpad has several non-ASCII characters that it sends as Alt codes
1743+
if (changed_modifiers & 0x04) {
1744+
if (modifiers & 0x04) {
1745+
attachment->altcode_digit = 0;
1746+
attachment->altcode = 0;
1747+
} else {
1748+
if (attachment->altcode_digit == 4) {
1749+
char utf8[4] = {0};
1750+
// Some Alt codes don't match their Unicode codepoint for some reason
1751+
switch (attachment->altcode) {
1752+
case 128:
1753+
SDL_UCS4ToUTF8(0x20AC, utf8);
1754+
break;
1755+
case 138:
1756+
SDL_UCS4ToUTF8(0x0160, utf8);
1757+
break;
1758+
case 140:
1759+
SDL_UCS4ToUTF8(0x0152, utf8);
1760+
break;
1761+
case 154:
1762+
SDL_UCS4ToUTF8(0x0161, utf8);
1763+
break;
1764+
case 156:
1765+
SDL_UCS4ToUTF8(0x0153, utf8);
1766+
break;
1767+
default:
1768+
SDL_UCS4ToUTF8(attachment->altcode, utf8);
1769+
break;
1770+
}
1771+
SDL_SendKeyboardText(utf8);
1772+
}
1773+
attachment->altcode_digit = -1;
1774+
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, true);
1775+
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, false);
1776+
}
1777+
}
1778+
1779+
if (!bytes[2] && attachment->last_key) {
1780+
if (attachment->last_key == SDL_SCANCODE_CAPSLOCK) {
1781+
attachment->capslock = !attachment->capslock;
1782+
}
1783+
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, attachment->last_key, false);
1784+
if (!(attachment->last_modifiers & 0xfd)) {
1785+
SDL_Keycode keycode = SDL_GetKeymapKeycode(NULL,
1786+
attachment->last_key,
1787+
((attachment->last_modifiers & 0x02) || attachment->capslock) ? SDL_KMOD_SHIFT : 0);
1788+
if (keycode && keycode < 0x80) {
1789+
char text[2] = { (char)keycode };
1790+
SDL_SendKeyboardText(text);
1791+
}
1792+
}
1793+
attachment->last_key = 0;
1794+
} else {
1795+
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, bytes[2], true);
1796+
attachment->last_key = bytes[2];
1797+
1798+
if ((modifiers & 0x04) && attachment->altcode_digit >= 0) {
1799+
int digit = bytes[2] - SDL_SCANCODE_KP_1 + 1;
1800+
if (digit < 1 || digit > 10) {
1801+
attachment->altcode_digit = -1;
1802+
} else {
1803+
attachment->altcode_digit++;
1804+
attachment->altcode *= 10;
1805+
if (digit < 10) {
1806+
attachment->altcode += digit;
1807+
}
1808+
}
1809+
}
1810+
}
1811+
1812+
attachment->last_modifiers = modifiers;
1813+
return true;
17071814
}
17081815

17091816
static bool GIP_HandleCommandExtended(
@@ -2085,6 +2192,18 @@ static bool GIP_HandleSystemMessage(
20852192
const Uint8 *bytes,
20862193
int num_bytes)
20872194
{
2195+
if (attachment->attachment_index > 0 && attachment->attachment_type == GIP_TYPE_UNKNOWN) {
2196+
// XXX If we reattach to a controller after it's been initialized, it might have
2197+
// attachments we don't know about. Try to figure out what this one is.
2198+
if (header->message_type == GIP_CMD_HID_REPORT && num_bytes == 8) {
2199+
if (!attachment->keyboard) {
2200+
attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment;
2201+
SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad", true);
2202+
}
2203+
attachment->attachment_type = GIP_TYPE_CHATPAD;
2204+
attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_HID_REPORT);
2205+
}
2206+
}
20882207
if (!GIP_SupportsSystemMessage(attachment, header->message_type, true)) {
20892208
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
20902209
"GIP: Received claimed-unsupported system message type %02x",
@@ -2128,6 +2247,9 @@ static GIP_Attachment * GIP_EnsureAttachment(GIP_Device *device, Uint8 attachmen
21282247
if (!attachment) {
21292248
attachment = SDL_calloc(1, sizeof(*attachment));
21302249
attachment->attachment_index = attachment_index;
2250+
if (attachment_index > 0) {
2251+
attachment->attachment_type = GIP_TYPE_UNKNOWN;
2252+
}
21312253
attachment->device = device;
21322254
attachment->metadata.device.in_system_messages[0] = GIP_DEFAULT_IN_SYSTEM_MESSAGES;
21332255
attachment->metadata.device.out_system_messages[0] = GIP_DEFAULT_OUT_SYSTEM_MESSAGES;
@@ -2679,6 +2801,9 @@ static void HIDAPI_DriverGIP_FreeDevice(SDL_HIDAPI_Device *device)
26792801
SDL_free(attachment->fragment_data);
26802802
attachment->fragment_data = NULL;
26812803
}
2804+
if (attachment->keyboard) {
2805+
SDL_RemoveKeyboard(attachment->keyboard, true);
2806+
}
26822807
GIP_MetadataFree(&attachment->metadata);
26832808
SDL_free(attachment);
26842809
context->attachments[i] = NULL;

0 commit comments

Comments
 (0)