diff options
Diffstat (limited to 'src/osd/modules/input')
22 files changed, 9290 insertions, 4452 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_common.cpp b/src/osd/modules/input/input_common.cpp index ab51b6bbe34..f3de3ca186e 100644 --- a/src/osd/modules/input/input_common.cpp +++ b/src/osd/modules/input/input_common.cpp @@ -8,201 +8,202 @@ // //============================================================ -#include "input_module.h" -#include "modules/lib/osdobj_common.h" - -#include <memory> - -// MAME headers -#include "emu.h" +#include "input_common.h" -// winnt.h defines this -#ifdef DELETE -#undef DELETE -#endif +#include "modules/lib/osdobj_common.h" -#include "input_common.h" +#include "emu.h" // so we can get an input manager from the running machine //============================================================ // Keyboard translation table //============================================================ -#if defined(OSD_WINDOWS) +#if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) #include <windows.h> -#define KEY_TRANS_ENTRY0(mame, sdlsc, sdlkey, disc, virtual, uwp, ascii, UI) { ITEM_ID_##mame, KEY_ ## disc, virtual, ascii, "ITEM_ID_"#mame, (char *) UI } -#define KEY_TRANS_ENTRY1(mame, sdlsc, sdlkey, disc, virtual, uwp, ascii) { ITEM_ID_##mame, KEY_ ## disc, virtual, ascii, "ITEM_ID_"#mame, (char*) #mame } -#elif defined(OSD_SDL) -// SDL include +#include <dinput.h> +#define DIK_UNKNOWN 0xffff // intentionally impossible +#define KEY_TRANS_WIN32(disc, virtual) DIK_##disc, virtual, +#else +#define KEY_TRANS_WIN32(disc, virtual) +#endif + +#if defined(OSD_SDL) || defined(SDLMAME_WIN32) #include <SDL2/SDL.h> -#define KEY_TRANS_ENTRY0(mame, sdlsc, sdlkey, disc, virtual, uwp, ascii, UI) { ITEM_ID_##mame, SDL_SCANCODE_ ## sdlsc, ascii, "ITEM_ID_"#mame, (char *) UI } -#define KEY_TRANS_ENTRY1(mame, sdlsc, sdlkey, disc, virtual, uwp, ascii) { ITEM_ID_##mame, SDL_SCANCODE_ ## sdlsc, ascii, "ITEM_ID_"#mame, (char*) #mame } -#elif defined(OSD_UWP) -#define KEY_TRANS_ENTRY0(mame, sdlsc, sdlkey, disc, virtual, uwp, ascii, UI) { ITEM_ID_##mame, KEY_ ## disc, Windows::System::VirtualKey:: ## uwp, ascii, "ITEM_ID_"#mame, (char *) UI } -#define KEY_TRANS_ENTRY1(mame, sdlsc, sdlkey, disc, virtual, uwp, ascii) { ITEM_ID_##mame, KEY_ ## disc, Windows::System::VirtualKey:: ## uwp, ascii, "ITEM_ID_"#mame, (char*) #mame } +#define KEY_TRANS_SDL(sdlsc) SDL_SCANCODE_##sdlsc, #else -// osd mini +#define KEY_TRANS_SDL(sdlsc) +#endif + +#define KEY_TRANS_ENTRY0(mame, sdlsc, disc, virtual, ascii, UI) { ITEM_ID_##mame, KEY_TRANS_SDL(sdlsc) KEY_TRANS_WIN32(disc, virtual) ascii, "ITEM_ID_"#mame, UI } +#define KEY_TRANS_ENTRY1(mame, sdlsc, disc, virtual, ascii) { ITEM_ID_##mame, KEY_TRANS_SDL(sdlsc) KEY_TRANS_WIN32(disc, virtual) ascii, "ITEM_ID_"#mame, #mame } + +// winnt.h defines this +#ifdef DELETE +#undef DELETE #endif -// FIXME: sdl_key can be removed from the table below. It is no longer used. +#if defined(OSD_WINDOWS) || defined(OSD_SDL) -#if defined(OSD_WINDOWS) || defined(OSD_SDL) || defined(OSD_UWP) key_trans_entry keyboard_trans_table::s_default_table[] = { - // MAME key sdl scancode sdl key di scancode virtual key uwp vkey ascii ui - KEY_TRANS_ENTRY0(ESC, ESCAPE, ESCAPE, ESCAPE, VK_ESCAPE, Escape, 27, "ESCAPE"), - KEY_TRANS_ENTRY1(1, 1, 1, 1, '1', Number1, '1'), - KEY_TRANS_ENTRY1(2, 2, 2, 2, '2', Number2, '2'), - KEY_TRANS_ENTRY1(3, 3, 3, 3, '3', Number3, '3'), - KEY_TRANS_ENTRY1(4, 4, 4, 4, '4', Number4, '4'), - KEY_TRANS_ENTRY1(5, 5, 5, 5, '5', Number5, '5'), - KEY_TRANS_ENTRY1(6, 6, 6, 6, '6', Number6, '6'), - KEY_TRANS_ENTRY1(7, 7, 7, 7, '7', Number7, '7'), - KEY_TRANS_ENTRY1(8, 8, 8, 8, '8', Number8, '8'), - KEY_TRANS_ENTRY1(9, 9, 9, 9, '9', Number9, '9'), - KEY_TRANS_ENTRY1(0, 0, 0, 0, '0', Number0, '0'), - KEY_TRANS_ENTRY1(MINUS, MINUS, MINUS, MINUS, VK_OEM_MINUS, None, '-'), - KEY_TRANS_ENTRY1(EQUALS, EQUALS, EQUALS, EQUALS, VK_OEM_PLUS, None, '='), - KEY_TRANS_ENTRY1(BACKSPACE, BACKSPACE, BACKSPACE, BACK, VK_BACK, Back, 8), - KEY_TRANS_ENTRY1(TAB, TAB, TAB, TAB, VK_TAB, Tab, 9), - KEY_TRANS_ENTRY1(Q, Q, q, Q, 'Q', Q, 'Q'), - KEY_TRANS_ENTRY1(W, W, w, W, 'W', W, 'W'), - KEY_TRANS_ENTRY1(E, E, e, E, 'E', E, 'E'), - KEY_TRANS_ENTRY1(R, R, r, R, 'R', R, 'R'), - KEY_TRANS_ENTRY1(T, T, t, T, 'T', T, 'T'), - KEY_TRANS_ENTRY1(Y, Y, y, Y, 'Y', Y, 'Y'), - KEY_TRANS_ENTRY1(U, U, u, U, 'U', U, 'U'), - KEY_TRANS_ENTRY1(I, I, i, I, 'I', I, 'I'), - KEY_TRANS_ENTRY1(O, O, o, O, 'O', O, 'O'), - KEY_TRANS_ENTRY1(P, P, p, P, 'P', P, 'P'), - KEY_TRANS_ENTRY1(OPENBRACE, LEFTBRACKET, LEFTBRACKET, LBRACKET, VK_OEM_4, None, '['), - KEY_TRANS_ENTRY1(CLOSEBRACE, RIGHTBRACKET, RIGHTBRACKET, RBRACKET, VK_OEM_6, None, ']'), - KEY_TRANS_ENTRY0(ENTER, RETURN, RETURN, RETURN, VK_RETURN, Enter, 13, "RETURN"), - KEY_TRANS_ENTRY1(LCONTROL, LCTRL, LCTRL, LCONTROL, VK_LCONTROL, LeftControl, 0), - KEY_TRANS_ENTRY1(A, A, a, A, 'A', A, 'A'), - KEY_TRANS_ENTRY1(S, S, s, S, 'S', S, 'S'), - KEY_TRANS_ENTRY1(D, D, d, D, 'D', D, 'D'), - KEY_TRANS_ENTRY1(F, F, f, F, 'F', F, 'F'), - KEY_TRANS_ENTRY1(G, G, g, G, 'G', G, 'G'), - KEY_TRANS_ENTRY1(H, H, h, H, 'H', H, 'H'), - KEY_TRANS_ENTRY1(J, J, j, J, 'J', J, 'J'), - KEY_TRANS_ENTRY1(K, K, k, K, 'K', K, 'K'), - KEY_TRANS_ENTRY1(L, L, l, L, 'L', L, 'L'), - KEY_TRANS_ENTRY1(COLON, SEMICOLON, SEMICOLON, SEMICOLON, VK_OEM_1, None, ';'), - KEY_TRANS_ENTRY1(QUOTE, APOSTROPHE, QUOTE, APOSTROPHE, VK_OEM_7, None, '\''), - KEY_TRANS_ENTRY1(TILDE, GRAVE, BACKQUOTE, GRAVE, VK_OEM_3, None, '`'), - KEY_TRANS_ENTRY1(LSHIFT, LSHIFT, LSHIFT, LSHIFT, VK_LSHIFT, LeftShift, 0), - KEY_TRANS_ENTRY1(BACKSLASH, BACKSLASH, BACKSLASH, BACKSLASH, VK_OEM_5, None, '\\'), -// KEY_TRANS_ENTRY1(BACKSLASH2, NONUSHASH, UNKNOWN, OEM_102, VK_OEM_102, None, '<'), + // MAME key SDL scancode DI scancode virtual key ASCII UI + KEY_TRANS_ENTRY0(ESC, ESCAPE, ESCAPE, VK_ESCAPE, 27, "ESCAPE"), + KEY_TRANS_ENTRY1(1, 1, 1, '1', '1'), + KEY_TRANS_ENTRY1(2, 2, 2, '2', '2'), + KEY_TRANS_ENTRY1(3, 3, 3, '3', '3'), + KEY_TRANS_ENTRY1(4, 4, 4, '4', '4'), + KEY_TRANS_ENTRY1(5, 5, 5, '5', '5'), + KEY_TRANS_ENTRY1(6, 6, 6, '6', '6'), + KEY_TRANS_ENTRY1(7, 7, 7, '7', '7'), + KEY_TRANS_ENTRY1(8, 8, 8, '8', '8'), + KEY_TRANS_ENTRY1(9, 9, 9, '9', '9'), + KEY_TRANS_ENTRY1(0, 0, 0, '0', '0'), + KEY_TRANS_ENTRY1(MINUS, MINUS, MINUS, VK_OEM_MINUS, '-'), + KEY_TRANS_ENTRY1(EQUALS, EQUALS, EQUALS, VK_OEM_PLUS, '='), + KEY_TRANS_ENTRY1(BACKSPACE, BACKSPACE, BACK, VK_BACK, 8), + KEY_TRANS_ENTRY1(TAB, TAB, TAB, VK_TAB, 9), + KEY_TRANS_ENTRY1(Q, Q, Q, 'Q', 'Q'), + KEY_TRANS_ENTRY1(W, W, W, 'W', 'W'), + KEY_TRANS_ENTRY1(E, E, E, 'E', 'E'), + KEY_TRANS_ENTRY1(R, R, R, 'R', 'R'), + KEY_TRANS_ENTRY1(T, T, T, 'T', 'T'), + KEY_TRANS_ENTRY1(Y, Y, Y, 'Y', 'Y'), + KEY_TRANS_ENTRY1(U, U, U, 'U', 'U'), + KEY_TRANS_ENTRY1(I, I, I, 'I', 'I'), + KEY_TRANS_ENTRY1(O, O, O, 'O', 'O'), + KEY_TRANS_ENTRY1(P, P, P, 'P', 'P'), + KEY_TRANS_ENTRY1(OPENBRACE, LEFTBRACKET, LBRACKET, VK_OEM_4, '['), + KEY_TRANS_ENTRY1(CLOSEBRACE, RIGHTBRACKET, RBRACKET, VK_OEM_6, ']'), + KEY_TRANS_ENTRY0(ENTER, RETURN, RETURN, VK_RETURN, 13, "RETURN"), + KEY_TRANS_ENTRY1(LCONTROL, LCTRL, LCONTROL, VK_LCONTROL, 0), + KEY_TRANS_ENTRY1(A, A, A, 'A', 'A'), + KEY_TRANS_ENTRY1(S, S, S, 'S', 'S'), + KEY_TRANS_ENTRY1(D, D, D, 'D', 'D'), + KEY_TRANS_ENTRY1(F, F, F, 'F', 'F'), + KEY_TRANS_ENTRY1(G, G, G, 'G', 'G'), + KEY_TRANS_ENTRY1(H, H, H, 'H', 'H'), + KEY_TRANS_ENTRY1(J, J, J, 'J', 'J'), + KEY_TRANS_ENTRY1(K, K, K, 'K', 'K'), + KEY_TRANS_ENTRY1(L, L, L, 'L', 'L'), + KEY_TRANS_ENTRY1(COLON, SEMICOLON, SEMICOLON, VK_OEM_1, ';'), + KEY_TRANS_ENTRY1(QUOTE, APOSTROPHE, APOSTROPHE, VK_OEM_7, '\''), + KEY_TRANS_ENTRY1(TILDE, GRAVE, GRAVE, VK_OEM_3, '`'), + KEY_TRANS_ENTRY1(LSHIFT, LSHIFT, LSHIFT, VK_LSHIFT, 0), + KEY_TRANS_ENTRY1(BACKSLASH, BACKSLASH, BACKSLASH, VK_OEM_5, '\\'), +// KEY_TRANS_ENTRY1(BACKSLASH2, NONUSHASH, UNKNOWN, OEM_102, VK_OEM_102, '<'), // This is the additional key that ISO keyboards have over ANSI ones, located between left shift and Y. - KEY_TRANS_ENTRY1(BACKSLASH2, NONUSBACKSLASH, UNKNOWN, OEM_102, VK_OEM_102, None, '<'), - KEY_TRANS_ENTRY1(Z, Z, z, Z, 'Z', Z, 'Z'), - KEY_TRANS_ENTRY1(X, X, x, X, 'X', X, 'X'), - KEY_TRANS_ENTRY1(C, C, c, C, 'C', C, 'C'), - KEY_TRANS_ENTRY1(V, V, v, V, 'V', V, 'V'), - KEY_TRANS_ENTRY1(B, B, b, B, 'B', B, 'B'), - KEY_TRANS_ENTRY1(N, N, n, N, 'N', N, 'N'), - KEY_TRANS_ENTRY1(M, M, m, M, 'M', M, 'M'), - KEY_TRANS_ENTRY1(COMMA, COMMA, COMMA, COMMA, VK_OEM_COMMA, None, ','), - KEY_TRANS_ENTRY1(STOP, PERIOD, PERIOD, PERIOD, VK_OEM_PERIOD, None, '.'), - KEY_TRANS_ENTRY1(SLASH, SLASH, SLASH, SLASH, VK_OEM_2, None, '/'), - KEY_TRANS_ENTRY1(RSHIFT, RSHIFT, RSHIFT, RSHIFT, VK_RSHIFT, RightShift, 0), - KEY_TRANS_ENTRY1(ASTERISK, KP_MULTIPLY, KP_MULTIPLY, MULTIPLY, VK_MULTIPLY, Multiply, '*'), - KEY_TRANS_ENTRY1(LALT, LALT, LALT, LMENU, VK_LMENU, LeftMenu, 0), - KEY_TRANS_ENTRY1(SPACE, SPACE, SPACE, SPACE, VK_SPACE, Space, ' '), - KEY_TRANS_ENTRY1(CAPSLOCK, CAPSLOCK, CAPSLOCK, CAPITAL, VK_CAPITAL, CapitalLock, 0), - KEY_TRANS_ENTRY1(F1, F1, F1, F1, VK_F1, F1, 0), - KEY_TRANS_ENTRY1(F2, F2, F2, F2, VK_F2, F2, 0), - KEY_TRANS_ENTRY1(F3, F3, F3, F3, VK_F3, F3, 0), - KEY_TRANS_ENTRY1(F4, F4, F4, F4, VK_F4, F4, 0), - KEY_TRANS_ENTRY1(F5, F5, F5, F5, VK_F5, F5, 0), - KEY_TRANS_ENTRY1(F6, F6, F6, F6, VK_F6, F6, 0), - KEY_TRANS_ENTRY1(F7, F7, F7, F7, VK_F7, F7, 0), - KEY_TRANS_ENTRY1(F8, F8, F8, F8, VK_F8, F8, 0), - KEY_TRANS_ENTRY1(F9, F9, F9, F9, VK_F9, F9, 0), - KEY_TRANS_ENTRY1(F10, F10, F10, F10, VK_F10, F10, 0), - KEY_TRANS_ENTRY1(NUMLOCK, NUMLOCKCLEAR, NUMLOCKCLEAR, NUMLOCK, VK_NUMLOCK, NumberKeyLock, 0), - KEY_TRANS_ENTRY1(SCRLOCK, SCROLLLOCK, SCROLLLOCK, SCROLL, VK_SCROLL, Scroll, 0), - KEY_TRANS_ENTRY1(7_PAD, KP_7, KP_7, NUMPAD7, VK_NUMPAD7, NumberPad7, 0), - KEY_TRANS_ENTRY1(8_PAD, KP_8, KP_8, NUMPAD8, VK_NUMPAD8, NumberPad8, 0), - KEY_TRANS_ENTRY1(9_PAD, KP_9, KP_9, NUMPAD9, VK_NUMPAD9, NumberPad9, 0), - KEY_TRANS_ENTRY1(MINUS_PAD, KP_MINUS, KP_MINUS, SUBTRACT, VK_SUBTRACT, Subtract, 0), - KEY_TRANS_ENTRY1(4_PAD, KP_4, KP_4, NUMPAD4, VK_NUMPAD4, NumberPad4, 0), - KEY_TRANS_ENTRY1(5_PAD, KP_5, KP_5, NUMPAD5, VK_NUMPAD5, NumberPad5, 0), - KEY_TRANS_ENTRY1(6_PAD, KP_6, KP_6, NUMPAD6, VK_NUMPAD6, NumberPad6, 0), - KEY_TRANS_ENTRY1(PLUS_PAD, KP_PLUS, KP_PLUS, ADD, VK_ADD, Add, 0), - KEY_TRANS_ENTRY1(1_PAD, KP_1, KP_1, NUMPAD1, VK_NUMPAD1, NumberPad1, 0), - KEY_TRANS_ENTRY1(2_PAD, KP_2, KP_2, NUMPAD2, VK_NUMPAD2, NumberPad2, 0), - KEY_TRANS_ENTRY1(3_PAD, KP_3, KP_3, NUMPAD3, VK_NUMPAD3, NumberPad3, 0), - KEY_TRANS_ENTRY1(0_PAD, KP_0, KP_0, NUMPAD0, VK_NUMPAD0, NumberPad0, 0), - KEY_TRANS_ENTRY1(DEL_PAD, KP_PERIOD, KP_PERIOD, DECIMAL, VK_DECIMAL, Decimal, 0), - KEY_TRANS_ENTRY1(F11, F11, F11, F11, VK_F11, F11, 0), - KEY_TRANS_ENTRY1(F12, F12, F12, F12, VK_F12, F12, 0), - KEY_TRANS_ENTRY1(F13, F13, F13, F13, VK_F13, F13, 0), - KEY_TRANS_ENTRY1(F14, F14, F14, F14, VK_F14, F14, 0), - KEY_TRANS_ENTRY1(F15, F15, F15, F15, VK_F15, F15, 0), - KEY_TRANS_ENTRY1(ENTER_PAD, KP_ENTER, KP_ENTER, NUMPADENTER, VK_RETURN, None, 0), - KEY_TRANS_ENTRY1(RCONTROL, RCTRL, RCTRL, RCONTROL, VK_RCONTROL, RightControl, 0), - KEY_TRANS_ENTRY1(SLASH_PAD, KP_DIVIDE, KP_DIVIDE, DIVIDE, VK_DIVIDE, Divide, 0), - KEY_TRANS_ENTRY1(PRTSCR, PRINTSCREEN, PRINTSCREEN, SYSRQ, 0, Print, 0), - KEY_TRANS_ENTRY1(RALT, RALT, RALT, RMENU, VK_RMENU, RightMenu, 0), - KEY_TRANS_ENTRY1(HOME, HOME, HOME, HOME, VK_HOME, Home, 0), - KEY_TRANS_ENTRY1(UP, UP, UP, UP, VK_UP, Up, 0), - KEY_TRANS_ENTRY1(PGUP, PAGEUP, PAGEUP, PRIOR, VK_PRIOR, PageUp, 0), - KEY_TRANS_ENTRY1(LEFT, LEFT, LEFT, LEFT, VK_LEFT, Left, 0), - KEY_TRANS_ENTRY1(RIGHT, RIGHT, RIGHT, RIGHT, VK_RIGHT, Right, 0), - KEY_TRANS_ENTRY1(END, END, END, END, VK_END, End, 0), - KEY_TRANS_ENTRY1(DOWN, DOWN, DOWN, DOWN, VK_DOWN, Down, 0), - KEY_TRANS_ENTRY1(PGDN, PAGEDOWN, PAGEDOWN, NEXT, VK_NEXT, PageDown, 0), - KEY_TRANS_ENTRY1(INSERT, INSERT, INSERT, INSERT, VK_INSERT, Insert, 0), - KEY_TRANS_ENTRY0(DEL, DELETE, DELETE, DELETE, VK_DELETE, Delete, 0, "DELETE"), - KEY_TRANS_ENTRY1(LWIN, LGUI, LGUI, LWIN, VK_LWIN, LeftWindows, 0), - KEY_TRANS_ENTRY1(RWIN, RGUI, RGUI, RWIN, VK_RWIN, RightWindows, 0), - KEY_TRANS_ENTRY1(MENU, MENU, MENU, APPS, VK_APPS, Menu, 0), - KEY_TRANS_ENTRY1(PAUSE, PAUSE, PAUSE, PAUSE, VK_PAUSE, Pause, 0), - KEY_TRANS_ENTRY0(CANCEL, CANCEL, CANCEL, UNKNOWN, 0, Cancel, 0, "CANCEL"), - KEY_TRANS_ENTRY1(BS_PAD, KP_BACKSPACE, KP_BACKSPACE, UNKNOWN, 0, None, 0), - KEY_TRANS_ENTRY1(TAB_PAD, KP_TAB, KP_TAB, UNKNOWN, 0, None, 0), - KEY_TRANS_ENTRY1(00_PAD, KP_00, KP_00, UNKNOWN, 0, None, 0), - KEY_TRANS_ENTRY1(000_PAD, KP_000, KP_000, UNKNOWN, 0, None, 0), - KEY_TRANS_ENTRY1(COMMA_PAD, KP_COMMA, KP_COMMA, NUMPADCOMMA, 0, None, 0), - KEY_TRANS_ENTRY1(EQUALS_PAD, KP_EQUALS, KP_EQUALS, NUMPADEQUALS, 0, None, 0), - - // New keys introduced in Windows 2000. These have no MAME codes to - // preserve compatibility with old config files that may refer to them - // as e.g. FORWARD instead of e.g. KEYCODE_WEBFORWARD. They need table - // entries anyway because otherwise they aren't recognized when - // GetAsyncKeyState polling is used (as happens currently when MAME is - // paused). Some codes are missing because the mapping to vkey codes - // isn't clear, and MapVirtualKey is no help. - KEY_TRANS_ENTRY1(OTHER_SWITCH, MUTE, MUTE, MUTE, VK_VOLUME_MUTE, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, VOLUMEDOWN, VOLUMEDOWN, VOLUMEDOWN, VK_VOLUME_DOWN, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, VOLUMEUP, VOLUMEUP, VOLUMEUP, VK_VOLUME_UP, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, AC_HOME, AC_HOME, WEBHOME, VK_BROWSER_HOME, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, AC_SEARCH, AC_SEARCH, WEBSEARCH, VK_BROWSER_SEARCH, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, AC_BOOKMARKS, AC_BOOKMARKS, WEBFAVORITES, VK_BROWSER_FAVORITES, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, AC_REFRESH, AC_REFRESH, WEBREFRESH, VK_BROWSER_REFRESH, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, AC_STOP, AC_STOP, WEBSTOP, VK_BROWSER_STOP, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, AC_FORWARD, AC_FORWARD, WEBFORWARD, VK_BROWSER_FORWARD, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, AC_BACK, AC_BACK, WEBBACK, VK_BROWSER_BACK, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, MAIL, MAIL, MAIL, VK_LAUNCH_MAIL, None, 0), - KEY_TRANS_ENTRY1(OTHER_SWITCH, MEDIASELECT, MEDIASELECT, MEDIASELECT, VK_LAUNCH_MEDIA_SELECT, None, 0), - KEY_TRANS_ENTRY0(INVALID, UNKNOWN, UNKNOWN, ESCAPE, 0, None, 0, "INVALID") + KEY_TRANS_ENTRY1(BACKSLASH2, NONUSBACKSLASH, OEM_102, VK_OEM_102, '<'), + KEY_TRANS_ENTRY1(Z, Z, Z, 'Z', 'Z'), + KEY_TRANS_ENTRY1(X, X, X, 'X', 'X'), + KEY_TRANS_ENTRY1(C, C, C, 'C', 'C'), + KEY_TRANS_ENTRY1(V, V, V, 'V', 'V'), + KEY_TRANS_ENTRY1(B, B, B, 'B', 'B'), + KEY_TRANS_ENTRY1(N, N, N, 'N', 'N'), + KEY_TRANS_ENTRY1(M, M, M, 'M', 'M'), + KEY_TRANS_ENTRY1(COMMA, COMMA, COMMA, VK_OEM_COMMA, ','), + KEY_TRANS_ENTRY1(STOP, PERIOD, PERIOD, VK_OEM_PERIOD, '.'), + KEY_TRANS_ENTRY1(SLASH, SLASH, SLASH, VK_OEM_2, '/'), + KEY_TRANS_ENTRY1(RSHIFT, RSHIFT, RSHIFT, VK_RSHIFT, 0), + KEY_TRANS_ENTRY1(ASTERISK, KP_MULTIPLY, MULTIPLY, VK_MULTIPLY, '*'), + KEY_TRANS_ENTRY1(LALT, LALT, LMENU, VK_LMENU, 0), + KEY_TRANS_ENTRY1(SPACE, SPACE, SPACE, VK_SPACE, ' '), + KEY_TRANS_ENTRY1(CAPSLOCK, CAPSLOCK, CAPITAL, VK_CAPITAL, 0), + KEY_TRANS_ENTRY1(F1, F1, F1, VK_F1, 0), + KEY_TRANS_ENTRY1(F2, F2, F2, VK_F2, 0), + KEY_TRANS_ENTRY1(F3, F3, F3, VK_F3, 0), + KEY_TRANS_ENTRY1(F4, F4, F4, VK_F4, 0), + KEY_TRANS_ENTRY1(F5, F5, F5, VK_F5, 0), + KEY_TRANS_ENTRY1(F6, F6, F6, VK_F6, 0), + KEY_TRANS_ENTRY1(F7, F7, F7, VK_F7, 0), + KEY_TRANS_ENTRY1(F8, F8, F8, VK_F8, 0), + KEY_TRANS_ENTRY1(F9, F9, F9, VK_F9, 0), + KEY_TRANS_ENTRY1(F10, F10, F10, VK_F10, 0), + KEY_TRANS_ENTRY1(NUMLOCK, NUMLOCKCLEAR, NUMLOCK, VK_NUMLOCK, 0), + KEY_TRANS_ENTRY1(SCRLOCK, SCROLLLOCK, SCROLL, VK_SCROLL, 0), + KEY_TRANS_ENTRY1(7_PAD, KP_7, NUMPAD7, VK_NUMPAD7, 0), + KEY_TRANS_ENTRY1(8_PAD, KP_8, NUMPAD8, VK_NUMPAD8, 0), + KEY_TRANS_ENTRY1(9_PAD, KP_9, NUMPAD9, VK_NUMPAD9, 0), + KEY_TRANS_ENTRY1(MINUS_PAD, KP_MINUS, SUBTRACT, VK_SUBTRACT, 0), + KEY_TRANS_ENTRY1(4_PAD, KP_4, NUMPAD4, VK_NUMPAD4, 0), + KEY_TRANS_ENTRY1(5_PAD, KP_5, NUMPAD5, VK_NUMPAD5, 0), + KEY_TRANS_ENTRY1(6_PAD, KP_6, NUMPAD6, VK_NUMPAD6, 0), + KEY_TRANS_ENTRY1(PLUS_PAD, KP_PLUS, ADD, VK_ADD, 0), + KEY_TRANS_ENTRY1(1_PAD, KP_1, NUMPAD1, VK_NUMPAD1, 0), + KEY_TRANS_ENTRY1(2_PAD, KP_2, NUMPAD2, VK_NUMPAD2, 0), + KEY_TRANS_ENTRY1(3_PAD, KP_3, NUMPAD3, VK_NUMPAD3, 0), + KEY_TRANS_ENTRY1(0_PAD, KP_0, NUMPAD0, VK_NUMPAD0, 0), + KEY_TRANS_ENTRY1(DEL_PAD, KP_PERIOD, DECIMAL, VK_DECIMAL, 0), + KEY_TRANS_ENTRY1(F11, F11, F11, VK_F11, 0), + KEY_TRANS_ENTRY1(F12, F12, F12, VK_F12, 0), + KEY_TRANS_ENTRY1(F13, F13, F13, VK_F13, 0), + KEY_TRANS_ENTRY1(F14, F14, F14, VK_F14, 0), + KEY_TRANS_ENTRY1(F15, F15, F15, VK_F15, 0), + KEY_TRANS_ENTRY1(ENTER_PAD, KP_ENTER, NUMPADENTER, VK_RETURN, 0), + KEY_TRANS_ENTRY1(RCONTROL, RCTRL, RCONTROL, VK_RCONTROL, 0), + KEY_TRANS_ENTRY1(SLASH_PAD, KP_DIVIDE, DIVIDE, VK_DIVIDE, 0), + KEY_TRANS_ENTRY1(PRTSCR, PRINTSCREEN, SYSRQ, 0, 0), + KEY_TRANS_ENTRY1(RALT, RALT, RMENU, VK_RMENU, 0), + KEY_TRANS_ENTRY1(HOME, HOME, HOME, VK_HOME, 0), + KEY_TRANS_ENTRY1(UP, UP, UP, VK_UP, 0), + KEY_TRANS_ENTRY1(PGUP, PAGEUP, PRIOR, VK_PRIOR, 0), + KEY_TRANS_ENTRY1(LEFT, LEFT, LEFT, VK_LEFT, 0), + KEY_TRANS_ENTRY1(RIGHT, RIGHT, RIGHT, VK_RIGHT, 0), + KEY_TRANS_ENTRY1(END, END, END, VK_END, 0), + KEY_TRANS_ENTRY1(DOWN, DOWN, DOWN, VK_DOWN, 0), + KEY_TRANS_ENTRY1(PGDN, PAGEDOWN, NEXT, VK_NEXT, 0), + KEY_TRANS_ENTRY1(INSERT, INSERT, INSERT, VK_INSERT, 0), + KEY_TRANS_ENTRY0(DEL, DELETE, DELETE, VK_DELETE, 0, "DELETE"), + KEY_TRANS_ENTRY1(LWIN, LGUI, LWIN, VK_LWIN, 0), + KEY_TRANS_ENTRY1(RWIN, RGUI, RWIN, VK_RWIN, 0), + KEY_TRANS_ENTRY1(MENU, MENU, APPS, VK_APPS, 0), + KEY_TRANS_ENTRY1(PAUSE, PAUSE, PAUSE, VK_PAUSE, 0), + KEY_TRANS_ENTRY1(CANCEL, CANCEL, UNKNOWN, 0, 0), + KEY_TRANS_ENTRY1(BS_PAD, KP_BACKSPACE, UNKNOWN, 0, 0), + KEY_TRANS_ENTRY1(TAB_PAD, KP_TAB, UNKNOWN, 0, 0), + KEY_TRANS_ENTRY1(00_PAD, KP_00, UNKNOWN, 0, 0), + KEY_TRANS_ENTRY1(000_PAD, KP_000, UNKNOWN, 0, 0), + KEY_TRANS_ENTRY1(COMMA_PAD, KP_COMMA, NUMPADCOMMA, VK_SEPARATOR, 0), + KEY_TRANS_ENTRY1(EQUALS_PAD, KP_EQUALS, NUMPADEQUALS, VK_OEM_NEC_EQUAL, 0), + + // keys that have no specific MAME input item IDs + KEY_TRANS_ENTRY0(OTHER_SWITCH, AUDIONEXT, NEXTTRACK, VK_MEDIA_NEXT_TRACK, 0, "AUDIONEXT"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, AUDIOMUTE, MUTE, VK_VOLUME_MUTE, 0, "VOLUMEMUTE"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, AUDIOPLAY, PLAYPAUSE, VK_MEDIA_PLAY_PAUSE, 0, "AUDIOPLAY"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, AUDIOSTOP, MEDIASTOP, VK_MEDIA_STOP, 0, "AUDIOSTOP"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, VOLUMEDOWN, VOLUMEDOWN, VK_VOLUME_DOWN, 0, "VOLUMEDOWN"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, VOLUMEUP, VOLUMEUP, VK_VOLUME_UP, 0, "VOLUMEUP"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_HOME, WEBHOME, VK_BROWSER_HOME, 0, "NAVHOME"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_SEARCH, WEBSEARCH, VK_BROWSER_SEARCH, 0, "NAVSEARCH"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_BOOKMARKS, WEBFAVORITES, VK_BROWSER_FAVORITES, 0, "NAVFAVORITES"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_REFRESH, WEBREFRESH, VK_BROWSER_REFRESH, 0, "NAVREFRESH"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_STOP, WEBSTOP, VK_BROWSER_STOP, 0, "NAVSTOP"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_FORWARD, WEBFORWARD, VK_BROWSER_FORWARD, 0, "NAVFORWARD"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_BACK, WEBBACK, VK_BROWSER_BACK, 0, "NAVBACK"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, MAIL, MAIL, VK_LAUNCH_MAIL, 0, "MAIL"), + KEY_TRANS_ENTRY0(OTHER_SWITCH, MEDIASELECT, MEDIASELECT, VK_LAUNCH_MEDIA_SELECT, 0, "MEDIASEL"), + + // sentinel + KEY_TRANS_ENTRY0(INVALID, UNKNOWN, UNKNOWN, 0, 0, "INVALID") }; // The private constructor to create the default instance keyboard_trans_table::keyboard_trans_table() { m_table = s_default_table; - m_table_size = ARRAY_LENGTH(s_default_table); + m_table_size = std::size(s_default_table); } -#else + +#else // defined(OSD_WINDOWS) || defined(OSD_SDL) + keyboard_trans_table::keyboard_trans_table() { m_table = nullptr; m_table_size = 0; } -#endif +#endif // defined(OSD_WINDOWS) || defined(OSD_SDL) + + // public constructor to allow creation of non-default instances keyboard_trans_table::keyboard_trans_table(std::unique_ptr<key_trans_entry[]> entries, unsigned int size) { @@ -231,14 +232,12 @@ input_item_id keyboard_trans_table::lookup_mame_code(const char *scode) const } // Windows specific lookup methods -#if defined(OSD_WINDOWS) || defined(OSD_UWP) +#if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) input_item_id keyboard_trans_table::map_di_scancode_to_itemid(int scancode) const { - int tablenum; - // scan the table for a match - for (tablenum = 0; tablenum < m_table_size; tablenum++) + for (int tablenum = 0; tablenum < m_table_size; tablenum++) if (m_table[tablenum].scan_code == scancode) return m_table[tablenum].mame_key; @@ -246,10 +245,6 @@ input_item_id keyboard_trans_table::map_di_scancode_to_itemid(int scancode) cons return ITEM_ID_OTHER_SWITCH; } -#endif - -#if defined(OSD_WINDOWS) - //============================================================ // wininput_vkey_for_mame_code //============================================================ @@ -259,48 +254,53 @@ int keyboard_trans_table::vkey_for_mame_code(input_code code) const // only works for keyboard switches if (code.device_class() == DEVICE_CLASS_KEYBOARD && code.item_class() == ITEM_CLASS_SWITCH) { - input_item_id id = code.item_id(); - int tablenum; + const input_item_id id = code.item_id(); // scan the table for a match - for (tablenum = 0; tablenum < m_table_size; tablenum++) + for (int tablenum = 0; tablenum < m_table_size; tablenum++) if (m_table[tablenum].mame_key == id) return m_table[tablenum].virtual_key; } return 0; } -#endif - -#if defined(OSD_UWP) +#endif // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) -const char* keyboard_trans_table::ui_label_for_mame_key(input_item_id itemid) const +input_module_base::input_module_base(const char *type, const char* name) : + osd_module(type, name), + m_clock(), + m_last_poll(timepoint_type::min()), + m_background_input(false), + m_options(nullptr), + m_manager(nullptr) { - // scan the table for a match - for (int tablenum = 0; tablenum < m_table_size; tablenum++) - if (m_table[tablenum].mame_key == itemid) - return m_table[tablenum].ui_name; - - // We didn't find one - return nullptr; } -#endif - - -int input_module_base::init(const osd_options &options) +int input_module_base::init(osd_interface &osd, const osd_options &options) { m_options = &options; - m_mouse_enabled = options.mouse(); - m_lightgun_enabled = options.lightgun(); + // don't enable background input when debugging + m_background_input = !options.debug() && options.background_input(); + + return 0; +} + +void input_module_base::input_init(running_machine &machine) +{ + m_manager = &machine.input(); +} - int result = init_internal(); - if (result != 0) - return result; +void input_module_base::poll_if_necessary(bool relative_reset) +{ + timepoint_type const now = m_clock.now(); + if (relative_reset || (now >= (m_last_poll + std::chrono::milliseconds(MIN_POLLING_INTERVAL)))) + { + // grab the current time + m_last_poll = now; - m_input_paused = false; - m_input_enabled = true; + before_poll(); - return 0; + poll(relative_reset); + } } diff --git a/src/osd/modules/input/input_common.h b/src/osd/modules/input/input_common.h index b4005a6b76a..73ddd2bf936 100644 --- a/src/osd/modules/input/input_common.h +++ b/src/osd/modules/input/input_common.h @@ -7,20 +7,26 @@ // SDLMAME by Olivier Galibert and R. Belmont // //============================================================ +#ifndef MAME_OSD_INPUT_INPUT_COMMON_H +#define MAME_OSD_INPUT_INPUT_COMMON_H -#ifndef INPUT_COMMON_H_ -#define INPUT_COMMON_H_ +#pragma once #include "input_module.h" -#include "inputdev.h" +#include "interface/inputdev.h" +#include "interface/inputman.h" +#include "modules/osdmodule.h" +#include "util/strformat.h" + +#include <algorithm> +#include <cassert> #include <chrono> +#include <functional> #include <memory> #include <mutex> #include <queue> -#include <algorithm> -#include <functional> //============================================================ @@ -41,225 +47,66 @@ enum #define MAX_HATS 8 #define MAX_POV 4 -/**************************************************************************** -* DirectInput compatible keyboard scan codes -****************************************************************************/ -#define KEY_UNKNOWN 0x00 -#define KEY_ESCAPE 0x01 -#define KEY_1 0x02 -#define KEY_2 0x03 -#define KEY_3 0x04 -#define KEY_4 0x05 -#define KEY_5 0x06 -#define KEY_6 0x07 -#define KEY_7 0x08 -#define KEY_8 0x09 -#define KEY_9 0x0A -#define KEY_0 0x0B -#define KEY_MINUS 0x0C /* - on main keyboard */ -#define KEY_EQUALS 0x0D -#define KEY_BACK 0x0E /* backspace */ -#define KEY_TAB 0x0F -#define KEY_Q 0x10 -#define KEY_W 0x11 -#define KEY_E 0x12 -#define KEY_R 0x13 -#define KEY_T 0x14 -#define KEY_Y 0x15 -#define KEY_U 0x16 -#define KEY_I 0x17 -#define KEY_O 0x18 -#define KEY_P 0x19 -#define KEY_LBRACKET 0x1A -#define KEY_RBRACKET 0x1B -#define KEY_RETURN 0x1C /* Enter on main keyboard */ -#define KEY_LCONTROL 0x1D -#define KEY_A 0x1E -#define KEY_S 0x1F -#define KEY_D 0x20 -#define KEY_F 0x21 -#define KEY_G 0x22 -#define KEY_H 0x23 -#define KEY_J 0x24 -#define KEY_K 0x25 -#define KEY_L 0x26 -#define KEY_SEMICOLON 0x27 -#define KEY_APOSTROPHE 0x28 -#define KEY_GRAVE 0x29 /* accent grave */ -#define KEY_LSHIFT 0x2A -#define KEY_BACKSLASH 0x2B -#define KEY_Z 0x2C -#define KEY_X 0x2D -#define KEY_C 0x2E -#define KEY_V 0x2F -#define KEY_B 0x30 -#define KEY_N 0x31 -#define KEY_M 0x32 -#define KEY_COMMA 0x33 -#define KEY_PERIOD 0x34 /* . on main keyboard */ -#define KEY_SLASH 0x35 /* / on main keyboard */ -#define KEY_RSHIFT 0x36 -#define KEY_MULTIPLY 0x37 /* * on numeric keypad */ -#define KEY_LMENU 0x38 /* left Alt */ -#define KEY_SPACE 0x39 -#define KEY_CAPITAL 0x3A -#define KEY_F1 0x3B -#define KEY_F2 0x3C -#define KEY_F3 0x3D -#define KEY_F4 0x3E -#define KEY_F5 0x3F -#define KEY_F6 0x40 -#define KEY_F7 0x41 -#define KEY_F8 0x42 -#define KEY_F9 0x43 -#define KEY_F10 0x44 -#define KEY_NUMLOCK 0x45 -#define KEY_SCROLL 0x46 /* Scroll Lock */ -#define KEY_NUMPAD7 0x47 -#define KEY_NUMPAD8 0x48 -#define KEY_NUMPAD9 0x49 -#define KEY_SUBTRACT 0x4A /* - on numeric keypad */ -#define KEY_NUMPAD4 0x4B -#define KEY_NUMPAD5 0x4C -#define KEY_NUMPAD6 0x4D -#define KEY_ADD 0x4E /* + on numeric keypad */ -#define KEY_NUMPAD1 0x4F -#define KEY_NUMPAD2 0x50 -#define KEY_NUMPAD3 0x51 -#define KEY_NUMPAD0 0x52 -#define KEY_DECIMAL 0x53 /* . on numeric keypad */ -#define KEY_OEM_102 0x56 /* <> or \| on RT 102-key keyboard (Non-U.S.) */ -#define KEY_F11 0x57 -#define KEY_F12 0x58 -#define KEY_F13 0x64 /* (NEC PC98) */ -#define KEY_F14 0x65 /* (NEC PC98) */ -#define KEY_F15 0x66 /* (NEC PC98) */ -#define KEY_KANA 0x70 /* (Japanese keyboard) */ -#define KEY_ABNT_C1 0x73 /* /? on Brazilian keyboard */ -#define KEY_CONVERT 0x79 /* (Japanese keyboard) */ -#define KEY_NOCONVERT 0x7B /* (Japanese keyboard) */ -#define KEY_YEN 0x7D /* (Japanese keyboard) */ -#define KEY_ABNT_C2 0x7E /* Numpad . on Brazilian keyboard */ -#define KEY_NUMPADEQUALS 0x8D /* = on numeric keypad (NEC PC98) */ -#define KEY_PREVTRACK 0x90 /* Previous Track (DIK_CIRCUMFLEX on Japanese keyboard) */ -#define KEY_AT 0x91 /* (NEC PC98) */ -#define KEY_COLON 0x92 /* (NEC PC98) */ -#define KEY_UNDERLINE 0x93 /* (NEC PC98) */ -#define KEY_KANJI 0x94 /* (Japanese keyboard) */ -#define KEY_STOP 0x95 /* (NEC PC98) */ -#define KEY_AX 0x96 /* (Japan AX) */ -#define KEY_UNLABELED 0x97 /* (J3100) */ -#define KEY_NEXTTRACK 0x99 /* Next Track */ -#define KEY_NUMPADENTER 0x9C /* Enter on numeric keypad */ -#define KEY_RCONTROL 0x9D -#define KEY_MUTE 0xA0 /* Mute */ -#define KEY_CALCULATOR 0xA1 /* Calculator */ -#define KEY_PLAYPAUSE 0xA2 /* Play / Pause */ -#define KEY_MEDIASTOP 0xA4 /* Media Stop */ -#define KEY_VOLUMEDOWN 0xAE /* Volume - */ -#define KEY_VOLUMEUP 0xB0 /* Volume + */ -#define KEY_WEBHOME 0xB2 /* Web home */ -#define KEY_NUMPADCOMMA 0xB3 /* , on numeric keypad (NEC PC98) */ -#define KEY_DIVIDE 0xB5 /* / on numeric keypad */ -#define KEY_SYSRQ 0xB7 -#define KEY_RMENU 0xB8 /* right Alt */ -#define KEY_PAUSE 0xC5 /* Pause */ -#define KEY_HOME 0xC7 /* Home on arrow keypad */ -#define KEY_UP 0xC8 /* UpArrow on arrow keypad */ -#define KEY_PRIOR 0xC9 /* PgUp on arrow keypad */ -#define KEY_LEFT 0xCB /* LeftArrow on arrow keypad */ -#define KEY_RIGHT 0xCD /* RightArrow on arrow keypad */ -#define KEY_END 0xCF /* End on arrow keypad */ -#define KEY_DOWN 0xD0 /* DownArrow on arrow keypad */ -#define KEY_NEXT 0xD1 /* PgDn on arrow keypad */ -#define KEY_INSERT 0xD2 /* Insert on arrow keypad */ -#define KEY_DELETE 0xD3 /* Delete on arrow keypad */ -#define KEY_LWIN 0xDB /* Left Windows key */ -#define KEY_RWIN 0xDC /* Right Windows key */ -#define KEY_APPS 0xDD /* AppMenu key */ -#define KEY_POWER 0xDE /* System Power */ -#define KEY_SLEEP 0xDF /* System Sleep */ -#define KEY_WAKE 0xE3 /* System Wake */ -#define KEY_WEBSEARCH 0xE5 /* Web Search */ -#define KEY_WEBFAVORITES 0xE6 /* Web Favorites */ -#define KEY_WEBREFRESH 0xE7 /* Web Refresh */ -#define KEY_WEBSTOP 0xE8 /* Web Stop */ -#define KEY_WEBFORWARD 0xE9 /* Web Forward */ -#define KEY_WEBBACK 0xEA /* Web Back */ -#define KEY_MYCOMPUTER 0xEB /* My Computer */ -#define KEY_MAIL 0xEC /* Mail */ -#define KEY_MEDIASELECT 0xED /* Media Select */ //============================================================ // device_info //============================================================ -class input_device_list; - class device_info { - friend input_device_list; - private: - std::string m_name; - std::string m_id; - input_device * m_device; - running_machine & m_machine; + const std::string m_name; + const std::string m_id; input_module & m_module; - input_device_class m_deviceclass; public: // Constructor - device_info(running_machine &machine, const char *name, const char *id, input_device_class deviceclass, input_module &module) - : m_name(name), - m_id(id), - m_device(nullptr), - m_machine(machine), - m_module(module), - m_deviceclass(deviceclass) + device_info(std::string &&name, std::string &&id, input_module &module) : + m_name(std::move(name)), + m_id(std::move(id)), + m_module(module) { } // Destructor - virtual ~device_info() {} + virtual ~device_info() = default; // Getters - running_machine & machine() const { return m_machine; } - const char * name() const { return m_name.c_str(); } - const char * id() const { return m_id.c_str(); } - input_device * device() const { return m_device; } - input_module & module() const { return m_module; } - input_device_class deviceclass() const { return m_deviceclass; } + const std::string &name() const { return m_name; } + const std::string &id() const { return m_id; } + input_module &module() const { return m_module; } // Poll and reset methods - virtual void poll() {}; + virtual void poll(bool relative_reset) = 0; virtual void reset() = 0; + virtual void configure(osd::input_device &device) = 0; }; + //============================================================ // event_based_device //============================================================ -#define DEFAULT_EVENT_QUEUE_SIZE 20 - template <class TEvent> class event_based_device : public device_info { private: - std::queue<TEvent> m_event_queue; + static inline constexpr unsigned DEFAULT_EVENT_QUEUE_SIZE = 64; + + std::queue<TEvent> m_event_queue; protected: std::mutex m_device_lock; - virtual void process_event(TEvent &ev) = 0; + virtual void process_event(TEvent const &ev) = 0; public: - event_based_device(running_machine &machine, const char *name, const char *id, input_device_class deviceclass, input_module &module) - : device_info(machine, name, id, deviceclass, module) + event_based_device(std::string &&name, std::string &&id, input_module &module) : + device_info(std::move(name), std::move(id), module) { } - void queue_events(const TEvent *events, int count) + void queue_events(TEvent const *events, int count) { std::lock_guard<std::mutex> scope_lock(m_device_lock); for (int i = 0; i < count; i++) @@ -270,38 +117,46 @@ public: m_event_queue.pop(); } - void virtual poll() override + virtual void poll(bool relative_reset) override { std::lock_guard<std::mutex> scope_lock(m_device_lock); // Process each event until the queue is empty while (!m_event_queue.empty()) { - TEvent &next_event = m_event_queue.front(); - process_event(next_event); + process_event(m_event_queue.front()); m_event_queue.pop(); } } + + virtual void reset() override + { + std::lock_guard<std::mutex> scope_lock(m_device_lock); + std::queue<TEvent>().swap(m_event_queue); + } }; + //============================================================ // input_device_list class //============================================================ +template <typename Info> class input_device_list { private: - std::vector<std::unique_ptr<device_info>> m_list; + std::vector<std::unique_ptr<Info> > m_list; public: - size_t size() const { return m_list.size(); } - auto begin() { return m_list.begin(); } - auto end() { return m_list.end(); } + auto size() const { return m_list.size(); } + auto empty() const { return m_list.empty(); } + auto begin() const { return m_list.begin(); } + auto end() const { return m_list.end(); } - void poll_devices() + void poll_devices(bool relative_reset) { for (auto &device: m_list) - device->poll(); + device->poll(relative_reset); } void reset_devices() @@ -310,17 +165,11 @@ public: device->reset(); } - void free_device(device_info* devinfo) - { - // find the device to remove - auto device_matches = [devinfo](std::unique_ptr<device_info> &device) { return devinfo == device.get(); }; - m_list.erase(std::remove_if(std::begin(m_list), std::end(m_list), device_matches), m_list.end()); - } - - void for_each_device(std::function<void (device_info*)> action) + template <typename T> + void for_each_device(T &&action) { for (auto &device: m_list) - action(device.get()); + action(*device); } void free_all_devices() @@ -329,46 +178,34 @@ public: m_list.pop_back(); } - template <typename TActual, typename... TArgs> - TActual* create_device(running_machine &machine, const char *name, const char *id, input_module &module, TArgs&&... args) + template <typename Actual> + Actual &add_device(std::unique_ptr<Actual> &&devinfo) { - // allocate the device object - auto devinfo = std::make_unique<TActual>(machine, name, id, module, std::forward<TArgs>(args)...); - - return add_device(machine, std::move(devinfo)); - } - - template <typename TActual> - TActual* add_device(running_machine &machine, std::unique_ptr<TActual> devinfo) - { - // Add the device to the machine - devinfo->m_device = machine.input().device_class(devinfo->deviceclass()).add_device(devinfo->name(), devinfo->id(), devinfo.get()); - - // append us to the list - m_list.push_back(std::move(devinfo)); - - return static_cast<TActual*>(m_list.back().get()); + // append us to the list and return reference + Actual &result = *devinfo; + m_list.emplace_back(std::move(devinfo)); + return result; } }; + // keyboard translation table -struct key_trans_entry { +struct key_trans_entry +{ input_item_id mame_key; -#if defined(OSD_SDL) +#if defined(OSD_SDL) || defined(SDLMAME_WIN32) int sdl_scancode; -#elif defined(OSD_WINDOWS) - int scan_code; +#endif +#if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) + uint16_t scan_code; unsigned char virtual_key; -#elif defined(OSD_UWP) - int scan_code; - Windows::System::VirtualKey virtual_key; #endif char ascii_key; - char const * mame_key_name; - char * ui_name; + char const * mame_key_name; + char const * ui_name; }; class keyboard_trans_table @@ -381,7 +218,7 @@ private: std::unique_ptr<key_trans_entry[]> m_custom_table; key_trans_entry * m_table; - uint32_t m_table_size; + uint32_t m_table_size; public: // constructor @@ -394,12 +231,9 @@ public: input_item_id lookup_mame_code(const char * scode) const; int lookup_mame_index(const char * scode) const; -#if defined(OSD_WINDOWS) +#if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) input_item_id map_di_scancode_to_itemid(int di_scancode) const; int vkey_for_mame_code(input_code code) const; -#elif defined(OSD_UWP) - input_item_id map_di_scancode_to_itemid(int di_scancode) const; - const char* ui_label_for_mame_key(input_item_id code) const; #endif static keyboard_trans_table& instance() @@ -411,155 +245,153 @@ public: key_trans_entry & operator [](int i) const { return m_table[i]; } }; + //============================================================ // input_module_base - base class for input modules //============================================================ -class osd_options; - -typedef std::chrono::high_resolution_clock clock_type; -typedef std::chrono::time_point<std::chrono::high_resolution_clock> timepoint_type; - -// 10 milliseconds polling interval -#define MIN_POLLING_INTERVAL 10 - -class input_module_base : public input_module +class input_module_base : public osd_module, public input_module { -public: - input_module_base(const char *type, const char* name) - : input_module(type, name), - m_input_enabled(false), - m_mouse_enabled(false), - m_lightgun_enabled(false), - m_input_paused(false), - m_options(nullptr) - { - } - private: - bool m_input_enabled; - bool m_mouse_enabled; - bool m_lightgun_enabled; - bool m_input_paused; - const osd_options * m_options; - input_device_list m_devicelist; + // 10 milliseconds polling interval + static constexpr inline unsigned MIN_POLLING_INTERVAL = 2; + + using clock_type = std::chrono::high_resolution_clock; + using timepoint_type = std::chrono::time_point<std::chrono::high_resolution_clock>; + clock_type m_clock; timepoint_type m_last_poll; + bool m_background_input; + const osd_options * m_options; + osd::input_manager * m_manager; + + virtual void poll(bool relative_reset) = 0; protected: - void set_mouse_enabled(bool value) { m_mouse_enabled = value; } + input_module_base(char const *type, char const *name); + + osd::input_manager & manager() { assert(m_manager); return *m_manager; } + const osd_options * options() const { return m_options; } + bool background_input() const { return m_background_input; } + + virtual void before_poll() { } public: + virtual int init(osd_interface &osd, const osd_options &options) override; - const osd_options * options() const { return m_options; } - input_device_list * devicelist() { return &m_devicelist; } - bool input_enabled() const { return m_input_enabled; } - bool input_paused() const { return m_input_paused; } - bool mouse_enabled() const { return m_mouse_enabled; } - bool lightgun_enabled() const { return m_lightgun_enabled; } + virtual void input_init(running_machine &machine) override; + virtual void poll_if_necessary(bool relative_reset) override; + + virtual void reset_devices() = 0; // SDL OSD uses this to forcibly release keys +}; - int init(const osd_options &options) override; - void poll_if_necessary(running_machine &machine) override +//============================================================ +// input_module_impl - base class for input modules +//============================================================ + +template <typename Info, typename OsdImpl> +class input_module_impl : public input_module_base +{ +public: + virtual void exit() override { - auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(m_clock.now() - m_last_poll); - if (elapsed.count() >= MIN_POLLING_INTERVAL) - { - poll(machine); - } + devicelist().free_all_devices(); } - virtual void poll(running_machine &machine) + virtual int init(osd_interface &osd, const osd_options &options) override { - // ignore if not enabled - if (m_input_enabled) - { - // grab the current time - m_last_poll = m_clock.now(); + m_osd = dynamic_cast<OsdImpl *>(&osd); + if (!m_osd) + return -1; - before_poll(machine); + return input_module_base::init(osd, options); + } - // track if mouse/lightgun is enabled, for mouse hiding purposes - m_mouse_enabled = machine.input().device_class(DEVICE_CLASS_MOUSE).enabled(); - m_lightgun_enabled = machine.input().device_class(DEVICE_CLASS_LIGHTGUN).enabled(); - } + virtual void reset_devices() override { devicelist().reset_devices(); } - // poll all of the devices - if (should_poll_devices(machine)) - { - m_devicelist.poll_devices(); - } - else - { - m_devicelist.reset_devices(); - } +protected: + using input_module_base::input_module_base; + + input_device_list<Info> &devicelist() { return m_devicelist; } + OsdImpl &osd() { assert(m_osd); return *m_osd; } + + virtual void before_poll() override + { + // periodically process events, in case they're not coming through + // this also will make sure the mouse state is up-to-date + osd().process_events(); } - virtual void pause() override + virtual bool should_poll_devices() { - // keep track of the paused state - m_input_paused = true; + return background_input() || osd().has_focus(); } - virtual void resume() override + template <typename Actual, typename... Params> + Actual &create_device(input_device_class deviceclass, std::string &&name, std::string &&id, Params &&... args) { - // keep track of the paused state - m_input_paused = false; + // allocate the device object and add it to the input manager + return add_device( + deviceclass, + std::make_unique<Actual>(std::move(name), std::move(id), *this, std::forward<Params>(args)...)); } - virtual void exit() override + template <typename Actual> + Actual &add_device(input_device_class deviceclass, std::unique_ptr<Actual> &&devinfo) { - devicelist()->free_all_devices(); + // add it to the input manager and append it to the list + osd::input_device &osddev = manager().add_device(deviceclass, devinfo->name(), devinfo->id(), devinfo.get()); + devinfo->configure(osddev); + return devicelist().add_device(std::move(devinfo)); } -protected: - virtual int init_internal() { return 0; } - virtual bool should_poll_devices(running_machine &machine) = 0; - virtual void before_poll(running_machine &machine) {} +private: + virtual void poll(bool relative_reset) override final + { + // poll all of the devices + if (should_poll_devices()) + m_devicelist.poll_devices(relative_reset); + else + m_devicelist.reset_devices(); + } + + input_device_list<Info> m_devicelist; + OsdImpl *m_osd = nullptr; }; + template <class TItem> int generic_button_get_state(void *device_internal, void *item_internal) { - device_info *devinfo = static_cast<device_info *>(device_internal); - TItem *itemdata = static_cast<TItem*>(item_internal); - // return the current state - devinfo->module().poll_if_necessary(devinfo->machine()); - return *itemdata >> 7; + return *reinterpret_cast<TItem const *>(item_internal) >> 7; } + template <class TItem> int generic_axis_get_state(void *device_internal, void *item_internal) { - device_info *devinfo = static_cast<device_info *>(device_internal); - TItem *axisdata = static_cast<TItem*>(item_internal); - - // return the current state - devinfo->module().poll_if_necessary(devinfo->machine()); - return *axisdata; + return *reinterpret_cast<TItem const *>(item_internal); } + //============================================================ // default_button_name //============================================================ -inline static const char *default_button_name(int which) +inline std::string default_button_name(int which) { - static char buffer[20]; - snprintf(buffer, ARRAY_LENGTH(buffer), "B%d", which); - return buffer; + return util::string_format("Button %d", which + 1); } //============================================================ // default_pov_name //============================================================ -inline static const char *default_pov_name(int which) +inline std::string default_pov_name(int which) { - static char buffer[20]; - snprintf(buffer, ARRAY_LENGTH(buffer), "POV%d", which); - return buffer; + return util::string_format("Hat %d", which + 1); } // default axis names @@ -569,27 +401,25 @@ const char *const default_axis_name[] = "RY", "RZ", "SL1", "SL2" }; -inline static int32_t normalize_absolute_axis(double raw, double rawmin, double rawmax) +inline int32_t normalize_absolute_axis(double raw, double rawmin, double rawmax) { - double center = (rawmax + rawmin) / 2.0; - - // make sure we have valid data + // make sure we have valid arguments if (rawmin >= rawmax) return int32_t(raw); - // above center + double const center = (rawmax + rawmin) / 2.0; if (raw >= center) { - double result = (raw - center) * INPUT_ABSOLUTE_MAX / (rawmax - center); - return std::min(result, (double)INPUT_ABSOLUTE_MAX); + // above center + double const result = (raw - center) * double(osd::input_device::ABSOLUTE_MAX) / (rawmax - center); + return int32_t(std::min(result, double(osd::input_device::ABSOLUTE_MAX))); } - - // below center else { - double result = -((center - raw) * (double)-INPUT_ABSOLUTE_MIN / (center - rawmin)); - return std::max(result, (double)INPUT_ABSOLUTE_MIN); + // below center + double result = -((center - raw) * double(-osd::input_device::ABSOLUTE_MIN) / (center - rawmin)); + return int32_t(std::max(result, double(osd::input_device::ABSOLUTE_MIN))); } } -#endif // INPUT_COMMON_H_ +#endif // MAME_OSD_INPUT_INPUT_COMMON_H diff --git a/src/osd/modules/input/input_dinput.cpp b/src/osd/modules/input/input_dinput.cpp index 0b5eb348dc0..6f539c2f7fc 100644 --- a/src/osd/modules/input/input_dinput.cpp +++ b/src/osd/modules/input/input_dinput.cpp @@ -1,281 +1,354 @@ // 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 DS4 Xinput Switch +X Left X Left X Left X Left X +Y Left Y Left Y Left Y Left Y +Z Right X Right X Triggers +Rx Left trigger Right X Right X +Ry Right trigger Right Y Right Y +Rz Right Y 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 "input_module.h" #include "modules/osdmodule.h" -#if defined(OSD_WINDOWS) +#if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) -// standard windows headers -#include <windows.h> -#include <initguid.h> -#include <tchar.h> -#include <wrl/client.h> +#include "input_dinput.h" -// undef WINNT for dinput.h to prevent duplicate definition -#undef WINNT -#include <dinput.h> -#undef interface +#include "interface/inputseq.h" +#include "windows/winutil.h" -#include <mutex> +// emu +#include "inpttype.h" -// MAME headers -#include "emu.h" -#include "strconv.h" +// lib/util +#include "util/corestr.h" -// MAMEOS headers -#include "window.h" -#include "winutil.h" +#ifdef SDLMAME_WIN32 +#include <SDL2/SDL.h> +#include <SDL2/SDL_syswm.h> +#endif -#include "input_common.h" -#include "input_windows.h" -#include "input_dinput.h" +#include <algorithm> +#include <cmath> +#include <iterator> +#include <memory> -using namespace Microsoft::WRL; +// standard windows headers +#include <initguid.h> +#include <tchar.h> -static int32_t dinput_joystick_pov_get_state(void *device_internal, void *item_internal); -//============================================================ -// dinput_set_dword_property -//============================================================ +namespace osd { -#if DIRECTINPUT_VERSION >= 0x0800 -static HRESULT dinput_set_dword_property(ComPtr<IDirectInputDevice8> device, REFGUID property_guid, DWORD object, DWORD how, DWORD value) -#else -static HRESULT dinput_set_dword_property(ComPtr<IDirectInputDevice> device, REFGUID property_guid, DWORD object, DWORD how, DWORD value) -#endif +namespace { + +std::string guid_to_string(GUID const &guid) { - DIPROPDWORD dipdw; + // 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; +} - dipdw.diph.dwSize = sizeof(dipdw); - dipdw.diph.dwHeaderSize = sizeof(dipdw.diph); - dipdw.diph.dwObj = object; - dipdw.diph.dwHow = how; - dipdw.dwData = value; - return device->SetProperty(property_guid, &dipdw.diph); -} //============================================================ -// dinput_device - base directinput device +// dinput_keyboard_device - DirectInput keyboard device //============================================================ -dinput_device::dinput_device(running_machine &machine, const char *name, const char *id, input_device_class deviceclass, input_module &module) - : device_info(machine, name, id, deviceclass, module), - dinput({nullptr}) +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(bool relative_reset) override; + virtual void reset() override; + virtual void configure(input_device &device) override; + +private: + std::mutex m_device_lock; + keyboard_state m_keyboard; +}; -dinput_device::~dinput_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 } }) { - if (dinput.device != nullptr) - dinput.device.Reset(); } -HRESULT dinput_device::poll_dinput(LPVOID pState) const +void dinput_keyboard_device::poll(bool relative_reset) { - HRESULT result; - - // first poll the device, then get the state -#if DIRECTINPUT_VERSION >= 0x0800 - dinput.device->Poll(); -#else - if (dinput.device2 != nullptr) - dinput.device2->Poll(); -#endif + // poll the DirectInput immediate state + std::lock_guard<std::mutex> scope_lock(m_device_lock); + poll_dinput(&m_keyboard.state); +} - // GetDeviceState returns the immediate state - result = dinput.device->GetDeviceState(dinput.format->dwDataSize, pState); +void dinput_keyboard_device::reset() +{ + memset(&m_keyboard.state, 0, sizeof(m_keyboard.state)); +} - // handle lost inputs here - if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) +void dinput_keyboard_device::configure(input_device &device) +{ + // populate it + char defname[20]; + for (unsigned keynum = 0; keynum < MAX_KEYS; keynum++) { - result = dinput.device->Acquire(); - if (result == DI_OK) - result = dinput.device->GetDeviceState(dinput.format->dwDataSize, pState); + 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]); } - - return result; } + //============================================================ -// dinput_keyboard_device - directinput keyboard device +// dinput_mouse_device - DirectInput mouse device //============================================================ -dinput_keyboard_device::dinput_keyboard_device(running_machine &machine, const char *name, const char *id, input_module &module) - : dinput_device(machine, name, id, DEVICE_CLASS_KEYBOARD, module), - keyboard({{0}}) +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(bool relative_reset) override; + void reset() override; + virtual void configure(input_device &device) override; + +private: + DIMOUSESTATE2 m_mouse; +}; -// Polls the direct input immediate state -void dinput_keyboard_device::poll() +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}) { - std::lock_guard<std::mutex> scope_lock(m_device_lock); - - // Poll the state - dinput_device::poll_dinput(&keyboard.state); + // 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_keyboard_device::reset() -{ - memset(&keyboard.state, 0, sizeof(keyboard.state)); -} - -//============================================================ -// dinput_api_helper - DirectInput API helper -//============================================================ - -dinput_api_helper::dinput_api_helper(int version) - : m_dinput(nullptr), - m_dinput_version(version), - m_dinput_create_prt(nullptr) +void dinput_mouse_device::poll(bool relative_reset) { + // poll + if (relative_reset && (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; + } } -dinput_api_helper::~dinput_api_helper() +void dinput_mouse_device::reset() { - m_dinput.Reset(); + memset(&m_mouse, 0, sizeof(m_mouse)); } -int dinput_api_helper::initialize() +void dinput_mouse_device::configure(input_device &device) { - HRESULT result; - -#if DIRECTINPUT_VERSION >= 0x0800 - if (m_dinput_version >= 0x0800) + // populate the axes + for (int axisnum = 0; axisnum < m_caps.dwAxes; axisnum++) { - result = DirectInput8Create(GetModuleHandleUni(), m_dinput_version, IID_IDirectInput8, reinterpret_cast<void **>(m_dinput.GetAddressOf()), nullptr); - if (result != DI_OK) - { - m_dinput_version = 0; - return result; - } + // 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); } - else -#endif - { - m_dinput_dll = osd::dynamic_module::open({ "dinput.dll" }); - m_dinput_create_prt = m_dinput_dll->bind<dinput_create_fn>("DirectInputCreateW"); - if (m_dinput_create_prt == nullptr) - { - osd_printf_verbose("Legacy DirectInput library dinput.dll is not available\n"); - return ERROR_DLL_NOT_FOUND; - } + // populate the buttons + for (int butnum = 0; butnum < m_caps.dwButtons; butnum++) + { + auto offset = reinterpret_cast<uintptr_t>(&static_cast<DIMOUSESTATE *>(nullptr)->rgbButtons[butnum]); - // first attempt to initialize DirectInput at v7 - m_dinput_version = 0x0700; - result = (*m_dinput_create_prt)(GetModuleHandleUni(), m_dinput_version, m_dinput.GetAddressOf(), nullptr); - if (result != DI_OK) - { - // if that fails, try version 5 - m_dinput_version = 0x0500; - result = (*m_dinput_create_prt)(GetModuleHandleUni(), m_dinput_version, m_dinput.GetAddressOf(), nullptr); - if (result != DI_OK) - { - m_dinput_version = 0; - return result; - } - } + // 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]); } - - osd_printf_verbose("DirectInput: Using DirectInput %d\n", m_dinput_version >> 8); - return 0; -} - - -HRESULT dinput_api_helper::enum_attached_devices(int devclass, device_enum_interface *enumerate_interface, void *state) const -{ - device_enum_interface::dinput_callback_context ctx; - ctx.self = enumerate_interface; - ctx.state = state; - - return m_dinput->EnumDevices(devclass, device_enum_interface::enum_callback, &ctx, DIEDFL_ATTACHEDONLY); } //============================================================ -// dinput_module - base directinput module +// dinput_module - base DirectInput module //============================================================ -class dinput_module : public wininput_module, public device_enum_interface +class dinput_module : public input_module_impl<dinput_device, osd_common_t> { -protected: - std::unique_ptr<dinput_api_helper> m_dinput_helper; - public: - dinput_module(const char* type, const char* name) - : wininput_module(type, name), - m_dinput_helper(nullptr) + dinput_module(const char* type, const char* name) : + input_module_impl<dinput_device, osd_common_t>(type, name), + m_dinput_helper(nullptr) { } - int init_internal() override + virtual int init(osd_interface &osd, osd_options const &options) override { - m_dinput_helper = std::make_unique<dinput_api_helper>(DIRECTINPUT_VERSION); - int result = m_dinput_helper->initialize(); - if (result != 0) + m_dinput_helper = std::make_unique<dinput_api_helper>(); + int const result = m_dinput_helper->initialize(); + if (result) + { + m_dinput_helper.reset(); return result; + } - return 0; + return input_module_impl<dinput_device, osd_common_t>::init(osd, options); } - void exit() override + virtual void exit() override { - wininput_module::exit(); - m_dinput_helper.reset(); - } + input_module_impl<dinput_device, osd_common_t>::exit(); - void input_init(running_machine &machine) override - { - HRESULT result = m_dinput_helper->enum_attached_devices(dinput_devclass(), this, &machine); - if (result != DI_OK) - fatalerror("DirectInput: Unable to enumerate keyboards (result=%08X)\n", static_cast<uint32_t>(result)); + m_dinput_helper.reset(); } - static std::string device_item_name(dinput_device * devinfo, int offset, const char * defstring, const TCHAR * suffix) + virtual void input_init(running_machine &machine) override { - DIDEVICEOBJECTINSTANCE instance = { 0 }; - HRESULT result; - - // query the key name - instance.dwSize = sizeof(instance); - result = devinfo->dinput.device->GetObjectInfo(&instance, offset, DIPH_BYOFFSET); + input_module_impl<dinput_device, osd_common_t>::input_init(machine); - // if we got an error and have no default string, just return nullptr + HRESULT const result = m_dinput_helper->enum_attached_devices( + dinput_devclass(), + [this] (LPCDIDEVICEINSTANCE instance) { return device_enum_callback(instance); }); if (result != DI_OK) - { - if (defstring == nullptr) - return nullptr; - - // Return the default value - return std::string(defstring); - } - - // convert the name to utf8 - std::string namestring = osd::text::from_tstring(instance.tszName); - - // if no suffix, return as-is - if (suffix == nullptr) - return namestring; - - // convert the suffix to utf8 - std::string suffix_utf8 = osd::text::from_tstring(suffix); - - // Concat the name and suffix - return namestring + " " + suffix_utf8; + fatalerror("DirectInput: Unable to enumerate devices (result=%08X)\n", uint32_t(result)); } protected: virtual int dinput_devclass() = 0; + virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) = 0; + + std::unique_ptr<dinput_api_helper> m_dinput_helper; }; + class keyboard_input_dinput : public dinput_module { public: @@ -284,69 +357,30 @@ public: { } - int dinput_devclass() override +protected: + virtual int dinput_devclass() override { -#if DIRECTINPUT_VERSION >= 0x0800 return DI8DEVCLASS_KEYBOARD; -#else - return DIDEVTYPE_KEYBOARD; -#endif } - BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance, LPVOID ref) override + virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) override { - running_machine &machine = *static_cast<running_machine *>(ref); - dinput_keyboard_device *devinfo; - int keynum; - // allocate and link in a new device - devinfo = m_dinput_helper->create_device<dinput_keyboard_device>(machine, *this, instance, &c_dfDIKeyboard, nullptr, dinput_cooperative_level::FOREGROUND); - if (devinfo == nullptr) - goto exit; - - // populate it - for (keynum = 0; keynum < MAX_KEYS; keynum++) - { - input_item_id itemid = keyboard_trans_table::instance().map_di_scancode_to_itemid(keynum); - char defname[20]; - std::string name; - - // generate/fetch the name - snprintf(defname, ARRAY_LENGTH(defname), "Scan%03d", keynum); - name = device_item_name(devinfo, keynum, defname, nullptr); + auto devinfo = m_dinput_helper->create_device<dinput_keyboard_device>( + *this, + instance, + &c_dfDIKeyboard, + nullptr, + background_input() ? dinput_cooperative_level::BACKGROUND : dinput_cooperative_level::FOREGROUND, + [] (auto...) { return true; }); + if (devinfo) + add_device(DEVICE_CLASS_KEYBOARD, std::move(devinfo)); - // add the item to the device - devinfo->device()->add_item(name.c_str(), itemid, generic_button_get_state<std::uint8_t>, &devinfo->keyboard.state[keynum]); - } - - exit: return DIENUM_CONTINUE; } }; -dinput_mouse_device::dinput_mouse_device(running_machine &machine, const char *name, const char *id, input_module &module) - : dinput_device(machine, name, id, DEVICE_CLASS_MOUSE, module), - mouse({0}) -{ -} - -void dinput_mouse_device::poll() -{ - // poll - dinput_device::poll_dinput(&mouse); - - // scale the axis data - mouse.lX *= INPUT_RELATIVE_PER_PIXEL; - mouse.lY *= INPUT_RELATIVE_PER_PIXEL; - mouse.lZ *= INPUT_RELATIVE_PER_PIXEL; -} - -void dinput_mouse_device::reset() -{ - memset(&mouse, 0, sizeof(mouse)); -} - class mouse_input_dinput : public dinput_module { public: @@ -355,284 +389,954 @@ public: { } - int dinput_devclass() override +protected: + virtual int dinput_devclass() override { -#if DIRECTINPUT_VERSION >= 0x0800 return DI8DEVCLASS_POINTER; -#else - return DIDEVTYPE_MOUSE; -#endif } - BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance, LPVOID ref) override + virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) override { - dinput_mouse_device *devinfo = nullptr; - running_machine &machine = *static_cast<running_machine *>(ref); - int axisnum, butnum; - HRESULT result; - // allocate and link in a new device - devinfo = m_dinput_helper->create_device<dinput_mouse_device>(machine, *this, instance, &c_dfDIMouse2, &c_dfDIMouse, dinput_cooperative_level::FOREGROUND); - if (devinfo == nullptr) - goto exit; + auto devinfo = m_dinput_helper->create_device<dinput_mouse_device>( + *this, + instance, + &c_dfDIMouse2, + &c_dfDIMouse, + background_input() ? dinput_cooperative_level::BACKGROUND : dinput_cooperative_level::FOREGROUND, + [] (auto const &device, auto const &format) -> bool + { + // set relative mode + HRESULT const result = dinput_api_helper::set_dword_property( + device, + DIPROP_AXISMODE, + 0, + DIPH_DEVICE, + DIPROPAXISMODE_REL); + if ((result != DI_OK) && (result != DI_PROPNOEFFECT)) + { + osd_printf_error("DirectInput: Unable to set relative mode for mouse.\n"); + return false; + } + return true; + }); + if (devinfo) + add_device(DEVICE_CLASS_MOUSE, std::move(devinfo)); - // set relative mode on the mouse device - result = dinput_set_dword_property(devinfo->dinput.device, DIPROP_AXISMODE, 0, DIPH_DEVICE, DIPROPAXISMODE_REL); - if (result != DI_OK && result != DI_PROPNOEFFECT) - { - osd_printf_error("DirectInput: Unable to set relative mode for mouse %u (%s)\n", static_cast<unsigned int>(devicelist()->size()), devinfo->name()); - goto error; - } + return DIENUM_CONTINUE; + } +}; - // cap the number of axes and buttons based on the format - devinfo->dinput.caps.dwAxes = std::min(devinfo->dinput.caps.dwAxes, DWORD(3)); - devinfo->dinput.caps.dwButtons = std::min(devinfo->dinput.caps.dwButtons, DWORD((devinfo->dinput.format == &c_dfDIMouse) ? 4 : 8)); - // populate the axes - for (axisnum = 0; axisnum < devinfo->dinput.caps.dwAxes; axisnum++) - { - // add to the mouse device and optionally to the gun device as well - std::string name = device_item_name(devinfo, offsetof(DIMOUSESTATE, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], nullptr); - devinfo->device()->add_item( - name.c_str(), - static_cast<input_item_id>(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &devinfo->mouse.lX + axisnum); - } +class joystick_input_dinput : public dinput_module +{ +public: + joystick_input_dinput() : + dinput_module(OSD_JOYSTICKINPUT_PROVIDER, "dinput") + { + } - // populate the buttons - for (butnum = 0; butnum < devinfo->dinput.caps.dwButtons; butnum++) - { - uintptr_t offset = reinterpret_cast<uintptr_t>(&static_cast<DIMOUSESTATE *>(nullptr)->rgbButtons[butnum]); +protected: + virtual int dinput_devclass() override + { + return DI8DEVCLASS_GAMECTRL; + } - // add to the mouse device - std::string name = device_item_name(devinfo, offset, default_button_name(butnum), nullptr); - devinfo->device()->add_item( - name.c_str(), - static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum), - generic_button_get_state<BYTE>, - &devinfo->mouse.rgbButtons[butnum]); - } + virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) override + { + // allocate and link in a new device + auto devinfo = m_dinput_helper->create_device<dinput_joystick_device>( + *this, + instance, + &c_dfDIJoystick, + nullptr, + background_input() ? dinput_cooperative_level::BACKGROUND : dinput_cooperative_level::FOREGROUND, + [] (auto const &device, auto const &format) -> bool + { + // set absolute mode + HRESULT const result = dinput_api_helper::set_dword_property( + device, + DIPROP_AXISMODE, + 0, + DIPH_DEVICE, + DIPROPAXISMODE_ABS); + if ((result != DI_OK) && (result != DI_PROPNOEFFECT)) + { + osd_printf_error("DirectInput: Unable to set absolute mode for joystick.\n"); + return false; + } + return true; + }); + if (devinfo) + add_device(DEVICE_CLASS_JOYSTICK, std::move(devinfo)); - exit: return DIENUM_CONTINUE; - - error: - if (devinfo != nullptr) - devicelist()->free_device(devinfo); - goto exit; } }; -dinput_joystick_device::dinput_joystick_device(running_machine &machine, const char *name, const char *id, input_module &module) - : dinput_device(machine, name, id, DEVICE_CLASS_JOYSTICK, module), - joystick({{0}}) +} // anonymous namespace + + +//============================================================ +// dinput_device - base DirectInput device +//============================================================ + +dinput_device::dinput_device( + std::string &&name, + std::string &&id, + input_module &module, + Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, + DIDEVCAPS const &caps, + LPCDIDATAFORMAT format) : + device_info(std::move(name), std::move(id), module), + m_device(std::move(device)), + m_caps(caps), + m_format(format) { } -void dinput_joystick_device::reset() +HRESULT dinput_device::poll_dinput(LPVOID pState) const +{ + HRESULT result; + + // first poll the device, then get the state + result = m_device->Poll(); + + // handle lost inputs here + if ((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED)) + { + result = m_device->Acquire(); + if ((result == DI_OK) || (result == S_FALSE)) + result = m_device->Poll(); + } + + // GetDeviceState returns the immediate state + if ((result == DI_OK) || (result == DI_NOEFFECT)) + result = m_device->GetDeviceState(m_format->dwDataSize, pState); + + return result; +} + +std::string dinput_device::item_name(int offset, std::string_view defstring, const char *suffix) const { - memset(&joystick.state, 0, sizeof(joystick.state)); + // query the key name + DIDEVICEOBJECTINSTANCE instance = { 0 }; + instance.dwSize = sizeof(instance); + HRESULT const result = m_device->GetObjectInfo(&instance, offset, DIPH_BYOFFSET); + + // use the default value if it failed + std::string name; + if (result != DI_OK) + name = defstring; + else + name = text::from_tstring(instance.tszName); + + // if no suffix, return as-is + if (suffix) + name.append(" ").append(suffix); + + return name; } -void dinput_joystick_device::poll() + +//============================================================ +// dinput_joystick_device - directinput joystick device +//============================================================ + +dinput_joystick_device::dinput_joystick_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_joystick({ { 0 } }) { - int axisnum; + // cap the number of axes, POVs, and buttons based on the format + m_caps.dwAxes = std::min(m_caps.dwAxes, DWORD(8)); + m_caps.dwPOVs = std::min(m_caps.dwPOVs, DWORD(4)); + m_caps.dwButtons = std::min(m_caps.dwButtons, DWORD(128)); +} - // poll the device first - if (dinput_device::poll_dinput(&joystick.state) != ERROR_SUCCESS) - return; +void dinput_joystick_device::reset() +{ + memset(&m_joystick.state, 0, sizeof(m_joystick.state)); + std::fill(std::begin(m_joystick.state.rgdwPOV), std::end(m_joystick.state.rgdwPOV), 0xffff); +} - // normalize axis values - for (axisnum = 0; axisnum < 8; axisnum++) +void dinput_joystick_device::poll(bool relative_reset) +{ + // poll the device first + if (dinput_device::poll_dinput(&m_joystick.state) == DI_OK) { - LONG *axis = (&joystick.state.lX) + axisnum; - *axis = normalize_absolute_axis(*axis, joystick.rangemin[axisnum], joystick.rangemax[axisnum]); + // normalize axis values + for (int axisnum = 0; axisnum < 8; 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)); + } + } } } -int dinput_joystick_device::configure() +void dinput_joystick_device::configure(input_device &device) { + input_device::assignment_vector assignments; HRESULT result; - uint32_t axisnum, axiscount; - - auto devicelist = static_cast<input_module_base&>(module()).devicelist(); - // temporary approximation of index - int devindex = devicelist->size(); - - // set absolute mode - result = dinput_set_dword_property(dinput.device, DIPROP_AXISMODE, 0, DIPH_DEVICE, DIPROPAXISMODE_ABS); - if (result != DI_OK && result != DI_PROPNOEFFECT) - osd_printf_warning("DirectInput: Unable to set absolute mode for joystick %d (%s)\n", devindex, name()); + // 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_set_dword_property(dinput.device, DIPROP_DEADZONE, 0, DIPH_DEVICE, 0); - if (result != DI_OK && result != DI_PROPNOEFFECT) - osd_printf_warning("DirectInput: Unable to reset deadzone for joystick %d (%s)\n", devindex, name()); + result = dinput_api_helper::set_dword_property(m_device, DIPROP_DEADZONE, 0, DIPH_DEVICE, 0); + 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_set_dword_property(dinput.device, DIPROP_SATURATION, 0, DIPH_DEVICE, 10000); - if (result != DI_OK && result != DI_PROPNOEFFECT) - osd_printf_warning("DirectInput: Unable to reset saturation for joystick %d (%s)\n", devindex, name()); - - // cap the number of axes, POVs, and buttons based on the format - dinput.caps.dwAxes = std::min(dinput.caps.dwAxes, DWORD(8)); - dinput.caps.dwPOVs = std::min(dinput.caps.dwPOVs, DWORD(4)); - dinput.caps.dwButtons = std::min(dinput.caps.dwButtons, DWORD(128)); + result = dinput_api_helper::set_dword_property(m_device, DIPROP_SATURATION, 0, DIPH_DEVICE, 10000); + if ((result != DI_OK) && (result != DI_PROPNOEFFECT)) + osd_printf_warning("DirectInput: Unable to reset saturation for joystick %s.\n", name()); // populate the axes - for (axisnum = axiscount = 0; axiscount < dinput.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++) { - DIPROPRANGE dipr; - std::string name; - // fetch the range of this axis + DIPROPRANGE dipr; dipr.diph.dwSize = sizeof(dipr); dipr.diph.dwHeaderSize = sizeof(dipr.diph); dipr.diph.dwObj = offsetof(DIJOYSTATE2, lX) + axisnum * sizeof(LONG); dipr.diph.dwHow = DIPH_BYOFFSET; - result = dinput.device->GetProperty(DIPROP_RANGE, &dipr.diph); + 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; + } - joystick.rangemin[axisnum] = dipr.lMin; - joystick.rangemax[axisnum] = dipr.lMax; + m_joystick.rangemin[axisnum] = dipr.lMin; + m_joystick.rangemax[axisnum] = dipr.lMax; // populate the item description as well - name = dinput_module::device_item_name(this, offsetof(DIJOYSTATE2, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], nullptr); - device()->add_item( - name.c_str(), - static_cast<input_item_id>(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &joystick.state.lX + axisnum); + 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), + generic_axis_get_state<LONG>, + &m_joystick.state.lX + axisnum); axiscount++; } - // populate the POVs - for (uint32_t povnum = 0; povnum < dinput.caps.dwPOVs; povnum++) + // 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) { - std::string name; + // 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 - name = dinput_module::device_item_name(this, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("L")); - device()->add_item(name.c_str(), ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, reinterpret_cast<void *>(static_cast<uintptr_t>(povnum * 4 + POVDIR_LEFT))); + 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), + &dinput_joystick_device::pov_get_state, + reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_LEFT))); // right - name = dinput_module::device_item_name(this, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("R")); - device()->add_item(name.c_str(), ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, reinterpret_cast<void *>(static_cast<uintptr_t>(povnum * 4 + POVDIR_RIGHT))); + 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), + &dinput_joystick_device::pov_get_state, + reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_RIGHT))); // up - name = dinput_module::device_item_name(this, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("U")); - device()->add_item(name.c_str(), ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, reinterpret_cast<void *>(static_cast<uintptr_t>(povnum * 4 + POVDIR_UP))); + 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), + &dinput_joystick_device::pov_get_state, + reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_UP))); // down - name = dinput_module::device_item_name(this, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("D")); - device()->add_item(name.c_str(), ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, reinterpret_cast<void *>(static_cast<uintptr_t>(povnum * 4 + POVDIR_DOWN))); + 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), + &dinput_joystick_device::pov_get_state, + reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_DOWN))); } // populate the buttons - for (uint32_t butnum = 0; butnum < dinput.caps.dwButtons; butnum++) + for (uint32_t butnum = 0; butnum < m_caps.dwButtons; butnum++) { - uintptr_t offset = reinterpret_cast<uintptr_t>(&static_cast<DIJOYSTATE2 *>(nullptr)->rgbButtons[butnum]); - std::string name = dinput_module::device_item_name(this, offset, default_button_name(butnum), nullptr); + auto offset = reinterpret_cast<uintptr_t>(&static_cast<DIJOYSTATE2 *>(nullptr)->rgbButtons[butnum]); input_item_id itemid; - if (butnum < INPUT_MAX_BUTTONS) - itemid = static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum); + itemid = input_item_id(ITEM_ID_BUTTON1 + butnum); else if (butnum < INPUT_MAX_BUTTONS + INPUT_MAX_ADD_SWITCH) - itemid = static_cast<input_item_id>(ITEM_ID_ADD_SWITCH1 - INPUT_MAX_BUTTONS + butnum); + itemid = input_item_id(ITEM_ID_ADD_SWITCH1 - INPUT_MAX_BUTTONS + butnum); else itemid = ITEM_ID_OTHER_SWITCH; - device()->add_item(name.c_str(), itemid, generic_button_get_state<BYTE>, &joystick.state.rgbButtons[butnum]); - } + 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]); - return 0; -} + // 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); -class joystick_input_dinput : public dinput_module -{ -public: - joystick_input_dinput() : - dinput_module(OSD_JOYSTICKINPUT_PROVIDER, "dinput") - { + // 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; + } + } } - int dinput_devclass() override + // add default assignments depending on type + if (DI8DEVTYPE_FLIGHT == type) { -#if DIRECTINPUT_VERSION >= 0x0800 - return DI8DEVCLASS_GAMECTRL; -#else - return DIDEVTYPE_JOYSTICK; -#endif - } + 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); + } + } - BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance, LPVOID ref) override - { - dinput_cooperative_level cooperative_level = dinput_cooperative_level::FOREGROUND; - running_machine &machine = *static_cast<running_machine *>(ref); - dinput_joystick_device *devinfo; - int result = 0; + // 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); + } + } - if (!osd_common_t::s_window_list.empty() && osd_common_t::s_window_list.front()->win_has_menu()) - cooperative_level = dinput_cooperative_level::BACKGROUND; + // 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); + } + } - // allocate and link in a new device - devinfo = m_dinput_helper->create_device<dinput_joystick_device>(machine, *this, instance, &c_dfDIJoystick, nullptr, cooperative_level); - if (devinfo == nullptr) - goto exit; + // 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); + } + } + } - result = devinfo->configure(); - if (result != 0) + // 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]) { - osd_printf_error("Failed to configure DI Joystick device. Error 0x%x\n", static_cast<unsigned int>(result)); + // 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[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 ((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 Z is present, use it as secondary Y + stickaxes[1][1] = axisitems[2]; } - exit: - return DIENUM_CONTINUE; + // 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]); } -}; -//============================================================ -// dinput_joystick_pov_get_state -//============================================================ + // set default assignments + device.set_default_assignments(std::move(assignments)); +} -static int32_t dinput_joystick_pov_get_state(void *device_internal, void *item_internal) +int32_t dinput_joystick_device::pov_get_state(void *device_internal, void *item_internal) { - dinput_joystick_device *devinfo = static_cast<dinput_joystick_device *>(device_internal); - int povnum = reinterpret_cast<uintptr_t>(item_internal) / 4; - int povdir = reinterpret_cast<uintptr_t>(item_internal) % 4; - int32_t result = 0; - DWORD pov; + auto *const devinfo = static_cast<dinput_joystick_device *>(device_internal); + int const povnum = uintptr_t(item_internal) / 4; + int const povdir = uintptr_t(item_internal) % 4; // get the current state - devinfo->module().poll_if_necessary(devinfo->machine()); - pov = devinfo->joystick.state.rgdwPOV[povnum]; + DWORD const pov = devinfo->m_joystick.state.rgdwPOV[povnum]; // if invalid, return 0 if ((pov & 0xffff) == 0xffff) - return result; + return 0; // return the current state switch (povdir) { - case POVDIR_LEFT: result = (pov >= 22500 && pov <= 31500); break; - case POVDIR_RIGHT: result = (pov >= 4500 && pov <= 13500); break; - case POVDIR_UP: result = (pov >= 31500 || pov <= 4500); break; - case POVDIR_DOWN: result = (pov >= 13500 && pov <= 22500); break; + case POVDIR_LEFT: return (pov >= 22500) && (pov <= 31500); + case POVDIR_RIGHT: return (pov >= 4500) && (pov <= 13500); + case POVDIR_UP: return (pov >= 31500) || (pov <= 4500); + case POVDIR_DOWN: return (pov >= 13500) && (pov <= 22500); } - return result; + + return 0; +} + + +//============================================================ +// dinput_api_helper - DirectInput API helper +//============================================================ + +dinput_api_helper::dinput_api_helper() +{ +} + +dinput_api_helper::~dinput_api_helper() +{ +} + +int dinput_api_helper::initialize() +{ + HRESULT result = DirectInput8Create(GetModuleHandleUni(), DIRECTINPUT_VERSION, IID_IDirectInput8, &m_dinput, nullptr); + if (result != DI_OK) + { + return result; + } + + osd_printf_verbose("DirectInput: Using DirectInput %d\n", DIRECTINPUT_VERSION >> 8); + return 0; +} + + +std::pair<Microsoft::WRL::ComPtr<IDirectInputDevice8>, LPCDIDATAFORMAT> dinput_api_helper::open_device( + LPCDIDEVICEINSTANCE instance, + LPCDIDATAFORMAT format1, + LPCDIDATAFORMAT format2, + dinput_cooperative_level cooperative_level) +{ + HRESULT result; + + // attempt to create a device + Microsoft::WRL::ComPtr<IDirectInputDevice8> device; + result = m_dinput->CreateDevice(instance->guidInstance, &device, nullptr); + if (result != DI_OK) + { + osd_printf_error("DirectInput: Unable to create device.\n"); + return std::make_pair(nullptr, nullptr); + } + + // attempt to set the data format + LPCDIDATAFORMAT format = format1; + result = device->SetDataFormat(format); + if ((result != DI_OK) && format2) + { + // use the secondary format if available + osd_printf_verbose("DirectInput: Error setting primary data format, trying secondary format.\n"); + format = format2; + result = device->SetDataFormat(format); + } + if (result != DI_OK) + { + osd_printf_error("DirectInput: Unable to set data format.\n"); + return std::make_pair(nullptr, nullptr); + } + + // default window to the first window in the list + // For now, we always use the desktop window due to multiple issues: + // * MAME recreates windows on toggling fullscreen. DirectInput really doesn't like this. + // * DirectInput doesn't like the window used for D3D fullscreen exclusive mode. + // * With multiple windows, the first window needs to have focus when using foreground mode. + // This makes it impossible to use force feedback as that requires foreground exclusive mode. + // The only way to get around this would be to reopen devices on focus changes. + [[maybe_unused]] HWND window_handle; + DWORD di_cooperative_level; +#if defined(OSD_WINDOWS) + auto const &window = dynamic_cast<win_window_info &>(*osd_common_t::window_list().front()); + bool const standalone_window = !window.attached_mode(); +#elif defined(SDLMAME_WIN32) + auto const &window = dynamic_cast<sdl_window_info &>(*osd_common_t::window_list().front()); + bool const standalone_window = true; +#endif + if (!standalone_window) + { + // in attached mode we have to ignore the caller and hook up to the desktop window + window_handle = GetDesktopWindow(); + di_cooperative_level = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE; + } + else + { +#if defined(OSD_WINDOWS) + window_handle = window.platform_window(); +#elif defined(SDLMAME_WIN32) + auto const sdlwindow = window.platform_window(); + SDL_SysWMinfo info; + SDL_VERSION(&info.version); + if (!SDL_GetWindowWMInfo(sdlwindow, &info)) + return std::make_pair(nullptr, nullptr); + window_handle = info.info.win.window; +#endif + switch (cooperative_level) + { + case dinput_cooperative_level::BACKGROUND: + di_cooperative_level = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE; + break; + case dinput_cooperative_level::FOREGROUND: + //di_cooperative_level = DISCL_FOREGROUND | DISCL_NONEXCLUSIVE; + di_cooperative_level = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE; + break; + default: + throw false; + } + } + + // set the cooperative level + result = device->SetCooperativeLevel(GetDesktopWindow(), di_cooperative_level); + if (result != DI_OK) + { + osd_printf_error("DirectInput: Unable to set cooperative level.\n"); + return std::make_pair(nullptr, nullptr); + } + + // return new device + 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); } -#else +} // namespace osd + + +#else // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) + +#include "input_module.h" + +namespace osd { + +namespace { + MODULE_NOT_SUPPORTED(keyboard_input_dinput, OSD_KEYBOARDINPUT_PROVIDER, "dinput") MODULE_NOT_SUPPORTED(mouse_input_dinput, OSD_MOUSEINPUT_PROVIDER, "dinput") MODULE_NOT_SUPPORTED(joystick_input_dinput, OSD_JOYSTICKINPUT_PROVIDER, "dinput") -#endif -MODULE_DEFINITION(KEYBOARDINPUT_DINPUT, keyboard_input_dinput) -MODULE_DEFINITION(MOUSEINPUT_DINPUT, mouse_input_dinput) -MODULE_DEFINITION(JOYSTICKINPUT_DINPUT, joystick_input_dinput) +} // anonymous namespace + +} // namespace osd + +#endif // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) + +MODULE_DEFINITION(KEYBOARDINPUT_DINPUT, osd::keyboard_input_dinput) +MODULE_DEFINITION(MOUSEINPUT_DINPUT, osd::mouse_input_dinput) +MODULE_DEFINITION(JOYSTICKINPUT_DINPUT, osd::joystick_input_dinput) diff --git a/src/osd/modules/input/input_dinput.h b/src/osd/modules/input/input_dinput.h index a2b6c2edcbb..98fdfb011bb 100644 --- a/src/osd/modules/input/input_dinput.h +++ b/src/osd/modules/input/input_dinput.h @@ -1,54 +1,42 @@ -#ifndef INPUT_DINPUT_H_ -#define INPUT_DINPUT_H_ +// license:BSD-3-Clause +// copyright-holders:Aaron Giles, Brad Hughes, Vas Crabb +//============================================================ +// +// input_dinput.h - Windows DirectInput support +// +//============================================================ +#ifndef MAME_OSD_INPUT_INPUT_DINPUT_H +#define MAME_OSD_INPUT_INPUT_DINPUT_H + +#pragma once + +#include "assignmenthelper.h" +#include "input_wincommon.h" -#include "input_common.h" #include "modules/lib/osdlib.h" +#include "modules/lib/osdobj_common.h" -//============================================================ -// dinput_device - base directinput device -//============================================================ +#include "window.h" -// DirectInput-specific information about a device -struct dinput_api_state -{ -#if DIRECTINPUT_VERSION >= 0x0800 - Microsoft::WRL::ComPtr<IDirectInputDevice8> device; -#else - Microsoft::WRL::ComPtr<IDirectInputDevice> device; - Microsoft::WRL::ComPtr<IDirectInputDevice2> device2; -#endif - DIDEVCAPS caps; - LPCDIDATAFORMAT format; -}; +#include "strconv.h" -class device_enum_interface -{ -public: - struct dinput_callback_context - { - device_enum_interface * self; - void * state; - }; +#include <memory> +#include <mutex> +#include <string> +#include <string_view> +#include <type_traits> +#include <utility> - virtual ~device_enum_interface() - { - } +#include <windows.h> +#include <dinput.h> +#include <wrl/client.h> - static BOOL CALLBACK enum_callback(LPCDIDEVICEINSTANCE instance, LPVOID ref) - { - auto context = static_cast<dinput_callback_context*>(ref); - return context->self->device_enum_callback(instance, context->state); - } - virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance, LPVOID ref) = 0; -}; +namespace osd { -// Typedef for dynamically loaded function -#if DIRECTINPUT_VERSION >= 0x0800 -typedef HRESULT (WINAPI *dinput_create_fn)(HINSTANCE, DWORD, LPDIRECTINPUT8 *, LPUNKNOWN); -#else -typedef HRESULT (WINAPI *dinput_create_fn)(HINSTANCE, DWORD, LPDIRECTINPUT *, LPUNKNOWN); -#endif +//============================================================ +// dinput_device - base directinput device +//============================================================ enum class dinput_cooperative_level { @@ -58,187 +46,144 @@ enum class dinput_cooperative_level class dinput_api_helper { -private: -#if DIRECTINPUT_VERSION >= 0x0800 - Microsoft::WRL::ComPtr<IDirectInput8> m_dinput; -#else - Microsoft::WRL::ComPtr<IDirectInput> m_dinput; -#endif - int m_dinput_version; - osd::dynamic_module::ptr m_dinput_dll; - dinput_create_fn m_dinput_create_prt; - public: - dinput_api_helper(int version); - virtual ~dinput_api_helper(); + dinput_api_helper(); + ~dinput_api_helper(); + int initialize(); - template<class TDevice> - TDevice* create_device( - running_machine &machine, - input_module_base &module, - LPCDIDEVICEINSTANCE instance, - LPCDIDATAFORMAT format1, - LPCDIDATAFORMAT format2, - dinput_cooperative_level cooperative_level) + template <typename TDevice, typename TCallback> + std::unique_ptr<TDevice> create_device( + input_module_base &module, + LPCDIDEVICEINSTANCE instance, + LPCDIDATAFORMAT format1, + LPCDIDATAFORMAT format2, + dinput_cooperative_level cooperative_level, + TCallback &&callback) { - HRESULT result; - std::shared_ptr<win_window_info> window; - HWND hwnd; - - // convert instance name to utf8 - std::string utf8_instance_name = osd::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 - TDevice* devinfo = module.devicelist()->create_device<TDevice>(machine, utf8_instance_name.c_str(), utf8_instance_id.c_str(), module); - - // attempt to create a device - result = m_dinput->CreateDevice(instance->guidInstance, devinfo->dinput.device.GetAddressOf(), nullptr); - if (result != DI_OK) - goto error; - -#if DIRECTINPUT_VERSION < 0x0800 - // try to get a version 2 device for it so we can use the poll method - result = devinfo->dinput.device.CopyTo(IID_IDirectInputDevice2, reinterpret_cast<void**>(devinfo->dinput.device2.GetAddressOf())); + auto [device, format] = open_device(instance, format1, format2, cooperative_level); + if (!device) + return nullptr; + + // get the capabilities + DIDEVCAPS caps; + caps.dwSize = sizeof(caps); + HRESULT const result = device->GetCapabilities(&caps); if (result != DI_OK) - devinfo->dinput.device2 = nullptr; -#endif + return nullptr; - // get the caps - devinfo->dinput.caps.dwSize = sizeof(devinfo->dinput.caps); - result = devinfo->dinput.device->GetCapabilities(&devinfo->dinput.caps); - if (result != DI_OK) - goto error; + if (!callback(device, format)) + return nullptr; - // attempt to set the data format - devinfo->dinput.format = format1; - result = devinfo->dinput.device->SetDataFormat(devinfo->dinput.format); - if (result != DI_OK) - { - // use the secondary format if available - if (format2 != nullptr) - { - devinfo->dinput.format = format2; - result = devinfo->dinput.device->SetDataFormat(devinfo->dinput.format); - } - if (result != DI_OK) - goto error; - } - - // default window to the first window in the list - window = std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front()); - DWORD di_cooperative_level; - if (window->attached_mode()) - { - // in attached mode we have to ignore the caller and hook up to the desktop window - hwnd = GetDesktopWindow(); - di_cooperative_level = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE; - } - else - { - hwnd = window->platform_window(); - switch (cooperative_level) - { - case dinput_cooperative_level::BACKGROUND: - di_cooperative_level = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE; - break; - case dinput_cooperative_level::FOREGROUND: - di_cooperative_level = DISCL_FOREGROUND | DISCL_NONEXCLUSIVE; - break; - default: - throw false; - } - } - - // set the cooperative level - result = devinfo->dinput.device->SetCooperativeLevel(hwnd, di_cooperative_level); - if (result != DI_OK) - goto error; + // allocate memory for the device object + return std::make_unique<TDevice>( + text::from_tstring(instance->tszInstanceName), + make_id(instance), + module, + std::move(device), + caps, + format); + } - return devinfo; + template <typename T> + HRESULT enum_attached_devices(int devclass, T &&callback) const + { + return m_dinput->EnumDevices( + devclass, + &di_enum_devices_cb<std::remove_reference_t<T> >, + LPVOID(&callback), + DIEDFL_ATTACHEDONLY); + } - error: - module.devicelist()->free_device(devinfo); - return nullptr; + template <typename T> + static HRESULT set_dword_property( + T &&device, + REFGUID property_guid, + DWORD object, + DWORD how, + DWORD value) + { + DIPROPDWORD dipdw; + dipdw.diph.dwSize = sizeof(dipdw); + dipdw.diph.dwHeaderSize = sizeof(dipdw.diph); + dipdw.diph.dwObj = object; + dipdw.diph.dwHow = how; + dipdw.dwData = value; + + return device->SetProperty(property_guid, &dipdw.diph); } - HRESULT enum_attached_devices(int devclass, device_enum_interface *enumerate_interface, void *state) const; +private: + std::pair<Microsoft::WRL::ComPtr<IDirectInputDevice8>, LPCDIDATAFORMAT> open_device( + LPCDIDEVICEINSTANCE instance, + LPCDIDATAFORMAT format1, + LPCDIDATAFORMAT format2, + dinput_cooperative_level cooperative_level); + + static std::string make_id(LPCDIDEVICEINSTANCE instance); - static std::string guid_to_string(const GUID& guid) + template <typename T> + static BOOL CALLBACK di_enum_devices_cb(LPCDIDEVICEINSTANCE instance, LPVOID ref) { - // Size of a GUID string with dashes plus null terminator - char guid_string[37]; - - snprintf( - guid_string, ARRAY_LENGTH(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; + return (*reinterpret_cast<T *>(ref))(instance); } + + Microsoft::WRL::ComPtr<IDirectInput8> m_dinput; + dynamic_module::ptr m_dinput_dll; }; + class dinput_device : public device_info { -public: - dinput_api_state dinput; - - dinput_device(running_machine &machine, const char *name, const char *id, input_device_class deviceclass, input_module &module); - virtual ~dinput_device(); - protected: - HRESULT poll_dinput(LPVOID pState) const; -}; - -class dinput_keyboard_device : public dinput_device -{ -private: - std::mutex m_device_lock; + dinput_device( + std::string &&name, + std::string &&id, + input_module &module, + Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, + DIDEVCAPS const &caps, + LPCDIDATAFORMAT format); -public: - keyboard_state keyboard; + HRESULT poll_dinput(LPVOID pState) const; - dinput_keyboard_device(running_machine &machine, const char *name, const char *id, input_module &module); + std::string item_name(int offset, std::string_view defstr, char const *suffix) const; - void poll() override; - void reset() override; + // DirectInput-specific information about a device + Microsoft::WRL::ComPtr<IDirectInputDevice8> const m_device; + DIDEVCAPS m_caps; + LPCDIDATAFORMAT const m_format; }; -class dinput_mouse_device : public dinput_device + +class dinput_joystick_device : public dinput_device, protected joystick_assignment_helper { public: - mouse_state mouse; + dinput_joystick_device( + std::string &&name, + std::string &&id, + input_module &module, + Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device, + DIDEVCAPS const &caps, + LPCDIDATAFORMAT format); -public: - dinput_mouse_device(running_machine &machine, const char *name, const char *id, input_module &module); - void poll() override; void reset() override; -}; + void poll(bool relative_reset) override; + void configure(input_device &device) override; -// state information for a joystick; DirectInput state must be first element -struct dinput_joystick_state -{ - DIJOYSTATE state; - LONG rangemin[8]; - LONG rangemax[8]; -}; +private: + // state information for a joystick; DirectInput state must be first element + struct dinput_joystick_state + { + DIJOYSTATE state; + LONG rangemin[8]; + LONG rangemax[8]; + }; -class dinput_joystick_device : public dinput_device -{ -public: - dinput_joystick_state joystick; -public: - dinput_joystick_device(running_machine &machine, const char *name, const char *id, input_module &module); - void reset() override; - void poll() override; - int configure(); + static int32_t pov_get_state(void *device_internal, void *item_internal); + + dinput_joystick_state m_joystick; }; +} // namespace osd -#endif +#endif // MAME_OSD_INPUT_INPUT_DINPUT_H diff --git a/src/osd/modules/input/input_mac.cpp b/src/osd/modules/input/input_mac.cpp index 4dd030b331a..90360adaa3f 100644 --- a/src/osd/modules/input/input_mac.cpp +++ b/src/osd/modules/input/input_mac.cpp @@ -14,8 +14,8 @@ #if defined(OSD_MAC) // System headers -#include <ctype.h> -#include <stddef.h> +#include <cctype> +#include <cstddef> #include <mutex> #include <memory> #include <algorithm> @@ -31,15 +31,8 @@ #include "../../mac/osdmac.h" #include "input_common.h" -extern void MacPollInputs(); - -void mac_osd_interface::customize_input_type_list(simple_list<input_type_entry> &typelist) -{ -} - -void mac_osd_interface::poll_inputs(running_machine &machine) +void mac_osd_interface::customize_input_type_list(std::vector<input_type_entry> &typelist) { - MacPollInputs(); } void mac_osd_interface::release_keys() diff --git a/src/osd/modules/input/input_module.h b/src/osd/modules/input/input_module.h index d521d0d1860..64e5ebb2332 100644 --- a/src/osd/modules/input/input_module.h +++ b/src/osd/modules/input/input_module.h @@ -5,29 +5,23 @@ // input_module.h - OSD input module contracts // //============================================================ +#ifndef MAME_OSD_INPUT_INPUT_MODULE_H +#define MAME_OSD_INPUT_INPUT_MODULE_H -#ifndef INPUT_MODULE_H_ -#define INPUT_MODULE_H_ +#pragma once #include "osdepend.h" #include "modules/osdmodule.h" -class input_module : public osd_module +class input_module { public: - input_module(const char *type, const char *name) - : osd_module(type, name) - { - } - - virtual ~input_module() { } + virtual ~input_module() = default; virtual void input_init(running_machine &machine) = 0; - virtual void poll_if_necessary(running_machine &machine) = 0; - virtual void pause() = 0; - virtual void resume() = 0; + virtual void poll_if_necessary(bool relative_reset) = 0; }; //============================================================ @@ -39,4 +33,4 @@ public: #define OSD_LIGHTGUNINPUT_PROVIDER "lightgunprovider" #define OSD_JOYSTICKINPUT_PROVIDER "joystickprovider" -#endif /* INPUT_MODULE_H_ */ +#endif // MAME_OSD_INPUT_INPUT_MODULE_H diff --git a/src/osd/modules/input/input_none.cpp b/src/osd/modules/input/input_none.cpp index 0bad0fb1e7b..96c3f116868 100644 --- a/src/osd/modules/input/input_none.cpp +++ b/src/osd/modules/input/input_none.cpp @@ -7,60 +7,58 @@ //============================================================ #include "input_module.h" -#include "modules/osdmodule.h" -class keyboard_input_none : public input_module + +namespace osd { + +namespace { + +class keyboard_input_none : public osd_module, public input_module { public: - keyboard_input_none() - : input_module(OSD_KEYBOARDINPUT_PROVIDER, "none") {} - int init(const osd_options &options) override { return 0; } - void poll_if_necessary(running_machine &machine) override {}; - void input_init(running_machine &machine) override {}; - void pause() override {}; - void resume() override {}; + keyboard_input_none() : osd_module(OSD_KEYBOARDINPUT_PROVIDER, "none") { } + int init(osd_interface &osd, const osd_options &options) override { return 0; } + void input_init(running_machine &machine) override { } + void poll_if_necessary(bool relative_reset) override { } }; -MODULE_DEFINITION(KEYBOARD_NONE, keyboard_input_none) -class mouse_input_none : public input_module +class mouse_input_none : public osd_module, public input_module { public: - mouse_input_none() - : input_module(OSD_MOUSEINPUT_PROVIDER, "none") {} - int init(const osd_options &options) override { return 0; } - void input_init(running_machine &machine) override {}; - void poll_if_necessary(running_machine &machine) override {}; - void pause() override {}; - void resume() override {}; + mouse_input_none() : osd_module(OSD_MOUSEINPUT_PROVIDER, "none") { } + int init(osd_interface &osd, const osd_options &options) override { return 0; } + void input_init(running_machine &machine) override { } + void poll_if_necessary(bool relative_reset) override { } }; -MODULE_DEFINITION(MOUSE_NONE, mouse_input_none) -class lightgun_input_none : public input_module +class lightgun_input_none : public osd_module, public input_module { public: - lightgun_input_none() - : input_module(OSD_LIGHTGUNINPUT_PROVIDER, "none") {} - int init(const osd_options &options) override { return 0; } - void input_init(running_machine &machine) override {}; - void poll_if_necessary(running_machine &machine) override {}; - void pause() override {}; - void resume() override {}; + lightgun_input_none() : osd_module(OSD_LIGHTGUNINPUT_PROVIDER, "none") { } + int init(osd_interface &osd, const osd_options &options) override { return 0; } + void input_init(running_machine &machine) override { } + void poll_if_necessary(bool relative_reset) override { } }; -MODULE_DEFINITION(LIGHTGUN_NONE, lightgun_input_none) -class joystick_input_none : public input_module +class joystick_input_none : public osd_module, public input_module { public: - joystick_input_none() - : input_module(OSD_JOYSTICKINPUT_PROVIDER, "none") {} - int init(const osd_options &options) override { return 0; } - void input_init(running_machine &machine) override {}; - void poll_if_necessary(running_machine &machine) override {}; - void pause() override {}; - void resume() override {}; + joystick_input_none() : osd_module(OSD_JOYSTICKINPUT_PROVIDER, "none") { } + int init(osd_interface &osd, const osd_options &options) override { return 0; } + void input_init(running_machine &machine) override { } + void poll_if_necessary(bool relative_reset) override { } }; -MODULE_DEFINITION(JOYSTICK_NONE, joystick_input_none) +} // anonymous namesapce + +} // namespace osd + + + +MODULE_DEFINITION(KEYBOARD_NONE, osd::keyboard_input_none) +MODULE_DEFINITION(MOUSE_NONE, osd::mouse_input_none) +MODULE_DEFINITION(LIGHTGUN_NONE, osd::lightgun_input_none) +MODULE_DEFINITION(JOYSTICK_NONE, osd::joystick_input_none) diff --git a/src/osd/modules/input/input_rawinput.cpp b/src/osd/modules/input/input_rawinput.cpp index 9243ec4a0f4..5b6813dcd59 100644 --- a/src/osd/modules/input/input_rawinput.cpp +++ b/src/osd/modules/input/input_rawinput.cpp @@ -6,41 +6,39 @@ // //============================================================ -#include "input_module.h" #include "modules/osdmodule.h" #if defined(OSD_WINDOWS) -// standard windows headers -#include <windows.h> -#include <tchar.h> -#undef interface +#include "input_windows.h" -#include <mutex> -#include <functional> -#include <algorithm> +#include "input_wincommon.h" -// MAME headers -#include "emu.h" -#include "strconv.h" - -// MAMEOS headers -#include "modules/lib/osdlib.h" #include "winmain.h" #include "window.h" -#include "input_common.h" -#include "input_windows.h" +#include "modules/lib/osdlib.h" +#include "strconv.h" -//============================================================ -// MACROS -//============================================================ +// MAME headers +#include "inpttype.h" + +#include <algorithm> +#include <cassert> +#include <chrono> +#include <functional> +#include <mutex> +#include <new> +#include <utility> + +// standard windows headers +#include <windows.h> +#include <tchar.h> -// Typedefs for dynamically loaded functions -typedef UINT (WINAPI *get_rawinput_device_list_ptr)(PRAWINPUTDEVICELIST, PUINT, UINT); -typedef UINT (WINAPI *get_rawinput_data_ptr)( HRAWINPUT, UINT, LPVOID, PUINT, UINT); -typedef UINT (WINAPI *get_rawinput_device_info_ptr)(HANDLE, UINT, LPVOID, PUINT); -typedef BOOL (WINAPI *register_rawinput_devices_ptr)(PCRAWINPUTDEVICE, UINT, UINT); + +namespace osd { + +namespace { class safe_regkey { @@ -48,17 +46,24 @@ private: HKEY m_key; public: - safe_regkey() - : m_key(nullptr) - { - } + safe_regkey() : m_key(nullptr) { } + safe_regkey(safe_regkey const &) = delete; + safe_regkey(safe_regkey &&key) : m_key(key.m_key) { key.m_key = nullptr; } + explicit safe_regkey(HKEY key) : m_key(key) { } - explicit safe_regkey(HKEY key) - : m_key(key) + ~safe_regkey() { close(); } + + safe_regkey &operator=(safe_regkey const &) = delete; + + safe_regkey &operator=(safe_regkey &&key) { + close(); + m_key = key.m_key; + key.m_key = nullptr; + return *this; } - bool valid() const { return m_key != nullptr; } + explicit operator bool() const { return m_key != nullptr; } void close() { @@ -69,70 +74,68 @@ public: } } - ~safe_regkey() - { - close(); - } - operator HKEY() const { return m_key; } -}; -//============================================================ -// reg_open_key -//============================================================ - -static safe_regkey reg_open_key(HKEY basekey, const std::wstring &subkey) -{ - HKEY key; - if (RegOpenKeyEx(basekey, subkey.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS) - return safe_regkey(key); + safe_regkey open(std::wstring const &subkey) const { return open(m_key, subkey); } - return safe_regkey(); + std::wstring enum_key(int index) const + { + WCHAR keyname[256]; + DWORD namelen = std::size(keyname); + if (RegEnumKeyEx(m_key, index, keyname, &namelen, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS) + return std::wstring(keyname, namelen); + else + return std::wstring(); + } -} + std::wstring query_string(WCHAR const *path) const + { + // first query to get the length + DWORD datalen; + if (RegQueryValueExW(m_key, path, nullptr, nullptr, nullptr, &datalen) != ERROR_SUCCESS) + return std::wstring(); -//============================================================ -// reg_enum_key -//============================================================ + // allocate a buffer + auto buffer = std::make_unique<WCHAR []>((datalen + (sizeof(WCHAR) * 2) - 1) / sizeof(WCHAR)); -static std::wstring reg_enum_key(HKEY key, int index) -{ - WCHAR keyname[MAX_PATH]; - DWORD namelen; - if (RegEnumKeyEx(key, index, keyname, &namelen, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS) - return std::wstring(keyname, namelen); + // now get the actual data + if (RegQueryValueExW(m_key, path, nullptr, nullptr, reinterpret_cast<LPBYTE>(buffer.get()), &datalen) != ERROR_SUCCESS) + return std::wstring(); - return std::wstring(); -} - -//============================================================ -// reg_query_string -//============================================================ - -static std::wstring reg_query_string(HKEY key, const TCHAR *path) -{ - DWORD datalen; - LONG result; + buffer[datalen / sizeof(WCHAR)] = 0; + return std::wstring(buffer.get()); + } - // first query to get the length - result = RegQueryValueEx(key, path, nullptr, nullptr, nullptr, &datalen); - if (result != ERROR_SUCCESS) - return std::wstring(); + template <typename T> void foreach_subkey(T &&action) const + { + std::wstring name; + for (int i = 0; ; i++) + { + name = enum_key(i); + if (name.empty()) + break; - // allocate a buffer - auto buffer = std::make_unique<TCHAR[]>(datalen + sizeof(TCHAR)); - buffer[datalen / sizeof(TCHAR)] = 0; + safe_regkey const subkey = open(name); + if (!subkey) + break; - // now get the actual data - result = RegQueryValueEx(key, path, nullptr, nullptr, reinterpret_cast<LPBYTE>(buffer.get()), &datalen); - if (result == ERROR_SUCCESS) - return std::wstring(buffer.get()); + bool const shouldcontinue = action(subkey); + if (!shouldcontinue) + break; + } + } - // otherwise return an empty string - return std::wstring(); -} + static safe_regkey open(HKEY basekey, std::wstring const &subkey) + { + HKEY key(nullptr); + if (RegOpenKeyEx(basekey, subkey.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS) + return safe_regkey(key); + else + return safe_regkey(); + } +}; -static std::wstring trim_prefix(const std::wstring &devicename) +std::wstring trim_prefix(const std::wstring &devicename) { // remove anything prior to the final semicolon auto semicolon_index = devicename.find_last_of(';'); @@ -142,22 +145,22 @@ static std::wstring trim_prefix(const std::wstring &devicename) return devicename; } -static std::wstring compute_device_regpath(const std::wstring &name) +std::wstring compute_device_regpath(const std::wstring &name) { static const std::wstring basepath(L"SYSTEM\\CurrentControlSet\\Enum\\"); // allocate a temporary string and concatenate the base path plus the name - auto regpath_buffer = std::make_unique<TCHAR[]>(basepath.length() + 1 + name.length()); + auto regpath_buffer = std::make_unique<WCHAR []>(basepath.length() + 1 + name.length()); wcscpy(regpath_buffer.get(), basepath.c_str()); - WCHAR * chdst = regpath_buffer.get() + basepath.length(); + WCHAR *chdst = regpath_buffer.get() + basepath.length(); // convert all # to \ in the name for (int i = 4; i < name.length(); i++) - *chdst++ = (name[i] == '#') ? '\\' : name[i]; + *chdst++ = (name[i] == '#') ? L'\\' : name[i]; *chdst = 0; // remove the final chunk - chdst = wcsrchr(regpath_buffer.get(), '\\'); + chdst = wcsrchr(regpath_buffer.get(), L'\\'); if (chdst == nullptr) return std::wstring(); @@ -166,15 +169,15 @@ static std::wstring compute_device_regpath(const std::wstring &name) return std::wstring(regpath_buffer.get()); } -static std::wstring improve_name_from_base_path(const std::wstring ®path, bool *hid) +std::wstring improve_name_from_base_path(const std::wstring ®path, bool *hid) { // now try to open the registry key - auto device_key = reg_open_key(HKEY_LOCAL_MACHINE, regpath); - if (!device_key.valid()) + auto device_key = safe_regkey::open(HKEY_LOCAL_MACHINE, regpath); + if (!device_key) return std::wstring(); // fetch the device description; if it exists, we are finished - auto regstring = reg_query_string(device_key, L"DeviceDesc"); + auto regstring = device_key.query_string(L"DeviceDesc"); if (!regstring.empty()) return trim_prefix(regstring); @@ -183,25 +186,7 @@ static std::wstring improve_name_from_base_path(const std::wstring ®path, boo return std::wstring(); } -static void foreach_subkey(HKEY key, std::function<bool(HKEY)> action) -{ - for (int i = 0; ; i++) - { - std::wstring name = reg_enum_key(key, i); - if (name.empty()) - break; - - safe_regkey subkey = reg_open_key(key, name); - if (!subkey.valid()) - break; - - bool shouldcontinue = action(subkey); - if (!shouldcontinue) - break; - } -} - -static std::wstring improve_name_from_usb_path(const std::wstring ®path) +std::wstring improve_name_from_usb_path(const std::wstring ®path) { static const std::wstring usbbasepath(L"SYSTEM\\CurrentControlSet\\Enum\\USB"); @@ -213,40 +198,43 @@ static std::wstring improve_name_from_usb_path(const std::wstring ®path) std::wstring parentid = regpath.substr(last_slash_index + 1); // open the USB key - auto usb_key = reg_open_key(HKEY_LOCAL_MACHINE, usbbasepath); - if (!usb_key.valid()) + auto usb_key = safe_regkey::open(HKEY_LOCAL_MACHINE, usbbasepath); + if (!usb_key) return std::wstring(); std::wstring regstring; - foreach_subkey(usb_key, [®string, &parentid](HKEY subkey) - { - foreach_subkey(subkey, [®string, &parentid](HKEY endkey) - { - std::wstring endparentid = reg_query_string(endkey, L"ParentIdPrefix"); + usb_key.foreach_subkey( + [®string, &parentid] (safe_regkey const &subkey) + { + subkey.foreach_subkey( + [®string, &parentid] (safe_regkey const &endkey) + { + std::wstring endparentid = endkey.query_string(L"ParentIdPrefix"); - // This key doesn't have a ParentIdPrefix - if (endparentid.empty()) - return true; + // This key doesn't have a ParentIdPrefix + if (endparentid.empty()) + return true; - // do we have a match? - if (parentid.find(endparentid) == 0) - regstring = reg_query_string(endkey, L"DeviceDesc"); + // do we have a match? + if (parentid.find(endparentid) == 0) + regstring = endkey.query_string(L"DeviceDesc"); - return regstring.empty(); - }); + return regstring.empty(); + }); - return regstring.empty(); - }); + return regstring.empty(); + }); return trim_prefix(regstring); } + //============================================================ // rawinput_device_improve_name //============================================================ -static std::wstring rawinput_device_improve_name(const std::wstring &name) +std::wstring rawinput_device_improve_name(const std::wstring &name) { // The RAW name received is formatted as: // \??\type-id#hardware-id#instance-id#{DeviceClasses-id} @@ -282,20 +270,36 @@ static std::wstring rawinput_device_improve_name(const std::wstring &name) class rawinput_device : public event_based_device<RAWINPUT> { -private: - HANDLE m_handle; - public: - rawinput_device(running_machine& machine, const char *name, const char *id, input_device_class deviceclass, input_module& module) - : event_based_device(machine, name, id, deviceclass, module), - m_handle(nullptr) + rawinput_device(std::string &&name, std::string &&id, input_module &module, HANDLE handle) : + event_based_device(std::move(name), std::move(id), module), + m_handle(handle) { } HANDLE device_handle() const { return m_handle; } - void set_handle(HANDLE handle) { m_handle = handle; } + + bool reconnect_candidate(std::string_view i) const { return !m_handle && (id() == i); } + + void detach_device() + { + assert(m_handle); + m_handle = nullptr; + osd_printf_verbose("RawInput: %s [ID %s] disconnected\n", name(), id()); + } + + void attach_device(HANDLE handle) + { + assert(!m_handle); + m_handle = handle; + osd_printf_verbose("RawInput: %s [ID %s] reconnected\n", name(), id()); + } + +private: + HANDLE m_handle; }; + //============================================================ // rawinput_keyboard_device //============================================================ @@ -303,490 +307,672 @@ public: class rawinput_keyboard_device : public rawinput_device { public: - keyboard_state keyboard; + rawinput_keyboard_device(std::string &&name, std::string &&id, input_module &module, HANDLE handle) : + rawinput_device(std::move(name), std::move(id), module, handle), + m_pause_pressed(std::chrono::steady_clock::time_point::min()), + m_e1(0xffff), + m_keyboard({ { 0 } }) + { + } - rawinput_keyboard_device(running_machine& machine, const char *name, const char *id, input_module& module) - : rawinput_device(machine, name, id, DEVICE_CLASS_KEYBOARD, module), - keyboard({{0}}) + virtual void reset() override { + rawinput_device::reset(); + m_pause_pressed = std::chrono::steady_clock::time_point::min(); + memset(&m_keyboard, 0, sizeof(m_keyboard)); + m_e1 = 0xffff; } - void reset() override + virtual void poll(bool relative_reset) override { - memset(&keyboard, 0, sizeof(keyboard)); + rawinput_device::poll(relative_reset); + if (m_keyboard.state[0x80 | 0x45] && (std::chrono::steady_clock::now() > (m_pause_pressed + std::chrono::milliseconds(30)))) + m_keyboard.state[0x80 | 0x45] = 0x00; } - void process_event(RAWINPUT &rawinput) override + virtual void process_event(RAWINPUT const &rawinput) override { // determine the full DIK-compatible scancode - uint8_t scancode = (rawinput.data.keyboard.MakeCode & 0x7f) | ((rawinput.data.keyboard.Flags & RI_KEY_E0) ? 0x80 : 0x00); + uint8_t scancode; - // scancode 0xaa is a special shift code we need to ignore - if (scancode == 0xaa) + // the only thing that uses this is Pause + if (rawinput.data.keyboard.Flags & RI_KEY_E1) + { + m_e1 = rawinput.data.keyboard.MakeCode; return; + } + else if (0xffff != m_e1) + { + auto const e1 = std::exchange(m_e1, 0xffff); + if (!(rawinput.data.keyboard.Flags & RI_KEY_E0)) + { + if (((e1 & ~USHORT(0x80)) == 0x1d) && ((rawinput.data.keyboard.MakeCode & ~USHORT(0x80)) == 0x45)) + { + if (rawinput.data.keyboard.Flags & RI_KEY_BREAK) + return; // RawInput generates a fake break immediately after the make - ignore it + + m_pause_pressed = std::chrono::steady_clock::now(); + scancode = 0x80 | 0x45; + } + else + { + return; // no idea + } + } + else + { + return; // shouldn't happen, ignore it + } + } + else + { + // strip bit 7 of the make code to work around dodgy drivers that set it for key up events + if (rawinput.data.keyboard.MakeCode & ~USHORT(0xff)) + { + // won't fit in a byte along with the E0 flag + return; + } + scancode = (rawinput.data.keyboard.MakeCode & 0x7f) | ((rawinput.data.keyboard.Flags & RI_KEY_E0) ? 0x80 : 0x00); + + // fake shift generated with cursor control and Ins/Del for compatibility with very old DOS software + if (scancode == 0xaa) + return; + } // set or clear the key - keyboard.state[scancode] = (rawinput.data.keyboard.Flags & RI_KEY_BREAK) ? 0x00 : 0x80; + m_keyboard.state[scancode] = (rawinput.data.keyboard.Flags & RI_KEY_BREAK) ? 0x00 : 0x80; } + + virtual void configure(input_device &device) override + { + keyboard_trans_table const &table = keyboard_trans_table::instance(); + + // FIXME: GetKeyNameTextW is for scan codes from WM_KEYDOWN, which aren't quite the same as DIK_* keycodes + // in particular, NumLock and Pause are reversed for US-style keyboard systems + for (unsigned keynum = 0; keynum < MAX_KEYS; keynum++) + { + input_item_id itemid = table.map_di_scancode_to_itemid(keynum); + WCHAR keyname[100]; + + // generate the name + // FIXME: GetKeyNameText gives bogus names for media keys and various other things + // in many cases it ignores the "extended" bit and returns the key name corresponding to the scan code alone + LONG lparam = ((keynum & 0x7f) << 16) | ((keynum & 0x80) << 17); + if ((keynum & 0x7f) == 0x45) + lparam ^= 0x0100'0000; // horrid hack + if (GetKeyNameTextW(lparam, keyname, std::size(keyname)) == 0) + _snwprintf(keyname, std::size(keyname), L"Scan%03d", keynum); + std::string name = text::from_wstring(keyname); + + // add the item to the device + device.add_item( + name, + util::string_format("SCAN%03d", keynum), + itemid, + generic_button_get_state<std::uint8_t>, + &m_keyboard.state[keynum]); + } + } + +private: + std::chrono::steady_clock::time_point m_pause_pressed; + uint16_t m_e1; + keyboard_state m_keyboard; }; + //============================================================ // rawinput_mouse_device //============================================================ class rawinput_mouse_device : public rawinput_device { -private: - std::mutex m_device_lock; public: - mouse_state mouse; - - rawinput_mouse_device(running_machine& machine, const char *name, const char *id, input_module& module) - : rawinput_device(machine, name, id, DEVICE_CLASS_MOUSE, module), - mouse({0}) + rawinput_mouse_device(std::string &&name, std::string &&id, input_module &module, HANDLE handle) : + rawinput_device(std::move(name), std::move(id), module, handle), + m_mouse({0}), + m_x(0), + m_y(0), + m_v(0), + m_h(0) { } - void poll() override + virtual void poll(bool relative_reset) override { - mouse.lX = 0; - mouse.lY = 0; - mouse.lZ = 0; - - rawinput_device::poll(); + rawinput_device::poll(relative_reset); + if (relative_reset) + { + m_mouse.lX = std::exchange(m_x, 0); + m_mouse.lY = std::exchange(m_y, 0); + m_mouse.lV = std::exchange(m_v, 0); + m_mouse.lH = std::exchange(m_h, 0); + } } - void reset() override + virtual void reset() override { - memset(&mouse, 0, sizeof(mouse)); + rawinput_device::reset(); + memset(&m_mouse, 0, sizeof(m_mouse)); + m_x = m_y = m_v = m_h = 0; } - void process_event(RAWINPUT &rawinput) override + virtual void configure(input_device &device) override { + // populate the axes + device.add_item( + "X", + std::string_view(), + ITEM_ID_XAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lX); + device.add_item( + "Y", + std::string_view(), + ITEM_ID_YAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lY); + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ZAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_RZAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lH); + + // populate the buttons + for (int butnum = 0; butnum < 5; butnum++) + { + device.add_item( + default_button_name(butnum), + std::string_view(), + input_item_id(ITEM_ID_BUTTON1 + butnum), + generic_button_get_state<BYTE>, + &m_mouse.rgbButtons[butnum]); + } + } + virtual void process_event(RAWINPUT const &rawinput) override + { // If this data was intended for a rawinput mouse if (rawinput.data.mouse.usFlags == MOUSE_MOVE_RELATIVE) { + m_x += rawinput.data.mouse.lLastX * input_device::RELATIVE_PER_PIXEL; + m_y += rawinput.data.mouse.lLastY * input_device::RELATIVE_PER_PIXEL; - mouse.lX += rawinput.data.mouse.lLastX * INPUT_RELATIVE_PER_PIXEL; - mouse.lY += rawinput.data.mouse.lLastY * INPUT_RELATIVE_PER_PIXEL; - - // update zaxis + // update Z/Rz axes (vertical/horizontal scroll) if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_WHEEL) - mouse.lZ += static_cast<int16_t>(rawinput.data.mouse.usButtonData) * INPUT_RELATIVE_PER_PIXEL; + m_v += int16_t(rawinput.data.mouse.usButtonData) * input_device::RELATIVE_PER_PIXEL; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_HWHEEL) + m_h += int16_t(rawinput.data.mouse.usButtonData) * input_device::RELATIVE_PER_PIXEL; // update the button states; always update the corresponding mouse buttons - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) mouse.rgbButtons[0] = 0x80; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) mouse.rgbButtons[0] = 0x00; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) mouse.rgbButtons[1] = 0x80; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) mouse.rgbButtons[1] = 0x00; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) mouse.rgbButtons[2] = 0x80; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) mouse.rgbButtons[2] = 0x00; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) mouse.rgbButtons[3] = 0x80; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) mouse.rgbButtons[3] = 0x00; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) mouse.rgbButtons[4] = 0x80; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) mouse.rgbButtons[4] = 0x00; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) m_mouse.rgbButtons[0] = 0x80; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) m_mouse.rgbButtons[0] = 0x00; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) m_mouse.rgbButtons[1] = 0x80; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) m_mouse.rgbButtons[1] = 0x00; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) m_mouse.rgbButtons[2] = 0x80; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) m_mouse.rgbButtons[2] = 0x00; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) m_mouse.rgbButtons[3] = 0x80; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) m_mouse.rgbButtons[3] = 0x00; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) m_mouse.rgbButtons[4] = 0x80; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) m_mouse.rgbButtons[4] = 0x00; } } + +private: + mouse_state m_mouse; + LONG m_x, m_y, m_v, m_h; }; + //============================================================ // rawinput_lightgun_device //============================================================ class rawinput_lightgun_device : public rawinput_device { -private: - std::mutex m_device_lock; public: - mouse_state lightgun; - - rawinput_lightgun_device(running_machine& machine, const char *name, const char *id, input_module& module) - : rawinput_device(machine, name, id, DEVICE_CLASS_LIGHTGUN, module), - lightgun({0}) + rawinput_lightgun_device(std::string &&name, std::string &&id, input_module &module, HANDLE handle) : + rawinput_device(std::move(name), std::move(id), module, handle), + m_lightgun({0}), + m_v(0), + m_h(0) { } - void poll() override + virtual void poll(bool relative_reset) override { - lightgun.lZ = 0; + rawinput_device::poll(relative_reset); + if (relative_reset) + { + m_lightgun.lV = std::exchange(m_v, 0); + m_lightgun.lH = std::exchange(m_h, 0); + } + } - rawinput_device::poll(); + virtual void reset() override + { + rawinput_device::reset(); + memset(&m_lightgun, 0, sizeof(m_lightgun)); + m_v = 0; + m_h = 0; } - void reset() override + virtual void configure(input_device &device) override { - memset(&lightgun, 0, sizeof(lightgun)); + // populate the axes + for (int axisnum = 0; axisnum < 2; axisnum++) + { + device.add_item( + default_axis_name[axisnum], + std::string_view(), + input_item_id(ITEM_ID_XAXIS + axisnum), + generic_axis_get_state<LONG>, + &m_lightgun.lX + axisnum); + } + + // scroll wheels are always relative if present + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ADD_RELATIVE1, + generic_axis_get_state<LONG>, + &m_lightgun.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_ADD_RELATIVE2, + generic_axis_get_state<LONG>, + &m_lightgun.lH); + + // populate the buttons + for (int butnum = 0; butnum < 5; butnum++) + { + device.add_item( + default_button_name(butnum), + std::string_view(), + input_item_id(ITEM_ID_BUTTON1 + butnum), + generic_button_get_state<BYTE>, + &m_lightgun.rgbButtons[butnum]); + } } - void process_event(RAWINPUT &rawinput) override + virtual void process_event(RAWINPUT const &rawinput) override { // If this data was intended for a rawinput lightgun if (rawinput.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - // update the X/Y positions - lightgun.lX = normalize_absolute_axis(rawinput.data.mouse.lLastX, 0, INPUT_ABSOLUTE_MAX); - lightgun.lY = normalize_absolute_axis(rawinput.data.mouse.lLastY, 0, INPUT_ABSOLUTE_MAX); + m_lightgun.lX = normalize_absolute_axis(rawinput.data.mouse.lLastX, 0, input_device::ABSOLUTE_MAX); + m_lightgun.lY = normalize_absolute_axis(rawinput.data.mouse.lLastY, 0, input_device::ABSOLUTE_MAX); - // update zaxis + // update Z/Rz axes if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_WHEEL) - lightgun.lZ += static_cast<int16_t>(rawinput.data.mouse.usButtonData) * INPUT_RELATIVE_PER_PIXEL; + m_v += int16_t(rawinput.data.mouse.usButtonData) * input_device::RELATIVE_PER_PIXEL; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_HWHEEL) + m_h += int16_t(rawinput.data.mouse.usButtonData) * input_device::RELATIVE_PER_PIXEL; // update the button states; always update the corresponding mouse buttons - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) lightgun.rgbButtons[0] = 0x80; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) lightgun.rgbButtons[0] = 0x00; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) lightgun.rgbButtons[1] = 0x80; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) lightgun.rgbButtons[1] = 0x00; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) lightgun.rgbButtons[2] = 0x80; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) lightgun.rgbButtons[2] = 0x00; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) lightgun.rgbButtons[3] = 0x80; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) lightgun.rgbButtons[3] = 0x00; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) lightgun.rgbButtons[4] = 0x80; - if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) lightgun.rgbButtons[4] = 0x00; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) m_lightgun.rgbButtons[0] = 0x80; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) m_lightgun.rgbButtons[0] = 0x00; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) m_lightgun.rgbButtons[1] = 0x80; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) m_lightgun.rgbButtons[1] = 0x00; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) m_lightgun.rgbButtons[2] = 0x80; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) m_lightgun.rgbButtons[2] = 0x00; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) m_lightgun.rgbButtons[3] = 0x80; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) m_lightgun.rgbButtons[3] = 0x00; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) m_lightgun.rgbButtons[4] = 0x80; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) m_lightgun.rgbButtons[4] = 0x00; } } + +private: + mouse_state m_lightgun; + LONG m_v, m_h; }; + //============================================================ -// rawinput_module - base class for rawinput modules +// rawinput_module - base class for RawInput modules //============================================================ -class rawinput_module : public wininput_module +class rawinput_module : public wininput_module<rawinput_device> { private: - osd::dynamic_module::ptr m_user32_dll; - get_rawinput_device_list_ptr get_rawinput_device_list; - get_rawinput_data_ptr get_rawinput_data; - get_rawinput_device_info_ptr get_rawinput_device_info; - register_rawinput_devices_ptr register_rawinput_devices; - std::mutex m_module_lock; + std::mutex m_module_lock; public: - rawinput_module(const char *type, const char* name) - : wininput_module(type, name), - get_rawinput_device_list(nullptr), - get_rawinput_data(nullptr), - get_rawinput_device_info(nullptr), - register_rawinput_devices(nullptr) + rawinput_module(const char *type, const char *name) : wininput_module<rawinput_device>(type, name) { } - bool probe() override + virtual bool probe() override { - m_user32_dll = osd::dynamic_module::open({ "user32.dll" }); - - get_rawinput_device_list = m_user32_dll->bind<get_rawinput_device_list_ptr>("GetRawInputDeviceList"); - get_rawinput_data = m_user32_dll->bind<get_rawinput_data_ptr>("GetRawInputData"); - get_rawinput_device_info = m_user32_dll->bind<get_rawinput_device_info_ptr>("GetRawInputDeviceInfoW"); - register_rawinput_devices = m_user32_dll->bind<register_rawinput_devices_ptr>("RegisterRawInputDevices"); - - if (!get_rawinput_device_list || !get_rawinput_data || - !get_rawinput_device_info || !register_rawinput_devices ) - { - return false; - } - return true; } - void input_init(running_machine &machine) override + virtual void input_init(running_machine &machine) override { - // get the number of devices, allocate a device list, and fetch it - UINT device_count = 0; - if ((*get_rawinput_device_list)(nullptr, &device_count, sizeof(RAWINPUTDEVICELIST)) != 0) - return; + wininput_module<rawinput_device>::input_init(machine); - if (device_count == 0) + // get initial number of devices + UINT device_count = 0; + if (GetRawInputDeviceList(nullptr, &device_count, sizeof(RAWINPUTDEVICELIST)) != 0) + { + osd_printf_error("Error getting initial number of RawInput devices.\n"); return; - - auto rawinput_devices = std::make_unique<RAWINPUTDEVICELIST[]>(device_count); - if ((*get_rawinput_device_list)(rawinput_devices.get(), &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) + } + if (!device_count) return; - // iterate backwards through devices; new devices are added at the head - for (int devnum = device_count - 1; devnum >= 0; devnum--) + std::unique_ptr<RAWINPUTDEVICELIST []> rawinput_devices; + UINT retrieved; + do { - RAWINPUTDEVICELIST *device = &rawinput_devices[devnum]; - add_rawinput_device(machine, device); + rawinput_devices.reset(new (std::nothrow) RAWINPUTDEVICELIST [device_count]); + if (!rawinput_devices) + { + osd_printf_error("Error allocating buffer for RawInput device list.\n"); + return; + } + retrieved = GetRawInputDeviceList(rawinput_devices.get(), &device_count, sizeof(RAWINPUTDEVICELIST)); } - - // don't enable global inputs when debugging - if (!machine.options().debug()) + while ((UINT(-1) == retrieved) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)); + if (UINT(-1) == retrieved) { - m_global_inputs_enabled = downcast<windows_options &>(machine.options()).global_inputs(); + osd_printf_error("Error listing RawInput devices.\n"); + return; } + // iterate backwards through devices; new devices are added at the head + for (int devnum = retrieved - 1; devnum >= 0; devnum--) + add_rawinput_device(rawinput_devices[devnum]); + // If we added no devices, no need to register for notifications - if (devicelist()->size() == 0) + if (devicelist().empty()) return; // finally, register to receive raw input WM_INPUT messages if we found devices RAWINPUTDEVICE registration; registration.usUsagePage = usagepage(); registration.usUsage = usage(); - registration.dwFlags = m_global_inputs_enabled ? 0x00000100 : 0; - registration.hwndTarget = std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window(); + registration.dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; + registration.hwndTarget = dynamic_cast<win_window_info &>(*osd_common_t::window_list().front()).platform_window(); // register the device - (*register_rawinput_devices)(®istration, 1, sizeof(registration)); + RegisterRawInputDevices(®istration, 1, sizeof(registration)); } protected: - virtual void add_rawinput_device(running_machine& machine, RAWINPUTDEVICELIST * device) = 0; + virtual void add_rawinput_device(RAWINPUTDEVICELIST const &device) = 0; virtual USHORT usagepage() = 0; virtual USHORT usage() = 0; - int init_internal() override - { - if (!get_rawinput_device_list || !get_rawinput_data || - !get_rawinput_device_info || !register_rawinput_devices ) - { - return 1; - } - - osd_printf_verbose("RawInput: APIs detected\n"); - return 0; - } - template<class TDevice> - TDevice* create_rawinput_device(running_machine &machine, PRAWINPUTDEVICELIST rawinputdevice) + TDevice *create_rawinput_device(input_device_class deviceclass, RAWINPUTDEVICELIST const &rawinputdevice) { - TDevice* devinfo; - UINT name_length = 0; // determine the length of the device name, allocate it, and fetch it if not nameless - if ((*get_rawinput_device_info)(rawinputdevice->hDevice, RIDI_DEVICENAME, nullptr, &name_length) != 0) + UINT name_length = 0; + if (GetRawInputDeviceInfoW(rawinputdevice.hDevice, RIDI_DEVICENAME, nullptr, &name_length) != 0) return nullptr; - std::unique_ptr<TCHAR[]> tname = std::make_unique<TCHAR[]>(name_length + 1); - if (name_length > 1 && (*get_rawinput_device_info)(rawinputdevice->hDevice, RIDI_DEVICENAME, tname.get(), &name_length) == -1) + std::unique_ptr<WCHAR []> tname = std::make_unique<WCHAR []>(name_length + 1); + if (name_length > 1 && GetRawInputDeviceInfoW(rawinputdevice.hDevice, RIDI_DEVICENAME, tname.get(), &name_length) == UINT(-1)) return nullptr; // if this is an RDP name, skip it - if (_tcsstr(tname.get(), TEXT("Root#RDP_")) != nullptr) + if (wcsstr(tname.get(), L"Root#RDP_") != nullptr) return nullptr; - // improve the name and then allocate a device - std::wstring name = rawinput_device_improve_name(tname.get()); - - // convert name to utf8 - std::string utf8_name = osd::text::from_wstring(name.c_str()); - - // set device id to raw input name - std::string utf8_id = osd::text::from_wstring(tname.get()); + // improve the name + std::string utf8_name = text::from_wstring(rawinput_device_improve_name(tname.get())); - devinfo = devicelist()->create_device<TDevice>(machine, utf8_name.c_str(), utf8_id.c_str(), *this); + // set device ID to raw input name + std::string utf8_id = text::from_wstring(tname.get()); - // Add the handle - devinfo->set_handle(rawinputdevice->hDevice); + tname.reset(); - return devinfo; + // allocate a device + return &create_device<TDevice>( + deviceclass, + std::move(utf8_name), + std::move(utf8_id), + rawinputdevice.hDevice); } - bool handle_input_event(input_event eventid, void* eventdata) override + virtual bool handle_input_event(input_event eventid, void const *eventdata) override { - // Only handle raw input data - if (!input_enabled() || eventid != INPUT_EVENT_RAWINPUT) - return false; - - HRAWINPUT rawinputdevice = *static_cast<HRAWINPUT*>(eventdata); - - BYTE small_buffer[4096]; - std::unique_ptr<BYTE[]> larger_buffer; - LPBYTE data = small_buffer; - UINT size; - - // ignore if not enabled - if (!input_enabled()) - return false; + switch (eventid) + { + // handle raw input data + case INPUT_EVENT_RAWINPUT: + { + HRAWINPUT const rawinputdevice = *reinterpret_cast<HRAWINPUT const *>(eventdata); + + union { RAWINPUT r; BYTE b[4096]; } small_buffer; + std::unique_ptr<BYTE []> larger_buffer; + LPVOID data = &small_buffer; + UINT size; + + // determine the size of data buffer we need + if (GetRawInputData(rawinputdevice, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)) != 0) + return false; + + // if necessary, allocate a temporary buffer and fetch the data + if (size > sizeof(small_buffer)) + { + larger_buffer.reset(new (std::align_val_t(alignof(RAWINPUT)), std::nothrow) BYTE [size]); + data = larger_buffer.get(); + if (!data) + return false; + } + + // fetch the data and process the appropriate message types + UINT result = GetRawInputData(rawinputdevice, RID_INPUT, data, &size, sizeof(RAWINPUTHEADER)); + if (UINT(-1) == result) + { + return false; + } + else if (result) + { + std::lock_guard<std::mutex> scope_lock(m_module_lock); + + auto *const input = reinterpret_cast<RAWINPUT const *>(data); + if (!input->header.hDevice) + return false; + + // find the device in the list and update + auto target_device = std::find_if( + devicelist().begin(), + devicelist().end(), + [input] (auto const &device) + { + return input->header.hDevice == device->device_handle(); + }); + if (devicelist().end() == target_device) + return false; + + (*target_device)->queue_events(input, 1); + return true; + } + } + break; - // determine the size of databuffer we need - if ((*get_rawinput_data)(rawinputdevice, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)) != 0) - return false; + case INPUT_EVENT_ARRIVAL: + { + HRAWINPUT const rawinputdevice = *reinterpret_cast<HRAWINPUT const *>(eventdata); + + // determine the length of the device name, allocate it, and fetch it if not nameless + UINT name_length = 0; + if (GetRawInputDeviceInfoW(rawinputdevice, RIDI_DEVICENAME, nullptr, &name_length) != 0) + return false; + + std::unique_ptr<WCHAR []> tname = std::make_unique<WCHAR []>(name_length + 1); + if (name_length > 1 && GetRawInputDeviceInfoW(rawinputdevice, RIDI_DEVICENAME, tname.get(), &name_length) == UINT(-1)) + return false; + std::string utf8_id = text::from_wstring(tname.get()); + tname.reset(); + + std::lock_guard<std::mutex> scope_lock(m_module_lock); + + // find the device in the list and update + auto target_device = std::find_if( + devicelist().begin(), + devicelist().end(), + [&utf8_id] (auto const &device) + { + return device->reconnect_candidate(utf8_id); + }); + if (devicelist().end() == target_device) + return false; + + (*target_device)->attach_device(rawinputdevice); + return true; + } + break; - // if necessary, allocate a temporary buffer and fetch the data - if (size > sizeof(small_buffer)) - { - larger_buffer = std::make_unique<BYTE[]>(size); - data = larger_buffer.get(); - if (data == nullptr) - return false; - } + case INPUT_EVENT_REMOVAL: + { + HRAWINPUT const rawinputdevice = *reinterpret_cast<HRAWINPUT const *>(eventdata); - // fetch the data and process the appropriate message types - bool result = (*get_rawinput_data)(static_cast<HRAWINPUT>(rawinputdevice), RID_INPUT, data, &size, sizeof(RAWINPUTHEADER)); - if (result) - { - std::lock_guard<std::mutex> scope_lock(m_module_lock); + std::lock_guard<std::mutex> scope_lock(m_module_lock); - RAWINPUT *input = reinterpret_cast<RAWINPUT*>(data); + // find the device in the list and update + auto target_device = std::find_if( + devicelist().begin(), + devicelist().end(), + [rawinputdevice] (auto const &device) + { + return rawinputdevice == device->device_handle(); + }); - // find the device in the list and update - auto target_device = std::find_if(devicelist()->begin(), devicelist()->end(), [input](auto &device) - { - auto devinfo = dynamic_cast<rawinput_device*>(device.get()); - return devinfo != nullptr && input->header.hDevice == devinfo->device_handle(); - }); + if (devicelist().end() == target_device) + return false; - if (target_device != devicelist()->end()) - { - static_cast<rawinput_device*>((*target_device).get())->queue_events(input, 1); + (*target_device)->reset(); + (*target_device)->detach_device(); return true; } + break; + + default: + break; } + // must have been unhandled return false; } }; + //============================================================ -// keyboard_input_rawinput - rawinput keyboard module +// keyboard_input_rawinput - RawInput keyboard module //============================================================ class keyboard_input_rawinput : public rawinput_module { public: - keyboard_input_rawinput() - : rawinput_module(OSD_KEYBOARDINPUT_PROVIDER, "rawinput") + keyboard_input_rawinput() : rawinput_module(OSD_KEYBOARDINPUT_PROVIDER, "rawinput") { } protected: - USHORT usagepage() override { return 1; } - USHORT usage() override { return 6; } - void add_rawinput_device(running_machine& machine, RAWINPUTDEVICELIST * device) override + virtual USHORT usagepage() override { return 1; } + virtual USHORT usage() override { return 6; } + + virtual void add_rawinput_device(RAWINPUTDEVICELIST const &device) override { // make sure this is a keyboard - if (device->dwType != RIM_TYPEKEYBOARD) + if (device.dwType != RIM_TYPEKEYBOARD) return; // allocate and link in a new device - rawinput_keyboard_device *devinfo = create_rawinput_device<rawinput_keyboard_device>(machine, device); - if (devinfo == nullptr) - return; - - keyboard_trans_table &table = keyboard_trans_table::instance(); - - // populate it - for (int keynum = 0; keynum < MAX_KEYS; keynum++) - { - input_item_id itemid = table.map_di_scancode_to_itemid(keynum); - TCHAR keyname[100]; - - // generate the name - if (GetKeyNameText(((keynum & 0x7f) << 16) | ((keynum & 0x80) << 17), keyname, ARRAY_LENGTH(keyname)) == 0) - _sntprintf(keyname, ARRAY_LENGTH(keyname), TEXT("Scan%03d"), keynum); - std::string name = osd::text::from_tstring(keyname); - - // add the item to the device - devinfo->device()->add_item(name.c_str(), itemid, generic_button_get_state<std::uint8_t>, &devinfo->keyboard.state[keynum]); - } + create_rawinput_device<rawinput_keyboard_device>(DEVICE_CLASS_KEYBOARD, device); } }; + //============================================================ -// mouse_input_rawinput - rawinput mouse module +// mouse_input_rawinput - RawInput mouse module //============================================================ class mouse_input_rawinput : public rawinput_module { public: - mouse_input_rawinput() - : rawinput_module(OSD_MOUSEINPUT_PROVIDER, "rawinput") + mouse_input_rawinput() : rawinput_module(OSD_MOUSEINPUT_PROVIDER, "rawinput") { } protected: - USHORT usagepage() override { return 1; } - USHORT usage() override { return 2; } - void add_rawinput_device(running_machine& machine, RAWINPUTDEVICELIST * device) override + virtual USHORT usagepage() override { return 1; } + virtual USHORT usage() override { return 2; } + + virtual void add_rawinput_device(RAWINPUTDEVICELIST const &device) override { // make sure this is a mouse - if (device->dwType != RIM_TYPEMOUSE) + if (device.dwType != RIM_TYPEMOUSE) return; // allocate and link in a new device - rawinput_mouse_device *devinfo = create_rawinput_device<rawinput_mouse_device>(machine, device); - if (devinfo == nullptr) - return; - - // populate the axes - for (int axisnum = 0; axisnum < 3; axisnum++) - { - devinfo->device()->add_item( - default_axis_name[axisnum], - static_cast<input_item_id>(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &devinfo->mouse.lX + axisnum); - } - - // populate the buttons - for (int butnum = 0; butnum < 5; butnum++) - { - devinfo->device()->add_item( - default_button_name(butnum), - static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum), - generic_button_get_state<BYTE>, - &devinfo->mouse.rgbButtons[butnum]); - } + create_rawinput_device<rawinput_mouse_device>(DEVICE_CLASS_MOUSE, device); } }; + //============================================================ -// lightgun_input_rawinput - rawinput lightgun module +// lightgun_input_rawinput - RawInput lightgun module //============================================================ class lightgun_input_rawinput : public rawinput_module { public: - lightgun_input_rawinput() - : rawinput_module(OSD_LIGHTGUNINPUT_PROVIDER, "rawinput") + lightgun_input_rawinput() : rawinput_module(OSD_LIGHTGUNINPUT_PROVIDER, "rawinput") { } protected: - USHORT usagepage() override { return 1; } - USHORT usage() override { return 2; } - void add_rawinput_device(running_machine& machine, RAWINPUTDEVICELIST * device) override - { + virtual USHORT usagepage() override { return 1; } + virtual USHORT usage() override { return 2; } + virtual void add_rawinput_device(RAWINPUTDEVICELIST const &device) override + { // make sure this is a mouse - if (device->dwType != RIM_TYPEMOUSE) + if (device.dwType != RIM_TYPEMOUSE) return; // allocate and link in a new device - rawinput_lightgun_device *devinfo = create_rawinput_device<rawinput_lightgun_device>(machine, device); - if (devinfo == nullptr) - return; - - // populate the axes - for (int axisnum = 0; axisnum < 3; axisnum++) - { - devinfo->device()->add_item( - default_axis_name[axisnum], - static_cast<input_item_id>(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &devinfo->lightgun.lX + axisnum); - } - - // populate the buttons - for (int butnum = 0; butnum < 5; butnum++) - { - devinfo->device()->add_item( - default_button_name(butnum), - static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum), - generic_button_get_state<BYTE>, - &devinfo->lightgun.rgbButtons[butnum]); - } + create_rawinput_device<rawinput_lightgun_device>(DEVICE_CLASS_LIGHTGUN, device); } }; -#else +} // anonymous namespace + +} // namespace osd + +#else // defined(OSD_WINDOWS) + +#include "input_module.h" + +namespace osd { + +namespace { + MODULE_NOT_SUPPORTED(keyboard_input_rawinput, OSD_KEYBOARDINPUT_PROVIDER, "rawinput") MODULE_NOT_SUPPORTED(mouse_input_rawinput, OSD_MOUSEINPUT_PROVIDER, "rawinput") MODULE_NOT_SUPPORTED(lightgun_input_rawinput, OSD_LIGHTGUNINPUT_PROVIDER, "rawinput") -#endif -MODULE_DEFINITION(KEYBOARDINPUT_RAWINPUT, keyboard_input_rawinput) -MODULE_DEFINITION(MOUSEINPUT_RAWINPUT, mouse_input_rawinput) -MODULE_DEFINITION(LIGHTGUNINPUT_RAWINPUT, lightgun_input_rawinput) +} // anonymous namespace + +} // namespace osd + +#endif // defined(OSD_WINDOWS) + +MODULE_DEFINITION(KEYBOARDINPUT_RAWINPUT, osd::keyboard_input_rawinput) +MODULE_DEFINITION(MOUSEINPUT_RAWINPUT, osd::mouse_input_rawinput) +MODULE_DEFINITION(LIGHTGUNINPUT_RAWINPUT, osd::lightgun_input_rawinput) diff --git a/src/osd/modules/input/input_sdl.cpp b/src/osd/modules/input/input_sdl.cpp index 40fe037f8c7..f1fb0241b40 100644 --- a/src/osd/modules/input/input_sdl.cpp +++ b/src/osd/modules/input/input_sdl.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Olivier Galibert, R. Belmont, Brad Hughes +// copyright-holders:Olivier Galibert, R. Belmont, Brad Hughes, Vas Crabb //============================================================ // // input_sdl.cpp - SDL 2.0 implementation of MAME input routines @@ -12,32 +12,39 @@ //============================================================ #include "input_module.h" + #include "modules/osdmodule.h" -#if defined(SDLMAME_SDL2) +#if defined(OSD_SDL) -// standard sdl header -#include <SDL2/SDL.h> -#include <ctype.h> -// ReSharper disable once CppUnusedIncludeDirective -#include <stddef.h> -#include <mutex> -#include <memory> -#include <queue> -#include <iterator> -#include <algorithm> +#include "assignmenthelper.h" +#include "input_common.h" -// MAME headers -#include "emu.h" -#include "uiinput.h" -#include "strconv.h" +#include "interface/inputseq.h" +#include "modules/lib/osdobj_common.h" +#include "sdl/osdsdl.h" -// MAMEOS headers -#include "input_common.h" -#include "../lib/osdobj_common.h" -#include "input_sdlcommon.h" -#include "../../sdl/osdsdl.h" -#include "../../sdl/window.h" +// emu +#include "inpttype.h" + +// standard SDL header +#include <SDL2/SDL.h> + +#include <algorithm> +#include <cctype> +#include <chrono> +#include <cmath> +#include <cstddef> +#include <cstring> +#include <initializer_list> +#include <iterator> +#include <list> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <tuple> +#include <utility> // winnt.h defines this #ifdef DELETE @@ -45,9 +52,217 @@ #endif -// FIXME: sdl does not properly report the window for certain OS. -#define GET_FOCUS_WINDOW(ev) focus_window() -//#define GET_FOCUS_WINDOW(ev) window_from_id((ev)->windowID) +namespace osd { + +namespace { + +char const *const CONTROLLER_AXIS_XBOX[]{ + "LSX", + "LSY", + "RSX", + "RSY", + "LT", + "RT" }; + +char const *const CONTROLLER_AXIS_PS[]{ + "LSX", + "LSY", + "RSX", + "RSY", + "L2", + "R2" }; + +char const *const CONTROLLER_AXIS_SWITCH[]{ + "LSX", + "LSY", + "RSX", + "RSY", + "ZL", + "ZR" }; + +char const *const CONTROLLER_BUTTON_XBOX360[]{ + "A", + "B", + "X", + "Y", + "Back", + "Guide", + "Start", + "LSB", + "RSB", + "LB", + "RB", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Share", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +char const *const CONTROLLER_BUTTON_XBOXONE[]{ + "A", + "B", + "X", + "Y", + "View", + "Logo", + "Menu", + "LSB", + "RSB", + "LB", + "RB", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Share", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +char const *const CONTROLLER_BUTTON_PS3[]{ + "Cross", + "Circle", + "Square", + "Triangle", + "Select", + "PS", + "Start", + "L3", + "R3", + "L1", + "R1", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Mute", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +char const *const CONTROLLER_BUTTON_PS4[]{ + "Cross", + "Circle", + "Square", + "Triangle", + "Share", + "PS", + "Options", + "L3", + "R3", + "L1", + "R1", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Mute", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +char const *const CONTROLLER_BUTTON_PS5[]{ + "Cross", + "Circle", + "Square", + "Triangle", + "Create", + "PS", + "Options", + "L3", + "R3", + "L1", + "R1", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Mute", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +char const *const CONTROLLER_BUTTON_SWITCH[]{ + "A", + "B", + "X", + "Y", + "-", + "Home", + "+", + "LSB", + "RSB", + "L", + "R", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Capture", + "RSR", + "LSL", + "RSL", + "LSR", + "Touchpad" }; + +[[maybe_unused]] char const *const CONTROLLER_BUTTON_STADIA[]{ + "A", + "B", + "X", + "Y", + "Options", + "Logo", + "Menu", + "L3", + "R3", + "L1", + "R1", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Capture", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +[[maybe_unused]] char const *const CONTROLLER_BUTTON_SHIELD[]{ + "A", + "B", + "X", + "Y", + "Back", + "Logo", + "Start", + "LSB", + "RSB", + "LB", + "RB", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Share", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; struct key_lookup_table { @@ -56,12 +271,8 @@ struct key_lookup_table }; #define KE(x) { SDL_SCANCODE_ ## x, "SDL_SCANCODE_" #x }, -#define KE8(A, B, C, D, E, F, G, H) KE(A) KE(B) KE(C) KE(D) KE(E) KE(F) KE(G) KE(H) -#define KE7(A, B, C, D, E, F, G) KE(A) KE(B) KE(C) KE(D) KE(E) KE(F) KE(G) -#define KE5(A, B, C, D, E) KE(A) KE(B) KE(C) KE(D) KE(E) -#define KE3(A, B, C) KE(A) KE(B) KE(C) -static key_lookup_table sdl_lookup_table[] = +key_lookup_table const sdl_lookup_table[] = { KE(UNKNOWN) @@ -320,520 +531,1711 @@ static key_lookup_table sdl_lookup_table[] = KE(APP1) KE(APP2) +}; + +//============================================================ +// lookup_sdl_code +//============================================================ +int lookup_sdl_code(std::string_view scode) { - -1, "" + auto const found = std::find_if( + std::begin(sdl_lookup_table), + std::end(sdl_lookup_table), + [&scode] (auto const &key) { return scode == key.name; }); + return (std::end(sdl_lookup_table) != found) ? found->code : -1; } -}; + //============================================================ -// lookup_sdl_code +// sdl_device //============================================================ -static int lookup_sdl_code(const char *scode) +using sdl_device = event_based_device<SDL_Event>; + + +//============================================================ +// sdl_keyboard_device +//============================================================ + +class sdl_keyboard_device : public sdl_device { - int i = 0; +public: + sdl_keyboard_device( + std::string &&name, + std::string &&id, + input_module &module, + keyboard_trans_table const &trans_table) : + sdl_device(std::move(name), std::move(id), module), + m_trans_table(trans_table), + m_keyboard({{0}}), + m_capslock_pressed(std::chrono::steady_clock::time_point::min()) + { + } - while (sdl_lookup_table[i].code >= 0) + virtual void poll(bool relative_reset) override { - if (!strcmp(scode, sdl_lookup_table[i].name)) - return sdl_lookup_table[i].code; - i++; + sdl_device::poll(relative_reset); + +#ifdef __APPLE__ + if (m_keyboard.state[SDL_SCANCODE_CAPSLOCK] && (std::chrono::steady_clock::now() > (m_capslock_pressed + std::chrono::milliseconds(30)))) + m_keyboard.state[SDL_SCANCODE_CAPSLOCK] = 0x00; +#endif } - return -1; -} + + virtual void process_event(SDL_Event const &event) override + { + switch (event.type) + { + case SDL_KEYDOWN: + if (event.key.keysym.scancode == SDL_SCANCODE_CAPSLOCK) + m_capslock_pressed = std::chrono::steady_clock::now(); + + m_keyboard.state[event.key.keysym.scancode] = 0x80; + break; + + case SDL_KEYUP: +#ifdef __APPLE__ + if (event.key.keysym.scancode == SDL_SCANCODE_CAPSLOCK) + break; +#endif + + m_keyboard.state[event.key.keysym.scancode] = 0x00; + break; + } + } + + virtual void reset() override + { + sdl_device::reset(); + memset(&m_keyboard.state, 0, sizeof(m_keyboard.state)); + m_capslock_pressed = std::chrono::steady_clock::time_point::min(); + } + + virtual void configure(input_device &device) override + { + // populate it + for (int keynum = 0; m_trans_table[keynum].mame_key != ITEM_ID_INVALID; keynum++) + { + input_item_id itemid = m_trans_table[keynum].mame_key; + device.add_item( + m_trans_table[keynum].ui_name, + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_keyboard.state[m_trans_table[keynum].sdl_scancode]); + } + } + +private: + // state information for a keyboard + struct keyboard_state + { + s32 state[0x3ff]; // must be s32! + s8 oldkey[MAX_KEYS]; + s8 currkey[MAX_KEYS]; + }; + + keyboard_trans_table const &m_trans_table; + keyboard_state m_keyboard; + std::chrono::steady_clock::time_point m_capslock_pressed; +}; + //============================================================ -// sdl_device +// sdl_mouse_device_base //============================================================ -class sdl_device : public event_based_device<SDL_Event> +class sdl_mouse_device_base : public sdl_device { public: - sdl_device(running_machine &machine, const char *name, const char *id, input_device_class devclass, input_module &module) - : event_based_device(machine, name, id, devclass, module) + virtual void poll(bool relative_reset) override { + sdl_device::poll(relative_reset); + + if (relative_reset) + { + m_mouse.lV = std::exchange(m_v, 0); + m_mouse.lH = std::exchange(m_h, 0); + } + } + + virtual void reset() override + { + sdl_device::reset(); + memset(&m_mouse, 0, sizeof(m_mouse)); + m_v = m_h = 0; } protected: - std::shared_ptr<sdl_window_info> focus_window() + // state information for a mouse + struct mouse_state + { + s32 lX, lY, lV, lH; + s32 buttons[MAX_BUTTONS]; + }; + + sdl_mouse_device_base(std::string &&name, std::string &&id, input_module &module) : + sdl_device(std::move(name), std::move(id), module), + m_mouse({0}), + m_v(0), + m_h(0) { - return sdl_event_manager::instance().focus_window(); } + + void add_common_items(input_device &device, unsigned buttons) + { + // add horizontal and vertical axes - relative for a mouse or absolute for a gun + device.add_item( + "X", + std::string_view(), + ITEM_ID_XAXIS, + generic_axis_get_state<s32>, + &m_mouse.lX); + device.add_item( + "Y", + std::string_view(), + ITEM_ID_YAXIS, + generic_axis_get_state<s32>, + &m_mouse.lY); + + // add buttons + for (int button = 0; button < buttons; button++) + { + input_item_id itemid = (input_item_id)(ITEM_ID_BUTTON1 + button); + int const offset = button ^ (((1 == button) || (2 == button)) ? 3 : 0); + device.add_item( + default_button_name(button), + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_mouse.buttons[offset]); + } + } + + mouse_state m_mouse; + s32 m_v, m_h; }; + //============================================================ -// sdl_keyboard_device +// sdl_mouse_device //============================================================ -class sdl_keyboard_device : public sdl_device +class sdl_mouse_device : public sdl_mouse_device_base { public: - keyboard_state keyboard; + sdl_mouse_device(std::string &&name, std::string &&id, input_module &module) : + sdl_mouse_device_base(std::move(name), std::move(id), module), + m_x(0), + m_y(0) + { + } - sdl_keyboard_device(running_machine &machine, const char *name, const char *id, input_module &module) - : sdl_device(machine, name, id, DEVICE_CLASS_KEYBOARD, module), - keyboard({{0}}) + virtual void poll(bool relative_reset) override { + sdl_mouse_device_base::poll(relative_reset); + + if (relative_reset) + { + m_mouse.lX = std::exchange(m_x, 0); + m_mouse.lY = std::exchange(m_y, 0); + } } - void process_event(SDL_Event &sdlevent) override + virtual void reset() override { - switch (sdlevent.type) + sdl_mouse_device_base::reset(); + m_x = m_y = 0; + } + + virtual void configure(input_device &device) override + { + add_common_items(device, 5); + + // add scroll axes + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ZAXIS, + generic_axis_get_state<s32>, + &m_mouse.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_RZAXIS, + generic_axis_get_state<s32>, + &m_mouse.lH); + } + + virtual void process_event(SDL_Event const &event) override + { + switch (event.type) { - case SDL_KEYDOWN: - keyboard.state[sdlevent.key.keysym.scancode] = 0x80; - if (sdlevent.key.keysym.sym < 0x20) - machine().ui_input().push_char_event(osd_common_t::s_window_list.front()->target(), sdlevent.key.keysym.sym); + case SDL_MOUSEMOTION: + m_x += event.motion.xrel * input_device::RELATIVE_PER_PIXEL; + m_y += event.motion.yrel * input_device::RELATIVE_PER_PIXEL; break; - case SDL_KEYUP: - keyboard.state[sdlevent.key.keysym.scancode] = 0x00; + case SDL_MOUSEBUTTONDOWN: + m_mouse.buttons[event.button.button - 1] = 0x80; break; - case SDL_TEXTINPUT: - if (*sdlevent.text.text) - { - auto window = GET_FOCUS_WINDOW(&event.text); - //printf("Focus window is %p - wl %p\n", window, osd_common_t::s_window_list); - if (window != nullptr) - { - auto ptr = sdlevent.text.text; - auto len = std::strlen(sdlevent.text.text); - while (len) - { - char32_t ch; - auto chlen = uchar_from_utf8(&ch, ptr, len); - if (0 > chlen) - { - ch = 0x0fffd; - chlen = 1; - } - ptr += chlen; - len -= chlen; - machine().ui_input().push_char_event(window->target(), ch); - } - } - } + case SDL_MOUSEBUTTONUP: + m_mouse.buttons[event.button.button - 1] = 0; + break; + + case SDL_MOUSEWHEEL: + // adjust SDL 1-per-click to match Win32 120-per-click +#if SDL_VERSION_ATLEAST(2, 0, 18) + m_v += std::lround(event.wheel.preciseY * 120 * input_device::RELATIVE_PER_PIXEL); + m_h += std::lround(event.wheel.preciseX * 120 * input_device::RELATIVE_PER_PIXEL); +#else + m_v += event.wheel.y * 120 * input_device::RELATIVE_PER_PIXEL; + m_h += event.wheel.x * 120 * input_device::RELATIVE_PER_PIXEL; +#endif break; } } - void reset() override - { - memset(&keyboard.state, 0, sizeof(keyboard.state)); - } +private: + s32 m_x, m_y; }; + //============================================================ -// sdl_mouse_device +// sdl_lightgun_device //============================================================ -class sdl_mouse_device : public sdl_device +class sdl_lightgun_device : public sdl_mouse_device_base { -private: - const std::chrono::milliseconds double_click_speed = std::chrono::milliseconds(250); - std::chrono::system_clock::time_point last_click; - int last_x; - int last_y; - public: - mouse_state mouse; + sdl_lightgun_device(std::string &&name, std::string &&id, input_module &module) : + sdl_mouse_device_base(std::move(name), std::move(id), module), + m_x(0), + m_y(0), + m_window(0) + { + } - sdl_mouse_device(running_machine &machine, const char *name, const char *id, input_module &module) - : sdl_device(machine, name, id, DEVICE_CLASS_MOUSE, module), - last_x(0), - last_y(0), - mouse({0}) + virtual void poll(bool relative_reset) override { + sdl_mouse_device_base::poll(relative_reset); + + SDL_Window *const win(m_window ? SDL_GetWindowFromID(m_window) : nullptr); + if (win) + { + int w, h; + SDL_GetWindowSize(win, &w, &h); + m_mouse.lX = normalize_absolute_axis(m_x, 0, w - 1); + m_mouse.lY = normalize_absolute_axis(m_y, 0, h - 1); + } + else + { + m_mouse.lX = 0; + m_mouse.lY = 0; + } } - void reset() override + virtual void reset() override { - memset(&mouse, 0, sizeof(mouse)); + sdl_mouse_device_base::reset(); + m_x = m_y = 0; + m_window = 0; } - void poll() override + virtual void configure(input_device &device) override { - mouse.lX = 0; - mouse.lY = 0; - sdl_device::poll(); + add_common_items(device, 5); + + // add scroll axes + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ADD_RELATIVE1, + generic_axis_get_state<s32>, + &m_mouse.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_ADD_RELATIVE2, + generic_axis_get_state<s32>, + &m_mouse.lH); } - virtual void process_event(SDL_Event &sdlevent) override + virtual void process_event(SDL_Event const &event) override { - switch (sdlevent.type) + switch (event.type) { case SDL_MOUSEMOTION: - mouse.lX += sdlevent.motion.xrel * INPUT_RELATIVE_PER_PIXEL; - mouse.lY += sdlevent.motion.yrel * INPUT_RELATIVE_PER_PIXEL; - - { - int cx = -1, cy = -1; - auto window = GET_FOCUS_WINDOW(&sdlevent.motion); - - if (window != nullptr && window->xy_to_render_target(sdlevent.motion.x, sdlevent.motion.y, &cx, &cy)) - machine().ui_input().push_mouse_move_event(window->target(), cx, cy); - } + m_x = event.motion.x; + m_y = event.motion.y; + m_window = event.motion.windowID; break; case SDL_MOUSEBUTTONDOWN: - mouse.buttons[sdlevent.button.button - 1] = 0x80; - //printf("But down %d %d %d %d %s\n", event.button.which, event.button.button, event.button.x, event.button.y, devinfo->name.c_str()); - if (sdlevent.button.button == 1) - { - int cx, cy; - auto click = std::chrono::system_clock::now(); - auto window = GET_FOCUS_WINDOW(&sdlevent.button); - if (window != nullptr && window->xy_to_render_target(sdlevent.button.x, sdlevent.button.y, &cx, &cy)) - { - machine().ui_input().push_mouse_down_event(window->target(), cx, cy); + m_mouse.buttons[event.button.button - 1] = 0x80; + m_x = event.button.x; + m_y = event.button.y; + m_window = event.button.windowID; + break; - // avoid overflow with std::chrono::time_point::min() by adding rather than subtracting - if (click < last_click + double_click_speed - && (cx >= last_x - 4 && cx <= last_x + 4) - && (cy >= last_y - 4 && cy <= last_y + 4)) - { - last_click = std::chrono::time_point<std::chrono::system_clock>::min(); - machine().ui_input().push_mouse_double_click_event(window->target(), cx, cy); - } - else - { - last_click = click; - last_x = cx; - last_y = cy; - } - } - } + case SDL_MOUSEBUTTONUP: + m_mouse.buttons[event.button.button - 1] = 0; + m_x = event.button.x; + m_y = event.button.y; + m_window = event.button.windowID; + break; - else if (sdlevent.button.button == 3) - { - int cx, cy; - auto window = GET_FOCUS_WINDOW(&sdlevent.button); + case SDL_MOUSEWHEEL: + // adjust SDL 1-per-click to match Win32 120-per-click +#if SDL_VERSION_ATLEAST(2, 0, 18) + m_v += std::lround(event.wheel.preciseY * 120 * input_device::RELATIVE_PER_PIXEL); + m_h += std::lround(event.wheel.preciseX * 120 * input_device::RELATIVE_PER_PIXEL); +#else + m_v += event.wheel.y * 120 * input_device::RELATIVE_PER_PIXEL; + m_h += event.wheel.x * 120 * input_device::RELATIVE_PER_PIXEL; +#endif + break; - if (window != nullptr && window->xy_to_render_target(sdlevent.button.x, sdlevent.button.y, &cx, &cy)) - { - machine().ui_input().push_mouse_rdown_event(window->target(), cx, cy); - } - } + case SDL_WINDOWEVENT: + if ((event.window.windowID == m_window) && (SDL_WINDOWEVENT_LEAVE == event.window.event)) + m_window = 0; break; + } + } - case SDL_MOUSEBUTTONUP: - mouse.buttons[sdlevent.button.button - 1] = 0; - //printf("But up %d %d %d %d\n", event.button.which, event.button.button, event.button.x, event.button.y); +private: + s32 m_x, m_y; + u32 m_window; +}; - if (sdlevent.button.button == 1) - { - int cx, cy; - auto window = GET_FOCUS_WINDOW(&sdlevent.button); - if (window != nullptr && window->xy_to_render_target(sdlevent.button.x, sdlevent.button.y, &cx, &cy)) - { - machine().ui_input().push_mouse_up_event(window->target(), cx, cy); - } - } - else if (sdlevent.button.button == 3) - { - int cx, cy; - auto window = GET_FOCUS_WINDOW(&sdlevent.button); +//============================================================ +// sdl_dual_lightgun_device +//============================================================ + +class sdl_dual_lightgun_device : public sdl_mouse_device_base +{ +public: + sdl_dual_lightgun_device(std::string &&name, std::string &&id, input_module &module, u8 index) : + sdl_mouse_device_base(std::move(name), std::move(id), module), + m_index(index) + { + } + + virtual void configure(input_device &device) override + { + add_common_items(device, 2); + } - if (window != nullptr && window->xy_to_render_target(sdlevent.button.x, sdlevent.button.y, &cx, &cy)) + virtual void process_event(SDL_Event const &event) override + { + switch (event.type) + { + case SDL_MOUSEBUTTONDOWN: + { + SDL_Window *const win(SDL_GetWindowFromID(event.button.windowID)); + u8 const button = translate_button(event); + if (win && ((button / 2) == m_index)) { - machine().ui_input().push_mouse_rup_event(window->target(), cx, cy); + int w, h; + SDL_GetWindowSize(win, &w, &h); + m_mouse.buttons[(button & 1) << 1] = 0x80; + m_mouse.lX = normalize_absolute_axis(event.button.x, 0, w - 1); + m_mouse.lY = normalize_absolute_axis(event.button.y, 0, h - 1); } } break; - case SDL_MOUSEWHEEL: - auto window = GET_FOCUS_WINDOW(&sdlevent.wheel); - if (window != nullptr) - machine().ui_input().push_mouse_wheel_event(window->target(), 0, 0, sdlevent.wheel.y, 3); + case SDL_MOUSEBUTTONUP: + { + u8 const button = translate_button(event); + if ((button / 2) == m_index) + m_mouse.buttons[(button & 1) << 1] = 0; + } break; } } + +private: + static u8 translate_button(SDL_Event const &event) + { + u8 const index(event.button.button - 1); + return index ^ (((1 == index) || (2 == index)) ? 3 : 0); + } + + u8 const m_index; }; + //============================================================ -// sdl_joystick_device +// sdl_joystick_device_base //============================================================ -// state information for a joystick -struct sdl_joystick_state +class sdl_joystick_device_base : public sdl_device, protected joystick_assignment_helper { - int32_t axes[MAX_AXES]; - int32_t buttons[MAX_BUTTONS]; - int32_t hatsU[MAX_HATS], hatsD[MAX_HATS], hatsL[MAX_HATS], hatsR[MAX_HATS]; - int32_t balls[MAX_AXES]; -}; +public: + std::optional<std::string> const &serial() const { return m_serial; } + SDL_JoystickID instance() const { return m_instance; } -struct sdl_api_state -{ - SDL_Joystick *device; - SDL_Haptic *hapdevice; - SDL_JoystickID joystick_id; -}; + bool is_instance(SDL_JoystickID instance) const { return m_instance == instance; } -class sdl_joystick_device : public sdl_device -{ -public: - sdl_joystick_state joystick; - sdl_api_state sdl_state; + bool reconnect_match(std::string_view g, char const *s) const + { + return + (0 > m_instance) && + (id() == g) && + ((s && serial() && (*serial() == s)) || (!s && !serial())); + } - sdl_joystick_device(running_machine &machine, const char *name, const char *id, input_module &module) - : sdl_device(machine, name, id, DEVICE_CLASS_JOYSTICK, module), - joystick({{0}}), - sdl_state({ nullptr }) +protected: + sdl_joystick_device_base( + std::string &&name, + std::string &&id, + input_module &module, + char const *serial) : + sdl_device(std::move(name), std::move(id), module), + m_instance(-1) { + if (serial) + m_serial = serial; } - ~sdl_joystick_device() + void set_instance(SDL_JoystickID instance) { - if (sdl_state.device != nullptr) - { - if (sdl_state.hapdevice != nullptr) - { - SDL_HapticClose(sdl_state.hapdevice); - sdl_state.hapdevice = nullptr; - } - SDL_JoystickClose(sdl_state.device); - sdl_state.device = nullptr; - } + assert(0 > m_instance); + assert(0 <= instance); + + m_instance = instance; } - void reset() override + void clear_instance() + { + m_instance = -1; + } + +private: + std::optional<std::string> m_serial; + SDL_JoystickID m_instance; +}; + + +//============================================================ +// sdl_joystick_device +//============================================================ + +class sdl_joystick_device : public sdl_joystick_device_base +{ +public: + sdl_joystick_device( + std::string &&name, + std::string &&id, + input_module &module, + SDL_Joystick *joy, + char const *serial) : + sdl_joystick_device_base( + std::move(name), + std::move(id), + module, + serial), + m_joystick({{0}}), + m_joydevice(joy), + m_hapdevice(SDL_HapticOpenFromJoystick(joy)) { - memset(&joystick, 0, sizeof(joystick)); + set_instance(SDL_JoystickInstanceID(joy)); } - void process_event(SDL_Event &sdlevent) override + virtual void configure(input_device &device) override { - switch (sdlevent.type) + input_device::assignment_vector assignments; + char tempname[32]; + + int const axiscount = SDL_JoystickNumAxes(m_joydevice); + int const buttoncount = SDL_JoystickNumButtons(m_joydevice); + int const hatcount = SDL_JoystickNumHats(m_joydevice); + 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++) { - case SDL_JOYAXISMOTION: - joystick.axes[sdlevent.jaxis.axis] = (sdlevent.jaxis.value * 2); - break; + input_item_id itemid; - case SDL_JOYBALLMOTION: - //printf("Ball %d %d\n", sdlevent.jball.xrel, sdlevent.jball.yrel); - joystick.balls[sdlevent.jball.ball * 2] = sdlevent.jball.xrel * INPUT_RELATIVE_PER_PIXEL; - joystick.balls[sdlevent.jball.ball * 2 + 1] = sdlevent.jball.yrel * INPUT_RELATIVE_PER_PIXEL; - break; + if (axis < INPUT_MAX_AXIS) + itemid = input_item_id(ITEM_ID_XAXIS + axis); + else if (axis < (INPUT_MAX_AXIS + INPUT_MAX_ADD_ABSOLUTE)) + itemid = input_item_id(ITEM_ID_ADD_ABSOLUTE1 + axis - INPUT_MAX_AXIS); + else + itemid = ITEM_ID_OTHER_AXIS_ABSOLUTE; + + snprintf(tempname, sizeof(tempname), "A%d", axis + 1); + axisactual[axis] = device.add_item( + tempname, + std::string_view(), + itemid, + generic_axis_get_state<s32>, + &m_joystick.axes[axis]); + } - case SDL_JOYHATMOTION: - if (sdlevent.jhat.value & SDL_HAT_UP) - { - joystick.hatsU[sdlevent.jhat.hat] = 0x80; - } + // loop over all buttons + for (int button = 0; (button < MAX_BUTTONS) && (button < buttoncount); button++) + { + input_item_id itemid; + + m_joystick.buttons[button] = 0; + + if (button < INPUT_MAX_BUTTONS) + itemid = input_item_id(ITEM_ID_BUTTON1 + button); + else if (button < INPUT_MAX_BUTTONS + INPUT_MAX_ADD_SWITCH) + itemid = input_item_id(ITEM_ID_ADD_SWITCH1 + button - INPUT_MAX_BUTTONS); else + itemid = ITEM_ID_OTHER_SWITCH; + + 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) { - joystick.hatsU[sdlevent.jhat.hat] = 0; - } - if (sdlevent.jhat.value & SDL_HAT_DOWN) - { - joystick.hatsD[sdlevent.jhat.hat] = 0x80; + 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); + hatactual[hat][0] = device.add_item( + tempname, + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_joystick.hatsU[hat]); + + 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); + hatactual[hat][1] = device.add_item( + tempname, + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_joystick.hatsD[hat]); + + 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); + hatactual[hat][2] = device.add_item( + tempname, + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_joystick.hatsL[hat]); + + 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); + hatactual[hat][3] = device.add_item( + tempname, + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_joystick.hatsR[hat]); + } + + // loop over all (track)balls + for (int ball = 0; (ball < (MAX_AXES / 2)) && (ball < ballcount); ball++) + { + int itemid; + + if (ball * 2 < INPUT_MAX_ADD_RELATIVE) + itemid = ITEM_ID_ADD_RELATIVE1 + ball * 2; else + itemid = ITEM_ID_OTHER_AXIS_RELATIVE; + + snprintf(tempname, sizeof(tempname), "R%d X", ball + 1); + input_item_id const xactual = device.add_item( + tempname, + std::string_view(), + input_item_id(itemid), + generic_axis_get_state<s32>, + &m_joystick.balls[ball * 2]); + + snprintf(tempname, sizeof(tempname), "R%d Y", ball + 1); + 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) { - joystick.hatsD[sdlevent.jhat.hat] = 0; + // 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); + } } - if (sdlevent.jhat.value & SDL_HAT_LEFT) + else if ((1 == ball) && (2 > axiscount)) { - joystick.hatsL[sdlevent.jhat.hat] = 0x80; + // 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); } - else + } + + // 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) { - joystick.hatsL[sdlevent.jhat.hat] = 0; + // 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); } - if (sdlevent.jhat.value & SDL_HAT_RIGHT) + + // 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() + { + close_device(); + } + + virtual void reset() override + { + sdl_joystick_device_base::reset(); + clear_buffer(); + } + + virtual void process_event(SDL_Event const &event) override + { + if (!m_joydevice) + return; + + switch (event.type) + { + case SDL_JOYAXISMOTION: + if (event.jaxis.axis < MAX_AXES) + m_joystick.axes[event.jaxis.axis] = (event.jaxis.value * 2); + break; + + case SDL_JOYBALLMOTION: + //printf("Ball %d %d\n", event.jball.xrel, event.jball.yrel); + if (event.jball.ball < (MAX_AXES / 2)) { - joystick.hatsR[sdlevent.jhat.hat] = 0x80; + m_joystick.balls[event.jball.ball * 2] = event.jball.xrel * input_device::RELATIVE_PER_PIXEL; + m_joystick.balls[event.jball.ball * 2 + 1] = event.jball.yrel * input_device::RELATIVE_PER_PIXEL; } - else + break; + + case SDL_JOYHATMOTION: + if (event.jhat.hat < MAX_HATS) { - joystick.hatsR[sdlevent.jhat.hat] = 0; + m_joystick.hatsU[event.jhat.hat] = (event.jhat.value & SDL_HAT_UP) ? 0x80 : 0; + m_joystick.hatsD[event.jhat.hat] = (event.jhat.value & SDL_HAT_DOWN) ? 0x80 : 0; + m_joystick.hatsL[event.jhat.hat] = (event.jhat.value & SDL_HAT_LEFT) ? 0x80 : 0; + m_joystick.hatsR[event.jhat.hat] = (event.jhat.value & SDL_HAT_RIGHT) ? 0x80 : 0; } break; case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: - joystick.buttons[sdlevent.jbutton.button] = (sdlevent.jbutton.state == SDL_PRESSED) ? 0x80 : 0; + if (event.jbutton.button < MAX_BUTTONS) + m_joystick.buttons[event.jbutton.button] = (event.jbutton.state == SDL_PRESSED) ? 0x80 : 0; break; + + case SDL_JOYDEVICEREMOVED: + osd_printf_verbose("Joystick: %s [ID %s] disconnected\n", name(), id()); + clear_instance(); + clear_buffer(); + close_device(); + break; + } + } + + bool has_haptic() const + { + return m_hapdevice != nullptr; + } + + void attach_device(SDL_Joystick *joy) + { + assert(joy); + assert(!m_joydevice); + + set_instance(SDL_JoystickInstanceID(joy)); + m_joydevice = joy; + m_hapdevice = SDL_HapticOpenFromJoystick(joy); + + osd_printf_verbose("Joystick: %s [ID %s] reconnected\n", name(), id()); + } + +protected: + // state information for a joystick + struct sdl_joystick_state + { + s32 axes[MAX_AXES]; + s32 buttons[MAX_BUTTONS]; + s32 hatsU[MAX_HATS], hatsD[MAX_HATS], hatsL[MAX_HATS], hatsR[MAX_HATS]; + s32 balls[MAX_AXES]; + }; + + sdl_joystick_state m_joystick; + +private: + SDL_Joystick *m_joydevice; + SDL_Haptic *m_hapdevice; + + void clear_buffer() + { + memset(&m_joystick, 0, sizeof(m_joystick)); + } + + void close_device() + { + if (m_joydevice) + { + if (m_hapdevice) + { + SDL_HapticClose(m_hapdevice); + m_hapdevice = nullptr; + } + SDL_JoystickClose(m_joydevice); + m_joydevice = nullptr; } } }; + +//============================================================ +// sdl_sixaxis_joystick_device +//============================================================ + class sdl_sixaxis_joystick_device : public sdl_joystick_device { public: - sdl_sixaxis_joystick_device(running_machine &machine, const char *name, const char *id, input_module &module) - : sdl_joystick_device(machine, name, id, module) - { - } + using sdl_joystick_device::sdl_joystick_device; - void process_event(SDL_Event &sdlevent) override + virtual void process_event(SDL_Event const &event) override { - switch (sdlevent.type) + switch (event.type) { case SDL_JOYAXISMOTION: { - int axis = sdlevent.jaxis.axis; - + int const axis = event.jaxis.axis; if (axis <= 3) { - joystick.axes[sdlevent.jaxis.axis] = (sdlevent.jaxis.value * 2); + m_joystick.axes[event.jaxis.axis] = (event.jaxis.value * 2); } else { - int magic = (sdlevent.jaxis.value / 2) + 16384; - joystick.axes[sdlevent.jaxis.axis] = magic; + int const magic = (event.jaxis.value / 2) + 16384; + m_joystick.axes[event.jaxis.axis] = magic; } } break; default: // Call the base for other events - sdl_joystick_device::process_event(sdlevent); + sdl_joystick_device::process_event(event); break; } } }; + //============================================================ -// sdl_input_module +// sdl_game_controller_device //============================================================ -class sdl_input_module : public input_module_base, public sdl_event_subscriber +class sdl_game_controller_device : public sdl_joystick_device_base { public: - sdl_input_module(const char *type) - : input_module_base(type, "sdl") + sdl_game_controller_device( + std::string &&name, + std::string &&id, + input_module &module, + SDL_GameController *ctrl, + char const *serial) : + sdl_joystick_device_base( + std::move(name), + std::move(id), + module, + serial), + m_controller({{0}}), + m_ctrldevice(ctrl) + { + set_instance(SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ctrl))); + } + + ~sdl_game_controller_device() { + close_device(); } - void input_init(running_machine &machine) override + virtual void configure(input_device &device) override { - if (machine.debug_flags & DEBUG_FLAG_OSD_ENABLED) + input_device::assignment_vector assignments; + char const *const *axisnames = CONTROLLER_AXIS_XBOX; + char const *const *buttonnames = CONTROLLER_BUTTON_XBOX360; + bool digitaltriggers = false; + bool avoidpaddles = false; + auto const ctrltype = SDL_GameControllerGetType(m_ctrldevice); + switch (ctrltype) + { + case SDL_CONTROLLER_TYPE_UNKNOWN: + osd_printf_verbose("Game Controller: ... unknown type\n", int(ctrltype)); + break; + case SDL_CONTROLLER_TYPE_XBOX360: + osd_printf_verbose("Game Controller: ... Xbox 360 type\n"); + axisnames = CONTROLLER_AXIS_XBOX; + buttonnames = CONTROLLER_BUTTON_XBOX360; + break; + case SDL_CONTROLLER_TYPE_XBOXONE: + osd_printf_verbose("Game Controller: ... Xbox One type\n"); + axisnames = CONTROLLER_AXIS_XBOX; + buttonnames = CONTROLLER_BUTTON_XBOXONE; + break; + case SDL_CONTROLLER_TYPE_PS3: + osd_printf_verbose("Game Controller: ... PlayStation 3 type\n"); + axisnames = CONTROLLER_AXIS_PS; + buttonnames = CONTROLLER_BUTTON_PS3; + break; + case SDL_CONTROLLER_TYPE_PS4: + osd_printf_verbose("Game Controller: ... PlayStation 4 type\n"); + axisnames = CONTROLLER_AXIS_PS; + buttonnames = CONTROLLER_BUTTON_PS4; + break; + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: + osd_printf_verbose("Game Controller: ... Switch Pro Controller type\n"); + axisnames = CONTROLLER_AXIS_SWITCH; + buttonnames = CONTROLLER_BUTTON_SWITCH; + digitaltriggers = true; + break; + //case SDL_CONTROLLER_TYPE_VIRTUAL: + case SDL_CONTROLLER_TYPE_PS5: + osd_printf_verbose("Game Controller: ... PlayStation 5 type\n"); + axisnames = CONTROLLER_AXIS_PS; + buttonnames = CONTROLLER_BUTTON_PS5; + break; +#if SDL_VERSION_ATLEAST(2, 0, 16) + //case SDL_CONTROLLER_TYPE_AMAZON_LUNA: + case SDL_CONTROLLER_TYPE_GOOGLE_STADIA: + osd_printf_verbose("Game Controller: ... Google Stadia type\n"); + axisnames = CONTROLLER_AXIS_PS; + buttonnames = CONTROLLER_BUTTON_STADIA; + break; +#endif +#if SDL_VERSION_ATLEAST(2, 24, 0) + case SDL_CONTROLLER_TYPE_NVIDIA_SHIELD: + osd_printf_verbose("Game Controller: ... NVIDIA Shield type\n"); + axisnames = CONTROLLER_AXIS_XBOX; + buttonnames = CONTROLLER_BUTTON_SHIELD; + break; + //case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + //case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + osd_printf_verbose("Game Controller: ... Joy-Con pair type\n"); + axisnames = CONTROLLER_AXIS_SWITCH; + buttonnames = CONTROLLER_BUTTON_SWITCH; + digitaltriggers = true; + avoidpaddles = true; + break; +#endif + default: // default to Xbox 360 names + osd_printf_verbose("Game Controller: ... unrecognized type (%d)\n", int(ctrltype)); + break; + } + + // 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 }, + { SDL_CONTROLLER_AXIS_LEFTY, ITEM_ID_YAXIS, false }, + { SDL_CONTROLLER_AXIS_RIGHTX, ITEM_ID_ZAXIS, false }, + { SDL_CONTROLLER_AXIS_RIGHTY, ITEM_ID_RZAXIS, false }, + { SDL_CONTROLLER_AXIS_TRIGGERLEFT, ITEM_ID_SLIDER1, true }, + { SDL_CONTROLLER_AXIS_TRIGGERRIGHT, ITEM_ID_SLIDER2, true } }; + for (auto [axis, item, buttontest] : axes) + { + bool avail = !buttontest || !digitaltriggers; + avail = avail && SDL_GameControllerHasAxis(m_ctrldevice, axis); + if (avail) + { + auto const binding = SDL_GameControllerGetBindForAxis(m_ctrldevice, axis); + switch (binding.bindType) + { + case SDL_CONTROLLER_BINDTYPE_NONE: + avail = false; + break; + case SDL_CONTROLLER_BINDTYPE_BUTTON: + if (buttontest) + avail = false; + break; + default: + break; + } + } + if (avail) + { + axisitems[axis].first = device.add_item( + axisnames[axis], + std::string_view(), + item, + generic_axis_get_state<s32>, + &m_controller.axes[axis]); + } + } + + // add automatically numbered buttons + 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 }, + { 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 }, + { SDL_CONTROLLER_BUTTON_GUIDE, SDL_CONTROLLER_AXIS_INVALID, false }, + { SDL_CONTROLLER_BUTTON_MISC1, SDL_CONTROLLER_AXIS_INVALID, false }, + { SDL_CONTROLLER_BUTTON_TOUCHPAD, SDL_CONTROLLER_AXIS_INVALID, false }, + }; + input_item_id button_item = ITEM_ID_BUTTON1; + 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) + { + avail = SDL_GameControllerHasButton(m_ctrldevice, button); + if (avail) + { + auto const binding = SDL_GameControllerGetBindForButton(m_ctrldevice, button); + switch (binding.bindType) + { + case SDL_CONTROLLER_BINDTYPE_NONE: + avail = false; + break; + default: + break; + } + } + if (avail) + { + 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 + { + avail = SDL_GameControllerHasAxis(m_ctrldevice, axis); + if (avail) + { + auto const binding = SDL_GameControllerGetBindForAxis(m_ctrldevice, axis); + switch (binding.bindType) + { + case SDL_CONTROLLER_BINDTYPE_NONE: + avail = false; + break; + case SDL_CONTROLLER_BINDTYPE_BUTTON: + break; + default: + avail = digitaltriggers; + } + } + if (avail) + { + actual = axisitems[axis].second = device.add_item( + axisnames[axis], + std::string_view(), + button_item++, + [] (void *device_internal, void *item_internal) -> int + { + 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 + std::pair<SDL_GameControllerButton, input_item_id> const fixedbuttons[]{ + { SDL_CONTROLLER_BUTTON_BACK, ITEM_ID_SELECT }, + { SDL_CONTROLLER_BUTTON_START, ITEM_ID_START }, + { SDL_CONTROLLER_BUTTON_DPAD_UP, ITEM_ID_HAT1UP }, + { SDL_CONTROLLER_BUTTON_DPAD_DOWN, ITEM_ID_HAT1DOWN }, + { SDL_CONTROLLER_BUTTON_DPAD_LEFT, ITEM_ID_HAT1LEFT }, + { SDL_CONTROLLER_BUTTON_DPAD_RIGHT, ITEM_ID_HAT1RIGHT } }; + for (auto [button, item] : fixedbuttons) { - osd_printf_warning("Debug Build: Disabling input grab for -debug\n"); - set_mouse_enabled(false); + bool avail = true; + avail = SDL_GameControllerHasButton(m_ctrldevice, button); + if (avail) + { + auto const binding = SDL_GameControllerGetBindForButton(m_ctrldevice, button); + switch (binding.bindType) + { + case SDL_CONTROLLER_BINDTYPE_NONE: + avail = false; + break; + default: + break; + } + } + if (avail) + { + buttonitems[button] = device.add_item( + buttonnames[button], + std::string_view(), + item, + generic_button_get_state<s32>, + &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 + } + 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 + } + } + + // 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 + } + 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 + } + 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 + } + 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 + } + 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 (!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 + 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)); } - void exit() override + virtual void reset() override { - // unsubscribe for events - sdl_event_manager::instance().unsubscribe(this); + sdl_joystick_device_base::reset(); + clear_buffer(); + } + + virtual void process_event(SDL_Event const &event) override + { + if (!m_ctrldevice) + return; + + switch (event.type) + { + case SDL_CONTROLLERAXISMOTION: + if (event.caxis.axis < SDL_CONTROLLER_AXIS_MAX) + { + switch (event.caxis.axis) + { + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: // MAME wants negative values for triggers + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + m_controller.axes[event.caxis.axis] = -normalize_absolute_axis(event.caxis.value, -32'767, 32'767); + break; + default: + m_controller.axes[event.caxis.axis] = normalize_absolute_axis(event.caxis.value, -32'767, 32'767); + } + } + break; - input_module_base::exit(); + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + if (event.cbutton.button < SDL_CONTROLLER_BUTTON_MAX) + m_controller.buttons[event.cbutton.button] = (event.cbutton.state == SDL_PRESSED) ? 0x80 : 0x00; + break; + + case SDL_CONTROLLERDEVICEREMOVED: + osd_printf_verbose("Game Controller: %s [ID %s] disconnected\n", name(), id()); + clear_instance(); + clear_buffer(); + close_device(); + break; + } } - void before_poll(running_machine& machine) override + void attach_device(SDL_GameController *ctrl) { - // Tell the event manager to process events and push them to the devices - sdl_event_manager::instance().process_events(machine); + assert(ctrl); + assert(!m_ctrldevice); + + set_instance(SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ctrl))); + m_ctrldevice = ctrl; + + osd_printf_verbose("Game Controller: %s [ID %s] reconnected\n", name(), id()); } - bool should_poll_devices(running_machine& machine) override +private: + // state information for a game controller + struct sdl_controller_state { - return sdl_event_manager::instance().has_focus() && input_enabled(); + s32 axes[SDL_CONTROLLER_AXIS_MAX]; + s32 buttons[SDL_CONTROLLER_BUTTON_MAX]; + }; + + sdl_controller_state m_controller; + SDL_GameController *m_ctrldevice; + + void clear_buffer() + { + memset(&m_controller, 0, sizeof(m_controller)); } - void handle_event(SDL_Event &sdlevent) override + void close_device() { - // By default dispatch event to every device - devicelist()->for_each_device([&sdlevent](auto device) { - downcast<sdl_device*>(device)->queue_events(&sdlevent, 1); - }); + if (m_ctrldevice) + { + SDL_GameControllerClose(m_ctrldevice); + m_ctrldevice = nullptr; + } } }; + //============================================================ -// sdl_keyboard_module +// sdl_input_module //============================================================ -class sdl_keyboard_module : public sdl_input_module +template <typename Info> +class sdl_input_module : + public input_module_impl<Info, sdl_osd_interface>, + protected sdl_event_manager::subscriber { - keyboard_trans_table * m_key_trans_table; public: - sdl_keyboard_module() - : sdl_input_module(OSD_KEYBOARDINPUT_PROVIDER), m_key_trans_table(nullptr) + sdl_input_module(char const *type, char const *name) : + input_module_impl<Info, sdl_osd_interface>(type, name) { } - void input_init(running_machine &machine) override + virtual void exit() override { - sdl_input_module::input_init(machine); + // unsubscribe for events + unsubscribe(); - static int event_types[] = { - static_cast<int>(SDL_KEYDOWN), - static_cast<int>(SDL_KEYUP), - static_cast<int>(SDL_TEXTINPUT) - }; + input_module_impl<Info, sdl_osd_interface>::exit(); + } - sdl_event_manager::instance().subscribe(event_types, ARRAY_LENGTH(event_types), this); +protected: + virtual void handle_event(SDL_Event const &event) override + { + // dispatch event to every device by default + this->devicelist().for_each_device( + [&event] (auto &device) { device.queue_events(&event, 1); }); + } +}; - sdl_keyboard_device *devinfo; - // Read our keymap and store a pointer to our table - m_key_trans_table = sdlinput_read_keymap(machine); +//============================================================ +// sdl_keyboard_module +//============================================================ - keyboard_trans_table& local_table = *m_key_trans_table; +class sdl_keyboard_module : public sdl_input_module<sdl_keyboard_device> +{ +public: + sdl_keyboard_module() : + sdl_input_module<sdl_keyboard_device>(OSD_KEYBOARDINPUT_PROVIDER, "sdl") + { + } - osd_printf_verbose("Keyboard: Start initialization\n"); + virtual void input_init(running_machine &machine) override + { + sdl_input_module<sdl_keyboard_device>::input_init(machine); - // SDL only has 1 keyboard add it now - devinfo = devicelist()->create_device<sdl_keyboard_device>(machine, "System keyboard", "System keyboard", *this); + constexpr int event_types[] = { + int(SDL_KEYDOWN), + int(SDL_KEYUP) }; - // populate it - for (int keynum = 0; local_table[keynum].mame_key != ITEM_ID_INVALID; keynum++) - { - input_item_id itemid = local_table[keynum].mame_key; + subscribe(osd(), event_types); + + // Read our keymap and store a pointer to our table + sdlinput_read_keymap(); - // generate the default / modified name - char defname[20]; - snprintf(defname, sizeof(defname) - 1, "%s", local_table[keynum].ui_name); + osd_printf_verbose("Keyboard: Start initialization\n"); - devinfo->device()->add_item(defname, itemid, generic_button_get_state<std::int32_t>, &devinfo->keyboard.state[local_table[keynum].sdl_scancode]); - } + // SDL only has 1 keyboard add it now + auto &devinfo = create_device<sdl_keyboard_device>( + DEVICE_CLASS_KEYBOARD, + "System keyboard", + "System keyboard", + *m_key_trans_table); - osd_printf_verbose("Keyboard: Registered %s\n", devinfo->name()); + osd_printf_verbose("Keyboard: Registered %s\n", devinfo.name()); osd_printf_verbose("Keyboard: End initialization\n"); } private: - static keyboard_trans_table* sdlinput_read_keymap(running_machine &machine) + void sdlinput_read_keymap() { - char *keymap_filename; - FILE *keymap_file; - int line = 1; - int sdl2section = 0; - keyboard_trans_table &default_table = keyboard_trans_table::instance(); - if (!machine.options().bool_value(SDLOPTION_KEYMAP)) - return &default_table; - - keymap_filename = (char *)downcast<sdl_options &>(machine.options()).keymap_file(); - osd_printf_verbose("Keymap: Start reading keymap_file %s\n", keymap_filename); - - keymap_file = fopen(keymap_filename, "r"); - if (keymap_file == nullptr) - { - osd_printf_warning("Keymap: Unable to open keymap %s, using default\n", keymap_filename); - return &default_table; - } - // Allocate a block of translation entries big enough to hold what's in the default table - auto key_trans_entries = std::make_unique<key_trans_entry[]>(default_table.size()); + auto key_trans_entries = std::make_unique<key_trans_entry []>(default_table.size()); - // copy the elements from the default table + // copy the elements from the default table and ask SDL for key names for (int i = 0; i < default_table.size(); i++) + { key_trans_entries[i] = default_table[i]; + char const *const name = SDL_GetScancodeName(SDL_Scancode(default_table[i].sdl_scancode)); + if (name && *name) + key_trans_entries[i].ui_name = name; + } // Allocate the trans table to be associated with the machine so we don't have to free it - keyboard_trans_table *custom_table = auto_alloc(machine, keyboard_trans_table(std::move(key_trans_entries), default_table.size())); + m_key_trans_table = std::make_unique<keyboard_trans_table>(std::move(key_trans_entries), default_table.size()); + + if (!options()->bool_value(SDLOPTION_KEYMAP)) + return; + + const char *const keymap_filename = dynamic_cast<sdl_options const &>(*options()).keymap_file(); + osd_printf_verbose("Keymap: Start reading keymap_file %s\n", keymap_filename); + + FILE *const keymap_file = fopen(keymap_filename, "r"); + if (!keymap_file) + { + osd_printf_warning("Keymap: Unable to open keymap %s, using default\n", keymap_filename); + return; + } + int line = 1; + int sdl2section = 0; while (!feof(keymap_file)) { char buf[256]; @@ -864,34 +2266,36 @@ private: if (sk >= 0 && index >= 0) { - key_trans_entry &entry = (*custom_table)[index]; + key_trans_entry &entry = (*m_key_trans_table)[index]; entry.sdl_scancode = sk; - entry.ui_name = auto_alloc_array(machine, char, strlen(kns) + 1); - strcpy(entry.ui_name, kns); + entry.ui_name = const_cast<char *>(m_ui_names.emplace_back(kns).c_str()); osd_printf_verbose("Keymap: Mapped <%s> to <%s> with ui-text <%s>\n", sks, mks, kns); } else + { osd_printf_error("Keymap: Error on line %d - %s key not found: %s\n", line, (sk<0) ? "sdl" : "mame", buf); + } } } line++; } fclose(keymap_file); osd_printf_verbose("Keymap: Processed %d lines\n", line); - - return custom_table; } + + std::unique_ptr<keyboard_trans_table> m_key_trans_table; + std::list<std::string> m_ui_names; }; + //============================================================ // sdl_mouse_module //============================================================ -class sdl_mouse_module : public sdl_input_module +class sdl_mouse_module : public sdl_input_module<sdl_mouse_device> { public: - sdl_mouse_module() - : sdl_input_module(OSD_MOUSEINPUT_PROVIDER) + sdl_mouse_module() : sdl_input_module<sdl_mouse_device>(OSD_MOUSEINPUT_PROVIDER, "sdl") { } @@ -899,285 +2303,578 @@ public: { sdl_input_module::input_init(machine); - static int event_types[] = { - static_cast<int>(SDL_MOUSEMOTION), - static_cast<int>(SDL_MOUSEBUTTONDOWN), - static_cast<int>(SDL_MOUSEBUTTONUP), - static_cast<int>(SDL_MOUSEWHEEL) - }; - - sdl_event_manager::instance().subscribe(event_types, ARRAY_LENGTH(event_types), this); + constexpr int event_types[] = { + int(SDL_MOUSEMOTION), + int(SDL_MOUSEBUTTONDOWN), + int(SDL_MOUSEBUTTONUP), + int(SDL_MOUSEWHEEL) }; - sdl_mouse_device *devinfo; - char defname[20]; - int button; + subscribe(osd(), event_types); osd_printf_verbose("Mouse: Start initialization\n"); // SDL currently only supports one mouse - devinfo = devicelist()->create_device<sdl_mouse_device>(machine, "System mouse", "System mouse", *this); - - // add the axes - devinfo->device()->add_item("X", ITEM_ID_XAXIS, generic_axis_get_state<std::int32_t>, &devinfo->mouse.lX); - devinfo->device()->add_item("Y", ITEM_ID_YAXIS, generic_axis_get_state<std::int32_t>, &devinfo->mouse.lY); + auto &devinfo = create_device<sdl_mouse_device>( + DEVICE_CLASS_MOUSE, + "System mouse", + "System mouse"); - for (button = 0; button < 4; button++) - { - input_item_id itemid = (input_item_id)(ITEM_ID_BUTTON1 + button); - snprintf(defname, sizeof(defname), "B%d", button + 1); - - devinfo->device()->add_item(defname, itemid, generic_button_get_state<std::int32_t>, &devinfo->mouse.buttons[button]); - } - - osd_printf_verbose("Mouse: Registered %s\n", devinfo->name()); + osd_printf_verbose("Mouse: Registered %s\n", devinfo.name()); osd_printf_verbose("Mouse: End initialization\n"); } }; -static void devmap_register(device_map_t &devmap, int physical_idx, const std::string &name) -{ - // Attempt to find the entry by name - auto entry = std::find_if(std::begin(devmap.map), std::end(devmap.map), [&name](auto &item) - { - return item.name == name && item.physical < 0; - }); +//============================================================ +// sdl_lightgun_module +//============================================================ - // If we didn't find it by name, find the first free slot - if (entry == std::end(devmap.map)) +class sdl_lightgun_module : public sdl_input_module<sdl_mouse_device_base> +{ +public: + sdl_lightgun_module() : sdl_input_module<sdl_mouse_device_base>(OSD_LIGHTGUNINPUT_PROVIDER, "sdl") { - entry = std::find_if(std::begin(devmap.map), std::end(devmap.map), [](auto &item) { return item.name.empty(); }); } - if (entry != std::end(devmap.map)) + virtual void input_init(running_machine &machine) override { - entry->physical = physical_idx; - entry->name = name; - int logical_idx = std::distance(std::begin(devmap.map), entry); - devmap.logical[physical_idx] = logical_idx; + auto &sdlopts = dynamic_cast<sdl_options const &>(*options()); + sdl_input_module::input_init(machine); + bool const dual(sdlopts.dual_lightgun()); + + if (!dual) + { + constexpr int event_types[] = { + int(SDL_MOUSEMOTION), + int(SDL_MOUSEBUTTONDOWN), + int(SDL_MOUSEBUTTONUP), + int(SDL_MOUSEWHEEL), + int(SDL_WINDOWEVENT) }; + subscribe(osd(), event_types); + } + else + { + constexpr int event_types[] = { + int(SDL_MOUSEBUTTONDOWN), + int(SDL_MOUSEBUTTONUP) }; + subscribe(osd(), event_types); + } + + osd_printf_verbose("Lightgun: Start initialization\n"); + + if (!dual) + { + auto &devinfo = create_device<sdl_lightgun_device>( + DEVICE_CLASS_LIGHTGUN, + "System pointer gun 1", + "System pointer gun 1"); + osd_printf_verbose("Lightgun: Registered %s\n", devinfo.name()); + } + else + { + auto &dev1info = create_device<sdl_dual_lightgun_device>( + DEVICE_CLASS_LIGHTGUN, + "System pointer gun 1", + "System pointer gun 1", + 0); + osd_printf_verbose("Lightgun: Registered %s\n", dev1info.name()); + + auto &dev2info = create_device<sdl_dual_lightgun_device>( + DEVICE_CLASS_LIGHTGUN, + "System pointer gun 2", + "System pointer gun 2", + 1); + osd_printf_verbose("Lightgun: Registered %s\n", dev2info.name()); + } + + osd_printf_verbose("Lightgun: End initialization\n"); } -} +}; + //============================================================ -// sdl_joystick_module +// sdl_joystick_module_base //============================================================ -class sdl_joystick_module : public sdl_input_module +class sdl_joystick_module_base : public sdl_input_module<sdl_joystick_device_base> { -private: - device_map_t m_joy_map; - bool m_initialized_joystick; - bool m_initialized_haptic; - bool m_sixaxis_mode; -public: - sdl_joystick_module() - : sdl_input_module(OSD_JOYSTICKINPUT_PROVIDER), m_initialized_joystick(false), m_initialized_haptic(false), m_sixaxis_mode(false) +protected: + sdl_joystick_module_base(char const *name) : + sdl_input_module<sdl_joystick_device_base>(OSD_JOYSTICKINPUT_PROVIDER, name), + m_initialized_joystick(false), + m_initialized_haptic(false) { } - virtual void exit() override + virtual ~sdl_joystick_module_base() + { + assert(!m_initialized_joystick); + assert(!m_initialized_haptic); + } + + bool have_joystick() const { return m_initialized_joystick; } + bool have_haptic() const { return m_initialized_haptic; } + + void init_joystick() { - sdl_input_module::exit(); + assert(!m_initialized_joystick); + assert(!m_initialized_haptic); + + m_initialized_joystick = !SDL_InitSubSystem(SDL_INIT_JOYSTICK); + if (!m_initialized_joystick) + { + osd_printf_error("Could not initialize SDL Joystick subsystem: %s.\n", SDL_GetError()); + return; + } + m_initialized_haptic = !SDL_InitSubSystem(SDL_INIT_HAPTIC); + if (!m_initialized_haptic) + osd_printf_verbose("Could not initialize SDL Haptic subsystem: %s.\n", SDL_GetError()); + } + + void quit_joystick() + { if (m_initialized_joystick) { SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + m_initialized_joystick = false; } if (m_initialized_haptic) { SDL_QuitSubSystem(SDL_INIT_HAPTIC); + m_initialized_haptic = false; } } - virtual void input_init(running_machine &machine) override + sdl_joystick_device *create_joystick_device(int index, bool sixaxis) { - SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); - - m_initialized_joystick = !SDL_InitSubSystem(SDL_INIT_JOYSTICK); - if (!m_initialized_joystick) + // open the joystick device + SDL_Joystick *const joy = SDL_JoystickOpen(index); + if (!joy) { - osd_printf_error("Could not initialize SDL Joystick: %s.\n", SDL_GetError()); - return; + osd_printf_error("Joystick: Could not open SDL joystick %d: %s.\n", index, SDL_GetError()); + return nullptr; } - m_initialized_haptic = !SDL_InitSubSystem(SDL_INIT_HAPTIC); - if (!m_initialized_haptic) + // get basic info + char const *const name = SDL_JoystickName(joy); + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joy); + char guid_str[256]; + guid_str[0] = '\0'; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str) - 1); + char const *const serial = SDL_JoystickGetSerial(joy); + std::string id(guid_str); + if (serial) + id.append(1, '-').append(serial); + + // print some diagnostic info + osd_printf_verbose("Joystick: %s [GUID %s] Vendor ID %04X, Product ID %04X, Revision %04X, Serial %s\n", + name ? name : "<nullptr>", + guid_str, + SDL_JoystickGetVendor(joy), + SDL_JoystickGetProduct(joy), + SDL_JoystickGetProductVersion(joy), + serial ? serial : "<nullptr>"); + osd_printf_verbose("Joystick: ... %d axes, %d buttons %d hats %d balls\n", + SDL_JoystickNumAxes(joy), + SDL_JoystickNumButtons(joy), + SDL_JoystickNumHats(joy), + SDL_JoystickNumBalls(joy)); + if (SDL_JoystickNumButtons(joy) > MAX_BUTTONS) + osd_printf_verbose("Joystick: ... Has %d buttons which exceeds supported %d buttons\n", SDL_JoystickNumButtons(joy), MAX_BUTTONS); + + // instantiate device + sdl_joystick_device &devinfo = sixaxis + ? create_device<sdl_sixaxis_joystick_device>(DEVICE_CLASS_JOYSTICK, name ? name : guid_str, guid_str, joy, serial) + : create_device<sdl_joystick_device>(DEVICE_CLASS_JOYSTICK, name ? name : guid_str, guid_str, joy, serial); + + if (devinfo.has_haptic()) + osd_printf_verbose("Joystick: ... Has haptic capability\n"); + else + osd_printf_verbose("Joystick: ... Does not have haptic capability\n"); + + return &devinfo; + } + + void dispatch_joystick_event(SDL_Event const &event) + { + // figure out which joystick this event is destined for + sdl_joystick_device_base *const target_device = find_joystick(event.jdevice.which); // FIXME: this depends on SDL_JoystickID being the same size as Sint32 + + // if we find a matching joystick, dispatch the event to the joystick + if (target_device) + target_device->queue_events(&event, 1); + } + + device_info *find_reconnect_match(SDL_JoystickGUID const &guid, char const *serial) + { + char guid_str[256]; + guid_str[0] = '\0'; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str) - 1); + auto target_device = std::find_if( + devicelist().begin(), + devicelist().end(), + [&guid_str, &serial] (auto const &device) + { + return device->reconnect_match(guid_str, serial); + }); + return (devicelist().end() != target_device) ? target_device->get() : nullptr; + } + + sdl_joystick_device_base *find_joystick(SDL_JoystickID instance) + { + for (auto &device : devicelist()) { - osd_printf_verbose("Could not initialize SDL Haptic subsystem: %s.\n", SDL_GetError()); + if (device->is_instance(instance)) + return device.get(); } + return nullptr; + } - sdl_input_module::input_init(machine); +private: + bool m_initialized_joystick; + bool m_initialized_haptic; +}; + + +//============================================================ +// sdl_joystick_module +//============================================================ + +class sdl_joystick_module : public sdl_joystick_module_base +{ +public: + sdl_joystick_module() : sdl_joystick_module_base("sdljoy") + { + } - char tempname[512]; + virtual void exit() override + { + sdl_joystick_module_base::exit(); - m_sixaxis_mode = downcast<const sdl_options*>(options())->sixaxis(); + quit_joystick(); + } - devmap_init(machine, &m_joy_map, SDLOPTION_JOYINDEX, 8, "Joystick mapping"); + virtual void input_init(running_machine &machine) override + { + auto &sdlopts = dynamic_cast<sdl_options const &>(*options()); + bool const sixaxis_mode = sdlopts.sixaxis(); - osd_printf_verbose("Joystick: Start initialization\n"); - int physical_stick; - for (physical_stick = 0; physical_stick < SDL_NumJoysticks(); physical_stick++) - { - std::string joy_name = remove_spaces(SDL_JoystickNameForIndex(physical_stick)); - devmap_register(m_joy_map, physical_stick, joy_name); - } + if (!sdlopts.debug() && sdlopts.background_input()) + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); - for (int stick = 0; stick < MAX_DEVMAP_ENTRIES; stick++) - { - sdl_joystick_device *devinfo = create_joystick_device(machine, &m_joy_map, stick, DEVICE_CLASS_JOYSTICK); + init_joystick(); + if (!have_joystick()) + return; - if (devinfo == nullptr) - continue; + sdl_joystick_module_base::input_init(machine); - physical_stick = m_joy_map.map[stick].physical; - SDL_Joystick *joy = SDL_JoystickOpen(physical_stick); - devinfo->sdl_state.device = joy; - devinfo->sdl_state.joystick_id = SDL_JoystickInstanceID(joy); - devinfo->sdl_state.hapdevice = SDL_HapticOpenFromJoystick(joy); + osd_printf_verbose("Joystick: Start initialization\n"); + for (int physical_stick = 0; physical_stick < SDL_NumJoysticks(); physical_stick++) + create_joystick_device(physical_stick, sixaxis_mode); + + constexpr int event_types[] = { + int(SDL_JOYAXISMOTION), + int(SDL_JOYBALLMOTION), + int(SDL_JOYHATMOTION), + int(SDL_JOYBUTTONDOWN), + int(SDL_JOYBUTTONUP), + int(SDL_JOYDEVICEADDED), + int(SDL_JOYDEVICEREMOVED) }; + subscribe(osd(), event_types); - osd_printf_verbose("Joystick: %s\n", SDL_JoystickNameForIndex(physical_stick)); - osd_printf_verbose("Joystick: ... %d axes, %d buttons %d hats %d balls\n", SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy), SDL_JoystickNumHats(joy), SDL_JoystickNumBalls(joy)); - osd_printf_verbose("Joystick: ... Physical id %d mapped to logical id %d\n", physical_stick, stick + 1); - if (devinfo->sdl_state.hapdevice != nullptr) + osd_printf_verbose("Joystick: End initialization\n"); + } + + virtual void handle_event(SDL_Event const &event) override + { + if (SDL_JOYDEVICEADDED == event.type) + { + SDL_Joystick *const joy = SDL_JoystickOpen(event.jdevice.which); + if (!joy) { - osd_printf_verbose("Joystick: ... Has haptic capability\n"); + osd_printf_error("Joystick: Could not open SDL joystick %d: %s.\n", event.jdevice.which, SDL_GetError()); } else { - osd_printf_verbose("Joystick: ... Does not have haptic capability\n"); + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joy); + char const *const serial = SDL_JoystickGetSerial(joy); + auto *const target_device = find_reconnect_match(guid, serial); + if (target_device) + { + auto &devinfo = dynamic_cast<sdl_joystick_device &>(*target_device); + devinfo.attach_device(joy); + } + else + { + SDL_JoystickClose(joy); + } } + } + else + { + dispatch_joystick_event(event); + } + } +}; - // loop over all axes - for (int axis = 0; axis < SDL_JoystickNumAxes(joy); axis++) - { - input_item_id itemid; - if (axis < INPUT_MAX_AXIS) - itemid = (input_item_id)(ITEM_ID_XAXIS + axis); - else if (axis < INPUT_MAX_AXIS + INPUT_MAX_ADD_ABSOLUTE) - itemid = (input_item_id)(ITEM_ID_ADD_ABSOLUTE1 - INPUT_MAX_AXIS + axis); - else - itemid = ITEM_ID_OTHER_AXIS_ABSOLUTE; +//============================================================ +// sdl_game_controller_module +//============================================================ - snprintf(tempname, sizeof(tempname), "A%d %s", axis, devinfo->name()); - devinfo->device()->add_item(tempname, itemid, generic_axis_get_state<std::int32_t>, &devinfo->joystick.axes[axis]); - } +class sdl_game_controller_module : public sdl_joystick_module_base +{ +public: + sdl_game_controller_module() : + sdl_joystick_module_base("sdlgame"), + m_initialized_game_controller(false) + { + } - // loop over all buttons - for (int button = 0; button < SDL_JoystickNumButtons(joy); button++) - { - input_item_id itemid; + virtual void exit() override + { + sdl_joystick_module_base::exit(); - devinfo->joystick.buttons[button] = 0; + if (m_initialized_game_controller) + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); - if (button < INPUT_MAX_BUTTONS) - itemid = (input_item_id)(ITEM_ID_BUTTON1 + button); - else if (button < INPUT_MAX_BUTTONS + INPUT_MAX_ADD_SWITCH) - itemid = (input_item_id)(ITEM_ID_ADD_SWITCH1 - INPUT_MAX_BUTTONS + button); - else - itemid = ITEM_ID_OTHER_SWITCH; + quit_joystick(); + } - snprintf(tempname, sizeof(tempname), "button %d", button); - devinfo->device()->add_item(tempname, itemid, generic_button_get_state<std::int32_t>, &devinfo->joystick.buttons[button]); - } + virtual void input_init(running_machine &machine) override + { + auto &sdlopts = dynamic_cast<sdl_options const &>(*options()); + bool const sixaxis_mode = sdlopts.sixaxis(); - // loop over all hats - for (int hat = 0; hat < SDL_JoystickNumHats(joy); hat++) - { - input_item_id itemid; - - snprintf(tempname, sizeof(tempname), "hat %d Up", hat); - itemid = (input_item_id)((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1UP + 4 * hat : ITEM_ID_OTHER_SWITCH); - devinfo->device()->add_item(tempname, itemid, generic_button_get_state<std::int32_t>, &devinfo->joystick.hatsU[hat]); - snprintf(tempname, sizeof(tempname), "hat %d Down", hat); - itemid = (input_item_id)((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1DOWN + 4 * hat : ITEM_ID_OTHER_SWITCH); - devinfo->device()->add_item(tempname, itemid, generic_button_get_state<std::int32_t>, &devinfo->joystick.hatsD[hat]); - snprintf(tempname, sizeof(tempname), "hat %d Left", hat); - itemid = (input_item_id)((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1LEFT + 4 * hat : ITEM_ID_OTHER_SWITCH); - devinfo->device()->add_item(tempname, itemid, generic_button_get_state<std::int32_t>, &devinfo->joystick.hatsL[hat]); - snprintf(tempname, sizeof(tempname), "hat %d Right", hat); - itemid = (input_item_id)((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1RIGHT + 4 * hat : ITEM_ID_OTHER_SWITCH); - devinfo->device()->add_item(tempname, itemid, generic_button_get_state<std::int32_t>, &devinfo->joystick.hatsR[hat]); - } + if (!sdlopts.debug() && sdlopts.background_input()) + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); - // loop over all (track)balls - for (int ball = 0; ball < SDL_JoystickNumBalls(joy); ball++) - { - int itemid; + init_joystick(); + if (!have_joystick()) + return; - if (ball * 2 < INPUT_MAX_ADD_RELATIVE) - itemid = ITEM_ID_ADD_RELATIVE1 + ball * 2; + m_initialized_game_controller = !SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); + if (m_initialized_game_controller) + { + char const *const mapfile = sdlopts.controller_mapping_file(); + if (mapfile && *mapfile && std::strcmp(mapfile, OSDOPTVAL_NONE)) + { + auto const count = SDL_GameControllerAddMappingsFromFile(mapfile); + if (0 <= count) + osd_printf_verbose("Game Controller: %d controller mapping(s) added from file [%s].\n", count, mapfile); else - itemid = ITEM_ID_OTHER_AXIS_RELATIVE; - - snprintf(tempname, sizeof(tempname), "R%d %s", ball * 2, devinfo->name()); - devinfo->device()->add_item(tempname, (input_item_id)itemid, generic_axis_get_state<std::int32_t>, &devinfo->joystick.balls[ball * 2]); - snprintf(tempname, sizeof(tempname), "R%d %s", ball * 2 + 1, devinfo->name()); - devinfo->device()->add_item(tempname, (input_item_id)(itemid + 1), generic_axis_get_state<std::int32_t>, &devinfo->joystick.balls[ball * 2 + 1]); + osd_printf_error("Game Controller: Error adding mappings from file [%s]: %s.\n", mapfile, SDL_GetError()); } } + else + { + osd_printf_warning("Could not initialize SDL Game Controller: %s.\n", SDL_GetError()); + } - static int event_types[] = { - static_cast<int>(SDL_JOYAXISMOTION), - static_cast<int>(SDL_JOYBALLMOTION), - static_cast<int>(SDL_JOYHATMOTION), - static_cast<int>(SDL_JOYBUTTONDOWN), - static_cast<int>(SDL_JOYBUTTONUP) - }; + sdl_joystick_module_base::input_init(machine); - sdl_event_manager::instance().subscribe(event_types, ARRAY_LENGTH(event_types), this); + osd_printf_verbose("Game Controller: Start initialization\n"); + for (int physical_stick = 0; physical_stick < SDL_NumJoysticks(); physical_stick++) + { + // try to open as a game controller + SDL_GameController *ctrl = nullptr; + if (m_initialized_game_controller && SDL_IsGameController(physical_stick)) + { + ctrl = SDL_GameControllerOpen(physical_stick); + if (!ctrl) + osd_printf_warning("Game Controller: Could not open SDL game controller %d: %s.\n", physical_stick, SDL_GetError()); + } - osd_printf_verbose("Joystick: End initialization\n"); + // fall back to joystick API if necessary + if (!ctrl) + create_joystick_device(physical_stick, sixaxis_mode); + else + create_game_controller_device(physical_stick, ctrl); + } + + constexpr int joy_event_types[] = { + int(SDL_JOYAXISMOTION), + int(SDL_JOYBALLMOTION), + int(SDL_JOYHATMOTION), + int(SDL_JOYBUTTONDOWN), + int(SDL_JOYBUTTONUP), + int(SDL_JOYDEVICEADDED), + int(SDL_JOYDEVICEREMOVED) }; + constexpr int event_types[] = { + int(SDL_JOYAXISMOTION), + int(SDL_JOYBALLMOTION), + int(SDL_JOYHATMOTION), + int(SDL_JOYBUTTONDOWN), + int(SDL_JOYBUTTONUP), + int(SDL_JOYDEVICEADDED), + int(SDL_JOYDEVICEREMOVED), + int(SDL_CONTROLLERAXISMOTION), + int(SDL_CONTROLLERBUTTONDOWN), + int(SDL_CONTROLLERBUTTONUP), + int(SDL_CONTROLLERDEVICEADDED), + int(SDL_CONTROLLERDEVICEREMOVED) }; + if (m_initialized_game_controller) + subscribe(osd(), event_types); + else + subscribe(osd(), joy_event_types); + + osd_printf_verbose("Game Controller: End initialization\n"); } - virtual void handle_event(SDL_Event &sdlevent) override + virtual void handle_event(SDL_Event const &event) override { - // Figure out which joystick this event id destined for - auto target_device = std::find_if(devicelist()->begin(), devicelist()->end(), [&sdlevent](auto &device) + switch (event.type) { - std::unique_ptr<device_info> &ptr = device; - return downcast<sdl_joystick_device*>(ptr.get())->sdl_state.joystick_id == sdlevent.jdevice.which; - }); + case SDL_JOYDEVICEADDED: + { + // make sure this isn't an event for a reconnected game controller + auto const controller = find_joystick(SDL_JoystickGetDeviceInstanceID(event.jdevice.which)); + if (find_joystick(SDL_JoystickGetDeviceInstanceID(event.jdevice.which))) + { + osd_printf_verbose( + "Game Controller: Got SDL joystick added event for reconnected game controller %s [ID %s]\n", + controller->name(), + controller->id()); + break; + } - // If we find a matching joystick, dispatch the event to the joystick - if (target_device != devicelist()->end()) - { - downcast<sdl_joystick_device*>((*target_device).get())->queue_events(&sdlevent, 1); + SDL_Joystick *const joy = SDL_JoystickOpen(event.jdevice.which); + if (!joy) + { + osd_printf_error("Joystick: Could not open SDL joystick %d: %s.\n", event.jdevice.which, SDL_GetError()); + break; + } + + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joy); + char const *const serial = SDL_JoystickGetSerial(joy); + auto *const target_device = find_reconnect_match(guid, serial); + if (target_device) + { + // if this downcast fails, opening as a game controller worked initially but failed on reconnection + auto *const devinfo = dynamic_cast<sdl_joystick_device *>(target_device); + if (devinfo) + devinfo->attach_device(joy); + else + SDL_JoystickClose(joy); + } + else + { + SDL_JoystickClose(joy); + } + } + break; + + // for devices supported by the game controller API, this is received before the corresponding SDL_JOYDEVICEADDED + case SDL_CONTROLLERDEVICEADDED: + if (m_initialized_game_controller) + { + SDL_GameController *const ctrl = SDL_GameControllerOpen(event.cdevice.which); + if (!ctrl) + { + osd_printf_error("Game Controller: Could not open SDL game controller %d: %s.\n", event.cdevice.which, SDL_GetError()); + break; + } + + SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(event.cdevice.which); + char const *const serial = SDL_GameControllerGetSerial(ctrl); + auto *const target_device = find_reconnect_match(guid, serial); + if (target_device) + { + // downcast can fail if there was an error opening the device as a game controller the first time + auto *const devinfo = dynamic_cast<sdl_game_controller_device *>(target_device); + if (devinfo) + devinfo->attach_device(ctrl); + else + SDL_GameControllerClose(ctrl); + } + else + { + SDL_GameControllerClose(ctrl); + } + } + break; + + default: + dispatch_joystick_event(event); } } private: - sdl_joystick_device* create_joystick_device(running_machine &machine, device_map_t *devmap, int index, input_device_class devclass) + sdl_game_controller_device *create_game_controller_device(int index, SDL_GameController *ctrl) { - char tempname[20]; - - if (devmap->map[index].name.empty()) + // get basic info + char const *const name = SDL_GameControllerName(ctrl); + SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(index); + char guid_str[256]; + guid_str[0] = '\0'; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str) - 1); + char const *const serial = SDL_GameControllerGetSerial(ctrl); + std::string id(guid_str); + if (serial) + id.append(1, '-').append(serial); + + // print some diagnostic info + osd_printf_verbose("Game Controller: %s [GUID %s] Vendor ID %04X, Product ID %04X, Revision %04X, Serial %s\n", + name ? name : "<nullptr>", + guid_str, + SDL_GameControllerGetVendor(ctrl), + SDL_GameControllerGetProduct(ctrl), + SDL_GameControllerGetProductVersion(ctrl), + serial ? serial : "<nullptr>"); + char *const mapping = SDL_GameControllerMapping(ctrl); + if (mapping) { - // only map place holders if there were mappings specified - if (devmap->initialized) - { - snprintf(tempname, ARRAY_LENGTH(tempname), "NC%d", index); - m_sixaxis_mode - ? devicelist()->create_device<sdl_sixaxis_joystick_device>(machine, tempname, tempname, *this) - : devicelist()->create_device<sdl_joystick_device>(machine, tempname, tempname, *this); - } - - return nullptr; + osd_printf_verbose("Game Controller: ... mapping [%s]\n", mapping); + SDL_free(mapping); + } + else + { + osd_printf_verbose("Game Controller: ... no mapping\n"); } - return m_sixaxis_mode - ? devicelist()->create_device<sdl_sixaxis_joystick_device>(machine, devmap->map[index].name.c_str(), devmap->map[index].name.c_str(), *this) - : devicelist()->create_device<sdl_joystick_device>(machine, devmap->map[index].name.c_str(), devmap->map[index].name.c_str(), *this); + // instantiate device + sdl_game_controller_device &devinfo = create_device<sdl_game_controller_device>( + DEVICE_CLASS_JOYSTICK, + name ? name : guid_str, + guid_str, + ctrl, + serial); + return &devinfo; } + + bool m_initialized_game_controller; }; -#else +} // anonymous namespace + +} // namespace osd + + +#else // defined(SDLMAME_SDL2) + +namespace osd { + +namespace { + MODULE_NOT_SUPPORTED(sdl_keyboard_module, OSD_KEYBOARDINPUT_PROVIDER, "sdl") MODULE_NOT_SUPPORTED(sdl_mouse_module, OSD_MOUSEINPUT_PROVIDER, "sdl") -MODULE_NOT_SUPPORTED(sdl_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "sdl") -#endif +MODULE_NOT_SUPPORTED(sdl_lightgun_module, OSD_LIGHTGUNINPUT_PROVIDER, "sdl") +MODULE_NOT_SUPPORTED(sdl_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "sdljoy") +MODULE_NOT_SUPPORTED(sdl_game_controller_module, OSD_JOYSTICKINPUT_PROVIDER, "sdlgame") + +} // anonymous namespace + +} // namespace osd + +#endif // defined(SDLMAME_SDL2) + -MODULE_DEFINITION(KEYBOARDINPUT_SDL, sdl_keyboard_module) -MODULE_DEFINITION(MOUSEINPUT_SDL, sdl_mouse_module) -MODULE_DEFINITION(JOYSTICKINPUT_SDL, sdl_joystick_module) +MODULE_DEFINITION(KEYBOARDINPUT_SDL, osd::sdl_keyboard_module) +MODULE_DEFINITION(MOUSEINPUT_SDL, osd::sdl_mouse_module) +MODULE_DEFINITION(LIGHTGUNINPUT_SDL, osd::sdl_lightgun_module) +MODULE_DEFINITION(JOYSTICKINPUT_SDLJOY, osd::sdl_joystick_module) +MODULE_DEFINITION(JOYSTICKINPUT_SDLGAME, osd::sdl_game_controller_module) diff --git a/src/osd/modules/input/input_sdlcommon.cpp b/src/osd/modules/input/input_sdlcommon.cpp deleted file mode 100644 index a5982ea891d..00000000000 --- a/src/osd/modules/input/input_sdlcommon.cpp +++ /dev/null @@ -1,318 +0,0 @@ -// license:BSD-3-Clause -// copyright-holders:Olivier Galibert, R. Belmont, Brad Hughes -//============================================================ -// -// input_sdlcommon.cpp - SDL Common code shared by SDL modules -// -// Note: this code is also used by the X11 input modules -// -//============================================================ - -#include "input_module.h" -#include "modules/osdmodule.h" - -#if defined(OSD_SDL) - -// standard sdl header -#include <SDL2/SDL.h> -#include <ctype.h> -#include <stddef.h> -#include <mutex> -#include <memory> -#include <algorithm> - -// MAME headers -#include "emu.h" -#include "osdepend.h" -#include "ui/uimain.h" -#include "uiinput.h" -#include "window.h" -#include "strconv.h" - -#include "../../sdl/osdsdl.h" -#include "input_common.h" -#include "input_sdlcommon.h" - -#define GET_WINDOW(ev) window_from_id((ev)->windowID) -//#define GET_WINDOW(ev) ((ev)->windowID) - -static std::shared_ptr<sdl_window_info> window_from_id(Uint32 windowID) -{ - SDL_Window *sdl_window = SDL_GetWindowFromID(windowID); - - auto& windows = osd_common_t::s_window_list; - auto window = std::find_if(windows.begin(), windows.end(), [sdl_window](std::shared_ptr<osd_window> w) - { - return std::static_pointer_cast<sdl_window_info>(w)->platform_window() == sdl_window; - }); - - if (window == windows.end()) - return nullptr; - - return std::static_pointer_cast<sdl_window_info>(*window); -} - -void sdl_event_manager::process_events(running_machine &machine) -{ - std::lock_guard<std::mutex> scope_lock(m_lock); - SDL_Event sdlevent; - while (SDL_PollEvent(&sdlevent)) - { - // process window events if they come in - if (sdlevent.type == SDL_WINDOWEVENT) - process_window_event(machine, sdlevent); - - // Find all subscribers for the event type - auto subscribers = m_subscription_index.equal_range(sdlevent.type); - - // Dispatch the events - std::for_each(subscribers.first, subscribers.second, [&sdlevent](auto sub) - { - sub.second->handle_event(sdlevent); - }); - } -} - -void sdl_event_manager::process_window_event(running_machine &machine, SDL_Event &sdlevent) -{ - std::shared_ptr<sdl_window_info> window = GET_WINDOW(&sdlevent.window); - - if (window == nullptr) - { - // This condition may occur when the fullscreen toggle is used - osd_printf_verbose("Skipped window event due to missing window param from SDL\n"); - return; - } - - switch (sdlevent.window.event) - { - case SDL_WINDOWEVENT_SHOWN: - m_has_focus = true; - break; - - case SDL_WINDOWEVENT_CLOSE: - machine.schedule_exit(); - break; - - case SDL_WINDOWEVENT_LEAVE: - machine.ui_input().push_mouse_leave_event(window->target()); - m_mouse_over_window = 0; - break; - - case SDL_WINDOWEVENT_MOVED: - window->notify_changed(); - m_focus_window = window; - m_has_focus = true; - break; - - case SDL_WINDOWEVENT_RESIZED: -#ifdef SDLMAME_LINUX - /* FIXME: SDL2 sends some spurious resize events on Ubuntu - * while in fullscreen mode. Ignore them for now. - */ - if (!window->fullscreen()) -#endif - { - //printf("event data1,data2 %d x %d %ld\n", event.window.data1, event.window.data2, sizeof(SDL_Event)); - window->resize(sdlevent.window.data1, sdlevent.window.data2); - } - m_focus_window = window; - m_has_focus = true; - break; - - case SDL_WINDOWEVENT_ENTER: - m_mouse_over_window = 1; - /* fall through */ - case SDL_WINDOWEVENT_FOCUS_GAINED: - case SDL_WINDOWEVENT_EXPOSED: - case SDL_WINDOWEVENT_MAXIMIZED: - case SDL_WINDOWEVENT_RESTORED: - m_focus_window = window; - m_has_focus = true; - break; - - case SDL_WINDOWEVENT_MINIMIZED: - case SDL_WINDOWEVENT_FOCUS_LOST: - m_has_focus = false; - break; - } -} - -//============================================================ -// customize_input_type_list -//============================================================ - -void sdl_osd_interface::customize_input_type_list(simple_list<input_type_entry> &typelist) -{ - input_item_id mameid_code; - input_code ui_code; - const char* uimode; - char fullmode[64]; - - // loop over the defaults - for (input_type_entry &entry : typelist) - { - switch (entry.type()) - { - // configurable UI mode switch - case IPT_UI_TOGGLE_UI: - uimode = options().ui_mode_key(); - if (!strcmp(uimode, "auto")) - { -#if defined(__APPLE__) && defined(__MACH__) - mameid_code = keyboard_trans_table::instance().lookup_mame_code("ITEM_ID_INSERT"); -#else - mameid_code = keyboard_trans_table::instance().lookup_mame_code("ITEM_ID_SCRLOCK"); -#endif - } - else - { - snprintf(fullmode, 63, "ITEM_ID_%s", uimode); - mameid_code = keyboard_trans_table::instance().lookup_mame_code(fullmode); - } - ui_code = input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, input_item_id(mameid_code)); - entry.defseq(SEQ_TYPE_STANDARD).set(ui_code); - break; - // alt-enter for fullscreen - case IPT_OSD_1: - entry.configure_osd("TOGGLE_FULLSCREEN", "Toggle Fullscreen"); - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_ENTER, KEYCODE_LALT); - break; - - // disable UI_SELECT when LALT is down, this stops selecting - // things in the menu when toggling fullscreen with LALT+ENTER - /* case IPT_UI_SELECT: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_ENTER, input_seq::not_code, KEYCODE_LALT); - break;*/ - - // page down for fastforward (must be OSD_3 as per src/emu/ui.c) - case IPT_UI_FAST_FORWARD: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_PGDN); - break; - - // OSD hotkeys use LCTRL and start at F3, they start at - // F3 because F1-F2 are hardcoded into many drivers to - // various dipswitches, and pressing them together with - // LCTRL will still press/toggle these dipswitches. - - // LCTRL-F3 to toggle fullstretch - case IPT_OSD_2: - entry.configure_osd("TOGGLE_FULLSTRETCH", "Toggle Uneven stretch"); - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F3, KEYCODE_LCONTROL); - break; - // add a Not lcrtl condition to the reset key - case IPT_UI_SOFT_RESET: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F3, input_seq::not_code, KEYCODE_LCONTROL, input_seq::not_code, KEYCODE_LSHIFT); - break; - - // LCTRL-F4 to toggle keep aspect - case IPT_OSD_4: - entry.configure_osd("TOGGLE_KEEP_ASPECT", "Toggle Keepaspect"); - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F4, KEYCODE_LCONTROL); - break; - // add a Not lcrtl condition to the show gfx key - case IPT_UI_SHOW_GFX: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F4, input_seq::not_code, KEYCODE_LCONTROL); - break; - - // LCTRL-F5 to toggle OpenGL filtering - case IPT_OSD_5: - entry.configure_osd("TOGGLE_FILTER", "Toggle Filter"); - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F5, KEYCODE_LCONTROL); - break; - // add a Not lcrtl condition to the toggle debug key - case IPT_UI_TOGGLE_DEBUG: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F5, input_seq::not_code, KEYCODE_LCONTROL); - break; - - // LCTRL-F6 to decrease OpenGL prescaling - case IPT_OSD_6: - entry.configure_osd("DECREASE_PRESCALE", "Decrease Prescaling"); - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F6, KEYCODE_LCONTROL); - break; - // add a Not lcrtl condition to the toggle cheat key - case IPT_UI_TOGGLE_CHEAT: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F6, input_seq::not_code, KEYCODE_LCONTROL); - break; - - // LCTRL-F7 to increase OpenGL prescaling - case IPT_OSD_7: - entry.configure_osd("INCREASE_PRESCALE", "Increase Prescaling"); - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F7, KEYCODE_LCONTROL); - break; - - // lshift-lalt-F12 for fullscreen video (BGFX) - case IPT_OSD_8: - entry.configure_osd("RENDER_AVI", "Record Rendered Video"); - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F12, KEYCODE_LSHIFT, KEYCODE_LALT); - break; - - // add a Not lcrtl condition to the load state key - case IPT_UI_LOAD_STATE: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F7, input_seq::not_code, KEYCODE_LCONTROL, input_seq::not_code, KEYCODE_LSHIFT); - break; - - // add a Not lcrtl condition to the throttle key - case IPT_UI_THROTTLE: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F10, input_seq::not_code, KEYCODE_LCONTROL); - break; - - // disable the config menu if the ALT key is down - // (allows ALT-TAB to switch between apps) - case IPT_UI_CONFIGURE: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_TAB, input_seq::not_code, KEYCODE_LALT, input_seq::not_code, KEYCODE_RALT); - break; - -#if defined(__APPLE__) && defined(__MACH__) - // 78-key Apple MacBook & Bluetooth keyboards have no right control key - case IPT_MAHJONG_SCORE: - if (entry.player() == 0) - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_SLASH); - break; -#endif - - // leave everything else alone - default: - break; - } - } -} - -void sdl_osd_interface::poll_inputs(running_machine &machine) -{ - m_keyboard_input->poll_if_necessary(machine); - m_mouse_input->poll_if_necessary(machine); - m_lightgun_input->poll_if_necessary(machine); - m_joystick_input->poll_if_necessary(machine); -} - -void sdl_osd_interface::release_keys() -{ - auto keybd = dynamic_cast<input_module_base*>(m_keyboard_input); - if (keybd != nullptr) - keybd->devicelist()->reset_devices(); -} - -bool sdl_osd_interface::should_hide_mouse() -{ - // if we are paused, no - if (machine().paused()) - return false; - - // if neither mice nor lightguns enabled in the core, then no - if (!options().mouse() && !options().lightgun()) - return false; - - if (!sdl_event_manager::instance().mouse_over_window()) - return false; - - // otherwise, yes - return true; -} - -void sdl_osd_interface::process_events_buf() -{ - SDL_PumpEvents(); -} - -#endif diff --git a/src/osd/modules/input/input_sdlcommon.h b/src/osd/modules/input/input_sdlcommon.h deleted file mode 100644 index 377b04d71c9..00000000000 --- a/src/osd/modules/input/input_sdlcommon.h +++ /dev/null @@ -1,204 +0,0 @@ -// license:BSD-3-Clause -// copyright-holders:Olivier Galibert, R. Belmont, Brad Hughes -//============================================================ -// -// input_sdlcommon.h - SDL Common code shared by SDL modules -// -// Note: this code is also used by the X11 input modules -// -//============================================================ - -#ifndef INPUT_SDLCOMMON_H_ -#define INPUT_SDLCOMMON_H_ - -#include <unordered_map> -#include <algorithm> - -#define MAX_DEVMAP_ENTRIES 16 -#define SDL_MODULE_EVENT_BUFFER_SIZE 5 - -// state information for a keyboard -struct keyboard_state -{ - int32_t state[0x3ff]; // must be int32_t! - int8_t oldkey[MAX_KEYS]; - int8_t currkey[MAX_KEYS]; -}; - -// state information for a mouse -struct mouse_state -{ - int32_t lX, lY; - int32_t buttons[MAX_BUTTONS]; -}; - - -// state information for a joystick; DirectInput state must be first element -struct joystick_state -{ - SDL_Joystick *device; - int32_t axes[MAX_AXES]; - int32_t buttons[MAX_BUTTONS]; - int32_t hatsU[MAX_HATS], hatsD[MAX_HATS], hatsL[MAX_HATS], hatsR[MAX_HATS]; - int32_t balls[MAX_AXES]; -}; - -struct device_map_t -{ - struct { - std::string name; - int physical; - } map[MAX_DEVMAP_ENTRIES]; - int logical[MAX_DEVMAP_ENTRIES]; - int initialized; -}; - -//============================================================ -// event_manager_t -//============================================================ - -class sdl_event_subscriber -{ -public: - virtual ~sdl_event_subscriber() {} - virtual void handle_event(SDL_Event &sdlevent) = 0; -}; - -template <class TSubscriber> -class event_manager_t -{ -protected: - std::mutex m_lock; - std::unordered_multimap<int, TSubscriber*> m_subscription_index; - event_manager_t() - { - } - -public: - virtual ~event_manager_t() - { - } - - void subscribe(int* event_types, int num_event_types, TSubscriber *subscriber) - { - std::lock_guard<std::mutex> scope_lock(m_lock); - - // Add the subscription - for (int i = 0; i < num_event_types; i++) - { - m_subscription_index.emplace(event_types[i], subscriber); - } - } - - void unsubscribe(TSubscriber *subscriber) - { - std::lock_guard<std::mutex> scope_lock(m_lock); - - // Remove the events that match the subscriber - for (auto it = begin(m_subscription_index); it != end(m_subscription_index);) - { - if (it->second == subscriber) - { - it = m_subscription_index.erase(it); - } - else - { - ++it; - } - } - } - - virtual void process_events(running_machine &machine) = 0; -}; - -class sdl_window_info; - -class sdl_event_manager : public event_manager_t<sdl_event_subscriber> -{ -private: - bool m_mouse_over_window; - bool m_has_focus; - std::shared_ptr<sdl_window_info> m_focus_window; - - sdl_event_manager() - : m_mouse_over_window(true), - m_has_focus(true), - m_focus_window(nullptr) - { - } - -public: - bool mouse_over_window() const { return m_mouse_over_window; } - bool has_focus() const { return m_focus_window != nullptr; } - std::shared_ptr<sdl_window_info> focus_window() const { return m_focus_window; } - - static sdl_event_manager& instance() - { - static sdl_event_manager s_instance; - return s_instance; - } - - void process_events(running_machine &machine) override; - -private: - void process_window_event(running_machine &machine, SDL_Event &sdlevent); -}; - -//============================================================ -// INLINE FUNCTIONS -//============================================================ - -static inline std::string remove_spaces(const char *s) -{ - // Remove the spaces - auto output = std::string(s); - output.erase(std::remove_if(output.begin(), output.end(), isspace), output.end()); - - return output; -} - -//============================================================ -// devmap_init - initializes a device_map based on -// an input option prefix and max number of devices -//============================================================ - -static inline void devmap_init(running_machine &machine, device_map_t *devmap, const char *opt, int max_devices, const char *label) -{ - int dev; - char defname[20]; - - // The max devices the user specified, better not be bigger than the max the arrays can old - assert(max_devices <= MAX_DEVMAP_ENTRIES); - - // Initialize the map to default uninitialized values - for (dev = 0; dev < MAX_DEVMAP_ENTRIES; dev++) - { - devmap->map[dev].name.clear(); - devmap->map[dev].physical = -1; - devmap->logical[dev] = -1; - } - devmap->initialized = 0; - - // populate the device map up to the max number of devices - for (dev = 0; dev < max_devices; dev++) - { - const char *dev_name; - - // derive the parameter name from the option name and index. For instance: lightgun_index1 to lightgun_index8 - sprintf(defname, "%s%d", opt, dev + 1); - - // Get the user-specified name that matches the parameter - dev_name = machine.options().value(defname); - - // If they've specified a name and it's not "auto", treat it as a custom mapping - if (dev_name && *dev_name && strcmp(dev_name, OSDOPTVAL_AUTO)) - { - // remove the spaces from the name store it in the index - devmap->map[dev].name = remove_spaces(dev_name); - osd_printf_verbose("%s: Logical id %d: %s\n", label, dev + 1, devmap->map[dev].name.c_str()); - devmap->initialized = 1; - } - } -} - -#endif diff --git a/src/osd/modules/input/input_uwp.cpp b/src/osd/modules/input/input_uwp.cpp deleted file mode 100644 index 6b3b8675cff..00000000000 --- a/src/osd/modules/input/input_uwp.cpp +++ /dev/null @@ -1,649 +0,0 @@ -// license:BSD-3-Clause -// copyright-holders:Brad Hughes -//============================================================ -// -// input_uwp.cpp - UWP input implementation -// -//============================================================ - -#include "input_module.h" -#include "modules/osdmodule.h" - -#if defined(OSD_UWP) - -#include <agile.h> -#include <ppltasks.h> -#include <collection.h> -#undef interface - -// MAME headers -#include "emu.h" -#include "uiinput.h" -#include "strconv.h" - -// MAMEOS headers -#include "winmain.h" -#include "input_common.h" -#include "input_windows.h" - -#define UWP_BUTTON_COUNT 32 - -using namespace concurrency; -using namespace Windows::UI::Core; -using namespace Windows::Foundation; -using namespace Windows::Foundation::Collections; -using namespace Windows::Gaming::Input; - -//============================================================ -// UWP Base device/module implementation -//============================================================ - -//============================================================ -// UwpInputDevice - base class for implementing an input -// device in C++/CX. To be used with uwp_input_device -//============================================================ - -private ref class UwpInputDevice -{ -private: - running_machine & m_machine; - std::string m_name; - std::string m_id; - input_device_class m_devclass; - input_module & m_module; - input_device *m_inputdevice; - -internal: - UwpInputDevice(running_machine &machine, const char *name, const char *id, input_device_class deviceclass, input_module &module) - : m_machine(machine), - m_name(name), - m_id(id), - m_devclass(deviceclass), - m_module(module), - m_inputdevice(nullptr) - { - } - - property running_machine & Machine - { - running_machine & get() { return m_machine; } - } - - property const std::string & Name - { - const std::string & get() { return m_name; } - } - - property const std::string & Id - { - const std::string & get() { return m_id; } - } - - property input_device_class DeviceClass - { - input_device_class get() { return m_devclass; } - } - - property input_module & Module - { - input_module & get() { return m_module; } - } - - property input_device* InputDevice - { - input_device* get() { return m_inputdevice; } - void set(input_device* value) { m_inputdevice = value; } - } - - virtual void Poll() - { - } - - virtual void Reset() - { - } -}; - -//============================================================ -// uwp_input_device - a device that can be used to wrap a -// C++/CX ref class for an input device implementation -//============================================================ - -class uwp_input_device : public device_info -{ -private: - UwpInputDevice ^m_wrapped_device; - -public: - uwp_input_device(UwpInputDevice ^device) - : device_info(device->Machine, device->Name.c_str(), device->Id.c_str(), device->DeviceClass, device->Module), - m_wrapped_device(device) - { - } - - void poll() override - { - m_wrapped_device->Poll(); - } - - void reset() override - { - m_wrapped_device->Reset(); - } -}; - -//============================================================ -// UwpInputModule - a base class that can be used to -// implement an input module with a C++/CX class. -// normally used with uwp_wininput_module -//============================================================ - -class uwp_input_module; - -private ref class UwpInputModule -{ -private: - const std::string m_type; - const std::string m_name; - uwp_input_module *m_module; - -internal: - UwpInputModule(const char *type, const char *name) - : m_type(type), - m_name(name), - m_module(nullptr) - { - } - - property const std::string & Type - { - const std::string & get() { return m_type; } - } - - property const std::string & Name - { - const std::string & get() { return m_name; } - } - - property uwp_input_module * NativeModule - { - uwp_input_module * get() { return m_module; } - void set(uwp_input_module * value) { m_module = value; } - } - - virtual void input_init(running_machine &machine) - { - } -}; - -//============================================================ -// uwp_input_module - an input module that can be -// used to create an input module with a C++/CX ref class -//============================================================ - -class uwp_input_module : public wininput_module -{ -private: - UwpInputModule^ m_refmodule; - -public: - uwp_input_module(UwpInputModule^ refmodule) - : wininput_module(refmodule->Type.c_str(), refmodule->Name.c_str()), - m_refmodule(refmodule) - { - refmodule->NativeModule = this; - } - - void input_init(running_machine &machine) override - { - m_refmodule->input_init(machine); - } -}; - -//============================================================ -// UWP Keyboard Implementation -//============================================================ - -//============================================================ -// UwpKeyboardDevice -//============================================================ - -private ref class UwpKeyboardDevice : public UwpInputDevice -{ -private: - keyboard_state keyboard; - Platform::Agile<CoreWindow> m_coreWindow; - std::mutex m_state_lock; - -internal: - UwpKeyboardDevice(Platform::Agile<CoreWindow> coreWindow, running_machine& machine, char *name, const char *id, input_module &module) - : UwpInputDevice(machine, name, id, DEVICE_CLASS_KEYBOARD, module), - keyboard({{0}}), - m_coreWindow(coreWindow) - { - coreWindow->Dispatcher->AcceleratorKeyActivated += ref new TypedEventHandler<CoreDispatcher^, AcceleratorKeyEventArgs^>(this, &UwpKeyboardDevice::OnAcceleratorKeyActivated); - coreWindow->KeyDown += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &UwpKeyboardDevice::OnKeyDown); - coreWindow->KeyUp += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &UwpKeyboardDevice::OnKeyUp); - coreWindow->CharacterReceived += ref new TypedEventHandler<CoreWindow^, CharacterReceivedEventArgs^>(this, &UwpKeyboardDevice::OnCharacterReceived); - } - - void Reset() override - { - std::lock_guard<std::mutex> scope_lock(m_state_lock); - memset(&keyboard, 0, sizeof(keyboard)); - } - - void Configure() - { - keyboard_trans_table &table = keyboard_trans_table::instance(); - - // populate it indexed by the scancode - for (int keynum = KEY_UNKNOWN + 1; keynum < MAX_KEYS; keynum++) - { - input_item_id itemid = table.map_di_scancode_to_itemid(keynum); - const char *keyname = table.ui_label_for_mame_key(itemid); - - char temp[256]; - if (keyname == nullptr) - { - snprintf(temp, ARRAY_LENGTH(temp), "Scan%03d", keynum); - keyname = temp; - } - - // add the item to the device - this->InputDevice->add_item(keyname, itemid, generic_button_get_state<std::uint8_t>, &keyboard.state[keynum]); - } - } - - void OnKeyDown(CoreWindow^ win, KeyEventArgs^ args) - { - std::lock_guard<std::mutex> scope_lock(m_state_lock); - CorePhysicalKeyStatus status = args->KeyStatus; - int discancode = (status.ScanCode & 0x7f) | (status.IsExtendedKey ? 0x80 : 0x00); - keyboard.state[discancode] = 0x80; - } - - void OnKeyUp(CoreWindow^ win, KeyEventArgs^ args) - { - std::lock_guard<std::mutex> scope_lock(m_state_lock); - CorePhysicalKeyStatus status = args->KeyStatus; - int discancode = (status.ScanCode & 0x7f) | (status.IsExtendedKey ? 0x80 : 0x00); - keyboard.state[discancode] = 0; - } - - void OnCharacterReceived(CoreWindow ^sender, CharacterReceivedEventArgs ^args) - { - this->Machine.ui_input().push_char_event(osd_common_t::s_window_list.front()->target(), args->KeyCode); - } - - void OnAcceleratorKeyActivated(CoreDispatcher ^sender, AcceleratorKeyEventArgs ^args) - { - std::lock_guard<std::mutex> scope_lock(m_state_lock); - auto eventType = args->EventType; - if (eventType == CoreAcceleratorKeyEventType::SystemKeyDown || - eventType == CoreAcceleratorKeyEventType::SystemKeyUp) - { - CorePhysicalKeyStatus status = args->KeyStatus; - int discancode = (status.ScanCode & 0x7f) | (status.IsExtendedKey ? 0x80 : 0x00); - keyboard.state[discancode] = - eventType == CoreAcceleratorKeyEventType::SystemKeyDown ? 0x80 : 0; - } - } -}; - -//============================================================ -// UwpKeyboardModule -//============================================================ - -private ref class UwpKeyboardModule : public UwpInputModule -{ -private: - running_machine *m_machine; - -internal: - UwpKeyboardModule() - : UwpInputModule(OSD_KEYBOARDINPUT_PROVIDER, "uwp") - { - } - - void input_init(running_machine &machine) override - { - auto first_window = std::static_pointer_cast<uwp_window_info>(osd_common_t::s_window_list.front()); - auto coreWindow = first_window->platform_window(); - - // allocate the UWP implementation of the device object - UwpKeyboardDevice ^refdevice = ref new UwpKeyboardDevice(coreWindow, machine, "UWP Keyboard 1", "UWP Keyboard 1", *this->NativeModule); - - // Allocate the wrapper and add it to the list - auto created_devinfo = std::make_unique<uwp_input_device>(refdevice); - uwp_input_device *devinfo = NativeModule->devicelist()->add_device<uwp_input_device>(machine, std::move(created_devinfo)); - - // Give the UWP implementation a handle to the input_device - refdevice->InputDevice = devinfo->device(); - - // Configure the device - refdevice->Configure(); - } -}; - -//============================================================ -// uwp_keyboard_module -//============================================================ - -class uwp_keyboard_module : public uwp_input_module -{ -public: - uwp_keyboard_module() - : uwp_input_module(ref new UwpKeyboardModule()) - { - } -}; - -// default axis names -static const char *const uwp_axis_name[] = -{ - "LSX", - "LSY", - "RSX", - "RSY" -}; - -static const input_item_id uwp_axis_ids[] = -{ - ITEM_ID_XAXIS, - ITEM_ID_YAXIS, - ITEM_ID_RXAXIS, - ITEM_ID_RYAXIS -}; - -struct gamepad_state -{ - BYTE buttons[UWP_BUTTON_COUNT]; - LONG left_trigger; - LONG right_trigger; - LONG left_thumb_x; - LONG left_thumb_y; - LONG right_thumb_x; - LONG right_thumb_y; -}; - -// Maps different UWP GameControllerButtonLabels to a halfway-sane input_item_id in many cases -static input_item_id buttonlabel_to_itemid[] = -{ - input_item_id::ITEM_ID_INVALID, // GameControllerButtonLabel::None - input_item_id::ITEM_ID_SELECT, // GameControllerButtonLabel::XboxBack - input_item_id::ITEM_ID_START, // GameControllerButtonLabel::XboxStart - input_item_id::ITEM_ID_START, // GameControllerButtonLabel::XboxMenu - input_item_id::ITEM_ID_SELECT, // GameControllerButtonLabel::XboxView - input_item_id::ITEM_ID_HAT1UP, // GameControllerButtonLabel::XboxUp - input_item_id::ITEM_ID_HAT1DOWN, // GameControllerButtonLabel::XboxDown - input_item_id::ITEM_ID_HAT1LEFT, // GameControllerButtonLabel::XboxLeft - input_item_id::ITEM_ID_HAT1RIGHT, // GameControllerButtonLabel::XboxRight - input_item_id::ITEM_ID_BUTTON1, // GameControllerButtonLabel::XboxA - input_item_id::ITEM_ID_BUTTON2, // GameControllerButtonLabel::XboxB - input_item_id::ITEM_ID_BUTTON3, // GameControllerButtonLabel::XboxX - input_item_id::ITEM_ID_BUTTON4, // GameControllerButtonLabel::XboxY - input_item_id::ITEM_ID_BUTTON5, // GameControllerButtonLabel::XboxLeftBumper - input_item_id::ITEM_ID_ZAXIS, // GameControllerButtonLabel::XboxLeftTrigger - input_item_id::ITEM_ID_BUTTON7, // GameControllerButtonLabel::XboxLeftStickButton - input_item_id::ITEM_ID_BUTTON6, // GameControllerButtonLabel::XboxRightBumper - input_item_id::ITEM_ID_RZAXIS, // GameControllerButtonLabel::XboxRightTrigger - input_item_id::ITEM_ID_BUTTON8, // GameControllerButtonLabel::XboxRightStickButton - input_item_id::ITEM_ID_BUTTON9, // GameControllerButtonLabel::XboxPaddle1 - input_item_id::ITEM_ID_BUTTON10, // GameControllerButtonLabel::XboxPaddle2 - input_item_id::ITEM_ID_BUTTON11, // GameControllerButtonLabel::XboxPaddle3 - input_item_id::ITEM_ID_BUTTON12, // GameControllerButtonLabel::XboxPaddle4 - input_item_id::ITEM_ID_INVALID, // GameControllerButtonLabel_Mode - input_item_id::ITEM_ID_SELECT, // GameControllerButtonLabel_Select - input_item_id::ITEM_ID_START, // GameControllerButtonLabel_Menu - input_item_id::ITEM_ID_SELECT, // GameControllerButtonLabel_View - input_item_id::ITEM_ID_SELECT, // GameControllerButtonLabel_Back - input_item_id::ITEM_ID_START, // GameControllerButtonLabel_Start - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Options - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Share - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Up - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Down - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Left - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Right - input_item_id::ITEM_ID_BUTTON1, // GameControllerButtonLabel_LetterA - input_item_id::ITEM_ID_BUTTON2, // GameControllerButtonLabel_LetterB - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_LetterC - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_LetterL - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_LetterR - input_item_id::ITEM_ID_BUTTON3, // GameControllerButtonLabel_LetterX - input_item_id::ITEM_ID_BUTTON4, // GameControllerButtonLabel_LetterY - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_LetterZ - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Cross - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Circle - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Square - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Triangle - input_item_id::ITEM_ID_BUTTON5, // GameControllerButtonLabel_LeftBumper - input_item_id::ITEM_ID_ZAXIS, // GameControllerButtonLabel_LeftTrigger - input_item_id::ITEM_ID_BUTTON7, // GameControllerButtonLabel_LeftStickButton - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Left1 - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Left2 - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Left3 - input_item_id::ITEM_ID_BUTTON6, // GameControllerButtonLabel_RightBumper - input_item_id::ITEM_ID_RZAXIS, // GameControllerButtonLabel_RightTrigger - input_item_id::ITEM_ID_BUTTON8, // GameControllerButtonLabel_RightStickButton - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Right1 - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Right2 - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Right3 - input_item_id::ITEM_ID_BUTTON9, // GameControllerButtonLabel_Paddle1 - input_item_id::ITEM_ID_BUTTON10, // GameControllerButtonLabel_Paddle2 - input_item_id::ITEM_ID_BUTTON11, // GameControllerButtonLabel_Paddle3 - input_item_id::ITEM_ID_BUTTON12, // GameControllerButtonLabel_Paddle4 - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Plus - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Minus - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_DownLeftArrow - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_DialLeft - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_DialRight - input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Suspension -}; - -//============================================================ -// UwpJoystickDevice -//============================================================ - -private ref class UwpJoystickDevice : public UwpInputDevice -{ -private: - Gamepad ^m_pad; - bool m_configured; - gamepad_state state; - -internal: - UwpJoystickDevice(Gamepad^ pad, running_machine &machine, const char *name, const char *id, input_module &module) - : UwpInputDevice(machine, name, id, DEVICE_CLASS_JOYSTICK, module), - m_pad(pad), - m_configured(false), - state({0}) - {} - - void Poll() override - { - // If the device hasn't been configured, don't poll - if (!m_configured) - return; - - GamepadReading reading = m_pad->GetCurrentReading(); - - for (int butnum = 0; butnum < UWP_BUTTON_COUNT; butnum++) - { - GamepadButtons currentButton = GamepadButtons(1 << butnum); - state.buttons[butnum] = (reading.Buttons & currentButton) != GamepadButtons::None ? 0xFF : 0; - } - - // Now grab the axis values - // Each of the thumbstick axis members is a signed value between -32768 and 32767 describing the position of the thumbstick - // However, the Y axis values are inverted from what MAME expects, so negate the value - state.left_thumb_x = normalize_absolute_axis(reading.LeftThumbstickX, -1, 1); - state.left_thumb_y = -normalize_absolute_axis(reading.LeftThumbstickY, -1, 1); - state.right_thumb_x = normalize_absolute_axis(reading.RightThumbstickX, -1, 1); - state.right_thumb_y = -normalize_absolute_axis(reading.RightThumbstickY, -1, 1); - - // Get the trigger values - state.left_trigger = normalize_absolute_axis(reading.LeftTrigger, 0.0, 1.0); - state.right_trigger = normalize_absolute_axis(reading.RightTrigger, 0.0, 1.0); - - // For the UI, triggering UI_CONFIGURE is odd. It requires a EVENT_CHAR first - static constexpr int menuhotkey = (int)(GamepadButtons::View | GamepadButtons::X); - if (((int)reading.Buttons & menuhotkey) == menuhotkey) - { - ui_event uiev; - memset(&uiev, 0, sizeof(uiev)); - uiev.event_type = ui_event::IME_CHAR; - this->Machine.ui_input().push_event(uiev); - } - } - - void Reset() override - { - memset(&state, 0, sizeof(state)); - } - - void Configure() - { - // If the device has already been configured, don't do it again - if (m_configured) - return; - - GamepadReading r = m_pad->GetCurrentReading(); - - // Add the axes - for (int axisnum = 0; axisnum < 4; axisnum++) - { - this->InputDevice->add_item( - uwp_axis_name[axisnum], - uwp_axis_ids[axisnum], - generic_axis_get_state<LONG>, - &state.left_thumb_x + axisnum); - } - - // populate the buttons - for (int butnum = 0; butnum < UWP_BUTTON_COUNT; butnum++) - { - GamepadButtons button = GamepadButtons(1 << butnum); - auto label = m_pad->GetButtonLabel(button); - if (label != GameControllerButtonLabel::None) - { - std::string desc = osd::text::from_wstring(label.ToString()->Data()); - this->InputDevice->add_item( - desc.c_str(), - buttonlabel_to_itemid[static_cast<int>(label)], - generic_button_get_state<BYTE>, - &state.buttons[butnum]); - } - } - - this->InputDevice->add_item( - "Left Trigger", - ITEM_ID_ZAXIS, - generic_axis_get_state<LONG>, - &state.left_trigger); - - this->InputDevice->add_item( - "Right Trigger", - ITEM_ID_RZAXIS, - generic_axis_get_state<LONG>, - &state.right_trigger); - - m_configured = true; - } -}; - -//============================================================ -// UwpJoystickModule -//============================================================ - -private ref class UwpJoystickModule : public UwpInputModule -{ -private: - boolean m_joysticks_discovered; - -internal: - UwpJoystickModule() - : UwpInputModule(OSD_JOYSTICKINPUT_PROVIDER, "uwp"), - m_joysticks_discovered(false) - { - } - - void input_init(running_machine &machine) override - { - PerformGamepadDiscovery(); - - auto pads = Gamepad::Gamepads; - - int padindex = 0; - std::for_each(begin(pads), end(pads), [&](Gamepad^ pad) - { - uwp_input_device *devinfo; - - std::ostringstream namestream; - namestream << "UWP Gamepad " << (padindex + 1); - - auto name = namestream.str(); - - // allocate the UWP implementation of the device object - UwpJoystickDevice ^refdevice = ref new UwpJoystickDevice(pad, machine, name.c_str(), name.c_str(), *this->NativeModule); - - // Allocate the wrapper and add it to the list - auto created_devinfo = std::make_unique<uwp_input_device>(refdevice); - devinfo = NativeModule->devicelist()->add_device<uwp_input_device>(machine, std::move(created_devinfo)); - - // Give the UWP implementation a handle to the input_device - refdevice->InputDevice = devinfo->device(); - - // Configure the device - refdevice->Configure(); - - padindex++; - }); - } - -private: - void PerformGamepadDiscovery() - { - Gamepad::GamepadAdded += ref new EventHandler<Gamepad ^>(this, &UwpJoystickModule::OnGamepadAdded); - auto start = std::chrono::system_clock::now(); - - // We need to pause a bit and pump events so gamepads get discovered - while (std::chrono::system_clock::now() - start < std::chrono::milliseconds(1000)) - CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); - - m_joysticks_discovered = true; - } - - void OnGamepadAdded(Platform::Object ^sender, Gamepad ^pad) - { - if (m_joysticks_discovered) - { - osd_printf_error("Input: UWP Compatible %s gamepad plugged in AFTER discovery complete!\n", pad->IsWireless ? "Wireless" : "Wired"); - } - else - { - osd_printf_verbose("Input: UWP Compatible %s gamepad discovered.\n", pad->IsWireless ? "Wireless" : "Wired"); - } - } -}; - -//============================================================ -// uwp_joystick_module -//============================================================ - -class uwp_joystick_module : public uwp_input_module -{ -public: - uwp_joystick_module() - : uwp_input_module(ref new UwpJoystickModule()) - { - } -}; - -#else -MODULE_NOT_SUPPORTED(uwp_keyboard_module, OSD_KEYBOARDINPUT_PROVIDER, "uwp") -MODULE_NOT_SUPPORTED(uwp_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "uwp") -#endif - -MODULE_DEFINITION(KEYBOARDINPUT_UWP, uwp_keyboard_module) -MODULE_DEFINITION(JOYSTICKINPUT_UWP, uwp_joystick_module) diff --git a/src/osd/modules/input/input_win32.cpp b/src/osd/modules/input/input_win32.cpp index c07a1c681d8..921c902ad31 100644 --- a/src/osd/modules/input/input_win32.cpp +++ b/src/osd/modules/input/input_win32.cpp @@ -7,25 +7,28 @@ //============================================================ #include "input_module.h" -#include "modules/osdmodule.h" #if defined(OSD_WINDOWS) +#include "input_windows.h" + +#include "input_wincommon.h" + +// osd/windows +#include "window.h" + +// emu +#include "inpttype.h" + +#include "strconv.h" + // standard windows headers -#include <windows.h> #include <tchar.h> -#undef interface -// MAME headers -#include "emu.h" -#include "strconv.h" -// MAMEOS headers -#include "winmain.h" -#include "window.h" +namespace osd { -#include "input_common.h" -#include "input_windows.h" +namespace { //============================================================ // win32_keyboard_device @@ -35,117 +38,118 @@ class win32_keyboard_device : public event_based_device<KeyPressEventArgs> { public: - keyboard_state keyboard; + win32_keyboard_device(std::string &&name, std::string &&id, input_module &module) : + event_based_device(std::move(name), std::move(id), module), + m_keyboard({{0}}) + { + } - win32_keyboard_device(running_machine& machine, const char *name, const char *id, input_module &module) - : event_based_device(machine, name, id, DEVICE_CLASS_KEYBOARD, module), - keyboard({{0}}) + virtual void reset() override { + event_based_device::reset(); + memset(&m_keyboard, 0, sizeof(m_keyboard)); } - void reset() override + virtual void configure(input_device &device) override { - memset(&keyboard, 0, sizeof(keyboard)); + keyboard_trans_table const &table = keyboard_trans_table::instance(); + + for (int keynum = 0; keynum < MAX_KEYS; keynum++) + { + input_item_id itemid = table.map_di_scancode_to_itemid(keynum); + TCHAR keyname[100]; + + // generate the name + // FIXME: GetKeyNameText gives bogus names for media keys and various other things + // in many cases it ignores the "extended" bit and returns the key name corresponding to the scan code alone + if (GetKeyNameText(((keynum & 0x7f) << 16) | ((keynum & 0x80) << 17), keyname, std::size(keyname)) == 0) + _sntprintf(keyname, std::size(keyname), TEXT("Scan%03d"), keynum); + std::string name = text::from_tstring(keyname); + + // add the item to the device + device.add_item( + name, + util::string_format("SCAN%03d", keynum), + itemid, + generic_button_get_state<std::uint8_t>, + &m_keyboard.state[keynum]); + } } protected: - void process_event(KeyPressEventArgs &args) override + virtual void process_event(KeyPressEventArgs const &args) override { - keyboard.state[args.scancode] = args.event_id == INPUT_EVENT_KEYDOWN ? 0x80 : 0x00; + m_keyboard.state[args.scancode] = args.event_id == INPUT_EVENT_KEYDOWN ? 0x80 : 0x00; } + +private: + keyboard_state m_keyboard; }; + //============================================================ // keyboard_input_win32 - win32 keyboard input module //============================================================ -class keyboard_input_win32 : public wininput_module +class keyboard_input_win32 : public wininput_module<win32_keyboard_device> { -private: - public: - keyboard_input_win32() - : wininput_module(OSD_KEYBOARDINPUT_PROVIDER, "win32") + keyboard_input_win32() : wininput_module<win32_keyboard_device>(OSD_KEYBOARDINPUT_PROVIDER, "win32") { } virtual void input_init(running_machine &machine) override { - // Add a single win32 keyboard device that we'll monitor using Win32 - win32_keyboard_device *devinfo = devicelist()->create_device<win32_keyboard_device>(machine, "Win32 Keyboard 1", "Win32 Keyboard 1", *this); - - keyboard_trans_table &table = keyboard_trans_table::instance(); - - // populate it - for (int keynum = 0; keynum < MAX_KEYS; keynum++) - { - input_item_id itemid = table.map_di_scancode_to_itemid(keynum); - TCHAR keyname[100]; - - // generate the name - if (GetKeyNameText(((keynum & 0x7f) << 16) | ((keynum & 0x80) << 17), keyname, ARRAY_LENGTH(keyname)) == 0) - _sntprintf(keyname, ARRAY_LENGTH(keyname), TEXT("Scan%03d"), keynum); - std::string name = osd::text::from_tstring(keyname); + wininput_module<win32_keyboard_device>::input_init(machine); - // add the item to the device - devinfo->device()->add_item(name.c_str(), itemid, generic_button_get_state<std::uint8_t>, &devinfo->keyboard.state[keynum]); - } + // Add a single win32 keyboard device that we'll monitor using Win32 + create_device<win32_keyboard_device>(DEVICE_CLASS_KEYBOARD, "Win32 Keyboard 1", "Win32 Keyboard 1"); } - bool handle_input_event(input_event eventid, void *eventdata) override + virtual bool handle_input_event(input_event eventid, void const *eventdata) override { - if (!input_enabled()) - return false; - - KeyPressEventArgs *args; - switch (eventid) { - case INPUT_EVENT_KEYDOWN: - case INPUT_EVENT_KEYUP: - args = static_cast<KeyPressEventArgs*>(eventdata); - devicelist()->for_each_device([args](auto device) - { - auto keyboard = dynamic_cast<win32_keyboard_device*>(device); - if (keyboard != nullptr) - keyboard->queue_events(args, 1); - }); - - return true; - - default: - return false; + case INPUT_EVENT_KEYDOWN: + case INPUT_EVENT_KEYUP: + devicelist().for_each_device( + [args = reinterpret_cast<KeyPressEventArgs const *>(eventdata)] (auto &device) + { + device.queue_events(args, 1); + }); + return false; // we still want text input events to be generated + + default: + return false; } } }; + //============================================================ // win32_mouse_device //============================================================ -struct win32_mouse_state -{ - POINT last_point; -}; - -class win32_mouse_device : public event_based_device<MouseButtonEventArgs> +class win32_mouse_device : public event_based_device<MouseUpdateEventArgs> { public: - mouse_state mouse; - win32_mouse_state win32_mouse; - - win32_mouse_device(running_machine& machine, const char *name, const char *id, input_module &module) - : event_based_device(machine, name, id, DEVICE_CLASS_MOUSE, module), - mouse({0}), - win32_mouse({{0}}) + win32_mouse_device(std::string &&name, std::string &&id, input_module &module) : + event_based_device(std::move(name), std::move(id), module), + m_mouse({0}), + m_win32_mouse({{0}}), + m_vscroll(0), + m_hscroll(0) { } - void poll() override + virtual void poll(bool relative_reset) override { - event_based_device::poll(); + event_based_device::poll(relative_reset); - CURSORINFO cursor_info = {0}; + if (!relative_reset) + return; + + CURSORINFO cursor_info = { 0 }; cursor_info.cbSize = sizeof(CURSORINFO); GetCursorInfo(&cursor_info); @@ -154,145 +158,228 @@ public: if (!(cursor_info.flags & CURSOR_SHOWING)) { // We measure the position change from the previously set center position - mouse.lX = (cursor_info.ptScreenPos.x - win32_mouse.last_point.x) * INPUT_RELATIVE_PER_PIXEL; - mouse.lY = (cursor_info.ptScreenPos.y - win32_mouse.last_point.y) * INPUT_RELATIVE_PER_PIXEL; + m_mouse.lX = (cursor_info.ptScreenPos.x - m_win32_mouse.last_point.x) * input_device::RELATIVE_PER_PIXEL; + m_mouse.lY = (cursor_info.ptScreenPos.y - m_win32_mouse.last_point.y) * input_device::RELATIVE_PER_PIXEL; RECT window_pos = {0}; - GetWindowRect(std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window(), &window_pos); + GetWindowRect( + dynamic_cast<win_window_info &>(*osd_common_t::window_list().front()).platform_window(), + &window_pos); // We reset the cursor position to the middle of the window each frame - win32_mouse.last_point.x = window_pos.left + (window_pos.right - window_pos.left) / 2; - win32_mouse.last_point.y = window_pos.top + (window_pos.bottom - window_pos.top) / 2; + m_win32_mouse.last_point.x = window_pos.left + (window_pos.right - window_pos.left) / 2; + m_win32_mouse.last_point.y = window_pos.top + (window_pos.bottom - window_pos.top) / 2; - SetCursorPos(win32_mouse.last_point.x, win32_mouse.last_point.y); + SetCursorPos(m_win32_mouse.last_point.x, m_win32_mouse.last_point.y); } + + // update scroll axes + m_mouse.lV = std::exchange(m_vscroll, 0) * input_device::RELATIVE_PER_PIXEL; + m_mouse.lH = std::exchange(m_hscroll, 0) * input_device::RELATIVE_PER_PIXEL; } - void reset() override + virtual void configure(input_device &device) override { - memset(&mouse, 0, sizeof(mouse)); - memset(&win32_mouse, 0, sizeof(win32_mouse)); + // populate the axes + device.add_item( + "X", + std::string_view(), + ITEM_ID_XAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lX); + device.add_item( + "Y", + std::string_view(), + ITEM_ID_YAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lY); + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ZAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_RZAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lH); + + // populate the buttons + for (int butnum = 0; butnum < 5; butnum++) + { + device.add_item( + default_button_name(butnum), + std::string_view(), + input_item_id(ITEM_ID_BUTTON1 + butnum), + generic_button_get_state<BYTE>, + &m_mouse.rgbButtons[butnum]); + } + } + + virtual void reset() override + { + event_based_device::reset(); + memset(&m_mouse, 0, sizeof(m_mouse)); + memset(&m_win32_mouse, 0, sizeof(m_win32_mouse)); + m_vscroll = m_hscroll = 0; } protected: - void process_event(MouseButtonEventArgs &args) override + virtual void process_event(MouseUpdateEventArgs const &args) override { // set the button state - mouse.rgbButtons[args.button] = args.keydown ? 0x80 : 0x00; + assert(!(args.pressed & args.released)); + for (unsigned i = 0; 5 > i; ++i) + { + if (BIT(args.pressed, i)) + m_mouse.rgbButtons[i] = 0x80; + else if (BIT(args.released, i)) + m_mouse.rgbButtons[i] = 0x00; + } - // Make sure we have a fresh mouse position on button down - if (args.keydown) - module().poll_if_necessary(machine()); + // accumulate scroll delta + m_vscroll += args.vdelta; + m_hscroll += args.hdelta; } + +private: + struct win32_mouse_state + { + POINT last_point; + }; + + mouse_state m_mouse; + win32_mouse_state m_win32_mouse; + long m_vscroll, m_hscroll; }; + //============================================================ // mouse_input_win32 - win32 mouse input module //============================================================ -class mouse_input_win32 : public wininput_module +class mouse_input_win32 : public wininput_module<win32_mouse_device> { public: - mouse_input_win32() - : wininput_module(OSD_MOUSEINPUT_PROVIDER, "win32") + mouse_input_win32() : wininput_module<win32_mouse_device>(OSD_MOUSEINPUT_PROVIDER, "win32") { } virtual void input_init(running_machine &machine) override { - win32_mouse_device *devinfo; - int axisnum, butnum; + wininput_module<win32_mouse_device>::input_init(machine); - if (!input_enabled() || !mouse_enabled()) + if (!options()->mouse()) return; // allocate a device - devinfo = devicelist()->create_device<win32_mouse_device>(machine, "Win32 Mouse 1", "Win32 Mouse 1", *this); - if (devinfo == nullptr) - return; + create_device<win32_mouse_device>(DEVICE_CLASS_MOUSE, "Win32 Mouse 1", "Win32 Mouse 1"); + } - // populate the axes - for (axisnum = 0; axisnum < 2; axisnum++) + virtual bool handle_input_event(input_event eventid, void const *eventdata) override + { + if (manager().class_enabled(DEVICE_CLASS_MOUSE)) { - devinfo->device()->add_item( - default_axis_name[axisnum], - static_cast<input_item_id>(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &devinfo->mouse.lX + axisnum); + if ((eventid == INPUT_EVENT_MOUSE_BUTTON) || (eventid == INPUT_EVENT_MOUSE_WHEEL)) + { + auto const *const args = reinterpret_cast<MouseUpdateEventArgs const *>(eventdata); + devicelist().for_each_device( + [args] (auto &device) { device.queue_events(args, 1); }); + return true; + } } - // populate the buttons - for (butnum = 0; butnum < 2; butnum++) - { - devinfo->device()->add_item( - default_button_name(butnum), - static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum), - generic_button_get_state<BYTE>, - &devinfo->mouse.rgbButtons[butnum]); - } + return false; } +}; + + +//============================================================ +// win32_lightgun_device_base +//============================================================ - bool handle_input_event(input_event eventid, void *eventdata) override +class win32_lightgun_device_base : public event_based_device<MouseUpdateEventArgs> +{ +public: + virtual void reset() override { - if (!input_enabled() || !mouse_enabled() || eventid != INPUT_EVENT_MOUSE_BUTTON) - return false; + event_based_device::reset(); + memset(&m_mouse, 0, sizeof(m_mouse)); + } - auto args = static_cast<MouseButtonEventArgs*>(eventdata); - devicelist()->for_each_device([args](auto device) +protected: + win32_lightgun_device_base( + std::string &&name, + std::string &&id, + input_module &module) : + event_based_device(std::move(name), std::move(id), module), + m_mouse({ 0 }) + { + } + + void do_configure(input_device &device, unsigned buttons) + { + // populate the axes + for (int axisnum = 0; axisnum < 2; axisnum++) { - auto mouse = dynamic_cast<win32_mouse_device*>(device); - if (mouse != nullptr) - mouse->queue_events(args, 1); - }); + device.add_item( + default_axis_name[axisnum], + std::string_view(), + input_item_id(ITEM_ID_XAXIS + axisnum), + generic_axis_get_state<LONG>, + &m_mouse.lX + axisnum); + } - return true; + // populate the buttons + for (int butnum = 0; butnum < buttons; butnum++) + { + device.add_item( + default_button_name(butnum), + std::string_view(), + input_item_id(ITEM_ID_BUTTON1 + butnum), + generic_button_get_state<BYTE>, + &m_mouse.rgbButtons[butnum]); + } } + + mouse_state m_mouse; }; + + //============================================================ // win32_lightgun_device //============================================================ -class win32_lightgun_device : public event_based_device<MouseButtonEventArgs> +class win32_lightgun_device : public win32_lightgun_device_base { -private: - BOOL m_lightgun_shared_axis_mode; - int m_gun_index; - public: - mouse_state mouse; - - win32_lightgun_device(running_machine& machine, const char *name, const char *id, input_module &module) - : event_based_device(machine, name, id, DEVICE_CLASS_LIGHTGUN, module), - m_lightgun_shared_axis_mode(FALSE), - m_gun_index(0), - mouse({0}) + win32_lightgun_device( + std::string &&name, + std::string &&id, + input_module &module) : + win32_lightgun_device_base(std::move(name), std::move(id), module), + m_vscroll(0), + m_hscroll(0) { - m_lightgun_shared_axis_mode = downcast<windows_options &>(machine.options()).dual_lightgun(); - - // Since we are about to be added to the list, the current size is the zero-based index of where we will be - m_gun_index = downcast<wininput_module&>(module).devicelist()->size(); } - void poll() override + virtual void poll(bool relative_reset) override { - event_based_device::poll(); + event_based_device::poll(relative_reset); int32_t xpos = 0, ypos = 0; - POINT mousepos; - - // if we are using the shared axis hack, the data is updated via Windows messages only - if (m_lightgun_shared_axis_mode) - return; // get the cursor position and transform into final results + POINT mousepos; GetCursorPos(&mousepos); - if (!osd_common_t::s_window_list.empty()) + if (!osd_common_t::window_list().empty()) { - RECT client_rect; - // get the position relative to the window - HWND hwnd = std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window(); + HWND const hwnd = dynamic_cast<win_window_info &>(*osd_common_t::window_list().front()).platform_window(); + RECT client_rect; GetClientRect(hwnd, &client_rect); ScreenToClient(hwnd, &mousepos); @@ -302,149 +389,194 @@ public: } // update the X/Y positions - mouse.lX = xpos; - mouse.lY = ypos; + m_mouse.lX = xpos; + m_mouse.lY = ypos; + + // update the scroll axes if appropriate + if (relative_reset) + { + m_mouse.lV = std::exchange(m_vscroll, 0) * input_device::RELATIVE_PER_PIXEL; + m_mouse.lH = std::exchange(m_hscroll, 0) * input_device::RELATIVE_PER_PIXEL; + } + } + + virtual void configure(input_device &device) override + { + do_configure(device, 5); + + // add scroll axes + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ADD_RELATIVE1, + generic_axis_get_state<LONG>, + &m_mouse.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_ADD_RELATIVE2, + generic_axis_get_state<LONG>, + &m_mouse.lH); } - void reset() override + virtual void reset() override { - memset(&mouse, 0, sizeof(mouse)); + win32_lightgun_device_base::reset(); + m_vscroll = m_hscroll = 0; } protected: - void process_event(MouseButtonEventArgs &args) override + virtual void process_event(MouseUpdateEventArgs const &args) override { - // Are we in shared axis mode? - if (m_lightgun_shared_axis_mode) - { - handle_shared_axis_mode(args); - } - else + // In non-shared axis mode, just update the button state + assert(!(args.pressed & args.released)); + for (unsigned i = 0; 5 > i; ++i) { - // In non-shared axis mode, just update the button state - mouse.rgbButtons[args.button] = args.keydown ? 0x80 : 0x00; + if (BIT(args.pressed, i)) + m_mouse.rgbButtons[i] = 0x80; + else if (BIT(args.released, i)) + m_mouse.rgbButtons[i] = 0x00; } + + // accumulate scroll delta + m_vscroll += args.vdelta; + m_hscroll += args.hdelta; } private: - void handle_shared_axis_mode(MouseButtonEventArgs &args) - { - int button = args.button; + long m_vscroll, m_hscroll; +}; - // We only handle the first four buttons in shared axis mode - if (button > 3) - return; - // First gun doesn't handle buttons 2 & 3 - if (button >= 2 && m_gun_index == 0) - return; +//============================================================ +// win32_dual_lightgun_device +//============================================================ - // Second gun doesn't handle buttons 0 & 1 - if (button < 2 && m_gun_index == 1) - return; +class win32_dual_lightgun_device : public win32_lightgun_device_base +{ +public: + win32_dual_lightgun_device( + std::string &&name, + std::string &&id, + input_module &module, + int index) : + win32_lightgun_device_base(std::move(name), std::move(id), module), + m_gun_index(index) + { + } - // Adjust the button if we're the second lightgun - int logical_button = m_gun_index == 1 ? button - 2 : button; + virtual void configure(input_device &device) override + { + do_configure(device, 2); + } - // set the button state - mouse.rgbButtons[logical_button] = args.keydown ? 0x80 : 0x00; - if (args.keydown) +protected: + virtual void process_event(MouseUpdateEventArgs const &args) override + { + // We only handle the first four buttons in shared axis mode + assert(!(args.pressed & args.released)); + for (unsigned i = 0; 2 > i; ++i) { - RECT client_rect; - POINT mousepos; + // Adjust the button if we're the second lightgun + unsigned const bit = i + ((1 == m_gun_index) ? 2 : 0); + if (BIT(args.pressed, bit)) + { + m_mouse.rgbButtons[i] = 0x80; - // get the position relative to the window - HWND hwnd = std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window(); - GetClientRect(hwnd, &client_rect); - mousepos.x = args.xpos; - mousepos.y = args.ypos; - ScreenToClient(hwnd, &mousepos); + // get the position relative to the window + HWND const hwnd = dynamic_cast<win_window_info &>(*osd_common_t::window_list().front()).platform_window(); + RECT client_rect; + GetClientRect(hwnd, &client_rect); - // convert to absolute coordinates - mouse.lX = normalize_absolute_axis(mousepos.x, client_rect.left, client_rect.right); - mouse.lY = normalize_absolute_axis(mousepos.y, client_rect.top, client_rect.bottom); + POINT mousepos; + mousepos.x = args.xpos; + mousepos.y = args.ypos; + ScreenToClient(hwnd, &mousepos); + + // convert to absolute coordinates + m_mouse.lX = normalize_absolute_axis(mousepos.x, client_rect.left, client_rect.right); + m_mouse.lY = normalize_absolute_axis(mousepos.y, client_rect.top, client_rect.bottom); + } + else if (BIT(args.released, bit)) + { + m_mouse.rgbButtons[i] = 0x00; + } } } + +private: + int const m_gun_index; }; + //============================================================ // lightgun_input_win32 - win32 lightgun input module //============================================================ -class lightgun_input_win32 : public wininput_module +class lightgun_input_win32 : public wininput_module<win32_lightgun_device_base> { public: - lightgun_input_win32() - : wininput_module(OSD_LIGHTGUNINPUT_PROVIDER, "win32") - { - } - - int init_internal() override + lightgun_input_win32() : wininput_module<win32_lightgun_device_base>(OSD_LIGHTGUNINPUT_PROVIDER, "win32") { - return 0; } virtual void input_init(running_machine &machine) override { - int max_guns = downcast<windows_options&>(machine.options()).dual_lightgun() ? 2 : 1; + wininput_module<win32_lightgun_device_base>::input_init(machine); + + bool const shared_axis_mode = dynamic_cast<windows_options const &>(*options()).dual_lightgun(); + int const max_guns = shared_axis_mode ? 2 : 1; // allocate the lightgun devices for (int gunnum = 0; gunnum < max_guns; gunnum++) { static const char *const gun_names[] = { "Win32 Gun 1", "Win32 Gun 2" }; - win32_lightgun_device *devinfo; - int axisnum, butnum; // allocate a device - devinfo = devicelist()->create_device<win32_lightgun_device>(machine, gun_names[gunnum], gun_names[gunnum], *this); - if (devinfo == nullptr) - break; - - // populate the axes - for (axisnum = 0; axisnum < 2; axisnum++) - { - devinfo->device()->add_item( - default_axis_name[axisnum], - static_cast<input_item_id>(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &devinfo->mouse.lX + axisnum); - } - - // populate the buttons - for (butnum = 0; butnum < 2; butnum++) - { - devinfo->device()->add_item( - default_button_name(butnum), - static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum), - generic_button_get_state<BYTE>, - &devinfo->mouse.rgbButtons[butnum]); - } + if (shared_axis_mode) + create_device<win32_dual_lightgun_device>(DEVICE_CLASS_LIGHTGUN, gun_names[gunnum], gun_names[gunnum], gunnum); + else + create_device<win32_lightgun_device>(DEVICE_CLASS_LIGHTGUN, gun_names[gunnum], gun_names[gunnum]); } } - bool handle_input_event(input_event eventid, void* eventdata) override + virtual bool handle_input_event(input_event eventid, void const *eventdata) override { - if (!input_enabled() || !lightgun_enabled() || eventid != INPUT_EVENT_MOUSE_BUTTON) - return false; - - auto args = static_cast<MouseButtonEventArgs*>(eventdata); - devicelist()->for_each_device([args](auto device) + if (manager().class_enabled(DEVICE_CLASS_LIGHTGUN)) { - auto lightgun = dynamic_cast<win32_lightgun_device*>(device); - if (lightgun != nullptr) - lightgun->queue_events(args, 1); - }); + if ((eventid == INPUT_EVENT_MOUSE_BUTTON) || (eventid == INPUT_EVENT_MOUSE_WHEEL)) + { + auto const *const args = reinterpret_cast<MouseUpdateEventArgs const *>(eventdata); + devicelist().for_each_device( + [args] (auto &device) { device.queue_events(args, 1); }); + return true; + } + } - return true; + return false; } }; -#else +} // anonymous namespace + +} // namespace osd + +#else // defined(OSD_WINDOWS) + +namespace osd { + +namespace { + MODULE_NOT_SUPPORTED(keyboard_input_win32, OSD_KEYBOARDINPUT_PROVIDER, "win32") MODULE_NOT_SUPPORTED(mouse_input_win32, OSD_MOUSEINPUT_PROVIDER, "win32") MODULE_NOT_SUPPORTED(lightgun_input_win32, OSD_LIGHTGUNINPUT_PROVIDER, "win32") -#endif -MODULE_DEFINITION(KEYBOARDINPUT_WIN32, keyboard_input_win32) -MODULE_DEFINITION(MOUSEINPUT_WIN32, mouse_input_win32) -MODULE_DEFINITION(LIGHTGUNINPUT_WIN32, lightgun_input_win32) +} // anonymous namespace + +} // namespace osd + +#endif // defined(OSD_WINDOWS) + +MODULE_DEFINITION(KEYBOARDINPUT_WIN32, osd::keyboard_input_win32) +MODULE_DEFINITION(MOUSEINPUT_WIN32, osd::mouse_input_win32) +MODULE_DEFINITION(LIGHTGUNINPUT_WIN32, osd::lightgun_input_win32) diff --git a/src/osd/modules/input/input_wincommon.h b/src/osd/modules/input/input_wincommon.h new file mode 100644 index 00000000000..6b34d60abca --- /dev/null +++ b/src/osd/modules/input/input_wincommon.h @@ -0,0 +1,40 @@ +// license:BSD-3-Clause +// copyright-holders:Aaron Giles +//============================================================ +// +// input_wincommon.h - Common code used by Windows input modules +// +//============================================================ +#ifndef MAME_OSD_INPUT_INPUT_WINCOMMON_H +#define MAME_OSD_INPUT_INPUT_WINCOMMON_H + +#pragma once + +#include "input_common.h" + +#include <windows.h> + + +namespace osd { + +// state information for a keyboard +struct keyboard_state +{ + uint8_t state[MAX_KEYS]; + int8_t oldkey[MAX_KEYS]; + int8_t currkey[MAX_KEYS]; +}; + +// state information for a mouse +struct mouse_state +{ + LONG lX; + LONG lY; + LONG lV; + LONG lH; + BYTE rgbButtons[8]; +}; + +} // namespace osd + +#endif // MAME_OSD_INPUT_INPUT_WINCOMMON_H diff --git a/src/osd/modules/input/input_windows.cpp b/src/osd/modules/input/input_windows.cpp index 164ee0e3c31..12002ccf893 100644 --- a/src/osd/modules/input/input_windows.cpp +++ b/src/osd/modules/input/input_windows.cpp @@ -8,123 +8,109 @@ #include "input_module.h" -#if defined(OSD_WINDOWS) || defined(OSD_UWP) +#if defined(OSD_WINDOWS) // MAME headers #include "emu.h" -#include "osdepend.h" -// MAMEOS headers +#include "input_windows.h" + +#include "window.h" #include "winmain.h" -#include "input_common.h" -#include "input_windows.h" +#include "util/language.h" -bool windows_osd_interface::should_hide_mouse() const -{ - bool hidemouse = false; - wininput_module* mod; +#include "osdepend.h" - mod = dynamic_cast<wininput_module*>(m_keyboard_input); - if (mod) hidemouse |= mod->should_hide_mouse(); - mod = dynamic_cast<wininput_module*>(m_mouse_input); - if (mod) hidemouse |= mod->should_hide_mouse(); +bool windows_osd_interface::should_hide_mouse() const +{ + if (!winwindow_has_focus()) + return false; - mod = dynamic_cast<wininput_module*>(m_lightgun_input); - if (mod) hidemouse |= mod->should_hide_mouse(); + if (machine().paused()) + return false; - mod = dynamic_cast<wininput_module*>(m_joystick_input); - if (mod) hidemouse |= mod->should_hide_mouse(); + // track if mouse/lightgun is enabled, for mouse hiding purposes + bool const mouse_enabled = machine().input().class_enabled(DEVICE_CLASS_MOUSE); + bool const lightgun_enabled = machine().input().class_enabled(DEVICE_CLASS_LIGHTGUN); + if (!mouse_enabled && !lightgun_enabled) + return false; - return hidemouse; + return true; } -bool windows_osd_interface::handle_input_event(input_event eventid, void* eventdata) const +bool windows_osd_interface::handle_input_event(input_event eventid, void const *eventdata) const { bool handled = false; - wininput_module* mod; + wininput_event_handler *mod; - mod = dynamic_cast<wininput_module*>(m_keyboard_input); - if (mod) handled |= mod->handle_input_event(eventid, eventdata); + mod = dynamic_cast<wininput_event_handler *>(m_keyboard_input); + if (mod) + handled |= mod->handle_input_event(eventid, eventdata); - mod = dynamic_cast<wininput_module*>(m_mouse_input); - if (mod) handled |= mod->handle_input_event(eventid, eventdata); + mod = dynamic_cast<wininput_event_handler *>(m_mouse_input); + if (mod) + handled |= mod->handle_input_event(eventid, eventdata); - mod = dynamic_cast<wininput_module*>(m_lightgun_input); - if (mod) handled |= mod->handle_input_event(eventid, eventdata); + mod = dynamic_cast<wininput_event_handler *>(m_lightgun_input); + if (mod) + handled |= mod->handle_input_event(eventid, eventdata); - mod = dynamic_cast<wininput_module*>(m_joystick_input); - if (mod) handled |= mod->handle_input_event(eventid, eventdata); + mod = dynamic_cast<wininput_event_handler *>(m_joystick_input); + if (mod) + handled |= mod->handle_input_event(eventid, eventdata); return handled; } -void windows_osd_interface::poll_input(running_machine &machine) const -{ - m_keyboard_input->poll_if_necessary(machine); - m_mouse_input->poll_if_necessary(machine); - m_lightgun_input->poll_if_necessary(machine); - m_joystick_input->poll_if_necessary(machine); -} - //============================================================ // customize_input_type_list //============================================================ -void windows_osd_interface::customize_input_type_list(simple_list<input_type_entry> &typelist) +void windows_osd_interface::customize_input_type_list(std::vector<input_type_entry> &typelist) { - const char* uimode; - // loop over the defaults for (input_type_entry &entry : typelist) switch (entry.type()) { -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) // 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; -#else - // UWP: Hotkey Select + X => UI_CONFIGURE (Menu) - case IPT_UI_CONFIGURE: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_TAB, input_seq::or_code, JOYCODE_SELECT, JOYCODE_BUTTON3); - break; - // UWP: Hotkey Select + Start => CANCEL - case IPT_UI_CANCEL: - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_ESC, input_seq::or_code, JOYCODE_SELECT, JOYCODE_START); - break; -#endif // configurable UI mode switch case IPT_UI_TOGGLE_UI: - uimode = options().ui_mode_key(); - if (strcmp(uimode, "auto")) { - std::string fullmode = "ITEM_ID_"; - fullmode += uimode; - input_item_id const mameid_code = keyboard_trans_table::instance().lookup_mame_code(fullmode.c_str()); - if (ITEM_ID_INVALID != mameid_code) + char const *const uimode = options().ui_mode_key(); + if (uimode && *uimode && strcmp(uimode, "auto")) { - input_code const ui_code = input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, input_item_id(mameid_code)); - entry.defseq(SEQ_TYPE_STANDARD).set(ui_code); + std::string fullmode("ITEM_ID_"); + fullmode.append(uimode); + input_item_id const mameid_code = keyboard_trans_table::instance().lookup_mame_code(fullmode.c_str()); + if (ITEM_ID_INVALID != mameid_code) + { + input_code const ui_code = input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, input_item_id(mameid_code)); + entry.defseq(SEQ_TYPE_STANDARD).set(ui_code); + } } } break; // alt-enter for fullscreen case IPT_OSD_1: - entry.configure_osd("TOGGLE_FULLSCREEN", "Toggle Fullscreen"); + entry.configure_osd("TOGGLE_FULLSCREEN", N_p("input-name", "Toggle Fullscreen")); entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_ENTER, KEYCODE_LALT, input_seq::or_code, KEYCODE_ENTER, KEYCODE_RALT); break; // lalt-F12 for fullscreen snap (HLSL) case IPT_OSD_2: - entry.configure_osd("RENDER_SNAP", "Take Rendered Snapshot"); + entry.configure_osd("RENDER_SNAP", N_p("input-name", "Take Rendered Snapshot")); entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F12, KEYCODE_LALT, input_seq::not_code, KEYCODE_LSHIFT); break; + // add a NOT-lalt to our default F12 case IPT_UI_SNAPSHOT: // emu/input.c: input_seq(KEYCODE_F12, input_seq::not_code, KEYCODE_LSHIFT) entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F12, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_LALT); @@ -132,7 +118,7 @@ void windows_osd_interface::customize_input_type_list(simple_list<input_type_ent // lshift-lalt-F12 for fullscreen video (HLSL, BGFX) case IPT_OSD_3: - entry.configure_osd("RENDER_AVI", "Record Rendered Video"); + entry.configure_osd("RENDER_AVI", N_p("input-name", "Record Rendered Video")); entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F12, KEYCODE_LSHIFT, KEYCODE_LALT); break; @@ -146,19 +132,15 @@ void windows_osd_interface::customize_input_type_list(simple_list<input_type_ent entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F12, KEYCODE_LSHIFT, KEYCODE_LCONTROL, input_seq::not_code, KEYCODE_LALT); break; - // add a NOT-lalt to write timecode file - case IPT_UI_TIMECODE: // emu/input.c: input_seq(KEYCODE_F12, input_seq::not_code, KEYCODE_LSHIFT) - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F12, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_LALT); - break; - - // lctrl-lalt-F5 to toggle post-processing + // lalt-F10 to toggle post-processing case IPT_OSD_4: - entry.configure_osd("POST_PROCESS", "Toggle Post-Processing"); - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F5, KEYCODE_LALT, KEYCODE_LCONTROL); + entry.configure_osd("POST_PROCESS", N_p("input-name", "Toggle Post-Processing")); + entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F10, KEYCODE_LALT); break; - // add a NOT-lctrl-lalt to our default F5 - case IPT_UI_TOGGLE_DEBUG: // emu/input.c: input_seq(KEYCODE_F5) - entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F5, input_seq::not_code, KEYCODE_LCONTROL, input_seq::not_code, KEYCODE_LALT); + + // add a Not LALT condition to the throttle key + case IPT_UI_THROTTLE: + entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F10, input_seq::not_code, KEYCODE_LALT); break; // leave everything else alone @@ -167,4 +149,4 @@ void windows_osd_interface::customize_input_type_list(simple_list<input_type_ent } } -#endif +#endif // defined(OSD_WINDOWS) diff --git a/src/osd/modules/input/input_windows.h b/src/osd/modules/input/input_windows.h index 7afc487fb62..882f80d053b 100644 --- a/src/osd/modules/input/input_windows.h +++ b/src/osd/modules/input/input_windows.h @@ -5,83 +5,44 @@ // input_windows.h - Common code used by Windows input modules // //============================================================ +#ifndef MAME_OSD_INPUT_INPUT_WINDOWS_H +#define MAME_OSD_INPUT_INPUT_WINDOWS_H -#ifndef INPUT_WIN_H_ -#define INPUT_WIN_H_ +#pragma once -// standard windows headers -#include <windows.h> -#undef interface +#include "input_common.h" #include "window.h" #include "winmain.h" +// standard windows headers +#include <windows.h> + + //============================================================ // TYPEDEFS //============================================================ -// state information for a keyboard -struct keyboard_state -{ - uint8_t state[MAX_KEYS]; - int8_t oldkey[MAX_KEYS]; - int8_t currkey[MAX_KEYS]; -}; - -// state information for a mouse (matches DIMOUSESTATE exactly) -struct mouse_state -{ - LONG lX; - LONG lY; - LONG lZ; - BYTE rgbButtons[8]; -}; -class wininput_module : public input_module_base +class wininput_event_handler { protected: - bool m_global_inputs_enabled; + wininput_event_handler() = default; + virtual ~wininput_event_handler() = default; public: - wininput_module(const char * type, const char * name) - : input_module_base(type, name), - m_global_inputs_enabled(false) + virtual bool handle_input_event(input_event eventid, void const *data) { - } - - virtual ~wininput_module() { } - - virtual bool should_hide_mouse() - { - if (winwindow_has_focus() // has focus - && (!video_config.windowed || !osd_common_t::s_window_list.front()->win_has_menu()) // not windowed or doesn't have a menu - && (input_enabled() && !input_paused()) // input enabled and not paused - && (mouse_enabled() || lightgun_enabled())) // either mouse or lightgun enabled in the core - { - return true; - } - return false; } +}; - virtual bool handle_input_event(input_event eventid, void* data) - { - return false; - } +template <typename Info> +class wininput_module : public input_module_impl<Info, osd_common_t>, public wininput_event_handler +{ protected: - - void before_poll(running_machine& machine) override - { - // periodically process events, in case they're not coming through - // this also will make sure the mouse state is up-to-date - winwindow_process_events_periodic(machine); - } - - bool should_poll_devices(running_machine &machine) override - { - return input_enabled() && (m_global_inputs_enabled || winwindow_has_focus()); - } + using input_module_impl<Info, osd_common_t>::input_module_impl; }; -#endif +#endif // MAME_OSD_INPUT_INPUT_WINDOWS_H diff --git a/src/osd/modules/input/input_winhybrid.cpp b/src/osd/modules/input/input_winhybrid.cpp index 1436ae009d6..2f1023e24fb 100644 --- a/src/osd/modules/input/input_winhybrid.cpp +++ b/src/osd/modules/input/input_winhybrid.cpp @@ -6,48 +6,31 @@ // //============================================================ -#include "input_module.h" #include "modules/osdmodule.h" -#if defined(OSD_WINDOWS) +#if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) + +#include "input_dinput.h" +#include "input_xinput.h" -#include <list> #include <vector> -// standard windows headers -#include <windows.h> -#include <wrl/client.h> +#include <oleauto.h> #include <wbemcli.h> -// XInput/DirectInput -#include <xinput.h> -#include <dinput.h> - -#undef interface -// MAME headers -#include "emu.h" - -// MAMEOS headers -#include "strconv.h" -#include "winmain.h" - -#include "input_common.h" -#include "input_windows.h" -#include "input_xinput.h" -#include "input_dinput.h" +namespace osd { -using namespace Microsoft::WRL; +namespace { -template<class TCom> +template <class TCom> class ComArray { private: - std::vector<TCom*> m_entries; + std::vector<TCom *> m_entries; public: - ComArray(size_t capacity) - : m_entries(capacity, nullptr) + ComArray(size_t capacity) : m_entries(capacity, nullptr) { } @@ -56,7 +39,7 @@ public: Release(); } - TCom** ReleaseAndGetAddressOf() + TCom **ReleaseAndGetAddressOf() { Release(); @@ -64,7 +47,7 @@ public: return &m_entries[0]; } - TCom* operator [] (int i) + TCom *operator[](int i) { return m_entries[i]; } @@ -76,12 +59,12 @@ public: void Release() { - for (int i = 0; i < m_entries.size(); i++) + for (auto &entry : m_entries) { - if (m_entries[i] != nullptr) + if (entry) { - m_entries[i]->Release(); - m_entries[i] = nullptr; + entry->Release(); + entry = nullptr; } } } @@ -91,7 +74,7 @@ struct bstr_deleter { void operator () (BSTR bstr) const { - if (bstr != nullptr) + if (bstr) SysFreeString(bstr); } }; @@ -104,8 +87,8 @@ private: public: variant_wrapper() - : m_variant({0}) { + VariantInit(&m_variant); } ~variant_wrapper() @@ -133,28 +116,24 @@ public: typedef std::unique_ptr<OLECHAR, bstr_deleter> bstr_ptr; + //============================================================ // winhybrid_joystick_module //============================================================ -class winhybrid_joystick_module : public wininput_module, public device_enum_interface +class winhybrid_joystick_module : public input_module_impl<device_info, osd_common_t> { private: - std::shared_ptr<xinput_api_helper> m_xinput_helper; + std::unique_ptr<xinput_api_helper> m_xinput_helper; std::unique_ptr<dinput_api_helper> m_dinput_helper; - std::list<DWORD> m_xinput_deviceids; - bool m_xinput_detect_failed; public: - winhybrid_joystick_module() - : wininput_module(OSD_JOYSTICKINPUT_PROVIDER, "winhybrid"), - m_xinput_helper(nullptr), - m_dinput_helper(nullptr), - m_xinput_detect_failed(false) + winhybrid_joystick_module() : + input_module_impl<device_info, osd_common_t>(OSD_JOYSTICKINPUT_PROVIDER, "winhybrid") { } - bool probe() override + virtual bool probe() override { int status = init_helpers(); if (status != 0) @@ -166,10 +145,12 @@ public: return true; } - int init(const osd_options &options) override + virtual int init(osd_interface &osd, const osd_options &options) override { + int status; + // Call the base - int status = wininput_module::init(options); + status = input_module_impl<device_info, osd_common_t>::init(osd, options); if (status != 0) return status; @@ -184,129 +165,116 @@ public: return 0; } - BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance, LPVOID ref) override + virtual void input_init(running_machine &machine) override { - dinput_cooperative_level cooperative_level = dinput_cooperative_level::FOREGROUND; - running_machine &machine = *static_cast<running_machine *>(ref); - dinput_joystick_device *devinfo; - int result = 0; - - // First check if this device is XInput Compatible. If so, don't add it here - // as it'll be picked up by Xinput - if (!m_xinput_detect_failed && is_xinput_device(&instance->guidProduct)) - { - osd_printf_verbose("Skipping DirectInput for XInput compatible joystick %S.\n", instance->tszInstanceName); - goto exit; - } + input_module_impl<device_info, osd_common_t>::input_init(machine); - if (!osd_common_t::s_window_list.empty() && osd_common_t::s_window_list.front()->win_has_menu()) - cooperative_level = dinput_cooperative_level::BACKGROUND; - - // allocate and link in a new device - devinfo = m_dinput_helper->create_device<dinput_joystick_device>(machine, *this, instance, &c_dfDIJoystick, nullptr, cooperative_level); - if (devinfo == nullptr) - goto exit; - - result = devinfo->configure(); + bool xinput_detect_failed = false; + std::vector<DWORD> xinput_deviceids; + HRESULT result = get_xinput_devices(xinput_deviceids); if (result != 0) { - osd_printf_error("Failed to configure DI Joystick device. Error 0x%x\n", static_cast<unsigned int>(result)); + xinput_detect_failed = true; + xinput_deviceids.clear(); + osd_printf_warning("XInput device detection failed. XInput won't be used. Error: 0x%X\n", uint32_t(result)); } - exit: - return DIENUM_CONTINUE; - } - - void exit() override - { - m_xinput_helper.reset(); - m_dinput_helper.reset(); - - wininput_module::exit(); - } - -protected: - virtual void input_init(running_machine &machine) override - { - HRESULT result = get_xinput_devices(m_xinput_deviceids); - if (result != 0) - { - m_xinput_detect_failed = true; - osd_printf_warning("XInput device detection failed. XInput won't be used. Error: 0x%X\n", static_cast<unsigned int>(result)); - } + // Enumerate all the DirectInput joysticks and add them if they aren't XInput compatible + result = m_dinput_helper->enum_attached_devices( + DI8DEVCLASS_GAMECTRL, + [this, &xinput_deviceids] (LPCDIDEVICEINSTANCE instance) + { + // First check if this device is XInput compatible. + // If so, don't add it here as it'll be picked up by Xinput. + auto const found = std::find( + xinput_deviceids.begin(), + xinput_deviceids.end(), + instance->guidProduct.Data1); + if (xinput_deviceids.end() != found) + { + osd_printf_verbose("Skipping DirectInput for XInput compatible joystick %S.\n", instance->tszInstanceName); + return DIENUM_CONTINUE; + } - // Enumerate all the directinput joysticks and add them if they aren't xinput compatible - result = m_dinput_helper->enum_attached_devices(DI8DEVCLASS_GAMECTRL, this, &machine); + // allocate and link in a new device + auto devinfo = m_dinput_helper->create_device<dinput_joystick_device>( + *this, + instance, + &c_dfDIJoystick, + nullptr, + background_input() ? dinput_cooperative_level::BACKGROUND : dinput_cooperative_level::FOREGROUND, + [] (auto const &device, auto const &format) -> bool + { + // set absolute mode + HRESULT const result = dinput_api_helper::set_dword_property( + device, + DIPROP_AXISMODE, + 0, + DIPH_DEVICE, + DIPROPAXISMODE_ABS); + if ((result != DI_OK) && (result != DI_PROPNOEFFECT)) + { + osd_printf_error("DirectInput: Unable to set absolute mode for joystick.\n"); + return false; + } + return true; + }); + if (devinfo) + add_device(DEVICE_CLASS_JOYSTICK, std::move(devinfo)); + + return DIENUM_CONTINUE; + }); if (result != DI_OK) - fatalerror("DirectInput: Unable to enumerate keyboards (result=%08X)\n", static_cast<uint32_t>(result)); - - xinput_joystick_device *devinfo; + fatalerror("DirectInput: Unable to enumerate game controllers (result=%08X).\n", uint32_t(result)); // now add all xinput devices - if (!m_xinput_detect_failed) + if (!xinput_detect_failed) { // Loop through each gamepad to determine if they are connected for (UINT i = 0; i < XUSER_MAX_COUNT; i++) { - XINPUT_STATE state = { 0 }; - - if (m_xinput_helper->xinput_get_state(i, &state) == ERROR_SUCCESS) - { - // allocate and link in a new device - devinfo = m_xinput_helper->create_xinput_device(machine, i, *this); - if (devinfo == nullptr) - continue; - - // Configure each gamepad to add buttons and Axes, etc. - devinfo->configure(); - } + // allocate and link in a new device + auto devinfo = m_xinput_helper->create_xinput_device(i, *this); + if (devinfo) + add_device(DEVICE_CLASS_JOYSTICK, std::move(devinfo)); } } } + virtual void exit() override + { + input_module_impl<device_info, osd_common_t>::exit(); + + m_xinput_helper.reset(); + m_dinput_helper.reset(); + } + private: int init_helpers() { - int status = 0; - - if (m_xinput_helper == nullptr) + if (!m_xinput_helper) { - m_xinput_helper = std::make_shared<xinput_api_helper>(); - status = m_xinput_helper->initialize(); + m_xinput_helper = std::make_unique<xinput_api_helper>(); + int const status = m_xinput_helper->initialize(); if (status != 0) { - osd_printf_verbose("xinput_api_helper failed to initialize! Error: %u\n", static_cast<unsigned int>(status)); + osd_printf_verbose("Failed to initialize XInput API! Error: %u\n", static_cast<unsigned int>(status)); return -1; } } - if (m_dinput_helper == nullptr) + if (!m_dinput_helper) { - m_dinput_helper = std::make_unique<dinput_api_helper>(DIRECTINPUT_VERSION); - status = m_dinput_helper->initialize(); + m_dinput_helper = std::make_unique<dinput_api_helper>(); + int const status = m_dinput_helper->initialize(); if (status != DI_OK) { - osd_printf_verbose("dinput_api_helper failed to initialize! Error: %u\n", static_cast<unsigned int>(status)); + osd_printf_verbose("Failed to initialize DirectInput API! Error: %u\n", static_cast<unsigned int>(status)); return -1; } } - return status; - } - - //----------------------------------------------------------------------------- - // Returns true if the DirectInput device is also an XInput device. - //----------------------------------------------------------------------------- - bool is_xinput_device(const GUID* pGuidProductFromDirectInput) - { - // Check each xinput device to see if this device's vid/pid matches - for (auto devid = m_xinput_deviceids.begin(); devid != m_xinput_deviceids.end(); ++devid) - { - if (*devid == pGuidProductFromDirectInput->Data1) - return true; - } - - return false; + return 0; } //----------------------------------------------------------------------------- @@ -316,54 +284,43 @@ private: // Checking against a VID/PID of 0x028E/0x045E won't find 3rd party or future // XInput devices. //----------------------------------------------------------------------------- - HRESULT get_xinput_devices(std::list<DWORD> &xinput_id_list) const + HRESULT get_xinput_devices(std::vector<DWORD> &xinput_deviceids) { - ComPtr<IWbemServices> pIWbemServices; - ComPtr<IEnumWbemClassObject> pEnumDevices; - ComPtr<IWbemLocator> pIWbemLocator; - ComArray<IWbemClassObject> pDevices(20); - bstr_ptr bstrDeviceID; - bstr_ptr bstrClassName; - bstr_ptr bstrNamespace; - DWORD uReturned = 0; - UINT iDevice; - variant_wrapper var; - HRESULT hr; + xinput_deviceids.clear(); - // CoInit if needed - CoInitialize(nullptr); + HRESULT hr; // Create WMI + Microsoft::WRL::ComPtr<IWbemLocator> pIWbemLocator; hr = CoCreateInstance( - __uuidof(WbemLocator), - nullptr, - CLSCTX_INPROC_SERVER, - __uuidof(IWbemLocator), - reinterpret_cast<void**>(pIWbemLocator.GetAddressOf())); - - if (FAILED(hr) || pIWbemLocator == nullptr) + __uuidof(WbemLocator), + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(IWbemLocator), + reinterpret_cast<void **>(pIWbemLocator.GetAddressOf())); + if (FAILED(hr) || !pIWbemLocator) { osd_printf_error("Creating WbemLocator failed. Error: 0x%X\n", static_cast<unsigned int>(hr)); return hr; } // Create BSTRs for WMI - bstrNamespace = bstr_ptr(SysAllocString(L"\\\\.\\root\\cimv2")); - bstrDeviceID = bstr_ptr(SysAllocString(L"DeviceID")); - bstrClassName = bstr_ptr(SysAllocString(L"Win32_PNPEntity")); + bstr_ptr bstrNamespace = bstr_ptr(SysAllocString(L"\\\\.\\root\\cimv2")); + bstr_ptr bstrDeviceID = bstr_ptr(SysAllocString(L"DeviceID")); + bstr_ptr bstrClassName = bstr_ptr(SysAllocString(L"Win32_PNPEntity")); // Connect to WMI + Microsoft::WRL::ComPtr<IWbemServices> pIWbemServices; hr = pIWbemLocator->ConnectServer( - bstrNamespace.get(), - nullptr, - nullptr, - nullptr, - 0L, - nullptr, - nullptr, - pIWbemServices.GetAddressOf()); - - if (FAILED(hr) || pIWbemServices == nullptr) + bstrNamespace.get(), + nullptr, + nullptr, + nullptr, + 0L, + nullptr, + nullptr, + pIWbemServices.GetAddressOf()); + if (FAILED(hr) || !pIWbemServices) { osd_printf_error("Connecting to WMI Server failed. Error: 0x%X\n", static_cast<unsigned int>(hr)); return hr; @@ -371,38 +328,41 @@ private: // Switch security level to IMPERSONATE (void)CoSetProxyBlanket( - pIWbemServices.Get(), - RPC_C_AUTHN_WINNT, - RPC_C_AUTHZ_NONE, - nullptr, - RPC_C_AUTHN_LEVEL_CALL, - RPC_C_IMP_LEVEL_IMPERSONATE, - nullptr, - 0); + pIWbemServices.Get(), + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + nullptr, + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + nullptr, + 0); // Get list of Win32_PNPEntity devices + Microsoft::WRL::ComPtr<IEnumWbemClassObject> pEnumDevices; hr = pIWbemServices->CreateInstanceEnum(bstrClassName.get(), 0, nullptr, pEnumDevices.GetAddressOf()); - if (FAILED(hr) || pEnumDevices == nullptr) + if (FAILED(hr) || !pEnumDevices) { osd_printf_error("Getting list of Win32_PNPEntity devices failed. Error: 0x%X\n", static_cast<unsigned int>(hr)); return hr; } // Loop over all devices - for (; ; ) + ComArray<IWbemClassObject> pDevices(20); + variant_wrapper var; + for ( ; ; ) { // Get a few at a time + DWORD uReturned = 0; hr = pEnumDevices->Next(10000, pDevices.Size(), pDevices.ReleaseAndGetAddressOf(), &uReturned); if (FAILED(hr)) { osd_printf_error("Enumerating WMI classes failed. Error: 0x%X\n", static_cast<unsigned int>(hr)); return hr; } - if (uReturned == 0) break; - for (iDevice = 0; iDevice < uReturned; iDevice++) + for (UINT iDevice = 0; iDevice < uReturned; iDevice++) { if (!pDevices[iDevice]) continue; @@ -416,19 +376,18 @@ private: if (wcsstr(var.Get().bstrVal, L"IG_")) { // If it does, then get the VID/PID from var.bstrVal - DWORD dwPid = 0, dwVid = 0; - WCHAR* strVid = wcsstr(var.Get().bstrVal, L"VID_"); + DWORD dwVid = 0; + WCHAR const *const strVid = wcsstr(var.Get().bstrVal, L"VID_"); if (strVid && swscanf(strVid, L"VID_%4X", &dwVid) != 1) dwVid = 0; - WCHAR* strPid = wcsstr(var.Get().bstrVal, L"PID_"); + DWORD dwPid = 0; + WCHAR const *const strPid = wcsstr(var.Get().bstrVal, L"PID_"); if (strPid && swscanf(strPid, L"PID_%4X", &dwPid) != 1) dwPid = 0; - DWORD dwVidPid = MAKELONG(dwVid, dwPid); - - // Add the VID/PID to a linked list - xinput_id_list.push_back(dwVidPid); + // Add the VID/PID to a list + xinput_deviceids.push_back(MAKELONG(dwVid, dwPid)); } } } @@ -441,8 +400,16 @@ private: } }; -#else -MODULE_NOT_SUPPORTED(winhybrid_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "winhybrid") -#endif +} // anonymous namespace + +} // namespace osd + +#else // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) + +#include "input_module.h" + +namespace osd { namespace { MODULE_NOT_SUPPORTED(winhybrid_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "winhybrid") } } + +#endif // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) -MODULE_DEFINITION(JOYSTICKINPUT_WINHYBRID, winhybrid_joystick_module) +MODULE_DEFINITION(JOYSTICKINPUT_WINHYBRID, osd::winhybrid_joystick_module) diff --git a/src/osd/modules/input/input_x11.cpp b/src/osd/modules/input/input_x11.cpp index 40df3d528b6..5230dfd7164 100644 --- a/src/osd/modules/input/input_x11.cpp +++ b/src/osd/modules/input/input_x11.cpp @@ -9,34 +9,41 @@ //============================================================ #include "input_module.h" + #include "modules/osdmodule.h" #if defined(SDLMAME_SDL2) && !defined(SDLMAME_WIN32) && defined(USE_XINPUT) && USE_XINPUT +#include "input_common.h" + +#include "sdl/osdsdl.h" + +// MAME headers +#include "inpttype.h" + +// standard SDL header +#include <SDL2/SDL.h> + // for X11 xinput #include <X11/Xlib.h> #include <X11/extensions/XInput.h> #include <X11/Xutil.h> -// standard sdl header -#include <SDL2/SDL.h> -#include <ctype.h> -#include <stddef.h> -#include <mutex> +#include <algorithm> +#include <cctype> +#include <cstddef> +#include <cstdio> +#include <cstring> #include <memory> +#include <mutex> #include <queue> #include <string> -#include <algorithm> +#include <unordered_map> -// MAME headers -#include "emu.h" -#include "osdepend.h" -// MAMEOS headers -#include "../lib/osdobj_common.h" -#include "input_common.h" -#include "../../sdl/osdsdl.h" -#include "input_sdlcommon.h" +namespace osd { + +namespace { #define MAX_DEVMAP_ENTRIES 16 @@ -49,20 +56,6 @@ static int key_release_type = INVALID_EVENT_TYPE; static int proximity_in_type = INVALID_EVENT_TYPE; static int proximity_out_type = INVALID_EVENT_TYPE; -// state information for a lightgun -struct lightgun_state -{ - int32_t lX, lY; - int32_t buttons[MAX_BUTTONS]; -}; - -struct x11_api_state -{ - XID deviceid; // X11 device id - int32_t maxx, maxy; - int32_t minx, miny; -}; - //============================================================ // DEBUG MACROS //============================================================ @@ -71,56 +64,64 @@ struct x11_api_state #define XI_DBG(format, ...) osd_printf_verbose(format, __VA_ARGS__) #define print_motion_event(motion) print_motion_event_impl(motion) -static inline void print_motion_event_impl(XDeviceMotionEvent *motion) +inline void print_motion_event_impl(XDeviceMotionEvent const *motion) { /* * print a lot of debug informations of the motion event(s). */ osd_printf_verbose( - "XDeviceMotionEvent:\n" - " type: %d\n" - " serial: %lu\n" - " send_event: %d\n" - " display: %p\n" - " window: --\n" - " deviceid: %lu\n" - " root: --\n" - " subwindow: --\n" - " time: --\n" - " x: %d, y: %d\n" - " x_root: %d, y_root: %d\n" - " state: %u\n" - " is_hint: %2.2X\n" - " same_screen: %d\n" - " device_state: %u\n" - " axes_count: %2.2X\n" - " first_axis: %2.2X\n" - " axis_data[6]: {%d,%d,%d,%d,%d,%d}\n", - motion->type, - motion->serial, - motion->send_event, - motion->display, - /* motion->window, */ - motion->deviceid, - /* motion->root */ - /* motion->subwindow */ - /* motion->time, */ - motion->x, motion->y, - motion->x_root, motion->y_root, - motion->state, - motion->is_hint, - motion->same_screen, - motion->device_state, - motion->axes_count, - motion->first_axis, - motion->axis_data[0], motion->axis_data[1], motion->axis_data[2], motion->axis_data[3], motion->axis_data[4], motion->axis_data[5] - ); + "XDeviceMotionEvent:\n" + " type: %d\n" + " serial: %lu\n" + " send_event: %d\n" + " display: %p\n" + " window: --\n" + " deviceid: %lu\n" + " root: --\n" + " subwindow: --\n" + " time: --\n" + " x: %d, y: %d\n" + " x_root: %d, y_root: %d\n" + " state: %u\n" + " is_hint: %2.2X\n" + " same_screen: %d\n" + " device_state: %u\n" + " axes_count: %2.2X\n" + " first_axis: %2.2X\n" + " axis_data[6]: {%d,%d,%d,%d,%d,%d}\n", + motion->type, + motion->serial, + motion->send_event, + motion->display, + /* motion->window, */ + motion->deviceid, + /* motion->root */ + /* motion->subwindow */ + /* motion->time, */ + motion->x, motion->y, + motion->x_root, motion->y_root, + motion->state, + motion->is_hint, + motion->same_screen, + motion->device_state, + motion->axes_count, + motion->first_axis, + motion->axis_data[0], motion->axis_data[1], motion->axis_data[2], motion->axis_data[3], motion->axis_data[4], motion->axis_data[5]); } #else -#define XI_DBG(format, ...) while(0) {} -#define print_motion_event(motion) while(0) {} +#define XI_DBG(format, ...) do { } while (false) +#define print_motion_event(motion) do { } while (false) #endif + +inline std::string remove_spaces(const char *s) +{ + std::string output(s); + output.erase(std::remove_if(output.begin(), output.end(), isspace), output.end()); + return output; +} + + //============================================================ // lightgun helpers: copy-past from xinfo //============================================================ @@ -138,7 +139,7 @@ find_device_info(Display *display, bool is_id = true; XID id = static_cast<XID>(-1); - for(loop = 0; loop < len; loop++) + for (loop = 0; loop < len; loop++) { if (!isdigit(name[loop])) { @@ -154,7 +155,7 @@ find_device_info(Display *display, devices = XListInputDevices(display, &num_devices); - for(loop = 0; loop < num_devices; loop++) + for (loop = 0; loop < num_devices; loop++) { osd_printf_verbose("Evaluating device with name: %s\n", devices[loop].name); @@ -185,7 +186,7 @@ find_device_info(Display *display, } //Copypasted from xinfo -static int +int register_events( Display *dpy, XDeviceInfo *info, @@ -252,32 +253,90 @@ register_events( return number; } -//============================================================ -// x11_event_manager -//============================================================ -class x11_event_handler +struct device_map { -public: - virtual ~x11_event_handler() {} + static inline constexpr unsigned MAX_ENTRIES = 16; + + struct { + std::string name; + int physical; + } map[MAX_ENTRIES]; + int logical[MAX_ENTRIES]; + int initialized; + + void init(osd_options const &options, const char *opt, int max_devices, const char *label) + { + // initialize based on an input option prefix and max number of devices + char defname[20]; + + // The max devices the user specified, better not be bigger than the max the arrays can old + assert(max_devices <= MAX_ENTRIES); - virtual void handle_event(XEvent &xevent) = 0; + // Initialize the map to default uninitialized values + for (int dev = 0; dev < MAX_ENTRIES; dev++) + { + map[dev].name.clear(); + map[dev].physical = -1; + logical[dev] = -1; + } + initialized = 0; + + // populate the device map up to the max number of devices + for (int dev = 0; dev < max_devices; dev++) + { + const char *dev_name; + + // derive the parameter name from the option name and index. For instance: lightgun_index1 to lightgun_index8 + sprintf(defname, "%s%d", opt, dev + 1); + + // Get the user-specified name that matches the parameter + dev_name = options.value(defname); + + // If they've specified a name and it's not "auto", treat it as a custom mapping + if (dev_name && *dev_name && strcmp(dev_name, OSDOPTVAL_AUTO)) + { + // remove the spaces from the name store it in the index + map[dev].name = remove_spaces(dev_name); + osd_printf_verbose("%s: Logical id %d: %s\n", label, dev + 1, map[dev].name); + initialized = 1; + } + } + } }; -class x11_event_manager : public event_manager_t<x11_event_handler> + +//============================================================ +// x11_event_manager +//============================================================ + +class x11_event_manager : public event_subscription_manager<XEvent, int> { private: - Display * m_display; - - x11_event_manager() - : event_manager_t(), - m_display(nullptr) + struct x_cleanup { - } + void operator()(Display *ptr) const + { + if (ptr) + XCloseDisplay(ptr); + } + void operator()(XExtensionVersion *ptr) const + { + if (ptr) + XFree(ptr); + } + }; + + template <typename T> using x_ptr = std::unique_ptr<T, x_cleanup>; + + x_ptr<Display> m_display; + + x11_event_manager() = default; + public: - Display * display() const { return m_display; } + Display *display() const { return m_display.get(); } - static x11_event_manager& instance() + static x11_event_manager &instance() { static x11_event_manager s_instance; return s_instance; @@ -285,20 +344,20 @@ public: int initialize() { - std::lock_guard<std::mutex> scope_lock(m_lock); + std::lock_guard<std::mutex> scope_lock(subscription_mutex()); - if (m_display != nullptr) + if (m_display) return 0; - m_display = XOpenDisplay(nullptr); - if (m_display == nullptr) + m_display.reset(XOpenDisplay(nullptr)); + if (!m_display) { osd_printf_verbose("Unable to connect to X server\n"); return -1; } - XExtensionVersion *version = XGetExtensionVersion(m_display, INAME); - if (!version || (version == reinterpret_cast<XExtensionVersion*>(NoSuchExtension))) + x_ptr<XExtensionVersion> version(XGetExtensionVersion(m_display.get(), INAME)); + if (!version || (version.get() == reinterpret_cast<XExtensionVersion *>(NoSuchExtension))) { osd_printf_verbose("xinput extension not available!\n"); return -1; @@ -307,28 +366,24 @@ public: return 0; } - void process_events(running_machine &machine) override + void process_events() { - std::lock_guard<std::mutex> scope_lock(m_lock); - XEvent xevent; + std::lock_guard<std::mutex> scope_lock(subscription_mutex()); - //Get XInput events - while (XPending(m_display) != 0) - { - XNextEvent(m_display, &xevent); - - // Find all subscribers for the event type - auto subscribers = m_subscription_index.equal_range(xevent.type); + // If X11 has become invalid for some reason, XPending will crash. Assert instead. + assert(m_display); - // Dispatch the events - std::for_each(subscribers.first, subscribers.second, [&xevent](auto &pair) - { - pair.second->handle_event(xevent); - }); + // Get XInput events + while (XPending(m_display.get()) != 0) + { + XEvent event; + XNextEvent(m_display.get(), &event); + dispatch_event(event.type, event); } } }; + //============================================================ // x11_input_device //============================================================ @@ -336,15 +391,23 @@ public: class x11_input_device : public event_based_device<XEvent> { public: - x11_api_state x11_state; - - x11_input_device(running_machine &machine, const char *name, const char *id, input_device_class devclass, input_module &module) - : event_based_device(machine, name, id, devclass, module), - x11_state({0}) + x11_input_device( + std::string &&name, + std::string &&id, + input_module &module, + XDeviceInfo const *info) : + event_based_device(std::move(name), std::move(id), module), + m_device_id(info ? info->id : 0) { } + + XID const &device_id() const { return m_device_id; } + +protected: + XID const m_device_id; // X11 device ID }; + //============================================================ // x11_lightgun_device //============================================================ @@ -352,55 +415,124 @@ public: class x11_lightgun_device : public x11_input_device { public: - lightgun_state lightgun; + x11_lightgun_device( + std::string &&name, + std::string &&id, + input_module + &module, + XDeviceInfo *info) : + x11_input_device(std::move(name), std::move(id), module, info), + m_axis_count(0), + m_button_count(0), + m_maxx(0), + m_maxy(0), + m_minx(0), + m_miny(0), + m_lightgun({ 0 }) + { + if (info && (info->num_classes > 0)) + { + // Grab device info and translate to stuff MAME can use + XAnyClassPtr any = static_cast<XAnyClassPtr>(info->inputclassinfo); + for (int i = 0; i < info->num_classes; i++) + { + switch (any->c_class) + { + // Set the axis min/max ranges if we got them + case ValuatorClass: + { + auto const valuator_info = reinterpret_cast<XValuatorInfoPtr>(any); + auto axis_info = reinterpret_cast<XAxisInfoPtr>(reinterpret_cast<char *>(valuator_info) + sizeof(XValuatorInfo)); + for (int j = 0; (j < valuator_info->num_axes) && (j < 2); j++, axis_info++) + { + if (j == 0) + { + XI_DBG("Set minx=%d, maxx=%d\n", axis_info->min_value, axis_info->max_value); + m_axis_count = 1; + m_maxx = axis_info->max_value; + m_minx = axis_info->min_value; + } + + if (j == 1) + { + XI_DBG("Set miny=%d, maxy=%d\n", axis_info->min_value, axis_info->max_value); + m_axis_count = 2; + m_maxy = axis_info->max_value; + m_miny = axis_info->min_value; + } + } + } + break; + + // Count the lightgun buttons based on what we read + case ButtonClass: + { + XButtonInfoPtr b = reinterpret_cast<XButtonInfoPtr>(any); + if (b->num_buttons < 0) + m_button_count = 0; + else if (b->num_buttons <= MAX_BUTTONS) + m_button_count = b->num_buttons; + else + m_button_count = MAX_BUTTONS; + } + break; + } - x11_lightgun_device(running_machine &machine, const char *name, const char *id, input_module &module) - : x11_input_device(machine, name, id, DEVICE_CLASS_LIGHTGUN, module), - lightgun({0}) + any = reinterpret_cast<XAnyClassPtr>(reinterpret_cast<char *>(any) + any->length); + } + } + } + + virtual void reset() override { + memset(&m_lightgun, 0, sizeof(m_lightgun)); } - void process_event(XEvent &xevent) override + virtual void configure(input_device &device) override + { + // Add buttons + for (int button = 0; button < m_button_count; button++) + { + input_item_id const itemid = input_item_id(ITEM_ID_BUTTON1 + button); + device.add_item(default_button_name(button), std::string_view(), itemid, generic_button_get_state<std::int32_t>, &m_lightgun.buttons[button]); + } + + // Add X and Y axis + if (1 <= m_axis_count) + device.add_item("X", std::string_view(), ITEM_ID_XAXIS, generic_axis_get_state<std::int32_t>, &m_lightgun.lX); + if (2 <= m_axis_count) + device.add_item("Y", std::string_view(), ITEM_ID_YAXIS, generic_axis_get_state<std::int32_t>, &m_lightgun.lY); + } + + virtual void process_event(XEvent const &xevent) override { if (xevent.type == motion_type) { - XDeviceMotionEvent *motion = reinterpret_cast<XDeviceMotionEvent *>(&xevent); + auto const motion = reinterpret_cast<XDeviceMotionEvent const *>(&xevent); print_motion_event(motion); - /* - * We have to check with axis will start on array index 0. - * We have also to check the number of axes that are stored in the array. - */ + // We have to check with axis will start on array index 0. + // We also have to check the number of axes that are stored in the array. switch (motion->first_axis) { - /* - * Starting with x, check number of axis, if there is also the y axis stored. - */ + // Starting with x, check number of axes, if there is also the y axis stored. case 0: if (motion->axes_count >= 1) - { - lightgun.lX = normalize_absolute_axis(motion->axis_data[0], x11_state.minx, x11_state.maxx); - if (motion->axes_count >= 2) - { - lightgun.lY = normalize_absolute_axis(motion->axis_data[1], x11_state.miny, x11_state.maxy); - } - } + m_lightgun.lX = normalize_absolute_axis(motion->axis_data[0], m_minx, m_maxx); + if (motion->axes_count >= 2) + m_lightgun.lY = normalize_absolute_axis(motion->axis_data[1], m_miny, m_maxy); break; - /* - * Starting with y, ... - */ + // Starting with y, ... case 1: if (motion->axes_count >= 1) - { - lightgun.lY = normalize_absolute_axis(motion->axis_data[0], x11_state.miny, x11_state.maxy); - } + m_lightgun.lY = normalize_absolute_axis(motion->axis_data[0], m_miny, m_maxy); break; } } else if (xevent.type == button_press_type || xevent.type == button_release_type) { - XDeviceButtonEvent *button = reinterpret_cast<XDeviceButtonEvent *>(&xevent); + auto const button = reinterpret_cast<XDeviceButtonEvent const *>(&xevent); /* * SDL/X11 Number the buttons 1,2,3, while windows and other parts of MAME @@ -408,140 +540,160 @@ public: * -1 the button number to align the numbering schemes. */ int button_number = button->button; - switch (button_number) + if (button_number <= MAX_BUTTONS) { + switch (button_number) + { case 2: - button_number = 3; - break; case 3: - button_number = 2; + button_number ^= 1; break; + } + m_lightgun.buttons[button_number - 1] = (xevent.type == button_press_type) ? 0x80 : 0; } - lightgun.buttons[button_number - 1] = (xevent.type == button_press_type) ? 0x80 : 0; } } - void reset() override +private: + struct lightgun_state { - memset(&lightgun, 0, sizeof(lightgun)); - } + int32_t lX, lY; + int32_t buttons[MAX_BUTTONS]; + }; + + int m_axis_count; + int m_button_count; + int32_t m_maxx; + int32_t m_maxy; + int32_t m_minx; + int32_t m_miny; + lightgun_state m_lightgun; }; + //============================================================ // x11_lightgun_module //============================================================ -class x11_lightgun_module : public input_module_base, public x11_event_handler +class x11_lightgun_module : public input_module_impl<x11_input_device, osd_common_t>, public x11_event_manager::subscriber { private: - device_map_t m_lightgun_map; - Display * m_display; + device_map m_lightgun_map; + Display *m_display; + public: - x11_lightgun_module() - : input_module_base(OSD_LIGHTGUNINPUT_PROVIDER, "x11"), - m_display(nullptr) + x11_lightgun_module() : + input_module_impl<x11_input_device, osd_common_t>(OSD_LIGHTGUNINPUT_PROVIDER, "x11"), + m_display(nullptr) { } - void input_init(running_machine &machine) override + virtual bool probe() override { - int index; + // If there is no X server, X11 lightguns cannot be supported + Display *const display = XOpenDisplay(nullptr); + if (!display) + return false; + XCloseDisplay(display); - osd_printf_verbose("Lightgun: Begin initialization\n"); - - devmap_init(machine, &m_lightgun_map, SDLOPTION_LIGHTGUNINDEX, 8, "Lightgun mapping"); + return true; + } + virtual int init(osd_interface &osd, osd_options const &options) override + { + // If the X server has become invalid, a crash can occur x11_event_manager::instance().initialize(); m_display = x11_event_manager::instance().display(); + if (!m_display) + return -1; + + return input_module_impl<x11_input_device, osd_common_t>::init(osd, options); + } + + virtual void input_init(running_machine &machine) override + { + assert(m_display); + + input_module_impl<x11_input_device, osd_common_t>::input_init(machine); + + osd_printf_verbose("Lightgun: Begin initialization\n"); + + m_lightgun_map.init(*options(), SDLOPTION_LIGHTGUNINDEX, 8, "Lightgun mapping"); // Loop through all 8 possible devices - for (index = 0; index < 8; index++) + for (int index = 0; index < 8; index++) { - XDeviceInfo *info; - // Skip if the name is empty - if (m_lightgun_map.map[index].name.length() == 0) + if (m_lightgun_map.map[index].name.empty()) continue; - x11_lightgun_device *devinfo; + // Find the device info associated with the name std::string const &name = m_lightgun_map.map[index].name; - char defname[512]; + osd_printf_verbose("%i: %s\n", index, name); + XDeviceInfo *const info = find_device_info(m_display, name.c_str(), 0); + if (!info) + osd_printf_verbose("Lightgun: Can't find device %s!\n", name); - // Register and add the device - devinfo = create_lightgun_device(machine, index); - osd_printf_verbose("%i: %s\n", index, name.c_str()); - - // Find the device info associated with the name - info = find_device_info(m_display, name.c_str(), 0); + // previously had code to use "NC%d" format if name was empty + // but that couldn't happen because device creation would be skipped - // If we couldn't find the device, skip - if (info == nullptr) - { - osd_printf_verbose("Can't find device %s!\n", name.c_str()); - continue; - } + // Register and add the device + create_device<x11_lightgun_device>( + DEVICE_CLASS_LIGHTGUN, + std::string(name), + std::string(name), + info); - //Grab device info and translate to stuff mame can use - if (info->num_classes > 0) + // Register this device to receive event notifications + if (info) { - // Add the lightgun buttons based on what we read - add_lightgun_buttons(static_cast<XAnyClassPtr>(info->inputclassinfo), info->num_classes, devinfo); - - // Also, set the axix min/max ranges if we got them - set_lightgun_axis_props(static_cast<XAnyClassPtr>(info->inputclassinfo), info->num_classes, devinfo); + int const events_registered = register_events(m_display, info, name.c_str(), 0); + osd_printf_verbose("Device %i: Registered %i events.\n", int(info->id), events_registered); } + } - // Add X and Y axis - sprintf(defname, "X %s", devinfo->name()); - devinfo->device()->add_item(defname, ITEM_ID_XAXIS, generic_axis_get_state<std::int32_t>, &devinfo->lightgun.lX); - - sprintf(defname, "Y %s", devinfo->name()); - devinfo->device()->add_item(defname, ITEM_ID_YAXIS, generic_axis_get_state<std::int32_t>, &devinfo->lightgun.lY); - - // Save the device id - devinfo->x11_state.deviceid = info->id; + // register ourself to handle events from event manager + int const event_types[] = { motion_type, button_press_type, button_release_type }; + osd_printf_verbose("Events types to register: motion:%d, press:%d, release:%d\n", motion_type, button_press_type, button_release_type); + subscribe(x11_event_manager::instance(), event_types); - // Register this device to receive event notifications - int events_registered = register_events(m_display, info, m_lightgun_map.map[index].name.c_str(), 0); - osd_printf_verbose("Device %i: Registered %i events.\n", static_cast<int>(info->id), events_registered); + osd_printf_verbose("Lightgun: End initialization\n"); + } - // register ourself to handle events from event manager - int event_types[] = { motion_type, button_press_type, button_release_type }; - osd_printf_verbose("Events types to register: motion:%d, press:%d, release:%d\n", motion_type, button_press_type, button_release_type); - x11_event_manager::instance().subscribe(event_types, ARRAY_LENGTH(event_types), this); - } + virtual void exit() override + { + // unsubscribe from events + unsubscribe(); - osd_printf_verbose("Lightgun: End initialization\n"); + input_module_impl<x11_input_device, osd_common_t>::exit(); } - bool should_poll_devices(running_machine &machine) override + virtual bool should_poll_devices() override { - return sdl_event_manager::instance().has_focus(); + return osd().has_focus(); } - void before_poll(running_machine &machine) override + virtual void before_poll() override { - if (!should_poll_devices(machine)) - return; + // trigger the SDL event manager so it can process window events + input_module_impl<x11_input_device, osd_common_t>::before_poll(); // Tell the event manager to process events and push them to the devices - x11_event_manager::instance().process_events(machine); - - // Also trigger the SDL event manager so it can process window events - sdl_event_manager::instance().process_events(machine); + if (should_poll_devices()) + x11_event_manager::instance().process_events(); } - void handle_event(XEvent &xevent) override + virtual void handle_event(XEvent const &xevent) override { XID deviceid; if (xevent.type == motion_type) { - XDeviceMotionEvent *motion = reinterpret_cast<XDeviceMotionEvent *>(&xevent); + auto const motion = reinterpret_cast<XDeviceMotionEvent const *>(&xevent); deviceid = motion->deviceid; } else if (xevent.type == button_press_type || xevent.type == button_release_type) { - XDeviceButtonEvent *button = reinterpret_cast<XDeviceButtonEvent *>(&xevent); + auto const button = reinterpret_cast<XDeviceButtonEvent const *>(&xevent); deviceid = button->deviceid; } else @@ -550,97 +702,27 @@ public: } // Figure out which lightgun this event id destined for - auto target_device = std::find_if(devicelist()->begin(), devicelist()->end(), [deviceid](auto &device) - { - std::unique_ptr<device_info> &ptr = device; - return downcast<x11_input_device*>(ptr.get())->x11_state.deviceid == deviceid; - }); + auto target_device = std::find_if( + devicelist().begin(), + devicelist().end(), + [&deviceid] (auto &device) { return device->device_id() == deviceid; }); // If we find a matching lightgun, dispatch the event to the lightgun - if (target_device != devicelist()->end()) - { - downcast<x11_input_device*>((*target_device).get())->queue_events(&xevent, 1); - } + if (target_device != devicelist().end()) + (*target_device)->queue_events(&xevent, 1); } +}; -private: - x11_lightgun_device* create_lightgun_device(running_machine &machine, int index) - { - char tempname[20]; - - if (m_lightgun_map.map[index].name.length() == 0) - { - if (m_lightgun_map.initialized) - { - snprintf(tempname, ARRAY_LENGTH(tempname), "NC%d", index); - devicelist()->create_device<x11_lightgun_device>(machine, tempname, tempname, *this); - } - - return nullptr; - } - - return devicelist()->create_device<x11_lightgun_device>(machine, m_lightgun_map.map[index].name.c_str(), m_lightgun_map.map[index].name.c_str(), *this); - } - - void add_lightgun_buttons(XAnyClassPtr first_info_class, int num_classes, x11_lightgun_device *devinfo) const - { - XAnyClassPtr any = first_info_class; - - for (int i = 0; i < num_classes; i++) - { - switch (any->c_class) - { - case ButtonClass: - XButtonInfoPtr b = reinterpret_cast<XButtonInfoPtr>(any); - for (int button = 0; button < b->num_buttons; button++) - { - input_item_id itemid = static_cast<input_item_id>(ITEM_ID_BUTTON1 + button); - devinfo->device()->add_item(default_button_name(button), itemid, generic_button_get_state<std::int32_t>, &devinfo->lightgun.buttons[button]); - } - break; - } +} // anonymous namespace - any = reinterpret_cast<XAnyClassPtr>(reinterpret_cast<char *>(any) + any->length); - } - } +} // namespace osd - void set_lightgun_axis_props(XAnyClassPtr first_info_class, int num_classes, x11_lightgun_device *devinfo) const - { - XAnyClassPtr any = first_info_class; - for (int i = 0; i < num_classes; i++) - { - switch (any->c_class) - { - case ValuatorClass: - XValuatorInfoPtr valuator_info = reinterpret_cast<XValuatorInfoPtr>(any); - XAxisInfoPtr axis_info = reinterpret_cast<XAxisInfoPtr>(reinterpret_cast<char *>(valuator_info) + sizeof(XValuatorInfo)); - for (int j = 0; j < valuator_info->num_axes; j++, axis_info++) - { - if (j == 0) - { - XI_DBG("Set minx=%d, maxx=%d\n", axis_info->min_value, axis_info->max_value); - devinfo->x11_state.maxx = axis_info->max_value; - devinfo->x11_state.minx = axis_info->min_value; - } +#else // defined(SDLMAME_SDL2) && !defined(SDLMAME_WIN32) && defined(USE_XINPUT) && USE_XINPUT - if (j == 1) - { - XI_DBG("Set miny=%d, maxy=%d\n", axis_info->min_value, axis_info->max_value); - devinfo->x11_state.maxy = axis_info->max_value; - devinfo->x11_state.miny = axis_info->min_value; - } - } - break; - } +namespace osd { namespace { MODULE_NOT_SUPPORTED(x11_lightgun_module, OSD_LIGHTGUNINPUT_PROVIDER, "x11") } } - any = reinterpret_cast<XAnyClassPtr>(reinterpret_cast<char *>(any) + any->length); - } - } -}; +#endif // defined(SDLMAME_SDL2) && !defined(SDLMAME_WIN32) && defined(USE_XINPUT) && USE_XINPUT -#else -MODULE_NOT_SUPPORTED(x11_lightgun_module, OSD_LIGHTGUNINPUT_PROVIDER, "x11") -#endif -MODULE_DEFINITION(LIGHTGUN_X11, x11_lightgun_module) +MODULE_DEFINITION(LIGHTGUN_X11, osd::x11_lightgun_module) diff --git a/src/osd/modules/input/input_xinput.cpp b/src/osd/modules/input/input_xinput.cpp index 67348ebe162..474ef6fe7ff 100644 --- a/src/osd/modules/input/input_xinput.cpp +++ b/src/osd/modules/input/input_xinput.cpp @@ -1,223 +1,2997 @@ // license:BSD-3-Clause -// copyright-holders:Brad Hughes +// copyright-holders:Brad Hughes, Vas Crabb //============================================================ // // input_xinput.cpp - XInput API input support for Windows // //============================================================ +/* -#include "input_module.h" -#include "modules/osdmodule.h" +XInput is infamously inflexible. It currently only supports a single +controller type (game controller) with a fixed report format. The +report format includes: +* 16-bit field for single-bit buttons and D-pad directions (two bits + undefined) +* Two 8-bit axis values, used for triggers, pedals, buttons or throttle + and rudder controls, depending on the controller +* Four 16-bit axis values, used for joysticks, steering wheels or hat + switches, depending on the controller + +Some button names have changed at various times: +* Face buttons White and Black became shoulder buttons LB and RB, + respectively. +* Navigation buttons Start and Back became Menu and View, respectively. + +Subtype Gamepad Wheel Arcade stick Arcade pad Flight stick Dance pad Guitar Drum kit + +D-pad D-pad D-pad Joystick D-pad Foot switch D-Pad/Strum D-pad +L trigger Trigger Brake Button Button Rudder Pickup select^ +R trigger Trigger Accelerator Button Button Throttle ^ +L stick X Stick Wheel Roll +L stick Y Stick Pitch +R stick X Stick Hat left/right Whammy bar +R stick Y Stick Hat up/down Orientation + +A Button Button Button Button Primary fire Foot switch Fret 1 Floor tom +B Button Button Button Button Secondary fire Foot switch Fret 2 Snare +X Button Button Button Button Button Foot switch Fret 4 Low tom +Y Button Button Button Button Button Foot switch Fret 3 High tom +LB Button Button Button^ Button Button^ Fret 5 Bass drum +RB Button Button Button^ Button Button^ Button^ Button^ +LSB Button Button^ Button^ Button^ Button^ Fret modifier^ Button^ +RSB Button Button^ Button^ Button^ Button^ Button^ Button^ + +^ optional + + +At least the vast majority of controllers report 8-bit trigger +resolution and 10-bit stick resolution, even when the physical controls +use digital switches. Resolution can't be used to reliably detect +nominal analog axes controlled by switches. + +Some arcade sticks report unknown or gamepad subtype, but have a single +digital joystick with a switch to select between controlling the D-pad, +left stick and right stick. You can't assume that all three can be +controlled at the same time. + +Many controllers don't correctly report the absence of analog sticks. + + +Some controllers use capabilities to indicate extended controller type +information: + + Type Sub LSX LSY RSX +Band Hero Wireless Guitar 0x01 0x07 0x1430 0x0705 0x0001 +Band Hero Wireless Drum Kit 0x01 0x08 0x1430 0x0805 0x0001 +DJ Hero 2 Turntable 0x01 0x17 0x1430 0x1705 0x0001 +Rock Band 3 Wireless Keyboard 0x01 0x0f 0x1bad 0x1330 0x0004 + + +There are multiple physical button layouts for arcade sticks, for +example: + +Gamester Xbox Arcade Stick, Hori Real Arcade Pro EX +LT X Y LB +RT A B RB + +PXN 0082 Arcade Stick +LB X Y RB +LT A B RT + +Mortal Kombat Tournament Edition Arcade Stick + X Y + RT +LB A B + +Hori Fighting Stick EX2 + B X Y +A LT RT + +Hori Real Arcade Pro VX-SA Kai, Razer Atrox + B X Y LB +A LT RT RB + +Hori Real Arcade Pro.V Kai, Mad Catz EGO Arcade Stick, Mayflash F300, Mayflash F500 + X Y RB LB +A B RT LT + +Mad Catz WWE All Stars Brawl Stick + X Y LB LT +A B RB RT + +Arcade pads typically have six face buttons, and come with different +layouts corresponding to the latter two arcade stick layouts, with the +rightmost column on the shoulder buttons. Examples of face button +layouts: + +Hori Fighting Commander OCTA, Mad Catz Street Fighter IV FightPad, PowerA FUSION Wired FightPad +X Y RB +A B RT -#if defined(OSD_WINDOWS) +Hori Pad EX Turbo 2, Mad Catz WWE All Stars BrawlPad, Mortal Kombat X Fight Pad, PDP Versus Fighting Pad +X Y LB +A B RB -// standard windows headers -#include <windows.h> -// XInput header -#include <xinput.h> +Dance mats usually have this layout: +BK ST +B U A +L R +Y D X -#undef interface +This layout seems somewhat unusual: +BK ST +A U B +L R +X D Y -// MAME headers -#include "emu.h" +This layout is also available but rare: +BK ST +A U B +L R +Y D X -// MAMEOS headers -#include "winutil.h" -#include "winmain.h" -#include "input_common.h" -#include "input_windows.h" +Drum kits also have multiple layouts. + +Rock Band: +B Y X A + LB + +Guitar Hero: + Y RB +A X B + LB + + +Rock Band keyboards use axes as bit fields: + +LT 7 C +LT 6 C# +LT 5 D +LT 4 D# +LT 3 E +LT 2 F +LT 1 F# +LT 0 G +RT 7 G# +RT 6 A +RT 5 A# +RT 4 B +RT 3 C +RT 2 C# +RT 1 D +RT 0 D# +LSX 7 E +LSX 6 F +LSX 5 F# +LSX 4 G +LSX 3 G# +LSX 2 A +LSX 1 A# +LSX 0 B +LSX 15 C + +*/ + +#include "modules/osdmodule.h" + +#if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) + #include "input_xinput.h" -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#include "assignmenthelper.h" + +#include "interface/inputseq.h" +#include "modules/lib/osdobj_common.h" + +// emu +#include "inpttype.h" + +// lib/util +#include "util/coretmpl.h" + +#include "eminline.h" + +#include <algorithm> +#include <cstdint> +#include <iterator> +#include <string> +#include <tuple> +#include <utility> + + #define XINPUT_LIBRARIES { "xinput1_4.dll", "xinput9_1_0.dll" } -#else -#define XINPUT_LIBRARIES { "xinput1_4.dll" } -#endif -int xinput_api_helper::initialize() +#define XINPUT_AXIS_MINVALUE (-32'767) +#define XINPUT_AXIS_MAXVALUE (32'767) + +namespace osd { + +namespace { + +using util::BIT; + + +char const *const AXIS_NAMES_GAMEPAD[]{ + "LSX", + "LSY", + "RSX", + "RSY", + "LT", + "RT" }; + +char const *const AXIS_NAMES_WHEEL[]{ + "Wheel", + "LSY", + "RSX", + "RSY", + "Brake", + "Accelerator" }; + +char const *const AXIS_NAMES_GUITAR[]{ + "LSX", + "LSY", + "Whammy Bar", + "Orientation", + "Pickup Selector", + "RT" }; + +input_item_id const AXIS_IDS_GAMEPAD[]{ + ITEM_ID_XAXIS, + ITEM_ID_YAXIS, + ITEM_ID_ZAXIS, + ITEM_ID_RZAXIS, + ITEM_ID_SLIDER1, + ITEM_ID_SLIDER2 }; + +char const *const HAT_NAMES_GAMEPAD[]{ + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "POV Hat Up", + "POV Hat Down", + "POV Hat Left", + "POV Hat Right" }; + +char const *const HAT_NAMES_ARCADE_STICK[]{ + "Joystick Up", + "Joystick Down", + "Joystick Left", + "Joystick Right", + nullptr, + nullptr, + nullptr, + nullptr }; + +char const *const HAT_NAMES_GUITAR[]{ + "Strum/D-pad Up", + "Strum/D-pad Down", + "D-pad Left", + "D-pad Right" }; + +char const *const BUTTON_NAMES_GAMEPAD[]{ + "A", + "B", + "X", + "Y", + "LT", + "RT", + "LB", + "RB", + "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", + "Fret 3", + "Fret 4", + "Fret 5", + "Fret Modifier", + "RB", + "RSB" }; + +char const *const BUTTON_NAMES_DRUMKIT[]{ + "Green", // floor tom + "Red", // snare + "Blue", // low tom + "Yellow", // Rock Band high tom, Guitar Hero hi-hat + "Orange", // Guitar Hero crash cymbal + "Bass Drum", + "LSB", + "RSB" }; + +char const *const BUTTON_NAMES_KEYBOARD[]{ + "A", + "B", + "X", + "Y", + "LB", + "RB", + "LSB", + "RSB" }; + + + +//============================================================ +// base class for XInput controller handlers +//============================================================ + +class xinput_device_base : public device_info, protected joystick_assignment_helper { - m_xinput_dll = osd::dynamic_module::open(XINPUT_LIBRARIES); +protected: + xinput_device_base( + std::string &&name, + std::string &&id, + input_module &module, + u32 player, + XINPUT_CAPABILITIES const &caps, + xinput_api_helper const &helper); - XInputGetState = m_xinput_dll->bind<xinput_get_state_fn>("XInputGetState"); - XInputGetCapabilities = m_xinput_dll->bind<xinput_get_caps_fn>("XInputGetCapabilities"); + // capabilities + BYTE device_type() const { return m_capabilities.Type; } + BYTE device_subtype() const { return m_capabilities.SubType; } + bool has_button(WORD mask) const { return (m_capabilities.Gamepad.wButtons & mask) != 0; } + bool has_trigger_left() const { return m_capabilities.Gamepad.bLeftTrigger != 0; } + bool has_trigger_right() const { return m_capabilities.Gamepad.bRightTrigger != 0; } + bool has_thumb_left_x() const { return m_capabilities.Gamepad.sThumbLX != 0; } + bool has_thumb_left_y() const { return m_capabilities.Gamepad.sThumbLY != 0; } + bool has_thumb_right_x() const { return m_capabilities.Gamepad.sThumbRX != 0; } + bool has_thumb_right_y() const { return m_capabilities.Gamepad.sThumbRY != 0; } - if (!XInputGetState || !XInputGetCapabilities) + // device state + WORD buttons() const { return m_xinput_state.Gamepad.wButtons; } + BYTE trigger_left() const { return m_xinput_state.Gamepad.bLeftTrigger; } + BYTE trigger_right() const { return m_xinput_state.Gamepad.bRightTrigger; } + SHORT thumb_left_x() const { return m_xinput_state.Gamepad.sThumbLX; } + SHORT thumb_left_y() const { return m_xinput_state.Gamepad.sThumbLY; } + SHORT thumb_right_x() const { return m_xinput_state.Gamepad.sThumbRX; } + SHORT thumb_right_y() const { return m_xinput_state.Gamepad.sThumbRY; } + + bool read_state(); + 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(); + + u32 const m_player_index; + XINPUT_CAPABILITIES m_capabilities; + XINPUT_STATE m_xinput_state; + bool m_reset; + + xinput_api_helper const &m_xinput_helper; +}; + + +xinput_device_base::xinput_device_base( + std::string &&name, + std::string &&id, + input_module &module, + u32 player, + XINPUT_CAPABILITIES const &caps, + xinput_api_helper const &helper) : + device_info(std::move(name), std::move(id), module), + m_player_index(player), + m_capabilities(caps), + m_xinput_state{ 0 }, + m_reset(true), + m_xinput_helper(helper) +{ + // get friendly names for controller type and subtype + char const *type_name = "unsupported"; + char const *subtype_name = "unsupported"; + switch (m_capabilities.Type) { - osd_printf_verbose("Could not find XInput. Please try to reinstall DirectX runtime package.\n"); - return -1; + case XINPUT_DEVTYPE_GAMEPAD: + type_name = "game controller"; + switch (m_capabilities.SubType) + { + case 0x00: // XINPUT_DEVSUBTYPE_UNKNOWN: work around MinGW header issues + subtype_name = "unknown"; + break; + case XINPUT_DEVSUBTYPE_GAMEPAD: + subtype_name = "gamepad"; + break; + case XINPUT_DEVSUBTYPE_WHEEL: + subtype_name = "wheel"; + break; + case XINPUT_DEVSUBTYPE_ARCADE_STICK: + subtype_name = "arcade stick"; + break; + case 0x04: // XINPUT_DEVSUBTYPE_FLIGHT_STICK: work around MinGW header issues + subtype_name = "flight stick"; + break; + case XINPUT_DEVSUBTYPE_DANCE_PAD: + subtype_name = "dance pad"; + break; + case XINPUT_DEVSUBTYPE_GUITAR: + subtype_name = "guitar"; + break; + case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE: + subtype_name = "alternate guitar"; + break; + case XINPUT_DEVSUBTYPE_DRUM_KIT: + subtype_name = "drum kit"; + break; + case XINPUT_DEVSUBTYPE_GUITAR_BASS: + subtype_name = "bass guitar"; + break; + case XINPUT_DEVSUBTYPE_ARCADE_PAD: + subtype_name = "arcade pad"; + break; + } + break; } - return 0; + // detect invalid axis resolutions + bool const ltcap_bad = m_capabilities.Gamepad.bLeftTrigger && count_leading_zeros_32(m_capabilities.Gamepad.bLeftTrigger << 24); + bool const rtcap_bad = m_capabilities.Gamepad.bRightTrigger && count_leading_zeros_32(m_capabilities.Gamepad.bRightTrigger << 24); + bool const lsxcap_bad = m_capabilities.Gamepad.sThumbLX && count_leading_zeros_32(m_capabilities.Gamepad.sThumbLX << 16); + bool const lsycap_bad = m_capabilities.Gamepad.sThumbLY && count_leading_zeros_32(m_capabilities.Gamepad.sThumbLY << 16); + bool const rsxcap_bad = m_capabilities.Gamepad.sThumbRX && count_leading_zeros_32(m_capabilities.Gamepad.sThumbRX << 16); + bool const rsycap_bad = m_capabilities.Gamepad.sThumbRY && count_leading_zeros_32(m_capabilities.Gamepad.sThumbRY << 16); + + // log some diagnostic information + osd_printf_verbose( + "XInput: Configuring player %d type 0x%02X (%s) sub type 0x%02X (%s).\n", + m_player_index + 1, + m_capabilities.Type, + type_name, + m_capabilities.SubType, + subtype_name); + osd_printf_verbose( + "XInput: Switch capabilities A=%d B=%d X=%d Y=%d LB=%d RB=%d LSB=%d RSB=%d Start=%d Back=%d Up=%d Down=%d Left=%d Right=%d.\n", + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_A) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_B) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_X) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_Y) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_START) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? 1 : 0, + (m_capabilities.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? 1 : 0); + osd_printf_verbose( + "XInput: Axis capabilities LT=0x%02X (%d-bit%s) RT=0x%02X (%d-bit%s) LSX=0x%04X (%d-bit%s) LSY=0x%04X (%d-bit%s) RSX=0x%04X (%d-bit%s) RSY=0x%04X (%d-bit%s).\n", + m_capabilities.Gamepad.bLeftTrigger, + count_leading_ones_32(u32(m_capabilities.Gamepad.bLeftTrigger) << 24), + ltcap_bad ? ", invalid" : "", + m_capabilities.Gamepad.bRightTrigger, + count_leading_ones_32(u32(m_capabilities.Gamepad.bRightTrigger) << 24), + rtcap_bad ? ", invalid" : "", + m_capabilities.Gamepad.sThumbLX, + count_leading_ones_32(u32(u16(m_capabilities.Gamepad.sThumbLX)) << 16), + lsxcap_bad ? ", invalid" : "", + m_capabilities.Gamepad.sThumbLY, + count_leading_ones_32(u32(u16(m_capabilities.Gamepad.sThumbLY)) << 16), + lsycap_bad ? ", invalid" : "", + m_capabilities.Gamepad.sThumbRX, + count_leading_ones_32(u32(u16(m_capabilities.Gamepad.sThumbRX)) << 16), + rsxcap_bad ? ", invalid" : "", + m_capabilities.Gamepad.sThumbRY, + count_leading_ones_32(u32(u16(m_capabilities.Gamepad.sThumbRY)) << 16), + rsycap_bad ? ", invalid" : ""); + + // ignore capabilities if invalid + if (!probe_extended_type()) + { + bool ignore_caps = false; + if (ltcap_bad || rtcap_bad || lsxcap_bad || lsycap_bad || rsxcap_bad || rsycap_bad) + { + // Retro-Bit Sega Saturn Control Pad reports garbage for axis resolutions and absence of several buttons + osd_printf_verbose("XInput: Ignoring invalid capabilities (invalid axis resolution).\n"); + ignore_caps = true; + } + else if (!m_capabilities.Gamepad.wButtons && !m_capabilities.Gamepad.bLeftTrigger && !m_capabilities.Gamepad.bRightTrigger && !m_capabilities.Gamepad.sThumbLX && !m_capabilities.Gamepad.sThumbLY && !m_capabilities.Gamepad.sThumbRX && !m_capabilities.Gamepad.sThumbRY) + { + // 8BitDo SN30 Pro V1 reports no controls at all, which would be completely useless + osd_printf_verbose("XInput: Ignoring invalid capabilities (no controls reported).\n"); + ignore_caps = true; + } + if (ignore_caps) + { + m_capabilities.Gamepad.wButtons = 0xf3ff; + m_capabilities.Gamepad.bLeftTrigger = 0xff; + m_capabilities.Gamepad.bRightTrigger = 0xff; + m_capabilities.Gamepad.sThumbLX = s16(u16(0xffc0)); + m_capabilities.Gamepad.sThumbLY = s16(u16(0xffc0)); + m_capabilities.Gamepad.sThumbRX = s16(u16(0xffc0)); + m_capabilities.Gamepad.sThumbRY = s16(u16(0xffc0)); + } + } } + +bool xinput_device_base::read_state() +{ + // save previous packet number and try to read peripheral state + DWORD const prevpacket = m_xinput_state.dwPacketNumber; + HRESULT const result = m_xinput_helper.xinput_get_state(m_player_index, &m_xinput_state); + + // only update if it succeeded and the packed number changed + if (FAILED(result)) + { + return false; + } + else if (m_reset) + { + m_reset = false; + return true; + } + else + { + return prevpacket != m_xinput_state.dwPacketNumber; + } +} + + +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) + { + case 0x1430: + switch (m_capabilities.Gamepad.sThumbLY) + { + case 0x0705: + osd_printf_verbose("XInput: Detected Band Hero guitar controller.\n"); + m_capabilities.Gamepad.sThumbLX = s16(u16(0xffc0)); // neck slider + m_capabilities.Gamepad.sThumbLY = 0; + m_capabilities.Gamepad.sThumbRX = s16(u16(0xffc0)); // whammy bar + return true; + case 0x0805: + osd_printf_verbose("XInput: Detected Band Hero drum kit controller.\n"); + m_capabilities.Gamepad.sThumbLX = 0; + m_capabilities.Gamepad.sThumbLY = s16(u16(0xffc0)); // green/red velocity + m_capabilities.Gamepad.sThumbRX = s16(u16(0xffc0)); // blue/yellow velocity + return true; + case 0x1705: + osd_printf_verbose("XInput: Detected DJ Hero turntable controller.\n"); + m_capabilities.Gamepad.sThumbLX = 0; + m_capabilities.Gamepad.sThumbLY = s16(u16(0xffc0)); // turntable + m_capabilities.Gamepad.sThumbRX = s16(u16(0xffc0)); // effects dial + return true; + } + break; + case 0x1bad: + switch (m_capabilities.Gamepad.sThumbLY) + { + case 0x1330: + osd_printf_verbose("XInput: Detected Rock Band keyboard controller.\n"); + m_capabilities.Gamepad.sThumbLX = 0; // keys, velocity + m_capabilities.Gamepad.sThumbLY = 0; // not present? + m_capabilities.Gamepad.sThumbRX = 0; // not present? + return true; + } + break; + } + return false; +} + + + //============================================================ -// create_xinput_device +// general XInput controller handler //============================================================ -xinput_joystick_device * xinput_api_helper::create_xinput_device(running_machine &machine, UINT index, wininput_module &module) +class xinput_joystick_device : public xinput_device_base { - xinput_joystick_device *devinfo; +public: + xinput_joystick_device( + std::string &&name, + std::string &&id, + input_module &module, + u32 player, + XINPUT_CAPABILITIES const &caps, + xinput_api_helper const &helper); - XINPUT_CAPABILITIES caps = { 0 }; - if (FAILED(xinput_get_capabilities(index, 0, &caps))) + virtual void poll(bool relative_reset) override; + virtual void reset() override; + virtual void configure(input_device &device) override; + +private: + static inline constexpr USHORT SWITCH_BITS[] = { - // If we can't get the capabilities skip this device - return nullptr; + 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_LT, // for arcade stick/pad with LT/RT buttons + SWITCH_RT, + + SWITCH_TOTAL + }; + + enum + { + AXIS_LT, // half-axes for triggers + AXIS_RT, + + AXIS_LSX, // full-precision axes + AXIS_LSY, + AXIS_RSX, + AXIS_RSY, + + 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]; +}; + + +xinput_joystick_device::xinput_joystick_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_joystick_device::poll(bool relative_reset) +{ + // 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 the triggers onto the negative side of the axes + m_axes[AXIS_LT] = -normalize_absolute_axis(trigger_left(), -255, 255); + m_axes[AXIS_RT] = -normalize_absolute_axis(trigger_right(), -255, 255); + + // translate full-precision axes - Y direction is opposite to what MAME uses + m_axes[AXIS_LSX] = normalize_absolute_axis(thumb_left_x(), XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + m_axes[AXIS_LSY] = normalize_absolute_axis(-thumb_left_y(), XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + m_axes[AXIS_RSX] = normalize_absolute_axis(thumb_right_x(), XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + m_axes[AXIS_RSY] = normalize_absolute_axis(-thumb_right_y(), XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + + // 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; +} + + +void xinput_joystick_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_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; + char const *const *axis_names = AXIS_NAMES_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; + + // consider the device type to decide how to map controls + switch (device_type()) + { + case XINPUT_DEVTYPE_GAMEPAD: + 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 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; } - char device_name[16]; - snprintf(device_name, sizeof(device_name), "XInput Player %u", index + 1); + // 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); - // allocate the device object - devinfo = module.devicelist()->create_device<xinput_joystick_device>(machine, device_name, device_name, module, shared_from_this()); + // add bidirectional axes + bool const axis_caps[]{ + has_thumb_left_x(), + has_thumb_left_y(), + has_thumb_right_x(), + has_thumb_right_y() }; + for (unsigned i = 0; std::size(axis_caps) > i; ++i) + { + if (axis_caps[i]) + { + axis_ids[AXIS_LSX + i] = device.add_item( + axis_names[i], + std::string_view(), + preferred_axis_ids[i], + generic_axis_get_state<s32>, + &m_axes[AXIS_LSX + i]); + } + else + { + axis_ids[AXIS_LSX + i] = ITEM_ID_INVALID; + } + } - // Set the player ID - devinfo->xinput_state.player_index = index; + // 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) }; + for (unsigned i = 0; (SWITCH_DPAD_RIGHT - SWITCH_DPAD_UP) >= i; ++i) + { + if (hat_caps[i]) + { + 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 + generic_button_get_state<u8>, + &m_switches[SWITCH_DPAD_UP + i]); + } + } - // Assign the caps we captured earlier - devinfo->xinput_state.caps = caps; + // add buttons + std::pair<unsigned, bool> const button_caps[]{ + { SWITCH_A, has_button(XINPUT_GAMEPAD_A) }, + { SWITCH_B, has_button(XINPUT_GAMEPAD_B) }, + { SWITCH_X, has_button(XINPUT_GAMEPAD_X) }, + { SWITCH_Y, has_button(XINPUT_GAMEPAD_Y) }, + { SWITCH_LT, lt_rt_button && has_trigger_left() }, + { SWITCH_RT, lt_rt_button && has_trigger_right() }, + { SWITCH_LB, has_button(XINPUT_GAMEPAD_LEFT_SHOULDER) }, + { SWITCH_RB, has_button(XINPUT_GAMEPAD_RIGHT_SHOULDER) }, + { 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) + { + 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)) + { + 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] }); + } - return devinfo; + // add triggers/pedals + if (!lt_rt_button) + { + for (unsigned i = 0; (AXIS_RT - AXIS_LT) >= i; ++i) + { + if (i ? has_trigger_right() : has_trigger_left()) + { + axis_ids[AXIS_LT + i] = device.add_item( + axis_names[std::size(axis_caps) + i], + std::string_view(), + 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_joystick_device +// XInput flight stick handler //============================================================ -xinput_joystick_device::xinput_joystick_device(running_machine &machine, const char *name, char const *id, input_module &module, std::shared_ptr<xinput_api_helper> helper) - : device_info(machine, name, id, DEVICE_CLASS_JOYSTICK, module), - gamepad({{0}}), - xinput_state({0}), - m_xinput_helper(helper), - m_configured(false) +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(bool relative_reset) 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_joystick_device::poll() + +void xinput_flight_stick_device::poll(bool relative_reset) { - if (!m_configured) + // poll the device first, and skip if nothing changed + if (!read_state()) return; - // poll the device first - HRESULT result = m_xinput_helper->xinput_get_state(xinput_state.player_index, &xinput_state.xstate); + // translate button bits + for (unsigned i = 0; std::size(SWITCH_BITS) > i; ++i) + m_switches[SWITCH_A + i] = (buttons() & SWITCH_BITS[i]) ? 0xff : 0x00; - // If we can't poll the device, skip - if (FAILED(result)) - return; + // 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] }); + } - // Copy the XState into State - // Start with the POV (DPAD) - for (int povindex = 0; povindex < XINPUT_MAX_POV; povindex++) + // 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]) { - int currentPov = xinput_pov_dir[povindex]; - gamepad.povs[povindex] = (xinput_state.xstate.Gamepad.wButtons & currentPov) ? 0xFF : 0; + 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]))); } - // Now do the buttons - for (int buttonindex = 0; buttonindex < XINPUT_MAX_BUTTONS; buttonindex++) + // 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 { - int currentButton = xinput_buttons[buttonindex]; - gamepad.buttons[buttonindex] = (xinput_state.xstate.Gamepad.wButtons & currentButton) ? 0xFF : 0; + // 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]); + } } - // Now grab the axis values - // Each of the thumbstick axis members is a signed value between -32768 and 32767 describing the position of the thumbstick - // However, the Y axis values are inverted from what MAME expects, so negate the value - gamepad.left_thumb_x = normalize_absolute_axis(xinput_state.xstate.Gamepad.sThumbLX, XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); - gamepad.left_thumb_y = normalize_absolute_axis(-xinput_state.xstate.Gamepad.sThumbLY, XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); - gamepad.right_thumb_x = normalize_absolute_axis(xinput_state.xstate.Gamepad.sThumbRX, XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); - gamepad.right_thumb_y = normalize_absolute_axis(-xinput_state.xstate.Gamepad.sThumbRY, XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + // 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); - // Now the triggers, place them on half-axes (negative side) - gamepad.left_trigger = -normalize_absolute_axis(xinput_state.xstate.Gamepad.bLeftTrigger, -255, 255); - gamepad.right_trigger = -normalize_absolute_axis(xinput_state.xstate.Gamepad.bRightTrigger, -255, 255); + // set default assignments + device.set_default_assignments(std::move(assignments)); } -void xinput_joystick_device::reset() + + +//============================================================ +// XInput guitar handler +//============================================================ + +class xinput_guitar_device : public xinput_device_base { - memset(&gamepad, 0, sizeof(gamepad)); +public: + xinput_guitar_device( + std::string &&name, + std::string &&id, + input_module &module, + u32 player, + XINPUT_CAPABILITIES const &caps, + xinput_api_helper const &helper); + + virtual void poll(bool relative_reset) 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_Y, + XINPUT_GAMEPAD_X, + XINPUT_GAMEPAD_LEFT_SHOULDER, + XINPUT_GAMEPAD_LEFT_THUMB, + XINPUT_GAMEPAD_RIGHT_SHOULDER, + 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_FRET1, + SWITCH_FRET2, + SWITCH_FRET3, + SWITCH_FRET4, + SWITCH_FRET5, + SWITCH_FRET_MOD, // indicates a "solo fret" is pressed, not a separate button + SWITCH_RB, // often reported present but no physical button + SWITCH_RSB, // usually reported absent + SWITCH_START, + SWITCH_BACK, // also used for "Star Power" in some games + + SWITCH_DPAD_UP, // D-pad bits + SWITCH_DPAD_DOWN, + SWITCH_DPAD_LEFT, + SWITCH_DPAD_RIGHT, + + SWITCH_TOTAL + }; + + enum + { + AXIS_SLIDER, // LSX, positive toward bridge + AXIS_LSY, // not used on guitar controllers? + AXIS_WHAMMY, // RSX, single-ended, neutral at negative extreme + + AXIS_ORIENT_NECK, // RSY + AXIS_ORIENT_BRIDGE, // LT, toward zero with frets up + AXIS_ORIENT_BODY, // RT, toward zero in right-handed orientation + + AXIS_PICKUP, // LT, positive extreme at one end + + AXIS_TOTAL + }; + + u8 m_switches[SWITCH_TOTAL]; + s32 m_axes[AXIS_TOTAL]; +}; + + +xinput_guitar_device::xinput_guitar_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_guitar_device::poll(bool relative_reset) +{ + // 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_FRET1 + i] = (buttons() & SWITCH_BITS[i]) ? 0xff : 0x00; + + // translate miscellaneous axes + m_axes[AXIS_SLIDER] = normalize_absolute_axis(thumb_left_x(), XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + m_axes[AXIS_LSY] = normalize_absolute_axis(-thumb_left_y(), XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + m_axes[AXIS_WHAMMY] = -normalize_absolute_axis(s32(thumb_right_x()) + 32'768, -65'535, 65'535); + + // translate orientation sensors + m_axes[AXIS_ORIENT_NECK] = normalize_absolute_axis(-thumb_right_y(), XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + m_axes[AXIS_ORIENT_BRIDGE] = normalize_absolute_axis(trigger_left(), 0, 255); + m_axes[AXIS_ORIENT_BODY] = normalize_absolute_axis(trigger_right(), 0, 255); + + // translate pickup selector + m_axes[AXIS_PICKUP] = -normalize_absolute_axis(trigger_left(), -255, 255); +} + + +void xinput_guitar_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_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() }, + { ITEM_ID_RYAXIS, "LSY", has_thumb_left_y() }, + { ITEM_ID_SLIDER1, "Whammy Bar", has_thumb_right_x() }, + { ITEM_ID_YAXIS, "Neck Orientation", has_thumb_right_y() }, + { 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() } }; + 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) + { + axis_ids[i] = device.add_item( + name, + std::string_view(), + item, + generic_axis_get_state<s32>, + &m_axes[i]); + } + else + { + axis_ids[i] = ITEM_ID_INVALID; + } + } + + // add hats + for (unsigned i = 0; (SWITCH_DPAD_RIGHT - SWITCH_DPAD_UP) >= i; ++i) + { + if (has_button(SWITCH_BITS[SWITCH_DPAD_UP + i])) + { + 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 + 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_FRET5 - SWITCH_FRET1 + 1]; + for (unsigned i = 0; (SWITCH_RSB - SWITCH_FRET1) >= i; ++i) + { + if (has_button(SWITCH_BITS[i])) + { + 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)) + { + 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 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)); } -void xinput_joystick_device::configure() + + +//============================================================ +// XInput drum kit handler +//============================================================ + +class xinput_drumkit_device : public xinput_device_base { - std::lock_guard<std::mutex> scope_lock(m_device_lock); +public: + xinput_drumkit_device( + std::string &&name, + std::string &&id, + input_module &module, + u32 player, + XINPUT_CAPABILITIES const &caps, + xinput_api_helper const &helper); + + virtual void poll(bool relative_reset) 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_RIGHT_SHOULDER, + XINPUT_GAMEPAD_LEFT_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_GREEN, // button bits + SWITCH_RED, + SWITCH_BLUE, + SWITCH_YELLOW, + SWITCH_ORANGE, + SWITCH_BASS_DRUM, + SWITCH_LSB, + SWITCH_RSB, + SWITCH_START, + SWITCH_BACK, - if (m_configured) + SWITCH_DPAD_UP, // D-pad bits + SWITCH_DPAD_DOWN, + SWITCH_DPAD_LEFT, + SWITCH_DPAD_RIGHT, + + SWITCH_TOTAL + }; + + enum + { + AXIS_GREEN, // LSY low + AXIS_RED, // LSY high + AXIS_BLUE, // RSX high + AXIS_YELLOW, // RSX low + AXIS_ORANGE, // RSY low + AXIS_BASS_DRUM, // RSY high + + AXIS_TOTAL + }; + + u8 m_switches[SWITCH_TOTAL]; + s32 m_axes[AXIS_TOTAL]; +}; + + +xinput_drumkit_device::xinput_drumkit_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_drumkit_device::poll(bool relative_reset) +{ + // poll the device first, and skip if nothing changed + if (!read_state()) return; - // Add the axes - for (int axisnum = 0; axisnum < XINPUT_MAX_AXIS; axisnum++) + // translate button bits + for (unsigned i = 0; std::size(SWITCH_BITS) > i; ++i) + m_switches[SWITCH_GREEN + i] = (buttons() & SWITCH_BITS[i]) ? 0xff : 0x00; + + // translate axes + m_axes[AXIS_GREEN] = -normalize_absolute_axis(BIT(u16(thumb_left_y()), 0, 8), -255, 255); + m_axes[AXIS_RED] = -normalize_absolute_axis(BIT(u16(thumb_left_y()), 8, 8), -255, 255); + m_axes[AXIS_BLUE] = -normalize_absolute_axis(BIT(u16(thumb_right_x()), 8, 8), -255, 255); + m_axes[AXIS_YELLOW] = -normalize_absolute_axis(BIT(u16(thumb_right_x()), 0, 8), -255, 255); + m_axes[AXIS_ORANGE] = -normalize_absolute_axis(BIT(u16(thumb_right_y()), 0, 8), -255, 255); + m_axes[AXIS_BASS_DRUM] = -normalize_absolute_axis(BIT(u16(thumb_right_y()), 8, 8), -255, 255); +} + + +void xinput_drumkit_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_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() }, + { ITEM_ID_YAXIS, "Red Velocity", has_thumb_left_y() }, + { ITEM_ID_ZAXIS, "Blue Velocity", has_thumb_right_x() }, + { ITEM_ID_RXAXIS, "Yellow Velocity", has_thumb_right_x() }, + { ITEM_ID_RYAXIS, "Orange Velocity", has_thumb_right_y() }, + { ITEM_ID_RZAXIS, "Bass Drum Velocity", has_thumb_right_y() } }; + for (unsigned i = 0; (AXIS_BASS_DRUM - AXIS_GREEN) >= i; ++i) { - device()->add_item( - xinput_axis_name[axisnum], - xinput_axis_ids[axisnum], - generic_axis_get_state<LONG>, - &gamepad.left_thumb_x + axisnum); + auto const [item, name, supported] = axis_caps[i]; + if (supported) + { + device.add_item( + name, + std::string_view(), + item, + generic_axis_get_state<s32>, + &m_axes[AXIS_GREEN + i]); + } } - // Populate the POVs - // For XBOX, we treat the DPAD as a hat switch - for (int povnum = 0; povnum < XINPUT_MAX_POV; povnum++) + // add hats + for (unsigned i = 0; (SWITCH_DPAD_RIGHT - SWITCH_DPAD_UP) >= i; ++i) { - device()->add_item( - xinput_pov_names[povnum], - ITEM_ID_OTHER_SWITCH, - generic_button_get_state<BYTE>, - &gamepad.povs[povnum]); + if (has_button(SWITCH_BITS[SWITCH_DPAD_UP + 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]); + } } - // populate the buttons - for (int butnum = 0; butnum < XINPUT_MAX_BUTTONS; butnum++) + // add buttons + unsigned button_count = 0; + unsigned numbered_buttons[SWITCH_RSB - SWITCH_GREEN + 1]; + for (unsigned i = 0; (SWITCH_RSB - SWITCH_GREEN) >= i; ++i) { - device()->add_item( - xinput_button_names[butnum], - static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum), - generic_button_get_state<BYTE>, - &gamepad.buttons[butnum]); + if (has_button(SWITCH_BITS[i])) + { + switch_ids[SWITCH_GREEN + i] = device.add_item( + BUTTON_NAMES_DRUMKIT[i], + std::string_view(), + 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; + } } - device()->add_item( - "RT", - ITEM_ID_ZAXIS, - generic_axis_get_state<LONG>, - &gamepad.right_trigger); + // 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] }); + } - device()->add_item( - "LT", - ITEM_ID_RZAXIS, - generic_axis_get_state<LONG>, - &gamepad.left_trigger); + // 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]); - m_configured = true; + // 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)); } + + //============================================================ -// xinput_joystick_module +// XInput turntable handler //============================================================ -class xinput_joystick_module : public wininput_module +class xinput_turntable_device : public xinput_device_base { +public: + xinput_turntable_device( + std::string &&name, + std::string &&id, + input_module &module, + u32 player, + XINPUT_CAPABILITIES const &caps, + xinput_api_helper const &helper); + + virtual void poll(bool relative_reset) override; + virtual void reset() override; + virtual void configure(input_device &device) override; + private: - std::shared_ptr<xinput_api_helper> m_xinput_helper; + 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_GREEN, // RT bits + SWITCH_RED, + SWITCH_BLUE, + + SWITCH_TOTAL + }; + + enum + { + AXIS_TURNTABLE, // LSY + AXIS_EFFECT, // RSX + AXIS_CROSSFADE, // RSY + + AXIS_TOTAL + }; + + u8 m_switches[SWITCH_TOTAL]; + s32 m_axes[AXIS_TOTAL]; + u16 m_prev_effect; +}; + + +xinput_turntable_device::xinput_turntable_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), + m_prev_effect(0) +{ + std::fill(std::begin(m_switches), std::end(m_switches), 0); + std::fill(std::begin(m_axes), std::end(m_axes), 0); +} + + +void xinput_turntable_device::poll(bool relative_reset) +{ + // poll the device first + bool const was_reset = is_reset(); + if (read_state()) + { + // 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 RT bits + for (unsigned i = 0; (SWITCH_BLUE - SWITCH_GREEN) >= i; ++i) + m_switches[SWITCH_GREEN + i] = BIT(trigger_right(), i) ? 0xff : 0x00; + + // translate axes + m_axes[AXIS_TURNTABLE] = s32(thumb_left_y()) * input_device::RELATIVE_PER_PIXEL * 2; + m_axes[AXIS_CROSSFADE] = normalize_absolute_axis(thumb_right_y(), XINPUT_AXIS_MINVALUE, XINPUT_AXIS_MAXVALUE); + } + + // handle effect dial + if (was_reset) + { + // just grab the current count after regaining focus + m_prev_effect = u16(thumb_right_x()); + } + else if (relative_reset) + { + // convert value to relative displacement + s32 effect_delta = s32(u32(u16(thumb_right_x()))) - s32(u32(m_prev_effect)); + m_prev_effect = u16(thumb_right_x()); + if (0x8000 < effect_delta) + effect_delta -= 0x1'0000; + else if (-0x8000 > effect_delta) + effect_delta += 0x1'0000; + m_axes[AXIS_EFFECT] = effect_delta * input_device::RELATIVE_PER_PIXEL / 128; + } +} + + +void xinput_turntable_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_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 + 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]); + 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]); + input_item_id const crossfade_id = device.add_item( + "Crossfade", + std::string_view(), + ITEM_ID_XAXIS, + generic_axis_get_state<s32>, + &m_axes[AXIS_CROSSFADE]); + + // add hats + for (unsigned i = 0; (SWITCH_DPAD_RIGHT - SWITCH_DPAD_UP) >= i; ++i) + { + if (has_button(SWITCH_BITS[SWITCH_DPAD_UP + 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_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; + } + } + + // 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]); + switch_ids[SWITCH_RED] = device.add_item( + "Red", + std::string_view(), + button_id++, + generic_button_get_state<u8>, + &m_switches[SWITCH_RED]); + switch_ids[SWITCH_BLUE] = device.add_item( + "Blue", + std::string_view(), + button_id++, + generic_button_get_state<u8>, + &m_switches[SWITCH_BLUE]); + + // 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 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)); +} + + + +//============================================================ +// XInput keyboard handler +//============================================================ + +class xinput_keyboard_device : public xinput_device_base +{ public: - xinput_joystick_module() - : wininput_module(OSD_JOYSTICKINPUT_PROVIDER, "xinput"), - m_xinput_helper(nullptr) + xinput_keyboard_device( + std::string &&name, + std::string &&id, + input_module &module, + u32 player, + XINPUT_CAPABILITIES const &caps, + xinput_api_helper const &helper); + + virtual void poll(bool relative_reset) 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_C1, + SWITCH_C3 = SWITCH_C1 + 24, + + SWITCH_TOTAL + }; + + enum + { + AXIS_VELOCITY, // field in LSX + AXIS_PEDAL, // RSY, most positive value neutral + + AXIS_TOTAL + }; + + u8 m_switches[SWITCH_TOTAL]; + s32 m_axes[AXIS_TOTAL]; +}; + + +xinput_keyboard_device::xinput_keyboard_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_keyboard_device::poll(bool relative_reset) +{ + // TODO: how many bits are really velocity? + // TODO: how are touch strip and overdrive read? + + // 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 keys + for (unsigned i = 0; 8 > i; ++i) + { + m_switches[SWITCH_C1 + i] = BIT(trigger_left(), 7 - i) ? 0xff : 0x00; + m_switches[SWITCH_C1 + 8 + i] = BIT(trigger_right(), 7 - i) ? 0xff : 0x00; + m_switches[SWITCH_C1 + 16 + i] = BIT(thumb_left_x(), 7 - i) ? 0xff : 0x00; } + m_switches[SWITCH_C3] = BIT(thumb_left_x(), 15) ? 0xff : 0x00; - int init(const osd_options &options) override + // translate axes + m_axes[AXIS_VELOCITY] = -normalize_absolute_axis(BIT(thumb_left_x(), 8, 7), -127, 127); + m_axes[AXIS_PEDAL] = -normalize_absolute_axis(32'767 - thumb_right_y(), -32'767, 32'767); +} + + +void xinput_keyboard_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_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", + std::string_view(), + ITEM_ID_SLIDER1, + generic_axis_get_state<s32>, + &m_axes[AXIS_VELOCITY]); + 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])) + { + 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_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) + { + 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)) { + 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 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)); +} + + + +//============================================================ +// XInput joystick module +//============================================================ + +class xinput_joystick_module : public input_module_impl<device_info, osd_common_t> +{ +public: + xinput_joystick_module() : input_module_impl<device_info, osd_common_t>(OSD_JOYSTICKINPUT_PROVIDER, "xinput") + { + } + + virtual int init(osd_interface &osd, const osd_options &options) override + { + int status; + // Call the base - int status = wininput_module::init(options); + status = input_module_impl<device_info, osd_common_t>::init(osd, options); if (status != 0) return status; // Create and initialize our helper - m_xinput_helper = std::make_shared<xinput_api_helper>(); + m_xinput_helper = std::make_unique<xinput_api_helper>(); status = m_xinput_helper->initialize(); if (status != 0) { @@ -228,32 +3002,140 @@ public: return 0; } -protected: virtual void input_init(running_machine &machine) override { - xinput_joystick_device *devinfo; + input_module_impl<device_info, osd_common_t>::input_init(machine); // Loop through each gamepad to determine if they are connected for (UINT i = 0; i < XUSER_MAX_COUNT; i++) { - XINPUT_STATE state = {0}; + // allocate and link in a new device + auto devinfo = m_xinput_helper->create_xinput_device(i, *this); + if (devinfo) + add_device(DEVICE_CLASS_JOYSTICK, std::move(devinfo)); + } + } - if (m_xinput_helper->xinput_get_state(i, &state) == ERROR_SUCCESS) - { - // allocate and link in a new device - devinfo = m_xinput_helper->create_xinput_device(machine, i, *this); - if (devinfo == nullptr) - continue; + virtual void exit() override + { + input_module_impl<device_info, osd_common_t>::exit(); - // Configure each gamepad to add buttons and Axes, etc. - devinfo->configure(); - } - } + m_xinput_helper.reset(); } + +private: + std::unique_ptr<xinput_api_helper> m_xinput_helper; }; -#else -MODULE_NOT_SUPPORTED(xinput_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "xinput") -#endif +} // anonymous namespace + + + +int xinput_api_helper::initialize() +{ + m_xinput_dll = dynamic_module::open(XINPUT_LIBRARIES); + + XInputGetState = m_xinput_dll->bind<xinput_get_state_fn>("XInputGetState"); + XInputGetCapabilities = m_xinput_dll->bind<xinput_get_caps_fn>("XInputGetCapabilities"); + + if (!XInputGetState || !XInputGetCapabilities) + { + osd_printf_error("XInput: Could not find API functions.\n"); + return -1; + } + + return 0; +} + + +//============================================================ +// create_xinput_device +//============================================================ + +std::unique_ptr<device_info> xinput_api_helper::create_xinput_device( + UINT index, + input_module_base &module) +{ + // If we can't get the capabilities skip this device + XINPUT_STATE state{ 0 }; + if (xinput_get_state(index, &state) != ERROR_SUCCESS) + return nullptr; + XINPUT_CAPABILITIES caps{ 0 }; + if (FAILED(xinput_get_capabilities(index, 0, &caps))) + return nullptr; + + char device_name[16]; + snprintf(device_name, sizeof(device_name), "XInput Player %u", index + 1); + + // allocate specialised device objects + switch (caps.Type) + { + 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: + return std::make_unique<xinput_guitar_device>( + device_name, + device_name, + module, + index, + caps, + *this); + case XINPUT_DEVSUBTYPE_DRUM_KIT: + return std::make_unique<xinput_drumkit_device>( + device_name, + device_name, + module, + index, + caps, + *this); + case 0x0f: + return std::make_unique<xinput_keyboard_device>( + device_name, + device_name, + module, + index, + caps, + *this); + case 0x17: + return std::make_unique<xinput_turntable_device>( + device_name, + device_name, + module, + index, + caps, + *this); + } + } + + // create default general-purpose device + return std::make_unique<xinput_joystick_device>( + device_name, + device_name, + module, + index, + caps, + *this); +} + +} // namespace osd + +#else // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) + +#include "input_module.h" + +namespace osd { namespace { MODULE_NOT_SUPPORTED(xinput_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "xinput") } } + +#endif // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) -MODULE_DEFINITION(JOYSTICKINPUT_XINPUT, xinput_joystick_module) +MODULE_DEFINITION(JOYSTICKINPUT_XINPUT, osd::xinput_joystick_module) diff --git a/src/osd/modules/input/input_xinput.h b/src/osd/modules/input/input_xinput.h index 9772e941bb7..fa36b7a2622 100644 --- a/src/osd/modules/input/input_xinput.h +++ b/src/osd/modules/input/input_xinput.h @@ -1,111 +1,30 @@ -#ifndef INPUT_XINPUT_H_ -#define INPUT_XINPUT_H_ +// license:BSD-3-Clause +// copyright-holders:Brad Hughes, Vas Crabb +#ifndef MAME_OSD_INPUT_INPUT_XINPUT_H +#define MAME_OSD_INPUT_INPUT_XINPUT_H -#include <mutex> +#pragma once -#include "modules/lib/osdlib.h" - -#define XINPUT_MAX_POV 4 -#define XINPUT_MAX_BUTTONS 10 -#define XINPUT_MAX_AXIS 4 - -#define XINPUT_AXIS_MINVALUE -32767 -#define XINPUT_AXIS_MAXVALUE 32767 - -class xinput_joystick_device; -// default axis names -static const char *const xinput_axis_name[] = -{ - "LSX", - "LSY", - "RSX", - "RSY" -}; - -static const input_item_id xinput_axis_ids[] = -{ - ITEM_ID_XAXIS, - ITEM_ID_YAXIS, - ITEM_ID_RXAXIS, - ITEM_ID_RYAXIS -}; - -static const USHORT xinput_pov_dir[] = { - XINPUT_GAMEPAD_DPAD_UP, - XINPUT_GAMEPAD_DPAD_DOWN, - XINPUT_GAMEPAD_DPAD_LEFT, - XINPUT_GAMEPAD_DPAD_RIGHT -}; - -static const char *const xinput_pov_names[] = { - "DPAD Up", - "DPAD Down", - "DPAD Left", - "DPAD Right" -}; +#include "input_common.h" -static const USHORT xinput_buttons[] = { - XINPUT_GAMEPAD_A, - XINPUT_GAMEPAD_B, - XINPUT_GAMEPAD_X, - XINPUT_GAMEPAD_Y, - XINPUT_GAMEPAD_LEFT_SHOULDER, - XINPUT_GAMEPAD_RIGHT_SHOULDER, - XINPUT_GAMEPAD_START, - XINPUT_GAMEPAD_BACK, - XINPUT_GAMEPAD_LEFT_THUMB, - XINPUT_GAMEPAD_RIGHT_THUMB, -}; +#include "modules/lib/osdlib.h" -static const char *const xinput_button_names[] = { - "A", - "B", - "X", - "Y", - "LB", - "RB", - "Start", - "Back", - "LS", - "RS" -}; +#include <memory> -struct gamepad_state -{ - BYTE buttons[XINPUT_MAX_BUTTONS]; - BYTE povs[XINPUT_MAX_POV]; - LONG left_trigger; - LONG right_trigger; - LONG left_thumb_x; - LONG left_thumb_y; - LONG right_thumb_x; - LONG right_thumb_y; -}; +#include <windows.h> +#include <xinput.h> -// state information for a gamepad; state must be first element -struct xinput_api_state -{ - uint32_t player_index; - XINPUT_STATE xstate; - XINPUT_CAPABILITIES caps; -}; -// Typedefs for dynamically loaded functions -typedef DWORD (WINAPI *xinput_get_state_fn)(DWORD, XINPUT_STATE *); -typedef DWORD (WINAPI *xinput_get_caps_fn)(DWORD, DWORD, XINPUT_CAPABILITIES *); +namespace osd { -class xinput_api_helper : public std::enable_shared_from_this<xinput_api_helper> +class xinput_api_helper { public: - xinput_api_helper() - : m_xinput_dll(nullptr), - XInputGetState(nullptr), - XInputGetCapabilities(nullptr) - { - } + xinput_api_helper() { } int initialize(); - xinput_joystick_device * create_xinput_device(running_machine &machine, UINT index, wininput_module &module); + + std::unique_ptr<device_info> create_xinput_device(UINT index, input_module_base &module); DWORD xinput_get_state(DWORD dwUserindex, XINPUT_STATE *pState) const { @@ -118,28 +37,15 @@ public: } private: - osd::dynamic_module::ptr m_xinput_dll; - xinput_get_state_fn XInputGetState; - xinput_get_caps_fn XInputGetCapabilities; -}; - -class xinput_joystick_device : public device_info -{ -public: - gamepad_state gamepad; - xinput_api_state xinput_state; - -private: - std::shared_ptr<xinput_api_helper> m_xinput_helper; - std::mutex m_device_lock; - bool m_configured; - -public: - xinput_joystick_device(running_machine &machine, const char *name, const char *id, input_module &module, std::shared_ptr<xinput_api_helper> helper); + // Typedefs for dynamically loaded functions + typedef DWORD (WINAPI *xinput_get_state_fn)(DWORD, XINPUT_STATE *); + typedef DWORD (WINAPI *xinput_get_caps_fn)(DWORD, DWORD, XINPUT_CAPABILITIES *); - void poll() override; - void reset() override; - void configure(); + dynamic_module::ptr m_xinput_dll = nullptr; + xinput_get_state_fn XInputGetState = nullptr; + xinput_get_caps_fn XInputGetCapabilities = nullptr; }; -#endif +} // namespace osd + +#endif // MAME_OSD_INPUT_INPUT_XINPUT_H |