From 8be91c83b8ab6b9fadb44745e2a8b0e8a90bcbfe Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Tue, 16 Jul 2024 22:00:21 +0200 Subject: [PATCH] artic base: Add Artic Controller support --- .../features/settings/model/IntSetting.kt | 7 +- .../settings/ui/SettingsFragmentPresenter.kt | 10 + src/android/app/src/main/jni/config.cpp | 2 + src/android/app/src/main/jni/default_ini.h | 3 + .../app/src/main/res/values/strings.xml | 3 + src/common/settings.cpp | 1 + src/common/settings.h | 1 + src/core/hle/service/hid/hid.cpp | 472 +++++++++++++----- src/core/hle/service/hid/hid.h | 49 +- src/core/hle/service/ir/extra_hid.cpp | 47 +- src/core/hle/service/ir/extra_hid.h | 10 + src/core/hle/service/ir/ir_rst.cpp | 46 +- src/core/hle/service/ir/ir_rst.h | 10 + src/core/hle/service/ir/ir_user.cpp | 6 + src/core/hle/service/ir/ir_user.h | 6 + src/core/loader/artic.cpp | 8 + src/lime_qt/configuration/config.cpp | 4 + .../configuration/configure_dialog.cpp | 2 +- src/lime_qt/configuration/configure_input.cpp | 12 +- src/lime_qt/configuration/configure_input.h | 3 +- src/lime_qt/configuration/configure_input.ui | 7 + src/network/artic_base/artic_base_client.cpp | 109 +++- src/network/artic_base/artic_base_client.h | 79 ++- 23 files changed, 739 insertions(+), 158 deletions(-) diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/IntSetting.kt index c4637d4b7..34b5dc128 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/IntSetting.kt @@ -41,7 +41,9 @@ enum class IntSetting( VSYNC("use_vsync_new", Settings.SECTION_RENDERER, 1), DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0), TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0), - USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1); + USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1), + DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0), + USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, 0); override var int: Int = defaultValue @@ -69,7 +71,8 @@ enum class IntSetting( DEBUG_RENDERER, CPU_JIT, ASYNC_CUSTOM_LOADING, - AUDIO_INPUT_TYPE + AUDIO_INPUT_TYPE, + USE_ARTIC_BASE_CONTROLLER ) fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsFragmentPresenter.kt index 9049d4dd6..df0231d4f 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsFragmentPresenter.kt @@ -643,6 +643,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) val button = getInputObject(key) add(InputBindingSetting(button, Settings.hotkeyTitles[i])) } + add(HeaderSetting(R.string.miscellaneous)) + add( + SwitchSetting( + IntSetting.USE_ARTIC_BASE_CONTROLLER, + R.string.use_artic_base_controller, + R.string.use_artic_base_controller_desc, + IntSetting.USE_ARTIC_BASE_CONTROLLER.key, + IntSetting.USE_ARTIC_BASE_CONTROLLER.defaultValue + ) + ) } } diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index eff17d63e..136671bff 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -128,6 +128,8 @@ void Config::ReadValues() { static_cast(sdl2_config->GetInteger("Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT)); + ReadSetting("Controls", Settings::values.use_artic_base_controller); + // Core ReadSetting("Core", Settings::values.use_cpu_jit); ReadSetting("Core", Settings::values.cpu_clock_percentage); diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index e0babfd5a..b96999da9 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -86,6 +86,9 @@ udp_input_port= # The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0) udp_pad_index= +# Use Artic Controller when connected to Artic Base Server. (Default 0) +use_artic_base_controller= + [Core] # Whether to use the Just-In-Time (JIT) compiler for CPU emulation # 0: Interpreter (slow), 1 (default): JIT (fast) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 1dc3fc5c8..caf3231ff 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -714,5 +714,8 @@ Connect to a real console that is running an Artic Base server Connect to Artic Base Enter Artic Base server address + Miscellaneous + Use Artic Controller when connected to Artic Base Server + Use the controls provided by Artic Base Server when connected to it instead of the configured input device. \ No newline at end of file diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 5a533750e..dbf85e710 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -83,6 +83,7 @@ void LogSettings() { LOG_INFO(Config, "Lime3DS Configuration:"); log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue()); log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue()); + log_setting("Controller_UseArticController", values.use_artic_base_controller.GetValue()); log_setting("Renderer_UseGLES", values.use_gles.GetValue()); log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue())); log_setting("Renderer_AsyncShaders", values.async_shader_compilation.GetValue()); diff --git a/src/common/settings.h b/src/common/settings.h index 3e28619d1..580c0b65c 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -428,6 +428,7 @@ struct Values { int current_input_profile_index; ///< The current input profile index std::vector input_profiles; ///< The list of input profiles std::vector touch_from_button_maps; + Setting use_artic_base_controller{false, "use_artic_base_controller"}; SwitchableSetting enable_gamemode{true, "enable_gamemode"}; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 98f53cff5..ca81128da 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -20,6 +20,8 @@ #include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid_spvr.h" #include "core/hle/service/hid/hid_user.h" +#include "core/hle/service/ir/ir_rst.h" +#include "core/hle/service/ir/ir_user.h" #include "core/hle/service/service.h" #include "core/movie.h" @@ -53,6 +55,32 @@ void Module::serialize(Archive& ar, const unsigned int file_version) { } SERIALIZE_IMPL(Module) +ArticBaseController::ArticBaseController( + const std::shared_ptr& client) { + + udp_stream = + client->NewUDPStream("ArticController", sizeof(ArticBaseController::ControllerData), + std::chrono::milliseconds(2)); + if (udp_stream.get()) { + udp_stream->Start(); + } +} + +ArticBaseController::ControllerData ArticBaseController::GetControllerData() { + + if (udp_stream.get() && udp_stream->IsReady()) { + auto data = udp_stream->GetLastPacket(); + if (data.size() == sizeof(ControllerData)) { + u32 id = *reinterpret_cast(data.data()); + if ((id - last_packet_id) < (std::numeric_limits::max() / 2)) { + last_packet_id = id; + memcpy(&last_controller_data, data.data(), data.size()); + } + } + } + return last_controller_data; +} + constexpr float accelerometer_coef = 512.0f; // measured from hw test result constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call @@ -111,96 +139,151 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { LoadInputDevices(); using namespace Settings::NativeButton; - state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); - state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); - state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); - state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); - state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); - state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); - state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); - state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); - state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); - state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); - state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); - state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); - state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus()); - state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus()); - // Get current circle pad position and update circle pad direction - float circle_pad_x_f, circle_pad_y_f; - std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); + if (artic_controller.get() && artic_controller->IsReady()) { + constexpr u32 HID_VALID_KEYS = 0xF0003FFF; + constexpr u32 LIBCTRU_TOUCH_KEY = (1 << 20); - // xperia64: 0x9A seems to be the calibrated limit of the circle pad - // Verified by using Input Redirector with very large-value digital inputs - // on the circle pad and calibrating using the system settings application - constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position + ArticBaseController::ControllerData data = artic_controller->GetControllerData(); - // These are rounded rather than truncated on actual hardware - s16 circle_pad_new_x = static_cast(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS)); - s16 circle_pad_new_y = static_cast(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS)); - s16 circle_pad_x = - (circle_pad_new_x + std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) / - CIRCLE_PAD_AVERAGING; - s16 circle_pad_y = - (circle_pad_new_y + std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) / - CIRCLE_PAD_AVERAGING; - circle_pad_old_x.erase(circle_pad_old_x.begin()); - circle_pad_old_x.push_back(circle_pad_new_x); - circle_pad_old_y.erase(circle_pad_old_y.begin()); - circle_pad_old_y.push_back(circle_pad_new_y); + state.hex = data.pad & HID_VALID_KEYS; - system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y); + s16 circle_pad_x = data.c_pad_x; + s16 circle_pad_y = data.c_pad_y; - const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); - state.circle_up.Assign(direction.up); - state.circle_down.Assign(direction.down); - state.circle_left.Assign(direction.left); - state.circle_right.Assign(direction.right); + system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y); - mem->pad.current_state.hex = state.hex; - mem->pad.index = next_pad_index; - next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); + mem->pad.current_state.hex = state.hex; + mem->pad.index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); - // Get the previous Pad state - u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); - PadState old_state = mem->pad.entries[last_entry_index].current_state; + // Get the previous Pad state + u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); + PadState old_state = mem->pad.entries[last_entry_index].current_state; - // Compute bitmask with 1s for bits different from the old state - PadState changed = {{(state.hex ^ old_state.hex)}}; + // Compute bitmask with 1s for bits different from the old state + PadState changed = {{(state.hex ^ old_state.hex)}}; - // Get the current Pad entry - PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; + // Get the current Pad entry + PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; - // Update entry properties - pad_entry.current_state.hex = state.hex; - pad_entry.delta_additions.hex = changed.hex & state.hex; - pad_entry.delta_removals.hex = changed.hex & old_state.hex; - pad_entry.circle_pad_x = circle_pad_x; - pad_entry.circle_pad_y = circle_pad_y; + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.circle_pad_x = circle_pad_x; + pad_entry.circle_pad_y = circle_pad_y; - // If we just updated index 0, provide a new timestamp - if (mem->pad.index == 0) { - mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; - mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks(); + // If we just updated index 0, provide a new timestamp + if (mem->pad.index == 0) { + mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; + mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks(); + } + + mem->touch.index = next_touch_index; + next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); + + // Get the current touch entry + TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; + bool pressed = (data.pad & LIBCTRU_TOUCH_KEY) != 0; + + touch_entry.x = static_cast(data.touch_x); + touch_entry.y = static_cast(data.touch_y); + touch_entry.valid.Assign(pressed ? 1 : 0); + + system.Movie().HandleTouchStatus(touch_entry); + } else { + state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); + state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); + state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); + state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); + state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); + state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); + state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); + state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); + state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); + state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); + state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); + state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); + state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus()); + state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus()); + + // Get current circle pad position and update circle pad direction + float circle_pad_x_f, circle_pad_y_f; + std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); + + // xperia64: 0x9A seems to be the calibrated limit of the circle pad + // Verified by using Input Redirector with very large-value digital inputs + // on the circle pad and calibrating using the system settings application + constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position + + // These are rounded rather than truncated on actual hardware + s16 circle_pad_new_x = static_cast(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS)); + s16 circle_pad_new_y = static_cast(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS)); + s16 circle_pad_x = (circle_pad_new_x + + std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) / + CIRCLE_PAD_AVERAGING; + s16 circle_pad_y = (circle_pad_new_y + + std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) / + CIRCLE_PAD_AVERAGING; + circle_pad_old_x.erase(circle_pad_old_x.begin()); + circle_pad_old_x.push_back(circle_pad_new_x); + circle_pad_old_y.erase(circle_pad_old_y.begin()); + circle_pad_old_y.push_back(circle_pad_new_y); + + system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y); + + const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); + state.circle_up.Assign(direction.up); + state.circle_down.Assign(direction.down); + state.circle_left.Assign(direction.left); + state.circle_right.Assign(direction.right); + + mem->pad.current_state.hex = state.hex; + mem->pad.index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); + + // Get the previous Pad state + u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); + PadState old_state = mem->pad.entries[last_entry_index].current_state; + + // Compute bitmask with 1s for bits different from the old state + PadState changed = {{(state.hex ^ old_state.hex)}}; + + // Get the current Pad entry + PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; + + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.circle_pad_x = circle_pad_x; + pad_entry.circle_pad_y = circle_pad_y; + + // If we just updated index 0, provide a new timestamp + if (mem->pad.index == 0) { + mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; + mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks(); + } + + mem->touch.index = next_touch_index; + next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); + + // Get the current touch entry + TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; + bool pressed = false; + float x, y; + std::tie(x, y, pressed) = touch_device->GetStatus(); + if (!pressed && touch_btn_device) { + std::tie(x, y, pressed) = touch_btn_device->GetStatus(); + } + touch_entry.x = static_cast(x * Core::kScreenBottomWidth); + touch_entry.y = static_cast(y * Core::kScreenBottomHeight); + touch_entry.valid.Assign(pressed ? 1 : 0); + + system.Movie().HandleTouchStatus(touch_entry); } - mem->touch.index = next_touch_index; - next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); - - // Get the current touch entry - TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; - bool pressed = false; - float x, y; - std::tie(x, y, pressed) = touch_device->GetStatus(); - if (!pressed && touch_btn_device) { - std::tie(x, y, pressed) = touch_btn_device->GetStatus(); - } - touch_entry.x = static_cast(x * Core::kScreenBottomWidth); - touch_entry.y = static_cast(y * Core::kScreenBottomHeight); - touch_entry.valid.Assign(pressed ? 1 : 0); - - system.Movie().HandleTouchStatus(touch_entry); - // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being // converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8). @@ -231,19 +314,27 @@ void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_la mem->accelerometer.index = next_accelerometer_index; next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); - Common::Vec3 accel; - std::tie(accel, std::ignore) = motion_device->GetStatus(); - accel *= accelerometer_coef; - // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback - // The time stretch formula should be like - // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity - AccelerometerDataEntry& accelerometer_entry = mem->accelerometer.entries[mem->accelerometer.index]; - accelerometer_entry.x = static_cast(accel.x); - accelerometer_entry.y = static_cast(accel.y); - accelerometer_entry.z = static_cast(accel.z); + if (artic_controller.get() && artic_controller->IsReady()) { + ArticBaseController::ControllerData data = artic_controller->GetControllerData(); + + accelerometer_entry.x = data.accel_x; + accelerometer_entry.y = data.accel_y; + accelerometer_entry.z = data.accel_z; + } else { + Common::Vec3 accel; + std::tie(accel, std::ignore) = motion_device->GetStatus(); + accel *= accelerometer_coef; + // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback + // The time stretch formula should be like + // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity + + accelerometer_entry.x = static_cast(accel.x); + accelerometer_entry.y = static_cast(accel.y); + accelerometer_entry.z = static_cast(accel.z); + } system.Movie().HandleAccelerometerStatus(accelerometer_entry); @@ -278,13 +369,21 @@ void Module::UpdateGyroscopeCallback(std::uintptr_t user_data, s64 cycles_late) GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; - Common::Vec3 gyro; - std::tie(std::ignore, gyro) = motion_device->GetStatus(); - double stretch = system.perf_stats->GetLastFrameTimeScale(); - gyro *= gyroscope_coef * static_cast(stretch); - gyroscope_entry.x = static_cast(gyro.x); - gyroscope_entry.y = static_cast(gyro.y); - gyroscope_entry.z = static_cast(gyro.z); + if (artic_controller.get() && artic_controller->IsReady()) { + ArticBaseController::ControllerData data = artic_controller->GetControllerData(); + + gyroscope_entry.x = data.gyro_x; + gyroscope_entry.y = data.gyro_y; + gyroscope_entry.z = data.gyro_z; + } else { + Common::Vec3 gyro; + std::tie(std::ignore, gyro) = motion_device->GetStatus(); + double stretch = system.perf_stats->GetLastFrameTimeScale(); + gyro *= gyroscope_coef * static_cast(stretch); + gyroscope_entry.x = static_cast(gyro.x); + gyroscope_entry.y = static_cast(gyro.y); + gyroscope_entry.z = static_cast(gyro.z); + } system.Movie().HandleGyroscopeStatus(gyroscope_entry); @@ -316,6 +415,23 @@ void Module::Interface::GetIPCHandles(Kernel::HLERequestContext& ctx) { void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_EnableAccelerometer"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + ++hid->enable_accelerometer_count; // Schedules the accelerometer update event if the accelerometer was just enabled @@ -324,15 +440,29 @@ void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) { hid->accelerometer_update_event); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_DisableAccelerometer"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + --hid->enable_accelerometer_count; // Unschedules the accelerometer update event if the accelerometer was just disabled @@ -340,15 +470,29 @@ void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) { hid->system.CoreTiming().UnscheduleEvent(hid->accelerometer_update_event, 0); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_EnableGyroscope"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + ++hid->enable_gyroscope_count; // Schedules the gyroscope update event if the gyroscope was just enabled @@ -356,15 +500,29 @@ void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) { hid->system.CoreTiming().ScheduleEvent(gyroscope_update_ticks, hid->gyroscope_update_event); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_DisableGyroscope"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + --hid->enable_gyroscope_count; // Unschedules the gyroscope update event if the gyroscope was just disabled @@ -372,9 +530,6 @@ void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) { hid->system.CoreTiming().UnscheduleEvent(hid->gyroscope_update_event, 0); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } @@ -382,25 +537,90 @@ void Module::Interface::GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestCon IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - rb.Push(ResultSuccess); - rb.Push(gyroscope_coef); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_GetGyroRawToDpsCoef"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + rb.Push(0.f); + return; + } + + Result res = Result{static_cast(resp->GetMethodResult())}; + if (res.IsError()) { + rb.Push(res); + rb.Push(0.f); + return; + } + + auto coef = resp->GetResponseFloat(0); + if (!coef.has_value()) { + rb.Push(ResultUnknown); + rb.Push(0.f); + return; + } + + rb.Push(res); + rb.Push(*coef); + } else { + rb.Push(ResultSuccess); + rb.Push(gyroscope_coef); + } } void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(6, 0); - rb.Push(ResultSuccess); - const s16 param_unit = 6700; // an approximate value taken from hw - GyroscopeCalibrateParam param = { - {0, param_unit, -param_unit}, - {0, param_unit, -param_unit}, - {0, param_unit, -param_unit}, - }; - rb.PushRaw(param); + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + GyroscopeCalibrateParam param; - LOG_WARNING(Service_HID, "(STUBBED) called"); + auto req = artic_client->NewRequest("HIDUSER_GetGyroCalibrateParam"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + rb.PushRaw(param); + return; + } + + Result res = Result{static_cast(resp->GetMethodResult())}; + if (res.IsError()) { + rb.Push(res); + rb.PushRaw(param); + return; + } + + auto param_buf = resp->GetResponseBuffer(0); + if (!param_buf.has_value() || param_buf->second != sizeof(param)) { + rb.Push(ResultUnknown); + rb.PushRaw(param); + return; + } + memcpy(¶m, param_buf->first, sizeof(param)); + + rb.Push(res); + rb.PushRaw(param); + } else { + rb.Push(ResultSuccess); + + const s16 param_unit = 6700; // an approximate value taken from hw + GyroscopeCalibrateParam param = { + {0, param_unit, -param_unit}, + {0, param_unit, -param_unit}, + {0, param_unit, -param_unit}, + }; + rb.PushRaw(param); + + LOG_WARNING(Service_HID, "(STUBBED) called"); + } } void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) { @@ -454,6 +674,24 @@ Module::Module(Core::System& system) : system(system) { timing.ScheduleEvent(pad_update_ticks, pad_update_event); } +void Module::UseArticClient(const std::shared_ptr& client) { + artic_client = client; + artic_controller = std::make_shared(client); + if (!artic_controller->IsCreated()) { + artic_controller.reset(); + } else { + auto ir_user = system.ServiceManager().GetService("ir:USER"); + if (ir_user.get()) { + ir_user->UseArticController(artic_controller); + } + + auto ir_rst = system.ServiceManager().GetService("ir:rst"); + if (ir_rst.get()) { + ir_rst->UseArticController(artic_controller); + } + } +} + void Module::ReloadInputDevices() { is_device_reload_pending.store(true); } diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 609cb9276..79713ca69 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -14,13 +14,11 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/settings.h" +#include "core/core.h" #include "core/core_timing.h" #include "core/frontend/input.h" #include "core/hle/service/service.h" - -namespace Core { -class System; -} +#include "network/artic_base/artic_base_client.h" namespace Kernel { class Event; @@ -199,6 +197,44 @@ struct DirectionState { /// Translates analog stick axes to directions. This is exposed for ir_rst module to use. DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y); +class ArticBaseController { +public: + struct ControllerData { + u32 index{}; + u32 pad{}; + s16 c_pad_x{}; + s16 c_pad_y{}; + u16 touch_x{}; + u16 touch_y{}; + s16 c_stick_x{}; + s16 c_stick_y{}; + s16 accel_x{}; + s16 accel_y{}; + s16 accel_z{}; + s16 gyro_x{}; + s16 gyro_y{}; + s16 gyro_z{}; + }; + static_assert(sizeof(ControllerData) == 0x20, "Incorrect ControllerData size"); + + ArticBaseController(const std::shared_ptr& client); + + bool IsCreated() { + return udp_stream.get(); + } + + bool IsReady() { + return udp_stream.get() ? udp_stream->IsReady() : false; + } + + ControllerData GetControllerData(); + +private: + std::shared_ptr udp_stream; + u32 last_packet_id{}; + ControllerData last_controller_data{}; +}; + class Module final { public: explicit Module(Core::System& system); @@ -296,6 +332,8 @@ public: std::shared_ptr hid; }; + void UseArticClient(const std::shared_ptr& client); + void ReloadInputDevices(); const PadState& GetState() const; @@ -355,6 +393,9 @@ private: std::unique_ptr touch_device; std::unique_ptr touch_btn_device; + std::shared_ptr artic_controller; + std::shared_ptr artic_client; + template void serialize(Archive& ar, const unsigned int); friend class boost::serialization::access; diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp index e159a70fe..7126facf6 100644 --- a/src/core/hle/service/ir/extra_hid.cpp +++ b/src/core/hle/service/ir/extra_hid.cpp @@ -6,6 +6,7 @@ #include "common/alignment.h" #include "common/settings.h" #include "core/core_timing.h" +#include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/extra_hid.h" #include "core/movie.h" @@ -230,23 +231,47 @@ void ExtraHID::SendHIDStatus() { if (is_device_reload_pending.exchange(false)) LoadInputDevices(); + constexpr u32 ZL_BUTTON = (1 << 14); + constexpr u32 ZR_BUTTON = (1 << 15); + constexpr int C_STICK_CENTER = 0x800; // TODO(wwylele): this value is not accurately measured. We currently assume that the axis can // take values in the whole range of a 12-bit integer. constexpr int C_STICK_RADIUS = 0x7FF; - float x, y; - std::tie(x, y) = c_stick->GetStatus(); - ExtraHIDResponse response{}; - response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); - response.c_stick.c_stick_x.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * x)); - response.c_stick.c_stick_y.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * y)); - response.buttons.battery_level.Assign(0x1F); - response.buttons.zl_not_held.Assign(!zl->GetStatus()); - response.buttons.zr_not_held.Assign(!zr->GetStatus()); - response.buttons.r_not_held.Assign(1); - response.unknown = 0; + + if (artic_controller.get() && artic_controller->IsReady()) { + Service::HID::ArticBaseController::ControllerData data = + artic_controller->GetControllerData(); + + constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius + + response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); + response.c_stick.c_stick_x.Assign(static_cast( + (static_cast(data.c_stick_x) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS + + C_STICK_CENTER)); + response.c_stick.c_stick_y.Assign(static_cast( + (static_cast(data.c_stick_y) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS + + C_STICK_CENTER)); + response.buttons.battery_level.Assign(0x1F); + response.buttons.zl_not_held.Assign((data.pad & ZL_BUTTON) == 0); + response.buttons.zr_not_held.Assign((data.pad & ZR_BUTTON) == 0); + response.buttons.r_not_held.Assign(1); + response.unknown = 0; + } else { + float x, y; + std::tie(x, y) = c_stick->GetStatus(); + + response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); + response.c_stick.c_stick_x.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * x)); + response.c_stick.c_stick_y.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * y)); + response.buttons.battery_level.Assign(0x1F); + response.buttons.zl_not_held.Assign(!zl->GetStatus()); + response.buttons.zr_not_held.Assign(!zr->GetStatus()); + response.buttons.r_not_held.Assign(1); + response.unknown = 0; + } movie.HandleExtraHidResponse(response); diff --git a/src/core/hle/service/ir/extra_hid.h b/src/core/hle/service/ir/extra_hid.h index 0132b07b9..8a3ca3372 100644 --- a/src/core/hle/service/ir/extra_hid.h +++ b/src/core/hle/service/ir/extra_hid.h @@ -19,6 +19,10 @@ class Timing; class Movie; } // namespace Core +namespace Service::HID { +class ArticBaseController; +}; + namespace Service::IR { struct ExtraHIDResponse { @@ -54,6 +58,10 @@ public: /// Requests input devices reload from current settings. Called when the input settings change. void RequestInputDevicesReload(); + void UseArticController(const std::shared_ptr& ac) { + artic_controller = ac; + } + private: void SendHIDStatus(); void HandleConfigureHIDPollingRequest(std::span request); @@ -70,6 +78,8 @@ private: std::unique_ptr c_stick; std::atomic is_device_reload_pending; + std::shared_ptr artic_controller = nullptr; + template void serialize(Archive& ar, const unsigned int) { ar & hid_period; diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 469abd413..fbe340181 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -72,25 +72,41 @@ void IR_RST::UpdateCallback(std::uintptr_t user_data, s64 cycles_late) { if (is_device_reload_pending.exchange(false)) LoadInputDevices(); + constexpr u32 VALID_EXTRAHID_KEYS = 0xF00C000; + PadState state; - state.zl.Assign(zl_button->GetStatus()); - state.zr.Assign(zr_button->GetStatus()); + s16 c_stick_x, c_stick_y; - // Get current c-stick position and update c-stick direction - float c_stick_x_f, c_stick_y_f; - std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); - constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius - s16 c_stick_x = static_cast(c_stick_x_f * MAX_CSTICK_RADIUS); - s16 c_stick_y = static_cast(c_stick_y_f * MAX_CSTICK_RADIUS); + if (artic_controller.get() && artic_controller->IsReady()) { + Service::HID::ArticBaseController::ControllerData data = + artic_controller->GetControllerData(); - system.Movie().HandleIrRst(state, c_stick_x, c_stick_y); + state.hex = data.pad & VALID_EXTRAHID_KEYS; - if (!raw_c_stick) { - const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); - state.c_stick_up.Assign(direction.up); - state.c_stick_down.Assign(direction.down); - state.c_stick_left.Assign(direction.left); - state.c_stick_right.Assign(direction.right); + c_stick_x = data.c_stick_x; + c_stick_y = data.c_stick_y; + + system.Movie().HandleIrRst(state, c_stick_x, c_stick_y); + } else { + state.zl.Assign(zl_button->GetStatus()); + state.zr.Assign(zr_button->GetStatus()); + + // Get current c-stick position and update c-stick direction + float c_stick_x_f, c_stick_y_f; + std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); + constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius + c_stick_x = static_cast(c_stick_x_f * MAX_CSTICK_RADIUS); + c_stick_y = static_cast(c_stick_y_f * MAX_CSTICK_RADIUS); + + system.Movie().HandleIrRst(state, c_stick_x, c_stick_y); + + if (!raw_c_stick) { + const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); + state.c_stick_up.Assign(direction.up); + state.c_stick_down.Assign(direction.down); + state.c_stick_left.Assign(direction.left); + state.c_stick_right.Assign(direction.right); + } } // TODO (wwylele): implement raw C-stick data for raw_c_stick = true diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index 2514ab6f9..d02d34aa3 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -21,6 +21,10 @@ namespace Core { struct TimingEventType; }; +namespace Service::HID { +class ArticBaseController; +}; + namespace Service::IR { union PadState { @@ -42,6 +46,10 @@ public: ~IR_RST(); void ReloadInputDevices(); + void UseArticController(const std::shared_ptr& ac) { + artic_controller = ac; + } + private: /** * GetHandles service function @@ -88,6 +96,8 @@ private: bool raw_c_stick{false}; int update_period{0}; + std::shared_ptr artic_controller = nullptr; + template void serialize(Archive& ar, const unsigned int); friend class boost::serialization::access; diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index c9e33a202..f850fa348 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -480,6 +480,12 @@ void IR_USER::ReloadInputDevices() { extra_hid->RequestInputDevicesReload(); } +void IR_USER::UseArticController(const std::shared_ptr& ac) { + if (extra_hid.get()) { + extra_hid->UseArticController(ac); + } +} + IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {} IRDevice::~IRDevice() = default; diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h index e724e9dc5..fac49edfa 100644 --- a/src/core/hle/service/ir/ir_user.h +++ b/src/core/hle/service/ir/ir_user.h @@ -14,6 +14,10 @@ class Event; class SharedMemory; } // namespace Kernel +namespace Service::HID { +class ArticBaseController; +}; + namespace Service::IR { class BufferManager; @@ -57,6 +61,8 @@ public: void ReloadInputDevices(); + void UseArticController(const std::shared_ptr& ac); + private: /** * InitializeIrNopShared service function diff --git a/src/core/loader/artic.cpp b/src/core/loader/artic.cpp index d4ba70060..80365ed6e 100644 --- a/src/core/loader/artic.cpp +++ b/src/core/loader/artic.cpp @@ -27,6 +27,7 @@ #include "core/hle/service/cfg/cfg_u.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/fs_user.h" +#include "core/hle/service/hid/hid_user.h" #include "core/loader/artic.h" #include "core/loader/smdh.h" #include "core/memory.h" @@ -361,6 +362,13 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr& process) { amapp->UseArticClient(client); } + if (Settings::values.use_artic_base_controller.GetValue()) { + auto hid_user = system.ServiceManager().GetService("hid:USER"); + if (hid_user.get()) { + hid_user->GetModule()->UseArticClient(client); + } + } + ParseRegionLockoutInfo(ncch_program_id); return ResultStatus::Success; diff --git a/src/lime_qt/configuration/config.cpp b/src/lime_qt/configuration/config.cpp index 3aa60b225..3919fe430 100644 --- a/src/lime_qt/configuration/config.cpp +++ b/src/lime_qt/configuration/config.cpp @@ -328,6 +328,8 @@ void Config::ReadCameraValues() { void Config::ReadControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); + ReadBasicSetting(Settings::values.use_artic_base_controller); + int num_touch_from_button_maps = qt_config->beginReadArray(QStringLiteral("touch_from_button_maps")); @@ -940,6 +942,8 @@ void Config::SaveCameraValues() { void Config::SaveControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); + WriteBasicSetting(Settings::values.use_artic_base_controller); + WriteSetting(QStringLiteral("profile"), Settings::values.current_input_profile_index, 0); qt_config->beginWriteArray(QStringLiteral("profiles")); for (std::size_t p = 0; p < Settings::values.input_profiles.size(); ++p) { diff --git a/src/lime_qt/configuration/configure_dialog.cpp b/src/lime_qt/configuration/configure_dialog.cpp index 12cdf1b3f..174663c13 100644 --- a/src/lime_qt/configuration/configure_dialog.cpp +++ b/src/lime_qt/configuration/configure_dialog.cpp @@ -30,7 +30,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor system{system_}, is_powered_on{system.IsPoweredOn()}, general_tab{std::make_unique(this)}, system_tab{std::make_unique(system, this)}, - input_tab{std::make_unique(this)}, + input_tab{std::make_unique(system, this)}, hotkeys_tab{std::make_unique(this)}, graphics_tab{ std::make_unique(gl_renderer, physical_devices, is_powered_on, this)}, diff --git a/src/lime_qt/configuration/configure_input.cpp b/src/lime_qt/configuration/configure_input.cpp index 754bd3999..39b461996 100644 --- a/src/lime_qt/configuration/configure_input.cpp +++ b/src/lime_qt/configuration/configure_input.cpp @@ -13,6 +13,7 @@ #include #include #include "common/param_package.h" +#include "core/core.h" #include "lime_qt/configuration/config.h" #include "lime_qt/configuration/configure_input.h" #include "lime_qt/configuration/configure_motion_touch.h" @@ -145,8 +146,8 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string return QObject::tr("[unknown]"); } -ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique()), +ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) + : QWidget(parent), system(_system), ui(std::make_unique()), timeout_timer(std::make_unique()), poll_timer(std::make_unique()) { ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -400,6 +401,9 @@ ConfigureInput::ConfigureInput(QWidget* parent) ConfigureInput::~ConfigureInput() = default; void ConfigureInput::ApplyConfiguration() { + + Settings::values.use_artic_base_controller = ui->use_artic_controller->isChecked(); + std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.current_input_profile.buttons.begin(), [](const Common::ParamPackage& param) { return param.Serialize(); }); @@ -444,6 +448,10 @@ QList ConfigureInput::GetUsedKeyboardKeys() { } void ConfigureInput::LoadConfiguration() { + + ui->use_artic_controller->setChecked(Settings::values.use_artic_base_controller.GetValue()); + ui->use_artic_controller->setEnabled(!system.IsPoweredOn()); + std::transform(Settings::values.current_input_profile.buttons.begin(), Settings::values.current_input_profile.buttons.end(), buttons_param.begin(), [](const std::string& str) { return Common::ParamPackage(str); }); diff --git a/src/lime_qt/configuration/configure_input.h b/src/lime_qt/configuration/configure_input.h index fb00444c6..45a7a8329 100644 --- a/src/lime_qt/configuration/configure_input.h +++ b/src/lime_qt/configuration/configure_input.h @@ -30,7 +30,7 @@ class ConfigureInput : public QWidget { Q_OBJECT public: - explicit ConfigureInput(QWidget* parent = nullptr); + explicit ConfigureInput(Core::System& system, QWidget* parent = nullptr); ~ConfigureInput() override; /// Save all button configurations to settings file @@ -50,6 +50,7 @@ signals: void InputKeysChanged(QList new_key_list); private: + Core::System& system; std::unique_ptr ui; std::unique_ptr timeout_timer; diff --git a/src/lime_qt/configuration/configure_input.ui b/src/lime_qt/configuration/configure_input.ui index 2d199e667..7168b7190 100644 --- a/src/lime_qt/configuration/configure_input.ui +++ b/src/lime_qt/configuration/configure_input.ui @@ -841,6 +841,13 @@ + + + + Use Artic Controller when connected to Artic Base Server + + + diff --git a/src/network/artic_base/artic_base_client.cpp b/src/network/artic_base/artic_base_client.cpp index 88d0feb3c..26f92f83f 100644 --- a/src/network/artic_base/artic_base_client.cpp +++ b/src/network/artic_base/artic_base_client.cpp @@ -121,6 +121,81 @@ Client::Request::Request(u32 request_id, const std::string& method, size_t max_p std::min(request_packet.method.size(), method.size())); } +void Client::UDPStream::Start() { + thread_run = true; + handle_thread = std::thread(&Client::UDPStream::Handle, this); +} + +void Client::UDPStream::Handle() { + struct sockaddr_in* servaddr = reinterpret_cast(serv_sockaddr_in.data()); + socklen_t serv_sockaddr_len = static_cast(serv_sockaddr_in.size()); + memcpy(servaddr, client.GetServerAddr().data(), client.GetServerAddr().size()); + servaddr->sin_port = htons(port); + + main_socket = ::socket(AF_INET, SOCK_DGRAM, 0); + if (main_socket == static_cast(-1) || !thread_run) { + LOG_ERROR(Network, "Failed to create socket"); + return; + } + + if (!SetNonBlock(main_socket, true) || !thread_run) { + closesocket(main_socket); + LOG_ERROR(Network, "Cannot set non-blocking socket mode"); + return; + } + + // Limit receive buffer so that packets don't get qeued and are dropped instead. + int buffer_size_int = static_cast(buffer_size); + if (::setsockopt(main_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&buffer_size_int), + sizeof(buffer_size_int)) || + !thread_run) { + closesocket(main_socket); + LOG_ERROR(Network, "Cannot change receive buffer size"); + return; + } + + // Send data to server so that it knows client address. + char zero = '\0'; + int send_res = + ::sendto(main_socket, &zero, sizeof(char), 0, + reinterpret_cast(serv_sockaddr_in.data()), serv_sockaddr_len); + if (send_res < 0 || !thread_run) { + closesocket(main_socket); + LOG_ERROR(Network, "Cannot send data to socket"); + return; + } + + ready = true; + std::vector buffer(buffer_size); + while (thread_run) { + std::chrono::steady_clock::time_point before = std::chrono::steady_clock::now(); + + int packet_size = ::recvfrom( + main_socket, reinterpret_cast(buffer.data()), static_cast(buffer.size()), 0, + reinterpret_cast(serv_sockaddr_in.data()), &serv_sockaddr_len); + if (packet_size > 0) { + if (client.report_traffic_callback) { + client.report_traffic_callback(packet_size); + } + + buffer.resize(packet_size); + { + std::scoped_lock l(current_buffer_mutex); + current_buffer = buffer; + } + } + + auto elapsed = std::chrono::steady_clock::now() - before; + + std::unique_lock lk(thread_cv_mutex); + thread_cv.wait_for(lk, elapsed < read_interval ? (read_interval - elapsed) + : std::chrono::microseconds(50)); + } + ready = false; + + closesocket(main_socket); +} + Client::~Client() { StopImpl(false); @@ -182,6 +257,7 @@ bool Client::Connect() { servaddr.sin_addr.s_addr = ((struct sockaddr_in*)(addrinfo->ai_addr))->sin_addr.s_addr; servaddr.sin_port = htons(port); freeaddrinfo(addrinfo); + memcpy(last_sockaddr_in.data(), &servaddr, last_sockaddr_in.size()); if (!ConnectWithTimeout(main_socket, &servaddr, sizeof(servaddr), 10)) { closesocket(main_socket); @@ -249,15 +325,15 @@ bool Client::Connect() { std::string str_port; std::stringstream ss_port(worker_ports.value()); while (std::getline(ss_port, str_port, ',')) { - int port = str_to_int(str_port); - if (port < 0 || port > USHRT_MAX) { + int port_curr = str_to_int(str_port); + if (port_curr < 0 || port_curr > static_cast(USHRT_MAX)) { shutdown(main_socket, SHUT_RDWR); closesocket(main_socket); LOG_ERROR(Network, "Couldn't parse server worker ports"); SignalCommunicationError(); return false; } - ports.push_back(static_cast(port)); + ports.push_back(static_cast(port_curr)); } if (ports.empty()) { shutdown(main_socket, SHUT_RDWR); @@ -294,6 +370,29 @@ bool Client::Connect() { return true; } +std::shared_ptr Client::NewUDPStream( + const std::string stream_id, size_t buffer_size, + const std::chrono::milliseconds& read_interval) { + + auto req = NewRequest("#" + stream_id); + + auto resp = Send(req); + + if (!resp.has_value()) { + return nullptr; + } + + auto port_udp = resp->GetResponseS32(0); + if (!port_udp.has_value()) { + return nullptr; + } + + udp_streams.push_back(std::make_shared(*this, static_cast(*port_udp), + buffer_size, read_interval)); + + return udp_streams.back(); +} + void Client::StopImpl(bool from_error) { bool expected = false; if (!stopped.compare_exchange_strong(expected, true)) @@ -303,6 +402,10 @@ void Client::StopImpl(bool from_error) { SendSimpleRequest("STOP"); } + for (auto it = udp_streams.begin(); it != udp_streams.end(); it++) { + it->get()->Stop(); + } + if (ping_thread.joinable()) { std::scoped_lock l2(ping_cv_mutex); ping_run = false; diff --git a/src/network/artic_base/artic_base_client.h b/src/network/artic_base/artic_base_client.h index 23079f832..040d50c9c 100644 --- a/src/network/artic_base/artic_base_client.h +++ b/src/network/artic_base/artic_base_client.h @@ -60,6 +60,61 @@ public: std::vector> pending_big_buffers; }; + class UDPStream { + public: + std::vector GetLastPacket() { + std::scoped_lock l(current_buffer_mutex); + return current_buffer; + } + + bool IsReady() { + return ready; + } + + void Start(); + void Stop() { + if (thread_run && handle_thread.joinable()) { + std::scoped_lock l2(thread_cv_mutex); + thread_run = false; + thread_cv.notify_one(); + } + } + + UDPStream(Client& _client, u16 _port, size_t _buffer_size, + const std::chrono::milliseconds& _read_interval) + : client(_client), port(_port), buffer_size(_buffer_size), + read_interval(_read_interval) {} + + ~UDPStream() { + Stop(); + if (handle_thread.joinable()) { + handle_thread.join(); + } + } + + private: + void Handle(); + + Client& client; + u16 port; + size_t buffer_size; + std::chrono::milliseconds read_interval; + + std::array serv_sockaddr_in{}; + bool ready = false; + + std::mutex current_buffer_mutex; + std::vector current_buffer; + + SocketHolder main_socket = -1; + + std::thread handle_thread; + std::condition_variable thread_cv; + std::mutex thread_cv_mutex; + std::atomic thread_run = true; + }; + friend class UDPStream; + Client(const std::string& _address, u16 _port) : address(_address), port(_port) { SocketManager::EnableSockets(); } @@ -76,6 +131,10 @@ public: return Request(GetNextRequestID(), method, max_parameter_count); } + std::shared_ptr NewUDPStream( + const std::string stream_id, size_t buffer_size, + const std::chrono::milliseconds& read_interval = std::chrono::milliseconds(0)); + void Stop() { StopImpl(false); } @@ -97,11 +156,17 @@ public: report_artic_event_callback = callback; } + // Returns the server address as a sockaddr_in struct + const std::array& GetServerAddr() { + return last_sockaddr_in; + } + private: - static constexpr const int SERVER_VERSION = 1; + static constexpr const int SERVER_VERSION = 2; std::string address; u16 port; + std::array last_sockaddr_in; SocketHolder main_socket = -1; std::atomic currRequestID; @@ -124,7 +189,7 @@ private: std::thread ping_thread; std::condition_variable ping_cv; std::mutex ping_cv_mutex; - bool ping_run = true; + std::atomic ping_run = true; void StopImpl(bool from_error); @@ -145,6 +210,8 @@ private: const std::chrono::nanoseconds& read_timeout = std::chrono::nanoseconds(0)); std::optional SendSimpleRequest(const std::string& method); + std::vector> udp_streams; + class Handler { public: Handler(Client& _client, u32 _addr, u16 _port, int _id); @@ -242,6 +309,14 @@ public: return *reinterpret_cast(buf->first); } + std::optional GetResponseFloat(u32 buffer_id) const { + auto buf = GetResponseBuffer(buffer_id); + if (!buf.has_value() || buf->second != sizeof(float)) { + return std::nullopt; + } + return *reinterpret_cast(buf->first); + } + private: friend class Client; friend class Client::Handler;