qt: Add controller touchpad support (#777)

This commit is contained in:
David Griswold 2026-03-11 16:21:17 -07:00 committed by GitHub
parent a3db3be4a6
commit 1febb83942
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 193 additions and 17 deletions

View File

@ -199,6 +199,8 @@ if (ENABLE_QT)
"name"
"bind"
"profile"
"use_touchpad"
"controller_touch_device"
"use_touch_from_button"
"touch_from_button_map"
"touch_from_button_maps" # Why are these two so similar? Basically typo bait

View File

@ -405,6 +405,11 @@ void QtConfig::ReadControlValues() {
ReadSetting(Settings::QKeys::touch_device, QStringLiteral("engine:emu_window"))
.toString()
.toStdString();
profile.use_touchpad = ReadSetting(Settings::QKeys::use_touchpad, false).toBool();
profile.controller_touch_device =
ReadSetting(Settings::QKeys::controller_touch_device, QStringLiteral(""))
.toString()
.toStdString();
profile.use_touch_from_button =
ReadSetting(Settings::QKeys::use_touch_from_button, false).toBool();
profile.touch_from_button_map_index =
@ -1005,6 +1010,9 @@ void QtConfig::SaveControlValues() {
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0"));
WriteSetting(Settings::QKeys::touch_device, QString::fromStdString(profile.touch_device),
QStringLiteral("engine:emu_window"));
WriteSetting(Settings::QKeys::use_touchpad, profile.use_touchpad, false);
WriteSetting(Settings::QKeys::controller_touch_device,
QString::fromStdString(profile.controller_touch_device), QStringLiteral(""));
WriteSetting(Settings::QKeys::use_touch_from_button, profile.use_touch_from_button, false);
WriteSetting(Settings::QKeys::touch_from_button_map, profile.touch_from_button_map_index,
0);

View File

@ -139,6 +139,7 @@ void ConfigureMotionTouch::SetConfiguration() {
ui->touch_provider->findData(QString::fromStdString(touch_engine)));
ui->touch_from_button_checkbox->setChecked(
Settings::values.current_input_profile.use_touch_from_button);
ui->touchpad_checkbox->setChecked(Settings::values.current_input_profile.use_touchpad);
touch_from_button_maps = Settings::values.touch_from_button_maps;
for (const auto& touch_map : touch_from_button_maps) {
ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
@ -164,7 +165,7 @@ void ConfigureMotionTouch::SetConfiguration() {
void ConfigureMotionTouch::UpdateUiDisplay() {
const std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
const std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
ui->touchpad_config_btn->setEnabled(ui->touchpad_checkbox->isChecked());
if (motion_engine == "motion_emu") {
ui->motion_sensitivity_label->setVisible(true);
ui->motion_sensitivity->setVisible(true);
@ -229,6 +230,33 @@ void ConfigureMotionTouch::ConnectEvents() {
poll_timer->start(200); // Check for new inputs every 200ms
}
});
connect(ui->touchpad_checkbox, &QCheckBox::checkStateChanged, this,
[this]() { UpdateUiDisplay(); });
connect(ui->touchpad_config_btn, &QPushButton::clicked, this, [this]() {
if (QMessageBox::information(this, tr("Information"),
tr("After pressing OK, tap the touchpad on the controller "
"you want to track."),
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
ui->touchpad_config_btn->setText(tr("[press touchpad]"));
ui->touchpad_config_btn->setFocus();
input_setter = [this](const Common::ParamPackage& params) {
tpguid = params.Get("guid", "0");
tpport = params.Get("port", 0);
tp = params.Get("touchpad", 0);
};
device_pollers =
InputCommon::Polling::GetPollers(InputCommon::Polling::DeviceType::Touchpad);
for (auto& poller : device_pollers) {
poller->Start();
}
timeout_timer->start(5000); // Cancel after 5 seconds
poll_timer->start(200); // Check for new inputs every 200ms
}
});
connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest);
connect(ui->touch_calibration_config, &QPushButton::clicked, this,
&ConfigureMotionTouch::OnConfigureTouchCalibration);
@ -253,7 +281,7 @@ void ConfigureMotionTouch::SetPollingResult(const Common::ParamPackage& params,
if (!abort && input_setter) {
(*input_setter)(params);
}
ui->touchpad_config_btn->setText(tr("Configure"));
ui->motion_controller_button->setText(tr("Configure"));
input_setter.reset();
}
@ -291,7 +319,6 @@ void ConfigureMotionTouch::OnConfigureTouchCalibration() {
"UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
min_x, min_y, max_x, max_y);
UpdateUiDisplay();
} else {
LOG_ERROR(Frontend, "UDP touchpad calibration config failed");
}
ui->touch_calibration_config->setEnabled(true);
@ -374,12 +401,21 @@ void ConfigureMotionTouch::ApplyConfiguration() {
touch_param.Set("max_y", max_y);
}
Common::ParamPackage touchpad_param{};
if (ui->touchpad_checkbox->isChecked()) {
touchpad_param.Set("engine", "sdl");
touchpad_param.Set("guid", tpguid);
touchpad_param.Set("port", tpport);
touchpad_param.Set("touchpad", tp);
}
Settings::values.current_input_profile.motion_device = motion_param.Serialize();
Settings::values.current_input_profile.touch_device = touch_param.Serialize();
Settings::values.current_input_profile.use_touch_from_button =
ui->touch_from_button_checkbox->isChecked();
Settings::values.current_input_profile.touch_from_button_map_index =
ui->touch_from_button_map->currentIndex();
Settings::values.current_input_profile.use_touchpad = ui->touchpad_checkbox->isChecked();
Settings::values.current_input_profile.controller_touch_device = touchpad_param.Serialize();
Settings::values.touch_from_button_maps = touch_from_button_maps;
Settings::values.current_input_profile.udp_input_address = ui->udp_server->text().toStdString();
Settings::values.current_input_profile.udp_input_port =

View File

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -76,6 +76,9 @@ private:
// Used for SDL input polling
std::string guid;
int port;
std::string tpguid; // guid for touchpad
int tpport; // port for touchpad
int tp; // which touchpad
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;

View File

@ -2,17 +2,17 @@
<ui version="4.0">
<class>ConfigureMotionTouch</class>
<widget class="QDialog" name="ConfigureMotionTouch">
<property name="windowTitle">
<string>Configure Motion / Touch</string>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>450</height>
<width>517</width>
<height>659</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Motion / Touch</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox" name="motion_group_box">
@ -175,6 +175,27 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="touchpad_hlayout">
<item>
<widget class="QCheckBox" name="touchpad_checkbox">
<property name="toolTip">
<string>Map touchpads on controllers like the DualSense directly to touch</string>
</property>
<property name="text">
<string>Use controller touchpad</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QPushButton" name="touchpad_config_btn">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@ -324,4 +345,5 @@
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -442,6 +442,8 @@ struct InputProfile {
std::array<std::string, NativeAnalog::NumAnalogs> analogs;
std::string motion_device;
std::string touch_device;
std::string controller_touch_device;
bool use_touchpad;
bool use_touch_from_button;
int touch_from_button_map_index;
std::string udp_input_address;

View File

@ -115,6 +115,7 @@ DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
}
void Module::LoadInputDevices() {
LOG_DEBUG(Frontend, "Loading input devices");
std::transform(Settings::values.current_input_profile.buttons.begin() +
Settings::NativeButton::BUTTON_HID_BEGIN,
Settings::values.current_input_profile.buttons.begin() +
@ -126,6 +127,13 @@ void Module::LoadInputDevices() {
Settings::values.current_input_profile.motion_device);
touch_device = Input::CreateDevice<Input::TouchDevice>(
Settings::values.current_input_profile.touch_device);
if (Settings::values.current_input_profile.use_touchpad &&
Settings::values.current_input_profile.controller_touch_device != "") {
controller_touch_device = Input::CreateDevice<Input::TouchDevice>(
Settings::values.current_input_profile.controller_touch_device);
} else {
controller_touch_device.reset();
}
if (Settings::values.current_input_profile.use_touch_from_button) {
touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button");
} else {
@ -278,6 +286,9 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) {
if (!pressed && touch_btn_device) {
std::tie(x, y, pressed) = touch_btn_device->GetStatus();
}
if (!pressed && controller_touch_device) {
std::tie(x, y, pressed) = controller_touch_device->GetStatus();
}
touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth);
touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight);
touch_entry.valid.Assign(pressed ? 1 : 0);

View File

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -390,6 +390,7 @@ private:
buttons;
std::unique_ptr<Input::AnalogDevice> circle_pad;
std::unique_ptr<Input::MotionDevice> motion_device;
std::unique_ptr<Input::TouchDevice> controller_touch_device;
std::unique_ptr<Input::TouchDevice> touch_device;
std::unique_ptr<Input::TouchDevice> touch_btn_device;

View File

@ -1,4 +1,4 @@
// Copyright 2017 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -45,7 +45,7 @@ void ReloadInputDevices();
namespace Polling {
enum class DeviceType { Button, Analog };
enum class DeviceType { Button, Analog, Touchpad };
/**
* A class that can be used to get inputs from an input device like controllers without having to

View File

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

View File

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -182,6 +182,11 @@ public:
return has_gyro || has_accel;
}
void SetTouchpad(float x, float y, int touchpad, bool down) {
std::lock_guard lock{mutex};
state.touchpad[touchpad] = std::make_tuple(x, y, down);
}
void SetButton(int button, bool value) {
std::lock_guard lock{mutex};
state.buttons[button] = value;
@ -246,6 +251,11 @@ public:
return std::make_tuple(state.accel, state.gyro);
}
std::tuple<float, float, bool> GetTouch(int pad) {
std::lock_guard lock{mutex};
return state.touchpad[pad];
}
/**
* The guid of the joystick
*/
@ -280,6 +290,7 @@ private:
std::unordered_map<int, Uint8> hats;
Common::Vec3<float> accel;
Common::Vec3<float> gyro;
std::unordered_map<int, std::tuple<float, float, bool>> touchpad;
} state;
std::string guid;
int port;
@ -590,6 +601,19 @@ void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
}
break;
}
case SDL_CONTROLLERTOUCHPADDOWN:
case SDL_CONTROLLERTOUCHPADMOTION:
if (auto joystick = GetSDLJoystickBySDLID(event.ctouchpad.which)) {
joystick->SetTouchpad(event.ctouchpad.x, event.ctouchpad.y, event.ctouchpad.touchpad,
true);
}
break;
case SDL_CONTROLLERTOUCHPADUP:
if (auto joystick = GetSDLJoystickBySDLID(event.ctouchpad.which)) {
joystick->SetTouchpad(event.ctouchpad.x, event.ctouchpad.y, event.ctouchpad.touchpad,
false);
}
break;
#endif
case SDL_JOYDEVICEREMOVED:
LOG_DEBUG(Input, "Joystick removed with Instance_ID {}", event.jdevice.which);
@ -691,6 +715,20 @@ private:
std::shared_ptr<SDLJoystick> joystick;
};
class SDLTouch final : public Input::TouchDevice {
public:
explicit SDLTouch(std::shared_ptr<SDLJoystick> joystick_, int pad_)
: joystick(std::move(joystick_)), pad(pad_) {}
std::tuple<float, float, bool> GetStatus() const override {
return joystick->GetTouch(pad);
}
private:
std::shared_ptr<SDLJoystick> joystick;
const int pad;
};
/// A button device factory that creates button devices from SDL joystick
class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
public:
@ -810,6 +848,30 @@ public:
return std::make_unique<SDLMotion>(joystick);
}
private:
SDLState& state;
};
/**
* A factory that creates a TouchDevice from an SDL Touchpad
*/
class SDLTouchFactory final : public Input::Factory<Input::TouchDevice> {
public:
explicit SDLTouchFactory(SDLState& state_) : state(state_) {}
/**
* Creates touch device from touchpad
* @param params contains parameters for creating the device:
* - "guid": the guid of the joystick to bind
* - "port": the nth joystick of the same type
* - "touchpad": which touchpad to bind
*/
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
const std::string guid = params.Get("guid", "0");
const int port = params.Get("port", 0);
const int touchpad = params.Get("touchpad", 0);
auto joystick = state.GetSDLJoystickByGUID(guid, port);
return std::make_unique<SDLTouch>(joystick, touchpad);
}
private:
SDLState& state;
};
@ -819,7 +881,7 @@ SDLState::SDLState() {
RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this));
RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this));
RegisterFactory<MotionDevice>("sdl", std::make_shared<SDLMotionFactory>(*this));
RegisterFactory<TouchDevice>("sdl", std::make_shared<SDLTouchFactory>(*this));
// If the frontend is going to manage the event loop, then we dont start one here
start_thread = !SDL_WasInit(SDL_INIT_GAMECONTROLLER);
if (start_thread && SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) {
@ -874,7 +936,7 @@ SDLState::~SDLState() {
UnregisterFactory<ButtonDevice>("sdl");
UnregisterFactory<AnalogDevice>("sdl");
UnregisterFactory<MotionDevice>("sdl");
UnregisterFactory<TouchDevice>("sdl");
CloseJoysticks();
SDL_DelEventWatch(&SDLEventWatcher, this);
@ -956,6 +1018,30 @@ protected:
SDLState& state;
};
class SDLTouchpadPoller final : public SDLPoller {
public:
explicit SDLTouchpadPoller(SDLState& state_) : SDLPoller(state_) {}
Common::ParamPackage GetNextInput() override {
SDL_Event event;
Common::ParamPackage params;
while (state.event_queue.Pop(event)) {
if (event.type != SDL_CONTROLLERTOUCHPADDOWN) {
continue;
}
switch (event.type) {
case SDL_CONTROLLERTOUCHPADDOWN:
auto joystick = state.GetSDLJoystickBySDLID(event.ctouchpad.which);
params.Set("engine", "sdl");
params.Set("touchpad", event.ctouchpad.touchpad);
params.Set("port", joystick->GetPort());
params.Set("guid", joystick->GetGUID());
}
}
return params;
}
};
class SDLButtonPoller final : public SDLPoller {
public:
explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {}
@ -1083,6 +1169,9 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
case InputCommon::Polling::DeviceType::Button:
pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
break;
case InputCommon::Polling::DeviceType::Touchpad:
pollers.emplace_back(std::make_unique<Polling::SDLTouchpadPoller>(*this));
break;
}
return pollers;

View File

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -24,6 +24,7 @@ class SDLGameController;
class SDLButtonFactory;
class SDLAnalogFactory;
class SDLMotionFactory;
class SDLTouchFactory;
class SDLState : public State {
public:
@ -62,6 +63,7 @@ private:
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
std::mutex joystick_map_mutex;
std::shared_ptr<SDLTouchFactory> touch_factory;
std::shared_ptr<SDLButtonFactory> button_factory;
std::shared_ptr<SDLAnalogFactory> analog_factory;
std::shared_ptr<SDLMotionFactory> motion_factory;