7
7
#include < windows.h>
8
8
9
9
#include < iostream>
10
+ #include < optional>
10
11
11
12
#include " flutter/shell/platform/common/cpp/json_method_codec.h"
13
+ #include " flutter/shell/platform/windows/string_conversion.h"
14
+ #include " flutter/shell/platform/windows/win32_flutter_window.h"
12
15
13
16
static constexpr char kChannelName [] = " flutter/platform" ;
14
17
@@ -18,11 +21,182 @@ static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData";
18
21
static constexpr char kTextPlainFormat [] = " text/plain" ;
19
22
static constexpr char kTextKey [] = " text" ;
20
23
21
- static constexpr char kUnknownClipboardFormatError [] =
22
- " Unknown clipboard format error" ;
24
+ static constexpr char kClipboardError [] = " Clipboard error" ;
25
+ static constexpr char kUnknownClipboardFormatMessage [] =
26
+ " Unknown clipboard format" ;
23
27
24
28
namespace flutter {
25
29
30
+ namespace {
31
+
32
+ // A scoped wrapper for GlobalAlloc/GlobalFree.
33
+ class ScopedGlobalMemory {
34
+ public:
35
+ // Allocates |bytes| bytes of global memory with the given flags.
36
+ ScopedGlobalMemory (unsigned int flags, size_t bytes) {
37
+ memory_ = ::GlobalAlloc (flags, bytes);
38
+ if (!memory_) {
39
+ std::cerr << " Unable to allocate global memory: " << ::GetLastError ();
40
+ }
41
+ }
42
+
43
+ ~ScopedGlobalMemory () {
44
+ if (memory_) {
45
+ if (::GlobalFree (memory_) != nullptr ) {
46
+ std::cerr << " Failed to free global allocation: " << ::GetLastError ();
47
+ }
48
+ }
49
+ }
50
+
51
+ // Prevent copying.
52
+ ScopedGlobalMemory (ScopedGlobalMemory const &) = delete ;
53
+ ScopedGlobalMemory& operator =(ScopedGlobalMemory const &) = delete ;
54
+
55
+ // Returns the memory pointer, which will be nullptr if allocation failed.
56
+ void * get () { return memory_; }
57
+
58
+ void * release () {
59
+ void * memory = memory_;
60
+ memory_ = nullptr ;
61
+ return memory;
62
+ }
63
+
64
+ private:
65
+ HGLOBAL memory_;
66
+ };
67
+
68
+ // A scoped wrapper for GlobalLock/GlobalUnlock.
69
+ class ScopedGlobalLock {
70
+ public:
71
+ // Attempts to acquire a global lock on |memory| for the life of this object.
72
+ ScopedGlobalLock (HGLOBAL memory) {
73
+ source_ = memory;
74
+ if (memory) {
75
+ locked_memory_ = ::GlobalLock (memory);
76
+ if (!locked_memory_) {
77
+ std::cerr << " Unable to acquire global lock: " << ::GetLastError ();
78
+ }
79
+ }
80
+ }
81
+
82
+ ~ScopedGlobalLock () {
83
+ if (locked_memory_) {
84
+ if (!::GlobalUnlock (source_)) {
85
+ DWORD error = ::GetLastError ();
86
+ if (error != NO_ERROR) {
87
+ std::cerr << " Unable to release global lock: " << ::GetLastError ();
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ // Prevent copying.
94
+ ScopedGlobalLock (ScopedGlobalLock const &) = delete ;
95
+ ScopedGlobalLock& operator =(ScopedGlobalLock const &) = delete ;
96
+
97
+ // Returns the locked memory pointer, which will be nullptr if acquiring the
98
+ // lock failed.
99
+ void * get () { return locked_memory_; }
100
+
101
+ private:
102
+ HGLOBAL source_;
103
+ void * locked_memory_;
104
+ };
105
+
106
+ // A Clipboard wrapper that automatically closes the clipboard when it goes out
107
+ // of scope.
108
+ class ScopedClipboard {
109
+ public:
110
+ ScopedClipboard ();
111
+ ~ScopedClipboard ();
112
+
113
+ // Prevent copying.
114
+ ScopedClipboard (ScopedClipboard const &) = delete ;
115
+ ScopedClipboard& operator =(ScopedClipboard const &) = delete ;
116
+
117
+ // Attempts to open the clipboard for the given window, returning true if
118
+ // successful.
119
+ bool Open (HWND window);
120
+
121
+ // Returns true if there is string data available to get.
122
+ bool HasString ();
123
+
124
+ // Returns string data from the clipboard.
125
+ //
126
+ // If getting a string fails, returns no value. Get error information with
127
+ // ::GetLastError().
128
+ //
129
+ // Open(...) must have succeeded to call this method.
130
+ std::optional<std::wstring> GetString ();
131
+
132
+ // Sets the string content of the clipboard, returning true on success.
133
+ //
134
+ // On failure, get error information with ::GetLastError().
135
+ //
136
+ // Open(...) must have succeeded to call this method.
137
+ bool SetString (const std::wstring string);
138
+
139
+ private:
140
+ bool opened_ = false ;
141
+ };
142
+
143
+ ScopedClipboard::ScopedClipboard () {}
144
+
145
+ ScopedClipboard::~ScopedClipboard () {
146
+ if (opened_) {
147
+ ::CloseClipboard ();
148
+ }
149
+ }
150
+
151
+ bool ScopedClipboard::Open (HWND window) {
152
+ opened_ = ::OpenClipboard (window);
153
+ return opened_;
154
+ }
155
+
156
+ bool ScopedClipboard::HasString () {
157
+ // Allow either plain text format, since getting data will auto-interpolate.
158
+ return ::IsClipboardFormatAvailable (CF_UNICODETEXT) ||
159
+ ::IsClipboardFormatAvailable (CF_TEXT);
160
+ }
161
+
162
+ std::optional<std::wstring> ScopedClipboard::GetString () {
163
+ assert (opened_);
164
+
165
+ HANDLE data = ::GetClipboardData (CF_UNICODETEXT);
166
+ if (data == nullptr ) {
167
+ return std::nullopt ;
168
+ }
169
+ ScopedGlobalLock locked_data (data);
170
+ if (!locked_data.get ()) {
171
+ return std::nullopt ;
172
+ }
173
+ return std::optional<std::wstring>(static_cast <wchar_t *>(locked_data.get ()));
174
+ }
175
+
176
+ bool ScopedClipboard::SetString (const std::wstring string) {
177
+ assert (opened_);
178
+ if (!::EmptyClipboard ()) {
179
+ return false ;
180
+ }
181
+ size_t null_terminated_byte_count =
182
+ sizeof (decltype (string)::traits_type::char_type) * (string.size () + 1 );
183
+ ScopedGlobalMemory destination_memory (GMEM_MOVEABLE,
184
+ null_terminated_byte_count);
185
+ ScopedGlobalLock locked_memory (destination_memory.get ());
186
+ if (!locked_memory.get ()) {
187
+ return false ;
188
+ }
189
+ memcpy (locked_memory.get (), string.c_str (), null_terminated_byte_count);
190
+ if (!::SetClipboardData (CF_UNICODETEXT, locked_memory.get ())) {
191
+ return false ;
192
+ }
193
+ // The clipboard now owns the global memory.
194
+ destination_memory.release ();
195
+ return true ;
196
+ }
197
+
198
+ } // namespace
199
+
26
200
PlatformHandler::PlatformHandler (flutter::BinaryMessenger* messenger,
27
201
Win32FlutterWindow* window)
28
202
: channel_(std::make_unique<flutter::MethodChannel<rapidjson::Document>>(
@@ -47,76 +221,65 @@ void PlatformHandler::HandleMethodCall(
47
221
const rapidjson::Value& format = method_call.arguments ()[0 ];
48
222
49
223
if (strcmp (format.GetString (), kTextPlainFormat ) != 0 ) {
50
- result->Error (kUnknownClipboardFormatError ,
51
- " Windows clipboard API only supports text." );
224
+ result->Error (kClipboardError , kUnknownClipboardFormatMessage );
52
225
return ;
53
226
}
54
227
55
- auto clipboardData = GetClipboardString () ;
56
-
57
- if (clipboardData. empty ()) {
58
- result-> Error ( kUnknownClipboardFormatError ,
59
- " Failed to retrieve clipboard data from win32 api. " );
228
+ ScopedClipboard clipboard ;
229
+ if (!clipboard. Open (window_-> GetWindowHandle ())) {
230
+ rapidjson::Document error_code;
231
+ error_code. SetInt (:: GetLastError ());
232
+ result-> Error ( kClipboardError , " Unable to open clipboard" , &error_code );
60
233
return ;
61
234
}
235
+ if (!clipboard.HasString ()) {
236
+ rapidjson::Document null;
237
+ result->Success (&null);
238
+ return ;
239
+ }
240
+ std::optional<std::wstring> clipboard_string = clipboard.GetString ();
241
+ if (!clipboard_string) {
242
+ rapidjson::Document error_code;
243
+ error_code.SetInt (::GetLastError ());
244
+ result->Error (kClipboardError , " Unable to get clipboard data" ,
245
+ &error_code);
246
+ return ;
247
+ }
248
+
62
249
rapidjson::Document document;
63
250
document.SetObject ();
64
251
rapidjson::Document::AllocatorType& allocator = document.GetAllocator ();
65
- document.AddMember (rapidjson::Value (kTextKey , allocator),
66
- rapidjson::Value (clipboardData, allocator), allocator);
252
+ document.AddMember (
253
+ rapidjson::Value (kTextKey , allocator),
254
+ rapidjson::Value (Utf8FromUtf16 (*clipboard_string), allocator),
255
+ allocator);
67
256
result->Success (&document);
68
-
69
257
} else if (method.compare (kSetClipboardDataMethod ) == 0 ) {
70
258
const rapidjson::Value& document = *method_call.arguments ();
71
259
rapidjson::Value::ConstMemberIterator itr = document.FindMember (kTextKey );
72
260
if (itr == document.MemberEnd ()) {
73
- result->Error (kUnknownClipboardFormatError ,
74
- " Missing text to store on clipboard." );
261
+ result->Error (kClipboardError , kUnknownClipboardFormatMessage );
262
+ return ;
263
+ }
264
+
265
+ ScopedClipboard clipboard;
266
+ if (!clipboard.Open (window_->GetWindowHandle ())) {
267
+ rapidjson::Document error_code;
268
+ error_code.SetInt (::GetLastError ());
269
+ result->Error (kClipboardError , " Unable to open clipboard" , &error_code);
270
+ return ;
271
+ }
272
+ if (!clipboard.SetString (Utf16FromUtf8 (itr->value .GetString ()))) {
273
+ rapidjson::Document error_code;
274
+ error_code.SetInt (::GetLastError ());
275
+ result->Error (kClipboardError , " Unable to set clipboard data" ,
276
+ &error_code);
75
277
return ;
76
278
}
77
- SetClipboardString (std::string (itr->value .GetString ()));
78
279
result->Success ();
79
280
} else {
80
281
result->NotImplemented ();
81
282
}
82
283
}
83
284
84
- std::string PlatformHandler::GetClipboardString () {
85
- if (!OpenClipboard (nullptr )) {
86
- return nullptr ;
87
- }
88
-
89
- HANDLE data = GetClipboardData (CF_TEXT);
90
- if (data == nullptr ) {
91
- CloseClipboard ();
92
- return nullptr ;
93
- }
94
-
95
- const char * clipboardData = static_cast <char *>(GlobalLock (data));
96
-
97
- if (clipboardData == nullptr ) {
98
- CloseClipboard ();
99
- return nullptr ;
100
- }
101
-
102
- auto result = std::string (clipboardData);
103
- GlobalUnlock (data);
104
- CloseClipboard ();
105
- return result;
106
- }
107
-
108
- void PlatformHandler::SetClipboardString (std::string data) {
109
- if (!OpenClipboard (nullptr )) {
110
- return ;
111
- }
112
-
113
- auto htext = GlobalAlloc (GMEM_MOVEABLE, data.size ());
114
-
115
- memcpy (GlobalLock (htext), data.c_str (), data.size ());
116
-
117
- SetClipboardData (CF_TEXT, htext);
118
-
119
- CloseClipboard ();
120
- }
121
-
122
285
} // namespace flutter
0 commit comments