Automatic combo button input
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.9, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.9, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.9, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.9, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (0, 51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, Intel) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (1, 8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, Apple Silicon) (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run
Build RPCS3 / RPCS3 Windows Clang ${{ matrix.arch }} (aarch64, clang, clangarm64, ARM64, windows-11-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Windows Clang ${{ matrix.arch }} (x86_64, clang, clang64, X64, windows-2025) (push) Waiting to run
Build RPCS3 / RPCS3 FreeBSD (push) Waiting to run

This commit is contained in:
Megamouse 2026-03-17 13:37:08 +01:00
parent 9f3adb3fc7
commit bc0c42b025
5 changed files with 148 additions and 46 deletions

View file

@ -334,12 +334,9 @@ PadHandlerBase::connection PadHandlerBase::get_next_button_press(const std::stri
// Check for each button in our list if its corresponding (maybe remapped) button or axis was pressed.
// Return the new value if the button was pressed (aka. its value was bigger than 0 or the defined threshold)
// Get all the legally pressed buttons and use the one with highest value (prioritize first)
struct
{
u16 value = 0;
std::string name;
} pressed_button{};
// Get all the legally pressed buttons. We only accept one value per stick though, otherwise it will get messy.
std::map<std::string, u16> pressed_buttons;
std::array<std::pair<std::string, u16>, 2> pressed_sticks{};
for (const auto& [keycode, name] : button_list)
{
@ -356,7 +353,9 @@ PadHandlerBase::connection PadHandlerBase::get_next_button_press(const std::stri
}
const bool is_trigger = get_is_left_trigger(device, keycode) || get_is_right_trigger(device, keycode);
const bool is_stick = !is_trigger && (get_is_left_stick(device, keycode) || get_is_right_stick(device, keycode));
const bool is_left_stick = !is_trigger && get_is_left_stick(device, keycode);
const bool is_right_stick = !is_trigger && !is_left_stick && get_is_right_stick(device, keycode);
const bool is_stick = is_left_stick || is_right_stick;
const bool is_touch_motion = !is_trigger && !is_stick && get_is_touch_pad_motion(device, keycode);
const bool is_button = !is_trigger && !is_stick && !is_touch_motion;
@ -374,9 +373,27 @@ PadHandlerBase::connection PadHandlerBase::get_next_button_press(const std::stri
const u16 diff = value > min_value ? value - min_value : 0;
if (diff > button_press_threshold && value > pressed_button.value)
if (diff > button_press_threshold)
{
pressed_button = { .value = value, .name = name };
if (is_left_stick)
{
if (pressed_sticks[0].second < value)
{
pressed_sticks[0] = { name, value };
}
}
else if (is_right_stick)
{
if (pressed_sticks[1].second < value)
{
pressed_sticks[1] = { name, value };
}
}
else
{
u16& pressed_value = pressed_buttons[name];
pressed_value = std::max(pressed_value, value);
}
}
}
}
@ -399,10 +416,7 @@ PadHandlerBase::connection PadHandlerBase::get_next_button_press(const std::stri
pad_capabilities capabilities = get_capabilities(pad_id);
const u32 battery_level = get_battery_level(pad_id);
if (pressed_button.value > 0)
callback(pressed_button.value, std::move(pressed_button.name), pad_id, battery_level, std::move(preview_values), std::move(capabilities));
else
callback(0, "", pad_id, battery_level, std::move(preview_values), std::move(capabilities));
callback(std::move(pressed_buttons), std::move(pressed_sticks), pad_id, battery_level, std::move(preview_values), std::move(capabilities));
}
return status;

View file

@ -90,12 +90,12 @@ struct pad_capabilities
};
using pad_preview_values = std::array<int, 6>;
using pad_callback = std::function<void(u16 /*button_value*/, std::string /*button_name*/, std::string /*pad_name*/, u32 /*battery_level*/, pad_preview_values, pad_capabilities)>;
using pad_callback = std::function<void(std::map<std::string, u16>&& /*pressed_buttons*/, std::array<std::pair<std::string, u16>, 2>&& /*pressed_sticks*/, std::string /*pad_name*/, u32 /*battery_level*/, pad_preview_values&&, pad_capabilities&&)>;
using pad_fail_callback = std::function<void(std::string /*pad_name*/)>;
using motion_preview_values = std::array<u16, 4>;
using motion_callback = std::function<void(std::string /*pad_name*/, motion_preview_values /*preview_values*/)>;
using motion_fail_callback = std::function<void(std::string /*pad_name*/, motion_preview_values /*preview_values*/)>;
using motion_callback = std::function<void(std::string /*pad_name*/, motion_preview_values&& /*preview_values*/)>;
using motion_fail_callback = std::function<void(std::string /*pad_name*/, motion_preview_values&& /*preview_values*/)>;
class PadHandlerBase
{

View file

@ -422,15 +422,15 @@ PadHandlerBase::connection evdev_joystick_handler::get_next_button_press(const s
if (call_type != gui_call_type::blacklist && call_type != gui_call_type::reset_input && !has_new_event)
{
if (callback)
callback(0, "", padId, 0, preview_values, get_capabilities(padId));
callback({}, {}, padId, 0, std::move(preview_values), get_capabilities(padId));
return connection::no_data;
}
struct
{
u16 value = 0;
std::string name;
} pressed_button{};
// Check for each button in our list if its corresponding (maybe remapped) button or axis was pressed.
// Return the new value if the button was pressed (aka. its value was bigger than 0 or the defined threshold)
// Get all the legally pressed buttons. We only accept one value for axis though, otherwise it will get messy.
std::map<std::string, u16> pressed_buttons;
std::array<std::pair<std::string, u16>, 2> pressed_sticks{};
const auto set_button_press = [&](const u32 code, const std::string& name, std::string_view type, u16 threshold, int ev_type, bool is_rev_axis)
{
@ -497,9 +497,20 @@ PadHandlerBase::connection evdev_joystick_handler::get_next_button_press(const s
const u16 diff = value > min_value ? value - min_value : 0;
if (diff > button_press_threshold && value > pressed_button.value)
if (diff > button_press_threshold)
{
pressed_button = { .value = value, .name = name };
if (ev_type == EV_ABS)
{
if (pressed_sticks[0].second < value)
{
pressed_sticks[0] = {name, value};
}
}
else
{
u16& pressed_value = pressed_buttons[name];
pressed_value = std::max(pressed_value, value);
}
}
};
@ -546,10 +557,7 @@ PadHandlerBase::connection evdev_joystick_handler::get_next_button_press(const s
{
pad_capabilities capabilities = get_capabilities(padId);
if (pressed_button.value > 0)
callback(pressed_button.value, pressed_button.name, padId, 0, std::move(preview_values), std::move(capabilities));
else
callback(0, "", padId, 0, std::move(preview_values), std::move(capabilities));
callback(std::move(pressed_buttons), std::move(pressed_sticks), padId, 0, std::move(preview_values), std::move(capabilities));
}
return connection::connected;

View file

@ -17,6 +17,7 @@
#include "Emu/System.h"
#include "Emu/system_utils.hpp"
#include "Utilities/File.h"
#include "Utilities/Timer.h"
#include "Input/pad_thread.h"
#include "Input/gui_pad_thread.h"
@ -551,16 +552,95 @@ void pad_settings_dialog::InitButtons()
// Enable Button Remapping
update_preview(data.pad_name, true, data.battery_level, data.preview_values[0], data.preview_values[1], data.preview_values[2], data.preview_values[3], data.preview_values[4], data.preview_values[5], data.capabilities);
static Timer s_first_input_timer = {};
static std::map<std::string, u16> s_pressed_buttons;
static std::array<std::pair<std::string, u16>, 2> s_pressed_sticks = {};
static u32 s_button_id = button_ids::id_pad_begin;
const u32 button_id = m_button_id;
if (s_button_id != button_id)
{
s_button_id = button_id;
s_pressed_buttons.clear();
s_pressed_sticks = {};
s_first_input_timer.Stop();
}
// Handle Button Presses
for (const input_callback_data::input_values& values : data.values)
{
if (values.val <= 0) continue;
cfg_log.notice("get_next_button_press: %s device %s button %s pressed with value %d", m_handler->m_type, data.pad_name, values.button_name, values.val);
if (m_button_id > button_ids::id_pad_begin && m_button_id < button_ids::id_pad_end && m_button_id == values.button_id)
for (const auto& [key, value] : values.buttons)
{
m_cfg_entries[m_button_id].insert_key(values.button_name, m_binding_mode);
if (value == 0) continue;
cfg_log.notice("get_next_button_press: %s device %s button %s pressed with value %d", m_handler->m_type, data.pad_name, key, value);
if (button_id > button_ids::id_pad_begin && button_id < button_ids::id_pad_end && button_id == values.button_id)
{
if (s_pressed_buttons.empty())
{
s_first_input_timer.Start();
}
u16& val = s_pressed_buttons[key];
val = std::max(val, value);
}
}
for (usz i = 0; i < values.sticks.size(); i++)
{
const auto& [key, value] = values.sticks[i];
if (value == 0) continue;
cfg_log.notice("get_next_button_press: %s device %s button %s pressed with value %d", m_handler->m_type, data.pad_name, key, value);
if (button_id > button_ids::id_pad_begin && button_id < button_ids::id_pad_end && button_id == values.button_id)
{
if (s_pressed_sticks[i].second == 0)
{
s_first_input_timer.Start();
}
if (value > s_pressed_sticks[i].second)
{
s_pressed_sticks[i] = {key, value};
}
}
}
}
if (button_id > button_ids::id_pad_begin && button_id < button_ids::id_pad_end && (!s_pressed_buttons.empty() || s_pressed_sticks[0].second || s_pressed_sticks[1].second))
{
const double elapsed_ms = s_first_input_timer.GetElapsedTimeInMilliSec();
if (elapsed_ms > 100.0)
{
binding_mode mode = m_binding_mode;
for (const auto& [key, value] : s_pressed_buttons)
{
if (value == 0) continue;
m_cfg_entries[m_button_id].insert_key(key, mode);
// Switch to combo mode for all further keys
mode = binding_mode::combo;
}
for (const auto& [key, value] : s_pressed_sticks)
{
if (value == 0) continue;
m_cfg_entries[m_button_id].insert_key(key, mode);
// Switch to combo mode for all further keys
mode = binding_mode::combo;
}
s_pressed_buttons.clear();
s_pressed_sticks = {};
s_first_input_timer.Stop();
ReactivateButtons();
}
}
@ -613,7 +693,7 @@ void pad_settings_dialog::InitButtons()
const PadHandlerBase::gui_call_type call_type = first_call ? PadHandlerBase::gui_call_type::reset_input : PadHandlerBase::gui_call_type::normal;
const PadHandlerBase::connection status = m_handler->get_next_button_press(m_device_name,
[this, button_id](u16 val, std::string button_name, std::string pad_name, u32 battery_level, pad_preview_values preview_values, pad_capabilities capabilities)
[this, button_id](std::map<std::string, u16>&& pressed_buttons, std::array<std::pair<std::string, u16>, 2>&& pressed_sticks, std::string pad_name, u32 battery_level, pad_preview_values&& preview_values, pad_capabilities&& capabilities)
{
std::lock_guard lock(m_input_mutex);
if (m_input_callback_data.pad_name != pad_name)
@ -626,13 +706,13 @@ void pad_settings_dialog::InitButtons()
m_input_callback_data.capabilities = std::move(capabilities);
m_input_callback_data.has_new_data = true;
m_input_callback_data.status = PadHandlerBase::connection::connected;
if (val > 0)
if (!pressed_buttons.empty() || !pressed_sticks.empty())
{
m_input_callback_data.values.push_back(input_callback_data::input_values
{
.button_name = std::move(button_name),
.button_id = button_id,
.val = val,
.buttons = std::move(pressed_buttons),
.sticks = std::move(pressed_sticks)
});
}
},
@ -917,7 +997,7 @@ void pad_settings_dialog::RepaintPreviewLabel(QLabel* label, int deadzone, int a
label->setPixmap(pixmap);
}
void pad_settings_dialog::keyPressEvent(QKeyEvent *keyEvent)
void pad_settings_dialog::keyPressEvent(QKeyEvent* keyEvent)
{
if (m_button_id == button_ids::id_pad_begin)
{
@ -976,7 +1056,7 @@ void pad_settings_dialog::mouseReleaseEvent(QMouseEvent* event)
ReactivateButtons();
}
void pad_settings_dialog::wheelEvent(QWheelEvent *event)
void pad_settings_dialog::wheelEvent(QWheelEvent* event)
{
if (m_button_id == button_ids::id_pad_begin)
{

View file

@ -199,9 +199,9 @@ private:
struct input_values
{
std::string button_name;
u32 button_id = button_ids::id_pad_begin;
u16 val = 0;
std::map<std::string, u16> buttons;
std::array<std::pair<std::string, u16>, 2> sticks{};
};
std::vector<input_values> values;
} m_input_callback_data;
@ -273,10 +273,10 @@ protected:
void showEvent(QShowEvent* event) override;
/** Handle keyboard handler input */
void keyPressEvent(QKeyEvent *keyEvent) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void keyPressEvent(QKeyEvent* keyEvent) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
bool eventFilter(QObject* object, QEvent* event) override;
void closeEvent(QCloseEvent* event) override;
};