diff options
| author | 2020-07-04 10:05:59 -0400 | |
|---|---|---|
| committer | 2020-07-04 10:05:59 -0400 | |
| commit | 9f8e17cb185d0d769ab81ef8de906cef37947ea5 (patch) | |
| tree | 0ae185ce3ef43ef9b085aae7b9ad5abb04e3d239 | |
| parent | Merge pull request #4218 from ogniK5377/opus-external (diff) | |
| parent | Fix merge conflicts? (diff) | |
| download | yuzu-9f8e17cb185d0d769ab81ef8de906cef37947ea5.tar.gz yuzu-9f8e17cb185d0d769ab81ef8de906cef37947ea5.tar.xz yuzu-9f8e17cb185d0d769ab81ef8de906cef37947ea5.zip | |
Merge pull request #4137 from ameerj/master
GC Adapter Implementation
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rw-r--r-- | CMakeLists.txt | 6 | ||||
| m--------- | externals/libusb | 0 | ||||
| -rw-r--r-- | src/input_common/CMakeLists.txt | 6 | ||||
| -rw-r--r-- | src/input_common/gcadapter/gc_adapter.cpp | 379 | ||||
| -rw-r--r-- | src/input_common/gcadapter/gc_adapter.h | 160 | ||||
| -rw-r--r-- | src/input_common/gcadapter/gc_poller.cpp | 272 | ||||
| -rw-r--r-- | src/input_common/gcadapter/gc_poller.h | 67 | ||||
| -rw-r--r-- | src/input_common/main.cpp | 24 | ||||
| -rw-r--r-- | src/input_common/main.h | 5 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_input_player.cpp | 60 |
11 files changed, 980 insertions, 2 deletions
diff --git a/.gitmodules b/.gitmodules index 6fa823c1c..79028bbb5 100644 --- a/.gitmodules +++ b/.gitmodules | |||
| @@ -34,6 +34,9 @@ | |||
| 34 | [submodule "xbyak"] | 34 | [submodule "xbyak"] |
| 35 | path = externals/xbyak | 35 | path = externals/xbyak |
| 36 | url = https://github.com/herumi/xbyak.git | 36 | url = https://github.com/herumi/xbyak.git |
| 37 | [submodule "externals/libusb"] | ||
| 38 | path = externals/libusb | ||
| 39 | url = https://github.com/ameerj/libusb | ||
| 37 | [submodule "opus"] | 40 | [submodule "opus"] |
| 38 | path = externals/opus/opus | 41 | path = externals/opus/opus |
| 39 | url = https://github.com/xiph/opus.git | 42 | url = https://github.com/xiph/opus.git |
diff --git a/CMakeLists.txt b/CMakeLists.txt index d0af994da..27383bce8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
| @@ -329,6 +329,12 @@ elseif(SDL2_FOUND) | |||
| 329 | target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARIES}") | 329 | target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARIES}") |
| 330 | endif() | 330 | endif() |
| 331 | 331 | ||
| 332 | # Ensure libusb is properly configured (based on dolphin libusb include) | ||
| 333 | find_package(LibUSB) | ||
| 334 | add_subdirectory(externals/libusb) | ||
| 335 | set(LIBUSB_LIBRARIES usb) | ||
| 336 | |||
| 337 | |||
| 332 | # Prefer the -pthread flag on Linux. | 338 | # Prefer the -pthread flag on Linux. |
| 333 | set(THREADS_PREFER_PTHREAD_FLAG ON) | 339 | set(THREADS_PREFER_PTHREAD_FLAG ON) |
| 334 | find_package(Threads REQUIRED) | 340 | find_package(Threads REQUIRED) |
diff --git a/externals/libusb b/externals/libusb new file mode 160000 | |||
| Subproject 3406d72cda879f8792a88bf5f6bd0b7a65636f7 | |||
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index a9c2392b1..3bd76dd23 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt | |||
| @@ -7,6 +7,10 @@ add_library(input_common STATIC | |||
| 7 | main.h | 7 | main.h |
| 8 | motion_emu.cpp | 8 | motion_emu.cpp |
| 9 | motion_emu.h | 9 | motion_emu.h |
| 10 | gcadapter/gc_adapter.cpp | ||
| 11 | gcadapter/gc_adapter.h | ||
| 12 | gcadapter/gc_poller.cpp | ||
| 13 | gcadapter/gc_poller.h | ||
| 10 | sdl/sdl.cpp | 14 | sdl/sdl.cpp |
| 11 | sdl/sdl.h | 15 | sdl/sdl.h |
| 12 | udp/client.cpp | 16 | udp/client.cpp |
| @@ -26,5 +30,7 @@ if(SDL2_FOUND) | |||
| 26 | target_compile_definitions(input_common PRIVATE HAVE_SDL2) | 30 | target_compile_definitions(input_common PRIVATE HAVE_SDL2) |
| 27 | endif() | 31 | endif() |
| 28 | 32 | ||
| 33 | target_link_libraries(input_common PUBLIC ${LIBUSB_LIBRARIES}) | ||
| 34 | |||
| 29 | create_target_directory_groups(input_common) | 35 | create_target_directory_groups(input_common) |
| 30 | target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost) | 36 | target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost) |
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp new file mode 100644 index 000000000..b39d2a3fb --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.cpp | |||
| @@ -0,0 +1,379 @@ | |||
| 1 | // Copyright 2014 Dolphin Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <chrono> | ||
| 6 | #include <thread> | ||
| 7 | #include "common/logging/log.h" | ||
| 8 | #include "input_common/gcadapter/gc_adapter.h" | ||
| 9 | |||
| 10 | namespace GCAdapter { | ||
| 11 | |||
| 12 | /// Used to loop through and assign button in poller | ||
| 13 | constexpr std::array<PadButton, 12> PadButtonArray{ | ||
| 14 | PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN, | ||
| 15 | PadButton::PAD_BUTTON_UP, PadButton::PAD_TRIGGER_Z, PadButton::PAD_TRIGGER_R, | ||
| 16 | PadButton::PAD_TRIGGER_L, PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, | ||
| 17 | PadButton::PAD_BUTTON_X, PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_START, | ||
| 18 | }; | ||
| 19 | |||
| 20 | Adapter::Adapter() { | ||
| 21 | if (usb_adapter_handle != nullptr) { | ||
| 22 | return; | ||
| 23 | } | ||
| 24 | LOG_INFO(Input, "GC Adapter Initialization started"); | ||
| 25 | |||
| 26 | current_status = NO_ADAPTER_DETECTED; | ||
| 27 | libusb_init(&libusb_ctx); | ||
| 28 | |||
| 29 | StartScanThread(); | ||
| 30 | } | ||
| 31 | |||
| 32 | GCPadStatus Adapter::GetPadStatus(int port, const std::array<u8, 37>& adapter_payload) { | ||
| 33 | GCPadStatus pad = {}; | ||
| 34 | bool get_origin = false; | ||
| 35 | |||
| 36 | ControllerTypes type = ControllerTypes(adapter_payload[1 + (9 * port)] >> 4); | ||
| 37 | if (type != ControllerTypes::None) { | ||
| 38 | get_origin = true; | ||
| 39 | } | ||
| 40 | |||
| 41 | adapter_controllers_status[port] = type; | ||
| 42 | |||
| 43 | static constexpr std::array<PadButton, 8> b1_buttons{ | ||
| 44 | PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X, | ||
| 45 | PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, | ||
| 46 | PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP, | ||
| 47 | }; | ||
| 48 | |||
| 49 | static constexpr std::array<PadButton, 4> b2_buttons{ | ||
| 50 | PadButton::PAD_BUTTON_START, | ||
| 51 | PadButton::PAD_TRIGGER_Z, | ||
| 52 | PadButton::PAD_TRIGGER_R, | ||
| 53 | PadButton::PAD_TRIGGER_L, | ||
| 54 | }; | ||
| 55 | |||
| 56 | if (adapter_controllers_status[port] != ControllerTypes::None) { | ||
| 57 | const u8 b1 = adapter_payload[1 + (9 * port) + 1]; | ||
| 58 | const u8 b2 = adapter_payload[1 + (9 * port) + 2]; | ||
| 59 | |||
| 60 | for (std::size_t i = 0; i < b1_buttons.size(); ++i) { | ||
| 61 | if ((b1 & (1U << i)) != 0) { | ||
| 62 | pad.button |= static_cast<u16>(b1_buttons[i]); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | for (std::size_t j = 0; j < b2_buttons.size(); ++j) { | ||
| 67 | if ((b2 & (1U << j)) != 0) { | ||
| 68 | pad.button |= static_cast<u16>(b2_buttons[j]); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | if (get_origin) { | ||
| 73 | pad.button |= PAD_GET_ORIGIN; | ||
| 74 | } | ||
| 75 | |||
| 76 | pad.stick_x = adapter_payload[1 + (9 * port) + 3]; | ||
| 77 | pad.stick_y = adapter_payload[1 + (9 * port) + 4]; | ||
| 78 | pad.substick_x = adapter_payload[1 + (9 * port) + 5]; | ||
| 79 | pad.substick_y = adapter_payload[1 + (9 * port) + 6]; | ||
| 80 | pad.trigger_left = adapter_payload[1 + (9 * port) + 7]; | ||
| 81 | pad.trigger_right = adapter_payload[1 + (9 * port) + 8]; | ||
| 82 | } | ||
| 83 | return pad; | ||
| 84 | } | ||
| 85 | |||
| 86 | void Adapter::PadToState(const GCPadStatus& pad, GCState& state) { | ||
| 87 | for (const auto& button : PadButtonArray) { | ||
| 88 | const u16 button_value = static_cast<u16>(button); | ||
| 89 | state.buttons.insert_or_assign(button_value, pad.button & button_value); | ||
| 90 | } | ||
| 91 | |||
| 92 | state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickX), pad.stick_x); | ||
| 93 | state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickY), pad.stick_y); | ||
| 94 | state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickX), pad.substick_x); | ||
| 95 | state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickY), pad.substick_y); | ||
| 96 | state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerLeft), pad.trigger_left); | ||
| 97 | state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerRight), pad.trigger_right); | ||
| 98 | } | ||
| 99 | |||
| 100 | void Adapter::Read() { | ||
| 101 | LOG_DEBUG(Input, "GC Adapter Read() thread started"); | ||
| 102 | |||
| 103 | int payload_size_in, payload_size_copy; | ||
| 104 | std::array<u8, 37> adapter_payload; | ||
| 105 | std::array<u8, 37> adapter_payload_copy; | ||
| 106 | std::array<GCPadStatus, 4> pads; | ||
| 107 | |||
| 108 | while (adapter_thread_running) { | ||
| 109 | libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), | ||
| 110 | sizeof(adapter_payload), &payload_size_in, 16); | ||
| 111 | payload_size_copy = 0; | ||
| 112 | // this mutex might be redundant? | ||
| 113 | { | ||
| 114 | std::lock_guard<std::mutex> lk(s_mutex); | ||
| 115 | std::copy(std::begin(adapter_payload), std::end(adapter_payload), | ||
| 116 | std::begin(adapter_payload_copy)); | ||
| 117 | payload_size_copy = payload_size_in; | ||
| 118 | } | ||
| 119 | |||
| 120 | if (payload_size_copy != sizeof(adapter_payload_copy) || | ||
| 121 | adapter_payload_copy[0] != LIBUSB_DT_HID) { | ||
| 122 | LOG_ERROR(Input, "error reading payload (size: {}, type: {:02x})", payload_size_copy, | ||
| 123 | adapter_payload_copy[0]); | ||
| 124 | adapter_thread_running = false; // error reading from adapter, stop reading. | ||
| 125 | break; | ||
| 126 | } | ||
| 127 | for (std::size_t port = 0; port < pads.size(); ++port) { | ||
| 128 | pads[port] = GetPadStatus(port, adapter_payload_copy); | ||
| 129 | if (DeviceConnected(port) && configuring) { | ||
| 130 | if (pads[port].button != PAD_GET_ORIGIN) { | ||
| 131 | pad_queue[port].Push(pads[port]); | ||
| 132 | } | ||
| 133 | |||
| 134 | // Accounting for a threshold here because of some controller variance | ||
| 135 | if (pads[port].stick_x > pads[port].MAIN_STICK_CENTER_X + pads[port].THRESHOLD || | ||
| 136 | pads[port].stick_x < pads[port].MAIN_STICK_CENTER_X - pads[port].THRESHOLD) { | ||
| 137 | pads[port].axis = GCAdapter::PadAxes::StickX; | ||
| 138 | pads[port].axis_value = pads[port].stick_x; | ||
| 139 | pad_queue[port].Push(pads[port]); | ||
| 140 | } | ||
| 141 | if (pads[port].stick_y > pads[port].MAIN_STICK_CENTER_Y + pads[port].THRESHOLD || | ||
| 142 | pads[port].stick_y < pads[port].MAIN_STICK_CENTER_Y - pads[port].THRESHOLD) { | ||
| 143 | pads[port].axis = GCAdapter::PadAxes::StickY; | ||
| 144 | pads[port].axis_value = pads[port].stick_y; | ||
| 145 | pad_queue[port].Push(pads[port]); | ||
| 146 | } | ||
| 147 | if (pads[port].substick_x > pads[port].C_STICK_CENTER_X + pads[port].THRESHOLD || | ||
| 148 | pads[port].substick_x < pads[port].C_STICK_CENTER_X - pads[port].THRESHOLD) { | ||
| 149 | pads[port].axis = GCAdapter::PadAxes::SubstickX; | ||
| 150 | pads[port].axis_value = pads[port].substick_x; | ||
| 151 | pad_queue[port].Push(pads[port]); | ||
| 152 | } | ||
| 153 | if (pads[port].substick_y > pads[port].C_STICK_CENTER_Y + pads[port].THRESHOLD || | ||
| 154 | pads[port].substick_y < pads[port].C_STICK_CENTER_Y - pads[port].THRESHOLD) { | ||
| 155 | pads[port].axis = GCAdapter::PadAxes::SubstickY; | ||
| 156 | pads[port].axis_value = pads[port].substick_y; | ||
| 157 | pad_queue[port].Push(pads[port]); | ||
| 158 | } | ||
| 159 | if (pads[port].trigger_left > pads[port].TRIGGER_THRESHOLD) { | ||
| 160 | pads[port].axis = GCAdapter::PadAxes::TriggerLeft; | ||
| 161 | pads[port].axis_value = pads[port].trigger_left; | ||
| 162 | pad_queue[port].Push(pads[port]); | ||
| 163 | } | ||
| 164 | if (pads[port].trigger_right > pads[port].TRIGGER_THRESHOLD) { | ||
| 165 | pads[port].axis = GCAdapter::PadAxes::TriggerRight; | ||
| 166 | pads[port].axis_value = pads[port].trigger_right; | ||
| 167 | pad_queue[port].Push(pads[port]); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | PadToState(pads[port], state[port]); | ||
| 171 | } | ||
| 172 | std::this_thread::yield(); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | void Adapter::ScanThreadFunc() { | ||
| 177 | LOG_INFO(Input, "GC Adapter scanning thread started"); | ||
| 178 | |||
| 179 | while (detect_thread_running) { | ||
| 180 | if (usb_adapter_handle == nullptr) { | ||
| 181 | std::lock_guard<std::mutex> lk(initialization_mutex); | ||
| 182 | Setup(); | ||
| 183 | } | ||
| 184 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | void Adapter::StartScanThread() { | ||
| 189 | if (detect_thread_running) { | ||
| 190 | return; | ||
| 191 | } | ||
| 192 | if (!libusb_ctx) { | ||
| 193 | return; | ||
| 194 | } | ||
| 195 | |||
| 196 | detect_thread_running = true; | ||
| 197 | detect_thread = std::thread([=] { ScanThreadFunc(); }); | ||
| 198 | } | ||
| 199 | |||
| 200 | void Adapter::StopScanThread() { | ||
| 201 | detect_thread_running = false; | ||
| 202 | detect_thread.join(); | ||
| 203 | } | ||
| 204 | |||
| 205 | void Adapter::Setup() { | ||
| 206 | // Reset the error status in case the adapter gets unplugged | ||
| 207 | if (current_status < 0) { | ||
| 208 | current_status = NO_ADAPTER_DETECTED; | ||
| 209 | } | ||
| 210 | |||
| 211 | adapter_controllers_status.fill(ControllerTypes::None); | ||
| 212 | |||
| 213 | // pointer to list of connected usb devices | ||
| 214 | libusb_device** devices; | ||
| 215 | |||
| 216 | // populate the list of devices, get the count | ||
| 217 | const std::size_t device_count = libusb_get_device_list(libusb_ctx, &devices); | ||
| 218 | |||
| 219 | for (std::size_t index = 0; index < device_count; ++index) { | ||
| 220 | if (CheckDeviceAccess(devices[index])) { | ||
| 221 | // GC Adapter found and accessible, registering it | ||
| 222 | GetGCEndpoint(devices[index]); | ||
| 223 | break; | ||
| 224 | } | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | bool Adapter::CheckDeviceAccess(libusb_device* device) { | ||
| 229 | libusb_device_descriptor desc; | ||
| 230 | const int get_descriptor_error = libusb_get_device_descriptor(device, &desc); | ||
| 231 | if (get_descriptor_error) { | ||
| 232 | // could not acquire the descriptor, no point in trying to use it. | ||
| 233 | LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}", | ||
| 234 | get_descriptor_error); | ||
| 235 | return false; | ||
| 236 | } | ||
| 237 | |||
| 238 | if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) { | ||
| 239 | // This isn't the device we are looking for. | ||
| 240 | return false; | ||
| 241 | } | ||
| 242 | const int open_error = libusb_open(device, &usb_adapter_handle); | ||
| 243 | |||
| 244 | if (open_error == LIBUSB_ERROR_ACCESS) { | ||
| 245 | LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.", | ||
| 246 | desc.idVendor, desc.idProduct); | ||
| 247 | return false; | ||
| 248 | } | ||
| 249 | if (open_error) { | ||
| 250 | LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error); | ||
| 251 | return false; | ||
| 252 | } | ||
| 253 | |||
| 254 | int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); | ||
| 255 | if (kernel_driver_error == 1) { | ||
| 256 | kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0); | ||
| 257 | if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { | ||
| 258 | LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}", | ||
| 259 | kernel_driver_error); | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { | ||
| 264 | libusb_close(usb_adapter_handle); | ||
| 265 | usb_adapter_handle = nullptr; | ||
| 266 | return false; | ||
| 267 | } | ||
| 268 | |||
| 269 | const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0); | ||
| 270 | if (interface_claim_error) { | ||
| 271 | LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); | ||
| 272 | libusb_close(usb_adapter_handle); | ||
| 273 | usb_adapter_handle = nullptr; | ||
| 274 | return false; | ||
| 275 | } | ||
| 276 | |||
| 277 | return true; | ||
| 278 | } | ||
| 279 | |||
| 280 | void Adapter::GetGCEndpoint(libusb_device* device) { | ||
| 281 | libusb_config_descriptor* config = nullptr; | ||
| 282 | libusb_get_config_descriptor(device, 0, &config); | ||
| 283 | for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { | ||
| 284 | const libusb_interface* interfaceContainer = &config->interface[ic]; | ||
| 285 | for (int i = 0; i < interfaceContainer->num_altsetting; i++) { | ||
| 286 | const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i]; | ||
| 287 | for (u8 e = 0; e < interface->bNumEndpoints; e++) { | ||
| 288 | const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; | ||
| 289 | if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) { | ||
| 290 | input_endpoint = endpoint->bEndpointAddress; | ||
| 291 | } else { | ||
| 292 | output_endpoint = endpoint->bEndpointAddress; | ||
| 293 | } | ||
| 294 | } | ||
| 295 | } | ||
| 296 | } | ||
| 297 | // This transfer seems to be responsible for clearing the state of the adapter | ||
| 298 | // Used to clear the "busy" state of when the device is unexpectedly unplugged | ||
| 299 | unsigned char clear_payload = 0x13; | ||
| 300 | libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload, | ||
| 301 | sizeof(clear_payload), nullptr, 16); | ||
| 302 | |||
| 303 | adapter_thread_running = true; | ||
| 304 | current_status = ADAPTER_DETECTED; | ||
| 305 | adapter_input_thread = std::thread([=] { Read(); }); // Read input | ||
| 306 | } | ||
| 307 | |||
| 308 | Adapter::~Adapter() { | ||
| 309 | StopScanThread(); | ||
| 310 | Reset(); | ||
| 311 | } | ||
| 312 | |||
| 313 | void Adapter::Reset() { | ||
| 314 | std::unique_lock<std::mutex> lock(initialization_mutex, std::defer_lock); | ||
| 315 | if (!lock.try_lock()) { | ||
| 316 | return; | ||
| 317 | } | ||
| 318 | if (current_status != ADAPTER_DETECTED) { | ||
| 319 | return; | ||
| 320 | } | ||
| 321 | |||
| 322 | if (adapter_thread_running) { | ||
| 323 | adapter_thread_running = false; | ||
| 324 | } | ||
| 325 | adapter_input_thread.join(); | ||
| 326 | |||
| 327 | adapter_controllers_status.fill(ControllerTypes::None); | ||
| 328 | current_status = NO_ADAPTER_DETECTED; | ||
| 329 | |||
| 330 | if (usb_adapter_handle) { | ||
| 331 | libusb_release_interface(usb_adapter_handle, 1); | ||
| 332 | libusb_close(usb_adapter_handle); | ||
| 333 | usb_adapter_handle = nullptr; | ||
| 334 | } | ||
| 335 | |||
| 336 | if (libusb_ctx) { | ||
| 337 | libusb_exit(libusb_ctx); | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | bool Adapter::DeviceConnected(int port) { | ||
| 342 | return adapter_controllers_status[port] != ControllerTypes::None; | ||
| 343 | } | ||
| 344 | |||
| 345 | void Adapter::ResetDeviceType(int port) { | ||
| 346 | adapter_controllers_status[port] = ControllerTypes::None; | ||
| 347 | } | ||
| 348 | |||
| 349 | void Adapter::BeginConfiguration() { | ||
| 350 | for (auto& pq : pad_queue) { | ||
| 351 | pq.Clear(); | ||
| 352 | } | ||
| 353 | configuring = true; | ||
| 354 | } | ||
| 355 | |||
| 356 | void Adapter::EndConfiguration() { | ||
| 357 | for (auto& pq : pad_queue) { | ||
| 358 | pq.Clear(); | ||
| 359 | } | ||
| 360 | configuring = false; | ||
| 361 | } | ||
| 362 | |||
| 363 | std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() { | ||
| 364 | return pad_queue; | ||
| 365 | } | ||
| 366 | |||
| 367 | const std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() const { | ||
| 368 | return pad_queue; | ||
| 369 | } | ||
| 370 | |||
| 371 | std::array<GCState, 4>& Adapter::GetPadState() { | ||
| 372 | return state; | ||
| 373 | } | ||
| 374 | |||
| 375 | const std::array<GCState, 4>& Adapter::GetPadState() const { | ||
| 376 | return state; | ||
| 377 | } | ||
| 378 | |||
| 379 | } // namespace GCAdapter | ||
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h new file mode 100644 index 000000000..0ea6263eb --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.h | |||
| @@ -0,0 +1,160 @@ | |||
| 1 | // Copyright 2014 Dolphin Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | #include <algorithm> | ||
| 7 | #include <functional> | ||
| 8 | #include <mutex> | ||
| 9 | #include <thread> | ||
| 10 | #include <libusb.h> | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/threadsafe_queue.h" | ||
| 13 | |||
| 14 | namespace GCAdapter { | ||
| 15 | |||
| 16 | enum { | ||
| 17 | PAD_USE_ORIGIN = 0x0080, | ||
| 18 | PAD_GET_ORIGIN = 0x2000, | ||
| 19 | PAD_ERR_STATUS = 0x8000, | ||
| 20 | }; | ||
| 21 | |||
| 22 | enum class PadButton { | ||
| 23 | PAD_BUTTON_LEFT = 0x0001, | ||
| 24 | PAD_BUTTON_RIGHT = 0x0002, | ||
| 25 | PAD_BUTTON_DOWN = 0x0004, | ||
| 26 | PAD_BUTTON_UP = 0x0008, | ||
| 27 | PAD_TRIGGER_Z = 0x0010, | ||
| 28 | PAD_TRIGGER_R = 0x0020, | ||
| 29 | PAD_TRIGGER_L = 0x0040, | ||
| 30 | PAD_BUTTON_A = 0x0100, | ||
| 31 | PAD_BUTTON_B = 0x0200, | ||
| 32 | PAD_BUTTON_X = 0x0400, | ||
| 33 | PAD_BUTTON_Y = 0x0800, | ||
| 34 | PAD_BUTTON_START = 0x1000, | ||
| 35 | // Below is for compatibility with "AxisButton" type | ||
| 36 | PAD_STICK = 0x2000, | ||
| 37 | }; | ||
| 38 | |||
| 39 | extern const std::array<PadButton, 12> PadButtonArray; | ||
| 40 | |||
| 41 | enum class PadAxes : u8 { | ||
| 42 | StickX, | ||
| 43 | StickY, | ||
| 44 | SubstickX, | ||
| 45 | SubstickY, | ||
| 46 | TriggerLeft, | ||
| 47 | TriggerRight, | ||
| 48 | Undefined, | ||
| 49 | }; | ||
| 50 | |||
| 51 | struct GCPadStatus { | ||
| 52 | u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits | ||
| 53 | u8 stick_x{}; // 0 <= stick_x <= 255 | ||
| 54 | u8 stick_y{}; // 0 <= stick_y <= 255 | ||
| 55 | u8 substick_x{}; // 0 <= substick_x <= 255 | ||
| 56 | u8 substick_y{}; // 0 <= substick_y <= 255 | ||
| 57 | u8 trigger_left{}; // 0 <= trigger_left <= 255 | ||
| 58 | u8 trigger_right{}; // 0 <= trigger_right <= 255 | ||
| 59 | |||
| 60 | static constexpr u8 MAIN_STICK_CENTER_X = 0x80; | ||
| 61 | static constexpr u8 MAIN_STICK_CENTER_Y = 0x80; | ||
| 62 | static constexpr u8 MAIN_STICK_RADIUS = 0x7f; | ||
| 63 | static constexpr u8 C_STICK_CENTER_X = 0x80; | ||
| 64 | static constexpr u8 C_STICK_CENTER_Y = 0x80; | ||
| 65 | static constexpr u8 C_STICK_RADIUS = 0x7f; | ||
| 66 | static constexpr u8 THRESHOLD = 10; | ||
| 67 | |||
| 68 | // 256/4, at least a quarter press to count as a press. For polling mostly | ||
| 69 | static constexpr u8 TRIGGER_THRESHOLD = 64; | ||
| 70 | |||
| 71 | u8 port{}; | ||
| 72 | PadAxes axis{PadAxes::Undefined}; | ||
| 73 | u8 axis_value{255}; | ||
| 74 | }; | ||
| 75 | |||
| 76 | struct GCState { | ||
| 77 | std::unordered_map<int, bool> buttons; | ||
| 78 | std::unordered_map<int, u16> axes; | ||
| 79 | }; | ||
| 80 | |||
| 81 | enum class ControllerTypes { None, Wired, Wireless }; | ||
| 82 | |||
| 83 | enum { | ||
| 84 | NO_ADAPTER_DETECTED = 0, | ||
| 85 | ADAPTER_DETECTED = 1, | ||
| 86 | }; | ||
| 87 | |||
| 88 | class Adapter { | ||
| 89 | public: | ||
| 90 | /// Initialize the GC Adapter capture and read sequence | ||
| 91 | Adapter(); | ||
| 92 | |||
| 93 | /// Close the adapter read thread and release the adapter | ||
| 94 | ~Adapter(); | ||
| 95 | /// Used for polling | ||
| 96 | void BeginConfiguration(); | ||
| 97 | void EndConfiguration(); | ||
| 98 | |||
| 99 | std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue(); | ||
| 100 | const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const; | ||
| 101 | |||
| 102 | std::array<GCState, 4>& GetPadState(); | ||
| 103 | const std::array<GCState, 4>& GetPadState() const; | ||
| 104 | |||
| 105 | private: | ||
| 106 | GCPadStatus GetPadStatus(int port, const std::array<u8, 37>& adapter_payload); | ||
| 107 | |||
| 108 | void PadToState(const GCPadStatus& pad, GCState& state); | ||
| 109 | |||
| 110 | void Read(); | ||
| 111 | void ScanThreadFunc(); | ||
| 112 | /// Begin scanning for the GC Adapter. | ||
| 113 | void StartScanThread(); | ||
| 114 | |||
| 115 | /// Stop scanning for the adapter | ||
| 116 | void StopScanThread(); | ||
| 117 | |||
| 118 | /// Returns true if there is a device connected to port | ||
| 119 | bool DeviceConnected(int port); | ||
| 120 | |||
| 121 | /// Resets status of device connected to port | ||
| 122 | void ResetDeviceType(int port); | ||
| 123 | |||
| 124 | /// Returns true if we successfully gain access to GC Adapter | ||
| 125 | bool CheckDeviceAccess(libusb_device* device); | ||
| 126 | |||
| 127 | /// Captures GC Adapter endpoint address, | ||
| 128 | void GetGCEndpoint(libusb_device* device); | ||
| 129 | |||
| 130 | /// For shutting down, clear all data, join all threads, release usb | ||
| 131 | void Reset(); | ||
| 132 | |||
| 133 | /// For use in initialization, querying devices to find the adapter | ||
| 134 | void Setup(); | ||
| 135 | |||
| 136 | int current_status = NO_ADAPTER_DETECTED; | ||
| 137 | libusb_device_handle* usb_adapter_handle = nullptr; | ||
| 138 | std::array<ControllerTypes, 4> adapter_controllers_status{}; | ||
| 139 | |||
| 140 | std::mutex s_mutex; | ||
| 141 | |||
| 142 | std::thread adapter_input_thread; | ||
| 143 | bool adapter_thread_running; | ||
| 144 | |||
| 145 | std::mutex initialization_mutex; | ||
| 146 | std::thread detect_thread; | ||
| 147 | bool detect_thread_running = false; | ||
| 148 | |||
| 149 | libusb_context* libusb_ctx; | ||
| 150 | |||
| 151 | u8 input_endpoint = 0; | ||
| 152 | u8 output_endpoint = 0; | ||
| 153 | |||
| 154 | bool configuring = false; | ||
| 155 | |||
| 156 | std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue; | ||
| 157 | std::array<GCState, 4> state; | ||
| 158 | }; | ||
| 159 | |||
| 160 | } // namespace GCAdapter | ||
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp new file mode 100644 index 000000000..385ce8430 --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.cpp | |||
| @@ -0,0 +1,272 @@ | |||
| 1 | // Copyright 2020 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <atomic> | ||
| 6 | #include <list> | ||
| 7 | #include <mutex> | ||
| 8 | #include <utility> | ||
| 9 | #include "common/threadsafe_queue.h" | ||
| 10 | #include "input_common/gcadapter/gc_adapter.h" | ||
| 11 | #include "input_common/gcadapter/gc_poller.h" | ||
| 12 | |||
| 13 | namespace InputCommon { | ||
| 14 | |||
| 15 | class GCButton final : public Input::ButtonDevice { | ||
| 16 | public: | ||
| 17 | explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter) | ||
| 18 | : port(port_), button(button_), gcadapter(adapter) {} | ||
| 19 | |||
| 20 | ~GCButton() override; | ||
| 21 | |||
| 22 | bool GetStatus() const override { | ||
| 23 | return gcadapter->GetPadState()[port].buttons.at(button); | ||
| 24 | } | ||
| 25 | |||
| 26 | private: | ||
| 27 | const int port; | ||
| 28 | const int button; | ||
| 29 | GCAdapter::Adapter* gcadapter; | ||
| 30 | }; | ||
| 31 | |||
| 32 | class GCAxisButton final : public Input::ButtonDevice { | ||
| 33 | public: | ||
| 34 | explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, | ||
| 35 | GCAdapter::Adapter* adapter) | ||
| 36 | : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), | ||
| 37 | gcadapter(adapter) { | ||
| 38 | // L/R triggers range is only in positive direction beginning near 0 | ||
| 39 | // 0.0 threshold equates to near half trigger press, but threshold accounts for variability. | ||
| 40 | if (axis > 3) { | ||
| 41 | threshold *= -0.5; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | bool GetStatus() const override { | ||
| 46 | const float axis_value = (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 128.0f; | ||
| 47 | if (trigger_if_greater) { | ||
| 48 | // TODO: Might be worthwile to set a slider for the trigger threshold. It is currently | ||
| 49 | // always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick | ||
| 50 | return axis_value > threshold; | ||
| 51 | } | ||
| 52 | return axis_value < -threshold; | ||
| 53 | } | ||
| 54 | |||
| 55 | private: | ||
| 56 | const int port; | ||
| 57 | const int axis; | ||
| 58 | float threshold; | ||
| 59 | bool trigger_if_greater; | ||
| 60 | GCAdapter::Adapter* gcadapter; | ||
| 61 | }; | ||
| 62 | |||
| 63 | GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) | ||
| 64 | : adapter(std::move(adapter_)) {} | ||
| 65 | |||
| 66 | GCButton::~GCButton() = default; | ||
| 67 | |||
| 68 | std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) { | ||
| 69 | const int button_id = params.Get("button", 0); | ||
| 70 | const int port = params.Get("port", 0); | ||
| 71 | |||
| 72 | constexpr int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_STICK); | ||
| 73 | |||
| 74 | // button is not an axis/stick button | ||
| 75 | if (button_id != PAD_STICK_ID) { | ||
| 76 | auto button = std::make_unique<GCButton>(port, button_id, adapter.get()); | ||
| 77 | return std::move(button); | ||
| 78 | } | ||
| 79 | |||
| 80 | // For Axis buttons, used by the binary sticks. | ||
| 81 | if (button_id == PAD_STICK_ID) { | ||
| 82 | const int axis = params.Get("axis", 0); | ||
| 83 | const float threshold = params.Get("threshold", 0.25f); | ||
| 84 | const std::string direction_name = params.Get("direction", ""); | ||
| 85 | bool trigger_if_greater; | ||
| 86 | if (direction_name == "+") { | ||
| 87 | trigger_if_greater = true; | ||
| 88 | } else if (direction_name == "-") { | ||
| 89 | trigger_if_greater = false; | ||
| 90 | } else { | ||
| 91 | trigger_if_greater = true; | ||
| 92 | LOG_ERROR(Input, "Unknown direction {}", direction_name); | ||
| 93 | } | ||
| 94 | return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater, | ||
| 95 | adapter.get()); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | Common::ParamPackage GCButtonFactory::GetNextInput() { | ||
| 100 | Common::ParamPackage params; | ||
| 101 | GCAdapter::GCPadStatus pad; | ||
| 102 | auto& queue = adapter->GetPadQueue(); | ||
| 103 | for (std::size_t port = 0; port < queue.size(); ++port) { | ||
| 104 | while (queue[port].Pop(pad)) { | ||
| 105 | // This while loop will break on the earliest detected button | ||
| 106 | params.Set("engine", "gcpad"); | ||
| 107 | params.Set("port", static_cast<int>(port)); | ||
| 108 | for (const auto& button : GCAdapter::PadButtonArray) { | ||
| 109 | const u16 button_value = static_cast<u16>(button); | ||
| 110 | if (pad.button & button_value) { | ||
| 111 | params.Set("button", button_value); | ||
| 112 | break; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | // For Axis button implementation | ||
| 117 | if (pad.axis != GCAdapter::PadAxes::Undefined) { | ||
| 118 | params.Set("axis", static_cast<u8>(pad.axis)); | ||
| 119 | params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK)); | ||
| 120 | if (pad.axis_value > 128) { | ||
| 121 | params.Set("direction", "+"); | ||
| 122 | params.Set("threshold", "0.25"); | ||
| 123 | } else { | ||
| 124 | params.Set("direction", "-"); | ||
| 125 | params.Set("threshold", "-0.25"); | ||
| 126 | } | ||
| 127 | break; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | return params; | ||
| 132 | } | ||
| 133 | |||
| 134 | void GCButtonFactory::BeginConfiguration() { | ||
| 135 | polling = true; | ||
| 136 | adapter->BeginConfiguration(); | ||
| 137 | } | ||
| 138 | |||
| 139 | void GCButtonFactory::EndConfiguration() { | ||
| 140 | polling = false; | ||
| 141 | adapter->EndConfiguration(); | ||
| 142 | } | ||
| 143 | |||
| 144 | class GCAnalog final : public Input::AnalogDevice { | ||
| 145 | public: | ||
| 146 | GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter) | ||
| 147 | : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter) {} | ||
| 148 | |||
| 149 | float GetAxis(int axis) const { | ||
| 150 | std::lock_guard lock{mutex}; | ||
| 151 | // division is not by a perfect 128 to account for some variance in center location | ||
| 152 | // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range | ||
| 153 | // [20-230] | ||
| 154 | return (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 95.0f; | ||
| 155 | } | ||
| 156 | |||
| 157 | std::pair<float, float> GetAnalog(int axis_x, int axis_y) const { | ||
| 158 | float x = GetAxis(axis_x); | ||
| 159 | float y = GetAxis(axis_y); | ||
| 160 | |||
| 161 | // Make sure the coordinates are in the unit circle, | ||
| 162 | // otherwise normalize it. | ||
| 163 | float r = x * x + y * y; | ||
| 164 | if (r > 1.0f) { | ||
| 165 | r = std::sqrt(r); | ||
| 166 | x /= r; | ||
| 167 | y /= r; | ||
| 168 | } | ||
| 169 | |||
| 170 | return {x, y}; | ||
| 171 | } | ||
| 172 | |||
| 173 | std::tuple<float, float> GetStatus() const override { | ||
| 174 | const auto [x, y] = GetAnalog(axis_x, axis_y); | ||
| 175 | const float r = std::sqrt((x * x) + (y * y)); | ||
| 176 | if (r > deadzone) { | ||
| 177 | return {x / r * (r - deadzone) / (1 - deadzone), | ||
| 178 | y / r * (r - deadzone) / (1 - deadzone)}; | ||
| 179 | } | ||
| 180 | return {0.0f, 0.0f}; | ||
| 181 | } | ||
| 182 | |||
| 183 | bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { | ||
| 184 | const auto [x, y] = GetStatus(); | ||
| 185 | const float directional_deadzone = 0.4f; | ||
| 186 | switch (direction) { | ||
| 187 | case Input::AnalogDirection::RIGHT: | ||
| 188 | return x > directional_deadzone; | ||
| 189 | case Input::AnalogDirection::LEFT: | ||
| 190 | return x < -directional_deadzone; | ||
| 191 | case Input::AnalogDirection::UP: | ||
| 192 | return y > directional_deadzone; | ||
| 193 | case Input::AnalogDirection::DOWN: | ||
| 194 | return y < -directional_deadzone; | ||
| 195 | } | ||
| 196 | return false; | ||
| 197 | } | ||
| 198 | |||
| 199 | private: | ||
| 200 | const int port; | ||
| 201 | const int axis_x; | ||
| 202 | const int axis_y; | ||
| 203 | const float deadzone; | ||
| 204 | mutable std::mutex mutex; | ||
| 205 | GCAdapter::Adapter* gcadapter; | ||
| 206 | }; | ||
| 207 | |||
| 208 | /// An analog device factory that creates analog devices from GC Adapter | ||
| 209 | GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) | ||
| 210 | : adapter(std::move(adapter_)) {} | ||
| 211 | |||
| 212 | /** | ||
| 213 | * Creates analog device from joystick axes | ||
| 214 | * @param params contains parameters for creating the device: | ||
| 215 | * - "port": the nth gcpad on the adapter | ||
| 216 | * - "axis_x": the index of the axis to be bind as x-axis | ||
| 217 | * - "axis_y": the index of the axis to be bind as y-axis | ||
| 218 | */ | ||
| 219 | std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) { | ||
| 220 | const int port = params.Get("port", 0); | ||
| 221 | const int axis_x = params.Get("axis_x", 0); | ||
| 222 | const int axis_y = params.Get("axis_y", 1); | ||
| 223 | const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); | ||
| 224 | |||
| 225 | return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get()); | ||
| 226 | } | ||
| 227 | |||
| 228 | void GCAnalogFactory::BeginConfiguration() { | ||
| 229 | polling = true; | ||
| 230 | adapter->BeginConfiguration(); | ||
| 231 | } | ||
| 232 | |||
| 233 | void GCAnalogFactory::EndConfiguration() { | ||
| 234 | polling = false; | ||
| 235 | adapter->EndConfiguration(); | ||
| 236 | } | ||
| 237 | |||
| 238 | Common::ParamPackage GCAnalogFactory::GetNextInput() { | ||
| 239 | GCAdapter::GCPadStatus pad; | ||
| 240 | auto& queue = adapter->GetPadQueue(); | ||
| 241 | for (std::size_t port = 0; port < queue.size(); ++port) { | ||
| 242 | while (queue[port].Pop(pad)) { | ||
| 243 | if (pad.axis == GCAdapter::PadAxes::Undefined || | ||
| 244 | std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.1) { | ||
| 245 | continue; | ||
| 246 | } | ||
| 247 | // An analog device needs two axes, so we need to store the axis for later and wait for | ||
| 248 | // a second input event. The axes also must be from the same joystick. | ||
| 249 | const u8 axis = static_cast<u8>(pad.axis); | ||
| 250 | if (analog_x_axis == -1) { | ||
| 251 | analog_x_axis = axis; | ||
| 252 | controller_number = port; | ||
| 253 | } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == port) { | ||
| 254 | analog_y_axis = axis; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | } | ||
| 258 | Common::ParamPackage params; | ||
| 259 | if (analog_x_axis != -1 && analog_y_axis != -1) { | ||
| 260 | params.Set("engine", "gcpad"); | ||
| 261 | params.Set("port", controller_number); | ||
| 262 | params.Set("axis_x", analog_x_axis); | ||
| 263 | params.Set("axis_y", analog_y_axis); | ||
| 264 | analog_x_axis = -1; | ||
| 265 | analog_y_axis = -1; | ||
| 266 | controller_number = -1; | ||
| 267 | return params; | ||
| 268 | } | ||
| 269 | return params; | ||
| 270 | } | ||
| 271 | |||
| 272 | } // namespace InputCommon | ||
diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h new file mode 100644 index 000000000..e96af7d51 --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.h | |||
| @@ -0,0 +1,67 @@ | |||
| 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 <memory> | ||
| 8 | #include "core/frontend/input.h" | ||
| 9 | #include "input_common/gcadapter/gc_adapter.h" | ||
| 10 | |||
| 11 | namespace InputCommon { | ||
| 12 | |||
| 13 | /** | ||
| 14 | * A button device factory representing a gcpad. It receives gcpad events and forward them | ||
| 15 | * to all button devices it created. | ||
| 16 | */ | ||
| 17 | class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> { | ||
| 18 | public: | ||
| 19 | explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Creates a button device from a button press | ||
| 23 | * @param params contains parameters for creating the device: | ||
| 24 | * - "code": the code of the key to bind with the button | ||
| 25 | */ | ||
| 26 | std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; | ||
| 27 | |||
| 28 | Common::ParamPackage GetNextInput(); | ||
| 29 | |||
| 30 | /// For device input configuration/polling | ||
| 31 | void BeginConfiguration(); | ||
| 32 | void EndConfiguration(); | ||
| 33 | |||
| 34 | bool IsPolling() const { | ||
| 35 | return polling; | ||
| 36 | } | ||
| 37 | |||
| 38 | private: | ||
| 39 | std::shared_ptr<GCAdapter::Adapter> adapter; | ||
| 40 | bool polling = false; | ||
| 41 | }; | ||
| 42 | |||
| 43 | /// An analog device factory that creates analog devices from GC Adapter | ||
| 44 | class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> { | ||
| 45 | public: | ||
| 46 | explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); | ||
| 47 | |||
| 48 | std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; | ||
| 49 | Common::ParamPackage GetNextInput(); | ||
| 50 | |||
| 51 | /// For device input configuration/polling | ||
| 52 | void BeginConfiguration(); | ||
| 53 | void EndConfiguration(); | ||
| 54 | |||
| 55 | bool IsPolling() const { | ||
| 56 | return polling; | ||
| 57 | } | ||
| 58 | |||
| 59 | private: | ||
| 60 | std::shared_ptr<GCAdapter::Adapter> adapter; | ||
| 61 | int analog_x_axis = -1; | ||
| 62 | int analog_y_axis = -1; | ||
| 63 | int controller_number = -1; | ||
| 64 | bool polling = false; | ||
| 65 | }; | ||
| 66 | |||
| 67 | } // namespace InputCommon | ||
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 95e351e24..fd0af1019 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp | |||
| @@ -4,8 +4,11 @@ | |||
| 4 | 4 | ||
| 5 | #include <memory> | 5 | #include <memory> |
| 6 | #include <thread> | 6 | #include <thread> |
| 7 | #include <libusb.h> | ||
| 7 | #include "common/param_package.h" | 8 | #include "common/param_package.h" |
| 8 | #include "input_common/analog_from_button.h" | 9 | #include "input_common/analog_from_button.h" |
| 10 | #include "input_common/gcadapter/gc_adapter.h" | ||
| 11 | #include "input_common/gcadapter/gc_poller.h" | ||
| 9 | #include "input_common/keyboard.h" | 12 | #include "input_common/keyboard.h" |
| 10 | #include "input_common/main.h" | 13 | #include "input_common/main.h" |
| 11 | #include "input_common/motion_emu.h" | 14 | #include "input_common/motion_emu.h" |
| @@ -22,8 +25,16 @@ static std::shared_ptr<MotionEmu> motion_emu; | |||
| 22 | static std::unique_ptr<SDL::State> sdl; | 25 | static std::unique_ptr<SDL::State> sdl; |
| 23 | #endif | 26 | #endif |
| 24 | static std::unique_ptr<CemuhookUDP::State> udp; | 27 | static std::unique_ptr<CemuhookUDP::State> udp; |
| 28 | static std::shared_ptr<GCButtonFactory> gcbuttons; | ||
| 29 | static std::shared_ptr<GCAnalogFactory> gcanalog; | ||
| 25 | 30 | ||
| 26 | void Init() { | 31 | void Init() { |
| 32 | auto gcadapter = std::make_shared<GCAdapter::Adapter>(); | ||
| 33 | gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); | ||
| 34 | Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); | ||
| 35 | gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); | ||
| 36 | Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog); | ||
| 37 | |||
| 27 | keyboard = std::make_shared<Keyboard>(); | 38 | keyboard = std::make_shared<Keyboard>(); |
| 28 | Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); | 39 | Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); |
| 29 | Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", | 40 | Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", |
| @@ -48,6 +59,11 @@ void Shutdown() { | |||
| 48 | sdl.reset(); | 59 | sdl.reset(); |
| 49 | #endif | 60 | #endif |
| 50 | udp.reset(); | 61 | udp.reset(); |
| 62 | Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); | ||
| 63 | Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); | ||
| 64 | |||
| 65 | gcbuttons.reset(); | ||
| 66 | gcanalog.reset(); | ||
| 51 | } | 67 | } |
| 52 | 68 | ||
| 53 | Keyboard* GetKeyboard() { | 69 | Keyboard* GetKeyboard() { |
| @@ -58,6 +74,14 @@ MotionEmu* GetMotionEmu() { | |||
| 58 | return motion_emu.get(); | 74 | return motion_emu.get(); |
| 59 | } | 75 | } |
| 60 | 76 | ||
| 77 | GCButtonFactory* GetGCButtons() { | ||
| 78 | return gcbuttons.get(); | ||
| 79 | } | ||
| 80 | |||
| 81 | GCAnalogFactory* GetGCAnalogs() { | ||
| 82 | return gcanalog.get(); | ||
| 83 | } | ||
| 84 | |||
| 61 | std::string GenerateKeyboardParam(int key_code) { | 85 | std::string GenerateKeyboardParam(int key_code) { |
| 62 | Common::ParamPackage param{ | 86 | Common::ParamPackage param{ |
| 63 | {"engine", "keyboard"}, | 87 | {"engine", "keyboard"}, |
diff --git a/src/input_common/main.h b/src/input_common/main.h index 77a0ce90b..0e32856f6 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include <memory> | 7 | #include <memory> |
| 8 | #include <string> | 8 | #include <string> |
| 9 | #include <vector> | 9 | #include <vector> |
| 10 | #include "input_common/gcadapter/gc_poller.h" | ||
| 10 | 11 | ||
| 11 | namespace Common { | 12 | namespace Common { |
| 12 | class ParamPackage; | 13 | class ParamPackage; |
| @@ -30,6 +31,10 @@ class MotionEmu; | |||
| 30 | /// Gets the motion emulation factory. | 31 | /// Gets the motion emulation factory. |
| 31 | MotionEmu* GetMotionEmu(); | 32 | MotionEmu* GetMotionEmu(); |
| 32 | 33 | ||
| 34 | GCButtonFactory* GetGCButtons(); | ||
| 35 | |||
| 36 | GCAnalogFactory* GetGCAnalogs(); | ||
| 37 | |||
| 33 | /// Generates a serialized param package for creating a keyboard button device | 38 | /// Generates a serialized param package for creating a keyboard button device |
| 34 | std::string GenerateKeyboardParam(int key_code); | 39 | std::string GenerateKeyboardParam(int key_code); |
| 35 | 40 | ||
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index a05fa64ba..00433926d 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp | |||
| @@ -70,6 +70,20 @@ static QString ButtonToText(const Common::ParamPackage& param) { | |||
| 70 | return GetKeyName(param.Get("code", 0)); | 70 | return GetKeyName(param.Get("code", 0)); |
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | if (param.Get("engine", "") == "gcpad") { | ||
| 74 | if (param.Has("axis")) { | ||
| 75 | const QString axis_str = QString::fromStdString(param.Get("axis", "")); | ||
| 76 | const QString direction_str = QString::fromStdString(param.Get("direction", "")); | ||
| 77 | |||
| 78 | return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str); | ||
| 79 | } | ||
| 80 | if (param.Has("button")) { | ||
| 81 | const QString button_str = QString::number(int(std::log2(param.Get("button", 0)))); | ||
| 82 | return QObject::tr("GC Button %1").arg(button_str); | ||
| 83 | } | ||
| 84 | return GetKeyName(param.Get("code", 0)); | ||
| 85 | } | ||
| 86 | |||
| 73 | if (param.Get("engine", "") == "sdl") { | 87 | if (param.Get("engine", "") == "sdl") { |
| 74 | if (param.Has("hat")) { | 88 | if (param.Has("hat")) { |
| 75 | const QString hat_str = QString::fromStdString(param.Get("hat", "")); | 89 | const QString hat_str = QString::fromStdString(param.Get("hat", "")); |
| @@ -126,6 +140,25 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string | |||
| 126 | return {}; | 140 | return {}; |
| 127 | } | 141 | } |
| 128 | 142 | ||
| 143 | if (param.Get("engine", "") == "gcpad") { | ||
| 144 | if (dir == "modifier") { | ||
| 145 | return QObject::tr("[unused]"); | ||
| 146 | } | ||
| 147 | |||
| 148 | if (dir == "left" || dir == "right") { | ||
| 149 | const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); | ||
| 150 | |||
| 151 | return QObject::tr("GC Axis %1").arg(axis_x_str); | ||
| 152 | } | ||
| 153 | |||
| 154 | if (dir == "up" || dir == "down") { | ||
| 155 | const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); | ||
| 156 | |||
| 157 | return QObject::tr("GC Axis %1").arg(axis_y_str); | ||
| 158 | } | ||
| 159 | |||
| 160 | return {}; | ||
| 161 | } | ||
| 129 | return QObject::tr("[unknown]"); | 162 | return QObject::tr("[unknown]"); |
| 130 | } | 163 | } |
| 131 | 164 | ||
| @@ -332,7 +365,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i | |||
| 332 | 365 | ||
| 333 | connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] { | 366 | connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] { |
| 334 | const float slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value(); | 367 | const float slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value(); |
| 335 | if (analogs_param[analog_id].Get("engine", "") == "sdl") { | 368 | if (analogs_param[analog_id].Get("engine", "") == "sdl" || |
| 369 | analogs_param[analog_id].Get("engine", "") == "gcpad") { | ||
| 336 | analog_map_deadzone_and_modifier_slider_label[analog_id]->setText( | 370 | analog_map_deadzone_and_modifier_slider_label[analog_id]->setText( |
| 337 | tr("Deadzone: %1%").arg(slider_value)); | 371 | tr("Deadzone: %1%").arg(slider_value)); |
| 338 | analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); | 372 | analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); |
| @@ -352,6 +386,20 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i | |||
| 352 | 386 | ||
| 353 | connect(poll_timer.get(), &QTimer::timeout, [this] { | 387 | connect(poll_timer.get(), &QTimer::timeout, [this] { |
| 354 | Common::ParamPackage params; | 388 | Common::ParamPackage params; |
| 389 | if (InputCommon::GetGCButtons()->IsPolling()) { | ||
| 390 | params = InputCommon::GetGCButtons()->GetNextInput(); | ||
| 391 | if (params.Has("engine")) { | ||
| 392 | SetPollingResult(params, false); | ||
| 393 | return; | ||
| 394 | } | ||
| 395 | } | ||
| 396 | if (InputCommon::GetGCAnalogs()->IsPolling()) { | ||
| 397 | params = InputCommon::GetGCAnalogs()->GetNextInput(); | ||
| 398 | if (params.Has("engine")) { | ||
| 399 | SetPollingResult(params, false); | ||
| 400 | return; | ||
| 401 | } | ||
| 402 | } | ||
| 355 | for (auto& poller : device_pollers) { | 403 | for (auto& poller : device_pollers) { |
| 356 | params = poller->GetNextInput(); | 404 | params = poller->GetNextInput(); |
| 357 | if (params.Has("engine")) { | 405 | if (params.Has("engine")) { |
| @@ -534,7 +582,7 @@ void ConfigureInputPlayer::UpdateButtonLabels() { | |||
| 534 | analog_map_deadzone_and_modifier_slider_label[analog_id]; | 582 | analog_map_deadzone_and_modifier_slider_label[analog_id]; |
| 535 | 583 | ||
| 536 | if (param.Has("engine")) { | 584 | if (param.Has("engine")) { |
| 537 | if (param.Get("engine", "") == "sdl") { | 585 | if (param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad") { |
| 538 | if (!param.Has("deadzone")) { | 586 | if (!param.Has("deadzone")) { |
| 539 | param.Set("deadzone", 0.1f); | 587 | param.Set("deadzone", 0.1f); |
| 540 | } | 588 | } |
| @@ -583,6 +631,11 @@ void ConfigureInputPlayer::HandleClick( | |||
| 583 | 631 | ||
| 584 | grabKeyboard(); | 632 | grabKeyboard(); |
| 585 | grabMouse(); | 633 | grabMouse(); |
| 634 | if (type == InputCommon::Polling::DeviceType::Button) { | ||
| 635 | InputCommon::GetGCButtons()->BeginConfiguration(); | ||
| 636 | } else { | ||
| 637 | InputCommon::GetGCAnalogs()->BeginConfiguration(); | ||
| 638 | } | ||
| 586 | timeout_timer->start(5000); // Cancel after 5 seconds | 639 | timeout_timer->start(5000); // Cancel after 5 seconds |
| 587 | poll_timer->start(200); // Check for new inputs every 200ms | 640 | poll_timer->start(200); // Check for new inputs every 200ms |
| 588 | } | 641 | } |
| @@ -596,6 +649,9 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, | |||
| 596 | poller->Stop(); | 649 | poller->Stop(); |
| 597 | } | 650 | } |
| 598 | 651 | ||
| 652 | InputCommon::GetGCButtons()->EndConfiguration(); | ||
| 653 | InputCommon::GetGCAnalogs()->EndConfiguration(); | ||
| 654 | |||
| 599 | if (!abort) { | 655 | if (!abort) { |
| 600 | (*input_setter)(params); | 656 | (*input_setter)(params); |
| 601 | } | 657 | } |