diff --git a/src/citra_qt/applets/swkbd.cpp b/src/citra_qt/applets/swkbd.cpp index 7514524b3e..8862a92a48 100644 --- a/src/citra_qt/applets/swkbd.cpp +++ b/src/citra_qt/applets/swkbd.cpp @@ -109,14 +109,20 @@ void QtKeyboardDialog::HandleValidationError(Frontend::ValidationError error) { QtKeyboard::QtKeyboard(QWidget& parent_) : parent(parent_) {} -void QtKeyboard::Setup(const Frontend::KeyboardConfig& config) { - SoftwareKeyboard::Setup(config); +void QtKeyboard::Execute(const Frontend::KeyboardConfig& config) { + SoftwareKeyboard::Execute(config); if (this->config.button_config != Frontend::ButtonConfig::None) { ok_id = static_cast(this->config.button_config); } QMetaObject::invokeMethod(this, "OpenInputDialog", Qt::BlockingQueuedConnection); } +void QtKeyboard::ShowError(const std::string& error) { + QString message = QString::fromStdString(error); + QMetaObject::invokeMethod(this, "ShowErrorDialog", Qt::BlockingQueuedConnection, + Q_ARG(QString, message)); +} + void QtKeyboard::OpenInputDialog() { QtKeyboardDialog dialog(&parent, this); dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | @@ -127,3 +133,7 @@ void QtKeyboard::OpenInputDialog() { dialog.button); Finalize(dialog.text.toStdString(), dialog.button); } + +void QtKeyboard::ShowErrorDialog(QString message) { + QMessageBox::critical(&parent, tr("Software Keyboard"), message); +} diff --git a/src/citra_qt/applets/swkbd.h b/src/citra_qt/applets/swkbd.h index 5fcbd71127..34b6a186d4 100644 --- a/src/citra_qt/applets/swkbd.h +++ b/src/citra_qt/applets/swkbd.h @@ -48,10 +48,12 @@ class QtKeyboard final : public QObject, public Frontend::SoftwareKeyboard { public: explicit QtKeyboard(QWidget& parent); - void Setup(const Frontend::KeyboardConfig& config) override; + void Execute(const Frontend::KeyboardConfig& config) override; + void ShowError(const std::string& error) override; private: Q_INVOKABLE void OpenInputDialog(); + Q_INVOKABLE void ShowErrorDialog(QString message); /// Index of the buttons u8 ok_id; diff --git a/src/core/frontend/applets/swkbd.cpp b/src/core/frontend/applets/swkbd.cpp index d1513e20c8..12788d945e 100644 --- a/src/core/frontend/applets/swkbd.cpp +++ b/src/core/frontend/applets/swkbd.cpp @@ -39,10 +39,6 @@ ValidationError SoftwareKeyboard::ValidateFilters(const std::string& input) cons // TODO: check the profanity filter LOG_INFO(Frontend, "App requested swkbd profanity filter, but its not implemented."); } - if (config.filters.enable_callback) { - // TODO: check the callback - LOG_INFO(Frontend, "App requested a swkbd callback, but its not implemented."); - } return ValidationError::None; } @@ -132,11 +128,21 @@ ValidationError SoftwareKeyboard::Finalize(const std::string& text, u8 button) { return error; } data = {text, button}; + data_ready = true; return ValidationError::None; } -void DefaultKeyboard::Setup(const Frontend::KeyboardConfig& config) { - SoftwareKeyboard::Setup(config); +bool SoftwareKeyboard::DataReady() { + return data_ready; +} + +const KeyboardData& SoftwareKeyboard::ReceiveData() { + data_ready = false; + return data; +} + +void DefaultKeyboard::Execute(const Frontend::KeyboardConfig& config) { + SoftwareKeyboard::Execute(config); auto cfg = Service::CFG::GetModule(Core::System::GetInstance()); ASSERT_MSG(cfg, "CFG Module missing!"); @@ -157,4 +163,8 @@ void DefaultKeyboard::Setup(const Frontend::KeyboardConfig& config) { } } +void DefaultKeyboard::ShowError(const std::string& error) { + LOG_ERROR(Applet_SWKBD, "Default keyboard text is unaccepted! error: {}", error); +} + } // namespace Frontend diff --git a/src/core/frontend/applets/swkbd.h b/src/core/frontend/applets/swkbd.h index f678c0f48f..c08dbe1d16 100644 --- a/src/core/frontend/applets/swkbd.h +++ b/src/core/frontend/applets/swkbd.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -82,13 +83,27 @@ enum class ValidationError { class SoftwareKeyboard { public: - virtual void Setup(const KeyboardConfig& config) { - this->config = KeyboardConfig(config); + /** + * Executes the software keyboard, configured with the given parameters. + */ + virtual void Execute(const KeyboardConfig& config) { + this->config = config; } - const KeyboardData& ReceiveData() const { - return data; - } + /** + * Whether the result data is ready to be received. + */ + bool DataReady(); + + /** + * 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 @@ -118,11 +133,14 @@ public: protected: KeyboardConfig config; KeyboardData data; + + std::atomic_bool data_ready = false; }; class DefaultKeyboard final : public SoftwareKeyboard { public: - void Setup(const KeyboardConfig& config) override; + void Execute(const KeyboardConfig& config) override; + void ShowError(const std::string& error) override; }; } // namespace Frontend diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp index 4e4dd1b90c..8e3b916e15 100644 --- a/src/core/hle/applets/swkbd.cpp +++ b/src/core/hle/applets/swkbd.cpp @@ -21,38 +21,89 @@ namespace HLE::Applets { +/** + * Converts a UTF-16 text in a container to a UTF-8 std::string. + */ +template +inline std::string TextFromBuffer(const T& text) { + std::size_t text_size = text.size(); + const auto text_end = std::find(text.begin(), text.end(), u'\0'); + if (text_end != text.end()) + text_size = std::distance(text.begin(), text_end); + std::u16string buffer(text_size, 0); + std::memcpy(buffer.data(), text.data(), text_size * sizeof(u16)); + return Common::UTF16ToUTF8(buffer); +} + ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter const& parameter) { - if (parameter.signal != Service::APT::SignalType::Request) { + switch (parameter.signal) { + case Service::APT::SignalType::Request: { + // The LibAppJustStarted message contains a buffer with the size of the framebuffer shared + // memory. + // Create the SharedMemory that will hold the framebuffer data + Service::APT::CaptureBufferInfo capture_info; + ASSERT(sizeof(capture_info) == parameter.buffer.size()); + + memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info)); + + using Kernel::MemoryPermission; + // Create a SharedMemory that directly points to this heap block. + framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( + 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, + "SoftwareKeyboard Memory"); + + // Send the response message with the newly created SharedMemory + Service::APT::MessageParameter result; + result.signal = Service::APT::SignalType::Response; + result.buffer.clear(); + result.destination_id = Service::APT::AppletId::Application; + result.sender_id = id; + result.object = framebuffer_memory; + + SendParameter(result); + return RESULT_SUCCESS; + } + + case Service::APT::SignalType::Message: { + // Callback result + ASSERT_MSG(parameter.buffer.size() == sizeof(config), + "The size of the parameter (SoftwareKeyboardConfig) is wrong"); + + memcpy(&config, parameter.buffer.data(), parameter.buffer.size()); + + switch (config.callback_result) { + case SoftwareKeyboardCallbackResult::OK: + // Finish execution + Finalize(); + return RESULT_SUCCESS; + + case SoftwareKeyboardCallbackResult::Close: + // Let the frontend display error and quit + frontend_applet->ShowError(TextFromBuffer(config.callback_msg)); + config.return_code = SoftwareKeyboardResult::BannedInput; + config.text_offset = config.text_length = 0; + Finalize(); + return RESULT_SUCCESS; + + case SoftwareKeyboardCallbackResult::Continue: + // Let the frontend display error and get input again + // The input will be sent for validation again on next Update(). + frontend_applet->ShowError(TextFromBuffer(config.callback_msg)); + frontend_applet->Execute(ToFrontendConfig(config)); + return RESULT_SUCCESS; + + default: + UNREACHABLE(); + } + } + + default: { LOG_ERROR(Service_APT, "unsupported signal {}", static_cast(parameter.signal)); UNIMPLEMENTED(); // TODO(Subv): Find the right error code return ResultCode(-1); } - - // The LibAppJustStarted message contains a buffer with the size of the framebuffer shared - // memory. - // Create the SharedMemory that will hold the framebuffer data - Service::APT::CaptureBufferInfo capture_info; - ASSERT(sizeof(capture_info) == parameter.buffer.size()); - - memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info)); - - using Kernel::MemoryPermission; - // Create a SharedMemory that directly points to this heap block. - framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( - 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, - "SoftwareKeyboard Memory"); - - // Send the response message with the newly created SharedMemory - Service::APT::MessageParameter result; - result.signal = Service::APT::SignalType::Response; - result.buffer.clear(); - result.destination_id = Service::APT::AppletId::Application; - result.sender_id = id; - result.object = framebuffer_memory; - - SendParameter(result); - return RESULT_SUCCESS; + } } ResultCode SoftwareKeyboard::StartImpl(Service::APT::AppletStartupParameter const& parameter) { @@ -71,16 +122,18 @@ ResultCode SoftwareKeyboard::StartImpl(Service::APT::AppletStartupParameter cons frontend_applet = Core::System::GetInstance().GetSoftwareKeyboard(); ASSERT(frontend_applet); - KeyboardConfig frontend_config = ToFrontendConfig(config); - frontend_applet->Setup(frontend_config); + frontend_applet->Execute(ToFrontendConfig(config)); is_running = true; return RESULT_SUCCESS; } void SoftwareKeyboard::Update() { + if (!frontend_applet->DataReady()) + return; + using namespace Frontend; - KeyboardData data(frontend_applet->ReceiveData()); + const KeyboardData& data = frontend_applet->ReceiveData(); std::u16string text = Common::UTF8ToUTF16(data.text); memcpy(text_memory->GetPointer(), text.c_str(), text.length() * sizeof(char16_t)); switch (config.num_buttons_m1) { @@ -114,9 +167,20 @@ void SoftwareKeyboard::Update() { config.text_length = static_cast(text.size()); config.text_offset = 0; - // TODO(Subv): We're finalizing the applet immediately after it's started, - // but we should defer this call until after all the input has been collected. - Finalize(); + if (config.filter_flags & HLE::Applets::SoftwareKeyboardFilter::Callback) { + // Send the message to invoke callback + Service::APT::MessageParameter message; + message.buffer.resize(sizeof(SoftwareKeyboardConfig)); + std::memcpy(message.buffer.data(), &config, message.buffer.size()); + message.signal = Service::APT::SignalType::Message; + message.destination_id = Service::APT::AppletId::Application; + message.sender_id = id; + SendParameter(message); + } else { + // TODO(Subv): We're finalizing the applet immediately after it's started, + // but we should defer this call until after all the input has been collected. + Finalize(); + } } void SoftwareKeyboard::DrawScreenKeyboard() { @@ -147,14 +211,7 @@ Frontend::KeyboardConfig SoftwareKeyboard::ToFrontendConfig( frontend_config.multiline_mode = config.multiline; frontend_config.max_text_length = config.max_text_length; frontend_config.max_digits = config.max_digits; - - std::size_t text_size = config.hint_text.size(); - const auto text_end = std::find(config.hint_text.begin(), config.hint_text.end(), u'\0'); - if (text_end != config.hint_text.end()) - text_size = std::distance(config.hint_text.begin(), text_end); - std::u16string buffer(text_size, 0); - std::memcpy(buffer.data(), config.hint_text.data(), text_size * sizeof(u16)); - frontend_config.hint_text = Common::UTF16ToUTF8(buffer); + frontend_config.hint_text = TextFromBuffer(config.hint_text); frontend_config.has_custom_button_text = !std::all_of(config.button_text.begin(), config.button_text.end(), [](std::array x) { @@ -162,14 +219,7 @@ Frontend::KeyboardConfig SoftwareKeyboard::ToFrontendConfig( }); if (frontend_config.has_custom_button_text) { for (const auto& text : config.button_text) { - text_size = text.size(); - const auto text_end = std::find(text.begin(), text.end(), u'\0'); - if (text_end != text.end()) - text_size = std::distance(text.begin(), text_end); - - buffer.resize(text_size); - std::memcpy(buffer.data(), text.data(), text_size * sizeof(u16)); - frontend_config.button_text.push_back(Common::UTF16ToUTF8(buffer)); + frontend_config.button_text.push_back(TextFromBuffer(text)); } } frontend_config.filters.prevent_digit = diff --git a/src/core/hle/applets/swkbd.h b/src/core/hle/applets/swkbd.h index b63871b2fb..e754496c94 100644 --- a/src/core/hle/applets/swkbd.h +++ b/src/core/hle/applets/swkbd.h @@ -161,7 +161,7 @@ struct SoftwareKeyboardConfig { u32_le text_offset; ///< Offset in the SharedMemory where the output text starts u16_le text_length; ///< Length in characters of the output text - s32_le callback_result; + enum_le callback_result; std::array callback_msg; bool skip_at_check; INSERT_PADDING_BYTES(0xAB);