// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter/shell/platform/windows/platform_handler_win32.h" #include #include #include #include #include "flutter/shell/platform/windows/flutter_windows_view.h" #include "flutter/shell/platform/windows/string_conversion.h" namespace flutter { 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: ScopedClipboard(); ~ScopedClipboard(); // Prevent copying. ScopedClipboard(ScopedClipboard const&) = delete; ScopedClipboard& operator=(ScopedClipboard const&) = delete; // Attempts to open the clipboard for the given window, returning true if // successful. bool Open(HWND window); // Returns true if there is string data available to get. bool HasString(); // Returns string data from the clipboard. // // If getting a string fails, returns no value. Get error information with // ::GetLastError(). // // Open(...) must have succeeded to call this method. std::optional GetString(); // Sets the string content of the clipboard, returning true on success. // // On failure, get error information with ::GetLastError(). // // Open(...) must have succeeded to call this method. bool SetString(const std::wstring string); private: bool opened_ = false; }; ScopedClipboard::ScopedClipboard() {} ScopedClipboard::~ScopedClipboard() { if (opened_) { ::CloseClipboard(); } } bool ScopedClipboard::Open(HWND window) { opened_ = ::OpenClipboard(window); return opened_; } bool ScopedClipboard::HasString() { // Allow either plain text format, since getting data will auto-interpolate. return ::IsClipboardFormatAvailable(CF_UNICODETEXT) || ::IsClipboardFormatAvailable(CF_TEXT); } std::optional ScopedClipboard::GetString() { assert(opened_); HANDLE data = ::GetClipboardData(CF_UNICODETEXT); if (data == nullptr) { return std::nullopt; } ScopedGlobalLock locked_data(data); if (!locked_data.get()) { return std::nullopt; } return std::optional(static_cast(locked_data.get())); } bool ScopedClipboard::SetString(const std::wstring string) { assert(opened_); if (!::EmptyClipboard()) { return false; } 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 false; } memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count); if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) { return false; } // The clipboard now owns the global memory. destination_memory.release(); return true; } } // namespace // static std::unique_ptr PlatformHandler::Create( BinaryMessenger* messenger, FlutterWindowsView* view) { return std::make_unique(messenger, view); } PlatformHandlerWin32::PlatformHandlerWin32(BinaryMessenger* messenger, FlutterWindowsView* view) : PlatformHandler(messenger), view_(view) {} PlatformHandlerWin32::~PlatformHandlerWin32() = default; void PlatformHandlerWin32::GetPlainText( std::unique_ptr> result, std::string_view key) { ScopedClipboard clipboard; if (!clipboard.Open(std::get(*view_->GetRenderTarget()))) { rapidjson::Document error_code; error_code.SetInt(::GetLastError()); result->Error(kClipboardError, "Unable to open clipboard", error_code); return; } if (!clipboard.HasString()) { result->Success(rapidjson::Document()); return; } std::optional clipboard_string = clipboard.GetString(); if (!clipboard_string) { rapidjson::Document error_code; error_code.SetInt(::GetLastError()); 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(Utf8FromUtf16(*clipboard_string), allocator), allocator); result->Success(document); } void PlatformHandlerWin32::SetPlainText( const std::string& text, std::unique_ptr> result) { ScopedClipboard clipboard; if (!clipboard.Open(std::get(*view_->GetRenderTarget()))) { rapidjson::Document error_code; error_code.SetInt(::GetLastError()); result->Error(kClipboardError, "Unable to open clipboard", error_code); return; } if (!clipboard.SetString(Utf16FromUtf8(text))) { rapidjson::Document error_code; error_code.SetInt(::GetLastError()); result->Error(kClipboardError, "Unable to set clipboard data", error_code); return; } result->Success(); } } // namespace flutter