mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-02-21 18:39:59 +00:00
Implement pen and touch support for Windows
This commit is contained in:
parent
43463a9d1e
commit
bd68aebe4c
270
src/input.cpp
270
src/input.cpp
@ -11,6 +11,7 @@ extern "C" {
|
||||
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <list>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
@ -90,6 +91,18 @@ namespace input {
|
||||
return boost::endian::endian_load<float, sizeof(float), boost::endian::order::little>(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a little-endian netfloat to a native endianness float and clamps it.
|
||||
* @param f Netfloat value.
|
||||
* @param min The minimium value for clamping.
|
||||
* @param max The maximum value for clamping.
|
||||
* @return Clamped float value.
|
||||
*/
|
||||
float
|
||||
from_clamped_netfloat(netfloat f, float min, float max) {
|
||||
return std::clamp(from_netfloat(f), min, max);
|
||||
}
|
||||
|
||||
static task_pool_util::TaskPool::task_id_t key_press_repeat_id {};
|
||||
static std::unordered_map<key_press_id_t, bool> key_press {};
|
||||
static std::array<std::uint8_t, 5> mouse_press {};
|
||||
@ -143,6 +156,7 @@ namespace input {
|
||||
platf::feedback_queue_t feedback_queue):
|
||||
shortcutFlags {},
|
||||
gamepads(MAX_GAMEPADS),
|
||||
client_context { platf::allocate_client_input_context(platf_input) },
|
||||
touch_port_event { std::move(touch_port_event) },
|
||||
feedback_queue { std::move(feedback_queue) },
|
||||
mouse_left_button_timeout {},
|
||||
@ -152,6 +166,7 @@ namespace input {
|
||||
int shortcutFlags;
|
||||
|
||||
std::vector<gamepad_t> gamepads;
|
||||
std::unique_ptr<platf::client_input_t> client_context;
|
||||
|
||||
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event;
|
||||
platf::feedback_queue_t feedback_queue;
|
||||
@ -274,6 +289,46 @@ namespace input {
|
||||
<< "--end controller packet--"sv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prints a touch packet.
|
||||
* @param packet The touch packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_TOUCH_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin touch packet--"sv << std::endl
|
||||
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
|
||||
<< "pointerId ["sv << util::hex(packet->pointerId).to_string_view() << ']' << std::endl
|
||||
<< "x ["sv << from_netfloat(packet->x) << ']' << std::endl
|
||||
<< "y ["sv << from_netfloat(packet->y) << ']' << std::endl
|
||||
<< "pressureOrDistance ["sv << from_netfloat(packet->pressureOrDistance) << ']' << std::endl
|
||||
<< "contactAreaMajor ["sv << from_netfloat(packet->contactAreaMajor) << ']' << std::endl
|
||||
<< "contactAreaMinor ["sv << from_netfloat(packet->contactAreaMinor) << ']' << std::endl
|
||||
<< "rotation ["sv << (uint32_t) packet->rotation << ']' << std::endl
|
||||
<< "--end touch packet--"sv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prints a pen packet.
|
||||
* @param packet The pen packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_PEN_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin pen packet--"sv << std::endl
|
||||
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
|
||||
<< "toolType ["sv << util::hex(packet->toolType).to_string_view() << ']' << std::endl
|
||||
<< "penButtons ["sv << util::hex(packet->penButtons).to_string_view() << ']' << std::endl
|
||||
<< "x ["sv << from_netfloat(packet->x) << ']' << std::endl
|
||||
<< "y ["sv << from_netfloat(packet->y) << ']' << std::endl
|
||||
<< "pressureOrDistance ["sv << from_netfloat(packet->pressureOrDistance) << ']' << std::endl
|
||||
<< "contactAreaMajor ["sv << from_netfloat(packet->contactAreaMajor) << ']' << std::endl
|
||||
<< "contactAreaMinor ["sv << from_netfloat(packet->contactAreaMinor) << ']' << std::endl
|
||||
<< "rotation ["sv << (uint32_t) packet->rotation << ']' << std::endl
|
||||
<< "tilt ["sv << (uint32_t) packet->tilt << ']' << std::endl
|
||||
<< "--end pen packet--"sv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prints a controller arrival packet.
|
||||
* @param packet The controller arrival packet.
|
||||
@ -367,6 +422,12 @@ namespace input {
|
||||
case MULTI_CONTROLLER_MAGIC_GEN5:
|
||||
print((PNV_MULTI_CONTROLLER_PACKET) payload);
|
||||
break;
|
||||
case SS_TOUCH_MAGIC:
|
||||
print((PSS_TOUCH_PACKET) payload);
|
||||
break;
|
||||
case SS_PEN_MAGIC:
|
||||
print((PSS_PEN_PACKET) payload);
|
||||
break;
|
||||
case SS_CONTROLLER_ARRIVAL_MAGIC:
|
||||
print((PSS_CONTROLLER_ARRIVAL_PACKET) payload);
|
||||
break;
|
||||
@ -392,6 +453,78 @@ namespace input {
|
||||
platf::move_mouse(platf_input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts client coordinates on the specified surface into screen coordinates.
|
||||
* @param input The input context.
|
||||
* @param val The cartesian coordinate pair to convert.
|
||||
* @param size The size of the client's surface containing the value.
|
||||
* @return The host-relative coordinate pair.
|
||||
*/
|
||||
std::pair<float, float>
|
||||
client_to_touchport(std::shared_ptr<input_t> &input, const std::pair<float, float> &val, const std::pair<float, float> &size) {
|
||||
auto &touch_port_event = input->touch_port_event;
|
||||
auto &touch_port = input->touch_port;
|
||||
if (touch_port_event->peek()) {
|
||||
touch_port = *touch_port_event->pop();
|
||||
}
|
||||
|
||||
auto scalarX = touch_port.width / size.first;
|
||||
auto scalarY = touch_port.height / size.second;
|
||||
|
||||
float x = std::clamp(val.first, 0.0f, size.first) * scalarX;
|
||||
float y = std::clamp(val.second, 0.0f, size.second) * scalarY;
|
||||
|
||||
auto offsetX = touch_port.client_offsetX;
|
||||
auto offsetY = touch_port.client_offsetY;
|
||||
|
||||
x = std::clamp(x, offsetX, size.first - offsetX);
|
||||
y = std::clamp(y, offsetY, size.second - offsetY);
|
||||
|
||||
return { (x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Multiplies a polar coordinate pair by a cartesian scaling factor.
|
||||
* @param r The radial coordinate.
|
||||
* @param angle The angular coordinate (radians).
|
||||
* @param scalar The scalar cartesian coordinate pair.
|
||||
* @return The scaled radial coordinate.
|
||||
*/
|
||||
float
|
||||
multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
|
||||
// Convert polar to cartesian coordinates
|
||||
float x = r * std::cos(angle);
|
||||
float y = r * std::sin(angle);
|
||||
|
||||
// Scale the values
|
||||
x *= scalar.first;
|
||||
y *= scalar.second;
|
||||
|
||||
// Convert the result back to a polar radial coordinate
|
||||
return std::sqrt(std::pow(x, 2) + std::pow(y, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Scales the ellipse axes according to the provided size.
|
||||
* @param val The major and minor axis pair.
|
||||
* @param rotation The rotation value from the touch/pen event.
|
||||
* @param scalar The scalar cartesian coordinate pair.
|
||||
* @return The major and minor axis pair.
|
||||
*/
|
||||
std::pair<float, float>
|
||||
scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar) {
|
||||
// If the rotation is unknown, we'll just scale both axes equally by using
|
||||
// a 45 degree angle for our scaling calculations
|
||||
float angle = rotation == LI_ROT_UNKNOWN ? (M_PI / 4) : (rotation * (M_PI / 180));
|
||||
|
||||
// If we have a major but not a minor axis, treat the touch as circular
|
||||
float major = val.first;
|
||||
float minor = val.second != 0.0f ? val.second : val.first;
|
||||
|
||||
// The minor axis is perpendicular to major axis so the angle must be rotated by 90 degrees
|
||||
return { multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar) };
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
@ -402,12 +535,6 @@ namespace input {
|
||||
input->mouse_left_button_timeout = ENABLE_LEFT_BUTTON_DELAY;
|
||||
}
|
||||
|
||||
auto &touch_port_event = input->touch_port_event;
|
||||
auto &touch_port = input->touch_port;
|
||||
if (touch_port_event->peek()) {
|
||||
touch_port = *touch_port_event->pop();
|
||||
}
|
||||
|
||||
float x = util::endian::big(packet->x);
|
||||
float y = util::endian::big(packet->y);
|
||||
|
||||
@ -422,24 +549,15 @@ namespace input {
|
||||
auto width = (float) util::endian::big(packet->width);
|
||||
auto height = (float) util::endian::big(packet->height);
|
||||
|
||||
auto scalarX = touch_port.width / width;
|
||||
auto scalarY = touch_port.height / height;
|
||||
|
||||
x *= scalarX;
|
||||
y *= scalarY;
|
||||
|
||||
auto offsetX = touch_port.client_offsetX;
|
||||
auto offsetY = touch_port.client_offsetY;
|
||||
|
||||
std::clamp(x, offsetX, width - offsetX);
|
||||
std::clamp(y, offsetY, height - offsetY);
|
||||
auto tpcoords = client_to_touchport(input, { x, y }, { width, height });
|
||||
|
||||
auto &touch_port = input->touch_port;
|
||||
platf::touch_port_t abs_port {
|
||||
touch_port.offset_x, touch_port.offset_y,
|
||||
touch_port.env_width, touch_port.env_height
|
||||
};
|
||||
|
||||
platf::abs_mouse(platf_input, abs_port, (x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv);
|
||||
platf::abs_mouse(platf_input, abs_port, tpcoords.first, tpcoords.second);
|
||||
}
|
||||
|
||||
void
|
||||
@ -741,6 +859,116 @@ namespace input {
|
||||
input->gamepads[packet->controllerNumber].id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called to pass a touch message to the platform backend.
|
||||
* @param input The input context pointer.
|
||||
* @param packet The touch packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the client normalized coordinates to touchport coordinates
|
||||
auto coords = client_to_touchport(input,
|
||||
{ from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f },
|
||||
{ 65535.f, 65535.f });
|
||||
|
||||
auto &touch_port = input->touch_port;
|
||||
platf::touch_port_t abs_port {
|
||||
touch_port.offset_x, touch_port.offset_y,
|
||||
touch_port.env_width, touch_port.env_height
|
||||
};
|
||||
|
||||
// Renormalize the coordinates
|
||||
coords.first /= abs_port.width;
|
||||
coords.second /= abs_port.height;
|
||||
|
||||
// Normalize rotation value to 0-359 degree range
|
||||
auto rotation = util::endian::little(packet->rotation);
|
||||
if (rotation != LI_ROT_UNKNOWN) {
|
||||
rotation %= 360;
|
||||
}
|
||||
|
||||
// Normalize the contact area based on the touchport
|
||||
auto contact_area = scale_client_contact_area(
|
||||
{ from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f },
|
||||
rotation,
|
||||
{ abs_port.width / 65535.f, abs_port.height / 65535.f });
|
||||
|
||||
platf::touch_input_t touch {
|
||||
packet->eventType,
|
||||
rotation,
|
||||
util::endian::little(packet->pointerId),
|
||||
coords.first,
|
||||
coords.second,
|
||||
from_clamped_netfloat(packet->pressureOrDistance, 0.0f, 1.0f),
|
||||
contact_area.first,
|
||||
contact_area.second,
|
||||
};
|
||||
|
||||
platf::touch(input->client_context.get(), abs_port, touch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called to pass a pen message to the platform backend.
|
||||
* @param input The input context pointer.
|
||||
* @param packet The pen packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the client normalized coordinates to touchport coordinates
|
||||
auto coords = client_to_touchport(input,
|
||||
{ from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f },
|
||||
{ 65535.f, 65535.f });
|
||||
|
||||
auto &touch_port = input->touch_port;
|
||||
platf::touch_port_t abs_port {
|
||||
touch_port.offset_x, touch_port.offset_y,
|
||||
touch_port.env_width, touch_port.env_height
|
||||
};
|
||||
|
||||
// Renormalize the coordinates
|
||||
coords.first /= abs_port.width;
|
||||
coords.second /= abs_port.height;
|
||||
|
||||
// Normalize rotation value to 0-359 degree range
|
||||
auto rotation = util::endian::little(packet->rotation);
|
||||
if (rotation != LI_ROT_UNKNOWN) {
|
||||
rotation %= 360;
|
||||
}
|
||||
|
||||
// Normalize the contact area based on the touchport
|
||||
auto contact_area = scale_client_contact_area(
|
||||
{ from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f },
|
||||
rotation,
|
||||
{ abs_port.width / 65535.f, abs_port.height / 65535.f });
|
||||
|
||||
platf::pen_input_t pen {
|
||||
packet->eventType,
|
||||
packet->toolType,
|
||||
packet->penButtons,
|
||||
packet->tilt,
|
||||
rotation,
|
||||
coords.first,
|
||||
coords.second,
|
||||
from_clamped_netfloat(packet->pressureOrDistance, 0.0f, 1.0f),
|
||||
contact_area.first,
|
||||
contact_area.second,
|
||||
};
|
||||
|
||||
platf::pen(input->client_context.get(), abs_port, pen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called to pass a controller touch message to the platform backend.
|
||||
* @param input The input context pointer.
|
||||
@ -1327,6 +1555,12 @@ namespace input {
|
||||
case MULTI_CONTROLLER_MAGIC_GEN5:
|
||||
passthrough(input, (PNV_MULTI_CONTROLLER_PACKET) payload);
|
||||
break;
|
||||
case SS_TOUCH_MAGIC:
|
||||
passthrough(input, (PSS_TOUCH_PACKET) payload);
|
||||
break;
|
||||
case SS_PEN_MAGIC:
|
||||
passthrough(input, (PSS_PEN_PACKET) payload);
|
||||
break;
|
||||
case SS_CONTROLLER_ARRIVAL_MAGIC:
|
||||
passthrough(input, (PSS_CONTROLLER_ARRIVAL_PACKET) payload);
|
||||
break;
|
||||
|
@ -279,6 +279,30 @@ namespace platf {
|
||||
std::uint8_t percentage;
|
||||
};
|
||||
|
||||
struct touch_input_t {
|
||||
std::uint8_t eventType;
|
||||
std::uint16_t rotation; // Degrees (0..360) or LI_ROT_UNKNOWN
|
||||
std::uint32_t pointerId;
|
||||
float x;
|
||||
float y;
|
||||
float pressureOrDistance; // Distance for hover and pressure for contact
|
||||
float contactAreaMajor;
|
||||
float contactAreaMinor;
|
||||
};
|
||||
|
||||
struct pen_input_t {
|
||||
std::uint8_t eventType;
|
||||
std::uint8_t toolType;
|
||||
std::uint8_t penButtons;
|
||||
std::uint8_t tilt; // Degrees (0..90) or LI_TILT_UNKNOWN
|
||||
std::uint16_t rotation; // Degrees (0..360) or LI_ROT_UNKNOWN
|
||||
float x;
|
||||
float y;
|
||||
float pressureOrDistance; // Distance for hover and pressure for contact
|
||||
float contactAreaMajor;
|
||||
float contactAreaMinor;
|
||||
};
|
||||
|
||||
class deinit_t {
|
||||
public:
|
||||
virtual ~deinit_t() = default;
|
||||
@ -564,9 +588,37 @@ namespace platf {
|
||||
void
|
||||
unicode(input_t &input, char *utf8, int size);
|
||||
|
||||
typedef deinit_t client_input_t;
|
||||
|
||||
/**
|
||||
* @brief Allocates a context to store per-client input data.
|
||||
* @param input The global input context.
|
||||
* @return A unique pointer to a per-client input data context.
|
||||
*/
|
||||
std::unique_ptr<client_input_t>
|
||||
allocate_client_input_context(input_t &input);
|
||||
|
||||
/**
|
||||
* @brief Sends a touch event to the OS.
|
||||
* @param input The client-specific input context.
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
touch(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch);
|
||||
|
||||
/**
|
||||
* @brief Sends a pen event to the OS.
|
||||
* @param input The client-specific input context.
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param pen The pen event.
|
||||
*/
|
||||
void
|
||||
pen(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad touch event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
@ -574,7 +626,7 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad motion event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param motion The motion event.
|
||||
*/
|
||||
void
|
||||
@ -582,7 +634,7 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad battery event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param battery The battery event.
|
||||
*/
|
||||
void
|
||||
@ -590,7 +642,7 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Creates a new virtual gamepad.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param id The gamepad ID.
|
||||
* @param metadata Controller metadata from client (empty if none provided).
|
||||
* @param feedback_queue The queue for posting messages back to the client.
|
||||
|
@ -1489,7 +1489,7 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Creates a new virtual gamepad.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param id The gamepad ID.
|
||||
* @param metadata Controller metadata from client (empty if none provided).
|
||||
* @param feedback_queue The queue for posting messages back to the client.
|
||||
@ -1567,9 +1567,42 @@ namespace platf {
|
||||
libevdev_uinput_write_event(uinput.get(), EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocates a context to store per-client input data.
|
||||
* @param input The global input context.
|
||||
* @return A unique pointer to a per-client input data context.
|
||||
*/
|
||||
std::unique_ptr<client_input_t>
|
||||
allocate_client_input_context(input_t &input) {
|
||||
// Unused
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends a touch event to the OS.
|
||||
* @param input The client-specific input context.
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
touch(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
|
||||
// Unimplemented feature - platform_caps::pen_touch
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends a pen event to the OS.
|
||||
* @param input The client-specific input context.
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param pen The pen event.
|
||||
*/
|
||||
void
|
||||
pen(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
|
||||
// Unimplemented feature - platform_caps::pen_touch
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad touch event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
@ -1579,7 +1612,7 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad motion event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param motion The motion event.
|
||||
*/
|
||||
void
|
||||
@ -1589,7 +1622,7 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad battery event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param battery The battery event.
|
||||
*/
|
||||
void
|
||||
|
@ -448,9 +448,42 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocates a context to store per-client input data.
|
||||
* @param input The global input context.
|
||||
* @return A unique pointer to a per-client input data context.
|
||||
*/
|
||||
std::unique_ptr<client_input_t>
|
||||
allocate_client_input_context(input_t &input) {
|
||||
// Unused
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends a touch event to the OS.
|
||||
* @param input The client-specific input context.
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
touch(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
|
||||
// Unimplemented feature - platform_caps::pen_touch
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends a pen event to the OS.
|
||||
* @param input The client-specific input context.
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param pen The pen event.
|
||||
*/
|
||||
void
|
||||
pen(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
|
||||
// Unimplemented feature - platform_caps::pen_touch
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad touch event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
@ -460,7 +493,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad motion event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param motion The motion event.
|
||||
*/
|
||||
void
|
||||
@ -470,7 +503,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad battery event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param battery The battery event.
|
||||
*/
|
||||
void
|
||||
|
@ -2,6 +2,7 @@
|
||||
* @file src/platform/windows/input.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#define WINVER 0x0A00
|
||||
#include <windows.h>
|
||||
|
||||
#include <cmath>
|
||||
@ -13,6 +14,16 @@
|
||||
#include "src/main.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
#ifdef __MINGW32__
|
||||
DECLARE_HANDLE(HSYNTHETICPOINTERDEVICE);
|
||||
WINUSERAPI HSYNTHETICPOINTERDEVICE WINAPI
|
||||
CreateSyntheticPointerDevice(POINTER_INPUT_TYPE pointerType, ULONG maxCount, POINTER_FEEDBACK_MODE mode);
|
||||
WINUSERAPI BOOL WINAPI
|
||||
InjectSyntheticPointerInput(HSYNTHETICPOINTERDEVICE device, CONST POINTER_TYPE_INFO *pointerInfo, UINT32 count);
|
||||
WINUSERAPI VOID WINAPI
|
||||
DestroySyntheticPointerDevice(HSYNTHETICPOINTERDEVICE device);
|
||||
#endif
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
|
||||
@ -390,6 +401,10 @@ namespace platf {
|
||||
vigem_t *vigem;
|
||||
HKL keyboard_layout;
|
||||
HKL active_layout;
|
||||
|
||||
decltype(CreateSyntheticPointerDevice) *fnCreateSyntheticPointerDevice;
|
||||
decltype(InjectSyntheticPointerInput) *fnInjectSyntheticPointerInput;
|
||||
decltype(DestroySyntheticPointerDevice) *fnDestroySyntheticPointerDevice;
|
||||
};
|
||||
|
||||
input_t
|
||||
@ -418,6 +433,11 @@ namespace platf {
|
||||
raw.keyboard_layout = NULL;
|
||||
}
|
||||
|
||||
// Get pointers to virtual touch/pen input functions (Win10 1809+)
|
||||
raw.fnCreateSyntheticPointerDevice = (decltype(CreateSyntheticPointerDevice) *) GetProcAddress(GetModuleHandleA("user32.dll"), "CreateSyntheticPointerDevice");
|
||||
raw.fnInjectSyntheticPointerInput = (decltype(InjectSyntheticPointerInput) *) GetProcAddress(GetModuleHandleA("user32.dll"), "InjectSyntheticPointerInput");
|
||||
raw.fnDestroySyntheticPointerDevice = (decltype(DestroySyntheticPointerDevice) *) GetProcAddress(GetModuleHandleA("user32.dll"), "DestroySyntheticPointerDevice");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -580,6 +600,506 @@ namespace platf {
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
struct client_input_raw_t: public client_input_t {
|
||||
client_input_raw_t(input_t &input) {
|
||||
global = (input_raw_t *) input.get();
|
||||
}
|
||||
|
||||
~client_input_raw_t() override {
|
||||
if (penRepeatTask) {
|
||||
task_pool.cancel(penRepeatTask);
|
||||
}
|
||||
if (touchRepeatTask) {
|
||||
task_pool.cancel(touchRepeatTask);
|
||||
}
|
||||
|
||||
if (pen) {
|
||||
global->fnDestroySyntheticPointerDevice(pen);
|
||||
}
|
||||
if (touch) {
|
||||
global->fnDestroySyntheticPointerDevice(touch);
|
||||
}
|
||||
}
|
||||
|
||||
input_raw_t *global;
|
||||
|
||||
// Device state and handles for pen and touch input must be stored in the per-client
|
||||
// input context, because each connected client may be sending their own independent
|
||||
// pen/touch events. To maintain separation, we expose separate pen and touch devices
|
||||
// for each client.
|
||||
|
||||
HSYNTHETICPOINTERDEVICE pen {};
|
||||
POINTER_TYPE_INFO penInfo {};
|
||||
thread_pool_util::ThreadPool::task_id_t penRepeatTask {};
|
||||
|
||||
HSYNTHETICPOINTERDEVICE touch {};
|
||||
POINTER_TYPE_INFO touchInfo[10] {};
|
||||
UINT32 activeTouchSlots {};
|
||||
thread_pool_util::ThreadPool::task_id_t touchRepeatTask {};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Allocates a context to store per-client input data.
|
||||
* @param input The global input context.
|
||||
* @return A unique pointer to a per-client input data context.
|
||||
*/
|
||||
std::unique_ptr<client_input_t>
|
||||
allocate_client_input_context(input_t &input) {
|
||||
return std::make_unique<client_input_raw_t>(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compacts the touch slots into a contiguous block and updates the active count.
|
||||
* @details Since this swaps entries around, all slot pointers/references are invalid after compaction.
|
||||
* @param raw The client-specific input context.
|
||||
*/
|
||||
void
|
||||
perform_touch_compaction(client_input_raw_t *raw) {
|
||||
// Windows requires all active touches be contiguous when fed into InjectSyntheticPointerInput().
|
||||
UINT32 i;
|
||||
for (i = 0; i < ARRAYSIZE(raw->touchInfo); i++) {
|
||||
if (raw->touchInfo[i].touchInfo.pointerInfo.pointerFlags == POINTER_FLAG_NONE) {
|
||||
// This is an empty slot. Look for a later entry to move into this slot.
|
||||
for (UINT32 j = i + 1; j < ARRAYSIZE(raw->touchInfo); j++) {
|
||||
if (raw->touchInfo[j].touchInfo.pointerInfo.pointerFlags != POINTER_FLAG_NONE) {
|
||||
std::swap(raw->touchInfo[i], raw->touchInfo[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find anything, we've reached the end of active slots.
|
||||
if (raw->touchInfo[i].touchInfo.pointerInfo.pointerFlags == POINTER_FLAG_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the number of active touch slots
|
||||
raw->activeTouchSlots = i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets a pointer slot by client-relative pointer ID, claiming a new one if necessary.
|
||||
* @param raw The raw client-specific input context.
|
||||
* @param pointerId The client's pointer ID.
|
||||
* @param eventType The LI_TOUCH_EVENT value from the client.
|
||||
* @return A pointer to the slot entry.
|
||||
*/
|
||||
POINTER_TYPE_INFO *
|
||||
pointer_by_id(client_input_raw_t *raw, uint32_t pointerId, uint8_t eventType) {
|
||||
// Compact active touches into a single contiguous block
|
||||
perform_touch_compaction(raw);
|
||||
|
||||
// Try to find a matching pointer ID
|
||||
for (UINT32 i = 0; i < ARRAYSIZE(raw->touchInfo); i++) {
|
||||
if (raw->touchInfo[i].touchInfo.pointerInfo.pointerId == pointerId &&
|
||||
raw->touchInfo[i].touchInfo.pointerInfo.pointerFlags != POINTER_FLAG_NONE) {
|
||||
if (eventType == LI_TOUCH_EVENT_DOWN && (raw->touchInfo[i].touchInfo.pointerInfo.pointerFlags & POINTER_FLAG_INCONTACT)) {
|
||||
BOOST_LOG(warning) << "Pointer "sv << pointerId << " already down. Did the client drop an up/cancel event?"sv;
|
||||
}
|
||||
|
||||
return &raw->touchInfo[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType != LI_TOUCH_EVENT_HOVER && eventType != LI_TOUCH_EVENT_DOWN) {
|
||||
BOOST_LOG(warning) << "Unexpected new pointer "sv << pointerId << " for event "sv << (uint32_t) eventType << ". Did the client drop a down/hover event?"sv;
|
||||
}
|
||||
|
||||
// If there was none, grab an unused entry and increment the active slot count
|
||||
for (UINT32 i = 0; i < ARRAYSIZE(raw->touchInfo); i++) {
|
||||
if (raw->touchInfo[i].touchInfo.pointerInfo.pointerFlags == POINTER_FLAG_NONE) {
|
||||
raw->touchInfo[i].touchInfo.pointerInfo.pointerId = pointerId;
|
||||
raw->activeTouchSlots = i + 1;
|
||||
return &raw->touchInfo[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate common `POINTER_INFO` members shared between pen and touch events.
|
||||
* @param pointerInfo The pointer info to populate.
|
||||
* @param touchPort The current viewport for translating to screen coordinates.
|
||||
* @param eventType The type of touch/pen event.
|
||||
* @param x The normalized 0.0-1.0 X coordinate.
|
||||
* @param y The normalized 0.0-1.0 Y coordinate.
|
||||
*/
|
||||
void
|
||||
populate_common_pointer_info(POINTER_INFO &pointerInfo, const touch_port_t &touchPort, uint8_t eventType, float x, float y) {
|
||||
switch (eventType) {
|
||||
case LI_TOUCH_EVENT_HOVER:
|
||||
pointerInfo.pointerFlags &= ~POINTER_FLAG_INCONTACT;
|
||||
pointerInfo.pointerFlags |= POINTER_FLAG_INRANGE | POINTER_FLAG_UPDATE;
|
||||
pointerInfo.ptPixelLocation.x = x * touchPort.width + touchPort.offset_x;
|
||||
pointerInfo.ptPixelLocation.y = y * touchPort.height + touchPort.offset_y;
|
||||
break;
|
||||
case LI_TOUCH_EVENT_DOWN:
|
||||
pointerInfo.pointerFlags |= POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN;
|
||||
pointerInfo.ptPixelLocation.x = x * touchPort.width + touchPort.offset_x;
|
||||
pointerInfo.ptPixelLocation.y = y * touchPort.height + touchPort.offset_y;
|
||||
break;
|
||||
case LI_TOUCH_EVENT_UP:
|
||||
// We expect to get another LI_TOUCH_EVENT_HOVER if the pointer remains in range
|
||||
pointerInfo.pointerFlags &= ~(POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE);
|
||||
pointerInfo.pointerFlags |= POINTER_FLAG_UP;
|
||||
break;
|
||||
case LI_TOUCH_EVENT_MOVE:
|
||||
pointerInfo.pointerFlags |= POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT | POINTER_FLAG_UPDATE;
|
||||
pointerInfo.ptPixelLocation.x = x * touchPort.width + touchPort.offset_x;
|
||||
pointerInfo.ptPixelLocation.y = y * touchPort.height + touchPort.offset_y;
|
||||
break;
|
||||
case LI_TOUCH_EVENT_CANCEL:
|
||||
case LI_TOUCH_EVENT_CANCEL_ALL:
|
||||
// If we were in contact with the touch surface at the time of the cancellation,
|
||||
// we'll set POINTER_FLAG_UP, otherwise set POINTER_FLAG_UPDATE.
|
||||
if (pointerInfo.pointerFlags & POINTER_FLAG_INCONTACT) {
|
||||
pointerInfo.pointerFlags |= POINTER_FLAG_UP;
|
||||
}
|
||||
else {
|
||||
pointerInfo.pointerFlags |= POINTER_FLAG_UPDATE;
|
||||
}
|
||||
pointerInfo.pointerFlags &= ~(POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE);
|
||||
pointerInfo.pointerFlags |= POINTER_FLAG_CANCELED;
|
||||
break;
|
||||
case LI_TOUCH_EVENT_HOVER_LEAVE:
|
||||
pointerInfo.pointerFlags &= ~(POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE);
|
||||
pointerInfo.pointerFlags |= POINTER_FLAG_UPDATE;
|
||||
break;
|
||||
case LI_TOUCH_EVENT_BUTTON_ONLY:
|
||||
// On Windows, we can only pass buttons if we have an active pointer
|
||||
if (pointerInfo.pointerFlags != POINTER_FLAG_NONE) {
|
||||
pointerInfo.pointerFlags |= POINTER_FLAG_UPDATE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(warning) << "Unknown touch event: "sv << (uint32_t) eventType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Active pointer interactions sent via InjectSyntheticPointerInput() seem to be automatically
|
||||
// cancelled by Windows if not repeated/updated within about a second. To avoid this, refresh
|
||||
// the injected input periodically.
|
||||
constexpr auto ISPI_REPEAT_INTERVAL = 50ms;
|
||||
|
||||
/**
|
||||
* @brief Repeats the current touch state to avoid the interactions timing out.
|
||||
* @param raw The raw client-specific input context.
|
||||
*/
|
||||
void
|
||||
repeat_touch(client_input_raw_t *raw) {
|
||||
if (!raw->global->fnInjectSyntheticPointerInput(raw->touch, raw->touchInfo, raw->activeTouchSlots)) {
|
||||
auto err = GetLastError();
|
||||
BOOST_LOG(warning) << "Failed to refresh virtual touch input: "sv << err;
|
||||
}
|
||||
|
||||
raw->touchRepeatTask = task_pool.pushDelayed(repeat_touch, ISPI_REPEAT_INTERVAL, raw).task_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Repeats the current pen state to avoid the interactions timing out.
|
||||
* @param raw The raw client-specific input context.
|
||||
*/
|
||||
void
|
||||
repeat_pen(client_input_raw_t *raw) {
|
||||
if (!raw->global->fnInjectSyntheticPointerInput(raw->pen, &raw->penInfo, 1)) {
|
||||
auto err = GetLastError();
|
||||
BOOST_LOG(warning) << "Failed to refresh virtual pen input: "sv << err;
|
||||
}
|
||||
|
||||
raw->penRepeatTask = task_pool.pushDelayed(repeat_pen, ISPI_REPEAT_INTERVAL, raw).task_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cancels all active touches.
|
||||
* @param raw The raw client-specific input context.
|
||||
*/
|
||||
void
|
||||
cancel_all_active_touches(client_input_raw_t *raw) {
|
||||
// Cancel touch repeat callbacks
|
||||
if (raw->touchRepeatTask) {
|
||||
task_pool.cancel(raw->touchRepeatTask);
|
||||
raw->touchRepeatTask = nullptr;
|
||||
}
|
||||
|
||||
// Compact touches to update activeTouchSlots
|
||||
perform_touch_compaction(raw);
|
||||
|
||||
// If we have active slots, cancel them all
|
||||
if (raw->activeTouchSlots > 0) {
|
||||
for (UINT32 i = 0; i < raw->activeTouchSlots; i++) {
|
||||
populate_common_pointer_info(raw->touchInfo[i].touchInfo.pointerInfo, {}, LI_TOUCH_EVENT_CANCEL_ALL, 0.0f, 0.0f);
|
||||
raw->touchInfo[i].touchInfo.touchMask = TOUCH_MASK_NONE;
|
||||
}
|
||||
if (!raw->global->fnInjectSyntheticPointerInput(raw->touch, raw->touchInfo, raw->activeTouchSlots)) {
|
||||
auto err = GetLastError();
|
||||
BOOST_LOG(warning) << "Failed to cancel all virtual touch input: "sv << err;
|
||||
}
|
||||
}
|
||||
|
||||
// Zero all touch state
|
||||
std::memset(raw->touchInfo, 0, sizeof(raw->touchInfo));
|
||||
raw->activeTouchSlots = 0;
|
||||
}
|
||||
|
||||
// These are edge-triggered pointer state flags that should always be cleared next frame
|
||||
constexpr auto EDGE_TRIGGERED_POINTER_FLAGS = POINTER_FLAG_DOWN | POINTER_FLAG_UP | POINTER_FLAG_CANCELED | POINTER_FLAG_UPDATE;
|
||||
|
||||
/**
|
||||
* @brief Sends a touch event to the OS.
|
||||
* @param input The client-specific input context.
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
touch(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
|
||||
auto raw = (client_input_raw_t *) input;
|
||||
|
||||
// Bail if we're not running on an OS that supports virtual touch input
|
||||
if (!raw->global->fnCreateSyntheticPointerDevice ||
|
||||
!raw->global->fnInjectSyntheticPointerInput ||
|
||||
!raw->global->fnDestroySyntheticPointerDevice) {
|
||||
BOOST_LOG(warning) << "Touch input requires Windows 10 1809 or later"sv;
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's not already a virtual touch device, create one now
|
||||
if (!raw->touch) {
|
||||
if (touch.eventType != LI_TOUCH_EVENT_CANCEL_ALL) {
|
||||
BOOST_LOG(info) << "Creating virtual touch input device"sv;
|
||||
raw->touch = raw->global->fnCreateSyntheticPointerDevice(PT_TOUCH, ARRAYSIZE(raw->touchInfo), POINTER_FEEDBACK_DEFAULT);
|
||||
if (!raw->touch) {
|
||||
auto err = GetLastError();
|
||||
BOOST_LOG(warning) << "Failed to create virtual touch device: "sv << err;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No need to cancel anything if we had no touch input device
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel touch repeat callbacks
|
||||
if (raw->touchRepeatTask) {
|
||||
task_pool.cancel(raw->touchRepeatTask);
|
||||
raw->touchRepeatTask = nullptr;
|
||||
}
|
||||
|
||||
// If this is a special request to cancel all touches, do that and return
|
||||
if (touch.eventType == LI_TOUCH_EVENT_CANCEL_ALL) {
|
||||
cancel_all_active_touches(raw);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find or allocate an entry for this touch pointer ID
|
||||
auto pointer = pointer_by_id(raw, touch.pointerId, touch.eventType);
|
||||
if (!pointer) {
|
||||
BOOST_LOG(error) << "No unused pointer entries! Cancelling all active touches!"sv;
|
||||
cancel_all_active_touches(raw);
|
||||
pointer = pointer_by_id(raw, touch.pointerId, touch.eventType);
|
||||
}
|
||||
|
||||
pointer->type = PT_TOUCH;
|
||||
|
||||
auto &touchInfo = pointer->touchInfo;
|
||||
touchInfo.pointerInfo.pointerType = PT_TOUCH;
|
||||
|
||||
// Populate shared pointer info fields
|
||||
populate_common_pointer_info(touchInfo.pointerInfo, touch_port, touch.eventType, touch.x, touch.y);
|
||||
|
||||
touchInfo.touchMask = TOUCH_MASK_NONE;
|
||||
|
||||
// Pressure and contact area only apply to in-contact pointers.
|
||||
//
|
||||
// The clients also pass distance and tool size for hovers, but Windows doesn't
|
||||
// provide APIs to receive that data.
|
||||
if (touchInfo.pointerInfo.pointerFlags & POINTER_FLAG_INCONTACT) {
|
||||
if (touch.pressureOrDistance != 0.0f) {
|
||||
touchInfo.touchMask |= TOUCH_MASK_PRESSURE;
|
||||
|
||||
// Convert the 0.0f..1.0f float to the 0..1024 range that Windows uses
|
||||
touchInfo.pressure = (UINT32) (touch.pressureOrDistance * 1024);
|
||||
}
|
||||
else {
|
||||
// The default touch pressure is 512
|
||||
touchInfo.pressure = 512;
|
||||
}
|
||||
|
||||
if (touch.contactAreaMajor != 0.0f && touch.contactAreaMinor != 0.0f) {
|
||||
// For the purposes of contact area calculation, we will assume the touches
|
||||
// are at a 45 degree angle if rotation is unknown. This will scale the major
|
||||
// axis value by width and height equally.
|
||||
float rotationAngleDegs = touch.rotation == LI_ROT_UNKNOWN ? 45 : touch.rotation;
|
||||
|
||||
float majorAxisAngle = rotationAngleDegs * (M_PI / 180);
|
||||
float minorAxisAngle = majorAxisAngle + (M_PI / 2);
|
||||
|
||||
// Estimate the contact rectangle
|
||||
float contactWidth = (std::cos(majorAxisAngle) * touch.contactAreaMajor) + (std::cos(minorAxisAngle) * touch.contactAreaMinor);
|
||||
float contactHeight = (std::sin(majorAxisAngle) * touch.contactAreaMajor) + (std::sin(minorAxisAngle) * touch.contactAreaMinor);
|
||||
|
||||
// Convert into screen coordinates centered at the touch location and constrained by screen dimensions
|
||||
touchInfo.rcContact.left = std::max<LONG>(touch_port.offset_x, touchInfo.pointerInfo.ptPixelLocation.x - std::floor(contactWidth / 2));
|
||||
touchInfo.rcContact.right = std::min<LONG>(touch_port.offset_x + touch_port.width, touchInfo.pointerInfo.ptPixelLocation.x + std::ceil(contactWidth / 2));
|
||||
touchInfo.rcContact.top = std::max<LONG>(touch_port.offset_y, touchInfo.pointerInfo.ptPixelLocation.y - std::floor(contactHeight / 2));
|
||||
touchInfo.rcContact.bottom = std::min<LONG>(touch_port.offset_y + touch_port.height, touchInfo.pointerInfo.ptPixelLocation.y + std::ceil(contactHeight / 2));
|
||||
|
||||
touchInfo.touchMask |= TOUCH_MASK_CONTACTAREA;
|
||||
}
|
||||
}
|
||||
else {
|
||||
touchInfo.pressure = 0;
|
||||
touchInfo.rcContact = {};
|
||||
}
|
||||
|
||||
if (touch.rotation != LI_ROT_UNKNOWN) {
|
||||
touchInfo.touchMask |= TOUCH_MASK_ORIENTATION;
|
||||
touchInfo.orientation = touch.rotation;
|
||||
}
|
||||
else {
|
||||
touchInfo.orientation = 0;
|
||||
}
|
||||
|
||||
if (!raw->global->fnInjectSyntheticPointerInput(raw->touch, raw->touchInfo, raw->activeTouchSlots)) {
|
||||
auto err = GetLastError();
|
||||
BOOST_LOG(warning) << "Failed to inject virtual touch input: "sv << err;
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear pointer flags that should only remain set for one frame
|
||||
touchInfo.pointerInfo.pointerFlags &= ~EDGE_TRIGGERED_POINTER_FLAGS;
|
||||
|
||||
// If we still have an active touch, refresh the touch state periodically
|
||||
if (raw->activeTouchSlots > 1 || touchInfo.pointerInfo.pointerFlags != POINTER_FLAG_NONE) {
|
||||
raw->touchRepeatTask = task_pool.pushDelayed(repeat_touch, ISPI_REPEAT_INTERVAL, raw).task_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends a pen event to the OS.
|
||||
* @param input The client-specific input context.
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param pen The pen event.
|
||||
*/
|
||||
void
|
||||
pen(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
|
||||
auto raw = (client_input_raw_t *) input;
|
||||
|
||||
// Bail if we're not running on an OS that supports virtual pen input
|
||||
if (!raw->global->fnCreateSyntheticPointerDevice ||
|
||||
!raw->global->fnInjectSyntheticPointerInput ||
|
||||
!raw->global->fnDestroySyntheticPointerDevice) {
|
||||
BOOST_LOG(warning) << "Pen input requires Windows 10 1809 or later"sv;
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's not already a virtual pen device, create one now
|
||||
if (!raw->pen) {
|
||||
if (pen.eventType != LI_TOUCH_EVENT_CANCEL_ALL) {
|
||||
BOOST_LOG(info) << "Creating virtual pen input device"sv;
|
||||
raw->pen = raw->global->fnCreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_DEFAULT);
|
||||
if (!raw->pen) {
|
||||
auto err = GetLastError();
|
||||
BOOST_LOG(warning) << "Failed to create virtual pen device: "sv << err;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No need to cancel anything if we had no pen input device
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel pen repeat callbacks
|
||||
if (raw->penRepeatTask) {
|
||||
task_pool.cancel(raw->penRepeatTask);
|
||||
raw->penRepeatTask = nullptr;
|
||||
}
|
||||
|
||||
raw->penInfo.type = PT_PEN;
|
||||
|
||||
auto &penInfo = raw->penInfo.penInfo;
|
||||
penInfo.pointerInfo.pointerType = PT_PEN;
|
||||
penInfo.pointerInfo.pointerId = 0;
|
||||
|
||||
// Populate shared pointer info fields
|
||||
populate_common_pointer_info(penInfo.pointerInfo, touch_port, pen.eventType, pen.x, pen.y);
|
||||
|
||||
// Windows only supports a single pen button, so send all buttons as the barrel button
|
||||
if (pen.penButtons) {
|
||||
penInfo.penFlags |= PEN_FLAG_BARREL;
|
||||
}
|
||||
else {
|
||||
penInfo.penFlags &= ~PEN_FLAG_BARREL;
|
||||
}
|
||||
|
||||
switch (pen.toolType) {
|
||||
default:
|
||||
case LI_TOOL_TYPE_PEN:
|
||||
penInfo.penFlags &= ~PEN_FLAG_ERASER;
|
||||
break;
|
||||
case LI_TOOL_TYPE_ERASER:
|
||||
penInfo.penFlags |= PEN_FLAG_ERASER;
|
||||
break;
|
||||
case LI_TOOL_TYPE_UNKNOWN:
|
||||
// Leave tool flags alone
|
||||
break;
|
||||
}
|
||||
|
||||
penInfo.penMask = PEN_MASK_NONE;
|
||||
|
||||
// Windows doesn't support hover distance, so only pass pressure/distance when the pointer is in contact
|
||||
if ((penInfo.pointerInfo.pointerFlags & POINTER_FLAG_INCONTACT) && pen.pressureOrDistance != 0.0f) {
|
||||
penInfo.penMask |= PEN_MASK_PRESSURE;
|
||||
|
||||
// Convert the 0.0f..1.0f float to the 0..1024 range that Windows uses
|
||||
penInfo.pressure = (UINT32) (pen.pressureOrDistance * 1024);
|
||||
}
|
||||
else {
|
||||
// The default pen pressure is 0
|
||||
penInfo.pressure = 0;
|
||||
}
|
||||
|
||||
if (pen.rotation != LI_ROT_UNKNOWN) {
|
||||
penInfo.penMask |= PEN_MASK_ROTATION;
|
||||
penInfo.rotation = pen.rotation;
|
||||
}
|
||||
else {
|
||||
penInfo.rotation = 0;
|
||||
}
|
||||
|
||||
// We require rotation and tilt to perform the polar to cartesian conversion
|
||||
if (pen.tilt != LI_TILT_UNKNOWN && pen.rotation != LI_ROT_UNKNOWN) {
|
||||
auto rotationRads = pen.rotation * (M_PI / 180.f);
|
||||
|
||||
// Convert into cartesian coordinates
|
||||
penInfo.penMask |= PEN_MASK_TILT_X | PEN_MASK_TILT_Y;
|
||||
penInfo.tiltX = (INT32) (std::cos(rotationRads) * pen.tilt);
|
||||
penInfo.tiltY = (INT32) (std::sin(rotationRads) * pen.tilt);
|
||||
}
|
||||
else {
|
||||
penInfo.tiltX = 0;
|
||||
penInfo.tiltY = 0;
|
||||
}
|
||||
|
||||
if (!raw->global->fnInjectSyntheticPointerInput(raw->pen, &raw->penInfo, 1)) {
|
||||
auto err = GetLastError();
|
||||
BOOST_LOG(warning) << "Failed to inject virtual pen input: "sv << err;
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear pointer flags that should only remain set for one frame
|
||||
penInfo.pointerInfo.pointerFlags &= ~EDGE_TRIGGERED_POINTER_FLAGS;
|
||||
|
||||
// If we still have an active pen interaction, refresh the pen state periodically
|
||||
if (penInfo.pointerInfo.pointerFlags != POINTER_FLAG_NONE) {
|
||||
raw->penRepeatTask = task_pool.pushDelayed(repeat_pen, ISPI_REPEAT_INTERVAL, raw).task_id;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
unicode(input_t &input, char *utf8, int size) {
|
||||
// We can do no worse than one UTF-16 character per byte of UTF-8
|
||||
@ -611,7 +1131,7 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Creates a new virtual gamepad.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param id The gamepad ID.
|
||||
* @param metadata Controller metadata from client (empty if none provided).
|
||||
* @param feedback_queue The queue for posting messages back to the client.
|
||||
@ -870,7 +1390,7 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad touch event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
@ -970,7 +1490,7 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad motion event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param motion The motion event.
|
||||
*/
|
||||
void
|
||||
@ -1002,7 +1522,7 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Sends a gamepad battery event to the OS.
|
||||
* @param input The input context.
|
||||
* @param input The global input context.
|
||||
* @param battery The battery event.
|
||||
*/
|
||||
void
|
||||
@ -1112,6 +1632,14 @@ namespace platf {
|
||||
caps |= platform_caps::controller_touch;
|
||||
}
|
||||
|
||||
// We support pen and touch input on Win10 1809+
|
||||
if (GetProcAddress(GetModuleHandleA("user32.dll"), "CreateSyntheticPointerDevice") != nullptr) {
|
||||
caps |= platform_caps::pen_touch;
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(warning) << "Touch input requires Windows 10 1809 or later"sv;
|
||||
}
|
||||
|
||||
return caps;
|
||||
}
|
||||
} // namespace platf
|
||||
|
2
third-party/moonlight-common-c
vendored
2
third-party/moonlight-common-c
vendored
@ -1 +1 @@
|
||||
Subproject commit f2cea4d6b0e5a784150e90cd505f76015c2ace81
|
||||
Subproject commit 0f17b4d0c5f40842bb81fec0f2f6b5afbf182d04
|
Loading…
x
Reference in New Issue
Block a user