diff options
author | 2023-02-18 06:18:45 +1100 | |
---|---|---|
committer | 2023-02-18 06:18:45 +1100 | |
commit | d4589e0b29d6085072ff635d87c3d95c21813a58 (patch) | |
tree | c0d3283ecdd99bd380fc155f4e09b7f4bd573116 /src/osd/modules | |
parent | 20d7135179ffe9959f4bfb759a0e5735b7aaaf15 (diff) |
Input refactoring:
osd/modules/input, emu/inpttype.cpp: Made most default joystick
assignments supplied by input modules. Input modules take available
controls into consideration when generating default assignments.
emu/inpttype.ipp: Added a separate "Back" UI input separate from Cancel.
You may want an easier to hit combination for moving to the previous
menu than for exiting or cancelling input. They both default to Escape.
emu/inpttype.ipp: Added a UI Help control. Currently only used by
analog inputs menu
emu/inpttype.h: Moved I/O port field type enum to its own header and
sorted UI controls so they appear in a more logical order.
ui: Don't use UI Select to restore defaults - people should be getting
used to the UI Clear input by now. UI Select cycles multi-value items
instead.
ui/inputmap.cpp: Don't use immediate cancel to cycle between clearing
and restoring default assignment (use UI Clear instead).
osd: Reduced the number of files needing to include the dreaded emu.h.
Got some implementation out of headers.
Diffstat (limited to 'src/osd/modules')
-rw-r--r-- | src/osd/modules/input/assignmenthelper.cpp | 592 | ||||
-rw-r--r-- | src/osd/modules/input/assignmenthelper.h | 118 | ||||
-rw-r--r-- | src/osd/modules/input/input_dinput.cpp | 912 | ||||
-rw-r--r-- | src/osd/modules/input/input_dinput.h | 74 | ||||
-rw-r--r-- | src/osd/modules/input/input_rawinput.cpp | 2 | ||||
-rw-r--r-- | src/osd/modules/input/input_sdl.cpp | 553 | ||||
-rw-r--r-- | src/osd/modules/input/input_win32.cpp | 2 | ||||
-rw-r--r-- | src/osd/modules/input/input_windows.cpp | 2 | ||||
-rw-r--r-- | src/osd/modules/input/input_x11.cpp | 2 | ||||
-rw-r--r-- | src/osd/modules/input/input_xinput.cpp | 1448 |
10 files changed, 3360 insertions, 345 deletions
diff --git a/src/osd/modules/input/assignmenthelper.cpp b/src/osd/modules/input/assignmenthelper.cpp new file mode 100644 index 00000000000..c044d4b7aa7 --- /dev/null +++ b/src/osd/modules/input/assignmenthelper.cpp @@ -0,0 +1,592 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +//============================================================ +// +// assignmenthelper.cpp - input assignment setup helper +// +//============================================================ + +#include "assignmenthelper.h" + +#include "interface/inputseq.h" + +#include "inpttype.h" + + +namespace osd { + +bool joystick_assignment_helper::add_assignment( + input_device::assignment_vector &assignments, + ioport_type fieldtype, + input_seq_type seqtype, + input_item_class itemclass, + input_item_modifier modifier, + std::initializer_list<input_item_id> items) +{ + for (input_item_id item : items) + { + if (ITEM_ID_INVALID != item) + { + assignments.emplace_back( + fieldtype, + seqtype, + input_seq(input_code(DEVICE_CLASS_JOYSTICK, 0, itemclass, modifier, item))); + return true; + } + } + return false; +} + + +bool joystick_assignment_helper::add_button_assignment( + input_device::assignment_vector &assignments, + ioport_type field_type, + std::initializer_list<input_item_id> items) +{ + return add_assignment( + assignments, + field_type, + SEQ_TYPE_STANDARD, + ITEM_CLASS_SWITCH, + ITEM_MODIFIER_NONE, + items); +} + + +bool joystick_assignment_helper::add_button_pair_assignment( + input_device::assignment_vector &assignments, + ioport_type field1, + ioport_type field2, + input_item_id button1, + input_item_id button2) +{ + if ((ITEM_ID_INVALID == button1) || (ITEM_ID_INVALID == button2)) + return false; + + assignments.emplace_back( + field1, + SEQ_TYPE_STANDARD, + make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, button1)); + assignments.emplace_back( + field2, + SEQ_TYPE_STANDARD, + make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, button2)); + return true; +} + + +bool joystick_assignment_helper::add_axis_inc_dec_assignment( + input_device::assignment_vector &assignments, + ioport_type field_type, + input_item_id button_dec, + input_item_id button_inc) +{ + if ((ITEM_ID_INVALID == button_dec) || (ITEM_ID_INVALID == button_inc)) + return false; + + assignments.emplace_back( + field_type, + SEQ_TYPE_DECREMENT, + make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, button_dec)); + assignments.emplace_back( + field_type, + SEQ_TYPE_INCREMENT, + make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, button_inc)); + return true; +} + + +bool joystick_assignment_helper::add_axis_pair_assignment( + input_device::assignment_vector &assignments, + ioport_type field1, + ioport_type field2, + input_item_id axis) +{ + if (ITEM_ID_INVALID == axis) + return false; + + assignments.emplace_back( + field1, + SEQ_TYPE_STANDARD, + make_code( + ITEM_CLASS_SWITCH, + (ITEM_ID_XAXIS == axis) ? ITEM_MODIFIER_LEFT : (ITEM_ID_YAXIS == axis) ? ITEM_MODIFIER_UP : ITEM_MODIFIER_NEG, + axis)); + assignments.emplace_back( + field2, + SEQ_TYPE_STANDARD, + make_code( + ITEM_CLASS_SWITCH, + (ITEM_ID_XAXIS == axis) ? ITEM_MODIFIER_RIGHT : (ITEM_ID_YAXIS == axis) ? ITEM_MODIFIER_DOWN : ITEM_MODIFIER_POS, + axis)); + return true; +} + + +bool joystick_assignment_helper::consume_button_pair( + input_device::assignment_vector &assignments, + ioport_type field1, + ioport_type field2, + input_item_id &button1, + input_item_id &button2) +{ + if (!add_button_pair_assignment(assignments, field1, field2, button1, button2)) + return false; + + button1 = ITEM_ID_INVALID; + button2 = ITEM_ID_INVALID; + return true; +} + + +bool joystick_assignment_helper::consume_trigger_pair( + input_device::assignment_vector &assignments, + ioport_type field1, + ioport_type field2, + input_item_id &axis1, + input_item_id &axis2) +{ + if ((ITEM_ID_INVALID == axis1) || (ITEM_ID_INVALID == axis2)) + return false; + + assignments.emplace_back( + field1, + SEQ_TYPE_STANDARD, + make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axis1)); + assignments.emplace_back( + field2, + SEQ_TYPE_STANDARD, + make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axis2)); + axis1 = ITEM_ID_INVALID; + axis2 = ITEM_ID_INVALID; + return true; +} + + +bool joystick_assignment_helper::consume_axis_pair( + input_device::assignment_vector &assignments, + ioport_type field1, + ioport_type field2, + input_item_id &axis) +{ + if (!add_axis_pair_assignment(assignments, field1, field2, axis)) + return false; + + axis = ITEM_ID_INVALID; + return true; +} + + +void joystick_assignment_helper::add_directional_assignments( + input_device::assignment_vector &assignments, + input_item_id xaxis, + input_item_id yaxis, + input_item_id leftswitch, + input_item_id rightswitch, + input_item_id upswitch, + input_item_id downswitch) +{ + // see if we have complementary pairs of directional switches + bool const hswitches = (ITEM_ID_INVALID != leftswitch) && (ITEM_ID_INVALID != rightswitch); + bool const vswitches = (ITEM_ID_INVALID != upswitch) && (ITEM_ID_INVALID != downswitch); + + // use X axis if present + if (ITEM_ID_INVALID != xaxis) + { + // use this for horizontal axis movement + input_seq const xseq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, xaxis)); + assignments.emplace_back(IPT_PADDLE, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_POSITIONAL, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_DIAL, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_TRACKBALL_X, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_AD_STICK_X, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_LIGHTGUN_X, SEQ_TYPE_STANDARD, xseq); + + // use it for the main left/right control, too + input_seq leftseq(make_code(ITEM_CLASS_SWITCH, (ITEM_ID_XAXIS == xaxis) ? ITEM_MODIFIER_LEFT : ITEM_MODIFIER_NEG, xaxis)); + input_seq rightseq(make_code(ITEM_CLASS_SWITCH, (ITEM_ID_XAXIS == xaxis) ? ITEM_MODIFIER_RIGHT : ITEM_MODIFIER_POS, xaxis)); + if (ITEM_ID_INVALID != leftswitch) + { + leftseq += input_seq::or_code; + leftseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, leftswitch); + } + if (ITEM_ID_INVALID != rightswitch) + { + rightseq += input_seq::or_code; + rightseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, rightswitch); + } + assignments.emplace_back(IPT_JOYSTICK_LEFT, SEQ_TYPE_STANDARD, leftseq); + assignments.emplace_back(IPT_JOYSTICK_RIGHT, SEQ_TYPE_STANDARD, rightseq); + + // use for vertical navigation if there's no Y axis, or horizontal otherwise + if (ITEM_ID_INVALID != yaxis) + { + // if left/right are both present but not both up/down, they'll be taken for vertical navigation + if (hswitches && !vswitches) + { + leftseq.backspace(); + if (ITEM_ID_INVALID != upswitch) + leftseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, upswitch); + else + leftseq.backspace(); + + rightseq.backspace(); + if (ITEM_ID_INVALID != downswitch) + rightseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, downswitch); + else + rightseq.backspace(); + } + assignments.emplace_back(IPT_UI_LEFT, SEQ_TYPE_STANDARD, leftseq); + assignments.emplace_back(IPT_UI_RIGHT, SEQ_TYPE_STANDARD, rightseq); + } + else + { + // prefer D-pad up/down for vertical navigation if present + if (!hswitches || vswitches) + { + while (leftseq.length() > 1) + leftseq.backspace(); + if (ITEM_ID_INVALID != upswitch) + { + leftseq += input_seq::or_code; + leftseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, upswitch); + } + + while (rightseq.length() > 1) + rightseq.backspace(); + if (ITEM_ID_INVALID != downswitch) + { + rightseq += input_seq::or_code; + rightseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, downswitch); + } + } + assignments.emplace_back(IPT_UI_UP, SEQ_TYPE_STANDARD, leftseq); + assignments.emplace_back(IPT_UI_DOWN, SEQ_TYPE_STANDARD, rightseq); + } + } + else + { + // without a primary analog X axis, we still want D-pad left/right controls if possible + add_button_assignment(assignments, IPT_JOYSTICK_LEFT, { leftswitch }); + add_button_assignment(assignments, IPT_JOYSTICK_RIGHT, { rightswitch }); + + // vertical navigation gets first pick on directional controls + add_button_assignment(assignments, IPT_UI_LEFT, { (hswitches && !vswitches) ? upswitch : leftswitch }); + add_button_assignment(assignments, IPT_UI_RIGHT, { (hswitches && !vswitches) ? downswitch : rightswitch }); + } + + // use Y axis if present + if (ITEM_ID_INVALID != yaxis) + { + // use this for vertical axis movement + input_seq const yseq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, yaxis)); + assignments.emplace_back(IPT_PADDLE_V, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_POSITIONAL_V, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_DIAL_V, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_TRACKBALL_Y, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_AD_STICK_Y, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_LIGHTGUN_Y, SEQ_TYPE_STANDARD, yseq); + + // use it for the main up/down control, too + input_seq upseq(make_code(ITEM_CLASS_SWITCH, (ITEM_ID_YAXIS == yaxis) ? ITEM_MODIFIER_UP : ITEM_MODIFIER_NEG, yaxis)); + input_seq downseq(make_code(ITEM_CLASS_SWITCH, (ITEM_ID_YAXIS == yaxis) ? ITEM_MODIFIER_DOWN : ITEM_MODIFIER_POS, yaxis)); + if (ITEM_ID_INVALID != upswitch) + { + upseq += input_seq::or_code; + upseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, upswitch); + } + if (ITEM_ID_INVALID != downswitch) + { + downseq += input_seq::or_code; + downseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, downswitch); + } + assignments.emplace_back(IPT_JOYSTICK_UP, SEQ_TYPE_STANDARD, upseq); + assignments.emplace_back(IPT_JOYSTICK_DOWN, SEQ_TYPE_STANDARD, downseq); + + // if available, this is used for vertical navigation + if (hswitches && !vswitches) + { + if (upseq.length() > 1) + upseq.backspace(); + else + upseq += input_seq::or_code; + upseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, leftswitch); + + if (downseq.length() > 1) + downseq.backspace(); + else + downseq += input_seq::or_code; + downseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, rightswitch); + } + assignments.emplace_back(IPT_UI_UP, SEQ_TYPE_STANDARD, upseq); + assignments.emplace_back(IPT_UI_DOWN, SEQ_TYPE_STANDARD, downseq); + } + else + { + // without a primary analog Y axis, we still want D-pad up/down controls if possible + add_button_assignment(assignments, IPT_JOYSTICK_UP, { upswitch }); + add_button_assignment(assignments, IPT_JOYSTICK_DOWN, { downswitch }); + + // vertical navigation may be assigned to X axis if Y axis is not present + bool const dpadflip = (ITEM_ID_INVALID != xaxis) == (!hswitches || vswitches); + add_button_assignment( + assignments, + (ITEM_ID_INVALID != xaxis) ? IPT_UI_LEFT : IPT_UI_UP, + { dpadflip ? leftswitch : upswitch }); + add_button_assignment( + assignments, + (ITEM_ID_INVALID != xaxis) ? IPT_UI_RIGHT : IPT_UI_DOWN, + { dpadflip ? rightswitch : downswitch }); + } + + // if we're missing either primary axis, fall back to D-pad for analog increment/decrement + if ((ITEM_ID_INVALID == xaxis) || (ITEM_ID_INVALID == yaxis)) + { + if (ITEM_ID_INVALID != leftswitch) + { + input_seq const leftseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, leftswitch)); + assignments.emplace_back(IPT_PADDLE, SEQ_TYPE_DECREMENT, leftseq); + assignments.emplace_back(IPT_POSITIONAL, SEQ_TYPE_DECREMENT, leftseq); + assignments.emplace_back(IPT_DIAL, SEQ_TYPE_DECREMENT, leftseq); + assignments.emplace_back(IPT_TRACKBALL_X, SEQ_TYPE_DECREMENT, leftseq); + assignments.emplace_back(IPT_AD_STICK_X, SEQ_TYPE_DECREMENT, leftseq); + assignments.emplace_back(IPT_LIGHTGUN_X, SEQ_TYPE_DECREMENT, leftseq); + } + if (ITEM_ID_INVALID != rightswitch) + { + input_seq const rightseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, rightswitch)); + assignments.emplace_back(IPT_PADDLE, SEQ_TYPE_INCREMENT, rightseq); + assignments.emplace_back(IPT_POSITIONAL, SEQ_TYPE_INCREMENT, rightseq); + assignments.emplace_back(IPT_DIAL, SEQ_TYPE_INCREMENT, rightseq); + assignments.emplace_back(IPT_TRACKBALL_X, SEQ_TYPE_INCREMENT, rightseq); + assignments.emplace_back(IPT_AD_STICK_X, SEQ_TYPE_INCREMENT, rightseq); + assignments.emplace_back(IPT_LIGHTGUN_X, SEQ_TYPE_INCREMENT, rightseq); + } + if (ITEM_ID_INVALID != upswitch) + { + input_seq const upseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, upswitch)); + assignments.emplace_back(IPT_PADDLE_V, SEQ_TYPE_DECREMENT, upseq); + assignments.emplace_back(IPT_POSITIONAL_V, SEQ_TYPE_DECREMENT, upseq); + assignments.emplace_back(IPT_DIAL_V, SEQ_TYPE_DECREMENT, upseq); + assignments.emplace_back(IPT_TRACKBALL_Y, SEQ_TYPE_DECREMENT, upseq); + assignments.emplace_back(IPT_AD_STICK_Y, SEQ_TYPE_DECREMENT, upseq); + assignments.emplace_back(IPT_LIGHTGUN_Y, SEQ_TYPE_DECREMENT, upseq); + } + if (ITEM_ID_INVALID != downswitch) + { + input_seq const downseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, downswitch)); + assignments.emplace_back(IPT_PADDLE_V, SEQ_TYPE_INCREMENT, downseq); + assignments.emplace_back(IPT_POSITIONAL_V, SEQ_TYPE_INCREMENT, downseq); + assignments.emplace_back(IPT_DIAL_V, SEQ_TYPE_INCREMENT, downseq); + assignments.emplace_back(IPT_TRACKBALL_Y, SEQ_TYPE_INCREMENT, downseq); + assignments.emplace_back(IPT_AD_STICK_Y, SEQ_TYPE_INCREMENT, downseq); + assignments.emplace_back(IPT_LIGHTGUN_Y, SEQ_TYPE_INCREMENT, downseq); + } + } +} + + +void joystick_assignment_helper::add_twin_stick_assignments( + input_device::assignment_vector &assignments, + input_item_id leftx, + input_item_id lefty, + input_item_id rightx, + input_item_id righty, + input_item_id leftleft, + input_item_id leftright, + input_item_id leftup, + input_item_id leftdown, + input_item_id rightleft, + input_item_id rightright, + input_item_id rightup, + input_item_id rightdown) +{ + // we'll add these at the end if they aren't empty + input_seq leftleftseq, leftrightseq, leftupseq, leftdownseq; + input_seq rightleftseq, rightrightseq, rightupseq, rightdownseq; + + // only use axes if there are at least two axes in the same orientation + bool const useaxes = + ((ITEM_ID_INVALID != leftx) && (ITEM_ID_INVALID != rightx)) || + ((ITEM_ID_INVALID != lefty) && (ITEM_ID_INVALID != righty)); + if (useaxes) + { + // left stick + if (ITEM_ID_INVALID != leftx) + { + leftleftseq += make_code( + ITEM_CLASS_SWITCH, + (ITEM_ID_XAXIS == leftx) ? ITEM_MODIFIER_LEFT : ITEM_MODIFIER_NEG, + leftx); + leftrightseq += make_code( + ITEM_CLASS_SWITCH, + (ITEM_ID_XAXIS == leftx) ? ITEM_MODIFIER_RIGHT : ITEM_MODIFIER_POS, + leftx); + } + if (ITEM_ID_INVALID != lefty) + { + leftupseq += make_code( + ITEM_CLASS_SWITCH, + (ITEM_ID_YAXIS == lefty) ? ITEM_MODIFIER_UP : ITEM_MODIFIER_NEG, + lefty); + leftdownseq += make_code( + ITEM_CLASS_SWITCH, + (ITEM_ID_YAXIS == lefty) ? ITEM_MODIFIER_DOWN : ITEM_MODIFIER_POS, + lefty); + } + + // right stick + if (ITEM_ID_INVALID != rightx) + { + rightleftseq += make_code( + ITEM_CLASS_SWITCH, + (ITEM_ID_XAXIS == rightx) ? ITEM_MODIFIER_LEFT : ITEM_MODIFIER_NEG, + rightx); + rightrightseq += make_code( + ITEM_CLASS_SWITCH, + (ITEM_ID_XAXIS == rightx) ? ITEM_MODIFIER_RIGHT : ITEM_MODIFIER_POS, + rightx); + } + if (ITEM_ID_INVALID != righty) + { + rightupseq += make_code( + ITEM_CLASS_SWITCH, + (ITEM_ID_YAXIS == righty) ? ITEM_MODIFIER_UP : ITEM_MODIFIER_NEG, + righty); + rightdownseq += make_code( + ITEM_CLASS_SWITCH, + (ITEM_ID_YAXIS == righty) ? ITEM_MODIFIER_DOWN : ITEM_MODIFIER_POS, + righty); + } + } + + // only use switches if we have at least one pair of matching opposing directions + bool const lefth = (ITEM_ID_INVALID != leftleft) && (ITEM_ID_INVALID != leftright); + bool const leftv = (ITEM_ID_INVALID != leftup) && (ITEM_ID_INVALID != leftdown); + bool const righth = (ITEM_ID_INVALID != rightleft) && (ITEM_ID_INVALID != rightright); + bool const rightv = (ITEM_ID_INVALID != rightup) && (ITEM_ID_INVALID != rightdown); + if ((lefth && righth) || (leftv && rightv)) + { + // left stick + if (ITEM_ID_INVALID != leftleft) + { + if (!leftleftseq.empty()) + leftleftseq += input_seq::or_code; + leftleftseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, leftleft); + } + if (ITEM_ID_INVALID != leftright) + { + if (!leftrightseq.empty()) + leftrightseq += input_seq::or_code; + leftrightseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, leftright); + } + if (ITEM_ID_INVALID != leftup) + { + if (!leftupseq.empty()) + leftupseq += input_seq::or_code; + leftupseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, leftup); + } + if (ITEM_ID_INVALID != leftdown) + { + if (!leftdownseq.empty()) + leftdownseq += input_seq::or_code; + leftdownseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, leftdown); + } + + // right stick + if (ITEM_ID_INVALID != rightleft) + { + if (!rightleftseq.empty()) + rightleftseq += input_seq::or_code; + rightleftseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, rightleft); + } + if (ITEM_ID_INVALID != rightright) + { + if (!rightrightseq.empty()) + rightrightseq += input_seq::or_code; + rightrightseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, rightright); + } + if (ITEM_ID_INVALID != rightup) + { + if (!rightupseq.empty()) + rightupseq += input_seq::or_code; + rightupseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, rightup); + } + if (ITEM_ID_INVALID != rightdown) + { + if (!rightdownseq.empty()) + rightdownseq += input_seq::or_code; + rightdownseq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, rightdown); + } + } + + // now add collected assignments + if (!leftleftseq.empty()) + assignments.emplace_back(IPT_JOYSTICKLEFT_LEFT, SEQ_TYPE_STANDARD, leftleftseq); + if (!leftrightseq.empty()) + assignments.emplace_back(IPT_JOYSTICKLEFT_RIGHT, SEQ_TYPE_STANDARD, leftrightseq); + if (!leftupseq.empty()) + assignments.emplace_back(IPT_JOYSTICKLEFT_UP, SEQ_TYPE_STANDARD, leftupseq); + if (!leftdownseq.empty()) + assignments.emplace_back(IPT_JOYSTICKLEFT_DOWN, SEQ_TYPE_STANDARD, leftdownseq); + if (!rightleftseq.empty()) + assignments.emplace_back(IPT_JOYSTICKRIGHT_LEFT, SEQ_TYPE_STANDARD, rightleftseq); + if (!rightrightseq.empty()) + assignments.emplace_back(IPT_JOYSTICKRIGHT_RIGHT, SEQ_TYPE_STANDARD, rightrightseq); + if (!rightupseq.empty()) + assignments.emplace_back(IPT_JOYSTICKRIGHT_UP, SEQ_TYPE_STANDARD, rightupseq); + if (!rightdownseq.empty()) + assignments.emplace_back(IPT_JOYSTICKRIGHT_DOWN, SEQ_TYPE_STANDARD, rightdownseq); +} + + +void joystick_assignment_helper::choose_primary_stick( + input_item_id (&stickaxes)[2][2], + input_item_id leftx, + input_item_id lefty, + input_item_id rightx, + input_item_id righty) +{ + if ((ITEM_ID_INVALID != leftx) && (ITEM_ID_INVALID != lefty)) + { + // left stick has both axes, make it primary + stickaxes[0][0] = leftx; + stickaxes[0][1] = lefty; + stickaxes[1][0] = rightx; + stickaxes[1][1] = righty; + } + else if ((ITEM_ID_INVALID != rightx) && (ITEM_ID_INVALID != righty)) + { + // right stick has both axes, make it primary + stickaxes[0][0] = rightx; + stickaxes[0][1] = righty; + stickaxes[1][0] = leftx; + stickaxes[1][1] = lefty; + } + else if (ITEM_ID_INVALID != leftx) + { + // degenerate case - left X and possibly right X or Y + stickaxes[0][0] = leftx; + stickaxes[0][1] = righty; + stickaxes[1][0] = rightx; + stickaxes[1][1] = ITEM_ID_INVALID; + } + else if ((ITEM_ID_INVALID != rightx) || (ITEM_ID_INVALID != lefty)) + { + // degenerate case - right X and possibly left Y, or one or two Y axes + stickaxes[0][0] = rightx; + stickaxes[0][1] = lefty; + stickaxes[1][0] = ITEM_ID_INVALID; + stickaxes[1][1] = righty; + } + else + { + // degenerate case - one Y axis at most + stickaxes[0][0] = ITEM_ID_INVALID; + stickaxes[0][1] = righty; + stickaxes[1][0] = ITEM_ID_INVALID; + stickaxes[1][1] = ITEM_ID_INVALID; + } +} + +} // namespace osd diff --git a/src/osd/modules/input/assignmenthelper.h b/src/osd/modules/input/assignmenthelper.h new file mode 100644 index 00000000000..ff5a9d34072 --- /dev/null +++ b/src/osd/modules/input/assignmenthelper.h @@ -0,0 +1,118 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +//============================================================ +// +// assignmenthelper.h - input assignment setup helper +// +//============================================================ +#ifndef MAME_OSD_INPUT_ASSIGNMENTHELPER_H +#define MAME_OSD_INPUT_ASSIGNMENTHELPER_H + +#pragma once + +#include "interface/inputcode.h" +#include "interface/inputdev.h" + +#include <initializer_list> + + +namespace osd { + +class joystick_assignment_helper +{ +protected: + static constexpr input_code make_code( + input_item_class itemclass, + input_item_modifier modifier, + input_item_id item) + { + return input_code(DEVICE_CLASS_JOYSTICK, 0, itemclass, modifier, item); + } + + static bool add_assignment( + input_device::assignment_vector &assignments, + ioport_type fieldtype, + input_seq_type seqtype, + input_item_class itemclass, + input_item_modifier modifier, + std::initializer_list<input_item_id> items); + + static bool add_button_assignment( + input_device::assignment_vector &assignments, + ioport_type field_type, + std::initializer_list<input_item_id> items); + + static bool add_button_pair_assignment( + input_device::assignment_vector &assignments, + ioport_type field1, + ioport_type field2, + input_item_id button1, + input_item_id button2); + + static bool add_axis_inc_dec_assignment( + input_device::assignment_vector &assignments, + ioport_type field_type, + input_item_id button_dec, + input_item_id button_inc); + + static bool add_axis_pair_assignment( + input_device::assignment_vector &assignments, + ioport_type field1, + ioport_type field2, + input_item_id axis); + + static bool consume_button_pair( + input_device::assignment_vector &assignments, + ioport_type field1, + ioport_type field2, + input_item_id &button1, + input_item_id &button2); + + static bool consume_trigger_pair( + input_device::assignment_vector &assignments, + ioport_type field1, + ioport_type field2, + input_item_id &axis1, + input_item_id &axis2); + + static bool consume_axis_pair( + input_device::assignment_vector &assignments, + ioport_type field1, + ioport_type field2, + input_item_id &axis); + + static void add_directional_assignments( + input_device::assignment_vector &assignments, + input_item_id xaxis, + input_item_id yaxis, + input_item_id leftswitch, + input_item_id rightswitch, + input_item_id upswitch, + input_item_id downswitch); + + static void add_twin_stick_assignments( + input_device::assignment_vector &assignments, + input_item_id leftx, + input_item_id lefty, + input_item_id rightx, + input_item_id righty, + input_item_id leftleft, + input_item_id leftright, + input_item_id leftup, + input_item_id leftdown, + input_item_id rightleft, + input_item_id rightright, + input_item_id rightup, + input_item_id rightdown); + + static void choose_primary_stick( + input_item_id (&stickaxes)[2][2], + input_item_id leftx, + input_item_id lefty, + input_item_id rightx, + input_item_id righty); +}; + +} // namespace osd + +#endif // MAME_OSD_INPUT_ASSIGNMENTHELPER_H diff --git a/src/osd/modules/input/input_dinput.cpp b/src/osd/modules/input/input_dinput.cpp index 2ac7c7458fd..8642f693d03 100644 --- a/src/osd/modules/input/input_dinput.cpp +++ b/src/osd/modules/input/input_dinput.cpp @@ -1,22 +1,108 @@ // license:BSD-3-Clause -// copyright-holders:Aaron Giles, Brad Hughes +// copyright-holders:Aaron Giles, Brad Hughes, Vas Crabb //============================================================ // // input_dinput.cpp - Windows DirectInput support // //============================================================ +/* + +DirectInput joystick input is a bit of a mess. It gives eight axes +called X, Y, Z, Rx, Ry, Rz, slider 0 and slider 1. The driver can +assign arbitrary physical axes to these axes. Up to four hat switches +are supported, giving a direction in hundredths of degrees. In theory, +this supports dial-like controls with an arbitrary number of stops. In +practice, it just makes dealing with 8-way hat switches more complicated +and prevents contradictory inputs from being reported altogether. + +You may get a vague indication of the type of controller, and you can +obtain usage information for HID controllers. + +The Windows HID driver supposedly uses the following mappings: + +0x01 Generic Desktop 0x30 X X +0x01 Generic Desktop 0x31 Y Y +0x01 Generic Desktop 0x32 Z Z +0x01 Generic Desktop 0x33 Rx Rx +0x01 Generic Desktop 0x34 Ry Ry +0x01 Generic Desktop 0x35 Rz Rz +0x01 Generic Desktop 0x36 Slider Slider +0x01 Generic Desktop 0x37 Dial Slider +0x01 Generic Desktop 0x39 Hat Switch POV Hat +0x02 Simulation 0xBA Rudder Rz +0x02 Simulation 0xBB Throttle Slider +0x02 Simulation 0xC4 Accelerator Y +0x02 Simulation 0xC5 Brake Rz +0x02 Simulation 0xC8 Steering X + +Anything without an explicit mapping is treated as a button. + +The WinMM driver supposedly uses the following axis mappings: + +X X +Y Y +Z Slider +R Rz +U Slider +V Slider + +The actual mapping used by various controllers doesn't match what you +might expect from the HID mapping. + +Gamepads: + +Axis Logitech Xinput Switch +X Left X Left X Left X +Y Left Y Left Y Left Y +Z Right X Triggers +Rx Right X Right X +Ry Right Y Right Y +Rz Right Y + +Thrustmaster controllers: + +Axis HOTAS Side stick Throttle/Pedals Dual Throttles Triple Throttles Driving +X Aileron Aileron Mini Stick X Left Throttle Right Brake Steering +Y Elevator Elevator Mini stick Y Right Throttle Left Brake Brake +Z Throttle Throttle Flaps Rudder +Rx Right Brake Right Brake Right Brake Left Throttle +Ry Left Brake Left Brake Left Brake Centre Throttle +Rz Twist Rudder Rocker Air Brake Right Throttle Accelerator +Slider 0 Rocker Throttle Antenna Rudder Clutch +Slider 1 Rudder Rudder + +Logitech controllers: + +Axis Pro Wheels +X Steering +Y +Z +Rx Accelerator +Ry Brake +Rz Clutch +Slider 0 +Slider 1 + +MFG Crosswind pedals: + +X Left Brake +Y Right Brake +Rz Rudder + +*/ #include "modules/osdmodule.h" #if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) -// emu -#include "emu.h" // put this here before Windows headers define interface as a macro - #include "input_dinput.h" +#include "interface/inputseq.h" #include "windows/winutil.h" +// emu +#include "inpttype.h" + // lib/util #include "util/corestr.h" @@ -26,6 +112,7 @@ #endif #include <algorithm> +#include <cmath> #include <iterator> #include <memory> @@ -44,8 +131,180 @@ BOOL CALLBACK device_enum_interface_callback(LPCDIDEVICEINSTANCE instance, LPVOI } +std::string guid_to_string(GUID const &guid) +{ + // size of a GUID string with dashes plus NUL terminator + char guid_string[37]; + snprintf( + guid_string, std::size(guid_string), + "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + guid.Data1, guid.Data2, guid.Data3, + guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], + guid.Data4[6], guid.Data4[7]); + + return guid_string; +} + + + //============================================================ -// dinput_module - base directinput module +// dinput_keyboard_device - DirectInput keyboard device +//============================================================ + +class dinput_keyboard_device : public dinput_device +{ +public: + dinput_keyboard_device( + std::string &&name, + std::string &&id, + input_module &module, + Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, + DIDEVCAPS const &caps, + LPCDIDATAFORMAT format); + + virtual void poll() override; + virtual void reset() override; + virtual void configure(input_device &device) override; + +private: + std::mutex m_device_lock; + keyboard_state m_keyboard; +}; + +dinput_keyboard_device::dinput_keyboard_device( + std::string &&name, + std::string &&id, + input_module &module, + Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, + DIDEVCAPS const &caps, + LPCDIDATAFORMAT format) : + dinput_device(std::move(name), std::move(id), module, std::move(device), caps, format), + m_keyboard({ { 0 } }) +{ +} + +void dinput_keyboard_device::poll() +{ + // poll the DirectInput immediate state + std::lock_guard<std::mutex> scope_lock(m_device_lock); + poll_dinput(&m_keyboard.state); +} + +void dinput_keyboard_device::reset() +{ + memset(&m_keyboard.state, 0, sizeof(m_keyboard.state)); +} + +void dinput_keyboard_device::configure(input_device &device) +{ + // populate it + char defname[20]; + for (int keynum = 0; keynum < MAX_KEYS; keynum++) + { + input_item_id itemid = keyboard_trans_table::instance().map_di_scancode_to_itemid(keynum); + + // generate/fetch the name + snprintf(defname, std::size(defname), "Scan%03d", keynum); + + // add the item to the device + device.add_item( + item_name(keynum, defname, nullptr), + strmakeupper(defname), + itemid, + generic_button_get_state<uint8_t>, + &m_keyboard.state[keynum]); + } +} + + +//============================================================ +// dinput_mouse_device - DirectInput mouse device +//============================================================ + +class dinput_mouse_device : public dinput_device +{ +public: + dinput_mouse_device( + std::string &&name, + std::string &&id, + input_module &module, + Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, + DIDEVCAPS const &caps, + LPCDIDATAFORMAT format); + + void poll() override; + void reset() override; + virtual void configure(input_device &device) override; + +private: + mouse_state m_mouse; +}; + +dinput_mouse_device::dinput_mouse_device( + std::string &&name, + std::string &&id, + input_module &module, + Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, + DIDEVCAPS const &caps, + LPCDIDATAFORMAT format) : + dinput_device(std::move(name), std::move(id), module, std::move(device), caps, format), + m_mouse({0}) +{ + // cap the number of axes and buttons based on the format + m_caps.dwAxes = std::min(m_caps.dwAxes, DWORD(3)); + m_caps.dwButtons = std::min(m_caps.dwButtons, DWORD((m_format == &c_dfDIMouse) ? 4 : 8)); +} + +void dinput_mouse_device::poll() +{ + // poll + if (poll_dinput(&m_mouse) == DI_OK) + { + // scale the axis data + m_mouse.lX *= input_device::RELATIVE_PER_PIXEL; + m_mouse.lY *= input_device::RELATIVE_PER_PIXEL; + m_mouse.lZ *= input_device::RELATIVE_PER_PIXEL; + } +} + +void dinput_mouse_device::reset() +{ + memset(&m_mouse, 0, sizeof(m_mouse)); +} + +void dinput_mouse_device::configure(input_device &device) +{ + // populate the axes + for (int axisnum = 0; axisnum < m_caps.dwAxes; axisnum++) + { + // add to the mouse device and optionally to the gun device as well + device.add_item( + item_name(offsetof(DIMOUSESTATE, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], nullptr), + std::string_view(), + input_item_id(ITEM_ID_XAXIS + axisnum), + generic_axis_get_state<LONG>, + &m_mouse.lX + axisnum); + } + + // populate the buttons + for (int butnum = 0; butnum < m_caps.dwButtons; butnum++) + { + auto offset = reinterpret_cast<uintptr_t>(&static_cast<DIMOUSESTATE *>(nullptr)->rgbButtons[butnum]); + + // add to the mouse device + device.add_item( + item_name(offset, default_button_name(butnum), nullptr), + std::string_view(), + input_item_id(ITEM_ID_BUTTON1 + butnum), + generic_button_get_state<BYTE>, + &m_mouse.rgbButtons[butnum]); + } +} + + +//============================================================ +// dinput_module - base DirectInput module //============================================================ class dinput_module : public input_module_impl<dinput_device, osd_common_t>, public device_enum_interface @@ -220,7 +479,7 @@ public: //============================================================ -// dinput_device - base directinput device +// dinput_device - base DirectInput device //============================================================ dinput_device::dinput_device( @@ -282,122 +541,6 @@ std::string dinput_device::item_name(int offset, std::string_view defstring, con //============================================================ -// dinput_keyboard_device - directinput keyboard device -//============================================================ - -dinput_keyboard_device::dinput_keyboard_device( - std::string &&name, - std::string &&id, - input_module &module, - Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, - DIDEVCAPS const &caps, - LPCDIDATAFORMAT format) : - dinput_device(std::move(name), std::move(id), module, std::move(device), caps, format), - m_keyboard({ { 0 } }) -{ -} - -void dinput_keyboard_device::poll() -{ - // poll the DirectInput immediate state - std::lock_guard<std::mutex> scope_lock(m_device_lock); - poll_dinput(&m_keyboard.state); -} - -void dinput_keyboard_device::reset() -{ - memset(&m_keyboard.state, 0, sizeof(m_keyboard.state)); -} - -void dinput_keyboard_device::configure(input_device &device) -{ - // populate it - char defname[20]; - for (int keynum = 0; keynum < MAX_KEYS; keynum++) - { - input_item_id itemid = keyboard_trans_table::instance().map_di_scancode_to_itemid(keynum); - - // generate/fetch the name - snprintf(defname, std::size(defname), "Scan%03d", keynum); - - // add the item to the device - device.add_item( - item_name(keynum, defname, nullptr), - strmakeupper(defname), - itemid, - generic_button_get_state<std::uint8_t>, - &m_keyboard.state[keynum]); - } -} - - -//============================================================ -// dinput_mouse_device - directinput mouse device -//============================================================ - -dinput_mouse_device::dinput_mouse_device( - std::string &&name, - std::string &&id, - input_module &module, - Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, - DIDEVCAPS const &caps, - LPCDIDATAFORMAT format) : - dinput_device(std::move(name), std::move(id), module, std::move(device), caps, format), - m_mouse({0}) -{ - // cap the number of axes and buttons based on the format - m_caps.dwAxes = std::min(m_caps.dwAxes, DWORD(3)); - m_caps.dwButtons = std::min(m_caps.dwButtons, DWORD((m_format == &c_dfDIMouse) ? 4 : 8)); -} - -void dinput_mouse_device::poll() -{ - // poll - if (poll_dinput(&m_mouse) == DI_OK) - { - // scale the axis data - m_mouse.lX *= input_device::RELATIVE_PER_PIXEL; - m_mouse.lY *= input_device::RELATIVE_PER_PIXEL; - m_mouse.lZ *= input_device::RELATIVE_PER_PIXEL; - } -} - -void dinput_mouse_device::reset() -{ - memset(&m_mouse, 0, sizeof(m_mouse)); -} - -void dinput_mouse_device::configure(input_device &device) -{ - // populate the axes - for (int axisnum = 0; axisnum < m_caps.dwAxes; axisnum++) - { - // add to the mouse device and optionally to the gun device as well - device.add_item( - item_name(offsetof(DIMOUSESTATE, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], nullptr), - std::string_view(), - input_item_id(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &m_mouse.lX + axisnum); - } - - // populate the buttons - for (int butnum = 0; butnum < m_caps.dwButtons; butnum++) - { - auto offset = reinterpret_cast<uintptr_t>(&static_cast<DIMOUSESTATE *>(nullptr)->rgbButtons[butnum]); - - // add to the mouse device - device.add_item( - item_name(offset, default_button_name(butnum), nullptr), - std::string_view(), - input_item_id(ITEM_ID_BUTTON1 + butnum), - generic_button_get_state<BYTE>, - &m_mouse.rgbButtons[butnum]); - } -} - - -//============================================================ // dinput_joystick_device - directinput joystick device //============================================================ @@ -431,28 +574,57 @@ void dinput_joystick_device::poll() // normalize axis values for (int axisnum = 0; axisnum < 8; axisnum++) { - LONG *const axis = &m_joystick.state.lX + axisnum; - *axis = normalize_absolute_axis(*axis, m_joystick.rangemin[axisnum], m_joystick.rangemax[axisnum]); + auto const range = m_joystick.rangemax[axisnum] - m_joystick.rangemin[axisnum]; + if (range) + { + // assumes output range is symmetrical + LONG *const axis = &m_joystick.state.lX + axisnum; + double const offset = *axis - m_joystick.rangemin[axisnum]; + double const scaled = offset * double(input_device::ABSOLUTE_MAX - input_device::ABSOLUTE_MIN) / double(range); + *axis = lround(std::clamp<double>(scaled + input_device::ABSOLUTE_MIN, input_device::ABSOLUTE_MIN, input_device::ABSOLUTE_MAX)); + } } } } void dinput_joystick_device::configure(input_device &device) { + input_device::assignment_vector assignments; HRESULT result; + // get device information - it gives clues about axis usage + DIDEVICEINSTANCE info; + info.dwSize = sizeof(info); + result = m_device->GetDeviceInfo(&info); + bool hid = false; + uint8_t type = DI8DEVTYPE_DEVICE; + uint8_t subtype = 0; + if (result == DI_OK) + { + hid = (info.dwDevType & DIDEVTYPE_HID) != 0; + type = info.dwDevType & 0x00ff; + subtype = (info.dwDevType >> 8) & 0x00ff; + osd_printf_verbose( + "DirectInput: Device type=0x%02X subtype=0x%02X HID=%u\n", + type, + subtype, + hid ? "yes" : "no"); + } + // turn off deadzone; we do our own calculations result = dinput_api_helper::set_dword_property(m_device, DIPROP_DEADZONE, 0, DIPH_DEVICE, 0); - if (result != DI_OK && result != DI_PROPNOEFFECT) + if ((result != DI_OK) && (result != DI_PROPNOEFFECT)) osd_printf_warning("DirectInput: Unable to reset deadzone for joystick %s.\n", name()); // turn off saturation; we do our own calculations result = dinput_api_helper::set_dword_property(m_device, DIPROP_SATURATION, 0, DIPH_DEVICE, 10000); - if (result != DI_OK && result != DI_PROPNOEFFECT) + if ((result != DI_OK) && (result != DI_PROPNOEFFECT)) osd_printf_warning("DirectInput: Unable to reset saturation for joystick %s.\n", name()); // populate the axes - for (uint32_t axisnum = 0, axiscount = 0; axiscount < m_caps.dwAxes && axisnum < 8; axisnum++) + input_item_id axisitems[8]; + std::fill(std::begin(axisitems), std::end(axisitems), ITEM_ID_INVALID); + for (uint32_t axisnum = 0, axiscount = 0; (axiscount < m_caps.dwAxes) && (axisnum < 8); axisnum++) { // fetch the range of this axis DIPROPRANGE dipr; @@ -463,6 +635,7 @@ void dinput_joystick_device::configure(input_device &device) result = m_device->GetProperty(DIPROP_RANGE, &dipr.diph); if (result != DI_OK) { + // this is normal when axes are skipped, e.g. X/Y/Z present, rX/rY absent, rZ present osd_printf_verbose("DirectInput: Unable to get properties for joystick %s axis %u.\n", name(), axisnum); continue; } @@ -471,7 +644,7 @@ void dinput_joystick_device::configure(input_device &device) m_joystick.rangemax[axisnum] = dipr.lMax; // populate the item description as well - device.add_item( + axisitems[axisnum] = device.add_item( item_name(offsetof(DIJOYSTATE2, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], nullptr), std::string_view(), input_item_id(ITEM_ID_XAXIS + axisnum), @@ -481,38 +654,114 @@ void dinput_joystick_device::configure(input_device &device) axiscount++; } - // populate the POVs + // take a guess at which axes might be pedals depending on type and remap onto negative half of range + input_item_id pedalitems[3] = { ITEM_ID_INVALID, ITEM_ID_INVALID, ITEM_ID_INVALID }; + if (DI8DEVTYPE_FLIGHT == type) + { + // Rx/Ry are often used for brakes + bool const rxpedal = (ITEM_ID_INVALID != axisitems[3]) && !m_joystick.rangemin[3] && (0 < m_joystick.rangemax[3]); + bool const rypedal = (ITEM_ID_INVALID != axisitems[4]) && !m_joystick.rangemin[4] && (0 < m_joystick.rangemax[4]); + if (rxpedal && rypedal) + { + pedalitems[0] = axisitems[3]; + m_joystick.rangemin[3] = m_joystick.rangemax[3]; + m_joystick.rangemax[3] = -m_joystick.rangemax[3]; + + pedalitems[1] = axisitems[4]; + m_joystick.rangemin[4] = m_joystick.rangemax[4]; + m_joystick.rangemax[4] = -m_joystick.rangemax[4]; + } + } + else if (DI8DEVTYPE_DRIVING == type) + { + bool const ypedal = (ITEM_ID_INVALID != axisitems[1]) && !m_joystick.rangemin[1] && (0 < m_joystick.rangemax[1]); + bool const rxpedal = (ITEM_ID_INVALID != axisitems[3]) && !m_joystick.rangemin[3] && (0 < m_joystick.rangemax[3]); + bool const rypedal = (ITEM_ID_INVALID != axisitems[4]) && !m_joystick.rangemin[4] && (0 < m_joystick.rangemax[4]); + bool const rzpedal = (ITEM_ID_INVALID != axisitems[5]) && !m_joystick.rangemin[5] && (0 < m_joystick.rangemax[5]); + bool const s0pedal = (ITEM_ID_INVALID != axisitems[6]) && !m_joystick.rangemin[6] && (0 < m_joystick.rangemax[6]); + if (DI8DEVTYPEDRIVING_DUALPEDALS == subtype) + { + // dual pedals are usually Y and Rz + if (ypedal && rzpedal) + { + pedalitems[0] = axisitems[1]; + m_joystick.rangemin[1] = m_joystick.rangemax[1]; + m_joystick.rangemax[1] = -m_joystick.rangemax[1]; + + pedalitems[1] = axisitems[5]; + m_joystick.rangemin[5] = m_joystick.rangemax[5]; + m_joystick.rangemax[5] = -m_joystick.rangemax[5]; + } + } + else if (DI8DEVTYPEDRIVING_THREEPEDALS == subtype) + { + // triple pedals may be Y, Rz and slider 0, or Rx, Ry and Rz + if (ypedal && rzpedal && s0pedal) + { + pedalitems[0] = axisitems[1]; + m_joystick.rangemin[1] = m_joystick.rangemax[1]; + m_joystick.rangemax[1] = -m_joystick.rangemax[1]; + + pedalitems[1] = axisitems[5]; + m_joystick.rangemin[5] = m_joystick.rangemax[5]; + m_joystick.rangemax[5] = -m_joystick.rangemax[5]; + + pedalitems[2] = axisitems[6]; + m_joystick.rangemin[6] = m_joystick.rangemax[6]; + m_joystick.rangemax[6] = -m_joystick.rangemax[6]; + } + else if (rxpedal && rypedal && rzpedal) + { + pedalitems[0] = axisitems[3]; + m_joystick.rangemin[3] = m_joystick.rangemax[3]; + m_joystick.rangemax[3] = -m_joystick.rangemax[3]; + + pedalitems[1] = axisitems[4]; + m_joystick.rangemin[4] = m_joystick.rangemax[4]; + m_joystick.rangemax[4] = -m_joystick.rangemax[4]; + + pedalitems[2] = axisitems[5]; + m_joystick.rangemin[5] = m_joystick.rangemax[5]; + m_joystick.rangemax[5] = -m_joystick.rangemax[5]; + } + } + } + + // populate the POV hats + input_item_id povitems[4][4]; + for (auto &pov : povitems) + std::fill(std::begin(pov), std::end(pov), ITEM_ID_INVALID); for (uint32_t povnum = 0; povnum < m_caps.dwPOVs; povnum++) { // left - device.add_item( + povitems[povnum][0] = device.add_item( item_name(offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), "Left"), std::string_view(), - input_item_id(povnum * 4 + ITEM_ID_HAT1LEFT), + input_item_id((povnum * 4) + ITEM_ID_HAT1LEFT), &dinput_joystick_device::pov_get_state, reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_LEFT))); // right - device.add_item( + povitems[povnum][1] = device.add_item( item_name(offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), "Right"), std::string_view(), - input_item_id(povnum * 4 + ITEM_ID_HAT1RIGHT), + input_item_id((povnum * 4) + ITEM_ID_HAT1RIGHT), &dinput_joystick_device::pov_get_state, reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_RIGHT))); // up - device.add_item( + povitems[povnum][2] = device.add_item( item_name(offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), "Up"), std::string_view(), - input_item_id(povnum * 4 + ITEM_ID_HAT1UP), + input_item_id((povnum * 4) + ITEM_ID_HAT1UP), &dinput_joystick_device::pov_get_state, reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_UP))); // down - device.add_item( + povitems[povnum][3] = device.add_item( item_name(offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), "Down"), std::string_view(), - input_item_id(povnum * 4 + ITEM_ID_HAT1DOWN), + input_item_id((povnum * 4) + ITEM_ID_HAT1DOWN), &dinput_joystick_device::pov_get_state, reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_DOWN))); } @@ -530,13 +779,386 @@ void dinput_joystick_device::configure(input_device &device) else itemid = ITEM_ID_OTHER_SWITCH; - device.add_item( + input_item_id const actual = device.add_item( item_name(offset, default_button_name(butnum), nullptr), std::string_view(), itemid, generic_button_get_state<BYTE>, &m_joystick.state.rgbButtons[butnum]); + + // there are sixteen action button types + if (butnum < 16) + { + input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, actual)); + assignments.emplace_back(ioport_type(IPT_BUTTON1 + butnum), SEQ_TYPE_STANDARD, seq); + + // assign the first few buttons to UI actions and pedals + // TODO: don't map pedals for driving controls that have them present + switch (butnum) + { + case 0: + assignments.emplace_back(IPT_PEDAL, SEQ_TYPE_INCREMENT, seq); + assignments.emplace_back(IPT_UI_SELECT, SEQ_TYPE_STANDARD, seq); + break; + case 1: + assignments.emplace_back(IPT_PEDAL2, SEQ_TYPE_INCREMENT, seq); + assignments.emplace_back((3 > m_caps.dwButtons) ? IPT_UI_CLEAR : IPT_UI_BACK, SEQ_TYPE_STANDARD, seq); + break; + case 2: + assignments.emplace_back(IPT_PEDAL3, SEQ_TYPE_INCREMENT, seq); + assignments.emplace_back(IPT_UI_CLEAR, SEQ_TYPE_STANDARD, seq); + break; + case 3: + assignments.emplace_back(IPT_UI_HELP, SEQ_TYPE_STANDARD, seq); + break; + } + } + } + + // add default assignments depending on type + if (DI8DEVTYPE_FLIGHT == type) + { + if (((ITEM_ID_INVALID == axisitems[0]) || (ITEM_ID_INVALID == axisitems[1])) && (1 <= m_caps.dwPOVs)) + { + // X or Y missing, fall back to using POV hat for navigation + add_directional_assignments( + assignments, + axisitems[0], + axisitems[1], + povitems[0][0], + povitems[0][1], + povitems[0][2], + povitems[0][3]); + + // try using throttle for zoom/focus + if (ITEM_ID_INVALID != axisitems[2]) + { + input_seq const negseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisitems[2])); + input_seq const posseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisitems[2])); + if (ITEM_ID_INVALID != axisitems[5]) + { + assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, posseq); + assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, negseq); + assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, negseq); + assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, posseq); + } + else + { + assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, posseq); + assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, negseq); + } + } + + // try using twist/rudder for next/previous group + if (ITEM_ID_INVALID != axisitems[5]) + { + input_seq const negseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisitems[5])); + input_seq const posseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisitems[5])); + assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, negseq); + assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, posseq); + } + } + else + { + // only use stick for primary navigation/movement + add_directional_assignments( + assignments, + axisitems[0], + axisitems[1], + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID); + + // try using hat for secondary navigation functions + if (1 <= m_caps.dwPOVs) + { + input_seq const leftseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, povitems[0][0])); + input_seq const rightseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, povitems[0][1])); + input_seq const upseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, povitems[0][2])); + input_seq const downseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, povitems[0][3])); + if ((ITEM_ID_INVALID != axisitems[2]) || (ITEM_ID_INVALID != axisitems[5])) + { + assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, leftseq); + assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, rightseq); + assignments.emplace_back(IPT_UI_PAGE_UP, SEQ_TYPE_STANDARD, upseq); + assignments.emplace_back(IPT_UI_PAGE_DOWN, SEQ_TYPE_STANDARD, downseq); + } + else + { + assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, leftseq); + assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, rightseq); + assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, leftseq); + assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, rightseq); + assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, upseq); + assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, downseq); + } + } + + // try using throttle for zoom + if (ITEM_ID_INVALID != axisitems[2]) + { + input_seq const negseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisitems[2])); + input_seq const posseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisitems[2])); + if ((1 <= m_caps.dwPOVs) || (ITEM_ID_INVALID != axisitems[5])) + { + assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, posseq); + assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, negseq); + if ((1 > m_caps.dwPOVs) || (ITEM_ID_INVALID == axisitems[5])) + { + assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, negseq); + assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, posseq); + } + } + else + { + assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, posseq); + assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, negseq); + } + } + + // try using twist/rudder for focus next/previous + if (ITEM_ID_INVALID != axisitems[5]) + { + input_seq const negseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisitems[5])); + input_seq const posseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisitems[5])); + if (1 <= m_caps.dwPOVs) + { + assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, negseq); + assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, posseq); + if (ITEM_ID_INVALID == axisitems[2]) + { + assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, posseq); + assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, negseq); + } + } + else + { + assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, negseq); + assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, posseq); + } + } + } + + // Z or slider 0 is usually the throttle - use one of them for joystick Z + add_assignment( + assignments, + IPT_AD_STICK_Z, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_NONE, + { axisitems[2], axisitems[6] }); + + // use Z for the first two pedals if present + if (ITEM_ID_INVALID != axisitems[2]) + { + // TODO: use Rx/Ry as well if they appear to be brakes + input_seq const pedal1seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_POS, axisitems[2])); + input_seq const pedal2seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, axisitems[2])); + assignments.emplace_back(IPT_PEDAL, SEQ_TYPE_STANDARD, pedal1seq); + assignments.emplace_back(IPT_PEDAL2, SEQ_TYPE_STANDARD, pedal2seq); + } + } + else if (DI8DEVTYPE_DRIVING == type) + { + // use the wheel and D-pad for navigation and directional controls + add_directional_assignments( + assignments, + axisitems[0], + ITEM_ID_INVALID, + povitems[0][0], + povitems[0][1], + povitems[0][2], + povitems[0][3]); + + // check subtype to determine how pedals should be assigned + if (DI8DEVTYPEDRIVING_COMBINEDPEDALS == subtype) + { + if (ITEM_ID_INVALID != axisitems[1]) + { + // put first two pedals on opposite sides of Y axis + assignments.emplace_back( + IPT_PEDAL, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_POS, axisitems[1]))); + assignments.emplace_back( + IPT_PEDAL2, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, axisitems[1]))); + + // use for previous/next group as well + assignments.emplace_back( + IPT_UI_NEXT_GROUP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisitems[1]))); + assignments.emplace_back( + IPT_UI_PREV_GROUP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisitems[1]))); + } + } + else + { + // see if we have individual pedals + if (ITEM_ID_INVALID != pedalitems[0]) + { + assignments.emplace_back( + IPT_PEDAL, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, pedalitems[0]))); + assignments.emplace_back( + IPT_UI_NEXT_GROUP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, pedalitems[0]))); + } + if (ITEM_ID_INVALID != pedalitems[1]) + { + assignments.emplace_back( + IPT_PEDAL2, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, pedalitems[1]))); + assignments.emplace_back( + IPT_UI_PREV_GROUP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, pedalitems[1]))); + } + if (ITEM_ID_INVALID != pedalitems[2]) + { + assignments.emplace_back( + IPT_PEDAL3, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, pedalitems[2]))); + assignments.emplace_back( + IPT_UI_FOCUS_NEXT, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, pedalitems[2]))); + } + } + } + else + { + // assume this is a gamepad - see if it looks like it has dual analog sticks + input_item_id stickaxes[2][2] = { + { axisitems[0], axisitems[1] }, + { ITEM_ID_INVALID, ITEM_ID_INVALID } }; + input_item_id pedalaxis = ITEM_ID_INVALID; + if ((ITEM_ID_INVALID != axisitems[3]) && (ITEM_ID_INVALID != axisitems[4])) + { + // assume Rx/Ry are right stick and Z is triggers if present + stickaxes[1][0] = axisitems[3]; + stickaxes[1][1] = axisitems[4]; + pedalaxis = axisitems[2]; + add_twin_stick_assignments( + assignments, + stickaxes[0][0], + stickaxes[0][1], + stickaxes[1][0], + stickaxes[1][1], + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID); + } + else if ((ITEM_ID_INVALID != axisitems[2]) && (ITEM_ID_INVALID != axisitems[5])) + { + // assume Z/Rz are right stick + stickaxes[1][0] = axisitems[2]; + stickaxes[1][1] = axisitems[5]; + add_twin_stick_assignments( + assignments, + stickaxes[0][0], + stickaxes[0][1], + stickaxes[1][0], + stickaxes[1][1], + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID); + } + else + { + // if Z is present, use it as secondary Y + stickaxes[1][1] = axisitems[2]; + } + + // try to find a "complete" stick for primary movement controls + input_item_id diraxis[2][2]; + choose_primary_stick(diraxis, stickaxes[0][0], stickaxes[0][1], stickaxes[1][0], stickaxes[1][1]); + add_directional_assignments( + assignments, + diraxis[0][0], + diraxis[0][1], + povitems[0][0], + povitems[0][1], + povitems[0][2], + povitems[0][3]); + + // assign a secondary stick axis to joystick Z + add_assignment( + assignments, + IPT_AD_STICK_Z, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_NONE, + { diraxis[1][1], diraxis[1][0] }); + + // try to find a suitable axis to use for the first two pedals + add_assignment( + assignments, + IPT_PEDAL, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_NEG, + { pedalaxis, diraxis[1][1], diraxis[0][1] }); + add_assignment( + assignments, + IPT_PEDAL2, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_POS, + { pedalaxis, diraxis[1][1], diraxis[0][1] }); + + // try to choose an axis for previous/next group + if (ITEM_ID_INVALID != pedalaxis) + { + // this is reversed because right trigger is negative direction + assignments.emplace_back( + IPT_UI_PREV_GROUP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, pedalaxis))); + assignments.emplace_back( + IPT_UI_NEXT_GROUP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, pedalaxis))); + pedalaxis = ITEM_ID_INVALID; + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, diraxis[1][1])) + { + // took secondary Y + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, diraxis[1][0])) + { + // took secondary X + } + + // use secondary Y for page up/down if available + consume_axis_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, diraxis[1][1]); + + // put focus previous/next and zoom on secondary X if available + add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, diraxis[1][0]); + add_axis_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, diraxis[1][0]); } + + // set default assignments + device.set_default_assignments(std::move(assignments)); } int32_t dinput_joystick_device::pov_get_state(void *device_internal, void *item_internal) @@ -580,7 +1202,7 @@ dinput_api_helper::~dinput_api_helper() int dinput_api_helper::initialize() { - HRESULT result = DirectInput8Create(GetModuleHandleUni(), DIRECTINPUT_VERSION, IID_IDirectInput8, reinterpret_cast<void **>(m_dinput.GetAddressOf()), nullptr); + HRESULT result = DirectInput8Create(GetModuleHandleUni(), DIRECTINPUT_VERSION, IID_IDirectInput8, &m_dinput, nullptr); if (result != DI_OK) { return result; @@ -683,6 +1305,18 @@ std::pair<Microsoft::WRL::ComPtr<IDirectInputDevice8>, LPCDIDATAFORMAT> dinput_a return std::make_pair(std::move(device), format); } + +std::string dinput_api_helper::make_id(LPCDIDEVICEINSTANCE instance) +{ + // use name, product GUID and instance GUID as identifier + return + text::from_tstring(instance->tszInstanceName) + + " product_" + + guid_to_string(instance->guidProduct) + + " instance_" + + guid_to_string(instance->guidInstance); +} + } // namespace osd diff --git a/src/osd/modules/input/input_dinput.h b/src/osd/modules/input/input_dinput.h index 75fc873d049..a5d6877723e 100644 --- a/src/osd/modules/input/input_dinput.h +++ b/src/osd/modules/input/input_dinput.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Aaron Giles, Brad Hughes +// copyright-holders:Aaron Giles, Brad Hughes, Vas Crabb //============================================================ // // input_dinput.h - Windows DirectInput support @@ -10,6 +10,7 @@ #pragma once +#include "assignmenthelper.h" #include "input_wincommon.h" #include "modules/lib/osdlib.h" @@ -81,16 +82,10 @@ public: if (!callback(device, format)) return nullptr; - // convert instance name to UTF-8 - std::string utf8_instance_name = text::from_tstring(instance->tszInstanceName); - - // set device id to name + product unique identifier + instance unique identifier - std::string utf8_instance_id = utf8_instance_name + " product_" + guid_to_string(instance->guidProduct) + " instance_" + guid_to_string(instance->guidInstance); - // allocate memory for the device object return std::make_unique<TDevice>( - std::move(utf8_instance_name), - std::move(utf8_instance_id), + text::from_tstring(instance->tszInstanceName), + make_id(instance), module, std::move(device), caps, @@ -117,22 +112,6 @@ public: return device->SetProperty(property_guid, &dipdw.diph); } - static std::string guid_to_string(const GUID& guid) - { - // Size of a GUID string with dashes plus null terminator - char guid_string[37]; - - snprintf( - guid_string, std::size(guid_string), - "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", - guid.Data1, guid.Data2, guid.Data3, - guid.Data4[0], guid.Data4[1], guid.Data4[2], - guid.Data4[3], guid.Data4[4], guid.Data4[5], - guid.Data4[6], guid.Data4[7]); - - return guid_string; - } - private: std::pair<Microsoft::WRL::ComPtr<IDirectInputDevice8>, LPCDIDATAFORMAT> open_device( LPCDIDEVICEINSTANCE instance, @@ -140,6 +119,8 @@ private: LPCDIDATAFORMAT format2, dinput_cooperative_level cooperative_level); + static std::string make_id(LPCDIDEVICEINSTANCE instance); + Microsoft::WRL::ComPtr<IDirectInput8> m_dinput; dynamic_module::ptr m_dinput_dll; }; @@ -167,48 +148,7 @@ protected: }; -class dinput_keyboard_device : public dinput_device -{ -public: - dinput_keyboard_device( - std::string &&name, - std::string &&id, - input_module &module, - Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, - DIDEVCAPS const &caps, - LPCDIDATAFORMAT format); - - virtual void poll() override; - virtual void reset() override; - virtual void configure(input_device &device) override; - -private: - std::mutex m_device_lock; - keyboard_state m_keyboard; -}; - - -class dinput_mouse_device : public dinput_device -{ -public: - dinput_mouse_device( - std::string &&name, - std::string &&id, - input_module &module, - Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, - DIDEVCAPS const &caps, - LPCDIDATAFORMAT format); - - void poll() override; - void reset() override; - virtual void configure(input_device &device) override; - -private: - mouse_state m_mouse; -}; - - -class dinput_joystick_device : public dinput_device +class dinput_joystick_device : public dinput_device, protected joystick_assignment_helper { public: dinput_joystick_device( diff --git a/src/osd/modules/input/input_rawinput.cpp b/src/osd/modules/input/input_rawinput.cpp index 4e4f1811e40..ca6522f20e2 100644 --- a/src/osd/modules/input/input_rawinput.cpp +++ b/src/osd/modules/input/input_rawinput.cpp @@ -21,7 +21,7 @@ #include "strconv.h" // MAME headers -#include "emu.h" +#include "inpttype.h" #include <algorithm> #include <cassert> diff --git a/src/osd/modules/input/input_sdl.cpp b/src/osd/modules/input/input_sdl.cpp index 6def55c5cc0..68779a151e4 100644 --- a/src/osd/modules/input/input_sdl.cpp +++ b/src/osd/modules/input/input_sdl.cpp @@ -17,6 +17,7 @@ #if defined(OSD_SDL) +#include "assignmenthelper.h" #include "input_common.h" #include "interface/inputseq.h" @@ -24,7 +25,7 @@ #include "sdl/osdsdl.h" // emu -#include "emu.h" +#include "inpttype.h" // standard SDL header #include <SDL2/SDL.h> @@ -759,7 +760,7 @@ private: // sdl_joystick_device_base //============================================================ -class sdl_joystick_device_base : public sdl_device +class sdl_joystick_device_base : public sdl_device, protected joystick_assignment_helper { public: std::optional<std::string> const &serial() const { return m_serial; } @@ -834,6 +835,7 @@ public: virtual void configure(input_device &device) override { + input_device::assignment_vector assignments; char tempname[32]; int const axiscount = SDL_JoystickNumAxes(m_joydevice); @@ -842,6 +844,7 @@ public: int const ballcount = SDL_JoystickNumBalls(m_joydevice); // loop over all axes + input_item_id axisactual[MAX_AXES]; for (int axis = 0; (axis < MAX_AXES) && (axis < axiscount); axis++) { input_item_id itemid; @@ -854,7 +857,7 @@ public: itemid = ITEM_ID_OTHER_AXIS_ABSOLUTE; snprintf(tempname, sizeof(tempname), "A%d", axis + 1); - device.add_item( + axisactual[axis] = device.add_item( tempname, std::string_view(), itemid, @@ -876,22 +879,50 @@ public: else itemid = ITEM_ID_OTHER_SWITCH; - device.add_item( + input_item_id const actual = device.add_item( default_button_name(button), std::string_view(), itemid, generic_button_get_state<s32>, &m_joystick.buttons[button]); + + // there are sixteen action button types + if (button < 16) + { + input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, actual)); + assignments.emplace_back(ioport_type(IPT_BUTTON1 + button), SEQ_TYPE_STANDARD, seq); + + // assign the first few buttons to UI actions and pedals + switch (button) + { + case 0: + assignments.emplace_back(IPT_PEDAL, SEQ_TYPE_INCREMENT, seq); + assignments.emplace_back(IPT_UI_SELECT, SEQ_TYPE_STANDARD, seq); + break; + case 1: + assignments.emplace_back(IPT_PEDAL2, SEQ_TYPE_INCREMENT, seq); + assignments.emplace_back((3 > buttoncount) ? IPT_UI_CLEAR : IPT_UI_BACK, SEQ_TYPE_STANDARD, seq); + break; + case 2: + assignments.emplace_back(IPT_PEDAL3, SEQ_TYPE_INCREMENT, seq); + assignments.emplace_back(IPT_UI_CLEAR, SEQ_TYPE_STANDARD, seq); + break; + case 3: + assignments.emplace_back(IPT_UI_HELP, SEQ_TYPE_STANDARD, seq); + break; + } + } } // loop over all hats + input_item_id hatactual[MAX_HATS][4]; for (int hat = 0; (hat < MAX_HATS) && (hat < hatcount); hat++) { input_item_id itemid; snprintf(tempname, sizeof(tempname), "Hat %d Up", hat + 1); itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1UP + (4 * hat) : ITEM_ID_OTHER_SWITCH); - device.add_item( + hatactual[hat][0] = device.add_item( tempname, std::string_view(), itemid, @@ -900,7 +931,7 @@ public: snprintf(tempname, sizeof(tempname), "Hat %d Down", hat + 1); itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1DOWN + (4 * hat) : ITEM_ID_OTHER_SWITCH); - device.add_item( + hatactual[hat][1] = device.add_item( tempname, std::string_view(), itemid, @@ -909,7 +940,7 @@ public: snprintf(tempname, sizeof(tempname), "Hat %d Left", hat + 1); itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1LEFT + (4 * hat) : ITEM_ID_OTHER_SWITCH); - device.add_item( + hatactual[hat][2] = device.add_item( tempname, std::string_view(), itemid, @@ -918,7 +949,7 @@ public: snprintf(tempname, sizeof(tempname), "Hat %d Right", hat + 1); itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1RIGHT + (4 * hat) : ITEM_ID_OTHER_SWITCH); - device.add_item( + hatactual[hat][3] = device.add_item( tempname, std::string_view(), itemid, @@ -937,7 +968,7 @@ public: itemid = ITEM_ID_OTHER_AXIS_RELATIVE; snprintf(tempname, sizeof(tempname), "R%d X", ball + 1); - device.add_item( + input_item_id const xactual = device.add_item( tempname, std::string_view(), input_item_id(itemid), @@ -945,13 +976,130 @@ public: &m_joystick.balls[ball * 2]); snprintf(tempname, sizeof(tempname), "R%d Y", ball + 1); - device.add_item( + input_item_id const yactual = device.add_item( tempname, std::string_view(), input_item_id(itemid + 1), generic_axis_get_state<s32>, &m_joystick.balls[ball * 2 + 1]); + + if (0 == ball) + { + // assign the first trackball to dial, trackball, mouse and lightgun inputs + input_seq const xseq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, xactual)); + input_seq const yseq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, yactual)); + assignments.emplace_back(IPT_DIAL, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_DIAL_V, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_TRACKBALL_X, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_TRACKBALL_Y, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_LIGHTGUN_X, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_LIGHTGUN_Y, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_MOUSE_X, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_MOUSE_Y, SEQ_TYPE_STANDARD, yseq); + if (2 > axiscount) + { + // use it for joystick inputs if axes are limited + assignments.emplace_back(IPT_AD_STICK_X, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_AD_STICK_Y, SEQ_TYPE_STANDARD, yseq); + } + else + { + // use for non-centring throttle control + assignments.emplace_back(IPT_AD_STICK_Z, SEQ_TYPE_STANDARD, yseq); + } + } + else if ((1 == ball) && (2 > axiscount)) + { + // provide a non-centring throttle control + input_seq const yseq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, yactual)); + assignments.emplace_back(IPT_AD_STICK_Z, SEQ_TYPE_STANDARD, yseq); + } + } + + // set up default assignments for axes and hats + add_directional_assignments( + assignments, + (1 <= axiscount) ? axisactual[0] : ITEM_ID_INVALID, // assume first axis is X + (2 <= axiscount) ? axisactual[1] : ITEM_ID_INVALID, // assume second axis is Y + (1 <= hatcount) ? hatactual[0][2] : ITEM_ID_INVALID, + (1 <= hatcount) ? hatactual[0][3] : ITEM_ID_INVALID, + (1 <= hatcount) ? hatactual[0][0] : ITEM_ID_INVALID, + (1 <= hatcount) ? hatactual[0][1] : ITEM_ID_INVALID); + if (2 <= axiscount) + { + // put pedals on the last of the second, third or fourth axis + input_item_id const pedalitem = axisactual[(std::min)(axiscount, 4) - 1]; + assignments.emplace_back( + IPT_PEDAL, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, pedalitem))); + assignments.emplace_back( + IPT_PEDAL2, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_POS, pedalitem))); + } + if (3 <= axiscount) + { + // assign X/Y to one of the twin sticks + assignments.emplace_back( + (4 <= axiscount) ? IPT_JOYSTICKLEFT_LEFT : IPT_JOYSTICKRIGHT_LEFT, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_LEFT, axisactual[0]))); + assignments.emplace_back( + (4 <= axiscount) ? IPT_JOYSTICKLEFT_RIGHT : IPT_JOYSTICKRIGHT_RIGHT, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_RIGHT, axisactual[0]))); + assignments.emplace_back( + (4 <= axiscount) ? IPT_JOYSTICKLEFT_UP : IPT_JOYSTICKRIGHT_UP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_UP, axisactual[1]))); + assignments.emplace_back( + (4 <= axiscount) ? IPT_JOYSTICKLEFT_DOWN : IPT_JOYSTICKRIGHT_DOWN, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_DOWN, axisactual[1]))); + + // use third or fourth axis for Z + input_seq const seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, axisactual[(std::min)(axiscount, 4) - 1])); + assignments.emplace_back(IPT_AD_STICK_Z, SEQ_TYPE_STANDARD, seq); + + // use this for focus next/previous to make system selection menu practical to navigate + input_seq const upseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisactual[2])); + input_seq const downseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisactual[2])); + assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, upseq); + assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, downseq); + if (4 <= axiscount) + { + // use for zoom as well if there's another axis to use for previous/next group + assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, downseq); + assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, upseq); + } + + // use this for twin sticks, too + assignments.emplace_back((4 <= axiscount) ? IPT_JOYSTICKRIGHT_LEFT : IPT_JOYSTICKLEFT_UP, SEQ_TYPE_STANDARD, upseq); + assignments.emplace_back((4 <= axiscount) ? IPT_JOYSTICKRIGHT_RIGHT : IPT_JOYSTICKLEFT_DOWN, SEQ_TYPE_STANDARD, downseq); + + // put previous/next group on the last of the third or fourth axis + input_item_id const groupitem = axisactual[(std::min)(axiscount, 4) - 1]; + assignments.emplace_back( + IPT_UI_PREV_GROUP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, groupitem))); + assignments.emplace_back( + IPT_UI_NEXT_GROUP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, groupitem))); + } + if (4 <= axiscount) + { + // use this for twin sticks + input_seq const upseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisactual[3])); + input_seq const downseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisactual[3])); + assignments.emplace_back(IPT_JOYSTICKRIGHT_UP, SEQ_TYPE_STANDARD, upseq); + assignments.emplace_back(IPT_JOYSTICKRIGHT_DOWN, SEQ_TYPE_STANDARD, downseq); } + + // set default assignments + device.set_default_assignments(std::move(assignments)); } ~sdl_joystick_device() @@ -1132,9 +1280,13 @@ public: virtual void configure(input_device &device) override { + input_device::assignment_vector assignments; char const *const *axisnames = CONTROLLER_AXIS_XBOX; char const *const *buttonnames = CONTROLLER_BUTTON_XBOX360; bool digitaltriggers = false; +#if SDL_VERSION_ATLEAST(2, 0, 14) + bool avoidpaddles = false; +#endif #if SDL_VERSION_ATLEAST(2, 0, 12) auto const ctrltype = SDL_GameControllerGetType(m_ctrldevice); switch (ctrltype) @@ -1197,6 +1349,7 @@ public: axisnames = CONTROLLER_AXIS_SWITCH; buttonnames = CONTROLLER_BUTTON_SWITCH; digitaltriggers = true; + avoidpaddles = true; break; #endif default: // default to Xbox 360 names @@ -1205,6 +1358,23 @@ public: } #endif + // keep track of item numbers as we add controls + std::pair<input_item_id, input_item_id> axisitems[SDL_CONTROLLER_AXIS_MAX]; + input_item_id buttonitems[SDL_CONTROLLER_BUTTON_MAX]; + std::tuple<input_item_id, SDL_GameControllerButton, SDL_GameControllerAxis> numberedbuttons[16]; + std::fill( + std::begin(axisitems), + std::end(axisitems), + std::make_pair(ITEM_ID_INVALID, ITEM_ID_INVALID)); + std::fill( + std::begin(buttonitems), + std::end(buttonitems), + ITEM_ID_INVALID); + std::fill( + std::begin(numberedbuttons), + std::end(numberedbuttons), + std::make_tuple(ITEM_ID_INVALID, SDL_CONTROLLER_BUTTON_INVALID, SDL_CONTROLLER_AXIS_INVALID)); + // add axes std::tuple<SDL_GameControllerAxis, input_item_id, bool> const axes[]{ { SDL_CONTROLLER_AXIS_LEFTX, ITEM_ID_XAXIS, false }, @@ -1237,7 +1407,7 @@ public: } if (avail) { - device.add_item( + axisitems[axis].first = device.add_item( axisnames[axis], std::string_view(), item, @@ -1247,33 +1417,35 @@ public: } // add automatically numbered buttons - std::tuple<SDL_GameControllerButton, SDL_GameControllerAxis> const generalbuttons[]{ - { SDL_CONTROLLER_BUTTON_A, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_B, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_X, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_Y, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_LEFTSHOULDER, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_INVALID, SDL_CONTROLLER_AXIS_TRIGGERLEFT }, - { SDL_CONTROLLER_BUTTON_INVALID, SDL_CONTROLLER_AXIS_TRIGGERRIGHT }, - { SDL_CONTROLLER_BUTTON_LEFTSTICK, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_RIGHTSTICK, SDL_CONTROLLER_AXIS_INVALID }, + std::tuple<SDL_GameControllerButton, SDL_GameControllerAxis, bool> const generalbuttons[]{ + { SDL_CONTROLLER_BUTTON_A, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_B, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_X, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_Y, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_LEFTSHOULDER, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_INVALID, SDL_CONTROLLER_AXIS_TRIGGERLEFT, true }, + { SDL_CONTROLLER_BUTTON_INVALID, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, true }, + { SDL_CONTROLLER_BUTTON_LEFTSTICK, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_RIGHTSTICK, SDL_CONTROLLER_AXIS_INVALID, true }, #if SDL_VERSION_ATLEAST(2, 0, 14) - { SDL_CONTROLLER_BUTTON_PADDLE1, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_PADDLE2, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_PADDLE3, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_PADDLE4, SDL_CONTROLLER_AXIS_INVALID }, + { SDL_CONTROLLER_BUTTON_PADDLE1, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_PADDLE2, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_PADDLE3, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_PADDLE4, SDL_CONTROLLER_AXIS_INVALID, true }, #endif - { SDL_CONTROLLER_BUTTON_GUIDE, SDL_CONTROLLER_AXIS_INVALID }, + { SDL_CONTROLLER_BUTTON_GUIDE, SDL_CONTROLLER_AXIS_INVALID, false }, #if SDL_VERSION_ATLEAST(2, 0, 14) - { SDL_CONTROLLER_BUTTON_MISC1, SDL_CONTROLLER_AXIS_INVALID }, - { SDL_CONTROLLER_BUTTON_TOUCHPAD, SDL_CONTROLLER_AXIS_INVALID }, + { SDL_CONTROLLER_BUTTON_MISC1, SDL_CONTROLLER_AXIS_INVALID, false }, + { SDL_CONTROLLER_BUTTON_TOUCHPAD, SDL_CONTROLLER_AXIS_INVALID, false }, #endif }; input_item_id button_item = ITEM_ID_BUTTON1; - for (auto [button, axis] : generalbuttons) + unsigned buttoncount = 0; + for (auto [button, axis, field] : generalbuttons) { bool avail = true; + input_item_id actual = ITEM_ID_INVALID; if (SDL_CONTROLLER_BUTTON_INVALID != button) { #if SDL_VERSION_ATLEAST(2, 0, 14) @@ -1293,12 +1465,14 @@ public: } if (avail) { - device.add_item( + actual = buttonitems[button] = device.add_item( buttonnames[button], std::string_view(), button_item++, generic_button_get_state<s32>, &m_controller.buttons[button]); + if (field && (std::size(numberedbuttons) > buttoncount)) + std::get<1>(numberedbuttons[buttoncount]) = button; } } else @@ -1322,7 +1496,7 @@ public: } if (avail) { - device.add_item( + actual = axisitems[axis].second = device.add_item( axisnames[axis], std::string_view(), button_item++, @@ -1332,8 +1506,17 @@ public: return (*reinterpret_cast<s32 const *>(item_internal) <= -16'384) ? 1 : 0; }, &m_controller.axes[axis]); + if (field && (std::size(numberedbuttons) > buttoncount)) + std::get<2>(numberedbuttons[buttoncount]) = axis; } } + + // add default button assignments + if (field && avail && (std::size(numberedbuttons) > buttoncount)) + { + std::get<0>(numberedbuttons[buttoncount]) = actual; + add_button_assignment(assignments, ioport_type(IPT_BUTTON1 + buttoncount++), { actual }); + } } // add buttons with fixed item IDs @@ -1364,7 +1547,7 @@ public: } if (avail) { - device.add_item( + buttonitems[button] = device.add_item( buttonnames[button], std::string_view(), item, @@ -1372,6 +1555,310 @@ public: &m_controller.buttons[button]); } } + + // try to get a "complete" joystick for primary movement controls + input_item_id diraxis[2][2]; + choose_primary_stick( + diraxis, + axisitems[SDL_CONTROLLER_AXIS_LEFTX].first, + axisitems[SDL_CONTROLLER_AXIS_LEFTY].first, + axisitems[SDL_CONTROLLER_AXIS_RIGHTX].first, + axisitems[SDL_CONTROLLER_AXIS_RIGHTY].first); + + // now set up controls using the primary joystick + add_directional_assignments( + assignments, + diraxis[0][0], + diraxis[0][1], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_LEFT], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_RIGHT], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_UP], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_DOWN]); + + // assign a secondary stick axis to joystick Z if available + bool const zaxis = add_assignment( + assignments, + IPT_AD_STICK_Z, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_NONE, + { diraxis[1][1], diraxis[1][0] }); + if (!zaxis) + { + // if both triggers are present, combine them, or failing that, fall back to a pair of buttons + if ((ITEM_ID_INVALID != axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].first) && (ITEM_ID_INVALID != axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].first)) + { + assignments.emplace_back( + IPT_AD_STICK_Z, + SEQ_TYPE_STANDARD, + input_seq( + make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].first), + make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_REVERSE, axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].first))); + } + else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, buttonitems[SDL_CONTROLLER_BUTTON_LEFTSHOULDER], buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER])) + { + // took shoulder buttons + } + else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].second, axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].second)) + { + // took trigger buttons + } +#if SDL_VERSION_ATLEAST(2, 0, 14) + else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE1], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE2])) + { + // took P1/P2 + } + else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE3], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE4])) + { + // took P3/P4 + } +#endif + } + + // prefer trigger axes for pedals, otherwise take half axes and buttons + unsigned pedalbutton = 0; + if (!add_assignment(assignments, IPT_PEDAL, SEQ_TYPE_STANDARD, ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, { axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].first })) + { + add_assignment( + assignments, + IPT_PEDAL, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_NEG, + { diraxis[1][1], diraxis[0][1] }); + bool const incbutton = add_assignment( + assignments, + IPT_PEDAL, + SEQ_TYPE_INCREMENT, + ITEM_CLASS_SWITCH, + ITEM_MODIFIER_NONE, + { axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].second, buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] }); + if (!incbutton) + { + if (add_assignment(assignments, IPT_PEDAL, SEQ_TYPE_INCREMENT, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, { std::get<0>(numberedbuttons[pedalbutton]) })) + ++pedalbutton; + } + } + if (!add_assignment(assignments, IPT_PEDAL2, SEQ_TYPE_STANDARD, ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, { axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].first })) + { + add_assignment( + assignments, + IPT_PEDAL2, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_POS, + { diraxis[1][1], diraxis[0][1] }); + bool const incbutton = add_assignment( + assignments, + IPT_PEDAL2, + SEQ_TYPE_INCREMENT, + ITEM_CLASS_SWITCH, + ITEM_MODIFIER_NONE, + { axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].second, buttonitems[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] }); + if (!incbutton) + { + if (add_assignment(assignments, IPT_PEDAL2, SEQ_TYPE_INCREMENT, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, { std::get<0>(numberedbuttons[pedalbutton]) })) + ++pedalbutton; + } + } + add_assignment(assignments, IPT_PEDAL3, SEQ_TYPE_INCREMENT, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, { std::get<0>(numberedbuttons[pedalbutton]) }); + + // potentially use thumb sticks and/or D-pad and A/B/X/Y diamond for twin sticks + add_twin_stick_assignments( + assignments, + axisitems[SDL_CONTROLLER_AXIS_LEFTX].first, + axisitems[SDL_CONTROLLER_AXIS_LEFTY].first, + axisitems[SDL_CONTROLLER_AXIS_RIGHTX].first, + axisitems[SDL_CONTROLLER_AXIS_RIGHTY].first, + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_LEFT], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_RIGHT], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_UP], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_DOWN], + buttonitems[SDL_CONTROLLER_BUTTON_X], + buttonitems[SDL_CONTROLLER_BUTTON_B], + buttonitems[SDL_CONTROLLER_BUTTON_Y], + buttonitems[SDL_CONTROLLER_BUTTON_A]); + + // add assignments for buttons with fixed functions + add_button_assignment(assignments, IPT_SELECT, { buttonitems[SDL_CONTROLLER_BUTTON_BACK] }); + add_button_assignment(assignments, IPT_START, { buttonitems[SDL_CONTROLLER_BUTTON_START] }); + add_button_assignment(assignments, IPT_UI_MENU, { buttonitems[SDL_CONTROLLER_BUTTON_GUIDE] }); + + // the first button is always UI select + if (add_button_assignment(assignments, IPT_UI_SELECT, { std::get<0>(numberedbuttons[0]) })) + { + if (SDL_CONTROLLER_BUTTON_INVALID != std::get<1>(numberedbuttons[0])) + buttonitems[std::get<1>(numberedbuttons[0])] = ITEM_ID_INVALID; + if (SDL_CONTROLLER_AXIS_INVALID != std::get<2>(numberedbuttons[0])) + axisitems[std::get<2>(numberedbuttons[0])].second = ITEM_ID_INVALID; + } + + // try to get a matching pair of buttons for previous/next group + if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].second, axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].second)) + { + // took digital triggers + } +#if SDL_VERSION_ATLEAST(2, 0, 14) + else if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE1], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE2])) + { + // took upper paddles + } +#endif + else if (consume_trigger_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].first, axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].first)) + { + // took analog triggers + } +#if SDL_VERSION_ATLEAST(2, 0, 14) + else if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE3], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE4])) + { + // took lower paddles + } +#endif + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, diraxis[1][1])) + { + // took secondary Y + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, diraxis[1][0])) + { + // took secondary X + } + + // try to get a matching pair of buttons for page up/down +#if SDL_VERSION_ATLEAST(2, 0, 14) + if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE1], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE2])) + { + // took upper paddles + } + else if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE3], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE4])) + { + // took lower paddles + } + else +#endif + if (consume_trigger_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].first, axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].first)) + { + // took analog triggers + } + else if (consume_axis_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, diraxis[1][1])) + { + // took secondary Y + } + + // try to assign X button to UI clear + if (add_button_assignment(assignments, IPT_UI_CLEAR, { buttonitems[SDL_CONTROLLER_BUTTON_X] })) + { + buttonitems[SDL_CONTROLLER_BUTTON_X] = ITEM_ID_INVALID; + } + else + { + // otherwise try to find an unassigned button + for (auto [item, button, axis] : numberedbuttons) + { + if ((SDL_CONTROLLER_BUTTON_INVALID != button) && (ITEM_ID_INVALID != buttonitems[button])) + { + add_button_assignment(assignments, IPT_UI_CLEAR, { item }); + buttonitems[button] = ITEM_ID_INVALID; + break; + } + else if ((SDL_CONTROLLER_AXIS_INVALID != axis) && (ITEM_ID_INVALID != axisitems[axis].second)) + { + add_button_assignment(assignments, IPT_UI_CLEAR, { item }); + axisitems[axis].second = ITEM_ID_INVALID; + break; + } + } + } + + // try to assign B button to UI back + if (add_button_assignment(assignments, IPT_UI_BACK, { buttonitems[SDL_CONTROLLER_BUTTON_B] })) + { + buttonitems[SDL_CONTROLLER_BUTTON_X] = ITEM_ID_INVALID; + } + else + { + // otherwise try to find an unassigned button + for (auto [item, button, axis] : numberedbuttons) + { + if ((SDL_CONTROLLER_BUTTON_INVALID != button) && (ITEM_ID_INVALID != buttonitems[button])) + { + add_button_assignment(assignments, IPT_UI_CLEAR, { item }); + buttonitems[button] = ITEM_ID_INVALID; + break; + } + else if ((SDL_CONTROLLER_AXIS_INVALID != axis) && (ITEM_ID_INVALID != axisitems[axis].second)) + { + add_button_assignment(assignments, IPT_UI_CLEAR, { item }); + axisitems[axis].second = ITEM_ID_INVALID; + break; + } + } + } + + // try to assign Y button to UI help + if (add_button_assignment(assignments, IPT_UI_HELP, { buttonitems[SDL_CONTROLLER_BUTTON_Y] })) + { + buttonitems[SDL_CONTROLLER_BUTTON_Y] = ITEM_ID_INVALID; + } + else + { + // otherwise try to find an unassigned button + for (auto [item, button, axis] : numberedbuttons) + { + if ((SDL_CONTROLLER_BUTTON_INVALID != button) && (ITEM_ID_INVALID != buttonitems[button])) + { + add_button_assignment(assignments, IPT_UI_HELP, { item }); + buttonitems[button] = ITEM_ID_INVALID; + break; + } + else if ((SDL_CONTROLLER_AXIS_INVALID != axis) && (ITEM_ID_INVALID != axisitems[axis].second)) + { + add_button_assignment(assignments, IPT_UI_HELP, { item }); + axisitems[axis].second = ITEM_ID_INVALID; + break; + } + } + } + + // put focus previous/next on the shoulder buttons if available - this can be overloaded with zoom + if (add_button_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, buttonitems[SDL_CONTROLLER_BUTTON_LEFTSHOULDER], buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER])) + { + // took shoulder buttons + } + else if (add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, diraxis[1][0])) + { + // took secondary X + } + else if (add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, diraxis[1][1])) + { + // took secondary Y + } + + // put zoom on the secondary stick if available, or fall back to shoulder buttons + if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, diraxis[1][0])) + { + // took secondary X + if (axisitems[SDL_CONTROLLER_AXIS_LEFTX].first == diraxis[1][0]) + add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_CONTROLLER_BUTTON_LEFTSTICK] }); + else if (axisitems[SDL_CONTROLLER_AXIS_RIGHTX].first == diraxis[1][0]) + add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSTICK] }); + diraxis[1][0] = ITEM_ID_INVALID; + } + else if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_IN, IPT_UI_ZOOM_OUT, diraxis[1][1])) + { + // took secondary Y + if (axisitems[SDL_CONTROLLER_AXIS_LEFTY].first == diraxis[1][1]) + add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_CONTROLLER_BUTTON_LEFTSTICK] }); + else if (axisitems[SDL_CONTROLLER_AXIS_RIGHTY].first == diraxis[1][1]) + add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSTICK] }); + diraxis[1][1] = ITEM_ID_INVALID; + } + else if (consume_button_pair(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, buttonitems[SDL_CONTROLLER_BUTTON_LEFTSHOULDER], buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER])) + { + // took shoulder buttons + } + + // set default assignments + device.set_default_assignments(std::move(assignments)); } virtual void reset() override diff --git a/src/osd/modules/input/input_win32.cpp b/src/osd/modules/input/input_win32.cpp index 820650c4b1d..5c584e33d93 100644 --- a/src/osd/modules/input/input_win32.cpp +++ b/src/osd/modules/input/input_win32.cpp @@ -18,7 +18,7 @@ #include "window.h" // emu -#include "emu.h" +#include "inpttype.h" #include "strconv.h" diff --git a/src/osd/modules/input/input_windows.cpp b/src/osd/modules/input/input_windows.cpp index 5e42e58e575..90101351a95 100644 --- a/src/osd/modules/input/input_windows.cpp +++ b/src/osd/modules/input/input_windows.cpp @@ -85,7 +85,7 @@ void windows_osd_interface::customize_input_type_list(std::vector<input_type_ent { // disable the config menu if the ALT key is down // (allows ALT-TAB to switch between windows apps) - case IPT_UI_CONFIGURE: + case IPT_UI_MENU: entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_TAB, input_seq::not_code, KEYCODE_LALT, input_seq::not_code, KEYCODE_RALT); break; // configurable UI mode switch diff --git a/src/osd/modules/input/input_x11.cpp b/src/osd/modules/input/input_x11.cpp index b1d21d56c86..5230dfd7164 100644 --- a/src/osd/modules/input/input_x11.cpp +++ b/src/osd/modules/input/input_x11.cpp @@ -19,7 +19,7 @@ #include "sdl/osdsdl.h" // MAME headers -#include "emu.h" +#include "inpttype.h" // standard SDL header #include <SDL2/SDL.h> diff --git a/src/osd/modules/input/input_xinput.cpp b/src/osd/modules/input/input_xinput.cpp index e8dc0af5e19..783dc720994 100644 --- a/src/osd/modules/input/input_xinput.cpp +++ b/src/osd/modules/input/input_xinput.cpp @@ -144,7 +144,7 @@ A X B LB -Rock band keyboards use axes as bit fields: +Rock Band keyboards use axes as bit fields: LT 7 C LT 6 C# @@ -178,13 +178,16 @@ LSX 15 C #if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) -// emu -#include "emu.h" // put this here before windows.h defines interface as a macro - #include "input_xinput.h" +#include "assignmenthelper.h" + +#include "interface/inputseq.h" #include "modules/lib/osdobj_common.h" +// emu +#include "inpttype.h" + // lib/util #include "util/coretmpl.h" @@ -192,6 +195,7 @@ LSX 15 C #include <algorithm> #include <cstdint> +#include <iterator> #include <string> #include <tuple> #include <utility> @@ -225,14 +229,6 @@ char const *const AXIS_NAMES_WHEEL[]{ "Brake", "Accelerator" }; -char const *const AXIS_NAMES_FLIGHT_STICK[]{ - "Joystick X", - "Joystick Y", - nullptr, - nullptr, - "Rudder", - "Throttle" }; - char const *const AXIS_NAMES_GUITAR[]{ "LSX", "LSY", @@ -249,14 +245,6 @@ input_item_id const AXIS_IDS_GAMEPAD[]{ ITEM_ID_SLIDER1, ITEM_ID_SLIDER2 }; -input_item_id const AXIS_IDS_FLIGHT_STICK[]{ - ITEM_ID_XAXIS, - ITEM_ID_YAXIS, - ITEM_ID_INVALID, - ITEM_ID_INVALID, - ITEM_ID_RZAXIS, - ITEM_ID_ZAXIS }; - char const *const HAT_NAMES_GAMEPAD[]{ "D-pad Up", "D-pad Down", @@ -295,6 +283,16 @@ char const *const BUTTON_NAMES_GAMEPAD[]{ "LSB", "RSB" }; +char const *const BUTTON_NAMES_FLIGHT_STICK[]{ + "A", + "B", + "X", + "Y", + "LB", + "RB", + "LSB", + "RSB" }; + char const *const BUTTON_NAMES_GUITAR[]{ "Fret 1", "Fret 2", @@ -331,7 +329,7 @@ char const *const BUTTON_NAMES_KEYBOARD[]{ // base class for XInput controller handlers //============================================================ -class xinput_device_base : public device_info +class xinput_device_base : public device_info, protected joystick_assignment_helper { protected: xinput_device_base( @@ -366,6 +364,28 @@ protected: bool is_reset() const { return m_reset; } void set_reset() { m_reset = true; } +protected: + template <unsigned M, unsigned N> + static bool assign_ui_button( + input_device::assignment_vector &assignments, + ioport_type type, + unsigned preferred, + input_item_id (&switch_ids)[M], + unsigned const (&numbered_buttons)[N], + unsigned button_count); + + template <unsigned M, unsigned N> + static void assign_ui_actions( + input_device::assignment_vector &assignments, + unsigned preferred_back, + unsigned preferred_clear, + unsigned preferred_help, + unsigned start, + unsigned back, + input_item_id (&switch_ids)[M], + unsigned const (&numbered_buttons)[N], + unsigned button_count); + private: bool probe_extended_type(); @@ -544,6 +564,96 @@ bool xinput_device_base::read_state() } +template <unsigned M, unsigned N> +bool xinput_device_base::assign_ui_button( + input_device::assignment_vector &assignments, + ioport_type type, + unsigned preferred, + input_item_id (&switch_ids)[M], + unsigned const (&numbered_buttons)[N], + unsigned button_count) +{ + assert(N >= button_count); + + // use preferred button if available + if (add_button_assignment(assignments, type, { switch_ids[preferred] })) + { + switch_ids[preferred] = ITEM_ID_INVALID; + return true; + } + + // otherwise find next available button + for (unsigned i = 0; button_count > i; ++i) + { + if (add_button_assignment(assignments, type, { switch_ids[numbered_buttons[i]] })) + { + switch_ids[numbered_buttons[i]] = ITEM_ID_INVALID; + return true; + } + } + + // didn't find a suitable button + return false; +} + + +template <unsigned M, unsigned N> +void xinput_device_base::assign_ui_actions( + input_device::assignment_vector &assignments, + unsigned preferred_back, + unsigned preferred_clear, + unsigned preferred_help, + unsigned start, + unsigned back, + input_item_id (&switch_ids)[M], + unsigned const (&numbered_buttons)[N], + unsigned button_count) +{ + // the first button is always UI select if present, or we can fall back to start + if (1U <= button_count) + { + add_button_assignment(assignments, IPT_UI_SELECT, { switch_ids[numbered_buttons[0]] }); + switch_ids[numbered_buttons[0]] = ITEM_ID_INVALID; + } + else if (add_button_assignment(assignments, IPT_UI_SELECT, { switch_ids[start] })) + { + switch_ids[start] = ITEM_ID_INVALID; + } + + // UI clear is usually X + assign_ui_button( + assignments, + IPT_UI_CLEAR, + preferred_clear, + switch_ids, + numbered_buttons, + button_count); + + // UI back can fall back from B to the back button + bool const assigned_back = assign_ui_button( + assignments, + IPT_UI_BACK, + preferred_back, + switch_ids, + numbered_buttons, + button_count); + if (!assigned_back) + { + if (add_button_assignment(assignments, IPT_UI_BACK, { switch_ids[back] })) + switch_ids[back] = ITEM_ID_INVALID; + } + + // help takes Y if present + assign_ui_button( + assignments, + IPT_UI_HELP, + preferred_help, + switch_ids, + numbered_buttons, + button_count); +} + + bool xinput_device_base::probe_extended_type() { switch (m_capabilities.Gamepad.sThumbLX) @@ -645,11 +755,6 @@ private: SWITCH_DPAD_LEFT, SWITCH_DPAD_RIGHT, - SWITCH_HAT_UP, // for flight stick with POV hat as right stick - SWITCH_HAT_DOWN, - SWITCH_HAT_LEFT, - SWITCH_HAT_RIGHT, - SWITCH_LT, // for arcade stick/pad with LT/RT buttons SWITCH_RT, @@ -666,12 +771,21 @@ private: AXIS_RSX, AXIS_RSY, - AXIS_RUDDER, // LT/RT mapped differently for flight sticks - AXIS_THROTTLE, - AXIS_TOTAL }; + static bool assign_pedal( + input_device::assignment_vector &assignments, + bool fallback_shoulder, + ioport_type type, + input_item_id preferred_axis, + input_item_id fallback_axis1, + input_item_id fallback_axis2, + input_item_modifier fallback_axis_modifier, + input_item_id trigger_button, + input_item_id shoulder_button, + input_item_id numbered_button); + u8 m_switches[SWITCH_TOTAL]; s32 m_axes[AXIS_TOTAL]; }; @@ -714,16 +828,6 @@ void xinput_joystick_device::poll() // translate LT/RT switches for arcade sticks/pads m_switches[SWITCH_LT] = (0x80 <= trigger_left()) ? 0xff : 0x00; m_switches[SWITCH_RT] = (0x80 <= trigger_right()) ? 0xff : 0x00; - - // translate POV hat for flight sticks - m_switches[SWITCH_HAT_UP] = (16'384 <= thumb_right_y()) ? 0xff : 0x00; - m_switches[SWITCH_HAT_DOWN] = (-16'384 >= thumb_right_y()) ? 0xff : 0x00; - m_switches[SWITCH_HAT_LEFT] = (-16'384 >= thumb_right_x()) ? 0xff : 0x00; - m_switches[SWITCH_HAT_RIGHT] = (16'384 <= thumb_right_x()) ? 0xff : 0x00; - - // translate rudder and throttle for flight sticks - m_axes[AXIS_RUDDER] = normalize_absolute_axis(trigger_left(), 0, 255); - m_axes[AXIS_THROTTLE] = normalize_absolute_axis(trigger_right(), 0, 255); } @@ -740,11 +844,11 @@ void xinput_joystick_device::configure(input_device &device) // TODO: proper support for dance mat controllers // default characteristics for a gamepad + bool button_diamond = true; + bool pedal_fallback_shoulder = true; bool lt_rt_button = false; - bool lt_rt_fullaxis = false; - bool rstick_hat = false; char const *const *axis_names = AXIS_NAMES_GAMEPAD; - input_item_id const *axis_ids = AXIS_IDS_GAMEPAD; + input_item_id const *preferred_axis_ids = AXIS_IDS_GAMEPAD; char const *const *hat_names = HAT_NAMES_GAMEPAD; char const *const *button_names = BUTTON_NAMES_GAMEPAD; @@ -755,47 +859,53 @@ void xinput_joystick_device::configure(input_device &device) switch (device_subtype()) { case XINPUT_DEVSUBTYPE_WHEEL: + pedal_fallback_shoulder = false; axis_names = AXIS_NAMES_WHEEL; break; case XINPUT_DEVSUBTYPE_ARCADE_STICK: + button_diamond = false; lt_rt_button = true; hat_names = HAT_NAMES_ARCADE_STICK; break; - case 0x04: // XINPUT_DEVSUBTYPE_FLIGHT_STICK: work around MinGW header issues - lt_rt_fullaxis = true; - rstick_hat = true; - axis_names = AXIS_NAMES_FLIGHT_STICK; - axis_ids = AXIS_IDS_FLIGHT_STICK; - break; case XINPUT_DEVSUBTYPE_DANCE_PAD: // TODO: proper support + button_diamond = false; break; case XINPUT_DEVSUBTYPE_ARCADE_PAD: + button_diamond = false; lt_rt_button = true; break; } break; } + // track item IDs for setting up default assignments + input_device::assignment_vector assignments; + input_item_id axis_ids[AXIS_TOTAL]; + input_item_id switch_ids[SWITCH_TOTAL]; + std::fill(std::begin(switch_ids), std::end(switch_ids), ITEM_ID_INVALID); + // add bidirectional axes bool const axis_caps[]{ has_thumb_left_x(), has_thumb_left_y(), - !rstick_hat && has_thumb_right_x(), - !rstick_hat && has_thumb_right_y(), - lt_rt_fullaxis && has_trigger_left(), - lt_rt_fullaxis && has_trigger_right() }; + has_thumb_right_x(), + has_thumb_right_y() }; for (unsigned i = 0; std::size(axis_caps) > i; ++i) { if (axis_caps[i]) { - device.add_item( + axis_ids[AXIS_LSX + i] = device.add_item( axis_names[i], std::string_view(), - axis_ids[i], + preferred_axis_ids[i], generic_axis_get_state<s32>, &m_axes[AXIS_LSX + i]); } + else + { + axis_ids[AXIS_LSX + i] = ITEM_ID_INVALID; + } } // add hats @@ -803,16 +913,12 @@ void xinput_joystick_device::configure(input_device &device) has_button(XINPUT_GAMEPAD_DPAD_UP), has_button(XINPUT_GAMEPAD_DPAD_DOWN), has_button(XINPUT_GAMEPAD_DPAD_LEFT), - has_button(XINPUT_GAMEPAD_DPAD_RIGHT), - rstick_hat && has_thumb_right_x(), - rstick_hat && has_thumb_right_x(), - rstick_hat && has_thumb_right_y(), - rstick_hat && has_thumb_right_y() }; - for (unsigned i = 0; (SWITCH_HAT_RIGHT - SWITCH_DPAD_UP) >= i; ++i) + has_button(XINPUT_GAMEPAD_DPAD_RIGHT) }; + for (unsigned i = 0; (SWITCH_DPAD_RIGHT - SWITCH_DPAD_UP) >= i; ++i) { if (hat_caps[i]) { - device.add_item( + switch_ids[SWITCH_DPAD_UP + i] = device.add_item( hat_names[i], std::string_view(), input_item_id(ITEM_ID_HAT1UP + i), // matches up/down/left/right order @@ -834,56 +940,764 @@ void xinput_joystick_device::configure(input_device &device) { SWITCH_LSB, has_button(XINPUT_GAMEPAD_LEFT_THUMB) }, { SWITCH_RSB, has_button(XINPUT_GAMEPAD_RIGHT_THUMB) } }; input_item_id button_id = ITEM_ID_BUTTON1; + unsigned button_count = 0; + unsigned numbered_buttons[SWITCH_RSB - SWITCH_A + 1]; for (unsigned i = 0; std::size(button_caps) > i; ++i) { auto const [offset, supported] = button_caps[i]; if (supported) { - device.add_item( + switch_ids[offset] = device.add_item( button_names[i], std::string_view(), button_id++, generic_button_get_state<u8>, &m_switches[offset]); + numbered_buttons[button_count] = offset; + + // use these for automatically numbered buttons + assignments.emplace_back( + ioport_type(IPT_BUTTON1 + button_count++), + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[offset]))); } } // add start/back if (has_button(XINPUT_GAMEPAD_START)) { - device.add_item( + switch_ids[SWITCH_START] = device.add_item( "Start", std::string_view(), ITEM_ID_START, generic_button_get_state<u8>, &m_switches[SWITCH_START]); + add_button_assignment(assignments, IPT_START, { switch_ids[SWITCH_START] }); } if (has_button(XINPUT_GAMEPAD_BACK)) { - device.add_item( + switch_ids[SWITCH_BACK] = device.add_item( "Back", std::string_view(), ITEM_ID_SELECT, generic_button_get_state<u8>, &m_switches[SWITCH_BACK]); + add_button_assignment(assignments, IPT_SELECT, { switch_ids[SWITCH_BACK] }); } // add triggers/pedals - if (!lt_rt_button && !lt_rt_fullaxis) + if (!lt_rt_button) { for (unsigned i = 0; (AXIS_RT - AXIS_LT) >= i; ++i) { if (i ? has_trigger_right() : has_trigger_left()) { - device.add_item( - axis_names[4 + i], + axis_ids[AXIS_LT + i] = device.add_item( + axis_names[std::size(axis_caps) + i], std::string_view(), - axis_ids[4 + i], + preferred_axis_ids[std::size(axis_caps) + i], generic_axis_get_state<s32>, &m_axes[AXIS_LT + i]); } + else + { + axis_ids[AXIS_LT + i] = ITEM_ID_INVALID; + } + } + } + + // try to get a "complete" joystick for primary movement controls + input_item_id directional_axes[2][2]; + choose_primary_stick( + directional_axes, + axis_ids[AXIS_LSX], + axis_ids[AXIS_LSY], + axis_ids[AXIS_RSX], + axis_ids[AXIS_RSY]); + + // now set up controls using the primary joystick + add_directional_assignments( + assignments, + directional_axes[0][0], + directional_axes[0][1], + switch_ids[SWITCH_DPAD_LEFT], + switch_ids[SWITCH_DPAD_RIGHT], + switch_ids[SWITCH_DPAD_UP], + switch_ids[SWITCH_DPAD_DOWN]); + + // assign a secondary stick axis to joystick Z if available + bool const stick_z = add_assignment( + assignments, + IPT_AD_STICK_Z, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_NONE, + { directional_axes[1][1], directional_axes[1][0] }); + if (!stick_z) + { + // if both triggers are present, combine them, or failing that, fall back to a pair of buttons + if ((ITEM_ID_INVALID != axis_ids[AXIS_LT]) && (ITEM_ID_INVALID != axis_ids[AXIS_RT])) + { + assignments.emplace_back( + IPT_AD_STICK_Z, + SEQ_TYPE_STANDARD, + input_seq( + make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, axis_ids[AXIS_LT]), + make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_REVERSE, axis_ids[AXIS_RT]))); + } + else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, switch_ids[SWITCH_LB], switch_ids[SWITCH_RB])) + { + // took shoulder buttons + } + else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, switch_ids[SWITCH_LT], switch_ids[SWITCH_RT])) + { + // took trigger buttons + } + } + + // prefer trigger axes for pedals, otherwise take half axes and buttons + unsigned pedal_button = 0; + bool const pedal1_numbered_button = assign_pedal( + assignments, + pedal_fallback_shoulder, + IPT_PEDAL, + axis_ids[AXIS_RT], + directional_axes[1][1], + directional_axes[0][1], + ITEM_MODIFIER_NEG, + switch_ids[SWITCH_RT], + switch_ids[SWITCH_RB], + (pedal_button < button_count) + ? switch_ids[numbered_buttons[pedal_button]] + : ITEM_ID_INVALID); + if (pedal1_numbered_button) + ++pedal_button; + bool const pedal2_numbered_button = assign_pedal( + assignments, + pedal_fallback_shoulder, + IPT_PEDAL2, + axis_ids[AXIS_LT], + directional_axes[1][1], + directional_axes[0][1], + ITEM_MODIFIER_POS, + switch_ids[SWITCH_LT], + switch_ids[SWITCH_LB], + (pedal_button < button_count) + ? switch_ids[numbered_buttons[pedal_button]] + : ITEM_ID_INVALID); + if (pedal2_numbered_button) + ++pedal_button; + if (pedal_button < button_count) + { + input_item_id const pedal_button_id = switch_ids[numbered_buttons[pedal_button]]; + assignments.emplace_back( + IPT_PEDAL3, + SEQ_TYPE_INCREMENT, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, pedal_button_id))); + } + + // potentially use thumb sticks and/or D-pad and A/B/X/Y diamond for twin sticks + add_twin_stick_assignments( + assignments, + axis_ids[AXIS_LSX], + axis_ids[AXIS_LSY], + axis_ids[AXIS_RSX], + axis_ids[AXIS_RSY], + button_diamond ? switch_ids[SWITCH_DPAD_LEFT] : ITEM_ID_INVALID, + button_diamond ? switch_ids[SWITCH_DPAD_RIGHT] : ITEM_ID_INVALID, + button_diamond ? switch_ids[SWITCH_DPAD_UP] : ITEM_ID_INVALID, + button_diamond ? switch_ids[SWITCH_DPAD_DOWN] : ITEM_ID_INVALID, + button_diamond ? switch_ids[SWITCH_X] : ITEM_ID_INVALID, + button_diamond ? switch_ids[SWITCH_B] : ITEM_ID_INVALID, + button_diamond ? switch_ids[SWITCH_Y] : ITEM_ID_INVALID, + button_diamond ? switch_ids[SWITCH_A] : ITEM_ID_INVALID); + + // assign UI select/back/clear/help + assign_ui_actions( + assignments, + SWITCH_B, + SWITCH_X, + SWITCH_Y, + SWITCH_START, + SWITCH_BACK, + switch_ids, + numbered_buttons, + button_count); + + // try to get a matching pair of buttons for previous/next group + if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_LT], switch_ids[SWITCH_RT])) + { + // took digital triggers + } + else if (consume_trigger_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axis_ids[AXIS_LT], axis_ids[AXIS_RT])) + { + // took analog triggers + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, directional_axes[1][1])) + { + // took secondary Y + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, directional_axes[1][0])) + { + // took secondary X + } + + // try to assign secondary stick to page up/down + consume_axis_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, directional_axes[1][1]); + + // put focus previous/next on the shoulder buttons if available - this can be overloaded with zoom + if (add_button_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, switch_ids[SWITCH_LB], switch_ids[SWITCH_RB])) + { + // took shoulder buttons + } + else if (add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, directional_axes[1][0])) + { + // took secondary X + } + + // put zoom on the secondary stick if available, or fall back to shoulder buttons + if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, directional_axes[1][0])) + { + // took secondary X + if (axis_ids[AXIS_LSX] == directional_axes[1][0]) + add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { switch_ids[SWITCH_LSB] }); + else if (axis_ids[AXIS_RSX] == directional_axes[1][0]) + add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { switch_ids[SWITCH_RSB] }); + directional_axes[1][0] = ITEM_ID_INVALID; + } + else if (consume_button_pair(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, switch_ids[SWITCH_LB], switch_ids[SWITCH_RB])) + { + // took shoulder buttons + } + + // set default assignments + device.set_default_assignments(std::move(assignments)); +} + + +bool xinput_joystick_device::assign_pedal( + input_device::assignment_vector &assignments, + bool fallback_shoulder, + ioport_type type, + input_item_id preferred_axis, + input_item_id fallback_axis1, + input_item_id fallback_axis2, + input_item_modifier fallback_axis_modifier, + input_item_id trigger_button, + input_item_id shoulder_button, + input_item_id numbered_button) +{ + // first try the preferred trigger/pedal axis + if (ITEM_ID_INVALID != preferred_axis) + { + assignments.emplace_back( + type, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, preferred_axis))); + return false; + } + + // try adding half a joystick axis + add_assignment( + assignments, + type, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + fallback_axis_modifier, + { fallback_axis1, fallback_axis2 }); + + // try a trigger button + if (ITEM_ID_INVALID != trigger_button) + { + assignments.emplace_back( + type, + SEQ_TYPE_INCREMENT, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, trigger_button))); + return false; + } + + // try a shoulder button if appropriate + if (fallback_shoulder && (ITEM_ID_INVALID != shoulder_button)) + { + assignments.emplace_back( + type, + SEQ_TYPE_INCREMENT, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, shoulder_button))); + return false; + } + + // if no numbered button, nothing can be done + if (ITEM_ID_INVALID == numbered_button) + return false; + + // last resort + assignments.emplace_back( + type, + SEQ_TYPE_INCREMENT, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, numbered_button))); + return true; +} + + + +//============================================================ +// XInput flight stick handler +//============================================================ + +class xinput_flight_stick_device : public xinput_device_base +{ +public: + xinput_flight_stick_device( + std::string &&name, + std::string &&id, + input_module &module, + u32 player, + XINPUT_CAPABILITIES const &caps, + xinput_api_helper const &helper); + + virtual void poll() override; + virtual void reset() override; + virtual void configure(input_device &device) override; + +private: + static inline constexpr USHORT SWITCH_BITS[] = + { + XINPUT_GAMEPAD_A, + XINPUT_GAMEPAD_B, + XINPUT_GAMEPAD_X, + XINPUT_GAMEPAD_Y, + XINPUT_GAMEPAD_LEFT_SHOULDER, + XINPUT_GAMEPAD_RIGHT_SHOULDER, + XINPUT_GAMEPAD_LEFT_THUMB, + XINPUT_GAMEPAD_RIGHT_THUMB, + XINPUT_GAMEPAD_START, + XINPUT_GAMEPAD_BACK, + + XINPUT_GAMEPAD_DPAD_UP, + XINPUT_GAMEPAD_DPAD_DOWN, + XINPUT_GAMEPAD_DPAD_LEFT, + XINPUT_GAMEPAD_DPAD_RIGHT + }; + + enum + { + SWITCH_A, // button bits + SWITCH_B, + SWITCH_X, + SWITCH_Y, + SWITCH_LB, + SWITCH_RB, + SWITCH_LSB, + SWITCH_RSB, + SWITCH_START, + SWITCH_BACK, + + SWITCH_DPAD_UP, // D-pad bits + SWITCH_DPAD_DOWN, + SWITCH_DPAD_LEFT, + SWITCH_DPAD_RIGHT, + + SWITCH_HAT_UP, // for POV hat as right stick + SWITCH_HAT_DOWN, + SWITCH_HAT_LEFT, + SWITCH_HAT_RIGHT, + + SWITCH_TOTAL + }; + + enum + { + AXIS_RUDDER, // LT/RT mapped as bidirectional axes + AXIS_THROTTLE, + + AXIS_X, // full-precision axes + AXIS_Y, + + AXIS_TOTAL + }; + + u8 m_switches[SWITCH_TOTAL]; + s32 m_axes[AXIS_TOTAL]; +}; + + +xinput_flight_stick_device::xinput_flight_stick_device( + std::string &&name, + std::string &&id, + input_module &module, + u32 player, + XINPUT_CAPABILITIES const &caps, + xinput_api_helper const &helper) : + xinput_device_base(std::move(name), std::move(id), module, player, caps, helper) +{ + std::fill(std::begin(m_switches), std::end(m_switches), 0); + std::fill(std::begin(m_axes), std::end(m_axes), 0); +} + + +void xinput_flight_stick_device::poll() +{ + // poll the device first, and skip if nothing changed + if (!read_state()) + return; + + // translate button bits + for (unsigned i = 0; std::size(SWITCH_BITS) > i; ++i) + m_switches[SWITCH_A + i] = (buttons() & SWITCH_BITS[i]) ? 0xff : 0x00; + + // translate rudder and throttle + m_axes[AXIS_RUDDER] = normalize_absolute_axis(trigger_left(), 0, 255); + m_axes[AXIS_THROTTLE] = normalize_absolute_axis(trigger_right(), 0, 255); + + // translate full-precision axes - Y direction is opposite to what MAME uses + m_axes[AXIS_X] = normalize_absolute_axis(thumb_left_x(), XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + m_axes[AXIS_Y] = normalize_absolute_axis(-thumb_left_y(), XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + + // translate right stick as POV hat + m_switches[SWITCH_HAT_UP] = (16'384 <= thumb_right_y()) ? 0xff : 0x00; + m_switches[SWITCH_HAT_DOWN] = (-16'384 >= thumb_right_y()) ? 0xff : 0x00; + m_switches[SWITCH_HAT_LEFT] = (-16'384 >= thumb_right_x()) ? 0xff : 0x00; + m_switches[SWITCH_HAT_RIGHT] = (16'384 <= thumb_right_x()) ? 0xff : 0x00; +} + + +void xinput_flight_stick_device::reset() +{ + set_reset(); + std::fill(std::begin(m_switches), std::end(m_switches), 0); + std::fill(std::begin(m_axes), std::end(m_axes), 0); +} + + +void xinput_flight_stick_device::configure(input_device &device) +{ + // track item IDs for setting up default assignments + input_device::assignment_vector assignments; + input_item_id switch_ids[SWITCH_TOTAL]; + std::fill(std::begin(switch_ids), std::end(switch_ids), ITEM_ID_INVALID); + + // add bidirectional axes + std::tuple<input_item_id, char const *, bool> const axis_caps[]{ + { ITEM_ID_RZAXIS, "Rudder", has_trigger_left() }, + { ITEM_ID_ZAXIS, "Throttle", has_trigger_right() }, + { ITEM_ID_XAXIS, "Joystick X", has_thumb_left_x() }, + { ITEM_ID_YAXIS, "Joystick Y", has_thumb_left_y() } }; + input_item_id axis_ids[AXIS_TOTAL]; + for (unsigned i = 0; AXIS_TOTAL > i; ++i) + { + auto const [id, name, supported] = axis_caps[i]; + if (supported) + { + axis_ids[i] = device.add_item( + name, + std::string_view(), + id, + generic_axis_get_state<s32>, + &m_axes[i]); + } + else + { + axis_ids[i] = ITEM_ID_INVALID; + } + } + + // add hats + bool const hat_caps[]{ + has_button(XINPUT_GAMEPAD_DPAD_UP), + has_button(XINPUT_GAMEPAD_DPAD_DOWN), + has_button(XINPUT_GAMEPAD_DPAD_LEFT), + has_button(XINPUT_GAMEPAD_DPAD_RIGHT), + has_thumb_right_x(), + has_thumb_right_x(), + has_thumb_right_y(), + has_thumb_right_y() }; + for (unsigned i = 0; (SWITCH_HAT_RIGHT - SWITCH_DPAD_UP) >= i; ++i) + { + if (hat_caps[i]) + { + switch_ids[SWITCH_DPAD_UP + i] = device.add_item( + HAT_NAMES_GAMEPAD[i], + std::string_view(), + input_item_id(ITEM_ID_HAT1UP + i), // matches up/down/left/right order + generic_button_get_state<u8>, + &m_switches[SWITCH_DPAD_UP + i]); + } + } + + // add buttons + input_item_id button_id = ITEM_ID_BUTTON1; + unsigned button_count = 0; + unsigned numbered_buttons[SWITCH_RSB - SWITCH_A + 1]; + for (unsigned i = 0; (SWITCH_RSB - SWITCH_A) >= i; ++i) + { + if (has_button(SWITCH_BITS[i])) + { + switch_ids[SWITCH_A + i] = device.add_item( + BUTTON_NAMES_FLIGHT_STICK[i], + std::string_view(), + button_id++, + generic_button_get_state<u8>, + &m_switches[SWITCH_A + i]); + numbered_buttons[button_count] = SWITCH_A + i; + + // use these for automatically numbered buttons and pedals + input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_A + i])); + assignments.emplace_back(ioport_type(IPT_BUTTON1 + button_count), SEQ_TYPE_STANDARD, seq); + if (3 > button_count) + assignments.emplace_back(ioport_type(IPT_PEDAL + button_count), SEQ_TYPE_INCREMENT, seq); + ++button_count; + } + } + + // add start/back + if (has_button(XINPUT_GAMEPAD_START)) + { + switch_ids[SWITCH_START] = device.add_item( + "Start", + std::string_view(), + ITEM_ID_START, + generic_button_get_state<u8>, + &m_switches[SWITCH_START]); + add_button_assignment(assignments, IPT_START, { switch_ids[SWITCH_START] }); + } + if (has_button(XINPUT_GAMEPAD_BACK)) + { + switch_ids[SWITCH_BACK] = device.add_item( + "Back", + std::string_view(), + ITEM_ID_SELECT, + generic_button_get_state<u8>, + &m_switches[SWITCH_BACK]); + add_button_assignment(assignments, IPT_SELECT, { switch_ids[SWITCH_BACK] }); + } + + // use throttle for joystick Z, or rudder if it isn't available + add_assignment( + assignments, + IPT_AD_STICK_Z, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_NONE, + { axis_ids[AXIS_THROTTLE], axis_ids[AXIS_RUDDER] }); + + // if throttle is available, use it for first two pedals, too + if (ITEM_ID_INVALID != axis_ids[AXIS_THROTTLE]) + { + assignments.emplace_back( + IPT_PEDAL, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, axis_ids[AXIS_THROTTLE]))); + assignments.emplace_back( + IPT_PEDAL2, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_POS, axis_ids[AXIS_THROTTLE]))); + } + + // find something to use for directional controls and navigation + bool const axis_missing = (ITEM_ID_INVALID == axis_ids[AXIS_X]) || (ITEM_ID_INVALID == axis_ids[AXIS_Y]); + bool const hat_complete = has_thumb_right_x() && has_thumb_right_y(); + if (axis_missing && hat_complete) + { + // X or Y missing - rely on POV hat + add_directional_assignments( + assignments, + axis_ids[AXIS_X], + axis_ids[AXIS_Y], + switch_ids[SWITCH_HAT_LEFT], + switch_ids[SWITCH_HAT_RIGHT], + switch_ids[SWITCH_HAT_UP], + switch_ids[SWITCH_HAT_DOWN]); + + // choose something for previous/next group + if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_DPAD_UP], switch_ids[SWITCH_DPAD_DOWN])) + { + // took D-pad up/down + } + else if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT])) + { + // took D-pad left/right + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axis_ids[AXIS_RUDDER])) + { + // took rudder + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axis_ids[AXIS_THROTTLE])) + { + // took throttle + } + + // choose something for zoom and focus previous/next + if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, axis_ids[AXIS_THROTTLE])) + { + if (!add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, axis_ids[AXIS_RUDDER])) + add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, axis_ids[AXIS_THROTTLE]); + } + else if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, axis_ids[AXIS_RUDDER])) + { + add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, axis_ids[AXIS_RUDDER]); + } + else if (add_button_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT])) + { + consume_button_pair(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT]); + } + } + else + { + // only use stick for the primary directional controls + add_directional_assignments( + assignments, + axis_ids[AXIS_X], + axis_ids[AXIS_Y], + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID); + + // assign the POV hat differently depending on whether rudder and/or throttle are present + if ((ITEM_ID_INVALID != axis_ids[AXIS_RUDDER]) || (ITEM_ID_INVALID != axis_ids[AXIS_THROTTLE])) + { + // previous/next group + if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_HAT_LEFT], switch_ids[SWITCH_HAT_RIGHT])) + { + // took hat left/right + } + else if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT])) + { + // took D-pad left/right + } + else if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_HAT_UP], switch_ids[SWITCH_HAT_DOWN])) + { + // took hat up/down + } + else if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_DPAD_UP], switch_ids[SWITCH_DPAD_DOWN])) + { + // took D-pad up/down + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axis_ids[AXIS_RUDDER])) + { + // took rudder + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axis_ids[AXIS_THROTTLE])) + { + // took throttle + } + + // page up/down + if (consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, switch_ids[SWITCH_HAT_UP], switch_ids[SWITCH_HAT_DOWN])) + { + // took hat up/down + } + else if (consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, switch_ids[SWITCH_DPAD_UP], switch_ids[SWITCH_DPAD_DOWN])) + { + // took D-pad up/down + } + else if (consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT])) + { + // took D-pad left/right + } + + // home/end + if (consume_button_pair(assignments, IPT_UI_HOME, IPT_UI_END, switch_ids[SWITCH_DPAD_UP], switch_ids[SWITCH_DPAD_DOWN])) + { + // took D-pad up/down + } + else if (consume_button_pair(assignments, IPT_UI_HOME, IPT_UI_END, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT])) + { + // took D-pad left/right + } + + // assign something for zoom - this can overlap with focus previous/next + if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, axis_ids[AXIS_THROTTLE])) + { + // took throttle + } + else if (add_button_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT])) + { + // took D-pad left/right + } + else if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, axis_ids[AXIS_RUDDER])) + { + // took rudder + } + + // assign something for focus previous/next + if (add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, axis_ids[AXIS_RUDDER])) + { + // took rudder + } + else if (add_button_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT])) + { + // took D-pad left/right + } + else if (add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, axis_ids[AXIS_THROTTLE])) + { + // took throttle + } + } + else + { + // previous/next group + if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_HAT_UP], switch_ids[SWITCH_HAT_DOWN])) + { + // took hat up/down + } + else if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_DPAD_UP], switch_ids[SWITCH_DPAD_DOWN])) + { + // took D-pad up/down + } + else if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_HAT_LEFT], switch_ids[SWITCH_HAT_RIGHT])) + { + // took hat left/right + } + else if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT])) + { + // took D-pad left/right + } + + // try to choose something for focus previous/next + if (add_button_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, switch_ids[SWITCH_HAT_LEFT], switch_ids[SWITCH_HAT_RIGHT])) + { + // took hat left/right - use it for zoom as well + consume_button_pair(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, switch_ids[SWITCH_HAT_LEFT], switch_ids[SWITCH_HAT_RIGHT]); + } + else if (add_button_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT])) + { + // took D-pad left/right - use it for zoom as well + consume_button_pair(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT]); + } + else if (add_button_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, switch_ids[SWITCH_DPAD_UP], switch_ids[SWITCH_DPAD_DOWN])) + { + // took D-pad left/right - use it for zoom as well + consume_button_pair(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, switch_ids[SWITCH_DPAD_UP], switch_ids[SWITCH_DPAD_DOWN]); + } + + // use D-pad for page up/down and home/end if it's still available + if (consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, switch_ids[SWITCH_DPAD_UP], switch_ids[SWITCH_DPAD_DOWN])) + { + // took D-pad up/down + } + else if (consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT])) + { + // took D-pad left/right + } + consume_button_pair(assignments, IPT_UI_HOME, IPT_UI_END, switch_ids[SWITCH_DPAD_LEFT], switch_ids[SWITCH_DPAD_RIGHT]); } } + + // assign UI select/back/clear/help + assign_ui_actions( + assignments, + SWITCH_B, + SWITCH_X, + SWITCH_Y, + SWITCH_START, + SWITCH_BACK, + switch_ids, + numbered_buttons, + button_count); + + // set default assignments + device.set_default_assignments(std::move(assignments)); } @@ -1019,6 +1833,11 @@ void xinput_guitar_device::configure(input_device &device) { // TODO: does subtype 0x06 indicate digital neck orientation sensor or lack of three-axis accelerometer? + // track item IDs for setting up default assignments + input_device::assignment_vector assignments; + input_item_id switch_ids[SWITCH_TOTAL]; + std::fill(std::begin(switch_ids), std::end(switch_ids), ITEM_ID_INVALID); + // add axes std::tuple<input_item_id, char const *, bool> const axis_caps[]{ { ITEM_ID_RXAXIS, "Neck Slider", has_thumb_left_x() }, @@ -1028,17 +1847,22 @@ void xinput_guitar_device::configure(input_device &device) { ITEM_ID_XAXIS, "Bridge Orientation", has_trigger_left() && has_trigger_right() }, { ITEM_ID_ZAXIS, "Body Orientation", has_trigger_right() }, { ITEM_ID_SLIDER2, "Pickup Selector", has_trigger_left() && !has_trigger_right() } }; - for (unsigned i = 0; (AXIS_PICKUP - AXIS_SLIDER) >= i; ++i) + input_item_id axis_ids[AXIS_TOTAL]; + for (unsigned i = 0; AXIS_TOTAL > i; ++i) { auto const [item, name, supported] = axis_caps[i]; if (supported) { - device.add_item( + axis_ids[i] = device.add_item( name, std::string_view(), item, generic_axis_get_state<s32>, - &m_axes[AXIS_SLIDER + i]); + &m_axes[i]); + } + else + { + axis_ids[i] = ITEM_ID_INVALID; } } @@ -1047,7 +1871,7 @@ void xinput_guitar_device::configure(input_device &device) { if (has_button(SWITCH_BITS[SWITCH_DPAD_UP + i])) { - device.add_item( + switch_ids[SWITCH_DPAD_UP + i] = device.add_item( HAT_NAMES_GUITAR[i], std::string_view(), input_item_id(ITEM_ID_HAT1UP + i), // matches up/down/left/right order @@ -1058,38 +1882,107 @@ void xinput_guitar_device::configure(input_device &device) // add buttons input_item_id button_id = ITEM_ID_BUTTON1; + unsigned button_count = 0; + unsigned numbered_buttons[SWITCH_FRET5 - SWITCH_FRET1 + 1]; for (unsigned i = 0; (SWITCH_RSB - SWITCH_FRET1) >= i; ++i) { if (has_button(SWITCH_BITS[i])) { - device.add_item( + switch_ids[SWITCH_FRET1 + i] = device.add_item( BUTTON_NAMES_GUITAR[i], std::string_view(), button_id++, generic_button_get_state<u8>, &m_switches[SWITCH_FRET1 + i]); + + // use fret buttons for automatically numbered buttons and pedals + if ((SWITCH_FRET5 - SWITCH_FRET1) >= i) + { + numbered_buttons[button_count] = SWITCH_FRET1 + i; + input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_FRET1 + i])); + assignments.emplace_back(ioport_type(IPT_BUTTON1 + button_count), SEQ_TYPE_STANDARD, seq); + if (((ITEM_ID_INVALID != axis_ids[AXIS_WHAMMY]) ? 2 : 3) > button_count) + { + ioport_type const first_pedal = (ITEM_ID_INVALID != axis_ids[AXIS_WHAMMY]) + ? IPT_PEDAL2 + : IPT_PEDAL; + assignments.emplace_back( + ioport_type(first_pedal + button_count), + SEQ_TYPE_INCREMENT, + seq); + } + ++button_count; + } } } // add start/back if (has_button(XINPUT_GAMEPAD_START)) { - device.add_item( + switch_ids[SWITCH_START] = device.add_item( "Start", std::string_view(), ITEM_ID_START, generic_button_get_state<u8>, &m_switches[SWITCH_START]); + add_button_assignment(assignments, IPT_START, { switch_ids[SWITCH_START] }); } if (has_button(XINPUT_GAMEPAD_BACK)) { - device.add_item( + switch_ids[SWITCH_BACK] = device.add_item( "Back", std::string_view(), ITEM_ID_SELECT, generic_button_get_state<u8>, &m_switches[SWITCH_BACK]); + add_button_assignment(assignments, IPT_SELECT, { switch_ids[SWITCH_BACK] }); } + + // use the D-pad for directional controls - accelerometers are an annoyance + add_directional_assignments( + assignments, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + switch_ids[SWITCH_DPAD_LEFT], + switch_ids[SWITCH_DPAD_RIGHT], + switch_ids[SWITCH_DPAD_UP], + switch_ids[SWITCH_DPAD_DOWN]); + + // use the whammy bar for the first pedal and focus next if present + if (ITEM_ID_INVALID != axis_ids[AXIS_WHAMMY]) + { + assignments.emplace_back( + IPT_PEDAL, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, axis_ids[AXIS_WHAMMY]))); + assignments.emplace_back( + IPT_UI_FOCUS_NEXT, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axis_ids[AXIS_WHAMMY]))); + } + + // use the neck slider for a couple of things if it's present + if (ITEM_ID_INVALID != axis_ids[AXIS_SLIDER]) + { + input_seq const seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, axis_ids[AXIS_SLIDER])); + assignments.emplace_back(IPT_PADDLE, SEQ_TYPE_STANDARD, seq); + assignments.emplace_back(IPT_AD_STICK_X, SEQ_TYPE_STANDARD, seq); + } + + // assign UI select/back/clear/help + assign_ui_actions( + assignments, + SWITCH_FRET2, + SWITCH_FRET3, + SWITCH_FRET4, + SWITCH_START, + SWITCH_BACK, + switch_ids, + numbered_buttons, + button_count); + + // set default assignments + device.set_default_assignments(std::move(assignments)); } @@ -1215,6 +2108,11 @@ void xinput_drumkit_device::reset() void xinput_drumkit_device::configure(input_device &device) { + // track item IDs for setting up default assignments + input_device::assignment_vector assignments; + input_item_id switch_ids[SWITCH_TOTAL]; + std::fill(std::begin(switch_ids), std::end(switch_ids), ITEM_ID_INVALID); + // add axes std::tuple<input_item_id, char const *, bool> const axis_caps[]{ { ITEM_ID_XAXIS, "Green Velocity", has_thumb_left_y() }, @@ -1242,7 +2140,7 @@ void xinput_drumkit_device::configure(input_device &device) { if (has_button(SWITCH_BITS[SWITCH_DPAD_UP + i])) { - device.add_item( + switch_ids[SWITCH_DPAD_UP + i] = device.add_item( HAT_NAMES_GAMEPAD[i], std::string_view(), input_item_id(ITEM_ID_HAT1UP + i), // matches up/down/left/right order @@ -1252,39 +2150,95 @@ void xinput_drumkit_device::configure(input_device &device) } // add buttons - input_item_id button_id = ITEM_ID_BUTTON1; + unsigned button_count = 0; + unsigned numbered_buttons[SWITCH_RSB - SWITCH_GREEN + 1]; for (unsigned i = 0; (SWITCH_RSB - SWITCH_GREEN) >= i; ++i) { if (has_button(SWITCH_BITS[i])) { - device.add_item( + switch_ids[SWITCH_GREEN + i] = device.add_item( BUTTON_NAMES_DRUMKIT[i], std::string_view(), - button_id++, + input_item_id(ITEM_ID_BUTTON1 + button_count), generic_button_get_state<u8>, &m_switches[SWITCH_GREEN + i]); + numbered_buttons[button_count] = SWITCH_GREEN + i; + + // use these for automatically numbered buttons and pedals + input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_GREEN + i])); + assignments.emplace_back(ioport_type(IPT_BUTTON1 + button_count), SEQ_TYPE_STANDARD, seq); + if (3 > button_count) + assignments.emplace_back(ioport_type(IPT_PEDAL + button_count), SEQ_TYPE_INCREMENT, seq); + ++button_count; } } // add start/back if (has_button(XINPUT_GAMEPAD_START)) { - device.add_item( + switch_ids[SWITCH_START] = device.add_item( "Start", std::string_view(), ITEM_ID_START, generic_button_get_state<u8>, &m_switches[SWITCH_START]); + add_button_assignment(assignments, IPT_START, { switch_ids[SWITCH_START] }); } if (has_button(XINPUT_GAMEPAD_BACK)) { - device.add_item( + switch_ids[SWITCH_BACK] = device.add_item( "Back", std::string_view(), ITEM_ID_SELECT, generic_button_get_state<u8>, &m_switches[SWITCH_BACK]); + add_button_assignment(assignments, IPT_SELECT, { switch_ids[SWITCH_BACK] }); } + + // use the D-pad for directional controls + add_directional_assignments( + assignments, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + switch_ids[SWITCH_DPAD_LEFT], + switch_ids[SWITCH_DPAD_RIGHT], + switch_ids[SWITCH_DPAD_UP], + switch_ids[SWITCH_DPAD_DOWN]); + + // use the D-pad and A/B/X/Y diamond for twin sticks + add_twin_stick_assignments( + assignments, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + switch_ids[SWITCH_DPAD_LEFT], + switch_ids[SWITCH_DPAD_RIGHT], + switch_ids[SWITCH_DPAD_UP], + switch_ids[SWITCH_DPAD_DOWN], + switch_ids[SWITCH_BLUE], + switch_ids[SWITCH_RED], + switch_ids[SWITCH_YELLOW], + switch_ids[SWITCH_GREEN]); + + // assign UI select/back/clear/help + assign_ui_actions( + assignments, + SWITCH_RED, + SWITCH_BLUE, + SWITCH_YELLOW, + SWITCH_START, + SWITCH_BACK, + switch_ids, + numbered_buttons, + button_count); + + // use bass drum pedal for focus next if available to make the system selection menu usable + if (add_button_assignment(assignments, IPT_UI_FOCUS_NEXT, { switch_ids[SWITCH_BASS_DRUM] })) + switch_ids[SWITCH_BASS_DRUM] = ITEM_ID_INVALID; + + // set default assignments + device.set_default_assignments(std::move(assignments)); } @@ -1432,20 +2386,25 @@ void xinput_turntable_device::reset() void xinput_turntable_device::configure(input_device &device) { + // track item IDs for setting up default assignments + input_device::assignment_vector assignments; + input_item_id switch_ids[SWITCH_TOTAL]; + std::fill(std::begin(switch_ids), std::end(switch_ids), ITEM_ID_INVALID); + // add axes - device.add_item( + input_item_id const turntable_id = device.add_item( "Turntable", std::string_view(), ITEM_ID_ADD_RELATIVE1, generic_axis_get_state<s32>, &m_axes[AXIS_TURNTABLE]); - device.add_item( + input_item_id const effect_id = device.add_item( "Effect", std::string_view(), ITEM_ID_ADD_RELATIVE2, generic_axis_get_state<s32>, &m_axes[AXIS_EFFECT]); - device.add_item( + input_item_id const crossfade_id = device.add_item( "Crossfade", std::string_view(), ITEM_ID_XAXIS, @@ -1457,7 +2416,7 @@ void xinput_turntable_device::configure(input_device &device) { if (has_button(SWITCH_BITS[SWITCH_DPAD_UP + i])) { - device.add_item( + switch_ids[SWITCH_DPAD_UP + i] = device.add_item( HAT_NAMES_GAMEPAD[i], std::string_view(), input_item_id(ITEM_ID_HAT1UP + i), // matches up/down/left/right order @@ -1468,31 +2427,43 @@ void xinput_turntable_device::configure(input_device &device) // add buttons input_item_id button_id = ITEM_ID_BUTTON1; + unsigned button_count = 0; + unsigned numbered_buttons[SWITCH_RSB - SWITCH_A + 1]; for (unsigned i = 0; (SWITCH_RSB - SWITCH_A) >= i; ++i) { if (has_button(SWITCH_BITS[i])) { - device.add_item( + switch_ids[SWITCH_A + i] = device.add_item( BUTTON_NAMES_KEYBOARD[i], std::string_view(), button_id++, generic_button_get_state<u8>, &m_switches[SWITCH_A + i]); + numbered_buttons[button_count] = SWITCH_A + i; + + // use these for automatically numbered buttons and pedals + input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_A + i])); + assignments.emplace_back(ioport_type(IPT_BUTTON1 + button_count), SEQ_TYPE_STANDARD, seq); + if (3 > button_count) + assignments.emplace_back(ioport_type(IPT_PEDAL + button_count), SEQ_TYPE_INCREMENT, seq); + ++button_count; } } - device.add_item( + + // turntable buttons activate these as well as A/B/X + switch_ids[SWITCH_GREEN] = device.add_item( "Green", std::string_view(), button_id++, generic_button_get_state<u8>, &m_switches[SWITCH_GREEN]); - device.add_item( + switch_ids[SWITCH_RED] = device.add_item( "Red", std::string_view(), button_id++, generic_button_get_state<u8>, &m_switches[SWITCH_RED]); - device.add_item( + switch_ids[SWITCH_BLUE] = device.add_item( "Blue", std::string_view(), button_id++, @@ -1502,22 +2473,197 @@ void xinput_turntable_device::configure(input_device &device) // add start/back if (has_button(XINPUT_GAMEPAD_START)) { - device.add_item( + switch_ids[SWITCH_START] = device.add_item( "Start", std::string_view(), ITEM_ID_START, generic_button_get_state<u8>, &m_switches[SWITCH_START]); + add_button_assignment(assignments, IPT_START, { switch_ids[SWITCH_START] }); } if (has_button(XINPUT_GAMEPAD_BACK)) { - device.add_item( + switch_ids[SWITCH_BACK] = device.add_item( "Back", std::string_view(), ITEM_ID_SELECT, generic_button_get_state<u8>, &m_switches[SWITCH_BACK]); + add_button_assignment(assignments, IPT_SELECT, { switch_ids[SWITCH_BACK] }); + } + + // use D-pad and A/B/X/Y diamond for twin sticks + add_twin_stick_assignments( + assignments, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + switch_ids[SWITCH_DPAD_LEFT], + switch_ids[SWITCH_DPAD_RIGHT], + switch_ids[SWITCH_DPAD_UP], + switch_ids[SWITCH_DPAD_DOWN], + switch_ids[SWITCH_X], + switch_ids[SWITCH_B], + switch_ids[SWITCH_Y], + switch_ids[SWITCH_A]); + + // for most analog player controls, use turntable for X and effect for Y + input_seq const turntable_seq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, turntable_id)); + input_seq const effect_seq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, effect_id)); + input_seq const crossfade_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, crossfade_id)); + input_seq joystick_left_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, turntable_id)); + input_seq joystick_right_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, turntable_id)); + input_seq joystick_up_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, effect_id)); + input_seq joystick_down_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, effect_id)); + assignments.emplace_back(IPT_PADDLE, SEQ_TYPE_STANDARD, turntable_seq); + assignments.emplace_back(IPT_PADDLE_V, SEQ_TYPE_STANDARD, effect_seq); + assignments.emplace_back(IPT_POSITIONAL, SEQ_TYPE_STANDARD, crossfade_seq); + assignments.emplace_back(IPT_POSITIONAL_V, SEQ_TYPE_STANDARD, effect_seq); + assignments.emplace_back(IPT_DIAL, SEQ_TYPE_STANDARD, turntable_seq); + assignments.emplace_back(IPT_DIAL_V, SEQ_TYPE_STANDARD, effect_seq); + assignments.emplace_back(IPT_TRACKBALL_X, SEQ_TYPE_STANDARD, turntable_seq); + assignments.emplace_back(IPT_TRACKBALL_Y, SEQ_TYPE_STANDARD, effect_seq); + assignments.emplace_back(IPT_AD_STICK_X, SEQ_TYPE_STANDARD, turntable_seq); + assignments.emplace_back(IPT_AD_STICK_Y, SEQ_TYPE_STANDARD, effect_seq); + assignments.emplace_back(IPT_AD_STICK_Z, SEQ_TYPE_STANDARD, crossfade_seq); + assignments.emplace_back(IPT_LIGHTGUN_X, SEQ_TYPE_STANDARD, turntable_seq); + assignments.emplace_back(IPT_LIGHTGUN_Y, SEQ_TYPE_STANDARD, effect_seq); + assignments.emplace_back(IPT_MOUSE_X, SEQ_TYPE_STANDARD, turntable_seq); + assignments.emplace_back(IPT_MOUSE_Y, SEQ_TYPE_STANDARD, effect_seq); + + // use D-pad for analog controls as well if present + bool const have_dpad_left = ITEM_ID_INVALID != switch_ids[SWITCH_DPAD_LEFT]; + bool const have_dpad_right = ITEM_ID_INVALID != switch_ids[SWITCH_DPAD_RIGHT]; + bool const have_dpad_up = ITEM_ID_INVALID != switch_ids[SWITCH_DPAD_UP]; + bool const have_dpad_down = ITEM_ID_INVALID != switch_ids[SWITCH_DPAD_DOWN]; + if (have_dpad_left) + { + input_code const code(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_LEFT])); + input_seq const left_seq(code); + joystick_left_seq += input_seq::or_code; + joystick_left_seq += code; + assignments.emplace_back(IPT_PADDLE, SEQ_TYPE_DECREMENT, left_seq); + assignments.emplace_back(IPT_POSITIONAL, SEQ_TYPE_DECREMENT, left_seq); + assignments.emplace_back(IPT_DIAL, SEQ_TYPE_DECREMENT, left_seq); + assignments.emplace_back(IPT_TRACKBALL_X, SEQ_TYPE_DECREMENT, left_seq); + assignments.emplace_back(IPT_AD_STICK_X, SEQ_TYPE_DECREMENT, left_seq); + assignments.emplace_back(IPT_LIGHTGUN_X, SEQ_TYPE_DECREMENT, left_seq); + } + if (have_dpad_right) + { + input_code const code(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_RIGHT])); + input_seq const right_seq(code); + joystick_right_seq += input_seq::or_code; + joystick_right_seq += code; + assignments.emplace_back(IPT_PADDLE, SEQ_TYPE_INCREMENT, right_seq); + assignments.emplace_back(IPT_POSITIONAL, SEQ_TYPE_INCREMENT, right_seq); + assignments.emplace_back(IPT_DIAL, SEQ_TYPE_INCREMENT, right_seq); + assignments.emplace_back(IPT_TRACKBALL_X, SEQ_TYPE_INCREMENT, right_seq); + assignments.emplace_back(IPT_AD_STICK_X, SEQ_TYPE_INCREMENT, right_seq); + assignments.emplace_back(IPT_LIGHTGUN_X, SEQ_TYPE_INCREMENT, right_seq); + } + if (have_dpad_up) + { + input_code const code(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_UP])); + input_seq const up_seq(code); + joystick_up_seq += input_seq::or_code; + joystick_up_seq += code; + assignments.emplace_back(IPT_PADDLE_V, SEQ_TYPE_DECREMENT, up_seq); + assignments.emplace_back(IPT_POSITIONAL_V, SEQ_TYPE_DECREMENT, up_seq); + assignments.emplace_back(IPT_DIAL_V, SEQ_TYPE_DECREMENT, up_seq); + assignments.emplace_back(IPT_TRACKBALL_Y, SEQ_TYPE_DECREMENT, up_seq); + assignments.emplace_back(IPT_AD_STICK_Y, SEQ_TYPE_DECREMENT, up_seq); + assignments.emplace_back(IPT_LIGHTGUN_Y, SEQ_TYPE_DECREMENT, up_seq); + } + if (have_dpad_down) + { + input_code const code(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_DOWN])); + input_seq const down_seq(code); + joystick_down_seq += input_seq::or_code; + joystick_down_seq += code; + assignments.emplace_back(IPT_PADDLE_V, SEQ_TYPE_INCREMENT, down_seq); + assignments.emplace_back(IPT_POSITIONAL_V, SEQ_TYPE_INCREMENT, down_seq); + assignments.emplace_back(IPT_DIAL_V, SEQ_TYPE_INCREMENT, down_seq); + assignments.emplace_back(IPT_TRACKBALL_Y, SEQ_TYPE_INCREMENT, down_seq); + assignments.emplace_back(IPT_AD_STICK_Y, SEQ_TYPE_INCREMENT, down_seq); + assignments.emplace_back(IPT_LIGHTGUN_Y, SEQ_TYPE_INCREMENT, down_seq); + } + assignments.emplace_back(IPT_JOYSTICK_LEFT, SEQ_TYPE_STANDARD, joystick_left_seq); + assignments.emplace_back(IPT_JOYSTICK_RIGHT, SEQ_TYPE_STANDARD, joystick_right_seq); + assignments.emplace_back(IPT_JOYSTICK_UP, SEQ_TYPE_STANDARD, joystick_up_seq); + assignments.emplace_back(IPT_JOYSTICK_DOWN, SEQ_TYPE_STANDARD, joystick_down_seq); + + // choose navigation controls + input_seq ui_up_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, turntable_id)); + input_seq ui_down_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, turntable_id)); + input_seq ui_left_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, effect_id)); + input_seq ui_right_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, effect_id)); + if ((have_dpad_up && have_dpad_down) || !have_dpad_left || !have_dpad_right) + { + if (have_dpad_up) + { + ui_up_seq += input_seq::or_code; + ui_up_seq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_UP]); + } + if (have_dpad_down) + { + ui_down_seq += input_seq::or_code; + ui_down_seq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_DOWN]); + } + if (have_dpad_left) + { + ui_left_seq += input_seq::or_code; + ui_left_seq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_LEFT]); + } + if (have_dpad_right) + { + ui_right_seq += input_seq::or_code; + ui_right_seq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_RIGHT]); + } } + else + { + if (have_dpad_left) + { + ui_up_seq += input_seq::or_code; + ui_up_seq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_LEFT]); + } + if (have_dpad_right) + { + ui_down_seq += input_seq::or_code; + ui_down_seq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_RIGHT]); + } + if (have_dpad_up) + { + ui_left_seq += input_seq::or_code; + ui_left_seq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_UP]); + } + if (have_dpad_down) + { + ui_right_seq += input_seq::or_code; + ui_right_seq += make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_DPAD_DOWN]); + } + } + assignments.emplace_back(IPT_UI_UP, SEQ_TYPE_STANDARD, ui_up_seq); + assignments.emplace_back(IPT_UI_DOWN, SEQ_TYPE_STANDARD, ui_down_seq); + assignments.emplace_back(IPT_UI_LEFT, SEQ_TYPE_STANDARD, ui_left_seq); + assignments.emplace_back(IPT_UI_RIGHT, SEQ_TYPE_STANDARD, ui_right_seq); + + // assign UI select/back/clear/help + assign_ui_actions( + assignments, + SWITCH_B, + SWITCH_X, + SWITCH_Y, + SWITCH_START, + SWITCH_BACK, + switch_ids, + numbered_buttons, + std::min<unsigned>(button_count, 4)); + + // set default assignments + device.set_default_assignments(std::move(assignments)); } @@ -1650,6 +2796,11 @@ void xinput_keyboard_device::reset() void xinput_keyboard_device::configure(input_device &device) { + // track item IDs for setting up default assignments + input_device::assignment_vector assignments; + input_item_id switch_ids[SWITCH_TOTAL]; + std::fill(std::begin(switch_ids), std::end(switch_ids), ITEM_ID_INVALID); + // add axes device.add_item( "Velocity", @@ -1657,19 +2808,23 @@ void xinput_keyboard_device::configure(input_device &device) ITEM_ID_SLIDER1, generic_axis_get_state<s32>, &m_axes[AXIS_VELOCITY]); - device.add_item( + input_item_id const pedal_id = device.add_item( "Pedal", std::string_view(), ITEM_ID_SLIDER2, generic_axis_get_state<s32>, &m_axes[AXIS_PEDAL]); + assignments.emplace_back( + IPT_PEDAL, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, pedal_id))); // add hats for (unsigned i = 0; (SWITCH_DPAD_RIGHT - SWITCH_DPAD_UP) >= i; ++i) { if (has_button(SWITCH_BITS[SWITCH_DPAD_UP + i])) { - device.add_item( + switch_ids[SWITCH_DPAD_UP + i] = device.add_item( HAT_NAMES_GAMEPAD[i], std::string_view(), input_item_id(ITEM_ID_HAT1UP + i), // matches up/down/left/right order @@ -1680,51 +2835,137 @@ void xinput_keyboard_device::configure(input_device &device) // add buttons input_item_id button_id = ITEM_ID_BUTTON1; + unsigned button_count = 0; + unsigned numbered_buttons[SWITCH_RSB - SWITCH_A + 1]; for (unsigned i = 0; (SWITCH_RSB - SWITCH_A) >= i; ++i) { if (has_button(SWITCH_BITS[i])) { - device.add_item( + switch_ids[SWITCH_A + i] = device.add_item( BUTTON_NAMES_KEYBOARD[i], std::string_view(), button_id++, generic_button_get_state<u8>, &m_switches[SWITCH_A + i]); + numbered_buttons[button_count] = SWITCH_A + i; + + // use these for automatically numbered buttons and pedals + input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_A + i])); + assignments.emplace_back(ioport_type(IPT_BUTTON1 + button_count), SEQ_TYPE_STANDARD, seq); + if (3 > button_count) + assignments.emplace_back(ioport_type(IPT_PEDAL + button_count), SEQ_TYPE_INCREMENT, seq); + ++button_count; } } // add keys char const *const key_formats[]{ "C %d", "C# %d", "D %d", "D# %d", "E %d", "F %d", "F# %d", "G %d", "G# %d", "A %d", "A# %d", "B %d" }; + std::pair<ioport_type, ioport_type> const key_ids[]{ + { IPT_MAHJONG_A, IPT_HANAFUDA_A }, // C + { IPT_MAHJONG_SCORE, IPT_INVALID }, // C# + { IPT_MAHJONG_B, IPT_HANAFUDA_B }, // D + { IPT_MAHJONG_DOUBLE_UP, IPT_INVALID }, // D# + { IPT_MAHJONG_C, IPT_HANAFUDA_C }, // E + { IPT_MAHJONG_D, IPT_HANAFUDA_D }, // F + { IPT_MAHJONG_BIG, IPT_INVALID }, // F# + { IPT_MAHJONG_E, IPT_HANAFUDA_E }, // G + { IPT_MAHJONG_SMALL, IPT_INVALID }, // G# + { IPT_MAHJONG_F, IPT_HANAFUDA_F }, // A + { IPT_MAHJONG_LAST_CHANCE, IPT_INVALID }, // A# + { IPT_MAHJONG_G, IPT_HANAFUDA_G }, // B + { IPT_MAHJONG_H, IPT_HANAFUDA_H }, // C + { IPT_MAHJONG_KAN, IPT_INVALID }, // C# + { IPT_MAHJONG_I, IPT_INVALID }, // D + { IPT_MAHJONG_PON, IPT_INVALID }, // D# + { IPT_MAHJONG_J, IPT_INVALID }, // E + { IPT_MAHJONG_K, IPT_INVALID }, // F + { IPT_MAHJONG_CHI, IPT_INVALID }, // F# + { IPT_MAHJONG_L, IPT_INVALID }, // G + { IPT_MAHJONG_REACH, IPT_INVALID }, // G# + { IPT_MAHJONG_M, IPT_HANAFUDA_YES }, // A + { IPT_MAHJONG_RON, IPT_INVALID }, // A# + { IPT_MAHJONG_N, IPT_HANAFUDA_NO }, // B + { IPT_MAHJONG_O, IPT_INVALID } }; // C for (unsigned i = 0; (SWITCH_C3 - SWITCH_C1) >= i; ++i) { - device.add_item( + switch_ids[SWITCH_C1 + i] = device.add_item( util::string_format(key_formats[i % 12], (i / 12) + 1), std::string_view(), (ITEM_ID_BUTTON32 >= button_id) ? button_id++ : ITEM_ID_OTHER_SWITCH, generic_button_get_state<u8>, &m_switches[SWITCH_C1 + i]); + + // add mahjong/hanafuda control assignments + input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, switch_ids[SWITCH_C1 + i])); + if (IPT_INVALID != key_ids[i].first) + assignments.emplace_back(key_ids[i].first, SEQ_TYPE_STANDARD, seq); + if (IPT_INVALID != key_ids[i].second) + assignments.emplace_back(key_ids[i].second, SEQ_TYPE_STANDARD, seq); } // add start/back if (has_button(XINPUT_GAMEPAD_START)) { - device.add_item( + switch_ids[SWITCH_START] = device.add_item( "Start", std::string_view(), ITEM_ID_START, generic_button_get_state<u8>, &m_switches[SWITCH_START]); + add_button_assignment(assignments, IPT_START, { switch_ids[SWITCH_START] }); } if (has_button(XINPUT_GAMEPAD_BACK)) { - device.add_item( + switch_ids[SWITCH_BACK] = device.add_item( "Back", std::string_view(), ITEM_ID_SELECT, generic_button_get_state<u8>, &m_switches[SWITCH_BACK]); + add_button_assignment(assignments, IPT_SELECT, { switch_ids[SWITCH_BACK] }); } + + // use the D-pad for directional controls + add_directional_assignments( + assignments, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + switch_ids[SWITCH_DPAD_LEFT], + switch_ids[SWITCH_DPAD_RIGHT], + switch_ids[SWITCH_DPAD_UP], + switch_ids[SWITCH_DPAD_DOWN]); + + // use the D-pad and A/B/X/Y diamond for twin sticks + add_twin_stick_assignments( + assignments, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + ITEM_ID_INVALID, + switch_ids[SWITCH_DPAD_LEFT], + switch_ids[SWITCH_DPAD_RIGHT], + switch_ids[SWITCH_DPAD_UP], + switch_ids[SWITCH_DPAD_DOWN], + switch_ids[SWITCH_X], + switch_ids[SWITCH_B], + switch_ids[SWITCH_Y], + switch_ids[SWITCH_A]); + + // assign UI select/back/clear/help + assign_ui_actions( + assignments, + SWITCH_B, + SWITCH_X, + SWITCH_Y, + SWITCH_START, + SWITCH_BACK, + switch_ids, + numbered_buttons, + button_count); + + // set default assignments + device.set_default_assignments(std::move(assignments)); } @@ -1832,6 +3073,14 @@ std::unique_ptr<device_info> xinput_api_helper::create_xinput_device( case XINPUT_DEVTYPE_GAMEPAD: switch (caps.SubType) { + case 0x04: // XINPUT_DEVSUBTYPE_FLIGHT_STICK: work around MinGW header issues + return std::make_unique<xinput_flight_stick_device>( + device_name, + device_name, + module, + index, + caps, + *this); case XINPUT_DEVSUBTYPE_GUITAR: case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE: case XINPUT_DEVSUBTYPE_GUITAR_BASS: @@ -1842,7 +3091,6 @@ std::unique_ptr<device_info> xinput_api_helper::create_xinput_device( index, caps, *this); - break; case XINPUT_DEVSUBTYPE_DRUM_KIT: return std::make_unique<xinput_drumkit_device>( device_name, @@ -1851,7 +3099,6 @@ std::unique_ptr<device_info> xinput_api_helper::create_xinput_device( index, caps, *this); - break; case 0x0f: return std::make_unique<xinput_keyboard_device>( device_name, @@ -1860,7 +3107,6 @@ std::unique_ptr<device_info> xinput_api_helper::create_xinput_device( index, caps, *this); - break; case 0x17: return std::make_unique<xinput_turntable_device>( device_name, @@ -1869,9 +3115,7 @@ std::unique_ptr<device_info> xinput_api_helper::create_xinput_device( index, caps, *this); - break; } - break; } // create default general-purpose device |