mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-18 22:42:23 +00:00
Implement IME dialog overlay
Build and Release KytyPS5 / Prepare build metadata (push) Canceled after 0s
Build and Release KytyPS5 / Build KytyPS5 (Windows) (push) Canceled after 0s
Build and Release KytyPS5 / Build KytyPS5 (macOS) (push) Canceled after 0s
Build and Release KytyPS5 / Build KytyPS5 (Linux) (push) Canceled after 0s
Build and Release KytyPS5 / Release KytyPS5 (push) Canceled after 0s
Build and Release KytyPS5 / Prepare build metadata (push) Canceled after 0s
Build and Release KytyPS5 / Build KytyPS5 (Windows) (push) Canceled after 0s
Build and Release KytyPS5 / Build KytyPS5 (macOS) (push) Canceled after 0s
Build and Release KytyPS5 / Build KytyPS5 (Linux) (push) Canceled after 0s
Build and Release KytyPS5 / Release KytyPS5 (push) Canceled after 0s
This commit is contained in:
@@ -105,12 +105,12 @@ jobs:
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --build _Build/windows --target launcher audio_out2_port_tests virtual_memory_allocation_tests --parallel
|
||||
cmake --build _Build/windows --target launcher audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests --parallel
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|virtual_memory_allocation)$"
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ime_dialog|virtual_memory_allocation)$"
|
||||
|
||||
- name: Install
|
||||
shell: cmd
|
||||
@@ -186,14 +186,14 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build _Build/macos \
|
||||
--target launcher audio_out2_port_tests virtual_memory_allocation_tests \
|
||||
--target launcher audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/macos --output-on-failure \
|
||||
-R '^(audio_out2_port|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|ime_dialog|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
@@ -329,14 +329,14 @@ jobs:
|
||||
run: |
|
||||
cmake --build _Build/linux \
|
||||
--target launcher page_manager_tests memory_tracker_tests \
|
||||
audio_out2_port_tests virtual_memory_allocation_tests \
|
||||
audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/linux --output-on-failure \
|
||||
-R '^(audio_out2_port|page_manager|memory_tracker|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|ime_dialog|page_manager|memory_tracker|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
|
||||
@@ -39,3 +39,6 @@
|
||||
[submodule "3rdparty/nlohmann_json"]
|
||||
path = 3rdparty/nlohmann_json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
[submodule "3rdparty/imgui"]
|
||||
path = 3rdparty/imgui
|
||||
url = https://github.com/ocornut/imgui.git
|
||||
|
||||
Vendored
+19
@@ -11,6 +11,25 @@ target_include_directories(Vulkan-Headers INTERFACE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Vulkan-Headers/include"
|
||||
)
|
||||
|
||||
add_library(imgui STATIC
|
||||
imgui/imgui.cpp
|
||||
imgui/imgui_draw.cpp
|
||||
imgui/imgui_tables.cpp
|
||||
imgui/imgui_widgets.cpp
|
||||
imgui/backends/imgui_impl_vulkan.cpp
|
||||
)
|
||||
target_include_directories(imgui PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/imgui"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/backends"
|
||||
)
|
||||
target_compile_definitions(imgui PRIVATE IMGUI_IMPL_VULKAN_NO_PROTOTYPES)
|
||||
target_link_libraries(imgui PUBLIC Vulkan::Headers)
|
||||
if(MSVC)
|
||||
target_compile_options(imgui PRIVATE /W0)
|
||||
else()
|
||||
target_compile_options(imgui PRIVATE -w)
|
||||
endif()
|
||||
|
||||
set(SPIRV_SKIP_TESTS ON CACHE BOOL "" FORCE)
|
||||
set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "" FORCE)
|
||||
set(SKIP_SPIRV_TOOLS_INSTALL ON CACHE BOOL "" FORCE)
|
||||
|
||||
+1
Submodule 3rdparty/imgui added at f1cc2ae15e
+9
-1
@@ -292,7 +292,7 @@ if (KYTY_CLANG_CL)
|
||||
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
||||
endif()
|
||||
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version)
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version)
|
||||
|
||||
# Linux system libraries required by the static FFmpeg archive.
|
||||
if(LINUX)
|
||||
@@ -385,6 +385,12 @@ add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
||||
)
|
||||
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/imeDialog.cpp"
|
||||
)
|
||||
target_include_directories(ime_dialog_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/MemoryTrackerTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||
@@ -499,6 +505,7 @@ if(NOT KYTY_CLANG_CL)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME ime_dialog COMMAND $<TARGET_FILE:ime_dialog_tests>)
|
||||
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
|
||||
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
|
||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||
@@ -548,6 +555,7 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
|
||||
add_custom_target(kyty_tests DEPENDS
|
||||
ime_dialog_tests
|
||||
shader_cfg_tests
|
||||
scalar_provenance_tests
|
||||
image_page_table_tests
|
||||
|
||||
@@ -0,0 +1,876 @@
|
||||
#include "graphics/presentation/imeDialogOverlay.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#include "libs/controller.h"
|
||||
#include "libs/imeDialog.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace Ime = Libs::Dialog::ImeDialog;
|
||||
|
||||
constexpr size_t INPUT_QUEUE_CAPACITY = 128;
|
||||
|
||||
enum class InputKind : uint8_t {
|
||||
Button,
|
||||
Axis,
|
||||
MousePosition,
|
||||
MouseButton,
|
||||
MouseWheel,
|
||||
ResetController
|
||||
};
|
||||
|
||||
struct InputEvent {
|
||||
InputKind kind;
|
||||
uint64_t generation;
|
||||
int id;
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct VisibilityUpdate {
|
||||
uint64_t generation;
|
||||
bool visible;
|
||||
bool capture_controller;
|
||||
bool capture_keyboard;
|
||||
bool multiline;
|
||||
};
|
||||
|
||||
std::atomic<Uint32> g_visibility_event {static_cast<Uint32>(-1)};
|
||||
std::mutex g_visibility_mutex;
|
||||
std::mutex g_input_mutex;
|
||||
std::deque<InputEvent> g_input_events;
|
||||
std::deque<VisibilityUpdate> g_visibility_updates;
|
||||
size_t g_missing_visibility_wakeups = 0;
|
||||
bool g_input_reset_requested = false;
|
||||
uint16_t g_last_external_keycode = 0;
|
||||
uint32_t g_last_external_status = 0;
|
||||
uint64_t g_input_generation = 0;
|
||||
bool g_input_active = false;
|
||||
bool g_input_controller = false;
|
||||
bool g_input_keyboard = false;
|
||||
bool g_input_multiline = false;
|
||||
bool g_input_lifecycle_active = false;
|
||||
bool g_active = false;
|
||||
bool g_controller_captured = false;
|
||||
uint64_t g_generation = 0;
|
||||
|
||||
void ClearInputEvents() {
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
g_input_events.clear();
|
||||
g_input_reset_requested = false;
|
||||
}
|
||||
|
||||
void QueueInput(InputEvent event) {
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
const bool replaceable =
|
||||
event.kind == InputKind::Axis || event.kind == InputKind::MousePosition;
|
||||
if (replaceable && !g_input_events.empty()) {
|
||||
auto& last = g_input_events.back();
|
||||
if (last.generation == event.generation && last.kind == event.kind && last.id == event.id) {
|
||||
last = event;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (g_input_events.size() == INPUT_QUEUE_CAPACITY) {
|
||||
g_input_events.clear();
|
||||
g_input_reset_requested = true;
|
||||
}
|
||||
g_input_events.push_back(event);
|
||||
}
|
||||
|
||||
void RetryVisibilityWakeup() {
|
||||
const Uint32 type = g_visibility_event.load(std::memory_order_acquire);
|
||||
if (type == static_cast<Uint32>(-1)) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
if (g_missing_visibility_wakeups == 0) {
|
||||
return;
|
||||
}
|
||||
SDL_Event event {};
|
||||
event.type = type;
|
||||
if (SDL_PushEvent(&event) > 0) {
|
||||
g_missing_visibility_wakeups--;
|
||||
}
|
||||
}
|
||||
|
||||
void OnVisibilityChanged(bool visible, uint64_t generation) {
|
||||
std::scoped_lock visibility_lock(g_visibility_mutex);
|
||||
if (!g_input_lifecycle_active) {
|
||||
return;
|
||||
}
|
||||
if (generation < g_generation || (generation == g_generation && visible == g_active)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_generation = generation;
|
||||
bool capture_controller = false;
|
||||
bool capture_keyboard = false;
|
||||
bool multiline = false;
|
||||
if (visible) {
|
||||
Ime::HostSnapshot snapshot;
|
||||
if (Ime::GetHostSnapshot(&snapshot) && snapshot.generation == generation) {
|
||||
capture_controller = (snapshot.disable_device & Ime::DISABLE_DEVICE_CONTROLLER) == 0;
|
||||
capture_keyboard = (snapshot.disable_device & Ime::DISABLE_DEVICE_EXT_KEYBOARD) == 0;
|
||||
multiline = (snapshot.option & Ime::OPTION_MULTILINE) != 0;
|
||||
} else {
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
const bool was_controller = std::exchange(g_controller_captured, capture_controller);
|
||||
if (capture_controller || was_controller) {
|
||||
Controller::ControllerResetInputState();
|
||||
}
|
||||
g_active = visible;
|
||||
|
||||
const Uint32 type = g_visibility_event.load(std::memory_order_acquire);
|
||||
if (type != static_cast<Uint32>(-1)) {
|
||||
SDL_Event event {};
|
||||
event.type = type;
|
||||
std::scoped_lock input_lock(g_input_mutex);
|
||||
g_visibility_updates.push_back(
|
||||
{generation, visible, capture_controller, capture_keyboard, multiline});
|
||||
if (g_missing_visibility_wakeups != 0 || SDL_PushEvent(&event) <= 0) {
|
||||
g_missing_visibility_wakeups++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ExternalKeyStatus(SDL_Keymod modifiers, bool character_valid) {
|
||||
uint32_t status = 0x00000001 | (character_valid ? 0x00000002 : 0);
|
||||
if ((modifiers & KMOD_LCTRL) != 0) status |= 0x00000100;
|
||||
if ((modifiers & KMOD_LSHIFT) != 0) status |= 0x00000200;
|
||||
if ((modifiers & KMOD_LALT) != 0) status |= 0x00000400;
|
||||
if ((modifiers & KMOD_LGUI) != 0) status |= 0x00000800;
|
||||
if ((modifiers & KMOD_RCTRL) != 0) status |= 0x00001000;
|
||||
if ((modifiers & KMOD_RSHIFT) != 0) status |= 0x00002000;
|
||||
if ((modifiers & KMOD_RALT) != 0) status |= 0x00004000;
|
||||
if ((modifiers & KMOD_RGUI) != 0) status |= 0x00008000;
|
||||
if ((modifiers & KMOD_NUM) != 0) status |= 0x00010000;
|
||||
if ((modifiers & KMOD_CAPS) != 0) status |= 0x00020000;
|
||||
return status;
|
||||
}
|
||||
|
||||
Ime::ExternalInput MakeExternalInput(Ime::ExternalAction action, uint16_t keycode,
|
||||
uint32_t status) {
|
||||
Ime::ExternalInput input {};
|
||||
input.key.keycode = keycode;
|
||||
input.key.status = status;
|
||||
input.key.type = 4;
|
||||
input.action = action;
|
||||
return input;
|
||||
}
|
||||
|
||||
std::u16string Utf8ToUtf16(std::string_view text) {
|
||||
std::u16string result;
|
||||
result.reserve(text.size());
|
||||
for (size_t i = 0; i < text.size();) {
|
||||
const auto first = static_cast<uint8_t>(text[i++]);
|
||||
uint32_t codepoint = 0;
|
||||
uint32_t remaining = 0;
|
||||
if (first < 0x80) {
|
||||
codepoint = first;
|
||||
} else if ((first & 0xe0) == 0xc0) {
|
||||
codepoint = first & 0x1f;
|
||||
remaining = 1;
|
||||
} else if ((first & 0xf0) == 0xe0) {
|
||||
codepoint = first & 0x0f;
|
||||
remaining = 2;
|
||||
} else if ((first & 0xf8) == 0xf0) {
|
||||
codepoint = first & 0x07;
|
||||
remaining = 3;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (i + remaining > text.size()) {
|
||||
break;
|
||||
}
|
||||
bool valid = true;
|
||||
for (uint32_t j = 0; j < remaining; j++) {
|
||||
const auto next = static_cast<uint8_t>(text[i++]);
|
||||
if ((next & 0xc0) != 0x80) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
codepoint = (codepoint << 6) | (next & 0x3f);
|
||||
}
|
||||
if (!valid || codepoint > 0x10ffff || (codepoint >= 0xd800 && codepoint <= 0xdfff)) {
|
||||
continue;
|
||||
}
|
||||
if (codepoint <= 0xffff) {
|
||||
result.push_back(static_cast<char16_t>(codepoint));
|
||||
} else {
|
||||
codepoint -= 0x10000;
|
||||
result.push_back(static_cast<char16_t>(0xd800 + (codepoint >> 10)));
|
||||
result.push_back(static_cast<char16_t>(0xdc00 + (codepoint & 0x3ff)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string VisibleText(const Ime::HostSnapshot& snapshot, size_t max_units) {
|
||||
const size_t cursor = std::min<size_t>(snapshot.cursor, snapshot.text.size());
|
||||
const size_t payload_units = max_units > 4 ? max_units - 4 : 1;
|
||||
size_t begin = cursor > payload_units / 2 ? cursor - payload_units / 2 : 0;
|
||||
size_t end = std::min(snapshot.text.size(), begin + payload_units);
|
||||
if (end == snapshot.text.size() && end - begin < payload_units) {
|
||||
begin = end > payload_units ? end - payload_units : 0;
|
||||
}
|
||||
if (begin > 0 && snapshot.text[begin] >= 0xdc00 && snapshot.text[begin] <= 0xdfff) {
|
||||
begin--;
|
||||
}
|
||||
if (end < snapshot.text.size() && end > begin && snapshot.text[end - 1] >= 0xd800 &&
|
||||
snapshot.text[end - 1] <= 0xdbff) {
|
||||
end--;
|
||||
}
|
||||
|
||||
std::u16string visible;
|
||||
if (begin != 0) {
|
||||
visible.push_back(u'\u2026');
|
||||
}
|
||||
const size_t caret = visible.size() + cursor - begin;
|
||||
if ((snapshot.option & Ime::OPTION_PASSWORD) != 0) {
|
||||
visible.append(end - begin, u'*');
|
||||
} else {
|
||||
visible.append(snapshot.text, begin, end - begin);
|
||||
std::replace(visible.begin(), visible.end(), u'\n', u'\u21b5');
|
||||
std::replace(visible.begin(), visible.end(), u'\r', u'\u21b5');
|
||||
}
|
||||
visible.insert(std::min(caret, visible.size()), 1, u'|');
|
||||
if (end != snapshot.text.size()) {
|
||||
visible.push_back(u'\u2026');
|
||||
}
|
||||
return Common::Utf16ToUtf8(visible.c_str());
|
||||
}
|
||||
|
||||
const char* EnterLabel(Ime::EnterLabel label) {
|
||||
switch (label) {
|
||||
case Ime::EnterLabel::Send: return "Send";
|
||||
case Ime::EnterLabel::Search: return "Search";
|
||||
case Ime::EnterLabel::Go: return "Go";
|
||||
default: return "Done";
|
||||
}
|
||||
}
|
||||
|
||||
float AlignmentPivot(Ime::Alignment alignment) {
|
||||
switch (alignment) {
|
||||
case Ime::Alignment::Start: return 0.0f;
|
||||
case Ime::Alignment::End: return 1.0f;
|
||||
default: return 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiKey ControllerButtonToKey(int button) {
|
||||
switch (button) {
|
||||
case SDL_CONTROLLER_BUTTON_A: return ImGuiKey_GamepadFaceDown;
|
||||
case SDL_CONTROLLER_BUTTON_B: return ImGuiKey_GamepadFaceRight;
|
||||
case SDL_CONTROLLER_BUTTON_X: return ImGuiKey_GamepadFaceLeft;
|
||||
case SDL_CONTROLLER_BUTTON_Y: return ImGuiKey_GamepadFaceUp;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown;
|
||||
default: return ImGuiKey_None;
|
||||
}
|
||||
}
|
||||
|
||||
PFN_vkVoidFunction LoadVulkanFunction(const char* name, void* user_data) {
|
||||
auto& graphics = *static_cast<GraphicContext*>(user_data);
|
||||
return graphics.instance.getProcAddr(name);
|
||||
}
|
||||
|
||||
void CheckVulkanResult(VkResult result) {
|
||||
EXIT_IF(result != VK_SUCCESS);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitializeImeDialogInput() {
|
||||
const Uint32 type = SDL_RegisterEvents(1);
|
||||
EXIT_IF(type == static_cast<Uint32>(-1));
|
||||
{
|
||||
std::scoped_lock lock(g_visibility_mutex);
|
||||
g_input_lifecycle_active = true;
|
||||
g_visibility_event.store(type, std::memory_order_release);
|
||||
}
|
||||
Ime::SetVisibilityCallback(OnVisibilityChanged);
|
||||
Ime::HostSnapshot snapshot;
|
||||
if (Ime::GetHostSnapshot(&snapshot)) {
|
||||
OnVisibilityChanged(true, snapshot.generation);
|
||||
}
|
||||
}
|
||||
|
||||
void ShutdownImeDialogInput() {
|
||||
Ime::SetVisibilityCallback(nullptr);
|
||||
{
|
||||
std::scoped_lock lock(g_visibility_mutex);
|
||||
g_input_lifecycle_active = false;
|
||||
g_active = false;
|
||||
if (std::exchange(g_controller_captured, false)) {
|
||||
Controller::ControllerResetInputState();
|
||||
}
|
||||
g_visibility_event.store(static_cast<Uint32>(-1), std::memory_order_release);
|
||||
}
|
||||
ClearInputEvents();
|
||||
{
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
g_visibility_updates.clear();
|
||||
g_missing_visibility_wakeups = 0;
|
||||
}
|
||||
g_input_active = false;
|
||||
g_input_controller = false;
|
||||
g_input_keyboard = false;
|
||||
g_input_multiline = false;
|
||||
if (SDL_IsTextInputActive() == SDL_TRUE) {
|
||||
SDL_StopTextInput();
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessImeDialogInput(const SDL_Event& event) {
|
||||
RetryVisibilityWakeup();
|
||||
if (event.type == g_visibility_event.load(std::memory_order_acquire)) {
|
||||
VisibilityUpdate update {};
|
||||
{
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
if (g_visibility_updates.empty()) {
|
||||
return true;
|
||||
}
|
||||
update = g_visibility_updates.front();
|
||||
g_visibility_updates.pop_front();
|
||||
}
|
||||
ClearInputEvents();
|
||||
g_last_external_keycode = 0;
|
||||
g_last_external_status = 0;
|
||||
g_input_generation = update.generation;
|
||||
g_input_active = update.visible;
|
||||
g_input_controller = update.capture_controller;
|
||||
g_input_keyboard = update.capture_keyboard;
|
||||
g_input_multiline = update.multiline;
|
||||
if (g_input_keyboard) {
|
||||
SDL_StartTextInput();
|
||||
} else if (SDL_IsTextInputActive() == SDL_TRUE) {
|
||||
SDL_StopTextInput();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (event.type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
if (g_input_active && g_input_controller) {
|
||||
QueueInput({InputKind::ResetController, g_input_generation, 0, 0.0f, 0.0f});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!g_input_active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint64_t generation = g_input_generation;
|
||||
const bool controller_event = event.type == SDL_CONTROLLERBUTTONDOWN ||
|
||||
event.type == SDL_CONTROLLERBUTTONUP ||
|
||||
event.type == SDL_CONTROLLERAXISMOTION;
|
||||
if (controller_event && !g_input_controller) {
|
||||
return false;
|
||||
}
|
||||
const bool keyboard_event = event.type == SDL_TEXTINPUT || event.type == SDL_TEXTEDITING ||
|
||||
event.type == SDL_KEYDOWN || event.type == SDL_KEYUP;
|
||||
if (keyboard_event && !g_input_keyboard) {
|
||||
return false;
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_TEXTINPUT: {
|
||||
const auto text = Utf8ToUtf16(event.text.text);
|
||||
if (!text.empty()) {
|
||||
auto input = MakeExternalInput(Ime::ExternalAction::Text, g_last_external_keycode,
|
||||
g_last_external_status | 0x00000002);
|
||||
input.key.character = text.front();
|
||||
input.text = text;
|
||||
Ime::HostQueueExternalInput(generation, std::move(input));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SDL_TEXTEDITING: return true;
|
||||
case SDL_KEYDOWN: {
|
||||
g_last_external_keycode = static_cast<uint16_t>(event.key.keysym.scancode);
|
||||
g_last_external_status =
|
||||
ExternalKeyStatus(static_cast<SDL_Keymod>(event.key.keysym.mod), false);
|
||||
auto action = Ime::ExternalAction::Text;
|
||||
bool queue = true;
|
||||
if (event.key.keysym.sym == SDLK_BACKSPACE) {
|
||||
action = Ime::ExternalAction::Backspace;
|
||||
} else if (event.key.keysym.sym == SDLK_LEFT) {
|
||||
action = Ime::ExternalAction::MoveLeft;
|
||||
} else if (event.key.keysym.sym == SDLK_RIGHT) {
|
||||
action = Ime::ExternalAction::MoveRight;
|
||||
} else if (event.key.keysym.sym == SDLK_ESCAPE) {
|
||||
action = Ime::ExternalAction::Cancel;
|
||||
} else if (event.key.keysym.sym == SDLK_RETURN ||
|
||||
event.key.keysym.sym == SDLK_KP_ENTER) {
|
||||
action =
|
||||
g_input_multiline ? Ime::ExternalAction::Newline : Ime::ExternalAction::Accept;
|
||||
} else if (event.key.keysym.sym == SDLK_TAB) {
|
||||
action = Ime::ExternalAction::None;
|
||||
} else {
|
||||
queue = false;
|
||||
}
|
||||
if (queue) {
|
||||
Ime::HostQueueExternalInput(
|
||||
generation,
|
||||
MakeExternalInput(action, g_last_external_keycode, g_last_external_status));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SDL_KEYUP: return true;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
QueueInput({InputKind::Button, generation, event.cbutton.button,
|
||||
event.type == SDL_CONTROLLERBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
|
||||
return true;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
QueueInput({InputKind::Axis, generation, event.caxis.axis,
|
||||
static_cast<float>(event.caxis.value), 0.0f});
|
||||
return true;
|
||||
case SDL_MOUSEMOTION:
|
||||
QueueInput({InputKind::MousePosition, generation, 0, static_cast<float>(event.motion.x),
|
||||
static_cast<float>(event.motion.y)});
|
||||
return true;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
QueueInput({InputKind::MouseButton, generation, event.button.button,
|
||||
event.type == SDL_MOUSEBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
|
||||
return true;
|
||||
case SDL_MOUSEWHEEL:
|
||||
QueueInput({InputKind::MouseWheel, generation, 0, static_cast<float>(event.wheel.x),
|
||||
static_cast<float>(event.wheel.y)});
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct ImeDialogOverlay::Impl {
|
||||
explicit Impl(GraphicContext& context): graphics(context) {}
|
||||
|
||||
~Impl() {
|
||||
ReleaseVulkan();
|
||||
if (imgui_context != nullptr) {
|
||||
ImGui::DestroyContext(imgui_context);
|
||||
}
|
||||
}
|
||||
|
||||
void EnsureContext() {
|
||||
if (imgui_context != nullptr) {
|
||||
ImGui::SetCurrentContext(imgui_context);
|
||||
return;
|
||||
}
|
||||
IMGUI_CHECKVERSION();
|
||||
imgui_context = ImGui::CreateContext();
|
||||
ImGui::SetCurrentContext(imgui_context);
|
||||
auto& io = ImGui::GetIO();
|
||||
io.IniFilename = nullptr;
|
||||
io.LogFilename = nullptr;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||
io.BackendPlatformName = "Kyty ImeDialog input";
|
||||
ImGui::StyleColorsDark();
|
||||
auto& style = ImGui::GetStyle();
|
||||
style.WindowRounding = 10.0f;
|
||||
style.FrameRounding = 6.0f;
|
||||
style.ItemSpacing = {8.0f, 8.0f};
|
||||
}
|
||||
|
||||
void EnsureVulkan(vk::Format format, uint32_t image_count) {
|
||||
EnsureContext();
|
||||
if (vulkan_initialized) {
|
||||
return;
|
||||
}
|
||||
EXIT_IF(image_count < 2);
|
||||
EXIT_IF(!ImGui_ImplVulkan_LoadFunctions(VK_API_VERSION_1_3, LoadVulkanFunction, &graphics));
|
||||
|
||||
const VkFormat color_format = static_cast<VkFormat>(format);
|
||||
ImGui_ImplVulkan_InitInfo info {};
|
||||
info.ApiVersion = VK_API_VERSION_1_3;
|
||||
info.Instance = static_cast<VkInstance>(graphics.instance);
|
||||
info.PhysicalDevice = static_cast<VkPhysicalDevice>(graphics.physical_device);
|
||||
info.Device = static_cast<VkDevice>(graphics.device);
|
||||
info.QueueFamily = graphics.queue_family;
|
||||
info.Queue = static_cast<VkQueue>(graphics.queue);
|
||||
info.DescriptorPoolSize = IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE;
|
||||
info.MinImageCount = image_count;
|
||||
info.ImageCount = image_count;
|
||||
info.UseDynamicRendering = true;
|
||||
info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
info.PipelineInfoMain.PipelineRenderingCreateInfo.sType =
|
||||
VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
|
||||
info.PipelineInfoMain.PipelineRenderingCreateInfo.colorAttachmentCount = 1;
|
||||
info.PipelineInfoMain.PipelineRenderingCreateInfo.pColorAttachmentFormats = &color_format;
|
||||
info.CheckVkResultFn = CheckVulkanResult;
|
||||
EXIT_IF(!ImGui_ImplVulkan_Init(&info));
|
||||
vulkan_initialized = true;
|
||||
}
|
||||
|
||||
void DrainInput(uint64_t generation) {
|
||||
std::deque<InputEvent> events;
|
||||
bool reset = false;
|
||||
{
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
events.swap(g_input_events);
|
||||
reset = g_input_reset_requested;
|
||||
g_input_reset_requested = false;
|
||||
}
|
||||
auto& io = ImGui::GetIO();
|
||||
if (reset) {
|
||||
io.ClearEventsQueue();
|
||||
io.ClearInputKeys();
|
||||
io.ClearInputMouse();
|
||||
right_stick = {};
|
||||
}
|
||||
for (const auto& event: events) {
|
||||
if (event.generation != generation) {
|
||||
continue;
|
||||
}
|
||||
switch (event.kind) {
|
||||
case InputKind::Button: {
|
||||
const bool down = event.x != 0.0f;
|
||||
if (down && event.id == SDL_CONTROLLER_BUTTON_B) {
|
||||
Ime::HostCancel(generation);
|
||||
} else if (down && event.id == SDL_CONTROLLER_BUTTON_Y) {
|
||||
Ime::HostBackspace(generation);
|
||||
}
|
||||
const ImGuiKey key = ControllerButtonToKey(event.id);
|
||||
if (key != ImGuiKey_None) {
|
||||
io.AddKeyEvent(key, down);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InputKind::Axis: {
|
||||
const float value = event.x / (event.x < 0.0f ? 32768.0f : 32767.0f);
|
||||
if (event.id == SDL_CONTROLLER_AXIS_LEFTX) {
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickLeft, value < -0.25f,
|
||||
std::max(-value, 0.0f));
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, value > 0.25f,
|
||||
std::max(value, 0.0f));
|
||||
} else if (event.id == SDL_CONTROLLER_AXIS_LEFTY) {
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickUp, value < -0.25f,
|
||||
std::max(-value, 0.0f));
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown, value > 0.25f,
|
||||
std::max(value, 0.0f));
|
||||
} else if (event.id == SDL_CONTROLLER_AXIS_RIGHTX) {
|
||||
right_stick.x = std::abs(value) > 0.2f ? value : 0.0f;
|
||||
} else if (event.id == SDL_CONTROLLER_AXIS_RIGHTY) {
|
||||
right_stick.y = std::abs(value) > 0.2f ? value : 0.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InputKind::MousePosition: io.AddMousePosEvent(event.x, event.y); break;
|
||||
case InputKind::MouseButton: {
|
||||
int button = -1;
|
||||
if (event.id == SDL_BUTTON_LEFT) button = 0;
|
||||
if (event.id == SDL_BUTTON_RIGHT) button = 1;
|
||||
if (event.id == SDL_BUTTON_MIDDLE) button = 2;
|
||||
if (button >= 0) io.AddMouseButtonEvent(button, event.x != 0.0f);
|
||||
break;
|
||||
}
|
||||
case InputKind::MouseWheel: io.AddMouseWheelEvent(event.x, event.y); break;
|
||||
case InputKind::ResetController:
|
||||
io.ClearEventsQueue();
|
||||
io.ClearInputKeys();
|
||||
right_stick = {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyButton(std::string_view label, char16_t value, uint64_t generation, float width,
|
||||
bool default_focus) {
|
||||
const bool pressed = ImGui::Button(label.data(), {width, button_height});
|
||||
if (default_focus) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
if (pressed) {
|
||||
Ime::HostInsertText(generation, std::u16string_view(&value, 1));
|
||||
}
|
||||
}
|
||||
|
||||
void DrawKeyRows(const Ime::HostSnapshot& snapshot, float width) {
|
||||
static constexpr std::array<std::string_view, 4> lower = {"1234567890", "qwertyuiop",
|
||||
"asdfghjkl", "zxcvbnm"};
|
||||
static constexpr std::array<std::string_view, 4> upper = {"1234567890", "QWERTYUIOP",
|
||||
"ASDFGHJKL", "ZXCVBNM"};
|
||||
static constexpr std::array<std::string_view, 3> symbols = {"1234567890", "!@#$%^&*()",
|
||||
"-_=+/:?."};
|
||||
static constexpr std::array<std::string_view, 4> number = {"123", "456", "789", "-0."};
|
||||
|
||||
std::span<const std::string_view> rows;
|
||||
if (snapshot.type == Ime::Type::Number) {
|
||||
rows = number;
|
||||
} else if (symbol_mode) {
|
||||
rows = symbols;
|
||||
} else {
|
||||
rows = shift ? std::span<const std::string_view>(upper)
|
||||
: std::span<const std::string_view>(lower);
|
||||
}
|
||||
bool first = focus_pending;
|
||||
size_t row_index = 0;
|
||||
for (const auto row: rows) {
|
||||
const float key_width =
|
||||
std::min(56.0f * ui_scale, (width - 8.0f * (row.size() - 1)) / row.size());
|
||||
const float row_width = key_width * row.size() + 8.0f * (row.size() - 1);
|
||||
ImGui::SetCursorPosX((width - row_width) * 0.5f);
|
||||
ImGui::PushID(static_cast<int>(row_index++));
|
||||
for (size_t i = 0; i < row.size(); i++) {
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
const char label[2] = {row[i], '\0'};
|
||||
KeyButton(label, static_cast<char16_t>(row[i]), snapshot.generation, key_width,
|
||||
first);
|
||||
first = false;
|
||||
ImGui::PopID();
|
||||
if (i + 1 < row.size()) ImGui::SameLine();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
focus_pending = false;
|
||||
}
|
||||
|
||||
void DrawDialog(const Ime::HostSnapshot& snapshot, vk::Extent2D extent) {
|
||||
const ImVec2 display(static_cast<float>(extent.width), static_cast<float>(extent.height));
|
||||
const bool over_2k = (snapshot.option & Ime::OPTION_USE_OVER_2K) != 0;
|
||||
const float reference_width = over_2k ? 3840.0f : 1920.0f;
|
||||
const float reference_height = over_2k ? 2160.0f : 1080.0f;
|
||||
const float scale_x = display.x / reference_width;
|
||||
const float scale_y = display.y / reference_height;
|
||||
const float width = static_cast<float>(snapshot.panel_width) * scale_x;
|
||||
const float height = static_cast<float>(snapshot.panel_height) * scale_y;
|
||||
ui_scale = std::min(scale_x, scale_y) * (over_2k ? 2.0f : 1.0f);
|
||||
button_height = std::max(28.0f, 42.0f * ui_scale);
|
||||
|
||||
const ImVec2 pivot {AlignmentPivot(snapshot.horizontal_alignment),
|
||||
AlignmentPivot(snapshot.vertical_alignment)};
|
||||
if ((snapshot.option & Ime::OPTION_FIXED_POSITION) == 0) {
|
||||
const float movement = 600.0f * ImGui::GetIO().DeltaTime;
|
||||
panel_offset.x += right_stick.x * movement;
|
||||
panel_offset.y += right_stick.y * movement;
|
||||
}
|
||||
const ImVec2 base_position {snapshot.posx * scale_x - pivot.x * width,
|
||||
snapshot.posy * scale_y - pivot.y * height};
|
||||
ImVec2 position {base_position.x + panel_offset.x, base_position.y + panel_offset.y};
|
||||
if ((snapshot.option & Ime::OPTION_DISABLE_POSITION_ADJ) == 0) {
|
||||
position.x = std::clamp(position.x, 0.0f, std::max(display.x - width, 0.0f));
|
||||
position.y = std::clamp(position.y, 0.0f, std::max(display.y - height, 0.0f));
|
||||
panel_offset = {position.x - base_position.x, position.y - base_position.y};
|
||||
}
|
||||
ImGui::SetNextWindowPos(position, ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize({width, height}, ImGuiCond_Always);
|
||||
constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoSavedSettings;
|
||||
ImGui::Begin("##ImeDialog", nullptr, flags);
|
||||
|
||||
const std::u16string title = snapshot.title.empty() ? u"Enter text" : snapshot.title;
|
||||
if (!snapshot.key_panel_visible) {
|
||||
std::string compact = Common::Utf16ToUtf8(title.c_str()) + ": ";
|
||||
const float glyph_width = std::max(ImGui::CalcTextSize("M").x, 1.0f);
|
||||
const float text_width =
|
||||
std::max(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(compact.c_str()).x,
|
||||
glyph_width);
|
||||
const size_t visible_units =
|
||||
std::max<size_t>(6, static_cast<size_t>(text_width / glyph_width));
|
||||
compact += snapshot.text.empty() && !snapshot.placeholder.empty()
|
||||
? Common::Utf16ToUtf8(snapshot.placeholder.c_str())
|
||||
: VisibleText(snapshot, visible_units);
|
||||
ImGui::TextUnformatted(compact.c_str());
|
||||
const float compact_height = std::min(button_height, 28.0f * ui_scale);
|
||||
if (ImGui::Button("Cancel", {90.0f * ui_scale, compact_height})) {
|
||||
Ime::HostCancel(snapshot.generation);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(EnterLabel(snapshot.enter_label),
|
||||
{90.0f * ui_scale, compact_height})) {
|
||||
Ime::HostAccept(snapshot.generation);
|
||||
}
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted(Common::Utf16ToUtf8(title.c_str()).c_str());
|
||||
ImGui::Separator();
|
||||
const float text_height = std::max(
|
||||
32.0f, ((snapshot.option & Ime::OPTION_MULTILINE) != 0 ? 80.0f : 48.0f) * ui_scale);
|
||||
ImGui::BeginChild("##ImeText", {0.0f, text_height}, true);
|
||||
if (snapshot.text.empty() && !snapshot.placeholder.empty()) {
|
||||
ImGui::TextDisabled("%s", Common::Utf16ToUtf8(snapshot.placeholder.c_str()).c_str());
|
||||
} else {
|
||||
const float glyph_width = std::max(ImGui::CalcTextSize("M").x, 1.0f);
|
||||
const size_t columns = std::max<size_t>(
|
||||
8, static_cast<size_t>(ImGui::GetContentRegionAvail().x / glyph_width));
|
||||
const size_t lines =
|
||||
std::max<size_t>(1, static_cast<size_t>(ImGui::GetContentRegionAvail().y /
|
||||
ImGui::GetTextLineHeightWithSpacing()));
|
||||
const auto text = VisibleText(snapshot, columns * lines);
|
||||
ImGui::TextWrapped("%s", text.c_str());
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::TextDisabled("%zu / %u", snapshot.text.size(), snapshot.max_text_length);
|
||||
ImGui::Separator();
|
||||
|
||||
const float content_width = ImGui::GetContentRegionAvail().x;
|
||||
DrawKeyRows(snapshot, content_width);
|
||||
if (snapshot.type != Ime::Type::Number) {
|
||||
if (ImGui::Button(shift ? "Lower" : "Shift", {84.0f * ui_scale, button_height})) {
|
||||
shift = !shift;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(symbol_mode ? "ABC" : "Symbols", {84.0f * ui_scale, button_height})) {
|
||||
symbol_mode = !symbol_mode;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Space", {120.0f * ui_scale, button_height})) {
|
||||
Ime::HostInsertText(snapshot.generation, u" ");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
const float action_width = snapshot.type == Ime::Type::Number
|
||||
? std::max((content_width - 16.0f) / 3.0f, 48.0f)
|
||||
: 100.0f * ui_scale;
|
||||
if (ImGui::Button("Backspace", {action_width, button_height})) {
|
||||
Ime::HostBackspace(snapshot.generation);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if ((snapshot.option & Ime::OPTION_MULTILINE) != 0) {
|
||||
if (ImGui::Button("Newline", {action_width, button_height})) {
|
||||
Ime::HostInsertText(snapshot.generation, u"\n");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (ImGui::Button("Cancel", {action_width, button_height})) {
|
||||
Ime::HostCancel(snapshot.generation);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(EnterLabel(snapshot.enter_label), {action_width, button_height})) {
|
||||
Ime::HostAccept(snapshot.generation);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
bool PrepareFrame(vk::Extent2D frame_extent, vk::Format format, uint32_t image_count) {
|
||||
Ime::HostSnapshot snapshot;
|
||||
if (!Ime::GetHostSnapshot(&snapshot)) {
|
||||
return false;
|
||||
}
|
||||
const uint64_t prepared_generation = snapshot.generation;
|
||||
EnsureVulkan(format, image_count);
|
||||
if (generation != snapshot.generation) {
|
||||
generation = snapshot.generation;
|
||||
focus_pending = true;
|
||||
shift = (snapshot.option & Ime::OPTION_NO_AUTO_CAPITALIZE) == 0;
|
||||
symbol_mode = false;
|
||||
panel_offset = {};
|
||||
right_stick = {};
|
||||
auto& io = ImGui::GetIO();
|
||||
io.ClearEventsQueue();
|
||||
io.ClearInputKeys();
|
||||
io.ClearInputMouse();
|
||||
}
|
||||
DrainInput(snapshot.generation);
|
||||
|
||||
auto& io = ImGui::GetIO();
|
||||
io.DisplaySize = {static_cast<float>(frame_extent.width),
|
||||
static_cast<float>(frame_extent.height)};
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
io.DeltaTime = last_frame == std::chrono::steady_clock::time_point {}
|
||||
? 1.0f / 60.0f
|
||||
: std::clamp(std::chrono::duration<float>(now - last_frame).count(),
|
||||
1.0f / 1000.0f, 0.1f);
|
||||
last_frame = now;
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
if (!Ime::GetHostSnapshot(&snapshot) || snapshot.generation != prepared_generation) {
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
DrawDialog(snapshot, frame_extent);
|
||||
ImGui::Render();
|
||||
extent = frame_extent;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Record(vk::CommandBuffer command, vk::ImageView target) {
|
||||
vk::RenderingAttachmentInfo color {};
|
||||
color.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
color.imageView = target;
|
||||
color.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
color.loadOp = vk::AttachmentLoadOp::eLoad;
|
||||
color.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.sType = vk::StructureType::eRenderingInfo;
|
||||
rendering.renderArea.extent = extent;
|
||||
rendering.layerCount = 1;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &color;
|
||||
command.beginRendering(rendering);
|
||||
{
|
||||
Common::LockGuard queue_lock(graphics.queue_mutex);
|
||||
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(),
|
||||
static_cast<VkCommandBuffer>(command));
|
||||
}
|
||||
command.endRendering();
|
||||
}
|
||||
|
||||
void ReleaseVulkan() {
|
||||
if (!vulkan_initialized) {
|
||||
return;
|
||||
}
|
||||
ImGui::SetCurrentContext(imgui_context);
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
vulkan_initialized = false;
|
||||
}
|
||||
|
||||
GraphicContext& graphics;
|
||||
ImGuiContext* imgui_context = nullptr;
|
||||
bool vulkan_initialized = false;
|
||||
bool shift = false;
|
||||
bool symbol_mode = false;
|
||||
bool focus_pending = true;
|
||||
float ui_scale = 1.0f;
|
||||
float button_height = 42.0f;
|
||||
ImVec2 panel_offset {};
|
||||
ImVec2 right_stick {};
|
||||
uint64_t generation = 0;
|
||||
vk::Extent2D extent {};
|
||||
std::chrono::steady_clock::time_point last_frame;
|
||||
};
|
||||
|
||||
ImeDialogOverlay::ImeDialogOverlay(GraphicContext& graphics)
|
||||
: m_impl(std::make_unique<Impl>(graphics)) {}
|
||||
|
||||
ImeDialogOverlay::~ImeDialogOverlay() = default;
|
||||
|
||||
bool ImeDialogOverlay::PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count) {
|
||||
return m_impl->PrepareFrame(extent, format, image_count);
|
||||
}
|
||||
|
||||
void ImeDialogOverlay::Record(vk::CommandBuffer command, vk::ImageView target) {
|
||||
m_impl->Record(command, target);
|
||||
}
|
||||
|
||||
void ImeDialogOverlay::ReleaseVulkan() {
|
||||
m_impl->ReleaseVulkan();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
union SDL_Event;
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
|
||||
void InitializeImeDialogInput();
|
||||
void ShutdownImeDialogInput();
|
||||
bool ProcessImeDialogInput(const SDL_Event& event);
|
||||
|
||||
class ImeDialogOverlay final {
|
||||
public:
|
||||
explicit ImeDialogOverlay(GraphicContext& graphics);
|
||||
~ImeDialogOverlay();
|
||||
KYTY_CLASS_NO_COPY(ImeDialogOverlay);
|
||||
|
||||
[[nodiscard]] bool PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count);
|
||||
void Record(vk::CommandBuffer command, vk::ImageView target);
|
||||
void ReleaseVulkan();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
|
||||
@@ -25,6 +25,7 @@ public:
|
||||
CommandBuffer* producer = nullptr);
|
||||
[[nodiscard]] Frame* PrepareLastFrame();
|
||||
[[nodiscard]] bool IsGuestPaused() const noexcept;
|
||||
[[nodiscard]] bool NeedsImeRefresh() const noexcept;
|
||||
[[nodiscard]] RenderContext& Renderer() const noexcept;
|
||||
void Present(Frame& frame, bool reuse = false);
|
||||
void Discard(Frame& frame);
|
||||
|
||||
@@ -817,7 +817,24 @@ void VideoOutDriver::Impl::PresentThread(std::stop_token token) {
|
||||
}
|
||||
|
||||
VblankBegin();
|
||||
const bool presented = m_flip_queue.Flip(0);
|
||||
bool presented = m_flip_queue.Flip(0);
|
||||
if (!presented && m_presenter.NeedsImeRefresh()) {
|
||||
if (auto* frame = m_presenter.PrepareLastFrame(); frame != nullptr) {
|
||||
m_presenter.Present(*frame, true);
|
||||
presented = true;
|
||||
} else {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
{
|
||||
Common::LockGuard lock(m_mutex);
|
||||
width = m_video_out_ctx[0].width;
|
||||
height = m_video_out_ctx[0].height;
|
||||
}
|
||||
auto& blank = m_presenter.PrepareBlankFrame(width, height, true);
|
||||
m_presenter.Present(blank);
|
||||
presented = true;
|
||||
}
|
||||
}
|
||||
if (!presented && total_wait < 0) {
|
||||
bool any_open = false;
|
||||
uint32_t width = 0;
|
||||
|
||||
@@ -31,11 +31,13 @@
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/imeDialogOverlay.h"
|
||||
#include "graphics/presentation/presenter.h"
|
||||
#include "graphics/presentation/renderDoc.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
#include "libs/controller.h"
|
||||
#include "libs/imeDialog.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -329,8 +331,9 @@ public:
|
||||
void Create();
|
||||
void Recreate(bool surface_lost = false);
|
||||
[[nodiscard]] Status AcquireNextImage();
|
||||
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source);
|
||||
void Submit(CommandBuffer& command);
|
||||
[[nodiscard]] bool PrepareImeOverlay();
|
||||
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source, bool draw_ime_overlay);
|
||||
void Submit(CommandBuffer& command);
|
||||
[[nodiscard]] Status Present();
|
||||
|
||||
[[nodiscard]] uint32_t ImageCount() const noexcept {
|
||||
@@ -342,16 +345,17 @@ private:
|
||||
void Destroy();
|
||||
void RefreshSurfaceSize();
|
||||
|
||||
WindowContext& m_window;
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
vk::Extent2D m_extent {};
|
||||
std::vector<vk::Image> m_images;
|
||||
std::vector<vk::ImageView> m_image_views;
|
||||
std::vector<vk::Semaphore> m_image_acquired;
|
||||
std::vector<vk::Semaphore> m_render_complete;
|
||||
uint32_t m_image_index = static_cast<uint32_t>(-1);
|
||||
uint32_t m_frame_index = 0;
|
||||
WindowContext& m_window;
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
vk::Extent2D m_extent {};
|
||||
std::vector<vk::Image> m_images;
|
||||
std::vector<vk::ImageView> m_image_views;
|
||||
std::vector<vk::Semaphore> m_image_acquired;
|
||||
std::vector<vk::Semaphore> m_render_complete;
|
||||
std::unique_ptr<ImeDialogOverlay> m_ime_overlay;
|
||||
uint32_t m_image_index = static_cast<uint32_t>(-1);
|
||||
uint32_t m_frame_index = 0;
|
||||
};
|
||||
|
||||
struct Presenter::Impl {
|
||||
@@ -389,11 +393,12 @@ struct Presenter::Impl {
|
||||
return image;
|
||||
}
|
||||
|
||||
RenderContext& renderer;
|
||||
WindowContext& window;
|
||||
Swapchain swapchain;
|
||||
CommandScheduler present_scheduler;
|
||||
FramePool frames;
|
||||
RenderContext& renderer;
|
||||
WindowContext& window;
|
||||
Swapchain swapchain;
|
||||
CommandScheduler present_scheduler;
|
||||
FramePool frames;
|
||||
std::atomic<uint64_t> presented_ime_revision {0};
|
||||
};
|
||||
|
||||
void Swapchain::Create() {
|
||||
@@ -522,6 +527,9 @@ void Swapchain::Destroy() {
|
||||
Common::LockGuard queue_lock(graphics.queue_mutex);
|
||||
RequireVulkanSuccess(graphics.queue.waitIdle(), "wait for swapchain queue");
|
||||
}
|
||||
if (m_ime_overlay != nullptr) {
|
||||
m_ime_overlay->ReleaseVulkan();
|
||||
}
|
||||
|
||||
for (const auto semaphore: m_image_acquired) {
|
||||
if (semaphore != nullptr) {
|
||||
@@ -606,7 +614,15 @@ Swapchain::Status Swapchain::AcquireNextImage() {
|
||||
return Status::Success;
|
||||
}
|
||||
|
||||
void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& source) {
|
||||
bool Swapchain::PrepareImeOverlay() {
|
||||
if (m_ime_overlay == nullptr) {
|
||||
m_ime_overlay = std::make_unique<ImeDialogOverlay>(m_window.graphic_ctx);
|
||||
}
|
||||
return m_ime_overlay->PrepareFrame(m_extent, m_format, ImageCount());
|
||||
}
|
||||
|
||||
void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& source,
|
||||
bool draw_ime_overlay) {
|
||||
if (source.state.layout != vk::ImageLayout::eTransferSrcOptimal) {
|
||||
EXIT("invalid prepared presentation image, vk_image=%p layout=%d\n",
|
||||
static_cast<void*>(source.image), static_cast<int>(source.state.layout));
|
||||
@@ -652,11 +668,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
|
||||
vk::Filter::eLinear);
|
||||
|
||||
vk::ImageMemoryBarrier to_present {};
|
||||
to_present.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
to_present.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
|
||||
to_present.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
to_present.newLayout = vk::ImageLayout::ePresentSrcKHR;
|
||||
to_present.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
to_present.dstAccessMask = draw_ime_overlay ? vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite
|
||||
: vk::AccessFlagBits::eMemoryRead;
|
||||
to_present.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
to_present.newLayout = draw_ime_overlay ? vk::ImageLayout::eColorAttachmentOptimal
|
||||
: vk::ImageLayout::ePresentSrcKHR;
|
||||
to_present.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
to_present.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
to_present.image = m_images[m_image_index];
|
||||
@@ -665,9 +684,22 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
|
||||
to_present.subresourceRange.levelCount = 1;
|
||||
to_present.subresourceRange.baseArrayLayer = 0;
|
||||
to_present.subresourceRange.layerCount = 1;
|
||||
vk_command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1, &to_present);
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
draw_ime_overlay ? vk::PipelineStageFlagBits::eColorAttachmentOutput
|
||||
: vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1,
|
||||
&to_present);
|
||||
if (draw_ime_overlay) {
|
||||
m_ime_overlay->Record(vk_command, m_image_views[m_image_index]);
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
to_present.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
|
||||
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
to_present.newLayout = vk::ImageLayout::ePresentSrcKHR;
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1,
|
||||
&to_present);
|
||||
}
|
||||
command.End();
|
||||
}
|
||||
|
||||
@@ -772,6 +804,12 @@ bool Presenter::IsGuestPaused() const noexcept {
|
||||
return m_impl->window.loop.paused.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool Presenter::NeedsImeRefresh() const noexcept {
|
||||
const auto visual = Dialog::ImeDialog::GetVisualState();
|
||||
return visual.active ||
|
||||
visual.revision != m_impl->presented_ime_revision.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
RenderContext& Presenter::Renderer() const noexcept {
|
||||
return m_impl->renderer;
|
||||
}
|
||||
@@ -803,7 +841,8 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
|
||||
}
|
||||
|
||||
auto& swapchain = m_impl->swapchain;
|
||||
const auto ime_visual = Dialog::ImeDialog::GetVisualState();
|
||||
auto& swapchain = m_impl->swapchain;
|
||||
for (uint32_t attempt = 0; attempt < 2; attempt++) {
|
||||
auto status = swapchain.AcquireNextImage();
|
||||
if (status != Swapchain::Status::Success) {
|
||||
@@ -816,8 +855,9 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
{
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
frame.present_commands->WaitForFenceAndReset();
|
||||
auto& command = *frame.present_commands;
|
||||
swapchain.RecordPresentCommands(command, frame.image);
|
||||
auto& command = *frame.present_commands;
|
||||
const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay();
|
||||
swapchain.RecordPresentCommands(command, frame.image, draw_ime_overlay);
|
||||
swapchain.Submit(command);
|
||||
}
|
||||
status = swapchain.Present();
|
||||
@@ -827,6 +867,7 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
}
|
||||
|
||||
RenderDocOnPresent();
|
||||
m_impl->presented_ime_revision.store(ime_visual.revision, std::memory_order_release);
|
||||
window.UpdateTitle();
|
||||
m_impl->frames.Release(&frame, true);
|
||||
return;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/imeDialogOverlay.h"
|
||||
#include "graphics/presentation/presenter.h"
|
||||
#include "graphics/presentation/renderDoc.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
@@ -1008,6 +1009,7 @@ void WindowContext::RecreateSurface() {
|
||||
}
|
||||
|
||||
WindowContext::~WindowContext() {
|
||||
ShutdownImeDialogInput();
|
||||
presenter.reset();
|
||||
LibKernel::Memory::InstallGpuResources(nullptr);
|
||||
render_context.reset();
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/imeDialogOverlay.h"
|
||||
#include "graphics/presentation/renderDoc.h"
|
||||
#include "graphics/presentation/window/hostInput.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
@@ -476,6 +477,9 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
auto& game = loop;
|
||||
auto* event = &game.event;
|
||||
EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE);
|
||||
if (ProcessImeDialogInput(*event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_QUIT: GameEventQuit(game); break;
|
||||
@@ -787,6 +791,7 @@ static void WindowCreate(WindowContext& context) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
HostInputInit();
|
||||
InitializeImeDialogInput();
|
||||
|
||||
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
void Disconnect(int id);
|
||||
void Button(int id, uint32_t button, bool down);
|
||||
void Axis(int id, Axis axis, int value);
|
||||
void ResetInputState();
|
||||
void GetConnectionInfo(bool* flag, int* count);
|
||||
void ReadState(ControllerState* state, bool* flag, int* count);
|
||||
int ReadStates(ControllerState* states, int states_num, bool* flag, int* count);
|
||||
@@ -256,6 +257,15 @@ void GameController::Axis(int id, Controller::Axis axis, int value) {
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::ResetInputState() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
ControllerState state {};
|
||||
state.time = LibKernel::KernelGetProcessTime();
|
||||
m_states_num = 0;
|
||||
m_first_state = 0;
|
||||
AddState(state);
|
||||
}
|
||||
|
||||
void GameController::GetConnectionInfo(bool* flag, int* count) {
|
||||
EXIT_IF(flag == nullptr);
|
||||
EXIT_IF(count == nullptr);
|
||||
@@ -334,6 +344,11 @@ void ControllerAxis(int id, Axis axis, int value) {
|
||||
g_controller->Axis(id, axis, value);
|
||||
}
|
||||
|
||||
void ControllerResetInputState() {
|
||||
EXIT_IF(g_controller == nullptr);
|
||||
g_controller->ResetInputState();
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadInit() {
|
||||
PRINT_NAME();
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ void ControllerConnect(int id);
|
||||
void ControllerDisconnect(int id);
|
||||
void ControllerButton(int id, uint32_t button, bool down);
|
||||
void ControllerAxis(int id, Axis axis, int value);
|
||||
void ControllerResetInputState();
|
||||
|
||||
int KYTY_SYSV_ABI PadInit();
|
||||
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param);
|
||||
|
||||
@@ -23,20 +23,6 @@ int KYTY_SYSV_ABI CommonDialogInitialize() {
|
||||
|
||||
} // namespace CommonDialog
|
||||
|
||||
namespace ImeDialog {
|
||||
|
||||
LIB_NAME("ImeDialog", "ImeDialog");
|
||||
|
||||
constexpr int IME_STATUS_NONE = 0;
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogGetStatus() {
|
||||
PRINT_NAME();
|
||||
|
||||
return IME_STATUS_NONE;
|
||||
}
|
||||
|
||||
} // namespace ImeDialog
|
||||
|
||||
namespace LoginDialog {
|
||||
|
||||
LIB_NAME("LoginDialog", "LoginDialog");
|
||||
|
||||
@@ -12,12 +12,6 @@ int KYTY_SYSV_ABI CommonDialogInitialize();
|
||||
|
||||
} // namespace CommonDialog
|
||||
|
||||
namespace ImeDialog {
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogGetStatus();
|
||||
|
||||
} // namespace ImeDialog
|
||||
|
||||
namespace LoginDialog {
|
||||
|
||||
int KYTY_SYSV_ABI LoginDialogInitialize();
|
||||
|
||||
@@ -0,0 +1,781 @@
|
||||
#include "libs/imeDialog.h"
|
||||
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Dialog::ImeDialog {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int Error(uint32_t value) {
|
||||
return static_cast<int32_t>(value);
|
||||
}
|
||||
|
||||
constexpr int ERROR_BUSY = Error(0x80bc0001);
|
||||
constexpr int ERROR_INVALID_USER_ID = Error(0x80bc0010);
|
||||
constexpr int ERROR_INVALID_TYPE = Error(0x80bc0011);
|
||||
constexpr int ERROR_INVALID_LANGUAGES = Error(0x80bc0012);
|
||||
constexpr int ERROR_INVALID_ENTER_LABEL = Error(0x80bc0013);
|
||||
constexpr int ERROR_INVALID_INPUT_METHOD = Error(0x80bc0014);
|
||||
constexpr int ERROR_INVALID_OPTION = Error(0x80bc0015);
|
||||
constexpr int ERROR_INVALID_MAX_TEXT_LENGTH = Error(0x80bc0016);
|
||||
constexpr int ERROR_INVALID_TEXT_BUFFER = Error(0x80bc0017);
|
||||
constexpr int ERROR_INVALID_POSX = Error(0x80bc0018);
|
||||
constexpr int ERROR_INVALID_POSY = Error(0x80bc0019);
|
||||
constexpr int ERROR_INVALID_HALIGN = Error(0x80bc001a);
|
||||
constexpr int ERROR_INVALID_VALIGN = Error(0x80bc001b);
|
||||
constexpr int ERROR_INVALID_EXTENDED = Error(0x80bc001c);
|
||||
constexpr int ERROR_INVALID_PARAM = Error(0x80bc0030);
|
||||
constexpr int ERROR_INVALID_ADDRESS = Error(0x80bc0031);
|
||||
constexpr int ERROR_INVALID_RESERVED = Error(0x80bc0032);
|
||||
constexpr int ERROR_INVALID_TITLE = Error(0x80bc0101);
|
||||
constexpr int ERROR_NOT_RUNNING = Error(0x80bc0105);
|
||||
constexpr int ERROR_NOT_FINISHED = Error(0x80bc0106);
|
||||
constexpr int ERROR_NOT_IN_USE = Error(0x80bc0107);
|
||||
|
||||
constexpr uint32_t VALID_OPTIONS = 0x00007bff;
|
||||
constexpr uint32_t VALID_EXTENDED_OPTIONS = 0x00005fde;
|
||||
constexpr uint32_t VALID_EXT_KEYBOARD_MODE = 0x1c000003;
|
||||
constexpr uint64_t VALID_LANGUAGES = 0x00000001ff1fffffULL;
|
||||
|
||||
struct State {
|
||||
Status status = Status::None;
|
||||
EndStatus end_status = EndStatus::Ok;
|
||||
uint64_t generation = 0;
|
||||
uint64_t revision = 0;
|
||||
Param param {};
|
||||
ExtendedParam extended {};
|
||||
bool input_changed = false;
|
||||
bool commit_pending = false;
|
||||
uint32_t cursor = 0;
|
||||
std::u16string original_text;
|
||||
std::u16string current_text;
|
||||
std::u16string title;
|
||||
std::u16string placeholder;
|
||||
std::vector<ExternalInput> external_inputs;
|
||||
};
|
||||
|
||||
std::mutex g_mutex;
|
||||
State g_state;
|
||||
std::atomic<Status> g_status {Status::None};
|
||||
std::atomic<uint64_t> g_revision {0};
|
||||
std::atomic<VisibilityCallback> g_visibility_callback {nullptr};
|
||||
|
||||
bool AllZero(const int8_t* data, size_t size) {
|
||||
return std::all_of(data, data + size, [](int8_t value) { return value == 0; });
|
||||
}
|
||||
|
||||
bool IsValidUtf16(std::u16string_view text);
|
||||
|
||||
bool ReadBounded(const char16_t* text, uint32_t limit, std::u16string* out) {
|
||||
out->clear();
|
||||
if (text == nullptr) {
|
||||
return true;
|
||||
}
|
||||
out->reserve(limit);
|
||||
for (uint32_t i = 0; i <= limit; ++i) {
|
||||
const char16_t value = text[i];
|
||||
if (value == u'\0') {
|
||||
return IsValidUtf16(*out);
|
||||
}
|
||||
if (i == limit) {
|
||||
return false;
|
||||
}
|
||||
out->push_back(value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsValidUtf16(std::u16string_view text) {
|
||||
for (size_t i = 0; i < text.size(); i++) {
|
||||
const char16_t current = text[i];
|
||||
if (current >= 0xd800 && current <= 0xdbff) {
|
||||
if (++i >= text.size() || text[i] < 0xdc00 || text[i] > 0xdfff) {
|
||||
return false;
|
||||
}
|
||||
} else if (current >= 0xdc00 && current <= 0xdfff) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsAllowedInput(char16_t value, Type type, uint32_t option) {
|
||||
if (value == u'\r') {
|
||||
return false;
|
||||
}
|
||||
if (value == u'\n') {
|
||||
return (option & OPTION_MULTILINE) != 0;
|
||||
}
|
||||
if (value == u'\0') {
|
||||
return false;
|
||||
}
|
||||
if (type == Type::Number) {
|
||||
return (value >= u'0' && value <= u'9') || value == u',' || value == u'-' || value == u'.';
|
||||
}
|
||||
if (type == Type::BasicLatin) {
|
||||
return value >= u' ' && value <= u'~';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
char16_t HidCharacter(uint16_t keycode, uint32_t status) {
|
||||
const bool shift = (status & 0x00002200) != 0;
|
||||
const bool caps = (status & 0x00020000) != 0;
|
||||
if (keycode >= 4 && keycode <= 29) {
|
||||
const bool upper = shift != caps;
|
||||
return static_cast<char16_t>((upper ? u'A' : u'a') + keycode - 4);
|
||||
}
|
||||
if (keycode >= 30 && keycode <= 39) {
|
||||
static constexpr char16_t plain[] = u"1234567890";
|
||||
static constexpr char16_t shifted[] = u"!@#$%^&*()";
|
||||
return (shift ? shifted : plain)[keycode - 30];
|
||||
}
|
||||
if (keycode == 44) {
|
||||
return u' ';
|
||||
}
|
||||
if (keycode >= 45 && keycode <= 56) {
|
||||
static constexpr char16_t plain[] = u"-=[]\\#;'`,./";
|
||||
static constexpr char16_t shifted[] = u"_+{}|~:\"~<>?";
|
||||
return (shift ? shifted : plain)[keycode - 45];
|
||||
}
|
||||
if (keycode >= 89 && keycode <= 98) {
|
||||
static constexpr char16_t keypad[] = u"1234567890";
|
||||
return keypad[keycode - 89];
|
||||
}
|
||||
if (keycode == 99) {
|
||||
return u'.';
|
||||
}
|
||||
return u'\0';
|
||||
}
|
||||
|
||||
bool ApplyKeyboardFilterOutput(ExternalInput* input, uint16_t keycode, uint32_t status,
|
||||
bool multiline) {
|
||||
constexpr uint32_t KEYCODE_VALID = 0x00000001;
|
||||
constexpr uint32_t CHARACTER_VALID = 0x00000002;
|
||||
if (keycode == input->key.keycode && status == input->key.status) {
|
||||
return true;
|
||||
}
|
||||
if ((status & KEYCODE_VALID) == 0 || keycode == 0) {
|
||||
return input->action == ExternalAction::Text && (status & CHARACTER_VALID) != 0;
|
||||
}
|
||||
input->key.keycode = keycode;
|
||||
input->key.status = status;
|
||||
input->text.clear();
|
||||
switch (keycode) {
|
||||
case 40:
|
||||
case 88:
|
||||
case 158:
|
||||
input->action = multiline ? ExternalAction::Newline : ExternalAction::Accept;
|
||||
break;
|
||||
case 41: input->action = ExternalAction::Cancel; break;
|
||||
case 42:
|
||||
case 187: input->action = ExternalAction::Backspace; break;
|
||||
case 43:
|
||||
case 186: input->action = ExternalAction::None; break;
|
||||
case 79: input->action = ExternalAction::MoveRight; break;
|
||||
case 80: input->action = ExternalAction::MoveLeft; break;
|
||||
default: {
|
||||
const char16_t character = HidCharacter(keycode, status);
|
||||
if (character == u'\0') {
|
||||
return false;
|
||||
}
|
||||
input->action = ExternalAction::Text;
|
||||
input->key.character = character;
|
||||
input->text.push_back(character);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClampText(std::u16string* text, uint32_t limit) {
|
||||
if (text->size() <= limit) {
|
||||
return;
|
||||
}
|
||||
text->resize(limit);
|
||||
if (!text->empty() && text->back() >= 0xd800 && text->back() <= 0xdbff) {
|
||||
text->pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t NormalizeCursor(std::u16string_view text, uint32_t cursor) {
|
||||
cursor = std::min<uint32_t>(cursor, static_cast<uint32_t>(text.size()));
|
||||
if (cursor > 0 && cursor < text.size() && text[cursor - 1] >= 0xd800 &&
|
||||
text[cursor - 1] <= 0xdbff && text[cursor] >= 0xdc00 && text[cursor] <= 0xdfff) {
|
||||
cursor++;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void WriteGuestText(const State& state, const std::u16string& text) {
|
||||
if (state.param.input_text_buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
const size_t size = std::min<size_t>(text.size(), state.param.max_text_length);
|
||||
std::memcpy(state.param.input_text_buffer, text.data(), size * sizeof(char16_t));
|
||||
state.param.input_text_buffer[size] = u'\0';
|
||||
}
|
||||
|
||||
void NotifyVisibility(bool visible, uint64_t generation) {
|
||||
if (const auto callback = g_visibility_callback.load(std::memory_order_acquire);
|
||||
callback != nullptr) {
|
||||
callback(visible, generation);
|
||||
}
|
||||
}
|
||||
|
||||
int ValidateExtended(const ExtendedParam* extended) {
|
||||
if (extended == nullptr) {
|
||||
return OK;
|
||||
}
|
||||
if ((extended->option & ~VALID_EXTENDED_OPTIONS) != 0 ||
|
||||
((extended->option & 0x00004000) != 0 && (extended->option & 0x00000080) == 0) ||
|
||||
extended->priority > 3 || extended->disable_device > 7 ||
|
||||
(extended->ext_keyboard_mode & ~VALID_EXT_KEYBOARD_MODE) != 0 ||
|
||||
!AllZero(extended->reserved, sizeof(extended->reserved))) {
|
||||
return ERROR_INVALID_EXTENDED;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
int ValidateParam(const Param* param, const ExtendedParam* extended, std::u16string* initial,
|
||||
std::u16string* title, std::u16string* placeholder) {
|
||||
if (param == nullptr) {
|
||||
return ERROR_INVALID_ADDRESS;
|
||||
}
|
||||
if (static_cast<uint32_t>(param->type) > static_cast<uint32_t>(Type::Number)) {
|
||||
return ERROR_INVALID_TYPE;
|
||||
}
|
||||
if ((param->option & ~VALID_OPTIONS) != 0) {
|
||||
return ERROR_INVALID_OPTION;
|
||||
}
|
||||
if ((param->supported_languages & ~VALID_LANGUAGES) != 0) {
|
||||
return ERROR_INVALID_LANGUAGES;
|
||||
}
|
||||
const bool over_2k = (param->option & OPTION_USE_OVER_2K) != 0;
|
||||
const float max_x = over_2k ? 3840.0f : 1920.0f;
|
||||
const float max_y = over_2k ? 2160.0f : 1080.0f;
|
||||
if (!std::isfinite(param->posx) || param->posx < 0.0f || param->posx >= max_x) {
|
||||
return ERROR_INVALID_POSX;
|
||||
}
|
||||
if (!std::isfinite(param->posy) || param->posy < 0.0f || param->posy >= max_y) {
|
||||
return ERROR_INVALID_POSY;
|
||||
}
|
||||
if (static_cast<uint32_t>(param->horizontal_alignment) > 2) {
|
||||
return ERROR_INVALID_HALIGN;
|
||||
}
|
||||
if (static_cast<uint32_t>(param->vertical_alignment) > 2) {
|
||||
return ERROR_INVALID_VALIGN;
|
||||
}
|
||||
const bool multiline = (param->option & OPTION_MULTILINE) != 0;
|
||||
const bool password = (param->option & OPTION_PASSWORD) != 0;
|
||||
if ((multiline && password) ||
|
||||
(multiline && param->type != Type::Default && param->type != Type::BasicLatin) ||
|
||||
(password && param->type != Type::BasicLatin && param->type != Type::Number)) {
|
||||
return ERROR_INVALID_PARAM;
|
||||
}
|
||||
if (param->user_id < 0 || param->user_id == 0xff) {
|
||||
return ERROR_INVALID_USER_ID;
|
||||
}
|
||||
if (!AllZero(param->reserved, sizeof(param->reserved))) {
|
||||
return ERROR_INVALID_RESERVED;
|
||||
}
|
||||
if (param->input_text_buffer == nullptr) {
|
||||
return ERROR_INVALID_TEXT_BUFFER;
|
||||
}
|
||||
const int extended_result = ValidateExtended(extended);
|
||||
if (extended_result != OK) {
|
||||
return extended_result;
|
||||
}
|
||||
if (static_cast<uint32_t>(param->enter_label) > static_cast<uint32_t>(EnterLabel::Go)) {
|
||||
return ERROR_INVALID_ENTER_LABEL;
|
||||
}
|
||||
if (param->input_method != 0) {
|
||||
return ERROR_INVALID_INPUT_METHOD;
|
||||
}
|
||||
if (param->max_text_length == 0 || param->max_text_length > IME_DIALOG_MAX_TEXT_LENGTH) {
|
||||
return ERROR_INVALID_MAX_TEXT_LENGTH;
|
||||
}
|
||||
if (!ReadBounded(param->input_text_buffer, param->max_text_length, initial)) {
|
||||
return ERROR_INVALID_TEXT_BUFFER;
|
||||
}
|
||||
if (!ReadBounded(param->title, IME_DIALOG_MAX_TITLE_LENGTH, title)) {
|
||||
return ERROR_INVALID_TITLE;
|
||||
}
|
||||
if (!ReadBounded(param->placeholder, IME_DIALOG_MAX_PLACEHOLDER_LENGTH, placeholder)) {
|
||||
return ERROR_INVALID_PARAM;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
void ComputePanelSize(const Param& param, const ExtendedParam* extended, uint32_t* width,
|
||||
uint32_t* height) {
|
||||
const bool multiline = (param.option & OPTION_MULTILINE) != 0;
|
||||
const bool hide_keyboard = extended != nullptr && (param.option & OPTION_EXT_KEYBOARD) != 0 &&
|
||||
(extended->option & 0x00000400) != 0;
|
||||
if (param.type == Type::Number) {
|
||||
*width = 370;
|
||||
*height = hide_keyboard ? 102 : 522;
|
||||
} else if (param.type == Type::BasicLatin) {
|
||||
*width = 793;
|
||||
if (hide_keyboard) {
|
||||
*height = multiline ? 203 : 103;
|
||||
} else {
|
||||
*height = multiline ? 628 : 528;
|
||||
}
|
||||
} else {
|
||||
*width = 793;
|
||||
if (hide_keyboard) {
|
||||
*height = multiline ? 268 : 168;
|
||||
} else {
|
||||
*height = multiline ? 628 : 528;
|
||||
}
|
||||
}
|
||||
if ((param.option & OPTION_USE_OVER_2K) != 0) {
|
||||
*width *= 2;
|
||||
*height *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyFilterAndCommit() {
|
||||
TextFilter filter = nullptr;
|
||||
std::u16string source;
|
||||
uint32_t max_length = 0;
|
||||
uint64_t generation = 0;
|
||||
uint64_t revision = 0;
|
||||
{
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status == Status::None || (!g_state.input_changed && !g_state.commit_pending)) {
|
||||
return;
|
||||
}
|
||||
filter = g_state.input_changed ? g_state.param.filter : nullptr;
|
||||
source = g_state.current_text;
|
||||
max_length = g_state.param.max_text_length;
|
||||
generation = g_state.generation;
|
||||
revision = g_state.revision;
|
||||
g_state.input_changed = false;
|
||||
}
|
||||
|
||||
if (filter != nullptr) {
|
||||
std::vector<char16_t> output(IME_DIALOG_MAX_TEXT_LENGTH + 1, u'\0');
|
||||
uint32_t output_length = IME_DIALOG_MAX_TEXT_LENGTH;
|
||||
if (filter(output.data(), &output_length, source.c_str(),
|
||||
static_cast<uint32_t>(source.size())) == 0 &&
|
||||
output_length <= IME_DIALOG_MAX_TEXT_LENGTH &&
|
||||
IsValidUtf16(std::u16string_view(output.data(), output_length))) {
|
||||
source.assign(output.data(), output.data() + output_length);
|
||||
ClampText(&source, max_length);
|
||||
}
|
||||
}
|
||||
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.generation != generation || g_state.revision != revision ||
|
||||
g_state.status == Status::None) {
|
||||
if (g_state.generation == generation &&
|
||||
(g_state.status == Status::Running ||
|
||||
(g_state.status == Status::Finished && g_state.end_status == EndStatus::Ok))) {
|
||||
g_state.input_changed = true;
|
||||
g_state.commit_pending = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!g_state.input_changed) {
|
||||
g_state.current_text = std::move(source);
|
||||
g_state.cursor = NormalizeCursor(g_state.current_text, g_state.cursor);
|
||||
}
|
||||
const auto& committed =
|
||||
g_state.status == Status::Finished && g_state.end_status != EndStatus::Ok
|
||||
? g_state.original_text
|
||||
: g_state.current_text;
|
||||
WriteGuestText(g_state, committed);
|
||||
g_state.commit_pending = false;
|
||||
}
|
||||
|
||||
bool MatchRunningGeneration(uint64_t generation) {
|
||||
return g_state.status == Status::Running && g_state.generation == generation;
|
||||
}
|
||||
|
||||
bool FinishFromHost(uint64_t generation, EndStatus end_status) {
|
||||
uint64_t notify_generation = 0;
|
||||
{
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (!MatchRunningGeneration(generation)) {
|
||||
return false;
|
||||
}
|
||||
g_state.status = Status::Finished;
|
||||
g_state.end_status = end_status;
|
||||
g_state.commit_pending = true;
|
||||
g_state.revision++;
|
||||
notify_generation = g_state.generation;
|
||||
g_status.store(Status::Finished, std::memory_order_release);
|
||||
g_revision.store(g_state.revision, std::memory_order_release);
|
||||
}
|
||||
NotifyVisibility(false, notify_generation);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ApplyExternalInputs() {
|
||||
std::vector<ExternalInput> inputs;
|
||||
ExtKeyboardFilter filter = nullptr;
|
||||
uint64_t generation = 0;
|
||||
int32_t user_id = 0;
|
||||
bool multiline = false;
|
||||
{
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status != Status::Running || g_state.external_inputs.empty()) {
|
||||
return;
|
||||
}
|
||||
inputs.swap(g_state.external_inputs);
|
||||
filter = g_state.extended.ext_keyboard_filter;
|
||||
generation = g_state.generation;
|
||||
user_id = g_state.param.user_id;
|
||||
multiline = (g_state.param.option & OPTION_MULTILINE) != 0;
|
||||
}
|
||||
|
||||
for (auto& input: inputs) {
|
||||
{
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (!MatchRunningGeneration(generation)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
input.key.user_id = user_id;
|
||||
bool accepted = true;
|
||||
if (filter != nullptr) {
|
||||
uint16_t output_keycode = input.key.keycode;
|
||||
uint32_t output_status = input.key.status;
|
||||
if (filter(&input.key, &output_keycode, &output_status, nullptr) == 0) {
|
||||
accepted =
|
||||
ApplyKeyboardFilterOutput(&input, output_keycode, output_status, multiline);
|
||||
}
|
||||
}
|
||||
if (!accepted) {
|
||||
continue;
|
||||
}
|
||||
switch (input.action) {
|
||||
case ExternalAction::None: break;
|
||||
case ExternalAction::Text: HostInsertText(generation, input.text); break;
|
||||
case ExternalAction::Backspace: HostBackspace(generation); break;
|
||||
case ExternalAction::MoveLeft: HostMoveCursor(generation, -1); break;
|
||||
case ExternalAction::MoveRight: HostMoveCursor(generation, 1); break;
|
||||
case ExternalAction::Cancel: HostCancel(generation); break;
|
||||
case ExternalAction::Accept: HostAccept(generation); break;
|
||||
case ExternalAction::Newline: HostInsertText(generation, u"\n"); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogGetPanelSize(const Param* param, uint32_t* width, uint32_t* height) {
|
||||
return ImeDialogGetPanelSizeExtended(param, nullptr, width, height);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogGetPanelSizeExtended(const Param* param, const ExtendedParam* extended,
|
||||
uint32_t* width, uint32_t* height) {
|
||||
if (param == nullptr || width == nullptr || height == nullptr) {
|
||||
return ERROR_INVALID_ADDRESS;
|
||||
}
|
||||
if (static_cast<uint32_t>(param->type) > static_cast<uint32_t>(Type::Number)) {
|
||||
return ERROR_INVALID_TYPE;
|
||||
}
|
||||
if ((param->option & ~VALID_OPTIONS) != 0) {
|
||||
return ERROR_INVALID_OPTION;
|
||||
}
|
||||
if ((param->supported_languages & ~VALID_LANGUAGES) != 0) {
|
||||
return ERROR_INVALID_LANGUAGES;
|
||||
}
|
||||
const int extended_result = ValidateExtended(extended);
|
||||
if (extended_result != OK) {
|
||||
return extended_result;
|
||||
}
|
||||
ComputePanelSize(*param, extended, width, height);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogInit(const Param* param, const ExtendedParam* extended) {
|
||||
if (g_status.load(std::memory_order_acquire) != Status::None) {
|
||||
return ERROR_BUSY;
|
||||
}
|
||||
|
||||
std::u16string initial;
|
||||
std::u16string title;
|
||||
std::u16string placeholder;
|
||||
const int validation = ValidateParam(param, extended, &initial, &title, &placeholder);
|
||||
if (validation != OK) {
|
||||
return validation;
|
||||
}
|
||||
|
||||
uint64_t generation = 0;
|
||||
{
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status != Status::None) {
|
||||
return ERROR_BUSY;
|
||||
}
|
||||
const uint64_t next_generation = g_state.generation + 1;
|
||||
const uint64_t next_revision = g_state.revision + 1;
|
||||
g_state = {};
|
||||
g_state.status = Status::Running;
|
||||
g_state.generation = next_generation;
|
||||
g_state.revision = next_revision;
|
||||
g_state.param = *param;
|
||||
if (extended != nullptr) {
|
||||
g_state.extended = *extended;
|
||||
}
|
||||
g_state.original_text = initial;
|
||||
g_state.current_text = std::move(initial);
|
||||
g_state.cursor = static_cast<uint32_t>(g_state.current_text.size());
|
||||
g_state.title = std::move(title);
|
||||
g_state.placeholder = std::move(placeholder);
|
||||
g_state.commit_pending = true;
|
||||
generation = g_state.generation;
|
||||
g_status.store(Status::Running, std::memory_order_release);
|
||||
g_revision.store(g_state.revision, std::memory_order_release);
|
||||
}
|
||||
NotifyVisibility(true, generation);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogGetStatus() {
|
||||
ApplyExternalInputs();
|
||||
ApplyFilterAndCommit();
|
||||
return static_cast<int>(g_status.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogAbort() {
|
||||
uint64_t generation = 0;
|
||||
{
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status == Status::None) {
|
||||
return ERROR_NOT_IN_USE;
|
||||
}
|
||||
if (g_state.status != Status::Running) {
|
||||
return ERROR_NOT_RUNNING;
|
||||
}
|
||||
generation = g_state.generation;
|
||||
}
|
||||
if (!FinishFromHost(generation, EndStatus::Aborted)) {
|
||||
return ERROR_NOT_RUNNING;
|
||||
}
|
||||
ApplyFilterAndCommit();
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogGetResult(Result* result) {
|
||||
{
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status == Status::None) {
|
||||
return ERROR_NOT_IN_USE;
|
||||
}
|
||||
}
|
||||
if (result == nullptr) {
|
||||
return ERROR_INVALID_ADDRESS;
|
||||
}
|
||||
if (!AllZero(result->reserved, sizeof(result->reserved))) {
|
||||
return ERROR_INVALID_RESERVED;
|
||||
}
|
||||
{
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status != Status::Finished) {
|
||||
return ERROR_NOT_FINISHED;
|
||||
}
|
||||
}
|
||||
ApplyFilterAndCommit();
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status == Status::None) {
|
||||
return ERROR_NOT_IN_USE;
|
||||
}
|
||||
if (g_state.status != Status::Finished) {
|
||||
return ERROR_NOT_FINISHED;
|
||||
}
|
||||
result->endstatus = g_state.end_status;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogTerm() {
|
||||
{
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status == Status::None) {
|
||||
return ERROR_NOT_IN_USE;
|
||||
}
|
||||
if (g_state.status != Status::Finished) {
|
||||
return ERROR_NOT_FINISHED;
|
||||
}
|
||||
}
|
||||
ApplyFilterAndCommit();
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status == Status::None) {
|
||||
return ERROR_NOT_IN_USE;
|
||||
}
|
||||
if (g_state.status != Status::Finished) {
|
||||
return ERROR_NOT_FINISHED;
|
||||
}
|
||||
const uint64_t generation = g_state.generation;
|
||||
const uint64_t revision = g_state.revision;
|
||||
g_state = {};
|
||||
g_state.generation = generation;
|
||||
g_state.revision = revision;
|
||||
g_status.store(Status::None, std::memory_order_release);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogGetPanelPositionAndForm(PositionAndForm* form) {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status == Status::None) {
|
||||
return ERROR_NOT_IN_USE;
|
||||
}
|
||||
if (form == nullptr) {
|
||||
return ERROR_INVALID_ADDRESS;
|
||||
}
|
||||
form->type = 2;
|
||||
form->posx = g_state.param.posx;
|
||||
form->posy = g_state.param.posy;
|
||||
form->horizontal_alignment = g_state.param.horizontal_alignment;
|
||||
form->vertical_alignment = g_state.param.vertical_alignment;
|
||||
ComputePanelSize(g_state.param, &g_state.extended, &form->width, &form->height);
|
||||
return OK;
|
||||
}
|
||||
|
||||
VisualState GetVisualState() noexcept {
|
||||
return {g_status.load(std::memory_order_acquire) == Status::Running,
|
||||
g_revision.load(std::memory_order_acquire)};
|
||||
}
|
||||
|
||||
void SetVisibilityCallback(VisibilityCallback callback) noexcept {
|
||||
g_visibility_callback.store(callback, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool GetHostSnapshot(HostSnapshot* snapshot) {
|
||||
if (snapshot == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (g_state.status != Status::Running) {
|
||||
return false;
|
||||
}
|
||||
snapshot->generation = g_state.generation;
|
||||
snapshot->type = g_state.param.type;
|
||||
snapshot->enter_label = g_state.param.enter_label;
|
||||
snapshot->option = g_state.param.option;
|
||||
snapshot->max_text_length = g_state.param.max_text_length;
|
||||
snapshot->cursor = g_state.cursor;
|
||||
snapshot->disable_device = g_state.extended.disable_device;
|
||||
snapshot->key_panel_visible = (g_state.param.option & OPTION_EXT_KEYBOARD) == 0 ||
|
||||
(g_state.extended.option & 0x00000400) == 0;
|
||||
snapshot->posx = g_state.param.posx;
|
||||
snapshot->posy = g_state.param.posy;
|
||||
snapshot->horizontal_alignment = g_state.param.horizontal_alignment;
|
||||
snapshot->vertical_alignment = g_state.param.vertical_alignment;
|
||||
ComputePanelSize(g_state.param, &g_state.extended, &snapshot->panel_width,
|
||||
&snapshot->panel_height);
|
||||
snapshot->text = g_state.current_text;
|
||||
snapshot->title = g_state.title;
|
||||
snapshot->placeholder = g_state.placeholder;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostInsertText(uint64_t generation, std::u16string_view text) {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (!MatchRunningGeneration(generation) || text.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (!IsValidUtf16(text)) {
|
||||
return false;
|
||||
}
|
||||
std::u16string allowed;
|
||||
allowed.reserve(text.size());
|
||||
for (const char16_t value: text) {
|
||||
if (IsAllowedInput(value, g_state.param.type, g_state.param.option)) {
|
||||
allowed.push_back(value);
|
||||
}
|
||||
}
|
||||
if (allowed.empty()) {
|
||||
return false;
|
||||
}
|
||||
const size_t available = g_state.param.max_text_length - g_state.current_text.size();
|
||||
if (available == 0) {
|
||||
return false;
|
||||
}
|
||||
std::u16string insertion(std::u16string_view(allowed).substr(0, available));
|
||||
if (insertion.size() < allowed.size() && !insertion.empty() && insertion.back() >= 0xd800 &&
|
||||
insertion.back() <= 0xdbff) {
|
||||
insertion.pop_back();
|
||||
}
|
||||
if (insertion.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::u16string candidate = g_state.current_text;
|
||||
candidate.insert(g_state.cursor, insertion);
|
||||
g_state.current_text = std::move(candidate);
|
||||
g_state.cursor += static_cast<uint32_t>(insertion.size());
|
||||
g_state.input_changed = true;
|
||||
g_state.commit_pending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostBackspace(uint64_t generation) {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (!MatchRunningGeneration(generation) || g_state.cursor == 0) {
|
||||
return false;
|
||||
}
|
||||
uint32_t first = g_state.cursor - 1;
|
||||
if (first > 0 && g_state.current_text[first] >= 0xdc00 &&
|
||||
g_state.current_text[first] <= 0xdfff && g_state.current_text[first - 1] >= 0xd800 &&
|
||||
g_state.current_text[first - 1] <= 0xdbff) {
|
||||
first--;
|
||||
}
|
||||
g_state.current_text.erase(first, g_state.cursor - first);
|
||||
g_state.cursor = first;
|
||||
g_state.input_changed = true;
|
||||
g_state.commit_pending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostMoveCursor(uint64_t generation, int delta) {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (!MatchRunningGeneration(generation) || delta == 0) {
|
||||
return false;
|
||||
}
|
||||
int next = std::clamp(static_cast<int>(g_state.cursor) + delta, 0,
|
||||
static_cast<int>(g_state.current_text.size()));
|
||||
if (delta < 0 && next > 0 && next < static_cast<int>(g_state.current_text.size()) &&
|
||||
g_state.current_text[next] >= 0xdc00 && g_state.current_text[next] <= 0xdfff &&
|
||||
g_state.current_text[next - 1] >= 0xd800 && g_state.current_text[next - 1] <= 0xdbff) {
|
||||
next--;
|
||||
} else if (delta > 0 && next > 0 && next < static_cast<int>(g_state.current_text.size()) &&
|
||||
g_state.current_text[next - 1] >= 0xd800 &&
|
||||
g_state.current_text[next - 1] <= 0xdbff && g_state.current_text[next] >= 0xdc00 &&
|
||||
g_state.current_text[next] <= 0xdfff) {
|
||||
next++;
|
||||
}
|
||||
if (next == static_cast<int>(g_state.cursor)) {
|
||||
return false;
|
||||
}
|
||||
g_state.cursor = static_cast<uint32_t>(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostAccept(uint64_t generation) {
|
||||
return FinishFromHost(generation, EndStatus::Ok);
|
||||
}
|
||||
|
||||
bool HostCancel(uint64_t generation) {
|
||||
return FinishFromHost(generation, EndStatus::UserCanceled);
|
||||
}
|
||||
|
||||
bool HostQueueExternalInput(uint64_t generation, ExternalInput input) {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
if (!MatchRunningGeneration(generation) || g_state.external_inputs.size() >= 128) {
|
||||
return false;
|
||||
}
|
||||
g_state.external_inputs.push_back(std::move(input));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Dialog::ImeDialog
|
||||
@@ -0,0 +1,199 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_LIBS_IMEDIALOG_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_LIBS_IMEDIALOG_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Libs::Dialog::ImeDialog {
|
||||
|
||||
constexpr uint32_t IME_DIALOG_MAX_TEXT_LENGTH = 2048;
|
||||
constexpr uint32_t IME_DIALOG_MAX_TITLE_LENGTH = 128;
|
||||
constexpr uint32_t IME_DIALOG_MAX_PLACEHOLDER_LENGTH = 64;
|
||||
|
||||
enum class Status : uint32_t { None = 0, Running = 1, Finished = 2 };
|
||||
enum class EndStatus : uint32_t { Ok = 0, UserCanceled = 1, Aborted = 2 };
|
||||
enum class Type : uint32_t { Default = 0, BasicLatin = 1, Url = 2, Mail = 3, Number = 4 };
|
||||
enum class EnterLabel : uint32_t { Default = 0, Send = 1, Search = 2, Go = 3 };
|
||||
enum class Alignment : uint32_t { Start = 0, Center = 1, End = 2 };
|
||||
|
||||
enum Option : uint32_t {
|
||||
OPTION_MULTILINE = 0x00000001,
|
||||
OPTION_NO_AUTO_CAPITALIZE = 0x00000002,
|
||||
OPTION_PASSWORD = 0x00000004,
|
||||
OPTION_LANGUAGES_FORCED = 0x00000008,
|
||||
OPTION_EXT_KEYBOARD = 0x00000010,
|
||||
OPTION_NO_LEARNING = 0x00000020,
|
||||
OPTION_FIXED_POSITION = 0x00000040,
|
||||
OPTION_DISABLE_COPY_PASTE = 0x00000080,
|
||||
OPTION_DISABLE_RESUME = 0x00000100,
|
||||
OPTION_DISABLE_AUTO_SPACE = 0x00000200,
|
||||
OPTION_DISABLE_POSITION_ADJ = 0x00000800,
|
||||
OPTION_EXPANDED_PREEDIT = 0x00001000,
|
||||
OPTION_JAPANESE_CAPS_LOCK = 0x00002000,
|
||||
OPTION_USE_OVER_2K = 0x00004000,
|
||||
};
|
||||
|
||||
enum DisableDevice : uint32_t {
|
||||
DISABLE_DEVICE_CONTROLLER = 0x00000001,
|
||||
DISABLE_DEVICE_EXT_KEYBOARD = 0x00000002,
|
||||
DISABLE_DEVICE_REMOTE_OSK = 0x00000004,
|
||||
};
|
||||
|
||||
struct Color {
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t a;
|
||||
};
|
||||
|
||||
struct Keycode;
|
||||
|
||||
using TextFilter = int32_t(KYTY_SYSV_ABI*)(char16_t* out_text, uint32_t* out_text_length,
|
||||
const char16_t* source_text,
|
||||
uint32_t source_text_length);
|
||||
using ExtKeyboardFilter = int(KYTY_SYSV_ABI*)(const Keycode* source_keycode, uint16_t* out_keycode,
|
||||
uint32_t* out_status, void* reserved);
|
||||
|
||||
struct Param {
|
||||
int32_t user_id;
|
||||
Type type;
|
||||
uint64_t supported_languages;
|
||||
EnterLabel enter_label;
|
||||
uint32_t input_method;
|
||||
TextFilter filter;
|
||||
uint32_t option;
|
||||
uint32_t max_text_length;
|
||||
char16_t* input_text_buffer;
|
||||
float posx;
|
||||
float posy;
|
||||
Alignment horizontal_alignment;
|
||||
Alignment vertical_alignment;
|
||||
const char16_t* placeholder;
|
||||
const char16_t* title;
|
||||
int8_t reserved[16];
|
||||
};
|
||||
|
||||
struct Result {
|
||||
EndStatus endstatus;
|
||||
int8_t reserved[12];
|
||||
};
|
||||
|
||||
struct ExtendedParam {
|
||||
uint32_t option;
|
||||
Color color_base;
|
||||
Color color_line;
|
||||
Color color_text_field;
|
||||
Color color_preedit;
|
||||
Color color_button_default;
|
||||
Color color_button_function;
|
||||
Color color_button_symbol;
|
||||
Color color_text;
|
||||
Color color_special;
|
||||
uint32_t priority;
|
||||
const char* additional_dictionary_path;
|
||||
ExtKeyboardFilter ext_keyboard_filter;
|
||||
uint32_t disable_device;
|
||||
uint32_t ext_keyboard_mode;
|
||||
int8_t reserved[60];
|
||||
};
|
||||
|
||||
struct PositionAndForm {
|
||||
uint32_t type;
|
||||
float posx;
|
||||
float posy;
|
||||
Alignment horizontal_alignment;
|
||||
Alignment vertical_alignment;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
struct Keycode {
|
||||
uint16_t keycode;
|
||||
char16_t character;
|
||||
uint32_t status;
|
||||
uint32_t type;
|
||||
int32_t user_id;
|
||||
uint32_t resource_id;
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
enum class ExternalAction : uint8_t {
|
||||
None,
|
||||
Text,
|
||||
Backspace,
|
||||
MoveLeft,
|
||||
MoveRight,
|
||||
Cancel,
|
||||
Accept,
|
||||
Newline,
|
||||
};
|
||||
|
||||
struct ExternalInput {
|
||||
Keycode key;
|
||||
ExternalAction action;
|
||||
std::u16string text;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Param) == 0x60);
|
||||
static_assert(offsetof(Param, input_text_buffer) == 0x28);
|
||||
static_assert(offsetof(Param, title) == 0x48);
|
||||
static_assert(sizeof(Result) == 0x10);
|
||||
static_assert(sizeof(ExtendedParam) == 0x88);
|
||||
static_assert(offsetof(ExtendedParam, additional_dictionary_path) == 0x30);
|
||||
static_assert(sizeof(PositionAndForm) == 0x1c);
|
||||
static_assert(sizeof(Keycode) == 0x20);
|
||||
|
||||
struct VisualState {
|
||||
bool active;
|
||||
uint64_t revision;
|
||||
};
|
||||
|
||||
struct HostSnapshot {
|
||||
uint64_t generation;
|
||||
Type type;
|
||||
EnterLabel enter_label;
|
||||
uint32_t option;
|
||||
uint32_t max_text_length;
|
||||
uint32_t cursor;
|
||||
uint32_t disable_device;
|
||||
bool key_panel_visible;
|
||||
float posx;
|
||||
float posy;
|
||||
Alignment horizontal_alignment;
|
||||
Alignment vertical_alignment;
|
||||
uint32_t panel_width;
|
||||
uint32_t panel_height;
|
||||
std::u16string text;
|
||||
std::u16string title;
|
||||
std::u16string placeholder;
|
||||
};
|
||||
|
||||
using VisibilityCallback = void (*)(bool visible, uint64_t generation);
|
||||
|
||||
int KYTY_SYSV_ABI ImeDialogGetPanelSize(const Param* param, uint32_t* width, uint32_t* height);
|
||||
int KYTY_SYSV_ABI ImeDialogGetPanelSizeExtended(const Param* param, const ExtendedParam* extended,
|
||||
uint32_t* width, uint32_t* height);
|
||||
int KYTY_SYSV_ABI ImeDialogInit(const Param* param, const ExtendedParam* extended);
|
||||
int KYTY_SYSV_ABI ImeDialogGetStatus();
|
||||
int KYTY_SYSV_ABI ImeDialogAbort();
|
||||
int KYTY_SYSV_ABI ImeDialogGetResult(Result* result);
|
||||
int KYTY_SYSV_ABI ImeDialogTerm();
|
||||
int KYTY_SYSV_ABI ImeDialogGetPanelPositionAndForm(PositionAndForm* form);
|
||||
|
||||
VisualState GetVisualState() noexcept;
|
||||
void SetVisibilityCallback(VisibilityCallback callback) noexcept;
|
||||
bool GetHostSnapshot(HostSnapshot* snapshot);
|
||||
bool HostInsertText(uint64_t generation, std::u16string_view text);
|
||||
bool HostBackspace(uint64_t generation);
|
||||
bool HostMoveCursor(uint64_t generation, int delta);
|
||||
bool HostAccept(uint64_t generation);
|
||||
bool HostCancel(uint64_t generation);
|
||||
bool HostQueueExternalInput(uint64_t generation, ExternalInput input);
|
||||
|
||||
} // namespace Libs::Dialog::ImeDialog
|
||||
|
||||
#endif // EMULATOR_INCLUDE_EMULATOR_LIBS_IMEDIALOG_H_
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "common/abi.h"
|
||||
#include "libs/dialog.h"
|
||||
#include "libs/imeDialog.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
@@ -24,7 +25,14 @@ LIB_VERSION("ImeDialog", 1, "ImeDialog", 1, 1);
|
||||
namespace ImeDialog = Dialog::ImeDialog;
|
||||
|
||||
LIB_DEFINE(InitDialog_1_ImeDialog) {
|
||||
LIB_FUNC("CRD+jSErEJQ", ImeDialog::ImeDialogGetPanelSizeExtended);
|
||||
LIB_FUNC("IADmD4tScBY", ImeDialog::ImeDialogGetStatus);
|
||||
LIB_FUNC("gyTyVn+bXMw", ImeDialog::ImeDialogTerm);
|
||||
LIB_FUNC("x01jxu+vxlc", ImeDialog::ImeDialogGetResult);
|
||||
LIB_FUNC("wqsJvRXwl58", ImeDialog::ImeDialogGetPanelSize);
|
||||
LIB_FUNC("NUeBrN7hzf0", ImeDialog::ImeDialogInit);
|
||||
LIB_FUNC("oBmw4xrmfKs", ImeDialog::ImeDialogAbort);
|
||||
LIB_FUNC("8jqzzPioYl8", ImeDialog::ImeDialogGetPanelPositionAndForm);
|
||||
}
|
||||
|
||||
} // namespace LibImeDialog
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
#include "libs/imeDialog.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
namespace Ime = Libs::Dialog::ImeDialog;
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
std::abort(); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int Error(uint32_t value) { return static_cast<int32_t>(value); }
|
||||
|
||||
int g_filter_calls = 0;
|
||||
int g_keyboard_filter_calls = 0;
|
||||
bool g_block_keyboard = false;
|
||||
uint16_t g_remap_keyboard = 0;
|
||||
|
||||
int32_t KYTY_SYSV_ABI CopyFilter(char16_t *out_text, uint32_t *out_length,
|
||||
const char16_t *source,
|
||||
uint32_t source_length) {
|
||||
g_filter_calls++;
|
||||
CHECK(*out_length >= source_length);
|
||||
std::memcpy(out_text, source, source_length * sizeof(char16_t));
|
||||
*out_length = source_length;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t KYTY_SYSV_ABI SupplementaryFilter(char16_t *out_text,
|
||||
uint32_t *out_length,
|
||||
const char16_t *, uint32_t) {
|
||||
CHECK(*out_length >= 2);
|
||||
out_text[0] = 0xd83d;
|
||||
out_text[1] = 0xde00;
|
||||
*out_length = 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KeyboardFilter(const Ime::Keycode *source,
|
||||
uint16_t *out_keycode, uint32_t *out_status,
|
||||
void *) {
|
||||
g_keyboard_filter_calls++;
|
||||
if (g_block_keyboard) {
|
||||
*out_keycode = 0;
|
||||
*out_status = 0;
|
||||
return 0;
|
||||
}
|
||||
if (g_remap_keyboard != 0) {
|
||||
*out_keycode = g_remap_keyboard;
|
||||
*out_status = source->status;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Ime::Param MakeParam(char16_t *text) {
|
||||
Ime::Param param{};
|
||||
param.user_id = 1000;
|
||||
param.type = Ime::Type::Default;
|
||||
param.supported_languages = 1;
|
||||
param.enter_label = Ime::EnterLabel::Default;
|
||||
param.input_text_buffer = text;
|
||||
param.max_text_length = 31;
|
||||
return param;
|
||||
}
|
||||
|
||||
void TestLayoutAndPanelSize() {
|
||||
static_assert(sizeof(Ime::Param) == 0x60);
|
||||
static_assert(sizeof(Ime::ExtendedParam) == 0x88);
|
||||
static_assert(sizeof(Ime::Result) == 0x10);
|
||||
static_assert(sizeof(Ime::PositionAndForm) == 0x1c);
|
||||
|
||||
std::array<char16_t, 32> text{};
|
||||
auto param = MakeParam(text.data());
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
CHECK(Ime::ImeDialogGetPanelSize(¶m, &width, &height) == 0);
|
||||
CHECK(width == 793 && height == 528);
|
||||
param.option = Ime::OPTION_MULTILINE;
|
||||
CHECK(Ime::ImeDialogGetPanelSize(¶m, &width, &height) == 0);
|
||||
CHECK(width == 793 && height == 628);
|
||||
param.option = 0;
|
||||
param.type = Ime::Type::Number;
|
||||
CHECK(Ime::ImeDialogGetPanelSize(¶m, &width, &height) == 0);
|
||||
CHECK(width == 370 && height == 522);
|
||||
|
||||
param = MakeParam(text.data());
|
||||
param.option = Ime::OPTION_EXT_KEYBOARD;
|
||||
Ime::ExtendedParam extended{};
|
||||
extended.option = 0x00000400;
|
||||
CHECK(Ime::ImeDialogGetPanelSizeExtended(¶m, &extended, &width,
|
||||
&height) == 0);
|
||||
CHECK(width == 793 && height == 168);
|
||||
param.type = Ime::Type::BasicLatin;
|
||||
CHECK(Ime::ImeDialogGetPanelSizeExtended(¶m, &extended, &width,
|
||||
&height) == 0);
|
||||
CHECK(width == 793 && height == 103);
|
||||
}
|
||||
|
||||
void TestAcceptLifecycle() {
|
||||
std::array<char16_t, 32> text{u'A', u'l', u'i', u'c', u'e'};
|
||||
auto param = MakeParam(text.data());
|
||||
CHECK(Ime::ImeDialogInit(¶m, nullptr) == 0);
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
|
||||
CHECK(Ime::ImeDialogInit(nullptr, nullptr) == Error(0x80bc0001));
|
||||
|
||||
Ime::HostSnapshot snapshot;
|
||||
CHECK(Ime::GetHostSnapshot(&snapshot));
|
||||
CHECK(snapshot.text == u"Alice");
|
||||
CHECK(Ime::HostInsertText(snapshot.generation, u" 2"));
|
||||
CHECK(Ime::HostBackspace(snapshot.generation));
|
||||
CHECK(Ime::HostAccept(snapshot.generation));
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Finished));
|
||||
CHECK(std::u16string(text.data()) == u"Alice ");
|
||||
|
||||
Ime::Result result{};
|
||||
CHECK(Ime::ImeDialogGetResult(&result) == 0);
|
||||
CHECK(result.endstatus == Ime::EndStatus::Ok);
|
||||
CHECK(Ime::ImeDialogTerm() == 0);
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::None));
|
||||
}
|
||||
|
||||
void TestCancelAndAbortRestoreText() {
|
||||
for (const auto expected :
|
||||
{Ime::EndStatus::UserCanceled, Ime::EndStatus::Aborted}) {
|
||||
std::array<char16_t, 32> text{u'o', u'l', u'd'};
|
||||
auto param = MakeParam(text.data());
|
||||
CHECK(Ime::ImeDialogInit(¶m, nullptr) == 0);
|
||||
Ime::HostSnapshot snapshot;
|
||||
CHECK(Ime::GetHostSnapshot(&snapshot));
|
||||
CHECK(Ime::HostInsertText(snapshot.generation, u" value"));
|
||||
if (expected == Ime::EndStatus::UserCanceled) {
|
||||
CHECK(Ime::HostCancel(snapshot.generation));
|
||||
} else {
|
||||
CHECK(Ime::ImeDialogAbort() == 0);
|
||||
}
|
||||
Ime::Result result{};
|
||||
CHECK(Ime::ImeDialogGetResult(&result) == 0);
|
||||
CHECK(result.endstatus == expected);
|
||||
CHECK(std::u16string(text.data()) == u"old");
|
||||
CHECK(Ime::ImeDialogTerm() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void TestValidation() {
|
||||
std::array<char16_t, 32> text{};
|
||||
auto param = MakeParam(text.data());
|
||||
param.reserved[0] = 1;
|
||||
CHECK(Ime::ImeDialogInit(¶m, nullptr) == Error(0x80bc0032));
|
||||
param.reserved[0] = 0;
|
||||
param.max_text_length = Ime::IME_DIALOG_MAX_TEXT_LENGTH + 1;
|
||||
CHECK(Ime::ImeDialogInit(¶m, nullptr) == Error(0x80bc0016));
|
||||
CHECK(Ime::ImeDialogGetResult(nullptr) == Error(0x80bc0107));
|
||||
|
||||
param = MakeParam(text.data());
|
||||
Ime::ExtendedParam extended{};
|
||||
extended.ext_keyboard_mode = 0x00000004;
|
||||
CHECK(Ime::ImeDialogInit(¶m, &extended) == Error(0x80bc001c));
|
||||
extended = {};
|
||||
extended.option = 0x00000200;
|
||||
extended.disable_device =
|
||||
Ime::DISABLE_DEVICE_CONTROLLER | Ime::DISABLE_DEVICE_EXT_KEYBOARD;
|
||||
CHECK(Ime::ImeDialogInit(¶m, &extended) == 0);
|
||||
Ime::HostSnapshot snapshot;
|
||||
CHECK(Ime::GetHostSnapshot(&snapshot));
|
||||
CHECK(snapshot.disable_device == 3);
|
||||
CHECK(Ime::ImeDialogAbort() == 0);
|
||||
Ime::Result result{};
|
||||
CHECK(Ime::ImeDialogGetResult(&result) == 0);
|
||||
CHECK(Ime::ImeDialogTerm() == 0);
|
||||
}
|
||||
|
||||
void TestFilteringAndInputPolicy() {
|
||||
std::array<char16_t, 32> text{u'1'};
|
||||
auto param = MakeParam(text.data());
|
||||
param.type = Ime::Type::Number;
|
||||
param.filter = CopyFilter;
|
||||
g_filter_calls = 0;
|
||||
CHECK(Ime::ImeDialogInit(¶m, nullptr) == 0);
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
|
||||
CHECK(g_filter_calls == 0);
|
||||
CHECK(Ime::ImeDialogGetResult(nullptr) == Error(0x80bc0031));
|
||||
CHECK(Ime::ImeDialogTerm() == Error(0x80bc0106));
|
||||
CHECK(g_filter_calls == 0);
|
||||
|
||||
Ime::HostSnapshot snapshot;
|
||||
CHECK(Ime::GetHostSnapshot(&snapshot));
|
||||
CHECK(Ime::HostInsertText(snapshot.generation, u"a2.\n"));
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
|
||||
CHECK(g_filter_calls == 1);
|
||||
CHECK(std::u16string(text.data()) == u"12.");
|
||||
CHECK(Ime::HostCancel(snapshot.generation));
|
||||
Ime::Result result{};
|
||||
CHECK(Ime::ImeDialogGetResult(&result) == 0);
|
||||
CHECK(Ime::ImeDialogTerm() == 0);
|
||||
}
|
||||
|
||||
void TestExternalKeyboardFilter() {
|
||||
std::array<char16_t, 32> text{};
|
||||
auto param = MakeParam(text.data());
|
||||
Ime::ExtendedParam extended{};
|
||||
extended.ext_keyboard_filter = KeyboardFilter;
|
||||
CHECK(Ime::ImeDialogInit(¶m, &extended) == 0);
|
||||
Ime::HostSnapshot snapshot;
|
||||
CHECK(Ime::GetHostSnapshot(&snapshot));
|
||||
|
||||
Ime::ExternalInput input{};
|
||||
input.key.status = 3;
|
||||
input.key.keycode = 4;
|
||||
input.action = Ime::ExternalAction::Text;
|
||||
input.text = u"x";
|
||||
g_keyboard_filter_calls = 0;
|
||||
g_block_keyboard = true;
|
||||
CHECK(Ime::HostQueueExternalInput(snapshot.generation, input));
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
|
||||
CHECK(g_keyboard_filter_calls == 1);
|
||||
CHECK(text[0] == u'\0');
|
||||
|
||||
g_block_keyboard = false;
|
||||
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
|
||||
CHECK(g_keyboard_filter_calls == 2);
|
||||
CHECK(std::u16string(text.data()) == u"x");
|
||||
|
||||
input = {};
|
||||
input.key.status = 3;
|
||||
input.key.keycode = 4;
|
||||
input.action = Ime::ExternalAction::Text;
|
||||
input.text = u"x";
|
||||
g_remap_keyboard = 5;
|
||||
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
|
||||
CHECK(g_keyboard_filter_calls == 3);
|
||||
CHECK(std::u16string(text.data()) == u"xb");
|
||||
|
||||
input = {};
|
||||
input.key.status = 0x00000201;
|
||||
input.key.keycode = 4;
|
||||
input.action = Ime::ExternalAction::Text;
|
||||
input.text = u"x";
|
||||
g_remap_keyboard = 50;
|
||||
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
|
||||
CHECK(g_keyboard_filter_calls == 4);
|
||||
CHECK(std::u16string(text.data()) == u"xb~");
|
||||
|
||||
input = {};
|
||||
input.key.status = 3;
|
||||
input.key.keycode = 4;
|
||||
input.action = Ime::ExternalAction::Text;
|
||||
input.text = u"x";
|
||||
g_remap_keyboard = 88;
|
||||
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Finished));
|
||||
CHECK(g_keyboard_filter_calls == 5);
|
||||
Ime::Result result{};
|
||||
CHECK(Ime::ImeDialogGetResult(&result) == 0);
|
||||
CHECK(result.endstatus == Ime::EndStatus::Ok);
|
||||
CHECK(Ime::ImeDialogTerm() == 0);
|
||||
|
||||
g_remap_keyboard = 0;
|
||||
CHECK(Ime::ImeDialogInit(¶m, &extended) == 0);
|
||||
CHECK(Ime::GetHostSnapshot(&snapshot));
|
||||
|
||||
input = {};
|
||||
input.key.status = 1;
|
||||
input.key.keycode = 41;
|
||||
input.action = Ime::ExternalAction::Cancel;
|
||||
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
|
||||
input = {};
|
||||
input.key.status = 3;
|
||||
input.key.keycode = 4;
|
||||
input.action = Ime::ExternalAction::Text;
|
||||
input.text = u"ignored";
|
||||
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Finished));
|
||||
CHECK(g_keyboard_filter_calls == 6);
|
||||
result = {};
|
||||
CHECK(Ime::ImeDialogGetResult(&result) == 0);
|
||||
CHECK(Ime::ImeDialogTerm() == 0);
|
||||
}
|
||||
|
||||
void TestFilteredCursorBoundary() {
|
||||
std::array<char16_t, 8> text{};
|
||||
auto param = MakeParam(text.data());
|
||||
param.filter = SupplementaryFilter;
|
||||
CHECK(Ime::ImeDialogInit(¶m, nullptr) == 0);
|
||||
Ime::HostSnapshot snapshot;
|
||||
CHECK(Ime::GetHostSnapshot(&snapshot));
|
||||
CHECK(Ime::HostInsertText(snapshot.generation, u"x"));
|
||||
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
|
||||
CHECK(Ime::GetHostSnapshot(&snapshot));
|
||||
CHECK(snapshot.cursor == 2);
|
||||
CHECK(text[0] == 0xd83d && text[1] == 0xde00 && text[2] == 0);
|
||||
CHECK(Ime::HostCancel(snapshot.generation));
|
||||
Ime::Result result{};
|
||||
CHECK(Ime::ImeDialogGetResult(&result) == 0);
|
||||
CHECK(Ime::ImeDialogTerm() == 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
TestLayoutAndPanelSize();
|
||||
TestAcceptLifecycle();
|
||||
TestCancelAndAbortRestoreText();
|
||||
TestValidation();
|
||||
TestFilteringAndInputPolicy();
|
||||
TestExternalKeyboardFilter();
|
||||
TestFilteredCursorBoundary();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user