diff options
Diffstat (limited to 'src/input_common/gcadapter')
| -rw-r--r-- | src/input_common/gcadapter/gc_adapter.cpp | 520 | ||||
| -rw-r--r-- | src/input_common/gcadapter/gc_adapter.h | 149 | ||||
| -rw-r--r-- | src/input_common/gcadapter/gc_poller.cpp | 238 | ||||
| -rw-r--r-- | src/input_common/gcadapter/gc_poller.h | 11 | 
4 files changed, 608 insertions, 310 deletions
| diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index 74759ea7d..d80195c82 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -4,20 +4,23 @@  #include <chrono>  #include <thread> + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union +#endif  #include <libusb.h> +#ifdef _MSC_VER +#pragma warning(pop) +#endif +  #include "common/logging/log.h" +#include "common/param_package.h"  #include "input_common/gcadapter/gc_adapter.h" +#include "input_common/settings.h"  namespace GCAdapter { -/// Used to loop through and assign button in poller -constexpr std::array<PadButton, 12> PadButtonArray{ -    PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN, -    PadButton::PAD_BUTTON_UP,   PadButton::PAD_TRIGGER_Z,    PadButton::PAD_TRIGGER_R, -    PadButton::PAD_TRIGGER_L,   PadButton::PAD_BUTTON_A,     PadButton::PAD_BUTTON_B, -    PadButton::PAD_BUTTON_X,    PadButton::PAD_BUTTON_Y,     PadButton::PAD_BUTTON_START, -}; -  Adapter::Adapter() {      if (usb_adapter_handle != nullptr) {          return; @@ -26,179 +29,261 @@ Adapter::Adapter() {      const int init_res = libusb_init(&libusb_ctx);      if (init_res == LIBUSB_SUCCESS) { -        Setup(); +        adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);      } else {          LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);      }  } -GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) { -    GCPadStatus pad = {}; -    const std::size_t offset = 1 + (9 * port); +Adapter::~Adapter() { +    Reset(); +} -    adapter_controllers_status[port] = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); +void Adapter::AdapterInputThread() { +    LOG_DEBUG(Input, "GC Adapter input thread started"); +    s32 payload_size{}; +    AdapterPayload adapter_payload{}; -    static constexpr std::array<PadButton, 8> b1_buttons{ -        PadButton::PAD_BUTTON_A,    PadButton::PAD_BUTTON_B,    PadButton::PAD_BUTTON_X, -        PadButton::PAD_BUTTON_Y,    PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, -        PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP, -    }; +    if (adapter_scan_thread.joinable()) { +        adapter_scan_thread.join(); +    } -    static constexpr std::array<PadButton, 4> b2_buttons{ -        PadButton::PAD_BUTTON_START, -        PadButton::PAD_TRIGGER_Z, -        PadButton::PAD_TRIGGER_R, -        PadButton::PAD_TRIGGER_L, -    }; +    while (adapter_input_thread_running) { +        libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), +                                  static_cast<s32>(adapter_payload.size()), &payload_size, 16); +        if (IsPayloadCorrect(adapter_payload, payload_size)) { +            UpdateControllers(adapter_payload); +            UpdateVibrations(); +        } +        std::this_thread::yield(); +    } -    static constexpr std::array<PadAxes, 6> axes{ -        PadAxes::StickX,    PadAxes::StickY,      PadAxes::SubstickX, -        PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, -    }; +    if (restart_scan_thread) { +        adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); +        restart_scan_thread = false; +    } +} -    if (adapter_controllers_status[port] == ControllerTypes::None && !get_origin[port]) { -        // Controller may have been disconnected, recalibrate if reconnected. -        get_origin[port] = true; +bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { +    if (payload_size != static_cast<s32>(adapter_payload.size()) || +        adapter_payload[0] != LIBUSB_DT_HID) { +        LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, +                  adapter_payload[0]); +        if (input_error_counter++ > 20) { +            LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?"); +            adapter_input_thread_running = false; +            restart_scan_thread = true; +        } +        return false;      } -    if (adapter_controllers_status[port] != ControllerTypes::None) { -        const u8 b1 = adapter_payload[offset + 1]; -        const u8 b2 = adapter_payload[offset + 2]; +    input_error_counter = 0; +    return true; +} -        for (std::size_t i = 0; i < b1_buttons.size(); ++i) { -            if ((b1 & (1U << i)) != 0) { -                pad.button |= static_cast<u16>(b1_buttons[i]); +void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { +    for (std::size_t port = 0; port < pads.size(); ++port) { +        const std::size_t offset = 1 + (9 * port); +        const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); +        UpdatePadType(port, type); +        if (DeviceConnected(port)) { +            const u8 b1 = adapter_payload[offset + 1]; +            const u8 b2 = adapter_payload[offset + 2]; +            UpdateStateButtons(port, b1, b2); +            UpdateStateAxes(port, adapter_payload); +            if (configuring) { +                UpdateYuzuSettings(port);              }          } +    } +} -        for (std::size_t j = 0; j < b2_buttons.size(); ++j) { -            if ((b2 & (1U << j)) != 0) { -                pad.button |= static_cast<u16>(b2_buttons[j]); -            } -        } -        for (PadAxes axis : axes) { -            const std::size_t index = static_cast<std::size_t>(axis); -            pad.axis_values[index] = adapter_payload[offset + 3 + index]; +void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { +    if (pads[port].type == pad_type) { +        return; +    } +    // Device changed reset device and set new type +    ResetDevice(port); +    pads[port].type = pad_type; +} + +void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { +    if (port >= pads.size()) { +        return; +    } + +    static constexpr std::array<PadButton, 8> b1_buttons{ +        PadButton::ButtonA,    PadButton::ButtonB,     PadButton::ButtonX,    PadButton::ButtonY, +        PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp, +    }; + +    static constexpr std::array<PadButton, 4> b2_buttons{ +        PadButton::ButtonStart, +        PadButton::TriggerZ, +        PadButton::TriggerR, +        PadButton::TriggerL, +    }; +    pads[port].buttons = 0; +    for (std::size_t i = 0; i < b1_buttons.size(); ++i) { +        if ((b1 & (1U << i)) != 0) { +            pads[port].buttons = +                static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i])); +            pads[port].last_button = b1_buttons[i];          } +    } -        if (get_origin[port]) { -            origin_status[port].axis_values = pad.axis_values; -            get_origin[port] = false; +    for (std::size_t j = 0; j < b2_buttons.size(); ++j) { +        if ((b2 & (1U << j)) != 0) { +            pads[port].buttons = +                static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j])); +            pads[port].last_button = b2_buttons[j];          }      } -    return pad;  } -void Adapter::PadToState(const GCPadStatus& pad, GCState& state) { -    for (const auto& button : PadButtonArray) { -        const u16 button_value = static_cast<u16>(button); -        state.buttons.insert_or_assign(button_value, pad.button & button_value); +void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { +    if (port >= pads.size()) { +        return;      } -    for (size_t i = 0; i < pad.axis_values.size(); ++i) { -        state.axes.insert_or_assign(static_cast<u8>(i), pad.axis_values[i]); +    const std::size_t offset = 1 + (9 * port); +    static constexpr std::array<PadAxes, 6> axes{ +        PadAxes::StickX,    PadAxes::StickY,      PadAxes::SubstickX, +        PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, +    }; + +    for (const PadAxes axis : axes) { +        const auto index = static_cast<std::size_t>(axis); +        const u8 axis_value = adapter_payload[offset + 3 + index]; +        if (pads[port].axis_origin[index] == 255) { +            pads[port].axis_origin[index] = axis_value; +        } +        pads[port].axis_values[index] = +            static_cast<s16>(axis_value - pads[port].axis_origin[index]);      }  } -void Adapter::Read() { -    LOG_DEBUG(Input, "GC Adapter Read() thread started"); +void Adapter::UpdateYuzuSettings(std::size_t port) { +    if (port >= pads.size()) { +        return; +    } -    int payload_size; -    std::array<u8, 37> adapter_payload; -    std::array<GCPadStatus, 4> pads; +    constexpr u8 axis_threshold = 50; +    GCPadStatus pad_status = {.port = port}; -    while (adapter_thread_running) { -        libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), -                                  sizeof(adapter_payload), &payload_size, 16); - -        if (payload_size != sizeof(adapter_payload) || adapter_payload[0] != LIBUSB_DT_HID) { -            LOG_ERROR(Input, -                      "Error reading payload (size: {}, type: {:02x}) Is the adapter connected?", -                      payload_size, adapter_payload[0]); -            adapter_thread_running = false; // error reading from adapter, stop reading. -            break; -        } -        for (std::size_t port = 0; port < pads.size(); ++port) { -            pads[port] = GetPadStatus(port, adapter_payload); -            if (DeviceConnected(port) && configuring) { -                if (pads[port].button != 0) { -                    pad_queue[port].Push(pads[port]); -                } +    if (pads[port].buttons != 0) { +        pad_status.button = pads[port].last_button; +        pad_queue.Push(pad_status); +    } -                // Accounting for a threshold here to ensure an intentional press -                for (size_t i = 0; i < pads[port].axis_values.size(); ++i) { -                    const u8 value = pads[port].axis_values[i]; -                    const u8 origin = origin_status[port].axis_values[i]; - -                    if (value > origin + pads[port].THRESHOLD || -                        value < origin - pads[port].THRESHOLD) { -                        pads[port].axis = static_cast<PadAxes>(i); -                        pads[port].axis_value = pads[port].axis_values[i]; -                        pad_queue[port].Push(pads[port]); -                    } -                } -            } -            PadToState(pads[port], state[port]); +    // Accounting for a threshold here to ensure an intentional press +    for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) { +        const s16 value = pads[port].axis_values[i]; + +        if (value > axis_threshold || value < -axis_threshold) { +            pad_status.axis = static_cast<PadAxes>(i); +            pad_status.axis_value = value; +            pad_status.axis_threshold = axis_threshold; +            pad_queue.Push(pad_status);          } -        std::this_thread::yield();      }  } -void Adapter::Setup() { -    // Initialize all controllers as unplugged -    adapter_controllers_status.fill(ControllerTypes::None); -    // Initialize all ports to store axis origin values -    get_origin.fill(true); - -    // pointer to list of connected usb devices -    libusb_device** devices{}; - -    // populate the list of devices, get the count -    const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices); -    if (device_count < 0) { -        LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count); -        return; +void Adapter::UpdateVibrations() { +    // Use 8 states to keep the switching between on/off fast enough for +    // a human to not notice the difference between switching from on/off +    // More states = more rumble strengths = slower update time +    constexpr u8 vibration_states = 8; + +    vibration_counter = (vibration_counter + 1) % vibration_states; + +    for (GCController& pad : pads) { +        const bool vibrate = pad.rumble_amplitude > vibration_counter; +        vibration_changed |= vibrate != pad.enable_vibration; +        pad.enable_vibration = vibrate;      } +    SendVibrations(); +} -    if (devices != nullptr) { -        for (std::size_t index = 0; index < static_cast<std::size_t>(device_count); ++index) { -            if (CheckDeviceAccess(devices[index])) { -                // GC Adapter found and accessible, registering it -                GetGCEndpoint(devices[index]); -                break; -            } +void Adapter::SendVibrations() { +    if (!rumble_enabled || !vibration_changed) { +        return; +    } +    s32 size{}; +    constexpr u8 rumble_command = 0x11; +    const u8 p1 = pads[0].enable_vibration; +    const u8 p2 = pads[1].enable_vibration; +    const u8 p3 = pads[2].enable_vibration; +    const u8 p4 = pads[3].enable_vibration; +    std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4}; +    const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(), +                                              static_cast<s32>(payload.size()), &size, 16); +    if (err) { +        LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err)); +        if (output_error_counter++ > 5) { +            LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled"); +            rumble_enabled = false;          } -        libusb_free_device_list(devices, 1); +        return;      } +    output_error_counter = 0; +    vibration_changed = false;  } -bool Adapter::CheckDeviceAccess(libusb_device* device) { -    libusb_device_descriptor desc; -    const int get_descriptor_error = libusb_get_device_descriptor(device, &desc); -    if (get_descriptor_error) { -        // could not acquire the descriptor, no point in trying to use it. -        LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}", -                  get_descriptor_error); -        return false; +bool Adapter::RumblePlay(std::size_t port, u8 amplitude) { +    pads[port].rumble_amplitude = amplitude; + +    return rumble_enabled; +} + +void Adapter::AdapterScanThread() { +    adapter_scan_thread_running = true; +    adapter_input_thread_running = false; +    if (adapter_input_thread.joinable()) { +        adapter_input_thread.join(); +    } +    ClearLibusbHandle(); +    ResetDevices(); +    while (adapter_scan_thread_running && !adapter_input_thread_running) { +        Setup(); +        std::this_thread::sleep_for(std::chrono::seconds(1));      } +} -    if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) { -        // This isn't the device we are looking for. -        return false; +void Adapter::Setup() { +    usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337); + +    if (usb_adapter_handle == NULL) { +        return; +    } +    if (!CheckDeviceAccess()) { +        ClearLibusbHandle(); +        return;      } -    const int open_error = libusb_open(device, &usb_adapter_handle); -    if (open_error == LIBUSB_ERROR_ACCESS) { -        LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.", -                  desc.idVendor, desc.idProduct); -        return false; +    libusb_device* device = libusb_get_device(usb_adapter_handle); + +    LOG_INFO(Input, "GC adapter is now connected"); +    // GC Adapter found and accessible, registering it +    if (GetGCEndpoint(device)) { +        adapter_scan_thread_running = false; +        adapter_input_thread_running = true; +        rumble_enabled = true; +        input_error_counter = 0; +        output_error_counter = 0; +        adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this);      } -    if (open_error) { -        LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error); -        return false; +} + +bool Adapter::CheckDeviceAccess() { +    // This fixes payload problems from offbrand GCAdapters +    const s32 control_transfer_error = +        libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000); +    if (control_transfer_error < 0) { +        LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);      } -    int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); +    s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0);      if (kernel_driver_error == 1) {          kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0);          if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { @@ -224,13 +309,13 @@ bool Adapter::CheckDeviceAccess(libusb_device* device) {      return true;  } -void Adapter::GetGCEndpoint(libusb_device* device) { +bool Adapter::GetGCEndpoint(libusb_device* device) {      libusb_config_descriptor* config = nullptr;      const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);      if (config_descriptor_return != LIBUSB_SUCCESS) {          LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}",                    config_descriptor_return); -        return; +        return false;      }      for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { @@ -239,7 +324,7 @@ void Adapter::GetGCEndpoint(libusb_device* device) {              const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i];              for (u8 e = 0; e < interface->bNumEndpoints; e++) {                  const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; -                if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) { +                if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) {                      input_endpoint = endpoint->bEndpointAddress;                  } else {                      output_endpoint = endpoint->bEndpointAddress; @@ -252,78 +337,173 @@ void Adapter::GetGCEndpoint(libusb_device* device) {      unsigned char clear_payload = 0x13;      libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload,                                sizeof(clear_payload), nullptr, 16); - -    adapter_thread_running = true; -    adapter_input_thread = std::thread(&Adapter::Read, this); +    return true;  } -Adapter::~Adapter() { -    Reset(); -} +void Adapter::JoinThreads() { +    restart_scan_thread = false; +    adapter_input_thread_running = false; +    adapter_scan_thread_running = false; -void Adapter::Reset() { -    if (adapter_thread_running) { -        adapter_thread_running = false; +    if (adapter_scan_thread.joinable()) { +        adapter_scan_thread.join();      } +      if (adapter_input_thread.joinable()) {          adapter_input_thread.join();      } +} -    adapter_controllers_status.fill(ControllerTypes::None); -    get_origin.fill(true); - +void Adapter::ClearLibusbHandle() {      if (usb_adapter_handle) {          libusb_release_interface(usb_adapter_handle, 1);          libusb_close(usb_adapter_handle);          usb_adapter_handle = nullptr;      } +} + +void Adapter::ResetDevices() { +    for (std::size_t i = 0; i < pads.size(); ++i) { +        ResetDevice(i); +    } +} + +void Adapter::ResetDevice(std::size_t port) { +    pads[port].type = ControllerTypes::None; +    pads[port].enable_vibration = false; +    pads[port].rumble_amplitude = 0; +    pads[port].buttons = 0; +    pads[port].last_button = PadButton::Undefined; +    pads[port].axis_values.fill(0); +    pads[port].axis_origin.fill(255); +} + +void Adapter::Reset() { +    JoinThreads(); +    ClearLibusbHandle(); +    ResetDevices();      if (libusb_ctx) {          libusb_exit(libusb_ctx);      }  } -bool Adapter::DeviceConnected(std::size_t port) { -    return adapter_controllers_status[port] != ControllerTypes::None; +std::vector<Common::ParamPackage> Adapter::GetInputDevices() const { +    std::vector<Common::ParamPackage> devices; +    for (std::size_t port = 0; port < pads.size(); ++port) { +        if (!DeviceConnected(port)) { +            continue; +        } +        std::string name = fmt::format("Gamecube Controller {}", port + 1); +        devices.emplace_back(Common::ParamPackage{ +            {"class", "gcpad"}, +            {"display", std::move(name)}, +            {"port", std::to_string(port)}, +        }); +    } +    return devices;  } -void Adapter::ResetDeviceType(std::size_t port) { -    adapter_controllers_status[port] = ControllerTypes::None; +InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( +    const Common::ParamPackage& params) const { +    // This list is missing ZL/ZR since those are not considered buttons. +    // We will add those afterwards +    // This list also excludes any button that can't be really mapped +    static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12> +        switch_to_gcadapter_button = { +            std::pair{Settings::NativeButton::A, PadButton::ButtonA}, +            {Settings::NativeButton::B, PadButton::ButtonB}, +            {Settings::NativeButton::X, PadButton::ButtonX}, +            {Settings::NativeButton::Y, PadButton::ButtonY}, +            {Settings::NativeButton::Plus, PadButton::ButtonStart}, +            {Settings::NativeButton::DLeft, PadButton::ButtonLeft}, +            {Settings::NativeButton::DUp, PadButton::ButtonUp}, +            {Settings::NativeButton::DRight, PadButton::ButtonRight}, +            {Settings::NativeButton::DDown, PadButton::ButtonDown}, +            {Settings::NativeButton::SL, PadButton::TriggerL}, +            {Settings::NativeButton::SR, PadButton::TriggerR}, +            {Settings::NativeButton::R, PadButton::TriggerZ}, +        }; +    if (!params.Has("port")) { +        return {}; +    } + +    InputCommon::ButtonMapping mapping{}; +    for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) { +        Common::ParamPackage button_params({{"engine", "gcpad"}}); +        button_params.Set("port", params.Get("port", 0)); +        button_params.Set("button", static_cast<int>(gcadapter_button)); +        mapping.insert_or_assign(switch_button, std::move(button_params)); +    } + +    // Add the missing bindings for ZL/ZR +    static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2> +        switch_to_gcadapter_axis = { +            std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft}, +            {Settings::NativeButton::ZR, PadAxes::TriggerRight}, +        }; +    for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) { +        Common::ParamPackage button_params({{"engine", "gcpad"}}); +        button_params.Set("port", params.Get("port", 0)); +        button_params.Set("button", static_cast<s32>(PadButton::Stick)); +        button_params.Set("axis", static_cast<s32>(gcadapter_axis)); +        button_params.Set("threshold", 0.5f); +        button_params.Set("direction", "+"); +        mapping.insert_or_assign(switch_button, std::move(button_params)); +    } +    return mapping;  } -void Adapter::BeginConfiguration() { -    get_origin.fill(true); -    for (auto& pq : pad_queue) { -        pq.Clear(); +InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice( +    const Common::ParamPackage& params) const { +    if (!params.Has("port")) { +        return {};      } + +    InputCommon::AnalogMapping mapping = {}; +    Common::ParamPackage left_analog_params; +    left_analog_params.Set("engine", "gcpad"); +    left_analog_params.Set("port", params.Get("port", 0)); +    left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX)); +    left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY)); +    mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); +    Common::ParamPackage right_analog_params; +    right_analog_params.Set("engine", "gcpad"); +    right_analog_params.Set("port", params.Get("port", 0)); +    right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX)); +    right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY)); +    mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); +    return mapping; +} + +bool Adapter::DeviceConnected(std::size_t port) const { +    return pads[port].type != ControllerTypes::None; +} + +void Adapter::BeginConfiguration() { +    pad_queue.Clear();      configuring = true;  }  void Adapter::EndConfiguration() { -    for (auto& pq : pad_queue) { -        pq.Clear(); -    } +    pad_queue.Clear();      configuring = false;  } -std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() { +Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() {      return pad_queue;  } -const std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() const { +const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const {      return pad_queue;  } -std::array<GCState, 4>& Adapter::GetPadState() { -    return state; -} - -const std::array<GCState, 4>& Adapter::GetPadState() const { -    return state; +GCController& Adapter::GetPadState(std::size_t port) { +    return pads.at(port);  } -int Adapter::GetOriginValue(int port, int axis) const { -    return origin_status[port].axis_values[axis]; +const GCController& Adapter::GetPadState(std::size_t port) const { +    return pads.at(port);  }  } // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h index bed81915c..7a6c545bd 100644 --- a/src/input_common/gcadapter/gc_adapter.h +++ b/src/input_common/gcadapter/gc_adapter.h @@ -10,6 +10,7 @@  #include <unordered_map>  #include "common/common_types.h"  #include "common/threadsafe_queue.h" +#include "input_common/main.h"  struct libusb_context;  struct libusb_device; @@ -18,24 +19,23 @@ struct libusb_device_handle;  namespace GCAdapter {  enum class PadButton { -    PAD_BUTTON_LEFT = 0x0001, -    PAD_BUTTON_RIGHT = 0x0002, -    PAD_BUTTON_DOWN = 0x0004, -    PAD_BUTTON_UP = 0x0008, -    PAD_TRIGGER_Z = 0x0010, -    PAD_TRIGGER_R = 0x0020, -    PAD_TRIGGER_L = 0x0040, -    PAD_BUTTON_A = 0x0100, -    PAD_BUTTON_B = 0x0200, -    PAD_BUTTON_X = 0x0400, -    PAD_BUTTON_Y = 0x0800, -    PAD_BUTTON_START = 0x1000, +    Undefined = 0x0000, +    ButtonLeft = 0x0001, +    ButtonRight = 0x0002, +    ButtonDown = 0x0004, +    ButtonUp = 0x0008, +    TriggerZ = 0x0010, +    TriggerR = 0x0020, +    TriggerL = 0x0040, +    ButtonA = 0x0100, +    ButtonB = 0x0200, +    ButtonX = 0x0400, +    ButtonY = 0x0800, +    ButtonStart = 0x1000,      // Below is for compatibility with "AxisButton" type -    PAD_STICK = 0x2000, +    Stick = 0x2000,  }; -extern const std::array<PadButton, 12> PadButtonArray; -  enum class PadAxes : u8 {      StickX,      StickY, @@ -46,85 +46,122 @@ enum class PadAxes : u8 {      Undefined,  }; +enum class ControllerTypes { +    None, +    Wired, +    Wireless, +}; +  struct GCPadStatus { -    u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits +    std::size_t port{}; -    std::array<u8, 6> axis_values{};    // Triggers and sticks, following indices defined in PadAxes -    static constexpr u8 THRESHOLD = 50; // Threshold for axis press for polling +    PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits -    u8 port{};      PadAxes axis{PadAxes::Undefined}; -    u8 axis_value{255}; +    s16 axis_value{}; +    u8 axis_threshold{50};  }; -struct GCState { -    std::unordered_map<int, bool> buttons; -    std::unordered_map<int, u16> axes; +struct GCController { +    ControllerTypes type{}; +    bool enable_vibration{}; +    u8 rumble_amplitude{}; +    u16 buttons{}; +    PadButton last_button{}; +    std::array<s16, 6> axis_values{}; +    std::array<u8, 6> axis_origin{};  }; -enum class ControllerTypes { None, Wired, Wireless }; -  class Adapter {  public: -    /// Initialize the GC Adapter capture and read sequence      Adapter(); - -    /// Close the adapter read thread and release the adapter      ~Adapter(); + +    /// Request a vibration for a controller +    bool RumblePlay(std::size_t port, u8 amplitude); +      /// Used for polling      void BeginConfiguration();      void EndConfiguration(); -    /// Returns true if there is a device connected to port -    bool DeviceConnected(std::size_t port); +    Common::SPSCQueue<GCPadStatus>& GetPadQueue(); +    const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const; -    std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue(); -    const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const; +    GCController& GetPadState(std::size_t port); +    const GCController& GetPadState(std::size_t port) const; -    std::array<GCState, 4>& GetPadState(); -    const std::array<GCState, 4>& GetPadState() const; +    /// Returns true if there is a device connected to port +    bool DeviceConnected(std::size_t port) const; -    int GetOriginValue(int port, int axis) const; +    /// Used for automapping features +    std::vector<Common::ParamPackage> GetInputDevices() const; +    InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; +    InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;  private: -    GCPadStatus GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload); +    using AdapterPayload = std::array<u8, 37>; + +    void UpdatePadType(std::size_t port, ControllerTypes pad_type); +    void UpdateControllers(const AdapterPayload& adapter_payload); +    void UpdateYuzuSettings(std::size_t port); +    void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); +    void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); +    void UpdateVibrations(); + +    void AdapterInputThread(); -    void PadToState(const GCPadStatus& pad, GCState& state); +    void AdapterScanThread(); -    void Read(); +    bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); -    /// Resets status of device connected to port -    void ResetDeviceType(std::size_t port); +    // Updates vibration state of all controllers +    void SendVibrations(); + +    /// For use in initialization, querying devices to find the adapter +    void Setup(); + +    /// Resets status of all GC controller devices to a disconnected state +    void ResetDevices(); + +    /// Resets status of device connected to a disconnected state +    void ResetDevice(std::size_t port);      /// Returns true if we successfully gain access to GC Adapter -    bool CheckDeviceAccess(libusb_device* device); +    bool CheckDeviceAccess(); -    /// Captures GC Adapter endpoint address, -    void GetGCEndpoint(libusb_device* device); +    /// Captures GC Adapter endpoint address +    /// Returns true if the endpoint was set correctly +    bool GetGCEndpoint(libusb_device* device);      /// For shutting down, clear all data, join all threads, release usb      void Reset(); -    /// For use in initialization, querying devices to find the adapter -    void Setup(); +    // Join all threads +    void JoinThreads(); + +    // Release usb handles +    void ClearLibusbHandle();      libusb_device_handle* usb_adapter_handle = nullptr; +    std::array<GCController, 4> pads; +    Common::SPSCQueue<GCPadStatus> pad_queue;      std::thread adapter_input_thread; -    bool adapter_thread_running; +    std::thread adapter_scan_thread; +    bool adapter_input_thread_running; +    bool adapter_scan_thread_running; +    bool restart_scan_thread;      libusb_context* libusb_ctx; -    u8 input_endpoint = 0; -    u8 output_endpoint = 0; +    u8 input_endpoint{0}; +    u8 output_endpoint{0}; +    u8 input_error_counter{0}; +    u8 output_error_counter{0}; +    int vibration_counter{0}; -    bool configuring = false; - -    std::array<GCState, 4> state; -    std::array<bool, 4> get_origin; -    std::array<GCPadStatus, 4> origin_status; -    std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue; -    std::array<ControllerTypes, 4> adapter_controllers_status{}; +    bool configuring{false}; +    bool rumble_enabled{true}; +    bool vibration_changed{true};  }; -  } // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index 71cd85eeb..1b6ded8d6 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -15,35 +15,35 @@ namespace InputCommon {  class GCButton final : public Input::ButtonDevice {  public: -    explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter) +    explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter)          : port(port_), button(button_), gcadapter(adapter) {}      ~GCButton() override;      bool GetStatus() const override {          if (gcadapter->DeviceConnected(port)) { -            return gcadapter->GetPadState()[port].buttons.at(button); +            return (gcadapter->GetPadState(port).buttons & button) != 0;          }          return false;      }  private: -    const int port; -    const int button; -    GCAdapter::Adapter* gcadapter; +    const u32 port; +    const s32 button; +    const GCAdapter::Adapter* gcadapter;  };  class GCAxisButton final : public Input::ButtonDevice {  public: -    explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, -                          GCAdapter::Adapter* adapter) +    explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_, +                          const GCAdapter::Adapter* adapter)          : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), -          gcadapter(adapter), origin_value(adapter->GetOriginValue(port_, axis_)) {} +          gcadapter(adapter) {}      bool GetStatus() const override {          if (gcadapter->DeviceConnected(port)) { -            const float current_axis_value = gcadapter->GetPadState()[port].axes.at(axis); -            const float axis_value = (current_axis_value - origin_value) / 128.0f; +            const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis); +            const float axis_value = current_axis_value / 128.0f;              if (trigger_if_greater) {                  // TODO: Might be worthwile to set a slider for the trigger threshold. It is                  // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick @@ -55,12 +55,11 @@ public:      }  private: -    const int port; -    const int axis; +    const u32 port; +    const u32 axis;      float threshold;      bool trigger_if_greater; -    GCAdapter::Adapter* gcadapter; -    const float origin_value; +    const GCAdapter::Adapter* gcadapter;  };  GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) @@ -69,10 +68,10 @@ GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)  GCButton::~GCButton() = default;  std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) { -    const int button_id = params.Get("button", 0); -    const int port = params.Get("port", 0); +    const auto button_id = params.Get("button", 0); +    const auto port = static_cast<u32>(params.Get("port", 0)); -    constexpr int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_STICK); +    constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick);      // button is not an axis/stick button      if (button_id != PAD_STICK_ID) { @@ -97,7 +96,6 @@ std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::Param                                                adapter.get());      } -    UNREACHABLE();      return nullptr;  } @@ -105,32 +103,25 @@ Common::ParamPackage GCButtonFactory::GetNextInput() const {      Common::ParamPackage params;      GCAdapter::GCPadStatus pad;      auto& queue = adapter->GetPadQueue(); -    for (std::size_t port = 0; port < queue.size(); ++port) { -        while (queue[port].Pop(pad)) { -            // This while loop will break on the earliest detected button -            params.Set("engine", "gcpad"); -            params.Set("port", static_cast<int>(port)); -            for (const auto& button : GCAdapter::PadButtonArray) { -                const u16 button_value = static_cast<u16>(button); -                if (pad.button & button_value) { -                    params.Set("button", button_value); -                    break; -                } -            } +    while (queue.Pop(pad)) { +        // This while loop will break on the earliest detected button +        params.Set("engine", "gcpad"); +        params.Set("port", static_cast<s32>(pad.port)); +        if (pad.button != GCAdapter::PadButton::Undefined) { +            params.Set("button", static_cast<u16>(pad.button)); +        } -            // For Axis button implementation -            if (pad.axis != GCAdapter::PadAxes::Undefined) { -                params.Set("axis", static_cast<u8>(pad.axis)); -                params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK)); -                if (pad.axis_value > 128) { -                    params.Set("direction", "+"); -                    params.Set("threshold", "0.25"); -                } else { -                    params.Set("direction", "-"); -                    params.Set("threshold", "-0.25"); -                } -                break; +        // For Axis button implementation +        if (pad.axis != GCAdapter::PadAxes::Undefined) { +            params.Set("axis", static_cast<u8>(pad.axis)); +            params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick)); +            params.Set("threshold", "0.25"); +            if (pad.axis_value > 0) { +                params.Set("direction", "+"); +            } else { +                params.Set("direction", "-");              } +            break;          }      }      return params; @@ -148,25 +139,30 @@ void GCButtonFactory::EndConfiguration() {  class GCAnalog final : public Input::AnalogDevice {  public: -    GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter, -             float range_) -        : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter), -          origin_value_x(adapter->GetOriginValue(port_, axis_x_)), -          origin_value_y(adapter->GetOriginValue(port_, axis_y_)), range(range_) {} +    explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_, +                      float deadzone_, float range_, const GCAdapter::Adapter* adapter) +        : port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_), +          deadzone(deadzone_), range(range_), gcadapter(adapter) {} -    float GetAxis(int axis) const { +    float GetAxis(u32 axis) const {          if (gcadapter->DeviceConnected(port)) {              std::lock_guard lock{mutex}; -            const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y; -            return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range); +            const auto axis_value = +                static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis)); +            return (axis_value) / (100.0f * range);          }          return 0.0f;      } -    std::pair<float, float> GetAnalog(int axis_x, int axis_y) const { -        float x = GetAxis(axis_x); -        float y = GetAxis(axis_y); - +    std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { +        float x = GetAxis(analog_axis_x); +        float y = GetAxis(analog_axis_y); +        if (invert_x) { +            x = -x; +        } +        if (invert_y) { +            y = -y; +        }          // Make sure the coordinates are in the unit circle,          // otherwise normalize it.          float r = x * x + y * y; @@ -189,6 +185,16 @@ public:          return {0.0f, 0.0f};      } +    std::tuple<float, float> GetRawStatus() const override { +        const float x = GetAxis(axis_x); +        const float y = GetAxis(axis_y); +        return {x, y}; +    } + +    Input::AnalogProperties GetAnalogProperties() const override { +        return {deadzone, range, 0.5f}; +    } +      bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {          const auto [x, y] = GetStatus();          const float directional_deadzone = 0.5f; @@ -206,14 +212,14 @@ public:      }  private: -    const int port; -    const int axis_x; -    const int axis_y; +    const u32 port; +    const u32 axis_x; +    const u32 axis_y; +    const bool invert_x; +    const bool invert_y;      const float deadzone; -    GCAdapter::Adapter* gcadapter; -    const float origin_value_x; -    const float origin_value_y;      const float range; +    const GCAdapter::Adapter* gcadapter;      mutable std::mutex mutex;  }; @@ -229,13 +235,18 @@ GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)   *     - "axis_y": the index of the axis to be bind as y-axis   */  std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) { -    const int port = params.Get("port", 0); -    const int axis_x = params.Get("axis_x", 0); -    const int axis_y = params.Get("axis_y", 1); -    const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); -    const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); - -    return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range); +    const auto port = static_cast<u32>(params.Get("port", 0)); +    const auto axis_x = static_cast<u32>(params.Get("axis_x", 0)); +    const auto axis_y = static_cast<u32>(params.Get("axis_y", 1)); +    const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); +    const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); +    const std::string invert_x_value = params.Get("invert_x", "+"); +    const std::string invert_y_value = params.Get("invert_y", "+"); +    const bool invert_x = invert_x_value == "-"; +    const bool invert_y = invert_y_value == "-"; + +    return std::make_unique<GCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range, +                                      adapter.get());  }  void GCAnalogFactory::BeginConfiguration() { @@ -250,31 +261,51 @@ void GCAnalogFactory::EndConfiguration() {  Common::ParamPackage GCAnalogFactory::GetNextInput() {      GCAdapter::GCPadStatus pad; +    Common::ParamPackage params;      auto& queue = adapter->GetPadQueue(); -    for (std::size_t port = 0; port < queue.size(); ++port) { -        while (queue[port].Pop(pad)) { -            if (pad.axis == GCAdapter::PadAxes::Undefined || -                std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.1) { -                continue; -            } -            // An analog device needs two axes, so we need to store the axis for later and wait for -            // a second input event. The axes also must be from the same joystick. -            const u8 axis = static_cast<u8>(pad.axis); -            if (analog_x_axis == -1) { -                analog_x_axis = axis; -                controller_number = static_cast<int>(port); -            } else if (analog_y_axis == -1 && analog_x_axis != axis && -                       controller_number == static_cast<int>(port)) { -                analog_y_axis = axis; -            } +    while (queue.Pop(pad)) { +        if (pad.button != GCAdapter::PadButton::Undefined) { +            params.Set("engine", "gcpad"); +            params.Set("port", static_cast<s32>(pad.port)); +            params.Set("button", static_cast<u16>(pad.button)); +            return params; +        } +        if (pad.axis == GCAdapter::PadAxes::Undefined || +            std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) { +            continue; +        } +        // An analog device needs two axes, so we need to store the axis for later and wait for +        // a second input event. The axes also must be from the same joystick. +        const u8 axis = static_cast<u8>(pad.axis); +        if (axis == 0 || axis == 1) { +            analog_x_axis = 0; +            analog_y_axis = 1; +            controller_number = static_cast<s32>(pad.port); +            break; +        } +        if (axis == 2 || axis == 3) { +            analog_x_axis = 2; +            analog_y_axis = 3; +            controller_number = static_cast<s32>(pad.port); +            break; +        } + +        if (analog_x_axis == -1) { +            analog_x_axis = axis; +            controller_number = static_cast<s32>(pad.port); +        } else if (analog_y_axis == -1 && analog_x_axis != axis && +                   controller_number == static_cast<s32>(pad.port)) { +            analog_y_axis = axis; +            break;          }      } -    Common::ParamPackage params;      if (analog_x_axis != -1 && analog_y_axis != -1) {          params.Set("engine", "gcpad");          params.Set("port", controller_number);          params.Set("axis_x", analog_x_axis);          params.Set("axis_y", analog_y_axis); +        params.Set("invert_x", "+"); +        params.Set("invert_y", "+");          analog_x_axis = -1;          analog_y_axis = -1;          controller_number = -1; @@ -283,4 +314,43 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() {      return params;  } +class GCVibration final : public Input::VibrationDevice { +public: +    explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter) +        : port(port_), gcadapter(adapter) {} + +    u8 GetStatus() const override { +        return gcadapter->RumblePlay(port, 0); +    } + +    bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high, +                       [[maybe_unused]] f32 freq_high) const override { +        const auto mean_amplitude = (amp_low + amp_high) * 0.5f; +        const auto processed_amplitude = +            static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); + +        return gcadapter->RumblePlay(port, processed_amplitude); +    } + +private: +    const u32 port; +    GCAdapter::Adapter* gcadapter; +}; + +/// An vibration device factory that creates vibration devices from GC Adapter +GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) +    : adapter(std::move(adapter_)) {} + +/** + * Creates a vibration device from a joystick + * @param params contains parameters for creating the device: + *     - "port": the nth gcpad on the adapter + */ +std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create( +    const Common::ParamPackage& params) { +    const auto port = static_cast<u32>(params.Get("port", 0)); + +    return std::make_unique<GCVibration>(port, adapter.get()); +} +  } // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h index 0527f328f..d1271e3ea 100644 --- a/src/input_common/gcadapter/gc_poller.h +++ b/src/input_common/gcadapter/gc_poller.h @@ -64,4 +64,15 @@ private:      bool polling = false;  }; +/// A vibration device factory creates vibration devices from GC Adapter +class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> { +public: +    explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); + +    std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override; + +private: +    std::shared_ptr<GCAdapter::Adapter> adapter; +}; +  } // namespace InputCommon | 
