Browse Source
Merge pull request #1667 from DarkLordZach/swkbd
Merge pull request #1667 from DarkLordZach/swkbd
am: Implement HLE software keyboard appletpull/15/merge
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1133 additions and 106 deletions
-
9src/common/string_util.cpp
-
8src/common/string_util.h
-
6src/core/CMakeLists.txt
-
17src/core/core.cpp
-
5src/core/core.h
-
29src/core/frontend/applets/software_keyboard.cpp
-
54src/core/frontend/applets/software_keyboard.h
-
36src/core/hle/kernel/svc.cpp
-
331src/core/hle/service/am/am.cpp
-
29src/core/hle/service/am/am.h
-
115src/core/hle/service/am/applets/applets.cpp
-
94src/core/hle/service/am/applets/applets.h
-
161src/core/hle/service/am/applets/software_keyboard.cpp
-
69src/core/hle/service/am/applets/software_keyboard.h
-
2src/yuzu/CMakeLists.txt
-
152src/yuzu/applets/software_keyboard.cpp
-
80src/yuzu/applets/software_keyboard.h
-
31src/yuzu/main.cpp
-
11src/yuzu/main.h
@ -0,0 +1,29 @@ |
|||||
|
// Copyright 2018 yuzu emulator team
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "common/logging/backend.h"
|
||||
|
#include "common/string_util.h"
|
||||
|
#include "core/frontend/applets/software_keyboard.h"
|
||||
|
|
||||
|
namespace Core::Frontend { |
||||
|
SoftwareKeyboardApplet::~SoftwareKeyboardApplet() = default; |
||||
|
|
||||
|
void DefaultSoftwareKeyboardApplet::RequestText( |
||||
|
std::function<void(std::optional<std::u16string>)> out, |
||||
|
SoftwareKeyboardParameters parameters) const { |
||||
|
if (parameters.initial_text.empty()) |
||||
|
out(u"yuzu"); |
||||
|
|
||||
|
out(parameters.initial_text); |
||||
|
} |
||||
|
|
||||
|
void DefaultSoftwareKeyboardApplet::SendTextCheckDialog( |
||||
|
std::u16string error_message, std::function<void()> finished_check) const { |
||||
|
LOG_WARNING(Service_AM, |
||||
|
"(STUBBED) called - Default fallback software keyboard does not support text " |
||||
|
"check! (error_message={})", |
||||
|
Common::UTF16ToUTF8(error_message)); |
||||
|
finished_check(); |
||||
|
} |
||||
|
} // namespace Core::Frontend
|
||||
@ -0,0 +1,54 @@ |
|||||
|
// Copyright 2018 yuzu emulator team |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <functional> |
||||
|
#include <optional> |
||||
|
#include <string> |
||||
|
#include "common/bit_field.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace Core::Frontend { |
||||
|
struct SoftwareKeyboardParameters { |
||||
|
std::u16string submit_text; |
||||
|
std::u16string header_text; |
||||
|
std::u16string sub_text; |
||||
|
std::u16string guide_text; |
||||
|
std::u16string initial_text; |
||||
|
std::size_t max_length; |
||||
|
bool password; |
||||
|
bool cursor_at_beginning; |
||||
|
|
||||
|
union { |
||||
|
u8 value; |
||||
|
|
||||
|
BitField<1, 1, u8> disable_space; |
||||
|
BitField<2, 1, u8> disable_address; |
||||
|
BitField<3, 1, u8> disable_percent; |
||||
|
BitField<4, 1, u8> disable_slash; |
||||
|
BitField<6, 1, u8> disable_number; |
||||
|
BitField<7, 1, u8> disable_download_code; |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
class SoftwareKeyboardApplet { |
||||
|
public: |
||||
|
virtual ~SoftwareKeyboardApplet(); |
||||
|
|
||||
|
virtual void RequestText(std::function<void(std::optional<std::u16string>)> out, |
||||
|
SoftwareKeyboardParameters parameters) const = 0; |
||||
|
virtual void SendTextCheckDialog(std::u16string error_message, |
||||
|
std::function<void()> finished_check) const = 0; |
||||
|
}; |
||||
|
|
||||
|
class DefaultSoftwareKeyboardApplet final : public SoftwareKeyboardApplet { |
||||
|
public: |
||||
|
void RequestText(std::function<void(std::optional<std::u16string>)> out, |
||||
|
SoftwareKeyboardParameters parameters) const override; |
||||
|
void SendTextCheckDialog(std::u16string error_message, |
||||
|
std::function<void()> finished_check) const override; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Core::Frontend |
||||
@ -0,0 +1,115 @@ |
|||||
|
// Copyright 2018 yuzu emulator team
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <cstring>
|
||||
|
#include "common/assert.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/hle/kernel/event.h"
|
||||
|
#include "core/hle/kernel/server_port.h"
|
||||
|
#include "core/hle/service/am/am.h"
|
||||
|
#include "core/hle/service/am/applets/applets.h"
|
||||
|
|
||||
|
namespace Service::AM::Applets { |
||||
|
|
||||
|
AppletDataBroker::AppletDataBroker() { |
||||
|
auto& kernel = Core::System::GetInstance().Kernel(); |
||||
|
state_changed_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, |
||||
|
"ILibraryAppletAccessor:StateChangedEvent"); |
||||
|
pop_out_data_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, |
||||
|
"ILibraryAppletAccessor:PopDataOutEvent"); |
||||
|
pop_interactive_out_data_event = Kernel::Event::Create( |
||||
|
kernel, Kernel::ResetType::OneShot, "ILibraryAppletAccessor:PopInteractiveDataOutEvent"); |
||||
|
} |
||||
|
|
||||
|
AppletDataBroker::~AppletDataBroker() = default; |
||||
|
|
||||
|
std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() { |
||||
|
if (out_channel.empty()) |
||||
|
return nullptr; |
||||
|
|
||||
|
auto out = std::move(out_channel.front()); |
||||
|
out_channel.pop(); |
||||
|
return out; |
||||
|
} |
||||
|
|
||||
|
std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() { |
||||
|
if (in_channel.empty()) |
||||
|
return nullptr; |
||||
|
|
||||
|
auto out = std::move(in_channel.front()); |
||||
|
in_channel.pop(); |
||||
|
return out; |
||||
|
} |
||||
|
|
||||
|
std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() { |
||||
|
if (out_interactive_channel.empty()) |
||||
|
return nullptr; |
||||
|
|
||||
|
auto out = std::move(out_interactive_channel.front()); |
||||
|
out_interactive_channel.pop(); |
||||
|
return out; |
||||
|
} |
||||
|
|
||||
|
std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() { |
||||
|
if (in_interactive_channel.empty()) |
||||
|
return nullptr; |
||||
|
|
||||
|
auto out = std::move(in_interactive_channel.front()); |
||||
|
in_interactive_channel.pop(); |
||||
|
return out; |
||||
|
} |
||||
|
|
||||
|
void AppletDataBroker::PushNormalDataFromGame(IStorage storage) { |
||||
|
in_channel.push(std::make_unique<IStorage>(storage)); |
||||
|
} |
||||
|
|
||||
|
void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) { |
||||
|
out_channel.push(std::make_unique<IStorage>(storage)); |
||||
|
pop_out_data_event->Signal(); |
||||
|
} |
||||
|
|
||||
|
void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) { |
||||
|
in_interactive_channel.push(std::make_unique<IStorage>(storage)); |
||||
|
} |
||||
|
|
||||
|
void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) { |
||||
|
out_interactive_channel.push(std::make_unique<IStorage>(storage)); |
||||
|
pop_interactive_out_data_event->Signal(); |
||||
|
} |
||||
|
|
||||
|
void AppletDataBroker::SignalStateChanged() const { |
||||
|
state_changed_event->Signal(); |
||||
|
} |
||||
|
|
||||
|
Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetNormalDataEvent() const { |
||||
|
return pop_out_data_event; |
||||
|
} |
||||
|
|
||||
|
Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetInteractiveDataEvent() const { |
||||
|
return pop_interactive_out_data_event; |
||||
|
} |
||||
|
|
||||
|
Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetStateChangedEvent() const { |
||||
|
return state_changed_event; |
||||
|
} |
||||
|
|
||||
|
Applet::Applet() = default; |
||||
|
|
||||
|
Applet::~Applet() = default; |
||||
|
|
||||
|
void Applet::Initialize(std::shared_ptr<AppletDataBroker> broker_) { |
||||
|
broker = std::move(broker_); |
||||
|
|
||||
|
const auto common = broker->PopNormalDataToApplet(); |
||||
|
ASSERT(common != nullptr); |
||||
|
|
||||
|
const auto common_data = common->GetData(); |
||||
|
|
||||
|
ASSERT(common_data.size() >= sizeof(CommonArguments)); |
||||
|
std::memcpy(&common_args, common_data.data(), sizeof(CommonArguments)); |
||||
|
|
||||
|
initialized = true; |
||||
|
} |
||||
|
|
||||
|
} // namespace Service::AM::Applets
|
||||
@ -0,0 +1,94 @@ |
|||||
|
// Copyright 2018 yuzu emulator team |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <functional> |
||||
|
#include <memory> |
||||
|
#include <queue> |
||||
|
#include "common/swap.h" |
||||
|
#include "core/hle/kernel/event.h" |
||||
|
|
||||
|
union ResultCode; |
||||
|
|
||||
|
namespace Service::AM { |
||||
|
|
||||
|
class IStorage; |
||||
|
|
||||
|
namespace Applets { |
||||
|
|
||||
|
class AppletDataBroker final { |
||||
|
public: |
||||
|
AppletDataBroker(); |
||||
|
~AppletDataBroker(); |
||||
|
|
||||
|
std::unique_ptr<IStorage> PopNormalDataToGame(); |
||||
|
std::unique_ptr<IStorage> PopNormalDataToApplet(); |
||||
|
|
||||
|
std::unique_ptr<IStorage> PopInteractiveDataToGame(); |
||||
|
std::unique_ptr<IStorage> PopInteractiveDataToApplet(); |
||||
|
|
||||
|
void PushNormalDataFromGame(IStorage storage); |
||||
|
void PushNormalDataFromApplet(IStorage storage); |
||||
|
|
||||
|
void PushInteractiveDataFromGame(IStorage storage); |
||||
|
void PushInteractiveDataFromApplet(IStorage storage); |
||||
|
|
||||
|
void SignalStateChanged() const; |
||||
|
|
||||
|
Kernel::SharedPtr<Kernel::Event> GetNormalDataEvent() const; |
||||
|
Kernel::SharedPtr<Kernel::Event> GetInteractiveDataEvent() const; |
||||
|
Kernel::SharedPtr<Kernel::Event> GetStateChangedEvent() const; |
||||
|
|
||||
|
private: |
||||
|
// Queues are named from applet's perspective |
||||
|
std::queue<std::unique_ptr<IStorage>> |
||||
|
in_channel; // PopNormalDataToApplet and PushNormalDataFromGame |
||||
|
std::queue<std::unique_ptr<IStorage>> |
||||
|
out_channel; // PopNormalDataToGame and PushNormalDataFromApplet |
||||
|
std::queue<std::unique_ptr<IStorage>> |
||||
|
in_interactive_channel; // PopInteractiveDataToApplet and PushInteractiveDataFromGame |
||||
|
std::queue<std::unique_ptr<IStorage>> |
||||
|
out_interactive_channel; // PopInteractiveDataToGame and PushInteractiveDataFromApplet |
||||
|
|
||||
|
Kernel::SharedPtr<Kernel::Event> state_changed_event; |
||||
|
Kernel::SharedPtr<Kernel::Event> pop_out_data_event; // Signaled on PushNormalDataFromApplet |
||||
|
Kernel::SharedPtr<Kernel::Event> |
||||
|
pop_interactive_out_data_event; // Signaled on PushInteractiveDataFromApplet |
||||
|
}; |
||||
|
|
||||
|
class Applet { |
||||
|
public: |
||||
|
Applet(); |
||||
|
virtual ~Applet(); |
||||
|
|
||||
|
virtual void Initialize(std::shared_ptr<AppletDataBroker> broker); |
||||
|
|
||||
|
virtual bool TransactionComplete() const = 0; |
||||
|
virtual ResultCode GetStatus() const = 0; |
||||
|
virtual void ExecuteInteractive() = 0; |
||||
|
virtual void Execute() = 0; |
||||
|
|
||||
|
bool IsInitialized() const { |
||||
|
return initialized; |
||||
|
} |
||||
|
|
||||
|
protected: |
||||
|
struct CommonArguments { |
||||
|
u32_le arguments_version; |
||||
|
u32_le size; |
||||
|
u32_le library_version; |
||||
|
u32_le theme_color; |
||||
|
u8 play_startup_sound; |
||||
|
u64_le system_tick; |
||||
|
}; |
||||
|
static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size."); |
||||
|
|
||||
|
CommonArguments common_args; |
||||
|
std::shared_ptr<AppletDataBroker> broker; |
||||
|
bool initialized = false; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Applets |
||||
|
} // namespace Service::AM |
||||
@ -0,0 +1,161 @@ |
|||||
|
// Copyright 2018 yuzu emulator team
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <cstring>
|
||||
|
#include "common/assert.h"
|
||||
|
#include "common/string_util.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/frontend/applets/software_keyboard.h"
|
||||
|
#include "core/hle/service/am/am.h"
|
||||
|
#include "core/hle/service/am/applets/software_keyboard.h"
|
||||
|
|
||||
|
namespace Service::AM::Applets { |
||||
|
|
||||
|
constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8; |
||||
|
constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4; |
||||
|
constexpr std::size_t DEFAULT_MAX_LENGTH = 500; |
||||
|
constexpr bool INTERACTIVE_STATUS_OK = false; |
||||
|
|
||||
|
static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( |
||||
|
KeyboardConfig config, std::u16string initial_text) { |
||||
|
Core::Frontend::SoftwareKeyboardParameters params{}; |
||||
|
|
||||
|
params.submit_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( |
||||
|
config.submit_text.data(), config.submit_text.size()); |
||||
|
params.header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( |
||||
|
config.header_text.data(), config.header_text.size()); |
||||
|
params.sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.sub_text.data(), |
||||
|
config.sub_text.size()); |
||||
|
params.guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.guide_text.data(), |
||||
|
config.guide_text.size()); |
||||
|
params.initial_text = initial_text; |
||||
|
params.max_length = config.length_limit == 0 ? DEFAULT_MAX_LENGTH : config.length_limit; |
||||
|
params.password = static_cast<bool>(config.is_password); |
||||
|
params.cursor_at_beginning = static_cast<bool>(config.initial_cursor_position); |
||||
|
params.value = static_cast<u8>(config.keyset_disable_bitmask); |
||||
|
|
||||
|
return params; |
||||
|
} |
||||
|
|
||||
|
SoftwareKeyboard::SoftwareKeyboard() = default; |
||||
|
|
||||
|
SoftwareKeyboard::~SoftwareKeyboard() = default; |
||||
|
|
||||
|
void SoftwareKeyboard::Initialize(std::shared_ptr<AppletDataBroker> broker_) { |
||||
|
complete = false; |
||||
|
initial_text.clear(); |
||||
|
final_data.clear(); |
||||
|
|
||||
|
Applet::Initialize(std::move(broker_)); |
||||
|
|
||||
|
const auto keyboard_config_storage = broker->PopNormalDataToApplet(); |
||||
|
ASSERT(keyboard_config_storage != nullptr); |
||||
|
const auto& keyboard_config = keyboard_config_storage->GetData(); |
||||
|
|
||||
|
ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig)); |
||||
|
std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig)); |
||||
|
|
||||
|
const auto work_buffer_storage = broker->PopNormalDataToApplet(); |
||||
|
ASSERT(work_buffer_storage != nullptr); |
||||
|
const auto& work_buffer = work_buffer_storage->GetData(); |
||||
|
|
||||
|
if (config.initial_string_size == 0) |
||||
|
return; |
||||
|
|
||||
|
std::vector<char16_t> string(config.initial_string_size); |
||||
|
std::memcpy(string.data(), work_buffer.data() + config.initial_string_offset, |
||||
|
string.size() * 2); |
||||
|
initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()); |
||||
|
} |
||||
|
|
||||
|
bool SoftwareKeyboard::TransactionComplete() const { |
||||
|
return complete; |
||||
|
} |
||||
|
|
||||
|
ResultCode SoftwareKeyboard::GetStatus() const { |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
void SoftwareKeyboard::ExecuteInteractive() { |
||||
|
if (complete) |
||||
|
return; |
||||
|
|
||||
|
const auto storage = broker->PopInteractiveDataToApplet(); |
||||
|
ASSERT(storage != nullptr); |
||||
|
const auto data = storage->GetData(); |
||||
|
const auto status = static_cast<bool>(data[0]); |
||||
|
|
||||
|
if (status == INTERACTIVE_STATUS_OK) { |
||||
|
complete = true; |
||||
|
} else { |
||||
|
const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()}; |
||||
|
|
||||
|
std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string; |
||||
|
std::memcpy(string.data(), data.data() + 4, string.size() * 2); |
||||
|
frontend.SendTextCheckDialog( |
||||
|
Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()), |
||||
|
[this] { broker->SignalStateChanged(); }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void SoftwareKeyboard::Execute() { |
||||
|
if (complete) { |
||||
|
broker->PushNormalDataFromApplet(IStorage{final_data}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()}; |
||||
|
|
||||
|
const auto parameters = ConvertToFrontendParameters(config, initial_text); |
||||
|
|
||||
|
frontend.RequestText([this](std::optional<std::u16string> text) { WriteText(text); }, |
||||
|
parameters); |
||||
|
} |
||||
|
|
||||
|
void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) { |
||||
|
std::vector<u8> output_main(SWKBD_OUTPUT_BUFFER_SIZE); |
||||
|
|
||||
|
if (text.has_value()) { |
||||
|
std::vector<u8> output_sub(SWKBD_OUTPUT_BUFFER_SIZE); |
||||
|
|
||||
|
if (config.utf_8) { |
||||
|
const u64 size = text->size() + 8; |
||||
|
const auto new_text = Common::UTF16ToUTF8(*text); |
||||
|
|
||||
|
std::memcpy(output_sub.data(), &size, sizeof(u64)); |
||||
|
std::memcpy(output_sub.data() + 8, new_text.data(), |
||||
|
std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 8)); |
||||
|
|
||||
|
output_main[0] = INTERACTIVE_STATUS_OK; |
||||
|
std::memcpy(output_main.data() + 4, new_text.data(), |
||||
|
std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 4)); |
||||
|
} else { |
||||
|
const u64 size = text->size() * 2 + 8; |
||||
|
std::memcpy(output_sub.data(), &size, sizeof(u64)); |
||||
|
std::memcpy(output_sub.data() + 8, text->data(), |
||||
|
std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 8)); |
||||
|
|
||||
|
output_main[0] = INTERACTIVE_STATUS_OK; |
||||
|
std::memcpy(output_main.data() + 4, text->data(), |
||||
|
std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 4)); |
||||
|
} |
||||
|
|
||||
|
complete = !config.text_check; |
||||
|
final_data = output_main; |
||||
|
|
||||
|
if (complete) { |
||||
|
broker->PushNormalDataFromApplet(IStorage{output_main}); |
||||
|
} else { |
||||
|
broker->PushInteractiveDataFromApplet(IStorage{output_sub}); |
||||
|
} |
||||
|
|
||||
|
broker->SignalStateChanged(); |
||||
|
} else { |
||||
|
output_main[0] = 1; |
||||
|
complete = true; |
||||
|
broker->PushNormalDataFromApplet(IStorage{output_main}); |
||||
|
broker->SignalStateChanged(); |
||||
|
} |
||||
|
} |
||||
|
} // namespace Service::AM::Applets
|
||||
@ -0,0 +1,69 @@ |
|||||
|
// Copyright 2018 yuzu emulator team |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "common/common_funcs.h" |
||||
|
#include "core/hle/service/am/am.h" |
||||
|
#include "core/hle/service/am/applets/applets.h" |
||||
|
|
||||
|
namespace Service::AM::Applets { |
||||
|
|
||||
|
enum class KeysetDisable : u32 { |
||||
|
Space = 0x02, |
||||
|
Address = 0x04, |
||||
|
Percent = 0x08, |
||||
|
Slashes = 0x10, |
||||
|
Numbers = 0x40, |
||||
|
DownloadCode = 0x80, |
||||
|
}; |
||||
|
|
||||
|
struct KeyboardConfig { |
||||
|
INSERT_PADDING_BYTES(4); |
||||
|
std::array<char16_t, 9> submit_text; |
||||
|
u16_le left_symbol_key; |
||||
|
u16_le right_symbol_key; |
||||
|
INSERT_PADDING_BYTES(1); |
||||
|
KeysetDisable keyset_disable_bitmask; |
||||
|
u32_le initial_cursor_position; |
||||
|
std::array<char16_t, 65> header_text; |
||||
|
std::array<char16_t, 129> sub_text; |
||||
|
std::array<char16_t, 257> guide_text; |
||||
|
u32_le length_limit; |
||||
|
INSERT_PADDING_BYTES(4); |
||||
|
u32_le is_password; |
||||
|
INSERT_PADDING_BYTES(5); |
||||
|
bool utf_8; |
||||
|
bool draw_background; |
||||
|
u32_le initial_string_offset; |
||||
|
u32_le initial_string_size; |
||||
|
u32_le user_dictionary_offset; |
||||
|
u32_le user_dictionary_size; |
||||
|
bool text_check; |
||||
|
u64_le text_check_callback; |
||||
|
}; |
||||
|
static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect size."); |
||||
|
|
||||
|
class SoftwareKeyboard final : public Applet { |
||||
|
public: |
||||
|
SoftwareKeyboard(); |
||||
|
~SoftwareKeyboard() override; |
||||
|
|
||||
|
void Initialize(std::shared_ptr<AppletDataBroker> broker) override; |
||||
|
|
||||
|
bool TransactionComplete() const override; |
||||
|
ResultCode GetStatus() const override; |
||||
|
void ExecuteInteractive() override; |
||||
|
void Execute() override; |
||||
|
|
||||
|
void WriteText(std::optional<std::u16string> text); |
||||
|
|
||||
|
private: |
||||
|
KeyboardConfig config; |
||||
|
std::u16string initial_text; |
||||
|
bool complete = false; |
||||
|
std::vector<u8> final_data; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Service::AM::Applets |
||||
@ -0,0 +1,152 @@ |
|||||
|
// Copyright 2018 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <algorithm>
|
||||
|
#include <mutex>
|
||||
|
#include <QDialogButtonBox>
|
||||
|
#include <QFont>
|
||||
|
#include <QLabel>
|
||||
|
#include <QLineEdit>
|
||||
|
#include <QVBoxLayout>
|
||||
|
#include "core/hle/lock.h"
|
||||
|
#include "yuzu/applets/software_keyboard.h"
|
||||
|
#include "yuzu/main.h"
|
||||
|
|
||||
|
QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator( |
||||
|
Core::Frontend::SoftwareKeyboardParameters parameters) |
||||
|
: parameters(std::move(parameters)) {} |
||||
|
|
||||
|
QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const { |
||||
|
if (input.size() > parameters.max_length) |
||||
|
return Invalid; |
||||
|
if (parameters.disable_space && input.contains(' ')) |
||||
|
return Invalid; |
||||
|
if (parameters.disable_address && input.contains('@')) |
||||
|
return Invalid; |
||||
|
if (parameters.disable_percent && input.contains('%')) |
||||
|
return Invalid; |
||||
|
if (parameters.disable_slash && (input.contains('/') || input.contains('\\'))) |
||||
|
return Invalid; |
||||
|
if (parameters.disable_number && |
||||
|
std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) { |
||||
|
return Invalid; |
||||
|
} |
||||
|
|
||||
|
if (parameters.disable_download_code && |
||||
|
std::any_of(input.begin(), input.end(), [](QChar c) { return c == 'O' || c == 'I'; })) { |
||||
|
return Invalid; |
||||
|
} |
||||
|
|
||||
|
return Acceptable; |
||||
|
} |
||||
|
|
||||
|
QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( |
||||
|
QWidget* parent, Core::Frontend::SoftwareKeyboardParameters parameters_) |
||||
|
: QDialog(parent), parameters(std::move(parameters_)) { |
||||
|
layout = new QVBoxLayout; |
||||
|
|
||||
|
header_label = new QLabel(QString::fromStdU16String(parameters.header_text)); |
||||
|
header_label->setFont({header_label->font().family(), 11, QFont::Bold}); |
||||
|
if (header_label->text().isEmpty()) |
||||
|
header_label->setText(tr("Enter text:")); |
||||
|
|
||||
|
sub_label = new QLabel(QString::fromStdU16String(parameters.sub_text)); |
||||
|
sub_label->setFont({sub_label->font().family(), sub_label->font().pointSize(), |
||||
|
sub_label->font().weight(), true}); |
||||
|
sub_label->setHidden(parameters.sub_text.empty()); |
||||
|
|
||||
|
guide_label = new QLabel(QString::fromStdU16String(parameters.guide_text)); |
||||
|
guide_label->setHidden(parameters.guide_text.empty()); |
||||
|
|
||||
|
length_label = new QLabel(QStringLiteral("0/%1").arg(parameters.max_length)); |
||||
|
length_label->setAlignment(Qt::AlignRight); |
||||
|
length_label->setFont({length_label->font().family(), 8}); |
||||
|
|
||||
|
line_edit = new QLineEdit; |
||||
|
line_edit->setValidator(new QtSoftwareKeyboardValidator(parameters)); |
||||
|
line_edit->setMaxLength(static_cast<int>(parameters.max_length)); |
||||
|
line_edit->setText(QString::fromStdU16String(parameters.initial_text)); |
||||
|
line_edit->setCursorPosition( |
||||
|
parameters.cursor_at_beginning ? 0 : static_cast<int>(parameters.initial_text.size())); |
||||
|
line_edit->setEchoMode(parameters.password ? QLineEdit::Password : QLineEdit::Normal); |
||||
|
|
||||
|
connect(line_edit, &QLineEdit::textChanged, this, [this](const QString& text) { |
||||
|
length_label->setText(QStringLiteral("%1/%2").arg(text.size()).arg(parameters.max_length)); |
||||
|
}); |
||||
|
|
||||
|
buttons = new QDialogButtonBox; |
||||
|
buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); |
||||
|
buttons->addButton(parameters.submit_text.empty() |
||||
|
? tr("OK") |
||||
|
: QString::fromStdU16String(parameters.submit_text), |
||||
|
QDialogButtonBox::AcceptRole); |
||||
|
|
||||
|
connect(buttons, &QDialogButtonBox::accepted, this, &QtSoftwareKeyboardDialog::Submit); |
||||
|
connect(buttons, &QDialogButtonBox::rejected, this, &QtSoftwareKeyboardDialog::Reject); |
||||
|
layout->addWidget(header_label); |
||||
|
layout->addWidget(sub_label); |
||||
|
layout->addWidget(guide_label); |
||||
|
layout->addWidget(length_label); |
||||
|
layout->addWidget(line_edit); |
||||
|
layout->addWidget(buttons); |
||||
|
setLayout(layout); |
||||
|
setWindowTitle(tr("Software Keyboard")); |
||||
|
} |
||||
|
|
||||
|
QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default; |
||||
|
|
||||
|
void QtSoftwareKeyboardDialog::Submit() { |
||||
|
ok = true; |
||||
|
text = line_edit->text().toStdU16String(); |
||||
|
accept(); |
||||
|
} |
||||
|
|
||||
|
void QtSoftwareKeyboardDialog::Reject() { |
||||
|
ok = false; |
||||
|
text.clear(); |
||||
|
accept(); |
||||
|
} |
||||
|
|
||||
|
std::u16string QtSoftwareKeyboardDialog::GetText() const { |
||||
|
return text; |
||||
|
} |
||||
|
|
||||
|
bool QtSoftwareKeyboardDialog::GetStatus() const { |
||||
|
return ok; |
||||
|
} |
||||
|
|
||||
|
QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { |
||||
|
connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window, |
||||
|
&GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection); |
||||
|
connect(this, &QtSoftwareKeyboard::MainWindowTextCheckDialog, &main_window, |
||||
|
&GMainWindow::SoftwareKeyboardInvokeCheckDialog, Qt::BlockingQueuedConnection); |
||||
|
connect(&main_window, &GMainWindow::SoftwareKeyboardFinishedText, this, |
||||
|
&QtSoftwareKeyboard::MainWindowFinishedText, Qt::QueuedConnection); |
||||
|
} |
||||
|
|
||||
|
QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; |
||||
|
|
||||
|
void QtSoftwareKeyboard::RequestText(std::function<void(std::optional<std::u16string>)> out, |
||||
|
Core::Frontend::SoftwareKeyboardParameters parameters) const { |
||||
|
text_output = out; |
||||
|
emit MainWindowGetText(parameters); |
||||
|
} |
||||
|
|
||||
|
void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message, |
||||
|
std::function<void()> finished_check) const { |
||||
|
this->finished_check = finished_check; |
||||
|
emit MainWindowTextCheckDialog(error_message); |
||||
|
} |
||||
|
|
||||
|
void QtSoftwareKeyboard::MainWindowFinishedText(std::optional<std::u16string> text) { |
||||
|
// Acquire the HLE mutex
|
||||
|
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); |
||||
|
text_output(text); |
||||
|
} |
||||
|
|
||||
|
void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() { |
||||
|
// Acquire the HLE mutex
|
||||
|
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); |
||||
|
finished_check(); |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
// Copyright 2018 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <QDialog> |
||||
|
#include <QValidator> |
||||
|
#include "common/assert.h" |
||||
|
#include "core/frontend/applets/software_keyboard.h" |
||||
|
|
||||
|
class GMainWindow; |
||||
|
class QDialogButtonBox; |
||||
|
class QLabel; |
||||
|
class QLineEdit; |
||||
|
class QVBoxLayout; |
||||
|
class QtSoftwareKeyboard; |
||||
|
|
||||
|
class QtSoftwareKeyboardValidator final : public QValidator { |
||||
|
public: |
||||
|
explicit QtSoftwareKeyboardValidator(Core::Frontend::SoftwareKeyboardParameters parameters); |
||||
|
State validate(QString& input, int& pos) const override; |
||||
|
|
||||
|
private: |
||||
|
Core::Frontend::SoftwareKeyboardParameters parameters; |
||||
|
}; |
||||
|
|
||||
|
class QtSoftwareKeyboardDialog final : public QDialog { |
||||
|
Q_OBJECT |
||||
|
|
||||
|
public: |
||||
|
QtSoftwareKeyboardDialog(QWidget* parent, |
||||
|
Core::Frontend::SoftwareKeyboardParameters parameters); |
||||
|
~QtSoftwareKeyboardDialog() override; |
||||
|
|
||||
|
void Submit(); |
||||
|
void Reject(); |
||||
|
|
||||
|
std::u16string GetText() const; |
||||
|
bool GetStatus() const; |
||||
|
|
||||
|
private: |
||||
|
bool ok = false; |
||||
|
std::u16string text; |
||||
|
|
||||
|
QDialogButtonBox* buttons; |
||||
|
QLabel* header_label; |
||||
|
QLabel* sub_label; |
||||
|
QLabel* guide_label; |
||||
|
QLabel* length_label; |
||||
|
QLineEdit* line_edit; |
||||
|
QVBoxLayout* layout; |
||||
|
|
||||
|
Core::Frontend::SoftwareKeyboardParameters parameters; |
||||
|
}; |
||||
|
|
||||
|
class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { |
||||
|
Q_OBJECT |
||||
|
|
||||
|
public: |
||||
|
explicit QtSoftwareKeyboard(GMainWindow& parent); |
||||
|
~QtSoftwareKeyboard() override; |
||||
|
|
||||
|
void RequestText(std::function<void(std::optional<std::u16string>)> out, |
||||
|
Core::Frontend::SoftwareKeyboardParameters parameters) const override; |
||||
|
void SendTextCheckDialog(std::u16string error_message, |
||||
|
std::function<void()> finished_check) const override; |
||||
|
|
||||
|
signals: |
||||
|
void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const; |
||||
|
void MainWindowTextCheckDialog(std::u16string error_message) const; |
||||
|
|
||||
|
public slots: |
||||
|
void MainWindowFinishedText(std::optional<std::u16string> text); |
||||
|
void MainWindowFinishedCheckDialog(); |
||||
|
|
||||
|
private: |
||||
|
mutable std::function<void(std::optional<std::u16string>)> text_output; |
||||
|
mutable std::function<void()> finished_check; |
||||
|
}; |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue