citra/src/core/frontend/applets/swkbd.h
Pengfei Zhu 60669a7dd8
swkbd: Fix a bug where clicking Cancel hangs the game (#5294)
* swkbd: Fix a bug where clicking Cancel hangs the game

The text is validated in `Finalize`. If the validation fails, an error is returned and the applet is not actually finalized. This can result in hangs.

This is usually not a problem as the frontend is expected to validate the text passed to `Finalize`. However, when the user clicked on `Cancel`, the text is ignored and the frontend won't do any validation. Therefore, we should skip the validation here as well.

Also fixed a potential data race. All these functions should now be called on the same thread

* Address review comments

Renamed the fields
Remove close button
2020-05-04 11:31:17 +02:00

146 lines
4.9 KiB
C++

// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <unordered_map>
#include <utility>
#include <vector>
#include "common/assert.h"
namespace Frontend {
enum class AcceptedInput {
Anything, /// All inputs are accepted.
NotEmpty, /// Empty inputs are not accepted.
NotEmptyAndNotBlank, /// Empty or blank inputs (consisting solely of whitespace) are not
/// accepted.
NotBlank, /// Blank inputs (consisting solely of whitespace) are not accepted, but empty
/// inputs are.
FixedLength, /// The input must have a fixed length (specified by maxTextLength in
/// swkbdInit).
};
enum class ButtonConfig {
Single, /// Ok button
Dual, /// Cancel | Ok buttons
Triple, /// Cancel | I Forgot | Ok buttons
None, /// No button (returned by swkbdInputText in special cases)
};
/// Default English button text mappings. Frontends may need to copy this to internationalize it.
constexpr char SWKBD_BUTTON_OKAY[] = "Ok";
constexpr char SWKBD_BUTTON_CANCEL[] = "Cancel";
constexpr char SWKBD_BUTTON_FORGOT[] = "I Forgot";
/// Configuration thats relevent to frontend implementation of applets. Anything missing that we
/// later learn is needed can be added here and filled in by the backend HLE applet
struct KeyboardConfig {
ButtonConfig button_config;
AcceptedInput accept_mode; /// What kinds of input are accepted (blank/empty/fixed width)
bool multiline_mode; /// True if the keyboard accepts multiple lines of input
u16 max_text_length; /// Maximum number of letters allowed if its a text input
u16 max_digits; /// Maximum number of numbers allowed if its a number input
std::string hint_text; /// Displayed in the field as a hint before
bool has_custom_button_text; /// If true, use the button_text instead
std::vector<std::string> button_text; /// Contains the button text that the caller provides
struct Filters {
bool prevent_digit; /// Limit maximum digit count to max_digits
bool prevent_at; /// Disallow the use of the @ sign.
bool prevent_percent; /// Disallow the use of the % sign.
bool prevent_backslash; /// Disallow the use of the \ sign.
bool prevent_profanity; /// Disallow profanity using Nintendo's profanity filter.
bool enable_callback; /// Use a callback in order to check the input.
};
Filters filters;
};
struct KeyboardData {
std::string text;
u8 button{};
};
enum class ValidationError {
None,
// Button Selection
ButtonOutOfRange,
// Configured Filters
MaxDigitsExceeded,
AtSignNotAllowed,
PercentNotAllowed,
BackslashNotAllowed,
ProfanityNotAllowed,
CallbackFailed,
// Allowed Input Type
FixedLengthRequired,
MaxLengthExceeded,
BlankInputNotAllowed,
EmptyInputNotAllowed,
};
class SoftwareKeyboard {
public:
virtual ~SoftwareKeyboard() = default;
/**
* Executes the software keyboard, configured with the given parameters.
*/
virtual void Execute(const KeyboardConfig& config) {
this->config = config;
}
/**
* Whether the result data is ready to be received.
*/
bool DataReady() const;
/**
* Receives the current result data stored in the applet, and clears the ready state.
*/
const KeyboardData& ReceiveData();
/**
* Shows an error text returned by the callback.
*/
virtual void ShowError(const std::string& error) = 0;
/**
* Validates if the provided string breaks any of the filter rules. This is meant to be called
* whenever the user input changes to check to see if the new input is valid. Frontends can
* decide if they want to check the input continuously or once before submission
*/
ValidationError ValidateFilters(const std::string& input) const;
/**
* Validates the the provided string doesn't break any extra rules like "input must not be
* empty". This will be called by Finalize but can be called earlier if the frontend needs
*/
ValidationError ValidateInput(const std::string& input) const;
/**
* Verifies that the selected button is valid. This should be used as the last check before
* closing.
*/
ValidationError ValidateButton(u8 button) const;
/**
* Runs all validation phases. If successful, stores the data so that the HLE applet in core can
* send this to the calling application
*/
ValidationError Finalize(const std::string& text, u8 button);
protected:
KeyboardConfig config;
KeyboardData data;
bool data_ready = false;
};
class DefaultKeyboard final : public SoftwareKeyboard {
public:
void Execute(const KeyboardConfig& config) override;
void ShowError(const std::string& error) override;
};
} // namespace Frontend