mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 14:20:42 -07:00
bd2fbfcb67
## 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.
176 lines
6.3 KiB
C++
176 lines
6.3 KiB
C++
// enable Windows 8 touch injection types; the functions are loaded dynamically
|
|
#define _WIN32_WINNT 0x0602
|
|
|
|
#include <mutex>
|
|
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
|
|
#include "inject.h"
|
|
#include "inject_internal.h"
|
|
#include "settings.h"
|
|
#include "transform.h"
|
|
|
|
#include "util/logging.h"
|
|
|
|
namespace nativetouch::inject {
|
|
|
|
constexpr UINT SYNTHETIC_CONTACT_TIMEOUT_MS = 100;
|
|
|
|
enum class SyntheticTouchMessage : WPARAM {
|
|
Up, // used by callers releasing a contact
|
|
DownGameSpace, // coordinates are relative to the game's logical touch surface
|
|
DownScreenSpace, // coordinates are absolute pixels in Windows desktop coordinates
|
|
};
|
|
|
|
static std::once_flag synthetic_initialization_once;
|
|
static int synthetic_contact_timer_token;
|
|
static UINT synthetic_touch_message;
|
|
|
|
void initialize_synthetic_touch() {
|
|
std::call_once(synthetic_initialization_once, [] {
|
|
synthetic_touch_message = RegisterWindowMessageW(L"spice2x.native_touch.inject");
|
|
if (synthetic_touch_message == 0) {
|
|
log_warning(
|
|
"touch::native", "failed to register synthetic touch message: {}", GetLastError());
|
|
}
|
|
});
|
|
}
|
|
|
|
// synthetic touches preempt the mouse and keep it disabled until release or timeout
|
|
static void begin_synthetic_contact(HWND window, POINT position, bool screen_space) {
|
|
// remember when Windows-returned coordinates must map back into game space
|
|
const auto transform_returned_coordinates =
|
|
transform::is_tdj_dedicated_subscreen(window) ||
|
|
settings::SYNTHETIC_TOUCH_USES_CLIENT_COORDINATES;
|
|
if (!screen_space && !transform::game_to_screen(window, &position)) {
|
|
return;
|
|
}
|
|
|
|
const auto timer_id = reinterpret_cast<UINT_PTR>(&synthetic_contact_timer_token);
|
|
|
|
// when this producer already owns the contact, move it instead of releasing and
|
|
// re-pressing so continuous input (such as the API surface) drags smoothly
|
|
if (contact_is_owned_by(ContactOwner::Synthetic, window)) {
|
|
if (update_contact(ContactOwner::Synthetic, window, position)) {
|
|
// refresh the safety timeout while updates keep arriving
|
|
if (SetTimer(window, timer_id, SYNTHETIC_CONTACT_TIMEOUT_MS, nullptr)) {
|
|
set_contact_timer(ContactOwner::Synthetic, window, timer_id);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!release_active_contact()) {
|
|
return;
|
|
}
|
|
if (!begin_contact(
|
|
ContactOwner::Synthetic,
|
|
window,
|
|
position,
|
|
transform_returned_coordinates)) {
|
|
return;
|
|
}
|
|
|
|
if (SetTimer(window, timer_id, SYNTHETIC_CONTACT_TIMEOUT_MS, nullptr)) {
|
|
set_contact_timer(ContactOwner::Synthetic, window, timer_id);
|
|
} else {
|
|
log_warning("touch::native", "failed to start synthetic touch timeout timer");
|
|
}
|
|
}
|
|
|
|
static void end_synthetic_contact(HWND window) {
|
|
if (contact_is_owned_by(ContactOwner::Synthetic, window)) {
|
|
release_active_contact();
|
|
}
|
|
}
|
|
|
|
bool handle_synthetic_message(
|
|
HWND window, UINT message, WPARAM w_param, LPARAM l_param) {
|
|
if (synthetic_touch_message != 0 && message == synthetic_touch_message) {
|
|
POINT position { GET_X_LPARAM(l_param), GET_Y_LPARAM(l_param) };
|
|
switch (static_cast<SyntheticTouchMessage>(w_param)) {
|
|
case SyntheticTouchMessage::DownGameSpace:
|
|
begin_synthetic_contact(window, position, false);
|
|
break;
|
|
case SyntheticTouchMessage::DownScreenSpace:
|
|
begin_synthetic_contact(window, position, true);
|
|
break;
|
|
default:
|
|
end_synthetic_contact(window);
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (message == WM_TIMER &&
|
|
w_param == reinterpret_cast<UINT_PTR>(&synthetic_contact_timer_token)) {
|
|
end_synthetic_contact(window);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static HWND prepare_synthetic_touch() {
|
|
if (!initialize_touch_injection()) {
|
|
return nullptr;
|
|
}
|
|
|
|
const auto window = get_injection_window();
|
|
if (window == nullptr || synthetic_touch_message == 0) {
|
|
return nullptr;
|
|
}
|
|
return window;
|
|
}
|
|
|
|
static bool post_synthetic_touch(
|
|
HWND window, POINT position, SyntheticTouchMessage message) {
|
|
return PostMessageW(
|
|
window,
|
|
synthetic_touch_message,
|
|
static_cast<WPARAM>(message),
|
|
MAKELPARAM(position.x, position.y)) != FALSE;
|
|
}
|
|
|
|
// inject a point expressed in the game's synthetic touch coordinate space
|
|
bool inject_synthetic_touch(POINT position, bool down) {
|
|
const auto window = prepare_synthetic_touch();
|
|
if (window == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
const auto message = down
|
|
? SyntheticTouchMessage::DownGameSpace
|
|
: SyntheticTouchMessage::Up;
|
|
return post_synthetic_touch(window, position, message);
|
|
}
|
|
|
|
// map a logical canvas point onto the live injection window before injecting it
|
|
bool inject_synthetic_touch_from_canvas(POINT position, SIZE canvas, bool down) {
|
|
const auto window = prepare_synthetic_touch();
|
|
if (window == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (!down) {
|
|
return post_synthetic_touch(window, position, SyntheticTouchMessage::Up);
|
|
}
|
|
|
|
RECT client_rect {};
|
|
if (canvas.cx <= 0 || canvas.cy <= 0 ||
|
|
!GetClientRect(window, &client_rect) ||
|
|
client_rect.right <= 0 || client_rect.bottom <= 0) {
|
|
return false;
|
|
}
|
|
|
|
position.x = MulDiv(position.x, client_rect.right, canvas.cx);
|
|
position.y = MulDiv(position.y, client_rect.bottom, canvas.cy);
|
|
if (!ClientToScreen(window, &position)) {
|
|
return false;
|
|
}
|
|
|
|
return post_synthetic_touch(window, position, SyntheticTouchMessage::DownScreenSpace);
|
|
}
|
|
}
|