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:
bicarus
2026-08-12 20:04:59 -07:00
committed by GitHub
parent 3b29227dbc
commit 82e0c053d0
18 changed files with 272 additions and 207 deletions
+1
View File
@@ -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);
+8
View File
@@ -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;
} }
+1
View File
@@ -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);
+13 -12
View File
@@ -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();
+11 -88
View File
@@ -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");
}
} }
+1 -2
View File
@@ -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
View File
@@ -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() {
+1 -3
View File
@@ -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();
+154
View File
@@ -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);
}
}
+13
View File
@@ -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();
}
+8
View File
@@ -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;
+2
View File
@@ -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();
} }
+25
View File
@@ -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,
+1
View File
@@ -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;