diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index 9fa0c0e..addc95e 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -578,6 +578,7 @@ set(SOURCE_FILES ${SOURCE_FILES} misc/device.cpp misc/eamuse.cpp misc/extdev.cpp + misc/hotkeys.cpp misc/sciunit.cpp misc/sde.cpp misc/wintouchemu.cpp diff --git a/src/spice2x/hooks/graphics/backends/d3d11/d3d11_swapchain.cpp b/src/spice2x/hooks/graphics/backends/d3d11/d3d11_swapchain.cpp index c7a8b00..6b00a48 100644 --- a/src/spice2x/hooks/graphics/backends/d3d11/d3d11_swapchain.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d11/d3d11_swapchain.cpp @@ -23,10 +23,7 @@ #include "external/imgui/backends/imgui_impl_dx11.h" #include "overlay/imgui/impl_spice.h" -#include "games/io.h" #include "hooks/graphics/graphics.h" -#include "launcher/launcher.h" -#include "misc/eamuse.h" #include "util/utils.h" // -------------------------------------------------------------------------- @@ -149,26 +146,12 @@ void try_create_overlay(IDXGISwapChain *swapchain) { device->Release(); } -// rising-edge screenshot hotkey poll (mirrors d3d9 backend behaviour). -void poll_screenshot_hotkey() { - static bool s_down = false; - auto buttons = games::get_buttons_overlay(eamuse_get_game()); - const bool pressed = buttons - && (!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered()) - && GameAPI::Buttons::getState(RI_MGR, - buttons->at(games::OverlayButtons::Screenshot)); - if (pressed && !s_down) { - graphics_screenshot_trigger(); - } - s_down = pressed; -} - void pump_overlay(IDXGISwapChain *swapchain) { if (!overlay::OVERLAY || !overlay::OVERLAY->uses_swapchain(swapchain)) { return; } - poll_screenshot_hotkey(); + graphics_poll_screenshot_hotkey(); // size imgui to the backbuffer (not window client). dxgi may upscale // a small backbuffer into a larger client rect; without this override diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp index 17755d1..1bf5a60 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp @@ -1522,7 +1522,7 @@ void graphics_d3d9_on_present( wintouchemu::update(); } - graphics_d3d9_poll_screenshot_hotkey(); + graphics_poll_screenshot_hotkey(); graphics_d3d9_process_screenshot_and_capture(device, SUB_SWAP_CHAIN); } diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_screenshot.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_screenshot.cpp index 4b53643..aa94645 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_screenshot.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_screenshot.cpp @@ -13,13 +13,9 @@ #endif #include "avs/game.h" -#include "games/io.h" #include "hooks/graphics/graphics.h" -#include "launcher/launcher.h" #include "misc/clipboard.h" -#include "misc/eamuse.h" #include "overlay/notifications.h" -#include "overlay/overlay.h" #include "util/fileutils.h" #include "util/libutils.h" #include "util/logging.h" @@ -240,21 +236,6 @@ static void save_screenshot( } } -void graphics_d3d9_poll_screenshot_hotkey() { - static bool trigger_last = false; - auto buttons = games::get_buttons_overlay(eamuse_get_game()); - if (buttons && (!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered()) && - GameAPI::Buttons::getState(RI_MGR, buttons->at(games::OverlayButtons::Screenshot))) - { - if (!trigger_last) { - graphics_screenshot_trigger(); - } - trigger_last = true; - } else { - trigger_last = false; - } -} - static std::optional acquire_backbuffer_copy( IDirect3DDevice9 *device, IDirect3DSwapChain9 *sub_swap_chain, int screen) { diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_screenshot.h b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_screenshot.h index efa9096..50ac633 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_screenshot.h +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_screenshot.h @@ -2,8 +2,6 @@ #include -void graphics_d3d9_poll_screenshot_hotkey(); - void graphics_d3d9_process_screenshot_and_capture( IDirect3DDevice9 *device, IDirect3DSwapChain9 *sub_swap_chain); diff --git a/src/spice2x/hooks/graphics/graphics.cpp b/src/spice2x/hooks/graphics/graphics.cpp index 7e91ad4..0416c66 100644 --- a/src/spice2x/hooks/graphics/graphics.cpp +++ b/src/spice2x/hooks/graphics/graphics.cpp @@ -17,11 +17,13 @@ #include "games/ddr/ddr.h" #include "games/gitadora/gitadora.h" #include "games/iidx/iidx.h" +#include "games/io.h" #include "games/sdvx/sdvx.h" #include "games/popn/popn.h" #include "hooks/graphics/backends/d3d9/d3d9_backend.h" #include "hooks/graphics/backends/d3d11/d3d11_backend.h" #include "launcher/shutdown.h" +#include "misc/hotkeys.h" #include "overlay/overlay.h" #include "touch/touch.h" #include "touch/touch_gestures.h" @@ -1374,6 +1376,12 @@ void graphics_screens_get(std::vector &screens) { screens.insert(screens.end(), GRAPHICS_SCREENS.begin(), GRAPHICS_SCREENS.end()); } +void graphics_poll_screenshot_hotkey() { + if (hotkeys::consume_screenshot()) { + graphics_screenshot_trigger(); + } +} + void graphics_screenshot_trigger() { GRAPHICS_SCREENSHOT_TRIGGER = true; } diff --git a/src/spice2x/hooks/graphics/graphics.h b/src/spice2x/hooks/graphics/graphics.h index 22663bd..75376c0 100644 --- a/src/spice2x/hooks/graphics/graphics.h +++ b/src/spice2x/hooks/graphics/graphics.h @@ -134,6 +134,7 @@ void graphics_hook_subscreen_window(HWND hWnd); void graphics_screens_register(int screen); void graphics_screens_unregister(int screen); void graphics_screens_get(std::vector &screens); +void graphics_poll_screenshot_hotkey(); void graphics_screenshot_trigger(); bool graphics_screenshot_consume(); void graphics_capture_trigger(int screen); diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index c768ee7..00fdf57 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -87,13 +87,13 @@ #include "launcher/launcher.h" #include "launcher/logger.h" #include "launcher/signal.h" -#include "launcher/superexit.h" #include "launcher/richpresence.h" #include "launcher/shutdown.h" #include "launcher/options.h" #include "misc/bt5api.h" #include "misc/device.h" #include "misc/eamuse.h" +#include "misc/hotkeys.h" #include "misc/extdev.h" #include "misc/ami2000.h" #include "misc/sciunit.h" @@ -1777,9 +1777,8 @@ int main_implementation(int argc, char *argv[]) { nvapi::initialize(); // add application profile to nvcp nvapi::set_profile_settings(); - // enable super exit - superexit::enable(); - + // keep ALT+F4 available during lengthy non-standalone boot + hotkeys::start(); // enable subscreen touch emulation if (options[launcher::Options::spice2x_IIDXEmulateSubscreenKeypadTouch].is_active()) { games::iidx::ENABLE_POKE = true; @@ -2452,6 +2451,7 @@ int main_implementation(int argc, char *argv[]) { // initialize raw input RI_MGR = std::make_unique(); + hotkeys::enable_raw_input(); for (const auto &device : sextet_devices) { RI_MGR->sextet_register(device); } @@ -2474,6 +2474,9 @@ int main_implementation(int argc, char *argv[]) { log_misc("rawinput", "Analog mappings:"); dump_analog_bindings(); + // mappings are ready; begin screenshot and coin polling during late startup + hotkeys::enable_input(); + // for certain games, show cursor if no touch is available (must be called after RI_MGR is available) if (show_cursor_if_no_touch && !is_touch_available("launcher::main_implementation")) { GRAPHICS_SHOW_CURSOR = true; @@ -2703,9 +2706,6 @@ int main_implementation(int argc, char *argv[]) { API_CONTROLLER->listen_serial(api_serial_port[i], api_serial_baud[i]); } - // start coin input thread - eamuse_coin_start_thread(); - // pin macro if (!cfg::CONFIGURATOR_STANDALONE && PIN_MACRO_ENABLED) { eamuse_pin_macro_start_thread(); @@ -2755,6 +2755,9 @@ int main_implementation(int argc, char *argv[]) { log_info("launcher", "calling game entry"); avs::game::entry_main(); + // stop screenshot and coin polling; mapped SuperExit and ALT+F4 remain active + hotkeys::disable_input(); + // clear presence richpresence::shutdown(); @@ -2792,9 +2795,6 @@ int main_implementation(int argc, char *argv[]) { // free api controller API_CONTROLLER.reset(); - // stop coin input thread - eamuse_coin_stop_thread(); - eamuse_pin_macro_stop_thread(); // BT5API @@ -2805,6 +2805,7 @@ int main_implementation(int argc, char *argv[]) { sdk::fini_sdk_modules(); // stop raw input + hotkeys::disable_raw_input(); RI_MGR.reset(); // debug hook @@ -2837,8 +2838,8 @@ int main_implementation(int argc, char *argv[]) { // dispose crypt crypt::dispose(); - // disable super exit - superexit::disable(); + // end early/late ALT+F4 monitoring at the same teardown point as legacy SuperExit + hotkeys::stop(); // disable poke games::iidx::poke::disable(); diff --git a/src/spice2x/launcher/superexit.cpp b/src/spice2x/launcher/superexit.cpp index 5219905..9db98a1 100644 --- a/src/spice2x/launcher/superexit.cpp +++ b/src/spice2x/launcher/superexit.cpp @@ -1,21 +1,14 @@ #include "superexit.h" -#include - #include "windows.h" + +#include "cfg/configurator.h" #include "launcher/shutdown.h" -#include "rawinput/rawinput.h" -#include "util/logging.h" #include "touch/touch.h" -#include "misc/eamuse.h" -#include "games/io.h" -#include "overlay/overlay.h" +#include "util/logging.h" namespace superexit { - static std::thread *THREAD = nullptr; - static bool THREAD_RUNNING = false; - bool has_focus() { HWND fg_wnd = GetForegroundWindow(); if (fg_wnd == NULL) { @@ -29,92 +22,22 @@ namespace superexit { return fg_pid == GetCurrentProcessId(); } - void enable() { - - // check if already running - if (THREAD) - return; - - // create new thread - THREAD_RUNNING = true; - THREAD = new std::thread([] { - - // log - log_info("superexit", "enabled"); - - // set variable to false to stop - while (THREAD_RUNNING) { - - // check rawinput for ALT+F4 - bool rawinput_exit = false; - if (RI_MGR != nullptr) { - auto devices = RI_MGR->devices_get(); - for (auto &device : devices) { - switch (device.type) { - case rawinput::KEYBOARD: { - auto &key_states = device.keyboardInfo->key_states; - for (int page_index = 0; page_index < 1024; page_index += 256) { - if (key_states[page_index + VK_MENU] - && key_states[page_index + VK_F4]) { - rawinput_exit = true; - } - } - break; - } - default: - break; - } - } - } - - bool async_key_exit = - (GetAsyncKeyState(VK_MENU) & 0x8000) != 0 && - (GetAsyncKeyState(VK_F4) & 0x8000) != 0; - - bool overlay_exit = false; - auto buttons = games::get_buttons_overlay(eamuse_get_game()); - if (buttons && - (!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered()) && - GameAPI::Buttons::getState(RI_MGR, buttons->at(games::OverlayButtons::SuperExit))) { - overlay_exit = true; - } - - // check for exit - if (rawinput_exit || async_key_exit) { - if (has_focus()) { - log_info("superexit", "detected ALT+F4, exiting..."); - launcher::shutdown(); - } - } - if (overlay_exit) { - if (has_focus()) { - log_info("superexit", "detected Force Exit Game overlay shortcut, exiting..."); - launcher::shutdown(); - } - } - - // slow down - Sleep(100); - } - - return nullptr; - }); - } - - void disable() { - if (!THREAD) { + void handle_hotkeys(bool alt_f4, bool mapped_exit) { + if (!alt_f4 && !mapped_exit) { return; } - - // stop old thread - THREAD_RUNNING = false; - THREAD->join(); - - // delete thread - delete THREAD; - THREAD = nullptr; - - // log - log_info("superexit", "disabled"); + if (cfg::CONFIGURATOR_STANDALONE) { + return; + } + if (!has_focus()) { + return; + } + if (alt_f4) { + log_info("superexit", "detected ALT+F4, exiting..."); + launcher::shutdown(); + return; + } + log_info("superexit", "detected Force Exit Game overlay shortcut, exiting..."); + launcher::shutdown(); } } diff --git a/src/spice2x/launcher/superexit.h b/src/spice2x/launcher/superexit.h index 193727a..575cd01 100644 --- a/src/spice2x/launcher/superexit.h +++ b/src/spice2x/launcher/superexit.h @@ -2,6 +2,5 @@ namespace superexit { bool has_focus(); - void enable(); - void disable(); + void handle_hotkeys(bool alt_f4, bool mapped_exit); } diff --git a/src/spice2x/misc/eamuse.cpp b/src/spice2x/misc/eamuse.cpp index a89d2ad..7e054f5 100644 --- a/src/spice2x/misc/eamuse.cpp +++ b/src/spice2x/misc/eamuse.cpp @@ -25,10 +25,8 @@ static double CARD_INSERT_TIME[2] = {0, 0}; static double CARD_INSERT_TIMEOUT = 2.0; static char CARD_INSERT_UID[2][8] = {{0}, {0}}; static char CARD_INSERT_UID_ENABLE[2] = {false, false}; -static int COIN_STOCK = 0; -static bool COIN_BLOCK = false; -static std::thread *COIN_INPUT_THREAD; -static bool COIN_INPUT_THREAD_ACTIVE = false; +static std::atomic_int COIN_STOCK {0}; +static std::atomic_bool COIN_BLOCK {false}; static uint16_t KEYPAD_STATE[] = {0, 0}; static uint16_t KEYPAD_STATE_OVERRIDES[] = {0, 0}; static uint16_t KEYPAD_STATE_OVERRIDES_BT5[] = {0, 0}; @@ -292,78 +290,49 @@ bool eamuse_card_insert_consume(int active_count, int unit_id) { } bool eamuse_coin_get_block() { - return COIN_BLOCK; + return COIN_BLOCK.load(std::memory_order_relaxed); } void eamuse_coin_set_block(bool block) { - COIN_BLOCK = block; + COIN_BLOCK.store(block, std::memory_order_relaxed); } int eamuse_coin_get_stock() { - return COIN_STOCK; + return COIN_STOCK.load(std::memory_order_relaxed); } void eamuse_coin_set_stock(int amount) { - COIN_STOCK = amount; + COIN_STOCK.store(amount, std::memory_order_relaxed); } bool eamuse_coin_consume(int amount) { - if (COIN_STOCK < amount) { - return false; - } else { - COIN_STOCK -= amount; - return true; + auto stock = COIN_STOCK.load(std::memory_order_relaxed); + while (stock >= amount) { + if (COIN_STOCK.compare_exchange_weak( + stock, + stock - amount, + std::memory_order_relaxed)) { + return true; + } } + return false; } int eamuse_coin_consume_stock() { - int stock = COIN_STOCK; - COIN_STOCK = 0; - return stock; + return COIN_STOCK.exchange(0, std::memory_order_relaxed); } int eamuse_coin_add() { - return ++COIN_STOCK; + return COIN_STOCK.fetch_add(1, std::memory_order_relaxed) + 1; } -void eamuse_coin_start_thread() { - - // set active - COIN_INPUT_THREAD_ACTIVE = true; - - // create thread - COIN_INPUT_THREAD = new std::thread([]() { - auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game()); - static bool COIN_INPUT_KEY_STATE = false; - while (COIN_INPUT_THREAD_ACTIVE) { - - // check input key - if (overlay_buttons && GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at( - games::OverlayButtons::InsertCoin))) { - if (!COIN_INPUT_KEY_STATE) { - if (COIN_BLOCK) - log_info("eamuse", "coin inserted while blocked"); - else { - log_info("eamuse", "coin insert"); - COIN_STOCK++; - } - } - COIN_INPUT_KEY_STATE = true; - } else { - COIN_INPUT_KEY_STATE = false; - } - - // once every two frames - std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 30)); - } - }); -} - -void eamuse_coin_stop_thread() { - COIN_INPUT_THREAD_ACTIVE = false; - COIN_INPUT_THREAD->join(); - delete COIN_INPUT_THREAD; - COIN_INPUT_THREAD = nullptr; +void eamuse_coin_insert() { + if (COIN_BLOCK.load(std::memory_order_relaxed)) { + log_info("eamuse", "coin inserted while blocked"); + } else { + log_info("eamuse", "coin insert"); + COIN_STOCK.fetch_add(1, std::memory_order_relaxed); + } } void eamuse_pin_macro_start_thread() { diff --git a/src/spice2x/misc/eamuse.h b/src/spice2x/misc/eamuse.h index b2bd51d..d772bb5 100644 --- a/src/spice2x/misc/eamuse.h +++ b/src/spice2x/misc/eamuse.h @@ -58,9 +58,7 @@ bool eamuse_coin_consume(int amount); int eamuse_coin_consume_stock(); int eamuse_coin_add(); - -void eamuse_coin_start_thread(); -void eamuse_coin_stop_thread(); +void eamuse_coin_insert(); void eamuse_pin_macro_start_thread(); void eamuse_pin_macro_stop_thread(); diff --git a/src/spice2x/misc/hotkeys.cpp b/src/spice2x/misc/hotkeys.cpp new file mode 100644 index 0000000..7a21852 --- /dev/null +++ b/src/spice2x/misc/hotkeys.cpp @@ -0,0 +1,154 @@ +#include "hotkeys.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "games/io.h" +#include "launcher/superexit.h" +#include "misc/eamuse.h" +#include "overlay/overlay.h" +#include "rawinput/rawinput.h" +#include "util/logging.h" + +namespace hotkeys { + + namespace { + + // 8 ms targets short screenshot pulses; sleep_for may use a coarser scheduler + // interval during early boot or when process timer adjustments are disabled + constexpr auto MIN_SAMPLE_INTERVAL = std::chrono::milliseconds(8); + + std::atomic_bool SCREENSHOT_PENDING {false}; + std::mutex INPUT_MUTEX; + bool INPUT_ENABLED = false; + bool RAW_INPUT_ENABLED = false; + std::jthread WORKER; + + bool read_button(std::vector