diff --git a/src/spice2x/api/modules/touch.cpp b/src/spice2x/api/modules/touch.cpp index 3986651..eb9d013 100644 --- a/src/spice2x/api/modules/touch.cpp +++ b/src/spice2x/api/modules/touch.cpp @@ -13,6 +13,7 @@ #include "touch/native/inject.h" #include "touch/native/nativetouchhook.h" #include "util/utils.h" +#include "games/gitadora/gitadora.h" #include "games/iidx/iidx.h" using namespace std::placeholders; @@ -69,6 +70,10 @@ namespace api::modules { // pop'n music API touch surface native_canvas_w = 1280; native_canvas_h = 800; + } else if (games::gitadora::is_arena_model()) { + // GITADORA arena SMALL subscreen, either in its own window or in the overlay + native_canvas_w = games::gitadora::ARENA_SUBSCREEN_WIDTH; + native_canvas_h = games::gitadora::ARENA_SUBSCREEN_HEIGHT; } functions["read"] = std::bind(&Touch::read, this, _1, _2); diff --git a/src/spice2x/games/gitadora/gitadora.cpp b/src/spice2x/games/gitadora/gitadora.cpp index e7f8b8d..998457e 100644 --- a/src/spice2x/games/gitadora/gitadora.cpp +++ b/src/spice2x/games/gitadora/gitadora.cpp @@ -41,6 +41,7 @@ namespace games::gitadora { bool ARENA_TWO_HEAD_EXCLUSIVE = false; std::optional ASIO_DRIVER = std::nullopt; bool ALLOW_REALTEK_AUDIO = false; + bool NATIVE_TOUCH = false; /* * Prevent GitaDora from creating folders on F drive @@ -809,17 +810,16 @@ namespace games::gitadora { hooks::audio::INJECT_FAKE_REALTEK_AUDIO = true; } - // Single-window mode needs touch injection for its overlay. Two-head fullscreen - // mode uses the real SMALL output, so it only needs the display-topology shim. - if (GRAPHICS_PREVENT_SECONDARY_WINDOWS) { - // enable touch hook for subscreen overlay - const auto native_touch_ready = !wintouchemu::FORCE && - nativetouch::hook(avs::game::DLL_INSTANCE); - if (!native_touch_ready) { - wintouchemu::FORCE = true; - wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true; - wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE); - } + // touch injection drives mouse-as-touch and API touch for the subscreen, + // no matter whether it is drawn by the overlay (single-window mode) or by + // the dedicated SMALL window + NATIVE_TOUCH = !wintouchemu::FORCE && + nativetouch::hook(avs::game::DLL_INSTANCE); + if (!NATIVE_TOUCH && GRAPHICS_PREVENT_SECONDARY_WINDOWS) { + // the legacy fallback can only feed the subscreen overlay + wintouchemu::FORCE = true; + wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true; + wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE); } #if !SPICE_XP diff --git a/src/spice2x/games/gitadora/gitadora.h b/src/spice2x/games/gitadora/gitadora.h index 7851949..32aa8e5 100644 --- a/src/spice2x/games/gitadora/gitadora.h +++ b/src/spice2x/games/gitadora/gitadora.h @@ -23,6 +23,11 @@ namespace games::gitadora { extern bool ARENA_TWO_HEAD_EXCLUSIVE; extern std::optional ASIO_DRIVER; extern bool ALLOW_REALTEK_AUDIO; + extern bool NATIVE_TOUCH; + + // arena SMALL subscreen (touch panel) resolution + static constexpr int ARENA_SUBSCREEN_WIDTH = 800; + static constexpr int ARENA_SUBSCREEN_HEIGHT = 1280; class GitaDoraGame : public games::Game { public: diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp index c7be737..0ff5452 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp @@ -1520,6 +1520,10 @@ void graphics_d3d9_on_present( graphics_d3d9_process_screenshot(device, wrapped_device); } + // API capture always includes the overlay; it must run before the subscreen present + // below, which leaves the arena SMALL back buffer black + graphics_d3d9_process_capture(device, wrapped_device); + // for IIDX TDJ / SDVX UFC, handle subscreen const bool is_vm = games::sdvx::is_valkyrie_model(); const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE; @@ -1533,9 +1537,6 @@ void graphics_d3d9_on_present( if (is_mfc) { wintouchemu::update(); } - - // API capture always includes the overlay - graphics_d3d9_process_capture(device, wrapped_device); } void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) { diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.h b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.h index 1791621..1c4fd3e 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.h +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.h @@ -4,10 +4,12 @@ #include +#include "games/gitadora/gitadora.h" + inline constexpr UINT GFDM_SIDE_WIDTH = 1080; inline constexpr UINT GFDM_SIDE_HEIGHT = 1920; -inline constexpr UINT GFDM_SMALL_WIDTH = 800; -inline constexpr UINT GFDM_SMALL_HEIGHT = 1280; +inline constexpr UINT GFDM_SMALL_WIDTH = games::gitadora::ARENA_SUBSCREEN_WIDTH; +inline constexpr UINT GFDM_SMALL_HEIGHT = games::gitadora::ARENA_SUBSCREEN_HEIGHT; inline constexpr UINT GFDM_LOGICAL_HEAD_COUNT = 4; inline constexpr UINT GFDM_NATIVE_SMALL_SWAPCHAIN = 1; diff --git a/src/spice2x/hooks/graphics/graphics.cpp b/src/spice2x/hooks/graphics/graphics.cpp index d382253..3ddba12 100644 --- a/src/spice2x/hooks/graphics/graphics.cpp +++ b/src/spice2x/hooks/graphics/graphics.cpp @@ -33,6 +33,7 @@ #include "util/utils.h" #include "misc/wintouchemu.h" #include "touch/native/inject.h" +#include "touch/native/nativetouchhook.h" #include "util/time.h" #include "rawinput/rawinput.h" @@ -216,6 +217,18 @@ static void gitadora_remember_window(HWND hWnd, const std::string &window_name) } else if (window_name == "SMALL") { GFDM_SUBSCREEN_WINDOW = hWnd; } + + // touch belongs to the SMALL panel when it exists, otherwise to the main window + // that draws the subscreen overlay + const bool hosts_touch = window_name == "SMALL" || + (window_name == "GITADORA" && !graphics_gitadora_has_dedicated_subscreen()); + if (nativetouch::is_hooked() && hWnd != nullptr && hosts_touch) { + nativetouch::inject::set_preferred_injection_window(hWnd); + } +} + +bool graphics_gitadora_has_dedicated_subscreen() { + return GFDM_SUBSCREEN_WINDOW != nullptr; } bool graphics_gitadora_prepare_two_head_device_window( @@ -731,6 +744,11 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC if (GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) { graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW); } + + // the dedicated SMALL window is the touch panel; mouse and API touch target it + if (nativetouch::is_hooked() && result != nullptr) { + nativetouch::inject::register_and_attach_window(result); + } } if (is_gfdm_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) { gitadora_force_window_style(result); @@ -1320,7 +1338,9 @@ void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParamet const bool native_touch_overlay = (games::iidx::NATIVE_TOUCH && games::iidx::TDJ_MODE && !GRAPHICS_IIDX_WSUB) || (games::popn::NATIVE_TOUCH && - games::popn::is_pikapika_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS); + games::popn::is_pikapika_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS) || + (games::gitadora::NATIVE_TOUCH && + games::gitadora::is_arena_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS); if (native_touch_overlay) { nativetouch::inject::register_and_attach_window(hWnd); } diff --git a/src/spice2x/hooks/graphics/graphics.h b/src/spice2x/hooks/graphics/graphics.h index c8823ec..717e7b4 100644 --- a/src/spice2x/hooks/graphics/graphics.h +++ b/src/spice2x/hooks/graphics/graphics.h @@ -121,6 +121,7 @@ extern bool D3D9_DEVICE_HOOK_DISABLE; void graphics_init(); void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParameters); +bool graphics_gitadora_has_dedicated_subscreen(); // The native GITADORA two-head D3D9 group uses the game's named SMALL // device window for the native physical SMALL head. The game requests // D3DCREATE_NOWINDOWCHANGES, so this host must be made borderless and sized diff --git a/src/spice2x/touch/native/inject.cpp b/src/spice2x/touch/native/inject.cpp index ebef28a..8ea717a 100644 --- a/src/spice2x/touch/native/inject.cpp +++ b/src/spice2x/touch/native/inject.cpp @@ -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 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) diff --git a/src/spice2x/touch/native/inject.h b/src/spice2x/touch/native/inject.h index d7077b3..2c8ca9c 100644 --- a/src/spice2x/touch/native/inject.h +++ b/src/spice2x/touch/native/inject.h @@ -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); diff --git a/src/spice2x/touch/native/transform.cpp b/src/spice2x/touch/native/transform.cpp index 3bfc128..dc2c67d 100644 --- a/src/spice2x/touch/native/transform.cpp +++ b/src/spice2x/touch/native/transform.cpp @@ -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 &&