]> Chaos Git - corbenik/ctrulib.git/commitdiff
Add software keyboard (swkbd) library applet support (finally!)
authorfincs <fincs.alt1@gmail.com>
Sun, 3 Jul 2016 23:08:12 +0000 (01:08 +0200)
committerfincs <fincs.alt1@gmail.com>
Sun, 3 Jul 2016 23:08:12 +0000 (01:08 +0200)
libctru/Makefile
libctru/include/3ds.h
libctru/include/3ds/applets/swkbd.h [new file with mode: 0644]
libctru/source/applets/swkbd.c [new file with mode: 0644]

index df51cae0e63a2ad596a4ae98bf99e7dcc5070817..74b999d5fd30ad9625c012b18398bd191bc4504b 100644 (file)
@@ -30,6 +30,7 @@ SOURCES               :=      source \
                        source/ndsp \
                        source/services \
                        source/services/soc \
+                       source/applets \
                        source/util/rbtree \
                        source/util/utf \
                        source/system
index aab3ee0655f4cf1f2376c5833280732faeb65928..1dc1683ab54a943d14b3bbd8704d15b477fb386a 100644 (file)
@@ -28,6 +28,7 @@ extern "C" {
 
 #include <3ds/services/ac.h>
 #include <3ds/services/am.h>
+#include <3ds/services/ampxi.h>
 #include <3ds/services/apt.h>
 #include <3ds/services/cam.h>
 #include <3ds/services/cfgnor.h>
@@ -60,8 +61,6 @@ extern "C" {
 #include <3ds/services/y2r.h>
 #include <3ds/services/hb.h>
 
-#include <3ds/services/ampxi.h>
-
 #include <3ds/gpu/gx.h>
 #include <3ds/gpu/gpu.h>
 #include <3ds/gpu/gpu-old.h>
@@ -71,6 +70,8 @@ extern "C" {
 #include <3ds/ndsp/ndsp.h>
 #include <3ds/ndsp/channel.h>
 
+#include <3ds/applets/swkbd.h>
+
 #include <3ds/sdmc.h>
 #include <3ds/romfs.h>
 #include <3ds/font.h>
diff --git a/libctru/include/3ds/applets/swkbd.h b/libctru/include/3ds/applets/swkbd.h
new file mode 100644 (file)
index 0000000..02c6e21
--- /dev/null
@@ -0,0 +1,320 @@
+/**
+ * @file swkbd.h
+ * @brief Software keyboard applet.
+ */
+#pragma once
+
+/// Keyboard types.
+typedef enum
+{
+       SWKBD_TYPE_NORMAL = 0, ///< Normal keyboard with several pages (QWERTY/accents/symbol/mobile)
+       SWKBD_TYPE_QWERTY,     ///< QWERTY keyboard only.
+       SWKBD_TYPE_NUMPAD,     ///< Number pad.
+       SWKBD_TYPE_WESTERN,    ///< On JPN systems, a text keyboard without Japanese input capabilities, otherwise same as SWKBD_TYPE_NORMAL.
+} SwkbdType;
+
+/// Accepted input types.
+typedef enum
+{
+       SWKBD_ANYTHING = 0,      ///< All inputs are accepted.
+       SWKBD_NOTEMPTY,          ///< Empty inputs are not accepted.
+       SWKBD_NOTEMPTY_NOTBLANK, ///< Empty or blank inputs (consisting solely of whitespace) are not accepted.
+       SWKBD_NOTBLANK_NOTEMPTY = SWKBD_NOTEMPTY_NOTBLANK,
+       SWKBD_NOTBLANK,          ///< Blank inputs (consisting solely of whitespace) are not accepted, but empty inputs are.
+       SWKBD_FIXEDLEN,          ///< The input must have a fixed length (specified by maxTextLength in swkbdInit).
+} SwkbdValidInput;
+
+/// Keyboard dialog buttons.
+typedef enum
+{
+       SWKBD_BUTTON_LEFT = 0, ///< Left button (usually Cancel)
+       SWKBD_BUTTON_MIDDLE,   ///< Middle button (usually I Forgot)
+       SWKBD_BUTTON_RIGHT,    ///< Right button (usually OK)
+       SWKBD_BUTTON_CONFIRM = SWKBD_BUTTON_RIGHT,
+       SWKBD_BUTTON_NONE,     ///< No button (returned by swkbdInputText in special cases)
+} SwkbdButton;
+
+/// Keyboard password modes.
+typedef enum
+{
+       SWKBD_PASSWORD_NONE = 0,   ///< Characters are not concealed.
+       SWKBD_PASSWORD_HIDE,       ///< Characters are concealed immediately.
+       SWKBD_PASSWORD_HIDE_DELAY, ///< Characters are concealed a second after they've been typed.
+} SwkbdPasswordMode;
+
+/// Keyboard input filtering flags.
+enum
+{
+       SWKBD_FILTER_DIGITS    = BIT(0), ///< Disallow the use of more than a certain number of digits (0 or more)
+       SWKBD_FILTER_AT        = BIT(1), ///< Disallow the use of the @ sign.
+       SWKBD_FILTER_PERCENT   = BIT(2), ///< Disallow the use of the % sign.
+       SWKBD_FILTER_BACKSLASH = BIT(3), ///< Disallow the use of the \ sign.
+       SWKBD_FILTER_PROFANITY = BIT(4), ///< Disallow profanity using Nintendo's profanity filter.
+       SWKBD_FILTER_CALLBACK  = BIT(5), ///< Use a callback in order to check the input.
+};
+
+/// Keyboard features.
+enum
+{
+       SWKBD_PARENTAL          = BIT(0), ///< Parental PIN mode.
+       SWKBD_DARKEN_TOP_SCREEN = BIT(1), ///< Darken the top screen when the keyboard is shown.
+       SWKBD_PREDICTIVE_INPUT  = BIT(2), ///< Enable predictive input (necessary for Kanji input in JPN systems).
+       SWKBD_MULTILINE         = BIT(3), ///< Enable multiline input.
+       SWKBD_FIXED_WIDTH       = BIT(4), ///< Enable fixed-width mode.
+       SWKBD_ALLOW_HOME        = BIT(5), ///< Allow the usage of the HOME button.
+       SWKBD_ALLOW_RESET       = BIT(6), ///< Allow the usage of a software-reset combination.
+       SWKBD_ALLOW_POWER       = BIT(7), ///< Allow the usage of the POWER button.
+       SWKBD_DEFAULT_QWERTY    = BIT(9), ///< Default to the QWERTY page when the keyboard is shown.
+};
+
+/// Keyboard filter callback return values.
+typedef enum
+{
+       SWKBD_CALLBACK_OK = 0,   ///< Specifies that the input is valid.
+       SWKBD_CALLBACK_CLOSE,    ///< Displays an error message, then closes the keyboard.
+       SWKBD_CALLBACK_CONTINUE, ///< Displays an error message and continues displaying the keyboard.
+} SwkbdCallbackResult;
+
+/// Keyboard return values.
+typedef enum
+{
+       SWKBD_NONE          = -1, ///< Dummy/unused.
+       SWKBD_INVALID_INPUT = -2, ///< Invalid parameters to swkbd.
+       SWKBD_OUTOFMEM      = -3, ///< Out of memory.
+
+       SWKBD_D0_CLICK = 0, ///< The button was clicked in 1-button dialogs.
+       SWKBD_D1_CLICK0,    ///< The left button was clicked in 2-button dialogs.
+       SWKBD_D1_CLICK1,    ///< The right button was clicked in 2-button dialogs.
+       SWKBD_D2_CLICK0,    ///< The left button was clicked in 3-button dialogs.
+       SWKBD_D2_CLICK1,    ///< The middle button was clicked in 3-button dialogs.
+       SWKBD_D2_CLICK2,    ///< The right button was clicked in 3-button dialogs.
+
+       SWKBD_HOMEPRESSED = 10, ///< The HOME button was pressed.
+       SWKBD_RESETPRESSED,     ///< The soft-reset key combination was pressed.
+       SWKBD_POWERPRESSED,     ///< The POWER button was pressed.
+
+       SWKBD_PARENTAL_OK = 20, ///< The parental PIN was verified successfully.
+       SWKBD_PARENTAL_FAIL,    ///< The parental PIN was incorrect.
+
+       SWKBD_BANNED_INPUT = 30, ///< The filter callback returned SWKBD_CALLBACK_CLOSE.
+} SwkbdResult;
+
+/// Maximum dictionary word length, in UTF-16 code units.
+#define SWKBD_MAX_WORD_LEN         40
+/// 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 dictionary word for predictive input.
+typedef struct
+{
+       u16 reading[SWKBD_MAX_WORD_LEN+1]; ///< Reading of the word (that is, the string that needs to be typed).
+       u16 word[SWKBD_MAX_WORD_LEN+1];    ///< Spelling of the word.
+       u8 language;                       ///< Language the word applies to.
+       bool all_languages;                ///< Specifies if the word applies to all languages.
+} SwkbdDictWord;
+
+/// Keyboard filter callback function.
+typedef SwkbdCallbackResult (* SwkbdCallbackFn)(void* user, const char** ppMessage, const char* text, size_t textlen);
+/// Keyboard status data.
+typedef struct { u32 data[0x11]; } SwkbdStatusData;
+/// Keyboard predictive input learning data.
+typedef struct { u32 data[0x291B]; } SwkbdLearningData;
+
+/// Internal libctru book-keeping structure for software keyboards.
+typedef struct
+{
+       const char* initial_text;
+       const SwkbdDictWord* dict;
+       SwkbdStatusData* status_data;
+       SwkbdLearningData* learning_data;
+       SwkbdCallbackFn callback;
+       void* callback_user;
+} SwkbdExtra;
+
+/// Software keyboard parameter structure, it shouldn't be modified directly.
+typedef struct
+{
+       int type;
+       int num_buttons_m1;
+       int valid_input;
+       int password_mode;
+       int is_parental_screen;
+       int darken_top_screen;
+       u32 filter_flags;
+       u32 save_state_flags;
+       u16 max_text_len;
+       u16 dict_word_count;
+       u16 max_digits;
+       u16 button_text[3][SWKBD_MAX_BUTTON_TEXT_LEN+1];
+       u16 numpad_keys[2];
+       u16 hint_text[SWKBD_MAX_HINT_TEXT_LEN+1];
+       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
+       int initial_text_offset;
+       int dict_offset;
+       int initial_status_offset;
+       int initial_learning_offset;
+       size_t shared_memory_size;
+       u32 version;
+       SwkbdResult result;
+       int status_offset;
+       int learning_offset;
+       int text_offset;
+       u16 text_length;
+       int callback_result;
+       u16 callback_msg[SWKBD_MAX_CALLBACK_MSG_LEN+1];
+       bool skip_at_check;
+       union
+       {
+               u8 reserved[171];
+               SwkbdExtra extra;
+       };
+} SwkbdState;
+
+/**
+ * @brief Initializes software keyboard status.
+ * @param swkbd Pointer to swkbd state.
+ * @param type Keyboard type.
+ * @param numButtons Number of dialog buttons to display (1, 2 or 3).
+ * @param maxTextLength Maximum number of UTF-16 code units that input text can have (or -1 to let libctru use a big default).
+ */
+void swkbdInit(SwkbdState* swkbd, SwkbdType type, int numButtons, int maxTextLength);
+
+/**
+ * @brief Configures password mode in a software keyboard.
+ * @param swkbd Pointer to swkbd state.
+ * @param mode Password mode.
+ */
+static inline void swkbdSetPasswordMode(SwkbdState* swkbd, SwkbdPasswordMode mode)
+{
+       swkbd->password_mode = mode;
+}
+
+/**
+ * @brief Configures input validation in a software keyboard.
+ * @param swkbd Pointer to swkbd state.
+ * @param validInput Specifies which inputs are valid.
+ * @param filterFlags Bitmask specifying which characters are disallowed (filtered).
+ * @param maxDigits In case digits are disallowed, specifies how many digits are allowed at maximum in input strings (0 completely restricts digit input).
+ */
+static inline void swkbdSetValidation(SwkbdState* swkbd, SwkbdValidInput validInput, u32 filterFlags, int maxDigits)
+{
+       swkbd->valid_input = validInput;
+       swkbd->filter_flags = filterFlags;
+       swkbd->max_digits = (filterFlags & SWKBD_FILTER_DIGITS) ? maxDigits : 0;
+}
+
+/**
+ * @brief Configures what characters will the two bottom keys in a numpad produce.
+ * @param swkbd Pointer to swkbd state.
+ * @param left Unicode codepoint produced by the leftmost key in the bottom row (0 hides the key).
+ * @param left Unicode codepoint produced by the rightmost key in the bottom row (0 hides the key).
+ */
+static inline void swkbdSetNumpadKeys(SwkbdState* swkbd, int left, int right)
+{
+       swkbd->numpad_keys[0] = left;
+       swkbd->numpad_keys[1] = right;
+}
+
+/**
+ * @brief Specifies which special features are enabled in a software keyboard.
+ * @param swkbd Pointer to swkbd state.
+ * @param features Feature bitmask.
+ */
+void swkbdSetFeatures(SwkbdState* swkbd, u32 features);
+
+/**
+ * @brief Sets the hint text of a software keyboard (that is, the help text that is displayed when the textbox is empty).
+ * @param swkbd Pointer to swkbd state.
+ * @param text Hint text.
+ */
+void swkbdSetHintText(SwkbdState* swkbd, const char* text);
+
+/**
+ * @brief Configures a dialog button in a software keyboard.
+ * @param swkbd Pointer to swkbd state.
+ * @param button Specifies which button to configure.
+ * @param text Button text.
+ * @param submit Specifies whether pushing the button will submit the text or discard it.
+ */
+void swkbdSetButton(SwkbdState* swkbd, SwkbdButton button, const char* text, bool submit);
+
+/**
+ * @brief Sets the initial text that a software keyboard will display on launch.
+ * @param swkbd Pointer to swkbd state.
+ * @param text Initial text.
+ */
+void swkbdSetInitialText(SwkbdState* swkbd, const char* text);
+
+/**
+ * @brief Configures a word in a predictive dictionary for use with a software keyboard.
+ * @param word Pointer to dictionary word structure.
+ * @param reading Reading of the word, that is, the sequence of characters that need to be typed to trigger the word in the predictive input system.
+ * @param text Spelling of the word, that is, the actual characters that will be produced when the user decides to select the word.
+ */
+void swkbdSetDictWord(SwkbdDictWord* word, const char* reading, const char* text);
+
+/**
+ * @brief Sets the custom word dictionary to be used with the predictive input system of a software keyboard.
+ * @param swkbd Pointer to swkbd state.
+ * @param dict Pointer to dictionary words.
+ * @param wordCount Number of words in the dictionary.
+ */
+void swkbdSetDictionary(SwkbdState* swkbd, const SwkbdDictWord* dict, int wordCount);
+
+/**
+ * @brief Configures software keyboard internal status management.
+ * @param swkbd Pointer to swkbd state.
+ * @param data Pointer to internal status structure (can be in, out or both depending on the other parameters).
+ * @param in Specifies whether the data should be read from the structure when the keyboard is launched.
+ * @param out Specifies whether the data should be written to the structure when the keyboard is closed.
+ */
+void swkbdSetStatusData(SwkbdState* swkbd, SwkbdStatusData* data, bool in, bool out);
+
+/**
+ * @brief Configures software keyboard predictive input learning data management.
+ * @param swkbd Pointer to swkbd state.
+ * @param data Pointer to learning data structure (can be in, out or both depending on the other parameters).
+ * @param in Specifies whether the data should be read from the structure when the keyboard is launched.
+ * @param out Specifies whether the data should be written to the structure when the keyboard is closed.
+ */
+void swkbdSetLearningData(SwkbdState* swkbd, SwkbdLearningData* data, bool in, bool out);
+
+/**
+ * @brief Configures a custom function to be used to check the validity of input when it is submitted in a software keyboard.
+ * @param swkbd Pointer to swkbd state.
+ * @param callback Filter callback function.
+ * @param user Custom data to be passed to the callback function.
+ */
+void swkbdSetFilterCallback(SwkbdState* swkbd, SwkbdCallbackFn callback, void* user);
+
+/**
+ * @brief Launches a software keyboard in order to input text.
+ * @param swkbd Pointer to swkbd state.
+ * @param buf Pointer to output buffer which will hold the inputted text.
+ * @param bufsize Maximum number of UTF-8 code units that the buffer can hold (including null terminator).
+ * @return The identifier of the dialog button that was pressed, or SWKBD_BUTTON_NONE if a different condition was triggered - in that case use swkbdGetResult to check the condition.
+ */
+SwkbdButton swkbdInputText(SwkbdState* swkbd, char* buf, size_t bufsize);
+
+/**
+ * @brief Retrieves the result condition of a software keyboard after it has been used.
+ * @param swkbd Pointer to swkbd state.
+ * @return The result value.
+ */
+static inline SwkbdResult swkbdGetResult(SwkbdState* swkbd)
+{
+       return swkbd->result;
+}
diff --git a/libctru/source/applets/swkbd.c b/libctru/source/applets/swkbd.c
new file mode 100644 (file)
index 0000000..8e712ae
--- /dev/null
@@ -0,0 +1,276 @@
+#include <stdlib.h>
+#include <string.h>
+#include <malloc.h>
+#include <3ds/types.h>
+#include <3ds/result.h>
+#include <3ds/svc.h>
+#include <3ds/synchronization.h>
+#include <3ds/services/apt.h>
+#include <3ds/applets/swkbd.h>
+#include <3ds/ipc.h>
+#include <3ds/env.h>
+#include <3ds/util/utf.h>
+
+static char* swkbdSharedMem;
+static Handle swkbdSharedMemHandle;
+
+void swkbdInit(SwkbdState* swkbd, SwkbdType type, int numButtons, int maxTextLength)
+{
+       memset(swkbd, 0, sizeof(*swkbd));
+       swkbd->type = type;
+       swkbd->num_buttons_m1 = numButtons-1;
+       swkbd->max_text_len = maxTextLength > 0 ? maxTextLength : 0xFDE8;
+       swkbd->button_submits_text[SWKBD_BUTTON_CONFIRM] = true;
+       swkbd->initial_text_offset = -1;
+       swkbd->dict_offset = -1;
+       swkbd->initial_status_offset = -1;
+       swkbd->initial_learning_offset = -1;
+       swkbd->version = 5;
+       swkbd->result = SWKBD_NONE;
+       swkbd->status_offset = -1;
+       swkbd->learning_offset = -1;
+       swkbd->text_offset = -1;
+}
+
+static void swkbdConvertToUTF8(char* out, const u16* in, int max)
+{
+       if (!in || !*in)
+       {
+               out[0] = 0;
+               return;
+       }
+
+       ssize_t units = utf16_to_utf8((uint8_t*)out, in, max);
+       if (units < 0)
+       {
+               out[0] = 0;
+               return;
+       }
+
+       out[units] = 0;
+}
+
+static void swkbdConvertToUTF16(u16* out, const char* in, int max)
+{
+       if (!in || !*in)
+       {
+               out[0] = 0;
+               return;
+       }
+
+       ssize_t units = utf8_to_utf16(out, (const uint8_t*)in, max);
+       if (units < 0)
+       {
+               out[0] = 0;
+               return;
+       }
+
+       out[units] = 0;
+}
+
+static const u16 swkbdFeatures[] =
+{
+       offsetof(SwkbdState, is_parental_screen),
+       offsetof(SwkbdState, darken_top_screen),
+       offsetof(SwkbdState, predictive_input),
+       offsetof(SwkbdState, multiline),
+       offsetof(SwkbdState, fixed_width),
+       offsetof(SwkbdState, allow_home),
+       offsetof(SwkbdState, allow_reset),
+       offsetof(SwkbdState, allow_power),
+       offsetof(SwkbdState, unknown),
+       offsetof(SwkbdState, default_qwerty),
+};
+
+void swkbdSetFeatures(SwkbdState* swkbd, u32 features)
+{
+       int i;
+       for (i = 0; i < (sizeof(swkbdFeatures)/sizeof(u16)); i ++)
+               *((u8*)swkbd + swkbdFeatures[i]) = (features & BIT(i)) ? 1 : 0;
+}
+
+void swkbdSetHintText(SwkbdState* swkbd, const char* text)
+{
+       swkbdConvertToUTF16(swkbd->hint_text, text, SWKBD_MAX_HINT_TEXT_LEN);
+}
+
+void swkbdSetButton(SwkbdState* swkbd, SwkbdButton button, const char* text, bool submit)
+{
+       swkbdConvertToUTF16(swkbd->button_text[button], text, SWKBD_MAX_BUTTON_TEXT_LEN);
+       swkbd->button_submits_text[button] = submit;
+}
+
+void swkbdSetInitialText(SwkbdState* swkbd, const char* text)
+{
+       swkbd->extra.initial_text = text;
+}
+
+void swkbdSetDictWord(SwkbdDictWord* word, const char* reading, const char* text)
+{
+       swkbdConvertToUTF16(word->reading, reading, SWKBD_MAX_WORD_LEN);
+       swkbdConvertToUTF16(word->word, text, SWKBD_MAX_WORD_LEN);
+       word->language = 0;
+       word->all_languages = true;
+}
+
+void swkbdSetDictionary(SwkbdState* swkbd, const SwkbdDictWord* dict, int wordCount)
+{
+       swkbd->dict_word_count = dict ? wordCount : 0;
+       swkbd->extra.dict = dict;
+}
+
+void swkbdSetStatusData(SwkbdState* swkbd, SwkbdStatusData* data, bool in, bool out)
+{
+       swkbd->extra.status_data = data;
+       swkbd->initial_status_offset = (data&&in) ? 0 : -1;
+       if (data&&out) swkbd->save_state_flags |= BIT(0);
+       else           swkbd->save_state_flags &= ~BIT(0);
+}
+
+void swkbdSetLearningData(SwkbdState* swkbd, SwkbdLearningData* data, bool in, bool out)
+{
+       swkbd->extra.learning_data = data;
+       swkbd->initial_learning_offset = (data&&in) ? 0 : -1;
+       if (data&&out) swkbd->save_state_flags |= BIT(1);
+       else           swkbd->save_state_flags &= ~BIT(1);
+}
+
+void swkbdSetFilterCallback(SwkbdState* swkbd, SwkbdCallbackFn callback, void* user)
+{
+       swkbd->extra.callback = callback;
+       swkbd->extra.callback_user = callback ? user : NULL;
+}
+
+static void swkbdMessageCallback(void* user, NS_APPID sender, void* msg, size_t msgsize)
+{
+       SwkbdExtra* extra = (SwkbdExtra*)user;
+       SwkbdState* swkbd = (SwkbdState*)msg;
+
+       if (sender != APPID_SOFTWARE_KEYBOARD || msgsize != sizeof(SwkbdState))
+               return;
+
+       u16* text16 = (u16*)(swkbdSharedMem + swkbd->text_offset);
+       ssize_t units = utf16_to_utf8(NULL, text16, 0);
+       if (units < 0) svcBreak(USERBREAK_PANIC); // Shouldn't happen.
+       char* text8 = (char*)malloc(units+1);
+       if (!text8) svcBreak(USERBREAK_PANIC); // Shouldn't happen.
+       swkbdConvertToUTF8(text8, text16, units);
+
+       const char* retmsg = NULL;
+       swkbd->callback_result = extra->callback(extra->callback_user, &retmsg, text8, units);
+       if (swkbd->callback_result > SWKBD_CALLBACK_OK)
+               swkbdConvertToUTF16(swkbd->callback_msg, retmsg, SWKBD_MAX_CALLBACK_MSG_LEN);
+       else
+               swkbd->callback_msg[0] = 0;
+
+       free(text8);
+       APT_SendParameter(envGetAptAppId(), sender, APTCMD_MESSAGE, swkbd, sizeof(*swkbd), 0);
+}
+
+SwkbdButton swkbdInputText(SwkbdState* swkbd, char* buf, size_t bufsize)
+{
+       SwkbdExtra extra = swkbd->extra; // Struct copy
+
+       // Calculate sharedmem size
+       size_t sharedMemSize = 0;
+       sharedMemSize += (sizeof(u16)*(swkbd->max_text_len+1) + 3) &~ 3;
+       size_t dictOff = sharedMemSize;
+       sharedMemSize += (sizeof(SwkbdDictWord)*swkbd->dict_word_count + 3) &~ 3;
+       size_t statusOff = sharedMemSize;
+       sharedMemSize += swkbd->initial_status_offset >= 0 ? sizeof(SwkbdStatusData) : 0;
+       size_t learningOff = sharedMemSize;
+       sharedMemSize += swkbd->initial_learning_offset >= 0 ? sizeof(SwkbdLearningData) : 0;
+       if (swkbd->save_state_flags & BIT(0))
+       {
+               swkbd->status_offset = sharedMemSize;
+               sharedMemSize += sizeof(SwkbdStatusData);
+       }
+       if (swkbd->save_state_flags & BIT(1))
+       {
+               swkbd->learning_offset = sharedMemSize;
+               sharedMemSize += sizeof(SwkbdLearningData);
+       }
+       sharedMemSize  = (sharedMemSize + 0xFFF) &~ 0xFFF;
+       swkbd->shared_memory_size = sharedMemSize;
+
+       // Allocate sharedmem
+       swkbdSharedMem = (char*)memalign(0x1000, sharedMemSize);
+       if (!swkbdSharedMem)
+       {
+               swkbd->result = SWKBD_OUTOFMEM;
+               return SWKBD_BUTTON_NONE;
+       }
+
+       // Create sharedmem block
+       Result res = svcCreateMemoryBlock(&swkbdSharedMemHandle, (u32)swkbdSharedMem, sharedMemSize, MEMPERM_READ|MEMPERM_WRITE, MEMPERM_READ|MEMPERM_WRITE);
+       if (R_FAILED(res))
+       {
+               free(swkbdSharedMem);
+               swkbd->result = SWKBD_OUTOFMEM;
+               return SWKBD_BUTTON_NONE;
+       }
+
+       // Copy stuff to shared mem
+       if (extra.initial_text)
+       {
+               swkbd->initial_text_offset = 0;
+               swkbdConvertToUTF16((u16*)swkbdSharedMem, extra.initial_text, swkbd->max_text_len);
+       }
+       if (extra.dict)
+       {
+               swkbd->dict_offset = dictOff;
+               memcpy(swkbdSharedMem+dictOff, extra.dict, sizeof(SwkbdDictWord)*swkbd->dict_word_count);
+       }
+       if (swkbd->initial_status_offset >= 0)
+       {
+               swkbd->initial_status_offset = statusOff;
+               memcpy(swkbdSharedMem+statusOff, extra.status_data, sizeof(SwkbdStatusData));
+       }
+       if (swkbd->initial_learning_offset >= 0)
+       {
+               swkbd->initial_learning_offset = learningOff;
+               memcpy(swkbdSharedMem+learningOff, extra.learning_data, sizeof(SwkbdLearningData));
+       }
+
+       if (extra.callback) swkbd->filter_flags |= SWKBD_FILTER_CALLBACK;
+       else                swkbd->filter_flags &= ~SWKBD_FILTER_CALLBACK;
+
+       // Launch swkbd
+       memset(swkbd->reserved, 0, sizeof(swkbd->reserved));
+       if (extra.callback) aptSetMessageCallback(swkbdMessageCallback, &extra);
+       bool ret = aptLaunchLibraryApplet(APPID_SOFTWARE_KEYBOARD, swkbd, sizeof(*swkbd), swkbdSharedMemHandle);
+       if (extra.callback) aptSetMessageCallback(NULL, NULL);
+       svcCloseHandle(swkbdSharedMemHandle);
+
+       SwkbdButton button = SWKBD_BUTTON_NONE;
+
+       if (ret)
+       {
+               u16* text16 = (u16*)(swkbdSharedMem+swkbd->text_offset);
+               text16[swkbd->text_length] = 0;
+               swkbdConvertToUTF8(buf, text16, bufsize-1);
+               if (swkbd->save_state_flags & BIT(0)) memcpy(extra.status_data, swkbdSharedMem+swkbd->status_offset, sizeof(SwkbdStatusData));
+               if (swkbd->save_state_flags & BIT(1)) memcpy(extra.learning_data, swkbdSharedMem+swkbd->learning_offset, sizeof(SwkbdLearningData));
+
+               switch (swkbd->result)
+               {
+                       case SWKBD_D1_CLICK0:
+                       case SWKBD_D2_CLICK0:
+                               button = SWKBD_BUTTON_LEFT;
+                               break;
+                       case SWKBD_D2_CLICK1:
+                               button = SWKBD_BUTTON_MIDDLE;
+                               break;
+                       case SWKBD_D0_CLICK:
+                       case SWKBD_D1_CLICK1:
+                       case SWKBD_D2_CLICK2:
+                               button = SWKBD_BUTTON_RIGHT;
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       free(swkbdSharedMem);
+       return button;
+}