diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 2a49e8b85..2cde7d7ff 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -328,4 +328,6 @@
Start File
Update Notes
This DuckStation update includes support for multiple controllers with vibration, and binding devices such as keyboards/volume buttons.\n\nYou must re-bind your controllers, otherwise they will no longer function. Do you want to do this now?
+ Show Controller Input
+ Shows the current controller state of the system in the bottom-left corner of the display.
diff --git a/android/app/src/main/res/xml/display_preferences.xml b/android/app/src/main/res/xml/display_preferences.xml
index d542c12c9..a195dbaae 100644
--- a/android/app/src/main/res/xml/display_preferences.xml
+++ b/android/app/src/main/res/xml/display_preferences.xml
@@ -98,5 +98,11 @@
app:defaultValue="false"
app:summary="@string/settings_summary_osd_show_resolution"
app:iconSpaceReserved="false" />
+
diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp
index ce14e4d7c..93c1702b0 100644
--- a/src/core/analog_controller.cpp
+++ b/src/core/analog_controller.cpp
@@ -104,6 +104,16 @@ std::optional AnalogController::GetButtonCodeByName(std::string_view button
return StaticGetButtonCodeByName(button_name);
}
+float AnalogController::GetAxisState(s32 axis_code) const
+{
+ if (axis_code < 0 || axis_code >= static_cast(Axis::Count))
+ return 0.0f;
+
+ // 0..255 -> -1..1
+ const float value = (((static_cast(m_axis_state[static_cast(axis_code)]) / 255.0f) * 2.0f) - 1.0f);
+ return std::clamp(value / m_axis_scale, -1.0f, 1.0f);
+}
+
void AnalogController::SetAxisState(s32 axis_code, float value)
{
if (axis_code < 0 || axis_code >= static_cast(Axis::Count))
@@ -124,6 +134,15 @@ void AnalogController::SetAxisState(Axis axis, u8 value)
m_axis_state[static_cast(axis)] = value;
}
+bool AnalogController::GetButtonState(s32 button_code) const
+{
+ if (button_code < 0 || button_code >= static_cast(Button::Analog))
+ return false;
+
+ const u16 bit = u16(1) << static_cast(button_code);
+ return ((m_button_state & bit) == 0);
+}
+
void AnalogController::SetButtonState(Button button, bool pressed)
{
if (button == Button::Analog)
diff --git a/src/core/analog_controller.h b/src/core/analog_controller.h
index 96cb6a6b9..659516d1e 100644
--- a/src/core/analog_controller.h
+++ b/src/core/analog_controller.h
@@ -59,7 +59,9 @@ public:
void Reset() override;
bool DoState(StateWrapper& sw, bool ignore_input_state) override;
+ float GetAxisState(s32 axis_code) const override;
void SetAxisState(s32 axis_code, float value) override;
+ bool GetButtonState(s32 button_code) const override;
void SetButtonState(s32 button_code, bool pressed) override;
u32 GetButtonStateBits() const override;
std::optional GetAnalogInputBytes() const override;
diff --git a/src/core/analog_joystick.cpp b/src/core/analog_joystick.cpp
index b47e88760..dceb52af0 100644
--- a/src/core/analog_joystick.cpp
+++ b/src/core/analog_joystick.cpp
@@ -69,6 +69,16 @@ std::optional AnalogJoystick::GetButtonCodeByName(std::string_view button_n
return StaticGetButtonCodeByName(button_name);
}
+float AnalogJoystick::GetAxisState(s32 axis_code) const
+{
+ if (axis_code < 0 || axis_code >= static_cast(Axis::Count))
+ return 0.0f;
+
+ // 0..255 -> -1..1
+ const float value = (((static_cast(m_axis_state[static_cast(axis_code)]) / 255.0f) * 2.0f) - 1.0f);
+ return std::clamp(value / m_axis_scale, -1.0f, 1.0f);
+}
+
void AnalogJoystick::SetAxisState(s32 axis_code, float value)
{
if (axis_code < 0 || axis_code >= static_cast(Axis::Count))
@@ -89,6 +99,15 @@ void AnalogJoystick::SetAxisState(Axis axis, u8 value)
m_axis_state[static_cast(axis)] = value;
}
+bool AnalogJoystick::GetButtonState(s32 button_code) const
+{
+ if (button_code < 0 || button_code >= static_cast(Button::Count))
+ return false;
+
+ const u16 bit = u16(1) << static_cast(button_code);
+ return ((m_button_state & bit) == 0);
+}
+
void AnalogJoystick::SetButtonState(Button button, bool pressed)
{
if (button == Button::Mode)
diff --git a/src/core/analog_joystick.h b/src/core/analog_joystick.h
index b5bf0d414..a833201ea 100644
--- a/src/core/analog_joystick.h
+++ b/src/core/analog_joystick.h
@@ -57,7 +57,9 @@ public:
void Reset() override;
bool DoState(StateWrapper& sw, bool apply_input_state) override;
+ float GetAxisState(s32 axis_code) const override;
void SetAxisState(s32 axis_code, float value) override;
+ bool GetButtonState(s32 button_code) const override;
void SetButtonState(s32 button_code, bool pressed) override;
u32 GetButtonStateBits() const override;
std::optional GetAnalogInputBytes() const override;
diff --git a/src/core/controller.cpp b/src/core/controller.cpp
index 4eaee7431..e4fd7b223 100644
--- a/src/core/controller.cpp
+++ b/src/core/controller.cpp
@@ -26,8 +26,18 @@ bool Controller::Transfer(const u8 data_in, u8* data_out)
return false;
}
+float Controller::GetAxisState(s32 axis_code) const
+{
+ return 0.0f;
+}
+
void Controller::SetAxisState(s32 axis_code, float value) {}
+bool Controller::GetButtonState(s32 button_code) const
+{
+ return false;
+}
+
void Controller::SetButtonState(s32 button_code, bool pressed) {}
u32 Controller::GetButtonStateBits() const
diff --git a/src/core/controller.h b/src/core/controller.h
index de3d26da5..28dafd4ed 100644
--- a/src/core/controller.h
+++ b/src/core/controller.h
@@ -45,9 +45,15 @@ public:
// Returns the value of ACK, as well as filling out_data.
virtual bool Transfer(const u8 data_in, u8* data_out);
+ /// Changes the specified axis state. Values are normalized from -1..1.
+ virtual float GetAxisState(s32 axis_code) const;
+
/// Changes the specified axis state. Values are normalized from -1..1.
virtual void SetAxisState(s32 axis_code, float value);
+ /// Returns the specified button state.
+ virtual bool GetButtonState(s32 button_code) const;
+
/// Changes the specified button state.
virtual void SetButtonState(s32 button_code, bool pressed);
diff --git a/src/core/digital_controller.cpp b/src/core/digital_controller.cpp
index a302a069c..8b4af20c9 100644
--- a/src/core/digital_controller.cpp
+++ b/src/core/digital_controller.cpp
@@ -42,7 +42,14 @@ bool DigitalController::DoState(StateWrapper& sw, bool apply_input_state)
return true;
}
-void DigitalController::SetAxisState(s32 axis_code, float value) {}
+bool DigitalController::GetButtonState(s32 button_code) const
+{
+ if (button_code < 0 || button_code >= static_cast(Button::Count))
+ return false;
+
+ const u16 bit = u16(1) << static_cast(button_code);
+ return ((m_button_state & bit) == 0);
+}
void DigitalController::SetButtonState(Button button, bool pressed)
{
diff --git a/src/core/digital_controller.h b/src/core/digital_controller.h
index d57b91331..8d3f1f29b 100644
--- a/src/core/digital_controller.h
+++ b/src/core/digital_controller.h
@@ -46,7 +46,7 @@ public:
void Reset() override;
bool DoState(StateWrapper& sw, bool apply_input_state) override;
- void SetAxisState(s32 axis_code, float value) override;
+ bool GetButtonState(s32 button_code) const override;
void SetButtonState(s32 button_code, bool pressed) override;
u32 GetButtonStateBits() const override;
diff --git a/src/core/namco_guncon.cpp b/src/core/namco_guncon.cpp
index 43e63651f..8a3122d98 100644
--- a/src/core/namco_guncon.cpp
+++ b/src/core/namco_guncon.cpp
@@ -56,7 +56,14 @@ bool NamcoGunCon::DoState(StateWrapper& sw, bool apply_input_state)
return true;
}
-void NamcoGunCon::SetAxisState(s32 axis_code, float value) {}
+bool NamcoGunCon::GetButtonState(s32 button_code) const
+{
+ if (button_code < 0 || button_code > static_cast(Button::B))
+ return false;
+
+ const u16 bit = u16(1) << static_cast(button_code);
+ return ((m_button_state & bit) == 0);
+}
void NamcoGunCon::SetButtonState(Button button, bool pressed)
{
diff --git a/src/core/namco_guncon.h b/src/core/namco_guncon.h
index eba8edf6f..cd99873fb 100644
--- a/src/core/namco_guncon.h
+++ b/src/core/namco_guncon.h
@@ -36,7 +36,7 @@ public:
void LoadSettings(const char* section) override;
bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode) override;
- void SetAxisState(s32 axis_code, float value) override;
+ bool GetButtonState(s32 button_code) const override;
void SetButtonState(s32 button_code, bool pressed) override;
void ResetTransferState() override;
diff --git a/src/core/negcon.cpp b/src/core/negcon.cpp
index 1d286dcfb..1c109df0b 100644
--- a/src/core/negcon.cpp
+++ b/src/core/negcon.cpp
@@ -48,6 +48,16 @@ bool NeGcon::DoState(StateWrapper& sw, bool apply_input_state)
return true;
}
+float NeGcon::GetAxisState(s32 axis_code) const
+{
+ if (axis_code < 0 || axis_code >= static_cast(Axis::Count))
+ return 0.0f;
+
+ // 0..255 -> -1..1
+ const float value = (((static_cast(m_axis_state[static_cast(axis_code)]) / 255.0f) * 2.0f) - 1.0f);
+ return std::clamp(value, -1.0f, 1.0f);
+}
+
void NeGcon::SetAxisState(s32 axis_code, float value)
{
if (axis_code < 0 || axis_code >= static_cast(Axis::Count))
@@ -78,6 +88,15 @@ void NeGcon::SetAxisState(Axis axis, u8 value)
m_axis_state[static_cast(axis)] = value;
}
+bool NeGcon::GetButtonState(s32 button_code) const
+{
+ if (button_code < 0 || button_code >= static_cast(Button::Count))
+ return false;
+
+ const u16 bit = u16(1) << static_cast(button_code);
+ return ((m_button_state & bit) == 0);
+}
+
void NeGcon::SetButtonState(s32 button_code, bool pressed)
{
if (button_code < 0 || button_code >= static_cast(Button::Count))
diff --git a/src/core/negcon.h b/src/core/negcon.h
index 6f5746173..8db6f1607 100644
--- a/src/core/negcon.h
+++ b/src/core/negcon.h
@@ -48,7 +48,10 @@ public:
void Reset() override;
bool DoState(StateWrapper& sw, bool apply_input_state) override;
+ float GetAxisState(s32 axis_code) const override;
void SetAxisState(s32 axis_code, float value) override;
+
+ bool GetButtonState(s32 button_code) const override;
void SetButtonState(s32 button_code, bool pressed) override;
void ResetTransferState() override;
diff --git a/src/core/playstation_mouse.cpp b/src/core/playstation_mouse.cpp
index b6ec365c7..ab57fe49f 100644
--- a/src/core/playstation_mouse.cpp
+++ b/src/core/playstation_mouse.cpp
@@ -59,7 +59,14 @@ bool PlayStationMouse::DoState(StateWrapper& sw, bool apply_input_state)
return true;
}
-void PlayStationMouse::SetAxisState(s32 axis_code, float value) {}
+bool PlayStationMouse::GetButtonState(s32 button_code) const
+{
+ if (button_code < 0 || button_code >= static_cast(Button::Count))
+ return false;
+
+ const u16 bit = u16(1) << static_cast(button_code);
+ return ((m_button_state & bit) == 0);
+}
void PlayStationMouse::SetButtonState(Button button, bool pressed)
{
diff --git a/src/core/playstation_mouse.h b/src/core/playstation_mouse.h
index ecfc4abfd..8631bff45 100644
--- a/src/core/playstation_mouse.h
+++ b/src/core/playstation_mouse.h
@@ -32,7 +32,7 @@ public:
void Reset() override;
bool DoState(StateWrapper& sw, bool apply_input_state) override;
- void SetAxisState(s32 axis_code, float value) override;
+ bool GetButtonState(s32 button_code) const override;
void SetButtonState(s32 button_code, bool pressed) override;
void ResetTransferState() override;
diff --git a/src/duckstation-qt/displaysettingswidget.cpp b/src/duckstation-qt/displaysettingswidget.cpp
index 7e31515fe..2b28f468f 100644
--- a/src/duckstation-qt/displaysettingswidget.cpp
+++ b/src/duckstation-qt/displaysettingswidget.cpp
@@ -51,6 +51,7 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showSpeed, "Display", "ShowSpeed", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showResolution, "Display", "ShowResolution",
false);
+ SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showInput, "Display", "ShowInputs", false);
connect(m_ui.renderer, QOverload::of(&QComboBox::currentIndexChanged), this,
&DisplaySettingsWidget::populateGPUAdaptersAndResolutions);
@@ -135,6 +136,9 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage."));
dialog->registerWidgetHelp(m_ui.showResolution, tr("Show Resolution"), tr("Unchecked"),
tr("Shows the resolution of the game in the top-right corner of the display."));
+ dialog->registerWidgetHelp(
+ m_ui.showInput, tr("Show Controller Input"), tr("Unchecked"),
+ tr("Shows the current controller state of the system in the bottom-left corner of the display."));
#ifdef _WIN32
{
diff --git a/src/duckstation-qt/displaysettingswidget.ui b/src/duckstation-qt/displaysettingswidget.ui
index 68021d442..9ac7ca856 100644
--- a/src/duckstation-qt/displaysettingswidget.ui
+++ b/src/duckstation-qt/displaysettingswidget.ui
@@ -209,6 +209,13 @@
+ -
+
+
+ Show Controller Input
+
+
+
diff --git a/src/frontend-common/CMakeLists.txt b/src/frontend-common/CMakeLists.txt
index 46f17a6b0..621ecea4b 100644
--- a/src/frontend-common/CMakeLists.txt
+++ b/src/frontend-common/CMakeLists.txt
@@ -17,8 +17,10 @@ add_library(frontend-common
icon.h
ini_settings_interface.cpp
ini_settings_interface.h
- imgui_fullscreen.h
+ input_overlay_ui.cpp
+ input_overlay_ui.h
imgui_fullscreen.cpp
+ imgui_fullscreen.h
imgui_impl_opengl3.cpp
imgui_impl_opengl3.h
imgui_impl_vulkan.cpp
diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp
index cf3df6b44..b03bc2816 100644
--- a/src/frontend-common/common_host_interface.cpp
+++ b/src/frontend-common/common_host_interface.cpp
@@ -28,6 +28,7 @@
#include "imgui_fullscreen.h"
#include "imgui_styles.h"
#include "ini_settings_interface.h"
+#include "input_overlay_ui.h"
#include "save_state_selector_ui.h"
#include "scmversion/scmversion.h"
#include
@@ -57,6 +58,7 @@
Log_SetChannel(CommonHostInterface);
static std::string s_settings_filename;
+static std::unique_ptr s_input_overlay_ui;
CommonHostInterface::CommonHostInterface() = default;
@@ -104,6 +106,8 @@ bool CommonHostInterface::Initialize()
void CommonHostInterface::Shutdown()
{
+ s_input_overlay_ui.reset();
+
HostInterface::Shutdown();
ImGui::DestroyContext();
@@ -1038,6 +1042,9 @@ void CommonHostInterface::DrawImGuiWindows()
if (m_save_state_selector_ui->IsOpen())
m_save_state_selector_ui->Draw();
+ if (s_input_overlay_ui)
+ s_input_overlay_ui->Draw();
+
if (m_fullscreen_ui_enabled)
{
FullscreenUI::Render();
@@ -1210,11 +1217,6 @@ void CommonHostInterface::DrawOSDMessages()
{
AcquirePendingOSDMessages();
- constexpr ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
- ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
- ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
- ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing;
-
const float scale = ImGui::GetIO().DisplayFramebufferScale.x;
const float spacing = 5.0f * scale;
const float margin = 10.0f * scale;
@@ -2639,6 +2641,13 @@ void CommonHostInterface::LoadSettings(SettingsInterface& si)
}
}
}
+
+ const bool input_display_enabled = si.GetBoolValue("Display", "ShowInputs", false);
+ const bool input_display_state = static_cast(s_input_overlay_ui);
+ if (input_display_enabled && !s_input_overlay_ui)
+ s_input_overlay_ui = std::make_unique();
+ else if (!input_display_enabled && s_input_overlay_ui)
+ s_input_overlay_ui.reset();
}
void CommonHostInterface::SaveSettings(SettingsInterface& si)
diff --git a/src/frontend-common/frontend-common.vcxproj b/src/frontend-common/frontend-common.vcxproj
index 5ea5b5e2a..eb376ea23 100644
--- a/src/frontend-common/frontend-common.vcxproj
+++ b/src/frontend-common/frontend-common.vcxproj
@@ -104,6 +104,7 @@
+
@@ -135,6 +136,7 @@
+
diff --git a/src/frontend-common/frontend-common.vcxproj.filters b/src/frontend-common/frontend-common.vcxproj.filters
index 08b2c5c4e..54e7c39a0 100644
--- a/src/frontend-common/frontend-common.vcxproj.filters
+++ b/src/frontend-common/frontend-common.vcxproj.filters
@@ -30,6 +30,7 @@
+
@@ -61,6 +62,7 @@
+
diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp
index 44537fb24..6069b1dd7 100644
--- a/src/frontend-common/fullscreen_ui.cpp
+++ b/src/frontend-common/fullscreen_ui.cpp
@@ -1980,6 +1980,10 @@ void DrawSettingsWindow()
ToggleButton("Show Resolution",
"Shows the current rendering resolution of the system in the top-right corner of the display.",
&s_settings_copy.display_show_resolution);
+ settings_changed |= ToggleButtonForNonSetting(
+ "Show Controller Input",
+ "Shows the current controller state of the system in the bottom-left corner of the display.", "Display",
+ "ShowInputs", false);
EndMenuButtons();
}
diff --git a/src/frontend-common/input_overlay_ui.cpp b/src/frontend-common/input_overlay_ui.cpp
new file mode 100644
index 000000000..a11ed19bf
--- /dev/null
+++ b/src/frontend-common/input_overlay_ui.cpp
@@ -0,0 +1,118 @@
+#include "input_overlay_ui.h"
+#include "common_host_interface.h"
+#include "core/pad.h"
+#include "core/settings.h"
+#include "core/system.h"
+#include "fullscreen_ui.h"
+#include "imgui_fullscreen.h"
+
+static CommonHostInterface* GetHostInterface()
+{
+ return static_cast(g_host_interface);
+}
+
+namespace FrontendCommon {
+
+InputOverlayUI::InputOverlayUI() = default;
+
+InputOverlayUI::~InputOverlayUI() = default;
+
+void InputOverlayUI::Draw()
+{
+ UpdateNames();
+
+ if (m_active_ports == 0)
+ return;
+
+ ImFont* font;
+ float margin, spacing, shadow_offset;
+
+ if (GetHostInterface()->IsFullscreenUIEnabled())
+ {
+ font = ImGuiFullscreen::g_large_font;
+ margin = ImGuiFullscreen::LayoutScale(10.0f);
+ spacing = ImGuiFullscreen::LayoutScale(5.0f);
+ shadow_offset = ImGuiFullscreen::LayoutScale(1.0f);
+ }
+ else
+ {
+ font = ImGui::GetFont();
+ margin = ImGuiFullscreen::DPIScale(10.0f);
+ spacing = ImGuiFullscreen::DPIScale(5.0f);
+ shadow_offset = ImGuiFullscreen::DPIScale(1.0f);
+ }
+
+ static constexpr u32 text_color = IM_COL32(0xff, 0xff, 0xff, 255);
+ static constexpr u32 shadow_color = IM_COL32(0x00, 0x00, 0x00, 100);
+
+ const ImVec2& display_size = ImGui::GetIO().DisplaySize;
+ ImDrawList* dl = ImGui::GetBackgroundDrawList();
+
+ float current_x = margin;
+ float current_y =
+ display_size.y - margin - ((static_cast(m_active_ports) * (font->FontSize + spacing)) - spacing);
+
+ const ImVec4 clip_rect(current_x, current_y, display_size.x - margin, display_size.y - margin);
+
+ LargeString text;
+
+ for (u32 port = 0; port < NUM_CONTROLLER_AND_CARD_PORTS; port++)
+ {
+ if (m_types[port] == ControllerType::None)
+ continue;
+
+ const Controller* controller = g_pad.GetController(port);
+ DebugAssert(controller);
+
+ text.Format("P%u |", port + 1u);
+
+ if (!m_axis_names[port].empty())
+ {
+ for (const auto& [axis_name, axis_code, axis_type] : m_axis_names[port])
+ {
+ const float value = controller->GetAxisState(axis_code);
+ text.AppendFormattedString(" %s: %.2f", axis_name.c_str(), value);
+ }
+
+ text.AppendString(" |");
+ }
+
+ for (const auto& [button_name, button_code] : m_button_names[port])
+ {
+ const bool pressed = controller->GetButtonState(button_code);
+ if (pressed)
+ text.AppendFormattedString(" %s", button_name.c_str());
+ }
+
+ dl->AddText(font, font->FontSize, ImVec2(current_x + shadow_offset, current_y + shadow_offset), shadow_color,
+ text.GetCharArray(), text.GetCharArray() + text.GetLength(), 0.0f, &clip_rect);
+ dl->AddText(font, font->FontSize, ImVec2(current_x, current_y), text_color, text.GetCharArray(),
+ text.GetCharArray() + text.GetLength(), 0.0f, &clip_rect);
+
+ current_y += font->FontSize + spacing;
+ }
+}
+
+void InputOverlayUI::UpdateNames()
+{
+ m_active_ports = 0;
+ if (!System::IsValid())
+ return;
+
+ for (u32 port = 0; port < NUM_CONTROLLER_AND_CARD_PORTS; port++)
+ {
+ const Controller* controller = g_pad.GetController(port);
+ const ControllerType type = (controller) ? controller->GetType() : ControllerType::None;
+ if (type != ControllerType::None)
+ m_active_ports++;
+
+ if (type == m_types[port])
+ continue;
+
+ m_axis_names[port] = Controller::GetAxisNames(type);
+ m_button_names[port] = Controller::GetButtonNames(type);
+ m_types[port] = type;
+ }
+}
+
+} // namespace FrontendCommon
\ No newline at end of file
diff --git a/src/frontend-common/input_overlay_ui.h b/src/frontend-common/input_overlay_ui.h
new file mode 100644
index 000000000..3c794d395
--- /dev/null
+++ b/src/frontend-common/input_overlay_ui.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "core/controller.h"
+#include
+
+namespace FrontendCommon {
+
+class InputOverlayUI
+{
+public:
+ InputOverlayUI();
+ ~InputOverlayUI();
+
+ void Draw();
+
+private:
+ void UpdateNames();
+
+ std::array m_axis_names;
+ std::array m_button_names;
+ std::array m_types{};
+ u32 m_active_ports = 0;
+};
+
+} // namespace FrontendCommon
\ No newline at end of file