configure_input_player: Implement input exclusivity and persistence

With this, the "Input Devices" combobox should accurately reflect the input device being used and disallows inputs from other input devices unless the input device is set to "Any".
master
Morph 2020-09-17 12:00:29 +07:00
parent 9d4edd4e88
commit 75eaab2e0f
4 changed files with 205 additions and 138 deletions

@ -78,7 +78,7 @@ struct InputSubsystem::Impl {
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
std::vector<Common::ParamPackage> devices = { std::vector<Common::ParamPackage> devices = {
Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}}, Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
}; };
#ifdef HAVE_SDL2 #ifdef HAVE_SDL2
auto sdl_devices = sdl->GetInputDevices(); auto sdl_devices = sdl->GetInputDevices();
@ -96,7 +96,7 @@ struct InputSubsystem::Impl {
if (!params.Has("class") || params.Get("class", "") == "any") { if (!params.Has("class") || params.Get("class", "") == "any") {
return {}; return {};
} }
if (params.Get("class", "") == "key") { if (params.Get("class", "") == "keyboard") {
// TODO consider returning the SDL key codes for the default keybindings // TODO consider returning the SDL key codes for the default keybindings
return {}; return {};
} }
@ -116,7 +116,7 @@ struct InputSubsystem::Impl {
if (!params.Has("class") || params.Get("class", "") == "any") { if (!params.Has("class") || params.Get("class", "") == "any") {
return {}; return {};
} }
if (params.Get("class", "") == "key") { if (params.Get("class", "") == "keyboard") {
// TODO consider returning the SDL key codes for the default keybindings // TODO consider returning the SDL key codes for the default keybindings
return {}; return {};
} }

@ -242,6 +242,6 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) {
void ConfigureInput::UpdateAllInputDevices() { void ConfigureInput::UpdateAllInputDevices() {
for (const auto& player : player_controllers) { for (const auto& player : player_controllers) {
player->UpdateInputDevices(); player->UpdateInputDeviceCombobox();
} }
} }

@ -477,11 +477,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
UpdateMotionButtons(); UpdateMotionButtons();
}); });
connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this, connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
&ConfigureInputPlayer::UpdateMappingWithDefaults); &ConfigureInputPlayer::UpdateMappingWithDefaults);
ui->comboDevices->setCurrentIndex(-1);
ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
UpdateInputDevices();
connect(ui->buttonRefreshDevices, &QPushButton::clicked, connect(ui->buttonRefreshDevices, &QPushButton::clicked,
[this] { emit RefreshInputDevices(); }); [this] { emit RefreshInputDevices(); });
@ -492,14 +493,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
Common::ParamPackage params; Common::ParamPackage params;
if (input_subsystem->GetGCButtons()->IsPolling()) { if (input_subsystem->GetGCButtons()->IsPolling()) {
params = input_subsystem->GetGCButtons()->GetNextInput(); params = input_subsystem->GetGCButtons()->GetNextInput();
if (params.Has("engine")) { if (params.Has("engine") && IsInputAcceptable(params)) {
SetPollingResult(params, false); SetPollingResult(params, false);
return; return;
} }
} }
if (input_subsystem->GetGCAnalogs()->IsPolling()) { if (input_subsystem->GetGCAnalogs()->IsPolling()) {
params = input_subsystem->GetGCAnalogs()->GetNextInput(); params = input_subsystem->GetGCAnalogs()->GetNextInput();
if (params.Has("engine")) { if (params.Has("engine") && IsInputAcceptable(params)) {
SetPollingResult(params, false); SetPollingResult(params, false);
return; return;
} }
@ -513,7 +514,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
} }
for (auto& poller : device_pollers) { for (auto& poller : device_pollers) {
params = poller->GetNextInput(); params = poller->GetNextInput();
if (params.Has("engine")) { if (params.Has("engine") && IsInputAcceptable(params)) {
SetPollingResult(params, false); SetPollingResult(params, false);
return; return;
} }
@ -572,6 +573,14 @@ void ConfigureInputPlayer::ApplyConfiguration() {
UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected); UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected);
} }
void ConfigureInputPlayer::showEvent(QShowEvent* event) {
if (bottom_row == nullptr) {
return;
}
QWidget::showEvent(event);
ui->main->addWidget(bottom_row);
}
void ConfigureInputPlayer::changeEvent(QEvent* event) { void ConfigureInputPlayer::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) { if (event->type() == QEvent::LanguageChange) {
RetranslateUI(); RetranslateUI();
@ -604,6 +613,7 @@ void ConfigureInputPlayer::LoadConfiguration() {
} }
UpdateUI(); UpdateUI();
UpdateInputDeviceCombobox();
if (debug) { if (debug) {
return; return;
@ -615,11 +625,56 @@ void ConfigureInputPlayer::LoadConfiguration() {
(player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected)); (player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected));
} }
void ConfigureInputPlayer::UpdateInputDevices() { void ConfigureInputPlayer::ConnectPlayer(bool connected) {
input_devices = input_subsystem->GetInputDevices(); ui->groupConnectedController->setChecked(connected);
ui->comboDevices->clear(); }
for (auto device : input_devices) {
ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
// Skip input device persistence if "Input Devices" is set to "Any".
if (ui->comboDevices->currentIndex() == 0) {
UpdateInputDevices();
return;
}
// Find the first button that isn't empty.
const auto button_param =
std::find_if(buttons_param.begin(), buttons_param.end(),
[](const Common::ParamPackage param) { return param.Has("engine"); });
const bool buttons_empty = button_param == buttons_param.end();
const auto current_engine = button_param->Get("engine", "");
const auto current_guid = button_param->Get("guid", "");
const auto current_port = button_param->Get("port", "");
UpdateInputDevices();
if (buttons_empty) {
return;
}
const bool all_one_device =
std::all_of(buttons_param.begin(), buttons_param.end(),
[current_engine, current_guid, current_port](const Common::ParamPackage param) {
return !param.Has("engine") || (param.Get("engine", "") == current_engine &&
param.Get("guid", "") == current_guid &&
param.Get("port", "") == current_port);
});
if (all_one_device) {
const auto devices_it = std::find_if(
input_devices.begin(), input_devices.end(),
[current_engine, current_guid, current_port](const Common::ParamPackage param) {
return param.Get("class", "") == current_engine &&
param.Get("guid", "") == current_guid &&
param.Get("port", "") == current_port;
});
const int device_index =
devices_it != input_devices.end()
? static_cast<int>(std::distance(input_devices.begin(), devices_it))
: 0;
ui->comboDevices->setCurrentIndex(device_index);
} else {
ui->comboDevices->setCurrentIndex(0);
} }
} }
@ -648,7 +703,7 @@ void ConfigureInputPlayer::RestoreDefaults() {
} }
UpdateUI(); UpdateUI();
UpdateInputDevices(); UpdateInputDeviceCombobox();
ui->comboControllerType->setCurrentIndex(0); ui->comboControllerType->setCurrentIndex(0);
} }
@ -752,117 +807,12 @@ void ConfigureInputPlayer::UpdateUI() {
} }
} }
void ConfigureInputPlayer::UpdateMappingWithDefaults() { void ConfigureInputPlayer::UpdateInputDevices() {
if (ui->comboDevices->currentIndex() < 2) { input_devices = input_subsystem->GetInputDevices();
return; ui->comboDevices->clear();
for (auto device : input_devices) {
ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
} }
const auto& device = input_devices[ui->comboDevices->currentIndex()];
auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
for (std::size_t i = 0; i < buttons_param.size(); ++i) {
buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
}
for (std::size_t i = 0; i < analogs_param.size(); ++i) {
analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
}
UpdateUI();
}
void ConfigureInputPlayer::HandleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
button->setText(tr("Shake!"));
} else {
button->setText(tr("[waiting]"));
}
button->setFocus();
// The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
// controller, then they don't want keyboard/mouse input
want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
input_setter = new_input_setter;
device_pollers = input_subsystem->GetPollers(type);
for (auto& poller : device_pollers) {
poller->Start();
}
QWidget::grabMouse();
QWidget::grabKeyboard();
if (type == InputCommon::Polling::DeviceType::Button) {
input_subsystem->GetGCButtons()->BeginConfiguration();
} else {
input_subsystem->GetGCAnalogs()->BeginConfiguration();
}
if (type == InputCommon::Polling::DeviceType::Motion) {
input_subsystem->GetUDPMotions()->BeginConfiguration();
}
timeout_timer->start(2500); // Cancel after 2.5 seconds
poll_timer->start(50); // Check for new inputs every 50ms
}
void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
QWidget::releaseMouse();
QWidget::releaseKeyboard();
input_subsystem->GetGCButtons()->EndConfiguration();
input_subsystem->GetGCAnalogs()->EndConfiguration();
input_subsystem->GetUDPMotions()->EndConfiguration();
if (!abort) {
(*input_setter)(params);
}
UpdateUI();
input_setter = std::nullopt;
}
void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
if (!input_setter || !event) {
return;
}
if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
false);
} else {
// We don't want any mouse buttons, so don't stop polling
return;
}
SetPollingResult({}, true);
}
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event) {
return;
}
if (event->key() != Qt::Key_Escape) {
if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
return;
}
}
SetPollingResult({}, true);
} }
void ConfigureInputPlayer::UpdateControllerIcon() { void ConfigureInputPlayer::UpdateControllerIcon() {
@ -986,14 +936,128 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
} }
} }
void ConfigureInputPlayer::showEvent(QShowEvent* event) { void ConfigureInputPlayer::UpdateMappingWithDefaults() {
if (bottom_row == nullptr) { if (ui->comboDevices->currentIndex() < 2) {
return; return;
} }
QWidget::showEvent(event); const auto& device = input_devices[ui->comboDevices->currentIndex()];
ui->main->addWidget(bottom_row); auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
for (std::size_t i = 0; i < buttons_param.size(); ++i) {
buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
}
for (std::size_t i = 0; i < analogs_param.size(); ++i) {
analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
}
UpdateUI();
} }
void ConfigureInputPlayer::ConnectPlayer(bool connected) { void ConfigureInputPlayer::HandleClick(
ui->groupConnectedController->setChecked(connected); QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
button->setText(tr("Shake!"));
} else {
button->setText(tr("[waiting]"));
}
button->setFocus();
// The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
// controller, then they don't want keyboard/mouse input
want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
input_setter = new_input_setter;
device_pollers = input_subsystem->GetPollers(type);
for (auto& poller : device_pollers) {
poller->Start();
}
QWidget::grabMouse();
QWidget::grabKeyboard();
if (type == InputCommon::Polling::DeviceType::Button) {
input_subsystem->GetGCButtons()->BeginConfiguration();
} else {
input_subsystem->GetGCAnalogs()->BeginConfiguration();
}
if (type == InputCommon::Polling::DeviceType::Motion) {
input_subsystem->GetUDPMotions()->BeginConfiguration();
}
timeout_timer->start(2500); // Cancel after 2.5 seconds
poll_timer->start(50); // Check for new inputs every 50ms
}
void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
QWidget::releaseMouse();
QWidget::releaseKeyboard();
input_subsystem->GetGCButtons()->EndConfiguration();
input_subsystem->GetGCAnalogs()->EndConfiguration();
input_subsystem->GetUDPMotions()->EndConfiguration();
if (!abort) {
(*input_setter)(params);
}
UpdateUI();
UpdateInputDeviceCombobox();
input_setter = std::nullopt;
}
bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const {
if (ui->comboDevices->currentIndex() == 0) {
return true;
}
const auto current_input_device = input_devices[ui->comboDevices->currentIndex()];
return params.Get("engine", "") == current_input_device.Get("class", "") &&
params.Get("guid", "") == current_input_device.Get("guid", "") &&
params.Get("port", "") == current_input_device.Get("port", "");
}
void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
if (!input_setter || !event) {
return;
}
if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
false);
} else {
// We don't want any mouse buttons, so don't stop polling
return;
}
SetPollingResult({}, true);
}
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event) {
return;
}
if (event->key() != Qt::Key_Escape) {
if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
return;
}
}
SetPollingResult({}, true);
} }

@ -51,8 +51,11 @@ public:
/// Save all button configurations to settings file. /// Save all button configurations to settings file.
void ApplyConfiguration(); void ApplyConfiguration();
/// Set the connection state checkbox (used to sync state).
void ConnectPlayer(bool connected);
/// Update the input devices combobox. /// Update the input devices combobox.
void UpdateInputDevices(); void UpdateInputDeviceCombobox();
/// Restore all buttons to their default values. /// Restore all buttons to their default values.
void RestoreDefaults(); void RestoreDefaults();
@ -60,9 +63,6 @@ public:
/// Clear all input configuration. /// Clear all input configuration.
void ClearAll(); void ClearAll();
/// Set the connection state checkbox (used to sync state).
void ConnectPlayer(bool connected);
signals: signals:
/// Emitted when this controller is connected by the user. /// Emitted when this controller is connected by the user.
void Connected(bool connected); void Connected(bool connected);
@ -89,6 +89,9 @@ private:
/// Finish polling and configure input using the input_setter. /// Finish polling and configure input using the input_setter.
void SetPollingResult(const Common::ParamPackage& params, bool abort); void SetPollingResult(const Common::ParamPackage& params, bool abort);
/// Checks whether a given input can be accepted.
bool IsInputAcceptable(const Common::ParamPackage& params) const;
/// Handle mouse button press events. /// Handle mouse button press events.
void mousePressEvent(QMouseEvent* event) override; void mousePressEvent(QMouseEvent* event) override;
@ -98,8 +101,8 @@ private:
/// Update UI to reflect current configuration. /// Update UI to reflect current configuration.
void UpdateUI(); void UpdateUI();
/// Update the controller selection combobox /// Update the available input devices.
void UpdateControllerCombobox(); void UpdateInputDevices();
/// Update the current controller icon. /// Update the current controller icon.
void UpdateControllerIcon(); void UpdateControllerIcon();
@ -164,7 +167,7 @@ private:
bool want_keyboard_mouse = false; bool want_keyboard_mouse = false;
/// List of physical devices users can map with. If a SDL backed device is selected, then you /// List of physical devices users can map with. If a SDL backed device is selected, then you
/// can usue this device to get a default mapping. /// can use this device to get a default mapping.
std::vector<Common::ParamPackage> input_devices; std::vector<Common::ParamPackage> input_devices;
/// Bottom row is where console wide settings are held, and its "owned" by the parent /// Bottom row is where console wide settings are held, and its "owned" by the parent