2019-09-06 17:09:30 +02:00
|
|
|
// Copyright 2019 Dolphin Emulator Project
|
2021-07-05 03:22:19 +02:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2019-09-06 17:09:30 +02:00
|
|
|
|
2019-10-26 18:05:16 +02:00
|
|
|
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h"
|
2019-09-06 17:09:30 +02:00
|
|
|
|
2019-11-22 16:07:12 -05:00
|
|
|
#include <algorithm>
|
|
|
|
#include <array>
|
2019-09-06 17:09:30 +02:00
|
|
|
#include <chrono>
|
2019-11-22 16:07:12 -05:00
|
|
|
#include <tuple>
|
2019-09-06 17:09:30 +02:00
|
|
|
|
|
|
|
#include <SFML/Network/SocketSelector.hpp>
|
|
|
|
#include <SFML/Network/UdpSocket.hpp>
|
2020-05-09 15:44:45 -05:00
|
|
|
#include <fmt/format.h>
|
2019-09-06 17:09:30 +02:00
|
|
|
|
|
|
|
#include "Common/Config/Config.h"
|
|
|
|
#include "Common/Flag.h"
|
|
|
|
#include "Common/Logging/Log.h"
|
|
|
|
#include "Common/MathUtil.h"
|
|
|
|
#include "Common/Random.h"
|
2021-05-15 12:28:30 +03:00
|
|
|
#include "Common/ScopeGuard.h"
|
2020-05-09 15:44:45 -05:00
|
|
|
#include "Common/StringUtil.h"
|
2019-09-06 17:09:30 +02:00
|
|
|
#include "Common/Thread.h"
|
|
|
|
#include "Core/CoreTiming.h"
|
|
|
|
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
2019-10-26 18:05:16 +02:00
|
|
|
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h"
|
2019-09-06 17:09:30 +02:00
|
|
|
|
2019-10-26 18:05:16 +02:00
|
|
|
namespace ciface::DualShockUDPClient
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
2021-01-02 19:32:53 +02:00
|
|
|
constexpr std::string_view DUALSHOCKUDP_SOURCE_NAME = "DSUClient";
|
|
|
|
|
2019-11-22 15:55:11 -05:00
|
|
|
namespace Settings
|
|
|
|
{
|
2020-07-13 00:26:43 -05:00
|
|
|
const Config::Info<std::string> SERVER_ADDRESS{
|
|
|
|
{Config::System::DualShockUDPClient, "Server", "IPAddress"}, ""};
|
|
|
|
const Config::Info<int> SERVER_PORT{{Config::System::DualShockUDPClient, "Server", "Port"}, 0};
|
2020-05-09 15:44:45 -05:00
|
|
|
const Config::Info<std::string> SERVERS{{Config::System::DualShockUDPClient, "Server", "Entries"},
|
|
|
|
""};
|
|
|
|
const Config::Info<bool> SERVERS_ENABLED{{Config::System::DualShockUDPClient, "Server", "Enabled"},
|
|
|
|
false};
|
2019-11-22 15:55:11 -05:00
|
|
|
} // namespace Settings
|
|
|
|
|
2019-11-22 16:49:43 -05:00
|
|
|
// Clock type used for querying timeframes
|
|
|
|
using SteadyClock = std::chrono::steady_clock;
|
|
|
|
|
2019-11-17 10:49:54 -06:00
|
|
|
class Device final : public Core::Device
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
|
|
|
private:
|
|
|
|
template <class T>
|
2019-11-17 10:49:54 -06:00
|
|
|
class Button final : public Input
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
|
|
|
public:
|
2019-11-17 10:49:54 -06:00
|
|
|
Button(const char* name, const T& buttons, T mask)
|
|
|
|
: m_name(name), m_buttons(buttons), m_mask(mask)
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
std::string GetName() const override { return m_name; }
|
|
|
|
ControlState GetState() const override { return (m_buttons & m_mask) != 0; }
|
|
|
|
|
|
|
|
private:
|
2019-11-17 10:49:54 -06:00
|
|
|
const char* const m_name;
|
2019-09-06 17:09:30 +02:00
|
|
|
const T& m_buttons;
|
2019-11-17 10:49:54 -06:00
|
|
|
const T m_mask;
|
2019-09-06 17:09:30 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
template <class T>
|
2019-11-17 10:49:54 -06:00
|
|
|
class AnalogInput : public Input
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
|
|
|
public:
|
2019-11-17 10:49:54 -06:00
|
|
|
AnalogInput(const char* name, const T& input, ControlState range, ControlState offset = 0)
|
|
|
|
: m_name(name), m_input(input), m_range(range), m_offset(offset)
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
|
|
|
}
|
2019-11-17 10:49:54 -06:00
|
|
|
std::string GetName() const final override { return m_name; }
|
|
|
|
ControlState GetState() const final override
|
|
|
|
{
|
|
|
|
return (ControlState(m_input) + m_offset) / m_range;
|
|
|
|
}
|
2019-09-06 17:09:30 +02:00
|
|
|
|
|
|
|
private:
|
2019-11-17 10:49:54 -06:00
|
|
|
const char* m_name;
|
2019-09-06 17:09:30 +02:00
|
|
|
const T& m_input;
|
|
|
|
const ControlState m_range;
|
|
|
|
const ControlState m_offset;
|
|
|
|
};
|
|
|
|
|
2019-11-17 10:49:54 -06:00
|
|
|
class TouchInput final : public AnalogInput<int>
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
|
|
|
public:
|
2019-11-17 10:49:54 -06:00
|
|
|
using AnalogInput::AnalogInput;
|
2020-02-22 10:27:43 -06:00
|
|
|
bool IsDetectable() const override { return false; }
|
2019-09-06 17:09:30 +02:00
|
|
|
};
|
|
|
|
|
2019-11-17 17:10:35 -06:00
|
|
|
class MotionInput final : public AnalogInput<float>
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
|
|
|
public:
|
2019-11-17 10:49:54 -06:00
|
|
|
using AnalogInput::AnalogInput;
|
2020-02-22 10:27:43 -06:00
|
|
|
bool IsDetectable() const override { return false; }
|
2019-09-06 17:09:30 +02:00
|
|
|
};
|
|
|
|
|
2019-11-17 10:49:54 -06:00
|
|
|
using AccelerometerInput = MotionInput;
|
|
|
|
using GyroInput = MotionInput;
|
2019-09-06 17:09:30 +02:00
|
|
|
|
2019-11-17 10:42:49 -06:00
|
|
|
class BatteryInput final : public Input
|
|
|
|
{
|
2019-11-17 10:49:54 -06:00
|
|
|
public:
|
2019-11-17 10:42:49 -06:00
|
|
|
using BatteryState = Proto::DsBattery;
|
|
|
|
|
|
|
|
BatteryInput(const BatteryState& battery) : m_battery(battery) {}
|
|
|
|
|
|
|
|
std::string GetName() const override { return "Battery"; }
|
|
|
|
|
|
|
|
ControlState GetState() const override
|
|
|
|
{
|
|
|
|
switch (m_battery)
|
|
|
|
{
|
2021-05-10 22:43:41 +03:00
|
|
|
case BatteryState::Charging: // We don't actually know the battery level in this case
|
2019-11-17 10:42:49 -06:00
|
|
|
case BatteryState::Charged:
|
|
|
|
return BATTERY_INPUT_MAX_VALUE;
|
|
|
|
default:
|
|
|
|
return ControlState(m_battery) / ControlState(BatteryState::Full) * BATTERY_INPUT_MAX_VALUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-22 10:27:43 -06:00
|
|
|
bool IsDetectable() const override { return false; }
|
2019-11-17 10:42:49 -06:00
|
|
|
|
|
|
|
private:
|
|
|
|
const BatteryState& m_battery;
|
|
|
|
};
|
|
|
|
|
2019-09-06 17:09:30 +02:00
|
|
|
public:
|
|
|
|
void UpdateInput() override;
|
|
|
|
|
2020-05-09 15:44:45 -05:00
|
|
|
Device(std::string name, int index, std::string server_address, u16 server_port);
|
2019-09-06 17:09:30 +02:00
|
|
|
|
|
|
|
std::string GetName() const final override;
|
|
|
|
std::string GetSource() const final override;
|
|
|
|
std::optional<int> GetPreferredId() const final override;
|
2021-05-15 12:14:11 +03:00
|
|
|
// Always add these at the end, given their hotplug nature
|
InputCommon: fix default input config default device not being loaded/found
Fixes bug: https://bugs.dolphin-emu.org/issues/12744
Before https://github.com/dolphin-emu/dolphin/commit/e1e3db13baabefa89991388d37db0bb260c4f535
the ControllerInterface m_devices_mutex was "wrongfully" locked for the whole Initialize() call, which included the first device population refresh,
this has the unwanted (accidental) consequence of often preventing the different pads (GC Pad, Wii Contollers, ...) input configs from loading
until that mutex was released (the input config defaults loading was blocked in EmulatedController::LoadDefaults()), which meant that the devices
population would often have the time to finish adding its first device, which would then be selected as default device (by design, the first device
added to the CI is the default default device, usually the "Keyboard and Mouse" device).
After the commit mentioned above removed the unnecessary m_devices_mutex calls, the default default device would fail to load (be found)
causing the default input mappings, which are specifically written for the default default device on every platform, to not be bound to any
physical device input, breaking input on new dolphin installations (until a user tried to customize the default device manually).
Default devices are now always added synchronously to avoid the problem, and so they should in the future (I added comments and warnings to help with that)
2021-11-27 14:31:04 +02:00
|
|
|
int GetSortPriority() const override { return -4; }
|
2019-09-06 17:09:30 +02:00
|
|
|
|
|
|
|
private:
|
2021-05-10 22:43:41 +03:00
|
|
|
void ResetPadData();
|
|
|
|
|
2020-05-09 15:44:45 -05:00
|
|
|
const std::string m_name;
|
2019-09-06 17:09:30 +02:00
|
|
|
const int m_index;
|
|
|
|
sf::UdpSocket m_socket;
|
2019-11-22 16:49:43 -05:00
|
|
|
SteadyClock::time_point m_next_reregister = SteadyClock::time_point::min();
|
2019-11-22 15:51:25 -05:00
|
|
|
Proto::MessageType::PadDataResponse m_pad_data{};
|
|
|
|
Proto::Touch m_prev_touch{};
|
|
|
|
bool m_prev_touch_valid = false;
|
|
|
|
int m_touch_x = 0;
|
|
|
|
int m_touch_y = 0;
|
2020-05-09 15:44:45 -05:00
|
|
|
std::string m_server_address;
|
|
|
|
u16 m_server_port;
|
2021-05-10 22:43:41 +03:00
|
|
|
|
|
|
|
s16 m_touch_x_min;
|
|
|
|
s16 m_touch_y_min;
|
|
|
|
s16 m_touch_x_max;
|
|
|
|
s16 m_touch_y_max;
|
2019-09-06 17:09:30 +02:00
|
|
|
};
|
|
|
|
|
2019-11-08 18:23:22 -06:00
|
|
|
using MathUtil::GRAVITY_ACCELERATION;
|
2019-11-22 15:55:11 -05:00
|
|
|
constexpr auto SERVER_REREGISTER_INTERVAL = std::chrono::seconds{1};
|
|
|
|
constexpr auto SERVER_LISTPORTS_INTERVAL = std::chrono::seconds{1};
|
|
|
|
constexpr int TOUCH_X_AXIS_MAX = 1000;
|
|
|
|
constexpr int TOUCH_Y_AXIS_MAX = 500;
|
2021-05-15 12:28:52 +03:00
|
|
|
constexpr auto THREAD_MAX_WAIT_INTERVAL = std::chrono::milliseconds{250};
|
|
|
|
constexpr auto SERVER_UNRESPONSIVE_INTERVAL = std::chrono::seconds{1}; // Can be 0
|
|
|
|
constexpr u32 SERVER_ASKED_PADS = 4;
|
2019-09-06 17:09:30 +02:00
|
|
|
|
2020-05-09 15:44:45 -05:00
|
|
|
struct Server
|
|
|
|
{
|
|
|
|
Server(std::string description, std::string address, u16 port)
|
|
|
|
: m_description{std::move(description)}, m_address{std::move(address)}, m_port{port}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
Server(const Server&) = delete;
|
|
|
|
Server(Server&& other) noexcept
|
|
|
|
{
|
|
|
|
m_description = std::move(other.m_description);
|
|
|
|
m_address = std::move(other.m_address);
|
|
|
|
m_port = other.m_port;
|
|
|
|
m_port_info = std::move(other.m_port_info);
|
|
|
|
}
|
|
|
|
|
|
|
|
Server& operator=(const Server&) = delete;
|
|
|
|
Server& operator=(Server&&) = delete;
|
|
|
|
|
|
|
|
~Server() = default;
|
|
|
|
|
|
|
|
std::string m_description;
|
|
|
|
std::string m_address;
|
|
|
|
u16 m_port;
|
2021-09-03 21:43:19 -07:00
|
|
|
std::array<Proto::MessageType::PortInfo, Proto::PORT_COUNT> m_port_info{};
|
2020-05-09 15:44:45 -05:00
|
|
|
sf::UdpSocket m_socket;
|
2021-05-15 12:28:52 +03:00
|
|
|
SteadyClock::time_point m_disconnect_time = SteadyClock::now();
|
2020-05-09 15:44:45 -05:00
|
|
|
};
|
|
|
|
|
2021-05-15 12:28:30 +03:00
|
|
|
static bool s_has_init;
|
2020-05-09 15:44:45 -05:00
|
|
|
static bool s_servers_enabled;
|
|
|
|
static std::vector<Server> s_servers;
|
2019-09-06 17:09:30 +02:00
|
|
|
static u32 s_client_uid;
|
2021-05-15 12:28:52 +03:00
|
|
|
static SteadyClock::time_point s_next_listports_time;
|
2019-09-06 17:09:30 +02:00
|
|
|
static std::thread s_hotplug_thread;
|
|
|
|
static Common::Flag s_hotplug_thread_running;
|
|
|
|
|
|
|
|
static bool IsSameController(const Proto::MessageType::PortInfo& a,
|
|
|
|
const Proto::MessageType::PortInfo& b)
|
|
|
|
{
|
|
|
|
// compare everything but battery_status
|
2019-11-22 16:07:12 -05:00
|
|
|
return std::tie(a.pad_id, a.pad_state, a.model, a.connection_type, a.pad_mac_address) ==
|
|
|
|
std::tie(b.pad_id, b.pad_state, b.model, b.connection_type, b.pad_mac_address);
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void HotplugThreadFunc()
|
|
|
|
{
|
2019-10-26 18:05:16 +02:00
|
|
|
Common::SetCurrentThreadName("DualShockUDPClient Hotplug Thread");
|
2021-05-04 23:37:06 +03:00
|
|
|
INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient hotplug thread started");
|
2021-05-15 12:28:30 +03:00
|
|
|
Common::ScopeGuard thread_stop_guard{
|
|
|
|
[] { INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient hotplug thread stopped"); }};
|
2019-09-06 17:09:30 +02:00
|
|
|
|
2021-05-15 12:28:52 +03:00
|
|
|
std::vector<bool> timed_out_servers(s_servers.size(), false);
|
|
|
|
|
2019-09-06 17:09:30 +02:00
|
|
|
while (s_hotplug_thread_running.IsSet())
|
|
|
|
{
|
2021-05-15 12:28:52 +03:00
|
|
|
using namespace std::chrono;
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
|
2019-11-22 16:49:43 -05:00
|
|
|
const auto now = SteadyClock::now();
|
2021-05-15 12:28:52 +03:00
|
|
|
if (now >= s_next_listports_time)
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
2021-05-15 12:28:52 +03:00
|
|
|
s_next_listports_time = now + SERVER_LISTPORTS_INTERVAL;
|
2019-09-06 17:09:30 +02:00
|
|
|
|
2021-05-15 12:28:52 +03:00
|
|
|
for (size_t i = 0; i < s_servers.size(); ++i)
|
2020-05-09 15:44:45 -05:00
|
|
|
{
|
2021-05-15 12:28:52 +03:00
|
|
|
auto& server = s_servers[i];
|
2020-05-09 15:44:45 -05:00
|
|
|
Proto::Message<Proto::MessageType::ListPorts> msg(s_client_uid);
|
|
|
|
auto& list_ports = msg.m_message;
|
2021-05-15 12:28:52 +03:00
|
|
|
// We ask for x possible devices. We will receive a message for every connected device.
|
|
|
|
list_ports.pad_request_count = SERVER_ASKED_PADS;
|
|
|
|
list_ports.pad_ids = {0, 1, 2, 3};
|
2020-05-09 15:44:45 -05:00
|
|
|
msg.Finish();
|
|
|
|
if (server.m_socket.send(&list_ports, sizeof list_ports, server.m_address, server.m_port) !=
|
|
|
|
sf::Socket::Status::Done)
|
|
|
|
{
|
2021-05-04 23:37:06 +03:00
|
|
|
ERROR_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient HotplugThreadFunc send failed");
|
2020-05-09 15:44:45 -05:00
|
|
|
}
|
2021-05-15 12:28:52 +03:00
|
|
|
timed_out_servers[i] = true;
|
2020-05-09 15:44:45 -05:00
|
|
|
}
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
|
2021-03-03 17:57:18 -06:00
|
|
|
sf::SocketSelector selector;
|
2020-05-09 15:44:45 -05:00
|
|
|
for (auto& server : s_servers)
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
2021-03-03 17:57:18 -06:00
|
|
|
selector.add(server.m_socket);
|
|
|
|
}
|
|
|
|
|
2021-05-15 12:28:52 +03:00
|
|
|
auto timeout = duration_cast<milliseconds>(s_next_listports_time - SteadyClock::now());
|
2021-03-03 17:57:18 -06:00
|
|
|
|
2021-05-15 12:28:52 +03:00
|
|
|
// Receive controller port info within a time from our request.
|
|
|
|
// Run this even if we sent no new requests, to disconnect devices,
|
|
|
|
// sleep (wait) the thread and catch old responses.
|
|
|
|
do
|
2021-03-03 17:57:18 -06:00
|
|
|
{
|
2021-05-15 12:28:52 +03:00
|
|
|
// Selector's wait treats a timeout of zero as infinite timeout, which we don't want,
|
|
|
|
// but we also don't want risk waiting for the whole SERVER_LISTPORTS_INTERVAL and hang
|
|
|
|
// the thead trying to close this one in case we received no answers.
|
|
|
|
const auto current_timeout = std::max(std::min(timeout, THREAD_MAX_WAIT_INTERVAL), 1ms);
|
|
|
|
timeout -= current_timeout;
|
|
|
|
// This will return at the first answer
|
|
|
|
if (selector.wait(sf::milliseconds(current_timeout.count())))
|
2021-03-03 17:57:18 -06:00
|
|
|
{
|
2021-05-15 12:28:52 +03:00
|
|
|
// Now check all the servers because we don't know which one(s) sent a reply
|
|
|
|
for (size_t i = 0; i < s_servers.size(); ++i)
|
|
|
|
{
|
|
|
|
auto& server = s_servers[i];
|
|
|
|
if (!selector.isReady(server.m_socket))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Proto::Message<Proto::MessageType::FromServer> msg;
|
|
|
|
std::size_t received_bytes;
|
|
|
|
sf::IpAddress sender;
|
|
|
|
u16 port;
|
|
|
|
if (server.m_socket.receive(&msg, sizeof(msg), received_bytes, sender, port) !=
|
|
|
|
sf::Socket::Status::Done)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (auto port_info = msg.CheckAndCastTo<Proto::MessageType::PortInfo>())
|
|
|
|
{
|
|
|
|
server.m_disconnect_time = SteadyClock::now() + SERVER_UNRESPONSIVE_INTERVAL;
|
|
|
|
// We have receive at least one valid update, that's enough. This is needed to avoid
|
|
|
|
// false positive when checking for disconnection in case our thread waited too long
|
|
|
|
timed_out_servers[i] = false;
|
|
|
|
|
|
|
|
const bool port_changed =
|
|
|
|
!IsSameController(*port_info, server.m_port_info[port_info->pad_id]);
|
|
|
|
if (port_changed)
|
|
|
|
{
|
|
|
|
server.m_port_info[port_info->pad_id] = *port_info;
|
|
|
|
// Just remove and re-add all the devices for simplicity
|
|
|
|
g_controller_interface.PlatformPopulateDevices([] { PopulateDevices(); });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-03-03 17:57:18 -06:00
|
|
|
}
|
2021-05-15 12:28:52 +03:00
|
|
|
if (!s_hotplug_thread_running.IsSet()) // Avoid hanging the thread for too long
|
|
|
|
return;
|
|
|
|
} while (timeout > 0ms);
|
2021-03-03 17:57:18 -06:00
|
|
|
|
2021-05-15 12:28:52 +03:00
|
|
|
// If we have failed to receive any information from the server (or even send it),
|
|
|
|
// disconnect all devices from it (after enough time has elapsed, to avoid false positives).
|
|
|
|
for (size_t i = 0; i < s_servers.size(); ++i)
|
|
|
|
{
|
|
|
|
auto& server = s_servers[i];
|
|
|
|
if (timed_out_servers[i] && SteadyClock::now() >= server.m_disconnect_time)
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
2021-05-15 12:28:52 +03:00
|
|
|
bool any_connected = false;
|
|
|
|
for (size_t port_index = 0; port_index < server.m_port_info.size(); port_index++)
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
2021-05-15 12:28:52 +03:00
|
|
|
any_connected = any_connected ||
|
|
|
|
server.m_port_info[port_index].pad_state == Proto::DsState::Connected;
|
|
|
|
server.m_port_info[port_index] = {};
|
|
|
|
server.m_port_info[port_index].pad_id = static_cast<u8>(port_index);
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
2021-05-15 12:28:52 +03:00
|
|
|
// We can't only remove devices added by this server as we wouldn't know which they are
|
|
|
|
if (any_connected)
|
|
|
|
g_controller_interface.PlatformPopulateDevices([] { PopulateDevices(); });
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void StartHotplugThread()
|
|
|
|
{
|
|
|
|
// Mark the thread as running.
|
|
|
|
if (!s_hotplug_thread_running.TestAndSet())
|
|
|
|
{
|
|
|
|
// It was already running.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
s_hotplug_thread = std::thread(HotplugThreadFunc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void StopHotplugThread()
|
|
|
|
{
|
|
|
|
// Tell the hotplug thread to stop.
|
|
|
|
if (!s_hotplug_thread_running.TestAndClear())
|
|
|
|
{
|
|
|
|
// It wasn't running, we're done.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-15 12:28:30 +03:00
|
|
|
s_hotplug_thread.join();
|
|
|
|
|
2020-05-09 15:44:45 -05:00
|
|
|
for (auto& server : s_servers)
|
|
|
|
{
|
|
|
|
server.m_socket.unbind(); // interrupt blocking socket
|
|
|
|
}
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
|
2021-05-15 12:28:30 +03:00
|
|
|
// Also just start
|
2019-10-23 01:49:48 +02:00
|
|
|
static void Restart()
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
2021-05-04 23:37:06 +03:00
|
|
|
INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient Restart");
|
2019-10-23 01:49:48 +02:00
|
|
|
|
|
|
|
StopHotplugThread();
|
2019-09-06 17:09:30 +02:00
|
|
|
|
2020-05-09 15:44:45 -05:00
|
|
|
for (auto& server : s_servers)
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
2020-05-09 15:44:45 -05:00
|
|
|
for (size_t port_index = 0; port_index < server.m_port_info.size(); port_index++)
|
|
|
|
{
|
|
|
|
server.m_port_info[port_index] = {};
|
|
|
|
server.m_port_info[port_index].pad_id = static_cast<u8>(port_index);
|
|
|
|
}
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
|
2021-05-15 12:28:30 +03:00
|
|
|
// Only removes devices as servers have been cleaned
|
|
|
|
g_controller_interface.PlatformPopulateDevices([] { PopulateDevices(); });
|
2019-10-23 01:49:48 +02:00
|
|
|
|
2021-05-15 12:25:31 +03:00
|
|
|
s_client_uid = Common::Random::GenerateValue<u32>();
|
2021-05-15 12:28:52 +03:00
|
|
|
s_next_listports_time = SteadyClock::now();
|
2021-05-15 12:25:31 +03:00
|
|
|
|
2020-05-09 15:44:45 -05:00
|
|
|
if (s_servers_enabled && !s_servers.empty())
|
2019-09-06 17:09:30 +02:00
|
|
|
StartHotplugThread();
|
|
|
|
}
|
|
|
|
|
2019-10-23 01:49:48 +02:00
|
|
|
static void ConfigChanged()
|
|
|
|
{
|
2021-05-15 12:28:30 +03:00
|
|
|
if (!s_has_init)
|
|
|
|
return;
|
|
|
|
|
2020-05-09 15:44:45 -05:00
|
|
|
const bool servers_enabled = Config::Get(Settings::SERVERS_ENABLED);
|
|
|
|
const std::string servers_setting = Config::Get(Settings::SERVERS);
|
|
|
|
|
|
|
|
std::string new_servers_setting;
|
|
|
|
for (const auto& server : s_servers)
|
|
|
|
{
|
|
|
|
new_servers_setting +=
|
|
|
|
fmt::format("{}:{}:{};", server.m_description, server.m_address, server.m_port);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (servers_enabled != s_servers_enabled || servers_setting != new_servers_setting)
|
2019-10-23 01:49:48 +02:00
|
|
|
{
|
2021-05-15 12:28:30 +03:00
|
|
|
// Stop the thread before writing to s_servers
|
|
|
|
StopHotplugThread();
|
|
|
|
|
2020-05-09 15:44:45 -05:00
|
|
|
s_servers_enabled = servers_enabled;
|
|
|
|
s_servers.clear();
|
|
|
|
|
|
|
|
const auto server_details = SplitString(servers_setting, ';');
|
|
|
|
for (const auto& server_detail : server_details)
|
|
|
|
{
|
|
|
|
const auto server_info = SplitString(server_detail, ':');
|
|
|
|
if (server_info.size() < 3)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const std::string description = server_info[0];
|
|
|
|
const std::string server_address = server_info[1];
|
|
|
|
const auto port = std::stoi(server_info[2]);
|
|
|
|
if (port >= std::numeric_limits<u16>::max())
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
u16 server_port = static_cast<u16>(port);
|
|
|
|
|
|
|
|
s_servers.emplace_back(description, server_address, server_port);
|
|
|
|
}
|
2019-10-23 01:49:48 +02:00
|
|
|
Restart();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Init()
|
|
|
|
{
|
2021-05-15 12:28:30 +03:00
|
|
|
// Does not support multiple init calls
|
|
|
|
s_has_init = true;
|
|
|
|
|
2020-07-13 00:26:43 -05:00
|
|
|
// The following is added for backwards compatibility
|
|
|
|
const auto server_address_setting = Config::Get(Settings::SERVER_ADDRESS);
|
|
|
|
const auto server_port_setting = Config::Get(Settings::SERVER_PORT);
|
|
|
|
|
|
|
|
if (!server_address_setting.empty() && server_port_setting != 0)
|
|
|
|
{
|
|
|
|
const auto& servers_setting = Config::Get(ciface::DualShockUDPClient::Settings::SERVERS);
|
|
|
|
Config::SetBaseOrCurrent(ciface::DualShockUDPClient::Settings::SERVERS,
|
|
|
|
servers_setting + fmt::format("{}:{}:{};", "DS4",
|
|
|
|
server_address_setting,
|
|
|
|
server_port_setting));
|
|
|
|
Config::SetBase(Settings::SERVER_ADDRESS, "");
|
|
|
|
Config::SetBase(Settings::SERVER_PORT, 0);
|
|
|
|
}
|
|
|
|
|
2021-02-26 01:14:00 +02:00
|
|
|
// It would be much better to unbind from this callback on DeInit but it's not possible as of now
|
2019-10-23 01:49:48 +02:00
|
|
|
Config::AddConfigChangedCallback(ConfigChanged);
|
2021-02-26 01:14:00 +02:00
|
|
|
ConfigChanged(); // Call it immediately to load settings
|
2019-10-23 01:49:48 +02:00
|
|
|
}
|
|
|
|
|
2021-05-15 12:28:30 +03:00
|
|
|
// This can be called by the host thread as well as the hotplug thread, concurrently.
|
|
|
|
// So use PlatformPopulateDevices().
|
|
|
|
// s_servers is already safe because it can only be modified when the DSU thread is not running,
|
|
|
|
// from the main thread
|
2019-09-06 17:09:30 +02:00
|
|
|
void PopulateDevices()
|
|
|
|
{
|
2021-05-04 23:37:06 +03:00
|
|
|
INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient PopulateDevices");
|
2019-09-06 17:09:30 +02:00
|
|
|
|
2021-01-02 19:32:53 +02:00
|
|
|
// s_servers has already been updated so we can't use it to know which devices we removed,
|
|
|
|
// also it's good to remove all of them before adding new ones so that their id will be set
|
|
|
|
// correctly if they have the same name
|
|
|
|
g_controller_interface.RemoveDevice(
|
|
|
|
[](const auto* dev) { return dev->GetSource() == DUALSHOCKUDP_SOURCE_NAME; });
|
|
|
|
|
2021-05-15 12:28:30 +03:00
|
|
|
// Users might have created more than one server on the same IP/Port.
|
|
|
|
// Devices might end up being duplicated (if the server responds two all requests)
|
|
|
|
// but they won't conflict.
|
|
|
|
for (const auto& server : s_servers)
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
2020-05-09 15:44:45 -05:00
|
|
|
for (size_t port_index = 0; port_index < server.m_port_info.size(); port_index++)
|
|
|
|
{
|
|
|
|
const Proto::MessageType::PortInfo& port_info = server.m_port_info[port_index];
|
|
|
|
if (port_info.pad_state != Proto::DsState::Connected)
|
|
|
|
continue;
|
2019-11-22 16:07:12 -05:00
|
|
|
|
2020-05-09 15:44:45 -05:00
|
|
|
g_controller_interface.AddDevice(std::make_shared<Device>(
|
|
|
|
server.m_description, static_cast<int>(port_index), server.m_address, server.m_port));
|
|
|
|
}
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DeInit()
|
|
|
|
{
|
|
|
|
StopHotplugThread();
|
2021-05-15 12:28:30 +03:00
|
|
|
|
|
|
|
s_has_init = false;
|
|
|
|
s_servers_enabled = false;
|
|
|
|
s_servers.clear();
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
|
2020-05-09 15:44:45 -05:00
|
|
|
Device::Device(std::string name, int index, std::string server_address, u16 server_port)
|
|
|
|
: m_name{std::move(name)}, m_index{index}, m_server_address{std::move(server_address)},
|
|
|
|
m_server_port{server_port}
|
2019-09-06 17:09:30 +02:00
|
|
|
{
|
|
|
|
m_socket.setBlocking(false);
|
|
|
|
|
|
|
|
AddInput(new AnalogInput<u8>("Pad W", m_pad_data.button_dpad_left_analog, 255));
|
|
|
|
AddInput(new AnalogInput<u8>("Pad S", m_pad_data.button_dpad_down_analog, 255));
|
|
|
|
AddInput(new AnalogInput<u8>("Pad E", m_pad_data.button_dpad_right_analog, 255));
|
|
|
|
AddInput(new AnalogInput<u8>("Pad N", m_pad_data.button_dpad_up_analog, 255));
|
|
|
|
AddInput(new AnalogInput<u8>("Square", m_pad_data.button_square_analog, 255));
|
|
|
|
AddInput(new AnalogInput<u8>("Cross", m_pad_data.button_cross_analog, 255));
|
|
|
|
AddInput(new AnalogInput<u8>("Circle", m_pad_data.button_circle_analog, 255));
|
|
|
|
AddInput(new AnalogInput<u8>("Triangle", m_pad_data.button_triangle_analog, 255));
|
|
|
|
AddInput(new AnalogInput<u8>("L1", m_pad_data.button_l1_analog, 255));
|
|
|
|
AddInput(new AnalogInput<u8>("R1", m_pad_data.button_r1_analog, 255));
|
|
|
|
|
|
|
|
AddInput(new AnalogInput<u8>("L2", m_pad_data.trigger_l2, 255));
|
|
|
|
AddInput(new AnalogInput<u8>("R2", m_pad_data.trigger_r2, 255));
|
|
|
|
|
|
|
|
AddInput(new Button<u8>("L3", m_pad_data.button_states1, 0x2));
|
|
|
|
AddInput(new Button<u8>("R3", m_pad_data.button_states1, 0x4));
|
|
|
|
AddInput(new Button<u8>("Share", m_pad_data.button_states1, 0x1));
|
|
|
|
AddInput(new Button<u8>("Options", m_pad_data.button_states1, 0x8));
|
|
|
|
AddInput(new Button<u8>("PS", m_pad_data.button_ps, 0x1));
|
|
|
|
AddInput(new Button<u8>("Touch Button", m_pad_data.button_touch, 0x1));
|
|
|
|
|
|
|
|
AddInput(new AnalogInput<u8>("Left X-", m_pad_data.left_stick_x, -128, -128));
|
|
|
|
AddInput(new AnalogInput<u8>("Left X+", m_pad_data.left_stick_x, 127, -128));
|
|
|
|
AddInput(new AnalogInput<u8>("Left Y-", m_pad_data.left_stick_y_inverted, -128, -128));
|
|
|
|
AddInput(new AnalogInput<u8>("Left Y+", m_pad_data.left_stick_y_inverted, 127, -128));
|
|
|
|
AddInput(new AnalogInput<u8>("Right X-", m_pad_data.right_stick_x, -128, -128));
|
|
|
|
AddInput(new AnalogInput<u8>("Right X+", m_pad_data.right_stick_x, 127, -128));
|
|
|
|
AddInput(new AnalogInput<u8>("Right Y-", m_pad_data.right_stick_y_inverted, -128, -128));
|
|
|
|
AddInput(new AnalogInput<u8>("Right Y+", m_pad_data.right_stick_y_inverted, 127, -128));
|
|
|
|
|
|
|
|
AddInput(new TouchInput("Touch X-", m_touch_x, -TOUCH_X_AXIS_MAX));
|
|
|
|
AddInput(new TouchInput("Touch X+", m_touch_x, TOUCH_X_AXIS_MAX));
|
|
|
|
AddInput(new TouchInput("Touch Y-", m_touch_y, -TOUCH_Y_AXIS_MAX));
|
|
|
|
AddInput(new TouchInput("Touch Y+", m_touch_y, TOUCH_Y_AXIS_MAX));
|
|
|
|
|
2019-11-17 17:10:35 -06:00
|
|
|
// Convert Gs to meters per second squared
|
|
|
|
constexpr auto accel_scale = 1.0 / GRAVITY_ACCELERATION;
|
|
|
|
|
|
|
|
AddInput(new AccelerometerInput("Accel Up", m_pad_data.accelerometer_y_g, -accel_scale));
|
|
|
|
AddInput(new AccelerometerInput("Accel Down", m_pad_data.accelerometer_y_g, accel_scale));
|
|
|
|
AddInput(new AccelerometerInput("Accel Left", m_pad_data.accelerometer_x_g, accel_scale));
|
|
|
|
AddInput(new AccelerometerInput("Accel Right", m_pad_data.accelerometer_x_g, -accel_scale));
|
|
|
|
AddInput(new AccelerometerInput("Accel Forward", m_pad_data.accelerometer_z_g, accel_scale));
|
|
|
|
AddInput(new AccelerometerInput("Accel Backward", m_pad_data.accelerometer_z_g, -accel_scale));
|
|
|
|
|
|
|
|
// Convert degrees per second to radians per second
|
|
|
|
constexpr auto gyro_scale = 360.0 / MathUtil::TAU;
|
|
|
|
|
|
|
|
AddInput(new GyroInput("Gyro Pitch Up", m_pad_data.gyro_pitch_deg_s, gyro_scale));
|
|
|
|
AddInput(new GyroInput("Gyro Pitch Down", m_pad_data.gyro_pitch_deg_s, -gyro_scale));
|
|
|
|
AddInput(new GyroInput("Gyro Roll Left", m_pad_data.gyro_roll_deg_s, -gyro_scale));
|
|
|
|
AddInput(new GyroInput("Gyro Roll Right", m_pad_data.gyro_roll_deg_s, gyro_scale));
|
|
|
|
AddInput(new GyroInput("Gyro Yaw Left", m_pad_data.gyro_yaw_deg_s, -gyro_scale));
|
|
|
|
AddInput(new GyroInput("Gyro Yaw Right", m_pad_data.gyro_yaw_deg_s, gyro_scale));
|
2021-05-10 22:43:41 +03:00
|
|
|
|
|
|
|
AddInput(new BatteryInput(m_pad_data.battery_status));
|
|
|
|
|
|
|
|
m_touch_x_min = 0;
|
|
|
|
m_touch_y_min = 0;
|
|
|
|
// DS4 touchpad max values
|
|
|
|
m_touch_x_max = 1919;
|
|
|
|
m_touch_y_max = 941;
|
|
|
|
|
|
|
|
ResetPadData();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Device::ResetPadData()
|
|
|
|
{
|
|
|
|
m_pad_data = Proto::MessageType::PadDataResponse{};
|
|
|
|
|
|
|
|
// Make sure they start from resting values, not from 0
|
|
|
|
m_touch_x = m_touch_x_min + ((m_touch_x_max - m_touch_x_min) / 2.0);
|
|
|
|
m_touch_y = m_touch_y_min + ((m_touch_y_max - m_touch_y_min) / 2.0);
|
|
|
|
m_pad_data.left_stick_x = 128;
|
|
|
|
m_pad_data.left_stick_y_inverted = 128;
|
|
|
|
m_pad_data.right_stick_x = 128;
|
|
|
|
m_pad_data.right_stick_y_inverted = 128;
|
|
|
|
m_pad_data.touch1.x = m_touch_x;
|
|
|
|
m_pad_data.touch1.y = m_touch_y;
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string Device::GetName() const
|
|
|
|
{
|
2020-05-09 15:44:45 -05:00
|
|
|
return m_name;
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string Device::GetSource() const
|
|
|
|
{
|
2021-01-02 19:32:53 +02:00
|
|
|
return std::string(DUALSHOCKUDP_SOURCE_NAME);
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Device::UpdateInput()
|
|
|
|
{
|
|
|
|
// Regularly tell the UDP server to feed us controller data
|
2019-11-22 16:49:43 -05:00
|
|
|
const auto now = SteadyClock::now();
|
2019-09-06 17:09:30 +02:00
|
|
|
if (now >= m_next_reregister)
|
|
|
|
{
|
|
|
|
m_next_reregister = now + SERVER_REREGISTER_INTERVAL;
|
|
|
|
|
2021-05-15 12:25:31 +03:00
|
|
|
Proto::Message<Proto::MessageType::PadDataRequest> msg(s_client_uid);
|
2019-09-06 17:09:30 +02:00
|
|
|
auto& data_req = msg.m_message;
|
|
|
|
data_req.register_flags = Proto::RegisterFlags::PadID;
|
|
|
|
data_req.pad_id_to_register = m_index;
|
|
|
|
msg.Finish();
|
2020-05-09 15:44:45 -05:00
|
|
|
if (m_socket.send(&data_req, sizeof(data_req), m_server_address, m_server_port) !=
|
2019-09-06 17:09:30 +02:00
|
|
|
sf::Socket::Status::Done)
|
2020-10-22 17:49:29 -04:00
|
|
|
{
|
2021-05-04 23:37:06 +03:00
|
|
|
ERROR_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient UpdateInput send failed");
|
2020-10-22 17:49:29 -04:00
|
|
|
}
|
2019-09-06 17:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Receive and handle controller data
|
|
|
|
Proto::Message<Proto::MessageType::FromServer> msg;
|
|
|
|
std::size_t received_bytes;
|
|
|
|
sf::IpAddress sender;
|
|
|
|
u16 port;
|
|
|
|
while (m_socket.receive(&msg, sizeof msg, received_bytes, sender, port) ==
|
|
|
|
sf::Socket::Status::Done)
|
|
|
|
{
|
|
|
|
if (auto pad_data = msg.CheckAndCastTo<Proto::MessageType::PadDataResponse>())
|
|
|
|
{
|
|
|
|
m_pad_data = *pad_data;
|
|
|
|
|
|
|
|
// Update touch pad relative coordinates
|
|
|
|
if (m_pad_data.touch1.id != m_prev_touch.id)
|
|
|
|
m_prev_touch_valid = false;
|
|
|
|
if (m_prev_touch_valid)
|
|
|
|
{
|
|
|
|
m_touch_x += m_pad_data.touch1.x - m_prev_touch.x;
|
|
|
|
m_touch_y += m_pad_data.touch1.y - m_prev_touch.y;
|
|
|
|
m_touch_x = std::clamp(m_touch_x, -TOUCH_X_AXIS_MAX, TOUCH_X_AXIS_MAX);
|
|
|
|
m_touch_y = std::clamp(m_touch_y, -TOUCH_Y_AXIS_MAX, TOUCH_Y_AXIS_MAX);
|
|
|
|
}
|
|
|
|
m_prev_touch = m_pad_data.touch1;
|
|
|
|
m_prev_touch_valid = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<int> Device::GetPreferredId() const
|
|
|
|
{
|
|
|
|
return m_index;
|
|
|
|
}
|
|
|
|
|
2019-10-26 18:05:16 +02:00
|
|
|
} // namespace ciface::DualShockUDPClient
|