InputManager: Support multiple mice via raw input

Only on Windows for now.
This commit is contained in:
Stenzek 2024-08-23 22:31:59 +10:00
parent 8b3fd538ea
commit 9e3507e0f4
No known key found for this signature in database
37 changed files with 1480 additions and 1040 deletions

View File

@ -885,9 +885,9 @@ const Controller::ControllerInfo AnalogController::INFO = {ControllerType::Analo
s_settings,
Controller::VibrationCapabilities::LargeSmallMotors};
void AnalogController::LoadSettings(SettingsInterface& si, const char* section)
void AnalogController::LoadSettings(SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section);
Controller::LoadSettings(si, section, initial);
m_force_analog_on_reset = si.GetBoolValue(section, "ForceAnalogOnReset", true);
m_analog_dpad_in_digital_mode = si.GetBoolValue(section, "AnalogDPadInDigitalMode", true);
m_analog_deadzone = std::clamp(si.GetFloatValue(section, "AnalogDeadzone", DEFAULT_STICK_DEADZONE), 0.0f, 1.0f);

View File

@ -80,7 +80,7 @@ public:
void ResetTransferState() override;
bool Transfer(const u8 data_in, u8* data_out) override;
void LoadSettings(SettingsInterface& si, const char* section) override;
void LoadSettings(SettingsInterface& si, const char* section, bool initial) override;
private:
using MotorState = std::array<u8, NUM_MOTORS>;

View File

@ -413,9 +413,9 @@ const Controller::ControllerInfo AnalogJoystick::INFO = {ControllerType::AnalogJ
s_settings,
Controller::VibrationCapabilities::NoVibration};
void AnalogJoystick::LoadSettings(SettingsInterface& si, const char* section)
void AnalogJoystick::LoadSettings(SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section);
Controller::LoadSettings(si, section, initial);
m_analog_deadzone = std::clamp(si.GetFloatValue(section, "AnalogDeadzone", DEFAULT_STICK_DEADZONE), 0.0f, 1.0f);
m_analog_sensitivity =
std::clamp(si.GetFloatValue(section, "AnalogSensitivity", DEFAULT_STICK_SENSITIVITY), 0.01f, 3.0f);

View File

@ -77,7 +77,7 @@ public:
void ResetTransferState() override;
bool Transfer(const u8 data_in, u8* data_out) override;
void LoadSettings(SettingsInterface& si, const char* section) override;
void LoadSettings(SettingsInterface& si, const char* section, bool initial) override;
private:
enum class TransferState : u8

View File

@ -91,7 +91,7 @@ u32 Controller::GetInputOverlayIconColor() const
return 0xFFFFFFFFu;
}
void Controller::LoadSettings(SettingsInterface& si, const char* section)
void Controller::LoadSettings(SettingsInterface& si, const char* section, bool initial)
{
}

View File

@ -93,7 +93,7 @@ public:
virtual u32 GetInputOverlayIconColor() const;
/// Loads/refreshes any per-controller settings.
virtual void LoadSettings(SettingsInterface& si, const char* section);
virtual void LoadSettings(SettingsInterface& si, const char* section, bool initial);
/// Creates a new controller of the specified type.
static std::unique_ptr<Controller> Create(ControllerType type, u32 index);

View File

@ -187,9 +187,9 @@ const Controller::ControllerInfo DigitalController::INFO = {ControllerType::Digi
s_settings,
Controller::VibrationCapabilities::NoVibration};
void DigitalController::LoadSettings(SettingsInterface& si, const char* section)
void DigitalController::LoadSettings(SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section);
Controller::LoadSettings(si, section, initial);
m_popn_controller_mode = si.GetBoolValue(section, "ForcePopnControllerMode", false);
}

View File

@ -50,7 +50,7 @@ public:
void ResetTransferState() override;
bool Transfer(const u8 data_in, u8* data_out) override;
void LoadSettings(SettingsInterface& si, const char* section) override;
void LoadSettings(SettingsInterface& si, const char* section, bool initial) override;
private:
enum class TransferState : u8

View File

@ -207,8 +207,8 @@ bool GunCon::Transfer(const u8 data_in, u8* data_out)
void GunCon::UpdatePosition()
{
float display_x, display_y;
const auto& [window_x, window_y] =
(m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() : InputManager::GetPointerAbsolutePosition(0);
const auto& [window_x, window_y] = (m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() :
InputManager::GetPointerAbsolutePosition(m_cursor_index);
g_gpu->ConvertScreenCoordinatesToDisplayCoordinates(window_x, window_y, &display_x, &display_y);
// are we within the active display area?
@ -245,7 +245,7 @@ bool GunCon::CanUseSoftwareCursor() const
u32 GunCon::GetSoftwarePointerIndex() const
{
return m_has_relative_binds ? (InputManager::MAX_POINTER_DEVICES + m_index) : 0;
return m_has_relative_binds ? (InputManager::MAX_POINTER_DEVICES + m_index) : m_cursor_index;
}
void GunCon::UpdateSoftwarePointerPosition()
@ -273,6 +273,7 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
}
// clang-format off
{"Pointer", TRANSLATE_NOOP("GunCon", "Pointer/Aiming"), ICON_PF_MOUSE, static_cast<u32>(GunCon::Binding::ButtonCount), InputBindingInfo::Type::AbsolutePointer, GenericInputBinding::Unknown},
BUTTON("Trigger", TRANSLATE_NOOP("GunCon", "Trigger"), ICON_PF_CROSS, GunCon::Binding::Trigger, GenericInputBinding::R2),
BUTTON("ShootOffscreen", TRANSLATE_NOOP("GunCon", "Shoot Offscreen"), nullptr, GunCon::Binding::ShootOffscreen, GenericInputBinding::L2),
BUTTON("A", TRANSLATE_NOOP("GunCon", "A"), ICON_PF_BUTTON_A, GunCon::Binding::A, GenericInputBinding::Cross),
@ -306,9 +307,9 @@ const Controller::ControllerInfo GunCon::INFO = {
ControllerType::GunCon, "GunCon", TRANSLATE_NOOP("ControllerType", "GunCon"), nullptr,
s_binding_info, s_settings, Controller::VibrationCapabilities::NoVibration};
void GunCon::LoadSettings(SettingsInterface& si, const char* section)
void GunCon::LoadSettings(SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section);
Controller::LoadSettings(si, section, initial);
m_x_scale = si.GetFloatValue(section, "XScale", 1.0f);
@ -334,13 +335,15 @@ void GunCon::LoadSettings(SettingsInterface& si, const char* section)
m_has_relative_binds = (si.ContainsValue(section, "RelativeLeft") || si.ContainsValue(section, "RelativeRight") ||
si.ContainsValue(section, "RelativeUp") || si.ContainsValue(section, "RelativeDown"));
m_cursor_index =
static_cast<u8>(InputManager::GetIndexFromPointerBinding(si.GetStringValue(section, "Pointer")).value_or(0));
const s32 new_pointer_index = GetSoftwarePointerIndex();
if (prev_pointer_index != new_pointer_index || m_cursor_path != cursor_path || m_cursor_scale != cursor_scale ||
m_cursor_color != cursor_color)
{
if (prev_pointer_index != new_pointer_index &&
if (!initial && prev_pointer_index != new_pointer_index &&
static_cast<u32>(prev_pointer_index) < InputManager::MAX_SOFTWARE_CURSORS)
{
ImGuiManager::ClearSoftwareCursor(prev_pointer_index);

View File

@ -37,7 +37,7 @@ public:
void Reset() override;
bool DoState(StateWrapper& sw, bool apply_input_state) override;
void LoadSettings(SettingsInterface& si, const char* section) override;
void LoadSettings(SettingsInterface& si, const char* section, bool initial) override;
float GetBindState(u32 index) const override;
void SetBindState(u32 index, float value) override;
@ -80,6 +80,7 @@ private:
u16 m_position_y = 0;
bool m_shoot_offscreen = false;
bool m_has_relative_binds = false;
u8 m_cursor_index = 0;
TransferState m_transfer_state = TransferState::Idle;
};

View File

@ -12,6 +12,7 @@
#include "util/gpu_device.h"
#include "util/imgui_manager.h"
#include "util/input_manager.h"
#include "common/assert.h"
#include "common/error.h"
@ -308,6 +309,8 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error)
return false;
}
InputManager::SetDisplayWindowSize(static_cast<float>(g_gpu_device->GetWindowWidth()),
static_cast<float>(g_gpu_device->GetWindowHeight()));
return true;
}
@ -322,7 +325,10 @@ void Host::UpdateDisplayWindow()
return;
}
ImGuiManager::WindowResized();
const float f_width = static_cast<float>(g_gpu_device->GetWindowWidth());
const float f_height = static_cast<float>(g_gpu_device->GetWindowHeight());
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
if (System::IsValid())
{
@ -343,7 +349,11 @@ void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
DEV_LOG("Display window resized to {}x{}", width, height);
g_gpu_device->ResizeWindow(width, height, scale);
ImGuiManager::WindowResized();
const float f_width = static_cast<float>(g_gpu_device->GetWindowWidth());
const float f_height = static_cast<float>(g_gpu_device->GetWindowHeight());
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
// If we're paused, re-present the current frame at the new window size.
if (System::IsValid())

View File

@ -16,6 +16,7 @@ struct InputBindingInfo
HalfAxis,
Motor,
Pointer, // Receive relative mouse movement events, bind_index is offset by the axis.
AbsolutePointer, // Allows selection of specific pointers, but defaults to the first.
Macro,
};
@ -68,4 +69,3 @@ enum class GenericInputBinding : u8
Count,
};

View File

@ -213,8 +213,8 @@ void Justifier::UpdatePosition()
}
float display_x, display_y;
const auto [window_x, window_y] =
(m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() : InputManager::GetPointerAbsolutePosition(0);
const auto [window_x, window_y] = (m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() :
InputManager::GetPointerAbsolutePosition(m_cursor_index);
g_gpu->ConvertScreenCoordinatesToDisplayCoordinates(window_x, window_y, &display_x, &display_y);
// are we within the active display area?
@ -308,7 +308,7 @@ bool Justifier::CanUseSoftwareCursor() const
u32 Justifier::GetSoftwarePointerIndex() const
{
return m_has_relative_binds ? (InputManager::MAX_POINTER_DEVICES + m_index) : 0;
return m_has_relative_binds ? (InputManager::MAX_POINTER_DEVICES + m_index) : m_cursor_index;
}
void Justifier::UpdateSoftwarePointerPosition()
@ -336,6 +336,7 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
}
// clang-format off
{"Pointer", TRANSLATE_NOOP("Justifier", "Pointer/Aiming"), ICON_PF_MOUSE, static_cast<u32>(Justifier::Binding::ButtonCount), InputBindingInfo::Type::AbsolutePointer, GenericInputBinding::Unknown},
BUTTON("Trigger", TRANSLATE_NOOP("Justifier", "Trigger"), ICON_PF_CROSS, Justifier::Binding::Trigger, GenericInputBinding::R2),
BUTTON("ShootOffscreen", TRANSLATE_NOOP("Justifier", "Shoot Offscreen"), nullptr, Justifier::Binding::ShootOffscreen, GenericInputBinding::L2),
BUTTON("Start", TRANSLATE_NOOP("Justifier", "Start"), ICON_PF_START, Justifier::Binding::Start, GenericInputBinding::Cross),
@ -395,9 +396,9 @@ const Controller::ControllerInfo Justifier::INFO = {ControllerType::Justifier,
s_settings,
Controller::VibrationCapabilities::NoVibration};
void Justifier::LoadSettings(SettingsInterface& si, const char* section)
void Justifier::LoadSettings(SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section);
Controller::LoadSettings(si, section, initial);
m_x_scale = si.GetFloatValue(section, "XScale", 1.0f);
@ -423,13 +424,15 @@ void Justifier::LoadSettings(SettingsInterface& si, const char* section)
m_has_relative_binds = (si.ContainsValue(section, "RelativeLeft") || si.ContainsValue(section, "RelativeRight") ||
si.ContainsValue(section, "RelativeUp") || si.ContainsValue(section, "RelativeDown"));
m_cursor_index =
static_cast<u8>(InputManager::GetIndexFromPointerBinding(si.GetStringValue(section, "Pointer")).value_or(0));
const s32 new_pointer_index = GetSoftwarePointerIndex();
if (prev_pointer_index != new_pointer_index || m_cursor_path != cursor_path || m_cursor_scale != cursor_scale ||
m_cursor_color != cursor_color)
{
if (prev_pointer_index != new_pointer_index &&
if (!initial && prev_pointer_index != new_pointer_index &&
static_cast<u32>(prev_pointer_index) < InputManager::MAX_SOFTWARE_CURSORS)
{
ImGuiManager::ClearSoftwareCursor(prev_pointer_index);

View File

@ -40,7 +40,7 @@ public:
void Reset() override;
bool DoState(StateWrapper& sw, bool apply_input_state) override;
void LoadSettings(SettingsInterface& si, const char* section) override;
void LoadSettings(SettingsInterface& si, const char* section, bool initial) override;
float GetBindState(u32 index) const override;
void SetBindState(u32 index, float value) override;
@ -100,6 +100,7 @@ private:
TimingEvent m_irq_event;
bool m_has_relative_binds = false;
u8 m_cursor_index = 0;
float m_relative_pos[4] = {};
std::string m_cursor_path;

View File

@ -345,9 +345,9 @@ const Controller::ControllerInfo NeGcon::INFO = {
ControllerType::NeGcon, "NeGcon", TRANSLATE_NOOP("ControllerType", "NeGcon"), ICON_PF_GAMEPAD,
s_binding_info, s_settings, Controller::VibrationCapabilities::NoVibration};
void NeGcon::LoadSettings(SettingsInterface& si, const char* section)
void NeGcon::LoadSettings(SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section);
Controller::LoadSettings(si, section, initial);
m_steering_modifier = {
.deadzone = si.GetFloatValue(section, "SteeringDeadzone", DEFAULT_STEERING_MODIFIER.deadzone),
.saturation = si.GetFloatValue(section, "SteeringSaturation", DEFAULT_STEERING_MODIFIER.saturation),

View File

@ -100,7 +100,7 @@ public:
u32 GetButtonStateBits() const override;
std::optional<u32> GetAnalogInputBytes() const override;
void LoadSettings(SettingsInterface& si, const char* section) override;
void LoadSettings(SettingsInterface& si, const char* section, bool initial) override;
private:
enum class TransferState : u8

View File

@ -766,9 +766,9 @@ const Controller::ControllerInfo NeGconRumble::INFO = {ControllerType::NeGconRum
s_settings,
Controller::VibrationCapabilities::LargeSmallMotors};
void NeGconRumble::LoadSettings(SettingsInterface& si, const char* section)
void NeGconRumble::LoadSettings(SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section);
Controller::LoadSettings(si, section, initial);
m_steering_deadzone = si.GetFloatValue(section, "SteeringDeadzone", 0.10f);
m_steering_sensitivity = si.GetFloatValue(section, "SteeringSensitivity", 1.00f);
m_rumble_bias = static_cast<u8>(std::min<u32>(si.GetIntValue(section, "VibrationBias", 8), 255));

View File

@ -69,7 +69,7 @@ public:
u32 GetButtonStateBits() const override;
std::optional<u32> GetAnalogInputBytes() const override;
void LoadSettings(SettingsInterface& si, const char* section) override;
void LoadSettings(SettingsInterface& si, const char* section, bool initial) override;
private:
using MotorState = std::array<u8, NUM_MOTORS>;

View File

@ -181,9 +181,9 @@ bool PlayStationMouse::Transfer(const u8 data_in, u8* data_out)
}
}
void PlayStationMouse::LoadSettings(SettingsInterface& si, const char* section)
void PlayStationMouse::LoadSettings(SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section);
Controller::LoadSettings(si, section, initial);
m_sensitivity_x = si.GetFloatValue(section, "SensitivityX", 1.0f);
m_sensitivity_y = si.GetFloatValue(section, "SensitivityY", 1.0f);
@ -219,7 +219,7 @@ static const SettingInfo s_settings[] = {
const Controller::ControllerInfo PlayStationMouse::INFO = {ControllerType::PlayStationMouse,
"PlayStationMouse",
TRANSLATE_NOOP("ControllerType", "PlayStation Mouse"),
TRANSLATE_NOOP("ControllerType", "Mouse"),
ICON_PF_MOUSE,
s_binding_info,
s_settings,

View File

@ -39,7 +39,7 @@ public:
void ResetTransferState() override;
bool Transfer(const u8 data_in, u8* data_out) override;
void LoadSettings(SettingsInterface& si, const char* section) override;
void LoadSettings(SettingsInterface& si, const char* section, bool initial) override;
private:
enum class TransferState : u8

View File

@ -176,7 +176,6 @@ static void UpdateRunningGame(const std::string_view path, CDImage* image, bool
static bool CheckForSBIFile(CDImage* image, Error* error);
static void UpdateControllers();
static void UpdateControllerSettings();
static void ResetControllers();
static void UpdatePerGameMemoryCards();
static std::unique_ptr<MemoryCard> GetMemoryCardForSlot(u32 slot, MemoryCardType type);
@ -1459,6 +1458,7 @@ void System::PauseSystem(bool paused)
FullscreenUI::OnSystemPaused();
InputManager::PauseVibration();
InputManager::UpdateHostMouseMode();
Achievements::OnSystemPaused(true);
@ -1478,6 +1478,8 @@ void System::PauseSystem(bool paused)
{
FullscreenUI::OnSystemResumed();
InputManager::UpdateHostMouseMode();
Achievements::OnSystemPaused(false);
if (g_settings.inhibit_screensaver)
@ -1732,6 +1734,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
FullscreenUI::OnSystemStarted();
InputManager::UpdateHostMouseMode();
if (g_settings.inhibit_screensaver)
PlatformMisc::SuspendScreensaver();
@ -1891,6 +1895,7 @@ void System::DestroySystem()
FullscreenUI::OnSystemDestroyed();
InputManager::PauseVibration();
InputManager::UpdateHostMouseMode();
if (g_settings.inhibit_screensaver)
PlatformMisc::ResumeScreensaver();
@ -3548,7 +3553,7 @@ void System::UpdateControllers()
std::unique_ptr<Controller> controller = Controller::Create(type, i);
if (controller)
{
controller->LoadSettings(*Host::GetSettingsInterface(), Controller::GetSettingsSection(i).c_str());
controller->LoadSettings(*Host::GetSettingsInterface(), Controller::GetSettingsSection(i).c_str(), true);
Pad::SetController(i, std::move(controller));
}
}
@ -3563,7 +3568,7 @@ void System::UpdateControllerSettings()
{
Controller* controller = Pad::GetController(i);
if (controller)
controller->LoadSettings(*Host::GetSettingsInterface(), Controller::GetSettingsSection(i).c_str());
controller->LoadSettings(*Host::GetSettingsInterface(), Controller::GetSettingsSection(i).c_str(), false);
}
}

View File

@ -247,6 +247,9 @@ void ReloadInputSources();
/// Reloads input bindings.
void ReloadInputBindings();
/// Reloads only controller settings.
void UpdateControllerSettings();
bool BootSystem(SystemBootParameters parameters, Error* error);
void PauseSystem(bool paused);
void ResetSystem();

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1010</width>
<height>418</height>
<width>1124</width>
<height>485</height>
</rect>
</property>
<property name="sizePolicy">
@ -22,210 +22,55 @@
<height>400</height>
</size>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Trigger</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Fire Offscreen</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>6</number>
<number>0</number>
</property>
<property name="topMargin">
<number>6</number>
<number>0</number>
</property>
<property name="rightMargin">
<number>6</number>
<number>0</number>
</property>
<property name="bottomMargin">
<number>6</number>
<number>0</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="ShootOffscreen">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QGroupBox" name="groupBox_18">
<property name="title">
<string>Fire</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Trigger">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
<string>Pointer Setup</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>PushButton</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, lightguns will use the mouse pointer. To use the mouse, you &lt;span style=&quot; font-weight:700;&quot;&gt;do not&lt;/span&gt; need to configure any bindings apart from the trigger and buttons. Aiming only needs to be set when you want to use multiple mice.&lt;/p&gt;&lt;p&gt;If you want to use a controller, or lightgun which simulates a controller instead of a mouse, then you should bind it to Relative Aiming. Otherwise, Relative Aiming should be &lt;span style=&quot; font-weight:700;&quot;&gt;left unbound&lt;/span&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<widget class="QGroupBox" name="groupBox_16">
<property name="title">
<string>Side Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout_16">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_17">
<property name="title">
<string>B</string>
</property>
<layout class="QGridLayout" name="gridLayout_17">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="B">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_19">
<property name="title">
<string>A</string>
</property>
<layout class="QGridLayout" name="gridLayout_19">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="A">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_10">
<item>
<widget class="QGroupBox" name="groupBox_20">
<property name="title">
<string extracomment="Try to use Sony's official terminology for this. A good place to start would be in the console or the DualShock 2's manual. If this element was officially translated to your language by Sony in later DualShocks, you may use that term.">Relative Aiming</string>
</property>
<layout class="QGridLayout" name="gridLayout_11">
<layout class="QGridLayout" name="gridLayout_18">
<item row="3" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_11">
<widget class="QGroupBox" name="groupBox_21">
<property name="title">
<string>Down</string>
</property>
<layout class="QGridLayout" name="gridLayout_12">
<layout class="QGridLayout" name="gridLayout_20">
<property name="leftMargin">
<number>6</number>
</property>
@ -260,52 +105,12 @@
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_12">
<property name="title">
<string>Left</string>
</property>
<layout class="QGridLayout" name="gridLayout_13">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="RelativeLeft">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_13">
<widget class="QGroupBox" name="groupBox_23">
<property name="title">
<string>Up</string>
</property>
<layout class="QGridLayout" name="gridLayout_14">
<layout class="QGridLayout" name="gridLayout_22">
<property name="leftMargin">
<number>6</number>
</property>
@ -340,12 +145,52 @@
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_22">
<property name="title">
<string>Left</string>
</property>
<layout class="QGridLayout" name="gridLayout_21">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="RelativeLeft">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="QGroupBox" name="groupBox_14">
<widget class="QGroupBox" name="groupBox_24">
<property name="title">
<string>Right</string>
</property>
<layout class="QGridLayout" name="gridLayout_15">
<layout class="QGridLayout" name="gridLayout_23">
<property name="leftMargin">
<number>6</number>
</property>
@ -383,34 +228,23 @@
</layout>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="1">
<spacer name="verticalSpacer_3">
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
@ -418,13 +252,13 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
<width>35</width>
<height>263</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<item row="1" column="1" colspan="2">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -442,52 +276,302 @@
<string/>
</property>
<property name="pixmap">
<pixmap resource="resources/resources.qrc">:/controllers/guncon.svg</pixmap>
<pixmap resource="resources/duckstation-qt.qrc">:/controllers/guncon.svg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<item row="1" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
<width>36</width>
<height>263</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1" colspan="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1" colspan="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_15">
<property name="title">
<string>Pointer Setup</string>
</property>
<item row="0" column="2">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>Aiming</string>
</property>
<layout class="QGridLayout" name="gridLayout_10">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Pointer">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>&lt;p&gt;By default, lightguns will use the mouse pointer. To use the mouse, you &lt;strong&gt;do not&lt;/strong&gt; need to configure any bindings apart from the trigger and buttons.&lt;/p&gt;
&lt;p&gt;If you want to use a controller, or lightgun which simulates a controller instead of a mouse, then you should bind it to Relative Aiming. Otherwise, Relative Aiming should be &lt;strong&gt;left unbound&lt;/strong&gt;.&lt;/p&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_8">
<property name="title">
<string>Trigger</string>
</property>
<layout class="QGridLayout" name="gridLayout_24">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_9">
<property name="title">
<string>Fire Offscreen</string>
</property>
<layout class="QGridLayout" name="gridLayout_25">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="ShootOffscreen">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_25">
<property name="title">
<string>Fire</string>
</property>
<layout class="QGridLayout" name="gridLayout_26">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Trigger">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_26">
<property name="title">
<string>Side Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout_27">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_27">
<property name="title">
<string>B</string>
</property>
<layout class="QGridLayout" name="gridLayout_28">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="B">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_28">
<property name="title">
<string>A</string>
</property>
<layout class="QGridLayout" name="gridLayout_29">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="A">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
@ -498,7 +582,8 @@
</customwidget>
</customwidgets>
<resources>
<include location="resources/resources.qrc"/>
<include location="resources/duckstation-qt.qrc"/>
<include location="resources/duckstation-qt.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1010</width>
<height>418</height>
<width>1124</width>
<height>485</height>
</rect>
</property>
<property name="sizePolicy">
@ -22,210 +22,55 @@
<height>400</height>
</size>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Trigger</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Fire Offscreen</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>6</number>
<number>0</number>
</property>
<property name="topMargin">
<number>6</number>
<number>0</number>
</property>
<property name="rightMargin">
<number>6</number>
<number>0</number>
</property>
<property name="bottomMargin">
<number>6</number>
<number>0</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="ShootOffscreen">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QGroupBox" name="groupBox_18">
<property name="title">
<string>Fire</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Trigger">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
<string>Pointer Setup</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>PushButton</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, lightguns will use the mouse pointer. To use the mouse, you &lt;span style=&quot; font-weight:700;&quot;&gt;do not&lt;/span&gt; need to configure any bindings apart from the trigger and buttons. Aiming only needs to be set when you want to use multiple mice.&lt;/p&gt;&lt;p&gt;If you want to use a controller, or lightgun which simulates a controller instead of a mouse, then you should bind it to Relative Aiming. Otherwise, Relative Aiming should be &lt;span style=&quot; font-weight:700;&quot;&gt;left unbound&lt;/span&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<widget class="QGroupBox" name="groupBox_16">
<property name="title">
<string>Side Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout_16">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_17">
<property name="title">
<string>Back</string>
</property>
<layout class="QGridLayout" name="gridLayout_17">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Back">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_19">
<property name="title">
<string>Start</string>
</property>
<layout class="QGridLayout" name="gridLayout_19">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Start">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_10">
<item>
<widget class="QGroupBox" name="groupBox_20">
<property name="title">
<string extracomment="Try to use Sony's official terminology for this. A good place to start would be in the console or the DualShock 2's manual. If this element was officially translated to your language by Sony in later DualShocks, you may use that term.">Relative Aiming</string>
</property>
<layout class="QGridLayout" name="gridLayout_11">
<layout class="QGridLayout" name="gridLayout_18">
<item row="3" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_11">
<widget class="QGroupBox" name="groupBox_21">
<property name="title">
<string>Down</string>
</property>
<layout class="QGridLayout" name="gridLayout_12">
<layout class="QGridLayout" name="gridLayout_20">
<property name="leftMargin">
<number>6</number>
</property>
@ -260,52 +105,12 @@
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_12">
<property name="title">
<string>Left</string>
</property>
<layout class="QGridLayout" name="gridLayout_13">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="RelativeLeft">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_13">
<widget class="QGroupBox" name="groupBox_23">
<property name="title">
<string>Up</string>
</property>
<layout class="QGridLayout" name="gridLayout_14">
<layout class="QGridLayout" name="gridLayout_22">
<property name="leftMargin">
<number>6</number>
</property>
@ -340,12 +145,52 @@
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_22">
<property name="title">
<string>Left</string>
</property>
<layout class="QGridLayout" name="gridLayout_21">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="RelativeLeft">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="QGroupBox" name="groupBox_14">
<widget class="QGroupBox" name="groupBox_24">
<property name="title">
<string>Right</string>
</property>
<layout class="QGridLayout" name="gridLayout_15">
<layout class="QGridLayout" name="gridLayout_23">
<property name="leftMargin">
<number>6</number>
</property>
@ -383,34 +228,23 @@
</layout>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="1">
<spacer name="verticalSpacer_3">
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
@ -418,13 +252,13 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
<width>35</width>
<height>263</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<item row="1" column="1" colspan="2">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -442,52 +276,302 @@
<string/>
</property>
<property name="pixmap">
<pixmap resource="resources/resources.qrc">:/controllers/justifier.svg</pixmap>
<pixmap resource="resources/duckstation-qt.qrc">:/controllers/justifier.svg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<item row="1" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
<width>36</width>
<height>263</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1" colspan="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1" colspan="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_15">
<property name="title">
<string>Pointer Setup</string>
</property>
<item row="0" column="2">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>Aiming</string>
</property>
<layout class="QGridLayout" name="gridLayout_10">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Pointer">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>&lt;p&gt;By default, lightguns will use the mouse pointer. To use the mouse, you &lt;strong&gt;do not&lt;/strong&gt; need to configure any bindings apart from the trigger and buttons.&lt;/p&gt;
&lt;p&gt;If you want to use a controller, or lightgun which simulates a controller instead of a mouse, then you should bind it to Relative Aiming. Otherwise, Relative Aiming should be &lt;strong&gt;left unbound&lt;/strong&gt;.&lt;/p&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_8">
<property name="title">
<string>Trigger</string>
</property>
<layout class="QGridLayout" name="gridLayout_24">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_9">
<property name="title">
<string>Fire Offscreen</string>
</property>
<layout class="QGridLayout" name="gridLayout_25">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="ShootOffscreen">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_25">
<property name="title">
<string>Fire</string>
</property>
<layout class="QGridLayout" name="gridLayout_26">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Trigger">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_26">
<property name="title">
<string>Side Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout_27">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_27">
<property name="title">
<string>Back</string>
</property>
<layout class="QGridLayout" name="gridLayout_28">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Back">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_28">
<property name="title">
<string>Start</string>
</property>
<layout class="QGridLayout" name="gridLayout_29">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Start">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
@ -498,7 +582,8 @@
</customwidget>
</customwidgets>
<resources>
<include location="resources/resources.qrc"/>
<include location="resources/duckstation-qt.qrc"/>
<include location="resources/duckstation-qt.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>1100</width>
<height>534</height>
<height>567</height>
</rect>
</property>
<property name="sizePolicy">
@ -29,7 +29,124 @@
</size>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<item row="1" column="5">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2" rowspan="2" colspan="2">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="resources/duckstation-qt.qrc">:/controllers/mouse.svg</pixmap>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="6">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>Pointer</string>
</property>
<layout class="QGridLayout" name="gridLayout_11">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Pointer">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
@ -47,46 +164,6 @@
<string>Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="1" column="2" colspan="2">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Right</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Right">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
@ -108,8 +185,48 @@
<item row="0" column="0">
<widget class="InputBindingWidget" name="Left">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="2" colspan="2">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Right</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Right">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
@ -130,55 +247,10 @@
</layout>
</widget>
</item>
<item row="3" column="3">
<spacer name="horizontalSpacer_2">
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="resources/resources.qrc">:/controllers/mouse.svg</pixmap>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -189,6 +261,8 @@
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
@ -198,7 +272,8 @@
</customwidget>
</customwidgets>
<resources>
<include location="resources/resources.qrc"/>
<include location="resources/duckstation-qt.qrc"/>
<include location="resources/duckstation-qt.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -478,7 +478,8 @@ void ControllerBindingWidget::bindBindingWidgets(QWidget* parent)
for (const Controller::ControllerBindingInfo& bi : m_controller_info->bindings)
{
if (bi.type == InputBindingInfo::Type::Axis || bi.type == InputBindingInfo::Type::HalfAxis ||
bi.type == InputBindingInfo::Type::Button || bi.type == InputBindingInfo::Type::Pointer)
bi.type == InputBindingInfo::Type::Button || bi.type == InputBindingInfo::Type::Pointer ||
bi.type == InputBindingInfo::Type::AbsolutePointer)
{
InputBindingWidget* widget = parent->findChild<InputBindingWidget*>(QString::fromUtf8(bi.name));
if (!widget)

View File

@ -100,11 +100,9 @@ void DisplayWidget::updateRelativeMode(bool enabled)
if (enabled)
{
#ifdef _WIN32
m_relative_mouse_enabled = !clip_cursor;
m_clip_mouse_enabled = clip_cursor;
#else
m_relative_mouse_enabled = true;
#ifdef _WIN32
m_clip_mouse_enabled = clip_cursor;
#endif
m_relative_mouse_start_pos = QCursor::pos();
updateCenterPos();
@ -263,12 +261,10 @@ bool DisplayWidget::event(QEvent* event)
case QEvent::MouseMove:
{
const QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
if (!m_relative_mouse_enabled)
{
const qreal dpr = QtUtils::GetDevicePixelRatioForWidget(this);
const QPoint mouse_pos = mouse_event->pos();
const QPoint mouse_pos = static_cast<QMouseEvent*>(event)->pos();
const float scaled_x = static_cast<float>(static_cast<qreal>(mouse_pos.x()) * dpr);
const float scaled_y = static_cast<float>(static_cast<qreal>(mouse_pos.y()) * dpr);
@ -298,11 +294,14 @@ bool DisplayWidget::event(QEvent* event)
}
#endif
if (!InputManager::IsUsingRawInput())
{
if (dx != 0.0f)
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::X, dx);
if (dy != 0.0f)
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::Y, dy);
}
}
return true;
}
@ -310,9 +309,12 @@ bool DisplayWidget::event(QEvent* event)
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonRelease:
{
if (!m_relative_mouse_enabled || !InputManager::IsUsingRawInput())
{
const u32 button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button()));
emit windowMouseButtonEvent(static_cast<int>(button_index), event->type() != QEvent::MouseButtonRelease);
}
// don't toggle fullscreen when we're bound.. that wouldn't end well.
if (event->type() == QEvent::MouseButtonDblClick &&

View File

@ -269,6 +269,8 @@ void InputBindingDialog::saveListToSettings()
else
Host::DeleteBaseSettingValue(m_section_name.c_str(), m_key_name.c_str());
Host::CommitBaseSettingChanges();
if (m_bind_type == InputBindingInfo::Type::Pointer || m_bind_type == InputBindingInfo::Type::AbsolutePointer)
g_emu_thread->updateControllerSettings();
g_emu_thread->reloadInputBindings();
}
}

View File

@ -44,8 +44,9 @@ InputBindingWidget::~InputBindingWidget()
bool InputBindingWidget::isMouseMappingEnabled(SettingsInterface* sif)
{
return sif ? sif->GetBoolValue("UI", "EnableMouseMapping", false) :
Host::GetBaseBoolSettingValue("UI", "EnableMouseMapping", false);
return (sif ? sif->GetBoolValue("UI", "EnableMouseMapping", false) :
Host::GetBaseBoolSettingValue("UI", "EnableMouseMapping", false)) &&
!InputManager::IsUsingRawInput();
}
void InputBindingWidget::initialize(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name,
@ -100,7 +101,7 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
const QEvent::Type event_type = event->type();
// if the key is being released, set the input
if (event_type == QEvent::KeyRelease || event_type == QEvent::MouseButtonRelease)
if (event_type == QEvent::KeyRelease || (event_type == QEvent::MouseButtonRelease && m_mouse_mapping_enabled))
{
setNewBinding();
stopListeningForInput();
@ -112,7 +113,8 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
m_new_bindings.push_back(InputManager::MakeHostKeyboardKey(QtUtils::KeyEventToCode(key_event)));
return true;
}
else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick)
else if ((event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick) &&
m_mouse_mapping_enabled)
{
// double clicks get triggered if we click bind, then click again quickly.
const u32 button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button()));
@ -225,6 +227,8 @@ void InputBindingWidget::setNewBinding()
{
Host::SetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), new_binding.c_str());
Host::CommitBaseSettingChanges();
if (m_bind_type == InputBindingInfo::Type::Pointer || m_bind_type == InputBindingInfo::Type::AbsolutePointer)
g_emu_thread->updateControllerSettings();
g_emu_thread->reloadInputBindings();
}
}
@ -246,6 +250,8 @@ void InputBindingWidget::clearBinding()
{
Host::DeleteBaseSettingValue(m_section_name.c_str(), m_key_name.c_str());
Host::CommitBaseSettingChanges();
if (m_bind_type == InputBindingInfo::Type::Pointer || m_bind_type == InputBindingInfo::Type::AbsolutePointer)
g_emu_thread->updateControllerSettings();
g_emu_thread->reloadInputBindings();
}
reloadBinding();

View File

@ -753,6 +753,20 @@ void EmuThread::updateEmuFolders()
EmuFolders::Update();
}
void EmuThread::updateControllerSettings()
{
if (!isOnThread())
{
QMetaObject::invokeMethod(this, &EmuThread::updateControllerSettings, Qt::QueuedConnection);
return;
}
if (!System::IsValid())
return;
System::UpdateControllerSettings();
}
void EmuThread::startFullscreenUI()
{
if (!isOnThread())

View File

@ -159,6 +159,7 @@ public Q_SLOTS:
void applySettings(bool display_osd_messages = false);
void reloadGameSettings(bool display_osd_messages = false);
void updateEmuFolders();
void updateControllerSettings();
void reloadInputSources();
void reloadInputBindings();
void reloadInputDevices();

View File

@ -233,14 +233,11 @@ float ImGuiManager::GetWindowHeight()
return s_window_height;
}
void ImGuiManager::WindowResized()
void ImGuiManager::WindowResized(float width, float height)
{
const u32 new_width = g_gpu_device ? g_gpu_device->GetWindowWidth() : 0;
const u32 new_height = g_gpu_device ? g_gpu_device->GetWindowHeight() : 0;
s_window_width = static_cast<float>(new_width);
s_window_height = static_cast<float>(new_height);
ImGui::GetIO().DisplaySize = ImVec2(s_window_width, s_window_height);
s_window_width = width;
s_window_height = height;
ImGui::GetIO().DisplaySize = ImVec2(width, height);
// Scale might have changed as a result of window resize.
RequestScaleUpdate();
@ -1053,7 +1050,7 @@ void ImGuiManager::DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair<
void ImGuiManager::RenderSoftwareCursors()
{
// This one's okay to race, worst that happens is we render the wrong number of cursors for a frame.
const u32 pointer_count = InputManager::MAX_POINTER_DEVICES;
const u32 pointer_count = InputManager::GetPointerCount();
for (u32 i = 0; i < pointer_count; i++)
DrawSoftwareCursor(s_software_cursors[i], InputManager::GetPointerAbsolutePosition(i));
@ -1076,8 +1073,8 @@ void ImGuiManager::SetSoftwareCursor(u32 index, std::string image_path, float im
UpdateSoftwareCursorTexture(index);
// Hide the system cursor when we activate a software cursor.
if (is_hiding_or_showing && index == 0)
InputManager::UpdateHostMouseMode();
if (is_hiding_or_showing && index <= InputManager::MAX_POINTER_DEVICES)
InputManager::UpdateRelativeMouseMode();
}
bool ImGuiManager::HasSoftwareCursor(u32 index)

View File

@ -35,7 +35,7 @@ float GetWindowWidth();
float GetWindowHeight();
/// Updates internal state when the window is size.
void WindowResized();
void WindowResized(float width, float height);
/// Updates scaling of the on-screen elements.
void RequestScaleUpdate();

View File

@ -108,6 +108,7 @@ static std::vector<std::string_view> SplitChord(std::string_view binding);
static bool SplitBinding(std::string_view binding, std::string_view* source, std::string_view* sub_binding);
static void PrettifyInputBindingPart(std::string_view binding, SmallString& ret, bool& changed);
static void AddBindings(const std::vector<std::string>& bindings, const InputEventHandler& handler);
static void UpdatePointerCount();
static bool IsAxisHandler(const InputEventHandler& handler);
static float ApplySingleBindingScale(float sensitivity, float deadzone, float value);
@ -180,11 +181,19 @@ static std::array<std::array<float, static_cast<u8>(InputPointerAxis::Count)>, I
static std::array<std::array<PointerAxisState, static_cast<u8>(InputPointerAxis::Count)>,
InputManager::MAX_POINTER_DEVICES>
s_pointer_state;
static u32 s_pointer_count = 0;
static std::array<float, static_cast<u8>(InputPointerAxis::Count)> s_pointer_axis_scale;
using PointerMoveCallback = std::function<void(InputBindingKey key, float value)>;
static std::vector<std::pair<u32, PointerMoveCallback>> s_pointer_move_callbacks;
// Window size, used for clamping the mouse position in raw input modes.
static std::array<float, 2> s_window_size = {};
static bool s_relative_mouse_mode = false;
static bool s_relative_mouse_mode_active = false;
static bool s_hide_host_mouse_cursor = false;
static bool s_hide_host_mouse_cusor_active = false;
// ------------------------------------------------------------------------
// Binding Parsing
// ------------------------------------------------------------------------
@ -293,12 +302,12 @@ bool InputManager::ParseBindingAndGetSource(std::string_view binding, InputBindi
std::string InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key)
{
if (binding_type == InputBindingInfo::Type::Pointer)
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::AbsolutePointer)
{
// pointer and device bindings don't have a data part
if (key.source_type == InputSourceType::Pointer)
{
return GetPointerDeviceName(key.data);
return GetPointerDeviceName(key.source_index);
}
else if (key.source_type < InputSourceType::Count && s_input_sources[static_cast<u32>(key.source_type)])
{
@ -346,7 +355,7 @@ std::string InputManager::ConvertInputBindingKeysToString(InputBindingInfo::Type
const InputBindingKey* keys, size_t num_keys)
{
// can't have a chord of devices/pointers
if (binding_type == InputBindingInfo::Type::Pointer)
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::AbsolutePointer)
{
// so only take the first
if (num_keys > 0)
@ -598,10 +607,10 @@ static std::array<const char*, static_cast<u32>(InputSourceType::Count)> s_input
#ifdef _WIN32
"DInput",
"XInput",
"RawInput",
#endif
#ifndef __ANDROID__
"SDL",
"RawInput",
#else
"Android",
#endif
@ -631,13 +640,13 @@ bool InputManager::GetInputSourceDefaultEnabled(InputSourceType type)
case InputSourceType::XInput:
return false;
case InputSourceType::RawInput:
return false;
#endif
#ifndef __ANDROID__
case InputSourceType::SDL:
return true;
case InputSourceType::RawInput:
return false;
#else
case InputSourceType::Android:
return true;
@ -844,6 +853,7 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
break;
case InputBindingInfo::Type::Pointer:
case InputBindingInfo::Type::AbsolutePointer:
{
auto cb = [pad_index, base = bi.bind_index](InputBindingKey key, float value) {
if (!System::IsValid())
@ -867,7 +877,7 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
if (!key.has_value())
continue;
s_pointer_move_callbacks.emplace_back(0, cb);
s_pointer_move_callbacks.emplace_back(key.value(), cb);
}
}
}
@ -1171,7 +1181,7 @@ void InputManager::GenerateRelativeMouseEvents()
{
const bool system_running = System::IsRunning();
for (u32 device = 0; device < MAX_POINTER_DEVICES; device++)
for (u32 device = 0; device < s_pointer_count; device++)
{
for (u32 axis = 0; axis < static_cast<u32>(static_cast<u8>(InputPointerAxis::Count)); axis++)
{
@ -1208,14 +1218,44 @@ void InputManager::GenerateRelativeMouseEvents()
}
}
void InputManager::UpdatePointerCount()
{
if (!IsUsingRawInput())
{
s_pointer_count = 1;
return;
}
#ifndef __ANDROID__
InputSource* ris = GetInputSourceInterface(InputSourceType::RawInput);
DebugAssert(ris);
s_pointer_count = 0;
for (const std::pair<std::string, std::string>& it : ris->EnumerateDevices())
{
if (it.first.starts_with("Pointer-"))
s_pointer_count++;
}
#endif
}
u32 InputManager::GetPointerCount()
{
return s_pointer_count;
}
std::pair<float, float> InputManager::GetPointerAbsolutePosition(u32 index)
{
DebugAssert(index < s_host_pointer_positions.size());
return std::make_pair(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::X)],
s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::Y)]);
}
void InputManager::UpdatePointerAbsolutePosition(u32 index, float x, float y)
{
if (index >= MAX_POINTER_DEVICES || s_relative_mouse_mode_active) [[unlikely]]
return;
const float dx = x - std::exchange(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::X)], x);
const float dy = y - std::exchange(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::Y)], y);
@ -1236,18 +1276,26 @@ void InputManager::UpdatePointerAbsolutePosition(u32 index, float x, float y)
void InputManager::UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input)
{
if (raw_input != IsUsingRawInput())
if (index >= MAX_POINTER_DEVICES || !s_relative_mouse_mode_active)
return;
s_host_pointer_positions[index][static_cast<u8>(axis)] += d;
s_pointer_state[index][static_cast<u8>(axis)].delta.fetch_add(static_cast<s32>(d * 65536.0f),
std::memory_order_release);
if (index == 0 && axis <= InputPointerAxis::Y)
// We need to clamp the position ourselves in relative mode.
if (axis <= InputPointerAxis::Y)
{
s_host_pointer_positions[index][static_cast<u8>(axis)] =
std::clamp(s_host_pointer_positions[index][static_cast<u8>(axis)], 0.0f, s_window_size[static_cast<u8>(axis)]);
// Imgui also needs to be updated, since the absolute position won't be set above.
if (index == 0)
ImGuiManager::UpdateMousePosition(s_host_pointer_positions[0][0], s_host_pointer_positions[0][1]);
}
}
void InputManager::UpdateHostMouseMode()
void InputManager::UpdateRelativeMouseMode()
{
// Check for relative mode bindings, and enable if there's anything using it.
bool has_relative_mode_bindings = !s_pointer_move_callbacks.empty();
@ -1265,8 +1313,29 @@ void InputManager::UpdateHostMouseMode()
}
}
const bool has_software_cursor = ImGuiManager::HasSoftwareCursor(0);
Host::SetMouseMode(has_relative_mode_bindings, has_relative_mode_bindings || has_software_cursor);
const bool hide_mouse_cursor = has_relative_mode_bindings || ImGuiManager::HasSoftwareCursor(0);
if (s_relative_mouse_mode == has_relative_mode_bindings && s_hide_host_mouse_cursor == hide_mouse_cursor)
return;
s_relative_mouse_mode = has_relative_mode_bindings;
s_hide_host_mouse_cursor = hide_mouse_cursor;
UpdateRelativeMouseMode();
}
void InputManager::UpdateHostMouseMode()
{
const bool can_change = System::IsRunning();
const bool wanted_relative_mouse_mode = (s_relative_mouse_mode && can_change);
const bool wanted_hide_host_mouse_cursor = (s_hide_host_mouse_cursor && can_change);
if (wanted_relative_mouse_mode == s_relative_mouse_mode_active &&
wanted_hide_host_mouse_cursor == s_hide_host_mouse_cusor_active)
{
return;
}
s_relative_mouse_mode_active = wanted_relative_mouse_mode;
s_hide_host_mouse_cusor_active = wanted_hide_host_mouse_cursor;
Host::SetMouseMode(wanted_relative_mouse_mode, wanted_hide_host_mouse_cursor);
}
bool InputManager::IsUsingRawInput()
@ -1278,6 +1347,12 @@ bool InputManager::IsUsingRawInput()
#endif
}
void InputManager::SetDisplayWindowSize(float width, float height)
{
s_window_size[0] = width;
s_window_size[1] = height;
}
void InputManager::SetDefaultSourceConfig(SettingsInterface& si)
{
si.ClearSection("InputSources");
@ -1454,11 +1529,13 @@ std::vector<std::string> InputManager::GetInputProfileNames()
void InputManager::OnInputDeviceConnected(std::string_view identifier, std::string_view device_name)
{
INFO_LOG("Device '{}' connected: '{}'", identifier, device_name);
Host::OnInputDeviceConnected(identifier, device_name);
}
void InputManager::OnInputDeviceDisconnected(InputBindingKey key, std::string_view identifier)
{
INFO_LOG("Device '{}' disconnected", identifier);
Host::OnInputDeviceDisconnected(key, identifier);
}
@ -1771,7 +1848,7 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
1.0f);
}
UpdateHostMouseMode();
UpdateRelativeMouseMode();
}
// ------------------------------------------------------------------------
@ -1788,6 +1865,8 @@ bool InputManager::ReloadDevices()
changed |= s_input_sources[i]->ReloadDevices();
}
UpdatePointerCount();
return changed;
}
@ -1972,4 +2051,6 @@ void InputManager::ReloadSources(SettingsInterface& si, std::unique_lock<std::mu
#else
UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource);
#endif
UpdatePointerCount();
}

View File

@ -27,10 +27,10 @@ enum class InputSourceType : u32
#ifdef _WIN32
DInput,
XInput,
RawInput,
#endif
#ifndef __ANDROID__
SDL,
RawInput,
#else
Android,
#endif
@ -174,12 +174,12 @@ namespace InputManager {
static constexpr double VIBRATION_UPDATE_INTERVAL_SECONDS = 0.5; // 500ms
/// Maximum number of host mouse devices.
static constexpr u32 MAX_POINTER_DEVICES = 1;
static constexpr u32 MAX_POINTER_DEVICES = 8;
static constexpr u32 MAX_POINTER_BUTTONS = 3;
/// Maximum number of software cursors. We allocate an extra two for controllers with
/// positioning data from the controller instead of a mouse.
static constexpr u32 MAX_SOFTWARE_CURSORS = MAX_POINTER_BUTTONS + 2;
static constexpr u32 MAX_SOFTWARE_CURSORS = MAX_POINTER_DEVICES + 2;
/// Number of macro buttons per controller.
static constexpr u32 NUM_MACRO_BUTTONS_PER_CONTROLLER = 4;
@ -311,6 +311,9 @@ void SetPadVibrationIntensity(u32 pad_index, float large_or_single_motor_intensi
/// The pad vibration state will internally remain, so that when emulation is unpaused, the effect resumes.
void PauseVibration();
/// Returns the number of currently-connected pointer devices.
u32 GetPointerCount();
/// Reads absolute pointer position.
std::pair<float, float> GetPointerAbsolutePosition(u32 index);
@ -322,6 +325,7 @@ void UpdatePointerAbsolutePosition(u32 index, float x, float y);
void UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input = false);
/// Updates host mouse mode (relative/cursor hiding).
void UpdateRelativeMouseMode();
void UpdateHostMouseMode();
/// Sets the state of the specified macro button.
@ -330,6 +334,9 @@ void SetMacroButtonState(u32 pad, u32 index, bool state);
/// Returns true if the raw input source is being used.
bool IsUsingRawInput();
/// Updates InputManager's view of the window size, used for clamping raw input coordinates.
void SetDisplayWindowSize(float width, float height);
/// Restores default configuration.
void SetDefaultSourceConfig(SettingsInterface& si);

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "win32_raw_input_source.h"
@ -8,7 +8,9 @@
#include "core/host.h"
#include "core/system.h"
#include "input_manager.h"
#include <cmath>
#include <hidsdi.h>
#include <hidusage.h>
#include <malloc.h>
@ -71,7 +73,11 @@ void Win32RawInputSource::PollEvents()
std::vector<std::pair<std::string, std::string>> Win32RawInputSource::EnumerateDevices()
{
return {};
std::vector<std::pair<std::string, std::string>> ret;
for (u32 pointer_index = 0; pointer_index < static_cast<u32>(m_mice.size()); pointer_index++)
ret.emplace_back(InputManager::GetPointerDeviceName(pointer_index), GetMouseDeviceName(pointer_index));
return ret;
}
void Win32RawInputSource::UpdateMotorState(InputBindingKey key, float intensity)
@ -117,7 +123,8 @@ bool Win32RawInputSource::RegisterDummyClass()
wc.hInstance = GetModuleHandleW(nullptr);
wc.lpfnWndProc = DummyWindowProc;
wc.lpszClassName = WINDOW_CLASS_NAME;
return (RegisterClassW(&wc) != 0);
s_window_class_registered = (RegisterClassW(&wc) != 0);
return s_window_class_registered;
}
bool Win32RawInputSource::CreateDummyWindow()
@ -160,12 +167,58 @@ LRESULT CALLBACK Win32RawInputSource::DummyWindowProc(HWND hwnd, UINT msg, WPARA
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
std::string Win32RawInputSource::GetMouseDeviceName(u32 index)
{
#if 0
// Doesn't work for mice :(
const HANDLE device = m_mice[index].device;
std::wstring wdevice_name;
UINT size = 0;
if (GetRawInputDeviceInfoW(device, RIDI_DEVICENAME, nullptr, &size) == static_cast<UINT>(-1))
goto error;
wdevice_name.resize(size);
UINT written_size = GetRawInputDeviceInfoW(device, RIDI_DEVICENAME, wdevice_name.data(), &size);
if (written_size == static_cast<UINT>(-1))
goto error;
wdevice_name.resize(written_size);
if (wdevice_name.empty())
goto error;
const HANDLE hFile = CreateFileW(wdevice_name.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
goto error;
wchar_t product_string[256];
if (!HidD_GetProductString(hFile, product_string, sizeof(product_string)))
{
CloseHandle(hFile);
goto error;
}
CloseHandle(hFile);
return StringUtil::WideStringToUTF8String(product_string);
error:
return "Unknown Device";
#else
return fmt::format("Raw Input Pointer {}", index);
#endif
}
bool Win32RawInputSource::OpenDevices()
{
UINT num_devices = 0;
if (GetRawInputDeviceList(nullptr, &num_devices, sizeof(RAWINPUTDEVICELIST)) == static_cast<UINT>(-1) ||
num_devices == 0)
{
return false;
}
std::vector<RAWINPUTDEVICELIST> devices(num_devices);
if (GetRawInputDeviceList(devices.data(), &num_devices, sizeof(RAWINPUTDEVICELIST)) == static_cast<UINT>(-1))
@ -174,28 +227,37 @@ bool Win32RawInputSource::OpenDevices()
for (const RAWINPUTDEVICELIST& rid : devices)
{
#if 0
if (rid.dwType == RIM_TYPEKEYBOARD)
m_num_keyboards++;
#endif
if (rid.dwType == RIM_TYPEMOUSE)
m_mice.push_back({rid.hDevice, 0u, 0, 0});
}
DEV_LOG("Found {} keyboards and {} mice", m_num_keyboards, m_mice.size());
// Grab all keyboard/mouse input.
if (m_num_keyboards > 0)
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, 0, m_dummy_window};
if (!RegisterRawInputDevices(&rrid, 1, sizeof(rrid)))
return false;
// Make sure it's a real mouse with buttons.
// My goal with this was to stop my silly Corsair keyboard from showing up as a mouse... but it reports 32
// buttons.
RID_DEVICE_INFO devinfo = {
.cbSize = sizeof(devinfo),
.dwType = RIM_TYPEMOUSE,
};
UINT devinfo_size = sizeof(devinfo);
if (GetRawInputDeviceInfoW(rid.hDevice, RIDI_DEVICEINFO, &devinfo, &devinfo_size) <= 0 ||
devinfo.mouse.dwNumberOfButtons == 0)
{
continue;
}
m_mice.push_back({.device = rid.hDevice, .button_state = 0, .last_x = 0, .last_y = 0});
}
}
DEV_LOG("Found {} mice", m_mice.size());
// Grab all mouse input.
if (!m_mice.empty())
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE, 0, m_dummy_window};
if (!RegisterRawInputDevices(&rrid, 1, sizeof(rrid)))
return false;
for (u32 i = 0; i < static_cast<u32>(m_mice.size()); i++)
InputManager::OnInputDeviceConnected(InputManager::GetPointerDeviceName(i), GetMouseDeviceName(i));
}
return true;
@ -203,17 +265,16 @@ bool Win32RawInputSource::OpenDevices()
void Win32RawInputSource::CloseDevices()
{
if (m_num_keyboards > 0)
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE, RIDEV_REMOVE, m_dummy_window};
RegisterRawInputDevices(&rrid, 1, sizeof(rrid));
m_num_keyboards = 0;
}
if (!m_mice.empty())
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, RIDEV_REMOVE, m_dummy_window};
RegisterRawInputDevices(&rrid, 1, sizeof(rrid));
for (u32 i = 0; i < static_cast<u32>(m_mice.size()); i++)
{
InputManager::OnInputDeviceDisconnected(InputManager::MakePointerAxisKey(i, InputPointerAxis::X),
InputManager::GetPointerDeviceName(i));
}
m_mice.clear();
}
}
@ -222,9 +283,9 @@ bool Win32RawInputSource::ProcessRawInputEvent(const RAWINPUT* event)
{
if (event->header.dwType == RIM_TYPEMOUSE)
{
const u32 mouse_index = 0;
for (MouseState& state : m_mice)
for (u32 pointer_index = 0; pointer_index < static_cast<u32>(m_mice.size()); pointer_index++)
{
MouseState& state = m_mice[pointer_index];
if (state.device != event->header.hDevice)
continue;
@ -244,10 +305,6 @@ bool Win32RawInputSource::ProcessRawInputEvent(const RAWINPUT* event)
(rm.usButtonFlags & (rm.usButtonFlags ^ std::exchange(state.button_state, rm.usButtonFlags))) &
ALL_BUTTON_MASKS;
// when the VM isn't running, allow events to run as normal (so we don't break the UI)
if (System::GetState() != System::State::Running)
return false;
while (button_mask != 0)
{
unsigned long bit_index;
@ -255,17 +312,17 @@ bool Win32RawInputSource::ProcessRawInputEvent(const RAWINPUT* event)
// these are ordered down..up for each button
const u32 button_number = bit_index >> 1;
const bool button_pressed = (bit_index & 1u) != 0;
InputManager::InvokeEvents(InputManager::MakePointerButtonKey(mouse_index, button_number),
const bool button_pressed = (bit_index & 1u) == 0;
InputManager::InvokeEvents(InputManager::MakePointerButtonKey(pointer_index, button_number),
static_cast<float>(button_pressed), GenericInputBinding::Unknown);
button_mask &= ~(1u << bit_index);
}
if (dx != 0)
InputManager::UpdatePointerRelativeDelta(mouse_index, InputPointerAxis::X, static_cast<float>(dx), true);
InputManager::UpdatePointerRelativeDelta(pointer_index, InputPointerAxis::X, static_cast<float>(dx), true);
if (dy != 0)
InputManager::UpdatePointerRelativeDelta(mouse_index, InputPointerAxis::Y, static_cast<float>(dy), true);
InputManager::UpdatePointerRelativeDelta(pointer_index, InputPointerAxis::Y, static_cast<float>(dy), true);
return true;
}

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
@ -46,6 +46,8 @@ private:
static bool RegisterDummyClass();
static LRESULT CALLBACK DummyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
static std::string GetMouseDeviceName(u32 index);
bool CreateDummyWindow();
void DestroyDummyWindow();
bool OpenDevices();
@ -54,7 +56,6 @@ private:
bool ProcessRawInputEvent(const RAWINPUT* event);
HWND m_dummy_window = {};
u32 m_num_keyboards = 0;
std::vector<MouseState> m_mice;
};