#include "stdafx.h" #include "Emu/System.h" #include "Emu/system_config.h" #include "Emu/Cell/PPUModule.h" #include "Emu/Io/interception.h" #include "Emu/Io/Keyboard.h" #include "Emu/RSX/Overlays/overlay_osk.h" #include "Emu/IdManager.h" #include "cellSysutil.h" #include "cellOskDialog.h" #include "cellMsgDialog.h" #include "cellImeJp.h" #include LOG_CHANNEL(cellOskDialog); template<> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto error) { switch (error) { STR_CASE(CELL_OSKDIALOG_ERROR_IME_ALREADY_IN_USE); STR_CASE(CELL_OSKDIALOG_ERROR_GET_SIZE_ERROR); STR_CASE(CELL_OSKDIALOG_ERROR_UNKNOWN); STR_CASE(CELL_OSKDIALOG_ERROR_PARAM); } return unknown; }); } template<> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto mode) { switch (mode) { STR_CASE(CELL_OSKDIALOG_CONTINUOUS_MODE_NONE); STR_CASE(CELL_OSKDIALOG_CONTINUOUS_MODE_REMAIN_OPEN); STR_CASE(CELL_OSKDIALOG_CONTINUOUS_MODE_HIDE); STR_CASE(CELL_OSKDIALOG_CONTINUOUS_MODE_SHOW); } return unknown; }); } void osk_info::reset() { std::lock_guard lock(text_mtx); dlg.reset(); valid_text = {}; use_separate_windows = false; lock_ext_input = false; device_mask = 0; input_field_window_width = 0; input_field_background_transparency = 1.0f; input_field_layout_info = {}; input_panel_layout_info = {}; key_layout_options = CELL_OSKDIALOG_10KEY_PANEL; initial_key_layout = CELL_OSKDIALOG_INITIAL_PANEL_LAYOUT_SYSTEM; initial_input_device = CELL_OSKDIALOG_INPUT_DEVICE_PAD; clipboard_enabled = false; half_byte_kana_enabled = false; supported_languages = 0; dimmer_enabled = true; base_color = OskDialogBase::color{ 0.2f, 0.2f, 0.2f, 1.0f }; pointer_enabled = false; pointer_x = 0.0f; pointer_y = 0.0f; initial_scale = 1.0f; layout = {}; osk_continuous_mode = CELL_OSKDIALOG_CONTINUOUS_MODE_NONE; last_dialog_state = CELL_SYSUTIL_OSKDIALOG_UNLOADED; osk_confirm_callback.store({}); osk_force_finish_callback.store({}); osk_hardware_keyboard_event_hook_callback.store({}); hook_event_mode.store(0); } // Align horizontally u32 osk_info::get_aligned_x(u32 layout_mode) { // Let's prefer a centered alignment. if (layout_mode & CELL_OSKDIALOG_LAYOUTMODE_X_ALIGN_CENTER) { return CELL_OSKDIALOG_LAYOUTMODE_X_ALIGN_CENTER; } if (layout_mode & CELL_OSKDIALOG_LAYOUTMODE_X_ALIGN_LEFT) { return CELL_OSKDIALOG_LAYOUTMODE_X_ALIGN_LEFT; } return CELL_OSKDIALOG_LAYOUTMODE_X_ALIGN_RIGHT; } // Align vertically u32 osk_info::get_aligned_y(u32 layout_mode) { // Let's prefer a centered alignment. if (layout_mode & CELL_OSKDIALOG_LAYOUTMODE_Y_ALIGN_CENTER) { return CELL_OSKDIALOG_LAYOUTMODE_Y_ALIGN_CENTER; } if (layout_mode & CELL_OSKDIALOG_LAYOUTMODE_Y_ALIGN_TOP) { return CELL_OSKDIALOG_LAYOUTMODE_Y_ALIGN_TOP; } return CELL_OSKDIALOG_LAYOUTMODE_Y_ALIGN_BOTTOM; } // TODO: don't use this function std::shared_ptr _get_osk_dialog(bool create) { auto& osk = g_fxo->get(); if (create) { const auto init = osk.init.init(); if (!init) { return nullptr; } if (auto manager = g_fxo->try_get()) { std::shared_ptr dlg = std::make_shared(); osk.dlg = manager->add(dlg); } else { osk.dlg = Emu.GetCallbacks().get_osk_dialog(); } return osk.dlg; } const auto init = osk.init.access(); if (!init) { return nullptr; } return osk.dlg; } extern bool close_osk_from_ps_button() { const auto osk = _get_osk_dialog(false); if (!osk) { // The OSK is not open return true; } osk_info& info = g_fxo->get(); // We can only close the osk in separate window mode when it is hidden (continuous_mode is set to CELL_OSKDIALOG_CONTINUOUS_MODE_HIDE) if (!info.use_separate_windows || osk->continuous_mode != CELL_OSKDIALOG_CONTINUOUS_MODE_HIDE) { cellOskDialog.warning("close_osk_from_ps_button: can't close OSK (use_separate_windows=%d, continuous_mode=%s)", info.use_separate_windows.load(), osk->continuous_mode.load()); return false; } std::lock_guard lock(info.text_mtx); if (auto cb = info.osk_force_finish_callback.load()) { bool done = false; bool close_osk = false; sysutil_register_cb([&](ppu_thread& cb_ppu) -> s32 { cellOskDialog.notice("osk_force_finish_callback()"); close_osk = cb(cb_ppu); cellOskDialog.notice("osk_force_finish_callback returned %d", close_osk); done = true; return 0; }); // wait for check callback while (!done && !Emu.IsStopped()) { std::this_thread::yield(); } if (!close_osk) { // We are not allowed to close the OSK cellOskDialog.warning("close_osk_from_ps_button: can't close OSK (osk_force_finish_callback returned false)"); return false; } } // Forcefully terminate the OSK cellOskDialog.warning("close_osk_from_ps_button: Terminating the OSK ..."); osk->Close(FAKE_CELL_OSKDIALOG_CLOSE_TERMINATE); osk->state = OskDialogState::Closed; cellOskDialog.notice("close_osk_from_ps_button: sending CELL_SYSUTIL_OSKDIALOG_FINISHED"); sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_FINISHED, 0); return true; } error_code cellOskDialogLoadAsync(u32 container, vm::ptr dialogParam, vm::ptr inputFieldInfo) { cellOskDialog.warning("cellOskDialogLoadAsync(container=0x%x, dialogParam=*0x%x, inputFieldInfo=*0x%x)", container, dialogParam, inputFieldInfo); if (!dialogParam || !inputFieldInfo || !inputFieldInfo->message || !inputFieldInfo->init_text || inputFieldInfo->limit_length > CELL_OSKDIALOG_STRING_SIZE) { return CELL_OSKDIALOG_ERROR_PARAM; } cellOskDialog.notice("cellOskDialogLoadAsync: dialogParam={ allowOskPanelFlg=0x%x, prohibitFlgs=0x%x, firstViewPanel=%d, controlPoint=(%.2f,%.2f) }", dialogParam->allowOskPanelFlg, dialogParam->prohibitFlgs, dialogParam->firstViewPanel, dialogParam->controlPoint.x, dialogParam->controlPoint.y); auto osk = _get_osk_dialog(true); // Can't open another dialog if this one is already open. if (!osk || osk->state.load() != OskDialogState::Unloaded) { return CELL_SYSUTIL_ERROR_BUSY; } // Get the OSK options auto& info = g_fxo->get(); u32 maxLength = (inputFieldInfo->limit_length >= CELL_OSKDIALOG_STRING_SIZE) ? 511 : u32{inputFieldInfo->limit_length}; const u32 prohibitFlgs = dialogParam->prohibitFlgs; const u32 allowOskPanelFlg = dialogParam->allowOskPanelFlg; const u32 firstViewPanel = dialogParam->firstViewPanel; info.layout.x_offset = dialogParam->controlPoint.x; info.layout.y_offset = dialogParam->controlPoint.y; // Get init text and prepare return value osk->osk_input_result = CELL_OSKDIALOG_INPUT_FIELD_RESULT_OK; std::memset(osk->osk_text, 0, sizeof(osk->osk_text)); // Also clear the info text just to be sure (it should be zeroed at this point anyway) { std::lock_guard lock(info.text_mtx); info.valid_text = {}; } if (inputFieldInfo->init_text) { for (u32 i = 0; (i < maxLength) && (inputFieldInfo->init_text[i] != 0); i++) { osk->osk_text[i] = inputFieldInfo->init_text[i]; } } // Get message to display above the input field // Guarantees 0 terminated (+1). In praxis only 128 but for now lets display all of it char16_t message[CELL_OSKDIALOG_STRING_SIZE + 1]{}; if (inputFieldInfo->message) { for (u32 i = 0; (i < CELL_OSKDIALOG_STRING_SIZE) && (inputFieldInfo->message[i] != 0); i++) { message[i] = inputFieldInfo->message[i]; } } osk->on_osk_close = [wptr = std::weak_ptr(osk)](s32 status) { cellOskDialog.notice("on_osk_close(status=%d)", status); const auto osk = wptr.lock(); osk->state = OskDialogState::Closed; auto& info = g_fxo->get(); const bool keep_seperate_window_open = info.use_separate_windows.load() && (info.osk_continuous_mode.load() != CELL_OSKDIALOG_CONTINUOUS_MODE_NONE); switch (status) { case CELL_OSKDIALOG_CLOSE_CONFIRM: { if (auto ccb = info.osk_confirm_callback.load()) { std::vector string_to_send(CELL_OSKDIALOG_STRING_SIZE); atomic_t done = false; u32 i; for (i = 0; i < CELL_OSKDIALOG_STRING_SIZE - 1; i++) { string_to_send[i] = osk->osk_text[i]; if (osk->osk_text[i] == 0) break; } sysutil_register_cb([&, length = i, string_to_send = std::move(string_to_send)](ppu_thread& cb_ppu) -> s32 { vm::var> string_var(CELL_OSKDIALOG_STRING_SIZE, string_to_send.data()); const u32 return_value = ccb(cb_ppu, string_var.begin(), static_cast(length)); cellOskDialog.warning("osk_confirm_callback return_value=%d", return_value); for (u32 i = 0; i < CELL_OSKDIALOG_STRING_SIZE - 1; i++) { osk->osk_text[i] = string_var.begin()[i]; } done = true; return 0; }); // wait for check callback while (!done && !Emu.IsStopped()) { std::this_thread::yield(); } } if (info.use_separate_windows.load() && osk->osk_text[0] == 0) { cellOskDialog.warning("on_osk_close: input result is CELL_OSKDIALOG_INPUT_FIELD_RESULT_NO_INPUT_TEXT"); osk->osk_input_result = CELL_OSKDIALOG_INPUT_FIELD_RESULT_NO_INPUT_TEXT; } else { osk->osk_input_result = CELL_OSKDIALOG_INPUT_FIELD_RESULT_OK; } break; } case CELL_OSKDIALOG_CLOSE_CANCEL: { osk->osk_input_result = CELL_OSKDIALOG_INPUT_FIELD_RESULT_CANCELED; break; } case FAKE_CELL_OSKDIALOG_CLOSE_ABORT: { osk->osk_input_result = CELL_OSKDIALOG_INPUT_FIELD_RESULT_ABORT; break; } default: { cellOskDialog.fatal("on_osk_close: Unknown status (%d)", status); break; } } // Send OSK status if (keep_seperate_window_open) { switch (status) { case CELL_OSKDIALOG_CLOSE_CONFIRM: { info.last_dialog_state = CELL_SYSUTIL_OSKDIALOG_INPUT_ENTERED; cellOskDialog.notice("on_osk_close: sending CELL_SYSUTIL_OSKDIALOG_INPUT_ENTERED"); sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_INPUT_ENTERED, 0); break; } case CELL_OSKDIALOG_CLOSE_CANCEL: { info.last_dialog_state = CELL_SYSUTIL_OSKDIALOG_INPUT_CANCELED; cellOskDialog.notice("on_osk_close: sending CELL_SYSUTIL_OSKDIALOG_INPUT_CANCELED"); sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_INPUT_CANCELED, 0); break; } case FAKE_CELL_OSKDIALOG_CLOSE_ABORT: { // Handled in cellOskDialogAbort break; } default: { cellOskDialog.fatal("on_osk_close: Unknown status (%d)", status); break; } } } else if (status != FAKE_CELL_OSKDIALOG_CLOSE_ABORT) // Handled in cellOskDialogAbort { info.last_dialog_state = CELL_SYSUTIL_OSKDIALOG_FINISHED; cellOskDialog.notice("on_osk_close: sending CELL_SYSUTIL_OSKDIALOG_FINISHED"); sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_FINISHED, 0); } // The interception status of the continuous separate window is handled differently if (!keep_seperate_window_open) { input::SetIntercepted(false); } }; // Set key callback osk->on_osk_key_input_entered = [wptr = std::weak_ptr(osk)](CellOskDialogKeyMessage key_message) { const auto osk = wptr.lock(); auto& info = g_fxo->get(); std::lock_guard lock(info.text_mtx); auto event_hook_callback = info.osk_hardware_keyboard_event_hook_callback.load(); cellOskDialog.notice("on_osk_key_input_entered: led=%d, mkey=%d, keycode=%d, hook_event_mode=%d, event_hook_callback=*0x%x", key_message.led, key_message.mkey, key_message.keycode, info.hook_event_mode.load(), event_hook_callback); if (!event_hook_callback) { // Nothing to do here return; } bool is_kook_key = false; switch (key_message.keycode) { case CELL_KEYC_NO_EVENT: { // Any shift/alt/ctrl key is_kook_key = key_message.mkey > 0 && (info.hook_event_mode & CELL_OSKDIALOG_EVENT_HOOK_TYPE_ONLY_MODIFIER); break; } case CELL_KEYC_E_ROLLOVER: case CELL_KEYC_E_POSTFAIL: case CELL_KEYC_E_UNDEF: case CELL_KEYC_ESCAPE: case CELL_KEYC_106_KANJI: case CELL_KEYC_CAPS_LOCK: case CELL_KEYC_F1: case CELL_KEYC_F2: case CELL_KEYC_F3: case CELL_KEYC_F4: case CELL_KEYC_F5: case CELL_KEYC_F6: case CELL_KEYC_F7: case CELL_KEYC_F8: case CELL_KEYC_F9: case CELL_KEYC_F10: case CELL_KEYC_F11: case CELL_KEYC_F12: case CELL_KEYC_PRINTSCREEN: case CELL_KEYC_SCROLL_LOCK: case CELL_KEYC_PAUSE: case CELL_KEYC_INSERT: case CELL_KEYC_HOME: case CELL_KEYC_PAGE_UP: case CELL_KEYC_DELETE: case CELL_KEYC_END: case CELL_KEYC_PAGE_DOWN: case CELL_KEYC_RIGHT_ARROW: case CELL_KEYC_LEFT_ARROW: case CELL_KEYC_DOWN_ARROW: case CELL_KEYC_UP_ARROW: case CELL_KEYC_NUM_LOCK: case CELL_KEYC_APPLICATION: case CELL_KEYC_KANA: case CELL_KEYC_HENKAN: case CELL_KEYC_MUHENKAN: { // Any function key or other special key like Delete is_kook_key = (info.hook_event_mode & CELL_OSKDIALOG_EVENT_HOOK_TYPE_FUNCTION_KEY); break; } default: { // Any regular ascii key is_kook_key = (info.hook_event_mode & CELL_OSKDIALOG_EVENT_HOOK_TYPE_ASCII_KEY); break; } } if (!is_kook_key) { cellOskDialog.notice("on_osk_key_input_entered: not a hook key: led=%d, mkey=%d, keycode=%d, hook_event_mode=%d", key_message.led, key_message.mkey, key_message.keycode, info.hook_event_mode.load()); return; } constexpr u32 max_size = 101; std::vector string_to_send(max_size, 0); atomic_t done = false; for (u32 i = 0; i < max_size - 1; i++) { string_to_send[i] = osk->osk_text[i]; if (osk->osk_text[i] == 0) break; } sysutil_register_cb([&](ppu_thread& cb_ppu) -> s32 { vm::var keyMessage(key_message); vm::var action(CELL_OSKDIALOG_CHANGE_NO_EVENT); vm::var> pActionInfo(max_size, string_to_send.data()); std::u16string utf16_string; utf16_string.insert(0, reinterpret_cast(string_to_send.data()), string_to_send.size()); std::string action_info = utf16_to_ascii8(utf16_string); cellOskDialog.notice("osk_hardware_keyboard_event_hook_callback(led=%d, mkey=%d, keycode=%d, action=%d, pActionInfo='%s')", keyMessage->led, keyMessage->mkey, keyMessage->keycode, *action, action_info); const bool return_value = event_hook_callback(cb_ppu, keyMessage, action, pActionInfo.begin()); ensure(action); ensure(pActionInfo); utf16_string.clear(); for (u32 i = 0; i < max_size; i++) { const u16 code = pActionInfo[i]; if (!code) break; utf16_string.push_back(code); } action_info = utf16_to_ascii8(utf16_string); cellOskDialog.notice("osk_hardware_keyboard_event_hook_callback: return_value=%d, action=%d, pActionInfo='%s'", return_value, *action, action_info); if (return_value) { switch (*action) { case CELL_OSKDIALOG_CHANGE_NO_EVENT: case CELL_OSKDIALOG_CHANGE_EVENT_CANCEL: { // Do nothing break; } case CELL_OSKDIALOG_CHANGE_WORDS_INPUT: { // Set unconfirmed string and reset unconfirmed string for (u32 i = 0; i < max_size; i++) { osk->osk_text[i] = pActionInfo.begin()[i]; } break; } case CELL_OSKDIALOG_CHANGE_WORDS_INSERT: { // Set confirmed string and reset unconfirmed string for (u32 i = 0; i < max_size; i++) { info.valid_text[i] = pActionInfo.begin()[i]; osk->osk_text[i] = 0; } break; } case CELL_OSKDIALOG_CHANGE_WORDS_REPLACE_ALL: { // Set confirmed string and reset all strings for (u32 i = 0; i < max_size; i++) { info.valid_text[i] = pActionInfo.begin()[i]; osk->osk_text[i] = 0; } break; } default: { cellOskDialog.error("osk_hardware_keyboard_event_hook_callback returned invalid action (%d)", *action); break; } } } done = true; return 0; }); // wait for callback while (!done && !Emu.IsStopped()) { std::this_thread::yield(); } }; // Set device mask and event lock osk->ignore_input_events = info.lock_ext_input.load(); osk->input_device = info.initial_input_device.load(); osk->continuous_mode = info.osk_continuous_mode.load(); if (info.use_separate_windows) { osk->pad_input_enabled = (info.device_mask != CELL_OSKDIALOG_DEVICE_MASK_PAD); osk->mouse_input_enabled = (info.device_mask != CELL_OSKDIALOG_DEVICE_MASK_PAD); } input::SetIntercepted(osk->pad_input_enabled, osk->keyboard_input_enabled, osk->mouse_input_enabled); cellOskDialog.notice("cellOskDialogLoadAsync: creating OSK dialog ..."); Emu.BlockingCallFromMainThread([=, &info]() { osk->Create({ .title = get_localized_string(localized_string_id::CELL_OSK_DIALOG_TITLE), .message = message, .init_text = osk->osk_text, .charlimit = maxLength, .prohibit_flags = prohibitFlgs, .panel_flag = allowOskPanelFlg, .support_language = info.supported_languages, .first_view_panel = firstViewPanel, .layout = info.layout, .input_layout = info.input_field_layout_info, .panel_layout = info.input_panel_layout_info, .input_field_window_width = info.input_field_window_width, .input_field_background_transparency = info.input_field_background_transparency, .initial_scale = info.initial_scale, .base_color = info.base_color, .dimmer_enabled = info.dimmer_enabled, .use_separate_windows = info.use_separate_windows, .intercept_input = false // We handle the interception manually based on the device mask }); }); g_fxo->get().last_dialog_state = CELL_SYSUTIL_OSKDIALOG_LOADED; if (info.use_separate_windows) { const bool visible = osk->continuous_mode != CELL_OSKDIALOG_CONTINUOUS_MODE_HIDE; cellOskDialog.notice("cellOskDialogLoadAsync: sending CELL_SYSUTIL_OSKDIALOG_DISPLAY_CHANGED with %s", visible ? "CELL_OSKDIALOG_DISPLAY_STATUS_SHOW" : "CELL_OSKDIALOG_DISPLAY_STATUS_HIDE"); sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_DISPLAY_CHANGED, visible ? CELL_OSKDIALOG_DISPLAY_STATUS_SHOW : CELL_OSKDIALOG_DISPLAY_STATUS_HIDE); } cellOskDialog.notice("cellOskDialogLoadAsync: sending CELL_SYSUTIL_OSKDIALOG_LOADED"); sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_LOADED, 0); cellOskDialog.notice("cellOskDialogLoadAsync: created OSK dialog"); return CELL_OK; } error_code cellOskDialogLoadAsyncExt() { cellOskDialog.todo("cellOskDialogLoadAsyncExt()"); return CELL_OK; } error_code getText(vm::ptr OutputInfo, bool is_unload) { if (!OutputInfo || OutputInfo->numCharsResultString < 0) { return CELL_OSKDIALOG_ERROR_PARAM; } const auto osk = _get_osk_dialog(false); if (!osk) { return CELL_MSGDIALOG_ERROR_DIALOG_NOT_OPENED; } auto& info = g_fxo->get(); const bool keep_seperate_window_open = info.use_separate_windows.load() && (info.osk_continuous_mode.load() != CELL_OSKDIALOG_CONTINUOUS_MODE_NONE); info.text_mtx.lock(); // Update text buffer if called from cellOskDialogUnloadAsync or if the user accepted the dialog during continuous seperate window mode. if (is_unload || (keep_seperate_window_open && info.last_dialog_state == CELL_SYSUTIL_OSKDIALOG_INPUT_ENTERED)) { for (s32 i = 0; i < CELL_OSKDIALOG_STRING_SIZE - 1; i++) { info.valid_text[i] = osk->osk_text[i]; } } if (is_unload) { OutputInfo->result = osk->osk_input_result.load(); } else { if (info.valid_text[0] == 0) { OutputInfo->result = CELL_OSKDIALOG_INPUT_FIELD_RESULT_NO_INPUT_TEXT; } else { OutputInfo->result = CELL_OSKDIALOG_INPUT_FIELD_RESULT_OK; } } const bool do_copy = OutputInfo->pResultString && (OutputInfo->result == CELL_OSKDIALOG_INPUT_FIELD_RESULT_OK || (is_unload && OutputInfo->result == CELL_OSKDIALOG_INPUT_FIELD_RESULT_NO_INPUT_TEXT)); for (s32 i = 0; do_copy && i < CELL_OSKDIALOG_STRING_SIZE - 1; i++) { if (i < OutputInfo->numCharsResultString) { OutputInfo->pResultString[i] = info.valid_text[i]; if (info.valid_text[i] == 0) { break; } } else { OutputInfo->pResultString[i] = 0; break; } } info.text_mtx.unlock(); if (is_unload) { // Unload should be called last, so remove the dialog here if (const auto reset_lock = info.init.reset()) { info.reset(); } if (keep_seperate_window_open) { cellOskDialog.notice("cellOskDialogUnloadAsync: terminating continuous overlay"); osk->Close(FAKE_CELL_OSKDIALOG_CLOSE_TERMINATE); } osk->state = OskDialogState::Unloaded; cellOskDialog.notice("cellOskDialogUnloadAsync: sending CELL_SYSUTIL_OSKDIALOG_UNLOADED"); sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_UNLOADED, 0); } else if (keep_seperate_window_open) { // Clear text buffer unless the dialog is still open during continuous seperate window mode. switch (info.last_dialog_state) { case CELL_SYSUTIL_OSKDIALOG_FINISHED: case CELL_SYSUTIL_OSKDIALOG_UNLOADED: case CELL_SYSUTIL_OSKDIALOG_INPUT_CANCELED: case CELL_SYSUTIL_OSKDIALOG_INPUT_ENTERED: info.valid_text = {}; break; default: break; } } return CELL_OK; } error_code cellOskDialogUnloadAsync(vm::ptr OutputInfo) { cellOskDialog.warning("cellOskDialogUnloadAsync(OutputInfo=*0x%x)", OutputInfo); return getText(OutputInfo, true); } error_code cellOskDialogGetSize(vm::ptr width, vm::ptr height, u32 /*CellOskDialogType*/ dialogType) { cellOskDialog.warning("cellOskDialogGetSize(width=*0x%x, height=*0x%x, dialogType=%d)", width, height, dialogType); if (!width || !height) { return CELL_OSKDIALOG_ERROR_PARAM; } if (dialogType >= CELL_OSKDIALOG_TYPE_SEPARATE_SINGLELINE_TEXT_WINDOW) { *width = 0; } else { *width = 1; } *height = 1; return CELL_OK; } error_code cellOskDialogAbort() { cellOskDialog.warning("cellOskDialogAbort()"); const auto osk = _get_osk_dialog(false); if (!osk) { return CELL_MSGDIALOG_ERROR_DIALOG_NOT_OPENED; } const error_code result = osk->state.atomic_op([](OskDialogState& state) -> error_code { // Check for open dialog. In this case the dialog is "Open" if it was not unloaded before. if (state == OskDialogState::Unloaded) { return CELL_MSGDIALOG_ERROR_DIALOG_NOT_OPENED; } state = OskDialogState::Abort; return CELL_OK; }); if (result == CELL_OK) { osk->osk_input_result = CELL_OSKDIALOG_INPUT_FIELD_RESULT_ABORT; osk->Close(FAKE_CELL_OSKDIALOG_CLOSE_ABORT); } g_fxo->get().last_dialog_state = CELL_SYSUTIL_OSKDIALOG_FINISHED; cellOskDialog.notice("cellOskDialogAbort: sending CELL_SYSUTIL_OSKDIALOG_FINISHED"); sysutil_send_system_cmd(CELL_SYSUTIL_OSKDIALOG_FINISHED, 0); return CELL_OK; } error_code cellOskDialogSetDeviceMask(u32 deviceMask) { cellOskDialog.warning("cellOskDialogSetDeviceMask(deviceMask=0x%x)", deviceMask); // TODO: It might also return an error if use_separate_windows is not enabled if (deviceMask > CELL_OSKDIALOG_DEVICE_MASK_PAD) { return CELL_OSKDIALOG_ERROR_PARAM; } auto& info = g_fxo->get(); info.device_mask = deviceMask; if (info.use_separate_windows) { if (const auto osk = _get_osk_dialog(false)) { osk->pad_input_enabled = (deviceMask != CELL_OSKDIALOG_DEVICE_MASK_PAD); osk->mouse_input_enabled = (deviceMask != CELL_OSKDIALOG_DEVICE_MASK_PAD); input::SetIntercepted(osk->pad_input_enabled, osk->keyboard_input_enabled, osk->mouse_input_enabled); } } return CELL_OK; } error_code cellOskDialogSetSeparateWindowOption(vm::ptr windowOption) { cellOskDialog.warning("cellOskDialogSetSeparateWindowOption(windowOption=*0x%x)", windowOption); if (!windowOption || !windowOption->inputFieldLayoutInfo || !!windowOption->reserved || windowOption->continuousMode > CELL_OSKDIALOG_CONTINUOUS_MODE_SHOW || windowOption->deviceMask > CELL_OSKDIALOG_DEVICE_MASK_PAD) { return CELL_OSKDIALOG_ERROR_PARAM; } auto& osk = g_fxo->get(); osk.use_separate_windows = true; osk.osk_continuous_mode = static_cast(+windowOption->continuousMode); osk.device_mask = windowOption->deviceMask; osk.input_field_window_width = windowOption->inputFieldWindowWidth; osk.input_field_background_transparency = std::clamp(windowOption->inputFieldBackgroundTrans, 0.0f, 1.0f); // Choose proper alignments, since the devs didn't make them exclusive for some reason. const auto aligned_layout = [](const CellOskDialogLayoutInfo& info) -> osk_window_layout { osk_window_layout res{}; res.layout_mode = info.layoutMode; res.x_align = osk_info::get_aligned_x(res.layout_mode); res.y_align = osk_info::get_aligned_y(res.layout_mode); res.x_offset = info.position.x; res.y_offset = info.position.y; return res; }; osk.input_field_layout_info = aligned_layout(*windowOption->inputFieldLayoutInfo); // Panel layout is optional if (windowOption->inputPanelLayoutInfo) { osk.input_panel_layout_info = aligned_layout(*windowOption->inputPanelLayoutInfo); } else { // Align to input field osk.input_panel_layout_info = osk.input_field_layout_info; } cellOskDialog.warning("cellOskDialogSetSeparateWindowOption: use_separate_windows=true, continuous_mode=%s, device_mask=0x%x, input_field_window_width=%f, input_field_background_transparency=%.2f, input_field_layout_info=%s, input_panel_layout_info=%s)", osk.osk_continuous_mode.load(), osk.device_mask.load(), osk.input_field_window_width.load(), osk.input_field_background_transparency.load(), osk.input_field_layout_info, osk.input_panel_layout_info); return CELL_OK; } error_code cellOskDialogSetInitialInputDevice(u32 inputDevice) { cellOskDialog.warning("cellOskDialogSetInitialInputDevice(inputDevice=%d)", inputDevice); if (inputDevice > CELL_OSKDIALOG_INPUT_DEVICE_KEYBOARD) { return CELL_OSKDIALOG_ERROR_PARAM; } g_fxo->get().initial_input_device = static_cast(inputDevice); return CELL_OK; } error_code cellOskDialogSetInitialKeyLayout(u32 initialKeyLayout) { cellOskDialog.warning("cellOskDialogSetInitialKeyLayout(initialKeyLayout=%d)", initialKeyLayout); if (initialKeyLayout > CELL_OSKDIALOG_INITIAL_PANEL_LAYOUT_FULLKEY) { return CELL_OSKDIALOG_ERROR_PARAM; } auto& osk = g_fxo->get(); if (osk.key_layout_options & initialKeyLayout) { osk.initial_key_layout = static_cast(initialKeyLayout); } return CELL_OK; } error_code cellOskDialogDisableDimmer() { cellOskDialog.warning("cellOskDialogDisableDimmer()"); g_fxo->get().dimmer_enabled = false; return CELL_OK; } error_code cellOskDialogSetKeyLayoutOption(u32 option) { cellOskDialog.warning("cellOskDialogSetKeyLayoutOption(option=0x%x)", option); if (option == 0 || option > 3) // CELL_OSKDIALOG_10KEY_PANEL OR CELL_OSKDIALOG_FULLKEY_PANEL { return CELL_OSKDIALOG_ERROR_PARAM; } g_fxo->get().key_layout_options = option; return CELL_OK; } error_code cellOskDialogAddSupportLanguage(u32 supportLanguage) { cellOskDialog.warning("cellOskDialogAddSupportLanguage(supportLanguage=0x%x)", supportLanguage); g_fxo->get().supported_languages = supportLanguage; return CELL_OK; } error_code cellOskDialogSetLayoutMode(s32 layoutMode) { cellOskDialog.warning("cellOskDialogSetLayoutMode(layoutMode=0x%x)", layoutMode); auto& osk = g_fxo->get(); osk.layout.layout_mode = layoutMode; // Choose proper alignments, since the devs didn't make them exclusive for some reason. osk.layout.x_align = osk_info::get_aligned_x(layoutMode); osk.layout.y_align = osk_info::get_aligned_y(layoutMode); return CELL_OK; } error_code cellOskDialogGetInputText(vm::ptr OutputInfo) { cellOskDialog.warning("cellOskDialogGetInputText(OutputInfo=*0x%x)", OutputInfo); return getText(OutputInfo, false); } error_code register_keyboard_event_hook_callback(u16 hookEventMode, vm::ptr pCallback) { cellOskDialog.warning("register_keyboard_event_hook_callback(hookEventMode=%u, pCallback=*0x%x)", hookEventMode, pCallback); if (!pCallback) { return CELL_OSKDIALOG_ERROR_PARAM; } g_fxo->get().osk_hardware_keyboard_event_hook_callback = pCallback; g_fxo->get().hook_event_mode = hookEventMode; return CELL_OK; } error_code cellOskDialogExtRegisterKeyboardEventHookCallback(u16 hookEventMode, vm::ptr pCallback) { cellOskDialog.warning("cellOskDialogExtRegisterKeyboardEventHookCallback(hookEventMode=%u, pCallback=*0x%x)", hookEventMode, pCallback); if (hookEventMode == 0 || hookEventMode > (CELL_OSKDIALOG_EVENT_HOOK_TYPE_FUNCTION_KEY | CELL_OSKDIALOG_EVENT_HOOK_TYPE_ASCII_KEY)) { return CELL_OSKDIALOG_ERROR_PARAM; } return register_keyboard_event_hook_callback(hookEventMode, pCallback); } error_code cellOskDialogExtRegisterKeyboardEventHookCallbackEx(u16 hookEventMode, vm::ptr pCallback) { cellOskDialog.warning("cellOskDialogExtRegisterKeyboardEventHookCallbackEx(hookEventMode=%u, pCallback=*0x%x)", hookEventMode, pCallback); if (hookEventMode == 0 || hookEventMode > (CELL_OSKDIALOG_EVENT_HOOK_TYPE_FUNCTION_KEY | CELL_OSKDIALOG_EVENT_HOOK_TYPE_ASCII_KEY | CELL_OSKDIALOG_EVENT_HOOK_TYPE_ONLY_MODIFIER)) { return CELL_OSKDIALOG_ERROR_PARAM; } return register_keyboard_event_hook_callback(hookEventMode, pCallback); } error_code cellOskDialogExtAddJapaneseOptionDictionary(vm::cpptr filePath) { cellOskDialog.todo("cellOskDialogExtAddJapaneseOptionDictionary(filePath=**0x%0x)", filePath); std::vector paths; if (filePath) { for (u32 i = 0; i < 4; i++) { if (!filePath[i]) { break; } std::array path{}; std::memcpy(path.data(), filePath[i].get_ptr(), CELL_IMEJP_DIC_PATH_MAXLENGTH); paths.push_back(path.data()); } } cellOskDialog.todo("cellOskDialogExtAddJapaneseOptionDictionary: got %d dictionaries:\n%s", paths.size(), fmt::merge(paths, "\n")); return CELL_OK; } error_code cellOskDialogExtEnableClipboard() { cellOskDialog.todo("cellOskDialogExtEnableClipboard()"); g_fxo->get().clipboard_enabled = true; // TODO: implement copy paste return CELL_OK; } error_code cellOskDialogExtSendFinishMessage(u32 /*CellOskDialogFinishReason*/ finishReason) { cellOskDialog.warning("cellOskDialogExtSendFinishMessage(finishReason=%d)", finishReason); const auto osk = _get_osk_dialog(false); // Check for "Open" dialog. if (!osk || osk->state.load() == OskDialogState::Unloaded) { return CELL_MSGDIALOG_ERROR_DIALOG_NOT_OPENED; } osk->Close(finishReason); return CELL_OK; } error_code cellOskDialogExtAddOptionDictionary(vm::cpptr dictionaryInfo) { cellOskDialog.todo("cellOskDialogExtAddOptionDictionary(dictionaryInfo=*0x%x)", dictionaryInfo); if (!dictionaryInfo) { return CELL_OSKDIALOG_ERROR_PARAM; } std::vector> paths; // language and path for (u32 i = 0; i < 10; i++) { if (!dictionaryInfo[i] || !dictionaryInfo[i]->dictionaryPath) { break; } std::array path{}; std::memcpy(path.data(), dictionaryInfo[i]->dictionaryPath.get_ptr(), CELL_IMEJP_DIC_PATH_MAXLENGTH); paths.push_back({ dictionaryInfo[i]->targetLanguage, path.data() }); } std::vector msgs; for (const auto& entry : paths) { msgs.push_back(fmt::format("languages=0x%x, path='%s'", entry.first, entry.second)); } cellOskDialog.todo("cellOskDialogExtAddOptionDictionary: got %d dictionaries:\n%s", msgs.size(), fmt::merge(msgs, "\n")); return CELL_OK; } error_code cellOskDialogExtSetInitialScale(f32 initialScale) { cellOskDialog.warning("cellOskDialogExtSetInitialScale(initialScale=%f)", initialScale); if (initialScale < CELL_OSKDIALOG_SCALE_MIN || initialScale > CELL_OSKDIALOG_SCALE_MAX) { return CELL_OSKDIALOG_ERROR_PARAM; } g_fxo->get().initial_scale = initialScale; return CELL_OK; } error_code cellOskDialogExtInputDeviceLock() { cellOskDialog.warning("cellOskDialogExtInputDeviceLock()"); g_fxo->get().lock_ext_input = true; if (const auto osk = _get_osk_dialog(false)) { osk->ignore_input_events = true; } return CELL_OK; } error_code cellOskDialogExtInputDeviceUnlock() { cellOskDialog.warning("cellOskDialogExtInputDeviceUnlock()"); g_fxo->get().lock_ext_input = false; if (const auto osk = _get_osk_dialog(false)) { osk->ignore_input_events = false; } return CELL_OK; } error_code cellOskDialogExtSetBaseColor(f32 red, f32 green, f32 blue, f32 alpha) { cellOskDialog.warning("cellOskDialogExtSetBaseColor(red=%f, blue=%f, green=%f, alpha=%f)", red, blue, green, alpha); if (red < 0.0f || red > 1.0f || green < 0.0f || green > 1.0f || blue < 0.0f || blue > 1.0f || alpha < 0.0f || alpha > 1.0f) { return CELL_OSKDIALOG_ERROR_PARAM; } auto& osk = g_fxo->get(); osk.base_color = OskDialogBase::color{ red, green, blue, alpha }; return CELL_OK; } error_code cellOskDialogExtRegisterConfirmWordFilterCallback(vm::ptr pCallback) { cellOskDialog.warning("cellOskDialogExtRegisterConfirmWordFilterCallback(pCallback=*0x%x)", pCallback); if (!pCallback) { return CELL_OSKDIALOG_ERROR_PARAM; } g_fxo->get().osk_confirm_callback = pCallback; return CELL_OK; } error_code cellOskDialogExtUpdateInputText() { cellOskDialog.todo("cellOskDialogExtUpdateInputText()"); // Usually, user input is only available when the dialog was accepted. // This function seems to be called in order to copy the current text to an internal buffer. // Afterwards, cellOskDialogGetInputText can be called to fetch the current text regardless of // user confirmation, even if the dialog is still in use. // TODO: error checks const auto osk = _get_osk_dialog(false); if (osk) { auto& info = g_fxo->get(); std::lock_guard lock(info.text_mtx); for (s32 i = 0; i < CELL_OSKDIALOG_STRING_SIZE - 1; i++) { info.valid_text[i] = osk->osk_text[i]; } } return CELL_OK; } error_code cellOskDialogExtSetPointerEnable(b8 enable) { cellOskDialog.warning("cellOskDialogExtSetPointerEnable(enable=%d)", enable); // TODO: While the pointer is already displayed in the osk overlay, it is not really useful right now. // On real hardware, this may be used for actual PS Move or mouse input. g_fxo->get().pointer_enabled = enable; return CELL_OK; } error_code cellOskDialogExtUpdatePointerDisplayPos(vm::cptr pos) { cellOskDialog.warning("cellOskDialogExtUpdatePointerDisplayPos(pos=0x%x, posX=%f, posY=%f)", pos, pos->x, pos->y); if (pos) { osk_info& osk = g_fxo->get(); osk.pointer_x = pos->x; osk.pointer_y = pos->y; } return CELL_OK; } error_code cellOskDialogExtEnableHalfByteKana() { cellOskDialog.todo("cellOskDialogExtEnableHalfByteKana()"); g_fxo->get().half_byte_kana_enabled = true; // TODO: use new value in osk return CELL_OK; } error_code cellOskDialogExtDisableHalfByteKana() { cellOskDialog.todo("cellOskDialogExtDisableHalfByteKana()"); g_fxo->get().half_byte_kana_enabled = false; // TODO: use new value in osk return CELL_OK; } error_code cellOskDialogExtRegisterForceFinishCallback(vm::ptr pCallback) { cellOskDialog.warning("cellOskDialogExtRegisterForceFinishCallback(pCallback=*0x%x)", pCallback); if (!pCallback) { return CELL_OSKDIALOG_ERROR_PARAM; } osk_info& info = g_fxo->get(); std::lock_guard lock(info.text_mtx); info.osk_force_finish_callback = pCallback; // We use the force finish callback when the PS-Button is pressed and a System dialog shall be spawned while the OSK is loaded // 1. Check if we are in any continuous mode and the dialog is hidden // 2. If the above is true, call osk_force_finish_callback, deny the PS-Button press otherwise // 3. Check the return value of osk_force_finish_callback. // if false, ignore the PS-Button press, // else close the dialog etc., send CELL_SYSUTIL_OSKDIALOG_FINISHED return CELL_OK; } void cellSysutil_OskDialog_init() { REG_FUNC(cellSysutil, cellOskDialogLoadAsync); REG_FUNC(cellSysutil, cellOskDialogLoadAsyncExt); REG_FUNC(cellSysutil, cellOskDialogUnloadAsync); REG_FUNC(cellSysutil, cellOskDialogGetSize); REG_FUNC(cellSysutil, cellOskDialogAbort); REG_FUNC(cellSysutil, cellOskDialogSetDeviceMask); REG_FUNC(cellSysutil, cellOskDialogSetSeparateWindowOption); REG_FUNC(cellSysutil, cellOskDialogSetInitialInputDevice); REG_FUNC(cellSysutil, cellOskDialogSetInitialKeyLayout); REG_FUNC(cellSysutil, cellOskDialogDisableDimmer); REG_FUNC(cellSysutil, cellOskDialogSetKeyLayoutOption); REG_FUNC(cellSysutil, cellOskDialogAddSupportLanguage); REG_FUNC(cellSysutil, cellOskDialogSetLayoutMode); REG_FUNC(cellSysutil, cellOskDialogGetInputText); } DECLARE(ppu_module_manager::cellOskDialog)("cellOskExtUtility", []() { REG_FUNC(cellOskExtUtility, cellOskDialogExtInputDeviceUnlock); REG_FUNC(cellOskExtUtility, cellOskDialogExtRegisterKeyboardEventHookCallback); REG_FUNC(cellOskExtUtility, cellOskDialogExtRegisterKeyboardEventHookCallbackEx); REG_FUNC(cellOskExtUtility, cellOskDialogExtAddJapaneseOptionDictionary); REG_FUNC(cellOskExtUtility, cellOskDialogExtEnableClipboard); REG_FUNC(cellOskExtUtility, cellOskDialogExtSendFinishMessage); REG_FUNC(cellOskExtUtility, cellOskDialogExtAddOptionDictionary); REG_FUNC(cellOskExtUtility, cellOskDialogExtSetInitialScale); REG_FUNC(cellOskExtUtility, cellOskDialogExtInputDeviceLock); REG_FUNC(cellOskExtUtility, cellOskDialogExtSetBaseColor); REG_FUNC(cellOskExtUtility, cellOskDialogExtRegisterConfirmWordFilterCallback); REG_FUNC(cellOskExtUtility, cellOskDialogExtUpdateInputText); REG_FUNC(cellOskExtUtility, cellOskDialogExtDisableHalfByteKana); REG_FUNC(cellOskExtUtility, cellOskDialogExtSetPointerEnable); REG_FUNC(cellOskExtUtility, cellOskDialogExtUpdatePointerDisplayPos); REG_FUNC(cellOskExtUtility, cellOskDialogExtEnableHalfByteKana); REG_FUNC(cellOskExtUtility, cellOskDialogExtRegisterForceFinishCallback); });