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
|
||
|
static bool DISPLAY_INITIALIZED = false;
|
||
|
static DWORD DISPLAY_ORIENTATION = DMDO_DEFAULT;
|
||
|
static long DISPLAY_SIZE_X = 1920L;
|
||
|
static long DISPLAY_SIZE_Y = 1080L;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|