Files
shadPS4/src/qt_gui/hotkeys.cpp
2025-08-16 19:45:54 +03:00

395 lines
12 KiB
C++

// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include <QKeyEvent>
#include <QtConcurrent>
#include <SDL3/SDL.h>
#include "common/config.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "hotkeys.h"
#include "input/input_handler.h"
#include "ui_hotkeys.h"
hotkeys::hotkeys(bool isGameRunning, QWidget* parent)
: QDialog(parent), GameRunning(isGameRunning), ui(new Ui::hotkeys) {
ui->setupUi(this);
installEventFilter(this);
if (!GameRunning) {
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
SDL_InitSubSystem(SDL_INIT_EVENTS);
} else {
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
}
LoadHotkeys();
CheckGamePad();
ButtonsList = {
ui->fpsButtonPad,
ui->quitButtonPad,
ui->fullscreenButtonPad,
ui->pauseButtonPad,
};
ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save"));
ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply"));
ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) {
if (button == ui->buttonBox->button(QDialogButtonBox::Save)) {
SaveHotkeys(true);
} else if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) {
SaveHotkeys(false);
} else if (button == ui->buttonBox->button(QDialogButtonBox::Cancel)) {
QWidget::close();
}
});
for (auto& button : ButtonsList) {
connect(button, &QPushButton::clicked, this,
[this, &button]() { StartTimer(button, true); });
}
connect(this, &hotkeys::PushGamepadEvent, this, [this]() { CheckMapping(MappingButton); });
SdlEventWrapper::Wrapper::wrapperActive = true;
QObject::connect(SdlEventWrapper::Wrapper::GetInstance(), &SdlEventWrapper::Wrapper::SDLEvent,
this, &hotkeys::processSDLEvents);
if (!GameRunning) {
Polling = QtConcurrent::run(&hotkeys::pollSDLEvents, this);
}
}
void hotkeys::DisableMappingButtons() {
for (const auto& i : ButtonsList) {
i->setEnabled(false);
}
}
void hotkeys::EnableMappingButtons() {
for (const auto& i : ButtonsList) {
i->setEnabled(true);
}
}
void hotkeys::SaveHotkeys(bool CloseOnSave) {
const auto hotkey_file = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "hotkeys.ini";
if (!std::filesystem::exists(hotkey_file)) {
Input::createHotkeyFile(hotkey_file);
}
QString controllerFullscreenString, controllerPauseString, controllerFpsString,
controllerQuitString = "";
std::ifstream file(hotkey_file);
int lineCount = 0;
std::string line = "";
std::vector<std::string> lines;
while (std::getline(file, line)) {
lineCount++;
std::size_t equal_pos = line.find('=');
if (equal_pos == std::string::npos) {
lines.push_back(line);
continue;
}
if (line.contains("controllerFullscreen")) {
line = "controllerFullscreen = " + ui->fullscreenButtonPad->text().toStdString();
} else if (line.contains("controllerQuit")) {
line = "controllerQuit = " + ui->quitButtonPad->text().toStdString();
} else if (line.contains("controllerFps")) {
line = "controllerFps = " + ui->fpsButtonPad->text().toStdString();
} else if (line.contains("controllerPause")) {
line = "controllerPause = " + ui->pauseButtonPad->text().toStdString();
}
lines.push_back(line);
}
file.close();
std::ofstream output_file(hotkey_file);
for (auto const& line : lines) {
output_file << line << '\n';
}
output_file.close();
Input::LoadHotkeyInputs();
if (CloseOnSave)
QWidget::close();
}
void hotkeys::LoadHotkeys() {
const auto hotkey_file = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "hotkeys.ini";
if (!std::filesystem::exists(hotkey_file)) {
Input::createHotkeyFile(hotkey_file);
}
QString controllerFullscreenString, controllerPauseString, controllerFpsString,
controllerQuitString = "";
std::ifstream file(hotkey_file);
int lineCount = 0;
std::string line = "";
while (std::getline(file, line)) {
lineCount++;
std::size_t equal_pos = line.find('=');
if (equal_pos == std::string::npos)
continue;
if (line.contains("controllerFullscreen")) {
controllerFullscreenString = QString::fromStdString(line.substr(equal_pos + 2));
} else if (line.contains("controllerQuit")) {
controllerQuitString = QString::fromStdString(line.substr(equal_pos + 2));
} else if (line.contains("controllerFps")) {
controllerFpsString = QString::fromStdString(line.substr(equal_pos + 2));
} else if (line.contains("controllerPause")) {
controllerPauseString = QString::fromStdString(line.substr(equal_pos + 2));
}
}
file.close();
ui->fpsButtonPad->setText(controllerFpsString);
ui->quitButtonPad->setText(controllerQuitString);
ui->fullscreenButtonPad->setText(controllerFullscreenString);
ui->pauseButtonPad->setText(controllerPauseString);
}
void hotkeys::CheckGamePad() {
if (h_gamepad) {
SDL_CloseGamepad(h_gamepad);
h_gamepad = nullptr;
}
h_gamepads = SDL_GetGamepads(&gamepad_count);
if (!h_gamepads) {
LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError());
}
int defaultIndex = GamepadSelect::GetIndexfromGUID(h_gamepads, gamepad_count,
Config::getDefaultControllerID());
int activeIndex = GamepadSelect::GetIndexfromGUID(h_gamepads, gamepad_count,
GamepadSelect::GetSelectedGamepad());
if (!GameRunning) {
if (activeIndex != -1) {
h_gamepad = SDL_OpenGamepad(h_gamepads[activeIndex]);
} else if (defaultIndex != -1) {
h_gamepad = SDL_OpenGamepad(h_gamepads[defaultIndex]);
} else {
LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count);
h_gamepad = SDL_OpenGamepad(h_gamepads[0]);
}
if (!h_gamepad) {
LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError());
}
}
}
void hotkeys::StartTimer(QPushButton*& button, bool isButton) {
MappingTimer = 3;
EnableButtonMapping = true;
MappingCompleted = false;
L2Pressed = false;
R2Pressed = false;
mapping = button->text();
DisableMappingButtons();
button->setText(tr("Press a button") + " [" + QString::number(MappingTimer) + "]");
timer = new QTimer(this);
MappingButton = button;
timer->start(1000);
connect(timer, &QTimer::timeout, this, [this]() { CheckMapping(MappingButton); });
}
void hotkeys::CheckMapping(QPushButton*& button) {
MappingTimer -= 1;
button->setText(tr("Press a button") + " [" + QString::number(MappingTimer) + "]");
if (pressedButtons.size() > 0) {
QStringList keyStrings;
for (const QString& buttonAction : pressedButtons) {
keyStrings << buttonAction;
}
QString combo = keyStrings.join(",");
SetMapping(combo);
MappingButton->setText(combo);
pressedButtons.clear();
}
if (MappingCompleted || MappingTimer <= 0) {
button->setText(mapping);
EnableButtonMapping = false;
EnableMappingButtons();
timer->stop();
}
}
void hotkeys::SetMapping(QString input) {
mapping = input;
MappingCompleted = true;
}
// use QT events instead of SDL to override default event closing the window with escape
bool hotkeys::eventFilter(QObject* obj, QEvent* event) {
if (event->type() == QEvent::KeyPress && EnableButtonMapping) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Escape) {
SetMapping("unmapped");
PushGamepadEvent();
return true;
}
}
return QDialog::eventFilter(obj, event);
}
void hotkeys::processSDLEvents(int Type, int Input, int Value) {
if (EnableButtonMapping) {
if (pressedButtons.size() >= 3) {
return;
}
if (Type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
switch (Input) {
case SDL_GAMEPAD_BUTTON_SOUTH:
pressedButtons.insert(5, "cross");
break;
case SDL_GAMEPAD_BUTTON_EAST:
pressedButtons.insert(6, "circle");
break;
case SDL_GAMEPAD_BUTTON_NORTH:
pressedButtons.insert(7, "triangle");
break;
case SDL_GAMEPAD_BUTTON_WEST:
pressedButtons.insert(8, "square");
break;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
pressedButtons.insert(3, "l1");
break;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
pressedButtons.insert(4, "r1");
break;
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
pressedButtons.insert(9, "l3");
break;
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
pressedButtons.insert(10, "r3");
break;
case SDL_GAMEPAD_BUTTON_DPAD_UP:
pressedButtons.insert(13, "pad_up");
break;
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
pressedButtons.insert(14, "pad_down");
break;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
pressedButtons.insert(15, "pad_left");
break;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
pressedButtons.insert(16, "pad_right");
break;
case SDL_GAMEPAD_BUTTON_BACK:
pressedButtons.insert(11, "back");
break;
case SDL_GAMEPAD_BUTTON_START:
pressedButtons.insert(12, "options");
break;
default:
break;
}
}
if (Type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
// SDL trigger axis values range from 0 to 32000, set mapping on half movement
// Set zone for trigger release signal arbitrarily at 5000
switch (Input) {
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
if (Value > 16000) {
pressedButtons.insert(1, "l2");
L2Pressed = true;
} else if (Value < 5000) {
if (L2Pressed && !R2Pressed)
emit PushGamepadEvent();
}
break;
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
if (Value > 16000) {
pressedButtons.insert(2, "r2");
R2Pressed = true;
} else if (Value < 5000) {
if (R2Pressed && !L2Pressed)
emit PushGamepadEvent();
}
break;
default:
break;
}
}
if (Type == SDL_EVENT_GAMEPAD_BUTTON_UP)
emit PushGamepadEvent();
}
if (Type == SDL_EVENT_GAMEPAD_ADDED || SDL_EVENT_GAMEPAD_REMOVED) {
CheckGamePad();
}
}
void hotkeys::pollSDLEvents() {
SDL_Event event;
while (SdlEventWrapper::Wrapper::wrapperActive) {
if (!SDL_WaitEvent(&event)) {
return;
}
if (event.type == SDL_EVENT_QUIT) {
return;
}
SdlEventWrapper::Wrapper::GetInstance()->Wrapper::ProcessEvent(&event);
}
}
void hotkeys::Cleanup() {
SdlEventWrapper::Wrapper::wrapperActive = false;
if (h_gamepad) {
SDL_CloseGamepad(h_gamepad);
h_gamepad = nullptr;
}
SDL_free(h_gamepads);
if (!GameRunning) {
SDL_Event quitLoop{};
quitLoop.type = SDL_EVENT_QUIT;
SDL_PushEvent(&quitLoop);
Polling.waitForFinished();
SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
SDL_QuitSubSystem(SDL_INIT_EVENTS);
SDL_Quit();
} else {
if (!Config::getBackgroundControllerInput()) {
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0");
}
}
}
hotkeys::~hotkeys() {}