// license:BSD-3-Clause // copyright-holders:Vas Crabb, Aaron Giles #include "emu.h" #include "iptseqpoll.h" #include "inputdev.h" #include #include input_code_poller::input_code_poller(input_manager &manager) noexcept : m_manager(manager), m_axis_memory(), m_switch_memory() { } input_code_poller::~input_code_poller() { } void input_code_poller::reset() { // iterate over device classes and devices m_axis_memory.clear(); m_switch_memory.clear(); for (input_device_class classno = DEVICE_CLASS_FIRST_VALID; DEVICE_CLASS_LAST_VALID >= classno; ++classno) { input_class &devclass(m_manager.device_class(classno)); if (devclass.enabled()) { for (int devnum = 0; devclass.maxindex() >= devnum; ++devnum) { // fetch the device; ignore if nullptr input_device *const device(devclass.device(devnum)); if (device) { // iterate over items within each device for (input_item_id itemid = ITEM_ID_FIRST_VALID; device->maxitem() >= itemid; ++itemid) { // for any non-switch items, set memory to the current value input_device_item *const item(device->item(itemid)); if (item && (item->itemclass() != ITEM_CLASS_SWITCH)) m_axis_memory.emplace_back(item, m_manager.code_value(item->code())); } } } } } std::sort(m_axis_memory.begin(), m_axis_memory.end()); } bool input_code_poller::code_pressed_once(input_code code, bool moved) { // look for the code in the memory bool const pressed(m_manager.code_pressed(code)); auto const found(std::lower_bound(m_switch_memory.begin(), m_switch_memory.end(), code)); if ((m_switch_memory.end() != found) && (*found == code)) { // if no longer pressed, clear entry if (!pressed) m_switch_memory.erase(found); // always return false return false; } // if we get here, we were not previously pressed; if still not pressed, return false if (!pressed || !moved) return false; // otherwise, add the code to the memory and return true m_switch_memory.emplace(found, code); return true; } axis_code_poller::axis_code_poller(input_manager &manager) noexcept : input_code_poller(manager), m_axis_active() { } void axis_code_poller::reset() { input_code_poller::reset(); m_axis_active.clear(); m_axis_active.resize(m_axis_memory.size(), false); } input_code axis_code_poller::poll() { // iterate over the axis items we found for (std::size_t i = 0; m_axis_memory.size() > i; ++i) { auto &memory = m_axis_memory[i]; input_code code = memory.first->code(); if (!memory.first->check_axis(code.item_modifier(), memory.second)) { m_axis_active[i] = false; } else if (!m_axis_active[i]) { if (code.item_class() == ITEM_CLASS_ABSOLUTE) { m_axis_active[i] = true; } else { // can only cycle modifiers on a relative item with append m_axis_memory.erase(m_axis_memory.begin() + i); m_axis_active.erase(m_axis_active.begin() + i); } if (!m_manager.device_class(memory.first->device().devclass()).multi()) code.set_device_index(0); return code; } } // iterate over device classes and devices, skipping disabled classes for (input_device_class classno = DEVICE_CLASS_FIRST_VALID; DEVICE_CLASS_LAST_VALID >= classno; ++classno) { input_class &devclass(m_manager.device_class(classno)); if (!devclass.enabled()) continue; for (int devnum = 0; devclass.maxindex() >= devnum; ++devnum) { // fetch the device; ignore if nullptr input_device *const device(devclass.device(devnum)); if (!device) continue; // iterate over items within each device for (input_item_id itemid = ITEM_ID_FIRST_VALID; device->maxitem() >= itemid; ++itemid) { input_device_item *const item(device->item(itemid)); if (!item) continue; input_code code = item->code(); if (item->itemclass() == ITEM_CLASS_SWITCH) { // item is natively a switch, poll it if (code_pressed_once(code, true)) return code; else continue; } } } } // if nothing, return an invalid code return INPUT_CODE_INVALID; } switch_code_poller::switch_code_poller(input_manager &manager) noexcept : input_code_poller(manager) { } input_code switch_code_poller::poll() { // iterate over device classes and devices, skipping disabled classes for (input_device_class classno = DEVICE_CLASS_FIRST_VALID; DEVICE_CLASS_LAST_VALID >= classno; ++classno) { input_class &devclass(m_manager.device_class(classno)); if (!devclass.enabled()) continue; for (int devnum = 0; devclass.maxindex() >= devnum; ++devnum) { // fetch the device; ignore if nullptr input_device *const device(devclass.device(devnum)); if (!device) continue; // iterate over items within each device for (input_item_id itemid = ITEM_ID_FIRST_VALID; device->maxitem() >= itemid; ++itemid) { input_device_item *const item(device->item(itemid)); if (!item) continue; input_code code = item->code(); if (item->itemclass() == ITEM_CLASS_SWITCH) { // item is natively a switch, poll it if (code_pressed_once(code, true)) return code; else continue; } auto const memory(std::lower_bound( m_axis_memory.begin(), m_axis_memory.end(), item, [] (auto const &x, auto const &y) { return x.first < y; })); if ((m_axis_memory.end() == memory) || (item != memory->first)) continue; // poll axes digitally bool const moved(item->check_axis(code.item_modifier(), memory->second)); code.set_item_class(ITEM_CLASS_SWITCH); if ((classno == DEVICE_CLASS_JOYSTICK) && (code.item_id() == ITEM_ID_XAXIS)) { // joystick X axis - check with left/right modifiers code.set_item_modifier(ITEM_MODIFIER_LEFT); if (code_pressed_once(code, moved)) return code; code.set_item_modifier(ITEM_MODIFIER_RIGHT); if (code_pressed_once(code, moved)) return code; } else if ((classno == DEVICE_CLASS_JOYSTICK) && (code.item_id() == ITEM_ID_YAXIS)) { // if this is a joystick Y axis, check with up/down modifiers code.set_item_modifier(ITEM_MODIFIER_UP); if (code_pressed_once(code, moved)) return code; code.set_item_modifier(ITEM_MODIFIER_DOWN); if (code_pressed_once(code, moved)) return code; } else { // any other axis, check with pos/neg modifiers code.set_item_modifier(ITEM_MODIFIER_POS); if (code_pressed_once(code, moved)) return code; code.set_item_modifier(ITEM_MODIFIER_NEG); if (code_pressed_once(code, moved)) return code; } } } } // if nothing, return an invalid code return INPUT_CODE_INVALID; } keyboard_code_poller::keyboard_code_poller(input_manager &manager) noexcept : input_code_poller(manager) { } input_code keyboard_code_poller::poll() { // iterate over devices in keyboard class input_class &devclass = m_manager.device_class(DEVICE_CLASS_KEYBOARD); for (int devnum = 0; devclass.maxindex() >= devnum; ++devnum) { // fetch the device; ignore if nullptr input_device *const device(devclass.device(devnum)); if (device) { // iterate over items within each device for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); ++itemid) { // iterate over items within each device for (input_item_id itemid = ITEM_ID_FIRST_VALID; device->maxitem() >= itemid; ++itemid) { input_device_item *const item = device->item(itemid); if (item && (item->itemclass() == ITEM_CLASS_SWITCH)) { input_code const code = item->code(); if (code_pressed_once(code, true)) return code; } } } } } // if nothing, return an invalid code return INPUT_CODE_INVALID; } input_sequence_poller::input_sequence_poller() noexcept : m_sequence(), m_last_ticks(0), m_modified(false) { } input_sequence_poller::~input_sequence_poller() { } void input_sequence_poller::start() { // start with an empty sequence m_sequence.reset(); // reset the recording count and the clock m_last_ticks = 0; m_modified = false; do_start(); } void input_sequence_poller::start(input_seq const &startseq) { // grab the starting sequence to append to, and append an OR if it isn't empty m_sequence = startseq; if (input_seq::end_code != m_sequence[0]) m_sequence += input_seq::or_code; // reset the recording count and the clock m_last_ticks = 0; m_modified = false; do_start(); } bool input_sequence_poller::poll() { // if we got a new code to append it, append it and reset the timer input_code const newcode = do_poll(); osd_ticks_t const newticks = osd_ticks(); if (INPUT_CODE_INVALID != newcode) { m_sequence += newcode; m_last_ticks = newticks; m_modified = true; } // if we've recorded at least one item and one second has passed, we're done if (m_last_ticks && ((m_last_ticks + osd_ticks_per_second()) < newticks)) return true; // return false to indicate we are still polling return false; } axis_sequence_poller::axis_sequence_poller(input_manager &manager) noexcept : input_sequence_poller(), m_code_poller(manager) { } void axis_sequence_poller::do_start() { // wait for any inputs that are already active to be released m_code_poller.reset(); for (input_code dummycode = KEYCODE_ENTER; INPUT_CODE_INVALID != dummycode; ) dummycode = m_code_poller.poll(); } input_code axis_sequence_poller::do_poll() { // absolute/relative case: see if we have an analog change of sufficient amount int const curlen = m_sequence.length(); input_code lastcode = m_sequence[curlen - 1]; bool const has_or = input_seq::or_code == lastcode; if (has_or) lastcode = m_sequence[curlen - 2]; input_code newcode = m_code_poller.poll(); // if not empty, see if it's the same control again to cycle modifiers if ((INPUT_CODE_INVALID != newcode) && curlen) { input_item_class const newclass = newcode.item_class(); input_code last_nomodifier = lastcode; last_nomodifier.set_item_modifier(ITEM_MODIFIER_NONE); if (newcode == last_nomodifier) { if (ITEM_CLASS_ABSOLUTE == newclass) { // increment the modifier, wrapping back to none switch (lastcode.item_modifier()) { case ITEM_MODIFIER_NONE: newcode.set_item_modifier(ITEM_MODIFIER_POS); break; case ITEM_MODIFIER_POS: newcode.set_item_modifier(ITEM_MODIFIER_NEG); break; case ITEM_MODIFIER_NEG: newcode.set_item_modifier(ITEM_MODIFIER_REVERSE); break; default: case ITEM_MODIFIER_REVERSE: newcode.set_item_modifier(ITEM_MODIFIER_NONE); break; } // back up over the previous code so we can re-append if (has_or) m_sequence.backspace(); m_sequence.backspace(); } else if (ITEM_CLASS_RELATIVE == newclass) { // increment the modifier, wrapping back to none switch (lastcode.item_modifier()) { case ITEM_MODIFIER_NONE: newcode.set_item_modifier(ITEM_MODIFIER_REVERSE); break; default: case ITEM_MODIFIER_REVERSE: newcode.set_item_modifier(ITEM_MODIFIER_NONE); break; } // back up over the previous code so we can re-append if (has_or) m_sequence.backspace(); m_sequence.backspace(); } else if (!has_or && (ITEM_CLASS_SWITCH == newclass)) { // back up over the existing code m_sequence.backspace(); // if there was a NOT preceding it, delete it as well, otherwise append a fresh one if (m_sequence[curlen - 2] == input_seq::not_code) m_sequence.backspace(); else m_sequence += input_seq::not_code; } } else if (!has_or && (ITEM_CLASS_SWITCH == newclass)) { // ignore switches following axes if (ITEM_CLASS_SWITCH != lastcode.item_class()) { // hack to stop it timing out so user can cancel m_sequence.backspace(); newcode = lastcode; } } } // hack to stop it timing out before assigning an axis if (ITEM_CLASS_SWITCH == newcode.item_class()) { m_sequence += newcode; set_modified(); return INPUT_CODE_INVALID; } else { return newcode; } } switch_sequence_poller::switch_sequence_poller(input_manager &manager) noexcept : input_sequence_poller(), m_code_poller(manager) { } void switch_sequence_poller::do_start() { // wait for any inputs that are already active to be released m_code_poller.reset(); for (input_code dummycode = KEYCODE_ENTER; INPUT_CODE_INVALID != dummycode; ) dummycode = m_code_poller.poll(); } input_code switch_sequence_poller::do_poll() { // switch case: see if we have a new code to process int const curlen = m_sequence.length(); input_code lastcode = m_sequence[curlen - 1]; input_code newcode = m_code_poller.poll(); if (INPUT_CODE_INVALID != newcode) { // if code is duplicate, toggle the NOT state on the code if (curlen && (newcode == lastcode)) { // back up over the existing code m_sequence.backspace(); // if there was a NOT preceding it, delete it as well, otherwise append a fresh one if (m_sequence[curlen - 2] == input_seq::not_code) m_sequence.backspace(); else m_sequence += input_seq::not_code; } } return newcode; }