diff options
| author | 2021-12-18 13:57:14 +0800 | |
|---|---|---|
| committer | 2021-12-18 13:57:14 +0800 | |
| commit | e49184e6069a9d791d2df3c1958f5c4b1187e124 (patch) | |
| tree | b776caf722e0be0e680f67b0ad0842628162ef1c /src/input_common/drivers | |
| parent | Implement convert legacy to generic (diff) | |
| parent | Merge pull request #7570 from ameerj/favorites-expanded (diff) | |
| download | yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.tar.gz yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.tar.xz yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.zip | |
Merge branch 'yuzu-emu:master' into convert_legacy
Diffstat (limited to 'src/input_common/drivers')
| -rw-r--r-- | src/input_common/drivers/gc_adapter.cpp | 527 | ||||
| -rw-r--r-- | src/input_common/drivers/gc_adapter.h | 135 | ||||
| -rw-r--r-- | src/input_common/drivers/keyboard.cpp | 112 | ||||
| -rw-r--r-- | src/input_common/drivers/keyboard.h | 56 | ||||
| -rw-r--r-- | src/input_common/drivers/mouse.cpp | 185 | ||||
| -rw-r--r-- | src/input_common/drivers/mouse.h | 81 | ||||
| -rw-r--r-- | src/input_common/drivers/sdl_driver.cpp | 926 | ||||
| -rw-r--r-- | src/input_common/drivers/sdl_driver.h | 117 | ||||
| -rw-r--r-- | src/input_common/drivers/tas_input.cpp | 320 | ||||
| -rw-r--r-- | src/input_common/drivers/tas_input.h | 201 | ||||
| -rw-r--r-- | src/input_common/drivers/touch_screen.cpp | 53 | ||||
| -rw-r--r-- | src/input_common/drivers/touch_screen.h | 44 | ||||
| -rw-r--r-- | src/input_common/drivers/udp_client.cpp | 591 | ||||
| -rw-r--r-- | src/input_common/drivers/udp_client.h | 185 |
14 files changed, 3533 insertions, 0 deletions
diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp new file mode 100644 index 000000000..7ab4540a8 --- /dev/null +++ b/src/input_common/drivers/gc_adapter.cpp | |||
| @@ -0,0 +1,527 @@ | |||
| 1 | // Copyright 2014 Dolphin Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <fmt/format.h> | ||
| 6 | #include <libusb.h> | ||
| 7 | |||
| 8 | #include "common/logging/log.h" | ||
| 9 | #include "common/param_package.h" | ||
| 10 | #include "common/settings_input.h" | ||
| 11 | #include "common/thread.h" | ||
| 12 | #include "input_common/drivers/gc_adapter.h" | ||
| 13 | |||
| 14 | namespace InputCommon { | ||
| 15 | |||
| 16 | class LibUSBContext { | ||
| 17 | public: | ||
| 18 | explicit LibUSBContext() { | ||
| 19 | init_result = libusb_init(&ctx); | ||
| 20 | } | ||
| 21 | |||
| 22 | ~LibUSBContext() { | ||
| 23 | libusb_exit(ctx); | ||
| 24 | } | ||
| 25 | |||
| 26 | LibUSBContext& operator=(const LibUSBContext&) = delete; | ||
| 27 | LibUSBContext(const LibUSBContext&) = delete; | ||
| 28 | |||
| 29 | LibUSBContext& operator=(LibUSBContext&&) noexcept = delete; | ||
| 30 | LibUSBContext(LibUSBContext&&) noexcept = delete; | ||
| 31 | |||
| 32 | [[nodiscard]] int InitResult() const noexcept { | ||
| 33 | return init_result; | ||
| 34 | } | ||
| 35 | |||
| 36 | [[nodiscard]] libusb_context* get() noexcept { | ||
| 37 | return ctx; | ||
| 38 | } | ||
| 39 | |||
| 40 | private: | ||
| 41 | libusb_context* ctx; | ||
| 42 | int init_result{}; | ||
| 43 | }; | ||
| 44 | |||
| 45 | class LibUSBDeviceHandle { | ||
| 46 | public: | ||
| 47 | explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept { | ||
| 48 | handle = libusb_open_device_with_vid_pid(ctx, vid, pid); | ||
| 49 | } | ||
| 50 | |||
| 51 | ~LibUSBDeviceHandle() noexcept { | ||
| 52 | if (handle) { | ||
| 53 | libusb_release_interface(handle, 1); | ||
| 54 | libusb_close(handle); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete; | ||
| 59 | LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete; | ||
| 60 | |||
| 61 | LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete; | ||
| 62 | LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete; | ||
| 63 | |||
| 64 | [[nodiscard]] libusb_device_handle* get() noexcept { | ||
| 65 | return handle; | ||
| 66 | } | ||
| 67 | |||
| 68 | private: | ||
| 69 | libusb_device_handle* handle{}; | ||
| 70 | }; | ||
| 71 | |||
| 72 | GCAdapter::GCAdapter(std::string input_engine_) : InputEngine(std::move(input_engine_)) { | ||
| 73 | if (usb_adapter_handle) { | ||
| 74 | return; | ||
| 75 | } | ||
| 76 | LOG_DEBUG(Input, "Initialization started"); | ||
| 77 | |||
| 78 | libusb_ctx = std::make_unique<LibUSBContext>(); | ||
| 79 | const int init_res = libusb_ctx->InitResult(); | ||
| 80 | if (init_res == LIBUSB_SUCCESS) { | ||
| 81 | adapter_scan_thread = | ||
| 82 | std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); }); | ||
| 83 | } else { | ||
| 84 | LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | GCAdapter::~GCAdapter() { | ||
| 89 | Reset(); | ||
| 90 | } | ||
| 91 | |||
| 92 | void GCAdapter::AdapterInputThread(std::stop_token stop_token) { | ||
| 93 | LOG_DEBUG(Input, "Input thread started"); | ||
| 94 | Common::SetCurrentThreadName("yuzu:input:GCAdapter"); | ||
| 95 | s32 payload_size{}; | ||
| 96 | AdapterPayload adapter_payload{}; | ||
| 97 | |||
| 98 | adapter_scan_thread = {}; | ||
| 99 | |||
| 100 | while (!stop_token.stop_requested()) { | ||
| 101 | libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(), | ||
| 102 | static_cast<s32>(adapter_payload.size()), &payload_size, 16); | ||
| 103 | if (IsPayloadCorrect(adapter_payload, payload_size)) { | ||
| 104 | UpdateControllers(adapter_payload); | ||
| 105 | UpdateVibrations(); | ||
| 106 | } | ||
| 107 | std::this_thread::yield(); | ||
| 108 | } | ||
| 109 | |||
| 110 | if (restart_scan_thread) { | ||
| 111 | adapter_scan_thread = | ||
| 112 | std::jthread([this](std::stop_token token) { AdapterScanThread(token); }); | ||
| 113 | restart_scan_thread = false; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { | ||
| 118 | if (payload_size != static_cast<s32>(adapter_payload.size()) || | ||
| 119 | adapter_payload[0] != LIBUSB_DT_HID) { | ||
| 120 | LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, | ||
| 121 | adapter_payload[0]); | ||
| 122 | if (input_error_counter++ > 20) { | ||
| 123 | LOG_ERROR(Input, "Timeout, Is the adapter connected?"); | ||
| 124 | adapter_input_thread.request_stop(); | ||
| 125 | restart_scan_thread = true; | ||
| 126 | } | ||
| 127 | return false; | ||
| 128 | } | ||
| 129 | |||
| 130 | input_error_counter = 0; | ||
| 131 | return true; | ||
| 132 | } | ||
| 133 | |||
| 134 | void GCAdapter::UpdateControllers(const AdapterPayload& adapter_payload) { | ||
| 135 | for (std::size_t port = 0; port < pads.size(); ++port) { | ||
| 136 | const std::size_t offset = 1 + (9 * port); | ||
| 137 | const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); | ||
| 138 | UpdatePadType(port, type); | ||
| 139 | if (DeviceConnected(port)) { | ||
| 140 | const u8 b1 = adapter_payload[offset + 1]; | ||
| 141 | const u8 b2 = adapter_payload[offset + 2]; | ||
| 142 | UpdateStateButtons(port, b1, b2); | ||
| 143 | UpdateStateAxes(port, adapter_payload); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { | ||
| 149 | if (pads[port].type == pad_type) { | ||
| 150 | return; | ||
| 151 | } | ||
| 152 | // Device changed reset device and set new type | ||
| 153 | pads[port].axis_origin = {}; | ||
| 154 | pads[port].reset_origin_counter = {}; | ||
| 155 | pads[port].enable_vibration = {}; | ||
| 156 | pads[port].rumble_amplitude = {}; | ||
| 157 | pads[port].type = pad_type; | ||
| 158 | } | ||
| 159 | |||
| 160 | void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1, | ||
| 161 | [[maybe_unused]] u8 b2) { | ||
| 162 | if (port >= pads.size()) { | ||
| 163 | return; | ||
| 164 | } | ||
| 165 | |||
| 166 | static constexpr std::array<PadButton, 8> b1_buttons{ | ||
| 167 | PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY, | ||
| 168 | PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp, | ||
| 169 | }; | ||
| 170 | |||
| 171 | static constexpr std::array<PadButton, 4> b2_buttons{ | ||
| 172 | PadButton::ButtonStart, | ||
| 173 | PadButton::TriggerZ, | ||
| 174 | PadButton::TriggerR, | ||
| 175 | PadButton::TriggerL, | ||
| 176 | }; | ||
| 177 | |||
| 178 | for (std::size_t i = 0; i < b1_buttons.size(); ++i) { | ||
| 179 | const bool button_status = (b1 & (1U << i)) != 0; | ||
| 180 | const int button = static_cast<int>(b1_buttons[i]); | ||
| 181 | SetButton(pads[port].identifier, button, button_status); | ||
| 182 | } | ||
| 183 | |||
| 184 | for (std::size_t j = 0; j < b2_buttons.size(); ++j) { | ||
| 185 | const bool button_status = (b2 & (1U << j)) != 0; | ||
| 186 | const int button = static_cast<int>(b2_buttons[j]); | ||
| 187 | SetButton(pads[port].identifier, button, button_status); | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { | ||
| 192 | if (port >= pads.size()) { | ||
| 193 | return; | ||
| 194 | } | ||
| 195 | |||
| 196 | const std::size_t offset = 1 + (9 * port); | ||
| 197 | static constexpr std::array<PadAxes, 6> axes{ | ||
| 198 | PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX, | ||
| 199 | PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, | ||
| 200 | }; | ||
| 201 | |||
| 202 | for (const PadAxes axis : axes) { | ||
| 203 | const auto index = static_cast<std::size_t>(axis); | ||
| 204 | const u8 axis_value = adapter_payload[offset + 3 + index]; | ||
| 205 | if (pads[port].reset_origin_counter <= 18) { | ||
| 206 | if (pads[port].axis_origin[index] != axis_value) { | ||
| 207 | pads[port].reset_origin_counter = 0; | ||
| 208 | } | ||
| 209 | pads[port].axis_origin[index] = axis_value; | ||
| 210 | pads[port].reset_origin_counter++; | ||
| 211 | } | ||
| 212 | const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f; | ||
| 213 | SetAxis(pads[port].identifier, static_cast<int>(index), axis_status); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | void GCAdapter::AdapterScanThread(std::stop_token stop_token) { | ||
| 218 | Common::SetCurrentThreadName("yuzu:input:ScanGCAdapter"); | ||
| 219 | usb_adapter_handle = nullptr; | ||
| 220 | pads = {}; | ||
| 221 | while (!stop_token.stop_requested() && !Setup()) { | ||
| 222 | std::this_thread::sleep_for(std::chrono::seconds(2)); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | bool GCAdapter::Setup() { | ||
| 227 | constexpr u16 nintendo_vid = 0x057e; | ||
| 228 | constexpr u16 gc_adapter_pid = 0x0337; | ||
| 229 | usb_adapter_handle = | ||
| 230 | std::make_unique<LibUSBDeviceHandle>(libusb_ctx->get(), nintendo_vid, gc_adapter_pid); | ||
| 231 | if (!usb_adapter_handle->get()) { | ||
| 232 | return false; | ||
| 233 | } | ||
| 234 | if (!CheckDeviceAccess()) { | ||
| 235 | usb_adapter_handle = nullptr; | ||
| 236 | return false; | ||
| 237 | } | ||
| 238 | |||
| 239 | libusb_device* const device = libusb_get_device(usb_adapter_handle->get()); | ||
| 240 | |||
| 241 | LOG_INFO(Input, "GC adapter is now connected"); | ||
| 242 | // GC Adapter found and accessible, registering it | ||
| 243 | if (GetGCEndpoint(device)) { | ||
| 244 | rumble_enabled = true; | ||
| 245 | input_error_counter = 0; | ||
| 246 | output_error_counter = 0; | ||
| 247 | |||
| 248 | std::size_t port = 0; | ||
| 249 | for (GCController& pad : pads) { | ||
| 250 | pad.identifier = { | ||
| 251 | .guid = Common::UUID{Common::INVALID_UUID}, | ||
| 252 | .port = port++, | ||
| 253 | .pad = 0, | ||
| 254 | }; | ||
| 255 | PreSetController(pad.identifier); | ||
| 256 | } | ||
| 257 | |||
| 258 | adapter_input_thread = | ||
| 259 | std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); }); | ||
| 260 | return true; | ||
| 261 | } | ||
| 262 | return false; | ||
| 263 | } | ||
| 264 | |||
| 265 | bool GCAdapter::CheckDeviceAccess() { | ||
| 266 | s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0); | ||
| 267 | if (kernel_driver_error == 1) { | ||
| 268 | kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 0); | ||
| 269 | if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { | ||
| 270 | LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}", | ||
| 271 | kernel_driver_error); | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { | ||
| 276 | usb_adapter_handle = nullptr; | ||
| 277 | return false; | ||
| 278 | } | ||
| 279 | |||
| 280 | const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0); | ||
| 281 | if (interface_claim_error) { | ||
| 282 | LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); | ||
| 283 | usb_adapter_handle = nullptr; | ||
| 284 | return false; | ||
| 285 | } | ||
| 286 | |||
| 287 | // This fixes payload problems from offbrand GCAdapters | ||
| 288 | const s32 control_transfer_error = | ||
| 289 | libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000); | ||
| 290 | if (control_transfer_error < 0) { | ||
| 291 | LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); | ||
| 292 | } | ||
| 293 | |||
| 294 | return true; | ||
| 295 | } | ||
| 296 | |||
| 297 | bool GCAdapter::GetGCEndpoint(libusb_device* device) { | ||
| 298 | libusb_config_descriptor* config = nullptr; | ||
| 299 | const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); | ||
| 300 | if (config_descriptor_return != LIBUSB_SUCCESS) { | ||
| 301 | LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}", | ||
| 302 | config_descriptor_return); | ||
| 303 | return false; | ||
| 304 | } | ||
| 305 | |||
| 306 | for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { | ||
| 307 | const libusb_interface* interfaceContainer = &config->interface[ic]; | ||
| 308 | for (int i = 0; i < interfaceContainer->num_altsetting; i++) { | ||
| 309 | const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i]; | ||
| 310 | for (u8 e = 0; e < interface->bNumEndpoints; e++) { | ||
| 311 | const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; | ||
| 312 | if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) { | ||
| 313 | input_endpoint = endpoint->bEndpointAddress; | ||
| 314 | } else { | ||
| 315 | output_endpoint = endpoint->bEndpointAddress; | ||
| 316 | } | ||
| 317 | } | ||
| 318 | } | ||
| 319 | } | ||
| 320 | // This transfer seems to be responsible for clearing the state of the adapter | ||
| 321 | // Used to clear the "busy" state of when the device is unexpectedly unplugged | ||
| 322 | unsigned char clear_payload = 0x13; | ||
| 323 | libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, &clear_payload, | ||
| 324 | sizeof(clear_payload), nullptr, 16); | ||
| 325 | return true; | ||
| 326 | } | ||
| 327 | |||
| 328 | Common::Input::VibrationError GCAdapter::SetRumble( | ||
| 329 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { | ||
| 330 | const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; | ||
| 331 | const auto processed_amplitude = | ||
| 332 | static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); | ||
| 333 | |||
| 334 | pads[identifier.port].rumble_amplitude = processed_amplitude; | ||
| 335 | |||
| 336 | if (!rumble_enabled) { | ||
| 337 | return Common::Input::VibrationError::Disabled; | ||
| 338 | } | ||
| 339 | return Common::Input::VibrationError::None; | ||
| 340 | } | ||
| 341 | |||
| 342 | void GCAdapter::UpdateVibrations() { | ||
| 343 | // Use 8 states to keep the switching between on/off fast enough for | ||
| 344 | // a human to feel different vibration strenght | ||
| 345 | // More states == more rumble strengths == slower update time | ||
| 346 | constexpr u8 vibration_states = 8; | ||
| 347 | |||
| 348 | vibration_counter = (vibration_counter + 1) % vibration_states; | ||
| 349 | |||
| 350 | for (GCController& pad : pads) { | ||
| 351 | const bool vibrate = pad.rumble_amplitude > vibration_counter; | ||
| 352 | vibration_changed |= vibrate != pad.enable_vibration; | ||
| 353 | pad.enable_vibration = vibrate; | ||
| 354 | } | ||
| 355 | SendVibrations(); | ||
| 356 | } | ||
| 357 | |||
| 358 | void GCAdapter::SendVibrations() { | ||
| 359 | if (!rumble_enabled || !vibration_changed) { | ||
| 360 | return; | ||
| 361 | } | ||
| 362 | s32 size{}; | ||
| 363 | constexpr u8 rumble_command = 0x11; | ||
| 364 | const u8 p1 = pads[0].enable_vibration; | ||
| 365 | const u8 p2 = pads[1].enable_vibration; | ||
| 366 | const u8 p3 = pads[2].enable_vibration; | ||
| 367 | const u8 p4 = pads[3].enable_vibration; | ||
| 368 | std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4}; | ||
| 369 | const int err = | ||
| 370 | libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(), | ||
| 371 | static_cast<s32>(payload.size()), &size, 16); | ||
| 372 | if (err) { | ||
| 373 | LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err)); | ||
| 374 | if (output_error_counter++ > 5) { | ||
| 375 | LOG_ERROR(Input, "Output timeout, Rumble disabled"); | ||
| 376 | rumble_enabled = false; | ||
| 377 | } | ||
| 378 | return; | ||
| 379 | } | ||
| 380 | output_error_counter = 0; | ||
| 381 | vibration_changed = false; | ||
| 382 | } | ||
| 383 | |||
| 384 | bool GCAdapter::DeviceConnected(std::size_t port) const { | ||
| 385 | return pads[port].type != ControllerTypes::None; | ||
| 386 | } | ||
| 387 | |||
| 388 | void GCAdapter::Reset() { | ||
| 389 | adapter_scan_thread = {}; | ||
| 390 | adapter_input_thread = {}; | ||
| 391 | usb_adapter_handle = nullptr; | ||
| 392 | pads = {}; | ||
| 393 | libusb_ctx = nullptr; | ||
| 394 | } | ||
| 395 | |||
| 396 | std::vector<Common::ParamPackage> GCAdapter::GetInputDevices() const { | ||
| 397 | std::vector<Common::ParamPackage> devices; | ||
| 398 | for (std::size_t port = 0; port < pads.size(); ++port) { | ||
| 399 | if (!DeviceConnected(port)) { | ||
| 400 | continue; | ||
| 401 | } | ||
| 402 | Common::ParamPackage identifier{}; | ||
| 403 | identifier.Set("engine", GetEngineName()); | ||
| 404 | identifier.Set("display", fmt::format("Gamecube Controller {}", port + 1)); | ||
| 405 | identifier.Set("port", static_cast<int>(port)); | ||
| 406 | devices.emplace_back(identifier); | ||
| 407 | } | ||
| 408 | return devices; | ||
| 409 | } | ||
| 410 | |||
| 411 | ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) { | ||
| 412 | // This list is missing ZL/ZR since those are not considered buttons. | ||
| 413 | // We will add those afterwards | ||
| 414 | // This list also excludes any button that can't be really mapped | ||
| 415 | static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12> | ||
| 416 | switch_to_gcadapter_button = { | ||
| 417 | std::pair{Settings::NativeButton::A, PadButton::ButtonA}, | ||
| 418 | {Settings::NativeButton::B, PadButton::ButtonB}, | ||
| 419 | {Settings::NativeButton::X, PadButton::ButtonX}, | ||
| 420 | {Settings::NativeButton::Y, PadButton::ButtonY}, | ||
| 421 | {Settings::NativeButton::Plus, PadButton::ButtonStart}, | ||
| 422 | {Settings::NativeButton::DLeft, PadButton::ButtonLeft}, | ||
| 423 | {Settings::NativeButton::DUp, PadButton::ButtonUp}, | ||
| 424 | {Settings::NativeButton::DRight, PadButton::ButtonRight}, | ||
| 425 | {Settings::NativeButton::DDown, PadButton::ButtonDown}, | ||
| 426 | {Settings::NativeButton::SL, PadButton::TriggerL}, | ||
| 427 | {Settings::NativeButton::SR, PadButton::TriggerR}, | ||
| 428 | {Settings::NativeButton::R, PadButton::TriggerZ}, | ||
| 429 | }; | ||
| 430 | if (!params.Has("port")) { | ||
| 431 | return {}; | ||
| 432 | } | ||
| 433 | |||
| 434 | ButtonMapping mapping{}; | ||
| 435 | for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) { | ||
| 436 | Common::ParamPackage button_params{}; | ||
| 437 | button_params.Set("engine", GetEngineName()); | ||
| 438 | button_params.Set("port", params.Get("port", 0)); | ||
| 439 | button_params.Set("button", static_cast<int>(gcadapter_button)); | ||
| 440 | mapping.insert_or_assign(switch_button, std::move(button_params)); | ||
| 441 | } | ||
| 442 | |||
| 443 | // Add the missing bindings for ZL/ZR | ||
| 444 | static constexpr std::array<std::tuple<Settings::NativeButton::Values, PadButton, PadAxes>, 2> | ||
| 445 | switch_to_gcadapter_axis = { | ||
| 446 | std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft}, | ||
| 447 | {Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight}, | ||
| 448 | }; | ||
| 449 | for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) { | ||
| 450 | Common::ParamPackage button_params{}; | ||
| 451 | button_params.Set("engine", GetEngineName()); | ||
| 452 | button_params.Set("port", params.Get("port", 0)); | ||
| 453 | button_params.Set("button", static_cast<s32>(gcadapter_buton)); | ||
| 454 | button_params.Set("axis", static_cast<s32>(gcadapter_axis)); | ||
| 455 | button_params.Set("threshold", 0.5f); | ||
| 456 | button_params.Set("range", 1.9f); | ||
| 457 | button_params.Set("direction", "+"); | ||
| 458 | mapping.insert_or_assign(switch_button, std::move(button_params)); | ||
| 459 | } | ||
| 460 | return mapping; | ||
| 461 | } | ||
| 462 | |||
| 463 | AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) { | ||
| 464 | if (!params.Has("port")) { | ||
| 465 | return {}; | ||
| 466 | } | ||
| 467 | |||
| 468 | AnalogMapping mapping = {}; | ||
| 469 | Common::ParamPackage left_analog_params; | ||
| 470 | left_analog_params.Set("engine", GetEngineName()); | ||
| 471 | left_analog_params.Set("port", params.Get("port", 0)); | ||
| 472 | left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX)); | ||
| 473 | left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY)); | ||
| 474 | mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); | ||
| 475 | Common::ParamPackage right_analog_params; | ||
| 476 | right_analog_params.Set("engine", GetEngineName()); | ||
| 477 | right_analog_params.Set("port", params.Get("port", 0)); | ||
| 478 | right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX)); | ||
| 479 | right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY)); | ||
| 480 | mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); | ||
| 481 | return mapping; | ||
| 482 | } | ||
| 483 | |||
| 484 | Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const { | ||
| 485 | PadButton button = static_cast<PadButton>(params.Get("button", 0)); | ||
| 486 | switch (button) { | ||
| 487 | case PadButton::ButtonLeft: | ||
| 488 | return Common::Input::ButtonNames::ButtonLeft; | ||
| 489 | case PadButton::ButtonRight: | ||
| 490 | return Common::Input::ButtonNames::ButtonRight; | ||
| 491 | case PadButton::ButtonDown: | ||
| 492 | return Common::Input::ButtonNames::ButtonDown; | ||
| 493 | case PadButton::ButtonUp: | ||
| 494 | return Common::Input::ButtonNames::ButtonUp; | ||
| 495 | case PadButton::TriggerZ: | ||
| 496 | return Common::Input::ButtonNames::TriggerZ; | ||
| 497 | case PadButton::TriggerR: | ||
| 498 | return Common::Input::ButtonNames::TriggerR; | ||
| 499 | case PadButton::TriggerL: | ||
| 500 | return Common::Input::ButtonNames::TriggerL; | ||
| 501 | case PadButton::ButtonA: | ||
| 502 | return Common::Input::ButtonNames::ButtonA; | ||
| 503 | case PadButton::ButtonB: | ||
| 504 | return Common::Input::ButtonNames::ButtonB; | ||
| 505 | case PadButton::ButtonX: | ||
| 506 | return Common::Input::ButtonNames::ButtonX; | ||
| 507 | case PadButton::ButtonY: | ||
| 508 | return Common::Input::ButtonNames::ButtonY; | ||
| 509 | case PadButton::ButtonStart: | ||
| 510 | return Common::Input::ButtonNames::ButtonStart; | ||
| 511 | default: | ||
| 512 | return Common::Input::ButtonNames::Undefined; | ||
| 513 | } | ||
| 514 | } | ||
| 515 | |||
| 516 | Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& params) const { | ||
| 517 | if (params.Has("button")) { | ||
| 518 | return GetUIButtonName(params); | ||
| 519 | } | ||
| 520 | if (params.Has("axis")) { | ||
| 521 | return Common::Input::ButtonNames::Value; | ||
| 522 | } | ||
| 523 | |||
| 524 | return Common::Input::ButtonNames::Invalid; | ||
| 525 | } | ||
| 526 | |||
| 527 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h new file mode 100644 index 000000000..7ce1912a3 --- /dev/null +++ b/src/input_common/drivers/gc_adapter.h | |||
| @@ -0,0 +1,135 @@ | |||
| 1 | // Copyright 2014 Dolphin Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <memory> | ||
| 9 | #include <mutex> | ||
| 10 | #include <stop_token> | ||
| 11 | #include <string> | ||
| 12 | #include <thread> | ||
| 13 | |||
| 14 | #include "input_common/input_engine.h" | ||
| 15 | |||
| 16 | struct libusb_context; | ||
| 17 | struct libusb_device; | ||
| 18 | struct libusb_device_handle; | ||
| 19 | |||
| 20 | namespace InputCommon { | ||
| 21 | |||
| 22 | class LibUSBContext; | ||
| 23 | class LibUSBDeviceHandle; | ||
| 24 | |||
| 25 | class GCAdapter : public InputEngine { | ||
| 26 | public: | ||
| 27 | explicit GCAdapter(std::string input_engine_); | ||
| 28 | ~GCAdapter() override; | ||
| 29 | |||
| 30 | Common::Input::VibrationError SetRumble( | ||
| 31 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; | ||
| 32 | |||
| 33 | /// Used for automapping features | ||
| 34 | std::vector<Common::ParamPackage> GetInputDevices() const override; | ||
| 35 | ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; | ||
| 36 | AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; | ||
| 37 | Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; | ||
| 38 | |||
| 39 | private: | ||
| 40 | enum class PadButton { | ||
| 41 | Undefined = 0x0000, | ||
| 42 | ButtonLeft = 0x0001, | ||
| 43 | ButtonRight = 0x0002, | ||
| 44 | ButtonDown = 0x0004, | ||
| 45 | ButtonUp = 0x0008, | ||
| 46 | TriggerZ = 0x0010, | ||
| 47 | TriggerR = 0x0020, | ||
| 48 | TriggerL = 0x0040, | ||
| 49 | ButtonA = 0x0100, | ||
| 50 | ButtonB = 0x0200, | ||
| 51 | ButtonX = 0x0400, | ||
| 52 | ButtonY = 0x0800, | ||
| 53 | ButtonStart = 0x1000, | ||
| 54 | }; | ||
| 55 | |||
| 56 | enum class PadAxes : u8 { | ||
| 57 | StickX, | ||
| 58 | StickY, | ||
| 59 | SubstickX, | ||
| 60 | SubstickY, | ||
| 61 | TriggerLeft, | ||
| 62 | TriggerRight, | ||
| 63 | Undefined, | ||
| 64 | }; | ||
| 65 | |||
| 66 | enum class ControllerTypes { | ||
| 67 | None, | ||
| 68 | Wired, | ||
| 69 | Wireless, | ||
| 70 | }; | ||
| 71 | |||
| 72 | struct GCController { | ||
| 73 | ControllerTypes type = ControllerTypes::None; | ||
| 74 | PadIdentifier identifier{}; | ||
| 75 | bool enable_vibration = false; | ||
| 76 | u8 rumble_amplitude{}; | ||
| 77 | std::array<u8, 6> axis_origin{}; | ||
| 78 | u8 reset_origin_counter{}; | ||
| 79 | }; | ||
| 80 | |||
| 81 | using AdapterPayload = std::array<u8, 37>; | ||
| 82 | |||
| 83 | void UpdatePadType(std::size_t port, ControllerTypes pad_type); | ||
| 84 | void UpdateControllers(const AdapterPayload& adapter_payload); | ||
| 85 | void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); | ||
| 86 | void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); | ||
| 87 | |||
| 88 | void AdapterInputThread(std::stop_token stop_token); | ||
| 89 | |||
| 90 | void AdapterScanThread(std::stop_token stop_token); | ||
| 91 | |||
| 92 | bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); | ||
| 93 | |||
| 94 | /// For use in initialization, querying devices to find the adapter | ||
| 95 | bool Setup(); | ||
| 96 | |||
| 97 | /// Returns true if we successfully gain access to GC Adapter | ||
| 98 | bool CheckDeviceAccess(); | ||
| 99 | |||
| 100 | /// Captures GC Adapter endpoint address | ||
| 101 | /// Returns true if the endpoint was set correctly | ||
| 102 | bool GetGCEndpoint(libusb_device* device); | ||
| 103 | |||
| 104 | /// Returns true if there is a device connected to port | ||
| 105 | bool DeviceConnected(std::size_t port) const; | ||
| 106 | |||
| 107 | /// For shutting down, clear all data, join all threads, release usb | ||
| 108 | void Reset(); | ||
| 109 | |||
| 110 | void UpdateVibrations(); | ||
| 111 | |||
| 112 | /// Updates vibration state of all controllers | ||
| 113 | void SendVibrations(); | ||
| 114 | |||
| 115 | Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; | ||
| 116 | |||
| 117 | std::unique_ptr<LibUSBDeviceHandle> usb_adapter_handle; | ||
| 118 | std::array<GCController, 4> pads; | ||
| 119 | |||
| 120 | std::jthread adapter_input_thread; | ||
| 121 | std::jthread adapter_scan_thread; | ||
| 122 | bool restart_scan_thread{}; | ||
| 123 | |||
| 124 | std::unique_ptr<LibUSBContext> libusb_ctx; | ||
| 125 | |||
| 126 | u8 input_endpoint{0}; | ||
| 127 | u8 output_endpoint{0}; | ||
| 128 | u8 input_error_counter{0}; | ||
| 129 | u8 output_error_counter{0}; | ||
| 130 | int vibration_counter{0}; | ||
| 131 | |||
| 132 | bool rumble_enabled{true}; | ||
| 133 | bool vibration_changed{true}; | ||
| 134 | }; | ||
| 135 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp new file mode 100644 index 000000000..4c1e5bbec --- /dev/null +++ b/src/input_common/drivers/keyboard.cpp | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included | ||
| 4 | |||
| 5 | #include "common/param_package.h" | ||
| 6 | #include "common/settings_input.h" | ||
| 7 | #include "input_common/drivers/keyboard.h" | ||
| 8 | |||
| 9 | namespace InputCommon { | ||
| 10 | |||
| 11 | constexpr PadIdentifier key_identifier = { | ||
| 12 | .guid = Common::UUID{Common::INVALID_UUID}, | ||
| 13 | .port = 0, | ||
| 14 | .pad = 0, | ||
| 15 | }; | ||
| 16 | constexpr PadIdentifier keyboard_key_identifier = { | ||
| 17 | .guid = Common::UUID{Common::INVALID_UUID}, | ||
| 18 | .port = 1, | ||
| 19 | .pad = 0, | ||
| 20 | }; | ||
| 21 | constexpr PadIdentifier keyboard_modifier_identifier = { | ||
| 22 | .guid = Common::UUID{Common::INVALID_UUID}, | ||
| 23 | .port = 1, | ||
| 24 | .pad = 1, | ||
| 25 | }; | ||
| 26 | |||
| 27 | Keyboard::Keyboard(std::string input_engine_) : InputEngine(std::move(input_engine_)) { | ||
| 28 | // Keyboard is broken into 3 diferent sets: | ||
| 29 | // key: Unfiltered intended for controllers. | ||
| 30 | // keyboard_key: Allows only Settings::NativeKeyboard::Keys intended for keyboard emulation. | ||
| 31 | // keyboard_modifier: Allows only Settings::NativeKeyboard::Modifiers intended for keyboard | ||
| 32 | // emulation. | ||
| 33 | PreSetController(key_identifier); | ||
| 34 | PreSetController(keyboard_key_identifier); | ||
| 35 | PreSetController(keyboard_modifier_identifier); | ||
| 36 | } | ||
| 37 | |||
| 38 | void Keyboard::PressKey(int key_code) { | ||
| 39 | SetButton(key_identifier, key_code, true); | ||
| 40 | } | ||
| 41 | |||
| 42 | void Keyboard::ReleaseKey(int key_code) { | ||
| 43 | SetButton(key_identifier, key_code, false); | ||
| 44 | } | ||
| 45 | |||
| 46 | void Keyboard::PressKeyboardKey(int key_index) { | ||
| 47 | if (key_index == Settings::NativeKeyboard::None) { | ||
| 48 | return; | ||
| 49 | } | ||
| 50 | SetButton(keyboard_key_identifier, key_index, true); | ||
| 51 | } | ||
| 52 | |||
| 53 | void Keyboard::ReleaseKeyboardKey(int key_index) { | ||
| 54 | if (key_index == Settings::NativeKeyboard::None) { | ||
| 55 | return; | ||
| 56 | } | ||
| 57 | SetButton(keyboard_key_identifier, key_index, false); | ||
| 58 | } | ||
| 59 | |||
| 60 | void Keyboard::SetKeyboardModifiers(int key_modifiers) { | ||
| 61 | for (int i = 0; i < 32; ++i) { | ||
| 62 | bool key_value = ((key_modifiers >> i) & 0x1) != 0; | ||
| 63 | SetButton(keyboard_modifier_identifier, i, key_value); | ||
| 64 | // Use the modifier to press the key button equivalent | ||
| 65 | switch (i) { | ||
| 66 | case Settings::NativeKeyboard::LeftControl: | ||
| 67 | SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftControlKey, key_value); | ||
| 68 | break; | ||
| 69 | case Settings::NativeKeyboard::LeftShift: | ||
| 70 | SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftShiftKey, key_value); | ||
| 71 | break; | ||
| 72 | case Settings::NativeKeyboard::LeftAlt: | ||
| 73 | SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftAltKey, key_value); | ||
| 74 | break; | ||
| 75 | case Settings::NativeKeyboard::LeftMeta: | ||
| 76 | SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftMetaKey, key_value); | ||
| 77 | break; | ||
| 78 | case Settings::NativeKeyboard::RightControl: | ||
| 79 | SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightControlKey, | ||
| 80 | key_value); | ||
| 81 | break; | ||
| 82 | case Settings::NativeKeyboard::RightShift: | ||
| 83 | SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightShiftKey, key_value); | ||
| 84 | break; | ||
| 85 | case Settings::NativeKeyboard::RightAlt: | ||
| 86 | SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightAltKey, key_value); | ||
| 87 | break; | ||
| 88 | case Settings::NativeKeyboard::RightMeta: | ||
| 89 | SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightMetaKey, key_value); | ||
| 90 | break; | ||
| 91 | default: | ||
| 92 | // Other modifier keys should be pressed with PressKey since they stay enabled until | ||
| 93 | // next press | ||
| 94 | break; | ||
| 95 | } | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | void Keyboard::ReleaseAllKeys() { | ||
| 100 | ResetButtonState(); | ||
| 101 | } | ||
| 102 | |||
| 103 | std::vector<Common::ParamPackage> Keyboard::GetInputDevices() const { | ||
| 104 | std::vector<Common::ParamPackage> devices; | ||
| 105 | devices.emplace_back(Common::ParamPackage{ | ||
| 106 | {"engine", GetEngineName()}, | ||
| 107 | {"display", "Keyboard Only"}, | ||
| 108 | }); | ||
| 109 | return devices; | ||
| 110 | } | ||
| 111 | |||
| 112 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h new file mode 100644 index 000000000..3856c882c --- /dev/null +++ b/src/input_common/drivers/keyboard.h | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "input_common/input_engine.h" | ||
| 8 | |||
| 9 | namespace InputCommon { | ||
| 10 | |||
| 11 | /** | ||
| 12 | * A button device factory representing a keyboard. It receives keyboard events and forward them | ||
| 13 | * to all button devices it created. | ||
| 14 | */ | ||
| 15 | class Keyboard final : public InputEngine { | ||
| 16 | public: | ||
| 17 | explicit Keyboard(std::string input_engine_); | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Sets the status of all buttons bound with the key to pressed | ||
| 21 | * @param key_code the code of the key to press | ||
| 22 | */ | ||
| 23 | void PressKey(int key_code); | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Sets the status of all buttons bound with the key to released | ||
| 27 | * @param key_code the code of the key to release | ||
| 28 | */ | ||
| 29 | void ReleaseKey(int key_code); | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Sets the status of the keyboard key to pressed | ||
| 33 | * @param key_index index of the key to press | ||
| 34 | */ | ||
| 35 | void PressKeyboardKey(int key_index); | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Sets the status of the keyboard key to released | ||
| 39 | * @param key_index index of the key to release | ||
| 40 | */ | ||
| 41 | void ReleaseKeyboardKey(int key_index); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Sets the status of all keyboard modifier keys | ||
| 45 | * @param key_modifiers the code of the key to release | ||
| 46 | */ | ||
| 47 | void SetKeyboardModifiers(int key_modifiers); | ||
| 48 | |||
| 49 | /// Sets all keys to the non pressed state | ||
| 50 | void ReleaseAllKeys(); | ||
| 51 | |||
| 52 | /// Used for automapping features | ||
| 53 | std::vector<Common::ParamPackage> GetInputDevices() const override; | ||
| 54 | }; | ||
| 55 | |||
| 56 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp new file mode 100644 index 000000000..aa69216c8 --- /dev/null +++ b/src/input_common/drivers/mouse.cpp | |||
| @@ -0,0 +1,185 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included | ||
| 4 | |||
| 5 | #include <stop_token> | ||
| 6 | #include <thread> | ||
| 7 | #include <fmt/format.h> | ||
| 8 | |||
| 9 | #include "common/param_package.h" | ||
| 10 | #include "common/settings.h" | ||
| 11 | #include "common/thread.h" | ||
| 12 | #include "input_common/drivers/mouse.h" | ||
| 13 | |||
| 14 | namespace InputCommon { | ||
| 15 | constexpr int mouse_axis_x = 0; | ||
| 16 | constexpr int mouse_axis_y = 1; | ||
| 17 | constexpr int wheel_axis_x = 2; | ||
| 18 | constexpr int wheel_axis_y = 3; | ||
| 19 | constexpr int touch_axis_x = 10; | ||
| 20 | constexpr int touch_axis_y = 11; | ||
| 21 | constexpr PadIdentifier identifier = { | ||
| 22 | .guid = Common::UUID{Common::INVALID_UUID}, | ||
| 23 | .port = 0, | ||
| 24 | .pad = 0, | ||
| 25 | }; | ||
| 26 | |||
| 27 | Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) { | ||
| 28 | PreSetController(identifier); | ||
| 29 | PreSetAxis(identifier, mouse_axis_x); | ||
| 30 | PreSetAxis(identifier, mouse_axis_y); | ||
| 31 | PreSetAxis(identifier, wheel_axis_x); | ||
| 32 | PreSetAxis(identifier, wheel_axis_y); | ||
| 33 | PreSetAxis(identifier, touch_axis_x); | ||
| 34 | PreSetAxis(identifier, touch_axis_x); | ||
| 35 | update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); }); | ||
| 36 | } | ||
| 37 | |||
| 38 | void Mouse::UpdateThread(std::stop_token stop_token) { | ||
| 39 | Common::SetCurrentThreadName("yuzu:input:Mouse"); | ||
| 40 | constexpr int update_time = 10; | ||
| 41 | while (!stop_token.stop_requested()) { | ||
| 42 | if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { | ||
| 43 | // Slow movement by 4% | ||
| 44 | last_mouse_change *= 0.96f; | ||
| 45 | const float sensitivity = | ||
| 46 | Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f; | ||
| 47 | SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity); | ||
| 48 | SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity); | ||
| 49 | } | ||
| 50 | |||
| 51 | if (mouse_panning_timout++ > 20) { | ||
| 52 | StopPanning(); | ||
| 53 | } | ||
| 54 | std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) { | ||
| 59 | // If native mouse is enabled just set the screen coordinates | ||
| 60 | if (Settings::values.mouse_enabled) { | ||
| 61 | SetAxis(identifier, mouse_axis_x, touch_x); | ||
| 62 | SetAxis(identifier, mouse_axis_y, touch_y); | ||
| 63 | return; | ||
| 64 | } | ||
| 65 | |||
| 66 | SetAxis(identifier, touch_axis_x, touch_x); | ||
| 67 | SetAxis(identifier, touch_axis_y, touch_y); | ||
| 68 | |||
| 69 | if (Settings::values.mouse_panning) { | ||
| 70 | auto mouse_change = | ||
| 71 | (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>(); | ||
| 72 | mouse_panning_timout = 0; | ||
| 73 | |||
| 74 | const auto move_distance = mouse_change.Length(); | ||
| 75 | if (move_distance == 0) { | ||
| 76 | return; | ||
| 77 | } | ||
| 78 | |||
| 79 | // Make slow movements at least 3 units on lenght | ||
| 80 | if (move_distance < 3.0f) { | ||
| 81 | // Normalize value | ||
| 82 | mouse_change /= move_distance; | ||
| 83 | mouse_change *= 3.0f; | ||
| 84 | } | ||
| 85 | |||
| 86 | // Average mouse movements | ||
| 87 | last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f); | ||
| 88 | |||
| 89 | const auto last_move_distance = last_mouse_change.Length(); | ||
| 90 | |||
| 91 | // Make fast movements clamp to 8 units on lenght | ||
| 92 | if (last_move_distance > 8.0f) { | ||
| 93 | // Normalize value | ||
| 94 | last_mouse_change /= last_move_distance; | ||
| 95 | last_mouse_change *= 8.0f; | ||
| 96 | } | ||
| 97 | |||
| 98 | // Ignore average if it's less than 1 unit and use current movement value | ||
| 99 | if (last_move_distance < 1.0f) { | ||
| 100 | last_mouse_change = mouse_change / mouse_change.Length(); | ||
| 101 | } | ||
| 102 | |||
| 103 | return; | ||
| 104 | } | ||
| 105 | |||
| 106 | if (button_pressed) { | ||
| 107 | const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin; | ||
| 108 | const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f; | ||
| 109 | SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity); | ||
| 110 | SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) { | ||
| 115 | SetAxis(identifier, touch_axis_x, touch_x); | ||
| 116 | SetAxis(identifier, touch_axis_y, touch_y); | ||
| 117 | SetButton(identifier, static_cast<int>(button), true); | ||
| 118 | // Set initial analog parameters | ||
| 119 | mouse_origin = {x, y}; | ||
| 120 | last_mouse_position = {x, y}; | ||
| 121 | button_pressed = true; | ||
| 122 | } | ||
| 123 | |||
| 124 | void Mouse::ReleaseButton(MouseButton button) { | ||
| 125 | SetButton(identifier, static_cast<int>(button), false); | ||
| 126 | |||
| 127 | if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) { | ||
| 128 | SetAxis(identifier, mouse_axis_x, 0); | ||
| 129 | SetAxis(identifier, mouse_axis_y, 0); | ||
| 130 | } | ||
| 131 | button_pressed = false; | ||
| 132 | } | ||
| 133 | |||
| 134 | void Mouse::MouseWheelChange(int x, int y) { | ||
| 135 | wheel_position.x += x; | ||
| 136 | wheel_position.y += y; | ||
| 137 | SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position.x)); | ||
| 138 | SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position.y)); | ||
| 139 | } | ||
| 140 | |||
| 141 | void Mouse::ReleaseAllButtons() { | ||
| 142 | ResetButtonState(); | ||
| 143 | button_pressed = false; | ||
| 144 | } | ||
| 145 | |||
| 146 | void Mouse::StopPanning() { | ||
| 147 | last_mouse_change = {}; | ||
| 148 | } | ||
| 149 | |||
| 150 | std::vector<Common::ParamPackage> Mouse::GetInputDevices() const { | ||
| 151 | std::vector<Common::ParamPackage> devices; | ||
| 152 | devices.emplace_back(Common::ParamPackage{ | ||
| 153 | {"engine", GetEngineName()}, | ||
| 154 | {"display", "Keyboard/Mouse"}, | ||
| 155 | }); | ||
| 156 | return devices; | ||
| 157 | } | ||
| 158 | |||
| 159 | AnalogMapping Mouse::GetAnalogMappingForDevice( | ||
| 160 | [[maybe_unused]] const Common::ParamPackage& params) { | ||
| 161 | // Only overwrite different buttons from default | ||
| 162 | AnalogMapping mapping = {}; | ||
| 163 | Common::ParamPackage right_analog_params; | ||
| 164 | right_analog_params.Set("engine", GetEngineName()); | ||
| 165 | right_analog_params.Set("axis_x", 0); | ||
| 166 | right_analog_params.Set("axis_y", 1); | ||
| 167 | right_analog_params.Set("threshold", 0.5f); | ||
| 168 | right_analog_params.Set("range", 1.0f); | ||
| 169 | right_analog_params.Set("deadzone", 0.0f); | ||
| 170 | mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); | ||
| 171 | return mapping; | ||
| 172 | } | ||
| 173 | |||
| 174 | Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) const { | ||
| 175 | if (params.Has("button")) { | ||
| 176 | return Common::Input::ButtonNames::Value; | ||
| 177 | } | ||
| 178 | if (params.Has("axis")) { | ||
| 179 | return Common::Input::ButtonNames::Value; | ||
| 180 | } | ||
| 181 | |||
| 182 | return Common::Input::ButtonNames::Invalid; | ||
| 183 | } | ||
| 184 | |||
| 185 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h new file mode 100644 index 000000000..040446178 --- /dev/null +++ b/src/input_common/drivers/mouse.h | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <stop_token> | ||
| 8 | #include <thread> | ||
| 9 | |||
| 10 | #include "common/vector_math.h" | ||
| 11 | #include "input_common/input_engine.h" | ||
| 12 | |||
| 13 | namespace InputCommon { | ||
| 14 | |||
| 15 | enum class MouseButton { | ||
| 16 | Left, | ||
| 17 | Right, | ||
| 18 | Wheel, | ||
| 19 | Backward, | ||
| 20 | Forward, | ||
| 21 | Task, | ||
| 22 | Extra, | ||
| 23 | Undefined, | ||
| 24 | }; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * A button device factory representing a keyboard. It receives keyboard events and forward them | ||
| 28 | * to all button devices it created. | ||
| 29 | */ | ||
| 30 | class Mouse final : public InputEngine { | ||
| 31 | public: | ||
| 32 | explicit Mouse(std::string input_engine_); | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Signals that mouse has moved. | ||
| 36 | * @param x the x-coordinate of the cursor | ||
| 37 | * @param y the y-coordinate of the cursor | ||
| 38 | * @param center_x the x-coordinate of the middle of the screen | ||
| 39 | * @param center_y the y-coordinate of the middle of the screen | ||
| 40 | */ | ||
| 41 | void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Sets the status of all buttons bound with the key to pressed | ||
| 45 | * @param key_code the code of the key to press | ||
| 46 | */ | ||
| 47 | void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button); | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Sets the status of all buttons bound with the key to released | ||
| 51 | * @param key_code the code of the key to release | ||
| 52 | */ | ||
| 53 | void ReleaseButton(MouseButton button); | ||
| 54 | |||
| 55 | /** | ||
| 56 | * Sets the status of the mouse wheel | ||
| 57 | * @param x delta movement in the x direction | ||
| 58 | * @param y delta movement in the y direction | ||
| 59 | */ | ||
| 60 | void MouseWheelChange(int x, int y); | ||
| 61 | |||
| 62 | void ReleaseAllButtons(); | ||
| 63 | |||
| 64 | std::vector<Common::ParamPackage> GetInputDevices() const override; | ||
| 65 | AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; | ||
| 66 | Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; | ||
| 67 | |||
| 68 | private: | ||
| 69 | void UpdateThread(std::stop_token stop_token); | ||
| 70 | void StopPanning(); | ||
| 71 | |||
| 72 | Common::Vec2<int> mouse_origin; | ||
| 73 | Common::Vec2<int> last_mouse_position; | ||
| 74 | Common::Vec2<float> last_mouse_change; | ||
| 75 | Common::Vec2<int> wheel_position; | ||
| 76 | bool button_pressed; | ||
| 77 | int mouse_panning_timout{}; | ||
| 78 | std::jthread update_thread; | ||
| 79 | }; | ||
| 80 | |||
| 81 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp new file mode 100644 index 000000000..0cda9df62 --- /dev/null +++ b/src/input_common/drivers/sdl_driver.cpp | |||
| @@ -0,0 +1,926 @@ | |||
| 1 | // Copyright 2018 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/logging/log.h" | ||
| 6 | #include "common/math_util.h" | ||
| 7 | #include "common/param_package.h" | ||
| 8 | #include "common/settings.h" | ||
| 9 | #include "common/thread.h" | ||
| 10 | #include "common/vector_math.h" | ||
| 11 | #include "input_common/drivers/sdl_driver.h" | ||
| 12 | |||
| 13 | namespace InputCommon { | ||
| 14 | |||
| 15 | namespace { | ||
| 16 | std::string GetGUID(SDL_Joystick* joystick) { | ||
| 17 | const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); | ||
| 18 | char guid_str[33]; | ||
| 19 | SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); | ||
| 20 | return guid_str; | ||
| 21 | } | ||
| 22 | } // Anonymous namespace | ||
| 23 | |||
| 24 | static int SDLEventWatcher(void* user_data, SDL_Event* event) { | ||
| 25 | auto* const sdl_state = static_cast<SDLDriver*>(user_data); | ||
| 26 | |||
| 27 | sdl_state->HandleGameControllerEvent(*event); | ||
| 28 | |||
| 29 | return 0; | ||
| 30 | } | ||
| 31 | |||
| 32 | class SDLJoystick { | ||
| 33 | public: | ||
| 34 | SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, | ||
| 35 | SDL_GameController* game_controller) | ||
| 36 | : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, | ||
| 37 | sdl_controller{game_controller, &SDL_GameControllerClose} { | ||
| 38 | EnableMotion(); | ||
| 39 | } | ||
| 40 | |||
| 41 | void EnableMotion() { | ||
| 42 | if (sdl_controller) { | ||
| 43 | SDL_GameController* controller = sdl_controller.get(); | ||
| 44 | if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) { | ||
| 45 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); | ||
| 46 | has_accel = true; | ||
| 47 | } | ||
| 48 | if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) { | ||
| 49 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); | ||
| 50 | has_gyro = true; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | bool HasGyro() const { | ||
| 56 | return has_gyro; | ||
| 57 | } | ||
| 58 | |||
| 59 | bool HasAccel() const { | ||
| 60 | return has_accel; | ||
| 61 | } | ||
| 62 | |||
| 63 | bool UpdateMotion(SDL_ControllerSensorEvent event) { | ||
| 64 | constexpr float gravity_constant = 9.80665f; | ||
| 65 | std::lock_guard lock{mutex}; | ||
| 66 | const u64 time_difference = event.timestamp - last_motion_update; | ||
| 67 | last_motion_update = event.timestamp; | ||
| 68 | switch (event.sensor) { | ||
| 69 | case SDL_SENSOR_ACCEL: { | ||
| 70 | motion.accel_x = -event.data[0] / gravity_constant; | ||
| 71 | motion.accel_y = event.data[2] / gravity_constant; | ||
| 72 | motion.accel_z = -event.data[1] / gravity_constant; | ||
| 73 | break; | ||
| 74 | } | ||
| 75 | case SDL_SENSOR_GYRO: { | ||
| 76 | motion.gyro_x = event.data[0] / (Common::PI * 2); | ||
| 77 | motion.gyro_y = -event.data[2] / (Common::PI * 2); | ||
| 78 | motion.gyro_z = event.data[1] / (Common::PI * 2); | ||
| 79 | break; | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | // Ignore duplicated timestamps | ||
| 84 | if (time_difference == 0) { | ||
| 85 | return false; | ||
| 86 | } | ||
| 87 | motion.delta_timestamp = time_difference * 1000; | ||
| 88 | return true; | ||
| 89 | } | ||
| 90 | |||
| 91 | const BasicMotion& GetMotion() const { | ||
| 92 | return motion; | ||
| 93 | } | ||
| 94 | |||
| 95 | bool RumblePlay(const Common::Input::VibrationStatus vibration) { | ||
| 96 | constexpr u32 rumble_max_duration_ms = 1000; | ||
| 97 | if (sdl_controller) { | ||
| 98 | return SDL_GameControllerRumble( | ||
| 99 | sdl_controller.get(), static_cast<u16>(vibration.low_amplitude), | ||
| 100 | static_cast<u16>(vibration.high_amplitude), rumble_max_duration_ms) != -1; | ||
| 101 | } else if (sdl_joystick) { | ||
| 102 | return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(vibration.low_amplitude), | ||
| 103 | static_cast<u16>(vibration.high_amplitude), | ||
| 104 | rumble_max_duration_ms) != -1; | ||
| 105 | } | ||
| 106 | |||
| 107 | return false; | ||
| 108 | } | ||
| 109 | |||
| 110 | bool HasHDRumble() const { | ||
| 111 | if (sdl_controller) { | ||
| 112 | return (SDL_GameControllerGetType(sdl_controller.get()) == | ||
| 113 | SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO); | ||
| 114 | } | ||
| 115 | return false; | ||
| 116 | } | ||
| 117 | /** | ||
| 118 | * The Pad identifier of the joystick | ||
| 119 | */ | ||
| 120 | const PadIdentifier GetPadIdentifier() const { | ||
| 121 | return { | ||
| 122 | .guid = Common::UUID{guid}, | ||
| 123 | .port = static_cast<std::size_t>(port), | ||
| 124 | .pad = 0, | ||
| 125 | }; | ||
| 126 | } | ||
| 127 | |||
| 128 | /** | ||
| 129 | * The guid of the joystick | ||
| 130 | */ | ||
| 131 | const std::string& GetGUID() const { | ||
| 132 | return guid; | ||
| 133 | } | ||
| 134 | |||
| 135 | /** | ||
| 136 | * The number of joystick from the same type that were connected before this joystick | ||
| 137 | */ | ||
| 138 | int GetPort() const { | ||
| 139 | return port; | ||
| 140 | } | ||
| 141 | |||
| 142 | SDL_Joystick* GetSDLJoystick() const { | ||
| 143 | return sdl_joystick.get(); | ||
| 144 | } | ||
| 145 | |||
| 146 | SDL_GameController* GetSDLGameController() const { | ||
| 147 | return sdl_controller.get(); | ||
| 148 | } | ||
| 149 | |||
| 150 | void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { | ||
| 151 | sdl_joystick.reset(joystick); | ||
| 152 | sdl_controller.reset(controller); | ||
| 153 | } | ||
| 154 | |||
| 155 | bool IsJoyconLeft() const { | ||
| 156 | const std::string controller_name = GetControllerName(); | ||
| 157 | if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) { | ||
| 158 | return true; | ||
| 159 | } | ||
| 160 | if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) { | ||
| 161 | return true; | ||
| 162 | } | ||
| 163 | return false; | ||
| 164 | } | ||
| 165 | |||
| 166 | bool IsJoyconRight() const { | ||
| 167 | const std::string controller_name = GetControllerName(); | ||
| 168 | if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) { | ||
| 169 | return true; | ||
| 170 | } | ||
| 171 | if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) { | ||
| 172 | return true; | ||
| 173 | } | ||
| 174 | return false; | ||
| 175 | } | ||
| 176 | |||
| 177 | BatteryLevel GetBatteryLevel() { | ||
| 178 | const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get()); | ||
| 179 | switch (level) { | ||
| 180 | case SDL_JOYSTICK_POWER_EMPTY: | ||
| 181 | return BatteryLevel::Empty; | ||
| 182 | case SDL_JOYSTICK_POWER_LOW: | ||
| 183 | return BatteryLevel::Critical; | ||
| 184 | case SDL_JOYSTICK_POWER_MEDIUM: | ||
| 185 | return BatteryLevel::Low; | ||
| 186 | case SDL_JOYSTICK_POWER_FULL: | ||
| 187 | return BatteryLevel::Medium; | ||
| 188 | case SDL_JOYSTICK_POWER_MAX: | ||
| 189 | return BatteryLevel::Full; | ||
| 190 | case SDL_JOYSTICK_POWER_UNKNOWN: | ||
| 191 | case SDL_JOYSTICK_POWER_WIRED: | ||
| 192 | default: | ||
| 193 | return BatteryLevel::Charging; | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | std::string GetControllerName() const { | ||
| 198 | if (sdl_controller) { | ||
| 199 | switch (SDL_GameControllerGetType(sdl_controller.get())) { | ||
| 200 | case SDL_CONTROLLER_TYPE_XBOX360: | ||
| 201 | return "XBox 360 Controller"; | ||
| 202 | case SDL_CONTROLLER_TYPE_XBOXONE: | ||
| 203 | return "XBox One Controller"; | ||
| 204 | default: | ||
| 205 | break; | ||
| 206 | } | ||
| 207 | const auto name = SDL_GameControllerName(sdl_controller.get()); | ||
| 208 | if (name) { | ||
| 209 | return name; | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | if (sdl_joystick) { | ||
| 214 | const auto name = SDL_JoystickName(sdl_joystick.get()); | ||
| 215 | if (name) { | ||
| 216 | return name; | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | return "Unknown"; | ||
| 221 | } | ||
| 222 | |||
| 223 | private: | ||
| 224 | std::string guid; | ||
| 225 | int port; | ||
| 226 | std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; | ||
| 227 | std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; | ||
| 228 | mutable std::mutex mutex; | ||
| 229 | |||
| 230 | u64 last_motion_update{}; | ||
| 231 | bool has_gyro{false}; | ||
| 232 | bool has_accel{false}; | ||
| 233 | BasicMotion motion; | ||
| 234 | }; | ||
| 235 | |||
| 236 | std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) { | ||
| 237 | std::lock_guard lock{joystick_map_mutex}; | ||
| 238 | const auto it = joystick_map.find(guid); | ||
| 239 | |||
| 240 | if (it != joystick_map.end()) { | ||
| 241 | while (it->second.size() <= static_cast<std::size_t>(port)) { | ||
| 242 | auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), | ||
| 243 | nullptr, nullptr); | ||
| 244 | it->second.emplace_back(std::move(joystick)); | ||
| 245 | } | ||
| 246 | |||
| 247 | return it->second[static_cast<std::size_t>(port)]; | ||
| 248 | } | ||
| 249 | |||
| 250 | auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr); | ||
| 251 | |||
| 252 | return joystick_map[guid].emplace_back(std::move(joystick)); | ||
| 253 | } | ||
| 254 | |||
| 255 | std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { | ||
| 256 | auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); | ||
| 257 | const std::string guid = GetGUID(sdl_joystick); | ||
| 258 | |||
| 259 | std::lock_guard lock{joystick_map_mutex}; | ||
| 260 | const auto map_it = joystick_map.find(guid); | ||
| 261 | |||
| 262 | if (map_it == joystick_map.end()) { | ||
| 263 | return nullptr; | ||
| 264 | } | ||
| 265 | |||
| 266 | const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), | ||
| 267 | [&sdl_joystick](const auto& joystick) { | ||
| 268 | return joystick->GetSDLJoystick() == sdl_joystick; | ||
| 269 | }); | ||
| 270 | |||
| 271 | if (vec_it == map_it->second.end()) { | ||
| 272 | return nullptr; | ||
| 273 | } | ||
| 274 | |||
| 275 | return *vec_it; | ||
| 276 | } | ||
| 277 | |||
| 278 | void SDLDriver::InitJoystick(int joystick_index) { | ||
| 279 | SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); | ||
| 280 | SDL_GameController* sdl_gamecontroller = nullptr; | ||
| 281 | |||
| 282 | if (SDL_IsGameController(joystick_index)) { | ||
| 283 | sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); | ||
| 284 | } | ||
| 285 | |||
| 286 | if (!sdl_joystick) { | ||
| 287 | LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); | ||
| 288 | return; | ||
| 289 | } | ||
| 290 | |||
| 291 | const std::string guid = GetGUID(sdl_joystick); | ||
| 292 | |||
| 293 | std::lock_guard lock{joystick_map_mutex}; | ||
| 294 | if (joystick_map.find(guid) == joystick_map.end()) { | ||
| 295 | auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); | ||
| 296 | PreSetController(joystick->GetPadIdentifier()); | ||
| 297 | SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); | ||
| 298 | joystick_map[guid].emplace_back(std::move(joystick)); | ||
| 299 | return; | ||
| 300 | } | ||
| 301 | |||
| 302 | auto& joystick_guid_list = joystick_map[guid]; | ||
| 303 | const auto joystick_it = | ||
| 304 | std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), | ||
| 305 | [](const auto& joystick) { return !joystick->GetSDLJoystick(); }); | ||
| 306 | |||
| 307 | if (joystick_it != joystick_guid_list.end()) { | ||
| 308 | (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); | ||
| 309 | return; | ||
| 310 | } | ||
| 311 | |||
| 312 | const int port = static_cast<int>(joystick_guid_list.size()); | ||
| 313 | auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); | ||
| 314 | PreSetController(joystick->GetPadIdentifier()); | ||
| 315 | SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); | ||
| 316 | joystick_guid_list.emplace_back(std::move(joystick)); | ||
| 317 | } | ||
| 318 | |||
| 319 | void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) { | ||
| 320 | const std::string guid = GetGUID(sdl_joystick); | ||
| 321 | |||
| 322 | std::lock_guard lock{joystick_map_mutex}; | ||
| 323 | // This call to guid is safe since the joystick is guaranteed to be in the map | ||
| 324 | const auto& joystick_guid_list = joystick_map[guid]; | ||
| 325 | const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), | ||
| 326 | [&sdl_joystick](const auto& joystick) { | ||
| 327 | return joystick->GetSDLJoystick() == sdl_joystick; | ||
| 328 | }); | ||
| 329 | |||
| 330 | if (joystick_it != joystick_guid_list.end()) { | ||
| 331 | (*joystick_it)->SetSDLJoystick(nullptr, nullptr); | ||
| 332 | } | ||
| 333 | } | ||
| 334 | |||
| 335 | void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { | ||
| 336 | switch (event.type) { | ||
| 337 | case SDL_JOYBUTTONUP: { | ||
| 338 | if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { | ||
| 339 | const PadIdentifier identifier = joystick->GetPadIdentifier(); | ||
| 340 | SetButton(identifier, event.jbutton.button, false); | ||
| 341 | } | ||
| 342 | break; | ||
| 343 | } | ||
| 344 | case SDL_JOYBUTTONDOWN: { | ||
| 345 | if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { | ||
| 346 | const PadIdentifier identifier = joystick->GetPadIdentifier(); | ||
| 347 | SetButton(identifier, event.jbutton.button, true); | ||
| 348 | } | ||
| 349 | break; | ||
| 350 | } | ||
| 351 | case SDL_JOYHATMOTION: { | ||
| 352 | if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { | ||
| 353 | const PadIdentifier identifier = joystick->GetPadIdentifier(); | ||
| 354 | SetHatButton(identifier, event.jhat.hat, event.jhat.value); | ||
| 355 | } | ||
| 356 | break; | ||
| 357 | } | ||
| 358 | case SDL_JOYAXISMOTION: { | ||
| 359 | if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { | ||
| 360 | const PadIdentifier identifier = joystick->GetPadIdentifier(); | ||
| 361 | SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f); | ||
| 362 | } | ||
| 363 | break; | ||
| 364 | } | ||
| 365 | case SDL_CONTROLLERSENSORUPDATE: { | ||
| 366 | if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { | ||
| 367 | if (joystick->UpdateMotion(event.csensor)) { | ||
| 368 | const PadIdentifier identifier = joystick->GetPadIdentifier(); | ||
| 369 | SetMotion(identifier, 0, joystick->GetMotion()); | ||
| 370 | } | ||
| 371 | } | ||
| 372 | break; | ||
| 373 | } | ||
| 374 | case SDL_JOYDEVICEREMOVED: | ||
| 375 | LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); | ||
| 376 | CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); | ||
| 377 | break; | ||
| 378 | case SDL_JOYDEVICEADDED: | ||
| 379 | LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); | ||
| 380 | InitJoystick(event.jdevice.which); | ||
| 381 | break; | ||
| 382 | } | ||
| 383 | } | ||
| 384 | |||
| 385 | void SDLDriver::CloseJoysticks() { | ||
| 386 | std::lock_guard lock{joystick_map_mutex}; | ||
| 387 | joystick_map.clear(); | ||
| 388 | } | ||
| 389 | |||
| 390 | SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) { | ||
| 391 | if (!Settings::values.enable_raw_input) { | ||
| 392 | // Disable raw input. When enabled this setting causes SDL to die when a web applet opens | ||
| 393 | SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); | ||
| 394 | } | ||
| 395 | |||
| 396 | // Prevent SDL from adding undesired axis | ||
| 397 | SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); | ||
| 398 | |||
| 399 | // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers | ||
| 400 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); | ||
| 401 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); | ||
| 402 | SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); | ||
| 403 | |||
| 404 | // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and | ||
| 405 | // not a generic one | ||
| 406 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); | ||
| 407 | |||
| 408 | // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native | ||
| 409 | // driver on Linux. | ||
| 410 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0"); | ||
| 411 | |||
| 412 | // If the frontend is going to manage the event loop, then we don't start one here | ||
| 413 | start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0; | ||
| 414 | if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { | ||
| 415 | LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError()); | ||
| 416 | return; | ||
| 417 | } | ||
| 418 | |||
| 419 | SDL_AddEventWatch(&SDLEventWatcher, this); | ||
| 420 | |||
| 421 | initialized = true; | ||
| 422 | if (start_thread) { | ||
| 423 | poll_thread = std::thread([this] { | ||
| 424 | Common::SetCurrentThreadName("yuzu:input:SDL"); | ||
| 425 | using namespace std::chrono_literals; | ||
| 426 | while (initialized) { | ||
| 427 | SDL_PumpEvents(); | ||
| 428 | std::this_thread::sleep_for(1ms); | ||
| 429 | } | ||
| 430 | }); | ||
| 431 | } | ||
| 432 | // Because the events for joystick connection happens before we have our event watcher added, we | ||
| 433 | // can just open all the joysticks right here | ||
| 434 | for (int i = 0; i < SDL_NumJoysticks(); ++i) { | ||
| 435 | InitJoystick(i); | ||
| 436 | } | ||
| 437 | } | ||
| 438 | |||
| 439 | SDLDriver::~SDLDriver() { | ||
| 440 | CloseJoysticks(); | ||
| 441 | SDL_DelEventWatch(&SDLEventWatcher, this); | ||
| 442 | |||
| 443 | initialized = false; | ||
| 444 | if (start_thread) { | ||
| 445 | poll_thread.join(); | ||
| 446 | SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); | ||
| 447 | } | ||
| 448 | } | ||
| 449 | |||
| 450 | std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const { | ||
| 451 | std::vector<Common::ParamPackage> devices; | ||
| 452 | std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs; | ||
| 453 | for (const auto& [key, value] : joystick_map) { | ||
| 454 | for (const auto& joystick : value) { | ||
| 455 | if (!joystick->GetSDLJoystick()) { | ||
| 456 | continue; | ||
| 457 | } | ||
| 458 | const std::string name = | ||
| 459 | fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort()); | ||
| 460 | devices.emplace_back(Common::ParamPackage{ | ||
| 461 | {"engine", GetEngineName()}, | ||
| 462 | {"display", std::move(name)}, | ||
| 463 | {"guid", joystick->GetGUID()}, | ||
| 464 | {"port", std::to_string(joystick->GetPort())}, | ||
| 465 | }); | ||
| 466 | if (joystick->IsJoyconLeft()) { | ||
| 467 | joycon_pairs.insert_or_assign(joystick->GetPort(), joystick); | ||
| 468 | } | ||
| 469 | } | ||
| 470 | } | ||
| 471 | |||
| 472 | // Add dual controllers | ||
| 473 | for (const auto& [key, value] : joystick_map) { | ||
| 474 | for (const auto& joystick : value) { | ||
| 475 | if (joystick->IsJoyconRight()) { | ||
| 476 | if (!joycon_pairs.contains(joystick->GetPort())) { | ||
| 477 | continue; | ||
| 478 | } | ||
| 479 | const auto joystick2 = joycon_pairs.at(joystick->GetPort()); | ||
| 480 | |||
| 481 | const std::string name = | ||
| 482 | fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort()); | ||
| 483 | devices.emplace_back(Common::ParamPackage{ | ||
| 484 | {"engine", GetEngineName()}, | ||
| 485 | {"display", std::move(name)}, | ||
| 486 | {"guid", joystick->GetGUID()}, | ||
| 487 | {"guid2", joystick2->GetGUID()}, | ||
| 488 | {"port", std::to_string(joystick->GetPort())}, | ||
| 489 | }); | ||
| 490 | } | ||
| 491 | } | ||
| 492 | } | ||
| 493 | return devices; | ||
| 494 | } | ||
| 495 | |||
| 496 | Common::Input::VibrationError SDLDriver::SetRumble( | ||
| 497 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { | ||
| 498 | const auto joystick = | ||
| 499 | GetSDLJoystickByGUID(identifier.guid.Format(), static_cast<int>(identifier.port)); | ||
| 500 | const auto process_amplitude_exp = [](f32 amplitude, f32 factor) { | ||
| 501 | return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF; | ||
| 502 | }; | ||
| 503 | |||
| 504 | // Default exponential curve for rumble | ||
| 505 | f32 factor = 0.35f; | ||
| 506 | |||
| 507 | // If vibration is set as a linear output use a flatter value | ||
| 508 | if (vibration.type == Common::Input::VibrationAmplificationType::Linear) { | ||
| 509 | factor = 0.5f; | ||
| 510 | } | ||
| 511 | |||
| 512 | // Amplitude for HD rumble needs no modification | ||
| 513 | if (joystick->HasHDRumble()) { | ||
| 514 | factor = 1.0f; | ||
| 515 | } | ||
| 516 | |||
| 517 | const Common::Input::VibrationStatus new_vibration{ | ||
| 518 | .low_amplitude = process_amplitude_exp(vibration.low_amplitude, factor), | ||
| 519 | .low_frequency = vibration.low_frequency, | ||
| 520 | .high_amplitude = process_amplitude_exp(vibration.high_amplitude, factor), | ||
| 521 | .high_frequency = vibration.high_frequency, | ||
| 522 | .type = Common::Input::VibrationAmplificationType::Exponential, | ||
| 523 | }; | ||
| 524 | |||
| 525 | if (!joystick->RumblePlay(new_vibration)) { | ||
| 526 | return Common::Input::VibrationError::Unknown; | ||
| 527 | } | ||
| 528 | |||
| 529 | return Common::Input::VibrationError::None; | ||
| 530 | } | ||
| 531 | |||
| 532 | Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid, | ||
| 533 | s32 axis, float value) const { | ||
| 534 | Common::ParamPackage params{}; | ||
| 535 | params.Set("engine", GetEngineName()); | ||
| 536 | params.Set("port", port); | ||
| 537 | params.Set("guid", std::move(guid)); | ||
| 538 | params.Set("axis", axis); | ||
| 539 | params.Set("threshold", "0.5"); | ||
| 540 | params.Set("invert", value < 0 ? "-" : "+"); | ||
| 541 | return params; | ||
| 542 | } | ||
| 543 | |||
| 544 | Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid, | ||
| 545 | s32 button) const { | ||
| 546 | Common::ParamPackage params{}; | ||
| 547 | params.Set("engine", GetEngineName()); | ||
| 548 | params.Set("port", port); | ||
| 549 | params.Set("guid", std::move(guid)); | ||
| 550 | params.Set("button", button); | ||
| 551 | return params; | ||
| 552 | } | ||
| 553 | |||
| 554 | Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat, | ||
| 555 | u8 value) const { | ||
| 556 | Common::ParamPackage params{}; | ||
| 557 | params.Set("engine", GetEngineName()); | ||
| 558 | params.Set("port", port); | ||
| 559 | params.Set("guid", std::move(guid)); | ||
| 560 | params.Set("hat", hat); | ||
| 561 | params.Set("direction", GetHatButtonName(value)); | ||
| 562 | return params; | ||
| 563 | } | ||
| 564 | |||
| 565 | Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const { | ||
| 566 | Common::ParamPackage params{}; | ||
| 567 | params.Set("engine", GetEngineName()); | ||
| 568 | params.Set("motion", 0); | ||
| 569 | params.Set("port", port); | ||
| 570 | params.Set("guid", std::move(guid)); | ||
| 571 | return params; | ||
| 572 | } | ||
| 573 | |||
| 574 | Common::ParamPackage SDLDriver::BuildParamPackageForBinding( | ||
| 575 | int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const { | ||
| 576 | switch (binding.bindType) { | ||
| 577 | case SDL_CONTROLLER_BINDTYPE_NONE: | ||
| 578 | break; | ||
| 579 | case SDL_CONTROLLER_BINDTYPE_AXIS: | ||
| 580 | return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); | ||
| 581 | case SDL_CONTROLLER_BINDTYPE_BUTTON: | ||
| 582 | return BuildButtonParamPackageForButton(port, guid, binding.value.button); | ||
| 583 | case SDL_CONTROLLER_BINDTYPE_HAT: | ||
| 584 | return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, | ||
| 585 | static_cast<u8>(binding.value.hat.hat_mask)); | ||
| 586 | } | ||
| 587 | return {}; | ||
| 588 | } | ||
| 589 | |||
| 590 | Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, | ||
| 591 | int axis_y, float offset_x, | ||
| 592 | float offset_y) const { | ||
| 593 | Common::ParamPackage params; | ||
| 594 | params.Set("engine", GetEngineName()); | ||
| 595 | params.Set("port", static_cast<int>(identifier.port)); | ||
| 596 | params.Set("guid", identifier.guid.Format()); | ||
| 597 | params.Set("axis_x", axis_x); | ||
| 598 | params.Set("axis_y", axis_y); | ||
| 599 | params.Set("offset_x", offset_x); | ||
| 600 | params.Set("offset_y", offset_y); | ||
| 601 | params.Set("invert_x", "+"); | ||
| 602 | params.Set("invert_y", "+"); | ||
| 603 | return params; | ||
| 604 | } | ||
| 605 | |||
| 606 | ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) { | ||
| 607 | if (!params.Has("guid") || !params.Has("port")) { | ||
| 608 | return {}; | ||
| 609 | } | ||
| 610 | const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); | ||
| 611 | |||
| 612 | auto* controller = joystick->GetSDLGameController(); | ||
| 613 | if (controller == nullptr) { | ||
| 614 | return {}; | ||
| 615 | } | ||
| 616 | |||
| 617 | // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. | ||
| 618 | // We will add those afterwards | ||
| 619 | // This list also excludes Screenshot since theres not really a mapping for that | ||
| 620 | ButtonBindings switch_to_sdl_button; | ||
| 621 | |||
| 622 | if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) { | ||
| 623 | switch_to_sdl_button = GetNintendoButtonBinding(joystick); | ||
| 624 | } else { | ||
| 625 | switch_to_sdl_button = GetDefaultButtonBinding(); | ||
| 626 | } | ||
| 627 | |||
| 628 | // Add the missing bindings for ZL/ZR | ||
| 629 | static constexpr ZButtonBindings switch_to_sdl_axis{{ | ||
| 630 | {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, | ||
| 631 | {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, | ||
| 632 | }}; | ||
| 633 | |||
| 634 | // Parameters contain two joysticks return dual | ||
| 635 | if (params.Has("guid2")) { | ||
| 636 | const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); | ||
| 637 | |||
| 638 | if (joystick2->GetSDLGameController() != nullptr) { | ||
| 639 | return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button, | ||
| 640 | switch_to_sdl_axis); | ||
| 641 | } | ||
| 642 | } | ||
| 643 | |||
| 644 | return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis); | ||
| 645 | } | ||
| 646 | |||
| 647 | ButtonBindings SDLDriver::GetDefaultButtonBinding() const { | ||
| 648 | return { | ||
| 649 | std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, | ||
| 650 | {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, | ||
| 651 | {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, | ||
| 652 | {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, | ||
| 653 | {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, | ||
| 654 | {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, | ||
| 655 | {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, | ||
| 656 | {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, | ||
| 657 | {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, | ||
| 658 | {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, | ||
| 659 | {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, | ||
| 660 | {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, | ||
| 661 | {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, | ||
| 662 | {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, | ||
| 663 | {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, | ||
| 664 | {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, | ||
| 665 | {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, | ||
| 666 | }; | ||
| 667 | } | ||
| 668 | |||
| 669 | ButtonBindings SDLDriver::GetNintendoButtonBinding( | ||
| 670 | const std::shared_ptr<SDLJoystick>& joystick) const { | ||
| 671 | // Default SL/SR mapping for pro controllers | ||
| 672 | auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; | ||
| 673 | auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; | ||
| 674 | |||
| 675 | if (joystick->IsJoyconLeft()) { | ||
| 676 | sl_button = SDL_CONTROLLER_BUTTON_PADDLE2; | ||
| 677 | sr_button = SDL_CONTROLLER_BUTTON_PADDLE4; | ||
| 678 | } | ||
| 679 | if (joystick->IsJoyconRight()) { | ||
| 680 | sl_button = SDL_CONTROLLER_BUTTON_PADDLE3; | ||
| 681 | sr_button = SDL_CONTROLLER_BUTTON_PADDLE1; | ||
| 682 | } | ||
| 683 | |||
| 684 | return { | ||
| 685 | std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A}, | ||
| 686 | {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B}, | ||
| 687 | {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X}, | ||
| 688 | {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y}, | ||
| 689 | {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, | ||
| 690 | {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, | ||
| 691 | {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, | ||
| 692 | {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, | ||
| 693 | {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, | ||
| 694 | {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, | ||
| 695 | {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, | ||
| 696 | {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, | ||
| 697 | {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, | ||
| 698 | {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, | ||
| 699 | {Settings::NativeButton::SL, sl_button}, | ||
| 700 | {Settings::NativeButton::SR, sr_button}, | ||
| 701 | {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, | ||
| 702 | }; | ||
| 703 | } | ||
| 704 | |||
| 705 | ButtonMapping SDLDriver::GetSingleControllerMapping( | ||
| 706 | const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button, | ||
| 707 | const ZButtonBindings& switch_to_sdl_axis) const { | ||
| 708 | ButtonMapping mapping; | ||
| 709 | mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); | ||
| 710 | auto* controller = joystick->GetSDLGameController(); | ||
| 711 | |||
| 712 | for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { | ||
| 713 | const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); | ||
| 714 | mapping.insert_or_assign( | ||
| 715 | switch_button, | ||
| 716 | BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); | ||
| 717 | } | ||
| 718 | for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { | ||
| 719 | const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); | ||
| 720 | mapping.insert_or_assign( | ||
| 721 | switch_button, | ||
| 722 | BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); | ||
| 723 | } | ||
| 724 | |||
| 725 | return mapping; | ||
| 726 | } | ||
| 727 | |||
| 728 | ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick, | ||
| 729 | const std::shared_ptr<SDLJoystick>& joystick2, | ||
| 730 | const ButtonBindings& switch_to_sdl_button, | ||
| 731 | const ZButtonBindings& switch_to_sdl_axis) const { | ||
| 732 | ButtonMapping mapping; | ||
| 733 | mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); | ||
| 734 | auto* controller = joystick->GetSDLGameController(); | ||
| 735 | auto* controller2 = joystick2->GetSDLGameController(); | ||
| 736 | |||
| 737 | for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { | ||
| 738 | if (IsButtonOnLeftSide(switch_button)) { | ||
| 739 | const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); | ||
| 740 | mapping.insert_or_assign( | ||
| 741 | switch_button, | ||
| 742 | BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); | ||
| 743 | continue; | ||
| 744 | } | ||
| 745 | const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); | ||
| 746 | mapping.insert_or_assign( | ||
| 747 | switch_button, | ||
| 748 | BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); | ||
| 749 | } | ||
| 750 | for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { | ||
| 751 | if (IsButtonOnLeftSide(switch_button)) { | ||
| 752 | const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); | ||
| 753 | mapping.insert_or_assign( | ||
| 754 | switch_button, | ||
| 755 | BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); | ||
| 756 | continue; | ||
| 757 | } | ||
| 758 | const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); | ||
| 759 | mapping.insert_or_assign( | ||
| 760 | switch_button, | ||
| 761 | BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); | ||
| 762 | } | ||
| 763 | |||
| 764 | return mapping; | ||
| 765 | } | ||
| 766 | |||
| 767 | bool SDLDriver::IsButtonOnLeftSide(Settings::NativeButton::Values button) const { | ||
| 768 | switch (button) { | ||
| 769 | case Settings::NativeButton::DDown: | ||
| 770 | case Settings::NativeButton::DLeft: | ||
| 771 | case Settings::NativeButton::DRight: | ||
| 772 | case Settings::NativeButton::DUp: | ||
| 773 | case Settings::NativeButton::L: | ||
| 774 | case Settings::NativeButton::LStick: | ||
| 775 | case Settings::NativeButton::Minus: | ||
| 776 | case Settings::NativeButton::Screenshot: | ||
| 777 | case Settings::NativeButton::ZL: | ||
| 778 | return true; | ||
| 779 | default: | ||
| 780 | return false; | ||
| 781 | } | ||
| 782 | } | ||
| 783 | |||
| 784 | AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) { | ||
| 785 | if (!params.Has("guid") || !params.Has("port")) { | ||
| 786 | return {}; | ||
| 787 | } | ||
| 788 | const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); | ||
| 789 | const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); | ||
| 790 | auto* controller = joystick->GetSDLGameController(); | ||
| 791 | if (controller == nullptr) { | ||
| 792 | return {}; | ||
| 793 | } | ||
| 794 | |||
| 795 | AnalogMapping mapping = {}; | ||
| 796 | const auto& binding_left_x = | ||
| 797 | SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); | ||
| 798 | const auto& binding_left_y = | ||
| 799 | SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); | ||
| 800 | if (params.Has("guid2")) { | ||
| 801 | const auto identifier = joystick2->GetPadIdentifier(); | ||
| 802 | PreSetController(identifier); | ||
| 803 | PreSetAxis(identifier, binding_left_x.value.axis); | ||
| 804 | PreSetAxis(identifier, binding_left_y.value.axis); | ||
| 805 | const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); | ||
| 806 | const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis); | ||
| 807 | mapping.insert_or_assign(Settings::NativeAnalog::LStick, | ||
| 808 | BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, | ||
| 809 | binding_left_y.value.axis, | ||
| 810 | left_offset_x, left_offset_y)); | ||
| 811 | } else { | ||
| 812 | const auto identifier = joystick->GetPadIdentifier(); | ||
| 813 | PreSetController(identifier); | ||
| 814 | PreSetAxis(identifier, binding_left_x.value.axis); | ||
| 815 | PreSetAxis(identifier, binding_left_y.value.axis); | ||
| 816 | const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); | ||
| 817 | const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis); | ||
| 818 | mapping.insert_or_assign(Settings::NativeAnalog::LStick, | ||
| 819 | BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, | ||
| 820 | binding_left_y.value.axis, | ||
| 821 | left_offset_x, left_offset_y)); | ||
| 822 | } | ||
| 823 | const auto& binding_right_x = | ||
| 824 | SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); | ||
| 825 | const auto& binding_right_y = | ||
| 826 | SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); | ||
| 827 | const auto identifier = joystick->GetPadIdentifier(); | ||
| 828 | PreSetController(identifier); | ||
| 829 | PreSetAxis(identifier, binding_right_x.value.axis); | ||
| 830 | PreSetAxis(identifier, binding_right_y.value.axis); | ||
| 831 | const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis); | ||
| 832 | const auto right_offset_y = -GetAxis(identifier, binding_right_y.value.axis); | ||
| 833 | mapping.insert_or_assign(Settings::NativeAnalog::RStick, | ||
| 834 | BuildParamPackageForAnalog(identifier, binding_right_x.value.axis, | ||
| 835 | binding_right_y.value.axis, right_offset_x, | ||
| 836 | right_offset_y)); | ||
| 837 | return mapping; | ||
| 838 | } | ||
| 839 | |||
| 840 | MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) { | ||
| 841 | if (!params.Has("guid") || !params.Has("port")) { | ||
| 842 | return {}; | ||
| 843 | } | ||
| 844 | const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); | ||
| 845 | const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); | ||
| 846 | auto* controller = joystick->GetSDLGameController(); | ||
| 847 | if (controller == nullptr) { | ||
| 848 | return {}; | ||
| 849 | } | ||
| 850 | |||
| 851 | MotionMapping mapping = {}; | ||
| 852 | joystick->EnableMotion(); | ||
| 853 | |||
| 854 | if (joystick->HasGyro() || joystick->HasAccel()) { | ||
| 855 | mapping.insert_or_assign(Settings::NativeMotion::MotionRight, | ||
| 856 | BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); | ||
| 857 | } | ||
| 858 | if (params.Has("guid2")) { | ||
| 859 | joystick2->EnableMotion(); | ||
| 860 | if (joystick2->HasGyro() || joystick2->HasAccel()) { | ||
| 861 | mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, | ||
| 862 | BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID())); | ||
| 863 | } | ||
| 864 | } else { | ||
| 865 | if (joystick->HasGyro() || joystick->HasAccel()) { | ||
| 866 | mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, | ||
| 867 | BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); | ||
| 868 | } | ||
| 869 | } | ||
| 870 | |||
| 871 | return mapping; | ||
| 872 | } | ||
| 873 | |||
| 874 | Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const { | ||
| 875 | if (params.Has("button")) { | ||
| 876 | // TODO(German77): Find how to substitue the values for real button names | ||
| 877 | return Common::Input::ButtonNames::Value; | ||
| 878 | } | ||
| 879 | if (params.Has("hat")) { | ||
| 880 | return Common::Input::ButtonNames::Value; | ||
| 881 | } | ||
| 882 | if (params.Has("axis")) { | ||
| 883 | return Common::Input::ButtonNames::Value; | ||
| 884 | } | ||
| 885 | if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { | ||
| 886 | return Common::Input::ButtonNames::Value; | ||
| 887 | } | ||
| 888 | if (params.Has("motion")) { | ||
| 889 | return Common::Input::ButtonNames::Engine; | ||
| 890 | } | ||
| 891 | |||
| 892 | return Common::Input::ButtonNames::Invalid; | ||
| 893 | } | ||
| 894 | |||
| 895 | std::string SDLDriver::GetHatButtonName(u8 direction_value) const { | ||
| 896 | switch (direction_value) { | ||
| 897 | case SDL_HAT_UP: | ||
| 898 | return "up"; | ||
| 899 | case SDL_HAT_DOWN: | ||
| 900 | return "down"; | ||
| 901 | case SDL_HAT_LEFT: | ||
| 902 | return "left"; | ||
| 903 | case SDL_HAT_RIGHT: | ||
| 904 | return "right"; | ||
| 905 | default: | ||
| 906 | return {}; | ||
| 907 | } | ||
| 908 | } | ||
| 909 | |||
| 910 | u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const { | ||
| 911 | Uint8 direction; | ||
| 912 | if (direction_name == "up") { | ||
| 913 | direction = SDL_HAT_UP; | ||
| 914 | } else if (direction_name == "down") { | ||
| 915 | direction = SDL_HAT_DOWN; | ||
| 916 | } else if (direction_name == "left") { | ||
| 917 | direction = SDL_HAT_LEFT; | ||
| 918 | } else if (direction_name == "right") { | ||
| 919 | direction = SDL_HAT_RIGHT; | ||
| 920 | } else { | ||
| 921 | direction = 0; | ||
| 922 | } | ||
| 923 | return direction; | ||
| 924 | } | ||
| 925 | |||
| 926 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h new file mode 100644 index 000000000..e9a5d2e26 --- /dev/null +++ b/src/input_common/drivers/sdl_driver.h | |||
| @@ -0,0 +1,117 @@ | |||
| 1 | // Copyright 2018 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <atomic> | ||
| 8 | #include <mutex> | ||
| 9 | #include <thread> | ||
| 10 | #include <unordered_map> | ||
| 11 | |||
| 12 | #include <SDL.h> | ||
| 13 | |||
| 14 | #include "common/common_types.h" | ||
| 15 | #include "input_common/input_engine.h" | ||
| 16 | |||
| 17 | union SDL_Event; | ||
| 18 | using SDL_GameController = struct _SDL_GameController; | ||
| 19 | using SDL_Joystick = struct _SDL_Joystick; | ||
| 20 | using SDL_JoystickID = s32; | ||
| 21 | |||
| 22 | namespace InputCommon { | ||
| 23 | |||
| 24 | class SDLJoystick; | ||
| 25 | |||
| 26 | using ButtonBindings = | ||
| 27 | std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>; | ||
| 28 | using ZButtonBindings = | ||
| 29 | std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; | ||
| 30 | |||
| 31 | class SDLDriver : public InputEngine { | ||
| 32 | public: | ||
| 33 | /// Initializes and registers SDL device factories | ||
| 34 | explicit SDLDriver(std::string input_engine_); | ||
| 35 | |||
| 36 | /// Unregisters SDL device factories and shut them down. | ||
| 37 | ~SDLDriver() override; | ||
| 38 | |||
| 39 | /// Handle SDL_Events for joysticks from SDL_PollEvent | ||
| 40 | void HandleGameControllerEvent(const SDL_Event& event); | ||
| 41 | |||
| 42 | /// Get the nth joystick with the corresponding GUID | ||
| 43 | std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so | ||
| 47 | * tie it to a SDLJoystick with the same guid and that port | ||
| 48 | */ | ||
| 49 | std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port); | ||
| 50 | |||
| 51 | std::vector<Common::ParamPackage> GetInputDevices() const override; | ||
| 52 | |||
| 53 | ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; | ||
| 54 | AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; | ||
| 55 | MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; | ||
| 56 | Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; | ||
| 57 | |||
| 58 | std::string GetHatButtonName(u8 direction_value) const override; | ||
| 59 | u8 GetHatButtonId(const std::string& direction_name) const override; | ||
| 60 | |||
| 61 | Common::Input::VibrationError SetRumble( | ||
| 62 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; | ||
| 63 | |||
| 64 | private: | ||
| 65 | void InitJoystick(int joystick_index); | ||
| 66 | void CloseJoystick(SDL_Joystick* sdl_joystick); | ||
| 67 | |||
| 68 | /// Needs to be called before SDL_QuitSubSystem. | ||
| 69 | void CloseJoysticks(); | ||
| 70 | |||
| 71 | Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis, | ||
| 72 | float value = 0.1f) const; | ||
| 73 | Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, | ||
| 74 | s32 button) const; | ||
| 75 | |||
| 76 | Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, | ||
| 77 | u8 value) const; | ||
| 78 | |||
| 79 | Common::ParamPackage BuildMotionParam(int port, std::string guid) const; | ||
| 80 | |||
| 81 | Common::ParamPackage BuildParamPackageForBinding( | ||
| 82 | int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const; | ||
| 83 | |||
| 84 | Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, | ||
| 85 | int axis_y, float offset_x, | ||
| 86 | float offset_y) const; | ||
| 87 | |||
| 88 | /// Returns the default button bindings list for generic controllers | ||
| 89 | ButtonBindings GetDefaultButtonBinding() const; | ||
| 90 | |||
| 91 | /// Returns the default button bindings list for nintendo controllers | ||
| 92 | ButtonBindings GetNintendoButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const; | ||
| 93 | |||
| 94 | /// Returns the button mappings from a single controller | ||
| 95 | ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick, | ||
| 96 | const ButtonBindings& switch_to_sdl_button, | ||
| 97 | const ZButtonBindings& switch_to_sdl_axis) const; | ||
| 98 | |||
| 99 | /// Returns the button mappings from two different controllers | ||
| 100 | ButtonMapping GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick, | ||
| 101 | const std::shared_ptr<SDLJoystick>& joystick2, | ||
| 102 | const ButtonBindings& switch_to_sdl_button, | ||
| 103 | const ZButtonBindings& switch_to_sdl_axis) const; | ||
| 104 | |||
| 105 | /// Returns true if the button is on the left joycon | ||
| 106 | bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const; | ||
| 107 | |||
| 108 | /// Map of GUID of a list of corresponding virtual Joysticks | ||
| 109 | std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; | ||
| 110 | std::mutex joystick_map_mutex; | ||
| 111 | |||
| 112 | bool start_thread = false; | ||
| 113 | std::atomic<bool> initialized = false; | ||
| 114 | |||
| 115 | std::thread poll_thread; | ||
| 116 | }; | ||
| 117 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp new file mode 100644 index 000000000..5bdd5dac3 --- /dev/null +++ b/src/input_common/drivers/tas_input.cpp | |||
| @@ -0,0 +1,320 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <cstring> | ||
| 6 | #include <fmt/format.h> | ||
| 7 | |||
| 8 | #include "common/fs/file.h" | ||
| 9 | #include "common/fs/fs_types.h" | ||
| 10 | #include "common/fs/path_util.h" | ||
| 11 | #include "common/logging/log.h" | ||
| 12 | #include "common/settings.h" | ||
| 13 | #include "input_common/drivers/tas_input.h" | ||
| 14 | |||
| 15 | namespace InputCommon::TasInput { | ||
| 16 | |||
| 17 | enum class Tas::TasAxis : u8 { | ||
| 18 | StickX, | ||
| 19 | StickY, | ||
| 20 | SubstickX, | ||
| 21 | SubstickY, | ||
| 22 | Undefined, | ||
| 23 | }; | ||
| 24 | |||
| 25 | // Supported keywords and buttons from a TAS file | ||
| 26 | constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = { | ||
| 27 | std::pair{"KEY_A", TasButton::BUTTON_A}, | ||
| 28 | {"KEY_B", TasButton::BUTTON_B}, | ||
| 29 | {"KEY_X", TasButton::BUTTON_X}, | ||
| 30 | {"KEY_Y", TasButton::BUTTON_Y}, | ||
| 31 | {"KEY_LSTICK", TasButton::STICK_L}, | ||
| 32 | {"KEY_RSTICK", TasButton::STICK_R}, | ||
| 33 | {"KEY_L", TasButton::TRIGGER_L}, | ||
| 34 | {"KEY_R", TasButton::TRIGGER_R}, | ||
| 35 | {"KEY_PLUS", TasButton::BUTTON_PLUS}, | ||
| 36 | {"KEY_MINUS", TasButton::BUTTON_MINUS}, | ||
| 37 | {"KEY_DLEFT", TasButton::BUTTON_LEFT}, | ||
| 38 | {"KEY_DUP", TasButton::BUTTON_UP}, | ||
| 39 | {"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, | ||
| 40 | {"KEY_DDOWN", TasButton::BUTTON_DOWN}, | ||
| 41 | {"KEY_SL", TasButton::BUTTON_SL}, | ||
| 42 | {"KEY_SR", TasButton::BUTTON_SR}, | ||
| 43 | {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, | ||
| 44 | {"KEY_HOME", TasButton::BUTTON_HOME}, | ||
| 45 | {"KEY_ZL", TasButton::TRIGGER_ZL}, | ||
| 46 | {"KEY_ZR", TasButton::TRIGGER_ZR}, | ||
| 47 | }; | ||
| 48 | |||
| 49 | Tas::Tas(std::string input_engine_) : InputEngine(std::move(input_engine_)) { | ||
| 50 | for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) { | ||
| 51 | PadIdentifier identifier{ | ||
| 52 | .guid = Common::UUID{}, | ||
| 53 | .port = player_index, | ||
| 54 | .pad = 0, | ||
| 55 | }; | ||
| 56 | PreSetController(identifier); | ||
| 57 | } | ||
| 58 | ClearInput(); | ||
| 59 | if (!Settings::values.tas_enable) { | ||
| 60 | needs_reset = true; | ||
| 61 | return; | ||
| 62 | } | ||
| 63 | LoadTasFiles(); | ||
| 64 | } | ||
| 65 | |||
| 66 | Tas::~Tas() { | ||
| 67 | Stop(); | ||
| 68 | } | ||
| 69 | |||
| 70 | void Tas::LoadTasFiles() { | ||
| 71 | script_length = 0; | ||
| 72 | for (size_t i = 0; i < commands.size(); i++) { | ||
| 73 | LoadTasFile(i, 0); | ||
| 74 | if (commands[i].size() > script_length) { | ||
| 75 | script_length = commands[i].size(); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | void Tas::LoadTasFile(size_t player_index, size_t file_index) { | ||
| 81 | commands[player_index].clear(); | ||
| 82 | |||
| 83 | std::string file = Common::FS::ReadStringFromFile( | ||
| 84 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / | ||
| 85 | fmt::format("script{}-{}.txt", file_index, player_index + 1), | ||
| 86 | Common::FS::FileType::BinaryFile); | ||
| 87 | std::istringstream command_line(file); | ||
| 88 | std::string line; | ||
| 89 | int frame_no = 0; | ||
| 90 | while (std::getline(command_line, line, '\n')) { | ||
| 91 | if (line.empty()) { | ||
| 92 | continue; | ||
| 93 | } | ||
| 94 | |||
| 95 | std::vector<std::string> seg_list; | ||
| 96 | { | ||
| 97 | std::istringstream line_stream(line); | ||
| 98 | std::string segment; | ||
| 99 | while (std::getline(line_stream, segment, ' ')) { | ||
| 100 | seg_list.push_back(std::move(segment)); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | if (seg_list.size() < 4) { | ||
| 105 | continue; | ||
| 106 | } | ||
| 107 | |||
| 108 | const auto num_frames = std::stoi(seg_list[0]); | ||
| 109 | while (frame_no < num_frames) { | ||
| 110 | commands[player_index].emplace_back(); | ||
| 111 | frame_no++; | ||
| 112 | } | ||
| 113 | |||
| 114 | TASCommand command = { | ||
| 115 | .buttons = ReadCommandButtons(seg_list[1]), | ||
| 116 | .l_axis = ReadCommandAxis(seg_list[2]), | ||
| 117 | .r_axis = ReadCommandAxis(seg_list[3]), | ||
| 118 | }; | ||
| 119 | commands[player_index].push_back(command); | ||
| 120 | frame_no++; | ||
| 121 | } | ||
| 122 | LOG_INFO(Input, "TAS file loaded! {} frames", frame_no); | ||
| 123 | } | ||
| 124 | |||
| 125 | void Tas::WriteTasFile(std::u8string_view file_name) { | ||
| 126 | std::string output_text; | ||
| 127 | for (size_t frame = 0; frame < record_commands.size(); frame++) { | ||
| 128 | const TASCommand& line = record_commands[frame]; | ||
| 129 | output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons), | ||
| 130 | WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis)); | ||
| 131 | } | ||
| 132 | |||
| 133 | const auto tas_file_name = Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name; | ||
| 134 | const auto bytes_written = | ||
| 135 | Common::FS::WriteStringToFile(tas_file_name, Common::FS::FileType::TextFile, output_text); | ||
| 136 | if (bytes_written == output_text.size()) { | ||
| 137 | LOG_INFO(Input, "TAS file written to file!"); | ||
| 138 | } else { | ||
| 139 | LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written, | ||
| 140 | output_text.size()); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) { | ||
| 145 | last_input = { | ||
| 146 | .buttons = buttons, | ||
| 147 | .l_axis = left_axis, | ||
| 148 | .r_axis = right_axis, | ||
| 149 | }; | ||
| 150 | } | ||
| 151 | |||
| 152 | std::tuple<TasState, size_t, size_t> Tas::GetStatus() const { | ||
| 153 | TasState state; | ||
| 154 | if (is_recording) { | ||
| 155 | return {TasState::Recording, 0, record_commands.size()}; | ||
| 156 | } | ||
| 157 | |||
| 158 | if (is_running) { | ||
| 159 | state = TasState::Running; | ||
| 160 | } else { | ||
| 161 | state = TasState::Stopped; | ||
| 162 | } | ||
| 163 | |||
| 164 | return {state, current_command, script_length}; | ||
| 165 | } | ||
| 166 | |||
| 167 | void Tas::UpdateThread() { | ||
| 168 | if (!Settings::values.tas_enable) { | ||
| 169 | if (is_running) { | ||
| 170 | Stop(); | ||
| 171 | } | ||
| 172 | return; | ||
| 173 | } | ||
| 174 | |||
| 175 | if (is_recording) { | ||
| 176 | record_commands.push_back(last_input); | ||
| 177 | } | ||
| 178 | if (needs_reset) { | ||
| 179 | current_command = 0; | ||
| 180 | needs_reset = false; | ||
| 181 | LoadTasFiles(); | ||
| 182 | LOG_DEBUG(Input, "tas_reset done"); | ||
| 183 | } | ||
| 184 | |||
| 185 | if (!is_running) { | ||
| 186 | ClearInput(); | ||
| 187 | return; | ||
| 188 | } | ||
| 189 | if (current_command < script_length) { | ||
| 190 | LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); | ||
| 191 | const size_t frame = current_command++; | ||
| 192 | for (size_t player_index = 0; player_index < commands.size(); player_index++) { | ||
| 193 | TASCommand command{}; | ||
| 194 | if (frame < commands[player_index].size()) { | ||
| 195 | command = commands[player_index][frame]; | ||
| 196 | } | ||
| 197 | |||
| 198 | PadIdentifier identifier{ | ||
| 199 | .guid = Common::UUID{}, | ||
| 200 | .port = player_index, | ||
| 201 | .pad = 0, | ||
| 202 | }; | ||
| 203 | for (std::size_t i = 0; i < sizeof(command.buttons) * 8; ++i) { | ||
| 204 | const bool button_status = (command.buttons & (1LLU << i)) != 0; | ||
| 205 | const int button = static_cast<int>(i); | ||
| 206 | SetButton(identifier, button, button_status); | ||
| 207 | } | ||
| 208 | SetTasAxis(identifier, TasAxis::StickX, command.l_axis.x); | ||
| 209 | SetTasAxis(identifier, TasAxis::StickY, command.l_axis.y); | ||
| 210 | SetTasAxis(identifier, TasAxis::SubstickX, command.r_axis.x); | ||
| 211 | SetTasAxis(identifier, TasAxis::SubstickY, command.r_axis.y); | ||
| 212 | } | ||
| 213 | } else { | ||
| 214 | is_running = Settings::values.tas_loop.GetValue(); | ||
| 215 | LoadTasFiles(); | ||
| 216 | current_command = 0; | ||
| 217 | ClearInput(); | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | void Tas::ClearInput() { | ||
| 222 | ResetButtonState(); | ||
| 223 | ResetAnalogState(); | ||
| 224 | } | ||
| 225 | |||
| 226 | TasAnalog Tas::ReadCommandAxis(const std::string& line) const { | ||
| 227 | std::vector<std::string> seg_list; | ||
| 228 | { | ||
| 229 | std::istringstream line_stream(line); | ||
| 230 | std::string segment; | ||
| 231 | while (std::getline(line_stream, segment, ';')) { | ||
| 232 | seg_list.push_back(std::move(segment)); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | const float x = std::stof(seg_list.at(0)) / 32767.0f; | ||
| 237 | const float y = std::stof(seg_list.at(1)) / 32767.0f; | ||
| 238 | |||
| 239 | return {x, y}; | ||
| 240 | } | ||
| 241 | |||
| 242 | u64 Tas::ReadCommandButtons(const std::string& line) const { | ||
| 243 | std::istringstream button_text(line); | ||
| 244 | std::string button_line; | ||
| 245 | u64 buttons = 0; | ||
| 246 | while (std::getline(button_text, button_line, ';')) { | ||
| 247 | for (const auto& [text, tas_button] : text_to_tas_button) { | ||
| 248 | if (text == button_line) { | ||
| 249 | buttons |= static_cast<u64>(tas_button); | ||
| 250 | break; | ||
| 251 | } | ||
| 252 | } | ||
| 253 | } | ||
| 254 | return buttons; | ||
| 255 | } | ||
| 256 | |||
| 257 | std::string Tas::WriteCommandButtons(u64 buttons) const { | ||
| 258 | std::string returns; | ||
| 259 | for (const auto& [text_button, tas_button] : text_to_tas_button) { | ||
| 260 | if ((buttons & static_cast<u64>(tas_button)) != 0) { | ||
| 261 | returns += fmt::format("{};", text_button); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | return returns.empty() ? "NONE" : returns; | ||
| 265 | } | ||
| 266 | |||
| 267 | std::string Tas::WriteCommandAxis(TasAnalog analog) const { | ||
| 268 | return fmt::format("{};{}", analog.x * 32767, analog.y * 32767); | ||
| 269 | } | ||
| 270 | |||
| 271 | void Tas::SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value) { | ||
| 272 | SetAxis(identifier, static_cast<int>(axis), value); | ||
| 273 | } | ||
| 274 | |||
| 275 | void Tas::StartStop() { | ||
| 276 | if (!Settings::values.tas_enable) { | ||
| 277 | return; | ||
| 278 | } | ||
| 279 | if (is_running) { | ||
| 280 | Stop(); | ||
| 281 | } else { | ||
| 282 | is_running = true; | ||
| 283 | } | ||
| 284 | } | ||
| 285 | |||
| 286 | void Tas::Stop() { | ||
| 287 | is_running = false; | ||
| 288 | } | ||
| 289 | |||
| 290 | void Tas::Reset() { | ||
| 291 | if (!Settings::values.tas_enable) { | ||
| 292 | return; | ||
| 293 | } | ||
| 294 | needs_reset = true; | ||
| 295 | } | ||
| 296 | |||
| 297 | bool Tas::Record() { | ||
| 298 | if (!Settings::values.tas_enable) { | ||
| 299 | return true; | ||
| 300 | } | ||
| 301 | is_recording = !is_recording; | ||
| 302 | return is_recording; | ||
| 303 | } | ||
| 304 | |||
| 305 | void Tas::SaveRecording(bool overwrite_file) { | ||
| 306 | if (is_recording) { | ||
| 307 | return; | ||
| 308 | } | ||
| 309 | if (record_commands.empty()) { | ||
| 310 | return; | ||
| 311 | } | ||
| 312 | WriteTasFile(u8"record.txt"); | ||
| 313 | if (overwrite_file) { | ||
| 314 | WriteTasFile(u8"script0-1.txt"); | ||
| 315 | } | ||
| 316 | needs_reset = true; | ||
| 317 | record_commands.clear(); | ||
| 318 | } | ||
| 319 | |||
| 320 | } // namespace InputCommon::TasInput | ||
diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h new file mode 100644 index 000000000..4b4e6c417 --- /dev/null +++ b/src/input_common/drivers/tas_input.h | |||
| @@ -0,0 +1,201 @@ | |||
| 1 | // Copyright 2020 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <string> | ||
| 9 | #include <vector> | ||
| 10 | |||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "input_common/input_engine.h" | ||
| 13 | |||
| 14 | /* | ||
| 15 | To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below | ||
| 16 | Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt | ||
| 17 | for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players). | ||
| 18 | |||
| 19 | A script file has the same format as TAS-nx uses, so final files will look like this: | ||
| 20 | |||
| 21 | 1 KEY_B 0;0 0;0 | ||
| 22 | 6 KEY_ZL 0;0 0;0 | ||
| 23 | 41 KEY_ZL;KEY_Y 0;0 0;0 | ||
| 24 | 43 KEY_X;KEY_A 32767;0 0;0 | ||
| 25 | 44 KEY_A 32767;0 0;0 | ||
| 26 | 45 KEY_A 32767;0 0;0 | ||
| 27 | 46 KEY_A 32767;0 0;0 | ||
| 28 | 47 KEY_A 32767;0 0;0 | ||
| 29 | |||
| 30 | After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey | ||
| 31 | CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file | ||
| 32 | has. Playback can be started or stopped using CTRL+F5. | ||
| 33 | |||
| 34 | However, for playback to actually work, the correct input device has to be selected: In the Controls | ||
| 35 | menu, select TAS from the device list for the controller that the script should be played on. | ||
| 36 | |||
| 37 | Recording a new script file is really simple: Just make sure that the proper device (not TAS) is | ||
| 38 | connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke | ||
| 39 | again (CTRL+F7). The new script will be saved at the location previously selected, as the filename | ||
| 40 | record.txt. | ||
| 41 | |||
| 42 | For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller | ||
| 43 | P1). | ||
| 44 | */ | ||
| 45 | |||
| 46 | namespace InputCommon::TasInput { | ||
| 47 | |||
| 48 | constexpr size_t PLAYER_NUMBER = 10; | ||
| 49 | |||
| 50 | enum class TasButton : u64 { | ||
| 51 | BUTTON_A = 1U << 0, | ||
| 52 | BUTTON_B = 1U << 1, | ||
| 53 | BUTTON_X = 1U << 2, | ||
| 54 | BUTTON_Y = 1U << 3, | ||
| 55 | STICK_L = 1U << 4, | ||
| 56 | STICK_R = 1U << 5, | ||
| 57 | TRIGGER_L = 1U << 6, | ||
| 58 | TRIGGER_R = 1U << 7, | ||
| 59 | TRIGGER_ZL = 1U << 8, | ||
| 60 | TRIGGER_ZR = 1U << 9, | ||
| 61 | BUTTON_PLUS = 1U << 10, | ||
| 62 | BUTTON_MINUS = 1U << 11, | ||
| 63 | BUTTON_LEFT = 1U << 12, | ||
| 64 | BUTTON_UP = 1U << 13, | ||
| 65 | BUTTON_RIGHT = 1U << 14, | ||
| 66 | BUTTON_DOWN = 1U << 15, | ||
| 67 | BUTTON_SL = 1U << 16, | ||
| 68 | BUTTON_SR = 1U << 17, | ||
| 69 | BUTTON_HOME = 1U << 18, | ||
| 70 | BUTTON_CAPTURE = 1U << 19, | ||
| 71 | }; | ||
| 72 | |||
| 73 | struct TasAnalog { | ||
| 74 | float x{}; | ||
| 75 | float y{}; | ||
| 76 | }; | ||
| 77 | |||
| 78 | enum class TasState { | ||
| 79 | Running, | ||
| 80 | Recording, | ||
| 81 | Stopped, | ||
| 82 | }; | ||
| 83 | |||
| 84 | class Tas final : public InputEngine { | ||
| 85 | public: | ||
| 86 | explicit Tas(std::string input_engine_); | ||
| 87 | ~Tas() override; | ||
| 88 | |||
| 89 | /** | ||
| 90 | * Changes the input status that will be stored in each frame | ||
| 91 | * @param buttons Bitfield with the status of the buttons | ||
| 92 | * @param left_axis Value of the left axis | ||
| 93 | * @param right_axis Value of the right axis | ||
| 94 | */ | ||
| 95 | void RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis); | ||
| 96 | |||
| 97 | // Main loop that records or executes input | ||
| 98 | void UpdateThread(); | ||
| 99 | |||
| 100 | // Sets the flag to start or stop the TAS command execution and swaps controllers profiles | ||
| 101 | void StartStop(); | ||
| 102 | |||
| 103 | // Stop the TAS and reverts any controller profile | ||
| 104 | void Stop(); | ||
| 105 | |||
| 106 | // Sets the flag to reload the file and start from the beginning in the next update | ||
| 107 | void Reset(); | ||
| 108 | |||
| 109 | /** | ||
| 110 | * Sets the flag to enable or disable recording of inputs | ||
| 111 | * @returns true if the current recording status is enabled | ||
| 112 | */ | ||
| 113 | bool Record(); | ||
| 114 | |||
| 115 | /** | ||
| 116 | * Saves contents of record_commands on a file | ||
| 117 | * @param overwrite_file Indicates if player 1 should be overwritten | ||
| 118 | */ | ||
| 119 | void SaveRecording(bool overwrite_file); | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Returns the current status values of TAS playback/recording | ||
| 123 | * @returns A Tuple of | ||
| 124 | * TasState indicating the current state out of Running ; | ||
| 125 | * Current playback progress ; | ||
| 126 | * Total length of script file currently loaded or being recorded | ||
| 127 | */ | ||
| 128 | std::tuple<TasState, size_t, size_t> GetStatus() const; | ||
| 129 | |||
| 130 | private: | ||
| 131 | enum class TasAxis : u8; | ||
| 132 | |||
| 133 | struct TASCommand { | ||
| 134 | u64 buttons{}; | ||
| 135 | TasAnalog l_axis{}; | ||
| 136 | TasAnalog r_axis{}; | ||
| 137 | }; | ||
| 138 | |||
| 139 | /// Loads TAS files from all players | ||
| 140 | void LoadTasFiles(); | ||
| 141 | |||
| 142 | /** | ||
| 143 | * Loads TAS file from the specified player | ||
| 144 | * @param player_index Player number to save the script | ||
| 145 | * @param file_index Script number of the file | ||
| 146 | */ | ||
| 147 | void LoadTasFile(size_t player_index, size_t file_index); | ||
| 148 | |||
| 149 | /** | ||
| 150 | * Writes a TAS file from the recorded commands | ||
| 151 | * @param file_name Name of the file to be written | ||
| 152 | */ | ||
| 153 | void WriteTasFile(std::u8string_view file_name); | ||
| 154 | |||
| 155 | /** | ||
| 156 | * Parses a string containing the axis values. X and Y have a range from -32767 to 32767 | ||
| 157 | * @param line String containing axis values with the following format "x;y" | ||
| 158 | * @returns A TAS analog object with axis values with range from -1.0 to 1.0 | ||
| 159 | */ | ||
| 160 | TasAnalog ReadCommandAxis(const std::string& line) const; | ||
| 161 | |||
| 162 | /** | ||
| 163 | * Parses a string containing the button values. Each button is represented by it's text format | ||
| 164 | * specified in text_to_tas_button array | ||
| 165 | * @param line string containing button name with the following format "a;b;c;d..." | ||
| 166 | * @returns A u64 with each bit representing the status of a button | ||
| 167 | */ | ||
| 168 | u64 ReadCommandButtons(const std::string& line) const; | ||
| 169 | |||
| 170 | /** | ||
| 171 | * Reset state of all players | ||
| 172 | */ | ||
| 173 | void ClearInput(); | ||
| 174 | |||
| 175 | /** | ||
| 176 | * Converts an u64 containing the button status into the text equivalent | ||
| 177 | * @param buttons Bitfield with the status of the buttons | ||
| 178 | * @returns A string with the name of the buttons to be written to the file | ||
| 179 | */ | ||
| 180 | std::string WriteCommandButtons(u64 buttons) const; | ||
| 181 | |||
| 182 | /** | ||
| 183 | * Converts an TAS analog object containing the axis status into the text equivalent | ||
| 184 | * @param analog Value of the axis | ||
| 185 | * @returns A string with the value of the axis to be written to the file | ||
| 186 | */ | ||
| 187 | std::string WriteCommandAxis(TasAnalog analog) const; | ||
| 188 | |||
| 189 | /// Sets an axis for a particular pad to the given value. | ||
| 190 | void SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value); | ||
| 191 | |||
| 192 | size_t script_length{0}; | ||
| 193 | bool is_recording{false}; | ||
| 194 | bool is_running{false}; | ||
| 195 | bool needs_reset{false}; | ||
| 196 | std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{}; | ||
| 197 | std::vector<TASCommand> record_commands{}; | ||
| 198 | size_t current_command{0}; | ||
| 199 | TASCommand last_input{}; // only used for recording | ||
| 200 | }; | ||
| 201 | } // namespace InputCommon::TasInput | ||
diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp new file mode 100644 index 000000000..880781825 --- /dev/null +++ b/src/input_common/drivers/touch_screen.cpp | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included | ||
| 4 | |||
| 5 | #include "common/param_package.h" | ||
| 6 | #include "input_common/drivers/touch_screen.h" | ||
| 7 | |||
| 8 | namespace InputCommon { | ||
| 9 | |||
| 10 | constexpr PadIdentifier identifier = { | ||
| 11 | .guid = Common::UUID{Common::INVALID_UUID}, | ||
| 12 | .port = 0, | ||
| 13 | .pad = 0, | ||
| 14 | }; | ||
| 15 | |||
| 16 | TouchScreen::TouchScreen(std::string input_engine_) : InputEngine(std::move(input_engine_)) { | ||
| 17 | PreSetController(identifier); | ||
| 18 | } | ||
| 19 | |||
| 20 | void TouchScreen::TouchMoved(float x, float y, std::size_t finger) { | ||
| 21 | if (finger >= 16) { | ||
| 22 | return; | ||
| 23 | } | ||
| 24 | TouchPressed(x, y, finger); | ||
| 25 | } | ||
| 26 | |||
| 27 | void TouchScreen::TouchPressed(float x, float y, std::size_t finger) { | ||
| 28 | if (finger >= 16) { | ||
| 29 | return; | ||
| 30 | } | ||
| 31 | SetButton(identifier, static_cast<int>(finger), true); | ||
| 32 | SetAxis(identifier, static_cast<int>(finger * 2), x); | ||
| 33 | SetAxis(identifier, static_cast<int>(finger * 2 + 1), y); | ||
| 34 | } | ||
| 35 | |||
| 36 | void TouchScreen::TouchReleased(std::size_t finger) { | ||
| 37 | if (finger >= 16) { | ||
| 38 | return; | ||
| 39 | } | ||
| 40 | SetButton(identifier, static_cast<int>(finger), false); | ||
| 41 | SetAxis(identifier, static_cast<int>(finger * 2), 0.0f); | ||
| 42 | SetAxis(identifier, static_cast<int>(finger * 2 + 1), 0.0f); | ||
| 43 | } | ||
| 44 | |||
| 45 | void TouchScreen::ReleaseAllTouch() { | ||
| 46 | for (int index = 0; index < 16; ++index) { | ||
| 47 | SetButton(identifier, index, false); | ||
| 48 | SetAxis(identifier, index * 2, 0.0f); | ||
| 49 | SetAxis(identifier, index * 2 + 1, 0.0f); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h new file mode 100644 index 000000000..bf395c40b --- /dev/null +++ b/src/input_common/drivers/touch_screen.h | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "input_common/input_engine.h" | ||
| 8 | |||
| 9 | namespace InputCommon { | ||
| 10 | |||
| 11 | /** | ||
| 12 | * A button device factory representing a keyboard. It receives keyboard events and forward them | ||
| 13 | * to all button devices it created. | ||
| 14 | */ | ||
| 15 | class TouchScreen final : public InputEngine { | ||
| 16 | public: | ||
| 17 | explicit TouchScreen(std::string input_engine_); | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Signals that mouse has moved. | ||
| 21 | * @param x the x-coordinate of the cursor | ||
| 22 | * @param y the y-coordinate of the cursor | ||
| 23 | * @param center_x the x-coordinate of the middle of the screen | ||
| 24 | * @param center_y the y-coordinate of the middle of the screen | ||
| 25 | */ | ||
| 26 | void TouchMoved(float x, float y, std::size_t finger); | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Sets the status of all buttons bound with the key to pressed | ||
| 30 | * @param key_code the code of the key to press | ||
| 31 | */ | ||
| 32 | void TouchPressed(float x, float y, std::size_t finger); | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Sets the status of all buttons bound with the key to released | ||
| 36 | * @param key_code the code of the key to release | ||
| 37 | */ | ||
| 38 | void TouchReleased(std::size_t finger); | ||
| 39 | |||
| 40 | /// Resets all inputs to their initial value | ||
| 41 | void ReleaseAllTouch(); | ||
| 42 | }; | ||
| 43 | |||
| 44 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp new file mode 100644 index 000000000..4ab991a7d --- /dev/null +++ b/src/input_common/drivers/udp_client.cpp | |||
| @@ -0,0 +1,591 @@ | |||
| 1 | // Copyright 2018 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <random> | ||
| 6 | #include <boost/asio.hpp> | ||
| 7 | #include <fmt/format.h> | ||
| 8 | |||
| 9 | #include "common/logging/log.h" | ||
| 10 | #include "common/param_package.h" | ||
| 11 | #include "common/settings.h" | ||
| 12 | #include "input_common/drivers/udp_client.h" | ||
| 13 | #include "input_common/helpers/udp_protocol.h" | ||
| 14 | |||
| 15 | using boost::asio::ip::udp; | ||
| 16 | |||
| 17 | namespace InputCommon::CemuhookUDP { | ||
| 18 | |||
| 19 | struct SocketCallback { | ||
| 20 | std::function<void(Response::Version)> version; | ||
| 21 | std::function<void(Response::PortInfo)> port_info; | ||
| 22 | std::function<void(Response::PadData)> pad_data; | ||
| 23 | }; | ||
| 24 | |||
| 25 | class Socket { | ||
| 26 | public: | ||
| 27 | using clock = std::chrono::system_clock; | ||
| 28 | |||
| 29 | explicit Socket(const std::string& host, u16 port, SocketCallback callback_) | ||
| 30 | : callback(std::move(callback_)), timer(io_service), | ||
| 31 | socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) { | ||
| 32 | boost::system::error_code ec{}; | ||
| 33 | auto ipv4 = boost::asio::ip::make_address_v4(host, ec); | ||
| 34 | if (ec.value() != boost::system::errc::success) { | ||
| 35 | LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host); | ||
| 36 | ipv4 = boost::asio::ip::address_v4{}; | ||
| 37 | } | ||
| 38 | |||
| 39 | send_endpoint = {udp::endpoint(ipv4, port)}; | ||
| 40 | } | ||
| 41 | |||
| 42 | void Stop() { | ||
| 43 | io_service.stop(); | ||
| 44 | } | ||
| 45 | |||
| 46 | void Loop() { | ||
| 47 | io_service.run(); | ||
| 48 | } | ||
| 49 | |||
| 50 | void StartSend(const clock::time_point& from) { | ||
| 51 | timer.expires_at(from + std::chrono::seconds(3)); | ||
| 52 | timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); | ||
| 53 | } | ||
| 54 | |||
| 55 | void StartReceive() { | ||
| 56 | socket.async_receive_from( | ||
| 57 | boost::asio::buffer(receive_buffer), receive_endpoint, | ||
| 58 | [this](const boost::system::error_code& error, std::size_t bytes_transferred) { | ||
| 59 | HandleReceive(error, bytes_transferred); | ||
| 60 | }); | ||
| 61 | } | ||
| 62 | |||
| 63 | private: | ||
| 64 | u32 GenerateRandomClientId() const { | ||
| 65 | std::random_device device; | ||
| 66 | return device(); | ||
| 67 | } | ||
| 68 | |||
| 69 | void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) { | ||
| 70 | if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { | ||
| 71 | switch (*type) { | ||
| 72 | case Type::Version: { | ||
| 73 | Response::Version version; | ||
| 74 | std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); | ||
| 75 | callback.version(std::move(version)); | ||
| 76 | break; | ||
| 77 | } | ||
| 78 | case Type::PortInfo: { | ||
| 79 | Response::PortInfo port_info; | ||
| 80 | std::memcpy(&port_info, &receive_buffer[sizeof(Header)], | ||
| 81 | sizeof(Response::PortInfo)); | ||
| 82 | callback.port_info(std::move(port_info)); | ||
| 83 | break; | ||
| 84 | } | ||
| 85 | case Type::PadData: { | ||
| 86 | Response::PadData pad_data; | ||
| 87 | std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); | ||
| 88 | callback.pad_data(std::move(pad_data)); | ||
| 89 | break; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
| 93 | StartReceive(); | ||
| 94 | } | ||
| 95 | |||
| 96 | void HandleSend(const boost::system::error_code&) { | ||
| 97 | boost::system::error_code _ignored{}; | ||
| 98 | // Send a request for getting port info for the pad | ||
| 99 | const Request::PortInfo port_info{4, {0, 1, 2, 3}}; | ||
| 100 | const auto port_message = Request::Create(port_info, client_id); | ||
| 101 | std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); | ||
| 102 | socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored); | ||
| 103 | |||
| 104 | // Send a request for getting pad data for the pad | ||
| 105 | const Request::PadData pad_data{ | ||
| 106 | Request::RegisterFlags::AllPads, | ||
| 107 | 0, | ||
| 108 | EMPTY_MAC_ADDRESS, | ||
| 109 | }; | ||
| 110 | const auto pad_message = Request::Create(pad_data, client_id); | ||
| 111 | std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); | ||
| 112 | socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored); | ||
| 113 | StartSend(timer.expiry()); | ||
| 114 | } | ||
| 115 | |||
| 116 | SocketCallback callback; | ||
| 117 | boost::asio::io_service io_service; | ||
| 118 | boost::asio::basic_waitable_timer<clock> timer; | ||
| 119 | udp::socket socket; | ||
| 120 | |||
| 121 | const u32 client_id; | ||
| 122 | |||
| 123 | static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>); | ||
| 124 | static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>); | ||
| 125 | std::array<u8, PORT_INFO_SIZE> send_buffer1; | ||
| 126 | std::array<u8, PAD_DATA_SIZE> send_buffer2; | ||
| 127 | udp::endpoint send_endpoint; | ||
| 128 | |||
| 129 | std::array<u8, MAX_PACKET_SIZE> receive_buffer; | ||
| 130 | udp::endpoint receive_endpoint; | ||
| 131 | }; | ||
| 132 | |||
| 133 | static void SocketLoop(Socket* socket) { | ||
| 134 | socket->StartReceive(); | ||
| 135 | socket->StartSend(Socket::clock::now()); | ||
| 136 | socket->Loop(); | ||
| 137 | } | ||
| 138 | |||
| 139 | UDPClient::UDPClient(std::string input_engine_) : InputEngine(std::move(input_engine_)) { | ||
| 140 | LOG_INFO(Input, "Udp Initialization started"); | ||
| 141 | ReloadSockets(); | ||
| 142 | } | ||
| 143 | |||
| 144 | UDPClient::~UDPClient() { | ||
| 145 | Reset(); | ||
| 146 | } | ||
| 147 | |||
| 148 | UDPClient::ClientConnection::ClientConnection() = default; | ||
| 149 | |||
| 150 | UDPClient::ClientConnection::~ClientConnection() = default; | ||
| 151 | |||
| 152 | void UDPClient::ReloadSockets() { | ||
| 153 | Reset(); | ||
| 154 | |||
| 155 | std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue()); | ||
| 156 | std::string server_token; | ||
| 157 | std::size_t client = 0; | ||
| 158 | while (std::getline(servers_ss, server_token, ',')) { | ||
| 159 | if (client == MAX_UDP_CLIENTS) { | ||
| 160 | break; | ||
| 161 | } | ||
| 162 | std::stringstream server_ss(server_token); | ||
| 163 | std::string token; | ||
| 164 | std::getline(server_ss, token, ':'); | ||
| 165 | std::string udp_input_address = token; | ||
| 166 | std::getline(server_ss, token, ':'); | ||
| 167 | char* temp; | ||
| 168 | const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0)); | ||
| 169 | if (*temp != '\0') { | ||
| 170 | LOG_ERROR(Input, "Port number is not valid {}", token); | ||
| 171 | continue; | ||
| 172 | } | ||
| 173 | |||
| 174 | const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port); | ||
| 175 | if (client_number != MAX_UDP_CLIENTS) { | ||
| 176 | LOG_ERROR(Input, "Duplicated UDP servers found"); | ||
| 177 | continue; | ||
| 178 | } | ||
| 179 | StartCommunication(client++, udp_input_address, udp_input_port); | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const { | ||
| 184 | for (std::size_t client = 0; client < clients.size(); client++) { | ||
| 185 | if (clients[client].active == -1) { | ||
| 186 | continue; | ||
| 187 | } | ||
| 188 | if (clients[client].host == host && clients[client].port == port) { | ||
| 189 | return client; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | return MAX_UDP_CLIENTS; | ||
| 193 | } | ||
| 194 | |||
| 195 | void UDPClient::OnVersion([[maybe_unused]] Response::Version data) { | ||
| 196 | LOG_TRACE(Input, "Version packet received: {}", data.version); | ||
| 197 | } | ||
| 198 | |||
| 199 | void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) { | ||
| 200 | LOG_TRACE(Input, "PortInfo packet received: {}", data.model); | ||
| 201 | } | ||
| 202 | |||
| 203 | void UDPClient::OnPadData(Response::PadData data, std::size_t client) { | ||
| 204 | const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id; | ||
| 205 | |||
| 206 | if (pad_index >= pads.size()) { | ||
| 207 | LOG_ERROR(Input, "Invalid pad id {}", data.info.id); | ||
| 208 | return; | ||
| 209 | } | ||
| 210 | |||
| 211 | LOG_TRACE(Input, "PadData packet received"); | ||
| 212 | if (data.packet_counter == pads[pad_index].packet_sequence) { | ||
| 213 | LOG_WARNING( | ||
| 214 | Input, | ||
| 215 | "PadData packet dropped because its stale info. Current count: {} Packet count: {}", | ||
| 216 | pads[pad_index].packet_sequence, data.packet_counter); | ||
| 217 | pads[pad_index].connected = false; | ||
| 218 | return; | ||
| 219 | } | ||
| 220 | |||
| 221 | clients[client].active = 1; | ||
| 222 | pads[pad_index].connected = true; | ||
| 223 | pads[pad_index].packet_sequence = data.packet_counter; | ||
| 224 | |||
| 225 | const auto now = std::chrono::steady_clock::now(); | ||
| 226 | const auto time_difference = static_cast<u64>( | ||
| 227 | std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update) | ||
| 228 | .count()); | ||
| 229 | pads[pad_index].last_update = now; | ||
| 230 | |||
| 231 | // Gyroscope values are not it the correct scale from better joy. | ||
| 232 | // Dividing by 312 allows us to make one full turn = 1 turn | ||
| 233 | // This must be a configurable valued called sensitivity | ||
| 234 | const float gyro_scale = 1.0f / 312.0f; | ||
| 235 | |||
| 236 | const BasicMotion motion{ | ||
| 237 | .gyro_x = data.gyro.pitch * gyro_scale, | ||
| 238 | .gyro_y = data.gyro.roll * gyro_scale, | ||
| 239 | .gyro_z = -data.gyro.yaw * gyro_scale, | ||
| 240 | .accel_x = data.accel.x, | ||
| 241 | .accel_y = -data.accel.z, | ||
| 242 | .accel_z = data.accel.y, | ||
| 243 | .delta_timestamp = time_difference, | ||
| 244 | }; | ||
| 245 | const PadIdentifier identifier = GetPadIdentifier(pad_index); | ||
| 246 | SetMotion(identifier, 0, motion); | ||
| 247 | |||
| 248 | for (std::size_t id = 0; id < data.touch.size(); ++id) { | ||
| 249 | const auto touch_pad = data.touch[id]; | ||
| 250 | const auto touch_axis_x_id = | ||
| 251 | static_cast<int>(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X); | ||
| 252 | const auto touch_axis_y_id = | ||
| 253 | static_cast<int>(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y); | ||
| 254 | const auto touch_button_id = | ||
| 255 | static_cast<int>(id == 0 ? PadButton::Touch1 : PadButton::touch2); | ||
| 256 | |||
| 257 | // TODO: Use custom calibration per device | ||
| 258 | const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); | ||
| 259 | const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100)); | ||
| 260 | const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50)); | ||
| 261 | const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800)); | ||
| 262 | const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850)); | ||
| 263 | |||
| 264 | const f32 x = | ||
| 265 | static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) / | ||
| 266 | static_cast<f32>(max_x - min_x); | ||
| 267 | const f32 y = | ||
| 268 | static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) / | ||
| 269 | static_cast<f32>(max_y - min_y); | ||
| 270 | |||
| 271 | if (touch_pad.is_active) { | ||
| 272 | SetAxis(identifier, touch_axis_x_id, x); | ||
| 273 | SetAxis(identifier, touch_axis_y_id, y); | ||
| 274 | SetButton(identifier, touch_button_id, true); | ||
| 275 | continue; | ||
| 276 | } | ||
| 277 | SetAxis(identifier, touch_axis_x_id, 0); | ||
| 278 | SetAxis(identifier, touch_axis_y_id, 0); | ||
| 279 | SetButton(identifier, touch_button_id, false); | ||
| 280 | } | ||
| 281 | |||
| 282 | SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX), | ||
| 283 | (data.left_stick_x - 127.0f) / 127.0f); | ||
| 284 | SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY), | ||
| 285 | (data.left_stick_y - 127.0f) / 127.0f); | ||
| 286 | SetAxis(identifier, static_cast<int>(PadAxes::RightStickX), | ||
| 287 | (data.right_stick_x - 127.0f) / 127.0f); | ||
| 288 | SetAxis(identifier, static_cast<int>(PadAxes::RightStickY), | ||
| 289 | (data.right_stick_y - 127.0f) / 127.0f); | ||
| 290 | |||
| 291 | static constexpr std::array<PadButton, 16> buttons{ | ||
| 292 | PadButton::Share, PadButton::L3, PadButton::R3, PadButton::Options, | ||
| 293 | PadButton::Up, PadButton::Right, PadButton::Down, PadButton::Left, | ||
| 294 | PadButton::L2, PadButton::R2, PadButton::L1, PadButton::R1, | ||
| 295 | PadButton::Triangle, PadButton::Circle, PadButton::Cross, PadButton::Square}; | ||
| 296 | |||
| 297 | for (std::size_t i = 0; i < buttons.size(); ++i) { | ||
| 298 | const bool button_status = (data.digital_button & (1U << i)) != 0; | ||
| 299 | const int button = static_cast<int>(buttons[i]); | ||
| 300 | SetButton(identifier, button, button_status); | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) { | ||
| 305 | SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, | ||
| 306 | [this](Response::PortInfo info) { OnPortInfo(info); }, | ||
| 307 | [this, client](Response::PadData data) { OnPadData(data, client); }}; | ||
| 308 | LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); | ||
| 309 | clients[client].uuid = GetHostUUID(host); | ||
| 310 | clients[client].host = host; | ||
| 311 | clients[client].port = port; | ||
| 312 | clients[client].active = 0; | ||
| 313 | clients[client].socket = std::make_unique<Socket>(host, port, callback); | ||
| 314 | clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; | ||
| 315 | for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { | ||
| 316 | const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index); | ||
| 317 | PreSetController(identifier); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | const PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const { | ||
| 322 | const std::size_t client = pad_index / PADS_PER_CLIENT; | ||
| 323 | return { | ||
| 324 | .guid = clients[client].uuid, | ||
| 325 | .port = static_cast<std::size_t>(clients[client].port), | ||
| 326 | .pad = pad_index, | ||
| 327 | }; | ||
| 328 | } | ||
| 329 | |||
| 330 | const Common::UUID UDPClient::GetHostUUID(const std::string host) const { | ||
| 331 | const auto ip = boost::asio::ip::address_v4::from_string(host); | ||
| 332 | const auto hex_host = fmt::format("{:06x}", ip.to_ulong()); | ||
| 333 | return Common::UUID{hex_host}; | ||
| 334 | } | ||
| 335 | |||
| 336 | void UDPClient::Reset() { | ||
| 337 | for (auto& client : clients) { | ||
| 338 | if (client.thread.joinable()) { | ||
| 339 | client.active = -1; | ||
| 340 | client.socket->Stop(); | ||
| 341 | client.thread.join(); | ||
| 342 | } | ||
| 343 | } | ||
| 344 | } | ||
| 345 | |||
| 346 | std::vector<Common::ParamPackage> UDPClient::GetInputDevices() const { | ||
| 347 | std::vector<Common::ParamPackage> devices; | ||
| 348 | if (!Settings::values.enable_udp_controller) { | ||
| 349 | return devices; | ||
| 350 | } | ||
| 351 | for (std::size_t client = 0; client < clients.size(); client++) { | ||
| 352 | if (clients[client].active != 1) { | ||
| 353 | continue; | ||
| 354 | } | ||
| 355 | for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { | ||
| 356 | const std::size_t pad_index = client * PADS_PER_CLIENT + index; | ||
| 357 | if (!pads[pad_index].connected) { | ||
| 358 | continue; | ||
| 359 | } | ||
| 360 | const auto pad_identifier = GetPadIdentifier(pad_index); | ||
| 361 | Common::ParamPackage identifier{}; | ||
| 362 | identifier.Set("engine", GetEngineName()); | ||
| 363 | identifier.Set("display", fmt::format("UDP Controller {}", pad_identifier.pad)); | ||
| 364 | identifier.Set("guid", pad_identifier.guid.Format()); | ||
| 365 | identifier.Set("port", static_cast<int>(pad_identifier.port)); | ||
| 366 | identifier.Set("pad", static_cast<int>(pad_identifier.pad)); | ||
| 367 | devices.emplace_back(identifier); | ||
| 368 | } | ||
| 369 | } | ||
| 370 | return devices; | ||
| 371 | } | ||
| 372 | |||
| 373 | ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& params) { | ||
| 374 | // This list excludes any button that can't be really mapped | ||
| 375 | static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 18> | ||
| 376 | switch_to_dsu_button = { | ||
| 377 | std::pair{Settings::NativeButton::A, PadButton::Circle}, | ||
| 378 | {Settings::NativeButton::B, PadButton::Cross}, | ||
| 379 | {Settings::NativeButton::X, PadButton::Triangle}, | ||
| 380 | {Settings::NativeButton::Y, PadButton::Square}, | ||
| 381 | {Settings::NativeButton::Plus, PadButton::Options}, | ||
| 382 | {Settings::NativeButton::Minus, PadButton::Share}, | ||
| 383 | {Settings::NativeButton::DLeft, PadButton::Left}, | ||
| 384 | {Settings::NativeButton::DUp, PadButton::Up}, | ||
| 385 | {Settings::NativeButton::DRight, PadButton::Right}, | ||
| 386 | {Settings::NativeButton::DDown, PadButton::Down}, | ||
| 387 | {Settings::NativeButton::L, PadButton::L1}, | ||
| 388 | {Settings::NativeButton::R, PadButton::R1}, | ||
| 389 | {Settings::NativeButton::ZL, PadButton::L2}, | ||
| 390 | {Settings::NativeButton::ZR, PadButton::R2}, | ||
| 391 | {Settings::NativeButton::SL, PadButton::L2}, | ||
| 392 | {Settings::NativeButton::SR, PadButton::R2}, | ||
| 393 | {Settings::NativeButton::LStick, PadButton::L3}, | ||
| 394 | {Settings::NativeButton::RStick, PadButton::R3}, | ||
| 395 | }; | ||
| 396 | if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) { | ||
| 397 | return {}; | ||
| 398 | } | ||
| 399 | |||
| 400 | ButtonMapping mapping{}; | ||
| 401 | for (const auto& [switch_button, dsu_button] : switch_to_dsu_button) { | ||
| 402 | Common::ParamPackage button_params{}; | ||
| 403 | button_params.Set("engine", GetEngineName()); | ||
| 404 | button_params.Set("guid", params.Get("guid", "")); | ||
| 405 | button_params.Set("port", params.Get("port", 0)); | ||
| 406 | button_params.Set("pad", params.Get("pad", 0)); | ||
| 407 | button_params.Set("button", static_cast<int>(dsu_button)); | ||
| 408 | mapping.insert_or_assign(switch_button, std::move(button_params)); | ||
| 409 | } | ||
| 410 | |||
| 411 | return mapping; | ||
| 412 | } | ||
| 413 | |||
| 414 | AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& params) { | ||
| 415 | if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) { | ||
| 416 | return {}; | ||
| 417 | } | ||
| 418 | |||
| 419 | AnalogMapping mapping = {}; | ||
| 420 | Common::ParamPackage left_analog_params; | ||
| 421 | left_analog_params.Set("engine", GetEngineName()); | ||
| 422 | left_analog_params.Set("guid", params.Get("guid", "")); | ||
| 423 | left_analog_params.Set("port", params.Get("port", 0)); | ||
| 424 | left_analog_params.Set("pad", params.Get("pad", 0)); | ||
| 425 | left_analog_params.Set("axis_x", static_cast<int>(PadAxes::LeftStickX)); | ||
| 426 | left_analog_params.Set("axis_y", static_cast<int>(PadAxes::LeftStickY)); | ||
| 427 | mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); | ||
| 428 | Common::ParamPackage right_analog_params; | ||
| 429 | right_analog_params.Set("engine", GetEngineName()); | ||
| 430 | right_analog_params.Set("guid", params.Get("guid", "")); | ||
| 431 | right_analog_params.Set("port", params.Get("port", 0)); | ||
| 432 | right_analog_params.Set("pad", params.Get("pad", 0)); | ||
| 433 | right_analog_params.Set("axis_x", static_cast<int>(PadAxes::RightStickX)); | ||
| 434 | right_analog_params.Set("axis_y", static_cast<int>(PadAxes::RightStickY)); | ||
| 435 | mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); | ||
| 436 | return mapping; | ||
| 437 | } | ||
| 438 | |||
| 439 | MotionMapping UDPClient::GetMotionMappingForDevice(const Common::ParamPackage& params) { | ||
| 440 | if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) { | ||
| 441 | return {}; | ||
| 442 | } | ||
| 443 | |||
| 444 | MotionMapping mapping = {}; | ||
| 445 | Common::ParamPackage motion_params; | ||
| 446 | motion_params.Set("engine", GetEngineName()); | ||
| 447 | motion_params.Set("guid", params.Get("guid", "")); | ||
| 448 | motion_params.Set("port", params.Get("port", 0)); | ||
| 449 | motion_params.Set("pad", params.Get("pad", 0)); | ||
| 450 | motion_params.Set("motion", 0); | ||
| 451 | mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(motion_params)); | ||
| 452 | mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(motion_params)); | ||
| 453 | return mapping; | ||
| 454 | } | ||
| 455 | |||
| 456 | Common::Input::ButtonNames UDPClient::GetUIButtonName(const Common::ParamPackage& params) const { | ||
| 457 | PadButton button = static_cast<PadButton>(params.Get("button", 0)); | ||
| 458 | switch (button) { | ||
| 459 | case PadButton::Left: | ||
| 460 | return Common::Input::ButtonNames::ButtonLeft; | ||
| 461 | case PadButton::Right: | ||
| 462 | return Common::Input::ButtonNames::ButtonRight; | ||
| 463 | case PadButton::Down: | ||
| 464 | return Common::Input::ButtonNames::ButtonDown; | ||
| 465 | case PadButton::Up: | ||
| 466 | return Common::Input::ButtonNames::ButtonUp; | ||
| 467 | case PadButton::L1: | ||
| 468 | return Common::Input::ButtonNames::L1; | ||
| 469 | case PadButton::L2: | ||
| 470 | return Common::Input::ButtonNames::L2; | ||
| 471 | case PadButton::L3: | ||
| 472 | return Common::Input::ButtonNames::L3; | ||
| 473 | case PadButton::R1: | ||
| 474 | return Common::Input::ButtonNames::R1; | ||
| 475 | case PadButton::R2: | ||
| 476 | return Common::Input::ButtonNames::R2; | ||
| 477 | case PadButton::R3: | ||
| 478 | return Common::Input::ButtonNames::R3; | ||
| 479 | case PadButton::Circle: | ||
| 480 | return Common::Input::ButtonNames::Circle; | ||
| 481 | case PadButton::Cross: | ||
| 482 | return Common::Input::ButtonNames::Cross; | ||
| 483 | case PadButton::Square: | ||
| 484 | return Common::Input::ButtonNames::Square; | ||
| 485 | case PadButton::Triangle: | ||
| 486 | return Common::Input::ButtonNames::Triangle; | ||
| 487 | case PadButton::Share: | ||
| 488 | return Common::Input::ButtonNames::Share; | ||
| 489 | case PadButton::Options: | ||
| 490 | return Common::Input::ButtonNames::Options; | ||
| 491 | default: | ||
| 492 | return Common::Input::ButtonNames::Undefined; | ||
| 493 | } | ||
| 494 | } | ||
| 495 | |||
| 496 | Common::Input::ButtonNames UDPClient::GetUIName(const Common::ParamPackage& params) const { | ||
| 497 | if (params.Has("button")) { | ||
| 498 | return GetUIButtonName(params); | ||
| 499 | } | ||
| 500 | if (params.Has("axis")) { | ||
| 501 | return Common::Input::ButtonNames::Value; | ||
| 502 | } | ||
| 503 | if (params.Has("motion")) { | ||
| 504 | return Common::Input::ButtonNames::Engine; | ||
| 505 | } | ||
| 506 | |||
| 507 | return Common::Input::ButtonNames::Invalid; | ||
| 508 | } | ||
| 509 | |||
| 510 | void TestCommunication(const std::string& host, u16 port, | ||
| 511 | const std::function<void()>& success_callback, | ||
| 512 | const std::function<void()>& failure_callback) { | ||
| 513 | std::thread([=] { | ||
| 514 | Common::Event success_event; | ||
| 515 | SocketCallback callback{ | ||
| 516 | .version = [](Response::Version) {}, | ||
| 517 | .port_info = [](Response::PortInfo) {}, | ||
| 518 | .pad_data = [&](Response::PadData) { success_event.Set(); }, | ||
| 519 | }; | ||
| 520 | Socket socket{host, port, std::move(callback)}; | ||
| 521 | std::thread worker_thread{SocketLoop, &socket}; | ||
| 522 | const bool result = | ||
| 523 | success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10)); | ||
| 524 | socket.Stop(); | ||
| 525 | worker_thread.join(); | ||
| 526 | if (result) { | ||
| 527 | success_callback(); | ||
| 528 | } else { | ||
| 529 | failure_callback(); | ||
| 530 | } | ||
| 531 | }).detach(); | ||
| 532 | } | ||
| 533 | |||
| 534 | CalibrationConfigurationJob::CalibrationConfigurationJob( | ||
| 535 | const std::string& host, u16 port, std::function<void(Status)> status_callback, | ||
| 536 | std::function<void(u16, u16, u16, u16)> data_callback) { | ||
| 537 | |||
| 538 | std::thread([=, this] { | ||
| 539 | Status current_status{Status::Initialized}; | ||
| 540 | SocketCallback callback{ | ||
| 541 | [](Response::Version) {}, [](Response::PortInfo) {}, | ||
| 542 | [&](Response::PadData data) { | ||
| 543 | static constexpr u16 CALIBRATION_THRESHOLD = 100; | ||
| 544 | static constexpr u16 MAX_VALUE = UINT16_MAX; | ||
| 545 | |||
| 546 | if (current_status == Status::Initialized) { | ||
| 547 | // Receiving data means the communication is ready now | ||
| 548 | current_status = Status::Ready; | ||
| 549 | status_callback(current_status); | ||
| 550 | } | ||
| 551 | const auto& touchpad_0 = data.touch[0]; | ||
| 552 | if (touchpad_0.is_active == 0) { | ||
| 553 | return; | ||
| 554 | } | ||
| 555 | LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y); | ||
| 556 | const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x)); | ||
| 557 | const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y)); | ||
| 558 | if (current_status == Status::Ready) { | ||
| 559 | // First touch - min data (min_x/min_y) | ||
| 560 | current_status = Status::Stage1Completed; | ||
| 561 | status_callback(current_status); | ||
| 562 | } | ||
| 563 | if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD && | ||
| 564 | touchpad_0.y - min_y > CALIBRATION_THRESHOLD) { | ||
| 565 | // Set the current position as max value and finishes configuration | ||
| 566 | const u16 max_x = touchpad_0.x; | ||
| 567 | const u16 max_y = touchpad_0.y; | ||
| 568 | current_status = Status::Completed; | ||
| 569 | data_callback(min_x, min_y, max_x, max_y); | ||
| 570 | status_callback(current_status); | ||
| 571 | |||
| 572 | complete_event.Set(); | ||
| 573 | } | ||
| 574 | }}; | ||
| 575 | Socket socket{host, port, std::move(callback)}; | ||
| 576 | std::thread worker_thread{SocketLoop, &socket}; | ||
| 577 | complete_event.Wait(); | ||
| 578 | socket.Stop(); | ||
| 579 | worker_thread.join(); | ||
| 580 | }).detach(); | ||
| 581 | } | ||
| 582 | |||
| 583 | CalibrationConfigurationJob::~CalibrationConfigurationJob() { | ||
| 584 | Stop(); | ||
| 585 | } | ||
| 586 | |||
| 587 | void CalibrationConfigurationJob::Stop() { | ||
| 588 | complete_event.Set(); | ||
| 589 | } | ||
| 590 | |||
| 591 | } // namespace InputCommon::CemuhookUDP | ||
diff --git a/src/input_common/drivers/udp_client.h b/src/input_common/drivers/udp_client.h new file mode 100644 index 000000000..1adc947c4 --- /dev/null +++ b/src/input_common/drivers/udp_client.h | |||
| @@ -0,0 +1,185 @@ | |||
| 1 | // Copyright 2018 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <optional> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "common/thread.h" | ||
| 11 | #include "input_common/input_engine.h" | ||
| 12 | |||
| 13 | namespace InputCommon::CemuhookUDP { | ||
| 14 | |||
| 15 | class Socket; | ||
| 16 | |||
| 17 | namespace Response { | ||
| 18 | struct PadData; | ||
| 19 | struct PortInfo; | ||
| 20 | struct TouchPad; | ||
| 21 | struct Version; | ||
| 22 | } // namespace Response | ||
| 23 | |||
| 24 | enum class PadTouch { | ||
| 25 | Click, | ||
| 26 | Undefined, | ||
| 27 | }; | ||
| 28 | |||
| 29 | struct UDPPadStatus { | ||
| 30 | std::string host{"127.0.0.1"}; | ||
| 31 | u16 port{26760}; | ||
| 32 | std::size_t pad_index{}; | ||
| 33 | }; | ||
| 34 | |||
| 35 | struct DeviceStatus { | ||
| 36 | std::mutex update_mutex; | ||
| 37 | |||
| 38 | // calibration data for scaling the device's touch area to 3ds | ||
| 39 | struct CalibrationData { | ||
| 40 | u16 min_x{}; | ||
| 41 | u16 min_y{}; | ||
| 42 | u16 max_x{}; | ||
| 43 | u16 max_y{}; | ||
| 44 | }; | ||
| 45 | std::optional<CalibrationData> touch_calibration; | ||
| 46 | }; | ||
| 47 | |||
| 48 | /** | ||
| 49 | * A button device factory representing a keyboard. It receives keyboard events and forward them | ||
| 50 | * to all button devices it created. | ||
| 51 | */ | ||
| 52 | class UDPClient final : public InputEngine { | ||
| 53 | public: | ||
| 54 | explicit UDPClient(std::string input_engine_); | ||
| 55 | ~UDPClient() override; | ||
| 56 | |||
| 57 | void ReloadSockets(); | ||
| 58 | |||
| 59 | /// Used for automapping features | ||
| 60 | std::vector<Common::ParamPackage> GetInputDevices() const override; | ||
| 61 | ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; | ||
| 62 | AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; | ||
| 63 | MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; | ||
| 64 | Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; | ||
| 65 | |||
| 66 | private: | ||
| 67 | enum class PadButton { | ||
| 68 | Undefined = 0x0000, | ||
| 69 | Share = 0x0001, | ||
| 70 | L3 = 0x0002, | ||
| 71 | R3 = 0x0004, | ||
| 72 | Options = 0x0008, | ||
| 73 | Up = 0x0010, | ||
| 74 | Right = 0x0020, | ||
| 75 | Down = 0x0040, | ||
| 76 | Left = 0x0080, | ||
| 77 | L2 = 0x0100, | ||
| 78 | R2 = 0x0200, | ||
| 79 | L1 = 0x0400, | ||
| 80 | R1 = 0x0800, | ||
| 81 | Triangle = 0x1000, | ||
| 82 | Circle = 0x2000, | ||
| 83 | Cross = 0x4000, | ||
| 84 | Square = 0x8000, | ||
| 85 | Touch1 = 0x10000, | ||
| 86 | touch2 = 0x20000, | ||
| 87 | }; | ||
| 88 | |||
| 89 | enum class PadAxes : u8 { | ||
| 90 | LeftStickX, | ||
| 91 | LeftStickY, | ||
| 92 | RightStickX, | ||
| 93 | RightStickY, | ||
| 94 | AnalogLeft, | ||
| 95 | AnalogDown, | ||
| 96 | AnalogRight, | ||
| 97 | AnalogUp, | ||
| 98 | AnalogSquare, | ||
| 99 | AnalogCross, | ||
| 100 | AnalogCircle, | ||
| 101 | AnalogTriangle, | ||
| 102 | AnalogR1, | ||
| 103 | AnalogL1, | ||
| 104 | AnalogR2, | ||
| 105 | AnalogL3, | ||
| 106 | AnalogR3, | ||
| 107 | Touch1X, | ||
| 108 | Touch1Y, | ||
| 109 | Touch2X, | ||
| 110 | Touch2Y, | ||
| 111 | Undefined, | ||
| 112 | }; | ||
| 113 | |||
| 114 | struct PadData { | ||
| 115 | std::size_t pad_index{}; | ||
| 116 | bool connected{}; | ||
| 117 | DeviceStatus status; | ||
| 118 | u64 packet_sequence{}; | ||
| 119 | |||
| 120 | std::chrono::time_point<std::chrono::steady_clock> last_update; | ||
| 121 | }; | ||
| 122 | |||
| 123 | struct ClientConnection { | ||
| 124 | ClientConnection(); | ||
| 125 | ~ClientConnection(); | ||
| 126 | Common::UUID uuid{"7F000001"}; | ||
| 127 | std::string host{"127.0.0.1"}; | ||
| 128 | u16 port{26760}; | ||
| 129 | s8 active{-1}; | ||
| 130 | std::unique_ptr<Socket> socket; | ||
| 131 | std::thread thread; | ||
| 132 | }; | ||
| 133 | |||
| 134 | // For shutting down, clear all data, join all threads, release usb | ||
| 135 | void Reset(); | ||
| 136 | |||
| 137 | // Translates configuration to client number | ||
| 138 | std::size_t GetClientNumber(std::string_view host, u16 port) const; | ||
| 139 | |||
| 140 | void OnVersion(Response::Version); | ||
| 141 | void OnPortInfo(Response::PortInfo); | ||
| 142 | void OnPadData(Response::PadData, std::size_t client); | ||
| 143 | void StartCommunication(std::size_t client, const std::string& host, u16 port); | ||
| 144 | const PadIdentifier GetPadIdentifier(std::size_t pad_index) const; | ||
| 145 | const Common::UUID GetHostUUID(const std::string host) const; | ||
| 146 | |||
| 147 | Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; | ||
| 148 | |||
| 149 | // Allocate clients for 8 udp servers | ||
| 150 | static constexpr std::size_t MAX_UDP_CLIENTS = 8; | ||
| 151 | static constexpr std::size_t PADS_PER_CLIENT = 4; | ||
| 152 | std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{}; | ||
| 153 | std::array<ClientConnection, MAX_UDP_CLIENTS> clients{}; | ||
| 154 | }; | ||
| 155 | |||
| 156 | /// An async job allowing configuration of the touchpad calibration. | ||
| 157 | class CalibrationConfigurationJob { | ||
| 158 | public: | ||
| 159 | enum class Status { | ||
| 160 | Initialized, | ||
| 161 | Ready, | ||
| 162 | Stage1Completed, | ||
| 163 | Completed, | ||
| 164 | }; | ||
| 165 | /** | ||
| 166 | * Constructs and starts the job with the specified parameter. | ||
| 167 | * | ||
| 168 | * @param status_callback Callback for job status updates | ||
| 169 | * @param data_callback Called when calibration data is ready | ||
| 170 | */ | ||
| 171 | explicit CalibrationConfigurationJob(const std::string& host, u16 port, | ||
| 172 | std::function<void(Status)> status_callback, | ||
| 173 | std::function<void(u16, u16, u16, u16)> data_callback); | ||
| 174 | ~CalibrationConfigurationJob(); | ||
| 175 | void Stop(); | ||
| 176 | |||
| 177 | private: | ||
| 178 | Common::Event complete_event; | ||
| 179 | }; | ||
| 180 | |||
| 181 | void TestCommunication(const std::string& host, u16 port, | ||
| 182 | const std::function<void()>& success_callback, | ||
| 183 | const std::function<void()>& failure_callback); | ||
| 184 | |||
| 185 | } // namespace InputCommon::CemuhookUDP | ||