Skip to content

Commit

Permalink
[Windows] Merge PlatformHandlerWin32 to superclass (flutter#35056)
Browse files Browse the repository at this point in the history
With the removal of the UWP embedder, we can merge Win32-specific
implementation classes with their abstract superclasses where those
superclasses existed only to support both UWP and Win32.

No new tests since this is purely a restructuring of existing code
within the Win32 embedder, with no expected change in behaviour.
Covered by existing tests in task_runner_unittests.cc.

Issue: flutter#108386
  • Loading branch information
cbracken authored Aug 1, 2022
1 parent e771729 commit eea2887
Show file tree
Hide file tree
Showing 9 changed files with 642 additions and 722 deletions.
3 changes: 0 additions & 3 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2412,9 +2412,6 @@ FILE: ../../../flutter/shell/platform/windows/keyboard_utils_unittests.cc
FILE: ../../../flutter/shell/platform/windows/platform_handler.cc
FILE: ../../../flutter/shell/platform/windows/platform_handler.h
FILE: ../../../flutter/shell/platform/windows/platform_handler_unittests.cc
FILE: ../../../flutter/shell/platform/windows/platform_handler_win32.cc
FILE: ../../../flutter/shell/platform/windows/platform_handler_win32.h
FILE: ../../../flutter/shell/platform/windows/platform_handler_win32_unittests.cc
FILE: ../../../flutter/shell/platform/windows/public/flutter_windows.h
FILE: ../../../flutter/shell/platform/windows/sequential_id_generator.cc
FILE: ../../../flutter/shell/platform/windows/sequential_id_generator.h
Expand Down
3 changes: 0 additions & 3 deletions shell/platform/windows/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ source_set("flutter_windows_source") {
"keyboard_utils.h",
"platform_handler.cc",
"platform_handler.h",
"platform_handler_win32.cc",
"platform_handler_win32.h",
"sequential_id_generator.cc",
"sequential_id_generator.h",
"settings_plugin.cc",
Expand Down Expand Up @@ -182,7 +180,6 @@ executable("flutter_windows_unittests") {
"keyboard_unittests.cc",
"keyboard_utils_unittests.cc",
"platform_handler_unittests.cc",
"platform_handler_win32_unittests.cc",
"sequential_id_generator_unittests.cc",
"settings_plugin_unittests.cc",
"system_utils_unittests.cc",
Expand Down
3 changes: 2 additions & 1 deletion shell/platform/windows/flutter_windows_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ void FlutterWindowsView::SetEngine(
// Set up the system channel handlers.
auto internal_plugin_messenger = internal_plugin_registrar_->messenger();
InitializeKeyboard();
platform_handler_ = PlatformHandler::Create(internal_plugin_messenger, this);
platform_handler_ =
std::make_unique<PlatformHandler>(internal_plugin_messenger, this);
cursor_handler_ = std::make_unique<CursorHandler>(internal_plugin_messenger,
binding_handler_.get());

Expand Down
292 changes: 289 additions & 3 deletions shell/platform/windows/platform_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@

#include "flutter/shell/platform/windows/platform_handler.h"

#include <windows.h>

#include <cstring>
#include <iostream>
#include <optional>

#include "flutter/fml/platform/win/wstring_conversion.h"
#include "flutter/shell/platform/common/json_method_codec.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"

static constexpr char kChannelName[] = "flutter/platform";

Expand All @@ -15,26 +23,304 @@ static constexpr char kPlaySoundMethod[] = "SystemSound.play";

static constexpr char kTextPlainFormat[] = "text/plain";
static constexpr char kTextKey[] = "text";

static constexpr char kUnknownClipboardFormatMessage[] =
"Unknown clipboard format";

static constexpr char kValueKey[] = "value";
static constexpr int kAccessDeniedErrorCode = 5;
static constexpr int kErrorSuccess = 0;

namespace flutter {

PlatformHandler::PlatformHandler(BinaryMessenger* messenger)
namespace {

// A scoped wrapper for GlobalAlloc/GlobalFree.
class ScopedGlobalMemory {
public:
// Allocates |bytes| bytes of global memory with the given flags.
ScopedGlobalMemory(unsigned int flags, size_t bytes) {
memory_ = ::GlobalAlloc(flags, bytes);
if (!memory_) {
std::cerr << "Unable to allocate global memory: " << ::GetLastError();
}
}

~ScopedGlobalMemory() {
if (memory_) {
if (::GlobalFree(memory_) != nullptr) {
std::cerr << "Failed to free global allocation: " << ::GetLastError();
}
}
}

// Prevent copying.
ScopedGlobalMemory(ScopedGlobalMemory const&) = delete;
ScopedGlobalMemory& operator=(ScopedGlobalMemory const&) = delete;

// Returns the memory pointer, which will be nullptr if allocation failed.
void* get() { return memory_; }

void* release() {
void* memory = memory_;
memory_ = nullptr;
return memory;
}

private:
HGLOBAL memory_;
};

// A scoped wrapper for GlobalLock/GlobalUnlock.
class ScopedGlobalLock {
public:
// Attempts to acquire a global lock on |memory| for the life of this object.
ScopedGlobalLock(HGLOBAL memory) {
source_ = memory;
if (memory) {
locked_memory_ = ::GlobalLock(memory);
if (!locked_memory_) {
std::cerr << "Unable to acquire global lock: " << ::GetLastError();
}
}
}

~ScopedGlobalLock() {
if (locked_memory_) {
if (!::GlobalUnlock(source_)) {
DWORD error = ::GetLastError();
if (error != NO_ERROR) {
std::cerr << "Unable to release global lock: " << ::GetLastError();
}
}
}
}

// Prevent copying.
ScopedGlobalLock(ScopedGlobalLock const&) = delete;
ScopedGlobalLock& operator=(ScopedGlobalLock const&) = delete;

// Returns the locked memory pointer, which will be nullptr if acquiring the
// lock failed.
void* get() { return locked_memory_; }

private:
HGLOBAL source_;
void* locked_memory_;
};

// A Clipboard wrapper that automatically closes the clipboard when it goes out
// of scope.
class ScopedClipboard : public ScopedClipboardInterface {
public:
ScopedClipboard();
virtual ~ScopedClipboard();

// Prevent copying.
ScopedClipboard(ScopedClipboard const&) = delete;
ScopedClipboard& operator=(ScopedClipboard const&) = delete;

int Open(HWND window) override;

bool HasString() override;

std::variant<std::wstring, int> GetString() override;

int SetString(const std::wstring string) override;

private:
bool opened_ = false;
};

ScopedClipboard::ScopedClipboard() {}

ScopedClipboard::~ScopedClipboard() {
if (opened_) {
::CloseClipboard();
}
}

int ScopedClipboard::Open(HWND window) {
opened_ = ::OpenClipboard(window);

if (!opened_) {
return ::GetLastError();
}

return kErrorSuccess;
}

bool ScopedClipboard::HasString() {
// Allow either plain text format, since getting data will auto-interpolate.
return ::IsClipboardFormatAvailable(CF_UNICODETEXT) ||
::IsClipboardFormatAvailable(CF_TEXT);
}

std::variant<std::wstring, int> ScopedClipboard::GetString() {
assert(opened_);

HANDLE data = ::GetClipboardData(CF_UNICODETEXT);
if (data == nullptr) {
return ::GetLastError();
}
ScopedGlobalLock locked_data(data);

if (!locked_data.get()) {
return ::GetLastError();
}
return static_cast<wchar_t*>(locked_data.get());
}

int ScopedClipboard::SetString(const std::wstring string) {
assert(opened_);
if (!::EmptyClipboard()) {
return ::GetLastError();
}
size_t null_terminated_byte_count =
sizeof(decltype(string)::traits_type::char_type) * (string.size() + 1);
ScopedGlobalMemory destination_memory(GMEM_MOVEABLE,
null_terminated_byte_count);
ScopedGlobalLock locked_memory(destination_memory.get());
if (!locked_memory.get()) {
return ::GetLastError();
}
memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count);
if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) {
return ::GetLastError();
}
// The clipboard now owns the global memory.
destination_memory.release();
return kErrorSuccess;
}

} // namespace

PlatformHandler::PlatformHandler(
BinaryMessenger* messenger,
FlutterWindowsView* view,
std::optional<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
scoped_clipboard_provider)
: channel_(std::make_unique<MethodChannel<rapidjson::Document>>(
messenger,
kChannelName,
&JsonMethodCodec::GetInstance())) {
&JsonMethodCodec::GetInstance())),
view_(view) {
channel_->SetMethodCallHandler(
[this](const MethodCall<rapidjson::Document>& call,
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
HandleMethodCall(call, std::move(result));
});
if (scoped_clipboard_provider.has_value()) {
scoped_clipboard_provider_ = scoped_clipboard_provider.value();
} else {
scoped_clipboard_provider_ = []() {
return std::make_unique<ScopedClipboard>();
};
}
}

PlatformHandler::~PlatformHandler() = default;

void PlatformHandler::GetPlainText(
std::unique_ptr<MethodResult<rapidjson::Document>> result,
std::string_view key) {
std::unique_ptr<ScopedClipboardInterface> clipboard =
scoped_clipboard_provider_();

int open_result = clipboard->Open(std::get<HWND>(*view_->GetRenderTarget()));
if (open_result != kErrorSuccess) {
rapidjson::Document error_code;
error_code.SetInt(open_result);
result->Error(kClipboardError, "Unable to open clipboard", error_code);
return;
}
if (!clipboard->HasString()) {
result->Success(rapidjson::Document());
return;
}
std::variant<std::wstring, int> get_string_result = clipboard->GetString();
if (std::holds_alternative<int>(get_string_result)) {
rapidjson::Document error_code;
error_code.SetInt(std::get<int>(get_string_result));
result->Error(kClipboardError, "Unable to get clipboard data", error_code);
return;
}

rapidjson::Document document;
document.SetObject();
rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
document.AddMember(
rapidjson::Value(key.data(), allocator),
rapidjson::Value(
fml::WideStringToUtf8(std::get<std::wstring>(get_string_result)),
allocator),
allocator);
result->Success(document);
}

void PlatformHandler::GetHasStrings(
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
std::unique_ptr<ScopedClipboardInterface> clipboard =
scoped_clipboard_provider_();

bool hasStrings;
int open_result = clipboard->Open(std::get<HWND>(*view_->GetRenderTarget()));
if (open_result != kErrorSuccess) {
// Swallow errors of type ERROR_ACCESS_DENIED. These happen when the app is
// not in the foreground and GetHasStrings is irrelevant.
// See https://github.com/flutter/flutter/issues/95817.
if (open_result != kAccessDeniedErrorCode) {
rapidjson::Document error_code;
error_code.SetInt(open_result);
result->Error(kClipboardError, "Unable to open clipboard", error_code);
return;
}
hasStrings = false;
} else {
hasStrings = clipboard->HasString();
}

rapidjson::Document document;
document.SetObject();
rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
document.AddMember(rapidjson::Value(kValueKey, allocator),
rapidjson::Value(hasStrings), allocator);
result->Success(document);
}

void PlatformHandler::SetPlainText(
const std::string& text,
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
std::unique_ptr<ScopedClipboardInterface> clipboard =
scoped_clipboard_provider_();

int open_result = clipboard->Open(std::get<HWND>(*view_->GetRenderTarget()));
if (open_result != kErrorSuccess) {
rapidjson::Document error_code;
error_code.SetInt(open_result);
result->Error(kClipboardError, "Unable to open clipboard", error_code);
return;
}
int set_result = clipboard->SetString(fml::Utf8ToWideString(text));
if (set_result != kErrorSuccess) {
rapidjson::Document error_code;
error_code.SetInt(set_result);
result->Error(kClipboardError, "Unable to set clipboard data", error_code);
return;
}
result->Success();
}

void PlatformHandler::SystemSoundPlay(
const std::string& sound_type,
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
if (sound_type.compare(kSoundTypeAlert) == 0) {
MessageBeep(MB_OK);
result->Success();
} else {
result->NotImplemented();
}
}

void PlatformHandler::HandleMethodCall(
const MethodCall<rapidjson::Document>& method_call,
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
Expand Down
Loading

0 comments on commit eea2887

Please sign in to comment.