Implement Software Keyboard

This commit is contained in:
citra-swkbd 2017-10-04 00:56:58 -05:00 committed by GitHub
parent 15c7641bef
commit a64f2c8e44

View File

@ -2,7 +2,11 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <algorithm>
#include <cctype>
#include <cstring> #include <cstring>
#include <functional>
#include <iostream>
#include <string> #include <string>
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@ -14,12 +18,17 @@
#include "core/hle/service/gsp_gpu.h" #include "core/hle/service/gsp_gpu.h"
#include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid.h"
#include "core/memory.h" #include "core/memory.h"
#include "video_core/video_core.h"
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
namespace HLE { namespace HLE {
namespace Applets { namespace Applets {
const static std::array<std::string, 1> swkbd_default_1_button = {"Ok"};
const static std::array<std::string, 2> swkbd_default_2_button = {"Cancel", "Ok"};
const static std::array<std::string, 3> swkbd_default_3_button = {"Cancel", "I Forgot", "Ok"};
ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter const& parameter) { ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter const& parameter) {
if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) { if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) {
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal); 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<std::vector<u8>>(capture_info.size); heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
// Create a SharedMemory that directly points to this heap block. // Create a SharedMemory that directly points to this heap block.
framebuffer_memory = Kernel::SharedMemory::CreateForApplet( framebuffer_memory = Kernel::SharedMemory::CreateForApplet(
heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, heap_memory, 0, static_cast<u32>(heap_memory->size()), MemoryPermission::ReadWrite,
"SoftwareKeyboard Memory"); MemoryPermission::ReadWrite, "SoftwareKeyboard Memory");
// Send the response message with the newly created SharedMemory // Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result; Service::APT::MessageParameter result;
@ -73,18 +82,192 @@ ResultCode SoftwareKeyboard::StartImpl(Service::APT::AppletStartupParameter cons
return RESULT_SUCCESS; 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<u32>(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() { void SoftwareKeyboard::Update() {
// TODO(Subv): Handle input using the touch events from the HID module // 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<char16_t*>(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::string option_text;
std::u16string text = Common::UTF8ToUTF16("Citra"); // convert all of the button texts into something we can output
memcpy(text_memory->GetPointer(), text.c_str(), text.length() * sizeof(char16_t)); // num_buttons is in the range of 0-2 so use <= instead of <
u32 num_buttons = static_cast<u32>(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<char16_t*>(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<u32>(config.num_buttons_m1), option));
// TODO(Subv): Ask for input and write it to the shared memory s32 button = std::stol(option);
// TODO(Subv): Find out what are the possible values for the return code, switch (config.num_buttons_m1) {
// some games seem to check for a hardcoded 2 case SwkbdButtonConfig::SINGLE_BUTTON:
config.return_code = 2; config.return_code = SwkbdResult::D0_CLICK;
config.text_length = 6; 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<u32>(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<u16>(utf16_input.size());
config.text_offset = 0; config.text_offset = 0;
// TODO(Subv): We're finalizing the applet immediately after it's started, // TODO(Subv): We're finalizing the applet immediately after it's started,