From b031ebb25d62b3bce016f5987931bef4f4b52a4a Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Thu, 26 Sep 2024 06:10:10 +0200 Subject: [PATCH] implement controller support --- input/ControllerInput.h | 83 +++ input/Input.h | 506 ++++++++++++++++--- platform/win32/input/DirectInput.h | 0 platform/win32/input/RawInput.h | 105 ++-- platform/win32/input/XInput.h | 43 +- platform/win32/input/controller/DualSense.h | 0 platform/win32/input/controller/DualShock4.h | 113 +++++ platform/win32/input/controller/XBoxS.h | 1 + 8 files changed, 683 insertions(+), 168 deletions(-) create mode 100644 input/ControllerInput.h create mode 100644 platform/win32/input/DirectInput.h create mode 100644 platform/win32/input/controller/DualSense.h create mode 100644 platform/win32/input/controller/DualShock4.h create mode 100644 platform/win32/input/controller/XBoxS.h diff --git a/input/ControllerInput.h b/input/ControllerInput.h new file mode 100644 index 0000000..1d3ef25 --- /dev/null +++ b/input/ControllerInput.h @@ -0,0 +1,83 @@ +/** + * Jingga + * + * @copyright Jingga + * @license OMS License 2.0 + * @version 1.0.0 + * @link https://jingga.app + */ +#ifndef TOS_PLATFORM_WIN32_INPUT_CONTROLLER_CONTROLLER_INPUT_H +#define TOS_PLATFORM_WIN32_INPUT_CONTROLLER_CONTROLLER_INPUT_H + +#include "../stdlib/Types.h" + +enum ControllerButton { + CONTROLLER_BUTTON_NONE, // Needs to be skipped for input system (see -1) + + CONTROLLER_BUTTON_STICK_LEFT_BUTTON, + CONTROLLER_BUTTON_STICK_LEFT_HORIZONTAL, + CONTROLLER_BUTTON_STICK_LEFT_VERTOCAL, + + CONTROLLER_BUTTON_STICK_RIGHT_BUTTON, + CONTROLLER_BUTTON_STICK_RIGHT_HORIZONTAL, + CONTROLLER_BUTTON_STICK_RIGHT_VERTOCAL, + + CONTROLLER_BUTTON_SHOULDER_LEFT_TRIGGER, + CONTROLLER_BUTTON_SHOULDER_LEFT_BUTTON, + + CONTROLLER_BUTTON_SHOULDER_RIGHT_TRIGGER, + CONTROLLER_BUTTON_SHOULDER_RIGHT_BUTTON, + + CONTROLLER_BUTTON_X, + CONTROLLER_BUTTON_C, + CONTROLLER_BUTTON_T, + CONTROLLER_BUTTON_S, + + CONTROLLER_BUTTON_DPAD_LEFT, + CONTROLLER_BUTTON_DPAD_RIGHT, + CONTROLLER_BUTTON_DPAD_UP, + CONTROLLER_BUTTON_DPAD_DOWN, + + CONTROLLER_BUTTON_OTHER_0, + CONTROLLER_BUTTON_OTHER_1, + CONTROLLER_BUTTON_OTHER_2, + CONTROLLER_BUTTON_OTHER_3, + CONTROLLER_BUTTON_OTHER_4, + CONTROLLER_BUTTON_OTHER_5, + CONTROLLER_BUTTON_OTHER_6, + CONTROLLER_BUTTON_OTHER_7, +}; + +struct ControllerInput { + uint8 stick_left_button; + int8 stick_left_x; + int8 stick_left_y; + + uint8 stick_right_button; + int8 stick_right_x; + int8 stick_right_y; + + int8 shoulder_trigger_left; + int8 shoulder_trigger_right; + + uint8 shoulder_button_left; + uint8 shoulder_button_right; + + uint8 button_X; + uint8 button_C; + uint8 button_T; + uint8 button_S; + + uint8 dpad_left; + uint8 dpad_right; + uint8 dpad_up; + uint8 dpad_down; + + int16 gyro_x; + int16 gyro_y; + int16 gyro_z; + + uint8 button_other[8]; +}; + +#endif \ No newline at end of file diff --git a/input/Input.h b/input/Input.h index 65c33b6..e896f08 100644 --- a/input/Input.h +++ b/input/Input.h @@ -32,8 +32,8 @@ // These values are used as bit flags to hint if a "key" is a keyboard/primary or mouse/secondary input // When adding a keybind the "key" can only be uint8 but we expand it to an int and set the first bit accordingly #define INPUT_MOUSE_PREFIX 0 -#define INPUT_KEYBOARD_PREFIX 16384 -#define INPUT_CONTROLLER_PREFIX 32768 +#define INPUT_KEYBOARD_PREFIX 8192 +#define INPUT_CONTROLLER_PREFIX 16384 #define INPUT_TYPE_MOUSE_KEYBOARD 0x01 #define INPUT_TYPE_CONTROLLER 0x02 @@ -43,6 +43,7 @@ #include "../stdlib/Types.h" #include "../utils/BitUtils.h" +#include "ControllerInput.h" #ifdef _WIN32 #include @@ -58,7 +59,10 @@ struct InputMapping { // A hotkey can be bound to a combination of up to 3 key/button presses uint8 hotkey_count; - uint16* hotkeys; + + // negative hotkeys mean any of them needs to be matched, positive hotkeys means all of them need to be matched + // mixing positive and negative keys for one hotkey is not possible + int16* hotkeys; }; enum KeyState { @@ -71,7 +75,7 @@ struct InputKey { // Includes flag for mouse, keyboard, controller uint16 key_id; uint16 key_state; - uint16 value; // e.g. stick/trigger keys + int16 value; // e.g. stick/trigger keys uint64 time; // when was this action performed (useful to decide if key state is held vs pressed) }; @@ -88,27 +92,46 @@ struct InputState { uint32 x; uint32 y; + + // Secondary coordinate input (usually used by controllers) + int32 dx2; + int32 dy2; + + uint32 x2; + uint32 y2; + + // Tertiary coordinate input (usually used by controllers) + int32 dx3; + int32 dy3; + + uint32 x3; + uint32 y3; }; struct Input { // Device - bool is_connected = false; + bool is_connected; #ifdef _WIN32 - // @todo maybe replace with id?! - // -> remove _WIN32 section + // @question maybe replace with id?! + // -> remove _WIN32 section? HANDLE handle_keyboard; HANDLE handle_mouse; HANDLE handle_controller; #endif - bool state_change_button = false; - bool state_change_mouse = false; + bool state_change_button; + bool state_change_mouse; bool mouse_movement; InputState state; - InputMapping input_mapping; + uint64 time_last_input_check; + + uint32 deadzone = 10; + + InputMapping input_mapping1; + InputMapping input_mapping2; }; inline @@ -122,7 +145,7 @@ void input_clean_state(InputState* state) } inline -bool input_action_exists(const InputState* state, uint16 key) +bool input_action_exists(const InputState* state, int16 key) { return state->state_keys[0].key_id == key || state->state_keys[1].key_id == key @@ -138,23 +161,23 @@ bool input_action_exists(const InputState* state, uint16 key) } inline -bool input_is_down(const InputState* state, uint16 key) +bool input_is_down(const InputState* state, int16 key) { - return (state->state_keys[0].key_id == key && state->state_keys[0].key_state < KEY_STATE_RELEASED) - || (state->state_keys[1].key_id == key && state->state_keys[1].key_state < KEY_STATE_RELEASED) - || (state->state_keys[2].key_id == key && state->state_keys[2].key_state < KEY_STATE_RELEASED) - || (state->state_keys[3].key_id == key && state->state_keys[3].key_state < KEY_STATE_RELEASED) - || (state->state_keys[4].key_id == key && state->state_keys[4].key_state < KEY_STATE_RELEASED) - || (state->state_keys[4].key_id == key && state->state_keys[4].key_state < KEY_STATE_RELEASED) - || (state->state_keys[5].key_id == key && state->state_keys[5].key_state < KEY_STATE_RELEASED) - || (state->state_keys[6].key_id == key && state->state_keys[6].key_state < KEY_STATE_RELEASED) - || (state->state_keys[7].key_id == key && state->state_keys[7].key_state < KEY_STATE_RELEASED) - || (state->state_keys[8].key_id == key && state->state_keys[8].key_state < KEY_STATE_RELEASED) - || (state->state_keys[9].key_id == key && state->state_keys[9].key_state < KEY_STATE_RELEASED); + return (state->state_keys[0].key_id == key && state->state_keys[0].key_state != KEY_STATE_RELEASED) + || (state->state_keys[1].key_id == key && state->state_keys[1].key_state != KEY_STATE_RELEASED) + || (state->state_keys[2].key_id == key && state->state_keys[2].key_state != KEY_STATE_RELEASED) + || (state->state_keys[3].key_id == key && state->state_keys[3].key_state != KEY_STATE_RELEASED) + || (state->state_keys[4].key_id == key && state->state_keys[4].key_state != KEY_STATE_RELEASED) + || (state->state_keys[4].key_id == key && state->state_keys[4].key_state != KEY_STATE_RELEASED) + || (state->state_keys[5].key_id == key && state->state_keys[5].key_state != KEY_STATE_RELEASED) + || (state->state_keys[6].key_id == key && state->state_keys[6].key_state != KEY_STATE_RELEASED) + || (state->state_keys[7].key_id == key && state->state_keys[7].key_state != KEY_STATE_RELEASED) + || (state->state_keys[8].key_id == key && state->state_keys[8].key_state != KEY_STATE_RELEASED) + || (state->state_keys[9].key_id == key && state->state_keys[9].key_state != KEY_STATE_RELEASED); } inline -bool input_is_pressed(const InputState* state, uint16 key) +bool input_is_pressed(const InputState* state, int16 key) { return (state->state_keys[0].key_id == key && state->state_keys[0].key_state == KEY_STATE_PRESSED) || (state->state_keys[1].key_id == key && state->state_keys[1].key_state == KEY_STATE_PRESSED) @@ -170,7 +193,7 @@ bool input_is_pressed(const InputState* state, uint16 key) } inline -bool input_is_held(const InputState* state, uint16 key) +bool input_is_held(const InputState* state, int16 key) { return (state->state_keys[0].key_id == key && state->state_keys[0].key_state == KEY_STATE_HELD) || (state->state_keys[1].key_id == key && state->state_keys[1].key_state == KEY_STATE_HELD) @@ -186,7 +209,7 @@ bool input_is_held(const InputState* state, uint16 key) } inline -bool input_is_released(const InputState* state, uint16 key) +bool input_is_released(const InputState* state, int16 key) { return (state->state_keys[0].key_id == key && state->state_keys[0].key_state == KEY_STATE_RELEASED) || (state->state_keys[1].key_id == key && state->state_keys[1].key_state == KEY_STATE_RELEASED) @@ -202,7 +225,7 @@ bool input_is_released(const InputState* state, uint16 key) } inline -bool input_was_down(const InputState* state, uint16 key) +bool input_was_down(const InputState* state, int16 key) { return (state->state_keys[0].key_id == key && state->state_keys[0].key_state == KEY_STATE_RELEASED) || (state->state_keys[1].key_id == key && state->state_keys[1].key_state == KEY_STATE_RELEASED) @@ -220,7 +243,7 @@ bool input_was_down(const InputState* state, uint16 key) inline bool inputs_are_down( const InputState* state, - uint16 key0, uint16 key1 = 0, uint16 key2 = 0, uint16 key3 = 0, uint16 key4 = 0 + int16 key0, int16 key1 = 0, int16 key2 = 0, int16 key3 = 0, int16 key4 = 0 ) { return (key0 != 0 && input_is_down(state, key0)) && (key1 == 0 || input_is_down(state, key1)) @@ -233,7 +256,7 @@ bool inputs_are_down( void input_add_hotkey( InputMapping* mapping, uint8 hotkey, - uint32 key0, uint32 key1 = 0, uint32 key2 = 0 + int32 key0, int32 key1 = 0, int32 key2 = 0 ) { int count = 0; @@ -241,19 +264,19 @@ input_add_hotkey( // Define required keys for hotkey if (key0 != 0) { // Note: -1 since the hotkeys MUST start at 1 (0 is a special value for empty) - mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION] = (uint16) key0; + mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION] = (int16) key0; ++count; } if (key1 != 0) { // Note: -1 since the hotkeys MUST start at 1 (0 is a special value for empty) - mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION + count] = (uint16) key1; + mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION + count] = (int16) key1; ++count; } if (key2 != 0) { // Note: -1 since the hotkeys MUST start at 1 (0 is a special value for empty) - mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION + count] = (uint16) key2; + mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION + count] = (int16) key2; } int key0_offset = ((bool) (key0 & INPUT_KEYBOARD_PREFIX)) * MAX_MOUSE_KEYS @@ -307,9 +330,9 @@ bool hotkey_is_active(const InputState* state, uint8 hotkey) inline bool hotkey_keys_are_active(const InputState* __restrict state, const InputMapping* __restrict mapping, uint8 hotkey) { - uint16 key0 = mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION]; - uint16 key1 = mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION + 1]; - uint16 key2 = mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION + 2]; + int16 key0 = mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION]; + int16 key1 = mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION + 1]; + int16 key2 = mapping->hotkeys[(hotkey - 1) * MAX_HOTKEY_COMBINATION + 2]; // This may seem a little bit confusing but we don't care if a input key is down or up // Any state means it was used recently BUT NOT YET HANDLED @@ -317,19 +340,19 @@ bool hotkey_keys_are_active(const InputState* __restrict state, const InputMappi // Therefore, if a key has a state -> treat it as if active bool is_active = input_action_exists(state, key0); if (!is_active || key1 == 0) { - return is_active; + return is_active || input_action_exists(state, -key0); } is_active &= input_action_exists(state, key1); if (!is_active || key2 == 0) { - return is_active; + return is_active || input_action_exists(state, -key1); } - return (is_active &= input_action_exists(state, key2)); + return (is_active &= input_action_exists(state, key2)) || input_action_exists(state, -key2); } inline -void input_set_state(InputState* state, uint16 key_id, uint16 new_state) +void input_set_state(InputState* state, InputKey* __restrict new_key) { InputKey* free_state = NULL; bool action_required = true; @@ -337,8 +360,10 @@ void input_set_state(InputState* state, uint16 key_id, uint16 new_state) for (int j = 0; j < MAX_KEY_STATES; ++j) { if (!free_state && state->state_keys[j].key_id == 0) { free_state = &state->state_keys[j]; - } else if (state->state_keys[j].key_id == key_id) { - state->state_keys[j].key_state = new_state; + } else if (state->state_keys[j].key_id == new_key->key_id) { + state->state_keys[j].key_state = new_key->key_state; + state->state_keys[j].value = new_key->value; + state->state_keys[j].time = new_key->time; action_required = false; } } @@ -347,23 +372,347 @@ void input_set_state(InputState* state, uint16 key_id, uint16 new_state) return; } - free_state->key_id = key_id; - free_state->key_state = new_state; - // @todo implement - // free_state->time = 0; + free_state->key_id = new_key->key_id; + free_state->key_state = new_key->key_state; + free_state->value = new_key->value; + free_state->time = new_key->time; +} + +// Controllers are a little bit special +// We need to manually check the specific buttons and set their key +// Since some controllers are constantly sending data like mad it's not possible to handle them event based +// We need to poll them and then check the old state against this new state (annoying but necessary) +// Mice are fully supported by RawInput and are fairly generalized in terms of their buttons -> no special function needed +inline +void input_set_controller_state(Input* input, ControllerInput* controller, uint64 time) +{ + // Check active keys that might need to be set to inactive + for (int i = 0; i < MAX_KEY_PRESSES; ++i) { + if ((input->state.state_keys[i].key_id & INPUT_CONTROLLER_PREFIX) + && input->state.state_keys[i].key_state != KEY_STATE_RELEASED + ) { + uint32 key_id = input->state.state_keys[i].key_id & ~INPUT_CONTROLLER_PREFIX; + + if ((key_id == CONTROLLER_BUTTON_STICK_LEFT_BUTTON && controller->stick_left_button == 0) + || (key_id == CONTROLLER_BUTTON_STICK_LEFT_HORIZONTAL && OMS_ABS(controller->stick_left_x) < input->deadzone) + || (key_id == CONTROLLER_BUTTON_STICK_LEFT_VERTOCAL && OMS_ABS(controller->stick_left_y) < input->deadzone) + || (key_id == CONTROLLER_BUTTON_STICK_RIGHT_BUTTON && controller->stick_right_button == 0) + || (key_id == CONTROLLER_BUTTON_STICK_RIGHT_HORIZONTAL && OMS_ABS(controller->stick_right_x) < input->deadzone) + || (key_id == CONTROLLER_BUTTON_STICK_RIGHT_VERTOCAL && OMS_ABS(controller->stick_right_y) < input->deadzone) + || (key_id == CONTROLLER_BUTTON_SHOULDER_LEFT_TRIGGER && OMS_ABS(controller->shoulder_trigger_left) < input->deadzone) + || (key_id == CONTROLLER_BUTTON_SHOULDER_LEFT_BUTTON && controller->shoulder_button_left == 0) + || (key_id == CONTROLLER_BUTTON_SHOULDER_RIGHT_TRIGGER && OMS_ABS(controller->shoulder_trigger_right) < input->deadzone) + || (key_id == CONTROLLER_BUTTON_SHOULDER_RIGHT_BUTTON && controller->shoulder_button_right == 0) + || (key_id == CONTROLLER_BUTTON_X && controller->button_X == 0) + || (key_id == CONTROLLER_BUTTON_C && controller->button_C == 0) + || (key_id == CONTROLLER_BUTTON_T && controller->button_T == 0) + || (key_id == CONTROLLER_BUTTON_S && controller->button_S == 0) + || (key_id == CONTROLLER_BUTTON_DPAD_LEFT && controller->dpad_left == 0) + || (key_id == CONTROLLER_BUTTON_DPAD_RIGHT && controller->dpad_right == 0) + || (key_id == CONTROLLER_BUTTON_DPAD_UP && controller->dpad_up == 0) + || (key_id == CONTROLLER_BUTTON_DPAD_DOWN && controller->dpad_down == 0) + || (key_id == CONTROLLER_BUTTON_OTHER_0 && controller->button_other[0] == 0) + || (key_id == CONTROLLER_BUTTON_OTHER_1 && controller->button_other[1] == 0) + || (key_id == CONTROLLER_BUTTON_OTHER_2 && controller->button_other[2] == 0) + || (key_id == CONTROLLER_BUTTON_OTHER_3 && controller->button_other[3] == 0) + || (key_id == CONTROLLER_BUTTON_OTHER_4 && controller->button_other[4] == 0) + || (key_id == CONTROLLER_BUTTON_OTHER_5 && controller->button_other[5] == 0) + || (key_id == CONTROLLER_BUTTON_OTHER_6 && controller->button_other[6] == 0) + || (key_id == CONTROLLER_BUTTON_OTHER_7 && controller->button_other[7] == 0) + ) { + input->state.state_keys[i].key_state = KEY_STATE_RELEASED; + } + } + } + + // Special keys + // @todo this code means we cannot change this behavior (e.g. swap mouse view to dpad, swap sticks, ...) + if (OMS_ABS(controller->stick_right_x) > input->deadzone) { + input->state.dx += controller->stick_right_x / 8; + input->state_change_mouse = true; + } else { + input->state.dx = 0; + } + + if (OMS_ABS(controller->stick_right_y) > input->deadzone) { + input->state.dy += controller->stick_right_y / 8; + input->state_change_mouse = true; + } else { + input->state.dy = 0; + } + + if (OMS_ABS(controller->stick_left_x) > input->deadzone) { + input->state.dx2 += controller->stick_left_x / 8; + // @todo needs state change flag like mouse?! + } else { + input->state.dx2 = 0; + } + + if (OMS_ABS(controller->stick_left_y) > input->deadzone) { + input->state.dy2 += controller->stick_left_y / 8; + input->state.y2 += controller->stick_left_y / 8; + // @todo needs state change flag like mouse?! + } else { + input->state.dy2 = 0; + } + + // General Keys + int count = 0; + InputKey keys[5]; + + // @todo this logic below is painful, fix + if (count < 5 && controller->stick_left_button != 0) { + keys[count].key_id = CONTROLLER_BUTTON_STICK_LEFT_BUTTON | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->stick_left_button; + keys[count].time = time; + + ++count; + } + + if (count < 5 && OMS_ABS(controller->stick_left_x) > input->deadzone) { + keys[count].key_id = CONTROLLER_BUTTON_STICK_LEFT_HORIZONTAL | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->stick_left_x; + keys[count].time = time; + + ++count; + } + + if (count < 5 && OMS_ABS(controller->stick_left_y) > input->deadzone) { + keys[count].key_id = CONTROLLER_BUTTON_STICK_LEFT_VERTOCAL | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->stick_left_y; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->stick_right_button != 0) { + keys[count].key_id = CONTROLLER_BUTTON_STICK_RIGHT_BUTTON | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->stick_right_button; + keys[count].time = time; + + ++count; + } + + if (count < 5 && OMS_ABS(controller->stick_right_x) > input->deadzone) { + keys[count].key_id = CONTROLLER_BUTTON_STICK_RIGHT_HORIZONTAL | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->stick_right_x; + keys[count].time = time; + + ++count; + } + + if (count < 5 && OMS_ABS(controller->stick_right_y) > input->deadzone) { + keys[count].key_id = CONTROLLER_BUTTON_STICK_RIGHT_VERTOCAL | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->stick_right_y; + keys[count].time = time; + + ++count; + } + + if (count < 5 && OMS_ABS(controller->shoulder_trigger_left) > input->deadzone) { + keys[count].key_id = CONTROLLER_BUTTON_SHOULDER_LEFT_TRIGGER | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->shoulder_trigger_left; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->shoulder_button_left != 0) { + keys[count].key_id = CONTROLLER_BUTTON_SHOULDER_LEFT_BUTTON | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->shoulder_button_left; + keys[count].time = time; + + ++count; + } + + if (count < 5 && OMS_ABS(controller->shoulder_trigger_right) > input->deadzone) { + keys[count].key_id = CONTROLLER_BUTTON_SHOULDER_RIGHT_TRIGGER | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->shoulder_trigger_right; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->shoulder_button_right != 0) { + keys[count].key_id = CONTROLLER_BUTTON_SHOULDER_RIGHT_BUTTON | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->shoulder_button_right; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_X != 0) { + keys[count].key_id = CONTROLLER_BUTTON_X | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_X; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_C != 0) { + keys[count].key_id = CONTROLLER_BUTTON_C | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_C; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_T != 0) { + keys[count].key_id = CONTROLLER_BUTTON_T | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_T; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_S != 0) { + keys[count].key_id = CONTROLLER_BUTTON_S | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_S; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->dpad_left != 0) { + keys[count].key_id = CONTROLLER_BUTTON_DPAD_LEFT | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->dpad_left; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->dpad_right != 0) { + keys[count].key_id = CONTROLLER_BUTTON_DPAD_RIGHT | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->dpad_right; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->dpad_up != 0) { + keys[count].key_id = CONTROLLER_BUTTON_DPAD_UP | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->dpad_up; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->dpad_down != 0) { + keys[count].key_id = CONTROLLER_BUTTON_DPAD_DOWN | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->dpad_down; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_other[0] != 0) { + keys[count].key_id = CONTROLLER_BUTTON_OTHER_0 | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_other[0]; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_other[1] != 0) { + keys[count].key_id = CONTROLLER_BUTTON_OTHER_1 | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_other[1]; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_other[2] != 0) { + keys[count].key_id = CONTROLLER_BUTTON_OTHER_2 | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_other[2]; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_other[3] != 0) { + keys[count].key_id = CONTROLLER_BUTTON_OTHER_3 | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_other[3]; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_other[4] != 0) { + keys[count].key_id = CONTROLLER_BUTTON_OTHER_4 | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_other[4]; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_other[5] != 0) { + keys[count].key_id = CONTROLLER_BUTTON_OTHER_5 | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_other[5]; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_other[6] != 0) { + keys[count].key_id = CONTROLLER_BUTTON_OTHER_6 | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_other[6]; + keys[count].time = time; + + ++count; + } + + if (count < 5 && controller->button_other[7] != 0) { + keys[count].key_id = CONTROLLER_BUTTON_OTHER_7 | INPUT_CONTROLLER_PREFIX; + keys[count].key_state = KEY_STATE_PRESSED; + keys[count].value = controller->button_other[7]; + keys[count].time = time; + + ++count; + } + + for (int i = 0; i < count; ++i) { + input_set_state(&input->state, &keys[i]); + } + + input->state_change_button = true; } void -input_hotkey_state(InputState* __restrict state, const InputMapping* mapping) +input_hotkey_state(Input* input) { - memset(state->state_hotkeys, 0, sizeof(uint8) * MAX_KEY_PRESSES); + memset(input->state.state_hotkeys, 0, sizeof(uint8) * MAX_KEY_PRESSES); - int i = 0; + int active_hotkeys = 0; // Check every key down state for (int key_state = 0; key_state < MAX_KEY_STATES; ++key_state) { - if (state->state_keys[key_state].key_id == 0 - || state->state_keys[key_state].key_state == KEY_STATE_RELEASED + if (input->state.state_keys[key_state].key_id == 0 + || input->state.state_keys[key_state].key_state == KEY_STATE_RELEASED ) { // no key defined for this down state continue; @@ -372,31 +721,46 @@ input_hotkey_state(InputState* __restrict state, const InputMapping* mapping) // Is a key defined for this state AND is at least one hotkey defined for this key // If no hotkey is defined we don't care // Careful, remember MAX_MOUSE_KEYS offset - InputKey* input = &state->state_keys[key_state]; - int32 internal_key_id = (input->key_id & ~(INPUT_KEYBOARD_PREFIX | INPUT_CONTROLLER_PREFIX)) - + ((bool) (input->key_id & INPUT_KEYBOARD_PREFIX)) * MAX_MOUSE_KEYS - + ((bool) (input->key_id & INPUT_CONTROLLER_PREFIX)) * (MAX_MOUSE_KEYS + MAX_KEYBOARD_KEYS); + InputKey* key = &input->state.state_keys[key_state]; + int32 internal_key_id = (key->key_id & ~(INPUT_KEYBOARD_PREFIX | INPUT_CONTROLLER_PREFIX)) + + ((bool) (key->key_id & INPUT_KEYBOARD_PREFIX)) * MAX_MOUSE_KEYS + + ((bool) (key->key_id & INPUT_CONTROLLER_PREFIX)) * (MAX_MOUSE_KEYS + MAX_KEYBOARD_KEYS); - const uint8* hotkeys_for_key = mapping->keys[internal_key_id - 1]; - if (hotkeys_for_key[0] == 0) { - // no possible hotkey associated with this key - continue; - } + // Handle 2 input devices (1 = keyboard + mouse, 2 = controller) + for (int i = 0; i < 2; ++i) { + InputMapping* mapping; + if (i == 0) { + mapping = &input->input_mapping1; + } else { + // @todo Maybe we want to ignore < INPUT_CONTROLLER_PREFIX since we could use this to handle alt keybinds + if (!input->handle_controller || key->key_id < INPUT_CONTROLLER_PREFIX) { + continue; + } - // Check every possible hotkey - // Since multiple input devices have their own button/key indices whe have to do this weird range handling - for (int possible_hotkey_idx = 0; possible_hotkey_idx < MAX_KEY_TO_HOTKEY; ++possible_hotkey_idx) { - // We only support a slimited amount of active hotkeys - if (i >= MAX_KEY_PRESSES) { - return; + mapping = &input->input_mapping2; } - bool is_pressed = hotkey_keys_are_active(state, mapping, hotkeys_for_key[possible_hotkey_idx]); + const uint8* hotkeys_for_key = mapping->keys[internal_key_id - 1]; + if (hotkeys_for_key[0] == 0) { + // no possible hotkey associated with this key + continue; + } - // store active hotkey, if it is not already active - if (is_pressed && !hotkey_is_active(state, hotkeys_for_key[possible_hotkey_idx])) { - state->state_hotkeys[i] = hotkeys_for_key[possible_hotkey_idx]; - ++i; + // Check every possible hotkey + // Since multiple input devices have their own button/key indices whe have to do this weird range handling + for (int possible_hotkey_idx = 0; possible_hotkey_idx < MAX_KEY_TO_HOTKEY; ++possible_hotkey_idx) { + // We only support a slimited amount of active hotkeys + if (active_hotkeys >= MAX_KEY_PRESSES) { + return; + } + + bool is_pressed = hotkey_keys_are_active(&input->state, mapping, hotkeys_for_key[possible_hotkey_idx]); + + // store active hotkey, if it is not already active + if (is_pressed && !hotkey_is_active(&input->state, hotkeys_for_key[possible_hotkey_idx])) { + input->state.state_hotkeys[active_hotkeys] = hotkeys_for_key[possible_hotkey_idx]; + ++active_hotkeys; + } } } } @@ -405,6 +769,8 @@ input_hotkey_state(InputState* __restrict state, const InputMapping* mapping) // @bug how to handle priority? e.g. there might be a hotkey for 1 and one for alt+1 // in this case only the hotkey for alt+1 should be triggered // @bug how to handle other conditions besides buttons pressed together? some hotkeys are only available in certain situations + // @bug how to handle alternative hotkeys (e.g. keyboard and controller at the same time) + // @bug how to handle values (e.g. stick may or may not set the x/y or dx/dy in some situations) } #endif \ No newline at end of file diff --git a/platform/win32/input/DirectInput.h b/platform/win32/input/DirectInput.h new file mode 100644 index 0000000..e69de29 diff --git a/platform/win32/input/RawInput.h b/platform/win32/input/RawInput.h index 2c956b0..5549733 100644 --- a/platform/win32/input/RawInput.h +++ b/platform/win32/input/RawInput.h @@ -6,13 +6,15 @@ * @version 1.0.0 * @link https://jingga.app */ -#ifndef TOS_INPUT_RAW_H -#define TOS_INPUT_RAW_H +#ifndef TOS_PLATFORM_WIN32_INPUT_RAW_H +#define TOS_PLATFORM_WIN32_INPUT_RAW_H #include #include "../../../stdlib/Types.h" #include "../../../input/Input.h" +#include "../../../input/ControllerInput.h" +#include "controller/DualShock4.h" #include "../../../utils/TestUtils.h" #include "../../../utils/MathUtils.h" #include "../../../memory/RingMemory.h" @@ -169,39 +171,38 @@ void input_raw_handle(RAWINPUT* __restrict raw, Input* states, int state_count, } if (raw->data.mouse.usButtonFlags) { - uint16 new_state; - uint16 button; + InputKey key; if (raw->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN) { - new_state = KEY_STATE_PRESSED; - button = 1; + key.key_state = KEY_STATE_PRESSED; + key.key_id = 1; } else if (raw->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP) { - new_state = KEY_STATE_RELEASED; - button = 1; + key.key_state = KEY_STATE_RELEASED; + key.key_id = 1; } else if (raw->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN) { - new_state = KEY_STATE_PRESSED; - button = 2; + key.key_state = KEY_STATE_PRESSED; + key.key_id = 2; } else if (raw->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP) { - new_state = KEY_STATE_RELEASED; - button = 2; + key.key_state = KEY_STATE_RELEASED; + key.key_id = 2; } else if (raw->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN) { - new_state = KEY_STATE_PRESSED; - button = 3; + key.key_state = KEY_STATE_PRESSED; + key.key_id = 3; } else if (raw->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP) { - new_state = KEY_STATE_RELEASED; - button = 3; + key.key_state = KEY_STATE_RELEASED; + key.key_id = 3; } else if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) { - new_state = KEY_STATE_PRESSED; - button = 4; + key.key_state = KEY_STATE_PRESSED; + key.key_id = 4; } else if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) { - new_state = KEY_STATE_RELEASED; - button = 4; + key.key_state = KEY_STATE_RELEASED; + key.key_id = 4; } else if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) { - new_state = KEY_STATE_PRESSED; - button = 5; + key.key_state = KEY_STATE_PRESSED; + key.key_id = 5; } else if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) { - new_state = KEY_STATE_RELEASED; - button = 5; + key.key_state = KEY_STATE_RELEASED; + key.key_id = 5; } else { return; } @@ -218,9 +219,11 @@ void input_raw_handle(RAWINPUT* __restrict raw, Input* states, int state_count, // @question is mouse wheel really considered a button change? - button |= INPUT_MOUSE_PREFIX; + key.key_id |= INPUT_MOUSE_PREFIX; + key.value = 0; + key.time = time; - input_set_state(&states[i].state, button, new_state); + input_set_state(&states[i].state, &key); states[i].state_change_button = true; } else if (states[i].mouse_movement) { // do we want to handle mouse movement for every individual movement, or do we want to pull it @@ -270,66 +273,45 @@ void input_raw_handle(RAWINPUT* __restrict raw, Input* states, int state_count, return; } - int16 new_state = -1; + uint16 new_state; if (raw->data.keyboard.Flags == RI_KEY_BREAK) { new_state = KEY_STATE_RELEASED; } else if (raw->data.keyboard.Flags == RI_KEY_MAKE) { new_state = KEY_STATE_PRESSED; - } - - if (new_state < 0) { + } else { return; } // @todo change to MakeCode instead of VKey - uint16 key = raw->data.keyboard.VKey | INPUT_KEYBOARD_PREFIX; - - input_set_state(&states[i].state, key, new_state); + InputKey key = {(uint16) (raw->data.keyboard.VKey | INPUT_KEYBOARD_PREFIX), new_state, 0, time}; + input_set_state(&states[i].state, &key); states[i].state_change_button = true; } else if (raw->header.dwType == RIM_TYPEHID) { if (raw->header.dwSize > sizeof(RAWINPUT)) { // @todo Find a way to handle most common controllers - // DualShock 3 - // Dualshock 4 // DualSense // Xbox - return; - /* + // @performance This shouldn't be done every time, it should be polling based + // Controllers often CONSTANTLY send data -> really bad + // Maybe we can add timer usage while (i < state_count && states[i].handle_controller != raw->header.hDevice ) { ++i; } - if (i >= state_count || !states[i].is_connected) { + if (i >= state_count || !states[i].is_connected + || time - states[i].time_last_input_check < 5 + ) { return; } - // @performance The code below is horrible, we need to probably make it controller dependant - // Sometimes a controller may have a time component or a gyro which results in the controller - // constantly sending input data + ControllerInput controller = {}; + input_map_dualshock4(&controller, raw->data.hid.bRawData); + input_set_controller_state(&states[i], &controller, time); - // @todo implement actual step usage - bool is_same = simd_compare( - raw->data.hid.bRawData, - states[i].state.controller_state, - raw->header.dwSize - sizeof(RAWINPUT), - 8 - ); - - if (!is_same) { - memcpy(states[i].state.controller_state, raw->data.hid.bRawData, raw->header.dwSize - sizeof(RAWINPUT)); - - char buffer[100]; - int j = 0; - for (j = 0; j < raw->header.dwSize - sizeof(RAWINPUT); ++j) { - buffer[j] = raw->data.hid.bRawData[j]; - } - - DEBUG_OUTPUT(buffer); - } - */ + states[i].time_last_input_check = time; } } } @@ -351,7 +333,6 @@ void input_handle(LPARAM lParam, Input* __restrict states, int state_count, Ring input_raw_handle((RAWINPUT *) lpb, states, state_count, time); } -// @bug Somehow this function skips some inputs (input_handle works)!!!!! void input_handle_buffered(int buffer_size, Input* __restrict states, int state_count, RingMemory* ring, uint64 time) { uint32 cb_size; diff --git a/platform/win32/input/XInput.h b/platform/win32/input/XInput.h index 0743fe3..5160d06 100644 --- a/platform/win32/input/XInput.h +++ b/platform/win32/input/XInput.h @@ -6,13 +6,14 @@ * @version 1.0.0 * @link https://jingga.app */ -#ifndef TOS_INPUT_XINPUT_H -#define TOS_INPUT_XINPUT_H +#ifndef TOS_PLATFORM_WIN32_INPUT_XINPUT_H +#define TOS_PLATFORM_WIN32_INPUT_XINPUT_H #include #include #include "../../../input/Input.h" +#include "../../../input/ControllerInput.h" #include "../../../stdlib/Types.h" #include "../../../utils/MathUtils.h" @@ -33,35 +34,6 @@ DWORD WINAPI XInputSetStateStub(DWORD, XINPUT_VIBRATION*) { global_persist x_input_set_state* XInputSetState_ = XInputSetStateStub; #define XInputSetState XInputSetState_ -struct ControllerInput { - uint32 id = 0; - bool is_connected = false; - - // After handling the state change the game loop should set this to false - bool state_change = false; - - // @question maybe make part of button - bool up = false; - bool down = false; - bool left = false; - bool right = false; - - byte trigger_old[4]; - byte trigger[4]; - - // these are bitfields - uint16 button_old; - uint16 button; - - int16 stickl_x = 0; - int16 stickl_y = 0; - bool stickl_press = false; - - int16 stickr_x = 0; - int16 stickr_y = 0; - bool stickr_press = false; -}; - void xinput_load() { HMODULE lib = LoadLibraryExA((LPCSTR) "xinput1_4.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); if(!lib) { @@ -107,9 +79,6 @@ ControllerInput* init_controllers() XINPUT_STATE controller_state; if (XInputGetState(controller_index, &controller_state) == ERROR_SUCCESS) { ++c; - - controllers[c].id = controller_index; - controllers[c].is_connected = true; } } @@ -118,6 +87,7 @@ ControllerInput* init_controllers() void handle_controller_input(ControllerInput* states) { + /* uint32 controller_index = 0; while(states[controller_index].is_connected) { XINPUT_STATE controller_state; @@ -127,7 +97,7 @@ void handle_controller_input(ControllerInput* states) continue; } - /* + states[controller_index].up = controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP; states[controller_index].down = controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN; states[controller_index].left = controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT; @@ -153,10 +123,11 @@ void handle_controller_input(ControllerInput* states) states[controller_index].stickr_x = controller_state.Gamepad.sThumbRX; states[controller_index].stickr_y = controller_state.Gamepad.sThumbRY; states[controller_index].stickr_press = controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB; - */ + ++controller_index; } + */ } #endif \ No newline at end of file diff --git a/platform/win32/input/controller/DualSense.h b/platform/win32/input/controller/DualSense.h new file mode 100644 index 0000000..e69de29 diff --git a/platform/win32/input/controller/DualShock4.h b/platform/win32/input/controller/DualShock4.h new file mode 100644 index 0000000..ce45f94 --- /dev/null +++ b/platform/win32/input/controller/DualShock4.h @@ -0,0 +1,113 @@ +/** + * Jingga + * + * @copyright Jingga + * @license OMS License 2.0 + * @version 1.0.0 + * @link https://jingga.app + */ +#ifndef TOS_PLATFORM_WIN32_INPUT_CONTROLLER_DUALSHOCK4_H +#define TOS_PLATFORM_WIN32_INPUT_CONTROLLER_DUALSHOCK4_H + +#include "../../../../stdlib/Types.h" +#include "../../../../input/ControllerInput.h" +#include "../../../../utils/BitUtils.h" + +inline +void input_map_dualshock4(ControllerInput* controller, byte* data) +{ + ++data; + + // 0 is not the origin -> need to shift + controller->stick_left_x = *data++; + controller->stick_left_x = OMS_MIN(controller->stick_left_x - 128, 127); + controller->stick_left_y = *data++; + controller->stick_left_y = OMS_MIN(controller->stick_left_y - 128, 127); + + controller->stick_right_x = *data++; + controller->stick_right_x = OMS_MIN(controller->stick_right_x - 128, 127); + controller->stick_right_y = *data++; + controller->stick_right_y = OMS_MIN(controller->stick_right_y - 128, 127); + + controller->button_T = BITS_GET_8_L2R(*data, 0, 1); + controller->button_C = BITS_GET_8_L2R(*data, 1, 1); + controller->button_X = BITS_GET_8_L2R(*data, 2, 1); + controller->button_S = BITS_GET_8_L2R(*data, 3, 1); + + uint32 d_pad_state = BITS_GET_8_L2R(*data, 4, 4); + if (d_pad_state == 8) { + controller->dpad_left = 0; + controller->dpad_right = 0; + controller->dpad_up = 0; + controller->dpad_down = 0; + } else if (d_pad_state == 0) { + controller->dpad_left = 0; + controller->dpad_right = 0; + controller->dpad_up = 127; + controller->dpad_down = 0; + } else if (d_pad_state == 1) { + controller->dpad_left = 0; + controller->dpad_right = 127; + controller->dpad_up = 127; + controller->dpad_down = 0; + } else if (d_pad_state == 2) { + controller->dpad_left = 0; + controller->dpad_right = 127; + controller->dpad_up = 0; + controller->dpad_down = 0; + } else if (d_pad_state == 3) { + controller->dpad_left = 0; + controller->dpad_right = 127; + controller->dpad_up = 0; + controller->dpad_down = 127; + } else if (d_pad_state == 4) { + controller->dpad_left = 0; + controller->dpad_right = 0; + controller->dpad_up = 0; + controller->dpad_down = 127; + } else if (d_pad_state == 5) { + controller->dpad_left = 127; + controller->dpad_right = 0; + controller->dpad_up = 0; + controller->dpad_down = 127; + } else if (d_pad_state == 6) { + controller->dpad_left = 127; + controller->dpad_right = 0; + controller->dpad_up = 0; + controller->dpad_down = 0; + } else if (d_pad_state == 7) { + controller->dpad_left = 127; + controller->dpad_right = 0; + controller->dpad_up = 127; + controller->dpad_down = 0; + } + + ++data; + + controller->stick_right_button = BITS_GET_8_L2R(*data, 0, 1); + controller->stick_left_button = BITS_GET_8_L2R(*data, 1, 1); + + controller->button_other[0] = BITS_GET_8_L2R(*data, 2, 1); // option + controller->button_other[1] = BITS_GET_8_L2R(*data, 3, 1); // share + + controller->shoulder_button_right = BITS_GET_8_L2R(*data, 6, 1); // tpad + controller->shoulder_button_left = BITS_GET_8_L2R(*data, 7, 1); // ps + + ++data; + + controller->button_other[2] = BITS_GET_8_L2R(*data, 6, 1); + controller->button_other[3] = BITS_GET_8_L2R(*data, 7, 1); + + ++data; + + controller->shoulder_trigger_right = *data++; + controller->shoulder_trigger_left = *data++; + + data += 3; + + controller->gyro_x = *data++; + controller->gyro_y = *data++; + controller->gyro_z = *data++; +} + +#endif \ No newline at end of file diff --git a/platform/win32/input/controller/XBoxS.h b/platform/win32/input/controller/XBoxS.h new file mode 100644 index 0000000..4d30184 --- /dev/null +++ b/platform/win32/input/controller/XBoxS.h @@ -0,0 +1 @@ +// Maybe: https://xboxdevwiki.net/Xbox_Input_Devices \ No newline at end of file