touch: restore wintouchemu, fall back to it when native touch hooks fail (#834)

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

## Description of change
Last couple PRs - such as #820 #827 #828 - made the native touch hook &
touch injection using `InjectTouchInput` the default path, since it
performs much more reliably with both real touch screens and mouse (or
any other synthetic source).

However, user has reported that WINE lacks `InjectTouchInput` which
means this won't work.

As a fix, revive the old wintouchemu code. Native touch is still the
default, but under following circumstances:

1. if `-touchemuforce` is set, or
2. if any of the required Windows touch APIs are unavailable

then we fail over from native touch to wintouchemu code. 

For Linux, condition #2 would be hit during init, and gracefully switch
over.

Caveat: the poke code for IIDX and Nost will continue to require native
touch, I do not want to maintain two paths for this. This means that
iidx poke will stop working on Linux, unfortunately.

## Testing
Tested on Windows with `-touchemuforce` set. This is mostly reverting
Linux code path back to where we were last release, so this should just
work with wine.
This commit is contained in:
bicarus
2026-07-26 04:29:45 -07:00
committed by GitHub
parent 64600826b7
commit bd2fbfcb67
20 changed files with 435 additions and 56 deletions
+34 -16
View File
@@ -338,9 +338,7 @@ namespace nativetouch::inject {
// attach mouse injection without replacing the window's existing procedure
static void attach_window_impl(HWND window, bool register_touch) {
initialize_touch_injection();
if (!touch_injection_available()) {
if (!initialize_touch_injection()) {
if (register_touch) {
log_warning(
"touch::native",
@@ -422,7 +420,7 @@ namespace nativetouch::inject {
}
// load and initialize Windows 8 touch injection without a static API dependency
void initialize_touch_injection() {
bool initialize_touch_injection() {
std::call_once(initialization_once, [] {
initialize_synthetic_touch();
contact_refresh_message =
@@ -433,17 +431,26 @@ namespace nativetouch::inject {
}
// load all APIs from user32 without adding static imports
//
// note that these are expected to be present in Windows 8 and above;
// however, on WINE, touch implementation remains in Windows 7 era
// and therefore the hooks below will fail, hence the fallback to
// legacy wintouchemu code
const auto user32 = libutils::load_library("user32.dll");
InitializeTouchInjection_ptr = libutils::try_proc<decltype(InitializeTouchInjection_ptr)>(
user32, "InitializeTouchInjection");
if (InitializeTouchInjection_ptr == nullptr) {
log_warning(
"touch::native", "InitializeTouchInjection unavailable; mouse touch injection disabled");
return;
}
InjectTouchInput_ptr = libutils::try_proc<decltype(InjectTouchInput_ptr)>(
user32, "InjectTouchInput");
if (InitializeTouchInjection_ptr == nullptr ||
InjectTouchInput_ptr == nullptr) {
if (InjectTouchInput_ptr == nullptr) {
clear_touch_injection_functions();
log_warning(
"touch::native", "touch injection API unavailable; mouse touch injection disabled");
"touch::native", "InjectTouchInput unavailable; mouse touch injection disabled");
return;
}
@@ -457,20 +464,31 @@ namespace nativetouch::inject {
log_misc("touch::native", "mouse touch injection initialized");
});
}
bool touch_injection_available() {
return InjectTouchInput_ptr != nullptr;
return InitializeTouchInjection_ptr != nullptr && InjectTouchInput_ptr != nullptr;
}
// install injection support for touch windows registered by the game module
void hook(HMODULE module) {
initialize_touch_injection();
bool hook_available(HMODULE module) {
if (detour::iat_find("RegisterTouchWindow", module) == nullptr) {
log_warning("touch::native", "RegisterTouchWindow unavailable");
return false;
}
return true;
}
bool hook(HMODULE module) {
if (!initialize_touch_injection()) {
return false;
}
RegisterTouchWindow_orig = detour::iat_try(
"RegisterTouchWindow", RegisterTouchWindowHook, module);
if (RegisterTouchWindow_orig != nullptr) {
log_misc("touch::native", "RegisterTouchWindow hooked");
if (RegisterTouchWindow_orig == nullptr) {
log_warning("touch::native", "failed to hook RegisterTouchWindow");
return false;
}
log_misc("touch::native", "RegisterTouchWindow hooked");
return true;
}
}
+2 -1
View File
@@ -7,7 +7,8 @@ struct tagTOUCHINPUT;
namespace nativetouch::inject {
void attach_window(HWND window);
void register_and_attach_window(HWND window);
void hook(HMODULE module);
bool hook_available(HMODULE module);
bool hook(HMODULE module);
bool inject_synthetic_touch(POINT position, bool down);
bool inject_synthetic_touch_from_canvas(POINT position, SIZE canvas, bool down);
bool transform_touch_input(tagTOUCHINPUT *point);
+1 -2
View File
@@ -10,9 +10,8 @@ namespace nativetouch::inject {
Synthetic,
};
void initialize_touch_injection();
bool initialize_touch_injection();
void initialize_synthetic_touch();
bool touch_injection_available();
void refresh_contact_lifetime();
bool contact_is_active();
@@ -113,11 +113,12 @@ namespace nativetouch::inject {
}
static HWND prepare_synthetic_touch() {
initialize_touch_injection();
if (!initialize_touch_injection()) {
return nullptr;
}
const auto window = get_injection_window();
if (!touch_injection_available() || window == nullptr ||
synthetic_touch_message == 0) {
if (window == nullptr || synthetic_touch_message == 0) {
return nullptr;
}
return window;
+40 -7
View File
@@ -223,25 +223,58 @@ namespace nativetouch {
}
}
void hook(HMODULE module) {
static bool hook_prerequisites_available(HMODULE module) {
if (detour::iat_find("GetTouchInputInfo", module) == nullptr) {
log_warning("touch::native", "GetTouchInputInfo unavailable");
return false;
}
if (settings::EMULATE_DIGITIZER &&
detour::iat_find("GetSystemMetrics", module) == nullptr) {
log_warning("touch::native", "GetSystemMetrics unavailable");
return false;
}
return true;
}
bool hook(HMODULE module) {
native_touch_hooked = false;
initialize_game_settings();
inject::hook(module);
// check if the OS supports touch API (Win7+) and injection API (requires Win8+)
if (!hook_prerequisites_available(module)) {
return false;
}
if (!inject::hook_available(module)) {
return false;
}
native_touch_hooked = true;
// try hooking injection API first since they require the highest OS level
// (WINE specifically did not implement this in 2026)
if (!inject::hook(module)) {
return false;
}
// GetSystemMetrics
if (settings::EMULATE_DIGITIZER) {
GetSystemMetrics_orig = detour::iat_try(
"GetSystemMetrics", GetSystemMetricsHook, module);
if (GetSystemMetrics_orig != nullptr) {
log_misc("touch::native", "GetSystemMetrics hooked");
if (GetSystemMetrics_orig == nullptr) {
log_warning("touch::native", "failed to hook GetSystemMetrics");
return false;
}
log_misc("touch::native", "GetSystemMetrics hooked");
}
// GetTouchInputInfo
GetTouchInputInfo_orig = detour::iat_try("GetTouchInputInfo", GetTouchInputInfoHook, module);
if (GetTouchInputInfo_orig != nullptr) {
log_misc("touch::native", "GetTouchInputInfo hooked");
if (GetTouchInputInfo_orig == nullptr) {
log_warning("touch::native", "failed to hook GetTouchInputInfo");
return false;
}
log_misc("touch::native", "GetTouchInputInfo hooked");
native_touch_hooked = true;
return true;
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ namespace nativetouch {
using TouchInputFilter = bool (*)(const NativeTouchEvent &event);
void hook(HMODULE module);
bool hook(HMODULE module);
void refresh_contact_lifetime();
void set_input_filter(TouchInputFilter filter);