diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp index 059297fbc..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" @@ -21,6 +25,10 @@ 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); @@ -42,7 +50,7 @@ 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, heap_memory->size(), MemoryPermission::ReadWrite, + heap_memory, 0, static_cast(heap_memory->size()), MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, "SoftwareKeyboard Memory"); // Send the response message with the newly created SharedMemory @@ -74,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, diff --git a/src/core/hle/applets/swkbd.h b/src/core/hle/applets/swkbd.h index cc92a8f19..5c7311e09 100644 --- a/src/core/hle/applets/swkbd.h +++ b/src/core/hle/applets/swkbd.h @@ -15,33 +15,153 @@ namespace HLE { namespace Applets { +/// Maximum number of buttons that can be in the keyboard. +#define SWKBD_MAX_BUTTON 3 +/// Maximum button text length, in UTF-16 code units. +#define SWKBD_MAX_BUTTON_TEXT_LEN 16 +/// Maximum hint text length, in UTF-16 code units. +#define SWKBD_MAX_HINT_TEXT_LEN 64 +/// Maximum filter callback error message length, in UTF-16 code units. +#define SWKBD_MAX_CALLBACK_MSG_LEN 256 + +/// Keyboard types +enum class SwkbdType : u32 { + NORMAL = 0, ///< Normal keyboard with several pages (QWERTY/accents/symbol/mobile) + QWERTY, ///< QWERTY keyboard only. + NUMPAD, ///< Number pad. + WESTERN, ///< On JPN systems, a text keyboard without Japanese input capabilities, + /// otherwise same as SWKBD_TYPE_NORMAL. +}; + +/// Keyboard dialog buttons. +enum class SwkbdButtonConfig : u32 { + SINGLE_BUTTON = 0, ///< Ok button + DUAL_BUTTON, ///< Cancel | Ok buttons + TRIPLE_BUTTON, ///< Cancel | I Forgot | Ok buttons + NO_BUTTON, ///< No button (returned by swkbdInputText in special cases) +}; + +/// Accepted input types. +enum class SwkbdValidInput : u32 { + ANYTHING = 0, ///< All inputs are accepted. + NOTEMPTY, ///< Empty inputs are not accepted. + NOTEMPTY_NOTBLANK, ///< 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. + FIXEDLEN, ///< The input must have a fixed length (specified by maxTextLength in + /// swkbdInit). +}; + +/// Keyboard password modes. +enum class SwkbdPasswordMode : u32 { + NONE = 0, ///< Characters are not concealed. + HIDE, ///< Characters are concealed immediately. + HIDE_DELAY, ///< Characters are concealed a second after they've been typed. +}; + +/// Keyboard input filtering flags. +enum SwkbdFilter { + SWKBDFILTER_DIGITS = + 1, ///< Disallow the use of more than a certain number of digits (0 or more) + SWKBDFILTER_AT = 1 << 1, ///< Disallow the use of the @ sign. + SWKBDFILTER_PERCENT = 1 << 2, ///< Disallow the use of the % sign. + SWKBDFILTER_BACKSLASH = 1 << 3, ///< Disallow the use of the \ sign. + SWKBDFILTER_PROFANITY = 1 << 4, ///< Disallow profanity using Nintendo's profanity filter. + SWKBDFILTER_CALLBACK = 1 << 5, ///< Use a callback in order to check the input. +}; + +/// Keyboard features. +enum SwkbdFeatures { + SWKBDFEATURES_PARENTAL = 1, ///< Parental PIN mode. + SWKBDFEATURES_DARKEN_TOP_SCREEN = 1 << 1, ///< Darken the top screen when the keyboard is shown. + SWKBDFEATURES_PREDICTIVE_INPUT = + 1 << 2, ///< Enable predictive input (necessary for Kanji input in JPN systems). + SWKBDFEATURES_MULTILINE = 1 << 3, ///< Enable multiline input. + SWKBDFEATURES_FIXED_WIDTH = 1 << 4, ///< Enable fixed-width mode. + SWKBDFEATURES_ALLOW_HOME = 1 << 5, ///< Allow the usage of the HOME button. + SWKBDFEATURES_ALLOW_RESET = 1 << 6, ///< Allow the usage of a software-reset combination. + SWKBDFEATURES_ALLOW_POWER = 1 << 7, ///< Allow the usage of the POWER button. + SWKBDFEATURES_DEFAULT_QWERTY = 1 + << 9, ///< Default to the QWERTY page when the keyboard is shown. +}; + +/// Keyboard filter callback return values. +enum class SwkbdCallbackResult : u32 { + OK = 0, ///< Specifies that the input is valid. + CLOSE, ///< Displays an error message, then closes the keyboard. + CONTINUE, ///< Displays an error message and continues displaying the keyboard. +}; + +/// Keyboard return values. +enum class SwkbdResult : s32 { + NONE = -1, ///< Dummy/unused. + INVALID_INPUT = -2, ///< Invalid parameters to swkbd. + OUTOFMEM = -3, ///< Out of memory. + + D0_CLICK = 0, ///< The button was clicked in 1-button dialogs. + D1_CLICK0, ///< The left button was clicked in 2-button dialogs. + D1_CLICK1, ///< The right button was clicked in 2-button dialogs. + D2_CLICK0, ///< The left button was clicked in 3-button dialogs. + D2_CLICK1, ///< The middle button was clicked in 3-button dialogs. + D2_CLICK2, ///< The right button was clicked in 3-button dialogs. + + HOMEPRESSED = 10, ///< The HOME button was pressed. + RESETPRESSED, ///< The soft-reset key combination was pressed. + POWERPRESSED, ///< The POWER button was pressed. + + PARENTAL_OK = 20, ///< The parental PIN was verified successfully. + PARENTAL_FAIL, ///< The parental PIN was incorrect. + + BANNED_INPUT = 30, ///< The filter callback returned SWKBD_CALLBACK_CLOSE. +}; + struct SoftwareKeyboardConfig { - INSERT_PADDING_WORDS(0x8); - + SwkbdType type; + SwkbdButtonConfig num_buttons_m1; + SwkbdValidInput valid_input; + SwkbdPasswordMode password_mode; + s32 is_parental_screen; + s32 darken_top_screen; + u32 filter_flags; + u32 save_state_flags; u16 max_text_length; ///< Maximum length of the input text + u16 dict_word_count; + u16 max_digits; + u16 button_text[SWKBD_MAX_BUTTON][SWKBD_MAX_BUTTON_TEXT_LEN + 1]; + u16 numpad_keys[2]; + u16 hint_text[SWKBD_MAX_HINT_TEXT_LEN + 1]; ///< Text to display when asking the user for input + bool predictive_input; + bool multiline; + bool fixed_width; + bool allow_home; + bool allow_reset; + bool allow_power; + bool unknown; // XX: what is this supposed to do? "communicateWithOtherRegions" + bool default_qwerty; + bool button_submits_text[4]; + u16 language; // XX: not working? supposedly 0 = use system language, CFG_Language+1 = pick + // language - INSERT_PADDING_BYTES(0x6E); - - char16_t display_text[65]; ///< Text to display when asking the user for input - - INSERT_PADDING_BYTES(0xE); - - u32 default_text_offset; ///< Offset of the default text in the output SharedMemory - - INSERT_PADDING_WORDS(0x3); - + u32 initial_text_offset; ///< Offset of the default text in the output SharedMemory + u32 dict_offset; + u32 initial_status_offset; + u32 initial_learning_offset; u32 shared_memory_size; ///< Size of the SharedMemory + u32 version; - INSERT_PADDING_WORDS(0x1); + SwkbdResult return_code; - u32 return_code; ///< Return code of the SoftwareKeyboard, usually 2, other values are unknown - - INSERT_PADDING_WORDS(0x2); + u32 status_offset; + u32 learning_offset; u32 text_offset; ///< Offset in the SharedMemory where the output text starts u16 text_length; ///< Length in characters of the output text - INSERT_PADDING_BYTES(0x2B6); + int callback_result; + u16 callback_msg[SWKBD_MAX_CALLBACK_MSG_LEN + 1]; + bool skip_at_check; + INSERT_PADDING_BYTES(0xAB); }; /** diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 615fe31ea..39478dcee 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -40,8 +40,12 @@ static ScreencapPostPermission screen_capture_post_permission; /// Parameter data to be returned in the next call to Glance/ReceiveParameter static MessageParameter next_parameter; +/// stores the state for CancelParameter +static bool cancelled = false; + void SendParameter(const MessageParameter& parameter) { next_parameter = parameter; + cancelled = false; // Signal the event to let the application know that a new parameter is ready to be read parameter_event->Signal(); } @@ -208,6 +212,11 @@ void ReceiveParameter(Service::Interface* self) { u32 buffer_size = cmd_buff[2]; VAddr buffer = cmd_buff[0x104 >> 2]; + if (cancelled) { + cmd_buff[1] = -1; + return; + } + cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = next_parameter.sender_id; cmd_buff[3] = next_parameter.signal; // Signal type @@ -230,6 +239,11 @@ void GlanceParameter(Service::Interface* self) { u32 buffer_size = cmd_buff[2]; VAddr buffer = cmd_buff[0x104 >> 2]; + if (cancelled) { + cmd_buff[1] = -1; + return; + } + cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = next_parameter.sender_id; cmd_buff[3] = next_parameter.signal; // Signal type @@ -254,6 +268,8 @@ void CancelParameter(Service::Interface* self) { u32 flag2 = cmd_buff[3]; u32 app_id = cmd_buff[4]; + cancelled = true; + cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 1; // Set to Success @@ -493,6 +509,13 @@ void CheckNew3DS(Service::Interface* self) { LOG_WARNING(Service_APT, "(STUBBED) called"); } +void ReplySleepQuery(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + LOG_WARNING(Service_APT, "(STUBBED) ReplySleepQuery called"); +} + void Init() { AddService(new APT_A_Interface); AddService(new APT_S_Interface); diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 80325361f..39ca01457 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -461,6 +461,13 @@ void CheckNew3DSApp(Service::Interface* self); */ void CheckNew3DS(Service::Interface* self); +/** + * APT::ReplySleepQuery service function + * Outputs: + * 1: Result code + */ +void ReplySleepQuery(Service::Interface* self); + /// Initialize the APT service void Init(); diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index effd23dce..6ea284293 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -20,10 +20,10 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00090040, nullptr, "IsRegistered"}, {0x000A0040, nullptr, "GetAttribute"}, {0x000B0040, InquireNotification, "InquireNotification"}, - {0x000C0104, nullptr, "SendParameter"}, + {0x000C0104, SendParameter, "SendParameter"}, {0x000D0080, ReceiveParameter, "ReceiveParameter"}, {0x000E0080, GlanceParameter, "GlanceParameter"}, - {0x000F0100, nullptr, "CancelParameter"}, + {0x000F0100, CancelParameter, "CancelParameter"}, {0x001000C2, nullptr, "DebugFunc"}, {0x001100C0, nullptr, "MapProgramIdForDebug"}, {0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"}, @@ -38,7 +38,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x001B00C4, nullptr, "StartApplication"}, {0x001C0000, nullptr, "WakeupApplication"}, {0x001D0000, nullptr, "CancelApplication"}, - {0x001E0084, nullptr, "StartLibraryApplet"}, + {0x001E0084, StartLibraryApplet, "StartLibraryApplet"}, {0x001F0084, nullptr, "StartSystemApplet"}, {0x00200044, nullptr, "StartNewestHomeMenu"}, {0x00210000, nullptr, "OrderToCloseApplication"}, @@ -70,7 +70,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x003B0040, nullptr, "CancelLibraryApplet"}, {0x003C0042, nullptr, "SendDspSleep"}, {0x003D0042, nullptr, "SendDspWakeUp"}, - {0x003E0080, nullptr, "ReplySleepQuery"}, + {0x003E0080, ReplySleepQuery, "ReplySleepQuery"}, {0x003F0040, nullptr, "ReplySleepNotificationComplete"}, {0x00400042, nullptr, "SendCaptureBufferInfo"}, {0x00410040, nullptr, "ReceiveCaptureBufferInfo"},