Files
spice2x-r3d/src/spice2x/overlay/overlay.h
T
bicarusandGitHub 82e0c053d0 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
2026-08-12 20:04:59 -07:00

241 lines
7.6 KiB
C++

#pragma once
#include <memory>
#include <mutex>
#include <functional>
#include <optional>
#include <vector>
#include <windows.h>
#include <d3d9.h>
#include "external/imgui/imgui.h"
#ifdef SPICE_D3D11
// forward decls for D3D11 (avoid pulling d3d11.h into every TU)
struct ID3D11Device;
struct ID3D11DeviceContext;
struct ID3D11RenderTargetView;
struct IDXGISwapChain;
#endif
namespace overlay {
class Window;
enum class OverlayRenderer {
D3D9,
#ifdef SPICE_D3D11
D3D11,
#endif
SOFTWARE,
};
// corner of the screen the FPS / clock / timer window is anchored to
enum class FpsLocation {
TopLeft,
TopRight,
BottomLeft,
BottomRight,
};
// settings
extern bool ENABLED;
extern bool AUTO_SHOW_FPS;
extern bool AUTO_SHOW_SUBSCREEN;
extern bool AUTO_SHOW_IOPANEL;
extern bool AUTO_SHOW_KEYPAD_P1;
extern bool AUTO_SHOW_KEYPAD_P2;
extern bool ENABLE_KEYBOARD_NAVIGATION;
extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT;
extern FpsLocation FPS_LOCATION;
extern bool SHOW_DEBUG_LOG_WINDOW;
extern std::optional<uint32_t> UI_SCALE_PERCENT;
template <typename T>
float apply_scaling(T input) {
if (!UI_SCALE_PERCENT.has_value()) {
return input;
}
return static_cast<float>(input) * UI_SCALE_PERCENT.value() / 100.f;
}
ImVec2 apply_scaling_to_vector(const ImVec2& input);
ImVec2 apply_scaling_to_vector(float x, float y);
class SpiceOverlay {
public:
D3DDEVICE_CREATION_PARAMETERS creation_parameters {};
D3DADAPTER_IDENTIFIER9 adapter_identifier {};
// windows
Window *window_iopanel = nullptr;
Window *window_config = nullptr;
Window *window_keypad1 = nullptr;
Window *window_keypad2 = nullptr;
Window *window_cards = nullptr;
Window *window_control = nullptr;
Window *window_resize = nullptr;
Window *window_camera = nullptr;
Window *window_sub = nullptr;
Window *window_log = nullptr;
Window *window_obs = nullptr;
// not part of `windows`: drawn/updated on the persistent layer (like
// notifications), independent of the overlay's active state and input gates.
std::unique_ptr<Window> window_fps;
std::unique_ptr<Window> window_nostalgia_touch_piano;
// not part of `windows`: the main menu / launcher. owned and drawn
// separately from the overlay window layer; it drives the overlay's
// active state while shown so its buttons still receive input.
std::unique_ptr<Window> window_main_menu;
explicit SpiceOverlay(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device);
#ifdef SPICE_D3D11
explicit SpiceOverlay(HWND hWnd, ID3D11Device *d3d11_device,
ID3D11DeviceContext *d3d11_context, IDXGISwapChain *d3d11_swapchain);
#endif
explicit SpiceOverlay(HWND hWnd);
~SpiceOverlay();
void window_add(Window *wnd);
void new_frame();
void render();
// configurator-only (spicecfg D3D9): submit ImGui draw data inside BeginScene
// after render() when the frame is being presented. In-game overlay draws in render().
void d3d9_render_draw(bool force_submit = false);
void update();
void toggle_active();
void show_main_menu();
void set_active(bool active);
bool get_active();
bool accepts_subscreen_mouse_input();
bool has_focus();
bool hotkeys_triggered();
static bool update_cursor();
static void reset_invalidate();
static void reset_recreate();
void input_char(unsigned int c);
uint32_t *sw_get_pixel_data(int *width, int *height);
inline bool uses_window(HWND hWnd) {
return this->hWnd == hWnd;
}
inline bool uses_context(IDirect3D9 *other) {
return this->d3d == other;
}
inline bool uses_device(IDirect3DDevice9 *other) {
return this->device == other;
}
#ifdef SPICE_D3D11
inline bool uses_device(ID3D11Device *other) {
return this->d3d11_device == other;
}
inline bool uses_swapchain(IDXGISwapChain *other) {
return this->d3d11_swapchain == other;
}
#endif
inline IDirect3DDevice9 *get_device() {
return this->device;
}
inline HWND get_window() {
return this->hWnd;
}
bool has_subscreen_touch_transform() {
return (this->subscreen_touch_transform != nullptr);
}
bool transform_touch_point(LONG *x, LONG *y) {
if (this->get_active() && this->subscreen_touch_transform) {
return this->subscreen_touch_transform(x, y);
} else {
return true;
}
}
void set_subscreen_touch_transform(const std::function<bool(LONG *, LONG *)> &f) {
this->subscreen_touch_transform = f;
}
// renderer
OverlayRenderer renderer;
float total_elapsed = 0.f;
// set to true by the SOFTWARE renderer when the pixel buffer was updated
// in the most recent render() call. Allows the configurator's WM_PAINT
// driver to skip its full-window InvalidateRect on idle frames where the
// ImGui draw data is bitwise-identical to the previous frame.
bool sw_pixels_dirty = false;
// configurator-only (spicecfg D3D9): set by render() when ImGui draw data
// changed since the last submitted frame; configurator skips Clear/Present when false.
bool d3d9_frame_dirty = false;
private:
HWND hWnd = nullptr;
// D3D9
IDirect3D9 *d3d = nullptr;
IDirect3DDevice9 *device = nullptr;
#ifdef SPICE_D3D11
// D3D11
ID3D11Device *d3d11_device = nullptr;
ID3D11DeviceContext *d3d11_context = nullptr;
IDXGISwapChain *d3d11_swapchain = nullptr;
ID3D11RenderTargetView *d3d11_rtv = nullptr;
#endif
// software
std::vector<uint32_t> pixel_data;
size_t pixel_data_width = 0;
size_t pixel_data_height = 0;
uint64_t sw_last_draw_hash = 0;
bool sw_has_last_draw_hash = false;
uint64_t d3d9_last_draw_hash = 0;
bool d3d9_has_last_draw_hash = false;
int d3d9_last_display_w = 0;
int d3d9_last_display_h = 0;
std::vector<std::unique_ptr<Window>> windows;
std::function<bool(LONG *, LONG *)> subscreen_touch_transform = nullptr;
bool active = false;
bool toggle_down = false;
bool main_menu_down = false;
bool fps_down = false;
bool hotkey_toggle = false;
bool hotkey_toggle_last = false;
std::mutex hotkeys_mutex;
// true between new_frame()'s ImGui::NewFrame() and render()'s ImGui::Render(),
// so render() never runs without a matching NewFrame.
bool has_pending_frame = false;
bool is_subscreen_overlay_visible();
void init();
void add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs);
};
// global
extern std::mutex OVERLAY_MUTEX;
extern std::unique_ptr<overlay::SpiceOverlay> OVERLAY;
extern ImFont* DSEG_FONT;
// synchronized helpers
void create_d3d9(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device);
#ifdef SPICE_D3D11
void create_d3d11(HWND hWnd, ID3D11Device *device, ID3D11DeviceContext *context,
IDXGISwapChain *swapchain);
#endif
void create_software(HWND hWnd);
void destroy(HWND hWnd = nullptr);
bool global_hotkeys_triggered();
}