overlay: create a dedicated thread for polling hotkeys (#864)
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change Create a new thread that polls for the following hotkeys: * super exit (both alt+f4 and bound key) * coin insert * screenshot The goal is to make the capture of these more reliable, because before this PR it wasn't. ## Testing
This commit is contained in:
@@ -578,6 +578,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
misc/device.cpp
|
misc/device.cpp
|
||||||
misc/eamuse.cpp
|
misc/eamuse.cpp
|
||||||
misc/extdev.cpp
|
misc/extdev.cpp
|
||||||
|
misc/hotkeys.cpp
|
||||||
misc/sciunit.cpp
|
misc/sciunit.cpp
|
||||||
misc/sde.cpp
|
misc/sde.cpp
|
||||||
misc/wintouchemu.cpp
|
misc/wintouchemu.cpp
|
||||||
|
|||||||
@@ -23,10 +23,7 @@
|
|||||||
#include "external/imgui/backends/imgui_impl_dx11.h"
|
#include "external/imgui/backends/imgui_impl_dx11.h"
|
||||||
#include "overlay/imgui/impl_spice.h"
|
#include "overlay/imgui/impl_spice.h"
|
||||||
|
|
||||||
#include "games/io.h"
|
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "launcher/launcher.h"
|
|
||||||
#include "misc/eamuse.h"
|
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
@@ -149,26 +146,12 @@ void try_create_overlay(IDXGISwapChain *swapchain) {
|
|||||||
device->Release();
|
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) {
|
void pump_overlay(IDXGISwapChain *swapchain) {
|
||||||
if (!overlay::OVERLAY || !overlay::OVERLAY->uses_swapchain(swapchain)) {
|
if (!overlay::OVERLAY || !overlay::OVERLAY->uses_swapchain(swapchain)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
poll_screenshot_hotkey();
|
graphics_poll_screenshot_hotkey();
|
||||||
|
|
||||||
// size imgui to the backbuffer (not window client). dxgi may upscale
|
// size imgui to the backbuffer (not window client). dxgi may upscale
|
||||||
// a small backbuffer into a larger client rect; without this override
|
// a small backbuffer into a larger client rect; without this override
|
||||||
|
|||||||
@@ -1522,7 +1522,7 @@ void graphics_d3d9_on_present(
|
|||||||
wintouchemu::update();
|
wintouchemu::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics_d3d9_poll_screenshot_hotkey();
|
graphics_poll_screenshot_hotkey();
|
||||||
graphics_d3d9_process_screenshot_and_capture(device, SUB_SWAP_CHAIN);
|
graphics_d3d9_process_screenshot_and_capture(device, SUB_SWAP_CHAIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
#include "games/io.h"
|
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "launcher/launcher.h"
|
|
||||||
#include "misc/clipboard.h"
|
#include "misc/clipboard.h"
|
||||||
#include "misc/eamuse.h"
|
|
||||||
#include "overlay/notifications.h"
|
#include "overlay/notifications.h"
|
||||||
#include "overlay/overlay.h"
|
|
||||||
#include "util/fileutils.h"
|
#include "util/fileutils.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "util/logging.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<BackbufferCopy> acquire_backbuffer_copy(
|
static std::optional<BackbufferCopy> acquire_backbuffer_copy(
|
||||||
IDirect3DDevice9 *device, IDirect3DSwapChain9 *sub_swap_chain, int screen) {
|
IDirect3DDevice9 *device, IDirect3DSwapChain9 *sub_swap_chain, int screen) {
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include <d3d9.h>
|
#include <d3d9.h>
|
||||||
|
|
||||||
void graphics_d3d9_poll_screenshot_hotkey();
|
|
||||||
|
|
||||||
void graphics_d3d9_process_screenshot_and_capture(
|
void graphics_d3d9_process_screenshot_and_capture(
|
||||||
IDirect3DDevice9 *device,
|
IDirect3DDevice9 *device,
|
||||||
IDirect3DSwapChain9 *sub_swap_chain);
|
IDirect3DSwapChain9 *sub_swap_chain);
|
||||||
|
|||||||
@@ -17,11 +17,13 @@
|
|||||||
#include "games/ddr/ddr.h"
|
#include "games/ddr/ddr.h"
|
||||||
#include "games/gitadora/gitadora.h"
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
|
#include "games/io.h"
|
||||||
#include "games/sdvx/sdvx.h"
|
#include "games/sdvx/sdvx.h"
|
||||||
#include "games/popn/popn.h"
|
#include "games/popn/popn.h"
|
||||||
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
||||||
#include "hooks/graphics/backends/d3d11/d3d11_backend.h"
|
#include "hooks/graphics/backends/d3d11/d3d11_backend.h"
|
||||||
#include "launcher/shutdown.h"
|
#include "launcher/shutdown.h"
|
||||||
|
#include "misc/hotkeys.h"
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "touch/touch_gestures.h"
|
#include "touch/touch_gestures.h"
|
||||||
@@ -1374,6 +1376,12 @@ void graphics_screens_get(std::vector<int> &screens) {
|
|||||||
screens.insert(screens.end(), GRAPHICS_SCREENS.begin(), GRAPHICS_SCREENS.end());
|
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() {
|
void graphics_screenshot_trigger() {
|
||||||
GRAPHICS_SCREENSHOT_TRIGGER = true;
|
GRAPHICS_SCREENSHOT_TRIGGER = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ void graphics_hook_subscreen_window(HWND hWnd);
|
|||||||
void graphics_screens_register(int screen);
|
void graphics_screens_register(int screen);
|
||||||
void graphics_screens_unregister(int screen);
|
void graphics_screens_unregister(int screen);
|
||||||
void graphics_screens_get(std::vector<int> &screens);
|
void graphics_screens_get(std::vector<int> &screens);
|
||||||
|
void graphics_poll_screenshot_hotkey();
|
||||||
void graphics_screenshot_trigger();
|
void graphics_screenshot_trigger();
|
||||||
bool graphics_screenshot_consume();
|
bool graphics_screenshot_consume();
|
||||||
void graphics_capture_trigger(int screen);
|
void graphics_capture_trigger(int screen);
|
||||||
|
|||||||
@@ -87,13 +87,13 @@
|
|||||||
#include "launcher/launcher.h"
|
#include "launcher/launcher.h"
|
||||||
#include "launcher/logger.h"
|
#include "launcher/logger.h"
|
||||||
#include "launcher/signal.h"
|
#include "launcher/signal.h"
|
||||||
#include "launcher/superexit.h"
|
|
||||||
#include "launcher/richpresence.h"
|
#include "launcher/richpresence.h"
|
||||||
#include "launcher/shutdown.h"
|
#include "launcher/shutdown.h"
|
||||||
#include "launcher/options.h"
|
#include "launcher/options.h"
|
||||||
#include "misc/bt5api.h"
|
#include "misc/bt5api.h"
|
||||||
#include "misc/device.h"
|
#include "misc/device.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
|
#include "misc/hotkeys.h"
|
||||||
#include "misc/extdev.h"
|
#include "misc/extdev.h"
|
||||||
#include "misc/ami2000.h"
|
#include "misc/ami2000.h"
|
||||||
#include "misc/sciunit.h"
|
#include "misc/sciunit.h"
|
||||||
@@ -1777,9 +1777,8 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
nvapi::initialize();
|
nvapi::initialize();
|
||||||
// add application profile to nvcp
|
// add application profile to nvcp
|
||||||
nvapi::set_profile_settings();
|
nvapi::set_profile_settings();
|
||||||
// enable super exit
|
// keep ALT+F4 available during lengthy non-standalone boot
|
||||||
superexit::enable();
|
hotkeys::start();
|
||||||
|
|
||||||
// enable subscreen touch emulation
|
// enable subscreen touch emulation
|
||||||
if (options[launcher::Options::spice2x_IIDXEmulateSubscreenKeypadTouch].is_active()) {
|
if (options[launcher::Options::spice2x_IIDXEmulateSubscreenKeypadTouch].is_active()) {
|
||||||
games::iidx::ENABLE_POKE = true;
|
games::iidx::ENABLE_POKE = true;
|
||||||
@@ -2452,6 +2451,7 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
|
|
||||||
// initialize raw input
|
// initialize raw input
|
||||||
RI_MGR = std::make_unique<rawinput::RawInputManager>();
|
RI_MGR = std::make_unique<rawinput::RawInputManager>();
|
||||||
|
hotkeys::enable_raw_input();
|
||||||
for (const auto &device : sextet_devices) {
|
for (const auto &device : sextet_devices) {
|
||||||
RI_MGR->sextet_register(device);
|
RI_MGR->sextet_register(device);
|
||||||
}
|
}
|
||||||
@@ -2474,6 +2474,9 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
log_misc("rawinput", "Analog mappings:");
|
log_misc("rawinput", "Analog mappings:");
|
||||||
dump_analog_bindings();
|
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)
|
// 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")) {
|
if (show_cursor_if_no_touch && !is_touch_available("launcher::main_implementation")) {
|
||||||
GRAPHICS_SHOW_CURSOR = true;
|
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]);
|
API_CONTROLLER->listen_serial(api_serial_port[i], api_serial_baud[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// start coin input thread
|
|
||||||
eamuse_coin_start_thread();
|
|
||||||
|
|
||||||
// pin macro
|
// pin macro
|
||||||
if (!cfg::CONFIGURATOR_STANDALONE && PIN_MACRO_ENABLED) {
|
if (!cfg::CONFIGURATOR_STANDALONE && PIN_MACRO_ENABLED) {
|
||||||
eamuse_pin_macro_start_thread();
|
eamuse_pin_macro_start_thread();
|
||||||
@@ -2755,6 +2755,9 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
log_info("launcher", "calling game entry");
|
log_info("launcher", "calling game entry");
|
||||||
avs::game::entry_main();
|
avs::game::entry_main();
|
||||||
|
|
||||||
|
// stop screenshot and coin polling; mapped SuperExit and ALT+F4 remain active
|
||||||
|
hotkeys::disable_input();
|
||||||
|
|
||||||
// clear presence
|
// clear presence
|
||||||
richpresence::shutdown();
|
richpresence::shutdown();
|
||||||
|
|
||||||
@@ -2792,9 +2795,6 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
// free api controller
|
// free api controller
|
||||||
API_CONTROLLER.reset();
|
API_CONTROLLER.reset();
|
||||||
|
|
||||||
// stop coin input thread
|
|
||||||
eamuse_coin_stop_thread();
|
|
||||||
|
|
||||||
eamuse_pin_macro_stop_thread();
|
eamuse_pin_macro_stop_thread();
|
||||||
|
|
||||||
// BT5API
|
// BT5API
|
||||||
@@ -2805,6 +2805,7 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
sdk::fini_sdk_modules();
|
sdk::fini_sdk_modules();
|
||||||
|
|
||||||
// stop raw input
|
// stop raw input
|
||||||
|
hotkeys::disable_raw_input();
|
||||||
RI_MGR.reset();
|
RI_MGR.reset();
|
||||||
|
|
||||||
// debug hook
|
// debug hook
|
||||||
@@ -2837,8 +2838,8 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
// dispose crypt
|
// dispose crypt
|
||||||
crypt::dispose();
|
crypt::dispose();
|
||||||
|
|
||||||
// disable super exit
|
// end early/late ALT+F4 monitoring at the same teardown point as legacy SuperExit
|
||||||
superexit::disable();
|
hotkeys::stop();
|
||||||
|
|
||||||
// disable poke
|
// disable poke
|
||||||
games::iidx::poke::disable();
|
games::iidx::poke::disable();
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
#include "superexit.h"
|
#include "superexit.h"
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "windows.h"
|
#include "windows.h"
|
||||||
|
|
||||||
|
#include "cfg/configurator.h"
|
||||||
#include "launcher/shutdown.h"
|
#include "launcher/shutdown.h"
|
||||||
#include "rawinput/rawinput.h"
|
|
||||||
#include "util/logging.h"
|
|
||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "misc/eamuse.h"
|
#include "util/logging.h"
|
||||||
#include "games/io.h"
|
|
||||||
#include "overlay/overlay.h"
|
|
||||||
|
|
||||||
namespace superexit {
|
namespace superexit {
|
||||||
|
|
||||||
static std::thread *THREAD = nullptr;
|
|
||||||
static bool THREAD_RUNNING = false;
|
|
||||||
|
|
||||||
bool has_focus() {
|
bool has_focus() {
|
||||||
HWND fg_wnd = GetForegroundWindow();
|
HWND fg_wnd = GetForegroundWindow();
|
||||||
if (fg_wnd == NULL) {
|
if (fg_wnd == NULL) {
|
||||||
@@ -29,92 +22,22 @@ namespace superexit {
|
|||||||
return fg_pid == GetCurrentProcessId();
|
return fg_pid == GetCurrentProcessId();
|
||||||
}
|
}
|
||||||
|
|
||||||
void enable() {
|
void handle_hotkeys(bool alt_f4, bool mapped_exit) {
|
||||||
|
if (!alt_f4 && !mapped_exit) {
|
||||||
// check if already running
|
|
||||||
if (THREAD)
|
|
||||||
return;
|
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;
|
|
||||||
}
|
}
|
||||||
|
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
break;
|
if (!has_focus()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
default:
|
if (alt_f4) {
|
||||||
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...");
|
log_info("superexit", "detected ALT+F4, exiting...");
|
||||||
launcher::shutdown();
|
launcher::shutdown();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (overlay_exit) {
|
|
||||||
if (has_focus()) {
|
|
||||||
log_info("superexit", "detected Force Exit Game overlay shortcut, exiting...");
|
log_info("superexit", "detected Force Exit Game overlay shortcut, exiting...");
|
||||||
launcher::shutdown();
|
launcher::shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// slow down
|
|
||||||
Sleep(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void disable() {
|
|
||||||
if (!THREAD) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop old thread
|
|
||||||
THREAD_RUNNING = false;
|
|
||||||
THREAD->join();
|
|
||||||
|
|
||||||
// delete thread
|
|
||||||
delete THREAD;
|
|
||||||
THREAD = nullptr;
|
|
||||||
|
|
||||||
// log
|
|
||||||
log_info("superexit", "disabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,5 @@
|
|||||||
|
|
||||||
namespace superexit {
|
namespace superexit {
|
||||||
bool has_focus();
|
bool has_focus();
|
||||||
void enable();
|
void handle_hotkeys(bool alt_f4, bool mapped_exit);
|
||||||
void disable();
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-51
@@ -25,10 +25,8 @@ static double CARD_INSERT_TIME[2] = {0, 0};
|
|||||||
static double CARD_INSERT_TIMEOUT = 2.0;
|
static double CARD_INSERT_TIMEOUT = 2.0;
|
||||||
static char CARD_INSERT_UID[2][8] = {{0}, {0}};
|
static char CARD_INSERT_UID[2][8] = {{0}, {0}};
|
||||||
static char CARD_INSERT_UID_ENABLE[2] = {false, false};
|
static char CARD_INSERT_UID_ENABLE[2] = {false, false};
|
||||||
static int COIN_STOCK = 0;
|
static std::atomic_int COIN_STOCK {0};
|
||||||
static bool COIN_BLOCK = false;
|
static std::atomic_bool COIN_BLOCK {false};
|
||||||
static std::thread *COIN_INPUT_THREAD;
|
|
||||||
static bool COIN_INPUT_THREAD_ACTIVE = false;
|
|
||||||
static uint16_t KEYPAD_STATE[] = {0, 0};
|
static uint16_t KEYPAD_STATE[] = {0, 0};
|
||||||
static uint16_t KEYPAD_STATE_OVERRIDES[] = {0, 0};
|
static uint16_t KEYPAD_STATE_OVERRIDES[] = {0, 0};
|
||||||
static uint16_t KEYPAD_STATE_OVERRIDES_BT5[] = {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() {
|
bool eamuse_coin_get_block() {
|
||||||
return COIN_BLOCK;
|
return COIN_BLOCK.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void eamuse_coin_set_block(bool block) {
|
void eamuse_coin_set_block(bool block) {
|
||||||
COIN_BLOCK = block;
|
COIN_BLOCK.store(block, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
int eamuse_coin_get_stock() {
|
int eamuse_coin_get_stock() {
|
||||||
return COIN_STOCK;
|
return COIN_STOCK.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void eamuse_coin_set_stock(int amount) {
|
void eamuse_coin_set_stock(int amount) {
|
||||||
COIN_STOCK = amount;
|
COIN_STOCK.store(amount, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eamuse_coin_consume(int amount) {
|
bool eamuse_coin_consume(int amount) {
|
||||||
if (COIN_STOCK < amount) {
|
auto stock = COIN_STOCK.load(std::memory_order_relaxed);
|
||||||
return false;
|
while (stock >= amount) {
|
||||||
} else {
|
if (COIN_STOCK.compare_exchange_weak(
|
||||||
COIN_STOCK -= amount;
|
stock,
|
||||||
|
stock - amount,
|
||||||
|
std::memory_order_relaxed)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int eamuse_coin_consume_stock() {
|
int eamuse_coin_consume_stock() {
|
||||||
int stock = COIN_STOCK;
|
return COIN_STOCK.exchange(0, std::memory_order_relaxed);
|
||||||
COIN_STOCK = 0;
|
|
||||||
return stock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int eamuse_coin_add() {
|
int eamuse_coin_add() {
|
||||||
return ++COIN_STOCK;
|
return COIN_STOCK.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void eamuse_coin_start_thread() {
|
void eamuse_coin_insert() {
|
||||||
|
if (COIN_BLOCK.load(std::memory_order_relaxed)) {
|
||||||
// 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");
|
log_info("eamuse", "coin inserted while blocked");
|
||||||
else {
|
|
||||||
log_info("eamuse", "coin insert");
|
|
||||||
COIN_STOCK++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
COIN_INPUT_KEY_STATE = true;
|
|
||||||
} else {
|
} else {
|
||||||
COIN_INPUT_KEY_STATE = false;
|
log_info("eamuse", "coin insert");
|
||||||
|
COIN_STOCK.fetch_add(1, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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_pin_macro_start_thread() {
|
void eamuse_pin_macro_start_thread() {
|
||||||
|
|||||||
@@ -58,9 +58,7 @@ bool eamuse_coin_consume(int amount);
|
|||||||
int eamuse_coin_consume_stock();
|
int eamuse_coin_consume_stock();
|
||||||
|
|
||||||
int eamuse_coin_add();
|
int eamuse_coin_add();
|
||||||
|
void eamuse_coin_insert();
|
||||||
void eamuse_coin_start_thread();
|
|
||||||
void eamuse_coin_stop_thread();
|
|
||||||
|
|
||||||
void eamuse_pin_macro_start_thread();
|
void eamuse_pin_macro_start_thread();
|
||||||
void eamuse_pin_macro_stop_thread();
|
void eamuse_pin_macro_stop_thread();
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
#include "hotkeys.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <stop_token>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#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<Button> *buttons, size_t index) {
|
||||||
|
// getState retains each binding's focus, modifier, inversion, and debounce policy
|
||||||
|
return RI_MGR && buttons && index < buttons->size() &&
|
||||||
|
GameAPI::Buttons::getState(RI_MGR, buttons->at(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool read_alt_f4() {
|
||||||
|
// preserve both legacy detection paths whenever raw input is available
|
||||||
|
bool pressed = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0 &&
|
||||||
|
(GetAsyncKeyState(VK_F4) & 0x8000) != 0;
|
||||||
|
if (!RAW_INPUT_ENABLED || !RI_MGR) {
|
||||||
|
return pressed;
|
||||||
|
}
|
||||||
|
return pressed || RI_MGR->keyboard_combo_pressed(VK_MENU, VK_F4);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rising_edge(bool current, bool &previous) {
|
||||||
|
const bool edge = current && !previous;
|
||||||
|
previous = current;
|
||||||
|
return edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(std::stop_token stop_token) {
|
||||||
|
bool screenshot_previous = false;
|
||||||
|
bool coin_previous = false;
|
||||||
|
|
||||||
|
while (!stop_token.stop_requested()) {
|
||||||
|
bool coin_edge = false;
|
||||||
|
bool super_exit_current = false;
|
||||||
|
bool alt_f4_current = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
// lifecycle functions hold this mutex until raw-input polling is complete
|
||||||
|
std::lock_guard<std::mutex> lock(INPUT_MUTEX);
|
||||||
|
if (INPUT_ENABLED || RAW_INPUT_ENABLED) {
|
||||||
|
auto *buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||||
|
const bool screenshot_down = INPUT_ENABLED && read_button(
|
||||||
|
buttons, games::OverlayButtons::Screenshot);
|
||||||
|
const bool coin_current = INPUT_ENABLED && read_button(
|
||||||
|
buttons, games::OverlayButtons::InsertCoin);
|
||||||
|
const bool super_exit_down = RAW_INPUT_ENABLED && read_button(
|
||||||
|
buttons, games::OverlayButtons::SuperExit);
|
||||||
|
|
||||||
|
// global_hotkeys_triggered takes OVERLAY_MUTEX, then the overlay's
|
||||||
|
// hotkeys_mutex; its button reads may then take device mutexes. polling
|
||||||
|
// every mapped-input tick is intentional so HotkeyToggle releases cannot
|
||||||
|
// be missed when the render thread stalls.
|
||||||
|
const bool gate_active = overlay::global_hotkeys_triggered();
|
||||||
|
const bool screenshot_current = screenshot_down && gate_active;
|
||||||
|
super_exit_current = super_exit_down && gate_active;
|
||||||
|
|
||||||
|
if (rising_edge(screenshot_current, screenshot_previous)) {
|
||||||
|
SCREENSHOT_PENDING.store(true, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
coin_edge = rising_edge(coin_current, coin_previous);
|
||||||
|
} else {
|
||||||
|
screenshot_previous = false;
|
||||||
|
coin_previous = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
alt_f4_current = read_alt_f4();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coin_edge) {
|
||||||
|
eamuse_coin_insert();
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass held state so returning focus can exit without another key press
|
||||||
|
superexit::handle_hotkeys(alt_f4_current, super_exit_current);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(MIN_SAMPLE_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
if (WORKER.joinable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCREENSHOT_PENDING.store(false, std::memory_order_relaxed);
|
||||||
|
WORKER = std::jthread(run);
|
||||||
|
log_info("hotkeys", "sampler started");
|
||||||
|
}
|
||||||
|
|
||||||
|
void enable_raw_input() {
|
||||||
|
std::lock_guard<std::mutex> lock(INPUT_MUTEX);
|
||||||
|
RAW_INPUT_ENABLED = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enable_input() {
|
||||||
|
std::lock_guard<std::mutex> lock(INPUT_MUTEX);
|
||||||
|
SCREENSHOT_PENDING.store(false, std::memory_order_relaxed);
|
||||||
|
INPUT_ENABLED = true;
|
||||||
|
log_info("hotkeys", "configured input enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
void disable_input() {
|
||||||
|
std::lock_guard<std::mutex> lock(INPUT_MUTEX);
|
||||||
|
INPUT_ENABLED = false;
|
||||||
|
SCREENSHOT_PENDING.store(false, std::memory_order_relaxed);
|
||||||
|
log_info("hotkeys", "configured input disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
void disable_raw_input() {
|
||||||
|
std::lock_guard<std::mutex> lock(INPUT_MUTEX);
|
||||||
|
RAW_INPUT_ENABLED = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (!WORKER.joinable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WORKER.request_stop();
|
||||||
|
WORKER.join();
|
||||||
|
log_info("hotkeys", "sampler stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool consume_screenshot() {
|
||||||
|
return SCREENSHOT_PENDING.exchange(false, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace hotkeys {
|
||||||
|
|
||||||
|
// ALT+F4 monitoring spans boot and teardown; configured actions are enabled separately
|
||||||
|
void start();
|
||||||
|
void enable_raw_input();
|
||||||
|
void enable_input();
|
||||||
|
void disable_input();
|
||||||
|
void disable_raw_input();
|
||||||
|
void stop();
|
||||||
|
bool consume_screenshot();
|
||||||
|
}
|
||||||
@@ -158,6 +158,11 @@ void overlay::destroy(HWND hWnd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool overlay::global_hotkeys_triggered() {
|
||||||
|
const std::lock_guard<std::mutex> lock(OVERLAY_MUTEX);
|
||||||
|
return !overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered();
|
||||||
|
}
|
||||||
|
|
||||||
overlay::SpiceOverlay::SpiceOverlay(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device)
|
overlay::SpiceOverlay::SpiceOverlay(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device)
|
||||||
: renderer(OverlayRenderer::D3D9), hWnd(hWnd), d3d(d3d), device(device) {
|
: renderer(OverlayRenderer::D3D9), hWnd(hWnd), d3d(d3d), device(device) {
|
||||||
log_info("overlay", "initializing (D3D9)");
|
log_info("overlay", "initializing (D3D9)");
|
||||||
@@ -924,6 +929,9 @@ bool overlay::SpiceOverlay::has_focus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool overlay::SpiceOverlay::hotkeys_triggered() {
|
bool overlay::SpiceOverlay::hotkeys_triggered() {
|
||||||
|
const std::lock_guard<std::mutex> lock(this->hotkeys_mutex);
|
||||||
|
|
||||||
|
// this query also consumes the shared HotkeyToggle edge; the mutex gives all callers one latch
|
||||||
// prevent hotkeys in spicecfg
|
// prevent hotkeys in spicecfg
|
||||||
if (cfg::CONFIGURATOR_STANDALONE) {
|
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -212,6 +212,7 @@ namespace overlay {
|
|||||||
bool fps_down = false;
|
bool fps_down = false;
|
||||||
bool hotkey_toggle = false;
|
bool hotkey_toggle = false;
|
||||||
bool hotkey_toggle_last = false;
|
bool hotkey_toggle_last = false;
|
||||||
|
std::mutex hotkeys_mutex;
|
||||||
|
|
||||||
// true between new_frame()'s ImGui::NewFrame() and render()'s ImGui::Render(),
|
// true between new_frame()'s ImGui::NewFrame() and render()'s ImGui::Render(),
|
||||||
// so render() never runs without a matching NewFrame.
|
// so render() never runs without a matching NewFrame.
|
||||||
@@ -235,4 +236,5 @@ namespace overlay {
|
|||||||
#endif
|
#endif
|
||||||
void create_software(HWND hWnd);
|
void create_software(HWND hWnd);
|
||||||
void destroy(HWND hWnd = nullptr);
|
void destroy(HWND hWnd = nullptr);
|
||||||
|
bool global_hotkeys_triggered();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
|
#include <iterator>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -2543,6 +2544,30 @@ rawinput::Device *rawinput::RawInputManager::devices_get(const std::string &name
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool rawinput::RawInputManager::keyboard_combo_pressed(uint16_t first, uint16_t second) {
|
||||||
|
if (first >= 256 || second >= 256) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::recursive_mutex> devices_lock(this->devices_mutex);
|
||||||
|
for (auto &device : this->devices) {
|
||||||
|
if (device.type != rawinput::KEYBOARD ||
|
||||||
|
device.keyboardInfo == nullptr ||
|
||||||
|
device.mutex == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> device_lock(*device.mutex);
|
||||||
|
const auto &states = device.keyboardInfo->key_states;
|
||||||
|
for (size_t page = 0; page < std::size(states); page += 256) {
|
||||||
|
if (states[page + first] && states[page + second]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void rawinput::RawInputManager::add_callback_add(void *data, std::function<void (void *, Device *)> callback) {
|
void rawinput::RawInputManager::add_callback_add(void *data, std::function<void (void *, Device *)> callback) {
|
||||||
this->callback_add.push_back(DeviceCallback {
|
this->callback_add.push_back(DeviceCallback {
|
||||||
.data = data,
|
.data = data,
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ namespace rawinput {
|
|||||||
|
|
||||||
void __stdcall devices_print();
|
void __stdcall devices_print();
|
||||||
Device *devices_get(const std::string &name, bool updated = false);
|
Device *devices_get(const std::string &name, bool updated = false);
|
||||||
|
bool keyboard_combo_pressed(uint16_t first, uint16_t second);
|
||||||
|
|
||||||
inline std::list<Device> &devices_get() {
|
inline std::list<Device> &devices_get() {
|
||||||
return devices;
|
return devices;
|
||||||
|
|||||||
Reference in New Issue
Block a user