diff --git a/rpcs3/Emu/Cell/Modules/cellOskDialog.cpp b/rpcs3/Emu/Cell/Modules/cellOskDialog.cpp index 0d0f761979..fd3580a9f8 100644 --- a/rpcs3/Emu/Cell/Modules/cellOskDialog.cpp +++ b/rpcs3/Emu/Cell/Modules/cellOskDialog.cpp @@ -488,31 +488,36 @@ error_code cellOskDialogLoadAsync(u32 container, vm::ptr dia return; } + // The max size is 100 characters plus '\0'. Apparently this used to be 30 plus '\0' in older firmware. constexpr u32 max_size = 101; - std::vector string_to_send(max_size, 0); - for (u32 i = 0; i < max_size - 1; i++) - { - string_to_send[i] = osk->osk_text[i]; - if (osk->osk_text[i] == 0) break; - } + // TODO: Send unconfirmed string if there is one. + // As far as I understand, this is for example supposed to be the IME preview. + // So when you type in japanese, you get some word propositions which you can select and confirm. + // The "confirmed" string is basically everything you already wrote, while the "unconfirmed" + // string is the auto-completion part that you haven't accepted as proper word yet. + // The game expects you to send this "preview", or an empty string if there is none. + std::array string_to_send{}; sysutil_register_cb([key_message, string_to_send, event_hook_callback](ppu_thread& cb_ppu) -> s32 { + // Prepare callback variables vm::var keyMessage(key_message); vm::var action(CELL_OSKDIALOG_CHANGE_NO_EVENT); vm::var> pActionInfo(string_to_send.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); + // Create helpers for logging + std::u16string utf16_string(reinterpret_cast(string_to_send.data()), string_to_send.size()); + std::string utf8_string = 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); + 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, utf8_string); - const bool return_value = event_hook_callback(cb_ppu, keyMessage, action, pActionInfo.begin()); + // Call the hook function. The game reads and writes pActionInfo. We need to react based on the returned action. + const bool return_value = event_hook_callback(cb_ppu, keyMessage, action, pActionInfo); ensure(action); ensure(pActionInfo); + // Parse returned text for logging utf16_string.clear(); for (u32 i = 0; i < max_size; i++) { @@ -520,10 +525,11 @@ error_code cellOskDialogLoadAsync(u32 container, vm::ptr dia if (!code) break; utf16_string.push_back(code); } - action_info = utf16_to_ascii8(utf16_string); + utf8_string = 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); + cellOskDialog.notice("osk_hardware_keyboard_event_hook_callback: return_value=%d, action=%d, pActionInfo='%s'", return_value, *action, utf8_string); + // Check if the hook function was successful if (return_value) { const auto osk = _get_osk_dialog(false); @@ -546,31 +552,25 @@ error_code cellOskDialogLoadAsync(u32 container, vm::ptr dia } 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]; - } + // TODO: Replace unconfirmed string. + cellOskDialog.todo("osk_hardware_keyboard_event_hook_callback: replace unconfirmed string with '%s'", utf8_string); break; } case CELL_OSKDIALOG_CHANGE_WORDS_INSERT: { + // TODO: Remove unconfirmed string + cellOskDialog.todo("osk_hardware_keyboard_event_hook_callback: remove unconfirmed string"); + // 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; - } + cellOskDialog.notice("osk_hardware_keyboard_event_hook_callback: inserting string '%s'", utf8_string); + osk->Insert(utf16_string); 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; - } + // Replace confirmed string and remove unconfirmed string. + cellOskDialog.notice("osk_hardware_keyboard_event_hook_callback: replacing all strings with '%s'", utf8_string); + osk->SetText(utf16_string); break; } default: diff --git a/rpcs3/Emu/Cell/Modules/cellOskDialog.h b/rpcs3/Emu/Cell/Modules/cellOskDialog.h index a6f560f7f4..4a18adc765 100644 --- a/rpcs3/Emu/Cell/Modules/cellOskDialog.h +++ b/rpcs3/Emu/Cell/Modules/cellOskDialog.h @@ -302,6 +302,8 @@ public: virtual void Create(const osk_params& params) = 0; virtual void Clear(bool clear_all_data) = 0; + virtual void Insert(const std::u16string& text) = 0; + virtual void SetText(const std::u16string& text) = 0; // Closes the dialog. // Set status to CELL_OSKDIALOG_CLOSE_CONFIRM or CELL_OSKDIALOG_CLOSE_CANCEL for user input. @@ -319,7 +321,7 @@ public: atomic_t pad_input_enabled{ true }; // Determines if the OSK consumes the device's input. atomic_t mouse_input_enabled{ true }; // Determines if the OSK consumes the device's input. atomic_t keyboard_input_enabled{ true }; // Determines if the OSK consumes the device's input. - atomic_t ignore_device_events{ false }; // Determines if the OSK ignores device events. + atomic_t ignore_device_events{ false }; // Determines if the OSK ignores device events. atomic_t osk_input_result{ CellOskDialogInputFieldResult::CELL_OSKDIALOG_INPUT_FIELD_RESULT_OK }; std::array osk_text{}; @@ -329,7 +331,7 @@ struct osk_info { std::shared_ptr dlg; - std::array valid_text{}; + std::array valid_text{}; // The string that's going to be served to the game. shared_mutex text_mtx; atomic_t use_separate_windows = false; diff --git a/rpcs3/Emu/RSX/Overlays/overlay_osk.cpp b/rpcs3/Emu/RSX/Overlays/overlay_osk.cpp index bbe4a92e37..e286d4f05f 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_osk.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_osk.cpp @@ -67,9 +67,6 @@ namespace rsx close(true, true); return; } - - // Clear text edit in continuous separate window mode. Keep actual data just in case. - Clear(false); }; fade_animation.active = true; @@ -77,11 +74,11 @@ namespace rsx void osk_dialog::Clear(bool clear_all_data) { - osk.notice("Clearing osk (clear_all_data=%d)", clear_all_data); - // Try to lock. Clear might be called recursively. const bool locked = m_preview_mutex.try_lock(); + osk.notice("Clearing osk (clear_all_data=%d)", clear_all_data); + m_preview.caret_position = 0; m_preview.set_text({}); @@ -98,6 +95,62 @@ namespace rsx m_update = true; } + void osk_dialog::SetText(const std::u16string& text) + { + // Try to lock. Insert might be called recursively. + const bool locked = m_preview_mutex.try_lock(); + + const std::u16string new_str = text.length() <= char_limit ? text : text.substr(0, char_limit); + + osk.notice("Setting osk text (text='%s', new_str='%s', char_limit=%d)", utf16_to_ascii8(text), utf16_to_ascii8(new_str), char_limit); + + m_preview.caret_position = new_str.length(); + m_preview.set_unicode_text(utf16_to_u32string(new_str)); + + on_text_changed(); + + if (locked) + { + m_preview_mutex.unlock(); + } + + m_update = true; + } + + void osk_dialog::Insert(const std::u16string& text) + { + // Try to lock. Insert might be called recursively. + const bool locked = m_preview_mutex.try_lock(); + + osk.notice("Inserting into osk at position %d (text='%s', char_limit=%d)", m_preview.caret_position, utf16_to_ascii8(text), char_limit); + + // Append to output text + if (m_preview.value.empty()) + { + const std::u16string new_str = text.length() <= char_limit ? text : text.substr(0, char_limit); + + m_preview.caret_position = new_str.length(); + m_preview.set_unicode_text(utf16_to_u32string(new_str)); + } + else if ((m_preview.value.length() + text.length()) <= char_limit) + { + m_preview.insert_text(utf16_to_u32string(text)); + } + else + { + osk.notice("Can't insert into osk: Character limit reached."); + } + + on_text_changed(); + + if (locked) + { + m_preview_mutex.unlock(); + } + + m_update = true; + } + void osk_dialog::add_panel(const osk_panel& panel) { // On PS3 apparently only 7 panels are added, the rest is ignored @@ -1008,13 +1061,12 @@ namespace rsx } else { - if (m_preview.value.length() == char_limit) + if (m_preview.value.length() >= char_limit) { return; } - const auto new_str = m_preview.value + str; - if (new_str.length() <= char_limit) + if ((m_preview.value.length() + str.length()) <= char_limit) { m_preview.insert_text(str); } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_osk.h b/rpcs3/Emu/RSX/Overlays/overlay_osk.h index 65d1a67c2d..96b16b4215 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_osk.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_osk.h @@ -108,6 +108,8 @@ namespace rsx void Create(const osk_params& params) override; void Close(s32 status) override; void Clear(bool clear_all_data) override; + void SetText(const std::u16string& text) override; + void Insert(const std::u16string& text) override; void initialize_layout(const std::u32string& title, const std::u32string& initial_text); void add_panel(const osk_panel& panel); diff --git a/rpcs3/rpcs3qt/osk_dialog_frame.cpp b/rpcs3/rpcs3qt/osk_dialog_frame.cpp index d57cb7cee4..031199789a 100644 --- a/rpcs3/rpcs3qt/osk_dialog_frame.cpp +++ b/rpcs3/rpcs3qt/osk_dialog_frame.cpp @@ -211,3 +211,20 @@ void osk_dialog_frame::Clear(bool clear_all_data) SetOskText(""); } } + +void osk_dialog_frame::SetText(const std::u16string& text) +{ + if (m_dialog) + { + SetOskText(QString::fromStdU16String(text)); + } +} + +void osk_dialog_frame::Insert(const std::u16string& text) +{ + if (m_dialog) + { + // TODO: Correct position (will probably never get implemented because this dialog is just a fallback) + SetOskText(QString::fromStdU16String(text)); + } +} diff --git a/rpcs3/rpcs3qt/osk_dialog_frame.h b/rpcs3/rpcs3qt/osk_dialog_frame.h index c09ece59b7..a402332a72 100644 --- a/rpcs3/rpcs3qt/osk_dialog_frame.h +++ b/rpcs3/rpcs3qt/osk_dialog_frame.h @@ -19,6 +19,8 @@ public: void Create(const osk_params& params) override; void Close(s32 status) override; void Clear(bool clear_all_data) override; + void SetText(const std::u16string& text) override; + void Insert(const std::u16string& text) override; private: void SetOskText(const QString& text);