From a64f2c8e446ffd46dbea77c7510946cdeb2149d4 Mon Sep 17 00:00:00 2001 From: citra-swkbd <32505789+citra-swkbd@users.noreply.github.com> Date: Wed, 4 Oct 2017 00:56:58 -0500 Subject: [PATCH] Implement Software Keyboard --- src/core/hle/applets/swkbd.cpp | 203 +++++++++++++++++++++++++++++++-- 1 file changed, 193 insertions(+), 10 deletions(-) diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp index 0bc471a3a..3c39aee0a 100644 --- a/src/core/hle/applets/swkbd.cpp +++ b/src/core/hle/applets/swkbd.cpp @@ -2,7 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include #include +#include +#include #include #include "common/assert.h" #include "common/logging/log.h" @@ -14,12 +18,17 @@ #include "core/hle/service/gsp_gpu.h" #include "core/hle/service/hid/hid.h" #include "core/memory.h" +#include "video_core/video_core.h" //////////////////////////////////////////////////////////////////////////////////////////////////// namespace HLE { namespace Applets { +const static std::array swkbd_default_1_button = {"Ok"}; +const static std::array swkbd_default_2_button = {"Cancel", "Ok"}; +const static std::array swkbd_default_3_button = {"Cancel", "I Forgot", "Ok"}; + ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter const& parameter) { if (parameter.signal != static_cast(Service::APT::SignalType::Request)) { LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal); @@ -41,8 +50,8 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con heap_memory = std::make_shared>(capture_info.size); // Create a SharedMemory that directly points to this heap block. framebuffer_memory = Kernel::SharedMemory::CreateForApplet( - heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, - "SoftwareKeyboard Memory"); + heap_memory, 0, static_cast(heap_memory->size()), MemoryPermission::ReadWrite, + MemoryPermission::ReadWrite, "SoftwareKeyboard Memory"); // Send the response message with the newly created SharedMemory Service::APT::MessageParameter result; @@ -73,18 +82,192 @@ ResultCode SoftwareKeyboard::StartImpl(Service::APT::AppletStartupParameter cons return RESULT_SUCCESS; } +static bool ValidateFilters(const u32 filters, const std::string& input) { + bool valid = true; + bool local_filter = true; + if ((filters & SWKBDFILTER_DIGITS) == SWKBDFILTER_DIGITS) { + valid &= local_filter = + std::all_of(input.begin(), input.end(), [](const char c) { return !std::isdigit(c); }); + if (!local_filter) { + std::cout << "Input must not contain any digits" << std::endl; + } + } + if ((filters & SWKBDFILTER_AT) == SWKBDFILTER_AT) { + valid &= local_filter = input.find("@") == std::string::npos; + if (!local_filter) { + std::cout << "Input must not contain the @ symbol" << std::endl; + } + } + if ((filters & SWKBDFILTER_PERCENT) == SWKBDFILTER_PERCENT) { + valid &= local_filter = input.find("%") == std::string::npos; + if (!local_filter) { + std::cout << "Input must not contain the % symbol" << std::endl; + } + } + if ((filters & SWKBDFILTER_BACKSLASH) == SWKBDFILTER_BACKSLASH) { + valid &= local_filter = input.find("\\") == std::string::npos; + if (!local_filter) { + std::cout << "Input must not contain the \\ symbol" << std::endl; + } + } + if ((filters & SWKBDFILTER_PROFANITY) == SWKBDFILTER_PROFANITY) { + // TODO: check the profanity filter + LOG_WARNING(Service_APT, "App requested profanity filter, but its not implemented."); + } + if ((filters & SWKBDFILTER_CALLBACK) == SWKBDFILTER_CALLBACK) { + // TODO: check the callback + LOG_WARNING(Service_APT, "App requested a callback check, but its not implemented."); + } + return valid; +} + +static bool ValidateInput(const SoftwareKeyboardConfig& config, const std::string input) { + // TODO(jroweboy): Is max_text_length inclusive or exclusive? + if (input.size() > config.max_text_length) { + std::cout << Common::StringFromFormat("Input is longer than the maximum length. Max: %u", + config.max_text_length) + << std::endl; + return false; + } + // return early if the text is filtered + if (config.filter_flags && !ValidateFilters(config.filter_flags, input)) { + return false; + } + + bool valid; + switch (config.valid_input) { + case SwkbdValidInput::FIXEDLEN: + valid = input.size() == config.max_text_length; + if (!valid) { + std::cout << Common::StringFromFormat("Input must be exactly %u characters.", + config.max_text_length) + << std::endl; + } + break; + case SwkbdValidInput::NOTEMPTY_NOTBLANK: + case SwkbdValidInput::NOTBLANK: + valid = + std::any_of(input.begin(), input.end(), [](const char c) { return !std::isspace(c); }); + if (!valid) { + std::cout << "Input must not be blank." << std::endl; + } + break; + case SwkbdValidInput::NOTEMPTY: + valid = input.empty(); + if (!valid) { + std::cout << "Input must not be empty." << std::endl; + } + break; + case SwkbdValidInput::ANYTHING: + valid = true; + break; + default: + // TODO(jroweboy): What does hardware do in this case? + LOG_CRITICAL(Service_APT, "Application requested unknown validation method. Method: %u", + static_cast(config.valid_input)); + UNREACHABLE(); + } + + return valid; +} + +static bool ValidateButton(u32 num_buttons, const std::string& input) { + // check that the input is a valid number + bool valid = false; + try { + u32 num = std::stoul(input); + valid = num <= num_buttons; + if (!valid) { + std::cout << Common::StringFromFormat("Please choose a number between 0 and %u", + num_buttons) + << std::endl; + } + } catch (const std::invalid_argument& e) { + (void)e; + std::cout << "Unable to parse input as a number." << std::endl; + } catch (const std::out_of_range& e) { + (void)e; + std::cout << "Input number is not valid." << std::endl; + } + return valid; +} + void SoftwareKeyboard::Update() { // TODO(Subv): Handle input using the touch events from the HID module + // Until then, just read input from the terminal + std::string input; + std::cout << "SOFTWARE KEYBOARD" << std::endl; + // Display hint text + std::u16string hint(reinterpret_cast(config.hint_text)); + if (!hint.empty()) { + std::cout << "Hint text: " << Common::UTF16ToUTF8(hint) << std::endl; + } + do { + std::cout << "Enter the text you will send to the application:" << std::endl; + std::getline(std::cin, input); + } while (!ValidateInput(config, input)); - // TODO(Subv): Remove this hardcoded text - std::u16string text = Common::UTF8ToUTF16("Citra"); - memcpy(text_memory->GetPointer(), text.c_str(), text.length() * sizeof(char16_t)); + std::string option_text; + // convert all of the button texts into something we can output + // num_buttons is in the range of 0-2 so use <= instead of < + u32 num_buttons = static_cast(config.num_buttons_m1); + for (u32 i = 0; i <= num_buttons; ++i) { + std::string final_text; + // apps are allowed to set custom text to display on the button + std::u16string custom_button_text(reinterpret_cast(config.button_text[i])); + if (custom_button_text.empty()) { + // Use the system default text for that button + if (num_buttons == 0) { + final_text = swkbd_default_1_button[i]; + } else if (num_buttons == 1) { + final_text = swkbd_default_2_button[i]; + } else { + final_text = swkbd_default_3_button[i]; + } + } else { + final_text = Common::UTF16ToUTF8(custom_button_text); + } + option_text += Common::StringFromFormat("\t(%u) %s\t", i, final_text); + } + std::string option; + do { + std::cout << "\nPlease type the number of the button you will press: \n" + << option_text << std::endl; + std::getline(std::cin, option); + } while (!ValidateButton(static_cast(config.num_buttons_m1), option)); - // TODO(Subv): Ask for input and write it to the shared memory - // TODO(Subv): Find out what are the possible values for the return code, - // some games seem to check for a hardcoded 2 - config.return_code = 2; - config.text_length = 6; + s32 button = std::stol(option); + switch (config.num_buttons_m1) { + case SwkbdButtonConfig::SINGLE_BUTTON: + config.return_code = SwkbdResult::D0_CLICK; + break; + case SwkbdButtonConfig::DUAL_BUTTON: + if (button == 0) { + config.return_code = SwkbdResult::D1_CLICK0; + } else { + config.return_code = SwkbdResult::D1_CLICK1; + } + break; + case SwkbdButtonConfig::TRIPLE_BUTTON: + if (button == 0) { + config.return_code = SwkbdResult::D2_CLICK0; + } else if (button == 1) { + config.return_code = SwkbdResult::D2_CLICK1; + } else { + config.return_code = SwkbdResult::D2_CLICK2; + } + break; + default: + // TODO: what does the hardware do + LOG_WARNING(Service_APT, "Unknown option for num_buttons_m1: %u", + static_cast(config.num_buttons_m1)); + config.return_code = SwkbdResult::NONE; + break; + } + + std::u16string utf16_input = Common::UTF8ToUTF16(input); + memcpy(text_memory->GetPointer(), utf16_input.c_str(), utf16_input.length() * sizeof(char16_t)); + config.text_length = static_cast(utf16_input.size()); config.text_offset = 0; // TODO(Subv): We're finalizing the applet immediately after it's started,