sdvx: fix api touch and -sdvxnosub sub window (#868)
## Link to GitHub Issue or related Pull Request, if one exists Related: #859 and #862. ## Description of change Three SDVX (Valkyrie model) fixes: * **API touch went to the wrong window.** SDVX registers touch on both its Main Screen and Sub Screen windows, and the native injector kept whichever attached last. The touch surface is now published explicitly: sub screen window when windowed, main window in fullscreen. * **Landscape never rotated API coordinates.** Synthetic contacts bypass `transform::hardware_to_game`, so they missed the rotation a real finger gets. Applied before injection now, as portrait already did. Extracted to `sdvx_landscape_rotate` so both paths share it; gated on the native path since `wintouchemu` rotates via the subscreen overlay instead. * **`-sdvxnosub` didn't hide the sub window in fullscreen.** `ShowWindow_hook` had branches for GITADORA, pop'n and IIDX but not SDVX. Added the missing one. ## Testing Tested Nabla
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "touch/native/inject.h"
|
#include "touch/native/inject.h"
|
||||||
#include "touch/native/nativetouchhook.h"
|
#include "touch/native/nativetouchhook.h"
|
||||||
|
#include "touch/native/transform.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "games/gitadora/gitadora.h"
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
@@ -41,6 +42,35 @@ namespace api::modules {
|
|||||||
return nativetouch::inject::inject_synthetic_touch(position, true);
|
return nativetouch::inject::inject_synthetic_touch(position, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// map API coordinates onto the touch space SDVX reads, which depends on how it is displayed
|
||||||
|
static void sdvx_touch_errata(
|
||||||
|
int &x, int &y, bool use_native, int canvas_w, int canvas_h) {
|
||||||
|
|
||||||
|
// windowed coordinates already match the sub screen window they land on
|
||||||
|
if (GRAPHICS_WINDOWED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// landscape mode: native injection hands the game these coordinates
|
||||||
|
// unchanged, so apply the rotation the touchscreen gets, while wintouchemu instead
|
||||||
|
// rotates them later through the subscreen overlay
|
||||||
|
if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||||
|
if (use_native) {
|
||||||
|
POINT position { x, y };
|
||||||
|
if (nativetouch::transform::sdvx_landscape_rotate(&position, canvas_w, canvas_h)) {
|
||||||
|
x = position.x;
|
||||||
|
y = position.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate into the portrait touch space
|
||||||
|
const int x_raw = x;
|
||||||
|
x = canvas_w - y;
|
||||||
|
y = x_raw;
|
||||||
|
}
|
||||||
|
|
||||||
Touch::Touch() : Module("touch") {
|
Touch::Touch() : Module("touch") {
|
||||||
is_sdvx = avs::game::is_model("KFC");
|
is_sdvx = avs::game::is_model("KFC");
|
||||||
|
|
||||||
@@ -56,8 +86,8 @@ namespace api::modules {
|
|||||||
native_canvas_w = 0;
|
native_canvas_w = 0;
|
||||||
native_canvas_h = 0;
|
native_canvas_h = 0;
|
||||||
if (is_sdvx) {
|
if (is_sdvx) {
|
||||||
// windowed and landscape API coordinates already match the primary screen orientation;
|
// windowed API coordinates land on the sub screen window as-is; fullscreen
|
||||||
// fullscreen portrait coordinates are rotated by apply_touch_errata
|
// coordinates are rotated into the game's touch space by apply_touch_errata
|
||||||
const bool landscape_coordinates =
|
const bool landscape_coordinates =
|
||||||
GRAPHICS_WINDOWED || GRAPHICS_FS_ORIENTATION_SWAP;
|
GRAPHICS_WINDOWED || GRAPHICS_FS_ORIENTATION_SWAP;
|
||||||
native_canvas_w = landscape_coordinates ? 1920 : 1080;
|
native_canvas_w = landscape_coordinates ? 1920 : 1080;
|
||||||
@@ -217,19 +247,14 @@ namespace api::modules {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Touch::apply_touch_errata(int &x, int &y) {
|
void Touch::apply_touch_errata(int &x, int &y) {
|
||||||
int x_raw = x;
|
|
||||||
int y_raw = y;
|
|
||||||
|
|
||||||
if (is_tdj_fhd) {
|
if (is_tdj_fhd) {
|
||||||
// deal with TDJ FHD resolution mismatch (upgrade 720p to 1080p)
|
// deal with TDJ FHD resolution mismatch (upgrade 720p to 1080p)
|
||||||
// we don't know what screen is being shown on the companion and the API doesn't specify
|
// we don't know what screen is being shown on the companion and the API doesn't specify
|
||||||
// the target of the touch events so just assume it's the sub screen
|
// the target of the touch events so just assume it's the sub screen
|
||||||
x = x_raw * 1920 / 1280;
|
x = x * 1920 / 1280;
|
||||||
y = y_raw * 1080 / 720;
|
y = y * 1080 / 720;
|
||||||
} else if (is_sdvx && !GRAPHICS_WINDOWED && !GRAPHICS_FS_ORIENTATION_SWAP) {
|
} else if (is_sdvx) {
|
||||||
// rotate API coordinates into SDVX's portrait touch space
|
sdvx_touch_errata(x, y, use_native, native_canvas_w, native_canvas_h);
|
||||||
x = 1080 - y_raw;
|
|
||||||
y = x_raw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -629,8 +629,10 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool is_sdvx = avs::game::is_model("KFC");
|
||||||
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
|
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
|
||||||
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
|
bool is_sdvx_sub_window = is_sdvx && window_name.ends_with(" Sub Screen");
|
||||||
|
bool is_sdvx_main_window = is_sdvx && window_name.ends_with(" Main Screen");
|
||||||
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
|
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
|
||||||
const std::string gfdm_window_name = games::gitadora::is_arena_model()
|
const std::string gfdm_window_name = games::gitadora::is_arena_model()
|
||||||
? gitadora_canonical_window_name(effective_window_name)
|
? gitadora_canonical_window_name(effective_window_name)
|
||||||
@@ -733,6 +735,20 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
|
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SDVX registers touch on both windows, so name the one synthetic touches must land on
|
||||||
|
// instead of letting window creation order decide: the sub screen window when windowed,
|
||||||
|
// the main window in fullscreen since the game reads it in primary-display coordinates
|
||||||
|
if (nativetouch::is_hooked() &&
|
||||||
|
result != nullptr &&
|
||||||
|
(GRAPHICS_WINDOWED ? is_sdvx_sub_window : is_sdvx_main_window)) {
|
||||||
|
log_misc(
|
||||||
|
"graphics",
|
||||||
|
"SDVX touch surface is {}, {}",
|
||||||
|
fmt::ptr(result),
|
||||||
|
window_name);
|
||||||
|
nativetouch::inject::set_preferred_injection_window(result);
|
||||||
|
}
|
||||||
|
|
||||||
// only hook touch window if multiple windows are allowed
|
// only hook touch window if multiple windows are allowed
|
||||||
if (gfdm_window_name == "LEFT" || gfdm_window_name == "RIGHT") {
|
if (gfdm_window_name == "LEFT" || gfdm_window_name == "RIGHT") {
|
||||||
gitadora_remember_window(result, gfdm_window_name);
|
gitadora_remember_window(result, gfdm_window_name);
|
||||||
@@ -1122,6 +1138,15 @@ static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fullscreen SDVX keeps two adapters so the subscreen overlay can draw, so the game still
|
||||||
|
// creates the sub window even when the user asked for it to be gone
|
||||||
|
if (avs::game::is_model("KFC") &&
|
||||||
|
GRAPHICS_PREVENT_SECONDARY_WINDOWS &&
|
||||||
|
hWnd == SDVX_SUBSCREEN_WINDOW) {
|
||||||
|
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// call original
|
// call original
|
||||||
return ShowWindow_orig(hWnd, nCmdShow);
|
return ShowWindow_orig(hWnd, nCmdShow);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,23 +94,28 @@ namespace nativetouch::transform {
|
|||||||
return overlay::OVERLAY->transform_touch_point(&position->x, &position->y);
|
return overlay::OVERLAY->transform_touch_point(&position->x, &position->y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the digitizer is mapped to the zero-based primary display, while SDVX
|
// SDVX still expects portrait coordinates when its image is rendered in landscape:
|
||||||
// still expects portrait coordinates when its image is rendered in landscape:
|
|
||||||
// (x, y) -> (width * (1 - y / height), height * x / width).
|
// (x, y) -> (width * (1 - y / height), height * x / width).
|
||||||
|
bool sdvx_landscape_rotate(POINT *position, LONG width, LONG height) {
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto input_x = position->x;
|
||||||
|
position->x = width - MulDiv(position->y, width, height);
|
||||||
|
position->y = MulDiv(input_x, height, width);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the digitizer is mapped to the zero-based primary display, so the contact is already
|
||||||
|
// in the effective landscape resolution the rotation is based on
|
||||||
static bool transform_sdvx_landscape_touch_position(POINT *position) {
|
static bool transform_sdvx_landscape_touch_position(POINT *position) {
|
||||||
const auto landscape_width = static_cast<LONG>(GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
|
const auto landscape_width = static_cast<LONG>(GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
|
||||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first : GRAPHICS_FS_ORIGINAL_HEIGHT);
|
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first : GRAPHICS_FS_ORIGINAL_HEIGHT);
|
||||||
const auto landscape_height = static_cast<LONG>(GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
|
const auto landscape_height = static_cast<LONG>(GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
|
||||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second : GRAPHICS_FS_ORIGINAL_WIDTH);
|
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second : GRAPHICS_FS_ORIGINAL_WIDTH);
|
||||||
if (landscape_width <= 0 || landscape_height <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto input_x = position->x;
|
return sdvx_landscape_rotate(position, landscape_width, landscape_height);
|
||||||
position->x = landscape_width -
|
|
||||||
MulDiv(position->y, landscape_width, landscape_height);
|
|
||||||
position->y = MulDiv(input_x, landscape_height, landscape_width);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert physical screen coordinates to game touch coordinates for a known target
|
// convert physical screen coordinates to game touch coordinates for a known target
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace nativetouch::transform {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool is_tdj_dedicated_subscreen(HWND window);
|
bool is_tdj_dedicated_subscreen(HWND window);
|
||||||
|
bool sdvx_landscape_rotate(POINT *position, LONG width, LONG height);
|
||||||
bool game_to_screen(HWND window, POINT *position);
|
bool game_to_screen(HWND window, POINT *position);
|
||||||
bool screen_to_game(HWND window, POINT *position);
|
bool screen_to_game(HWND window, POINT *position);
|
||||||
bool mouse_to_game(HWND window, POINT *position);
|
bool mouse_to_game(HWND window, POINT *position);
|
||||||
|
|||||||
Reference in New Issue
Block a user