mirror of https://git.suyu.dev/suyu/suyu
Merge pull request #1667 from DarkLordZach/swkbd
am: Implement HLE software keyboard appletmerge-requests/60/head
commit
b6d2c64f4d
@ -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;
|
||||||
|
};
|
Loading…
Reference in New Issue