From 4b5fef111b0750629b1e2c3cc5d2c6a8c965aaf8 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Sun, 6 Apr 2025 10:48:35 -0300 Subject: [PATCH 01/16] Implemented swkbd on the Wii U. --- CMakeLists.txt | 7 +- include/SDL_system.h | 47 ++ include/SDL_syswm.h | 8 +- src/events/SDL_events_c.h | 9 + src/events/SDL_keyboard_c.h | 8 + src/joystick/wiiu/SDL_wiiujoystick.c | 51 +- src/render/wiiu/SDL_rpresent_wiiu.c | 24 + src/video/wiiu/SDL_wiiu_swkbd.cpp | 820 +++++++++++++++++++++++++++ src/video/wiiu/SDL_wiiu_swkbd.h | 57 ++ src/video/wiiu/SDL_wiiukeyboard.c | 35 +- src/video/wiiu/SDL_wiiuvideo.c | 15 + 11 files changed, 1054 insertions(+), 27 deletions(-) create mode 100644 src/video/wiiu/SDL_wiiu_swkbd.cpp create mode 100644 src/video/wiiu/SDL_wiiu_swkbd.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 013dab9ee2e42..7f3b1da9b2c90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,6 +240,9 @@ endif() if(WIIU) # Prefer coreinit atomics on Wii U due to a hardware bug in load-exclusive and store-exclusive instructions set(OPT_DEF_GCC_ATOMICS OFF) + # Needed for nn::swkbd API + set(LINKER_LANGUAGE CXX) + set (CMAKE_CXX_STANDARD 23) endif() # Default option knobs @@ -2920,7 +2923,9 @@ elseif(WIIU) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_WIIU 1) set(SDL_VIDEO_RENDER_WIIU 1) - file(GLOB WIIU_VIDEO_SOURCES ${SDL2_SOURCE_DIR}/src/video/wiiu/*.c) + file(GLOB WIIU_VIDEO_SOURCES + ${SDL2_SOURCE_DIR}/src/video/wiiu/*.c + ${SDL2_SOURCE_DIR}/src/video/wiiu/*.cpp) set(SOURCE_FILES ${SOURCE_FILES} ${WIIU_VIDEO_SOURCES}) set(HAVE_SDL_VIDEO TRUE) endif() diff --git a/include/SDL_system.h b/include/SDL_system.h index 4b7eaddcc0ed9..c9980a76cfd3a 100644 --- a/include/SDL_system.h +++ b/include/SDL_system.h @@ -612,6 +612,53 @@ extern DECLSPEC int SDLCALL SDL_GDKGetTaskQueue(XTaskQueueHandle * outTaskQueue) #endif +/* Platform specific functions for Wii U */ +#if defined(__WIIU__) +typedef enum SDL_WiiUSysWMEventType { + SDL_WIIU_SYSWM_SWKBD_OK_EVENT = 1, + SDL_WIIU_SYSWM_SWKBD_CANCEL_EVENT +} SDL_WiiUSysWMEventType; + +/** + * Set a pointer to a nn::swkbd::CreateArg object that will be used to create the swkbd. + * + * \param createArg a pointer to a persistent nn::swkbd::CreateArg object, or NULL to use the default. + */ +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDCreateArg(void * createArg); + +/** + * Set a pointer to a nn::swkbd::AppearArg object that will be used every time + * the swkbd is shown. + * + * \param appearArg a pointer to a persistent nn::swkbd::AppearArg object, or NULL to use the default. + */ +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDAppearArg(void * appearArg); + +// TODO: write docs +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDKeyboardMode(int mode); + +// TODO: write docs +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDOKLabel(const char * label); + +// TODO: write docs +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDShowWordSuggestions(SDL_bool show); + +// TODO: write docs +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDInitialText(const char * text); + +// TODO: write docs +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDHintText(const char * text); + +// TODO: write docs +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDPasswordMode(int mode); + +// TODO: write docs +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDHighlightInitialText(SDL_bool highlight); + +// TODO: write docs +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDShowCopyPasteButtons(SDL_bool show); +#endif /* Wii U */ + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/include/SDL_syswm.h b/include/SDL_syswm.h index b35734deb3345..818ce505757a5 100644 --- a/include/SDL_syswm.h +++ b/include/SDL_syswm.h @@ -148,7 +148,8 @@ typedef enum SDL_SYSWM_OS2, SDL_SYSWM_HAIKU, SDL_SYSWM_KMSDRM, - SDL_SYSWM_RISCOS + SDL_SYSWM_RISCOS, + SDL_SYSWM_WIIU } SDL_SYSWM_TYPE; /** @@ -211,6 +212,11 @@ struct SDL_SysWMmsg MPARAM mp1; /**< The first first message parameter */ MPARAM mp2; /**< The second first message parameter */ } os2; +#endif +#if defined(SDL_VIDEO_DRIVER_WIIU) + struct { + unsigned event; + } wiiu; #endif /* Can't have an empty union */ int dummy; diff --git a/src/events/SDL_events_c.h b/src/events/SDL_events_c.h index 7f27b1a2fd3c9..0699d0c480b2a 100644 --- a/src/events/SDL_events_c.h +++ b/src/events/SDL_events_c.h @@ -24,6 +24,10 @@ #include "../SDL_internal.h" +#ifdef __cplusplus +extern "C" { +#endif + /* Useful functions and variables from SDL_events.c */ #include "SDL_events.h" #include "SDL_thread.h" @@ -58,6 +62,11 @@ extern void SDL_SendPendingSignalEvents(void); extern int SDL_QuitInit(void); extern void SDL_QuitQuit(void); +#ifdef __cplusplus +} +#endif + + #endif /* SDL_events_c_h_ */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/events/SDL_keyboard_c.h b/src/events/SDL_keyboard_c.h index 16a5fad2e0e72..d0733dfff8f06 100644 --- a/src/events/SDL_keyboard_c.h +++ b/src/events/SDL_keyboard_c.h @@ -26,6 +26,10 @@ #include "SDL_keycode.h" #include "SDL_events.h" +#ifdef __cplusplus +extern "C" { +#endif + /* Initialize the keyboard subsystem */ extern int SDL_KeyboardInit(void); @@ -84,6 +88,10 @@ extern char *SDL_UCS4ToUTF8(Uint32 ch, char *dst); /* Toggle on or off pieces of the keyboard mod state. */ extern void SDL_ToggleModState(const SDL_Keymod modstate, const SDL_bool toggle); +#ifdef __cplusplus +} +#endif + #endif /* SDL_keyboard_c_h_ */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/joystick/wiiu/SDL_wiiujoystick.c b/src/joystick/wiiu/SDL_wiiujoystick.c index e7a6d18cfdfd7..0d9c4a844f5d4 100644 --- a/src/joystick/wiiu/SDL_wiiujoystick.c +++ b/src/joystick/wiiu/SDL_wiiujoystick.c @@ -40,6 +40,7 @@ #include "SDL_wiiujoystick.h" #include "../../video/SDL_sysvideo.h" +#include "../../video/wiiu/SDL_wiiu_swkbd.h" //index with device_index, get WIIU_DEVICE* static int deviceMap[MAX_CONTROLLERS]; @@ -152,7 +153,7 @@ static int WIIU_JoystickGetCount(void) static void WIIU_JoystickDetect(void) { /* Make sure there are no dangling instances or device indexes - These checks *should* be unneccesary, remove once battle-tested */ + These checks *should* be unneccesary, remove once battle-tested */ for (int i = 0; i < MAX_CONTROLLERS; i++) { if (deviceMap[i] == WIIU_DEVICE_INVALID && instanceMap[i] != -1) { @@ -464,37 +465,44 @@ static void WIIU_JoystickUpdate(SDL_Joystick *joystick) VPADStatus vpad; VPADReadError error; - VPADTouchData tpdata; - VPADRead(VPAD_CHAN_0, &vpad, 1, &error); + int32_t read; + + read = VPADRead(VPAD_CHAN_0, &vpad, 1, &error); if (error == VPAD_READ_INVALID_CONTROLLER) { /* Gamepad disconnected! */ SDL_PrivateJoystickRemoved(joystick->instance_id); /* Unlink Gamepad, device_index, instance_id */ WIIU_RemoveDevice(WIIU_DEVICE_GAMEPAD); return; - } else if (error != VPAD_READ_SUCCESS) { + } else if (read != 1 || error != VPAD_READ_SUCCESS) { return; } /* touchscreen */ - VPADGetTPCalibratedPoint(VPAD_CHAN_0, &tpdata, &vpad.tpNormal); - if (tpdata.touched) { + VPADGetTPCalibratedPoint(VPAD_CHAN_0, &vpad.tpNormal, &vpad.tpNormal); + + if (WIIU_SWKBD_ConsumeVPAD(&vpad)) { + /* Do not generate SDL events when the swkbd is consuming the input. */ + return; + } + + if (vpad.tpNormal.touched) { SDL_Window *window = WIIU_GetGamepadWindow(); if (!last_touched) { /* Send an initial touch */ SDL_SendTouch(0, 0, window, SDL_TRUE, - (float) tpdata.x / 1280.0f, - (float) tpdata.y / 720.0f, 1); + (float) vpad.tpNormal.x / 1280.0f, + (float) vpad.tpNormal.y / 720.0f, 1); } /* Always send the motion */ SDL_SendTouchMotion(0, 0, window, - (float) tpdata.x / 1280.0f, - (float) tpdata.y / 720.0f, 1); + (float) vpad.tpNormal.x / 1280.0f, + (float) vpad.tpNormal.y / 720.0f, 1); /* Update old values */ - last_touch_x = tpdata.x; - last_touch_y = tpdata.y; + last_touch_x = vpad.tpNormal.x; + last_touch_y = vpad.tpNormal.y; last_touched = 1; } else if (last_touched) { SDL_Window *window = WIIU_GetGamepadWindow(); @@ -544,14 +552,21 @@ static void WIIU_JoystickUpdate(SDL_Joystick *joystick) WPADExtensionType ext; KPADStatus kpad; int32_t err; + int32_t read; if (WPADProbe(WIIU_WPAD_CHAN(wiiu_device), &ext) != 0) { /* Do nothing, we'll catch it in Detect() */ return; } - KPADReadEx(WIIU_WPAD_CHAN(wiiu_device), &kpad, 1, &err); - if (err != KPAD_ERROR_OK) return; + read = KPADReadEx(WIIU_WPAD_CHAN(wiiu_device), &kpad, 1, &err); + if (read != 1 || err != KPAD_ERROR_OK) + return; + + if (WIIU_SWKBD_ConsumeKPAD(WIIU_WPAD_CHAN(wiiu_device), &kpad)) { + /* Do not generate SDL events when the swkbd is consuming the input. */ + return; + } switch (ext) { case WPAD_EXT_CORE: @@ -662,3 +677,11 @@ SDL_JoystickDriver SDL_WIIU_JoystickDriver = }; #endif + +/* + * Local Variables: + * indent-tabs-mode: t + * tab-width: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/render/wiiu/SDL_rpresent_wiiu.c b/src/render/wiiu/SDL_rpresent_wiiu.c index c0ac7a1cc5b8a..9469a1500a1ff 100644 --- a/src/render/wiiu/SDL_rpresent_wiiu.c +++ b/src/render/wiiu/SDL_rpresent_wiiu.c @@ -26,6 +26,7 @@ #include "../SDL_sysrender.h" #include "SDL_render_wiiu.h" +#include "../../video/wiiu/SDL_wiiu_swkbd.h" #include #include @@ -48,12 +49,35 @@ int WIIU_SDL_SetVSync(SDL_Renderer * renderer, const int vsync) return 0; } +#define WIIU_FIX_SWKBD_GAMMA + int WIIU_SDL_RenderPresent(SDL_Renderer * renderer) { WIIU_RenderData *data = (WIIU_RenderData *) renderer->driverdata; WIIU_TextureData *tdata = (WIIU_TextureData *) data->windowTex.driverdata; Uint32 flags = SDL_GetWindowFlags(renderer->window); + if (WIIU_SWKBD_IsScreenKeyboardShown(NULL, renderer->window)) { + GX2SetContextState(NULL); +#ifdef WIIU_FIX_SWKBD_GAMMA + if (tdata->cbuf.surface.format == GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8) { + GX2SurfaceFormat old_format = tdata->cbuf.surface.format; + tdata->cbuf.surface.format = GX2_SURFACE_FORMAT_SRGB_R8_G8_B8_A8; + GX2InitColorBufferRegs(&tdata->cbuf); + GX2SetColorBuffer(&tdata->cbuf, GX2_RENDER_TARGET_0); + WIIU_SWKBD_Draw(renderer->window); + tdata->cbuf.surface.format = old_format; + GX2InitColorBufferRegs(&tdata->cbuf); + GX2SetColorBuffer(&tdata->cbuf, GX2_RENDER_TARGET_0); + } else { + WIIU_SWKBD_Draw(renderer->window); + } +#else + WIIU_SWKBD_Draw(renderer->window); +#endif + + } + /* Only render to TV if the window is *not* drc-only */ if (!(flags & SDL_WINDOW_WIIU_GAMEPAD_ONLY)) { GX2CopyColorBufferToScanBuffer(&tdata->cbuf, GX2_SCAN_TARGET_TV); diff --git a/src/video/wiiu/SDL_wiiu_swkbd.cpp b/src/video/wiiu/SDL_wiiu_swkbd.cpp new file mode 100644 index 0000000000000..61ef9ec8c7d80 --- /dev/null +++ b/src/video/wiiu/SDL_wiiu_swkbd.cpp @@ -0,0 +1,820 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Daniel K. O. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SDL_wiiu_swkbd.h" + +#include +#include +#include +#include + +#include "SDL_stdinc.h" +#include "SDL_system.h" +#include "SDL_syswm.h" + +#include "../../events/SDL_events_c.h" +#include "../../events/SDL_keyboard_c.h" + +using namespace std::string_literals; +using nn::swkbd::RegionType; +using nn::swkbd::LanguageType; + +// #define SDL_WIIU_DONT_REPLACE_NEW_DELETE + +#ifndef SDL_WIIU_DONT_REPLACE_NEW_DELETE +// Override global new/delete operators to use SDL_malloc()/SDL_free() +void* +operator new(std::size_t size) +{ + if (void* data = SDL_malloc(size)) + return data; + throw std::bad_alloc{}; +} + +void +operator delete(void* data) + noexcept +{ + SDL_free(data); +} +#endif // SDL_WIIU_DONT_REPLACE_NEW_DELETE + +namespace { + namespace detail { + + struct FSLib { + FSLib() + { + FSInit(); + } + + ~FSLib() + { + FSShutdown(); + } + + }; + + std::optional fsLib; + + struct FSClientWrapper : FSClient { + + FSClientWrapper() + { + auto status = FSAddClient(this, FS_ERROR_FLAG_ALL); + if (status != FS_STATUS_OK) + throw std::runtime_error{"FSAddClient() failed"}; + } + + // disallow moving + FSClientWrapper(FSClientWrapper&&) = delete; + + ~FSClientWrapper() + { + FSDelClient(this, FS_ERROR_FLAG_NONE); + } + + }; + + + bool initialized = false; + + std::unique_ptr workMemory; + std::unique_ptr fsClient; + + SDL_Window *currentWindow = nullptr; + + nn::swkbd::CreateArg createArg; + const nn::swkbd::CreateArg *customCreateArg = nullptr; + std::string createdLocale; + + namespace appear { + const nn::swkbd::AppearArg *customArg = nullptr; + + // keyboard config options + nn::swkbd::KeyboardMode keyboardMode = nn::swkbd::KeyboardMode::Full; + std::u16string okText; + bool showWordSuggestions = false; + + // TODO: control disabled inputs, needs to fix nn::swkbd::ConfigArg + + // input form options + std::u16string initialText; + std::u16string hintText; + nn::swkbd::PasswordMode passwordMode = nn::swkbd::PasswordMode::Clear; + bool highlightInitialText = false; + bool showCopyPasteButtons = false; + } + + nn::swkbd::ControllerInfo controllerInfo; + + VPADStatus vpad; + std::array kpad; + + SDL_SysWMmsg wmMsg; + + + std::pair + parse_lang_str(const char* lang) + { + char language[3]; + char country[3]; + int r = SDL_sscanf(lang, "%2[Ca-z]_%2[A-Z]", language, country); + if (r == 2) + return { language, country }; + if (r == 1) + return { language, {} }; + return {}; + } + + std::optional + to_language(const std::string& language, const std::string& country) + { + // two-letter codes, from ISO 639 + if (language == "ja") + return LanguageType::Japanese; + if (language == "en") + return LanguageType::English; + if (language == "fr") + return LanguageType::French; + if (language == "de") + return LanguageType::German; + if (language == "it") + return LanguageType::Italian; + if (language == "es") + return LanguageType::Spanish; + if (language == "zh") { + if (country == "TW") + return LanguageType::TraditionalChinese; + if (country == "CN") + return LanguageType::SimplifiedChinese; + return LanguageType::TraditionalChinese; + } + if (language == "ko") + return LanguageType::Korean; + if (language == "nl") + return LanguageType::Dutch; + if (language == "pt") + return LanguageType::Portuguese; + if (language == "ru") + return LanguageType::Russian; + + // printf("failed to detect language for [%s], [%s]\n", + // language.data(), + // country.data()); + return {}; + } + + std::optional + to_region(const std::string& language, const std::string& country) + { + static const std::array usa_countries = { + "US", "CA", "MX", "BR", + }; + static const std::array eur_countries = { + "DE", "ES", "FR", "GB", "IT", "NL", "PT", "RU", + }; + + static const std::array eur_languages = { + "de", "es", "fr", "it", "nl", "pt", "ru", + }; + + if (country == "JP") + return RegionType::Japan; + + for (auto c : usa_countries) + if (country == c) + return RegionType::USA; + + for (auto c : eur_countries) + if (country == c) + return RegionType::Europe; + + if (country == "CN") + return RegionType::China; + + if (country == "KR" || country == "KP") + return RegionType::Korea; + + if (country == "TW") + return RegionType::Taiwan; + + // If country doesn't match, return a compatible region based on language alone + if (language == "ja") + return RegionType::Japan; + if (language == "en") + return RegionType::USA; + for (auto lang : eur_languages) + if (language == lang) + return RegionType::Europe; + if (language == "zh") + return RegionType::China; + if (language == "ko") + return RegionType::Korea; + + // printf("failed to detect region for [%s], [%s]\n", + // language.data(), + // country.data()); + return {}; + } + + uint32_t + to_keyboard_layout(LanguageType language, + RegionType region) + { + switch (language) { + case LanguageType::Japanese: + return 0; + + case LanguageType::English: + if (region == RegionType::USA) + return 1; + else + return 5; + + case LanguageType::French: + if (region == RegionType::USA) + return 2; + else + return 6; + + case LanguageType::German: + return 7; + + case LanguageType::Italian: + return 8; + + case LanguageType::Spanish: + if (region == RegionType::USA) + return 3; + else + return 9; + + case LanguageType::SimplifiedChinese: + case LanguageType::TraditionalChinese: + case LanguageType::Korean: + return 19; + + case LanguageType::Dutch: + return 10; + + case LanguageType::Portuguese: + if (region == RegionType::USA) + return 4; + else + return 11; + + case LanguageType::Russian: + return 12; + + default: + return 19; + } + } + + std::optional + get_language_from_locale() + { + const char* lang = std::setlocale(LC_CTYPE, nullptr); + if (!lang) + return {}; + if (lang[0] == '\0') // quickly handle empty string + return {}; + if (lang == "C"s) + return {}; + auto [language, country] = parse_lang_str(lang); + return to_language(language, country); + } + + std::optional + get_region_from_locale() + { + const char* lang = std::setlocale(LC_CTYPE, nullptr); + if (!lang) + return {}; + if (lang[0] == '\0') // quickly handle empty string + return {}; + if (lang == "C"s) + return {}; + auto [language, country] = parse_lang_str(lang); + return to_region(language, country); + } + + uint32_t + read_system_config_u32(const char* key) + noexcept + { + UCHandle handle = UCOpen(); + if (handle < 0) { + unsigned result; + alignas(64) UCSysConfig arg{}; + SDL_strlcpy(arg.name, key, sizeof arg.name); + arg.dataType = UC_DATATYPE_UNSIGNED_INT; + arg.dataSize = sizeof result; + arg.data = &result; + auto status = UCReadSysConfig(handle, 1, &arg); + UCClose(handle); + if (status == UC_ERROR_OK) + return result; + } + // DEBUG + // printf("failed to read config %s\n", key); + return -1; + } + + LanguageType + read_system_language() + noexcept + { + auto lang = read_system_config_u32("cafe.language"); + if (lang <= 11) + return static_cast(lang); + return LanguageType::English; + } + + LanguageType + get_language_from_sys() + noexcept + { + static LanguageType cached = read_system_language(); + return cached; + } + + RegionType + read_system_region() + noexcept + { + alignas(64) MCPSysProdSettings settings{}; + MCPError status = 0; + int handle = MCP_Open(); + if (handle < 0) + goto error; + status = MCP_GetSysProdSettings(handle, &settings); + MCP_Close(handle); + if (status) + goto error; + + if (settings.product_area & MCP_REGION_JAPAN) + return RegionType::Japan; + if (settings.product_area & MCP_REGION_USA) + return RegionType::USA; + if (settings.product_area & MCP_REGION_EUROPE) + return RegionType::Europe; + if (settings.product_area & MCP_REGION_CHINA) + return RegionType::China; + if (settings.product_area & MCP_REGION_KOREA) + return RegionType::Korea; + if (settings.product_area & MCP_REGION_TAIWAN) + return RegionType::Taiwan; + + error: + return RegionType::Europe; + } + + + RegionType + get_region_from_sys() + noexcept + { + static RegionType cached = read_system_region(); + return cached; + } + + std::size_t + strlen_16(const char16_t* s) + { + if (!s) + return 0; + std::size_t result = 0; + while (*s++) + ++result; + return result; + } + + std::u8string + to_utf8(const char16_t* input) + { + auto output = SDL_iconv_string("UTF-8", + "UTF-16BE", + reinterpret_cast(input), + 2 * (strlen_16(input) + 1)); + if (!output) + throw std::runtime_error{"SDL_iconv_string() failed"}; + + try { + std::u8string result(reinterpret_cast(output)); + SDL_free(output); + return result; + } + catch (...) { + SDL_free(output); + throw; + } + } + + std::u16string + to_utf16(const char* input) + { + auto output = SDL_iconv_string("UTF-16BE", + "UTF-8", + input, + SDL_strlen(input) + 1); + if (!output) + throw std::runtime_error{"SDL_iconv_string() failed"}; + try { + std::u16string result(reinterpret_cast(output)); + SDL_free(output); + return result; + } + catch (...) { + SDL_free(output); + throw; + } + } + + } // namespace detail +} // namespace + +void WIIU_SWKBD_Initialize(void) +{ + if (detail::initialized) + return; + + try { + if (!detail::fsLib) + detail::fsLib.emplace(); + + if (detail::customCreateArg) + detail::createArg = *detail::customCreateArg; + else { + detail::createArg = {}; + if (auto loc_region = detail::get_region_from_locale()) + detail::createArg.regionType = *loc_region; + else + detail::createArg.regionType = detail::get_region_from_sys(); + } + + std::unique_ptr local_fsClient; + if (!detail::createArg.fsClient) { + local_fsClient = std::make_unique(); + detail::createArg.fsClient = local_fsClient.get(); + } + + std::unique_ptr local_workMemory; + if (!detail::createArg.workMemory) { + local_workMemory = std::make_unique(nn::swkbd::GetWorkMemorySize(0)); + detail::createArg.workMemory = local_workMemory.get(); + } + + if (!nn::swkbd::Create(detail::createArg)) + return; + + detail::workMemory = std::move(local_workMemory); + detail::fsClient = std::move(local_fsClient); + + if (const char* loc = std::setlocale(LC_CTYPE, nullptr)) + detail::createdLocale = loc; + else + detail::createdLocale.clear(); + + detail::initialized = true; + } + catch (...) { + } +} + +__attribute__ (( __destructor__ )) +void WIIU_SWKBD_Finalize(void) +{ + if (!detail::initialized) + return; + + nn::swkbd::Destroy(); + detail::workMemory.reset(); + detail::fsClient.reset(); + detail::currentWindow = nullptr; + detail::createdLocale.clear(); + detail::initialized = false; +} + +SDL_bool WIIU_SWKBD_ConsumeVPAD(const VPADStatus *vpad) +{ + if (!detail::initialized) + return SDL_FALSE; + + nn::swkbd::State state = nn::swkbd::GetStateInputForm(); + if (state != nn::swkbd::State::Visible) + return SDL_FALSE; + + // printf("swkbd is consuming vpad input\n"); + detail::vpad = *vpad; + detail::controllerInfo.vpad = &detail::vpad; + return SDL_TRUE; +} + +SDL_bool WIIU_SWKBD_ConsumeKPAD(KPADChan channel, const KPADStatus *kpad) +{ + if (!detail::initialized) + return SDL_FALSE; + + if (channel < 0 || channel > 3) + return SDL_FALSE; + + nn::swkbd::State state = nn::swkbd::GetStateInputForm(); + if (state != nn::swkbd::State::Visible) + return SDL_FALSE; + + // printf("swkbd is consuming kpad input\n"); + detail::kpad[channel] = *kpad; + detail::controllerInfo.kpad[channel] = &detail::kpad[channel]; + return SDL_TRUE; +} + +void WIIU_SWKBD_Calc(void) +{ + if (!detail::initialized) + return; + + nn::swkbd::Calc(detail::controllerInfo); + detail::controllerInfo = {}; + + // TODO: these could go into a background thread + if (nn::swkbd::IsNeedCalcSubThreadFont()) + nn::swkbd::CalcSubThreadFont(); + if (nn::swkbd::IsNeedCalcSubThreadPredict()) + nn::swkbd::CalcSubThreadPredict(); + + if (detail::currentWindow) { + nn::swkbd::State state = nn::swkbd::GetStateInputForm(); + if (state == nn::swkbd::State::Hidden) + detail::currentWindow = nullptr; + } + + // Check if user confirmed input. + if (nn::swkbd::IsDecideOkButton(nullptr)) { + try { + auto str16 = nn::swkbd::GetInputFormString(); + if (str16) { + auto str8 = detail::to_utf8(str16); + SDL_SendKeyboardText(reinterpret_cast(str8.data())); + } + } + catch (std::exception& e) { + std::printf("could not convert utf-16 to utf-8: %s\n", e.what()); + } + + WIIU_SWKBD_HideScreenKeyboard(nullptr, nullptr); + + // notify application + SDL_VERSION(&detail::wmMsg.version); + detail::wmMsg.subsystem = SDL_SYSWM_WIIU; + detail::wmMsg.msg.wiiu.event = SDL_WIIU_SYSWM_SWKBD_OK_EVENT; + SDL_SendSysWMEvent(&detail::wmMsg); + } + + if (nn::swkbd::IsDecideCancelButton(nullptr)) { + WIIU_SWKBD_HideScreenKeyboard(nullptr, nullptr); + + // notify application + SDL_VERSION(&detail::wmMsg.version); + detail::wmMsg.subsystem = SDL_SYSWM_WIIU; + detail::wmMsg.msg.wiiu.event = SDL_WIIU_SYSWM_SWKBD_CANCEL_EVENT; + SDL_SendSysWMEvent(&detail::wmMsg); + } +} + +void WIIU_SWKBD_Draw(SDL_Window *window) +{ + if (window != detail::currentWindow) + return; + + nn::swkbd::State state = nn::swkbd::GetStateInputForm(); + if (state == nn::swkbd::State::Hidden) + return; + + if (window->flags & SDL_WINDOW_WIIU_TV_ONLY) + nn::swkbd::DrawTV(); + else + nn::swkbd::DrawDRC(); +} + +SDL_bool WIIU_SWKBD_HasScreenKeyboardSupport(_THIS) +{ + return SDL_TRUE; +} + +void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) +{ + // If the locale changed, force it to be constructed again. + if (const char* loc = std::setlocale(LC_CTYPE, nullptr)) { + if (loc != detail::createdLocale) + WIIU_SWKBD_Finalize(); + } + + WIIU_SWKBD_Initialize(); + + if (!detail::currentWindow) + detail::currentWindow = window; + + nn::swkbd::AppearArg arg; + if (detail::appear::customArg) + arg = *detail::appear::customArg; + else { + // Set language + if (auto loc_lang = detail::get_language_from_locale()) + arg.keyboardArg.configArg.languageType = *loc_lang; + else + arg.keyboardArg.configArg.languageType = detail::get_language_from_sys(); + + // set keyboard layout according to language + // TODO: fix wut: it's the unk_0x10 field + arg.keyboardArg.configArg.unk_0x10 + = detail::to_keyboard_layout(arg.keyboardArg.configArg.languageType, + detail::createArg.regionType); + + // Set keyboard mode + arg.keyboardArg.configArg.keyboardMode = detail::appear::keyboardMode; + + // Set OK text + if (!detail::appear::okText.empty()) + arg.keyboardArg.configArg.okString = detail::appear::okText.data(); + + // Set word suggestions + arg.keyboardArg.configArg.showWordSuggestions = detail::appear::showWordSuggestions; + + // Always enable Wiimote pointer + arg.keyboardArg.configArg.drawSysWiiPointer = true; + + // Set initial text + if (!detail::appear::initialText.empty()) + arg.inputFormArg.initialText = detail::appear::initialText.data(); + + // Set hint text + if (!detail::appear::hintText.empty()) + arg.inputFormArg.hintText = detail::appear::hintText.data(); + + // Set password mode + arg.inputFormArg.passwordMode = detail::appear::passwordMode; + + // Set highlight initial text + // Note the typo, must fix this after we fix WUT + arg.inputFormArg.higlightInitialText = detail::appear::highlightInitialText; + + // Set show copy paste buttons + arg.inputFormArg.showCopyPasteButtons = detail::appear::showCopyPasteButtons; + + } + + if (window->flags & SDL_WINDOW_WIIU_TV_ONLY) + arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::WiiRemote0; + else + arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::DrcGamepad; + + nn::swkbd::AppearInputForm(arg); + + // Reset all customization + detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Full; + detail::appear::okText.clear(); + detail::appear::showWordSuggestions = false; + detail::appear::initialText.clear(); + detail::appear::hintText.clear(); + detail::appear::passwordMode = nn::swkbd::PasswordMode::Clear; + detail::appear::highlightInitialText = false; + detail::appear::showCopyPasteButtons = false; +} + +void WIIU_SWKBD_HideScreenKeyboard(_THIS, SDL_Window *) +{ + if (!detail::initialized) + return; + + // printf("hiding swkbd\n"); + nn::swkbd::DisappearInputForm(); + detail::currentWindow = nullptr; +} + +SDL_bool WIIU_SWKBD_IsScreenKeyboardShown(_THIS, SDL_Window *) +{ + if (!detail::initialized) + return SDL_FALSE; + + nn::swkbd::State state = nn::swkbd::GetStateInputForm(); + if (state != nn::swkbd::State::Hidden) + return SDL_TRUE; + + return SDL_FALSE; +} + +void SDL_WiiUSetSWKBDCreateArg(void * arg) +{ + detail::customCreateArg = reinterpret_cast(arg); + // force swkbd to be created again next time it's shown + WIIU_SWKBD_Finalize(); +} + +void SDL_WiiUSetSWKBDAppearArg(void * arg) +{ + detail::appear::customArg = reinterpret_cast(arg); +} + +void SDL_WiiUSetSWKBDKeyboardMode(int mode) +{ + switch (mode) { + case 1: // numpad + detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Numpad; + break; + case 2: // restricted + detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Utf8; + break; + case 3: // NNID + detail::appear::keyboardMode = nn::swkbd::KeyboardMode::NNID; + break; + default: + case 0: // full + detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Full; + } +} + +void SDL_WiiUSetSWKBDOKLabel(const char * label) +{ + if (label) + detail::appear::okText = detail::to_utf16(label); + else + detail::appear::okText.clear(); +} + +void SDL_WiiUSetSWKBDShowWordSuggestions(SDL_bool show) +{ + detail::appear::showWordSuggestions = show; +} + +void SDL_WiiUSetSWKBDInitialText(const char * text) +{ + if (text) + detail::appear::initialText = detail::to_utf16(text); + else + detail::appear::initialText.clear(); +} + +void SDL_WiiUSetSWKBDHintText(const char * text) +{ + if (text) + detail::appear::hintText = detail::to_utf16(text); + else + detail::appear::hintText.clear(); +} + +void SDL_WiiUSetSWKBDPasswordMode(int mode) +{ + switch (mode) { + case 1: + detail::appear::passwordMode = nn::swkbd::PasswordMode::Hide; + break; + case 2: + detail::appear::passwordMode = nn::swkbd::PasswordMode::Fade; + break; + case 0: + default: + detail::appear::passwordMode = nn::swkbd::PasswordMode::Clear; + } +} + +void SDL_WiiUSetSWKBDHighlightInitialText(SDL_bool highlight) +{ + detail::appear::highlightInitialText = highlight; +} + +void SDL_WiiUSetSWKBDShowCopyPasteButtons(SDL_bool show) +{ + detail::appear::showCopyPasteButtons = show; +} diff --git a/src/video/wiiu/SDL_wiiu_swkbd.h b/src/video/wiiu/SDL_wiiu_swkbd.h new file mode 100644 index 0000000000000..341838d0d595a --- /dev/null +++ b/src/video/wiiu/SDL_wiiu_swkbd.h @@ -0,0 +1,57 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Daniel K. O. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_wiiu_swkbd_h +#define SDL_wiiu_swkbd_h + +#include "../../SDL_internal.h" +#include "../SDL_sysvideo.h" + +#include +#include + +#if SDL_VIDEO_DRIVER_WIIU + +#ifdef __cplusplus +extern "C" { +#endif + +void WIIU_SWKBD_Initialize(void); +void WIIU_SWKBD_Finalize(void); + +SDL_bool WIIU_SWKBD_ConsumeVPAD(const VPADStatus *vpad); +SDL_bool WIIU_SWKBD_ConsumeKPAD(KPADChan channel, const KPADStatus *kpad); +void WIIU_SWKBD_Calc(void); + +void WIIU_SWKBD_Draw(SDL_Window *window); + +SDL_bool WIIU_SWKBD_HasScreenKeyboardSupport(_THIS); +void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window); +void WIIU_SWKBD_HideScreenKeyboard(_THIS, SDL_Window *window); +SDL_bool WIIU_SWKBD_IsScreenKeyboardShown(_THIS, SDL_Window *window); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SDL_VIDEO_DRIVER_WIIU */ + +#endif /* SDL_wiiu_swkbd_h */ diff --git a/src/video/wiiu/SDL_wiiukeyboard.c b/src/video/wiiu/SDL_wiiukeyboard.c index f3e39ffb6c420..173d01377efe7 100644 --- a/src/video/wiiu/SDL_wiiukeyboard.c +++ b/src/video/wiiu/SDL_wiiukeyboard.c @@ -21,6 +21,8 @@ #include "SDL_wiiukeyboard.h" +#include "SDL_wiiu_swkbd.h" + #include "../../SDL_internal.h" #include "../../events/SDL_keyboard_c.h" @@ -79,7 +81,7 @@ static void WIIU_SendKeyEventText(KBDKeyEvent *e) utf8[1] = 0x80 | ((symbol >> 6) & 0x3F); utf8[2] = 0x80 | (symbol & 0x3F); } - + SDL_SendKeyboardText((char *)utf8); } @@ -89,17 +91,20 @@ void SDL_WIIU_PumpKeyboardEvents(_THIS) SDL_LockMutex(event_buffer_mutex); - /* process each key event */ - for (i = 0; i < event_buffer.current; i++) { - SDL_SendKeyboardKey( - event_buffer.events[i].isPressedDown ? SDL_PRESSED : SDL_RELEASED, - (SDL_Scancode)event_buffer.events[i].hidCode - ); - - if (event_buffer.events[i].isPressedDown) - WIIU_SendKeyEventText(&event_buffer.events[i]); + /* only generate keyboard and text events if swkbd is not visible */ + if (!WIIU_SWKBD_IsScreenKeyboardShown(NULL, NULL)) { + /* process each key event */ + for (i = 0; i < event_buffer.current; i++) { + SDL_SendKeyboardKey( + event_buffer.events[i].isPressedDown ? SDL_PRESSED : SDL_RELEASED, + (SDL_Scancode)event_buffer.events[i].hidCode + ); + + if (event_buffer.events[i].isPressedDown) + WIIU_SendKeyEventText(&event_buffer.events[i]); + } } - + /* reset the buffer */ event_buffer.current = 0; @@ -130,3 +135,11 @@ int SDL_WIIU_QuitKeyboard(_THIS) return 1; } + +/* + * Local Variables: + * indent-tabs-mode: t + * tab-width: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/video/wiiu/SDL_wiiuvideo.c b/src/video/wiiu/SDL_wiiuvideo.c index 99e02f6d76385..2273bd40d1092 100644 --- a/src/video/wiiu/SDL_wiiuvideo.c +++ b/src/video/wiiu/SDL_wiiuvideo.c @@ -38,6 +38,7 @@ #include "SDL_wiiuvideo.h" #include "SDL_wiiukeyboard.h" #include "SDL_wiiu_gfx_heap.h" +#include "SDL_wiiu_swkbd.h" #include "../../render/wiiu/SDL_render_wiiu.h" @@ -343,6 +344,7 @@ static void WIIU_PumpEvents(_THIS) } SDL_WIIU_PumpKeyboardEvents(_this); + WIIU_SWKBD_Calc(); } static void WIIU_DeleteDevice(SDL_VideoDevice *device) @@ -382,6 +384,11 @@ static SDL_VideoDevice *WIIU_CreateDevice(void) device->SetDisplayMode = WIIU_SetDisplayMode; device->PumpEvents = WIIU_PumpEvents; + device->HasScreenKeyboardSupport = WIIU_SWKBD_HasScreenKeyboardSupport; + device->ShowScreenKeyboard = WIIU_SWKBD_ShowScreenKeyboard; + device->HideScreenKeyboard = WIIU_SWKBD_HideScreenKeyboard; + device->IsScreenKeyboardShown = WIIU_SWKBD_IsScreenKeyboardShown; + device->free = WIIU_DeleteDevice; return device; @@ -393,3 +400,11 @@ VideoBootStrap WIIU_bootstrap = { }; #endif /* SDL_VIDEO_DRIVER_WIIU */ + +/* + * Local Variables: + * indent-tabs-mode: t + * tab-width: 8 + * c-basic-offset: 8 + * End: + */ From 6fba78becbd66d65a50ca77b6abe66901f957bba Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Sun, 6 Apr 2025 10:52:16 -0300 Subject: [PATCH 02/16] Let clang format it. --- src/video/wiiu/SDL_wiiu_swkbd.cpp | 808 +++++++++++++++--------------- src/video/wiiu/SDL_wiiu_swkbd.h | 6 +- 2 files changed, 408 insertions(+), 406 deletions(-) diff --git a/src/video/wiiu/SDL_wiiu_swkbd.cpp b/src/video/wiiu/SDL_wiiu_swkbd.cpp index 61ef9ec8c7d80..3be3941b5b5f4 100644 --- a/src/video/wiiu/SDL_wiiu_swkbd.cpp +++ b/src/video/wiiu/SDL_wiiu_swkbd.cpp @@ -46,423 +46,430 @@ #include "../../events/SDL_keyboard_c.h" using namespace std::string_literals; -using nn::swkbd::RegionType; using nn::swkbd::LanguageType; +using nn::swkbd::RegionType; // #define SDL_WIIU_DONT_REPLACE_NEW_DELETE #ifndef SDL_WIIU_DONT_REPLACE_NEW_DELETE // Override global new/delete operators to use SDL_malloc()/SDL_free() -void* +void * operator new(std::size_t size) { - if (void* data = SDL_malloc(size)) + if (void *data = SDL_malloc(size)) return data; throw std::bad_alloc{}; } -void -operator delete(void* data) - noexcept +void operator delete(void *data) noexcept { SDL_free(data); } #endif // SDL_WIIU_DONT_REPLACE_NEW_DELETE -namespace { - namespace detail { +namespace +{ +namespace detail +{ - struct FSLib { - FSLib() - { - FSInit(); - } +struct FSLib +{ + FSLib() + { + FSInit(); + } - ~FSLib() - { - FSShutdown(); - } + ~FSLib() + { + FSShutdown(); + } +}; - }; +std::optional fsLib; - std::optional fsLib; +struct FSClientWrapper : FSClient +{ - struct FSClientWrapper : FSClient { + FSClientWrapper() + { + auto status = FSAddClient(this, FS_ERROR_FLAG_ALL); + if (status != FS_STATUS_OK) + throw std::runtime_error{ "FSAddClient() failed" }; + } - FSClientWrapper() - { - auto status = FSAddClient(this, FS_ERROR_FLAG_ALL); - if (status != FS_STATUS_OK) - throw std::runtime_error{"FSAddClient() failed"}; - } + // disallow moving + FSClientWrapper(FSClientWrapper &&) = delete; - // disallow moving - FSClientWrapper(FSClientWrapper&&) = delete; + ~FSClientWrapper() + { + FSDelClient(this, FS_ERROR_FLAG_NONE); + } +}; - ~FSClientWrapper() - { - FSDelClient(this, FS_ERROR_FLAG_NONE); - } +bool initialized = false; - }; +std::unique_ptr workMemory; +std::unique_ptr fsClient; +SDL_Window *currentWindow = nullptr; - bool initialized = false; +nn::swkbd::CreateArg createArg; +const nn::swkbd::CreateArg *customCreateArg = nullptr; +std::string createdLocale; - std::unique_ptr workMemory; - std::unique_ptr fsClient; +namespace appear +{ +const nn::swkbd::AppearArg *customArg = nullptr; - SDL_Window *currentWindow = nullptr; +// keyboard config options +nn::swkbd::KeyboardMode keyboardMode = nn::swkbd::KeyboardMode::Full; +std::u16string okText; +bool showWordSuggestions = false; - nn::swkbd::CreateArg createArg; - const nn::swkbd::CreateArg *customCreateArg = nullptr; - std::string createdLocale; +// TODO: control disabled inputs, needs to fix nn::swkbd::ConfigArg - namespace appear { - const nn::swkbd::AppearArg *customArg = nullptr; +// input form options +std::u16string initialText; +std::u16string hintText; +nn::swkbd::PasswordMode passwordMode = nn::swkbd::PasswordMode::Clear; +bool highlightInitialText = false; +bool showCopyPasteButtons = false; +} // namespace appear - // keyboard config options - nn::swkbd::KeyboardMode keyboardMode = nn::swkbd::KeyboardMode::Full; - std::u16string okText; - bool showWordSuggestions = false; +nn::swkbd::ControllerInfo controllerInfo; - // TODO: control disabled inputs, needs to fix nn::swkbd::ConfigArg +VPADStatus vpad; +std::array kpad; - // input form options - std::u16string initialText; - std::u16string hintText; - nn::swkbd::PasswordMode passwordMode = nn::swkbd::PasswordMode::Clear; - bool highlightInitialText = false; - bool showCopyPasteButtons = false; - } +SDL_SysWMmsg wmMsg; - nn::swkbd::ControllerInfo controllerInfo; +std::pair +parse_lang_str(const char *lang) +{ + char language[3]; + char country[3]; + int r = SDL_sscanf(lang, "%2[Ca-z]_%2[A-Z]", language, country); + if (r == 2) + return { language, country }; + if (r == 1) + return { language, {} }; + return {}; +} - VPADStatus vpad; - std::array kpad; +std::optional +to_language(const std::string &language, const std::string &country) +{ + // two-letter codes, from ISO 639 + if (language == "ja") + return LanguageType::Japanese; + if (language == "en") + return LanguageType::English; + if (language == "fr") + return LanguageType::French; + if (language == "de") + return LanguageType::German; + if (language == "it") + return LanguageType::Italian; + if (language == "es") + return LanguageType::Spanish; + if (language == "zh") { + if (country == "TW") + return LanguageType::TraditionalChinese; + if (country == "CN") + return LanguageType::SimplifiedChinese; + return LanguageType::TraditionalChinese; + } + if (language == "ko") + return LanguageType::Korean; + if (language == "nl") + return LanguageType::Dutch; + if (language == "pt") + return LanguageType::Portuguese; + if (language == "ru") + return LanguageType::Russian; + + // printf("failed to detect language for [%s], [%s]\n", + // language.data(), + // country.data()); + return {}; +} + +std::optional +to_region(const std::string &language, const std::string &country) +{ + static const std::array usa_countries = { + "US", + "CA", + "MX", + "BR", + }; + static const std::array eur_countries = { + "DE", + "ES", + "FR", + "GB", + "IT", + "NL", + "PT", + "RU", + }; + + static const std::array eur_languages = { + "de", + "es", + "fr", + "it", + "nl", + "pt", + "ru", + }; + + if (country == "JP") + return RegionType::Japan; + + for (auto c : usa_countries) + if (country == c) + return RegionType::USA; + + for (auto c : eur_countries) + if (country == c) + return RegionType::Europe; - SDL_SysWMmsg wmMsg; + if (country == "CN") + return RegionType::China; + if (country == "KR" || country == "KP") + return RegionType::Korea; - std::pair - parse_lang_str(const char* lang) - { - char language[3]; - char country[3]; - int r = SDL_sscanf(lang, "%2[Ca-z]_%2[A-Z]", language, country); - if (r == 2) - return { language, country }; - if (r == 1) - return { language, {} }; - return {}; - } + if (country == "TW") + return RegionType::Taiwan; - std::optional - to_language(const std::string& language, const std::string& country) - { - // two-letter codes, from ISO 639 - if (language == "ja") - return LanguageType::Japanese; - if (language == "en") - return LanguageType::English; - if (language == "fr") - return LanguageType::French; - if (language == "de") - return LanguageType::German; - if (language == "it") - return LanguageType::Italian; - if (language == "es") - return LanguageType::Spanish; - if (language == "zh") { - if (country == "TW") - return LanguageType::TraditionalChinese; - if (country == "CN") - return LanguageType::SimplifiedChinese; - return LanguageType::TraditionalChinese; - } - if (language == "ko") - return LanguageType::Korean; - if (language == "nl") - return LanguageType::Dutch; - if (language == "pt") - return LanguageType::Portuguese; - if (language == "ru") - return LanguageType::Russian; - - // printf("failed to detect language for [%s], [%s]\n", - // language.data(), - // country.data()); - return {}; - } + // If country doesn't match, return a compatible region based on language alone + if (language == "ja") + return RegionType::Japan; + if (language == "en") + return RegionType::USA; + for (auto lang : eur_languages) + if (language == lang) + return RegionType::Europe; + if (language == "zh") + return RegionType::China; + if (language == "ko") + return RegionType::Korea; + + // printf("failed to detect region for [%s], [%s]\n", + // language.data(), + // country.data()); + return {}; +} - std::optional - to_region(const std::string& language, const std::string& country) - { - static const std::array usa_countries = { - "US", "CA", "MX", "BR", - }; - static const std::array eur_countries = { - "DE", "ES", "FR", "GB", "IT", "NL", "PT", "RU", - }; - - static const std::array eur_languages = { - "de", "es", "fr", "it", "nl", "pt", "ru", - }; - - if (country == "JP") - return RegionType::Japan; - - for (auto c : usa_countries) - if (country == c) - return RegionType::USA; - - for (auto c : eur_countries) - if (country == c) - return RegionType::Europe; - - if (country == "CN") - return RegionType::China; - - if (country == "KR" || country == "KP") - return RegionType::Korea; - - if (country == "TW") - return RegionType::Taiwan; - - // If country doesn't match, return a compatible region based on language alone - if (language == "ja") - return RegionType::Japan; - if (language == "en") - return RegionType::USA; - for (auto lang : eur_languages) - if (language == lang) - return RegionType::Europe; - if (language == "zh") - return RegionType::China; - if (language == "ko") - return RegionType::Korea; - - // printf("failed to detect region for [%s], [%s]\n", - // language.data(), - // country.data()); - return {}; - } +uint32_t +to_keyboard_layout(LanguageType language, + RegionType region) +{ + switch (language) { + case LanguageType::Japanese: + return 0; - uint32_t - to_keyboard_layout(LanguageType language, - RegionType region) - { - switch (language) { - case LanguageType::Japanese: - return 0; - - case LanguageType::English: - if (region == RegionType::USA) - return 1; - else - return 5; - - case LanguageType::French: - if (region == RegionType::USA) - return 2; - else - return 6; - - case LanguageType::German: - return 7; - - case LanguageType::Italian: - return 8; - - case LanguageType::Spanish: - if (region == RegionType::USA) - return 3; - else - return 9; - - case LanguageType::SimplifiedChinese: - case LanguageType::TraditionalChinese: - case LanguageType::Korean: - return 19; - - case LanguageType::Dutch: - return 10; - - case LanguageType::Portuguese: - if (region == RegionType::USA) - return 4; - else - return 11; - - case LanguageType::Russian: - return 12; - - default: - return 19; - } - } + case LanguageType::English: + if (region == RegionType::USA) + return 1; + else + return 5; - std::optional - get_language_from_locale() - { - const char* lang = std::setlocale(LC_CTYPE, nullptr); - if (!lang) - return {}; - if (lang[0] == '\0') // quickly handle empty string - return {}; - if (lang == "C"s) - return {}; - auto [language, country] = parse_lang_str(lang); - return to_language(language, country); - } + case LanguageType::French: + if (region == RegionType::USA) + return 2; + else + return 6; - std::optional - get_region_from_locale() - { - const char* lang = std::setlocale(LC_CTYPE, nullptr); - if (!lang) - return {}; - if (lang[0] == '\0') // quickly handle empty string - return {}; - if (lang == "C"s) - return {}; - auto [language, country] = parse_lang_str(lang); - return to_region(language, country); - } + case LanguageType::German: + return 7; - uint32_t - read_system_config_u32(const char* key) - noexcept - { - UCHandle handle = UCOpen(); - if (handle < 0) { - unsigned result; - alignas(64) UCSysConfig arg{}; - SDL_strlcpy(arg.name, key, sizeof arg.name); - arg.dataType = UC_DATATYPE_UNSIGNED_INT; - arg.dataSize = sizeof result; - arg.data = &result; - auto status = UCReadSysConfig(handle, 1, &arg); - UCClose(handle); - if (status == UC_ERROR_OK) - return result; - } - // DEBUG - // printf("failed to read config %s\n", key); - return -1; - } + case LanguageType::Italian: + return 8; - LanguageType - read_system_language() - noexcept - { - auto lang = read_system_config_u32("cafe.language"); - if (lang <= 11) - return static_cast(lang); - return LanguageType::English; - } + case LanguageType::Spanish: + if (region == RegionType::USA) + return 3; + else + return 9; - LanguageType - get_language_from_sys() - noexcept - { - static LanguageType cached = read_system_language(); - return cached; - } + case LanguageType::SimplifiedChinese: + case LanguageType::TraditionalChinese: + case LanguageType::Korean: + return 19; - RegionType - read_system_region() - noexcept - { - alignas(64) MCPSysProdSettings settings{}; - MCPError status = 0; - int handle = MCP_Open(); - if (handle < 0) - goto error; - status = MCP_GetSysProdSettings(handle, &settings); - MCP_Close(handle); - if (status) - goto error; - - if (settings.product_area & MCP_REGION_JAPAN) - return RegionType::Japan; - if (settings.product_area & MCP_REGION_USA) - return RegionType::USA; - if (settings.product_area & MCP_REGION_EUROPE) - return RegionType::Europe; - if (settings.product_area & MCP_REGION_CHINA) - return RegionType::China; - if (settings.product_area & MCP_REGION_KOREA) - return RegionType::Korea; - if (settings.product_area & MCP_REGION_TAIWAN) - return RegionType::Taiwan; - - error: - return RegionType::Europe; - } + case LanguageType::Dutch: + return 10; + case LanguageType::Portuguese: + if (region == RegionType::USA) + return 4; + else + return 11; - RegionType - get_region_from_sys() - noexcept - { - static RegionType cached = read_system_region(); - return cached; - } + case LanguageType::Russian: + return 12; - std::size_t - strlen_16(const char16_t* s) - { - if (!s) - return 0; - std::size_t result = 0; - while (*s++) - ++result; + default: + return 19; + } +} + +std::optional +get_language_from_locale() +{ + const char *lang = std::setlocale(LC_CTYPE, nullptr); + if (!lang) + return {}; + if (lang[0] == '\0') // quickly handle empty string + return {}; + if (lang == "C"s) + return {}; + auto [language, country] = parse_lang_str(lang); + return to_language(language, country); +} + +std::optional +get_region_from_locale() +{ + const char *lang = std::setlocale(LC_CTYPE, nullptr); + if (!lang) + return {}; + if (lang[0] == '\0') // quickly handle empty string + return {}; + if (lang == "C"s) + return {}; + auto [language, country] = parse_lang_str(lang); + return to_region(language, country); +} + +uint32_t +read_system_config_u32(const char *key) noexcept +{ + UCHandle handle = UCOpen(); + if (handle < 0) { + unsigned result; + alignas(64) UCSysConfig arg{}; + SDL_strlcpy(arg.name, key, sizeof arg.name); + arg.dataType = UC_DATATYPE_UNSIGNED_INT; + arg.dataSize = sizeof result; + arg.data = &result; + auto status = UCReadSysConfig(handle, 1, &arg); + UCClose(handle); + if (status == UC_ERROR_OK) return result; - } + } + // DEBUG + // printf("failed to read config %s\n", key); + return -1; +} - std::u8string - to_utf8(const char16_t* input) - { - auto output = SDL_iconv_string("UTF-8", - "UTF-16BE", - reinterpret_cast(input), - 2 * (strlen_16(input) + 1)); - if (!output) - throw std::runtime_error{"SDL_iconv_string() failed"}; - - try { - std::u8string result(reinterpret_cast(output)); - SDL_free(output); - return result; - } - catch (...) { - SDL_free(output); - throw; - } - } +LanguageType +read_system_language() noexcept +{ + auto lang = read_system_config_u32("cafe.language"); + if (lang <= 11) + return static_cast(lang); + return LanguageType::English; +} - std::u16string - to_utf16(const char* input) - { - auto output = SDL_iconv_string("UTF-16BE", - "UTF-8", - input, - SDL_strlen(input) + 1); - if (!output) - throw std::runtime_error{"SDL_iconv_string() failed"}; - try { - std::u16string result(reinterpret_cast(output)); - SDL_free(output); - return result; - } - catch (...) { - SDL_free(output); - throw; - } - } +LanguageType +get_language_from_sys() noexcept +{ + static LanguageType cached = read_system_language(); + return cached; +} - } // namespace detail +RegionType +read_system_region() noexcept +{ + alignas(64) MCPSysProdSettings settings{}; + MCPError status = 0; + int handle = MCP_Open(); + if (handle < 0) + goto error; + status = MCP_GetSysProdSettings(handle, &settings); + MCP_Close(handle); + if (status) + goto error; + + if (settings.product_area & MCP_REGION_JAPAN) + return RegionType::Japan; + if (settings.product_area & MCP_REGION_USA) + return RegionType::USA; + if (settings.product_area & MCP_REGION_EUROPE) + return RegionType::Europe; + if (settings.product_area & MCP_REGION_CHINA) + return RegionType::China; + if (settings.product_area & MCP_REGION_KOREA) + return RegionType::Korea; + if (settings.product_area & MCP_REGION_TAIWAN) + return RegionType::Taiwan; + +error: + return RegionType::Europe; +} + +RegionType +get_region_from_sys() noexcept +{ + static RegionType cached = read_system_region(); + return cached; +} + +std::size_t +strlen_16(const char16_t *s) +{ + if (!s) + return 0; + std::size_t result = 0; + while (*s++) + ++result; + return result; +} + +std::u8string +to_utf8(const char16_t *input) +{ + auto output = SDL_iconv_string("UTF-8", + "UTF-16BE", + reinterpret_cast(input), + 2 * (strlen_16(input) + 1)); + if (!output) + throw std::runtime_error{ "SDL_iconv_string() failed" }; + + try { + std::u8string result(reinterpret_cast(output)); + SDL_free(output); + return result; + } catch (...) { + SDL_free(output); + throw; + } +} + +std::u16string +to_utf16(const char *input) +{ + auto output = SDL_iconv_string("UTF-16BE", + "UTF-8", + input, + SDL_strlen(input) + 1); + if (!output) + throw std::runtime_error{ "SDL_iconv_string() failed" }; + try { + std::u16string result(reinterpret_cast(output)); + SDL_free(output); + return result; + } catch (...) { + SDL_free(output); + throw; + } +} + +} // namespace detail } // namespace void WIIU_SWKBD_Initialize(void) @@ -500,21 +507,19 @@ void WIIU_SWKBD_Initialize(void) return; detail::workMemory = std::move(local_workMemory); - detail::fsClient = std::move(local_fsClient); + detail::fsClient = std::move(local_fsClient); - if (const char* loc = std::setlocale(LC_CTYPE, nullptr)) + if (const char *loc = std::setlocale(LC_CTYPE, nullptr)) detail::createdLocale = loc; else detail::createdLocale.clear(); detail::initialized = true; - } - catch (...) { + } catch (...) { } } -__attribute__ (( __destructor__ )) -void WIIU_SWKBD_Finalize(void) +__attribute__((__destructor__)) void WIIU_SWKBD_Finalize(void) { if (!detail::initialized) return; @@ -586,10 +591,9 @@ void WIIU_SWKBD_Calc(void) auto str16 = nn::swkbd::GetInputFormString(); if (str16) { auto str8 = detail::to_utf8(str16); - SDL_SendKeyboardText(reinterpret_cast(str8.data())); + SDL_SendKeyboardText(reinterpret_cast(str8.data())); } - } - catch (std::exception& e) { + } catch (std::exception &e) { std::printf("could not convert utf-16 to utf-8: %s\n", e.what()); } @@ -636,7 +640,7 @@ SDL_bool WIIU_SWKBD_HasScreenKeyboardSupport(_THIS) void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) { // If the locale changed, force it to be constructed again. - if (const char* loc = std::setlocale(LC_CTYPE, nullptr)) { + if (const char *loc = std::setlocale(LC_CTYPE, nullptr)) { if (loc != detail::createdLocale) WIIU_SWKBD_Finalize(); } @@ -658,9 +662,8 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) // set keyboard layout according to language // TODO: fix wut: it's the unk_0x10 field - arg.keyboardArg.configArg.unk_0x10 - = detail::to_keyboard_layout(arg.keyboardArg.configArg.languageType, - detail::createArg.regionType); + arg.keyboardArg.configArg.unk_0x10 = detail::to_keyboard_layout(arg.keyboardArg.configArg.languageType, + detail::createArg.regionType); // Set keyboard mode arg.keyboardArg.configArg.keyboardMode = detail::appear::keyboardMode; @@ -692,7 +695,6 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) // Set show copy paste buttons arg.inputFormArg.showCopyPasteButtons = detail::appear::showCopyPasteButtons; - } if (window->flags & SDL_WINDOW_WIIU_TV_ONLY) @@ -735,37 +737,37 @@ SDL_bool WIIU_SWKBD_IsScreenKeyboardShown(_THIS, SDL_Window *) return SDL_FALSE; } -void SDL_WiiUSetSWKBDCreateArg(void * arg) +void SDL_WiiUSetSWKBDCreateArg(void *arg) { - detail::customCreateArg = reinterpret_cast(arg); + detail::customCreateArg = reinterpret_cast(arg); // force swkbd to be created again next time it's shown WIIU_SWKBD_Finalize(); } -void SDL_WiiUSetSWKBDAppearArg(void * arg) +void SDL_WiiUSetSWKBDAppearArg(void *arg) { - detail::appear::customArg = reinterpret_cast(arg); + detail::appear::customArg = reinterpret_cast(arg); } void SDL_WiiUSetSWKBDKeyboardMode(int mode) { switch (mode) { - case 1: // numpad - detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Numpad; - break; - case 2: // restricted - detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Utf8; - break; - case 3: // NNID - detail::appear::keyboardMode = nn::swkbd::KeyboardMode::NNID; - break; - default: - case 0: // full - detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Full; + case 1: // numpad + detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Numpad; + break; + case 2: // restricted + detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Utf8; + break; + case 3: // NNID + detail::appear::keyboardMode = nn::swkbd::KeyboardMode::NNID; + break; + default: + case 0: // full + detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Full; } } -void SDL_WiiUSetSWKBDOKLabel(const char * label) +void SDL_WiiUSetSWKBDOKLabel(const char *label) { if (label) detail::appear::okText = detail::to_utf16(label); @@ -778,7 +780,7 @@ void SDL_WiiUSetSWKBDShowWordSuggestions(SDL_bool show) detail::appear::showWordSuggestions = show; } -void SDL_WiiUSetSWKBDInitialText(const char * text) +void SDL_WiiUSetSWKBDInitialText(const char *text) { if (text) detail::appear::initialText = detail::to_utf16(text); @@ -786,7 +788,7 @@ void SDL_WiiUSetSWKBDInitialText(const char * text) detail::appear::initialText.clear(); } -void SDL_WiiUSetSWKBDHintText(const char * text) +void SDL_WiiUSetSWKBDHintText(const char *text) { if (text) detail::appear::hintText = detail::to_utf16(text); @@ -797,15 +799,15 @@ void SDL_WiiUSetSWKBDHintText(const char * text) void SDL_WiiUSetSWKBDPasswordMode(int mode) { switch (mode) { - case 1: - detail::appear::passwordMode = nn::swkbd::PasswordMode::Hide; - break; - case 2: - detail::appear::passwordMode = nn::swkbd::PasswordMode::Fade; - break; - case 0: - default: - detail::appear::passwordMode = nn::swkbd::PasswordMode::Clear; + case 1: + detail::appear::passwordMode = nn::swkbd::PasswordMode::Hide; + break; + case 2: + detail::appear::passwordMode = nn::swkbd::PasswordMode::Fade; + break; + case 0: + default: + detail::appear::passwordMode = nn::swkbd::PasswordMode::Clear; } } diff --git a/src/video/wiiu/SDL_wiiu_swkbd.h b/src/video/wiiu/SDL_wiiu_swkbd.h index 341838d0d595a..026491a435ddb 100644 --- a/src/video/wiiu/SDL_wiiu_swkbd.h +++ b/src/video/wiiu/SDL_wiiu_swkbd.h @@ -25,8 +25,8 @@ #include "../../SDL_internal.h" #include "../SDL_sysvideo.h" -#include #include +#include #if SDL_VIDEO_DRIVER_WIIU @@ -44,8 +44,8 @@ void WIIU_SWKBD_Calc(void); void WIIU_SWKBD_Draw(SDL_Window *window); SDL_bool WIIU_SWKBD_HasScreenKeyboardSupport(_THIS); -void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window); -void WIIU_SWKBD_HideScreenKeyboard(_THIS, SDL_Window *window); +void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window); +void WIIU_SWKBD_HideScreenKeyboard(_THIS, SDL_Window *window); SDL_bool WIIU_SWKBD_IsScreenKeyboardShown(_THIS, SDL_Window *window); #ifdef __cplusplus From f4ee74aac6954f38f2330088530525720390c8ed Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Thu, 10 Apr 2025 12:01:50 -0300 Subject: [PATCH 03/16] - Cleaned up code. - No more replacement of global new/delete operators; use strings with custom allocators instead. - Don't rely on C locale API, let the user control the locale explicitly. - A few more customization functions. - Properly documented all functions. - Added enums. - Input feeding from VPAD and KPAD is now public. --- .clang-format | 3 + include/SDL_system.h | 225 +++++- src/joystick/wiiu/SDL_wiiujoystick.c | 5 +- src/video/wiiu/SDL_cpp_allocator.h | 81 ++ src/video/wiiu/SDL_wiiu_swkbd.cpp | 1117 ++++++++++++++------------ src/video/wiiu/SDL_wiiu_swkbd.h | 2 - 6 files changed, 887 insertions(+), 546 deletions(-) create mode 100644 src/video/wiiu/SDL_cpp_allocator.h diff --git a/.clang-format b/.clang-format index e98e5cd70af8d..3105e5c0f756d 100644 --- a/.clang-format +++ b/.clang-format @@ -34,6 +34,7 @@ BraceWrapping: AfterStruct: true AfterUnion: true AfterExternBlock: false + BeforeCatch: true BeforeElse: false BeforeWhile: false IndentBraces: false @@ -62,6 +63,8 @@ IndentGotoLabels: true IndentPPDirectives: None IndentExternBlock: NoIndent +NamespaceIndentation: All + PointerAlignment: Right SpaceAfterCStyleCast: false SpacesInCStyleCastParentheses: false diff --git a/include/SDL_system.h b/include/SDL_system.h index c9980a76cfd3a..d262bdfd1e96d 100644 --- a/include/SDL_system.h +++ b/include/SDL_system.h @@ -620,43 +620,234 @@ typedef enum SDL_WiiUSysWMEventType { } SDL_WiiUSysWMEventType; /** - * Set a pointer to a nn::swkbd::CreateArg object that will be used to create the swkbd. + * Sets a nn::swkbd::CreateArg object that will be used to create the swkbd. + * + * You don't need to set `arg->fsClient` nor `arg->workMemory`, these will be + * allocated if they are `NULL`, when the swkbd is first shown. * - * \param createArg a pointer to a persistent nn::swkbd::CreateArg object, or NULL to use the default. + * \param arg Pointer to a persistent nn::swkbd::CreateArg object, or `NULL` to use the + * default. + * + * \note + * The swkbd will be re-initialized after calling this function. */ -extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDCreateArg(void * createArg); +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDCreateArg(void * arg); /** - * Set a pointer to a nn::swkbd::AppearArg object that will be used every time - * the swkbd is shown. + * Sets a nn::swkbd::AppearArg object that will be used every time the swkbd is shown. + * + * \param arg Pointer to a persistent nn::swkbd::AppearArg object, or `NULL` to use the + * default. * - * \param appearArg a pointer to a persistent nn::swkbd::AppearArg object, or NULL to use the default. + * \sa SDL_WiiUSetSWKBDKeyboardMode + * \sa SDL_WiiUSetSWKBDOKLabel + * \sa SDL_WiiUSetSWKBDShowWordSuggestions + * \sa SDL_WiiUSetSWKBDInitialText + * \sa SDL_WiiUSetSWKBDInitialText + * \sa SDL_WiiUSetSWKBDHintText + * \sa SDL_WiiUSetSWKBDPasswordMode + * \sa SDL_WiiUSetSWKBDHighlightInitialText + * \sa SDL_WiiUSetSWKBDShowCopyPasteButtons */ -extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDAppearArg(void * appearArg); +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDAppearArg(void * arg); -// TODO: write docs -extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDKeyboardMode(int mode); +/** + * Select the swkbd keyboard mode. + * + * \sa SDL_WiiUSetSWKBDKeyboardMode + */ +typedef enum SDL_WiiUSWKBDKeyboardMode { + /** Full keyboard. */ + SDL_WIIU_SWKBD_KEYBOARD_MODE_FULL, + /** Numeric keyboard. */ + SDL_WIIU_SWKBD_KEYBOARD_MODE_NUMPAD, + /** Restricted keyboard (only letters, numbers and symbols.) */ + SDL_WIIU_SWKBD_KEYBOARD_MODE_RESTRICTED, + /** NNID keyboard. */ + SDL_WIIU_SWKBD_KEYBOARD_MODE_NNID +} SDL_WiiUSWKBDKeyboardMode; -// TODO: write docs +/** + * Sets the swkbd's keyboard mode. + * + * \param mode One of SDL_WiiUSWKBDKeyboardMode. The default is + * `SDL_WIIU_SWKBD_KEYBOARD_MODE_FULL`. + * + * \note + * This option is reset to the default value after the swkbd is shown, and is ignored if + * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. + * + * \sa SDL_WiiUSetSWKBDAppearArg + */ +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDKeyboardMode(SDL_WiiUSWKBDKeyboardMode mode); + +/** + * Sets the label for the swkbd's "OK" button. + * + * \param label String for the "OK" button, encoded in UTF-8, or `NULL` to use the + * default. + * + * \note + * This option is reset to the default value after the swkbd is shown, and is ignored if + * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. + * + * \sa SDL_WiiUSetSWKBDAppearArg + */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDOKLabel(const char * label); -// TODO: write docs +/** + * Sets the swkbd's word suggestions option. + * + * \param show `SDL_TRUE` to enable word suggestions. The default is `SDL_TRUE`. + * + * \note + * This option is reset to the default value after the swkbd is shown, and is ignored if + * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. + * + * \sa SDL_WiiUSetSWKBDAppearArg + */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDShowWordSuggestions(SDL_bool show); -// TODO: write docs +/** + * Sets the swkbd's initial text. + * + * \param text String for the initial text, encoded in UTF-8, or `NULL` to use the + * default (no initial text.) + * + * \note + * This option is reset to the default value after the swkbd is shown, and is ignored if + * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. + * + * \sa SDL_WiiUSetSWKBDAppearArg + */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDInitialText(const char * text); -// TODO: write docs +/** + * Sets the swkbd's hint text. + * + * \param text String for the hint text, encoded in UTF-8, or `NULL` to use the default + * (no hint.) + * + * \note + * This option is reset to the default value after the swkbd is shown, and is ignored if + * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. + * + * \sa SDL_WiiUSetSWKBDAppearArg + */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDHintText(const char * text); -// TODO: write docs -extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDPasswordMode(int mode); +/** + * Controls how to display passwwords in the swkbd when in password mode. + */ +typedef enum SDL_WiiUSWKBDPasswordMode { + SDL_WIIU_SWKBD_PASSWORD_MODE_SHOW, /**< Show password. */ + SDL_WIIU_SWKBD_PASSWORD_MODE_HIDE, /**< Hide password. */ + SDL_WIIU_SWKBD_PASSWORD_MODE_FADE /**< Hide password after 1 second. */ +} SDL_WiiUSWKBDPasswordMode; -// TODO: write docs +/** + * Sets the swkbd's password mode. + * + * \param mode One of of the SDL_WiiUSWKBDPasswordMode values. The default is + * `SDL_WIIU_SWKBD_PASSWORD_MODE_SHOW`. + * + * \note + * This option is reset to the default value after the swkbd is shown, and is ignored if + * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. + * + * \sa SDL_WiiUSetSWKBDAppearArg + * \sa SDL_WiiUSetSWKBDKeyboardMode + */ +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDPasswordMode(SDL_WiiUSWKBDPasswordMode mode); + +/** + * Sets whether to highlight (select) the swkbd's initial text. + * + * \param highlight `SDL_TRUE` to highlight the initial text. The default is `SDL_FALSE`. + + * \note + * This option is reset to the default value after the swkbd is shown, and is ignored if + * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. + * + * \sa SDL_WiiUSetSWKBDAppearArg + */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDHighlightInitialText(SDL_bool highlight); -// TODO: write docs +/** + * Sets the swkbd's copy-paste button option. + * + * \param show `SDL_TRUE` to show copy-paste buttons. The default is `SDL_FALSE`. + * + * \note + * This option is reset to the default value after the swkbd is shown, and is ignored if + * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. + * + * \sa SDL_WiiUSetSWKBDAppearArg + */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDShowCopyPasteButtons(SDL_bool show); + +/** + * Sets the swkbd's built-in rendering of the Wii remote pointer. + * + * \param draw `SDL_TRUE` to let the swkbd draw its own Wii remote pointer. The default is + * `SDL_TRUE`. + * + * This option is reset to the default value after the swkbd is shown, and is ignored if + * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. + * + * \sa SDL_WiiUSetSWKBDAppearArg + */ +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDDrawWiiPointer(SDL_bool draw); + +/** + * Sets the swkbd's locale (region and language.) + * + * \param locale String representing the intended keyboard region and language, using + * Unix-style locale format (e.g. `"en_US"`, `"fr_CA"`, `ja_JP`, `"en"`.) Set to `NULL` to + * use the system region and language. The default is `NULL`. + * + * \note + * The swkbd will be re-initialized after calling this function. + * + * \note + * Calling SDL_WiiUSetSWKBDCreateArg with a non-`NULL` argument will override the region. + * + * \note + * Calling SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument will override the + * language. + * + * \sa SDL_WiiUSetSWKBDCreateArg + * \sa SDL_WiiUSetSWKBDAppearArg + */ +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDLocale(const char * locale); + +/** + * Sends VPAD input to the swkbd. + * + * Call this function at every frame if your application calls `VPADRead()` instead of + * using the SDL game controller subsystem. + * + * \param vpad Pointer to a `VPADStatus` object. + * \returns `SDL_TRUE` if the swkbd is visible and is going to use the input. + * + * \note + * The touch point in `tpNormal` should contain calibrated point, by calling + * `VPADGetTPCalibratedPoint()`, prior to calling this function. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_WiiUSetSWKBDVPAD(const void * vpad); + +/** + * Sends KPAD input to the swkbd. + * + * Call this function at every frame if you call `KPADRead()` or `KPADReadEx()` in your + * application, instead of using the SDL game controller subsystem. + * + * \param channel Number of channel. + * \param kpad Pointer to a `KPADStatus` object. + * \returns `SDL_TRUE` if the swkbd is visible and is going to use the input. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_WiiUSetSWKBDKPAD(int channel, const void * kpad); + #endif /* Wii U */ /* Ends C function definitions when using C++ */ diff --git a/src/joystick/wiiu/SDL_wiiujoystick.c b/src/joystick/wiiu/SDL_wiiujoystick.c index 0d9c4a844f5d4..d47d1ed2df991 100644 --- a/src/joystick/wiiu/SDL_wiiujoystick.c +++ b/src/joystick/wiiu/SDL_wiiujoystick.c @@ -37,6 +37,7 @@ #include "SDL_log.h" #include "SDL_assert.h" #include "SDL_events.h" +#include "SDL_system.h" #include "SDL_wiiujoystick.h" #include "../../video/SDL_sysvideo.h" @@ -481,7 +482,7 @@ static void WIIU_JoystickUpdate(SDL_Joystick *joystick) /* touchscreen */ VPADGetTPCalibratedPoint(VPAD_CHAN_0, &vpad.tpNormal, &vpad.tpNormal); - if (WIIU_SWKBD_ConsumeVPAD(&vpad)) { + if (SDL_WiiUSetSWKBDVPAD(&vpad)) { /* Do not generate SDL events when the swkbd is consuming the input. */ return; } @@ -563,7 +564,7 @@ static void WIIU_JoystickUpdate(SDL_Joystick *joystick) if (read != 1 || err != KPAD_ERROR_OK) return; - if (WIIU_SWKBD_ConsumeKPAD(WIIU_WPAD_CHAN(wiiu_device), &kpad)) { + if (SDL_WiiUSetSWKBDKPAD(WIIU_WPAD_CHAN(wiiu_device), &kpad)) { /* Do not generate SDL events when the swkbd is consuming the input. */ return; } diff --git a/src/video/wiiu/SDL_cpp_allocator.h b/src/video/wiiu/SDL_cpp_allocator.h new file mode 100644 index 0000000000000..2252916a1b3c9 --- /dev/null +++ b/src/video/wiiu/SDL_cpp_allocator.h @@ -0,0 +1,81 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Daniel K. O. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_cpp_allocator_h +#define SDL_cpp_allocator_h + +#include +#include +#include + +#include "SDL_stdinc.h" + +/* + * A custom allocator to use with C++ standard library containers. + * + * All allocations are done through SDL_malloc() and SDL_free(). + */ +namespace +{ + namespace sdl + { + template + struct allocator + { + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + using propagate_on_container_move_assignment = std::true_type; + + T * + allocate(std::size_t count) + { + auto ptr = reinterpret_cast(SDL_malloc(count * sizeof(T))); + if (!ptr) + throw std::bad_alloc{}; + return ptr; + } + + void + deallocate(T *ptr, std::size_t /*count*/) + { + SDL_free(ptr); + } + + constexpr bool + operator==(const allocator &) noexcept + { + return true; + } + }; + + } // namespace sdl + +} // namespace + +#endif + +/* + * Local Variables: + * mode: c++ + * End: + */ diff --git a/src/video/wiiu/SDL_wiiu_swkbd.cpp b/src/video/wiiu/SDL_wiiu_swkbd.cpp index 3be3941b5b5f4..56f47c7d23b17 100644 --- a/src/video/wiiu/SDL_wiiu_swkbd.cpp +++ b/src/video/wiiu/SDL_wiiu_swkbd.cpp @@ -11,17 +11,15 @@ freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. + misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include -#include -#include #include #include #include @@ -30,6 +28,7 @@ #include #include #include +#include #include "SDL_wiiu_swkbd.h" @@ -38,6 +37,7 @@ #include #include +#include "SDL_log.h" #include "SDL_stdinc.h" #include "SDL_system.h" #include "SDL_syswm.h" @@ -45,529 +45,521 @@ #include "../../events/SDL_events_c.h" #include "../../events/SDL_keyboard_c.h" -using namespace std::string_literals; +#include "SDL_cpp_allocator.h" + using nn::swkbd::LanguageType; using nn::swkbd::RegionType; -// #define SDL_WIIU_DONT_REPLACE_NEW_DELETE - -#ifndef SDL_WIIU_DONT_REPLACE_NEW_DELETE -// Override global new/delete operators to use SDL_malloc()/SDL_free() -void * -operator new(std::size_t size) -{ - if (void *data = SDL_malloc(size)) - return data; - throw std::bad_alloc{}; -} - -void operator delete(void *data) noexcept -{ - SDL_free(data); -} -#endif // SDL_WIIU_DONT_REPLACE_NEW_DELETE - namespace { -namespace detail -{ -struct FSLib -{ - FSLib() + namespace detail { - FSInit(); - } - - ~FSLib() - { - FSShutdown(); - } -}; - -std::optional fsLib; - -struct FSClientWrapper : FSClient -{ - - FSClientWrapper() - { - auto status = FSAddClient(this, FS_ERROR_FLAG_ALL); - if (status != FS_STATUS_OK) - throw std::runtime_error{ "FSAddClient() failed" }; - } - - // disallow moving - FSClientWrapper(FSClientWrapper &&) = delete; - - ~FSClientWrapper() - { - FSDelClient(this, FS_ERROR_FLAG_NONE); - } -}; - -bool initialized = false; - -std::unique_ptr workMemory; -std::unique_ptr fsClient; - -SDL_Window *currentWindow = nullptr; -nn::swkbd::CreateArg createArg; -const nn::swkbd::CreateArg *customCreateArg = nullptr; -std::string createdLocale; - -namespace appear -{ -const nn::swkbd::AppearArg *customArg = nullptr; + // Make sure strings are allocated with SDL_malloc() + template + using basic_string = std::basic_string, sdl::allocator>; + + using string = basic_string; + using u8string = basic_string; + using u16string = basic_string; + + // We use vector to store the work memory + template + using vector = std::vector>; + + template + struct SDL_Deleter + { + void + operator()(T *ptr) + const + { + if (ptr) { + ptr->~T(); + SDL_free(ptr); + } + } + }; + + template + using unique_ptr = std::unique_ptr>; + + template + unique_ptr + make_unique(Args &&...args) + { + T *ptr = reinterpret_cast(SDL_malloc(sizeof(T))); + if (!ptr) + throw std::bad_alloc{}; + try { + new (ptr) T(std::forward(args)...); + } + catch (...) { + SDL_free(ptr); + throw; + } + return unique_ptr{ ptr }; + } -// keyboard config options -nn::swkbd::KeyboardMode keyboardMode = nn::swkbd::KeyboardMode::Full; -std::u16string okText; -bool showWordSuggestions = false; + // RAII class to keep the FS module initialized. + struct FSLib + { + FSLib() + { + FSInit(); + } -// TODO: control disabled inputs, needs to fix nn::swkbd::ConfigArg + ~FSLib() + { + FSShutdown(); + } + }; + std::optional fsLib; -// input form options -std::u16string initialText; -std::u16string hintText; -nn::swkbd::PasswordMode passwordMode = nn::swkbd::PasswordMode::Clear; -bool highlightInitialText = false; -bool showCopyPasteButtons = false; -} // namespace appear + struct FSClientWrapper : FSClient + { -nn::swkbd::ControllerInfo controllerInfo; + FSClientWrapper() + { + auto status = FSAddClient(this, FS_ERROR_FLAG_ALL); + if (status != FS_STATUS_OK) + throw std::runtime_error{ "FSAddClient() failed" }; + } -VPADStatus vpad; -std::array kpad; + // disallow moving + FSClientWrapper(FSClientWrapper &&) = delete; -SDL_SysWMmsg wmMsg; + ~FSClientWrapper() + { + FSDelClient(this, FS_ERROR_FLAG_NONE); + } + }; + + namespace create + { + + // We cannot call nn::swkbd functions unless a keyboard is created, so we keep + // track of it here. + bool created = false; + + const nn::swkbd::CreateArg *customArg = nullptr; + unique_ptr fsClient; + vector workMemory; + std::optional region; // store region used to create keyboard + + } // namespace create + + namespace appear + { + + const nn::swkbd::AppearArg *customArg = nullptr; + // Keep track of wich window has the swkbd. + SDL_Window *window = nullptr; + // keyboard config options + nn::swkbd::KeyboardMode keyboardMode = nn::swkbd::KeyboardMode::Full; + u16string okText; + bool showWordSuggestions = true; + + // TODO: control disabled inputs, needs to fix nn::swkbd::ConfigArg + + // input form options + u16string initialText; + u16string hintText; + nn::swkbd::PasswordMode passwordMode = nn::swkbd::PasswordMode::Clear; + bool highlightInitialText = false; + bool showCopyPasteButtons = false; + bool drawWiiPointer = true; + + } // namespace appear + + string swkbdLocale; + + nn::swkbd::ControllerInfo controllerInfo; + + VPADStatus vpad; + std::array kpad; + + SDL_SysWMmsg wmMsg; + + // return language, country pair + std::pair + parse_locale(const string &locale) + { + if (locale.empty()) + return {}; + char language[3]; + char country[3]; + int r = SDL_sscanf(locale.data(), "%2[Ca-z]_%2[A-Z]", language, country); + if (r == 2) + return { language, country }; + if (r == 1) + return { language, {} }; + return {}; + } -std::pair -parse_lang_str(const char *lang) -{ - char language[3]; - char country[3]; - int r = SDL_sscanf(lang, "%2[Ca-z]_%2[A-Z]", language, country); - if (r == 2) - return { language, country }; - if (r == 1) - return { language, {} }; - return {}; -} + std::optional + to_language(const string &language, + const string &country) + { + // two-letter codes, from ISO 639 + if (language == "ja") + return LanguageType::Japanese; + if (language == "en") + return LanguageType::English; + if (language == "fr") + return LanguageType::French; + if (language == "de") + return LanguageType::German; + if (language == "it") + return LanguageType::Italian; + if (language == "es") + return LanguageType::Spanish; +#if 0 + // Chinese and Korean languages seem to crash the swkbd. + if (language == "zh") { + if (country == "TW") + return LanguageType::TraditionalChinese; + if (country == "CN") + return LanguageType::SimplifiedChinese; + return LanguageType::TraditionalChinese; + } + if (language == "ko") + return LanguageType::Korean; +#endif + if (language == "nl") + return LanguageType::Dutch; + if (language == "pt") + return LanguageType::Portuguese; + if (language == "ru") + return LanguageType::Russian; + return {}; + } -std::optional -to_language(const std::string &language, const std::string &country) -{ - // two-letter codes, from ISO 639 - if (language == "ja") - return LanguageType::Japanese; - if (language == "en") - return LanguageType::English; - if (language == "fr") - return LanguageType::French; - if (language == "de") - return LanguageType::German; - if (language == "it") - return LanguageType::Italian; - if (language == "es") - return LanguageType::Spanish; - if (language == "zh") { - if (country == "TW") - return LanguageType::TraditionalChinese; - if (country == "CN") - return LanguageType::SimplifiedChinese; - return LanguageType::TraditionalChinese; - } - if (language == "ko") - return LanguageType::Korean; - if (language == "nl") - return LanguageType::Dutch; - if (language == "pt") - return LanguageType::Portuguese; - if (language == "ru") - return LanguageType::Russian; - - // printf("failed to detect language for [%s], [%s]\n", - // language.data(), - // country.data()); - return {}; -} + std::optional + to_region(const string &language, + const string &country) + { + static const std::array usa_countries = { + "US", + "CA", + "MX", + "BR", + }; + static const std::array eur_countries = { + "DE", + "ES", + "FR", + "GB", + "IT", + "NL", + "PT", + "RU", + }; + static const std::array eur_languages = { + "de", + "es", + "fr", + "it", + "nl", + "pt", + "ru", + }; + + if (country == "JP") + return RegionType::Japan; + + for (auto c : usa_countries) + if (country == c) + return RegionType::USA; + + for (auto c : eur_countries) + if (country == c) + return RegionType::Europe; + +#if 0 + // China, Korea and Taiwan seem to crash the swkbd. + if (country == "CN") + return RegionType::China; + + if (country == "KR" || country == "KP") + return RegionType::Korea; + + if (country == "TW") + return RegionType::Taiwan; +#endif + + // If country doesn't match, return a compatible region based on language alone + if (language == "ja") + return RegionType::Japan; + if (language == "en") + return RegionType::USA; + for (auto lang : eur_languages) + if (language == lang) + return RegionType::Europe; +#if 0 + // China and Korea seem to crash the swkbd. + if (language == "zh") + return RegionType::China; + if (language == "ko") + return RegionType::Korea; +#endif + + return {}; + } -std::optional -to_region(const std::string &language, const std::string &country) -{ - static const std::array usa_countries = { - "US", - "CA", - "MX", - "BR", - }; - static const std::array eur_countries = { - "DE", - "ES", - "FR", - "GB", - "IT", - "NL", - "PT", - "RU", - }; - - static const std::array eur_languages = { - "de", - "es", - "fr", - "it", - "nl", - "pt", - "ru", - }; - - if (country == "JP") - return RegionType::Japan; - - for (auto c : usa_countries) - if (country == c) - return RegionType::USA; - - for (auto c : eur_countries) - if (country == c) - return RegionType::Europe; + uint32_t + to_keyboard_layout(LanguageType language, + RegionType region) + { + switch (language) { + case LanguageType::Japanese: + return 0; + + case LanguageType::English: + if (region == RegionType::USA) + return 1; + else + return 5; + + case LanguageType::French: + if (region == RegionType::USA) + return 2; + else + return 6; + + case LanguageType::German: + return 7; + + case LanguageType::Italian: + return 8; + + case LanguageType::Spanish: + if (region == RegionType::USA) + return 3; + else + return 9; + + case LanguageType::Dutch: + return 10; + + case LanguageType::Portuguese: + if (region == RegionType::USA) + return 4; + else + return 11; + + case LanguageType::Russian: + return 12; + + default: + return 19; + } + } - if (country == "CN") - return RegionType::China; + uint32_t + read_system_config_u32(const char *key) noexcept + { + UCHandle handle = UCOpen(); + if (handle < 0) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, + "UCOpen() returned: %d\n", handle); + return -1; + } + unsigned result; + alignas(0x40) UCSysConfig arg{}; + SDL_strlcpy(arg.name, key, sizeof arg.name); + arg.dataType = UC_DATATYPE_UNSIGNED_INT; + arg.dataSize = sizeof result; + arg.data = &result; + auto status = UCReadSysConfig(handle, 1, &arg); + UCClose(handle); + if (status == UC_ERROR_OK) + return result; + else + return -1; + } - if (country == "KR" || country == "KP") - return RegionType::Korea; + LanguageType + read_system_language() noexcept + { + auto lang = read_system_config_u32("cafe.language"); + if (lang <= 11) + return static_cast(lang); + return LanguageType::English; + } - if (country == "TW") - return RegionType::Taiwan; + LanguageType + get_language_from_system() noexcept + { + static LanguageType cached = read_system_language(); + return cached; + } - // If country doesn't match, return a compatible region based on language alone - if (language == "ja") - return RegionType::Japan; - if (language == "en") - return RegionType::USA; - for (auto lang : eur_languages) - if (language == lang) + RegionType + read_system_region() noexcept + { + alignas(64) MCPSysProdSettings settings{}; + MCPError status = 0; + int handle = MCP_Open(); + if (handle < 0) + goto error; + status = MCP_GetSysProdSettings(handle, &settings); + MCP_Close(handle); + if (status) + goto error; + + if (settings.product_area & MCP_REGION_JAPAN) + return RegionType::Japan; + if (settings.product_area & MCP_REGION_USA) + return RegionType::USA; + if (settings.product_area & MCP_REGION_EUROPE) + return RegionType::Europe; + if (settings.product_area & MCP_REGION_CHINA) + return RegionType::China; + if (settings.product_area & MCP_REGION_KOREA) + return RegionType::Korea; + if (settings.product_area & MCP_REGION_TAIWAN) + return RegionType::Taiwan; + + error: return RegionType::Europe; - if (language == "zh") - return RegionType::China; - if (language == "ko") - return RegionType::Korea; - - // printf("failed to detect region for [%s], [%s]\n", - // language.data(), - // country.data()); - return {}; -} - -uint32_t -to_keyboard_layout(LanguageType language, - RegionType region) -{ - switch (language) { - case LanguageType::Japanese: - return 0; - - case LanguageType::English: - if (region == RegionType::USA) - return 1; - else - return 5; - - case LanguageType::French: - if (region == RegionType::USA) - return 2; - else - return 6; - - case LanguageType::German: - return 7; - - case LanguageType::Italian: - return 8; - - case LanguageType::Spanish: - if (region == RegionType::USA) - return 3; - else - return 9; - - case LanguageType::SimplifiedChinese: - case LanguageType::TraditionalChinese: - case LanguageType::Korean: - return 19; - - case LanguageType::Dutch: - return 10; - - case LanguageType::Portuguese: - if (region == RegionType::USA) - return 4; - else - return 11; - - case LanguageType::Russian: - return 12; - - default: - return 19; - } -} - -std::optional -get_language_from_locale() -{ - const char *lang = std::setlocale(LC_CTYPE, nullptr); - if (!lang) - return {}; - if (lang[0] == '\0') // quickly handle empty string - return {}; - if (lang == "C"s) - return {}; - auto [language, country] = parse_lang_str(lang); - return to_language(language, country); -} + } -std::optional -get_region_from_locale() -{ - const char *lang = std::setlocale(LC_CTYPE, nullptr); - if (!lang) - return {}; - if (lang[0] == '\0') // quickly handle empty string - return {}; - if (lang == "C"s) - return {}; - auto [language, country] = parse_lang_str(lang); - return to_region(language, country); -} + RegionType + get_region_from_system() noexcept + { + static RegionType cached = read_system_region(); + return cached; + } -uint32_t -read_system_config_u32(const char *key) noexcept -{ - UCHandle handle = UCOpen(); - if (handle < 0) { - unsigned result; - alignas(64) UCSysConfig arg{}; - SDL_strlcpy(arg.name, key, sizeof arg.name); - arg.dataType = UC_DATATYPE_UNSIGNED_INT; - arg.dataSize = sizeof result; - arg.data = &result; - auto status = UCReadSysConfig(handle, 1, &arg); - UCClose(handle); - if (status == UC_ERROR_OK) + std::size_t + strlen_16(const char16_t *s) + { + if (!s) + return 0; + std::size_t result = 0; + while (*s++) + ++result; return result; - } - // DEBUG - // printf("failed to read config %s\n", key); - return -1; -} - -LanguageType -read_system_language() noexcept -{ - auto lang = read_system_config_u32("cafe.language"); - if (lang <= 11) - return static_cast(lang); - return LanguageType::English; -} - -LanguageType -get_language_from_sys() noexcept -{ - static LanguageType cached = read_system_language(); - return cached; -} - -RegionType -read_system_region() noexcept -{ - alignas(64) MCPSysProdSettings settings{}; - MCPError status = 0; - int handle = MCP_Open(); - if (handle < 0) - goto error; - status = MCP_GetSysProdSettings(handle, &settings); - MCP_Close(handle); - if (status) - goto error; - - if (settings.product_area & MCP_REGION_JAPAN) - return RegionType::Japan; - if (settings.product_area & MCP_REGION_USA) - return RegionType::USA; - if (settings.product_area & MCP_REGION_EUROPE) - return RegionType::Europe; - if (settings.product_area & MCP_REGION_CHINA) - return RegionType::China; - if (settings.product_area & MCP_REGION_KOREA) - return RegionType::Korea; - if (settings.product_area & MCP_REGION_TAIWAN) - return RegionType::Taiwan; - -error: - return RegionType::Europe; -} - -RegionType -get_region_from_sys() noexcept -{ - static RegionType cached = read_system_region(); - return cached; -} - -std::size_t -strlen_16(const char16_t *s) -{ - if (!s) - return 0; - std::size_t result = 0; - while (*s++) - ++result; - return result; -} - -std::u8string -to_utf8(const char16_t *input) -{ - auto output = SDL_iconv_string("UTF-8", - "UTF-16BE", - reinterpret_cast(input), - 2 * (strlen_16(input) + 1)); - if (!output) - throw std::runtime_error{ "SDL_iconv_string() failed" }; + } - try { - std::u8string result(reinterpret_cast(output)); - SDL_free(output); - return result; - } catch (...) { - SDL_free(output); - throw; - } -} + u8string + to_utf8(const char16_t *input) + { + auto output = SDL_iconv_string("UTF-8", + "UTF-16BE", + reinterpret_cast(input), + 2 * (strlen_16(input) + 1)); + if (!output) + throw std::runtime_error{ "SDL_iconv_string() failed" }; + + try { + u8string result(reinterpret_cast(output)); + SDL_free(output); + return result; + } + catch (...) { + SDL_free(output); + throw; + } + } -std::u16string -to_utf16(const char *input) -{ - auto output = SDL_iconv_string("UTF-16BE", - "UTF-8", - input, - SDL_strlen(input) + 1); - if (!output) - throw std::runtime_error{ "SDL_iconv_string() failed" }; - try { - std::u16string result(reinterpret_cast(output)); - SDL_free(output); - return result; - } catch (...) { - SDL_free(output); - throw; - } -} + u16string + to_utf16(const char *input) + { + auto output = SDL_iconv_string("UTF-16BE", + "UTF-8", + input, + SDL_strlen(input) + 1); + if (!output) + throw std::runtime_error{ "SDL_iconv_string() failed" }; + try { + u16string result(reinterpret_cast(output)); + SDL_free(output); + return result; + } + catch (...) { + SDL_free(output); + throw; + } + } -} // namespace detail + } // namespace detail } // namespace void WIIU_SWKBD_Initialize(void) { - if (detail::initialized) + if (detail::create::created) return; try { if (!detail::fsLib) detail::fsLib.emplace(); - if (detail::customCreateArg) - detail::createArg = *detail::customCreateArg; - else { - detail::createArg = {}; - if (auto loc_region = detail::get_region_from_locale()) - detail::createArg.regionType = *loc_region; + nn::swkbd::CreateArg arg; + + if (detail::create::customArg) { + arg = *detail::create::customArg; + } else { + auto [language, country] = detail::parse_locale(detail::swkbdLocale); + if (auto region = detail::to_region(language, country)) + arg.regionType = *region; else - detail::createArg.regionType = detail::get_region_from_sys(); + arg.regionType = detail::get_region_from_system(); } - std::unique_ptr local_fsClient; - if (!detail::createArg.fsClient) { - local_fsClient = std::make_unique(); - detail::createArg.fsClient = local_fsClient.get(); + detail::unique_ptr local_fsClient = std::move(detail::create::fsClient); + if (!arg.fsClient) { + if (!local_fsClient) + local_fsClient = detail::make_unique(); + arg.fsClient = local_fsClient.get(); + } else { + // user provided their own fsClient, so destroy the internal one + local_fsClient.reset(); } - std::unique_ptr local_workMemory; - if (!detail::createArg.workMemory) { - local_workMemory = std::make_unique(nn::swkbd::GetWorkMemorySize(0)); - detail::createArg.workMemory = local_workMemory.get(); + detail::vector local_workMemory = std::move(detail::create::workMemory); + if (!arg.workMemory) { + local_workMemory.resize(nn::swkbd::GetWorkMemorySize(0)); + local_workMemory.shrink_to_fit(); + arg.workMemory = local_workMemory.data(); + } else { + // user provided their own workMemory, so destroy the internal one + local_workMemory.clear(); + local_workMemory.shrink_to_fit(); } - if (!nn::swkbd::Create(detail::createArg)) + if (!nn::swkbd::Create(arg)) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "nn::swkbd::Create() failed\n"); return; + } - detail::workMemory = std::move(local_workMemory); - detail::fsClient = std::move(local_fsClient); - - if (const char *loc = std::setlocale(LC_CTYPE, nullptr)) - detail::createdLocale = loc; - else - detail::createdLocale.clear(); - - detail::initialized = true; - } catch (...) { + detail::create::workMemory = std::move(local_workMemory); + detail::create::fsClient = std::move(local_fsClient); + detail::create::region = arg.regionType; + detail::create::created = true; + } + catch (std::exception &e) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "WIIU_SWKBD_Initialize() failed: %s\n", e.what()); } } __attribute__((__destructor__)) void WIIU_SWKBD_Finalize(void) { - if (!detail::initialized) + if (!detail::create::created) return; nn::swkbd::Destroy(); - detail::workMemory.reset(); - detail::fsClient.reset(); - detail::currentWindow = nullptr; - detail::createdLocale.clear(); - detail::initialized = false; -} - -SDL_bool WIIU_SWKBD_ConsumeVPAD(const VPADStatus *vpad) -{ - if (!detail::initialized) - return SDL_FALSE; - - nn::swkbd::State state = nn::swkbd::GetStateInputForm(); - if (state != nn::swkbd::State::Visible) - return SDL_FALSE; - - // printf("swkbd is consuming vpad input\n"); - detail::vpad = *vpad; - detail::controllerInfo.vpad = &detail::vpad; - return SDL_TRUE; -} - -SDL_bool WIIU_SWKBD_ConsumeKPAD(KPADChan channel, const KPADStatus *kpad) -{ - if (!detail::initialized) - return SDL_FALSE; - - if (channel < 0 || channel > 3) - return SDL_FALSE; - - nn::swkbd::State state = nn::swkbd::GetStateInputForm(); - if (state != nn::swkbd::State::Visible) - return SDL_FALSE; - - // printf("swkbd is consuming kpad input\n"); - detail::kpad[channel] = *kpad; - detail::controllerInfo.kpad[channel] = &detail::kpad[channel]; - return SDL_TRUE; + detail::appear::window = nullptr; + detail::create::region.reset(); + detail::create::created = false; } void WIIU_SWKBD_Calc(void) { - if (!detail::initialized) + if (!detail::create::created) return; nn::swkbd::Calc(detail::controllerInfo); @@ -579,22 +571,24 @@ void WIIU_SWKBD_Calc(void) if (nn::swkbd::IsNeedCalcSubThreadPredict()) nn::swkbd::CalcSubThreadPredict(); - if (detail::currentWindow) { + if (detail::appear::window) { nn::swkbd::State state = nn::swkbd::GetStateInputForm(); if (state == nn::swkbd::State::Hidden) - detail::currentWindow = nullptr; + detail::appear::window = nullptr; } // Check if user confirmed input. if (nn::swkbd::IsDecideOkButton(nullptr)) { - try { - auto str16 = nn::swkbd::GetInputFormString(); - if (str16) { + auto str16 = nn::swkbd::GetInputFormString(); + if (str16) { + try { auto str8 = detail::to_utf8(str16); SDL_SendKeyboardText(reinterpret_cast(str8.data())); } - } catch (std::exception &e) { - std::printf("could not convert utf-16 to utf-8: %s\n", e.what()); + catch (std::exception &e) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, + "could not convert utf-16 to utf-8: %s\n", e.what()); + } } WIIU_SWKBD_HideScreenKeyboard(nullptr, nullptr); @@ -619,7 +613,7 @@ void WIIU_SWKBD_Calc(void) void WIIU_SWKBD_Draw(SDL_Window *window) { - if (window != detail::currentWindow) + if (window != detail::appear::window) return; nn::swkbd::State state = nn::swkbd::GetStateInputForm(); @@ -639,31 +633,26 @@ SDL_bool WIIU_SWKBD_HasScreenKeyboardSupport(_THIS) void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) { - // If the locale changed, force it to be constructed again. - if (const char *loc = std::setlocale(LC_CTYPE, nullptr)) { - if (loc != detail::createdLocale) - WIIU_SWKBD_Finalize(); - } - WIIU_SWKBD_Initialize(); - if (!detail::currentWindow) - detail::currentWindow = window; + if (!detail::appear::window) + detail::appear::window = window; nn::swkbd::AppearArg arg; - if (detail::appear::customArg) + if (detail::appear::customArg) { arg = *detail::appear::customArg; - else { + } else { // Set language - if (auto loc_lang = detail::get_language_from_locale()) - arg.keyboardArg.configArg.languageType = *loc_lang; + auto [language, country] = detail::parse_locale(detail::swkbdLocale); + if (auto lang = detail::to_language(language, country)) + arg.keyboardArg.configArg.languageType = *lang; else - arg.keyboardArg.configArg.languageType = detail::get_language_from_sys(); + arg.keyboardArg.configArg.languageType = detail::get_language_from_system(); // set keyboard layout according to language // TODO: fix wut: it's the unk_0x10 field arg.keyboardArg.configArg.unk_0x10 = detail::to_keyboard_layout(arg.keyboardArg.configArg.languageType, - detail::createArg.regionType); + detail::create::region.value_or(nn::swkbd::RegionType::Europe)); // Set keyboard mode arg.keyboardArg.configArg.keyboardMode = detail::appear::keyboardMode; @@ -675,8 +664,8 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) // Set word suggestions arg.keyboardArg.configArg.showWordSuggestions = detail::appear::showWordSuggestions; - // Always enable Wiimote pointer - arg.keyboardArg.configArg.drawSysWiiPointer = true; + // Set show Wii pointer + arg.keyboardArg.configArg.drawSysWiiPointer = detail::appear::drawWiiPointer; // Set initial text if (!detail::appear::initialText.empty()) @@ -707,27 +696,29 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) // Reset all customization detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Full; detail::appear::okText.clear(); - detail::appear::showWordSuggestions = false; + detail::appear::showWordSuggestions = true; detail::appear::initialText.clear(); detail::appear::hintText.clear(); detail::appear::passwordMode = nn::swkbd::PasswordMode::Clear; detail::appear::highlightInitialText = false; detail::appear::showCopyPasteButtons = false; + detail::appear::drawWiiPointer = true; } void WIIU_SWKBD_HideScreenKeyboard(_THIS, SDL_Window *) { - if (!detail::initialized) + if (!detail::create::created) return; - - // printf("hiding swkbd\n"); nn::swkbd::DisappearInputForm(); - detail::currentWindow = nullptr; + detail::appear::window = nullptr; } -SDL_bool WIIU_SWKBD_IsScreenKeyboardShown(_THIS, SDL_Window *) +SDL_bool WIIU_SWKBD_IsScreenKeyboardShown(_THIS, SDL_Window *window) { - if (!detail::initialized) + if (!detail::create::created) + return SDL_FALSE; + + if (window != detail::appear::window) return SDL_FALSE; nn::swkbd::State state = nn::swkbd::GetStateInputForm(); @@ -739,7 +730,7 @@ SDL_bool WIIU_SWKBD_IsScreenKeyboardShown(_THIS, SDL_Window *) void SDL_WiiUSetSWKBDCreateArg(void *arg) { - detail::customCreateArg = reinterpret_cast(arg); + detail::create::customArg = reinterpret_cast(arg); // force swkbd to be created again next time it's shown WIIU_SWKBD_Finalize(); } @@ -749,30 +740,38 @@ void SDL_WiiUSetSWKBDAppearArg(void *arg) detail::appear::customArg = reinterpret_cast(arg); } -void SDL_WiiUSetSWKBDKeyboardMode(int mode) +void SDL_WiiUSetSWKBDKeyboardMode(SDL_WiiUSWKBDKeyboardMode mode) { switch (mode) { - case 1: // numpad + case SDL_WIIU_SWKBD_KEYBOARD_MODE_FULL: + detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Full; + break; + case SDL_WIIU_SWKBD_KEYBOARD_MODE_NUMPAD: detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Numpad; break; - case 2: // restricted + case SDL_WIIU_SWKBD_KEYBOARD_MODE_RESTRICTED: detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Utf8; break; - case 3: // NNID + case SDL_WIIU_SWKBD_KEYBOARD_MODE_NNID: detail::appear::keyboardMode = nn::swkbd::KeyboardMode::NNID; break; default: - case 0: // full - detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Full; + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, + "set swkbd keyboard mode failed: invalid mode %d\n", mode); } } void SDL_WiiUSetSWKBDOKLabel(const char *label) { - if (label) - detail::appear::okText = detail::to_utf16(label); - else - detail::appear::okText.clear(); + try { + if (label) + detail::appear::okText = detail::to_utf16(label); + else + detail::appear::okText.clear(); + } + catch (std::exception &e) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "set swkbd OK label failed: %s\n", e.what()); + } } void SDL_WiiUSetSWKBDShowWordSuggestions(SDL_bool show) @@ -782,32 +781,45 @@ void SDL_WiiUSetSWKBDShowWordSuggestions(SDL_bool show) void SDL_WiiUSetSWKBDInitialText(const char *text) { - if (text) - detail::appear::initialText = detail::to_utf16(text); - else - detail::appear::initialText.clear(); + try { + if (text) + detail::appear::initialText = detail::to_utf16(text); + else + detail::appear::initialText.clear(); + } + catch (std::exception &e) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "set swkbd initial text failed: %s\n", e.what()); + } } void SDL_WiiUSetSWKBDHintText(const char *text) { - if (text) - detail::appear::hintText = detail::to_utf16(text); - else - detail::appear::hintText.clear(); + try { + if (text) + detail::appear::hintText = detail::to_utf16(text); + else + detail::appear::hintText.clear(); + } + catch (std::exception &e) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "set swkbd hint text failed: %s\n", e.what()); + } } -void SDL_WiiUSetSWKBDPasswordMode(int mode) +void SDL_WiiUSetSWKBDPasswordMode(SDL_WiiUSWKBDPasswordMode mode) { switch (mode) { - case 1: + case SDL_WIIU_SWKBD_PASSWORD_MODE_SHOW: + detail::appear::passwordMode = nn::swkbd::PasswordMode::Clear; + break; + case SDL_WIIU_SWKBD_PASSWORD_MODE_HIDE: detail::appear::passwordMode = nn::swkbd::PasswordMode::Hide; break; - case 2: + case SDL_WIIU_SWKBD_PASSWORD_MODE_FADE: detail::appear::passwordMode = nn::swkbd::PasswordMode::Fade; break; - case 0: default: - detail::appear::passwordMode = nn::swkbd::PasswordMode::Clear; + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, + "set swkbd password failed: invalid mode %d\n", mode); } } @@ -820,3 +832,58 @@ void SDL_WiiUSetSWKBDShowCopyPasteButtons(SDL_bool show) { detail::appear::showCopyPasteButtons = show; } + +void SDL_WiiUSetSWKBDDrawWiiPointer(SDL_bool draw) +{ + detail::appear::drawWiiPointer = draw; +} + +void SDL_WiiUSetSWKBDLocale(const char *locale) +{ + // Don't do anything if the locale didn't change. + if (locale) { + if (locale == detail::swkbdLocale) + return; + } else { + if (!locale && detail::swkbdLocale.empty()) + return; + } + WIIU_SWKBD_Finalize(); + if (locale) + detail::swkbdLocale = locale; + else + detail::swkbdLocale.clear(); +} + +SDL_bool SDL_WiiUSetSWKBDVPAD(const void *vpad) +{ + if (!detail::create::created) + return SDL_FALSE; + + nn::swkbd::State state = nn::swkbd::GetStateInputForm(); + if (state != nn::swkbd::State::Visible) + return SDL_FALSE; + + // printf("swkbd is consuming vpad input\n"); + detail::vpad = *reinterpret_cast(vpad); + detail::controllerInfo.vpad = &detail::vpad; + return SDL_TRUE; +} + +SDL_bool SDL_WiiUSetSWKBDKPAD(int channel, const void *kpad) +{ + if (!detail::create::created) + return SDL_FALSE; + + if (channel < 0 || channel > 3) + return SDL_FALSE; + + nn::swkbd::State state = nn::swkbd::GetStateInputForm(); + if (state != nn::swkbd::State::Visible) + return SDL_FALSE; + + // printf("swkbd is consuming kpad input\n"); + detail::kpad[channel] = *reinterpret_cast(kpad); + detail::controllerInfo.kpad[channel] = &detail::kpad[channel]; + return SDL_TRUE; +} diff --git a/src/video/wiiu/SDL_wiiu_swkbd.h b/src/video/wiiu/SDL_wiiu_swkbd.h index 026491a435ddb..478714813b32b 100644 --- a/src/video/wiiu/SDL_wiiu_swkbd.h +++ b/src/video/wiiu/SDL_wiiu_swkbd.h @@ -37,8 +37,6 @@ extern "C" { void WIIU_SWKBD_Initialize(void); void WIIU_SWKBD_Finalize(void); -SDL_bool WIIU_SWKBD_ConsumeVPAD(const VPADStatus *vpad); -SDL_bool WIIU_SWKBD_ConsumeKPAD(KPADChan channel, const KPADStatus *kpad); void WIIU_SWKBD_Calc(void); void WIIU_SWKBD_Draw(SDL_Window *window); From 2c2a7a025bd792cac856902f618c2e538ae4c372 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 11 Apr 2025 09:28:45 -0300 Subject: [PATCH 04/16] - Added events to differentiate before and after all text is received. - Added function to manually disable the swkbd. --- include/SDL_system.h | 16 ++++- src/video/wiiu/SDL_wiiu_swkbd.cpp | 106 ++++++++++++++++++++++-------- 2 files changed, 95 insertions(+), 27 deletions(-) diff --git a/include/SDL_system.h b/include/SDL_system.h index d262bdfd1e96d..11b58d9e22e56 100644 --- a/include/SDL_system.h +++ b/include/SDL_system.h @@ -615,10 +615,24 @@ extern DECLSPEC int SDLCALL SDL_GDKGetTaskQueue(XTaskQueueHandle * outTaskQueue) /* Platform specific functions for Wii U */ #if defined(__WIIU__) typedef enum SDL_WiiUSysWMEventType { - SDL_WIIU_SYSWM_SWKBD_OK_EVENT = 1, + /** Sent before any text input event. */ + SDL_WIIU_SYSWM_SWKBD_OK_START_EVENT = 1, + /** Sent after all text input events. */ + SDL_WIIU_SYSWM_SWKBD_OK_FINISH_EVENT, + /** Sent after the swkbd was canceled. */ SDL_WIIU_SYSWM_SWKBD_CANCEL_EVENT } SDL_WiiUSysWMEventType; +/** + * Disable the swkbd. + * + * Use this function if you only want text input from a physical USB keyboard. + * + * \param enabled `SDL_FALSE` if you do not want the swkbd to show up after calling + * `SDL_StartTextInput()`. + */ +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDEnabled(SDL_bool enabled); + /** * Sets a nn::swkbd::CreateArg object that will be used to create the swkbd. * diff --git a/src/video/wiiu/SDL_wiiu_swkbd.cpp b/src/video/wiiu/SDL_wiiu_swkbd.cpp index 56f47c7d23b17..2416675881327 100644 --- a/src/video/wiiu/SDL_wiiu_swkbd.cpp +++ b/src/video/wiiu/SDL_wiiu_swkbd.cpp @@ -139,7 +139,6 @@ namespace namespace create { - // We cannot call nn::swkbd functions unless a keyboard is created, so we keep // track of it here. bool created = false; @@ -149,6 +148,24 @@ namespace vector workMemory; std::optional region; // store region used to create keyboard + void + cleanup() + { + /* + * Free all memory allocated for the swkbd creation. + * + * Normally we'd reuse the memory in case we need to re-create it in + * another region. But if swkbd is manually disabled, we better actually + * free up all memory. + * + * Only call this after WIIU_SWKBD_Finalize(). + */ + region.reset(); + fsClient.reset(); + workMemory.clear(); + workMemory.shrink_to_fit(); + } + } // namespace create namespace appear @@ -157,13 +174,12 @@ namespace const nn::swkbd::AppearArg *customArg = nullptr; // Keep track of wich window has the swkbd. SDL_Window *window = nullptr; + // keyboard config options nn::swkbd::KeyboardMode keyboardMode = nn::swkbd::KeyboardMode::Full; u16string okText; bool showWordSuggestions = true; - // TODO: control disabled inputs, needs to fix nn::swkbd::ConfigArg - // input form options u16string initialText; u16string hintText; @@ -172,8 +188,25 @@ namespace bool showCopyPasteButtons = false; bool drawWiiPointer = true; + void + reset() + { + // Reset all customization after the keyboard is shown. + keyboardMode = nn::swkbd::KeyboardMode::Full; + okText.clear(); + showWordSuggestions = true; + initialText.clear(); + hintText.clear(); + passwordMode = nn::swkbd::PasswordMode::Clear; + highlightInitialText = false; + showCopyPasteButtons = false; + drawWiiPointer = true; + } + } // namespace appear + bool enabled = true; + string swkbdLocale; nn::swkbd::ControllerInfo controllerInfo; @@ -181,7 +214,8 @@ namespace VPADStatus vpad; std::array kpad; - SDL_SysWMmsg wmMsg; + SDL_SysWMmsg wmMsgStart; + SDL_SysWMmsg wmMsgFinish; // return language, country pair std::pair @@ -494,6 +528,9 @@ void WIIU_SWKBD_Initialize(void) if (detail::create::created) return; + if (!detail::enabled) + return; + try { if (!detail::fsLib) detail::fsLib.emplace(); @@ -562,6 +599,9 @@ void WIIU_SWKBD_Calc(void) if (!detail::create::created) return; + if (!detail::enabled) + return; + nn::swkbd::Calc(detail::controllerInfo); detail::controllerInfo = {}; @@ -579,6 +619,12 @@ void WIIU_SWKBD_Calc(void) // Check if user confirmed input. if (nn::swkbd::IsDecideOkButton(nullptr)) { + // Send an event before we send out the string. + SDL_VERSION(&detail::wmMsgStart.version); + detail::wmMsgStart.subsystem = SDL_SYSWM_WIIU; + detail::wmMsgStart.msg.wiiu.event = SDL_WIIU_SYSWM_SWKBD_OK_START_EVENT; + SDL_SendSysWMEvent(&detail::wmMsgStart); + auto str16 = nn::swkbd::GetInputFormString(); if (str16) { try { @@ -593,21 +639,19 @@ void WIIU_SWKBD_Calc(void) WIIU_SWKBD_HideScreenKeyboard(nullptr, nullptr); - // notify application - SDL_VERSION(&detail::wmMsg.version); - detail::wmMsg.subsystem = SDL_SYSWM_WIIU; - detail::wmMsg.msg.wiiu.event = SDL_WIIU_SYSWM_SWKBD_OK_EVENT; - SDL_SendSysWMEvent(&detail::wmMsg); + // Send an event after the string. + SDL_VERSION(&detail::wmMsgFinish.version); + detail::wmMsgFinish.subsystem = SDL_SYSWM_WIIU; + detail::wmMsgFinish.msg.wiiu.event = SDL_WIIU_SYSWM_SWKBD_OK_FINISH_EVENT; + SDL_SendSysWMEvent(&detail::wmMsgFinish); } if (nn::swkbd::IsDecideCancelButton(nullptr)) { WIIU_SWKBD_HideScreenKeyboard(nullptr, nullptr); - - // notify application - SDL_VERSION(&detail::wmMsg.version); - detail::wmMsg.subsystem = SDL_SYSWM_WIIU; - detail::wmMsg.msg.wiiu.event = SDL_WIIU_SYSWM_SWKBD_CANCEL_EVENT; - SDL_SendSysWMEvent(&detail::wmMsg); + SDL_VERSION(&detail::wmMsgFinish.version); + detail::wmMsgFinish.subsystem = SDL_SYSWM_WIIU; + detail::wmMsgFinish.msg.wiiu.event = SDL_WIIU_SYSWM_SWKBD_CANCEL_EVENT; + SDL_SendSysWMEvent(&detail::wmMsgFinish); } } @@ -616,6 +660,9 @@ void WIIU_SWKBD_Draw(SDL_Window *window) if (window != detail::appear::window) return; + if (!detail::enabled) + return; + nn::swkbd::State state = nn::swkbd::GetStateInputForm(); if (state == nn::swkbd::State::Hidden) return; @@ -628,11 +675,16 @@ void WIIU_SWKBD_Draw(SDL_Window *window) SDL_bool WIIU_SWKBD_HasScreenKeyboardSupport(_THIS) { + if (!detail::enabled) + return SDL_FALSE; return SDL_TRUE; } void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) { + if (!detail::enabled) + return; + WIIU_SWKBD_Initialize(); if (!detail::appear::window) @@ -692,17 +744,7 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::DrcGamepad; nn::swkbd::AppearInputForm(arg); - - // Reset all customization - detail::appear::keyboardMode = nn::swkbd::KeyboardMode::Full; - detail::appear::okText.clear(); - detail::appear::showWordSuggestions = true; - detail::appear::initialText.clear(); - detail::appear::hintText.clear(); - detail::appear::passwordMode = nn::swkbd::PasswordMode::Clear; - detail::appear::highlightInitialText = false; - detail::appear::showCopyPasteButtons = false; - detail::appear::drawWiiPointer = true; + detail::appear::reset(); } void WIIU_SWKBD_HideScreenKeyboard(_THIS, SDL_Window *) @@ -728,6 +770,18 @@ SDL_bool WIIU_SWKBD_IsScreenKeyboardShown(_THIS, SDL_Window *window) return SDL_FALSE; } +void SDL_WiiUSetSWKBDEnabled(SDL_bool enabled) +{ + if (detail::enabled != !!enabled) { + detail::enabled = enabled; + if (!detail::enabled) { + // If application is turning swkbd off, we better free up all memory too. + WIIU_SWKBD_Finalize(); + detail::create::cleanup(); + } + } +} + void SDL_WiiUSetSWKBDCreateArg(void *arg) { detail::create::customArg = reinterpret_cast(arg); From c9a3b80691747a2fda916ac69510dad2ee272651 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 11 Apr 2025 23:55:47 -0300 Subject: [PATCH 05/16] Don't reset swkbd window to null until it's fully hidden. --- src/video/wiiu/SDL_wiiu_swkbd.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/video/wiiu/SDL_wiiu_swkbd.cpp b/src/video/wiiu/SDL_wiiu_swkbd.cpp index 2416675881327..0ad2ce127978a 100644 --- a/src/video/wiiu/SDL_wiiu_swkbd.cpp +++ b/src/video/wiiu/SDL_wiiu_swkbd.cpp @@ -637,13 +637,13 @@ void WIIU_SWKBD_Calc(void) } } - WIIU_SWKBD_HideScreenKeyboard(nullptr, nullptr); - // Send an event after the string. SDL_VERSION(&detail::wmMsgFinish.version); detail::wmMsgFinish.subsystem = SDL_SYSWM_WIIU; detail::wmMsgFinish.msg.wiiu.event = SDL_WIIU_SYSWM_SWKBD_OK_FINISH_EVENT; SDL_SendSysWMEvent(&detail::wmMsgFinish); + + // WIIU_SWKBD_HideScreenKeyboard(nullptr, nullptr); } if (nn::swkbd::IsDecideCancelButton(nullptr)) { @@ -652,6 +652,8 @@ void WIIU_SWKBD_Calc(void) detail::wmMsgFinish.subsystem = SDL_SYSWM_WIIU; detail::wmMsgFinish.msg.wiiu.event = SDL_WIIU_SYSWM_SWKBD_CANCEL_EVENT; SDL_SendSysWMEvent(&detail::wmMsgFinish); + + // WIIU_SWKBD_HideScreenKeyboard(nullptr, nullptr); } } @@ -752,7 +754,7 @@ void WIIU_SWKBD_HideScreenKeyboard(_THIS, SDL_Window *) if (!detail::create::created) return; nn::swkbd::DisappearInputForm(); - detail::appear::window = nullptr; + // detail::appear::window = nullptr; } SDL_bool WIIU_SWKBD_IsScreenKeyboardShown(_THIS, SDL_Window *window) @@ -918,7 +920,7 @@ SDL_bool SDL_WiiUSetSWKBDVPAD(const void *vpad) if (state != nn::swkbd::State::Visible) return SDL_FALSE; - // printf("swkbd is consuming vpad input\n"); + // printf("swkbd is consuming vpad input, window=%p\n", detail::appear::window); detail::vpad = *reinterpret_cast(vpad); detail::controllerInfo.vpad = &detail::vpad; return SDL_TRUE; From 40551f1dc30db5e36d223452001730ab0fb923cc Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Thu, 22 May 2025 00:06:38 -0300 Subject: [PATCH 06/16] - Added `-lstdc++` as a dependency, because the swkbd API is C++. - Added missing include ``. - Don't copy the user-supplied `nn::swkbd::AppearArg`. --- CMakeLists.txt | 4 +--- include/SDL_system.h | 25 +++++++++++++++---------- src/video/wiiu/SDL_cpp_allocator.h | 2 +- src/video/wiiu/SDL_wiiu_swkbd.cpp | 27 ++++++++++++++++----------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f3b1da9b2c90..b733adf8a5e69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2930,9 +2930,7 @@ elseif(WIIU) set(HAVE_SDL_VIDEO TRUE) endif() - list(APPEND EXTRA_LIBS - wut - ) + list(APPEND EXTRA_LIBS wut stdc++) endif() if(HAVE_VULKAN AND NOT SDL_LOADSO) diff --git a/include/SDL_system.h b/include/SDL_system.h index 11b58d9e22e56..c39d7f3d8477f 100644 --- a/include/SDL_system.h +++ b/include/SDL_system.h @@ -634,12 +634,12 @@ typedef enum SDL_WiiUSysWMEventType { extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDEnabled(SDL_bool enabled); /** - * Sets a nn::swkbd::CreateArg object that will be used to create the swkbd. + * Sets a `nn::swkbd::CreateArg` object that will be used to create the swkbd. * * You don't need to set `arg->fsClient` nor `arg->workMemory`, these will be * allocated if they are `NULL`, when the swkbd is first shown. * - * \param arg Pointer to a persistent nn::swkbd::CreateArg object, or `NULL` to use the + * \param arg Pointer to a persistent `nn::swkbd::CreateArg` object, or `NULL` to use the * default. * * \note @@ -648,22 +648,27 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDEnabled(SDL_bool enabled); extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDCreateArg(void * arg); /** - * Sets a nn::swkbd::AppearArg object that will be used every time the swkbd is shown. + * Sets a `nn::swkbd::AppearArg` object that will be used every time the swkbd is shown. + * You don't have to call this function again if you update the argument's content. * - * \param arg Pointer to a persistent nn::swkbd::AppearArg object, or `NULL` to use the + * \param arg Pointer to a persistent `nn::swkbd::AppearArg` object, or `NULL` to use the * default. * - * \sa SDL_WiiUSetSWKBDKeyboardMode - * \sa SDL_WiiUSetSWKBDOKLabel - * \sa SDL_WiiUSetSWKBDShowWordSuggestions + * \note + * When a `nn::swkbd::AppearArg` argument is used, all related customization options + * are ignored. + * + * \sa SDL_WiiUSetSWKBDHighlightInitialText + * \sa SDL_WiiUSetSWKBDHintText * \sa SDL_WiiUSetSWKBDInitialText * \sa SDL_WiiUSetSWKBDInitialText - * \sa SDL_WiiUSetSWKBDHintText + * \sa SDL_WiiUSetSWKBDKeyboardMode + * \sa SDL_WiiUSetSWKBDOKLabel * \sa SDL_WiiUSetSWKBDPasswordMode - * \sa SDL_WiiUSetSWKBDHighlightInitialText * \sa SDL_WiiUSetSWKBDShowCopyPasteButtons + * \sa SDL_WiiUSetSWKBDShowWordSuggestions */ -extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDAppearArg(void * arg); +extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDAppearArg(const void * arg); /** * Select the swkbd keyboard mode. diff --git a/src/video/wiiu/SDL_cpp_allocator.h b/src/video/wiiu/SDL_cpp_allocator.h index 2252916a1b3c9..3e0bc68dbabf5 100644 --- a/src/video/wiiu/SDL_cpp_allocator.h +++ b/src/video/wiiu/SDL_cpp_allocator.h @@ -23,7 +23,7 @@ #define SDL_cpp_allocator_h #include -#include +#include #include #include "SDL_stdinc.h" diff --git a/src/video/wiiu/SDL_wiiu_swkbd.cpp b/src/video/wiiu/SDL_wiiu_swkbd.cpp index 0ad2ce127978a..5fe3389eec186 100644 --- a/src/video/wiiu/SDL_wiiu_swkbd.cpp +++ b/src/video/wiiu/SDL_wiiu_swkbd.cpp @@ -172,6 +172,7 @@ namespace { const nn::swkbd::AppearArg *customArg = nullptr; + nn::swkbd::AppearArg theArg; // Keep track of wich window has the swkbd. SDL_Window *window = nullptr; @@ -343,7 +344,7 @@ namespace return {}; } - uint32_t + Uint32 to_keyboard_layout(LanguageType language, RegionType region) { @@ -392,7 +393,7 @@ namespace } } - uint32_t + Uint32 read_system_config_u32(const char *key) noexcept { UCHandle handle = UCOpen(); @@ -692,10 +693,12 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) if (!detail::appear::window) detail::appear::window = window; - nn::swkbd::AppearArg arg; + const nn::swkbd::AppearArg *parg; if (detail::appear::customArg) { - arg = *detail::appear::customArg; + parg = detail::appear::customArg; } else { + nn::swkbd::AppearArg &arg = detail::appear::theArg; + arg = nn::swkbd::AppearArg{}; // reset all values to default // Set language auto [language, country] = detail::parse_locale(detail::swkbdLocale); if (auto lang = detail::to_language(language, country)) @@ -738,14 +741,16 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) // Set show copy paste buttons arg.inputFormArg.showCopyPasteButtons = detail::appear::showCopyPasteButtons; - } - if (window->flags & SDL_WINDOW_WIIU_TV_ONLY) - arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::WiiRemote0; - else - arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::DrcGamepad; + if (window->flags & SDL_WINDOW_WIIU_TV_ONLY) + arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::WiiRemote0; + else + arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::DrcGamepad; + + parg = &arg; + } - nn::swkbd::AppearInputForm(arg); + nn::swkbd::AppearInputForm(*parg); detail::appear::reset(); } @@ -791,7 +796,7 @@ void SDL_WiiUSetSWKBDCreateArg(void *arg) WIIU_SWKBD_Finalize(); } -void SDL_WiiUSetSWKBDAppearArg(void *arg) +void SDL_WiiUSetSWKBDAppearArg(const void *arg) { detail::appear::customArg = reinterpret_cast(arg); } From 2d9010f7ebc2802cef4b86429c3eb9924693c289 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Thu, 22 May 2025 01:03:26 -0300 Subject: [PATCH 07/16] Removed duplicated name. --- include/SDL_system.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/SDL_system.h b/include/SDL_system.h index c39d7f3d8477f..701fee2dd43b9 100644 --- a/include/SDL_system.h +++ b/include/SDL_system.h @@ -661,7 +661,6 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDCreateArg(void * arg); * \sa SDL_WiiUSetSWKBDHighlightInitialText * \sa SDL_WiiUSetSWKBDHintText * \sa SDL_WiiUSetSWKBDInitialText - * \sa SDL_WiiUSetSWKBDInitialText * \sa SDL_WiiUSetSWKBDKeyboardMode * \sa SDL_WiiUSetSWKBDOKLabel * \sa SDL_WiiUSetSWKBDPasswordMode From ca1a6bb8cd57c2c4daa10a403d8d7873c5e199b6 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 23 May 2025 01:04:13 -0300 Subject: [PATCH 08/16] Link libstdc++ before libwut. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b733adf8a5e69..dcaacbe88154b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2930,7 +2930,7 @@ elseif(WIIU) set(HAVE_SDL_VIDEO TRUE) endif() - list(APPEND EXTRA_LIBS wut stdc++) + list(APPEND EXTRA_LIBS stdc++ wut) endif() if(HAVE_VULKAN AND NOT SDL_LOADSO) From 4599391a5301f6f0e70ab769c7f52dfaaa03ab25 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 23 May 2025 01:13:49 -0300 Subject: [PATCH 09/16] Fixed inconsistent indentation. --- src/video/wiiu/SDL_wiiuvideo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video/wiiu/SDL_wiiuvideo.c b/src/video/wiiu/SDL_wiiuvideo.c index 2273bd40d1092..849f1028f4610 100644 --- a/src/video/wiiu/SDL_wiiuvideo.c +++ b/src/video/wiiu/SDL_wiiuvideo.c @@ -344,7 +344,7 @@ static void WIIU_PumpEvents(_THIS) } SDL_WIIU_PumpKeyboardEvents(_this); - WIIU_SWKBD_Calc(); + WIIU_SWKBD_Calc(); } static void WIIU_DeleteDevice(SDL_VideoDevice *device) From 64ad1d771dba4125fc9d858b7071429713eeb4f7 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Mon, 26 May 2025 01:17:35 -0300 Subject: [PATCH 10/16] Removed dependency on libstdc++. --- CMakeLists.txt | 20 +- src/video/wiiu/SDL_cpp_allocator.h | 81 -------- src/video/wiiu/SDL_wiiu_swkbd.cpp | 312 ++++++++++++----------------- 3 files changed, 145 insertions(+), 268 deletions(-) delete mode 100644 src/video/wiiu/SDL_cpp_allocator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dcaacbe88154b..5a686e7ea5e2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2929,8 +2929,8 @@ elseif(WIIU) set(SOURCE_FILES ${SOURCE_FILES} ${WIIU_VIDEO_SOURCES}) set(HAVE_SDL_VIDEO TRUE) endif() - - list(APPEND EXTRA_LIBS stdc++ wut) + list(APPEND EXTRA_CXXFLAGS "-fno-exceptions") + list(APPEND EXTRA_LIBS wut) endif() if(HAVE_VULKAN AND NOT SDL_LOADSO) @@ -3066,8 +3066,13 @@ endif() if(EXTRA_CFLAGS) list(REMOVE_DUPLICATES EXTRA_CFLAGS) endif() +if(EXTRA_CXXFLAGS) + list(REMOVE_DUPLICATES EXTRA_CXXFLAGS) +endif() listtostr(EXTRA_CFLAGS _EXTRA_CFLAGS) set(EXTRA_CFLAGS ${_EXTRA_CFLAGS}) +listtostr(EXTRA_CXXFLAGS _EXTRA_CXXFLAGS) +set(EXTRA_CXXFLAGS ${_EXTRA_CXXFLAGS}) # Compat helpers for the configuration files @@ -3312,10 +3317,11 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") message(STATUS " CMAKE_CXX_FLAGS_DEBUG: ${CMAKE_CXX_FLAGS_DEBUG}") endif() message(STATUS "") -message(STATUS " CFLAGS: ${CMAKE_C_FLAGS}") -message(STATUS " EXTRA_CFLAGS: ${EXTRA_CFLAGS}") -message(STATUS " EXTRA_LDFLAGS: ${EXTRA_LDFLAGS} ${EXTRA_LDFLAGS_BUILD}") -message(STATUS " EXTRA_LIBS: ${EXTRA_LIBS}") +message(STATUS " CFLAGS: ${CMAKE_C_FLAGS}") +message(STATUS " EXTRA_CFLAGS: ${EXTRA_CFLAGS}") +message(STATUS " EXTRA_CXXFLAGS: ${EXTRA_CXXFLAGS}") +message(STATUS " EXTRA_LDFLAGS: ${EXTRA_LDFLAGS} ${EXTRA_LDFLAGS_BUILD}") +message(STATUS " EXTRA_LIBS: ${EXTRA_LIBS}") message(STATUS "") message(STATUS " Build Shared Library: ${SDL_SHARED}") message(STATUS " Build Static Library: ${SDL_STATIC}") @@ -3350,6 +3356,8 @@ endif() # Ensure that the extra cflags are used at compile time set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} ${EXTRA_CFLAGS_BUILD}") +# Same for cxxflags +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CXXFLAGS} ${EXTRA_CXXFLAGS_BUILD}") if(NOT WINDOWS_STORE AND NOT SDL2_DISABLE_SDL2MAIN) # Build SDLmain diff --git a/src/video/wiiu/SDL_cpp_allocator.h b/src/video/wiiu/SDL_cpp_allocator.h deleted file mode 100644 index 3e0bc68dbabf5..0000000000000 --- a/src/video/wiiu/SDL_cpp_allocator.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 2025 Daniel K. O. - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ - -#ifndef SDL_cpp_allocator_h -#define SDL_cpp_allocator_h - -#include -#include -#include - -#include "SDL_stdinc.h" - -/* - * A custom allocator to use with C++ standard library containers. - * - * All allocations are done through SDL_malloc() and SDL_free(). - */ -namespace -{ - namespace sdl - { - template - struct allocator - { - using value_type = T; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - - using propagate_on_container_move_assignment = std::true_type; - - T * - allocate(std::size_t count) - { - auto ptr = reinterpret_cast(SDL_malloc(count * sizeof(T))); - if (!ptr) - throw std::bad_alloc{}; - return ptr; - } - - void - deallocate(T *ptr, std::size_t /*count*/) - { - SDL_free(ptr); - } - - constexpr bool - operator==(const allocator &) noexcept - { - return true; - } - }; - - } // namespace sdl - -} // namespace - -#endif - -/* - * Local Variables: - * mode: c++ - * End: - */ diff --git a/src/video/wiiu/SDL_wiiu_swkbd.cpp b/src/video/wiiu/SDL_wiiu_swkbd.cpp index 5fe3389eec186..ce354ad1cf09c 100644 --- a/src/video/wiiu/SDL_wiiu_swkbd.cpp +++ b/src/video/wiiu/SDL_wiiu_swkbd.cpp @@ -20,15 +20,9 @@ */ #include -#include #include -#include #include -#include -#include -#include #include -#include #include "SDL_wiiu_swkbd.h" @@ -45,8 +39,6 @@ #include "../../events/SDL_events_c.h" #include "../../events/SDL_keyboard_c.h" -#include "SDL_cpp_allocator.h" - using nn::swkbd::LanguageType; using nn::swkbd::RegionType; @@ -55,19 +47,6 @@ namespace namespace detail { - - // Make sure strings are allocated with SDL_malloc() - template - using basic_string = std::basic_string, sdl::allocator>; - - using string = basic_string; - using u8string = basic_string; - using u16string = basic_string; - - // We use vector to store the work memory - template - using vector = std::vector>; - template struct SDL_Deleter { @@ -76,7 +55,7 @@ namespace const { if (ptr) { - ptr->~T(); + std::destroy_at(ptr); SDL_free(ptr); } } @@ -92,26 +71,38 @@ namespace { T *ptr = reinterpret_cast(SDL_malloc(sizeof(T))); if (!ptr) - throw std::bad_alloc{}; - try { - new (ptr) T(std::forward(args)...); - } - catch (...) { - SDL_free(ptr); - throw; - } + return {}; + std::construct_at(ptr, std::forward(args)...); return unique_ptr{ ptr }; } + // Note: we allow null pointers for these. + using raw_string = unique_ptr; + using raw_u8string = unique_ptr; + using raw_u16string = unique_ptr; + + bool + operator==(const raw_string &a, + const char *b) + { + if (a.get() == b) + return true; + if (!a.get() || !b) + return false; + return SDL_strcmp(a.get(), b) == 0; + } + // RAII class to keep the FS module initialized. struct FSLib { FSLib() + { FSInit(); } ~FSLib() + { FSShutdown(); } @@ -120,18 +111,19 @@ namespace struct FSClientWrapper : FSClient { + bool valid; FSClientWrapper() { auto status = FSAddClient(this, FS_ERROR_FLAG_ALL); - if (status != FS_STATUS_OK) - throw std::runtime_error{ "FSAddClient() failed" }; + valid = status == FS_STATUS_OK; } // disallow moving FSClientWrapper(FSClientWrapper &&) = delete; ~FSClientWrapper() + { FSDelClient(this, FS_ERROR_FLAG_NONE); } @@ -145,7 +137,7 @@ namespace const nn::swkbd::CreateArg *customArg = nullptr; unique_ptr fsClient; - vector workMemory; + unique_ptr workMemory; std::optional region; // store region used to create keyboard void @@ -157,13 +149,10 @@ namespace * Normally we'd reuse the memory in case we need to re-create it in * another region. But if swkbd is manually disabled, we better actually * free up all memory. - * - * Only call this after WIIU_SWKBD_Finalize(). */ region.reset(); fsClient.reset(); - workMemory.clear(); - workMemory.shrink_to_fit(); + workMemory.reset(); } } // namespace create @@ -178,12 +167,12 @@ namespace // keyboard config options nn::swkbd::KeyboardMode keyboardMode = nn::swkbd::KeyboardMode::Full; - u16string okText; + raw_u16string okText; bool showWordSuggestions = true; // TODO: control disabled inputs, needs to fix nn::swkbd::ConfigArg // input form options - u16string initialText; - u16string hintText; + raw_u16string initialText; + raw_u16string hintText; nn::swkbd::PasswordMode passwordMode = nn::swkbd::PasswordMode::Clear; bool highlightInitialText = false; bool showCopyPasteButtons = false; @@ -194,10 +183,10 @@ namespace { // Reset all customization after the keyboard is shown. keyboardMode = nn::swkbd::KeyboardMode::Full; - okText.clear(); + okText.reset(); showWordSuggestions = true; - initialText.clear(); - hintText.clear(); + initialText.reset(); + hintText.reset(); passwordMode = nn::swkbd::PasswordMode::Clear; highlightInitialText = false; showCopyPasteButtons = false; @@ -208,7 +197,7 @@ namespace bool enabled = true; - string swkbdLocale; + raw_string swkbdLocale; nn::swkbd::ControllerInfo controllerInfo; @@ -219,24 +208,27 @@ namespace SDL_SysWMmsg wmMsgFinish; // return language, country pair - std::pair - parse_locale(const string &locale) + std::pair + parse_locale(const raw_string &locale) { - if (locale.empty()) + if (!locale) return {}; - char language[3]; - char country[3]; - int r = SDL_sscanf(locale.data(), "%2[Ca-z]_%2[A-Z]", language, country); + raw_string language{ reinterpret_cast(SDL_calloc(3, 1)) }; + raw_string country{ reinterpret_cast(SDL_calloc(3, 1)) }; + int r = SDL_sscanf(locale.get(), + "%2[Ca-z]_%2[A-Z]", + language.get(), + country.get()); if (r == 2) - return { language, country }; + return { std::move(language), std::move(country) }; if (r == 1) - return { language, {} }; + return { std::move(language), {} }; return {}; } std::optional - to_language(const string &language, - const string &country) + to_language(const raw_string &language, + const raw_string &country) { // two-letter codes, from ISO 639 if (language == "ja") @@ -273,8 +265,8 @@ namespace } std::optional - to_region(const string &language, - const string &country) + to_region(const raw_string &language, + const raw_string &country) { static const std::array usa_countries = { "US", @@ -394,7 +386,7 @@ namespace } Uint32 - read_system_config_u32(const char *key) noexcept + read_system_config_u32(const char *key) { UCHandle handle = UCOpen(); if (handle < 0) { @@ -417,7 +409,7 @@ namespace } LanguageType - read_system_language() noexcept + read_system_language() { auto lang = read_system_config_u32("cafe.language"); if (lang <= 11) @@ -425,15 +417,18 @@ namespace return LanguageType::English; } + std::optional cached_system_language; + LanguageType - get_language_from_system() noexcept + get_language_from_system() { - static LanguageType cached = read_system_language(); - return cached; + if (!cached_system_language) + cached_system_language = read_system_language(); + return *cached_system_language; } RegionType - read_system_region() noexcept + read_system_region() { alignas(64) MCPSysProdSettings settings{}; MCPError status = 0; @@ -462,11 +457,14 @@ namespace return RegionType::Europe; } + std::optional cached_system_region; + RegionType - get_region_from_system() noexcept + get_region_from_system() { - static RegionType cached = read_system_region(); - return cached; + if (!cached_system_region) + cached_system_region = read_system_region(); + return *cached_system_region; } std::size_t @@ -480,45 +478,28 @@ namespace return result; } - u8string + raw_u8string to_utf8(const char16_t *input) { + if (!input) + return {}; auto output = SDL_iconv_string("UTF-8", "UTF-16BE", reinterpret_cast(input), 2 * (strlen_16(input) + 1)); - if (!output) - throw std::runtime_error{ "SDL_iconv_string() failed" }; - - try { - u8string result(reinterpret_cast(output)); - SDL_free(output); - return result; - } - catch (...) { - SDL_free(output); - throw; - } + return raw_u8string{ reinterpret_cast(output) }; } - u16string + raw_u16string to_utf16(const char *input) { + if (!input) + return {}; auto output = SDL_iconv_string("UTF-16BE", "UTF-8", input, SDL_strlen(input) + 1); - if (!output) - throw std::runtime_error{ "SDL_iconv_string() failed" }; - try { - u16string result(reinterpret_cast(output)); - SDL_free(output); - return result; - } - catch (...) { - SDL_free(output); - throw; - } + return raw_u16string{ reinterpret_cast(output) }; } } // namespace detail @@ -532,56 +513,57 @@ void WIIU_SWKBD_Initialize(void) if (!detail::enabled) return; - try { - if (!detail::fsLib) - detail::fsLib.emplace(); - - nn::swkbd::CreateArg arg; + if (!detail::fsLib) + detail::fsLib.emplace(); - if (detail::create::customArg) { - arg = *detail::create::customArg; - } else { - auto [language, country] = detail::parse_locale(detail::swkbdLocale); - if (auto region = detail::to_region(language, country)) - arg.regionType = *region; - else - arg.regionType = detail::get_region_from_system(); - } + nn::swkbd::CreateArg arg; - detail::unique_ptr local_fsClient = std::move(detail::create::fsClient); - if (!arg.fsClient) { - if (!local_fsClient) - local_fsClient = detail::make_unique(); - arg.fsClient = local_fsClient.get(); - } else { - // user provided their own fsClient, so destroy the internal one - local_fsClient.reset(); - } + if (detail::create::customArg) { + arg = *detail::create::customArg; + } else { + auto [language, country] = detail::parse_locale(detail::swkbdLocale); + if (auto region = detail::to_region(language, country)) + arg.regionType = *region; + else + arg.regionType = detail::get_region_from_system(); + } - detail::vector local_workMemory = std::move(detail::create::workMemory); - if (!arg.workMemory) { - local_workMemory.resize(nn::swkbd::GetWorkMemorySize(0)); - local_workMemory.shrink_to_fit(); - arg.workMemory = local_workMemory.data(); - } else { - // user provided their own workMemory, so destroy the internal one - local_workMemory.clear(); - local_workMemory.shrink_to_fit(); + auto local_fsClient = std::move(detail::create::fsClient); + if (!arg.fsClient) { + if (!local_fsClient) + local_fsClient = detail::make_unique(); + if (!local_fsClient) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not create FSClientfor nn::swkbd\n"); + return; } + arg.fsClient = local_fsClient.get(); + } else { + // user provided their own fsClient, so destroy the internal one + local_fsClient.reset(); + } - if (!nn::swkbd::Create(arg)) { - SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "nn::swkbd::Create() failed\n"); + auto local_workMemory = std::move(detail::create::workMemory); + if (!arg.workMemory) { + local_workMemory.reset(reinterpret_cast(SDL_malloc(nn::swkbd::GetWorkMemorySize(0)))); + if (!local_workMemory) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not allocate memory for nn::swkbd\n"); return; } - - detail::create::workMemory = std::move(local_workMemory); - detail::create::fsClient = std::move(local_fsClient); - detail::create::region = arg.regionType; - detail::create::created = true; + arg.workMemory = local_workMemory.get(); + } else { + // user provided their own workMemory, so destroy the internal one + local_workMemory.reset(); } - catch (std::exception &e) { - SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "WIIU_SWKBD_Initialize() failed: %s\n", e.what()); + + if (!nn::swkbd::Create(arg)) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "nn::swkbd::Create() failed\n"); + return; } + + detail::create::workMemory = std::move(local_workMemory); + detail::create::fsClient = std::move(local_fsClient); + detail::create::region = arg.regionType; + detail::create::created = true; } __attribute__((__destructor__)) void WIIU_SWKBD_Finalize(void) @@ -628,14 +610,11 @@ void WIIU_SWKBD_Calc(void) auto str16 = nn::swkbd::GetInputFormString(); if (str16) { - try { - auto str8 = detail::to_utf8(str16); - SDL_SendKeyboardText(reinterpret_cast(str8.data())); - } - catch (std::exception &e) { - SDL_LogError(SDL_LOG_CATEGORY_VIDEO, - "could not convert utf-16 to utf-8: %s\n", e.what()); - } + auto str8 = detail::to_utf8(str16); + if (str8) + SDL_SendKeyboardText(reinterpret_cast(str8.get())); + else + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "could not convert utf-16 to utf-8\n"); } // Send an event after the string. @@ -715,8 +694,8 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) arg.keyboardArg.configArg.keyboardMode = detail::appear::keyboardMode; // Set OK text - if (!detail::appear::okText.empty()) - arg.keyboardArg.configArg.okString = detail::appear::okText.data(); + if (detail::appear::okText) + arg.keyboardArg.configArg.okString = detail::appear::okText.get(); // Set word suggestions arg.keyboardArg.configArg.showWordSuggestions = detail::appear::showWordSuggestions; @@ -725,12 +704,12 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) arg.keyboardArg.configArg.drawSysWiiPointer = detail::appear::drawWiiPointer; // Set initial text - if (!detail::appear::initialText.empty()) - arg.inputFormArg.initialText = detail::appear::initialText.data(); + if (detail::appear::initialText) + arg.inputFormArg.initialText = detail::appear::initialText.get(); // Set hint text - if (!detail::appear::hintText.empty()) - arg.inputFormArg.hintText = detail::appear::hintText.data(); + if (detail::appear::hintText) + arg.inputFormArg.hintText = detail::appear::hintText.get(); // Set password mode arg.inputFormArg.passwordMode = detail::appear::passwordMode; @@ -824,15 +803,7 @@ void SDL_WiiUSetSWKBDKeyboardMode(SDL_WiiUSWKBDKeyboardMode mode) void SDL_WiiUSetSWKBDOKLabel(const char *label) { - try { - if (label) - detail::appear::okText = detail::to_utf16(label); - else - detail::appear::okText.clear(); - } - catch (std::exception &e) { - SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "set swkbd OK label failed: %s\n", e.what()); - } + detail::appear::okText = detail::to_utf16(label); } void SDL_WiiUSetSWKBDShowWordSuggestions(SDL_bool show) @@ -842,28 +813,12 @@ void SDL_WiiUSetSWKBDShowWordSuggestions(SDL_bool show) void SDL_WiiUSetSWKBDInitialText(const char *text) { - try { - if (text) - detail::appear::initialText = detail::to_utf16(text); - else - detail::appear::initialText.clear(); - } - catch (std::exception &e) { - SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "set swkbd initial text failed: %s\n", e.what()); - } + detail::appear::initialText = detail::to_utf16(text); } void SDL_WiiUSetSWKBDHintText(const char *text) { - try { - if (text) - detail::appear::hintText = detail::to_utf16(text); - else - detail::appear::hintText.clear(); - } - catch (std::exception &e) { - SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "set swkbd hint text failed: %s\n", e.what()); - } + detail::appear::hintText = detail::to_utf16(text); } void SDL_WiiUSetSWKBDPasswordMode(SDL_WiiUSWKBDPasswordMode mode) @@ -902,18 +857,13 @@ void SDL_WiiUSetSWKBDDrawWiiPointer(SDL_bool draw) void SDL_WiiUSetSWKBDLocale(const char *locale) { // Don't do anything if the locale didn't change. - if (locale) { - if (locale == detail::swkbdLocale) - return; - } else { - if (!locale && detail::swkbdLocale.empty()) - return; - } + if (detail::swkbdLocale == locale) + return; WIIU_SWKBD_Finalize(); if (locale) - detail::swkbdLocale = locale; + detail::swkbdLocale.reset(SDL_strdup(locale)); else - detail::swkbdLocale.clear(); + detail::swkbdLocale.reset(); } SDL_bool SDL_WiiUSetSWKBDVPAD(const void *vpad) From 15d8d09d6d47e609898d6372471d18f7e01f107a Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 24 Oct 2025 03:08:00 -0300 Subject: [PATCH 11/16] Removed emacs local variables. --- src/joystick/wiiu/SDL_wiiujoystick.c | 8 -------- src/video/wiiu/SDL_wiiukeyboard.c | 8 -------- src/video/wiiu/SDL_wiiuvideo.c | 8 -------- 3 files changed, 24 deletions(-) diff --git a/src/joystick/wiiu/SDL_wiiujoystick.c b/src/joystick/wiiu/SDL_wiiujoystick.c index d47d1ed2df991..f2976ec4c2638 100644 --- a/src/joystick/wiiu/SDL_wiiujoystick.c +++ b/src/joystick/wiiu/SDL_wiiujoystick.c @@ -678,11 +678,3 @@ SDL_JoystickDriver SDL_WIIU_JoystickDriver = }; #endif - -/* - * Local Variables: - * indent-tabs-mode: t - * tab-width: 8 - * c-basic-offset: 8 - * End: - */ diff --git a/src/video/wiiu/SDL_wiiukeyboard.c b/src/video/wiiu/SDL_wiiukeyboard.c index 173d01377efe7..46d353471bdf2 100644 --- a/src/video/wiiu/SDL_wiiukeyboard.c +++ b/src/video/wiiu/SDL_wiiukeyboard.c @@ -135,11 +135,3 @@ int SDL_WIIU_QuitKeyboard(_THIS) return 1; } - -/* - * Local Variables: - * indent-tabs-mode: t - * tab-width: 8 - * c-basic-offset: 8 - * End: - */ diff --git a/src/video/wiiu/SDL_wiiuvideo.c b/src/video/wiiu/SDL_wiiuvideo.c index 0808481a578d3..130644fedcfc1 100644 --- a/src/video/wiiu/SDL_wiiuvideo.c +++ b/src/video/wiiu/SDL_wiiuvideo.c @@ -464,11 +464,3 @@ VideoBootStrap WIIU_bootstrap = { }; #endif /* SDL_VIDEO_DRIVER_WIIU */ - -/* - * Local Variables: - * indent-tabs-mode: t - * tab-width: 8 - * c-basic-offset: 8 - * End: - */ From 849eda9bbc9dff476b6148969a5b86950e55bfac Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 24 Oct 2025 03:32:12 -0300 Subject: [PATCH 12/16] Renamed SDL_wiiu_swkbd.* to SDL_wiiuswkbd.* --- src/joystick/wiiu/SDL_wiiujoystick.c | 2 +- src/render/wiiu/SDL_rpresent_wiiu.c | 2 +- src/video/wiiu/SDL_wiiukeyboard.c | 2 +- src/video/wiiu/{SDL_wiiu_swkbd.cpp => SDL_wiiuswkbd.cpp} | 2 +- src/video/wiiu/{SDL_wiiu_swkbd.h => SDL_wiiuswkbd.h} | 4 ++-- src/video/wiiu/SDL_wiiuvideo.c | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename src/video/wiiu/{SDL_wiiu_swkbd.cpp => SDL_wiiuswkbd.cpp} (99%) rename src/video/wiiu/{SDL_wiiu_swkbd.h => SDL_wiiuswkbd.h} (97%) diff --git a/src/joystick/wiiu/SDL_wiiujoystick.c b/src/joystick/wiiu/SDL_wiiujoystick.c index f2976ec4c2638..f6de2a8d74565 100644 --- a/src/joystick/wiiu/SDL_wiiujoystick.c +++ b/src/joystick/wiiu/SDL_wiiujoystick.c @@ -41,7 +41,7 @@ #include "SDL_wiiujoystick.h" #include "../../video/SDL_sysvideo.h" -#include "../../video/wiiu/SDL_wiiu_swkbd.h" +#include "../../video/wiiu/SDL_wiiuswkbd.h" //index with device_index, get WIIU_DEVICE* static int deviceMap[MAX_CONTROLLERS]; diff --git a/src/render/wiiu/SDL_rpresent_wiiu.c b/src/render/wiiu/SDL_rpresent_wiiu.c index 7f322631a4f81..a04d049de7453 100644 --- a/src/render/wiiu/SDL_rpresent_wiiu.c +++ b/src/render/wiiu/SDL_rpresent_wiiu.c @@ -26,7 +26,7 @@ #include "../SDL_sysrender.h" #include "SDL_render_wiiu.h" -#include "../../video/wiiu/SDL_wiiu_swkbd.h" +#include "../../video/wiiu/SDL_wiiuswkbd.h" #include #include diff --git a/src/video/wiiu/SDL_wiiukeyboard.c b/src/video/wiiu/SDL_wiiukeyboard.c index 46d353471bdf2..6eb6f21abebdb 100644 --- a/src/video/wiiu/SDL_wiiukeyboard.c +++ b/src/video/wiiu/SDL_wiiukeyboard.c @@ -21,7 +21,7 @@ #include "SDL_wiiukeyboard.h" -#include "SDL_wiiu_swkbd.h" +#include "SDL_wiiuswkbd.h" #include "../../SDL_internal.h" diff --git a/src/video/wiiu/SDL_wiiu_swkbd.cpp b/src/video/wiiu/SDL_wiiuswkbd.cpp similarity index 99% rename from src/video/wiiu/SDL_wiiu_swkbd.cpp rename to src/video/wiiu/SDL_wiiuswkbd.cpp index ce354ad1cf09c..3d0385b9854c1 100644 --- a/src/video/wiiu/SDL_wiiu_swkbd.cpp +++ b/src/video/wiiu/SDL_wiiuswkbd.cpp @@ -24,7 +24,7 @@ #include #include -#include "SDL_wiiu_swkbd.h" +#include "SDL_wiiuswkbd.h" #include #include diff --git a/src/video/wiiu/SDL_wiiu_swkbd.h b/src/video/wiiu/SDL_wiiuswkbd.h similarity index 97% rename from src/video/wiiu/SDL_wiiu_swkbd.h rename to src/video/wiiu/SDL_wiiuswkbd.h index 478714813b32b..31ff92e195765 100644 --- a/src/video/wiiu/SDL_wiiu_swkbd.h +++ b/src/video/wiiu/SDL_wiiuswkbd.h @@ -19,8 +19,8 @@ 3. This notice may not be removed or altered from any source distribution. */ -#ifndef SDL_wiiu_swkbd_h -#define SDL_wiiu_swkbd_h +#ifndef SDL_wiiuswkbd_h +#define SDL_wiiuswkbd_h #include "../../SDL_internal.h" #include "../SDL_sysvideo.h" diff --git a/src/video/wiiu/SDL_wiiuvideo.c b/src/video/wiiu/SDL_wiiuvideo.c index 130644fedcfc1..e003fc9c9fdad 100644 --- a/src/video/wiiu/SDL_wiiuvideo.c +++ b/src/video/wiiu/SDL_wiiuvideo.c @@ -38,7 +38,7 @@ #include "SDL_wiiuvideo.h" #include "SDL_wiiukeyboard.h" #include "SDL_wiiu_gfx_heap.h" -#include "SDL_wiiu_swkbd.h" +#include "SDL_wiiuswkbd.h" #include "../../render/wiiu/SDL_render_wiiu.h" From e28be065b29879df2dd1c875bccf73090b76d915 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 24 Oct 2025 03:55:20 -0300 Subject: [PATCH 13/16] Removed CreateArg and AppearArg API. --- include/SDL_system.h | 112 +++++++++--------------------- src/video/wiiu/SDL_wiiuswkbd.cpp | 113 ++++++++++++------------------- 2 files changed, 75 insertions(+), 150 deletions(-) diff --git a/include/SDL_system.h b/include/SDL_system.h index 701fee2dd43b9..03867316646f9 100644 --- a/include/SDL_system.h +++ b/include/SDL_system.h @@ -633,42 +633,6 @@ typedef enum SDL_WiiUSysWMEventType { */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDEnabled(SDL_bool enabled); -/** - * Sets a `nn::swkbd::CreateArg` object that will be used to create the swkbd. - * - * You don't need to set `arg->fsClient` nor `arg->workMemory`, these will be - * allocated if they are `NULL`, when the swkbd is first shown. - * - * \param arg Pointer to a persistent `nn::swkbd::CreateArg` object, or `NULL` to use the - * default. - * - * \note - * The swkbd will be re-initialized after calling this function. - */ -extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDCreateArg(void * arg); - -/** - * Sets a `nn::swkbd::AppearArg` object that will be used every time the swkbd is shown. - * You don't have to call this function again if you update the argument's content. - * - * \param arg Pointer to a persistent `nn::swkbd::AppearArg` object, or `NULL` to use the - * default. - * - * \note - * When a `nn::swkbd::AppearArg` argument is used, all related customization options - * are ignored. - * - * \sa SDL_WiiUSetSWKBDHighlightInitialText - * \sa SDL_WiiUSetSWKBDHintText - * \sa SDL_WiiUSetSWKBDInitialText - * \sa SDL_WiiUSetSWKBDKeyboardMode - * \sa SDL_WiiUSetSWKBDOKLabel - * \sa SDL_WiiUSetSWKBDPasswordMode - * \sa SDL_WiiUSetSWKBDShowCopyPasteButtons - * \sa SDL_WiiUSetSWKBDShowWordSuggestions - */ -extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDAppearArg(const void * arg); - /** * Select the swkbd keyboard mode. * @@ -692,10 +656,7 @@ typedef enum SDL_WiiUSWKBDKeyboardMode { * `SDL_WIIU_SWKBD_KEYBOARD_MODE_FULL`. * * \note - * This option is reset to the default value after the swkbd is shown, and is ignored if - * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. - * - * \sa SDL_WiiUSetSWKBDAppearArg + * This option is reset to the default value after the swkbd is shown. */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDKeyboardMode(SDL_WiiUSWKBDKeyboardMode mode); @@ -706,10 +667,7 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDKeyboardMode(SDL_WiiUSWKBDKeyboardM * default. * * \note - * This option is reset to the default value after the swkbd is shown, and is ignored if - * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. - * - * \sa SDL_WiiUSetSWKBDAppearArg + * This option is reset to the default value after the swkbd is shown. */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDOKLabel(const char * label); @@ -719,10 +677,7 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDOKLabel(const char * label); * \param show `SDL_TRUE` to enable word suggestions. The default is `SDL_TRUE`. * * \note - * This option is reset to the default value after the swkbd is shown, and is ignored if - * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. - * - * \sa SDL_WiiUSetSWKBDAppearArg + * This option is reset to the default value after the swkbd is shown. */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDShowWordSuggestions(SDL_bool show); @@ -733,10 +688,7 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDShowWordSuggestions(SDL_bool show); * default (no initial text.) * * \note - * This option is reset to the default value after the swkbd is shown, and is ignored if - * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. - * - * \sa SDL_WiiUSetSWKBDAppearArg + * This option is reset to the default value after the swkbd is shown. */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDInitialText(const char * text); @@ -747,10 +699,7 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDInitialText(const char * text); * (no hint.) * * \note - * This option is reset to the default value after the swkbd is shown, and is ignored if - * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. - * - * \sa SDL_WiiUSetSWKBDAppearArg + * This option is reset to the default value after the swkbd is shown. */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDHintText(const char * text); @@ -770,10 +719,8 @@ typedef enum SDL_WiiUSWKBDPasswordMode { * `SDL_WIIU_SWKBD_PASSWORD_MODE_SHOW`. * * \note - * This option is reset to the default value after the swkbd is shown, and is ignored if - * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. + * This option is reset to the default value after the swkbd is shown. * - * \sa SDL_WiiUSetSWKBDAppearArg * \sa SDL_WiiUSetSWKBDKeyboardMode */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDPasswordMode(SDL_WiiUSWKBDPasswordMode mode); @@ -784,10 +731,7 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDPasswordMode(SDL_WiiUSWKBDPasswordM * \param highlight `SDL_TRUE` to highlight the initial text. The default is `SDL_FALSE`. * \note - * This option is reset to the default value after the swkbd is shown, and is ignored if - * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. - * - * \sa SDL_WiiUSetSWKBDAppearArg + * This option is reset to the default value after the swkbd is shown. */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDHighlightInitialText(SDL_bool highlight); @@ -797,10 +741,7 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDHighlightInitialText(SDL_bool highl * \param show `SDL_TRUE` to show copy-paste buttons. The default is `SDL_FALSE`. * * \note - * This option is reset to the default value after the swkbd is shown, and is ignored if - * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. - * - * \sa SDL_WiiUSetSWKBDAppearArg + * This option is reset to the default value after the swkbd is shown. */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDShowCopyPasteButtons(SDL_bool show); @@ -810,10 +751,7 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDShowCopyPasteButtons(SDL_bool show) * \param draw `SDL_TRUE` to let the swkbd draw its own Wii remote pointer. The default is * `SDL_TRUE`. * - * This option is reset to the default value after the swkbd is shown, and is ignored if - * you use SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument. - * - * \sa SDL_WiiUSetSWKBDAppearArg + * This option is reset to the default value after the swkbd is shown. */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDDrawWiiPointer(SDL_bool draw); @@ -824,18 +762,30 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDDrawWiiPointer(SDL_bool draw); * Unix-style locale format (e.g. `"en_US"`, `"fr_CA"`, `ja_JP`, `"en"`.) Set to `NULL` to * use the system region and language. The default is `NULL`. * - * \note - * The swkbd will be re-initialized after calling this function. + * The recognized languages are: + * - ja: Japanese + * - en: English + * - fr: French + * - de: German + * - it: Italian + * - es: Spanish + * - nl: Dutch + * - pt: Portuguese + * - ru: Russian * - * \note - * Calling SDL_WiiUSetSWKBDCreateArg with a non-`NULL` argument will override the region. + * The recognized regions are: + * - US, CA, MX, BR: mapped to the "USA" console region. + * - DE, ES, FR, GB, IT, NL, PT, RU: mapped to the "EUR" console region. + * - JP: mapped to the "JPN" console region. * - * \note - * Calling SDL_WiiUSetSWKBDAppearArg with a non-`NULL` argument will override the - * language. + * When no country is specified, "jp" will map to the "JPN" region, "en" to "USA" region, + * and everything else will map to "EUR" region. + * + * By default the locale is obtained from the system configuration (cafe.language). The + * empty locale string is equivalent to the system configuration. * - * \sa SDL_WiiUSetSWKBDCreateArg - * \sa SDL_WiiUSetSWKBDAppearArg + * \note + * The swkbd will be re-initialized after calling this function. */ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDLocale(const char * locale); @@ -849,7 +799,7 @@ extern DECLSPEC void SDLCALL SDL_WiiUSetSWKBDLocale(const char * locale); * \returns `SDL_TRUE` if the swkbd is visible and is going to use the input. * * \note - * The touch point in `tpNormal` should contain calibrated point, by calling + * The touch point in `tpNormal` should contain a calibrated point, by calling * `VPADGetTPCalibratedPoint()`, prior to calling this function. */ extern DECLSPEC SDL_bool SDLCALL SDL_WiiUSetSWKBDVPAD(const void * vpad); diff --git a/src/video/wiiu/SDL_wiiuswkbd.cpp b/src/video/wiiu/SDL_wiiuswkbd.cpp index 3d0385b9854c1..c3015464f9a48 100644 --- a/src/video/wiiu/SDL_wiiuswkbd.cpp +++ b/src/video/wiiu/SDL_wiiuswkbd.cpp @@ -135,7 +135,6 @@ namespace // track of it here. bool created = false; - const nn::swkbd::CreateArg *customArg = nullptr; unique_ptr fsClient; unique_ptr workMemory; std::optional region; // store region used to create keyboard @@ -160,7 +159,6 @@ namespace namespace appear { - const nn::swkbd::AppearArg *customArg = nullptr; nn::swkbd::AppearArg theArg; // Keep track of wich window has the swkbd. SDL_Window *window = nullptr; @@ -518,15 +516,11 @@ void WIIU_SWKBD_Initialize(void) nn::swkbd::CreateArg arg; - if (detail::create::customArg) { - arg = *detail::create::customArg; - } else { - auto [language, country] = detail::parse_locale(detail::swkbdLocale); - if (auto region = detail::to_region(language, country)) - arg.regionType = *region; - else - arg.regionType = detail::get_region_from_system(); - } + auto [language, country] = detail::parse_locale(detail::swkbdLocale); + if (auto region = detail::to_region(language, country)) + arg.regionType = *region; + else + arg.regionType = detail::get_region_from_system(); auto local_fsClient = std::move(detail::create::fsClient); if (!arg.fsClient) { @@ -672,64 +666,57 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) if (!detail::appear::window) detail::appear::window = window; - const nn::swkbd::AppearArg *parg; - if (detail::appear::customArg) { - parg = detail::appear::customArg; - } else { - nn::swkbd::AppearArg &arg = detail::appear::theArg; - arg = nn::swkbd::AppearArg{}; // reset all values to default - // Set language - auto [language, country] = detail::parse_locale(detail::swkbdLocale); - if (auto lang = detail::to_language(language, country)) - arg.keyboardArg.configArg.languageType = *lang; - else - arg.keyboardArg.configArg.languageType = detail::get_language_from_system(); - - // set keyboard layout according to language - // TODO: fix wut: it's the unk_0x10 field - arg.keyboardArg.configArg.unk_0x10 = detail::to_keyboard_layout(arg.keyboardArg.configArg.languageType, - detail::create::region.value_or(nn::swkbd::RegionType::Europe)); + nn::swkbd::AppearArg &arg = detail::appear::theArg; + arg = nn::swkbd::AppearArg{}; // reset all values to default + // Set language + auto [language, country] = detail::parse_locale(detail::swkbdLocale); + if (auto lang = detail::to_language(language, country)) + arg.keyboardArg.configArg.languageType = *lang; + else + arg.keyboardArg.configArg.languageType = detail::get_language_from_system(); - // Set keyboard mode - arg.keyboardArg.configArg.keyboardMode = detail::appear::keyboardMode; + // set keyboard layout according to language + // TODO: fix wut: it's the unk_0x10 field + arg.keyboardArg.configArg.unk_0x10 = detail::to_keyboard_layout(arg.keyboardArg.configArg.languageType, + detail::create::region.value_or(nn::swkbd::RegionType::Europe)); - // Set OK text - if (detail::appear::okText) - arg.keyboardArg.configArg.okString = detail::appear::okText.get(); + // Set keyboard mode + arg.keyboardArg.configArg.keyboardMode = detail::appear::keyboardMode; - // Set word suggestions - arg.keyboardArg.configArg.showWordSuggestions = detail::appear::showWordSuggestions; + // Set OK text + if (detail::appear::okText) + arg.keyboardArg.configArg.okString = detail::appear::okText.get(); - // Set show Wii pointer - arg.keyboardArg.configArg.drawSysWiiPointer = detail::appear::drawWiiPointer; + // Set word suggestions + arg.keyboardArg.configArg.showWordSuggestions = detail::appear::showWordSuggestions; - // Set initial text - if (detail::appear::initialText) - arg.inputFormArg.initialText = detail::appear::initialText.get(); + // Set show Wii pointer + arg.keyboardArg.configArg.drawSysWiiPointer = detail::appear::drawWiiPointer; - // Set hint text - if (detail::appear::hintText) - arg.inputFormArg.hintText = detail::appear::hintText.get(); + // Set initial text + if (detail::appear::initialText) + arg.inputFormArg.initialText = detail::appear::initialText.get(); - // Set password mode - arg.inputFormArg.passwordMode = detail::appear::passwordMode; + // Set hint text + if (detail::appear::hintText) + arg.inputFormArg.hintText = detail::appear::hintText.get(); - // Set highlight initial text - // Note the typo, must fix this after we fix WUT - arg.inputFormArg.higlightInitialText = detail::appear::highlightInitialText; + // Set password mode + arg.inputFormArg.passwordMode = detail::appear::passwordMode; - // Set show copy paste buttons - arg.inputFormArg.showCopyPasteButtons = detail::appear::showCopyPasteButtons; + // Set highlight initial text + // Note the typo, must fix this after we fix WUT + arg.inputFormArg.higlightInitialText = detail::appear::highlightInitialText; - if (window->flags & SDL_WINDOW_WIIU_TV_ONLY) - arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::WiiRemote0; - else - arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::DrcGamepad; + // Set show copy paste buttons + arg.inputFormArg.showCopyPasteButtons = detail::appear::showCopyPasteButtons; - parg = &arg; - } + if (window->flags & SDL_WINDOW_WIIU_TV_ONLY) + arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::WiiRemote0; + else + arg.keyboardArg.configArg.controllerType = nn::swkbd::ControllerType::DrcGamepad; - nn::swkbd::AppearInputForm(*parg); + nn::swkbd::AppearInputForm(arg); detail::appear::reset(); } @@ -768,18 +755,6 @@ void SDL_WiiUSetSWKBDEnabled(SDL_bool enabled) } } -void SDL_WiiUSetSWKBDCreateArg(void *arg) -{ - detail::create::customArg = reinterpret_cast(arg); - // force swkbd to be created again next time it's shown - WIIU_SWKBD_Finalize(); -} - -void SDL_WiiUSetSWKBDAppearArg(const void *arg) -{ - detail::appear::customArg = reinterpret_cast(arg); -} - void SDL_WiiUSetSWKBDKeyboardMode(SDL_WiiUSWKBDKeyboardMode mode) { switch (mode) { From 67f4ffd5b1495563ffbd26d0663d28441a43364d Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 24 Oct 2025 04:07:16 -0300 Subject: [PATCH 14/16] Clean up swkbd from WIIU_VideoQuit(). --- src/video/wiiu/SDL_wiiuvideo.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/video/wiiu/SDL_wiiuvideo.c b/src/video/wiiu/SDL_wiiuvideo.c index e003fc9c9fdad..b2fa44eafdee2 100644 --- a/src/video/wiiu/SDL_wiiuvideo.c +++ b/src/video/wiiu/SDL_wiiuvideo.c @@ -292,6 +292,8 @@ static void WIIU_VideoQuit(_THIS) { WIIU_VideoData *videodata = (WIIU_VideoData *) _this->driverdata; + WIIU_SWKBD_Finalize(); + if (videodata->handleProcUI) { // Put ProcUI into EXIT/shutdown state if user stopped processing events // before SDL_QUIT was generated. From d6f32fc6cfedb18b3d61f35557d9e9f02a023567 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 24 Oct 2025 04:12:48 -0300 Subject: [PATCH 15/16] Forgot to remove attribute. --- src/video/wiiu/SDL_wiiuswkbd.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video/wiiu/SDL_wiiuswkbd.cpp b/src/video/wiiu/SDL_wiiuswkbd.cpp index c3015464f9a48..ad589bcd1c08d 100644 --- a/src/video/wiiu/SDL_wiiuswkbd.cpp +++ b/src/video/wiiu/SDL_wiiuswkbd.cpp @@ -560,7 +560,7 @@ void WIIU_SWKBD_Initialize(void) detail::create::created = true; } -__attribute__((__destructor__)) void WIIU_SWKBD_Finalize(void) +void WIIU_SWKBD_Finalize(void) { if (!detail::create::created) return; From d79fa4c0e40827859c555c356c60e7533b1df16f Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 24 Oct 2025 12:53:49 -0300 Subject: [PATCH 16/16] The default swkbd layout works for all languages. --- src/video/wiiu/SDL_wiiuswkbd.cpp | 54 -------------------------------- 1 file changed, 54 deletions(-) diff --git a/src/video/wiiu/SDL_wiiuswkbd.cpp b/src/video/wiiu/SDL_wiiuswkbd.cpp index ad589bcd1c08d..7593a6b102997 100644 --- a/src/video/wiiu/SDL_wiiuswkbd.cpp +++ b/src/video/wiiu/SDL_wiiuswkbd.cpp @@ -334,55 +334,6 @@ namespace return {}; } - Uint32 - to_keyboard_layout(LanguageType language, - RegionType region) - { - switch (language) { - case LanguageType::Japanese: - return 0; - - case LanguageType::English: - if (region == RegionType::USA) - return 1; - else - return 5; - - case LanguageType::French: - if (region == RegionType::USA) - return 2; - else - return 6; - - case LanguageType::German: - return 7; - - case LanguageType::Italian: - return 8; - - case LanguageType::Spanish: - if (region == RegionType::USA) - return 3; - else - return 9; - - case LanguageType::Dutch: - return 10; - - case LanguageType::Portuguese: - if (region == RegionType::USA) - return 4; - else - return 11; - - case LanguageType::Russian: - return 12; - - default: - return 19; - } - } - Uint32 read_system_config_u32(const char *key) { @@ -675,11 +626,6 @@ void WIIU_SWKBD_ShowScreenKeyboard(_THIS, SDL_Window *window) else arg.keyboardArg.configArg.languageType = detail::get_language_from_system(); - // set keyboard layout according to language - // TODO: fix wut: it's the unk_0x10 field - arg.keyboardArg.configArg.unk_0x10 = detail::to_keyboard_layout(arg.keyboardArg.configArg.languageType, - detail::create::region.value_or(nn::swkbd::RegionType::Europe)); - // Set keyboard mode arg.keyboardArg.configArg.keyboardMode = detail::appear::keyboardMode;