Browse Source
Merge pull request #823 from Subv/applets_drawing
Merge pull request #823 from Subv/applets_drawing
Library applet support (swkbd for now)pull/15/merge
11 changed files with 567 additions and 58 deletions
-
4src/core/CMakeLists.txt
-
94src/core/hle/applets/applet.cpp
-
77src/core/hle/applets/applet.h
-
105src/core/hle/applets/swkbd.cpp
-
87src/core/hle/applets/swkbd.h
-
160src/core/hle/service/apt/apt.cpp
-
49src/core/hle/service/apt/apt.h
-
31src/core/hle/service/apt/apt_a.cpp
-
4src/core/hle/service/apt/apt_u.cpp
-
4src/core/hle/service/gsp_gpu.cpp
-
10src/core/hle/service/gsp_gpu.h
@ -0,0 +1,94 @@ |
|||||
|
// Copyright 2015 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "common/assert.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
|
||||
|
#include "core/core_timing.h"
|
||||
|
#include "core/hle/applets/applet.h"
|
||||
|
#include "core/hle/applets/swkbd.h"
|
||||
|
|
||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
||||
|
// Specializes std::hash for AppletId, so that we can use it in std::unordered_map.
|
||||
|
// Workaround for libstdc++ bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970
|
||||
|
namespace std { |
||||
|
template <> |
||||
|
struct hash<Service::APT::AppletId> { |
||||
|
typedef Service::APT::AppletId argument_type; |
||||
|
typedef std::size_t result_type; |
||||
|
|
||||
|
result_type operator()(const argument_type& id_code) const { |
||||
|
typedef std::underlying_type<argument_type>::type Type; |
||||
|
return std::hash<Type>()(static_cast<Type>(id_code)); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
namespace HLE { |
||||
|
namespace Applets { |
||||
|
|
||||
|
static std::unordered_map<Service::APT::AppletId, std::shared_ptr<Applet>> applets; |
||||
|
static u32 applet_update_event = -1; ///< The CoreTiming event identifier for the Applet update callback.
|
||||
|
/// The interval at which the Applet update callback will be called, 16.6ms
|
||||
|
static const u64 applet_update_interval_us = 16666; |
||||
|
|
||||
|
ResultCode Applet::Create(Service::APT::AppletId id) { |
||||
|
switch (id) { |
||||
|
case Service::APT::AppletId::SoftwareKeyboard1: |
||||
|
case Service::APT::AppletId::SoftwareKeyboard2: |
||||
|
applets[id] = std::make_shared<SoftwareKeyboard>(id); |
||||
|
break; |
||||
|
default: |
||||
|
// TODO(Subv): Find the right error code
|
||||
|
return ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotSupported, ErrorLevel::Permanent); |
||||
|
} |
||||
|
|
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
std::shared_ptr<Applet> Applet::Get(Service::APT::AppletId id) { |
||||
|
auto itr = applets.find(id); |
||||
|
if (itr != applets.end()) |
||||
|
return itr->second; |
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
/// Handles updating the current Applet every time it's called.
|
||||
|
static void AppletUpdateEvent(u64 applet_id, int cycles_late) { |
||||
|
Service::APT::AppletId id = static_cast<Service::APT::AppletId>(applet_id); |
||||
|
std::shared_ptr<Applet> applet = Applet::Get(id); |
||||
|
ASSERT_MSG(applet != nullptr, "Applet doesn't exist! applet_id=%08X", id); |
||||
|
|
||||
|
applet->Update(); |
||||
|
|
||||
|
// If the applet is still running after the last update, reschedule the event
|
||||
|
if (applet->IsRunning()) { |
||||
|
CoreTiming::ScheduleEvent(usToCycles(applet_update_interval_us) - cycles_late, |
||||
|
applet_update_event, applet_id); |
||||
|
} else { |
||||
|
// Otherwise the applet has terminated, in which case we should clean it up
|
||||
|
applets[id] = nullptr; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ResultCode Applet::Start(const Service::APT::AppletStartupParameter& parameter) { |
||||
|
ResultCode result = StartImpl(parameter); |
||||
|
if (result.IsError()) |
||||
|
return result; |
||||
|
// Schedule the update event
|
||||
|
CoreTiming::ScheduleEvent(usToCycles(applet_update_interval_us), applet_update_event, static_cast<u64>(id)); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
void Init() { |
||||
|
// Register the applet update callback
|
||||
|
applet_update_event = CoreTiming::RegisterEvent("HLE Applet Update Event", AppletUpdateEvent); |
||||
|
} |
||||
|
|
||||
|
void Shutdown() { |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} // namespace
|
||||
@ -0,0 +1,77 @@ |
|||||
|
// Copyright 2015 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
#include "core/hle/kernel/kernel.h" |
||||
|
#include "core/hle/kernel/shared_memory.h" |
||||
|
#include "core/hle/service/apt/apt.h" |
||||
|
|
||||
|
namespace HLE { |
||||
|
namespace Applets { |
||||
|
|
||||
|
class Applet { |
||||
|
public: |
||||
|
virtual ~Applet() { } |
||||
|
Applet(Service::APT::AppletId id) : id(id) { } |
||||
|
|
||||
|
/** |
||||
|
* Creates an instance of the Applet subclass identified by the parameter. |
||||
|
* and stores it in a global map. |
||||
|
* @param id Id of the applet to create. |
||||
|
* @returns ResultCode Whether the operation was successful or not. |
||||
|
*/ |
||||
|
static ResultCode Create(Service::APT::AppletId id); |
||||
|
|
||||
|
/** |
||||
|
* Retrieves the Applet instance identified by the specified id. |
||||
|
* @param id Id of the Applet to retrieve. |
||||
|
* @returns Requested Applet or nullptr if not found. |
||||
|
*/ |
||||
|
static std::shared_ptr<Applet> Get(Service::APT::AppletId id); |
||||
|
|
||||
|
/** |
||||
|
* Handles a parameter from the application. |
||||
|
* @param parameter Parameter data to handle. |
||||
|
* @returns ResultCode Whether the operation was successful or not. |
||||
|
*/ |
||||
|
virtual ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) = 0; |
||||
|
|
||||
|
/** |
||||
|
* Handles the Applet start event, triggered from the application. |
||||
|
* @param parameter Parameter data to handle. |
||||
|
* @returns ResultCode Whether the operation was successful or not. |
||||
|
*/ |
||||
|
ResultCode Start(const Service::APT::AppletStartupParameter& parameter); |
||||
|
|
||||
|
/** |
||||
|
* Whether the applet is currently executing instead of the host application or not. |
||||
|
*/ |
||||
|
virtual bool IsRunning() const = 0; |
||||
|
|
||||
|
/** |
||||
|
* Handles an update tick for the Applet, lets it update the screen, send commands, etc. |
||||
|
*/ |
||||
|
virtual void Update() = 0; |
||||
|
|
||||
|
protected: |
||||
|
/** |
||||
|
* Handles the Applet start event, triggered from the application. |
||||
|
* @param parameter Parameter data to handle. |
||||
|
* @returns ResultCode Whether the operation was successful or not. |
||||
|
*/ |
||||
|
virtual ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) = 0; |
||||
|
|
||||
|
Service::APT::AppletId id; ///< Id of this Applet |
||||
|
}; |
||||
|
|
||||
|
/// Initializes the HLE applets |
||||
|
void Init(); |
||||
|
|
||||
|
/// Shuts down the HLE applets |
||||
|
void Shutdown(); |
||||
|
|
||||
|
} |
||||
|
} // namespace |
||||
@ -0,0 +1,105 @@ |
|||||
|
// Copyright 2015 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "common/assert.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "common/string_util.h"
|
||||
|
|
||||
|
#include "core/hle/applets/swkbd.h"
|
||||
|
#include "core/hle/service/hid/hid.h"
|
||||
|
#include "core/hle/service/gsp_gpu.h"
|
||||
|
#include "video_core/video_core.h"
|
||||
|
|
||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
||||
|
namespace HLE { |
||||
|
namespace Applets { |
||||
|
|
||||
|
SoftwareKeyboard::SoftwareKeyboard(Service::APT::AppletId id) : Applet(id), started(false) { |
||||
|
// Create the SharedMemory that will hold the framebuffer data
|
||||
|
// TODO(Subv): What size should we use here?
|
||||
|
using Kernel::MemoryPermission; |
||||
|
framebuffer_memory = Kernel::SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, "SoftwareKeyboard Memory"); |
||||
|
} |
||||
|
|
||||
|
ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter const& parameter) { |
||||
|
if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) { |
||||
|
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal); |
||||
|
UNIMPLEMENTED(); |
||||
|
// TODO(Subv): Find the right error code
|
||||
|
return ResultCode(-1); |
||||
|
} |
||||
|
|
||||
|
Service::APT::MessageParameter result; |
||||
|
// The buffer passed in parameter contains the data returned by GSPGPU::ImportDisplayCaptureInfo
|
||||
|
result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished); |
||||
|
result.data = nullptr; |
||||
|
result.buffer_size = 0; |
||||
|
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application); |
||||
|
result.sender_id = static_cast<u32>(id); |
||||
|
result.object = framebuffer_memory; |
||||
|
|
||||
|
Service::APT::SendParameter(result); |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
ResultCode SoftwareKeyboard::StartImpl(Service::APT::AppletStartupParameter const& parameter) { |
||||
|
ASSERT_MSG(parameter.buffer_size == sizeof(config), "The size of the parameter (SoftwareKeyboardConfig) is wrong"); |
||||
|
|
||||
|
memcpy(&config, parameter.data, parameter.buffer_size); |
||||
|
text_memory = boost::static_pointer_cast<Kernel::SharedMemory, Kernel::Object>(parameter.object); |
||||
|
|
||||
|
// TODO(Subv): Verify if this is the correct behavior
|
||||
|
memset(text_memory->GetPointer(), 0, text_memory->size); |
||||
|
|
||||
|
DrawScreenKeyboard(); |
||||
|
|
||||
|
started = true; |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
void SoftwareKeyboard::Update() { |
||||
|
// TODO(Subv): Handle input using the touch events from the HID module
|
||||
|
|
||||
|
// TODO(Subv): Remove this hardcoded text
|
||||
|
std::u16string text = Common::UTF8ToUTF16("Citra"); |
||||
|
memcpy(text_memory->GetPointer(), text.c_str(), text.length() * sizeof(char16_t)); |
||||
|
|
||||
|
// TODO(Subv): Ask for input and write it to the shared memory
|
||||
|
// TODO(Subv): Find out what are the possible values for the return code,
|
||||
|
// some games seem to check for a hardcoded 2
|
||||
|
config.return_code = 2; |
||||
|
config.text_length = 6; |
||||
|
config.text_offset = 0; |
||||
|
|
||||
|
// TODO(Subv): We're finalizing the applet immediately after it's started,
|
||||
|
// but we should defer this call until after all the input has been collected.
|
||||
|
Finalize(); |
||||
|
} |
||||
|
|
||||
|
void SoftwareKeyboard::DrawScreenKeyboard() { |
||||
|
auto bottom_screen = GSP_GPU::GetFrameBufferInfo(0, 1); |
||||
|
auto info = bottom_screen->framebuffer_info[bottom_screen->index]; |
||||
|
|
||||
|
// TODO(Subv): Draw the HLE keyboard, for now just zero-fill the framebuffer
|
||||
|
memset(Memory::GetPointer(info.address_left), 0, info.stride * 320); |
||||
|
|
||||
|
GSP_GPU::SetBufferSwap(1, info); |
||||
|
} |
||||
|
|
||||
|
void SoftwareKeyboard::Finalize() { |
||||
|
// Let the application know that we're closing
|
||||
|
Service::APT::MessageParameter message; |
||||
|
message.buffer_size = sizeof(SoftwareKeyboardConfig); |
||||
|
message.data = reinterpret_cast<u8*>(&config); |
||||
|
message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed); |
||||
|
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application); |
||||
|
message.sender_id = static_cast<u32>(id); |
||||
|
Service::APT::SendParameter(message); |
||||
|
|
||||
|
started = false; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} // namespace
|
||||
@ -0,0 +1,87 @@ |
|||||
|
// Copyright 2015 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
#include "core/hle/applets/applet.h" |
||||
|
#include "core/hle/kernel/kernel.h" |
||||
|
#include "core/hle/kernel/shared_memory.h" |
||||
|
#include "core/hle/service/apt/apt.h" |
||||
|
|
||||
|
namespace HLE { |
||||
|
namespace Applets { |
||||
|
|
||||
|
struct SoftwareKeyboardConfig { |
||||
|
INSERT_PADDING_WORDS(0x8); |
||||
|
|
||||
|
u16 max_text_length; ///< Maximum length of the input text |
||||
|
|
||||
|
INSERT_PADDING_BYTES(0x6E); |
||||
|
|
||||
|
char16_t display_text[65]; ///< Text to display when asking the user for input |
||||
|
|
||||
|
INSERT_PADDING_BYTES(0xE); |
||||
|
|
||||
|
u32 default_text_offset; ///< Offset of the default text in the output SharedMemory |
||||
|
|
||||
|
INSERT_PADDING_WORDS(0x3); |
||||
|
|
||||
|
u32 shared_memory_size; ///< Size of the SharedMemory |
||||
|
|
||||
|
INSERT_PADDING_WORDS(0x1); |
||||
|
|
||||
|
u32 return_code; ///< Return code of the SoftwareKeyboard, usually 2, other values are unknown |
||||
|
|
||||
|
INSERT_PADDING_WORDS(0x2); |
||||
|
|
||||
|
u32 text_offset; ///< Offset in the SharedMemory where the output text starts |
||||
|
u16 text_length; ///< Length in characters of the output text |
||||
|
|
||||
|
INSERT_PADDING_BYTES(0x2B6); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* The size of this structure (0x400) has been verified via reverse engineering of multiple games |
||||
|
* that use the software keyboard. |
||||
|
*/ |
||||
|
static_assert(sizeof(SoftwareKeyboardConfig) == 0x400, "Software Keyboard Config size is wrong"); |
||||
|
|
||||
|
class SoftwareKeyboard final : public Applet { |
||||
|
public: |
||||
|
SoftwareKeyboard(Service::APT::AppletId id); |
||||
|
~SoftwareKeyboard() {} |
||||
|
|
||||
|
ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) override; |
||||
|
ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) override; |
||||
|
void Update() override; |
||||
|
bool IsRunning() const override { return started; } |
||||
|
|
||||
|
/** |
||||
|
* Draws a keyboard to the current bottom screen framebuffer. |
||||
|
*/ |
||||
|
void DrawScreenKeyboard(); |
||||
|
|
||||
|
/** |
||||
|
* Sends the LibAppletClosing signal to the application, |
||||
|
* along with the relevant data buffers. |
||||
|
*/ |
||||
|
void Finalize(); |
||||
|
|
||||
|
/// TODO(Subv): Find out what this is actually used for. |
||||
|
/// It is believed that the application stores the current screen image here. |
||||
|
Kernel::SharedPtr<Kernel::SharedMemory> framebuffer_memory; |
||||
|
|
||||
|
/// SharedMemory where the output text will be stored |
||||
|
Kernel::SharedPtr<Kernel::SharedMemory> text_memory; |
||||
|
|
||||
|
/// Configuration of this instance of the SoftwareKeyboard, as received from the application |
||||
|
SoftwareKeyboardConfig config; |
||||
|
|
||||
|
/// Whether this applet is currently running instead of the host application or not. |
||||
|
bool started; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
} // namespace |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue