gitadora: fix synthetic touch (mouse and api) (#867)

## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Fix mouse and touch API handling for gitadora.

* hook native touch in every arena window mode (previously only using
wintouchemu for 1 windowed mode)
* publish the touch window (SMALL panel, or the main window drawing the
overlay) and only attach injection there
* route touch on the SMALL window straight through instead of the
overlay transform, and accept the mouse only there
* fix spiceapi by forcing 800x1280 touch canvas size
* run API capture before the subscreen present to fix api not showing
sub image


## Testing
Test:

gitadora 1/2/4 windowed mode, fullscreen

iidx /sdvx / popn windowed/fullscreen for regressions
This commit is contained in:
bicarus
2026-08-16 01:43:20 -07:00
committed by GitHub
parent f5888609a8
commit a2e508208c
10 changed files with 112 additions and 25 deletions
+38 -3
View File
@@ -123,6 +123,30 @@ namespace nativetouch::inject {
// asks the touch window thread to send an UPDATE when the game loop runs elsewhere
static UINT contact_refresh_message;
// the window that shows the touch surface; contacts can be registered from any thread,
// so this is published by the graphics window hooks rather than derived from the globals
static std::atomic<HWND> preferred_injection_window { nullptr };
void set_preferred_injection_window(HWND window) {
preferred_injection_window.store(window, std::memory_order_release);
}
// a game that publishes its touch surface needs injection only on that window
static bool is_published_touch_window(HWND window) {
const auto preferred = preferred_injection_window.load(std::memory_order_acquire);
return preferred == nullptr || window == preferred;
}
// games can register several windows for touch; keep synthetic contacts on the one
// that actually shows the touch surface
static bool is_preferred_injection_window(HWND window) {
if (GRAPHICS_IIDX_WSUB) {
return window == TDJ_SUBSCREEN_WINDOW;
}
return is_published_touch_window(window);
}
// submit one synthetic contact frame to Windows touch injection
static bool inject_touch_frame(
POINT position, POINTER_FLAGS pointer_flags, bool retry_if_not_ready = false) {
@@ -325,11 +349,22 @@ namespace nativetouch::inject {
if (contact_state.input_window == window) {
release_active_contact();
}
HWND expected_window = window;
injection_window.compare_exchange_strong(
expected_window, nullptr, std::memory_order_acq_rel);
HWND expected_preferred_window = window;
preferred_injection_window.compare_exchange_strong(
expected_preferred_window, nullptr, std::memory_order_acq_rel);
if (GRAPHICS_IIDX_WSUB) {
HWND expected_delivery_window = window;
touch_delivery_window.compare_exchange_strong(
expected_delivery_window, nullptr, std::memory_order_acq_rel);
}
contact_refresh_pending.store(false, std::memory_order_release);
touch_delivery_window.store(nullptr, std::memory_order_release);
RemoveWindowSubclass(window, touch_window_subclass_proc, subclass_id);
}
@@ -370,7 +405,7 @@ namespace nativetouch::inject {
}
// publish the UI-thread target for synthetic touch requests
if (!GRAPHICS_IIDX_WSUB || window == TDJ_SUBSCREEN_WINDOW) {
if (is_preferred_injection_window(window)) {
injection_window.store(window, std::memory_order_release);
}
log_misc(
@@ -403,7 +438,7 @@ namespace nativetouch::inject {
// call original
const auto result = RegisterTouchWindow_orig(window, flags);
if (result) {
if (result && is_published_touch_window(window)) {
// attach but don't register for touch messages
// (we're already in the middle of RegisterTouchWindow as a result
// of the game calling it)
+1
View File
@@ -7,6 +7,7 @@ struct tagTOUCHINPUT;
namespace nativetouch::inject {
void attach_window(HWND window);
void register_and_attach_window(HWND window);
void set_preferred_injection_window(HWND window);
bool hook_available(HMODULE module);
bool hook(HMODULE module);
bool inject_synthetic_touch(POINT position, bool down);
+22 -5
View File
@@ -38,6 +38,11 @@ namespace nativetouch::transform {
window == TDJ_SUBSCREEN_WINDOW;
}
// mouse-as-touch only applies while the cursor is over the target window
static bool is_cursor_over_window(HWND window, POINT position) {
return screen_to_game_client(window, &position);
}
// convert game touch coordinates to Windows desktop coordinates
bool game_to_screen(HWND window, POINT *position) {
if (settings::SYNTHETIC_TOUCH_USES_CLIENT_COORDINATES) {
@@ -66,7 +71,13 @@ namespace nativetouch::transform {
return ClientToScreen(window, position) != FALSE;
}
static bool has_active_overlay_transform() {
static bool overlay_owns_touch_input() {
// the arena SMALL window is the touch surface whenever it exists, so the
// subscreen overlay must not claim touch input in those window modes
if (graphics_gitadora_has_dedicated_subscreen()) {
return false;
}
return overlay::OVERLAY != nullptr &&
overlay::OVERLAY->get_active() &&
overlay::OVERLAY->has_subscreen_touch_transform();
@@ -132,7 +143,7 @@ namespace nativetouch::transform {
// check if subscreen overlay is active and can transform the touch point;
// if not, the touch point is valid as-is
if (!has_active_overlay_transform()) {
if (!overlay_owns_touch_input()) {
return true;
}
@@ -149,8 +160,14 @@ namespace nativetouch::transform {
// exception: sdvx windowed subscreen does not use the subscreen overlay transform
if (GRAPHICS_WINDOWED && window == SDVX_SUBSCREEN_WINDOW) {
POINT client_position = *position;
return screen_to_game_client(window, &client_position);
return is_cursor_over_window(window, *position);
}
// exception: the arena SMALL window is the touch panel, so accept the mouse there
// (and only there) with the coordinates a real contact on it would produce
if (graphics_gitadora_has_dedicated_subscreen()) {
return window == GFDM_SUBSCREEN_WINDOW &&
is_cursor_over_window(window, *position);
}
// if this game has a subscreen overlay that can transform touch input
@@ -168,7 +185,7 @@ namespace nativetouch::transform {
// route hardware screen coordinates through dedicated or overlay mapping and report the result
Result hardware_to_game(POINT *position) {
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
const auto active_overlay = has_active_overlay_transform();
const auto active_overlay = overlay_owns_touch_input();
// special case for SDVX landscape mode
if (!dedicated_subscreen && !active_overlay &&