mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-10-04 20:58:02 +03:00
## Link to GitHub Issue, if one exists
n/a
## Description of change
Game expects primary monitor to be in 90 deg rotation, and does the
touch calculation accordingly.
If the user does 270 deg rotation ("portrait, flipped") then the game's
touch calculation is flipped upside down. Detect this and provide the
fixed up values in the touch hook.
## Testing
Tested with SDVX in 90 deg, 270 deg rotation, and 1080p, forced 1440p.
568 lines
19 KiB
C++
568 lines
19 KiB
C++
#include "touch.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <windows.h>
|
|
#include <versionhelpers.h>
|
|
|
|
#include "util/logging.h"
|
|
#include "util/time.h"
|
|
#include "touch/touch.h"
|
|
|
|
// std::min
|
|
#ifdef min
|
|
#undef min
|
|
#endif
|
|
|
|
// std::max
|
|
#ifdef max
|
|
#undef max
|
|
#endif
|
|
|
|
namespace rawinput::touch {
|
|
|
|
// settings
|
|
bool DISABLED = false;
|
|
bool INVERTED = false;
|
|
|
|
// state
|
|
DWORD DISPLAY_ORIENTATION = DMDO_DEFAULT;
|
|
long DISPLAY_SIZE_X = 1920L;
|
|
long DISPLAY_SIZE_Y = 1080L;
|
|
bool DISPLAY_INITIALIZED = false;
|
|
|
|
bool is_touchscreen(Device *device) {
|
|
|
|
// check if disabled
|
|
if (DISABLED) {
|
|
return false;
|
|
}
|
|
|
|
// check device type
|
|
if (device->type != HID) {
|
|
return false;
|
|
}
|
|
|
|
auto *hid = device->hidInfo;
|
|
auto &attributes = hid->attributes;
|
|
|
|
// filter by VID/PID
|
|
if (attributes.VendorID > 0) {
|
|
|
|
// P2418HT
|
|
// touch points apparently aren't released and will stay
|
|
if (attributes.VendorID == 0x1FD2 && attributes.ProductID == 0x6103) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ignore the "Touch Pad" (0x05) usage under the "Digitizers" (0x0d) usage page
|
|
if (hid->caps.UsagePage == 0x0d && hid->caps.Usage == 0x05) {
|
|
return false;
|
|
}
|
|
|
|
// check description
|
|
if (device->desc == "HID-compliant touch screen") {
|
|
// TODO: this only works on english OS (?)
|
|
return true;
|
|
}
|
|
|
|
// we can also check if there's touch point values inside
|
|
for (const auto &value_name : hid->value_caps_names) {
|
|
if (value_name == "Contact identifier") {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// nope this one probably not
|
|
return false;
|
|
}
|
|
|
|
void enable(Device *device) {
|
|
if (device->type == HID) {
|
|
device->hidInfo->touch.valid = true;
|
|
log_info("rawinput", "enabled touchscreen device: {} ({})", device->desc, device->name);
|
|
} else {
|
|
log_fatal("rawinput", "tried to enable touch functionality on non HID device");
|
|
}
|
|
}
|
|
|
|
void disable(Device *device) {
|
|
if (device->type == HID) {
|
|
device->hidInfo->touch.valid = false;
|
|
log_info("rawinput", "disabled touchscreen device: {} ({})", device->desc, device->name);
|
|
} else
|
|
log_fatal("rawinput", "tried to disable touch functionality on non HID device");
|
|
}
|
|
|
|
void update_input(Device *device) {
|
|
|
|
// check type
|
|
if (device->type != HID) {
|
|
log_fatal("rawinput", "touch update called on non HID device");
|
|
return;
|
|
}
|
|
|
|
// get touch info
|
|
auto hid = device->hidInfo;
|
|
auto &touch = hid->touch;
|
|
if (!touch.valid) {
|
|
|
|
// not a registered touchscreen
|
|
return;
|
|
}
|
|
|
|
// parse elements
|
|
if (!touch.parsed_elements) {
|
|
log_info("rawinput", "parsing touch elements");
|
|
|
|
// clear lists first
|
|
touch.elements_contact_count.clear();
|
|
touch.elements_contact_identifier.clear();
|
|
touch.elements_x.clear();
|
|
touch.elements_y.clear();
|
|
touch.elements_width.clear();
|
|
touch.elements_height.clear();
|
|
touch.elements_pressed.clear();
|
|
touch.elements_pressure.clear();
|
|
|
|
// get value indices
|
|
for (int i = 0; i < (int) hid->value_caps_names.size(); i++) {
|
|
auto &name = hid->value_caps_names[i];
|
|
if (name == "Contact count") {
|
|
touch.elements_contact_count.push_back(i);
|
|
} else if (name == "X") {
|
|
touch.elements_x.push_back(i);
|
|
} else if (name == "Y") {
|
|
touch.elements_y.push_back(i);
|
|
} else if (name == "Width") {
|
|
touch.elements_width.push_back(i);
|
|
} else if (name == "Height") {
|
|
touch.elements_height.push_back(i);
|
|
} else if (name == "Contact identifier") {
|
|
touch.elements_contact_identifier.push_back(i);
|
|
} else if (name == "Pressure") {
|
|
touch.elements_pressure.push_back(i);
|
|
}
|
|
}
|
|
|
|
// get button indices
|
|
for (int i = 0; i < (int) hid->button_caps_names.size(); i++) {
|
|
auto &name = hid->button_caps_names[i];
|
|
if (name == "Tip Switch") {
|
|
touch.elements_pressed.push_back(i);
|
|
}
|
|
}
|
|
|
|
// check sizes
|
|
auto touch_size = touch.elements_contact_identifier.size();
|
|
if (touch_size != touch.elements_x.size() ||
|
|
touch_size != touch.elements_y.size() ||
|
|
touch_size != touch.elements_pressed.size())
|
|
{
|
|
log_info("rawinput", "touch element size mismatch: {}:contacts, {}:x, {}:y, {}:pressed",
|
|
touch.elements_contact_identifier.size(),
|
|
touch.elements_x.size(),
|
|
touch.elements_y.size(),
|
|
touch.elements_pressed.size());
|
|
disable(device);
|
|
return;
|
|
}
|
|
|
|
// mark as parsed
|
|
touch.parsed_elements = true;
|
|
log_info("rawinput", "touch elements parsed: {} status fields", touch.elements_x.size());
|
|
}
|
|
|
|
// check if display is initialized
|
|
if (!DISPLAY_INITIALIZED) {
|
|
display_update();
|
|
}
|
|
|
|
// update timeouts here as well so events are in the right order
|
|
update_timeouts(device);
|
|
|
|
// determine the number of touches contained in this report, defaulting to the size of `elements_x`
|
|
size_t touch_report_count = touch.elements_x.size();
|
|
if (!touch.elements_contact_count.empty()) {
|
|
|
|
// support devices that have multiple "Contact count" fields, for some reason
|
|
size_t contact_count = 0;
|
|
for (auto &index : touch.elements_contact_count) {
|
|
contact_count = std::max(contact_count, (size_t) hid->value_states_raw[index]);
|
|
}
|
|
|
|
// hybrid mode devices will report a contact count of 0 for subsequent reports that are
|
|
// part of the same initial frame
|
|
if (contact_count > 0) {
|
|
if (contact_count > touch_report_count) {
|
|
touch.remaining_contact_count = contact_count - touch_report_count;
|
|
} else {
|
|
touch_report_count = contact_count;
|
|
touch.remaining_contact_count = 0;
|
|
}
|
|
} else if (touch.remaining_contact_count > 0) {
|
|
if (touch.remaining_contact_count > touch_report_count) {
|
|
touch.remaining_contact_count -= touch_report_count;
|
|
} else {
|
|
touch_report_count = touch.remaining_contact_count;
|
|
touch.remaining_contact_count = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// iterate all input data and get touch points
|
|
std::vector<HIDTouchPoint> touch_points;
|
|
touch_points.reserve(touch.elements_x.size());
|
|
for (size_t i = 0; i < touch.elements_x.size(); i++) {
|
|
|
|
// build touch point
|
|
HIDTouchPoint hid_tp{};
|
|
auto pos_x = hid->value_states[touch.elements_x[i]];
|
|
auto pos_y = hid->value_states[touch.elements_y[i]];
|
|
switch (DISPLAY_ORIENTATION) {
|
|
case DMDO_DEFAULT:
|
|
hid_tp.x = pos_x;
|
|
hid_tp.y = pos_y;
|
|
break;
|
|
case DMDO_90:
|
|
hid_tp.x = 1.f - pos_y;
|
|
hid_tp.y = pos_x;
|
|
break;
|
|
case DMDO_180:
|
|
hid_tp.x = 1.f - pos_x;
|
|
hid_tp.y = 1.f - pos_y;
|
|
break;
|
|
case DMDO_270:
|
|
hid_tp.x = pos_y;
|
|
hid_tp.y = 1.f - pos_x;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// optionally invert
|
|
if (INVERTED) {
|
|
hid_tp.x = 1.f - hid_tp.x;
|
|
hid_tp.y = 1.f - hid_tp.y;
|
|
}
|
|
|
|
// check if this touch point should be considered valid
|
|
//
|
|
// If "Contact count" reports there are no touch reports remaining and the X and Y
|
|
// coordinates of this touch point are zero, then this report element should be
|
|
// skipped.
|
|
if (touch_report_count == 0 && hid_tp.x == 0.f && hid_tp.y == 0.f) {
|
|
continue;
|
|
} else if (touch_report_count > 0) {
|
|
touch_report_count--;
|
|
}
|
|
|
|
// generate ID (hopefully unique)
|
|
hid_tp.id = (DWORD) hid->value_states_raw[touch.elements_contact_identifier[i]];
|
|
hid_tp.id += (DWORD) (0xFFFFFF + device->id * 512);
|
|
|
|
//std::string src = "(none)";
|
|
|
|
// check if tip switch is down
|
|
size_t index_pressed = touch.elements_pressed[i];
|
|
for (auto &button_states : hid->button_states) {
|
|
if (index_pressed < button_states.size()) {
|
|
hid_tp.down = button_states[index_pressed];
|
|
|
|
/*
|
|
if (hid_tp.down)
|
|
src = "pressed (index: " + to_string(touch.elements_pressed[i]) + ", state: " + to_string(button_states[index_pressed]) +")";
|
|
*/
|
|
|
|
break;
|
|
} else
|
|
index_pressed -= button_states.size();
|
|
}
|
|
|
|
// check width/height of touch point to see if pressed
|
|
float width = 0.f, height = 0.f;
|
|
if (!hid_tp.down && i < touch.elements_width.size() && i < touch.elements_height.size()) {
|
|
width = hid->value_states[touch.elements_width[i]];
|
|
height = hid->value_states[touch.elements_height[i]];
|
|
hid_tp.down = width > 0.f && height > 0.f;
|
|
|
|
/*
|
|
if (hid_tp.down)
|
|
src = "width_height (width index: " + to_string(touch.elements_width[i]) + ", height index: " + to_string(touch.elements_height[i]) + ")";
|
|
*/
|
|
}
|
|
|
|
// so last thing we can check is the pressure
|
|
if (!hid_tp.down && i < touch.elements_pressure.size()) {
|
|
auto pressure = hid->value_states[touch.elements_pressure[i]];
|
|
hid_tp.down = pressure > 0.f;
|
|
|
|
/*
|
|
if (hid_tp.down)
|
|
src = "pressure (index: " + to_string(touch.elements_pressure[i]) + ")";
|
|
*/
|
|
}
|
|
|
|
/*
|
|
log_info("rawinput",
|
|
"touch i: " + to_string(i) +
|
|
" (id: " + to_string(hid_tp.id) +
|
|
"), ci: " + to_string(hid->value_states_raw[touch.elements_contact_identifier[i]]) +
|
|
", x: " + to_string(pos_x) +
|
|
", y: " + to_string(pos_y) +
|
|
", width: " + to_string(width) +
|
|
", height: " + to_string(height) +
|
|
", down: " + to_string(hid_tp.down) +
|
|
", src: " + src);
|
|
*/
|
|
|
|
// add to touch points
|
|
touch_points.emplace_back(hid_tp);
|
|
}
|
|
|
|
// process touch points
|
|
std::vector<DWORD> touch_removes;
|
|
std::vector<TouchPoint> touch_writes;
|
|
std::vector<DWORD> touch_modifications;
|
|
for (auto &hid_tp : touch_points) {
|
|
|
|
// check if existing
|
|
auto existing = std::find_if(
|
|
touch.touch_points.begin(),
|
|
touch.touch_points.end(),
|
|
[hid_tp] (const HIDTouchPoint &x) {
|
|
return x.id == hid_tp.id;
|
|
});
|
|
|
|
/*
|
|
ssize_t ttl = -1;
|
|
int64_t last_report = -1;
|
|
if (existing != touch.touch_points.end()) {
|
|
ttl = existing->ttl;
|
|
last_report = existing->last_report;
|
|
}
|
|
|
|
log_info("rawinput",
|
|
"id: " + to_string(hid_tp.id) +
|
|
", existing: " + to_string(existing != touch.touch_points.end()) +
|
|
", ttl: " + to_string(ttl) +
|
|
", last_report: " + to_string(last_report) +
|
|
", down: " + to_string(hid_tp.down));
|
|
*/
|
|
|
|
// check if pressed
|
|
if (!hid_tp.down) {
|
|
|
|
// only remove if it exists
|
|
if (existing != touch.touch_points.end()) {
|
|
|
|
// remove all touch points with this ID
|
|
touch_removes.push_back(hid_tp.id);
|
|
touch.touch_points.erase(std::remove_if(
|
|
touch.touch_points.begin(),
|
|
touch.touch_points.end(),
|
|
[hid_tp] (const HIDTouchPoint &x) {
|
|
return x.id == hid_tp.id;
|
|
}), touch.touch_points.end());
|
|
}
|
|
} else {
|
|
|
|
// write touch point
|
|
TouchPoint tp {
|
|
.id = hid_tp.id,
|
|
.x = (long) (hid_tp.x * DISPLAY_SIZE_X),
|
|
.y = (long) (hid_tp.y * DISPLAY_SIZE_Y),
|
|
.mouse = false,
|
|
};
|
|
touch_writes.push_back(tp);
|
|
touch_modifications.push_back(hid_tp.id);
|
|
|
|
// check if existing
|
|
if (existing == touch.touch_points.end()) {
|
|
|
|
// add new touch point
|
|
touch.touch_points.push_back(hid_tp);
|
|
|
|
} else {
|
|
|
|
// update existing touch point
|
|
*existing = hid_tp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set TTL and last report time
|
|
if (!touch.elements_x.empty() && !touch_modifications.empty()) {
|
|
auto ttl = touch.touch_points.size() / touch.elements_x.size() + 1;
|
|
auto system_time_ms = get_system_milliseconds();
|
|
for (auto &hid_tp_id : touch_modifications) {
|
|
for (auto &hid_tp : touch.touch_points) {
|
|
if (hid_tp.id == hid_tp_id) {
|
|
hid_tp.ttl = ttl;
|
|
hid_tp.last_report = system_time_ms;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove dead touch points
|
|
auto ttl_it = touch.touch_points.begin();
|
|
while (ttl_it != touch.touch_points.end()) {
|
|
auto &tp = *ttl_it;
|
|
if (tp.ttl == 0) {
|
|
touch_removes.push_back(tp.id);
|
|
ttl_it = touch.touch_points.erase(ttl_it);
|
|
} else {
|
|
tp.ttl--;
|
|
ttl_it++;
|
|
}
|
|
}
|
|
|
|
#ifndef SPICETOOLS_SPICECFG_STANDALONE
|
|
|
|
// update touch module
|
|
touch_remove_points(&touch_removes);
|
|
touch_write_points(&touch_writes);
|
|
#endif
|
|
}
|
|
|
|
void update_timeouts(Device *device) {
|
|
|
|
// check type
|
|
if (device->type != HID) {
|
|
log_fatal("rawinput", "touch timeout update called on non HID device");
|
|
return;
|
|
}
|
|
|
|
// get touch info
|
|
auto hid = device->hidInfo;
|
|
auto &touch = hid->touch;
|
|
if (!touch.valid) {
|
|
return; // not a registered touchscreen
|
|
}
|
|
|
|
// calculate deadline - we allow the devices 50ms of not sending shit
|
|
auto deadline = get_system_milliseconds() - 50;
|
|
|
|
// check touch points
|
|
std::vector<DWORD> touch_removes;
|
|
auto touch_it = touch.touch_points.begin();
|
|
while (touch_it != touch.touch_points.end()) {
|
|
auto &hid_tp = *touch_it;
|
|
|
|
// see if it's behind the deadline
|
|
if (hid_tp.last_report < deadline) {
|
|
|
|
// oops it's gone
|
|
touch_removes.push_back(hid_tp.id);
|
|
touch_it = touch.touch_points.erase(touch_it);
|
|
|
|
} else {
|
|
|
|
// check the next device
|
|
touch_it++;
|
|
}
|
|
}
|
|
|
|
#ifndef SPICETOOLS_SPICECFG_STANDALONE
|
|
|
|
// remove from touch module
|
|
touch_remove_points(&touch_removes);
|
|
#endif
|
|
}
|
|
|
|
void update_timeouts(RawInputManager *manager) {
|
|
|
|
// iterate all devices
|
|
for (auto &device : manager->devices_get()) {
|
|
|
|
// check if it's a valid touchscreen
|
|
if (device.type == HID && device.hidInfo->touch.valid) {
|
|
|
|
// lock n' load
|
|
device.mutex->lock();
|
|
update_timeouts(&device);
|
|
device.mutex->unlock();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool is_enabled(RawInputManager *manager) {
|
|
|
|
// check if disabled or manager is null
|
|
if (DISABLED || manager == nullptr)
|
|
return false;
|
|
|
|
// check if at least one device is marked as valid touchscreen
|
|
for (auto &device : manager->devices_get()) {
|
|
if (device.type == HID && device.hidInfo->touch.valid) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// no valid touch screen found
|
|
return false;
|
|
}
|
|
|
|
void display_update() {
|
|
|
|
// check if disabled
|
|
if (DISABLED)
|
|
return;
|
|
|
|
// determine monitor size
|
|
static RECT display_rect;
|
|
GetWindowRect(GetDesktopWindow(), &display_rect);
|
|
DISPLAY_SIZE_X = display_rect.right - display_rect.left;
|
|
DISPLAY_SIZE_Y = display_rect.bottom - display_rect.top;
|
|
log_info("rawinput", "display size: {}x{}", (int) DISPLAY_SIZE_X, (int) DISPLAY_SIZE_Y);
|
|
|
|
// determine monitor orientation
|
|
DEVMODE display_mode{};
|
|
display_mode.dmSize = sizeof(DEVMODE);
|
|
if (!EnumDisplaySettingsEx(nullptr, ENUM_CURRENT_SETTINGS, &display_mode, EDS_RAWMODE)) {
|
|
log_info("rawinput", "failed to determine monitor mode");
|
|
} else if (display_mode.dmFields & DM_DISPLAYORIENTATION) {
|
|
DISPLAY_ORIENTATION = display_mode.dmDisplayOrientation;
|
|
switch (DISPLAY_ORIENTATION) {
|
|
case DMDO_DEFAULT:
|
|
log_info("rawinput", "display rotation: 0");
|
|
break;
|
|
case DMDO_90:
|
|
log_info("rawinput", "display rotation: 90");
|
|
break;
|
|
case DMDO_180:
|
|
log_info("rawinput", "display rotation: 180");
|
|
break;
|
|
case DMDO_270:
|
|
log_info("rawinput", "display rotation: 270");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// another XP fix
|
|
if (!IsWindowsVistaOrGreater()) {
|
|
switch (DISPLAY_ORIENTATION) {
|
|
case DMDO_90:
|
|
DISPLAY_ORIENTATION = DMDO_180;
|
|
log_info("rawinput", "flipping to 180");
|
|
break;
|
|
case DMDO_270:
|
|
DISPLAY_ORIENTATION = DMDO_90;
|
|
log_info("rawinput", "flipping to 90");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
log_info("rawinput", "failed to determine monitor orientation");
|
|
}
|
|
|
|
// mark as initialized
|
|
DISPLAY_INITIALIZED = true;
|
|
}
|
|
}
|