move sunshine to src
- this will allow for common cpp workflow files within org
This commit is contained in:
491
src/platform/windows/input.cpp
Normal file
491
src/platform/windows/input.cpp
Normal file
@@ -0,0 +1,491 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <ViGEm/Client.h>
|
||||
|
||||
#include "misc.h"
|
||||
#include "src/config.h"
|
||||
#include "src/main.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
|
||||
thread_local HDESK _lastKnownInputDesktop = nullptr;
|
||||
|
||||
constexpr touch_port_t target_touch_port {
|
||||
0, 0,
|
||||
65535, 65535
|
||||
};
|
||||
|
||||
using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>;
|
||||
using target_t = util::safe_ptr<_VIGEM_TARGET_T, vigem_target_free>;
|
||||
|
||||
static VIGEM_TARGET_TYPE map(const std::string_view &gp) {
|
||||
if(gp == "x360"sv) {
|
||||
return Xbox360Wired;
|
||||
}
|
||||
|
||||
return DualShock4Wired;
|
||||
}
|
||||
|
||||
void CALLBACK x360_notify(
|
||||
client_t::pointer client,
|
||||
target_t::pointer target,
|
||||
std::uint8_t largeMotor, std::uint8_t smallMotor,
|
||||
std::uint8_t /* led_number */,
|
||||
void *userdata);
|
||||
|
||||
void CALLBACK ds4_notify(
|
||||
client_t::pointer client,
|
||||
target_t::pointer target,
|
||||
std::uint8_t largeMotor, std::uint8_t smallMotor,
|
||||
DS4_LIGHTBAR_COLOR /* led_color */,
|
||||
void *userdata);
|
||||
|
||||
class vigem_t {
|
||||
public:
|
||||
int init() {
|
||||
VIGEM_ERROR status;
|
||||
|
||||
client.reset(vigem_alloc());
|
||||
|
||||
status = vigem_connect(client.get());
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(warning) << "Couldn't setup connection to ViGEm for gamepad support ["sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
gamepads.resize(MAX_GAMEPADS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int alloc_gamepad_interal(int nr, rumble_queue_t &rumble_queue, VIGEM_TARGET_TYPE gp_type) {
|
||||
auto &[rumble, gp] = gamepads[nr];
|
||||
assert(!gp);
|
||||
|
||||
if(gp_type == Xbox360Wired) {
|
||||
gp.reset(vigem_target_x360_alloc());
|
||||
}
|
||||
else {
|
||||
gp.reset(vigem_target_ds4_alloc());
|
||||
}
|
||||
|
||||
auto status = vigem_target_add(client.get(), gp.get());
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(error) << "Couldn't add Gamepad to ViGEm connection ["sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
rumble = std::move(rumble_queue);
|
||||
|
||||
if(gp_type == Xbox360Wired) {
|
||||
status = vigem_target_x360_register_notification(client.get(), gp.get(), x360_notify, this);
|
||||
}
|
||||
else {
|
||||
status = vigem_target_ds4_register_notification(client.get(), gp.get(), ds4_notify, this);
|
||||
}
|
||||
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(warning) << "Couldn't register notifications for rumble support ["sv << util::hex(status).to_string_view() << ']';
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void free_target(int nr) {
|
||||
auto &[_, gp] = gamepads[nr];
|
||||
|
||||
if(gp && vigem_target_is_attached(gp.get())) {
|
||||
auto status = vigem_target_remove(client.get(), gp.get());
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(warning) << "Couldn't detach gamepad from ViGEm ["sv << util::hex(status).to_string_view() << ']';
|
||||
}
|
||||
}
|
||||
|
||||
gp.reset();
|
||||
}
|
||||
|
||||
void rumble(target_t::pointer target, std::uint8_t smallMotor, std::uint8_t largeMotor) {
|
||||
for(int x = 0; x < gamepads.size(); ++x) {
|
||||
auto &[rumble_queue, gp] = gamepads[x];
|
||||
|
||||
if(gp.get() == target) {
|
||||
rumble_queue->raise(x, ((std::uint16_t)smallMotor) << 8, ((std::uint16_t)largeMotor) << 8);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~vigem_t() {
|
||||
if(client) {
|
||||
for(auto &[_, gp] : gamepads) {
|
||||
if(gp && vigem_target_is_attached(gp.get())) {
|
||||
auto status = vigem_target_remove(client.get(), gp.get());
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(warning) << "Couldn't detach gamepad from ViGEm ["sv << util::hex(status).to_string_view() << ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vigem_disconnect(client.get());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<rumble_queue_t, target_t>> gamepads;
|
||||
|
||||
client_t client;
|
||||
};
|
||||
|
||||
void CALLBACK x360_notify(
|
||||
client_t::pointer client,
|
||||
target_t::pointer target,
|
||||
std::uint8_t largeMotor, std::uint8_t smallMotor,
|
||||
std::uint8_t /* led_number */,
|
||||
void *userdata) {
|
||||
|
||||
BOOST_LOG(debug)
|
||||
<< "largeMotor: "sv << (int)largeMotor << std::endl
|
||||
<< "smallMotor: "sv << (int)smallMotor;
|
||||
|
||||
task_pool.push(&vigem_t::rumble, (vigem_t *)userdata, target, smallMotor, largeMotor);
|
||||
}
|
||||
|
||||
void CALLBACK ds4_notify(
|
||||
client_t::pointer client,
|
||||
target_t::pointer target,
|
||||
std::uint8_t largeMotor, std::uint8_t smallMotor,
|
||||
DS4_LIGHTBAR_COLOR /* led_color */,
|
||||
void *userdata) {
|
||||
|
||||
BOOST_LOG(debug)
|
||||
<< "largeMotor: "sv << (int)largeMotor << std::endl
|
||||
<< "smallMotor: "sv << (int)smallMotor;
|
||||
|
||||
task_pool.push(&vigem_t::rumble, (vigem_t *)userdata, target, smallMotor, largeMotor);
|
||||
}
|
||||
|
||||
input_t input() {
|
||||
input_t result { new vigem_t {} };
|
||||
|
||||
auto vigem = (vigem_t *)result.get();
|
||||
if(vigem->init()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void send_input(INPUT &i) {
|
||||
retry:
|
||||
auto send = SendInput(1, &i, sizeof(INPUT));
|
||||
if(send != 1) {
|
||||
auto hDesk = syncThreadDesktop();
|
||||
if(_lastKnownInputDesktop != hDesk) {
|
||||
_lastKnownInputDesktop = hDesk;
|
||||
goto retry;
|
||||
}
|
||||
BOOST_LOG(error) << "Couldn't send input"sv;
|
||||
}
|
||||
}
|
||||
|
||||
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
|
||||
INPUT i {};
|
||||
|
||||
i.type = INPUT_MOUSE;
|
||||
auto &mi = i.mi;
|
||||
|
||||
mi.dwFlags =
|
||||
MOUSEEVENTF_MOVE |
|
||||
MOUSEEVENTF_ABSOLUTE |
|
||||
|
||||
// MOUSEEVENTF_VIRTUALDESK maps to the entirety of the desktop rather than the primary desktop
|
||||
MOUSEEVENTF_VIRTUALDESK;
|
||||
|
||||
auto scaled_x = std::lround((x + touch_port.offset_x) * ((float)target_touch_port.width / (float)touch_port.width));
|
||||
auto scaled_y = std::lround((y + touch_port.offset_y) * ((float)target_touch_port.height / (float)touch_port.height));
|
||||
|
||||
mi.dx = scaled_x;
|
||||
mi.dy = scaled_y;
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
void move_mouse(input_t &input, int deltaX, int deltaY) {
|
||||
INPUT i {};
|
||||
|
||||
i.type = INPUT_MOUSE;
|
||||
auto &mi = i.mi;
|
||||
|
||||
mi.dwFlags = MOUSEEVENTF_MOVE;
|
||||
mi.dx = deltaX;
|
||||
mi.dy = deltaY;
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
void button_mouse(input_t &input, int button, bool release) {
|
||||
constexpr auto KEY_STATE_DOWN = (SHORT)0x8000;
|
||||
|
||||
INPUT i {};
|
||||
|
||||
i.type = INPUT_MOUSE;
|
||||
auto &mi = i.mi;
|
||||
|
||||
int mouse_button;
|
||||
if(button == 1) {
|
||||
mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN;
|
||||
mouse_button = VK_LBUTTON;
|
||||
}
|
||||
else if(button == 2) {
|
||||
mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN;
|
||||
mouse_button = VK_MBUTTON;
|
||||
}
|
||||
else if(button == 3) {
|
||||
mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN;
|
||||
mouse_button = VK_RBUTTON;
|
||||
}
|
||||
else if(button == 4) {
|
||||
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
|
||||
mi.mouseData = XBUTTON1;
|
||||
mouse_button = VK_XBUTTON1;
|
||||
}
|
||||
else {
|
||||
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
|
||||
mi.mouseData = XBUTTON2;
|
||||
mouse_button = VK_XBUTTON2;
|
||||
}
|
||||
|
||||
auto key_state = GetAsyncKeyState(mouse_button);
|
||||
bool key_state_down = (key_state & KEY_STATE_DOWN) != 0;
|
||||
if(key_state_down != release) {
|
||||
BOOST_LOG(warning) << "Button state of mouse_button ["sv << button << "] does not match the desired state"sv;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
void scroll(input_t &input, int distance) {
|
||||
INPUT i {};
|
||||
|
||||
i.type = INPUT_MOUSE;
|
||||
auto &mi = i.mi;
|
||||
|
||||
mi.dwFlags = MOUSEEVENTF_WHEEL;
|
||||
mi.mouseData = distance;
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
void keyboard(input_t &input, uint16_t modcode, bool release) {
|
||||
INPUT i {};
|
||||
i.type = INPUT_KEYBOARD;
|
||||
auto &ki = i.ki;
|
||||
|
||||
// For some reason, MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC) doesn't seem to work :/
|
||||
if(modcode != VK_LWIN && modcode != VK_RWIN && modcode != VK_PAUSE) {
|
||||
ki.wScan = MapVirtualKey(modcode, MAPVK_VK_TO_VSC);
|
||||
ki.dwFlags = KEYEVENTF_SCANCODE;
|
||||
}
|
||||
else {
|
||||
ki.wVk = modcode;
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-message-flags
|
||||
switch(modcode) {
|
||||
case VK_RMENU:
|
||||
case VK_RCONTROL:
|
||||
case VK_INSERT:
|
||||
case VK_DELETE:
|
||||
case VK_HOME:
|
||||
case VK_END:
|
||||
case VK_PRIOR:
|
||||
case VK_NEXT:
|
||||
case VK_UP:
|
||||
case VK_DOWN:
|
||||
case VK_LEFT:
|
||||
case VK_RIGHT:
|
||||
case VK_DIVIDE:
|
||||
ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(release) {
|
||||
ki.dwFlags |= KEYEVENTF_KEYUP;
|
||||
}
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
int alloc_gamepad(input_t &input, int nr, rumble_queue_t rumble_queue) {
|
||||
if(!input) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ((vigem_t *)input.get())->alloc_gamepad_interal(nr, rumble_queue, map(config::input.gamepad));
|
||||
}
|
||||
|
||||
void free_gamepad(input_t &input, int nr) {
|
||||
if(!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
((vigem_t *)input.get())->free_target(nr);
|
||||
}
|
||||
|
||||
static VIGEM_ERROR x360_update(client_t::pointer client, target_t::pointer gp, const gamepad_state_t &gamepad_state) {
|
||||
auto &xusb = *(PXUSB_REPORT)&gamepad_state;
|
||||
|
||||
return vigem_target_x360_update(client, gp, xusb);
|
||||
}
|
||||
|
||||
static DS4_DPAD_DIRECTIONS ds4_dpad(const gamepad_state_t &gamepad_state) {
|
||||
auto flags = gamepad_state.buttonFlags;
|
||||
if(flags & DPAD_UP) {
|
||||
if(flags & DPAD_RIGHT) {
|
||||
return DS4_BUTTON_DPAD_NORTHEAST;
|
||||
}
|
||||
else if(flags & DPAD_LEFT) {
|
||||
return DS4_BUTTON_DPAD_NORTHWEST;
|
||||
}
|
||||
else {
|
||||
return DS4_BUTTON_DPAD_NORTH;
|
||||
}
|
||||
}
|
||||
|
||||
else if(flags & DPAD_DOWN) {
|
||||
if(flags & DPAD_RIGHT) {
|
||||
return DS4_BUTTON_DPAD_SOUTHEAST;
|
||||
}
|
||||
else if(flags & DPAD_LEFT) {
|
||||
return DS4_BUTTON_DPAD_SOUTHWEST;
|
||||
}
|
||||
else {
|
||||
return DS4_BUTTON_DPAD_SOUTH;
|
||||
}
|
||||
}
|
||||
|
||||
else if(flags & DPAD_RIGHT) {
|
||||
return DS4_BUTTON_DPAD_EAST;
|
||||
}
|
||||
|
||||
else if(flags & DPAD_LEFT) {
|
||||
return DS4_BUTTON_DPAD_WEST;
|
||||
}
|
||||
|
||||
return DS4_BUTTON_DPAD_NONE;
|
||||
}
|
||||
|
||||
static DS4_BUTTONS ds4_buttons(const gamepad_state_t &gamepad_state) {
|
||||
int buttons {};
|
||||
|
||||
auto flags = gamepad_state.buttonFlags;
|
||||
// clang-format off
|
||||
if(flags & LEFT_STICK) buttons |= DS4_BUTTON_THUMB_LEFT;
|
||||
if(flags & RIGHT_STICK) buttons |= DS4_BUTTON_THUMB_RIGHT;
|
||||
if(flags & LEFT_BUTTON) buttons |= DS4_BUTTON_SHOULDER_LEFT;
|
||||
if(flags & RIGHT_BUTTON) buttons |= DS4_BUTTON_SHOULDER_RIGHT;
|
||||
if(flags & START) buttons |= DS4_BUTTON_OPTIONS;
|
||||
if(flags & A) buttons |= DS4_BUTTON_CROSS;
|
||||
if(flags & B) buttons |= DS4_BUTTON_CIRCLE;
|
||||
if(flags & X) buttons |= DS4_BUTTON_SQUARE;
|
||||
if(flags & Y) buttons |= DS4_BUTTON_TRIANGLE;
|
||||
|
||||
if(gamepad_state.lt > 0) buttons |= DS4_BUTTON_TRIGGER_LEFT;
|
||||
if(gamepad_state.rt > 0) buttons |= DS4_BUTTON_TRIGGER_RIGHT;
|
||||
// clang-format on
|
||||
|
||||
return (DS4_BUTTONS)buttons;
|
||||
}
|
||||
|
||||
static DS4_SPECIAL_BUTTONS ds4_special_buttons(const gamepad_state_t &gamepad_state) {
|
||||
int buttons {};
|
||||
|
||||
if(gamepad_state.buttonFlags & BACK) buttons |= DS4_SPECIAL_BUTTON_TOUCHPAD;
|
||||
if(gamepad_state.buttonFlags & HOME) buttons |= DS4_SPECIAL_BUTTON_PS;
|
||||
|
||||
return (DS4_SPECIAL_BUTTONS)buttons;
|
||||
}
|
||||
|
||||
static std::uint8_t to_ds4_triggerX(std::int16_t v) {
|
||||
return (v + std::numeric_limits<std::uint16_t>::max() / 2 + 1) / 257;
|
||||
}
|
||||
|
||||
static std::uint8_t to_ds4_triggerY(std::int16_t v) {
|
||||
auto new_v = -((std::numeric_limits<std::uint16_t>::max() / 2 + v - 1)) / 257;
|
||||
|
||||
return new_v == 0 ? 0xFF : (std::uint8_t)new_v;
|
||||
}
|
||||
|
||||
static VIGEM_ERROR ds4_update(client_t::pointer client, target_t::pointer gp, const gamepad_state_t &gamepad_state) {
|
||||
DS4_REPORT report;
|
||||
|
||||
DS4_REPORT_INIT(&report);
|
||||
DS4_SET_DPAD(&report, ds4_dpad(gamepad_state));
|
||||
report.wButtons |= ds4_buttons(gamepad_state);
|
||||
report.bSpecial = ds4_special_buttons(gamepad_state);
|
||||
|
||||
report.bTriggerL = gamepad_state.lt;
|
||||
report.bTriggerR = gamepad_state.rt;
|
||||
|
||||
report.bThumbLX = to_ds4_triggerX(gamepad_state.lsX);
|
||||
report.bThumbLY = to_ds4_triggerY(gamepad_state.lsY);
|
||||
|
||||
report.bThumbRX = to_ds4_triggerX(gamepad_state.rsX);
|
||||
report.bThumbRY = to_ds4_triggerY(gamepad_state.rsY);
|
||||
|
||||
return vigem_target_ds4_update(client, gp, report);
|
||||
}
|
||||
|
||||
|
||||
void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
|
||||
// If there is no gamepad support
|
||||
if(!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto vigem = (vigem_t *)input.get();
|
||||
|
||||
auto &[_, gp] = vigem->gamepads[nr];
|
||||
|
||||
VIGEM_ERROR status;
|
||||
|
||||
if(vigem_target_get_type(gp.get()) == Xbox360Wired) {
|
||||
status = x360_update(vigem->client.get(), gp.get(), gamepad_state);
|
||||
}
|
||||
else {
|
||||
status = ds4_update(vigem->client.get(), gp.get(), gamepad_state);
|
||||
}
|
||||
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(fatal) << "Couldn't send gamepad input to ViGEm ["sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
log_flush();
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void freeInput(void *p) {
|
||||
auto vigem = (vigem_t *)p;
|
||||
|
||||
delete vigem;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> &supported_gamepads() {
|
||||
// ds4 == ps4
|
||||
static std::vector<std::string_view> gps {
|
||||
"x360"sv, "ds4"sv, "ps4"sv
|
||||
};
|
||||
|
||||
return gps;
|
||||
}
|
||||
} // namespace platf
|
||||
Reference in New Issue
Block a user