Flutter Engine
 
Loading...
Searching...
No Matches
platform_handler.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <windows.h>
8
9#include <cstring>
10#include <optional>
11
12#include "flutter/fml/logging.h"
13#include "flutter/fml/macros.h"
18
19static constexpr char kChannelName[] = "flutter/platform";
20
21static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData";
22static constexpr char kHasStringsClipboardMethod[] = "Clipboard.hasStrings";
23static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData";
24static constexpr char kExitApplicationMethod[] = "System.exitApplication";
25static constexpr char kRequestAppExitMethod[] = "System.requestAppExit";
26static constexpr char kInitializationCompleteMethod[] =
27 "System.initializationComplete";
28static constexpr char kPlaySoundMethod[] = "SystemSound.play";
29
30static constexpr char kExitCodeKey[] = "exitCode";
31
32static constexpr char kExitTypeKey[] = "type";
33
34static constexpr char kExitResponseKey[] = "response";
35static constexpr char kExitResponseCancel[] = "cancel";
36static constexpr char kExitResponseExit[] = "exit";
37
38static constexpr char kTextPlainFormat[] = "text/plain";
39static constexpr char kTextKey[] = "text";
40static constexpr char kUnknownClipboardFormatMessage[] =
41 "Unknown clipboard format";
42
43static constexpr char kValueKey[] = "value";
44static constexpr int kAccessDeniedErrorCode = 5;
45static constexpr int kErrorSuccess = 0;
46
47static constexpr char kExitRequestError[] = "ExitApplication error";
48static constexpr char kInvalidExitRequestMessage[] =
49 "Invalid application exit request";
50
51namespace flutter {
52
53namespace {
54
55static const std::wstring kWindowClassName = L"FlutterPlatformHandler";
56
57// A scoped wrapper for GlobalAlloc/GlobalFree.
58class ScopedGlobalMemory {
59 public:
60 // Allocates |bytes| bytes of global memory with the given flags.
61 ScopedGlobalMemory(unsigned int flags, size_t bytes) {
62 memory_ = ::GlobalAlloc(flags, bytes);
63 if (!memory_) {
64 FML_LOG(ERROR) << "Unable to allocate global memory: "
65 << static_cast<int>(::GetLastError());
66 }
67 }
68
69 ~ScopedGlobalMemory() {
70 if (memory_) {
71 if (::GlobalFree(memory_) != nullptr) {
72 FML_LOG(ERROR) << "Failed to free global allocation: "
73 << static_cast<int>(::GetLastError());
74 }
75 }
76 }
77
78 // Returns the memory pointer, which will be nullptr if allocation failed.
79 void* get() { return memory_; }
80
81 void* release() {
82 void* memory = memory_;
83 memory_ = nullptr;
84 return memory;
85 }
86
87 private:
88 HGLOBAL memory_;
89
90 FML_DISALLOW_COPY_AND_ASSIGN(ScopedGlobalMemory);
91};
92
93// A scoped wrapper for GlobalLock/GlobalUnlock.
94class ScopedGlobalLock {
95 public:
96 // Attempts to acquire a global lock on |memory| for the life of this object.
97 ScopedGlobalLock(HGLOBAL memory) {
98 source_ = memory;
99 if (memory) {
100 locked_memory_ = ::GlobalLock(memory);
101 if (!locked_memory_) {
102 FML_LOG(ERROR) << "Unable to acquire global lock: " << ::GetLastError();
103 }
104 }
105 }
106
107 ~ScopedGlobalLock() {
108 if (locked_memory_) {
109 if (!::GlobalUnlock(source_)) {
111 if (error != NO_ERROR) {
112 FML_LOG(ERROR) << "Unable to release global lock: "
113 << ::GetLastError();
114 }
115 }
116 }
117 }
118
119 // Returns the locked memory pointer, which will be nullptr if acquiring the
120 // lock failed.
121 void* get() { return locked_memory_; }
122
123 private:
124 HGLOBAL source_;
125 void* locked_memory_;
126
127 FML_DISALLOW_COPY_AND_ASSIGN(ScopedGlobalLock);
128};
129
130// A Clipboard wrapper that automatically closes the clipboard when it goes out
131// of scope.
132class ScopedClipboard : public ScopedClipboardInterface {
133 public:
134 ScopedClipboard();
135 virtual ~ScopedClipboard();
136
137 int Open(HWND window) override;
138
139 bool HasString() override;
140
141 std::variant<std::wstring, int> GetString() override;
142
143 int SetString(const std::wstring string) override;
144
145 private:
146 bool opened_ = false;
147
148 FML_DISALLOW_COPY_AND_ASSIGN(ScopedClipboard);
149};
150
151ScopedClipboard::ScopedClipboard() {}
152
153ScopedClipboard::~ScopedClipboard() {
154 if (opened_) {
155 ::CloseClipboard();
156 }
157}
158
159int ScopedClipboard::Open(HWND window) {
160 opened_ = ::OpenClipboard(window);
161
162 if (!opened_) {
163 return ::GetLastError();
164 }
165
166 return kErrorSuccess;
167}
168
169bool ScopedClipboard::HasString() {
170 // Allow either plain text format, since getting data will auto-interpolate.
171 return ::IsClipboardFormatAvailable(CF_UNICODETEXT) ||
172 ::IsClipboardFormatAvailable(CF_TEXT);
173}
174
175std::variant<std::wstring, int> ScopedClipboard::GetString() {
176 FML_DCHECK(opened_) << "Called GetString when clipboard is closed";
177
178 HANDLE data = ::GetClipboardData(CF_UNICODETEXT);
179 if (data == nullptr) {
180 return static_cast<int>(::GetLastError());
181 }
182 ScopedGlobalLock locked_data(data);
183
184 if (!locked_data.get()) {
185 return static_cast<int>(::GetLastError());
186 }
187 return static_cast<wchar_t*>(locked_data.get());
188}
189
190int ScopedClipboard::SetString(const std::wstring string) {
191 FML_DCHECK(opened_) << "Called GetString when clipboard is closed";
192 if (!::EmptyClipboard()) {
193 return ::GetLastError();
194 }
195 size_t null_terminated_byte_count =
196 sizeof(decltype(string)::traits_type::char_type) * (string.size() + 1);
197 ScopedGlobalMemory destination_memory(GMEM_MOVEABLE,
198 null_terminated_byte_count);
199 ScopedGlobalLock locked_memory(destination_memory.get());
200 if (!locked_memory.get()) {
201 return ::GetLastError();
202 }
203 memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count);
204 if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) {
205 return ::GetLastError();
206 }
207 // The clipboard now owns the global memory.
208 destination_memory.release();
209 return kErrorSuccess;
210}
211
212} // namespace
213
214static AppExitType StringToAppExitType(const std::string& string) {
215 if (string.compare(PlatformHandler::kExitTypeRequired) == 0) {
217 } else if (string.compare(PlatformHandler::kExitTypeCancelable) == 0) {
219 }
220 FML_LOG(ERROR) << string << " is not recognized as a valid exit type.";
222}
223
225 BinaryMessenger* messenger,
227 std::optional<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
228 scoped_clipboard_provider)
229 : channel_(std::make_unique<MethodChannel<rapidjson::Document>>(
230 messenger,
232 &JsonMethodCodec::GetInstance())),
233 engine_(engine) {
234 channel_->SetMethodCallHandler(
235 [this](const MethodCall<rapidjson::Document>& call,
236 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
237 HandleMethodCall(call, std::move(result));
238 });
239 if (scoped_clipboard_provider.has_value()) {
240 scoped_clipboard_provider_ = scoped_clipboard_provider.value();
241 } else {
242 scoped_clipboard_provider_ = []() {
243 return std::make_unique<ScopedClipboard>();
244 };
245 }
246
247 WNDCLASS window_class = RegisterWindowClass();
248 window_handle_ =
249 CreateWindowEx(0, window_class.lpszClassName, L"", 0, 0, 0, 0, 0,
250 HWND_MESSAGE, nullptr, window_class.hInstance, nullptr);
251
252 if (window_handle_) {
253 SetWindowLongPtr(window_handle_, GWLP_USERDATA,
254 reinterpret_cast<LONG_PTR>(this));
255 } else {
256 auto error = GetLastError();
257 LPWSTR message = nullptr;
258 size_t size = FormatMessageW(
259 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
260 FORMAT_MESSAGE_IGNORE_INSERTS,
261 NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
262 reinterpret_cast<LPWSTR>(&message), 0, NULL);
263 OutputDebugString(message);
264 LocalFree(message);
265 }
266}
267
269 if (window_handle_) {
270 DestroyWindow(window_handle_);
271 window_handle_ = nullptr;
272 }
273 UnregisterClass(kWindowClassName.c_str(), nullptr);
274}
275
277 std::unique_ptr<MethodResult<rapidjson::Document>> result,
278 std::string_view key) {
279 std::unique_ptr<ScopedClipboardInterface> clipboard =
280 scoped_clipboard_provider_();
281
282 int open_result = clipboard->Open(window_handle_);
283 if (open_result != kErrorSuccess) {
284 rapidjson::Document error_code;
285 error_code.SetInt(open_result);
286 result->Error(kClipboardError, "Unable to open clipboard", error_code);
287 return;
288 }
289 if (!clipboard->HasString()) {
290 result->Success(rapidjson::Document());
291 return;
292 }
293 std::variant<std::wstring, int> get_string_result = clipboard->GetString();
294 if (std::holds_alternative<int>(get_string_result)) {
295 rapidjson::Document error_code;
296 error_code.SetInt(std::get<int>(get_string_result));
297 result->Error(kClipboardError, "Unable to get clipboard data", error_code);
298 return;
299 }
300
301 rapidjson::Document document;
302 document.SetObject();
303 rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
304 document.AddMember(
305 rapidjson::Value(key.data(), allocator),
306 rapidjson::Value(
307 fml::WideStringToUtf8(std::get<std::wstring>(get_string_result)),
308 allocator),
309 allocator);
310 result->Success(document);
311}
312
314 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
315 std::unique_ptr<ScopedClipboardInterface> clipboard =
316 scoped_clipboard_provider_();
317
318 bool hasStrings;
319 int open_result = clipboard->Open(window_handle_);
320 if (open_result != kErrorSuccess) {
321 // Swallow errors of type ERROR_ACCESS_DENIED. These happen when the app is
322 // not in the foreground and GetHasStrings is irrelevant.
323 // See https://github.com/flutter/flutter/issues/95817.
324 if (open_result != kAccessDeniedErrorCode) {
325 rapidjson::Document error_code;
326 error_code.SetInt(open_result);
327 result->Error(kClipboardError, "Unable to open clipboard", error_code);
328 return;
329 }
330 hasStrings = false;
331 } else {
332 hasStrings = clipboard->HasString();
333 }
334
335 rapidjson::Document document;
336 document.SetObject();
337 rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
338 document.AddMember(rapidjson::Value(kValueKey, allocator),
339 rapidjson::Value(hasStrings), allocator);
340 result->Success(document);
341}
342
344 const std::string& text,
345 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
346 std::unique_ptr<ScopedClipboardInterface> clipboard =
347 scoped_clipboard_provider_();
348
349 int open_result = clipboard->Open(window_handle_);
350 if (open_result != kErrorSuccess) {
351 rapidjson::Document error_code;
352 error_code.SetInt(open_result);
353 result->Error(kClipboardError, "Unable to open clipboard", error_code);
354 return;
355 }
356 int set_result = clipboard->SetString(fml::Utf8ToWideString(text));
357 if (set_result != kErrorSuccess) {
358 rapidjson::Document error_code;
359 error_code.SetInt(set_result);
360 result->Error(kClipboardError, "Unable to set clipboard data", error_code);
361 return;
362 }
363 result->Success();
364}
365
367 const std::string& sound_type,
368 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
369 if (sound_type.compare(kSoundTypeAlert) == 0) {
370 MessageBeep(MB_OK);
371 result->Success();
372 } else if (sound_type.compare(kSoundTypeClick) == 0) {
373 // No-op, as there is no system sound for key presses.
374 result->Success();
375 } else if (sound_type.compare(kSoundTypeTick) == 0) {
376 // No-op, as there is no system sound for ticks.
377 result->Success();
378 } else {
379 result->NotImplemented();
380 }
381}
382
384 AppExitType exit_type,
385 UINT exit_code,
386 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
387 rapidjson::Document result_doc;
388 result_doc.SetObject();
389 if (exit_type == AppExitType::required) {
390 QuitApplication(std::nullopt, std::nullopt, std::nullopt, exit_code);
391 result_doc.GetObjectW().AddMember(kExitResponseKey, kExitResponseExit,
392 result_doc.GetAllocator());
393 result->Success(result_doc);
394 } else {
395 RequestAppExit(std::nullopt, std::nullopt, std::nullopt, exit_type,
396 exit_code);
397 result_doc.GetObjectW().AddMember(kExitResponseKey, kExitResponseCancel,
398 result_doc.GetAllocator());
399 result->Success(result_doc);
400 }
401}
402
403// Indicates whether an exit request may be canceled by the framework.
404// These values must be kept in sync with ExitType in platform_handler.h
407
408void PlatformHandler::RequestAppExit(std::optional<HWND> hwnd,
409 std::optional<WPARAM> wparam,
410 std::optional<LPARAM> lparam,
411 AppExitType exit_type,
412 UINT exit_code) {
413 auto callback = std::make_unique<MethodResultFunctions<rapidjson::Document>>(
414 [this, exit_code, hwnd, wparam,
415 lparam](const rapidjson::Document* response) {
416 RequestAppExitSuccess(hwnd, wparam, lparam, response, exit_code);
417 },
418 nullptr, nullptr);
419 auto args = std::make_unique<rapidjson::Document>();
420 args->SetObject();
421 args->GetObjectW().AddMember(
422 kExitTypeKey, std::string(kExitTypeNames[static_cast<int>(exit_type)]),
423 args->GetAllocator());
424 channel_->InvokeMethod(kRequestAppExitMethod, std::move(args),
425 std::move(callback));
426}
427
428void PlatformHandler::RequestAppExitSuccess(std::optional<HWND> hwnd,
429 std::optional<WPARAM> wparam,
430 std::optional<LPARAM> lparam,
431 const rapidjson::Document* result,
432 UINT exit_code) {
433 rapidjson::Value::ConstMemberIterator itr =
434 result->FindMember(kExitResponseKey);
435 if (itr == result->MemberEnd() || !itr->value.IsString()) {
436 FML_LOG(ERROR) << "Application request response did not contain a valid "
437 "response value";
438 return;
439 }
440 const std::string& exit_type = itr->value.GetString();
441
442 if (exit_type.compare(kExitResponseExit) == 0) {
443 QuitApplication(hwnd, wparam, lparam, exit_code);
444 }
445}
446
447void PlatformHandler::QuitApplication(std::optional<HWND> hwnd,
448 std::optional<WPARAM> wparam,
449 std::optional<LPARAM> lparam,
450 UINT exit_code) {
451 engine_->OnQuit(hwnd, wparam, lparam, exit_code);
452}
453
454void PlatformHandler::HandleMethodCall(
456 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
457 const std::string& method = method_call.method_name();
458 if (method.compare(kExitApplicationMethod) == 0) {
459 const rapidjson::Value& arguments = method_call.arguments()[0];
460
461 rapidjson::Value::ConstMemberIterator itr =
462 arguments.FindMember(kExitTypeKey);
463 if (itr == arguments.MemberEnd() || !itr->value.IsString()) {
465 return;
466 }
467 const std::string& exit_type = itr->value.GetString();
468
469 itr = arguments.FindMember(kExitCodeKey);
470 if (itr == arguments.MemberEnd() || !itr->value.IsInt()) {
472 return;
473 }
474 UINT exit_code = arguments[kExitCodeKey].GetInt();
475
476 SystemExitApplication(StringToAppExitType(exit_type), exit_code,
477 std::move(result));
478 } else if (method.compare(kGetClipboardDataMethod) == 0) {
479 // Only one string argument is expected.
480 const rapidjson::Value& format = method_call.arguments()[0];
481
482 if (strcmp(format.GetString(), kTextPlainFormat) != 0) {
484 return;
485 }
486 GetPlainText(std::move(result), kTextKey);
487 } else if (method.compare(kHasStringsClipboardMethod) == 0) {
488 // Only one string argument is expected.
489 const rapidjson::Value& format = method_call.arguments()[0];
490
491 if (strcmp(format.GetString(), kTextPlainFormat) != 0) {
493 return;
494 }
495 GetHasStrings(std::move(result));
496 } else if (method.compare(kSetClipboardDataMethod) == 0) {
497 const rapidjson::Value& document = *method_call.arguments();
498 rapidjson::Value::ConstMemberIterator itr = document.FindMember(kTextKey);
499 if (itr == document.MemberEnd()) {
501 return;
502 }
503 if (!itr->value.IsString()) {
505 return;
506 }
507 SetPlainText(itr->value.GetString(), std::move(result));
508 } else if (method.compare(kPlaySoundMethod) == 0) {
509 // Only one string argument is expected.
510 const rapidjson::Value& sound_type = method_call.arguments()[0];
511
512 SystemSoundPlay(sound_type.GetString(), std::move(result));
513 } else if (method.compare(kInitializationCompleteMethod) == 0) {
514 // Deprecated but should not cause an error.
515 result->Success();
516 } else {
517 result->NotImplemented();
518 }
519}
520
521WNDCLASS PlatformHandler::RegisterWindowClass() {
522 WNDCLASS window_class{};
523 window_class.hCursor = nullptr;
524 window_class.lpszClassName = kWindowClassName.c_str();
525 window_class.style = 0;
526 window_class.cbClsExtra = 0;
527 window_class.cbWndExtra = 0;
528 window_class.hInstance = GetModuleHandle(nullptr);
529 window_class.hIcon = nullptr;
530 window_class.hbrBackground = 0;
531 window_class.lpszMenuName = nullptr;
532 window_class.lpfnWndProc = WndProc;
533 RegisterClass(&window_class);
534 return window_class;
535}
536
537LRESULT PlatformHandler::WndProc(HWND const window,
538 UINT const message,
539 WPARAM const wparam,
540 LPARAM const lparam) noexcept {
541 return DefWindowProc(window, message, wparam, lparam);
542}
543
544} // namespace flutter
static NSString *const kChannelName
void OnQuit(std::optional< HWND > hwnd, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, UINT exit_code)
virtual void QuitApplication(std::optional< HWND > hwnd, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, UINT exit_code)
static constexpr char kSoundTypeTick[]
PlatformHandler(flutter::BinaryMessenger *messenger, GLFWwindow *window)
virtual void RequestAppExitSuccess(std::optional< HWND > hwnd, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, const rapidjson::Document *result, UINT exit_code)
static constexpr char kClipboardError[]
static constexpr char kSoundTypeClick[]
virtual void GetHasStrings(std::unique_ptr< MethodResult< rapidjson::Document > > result)
static constexpr char kExitTypeCancelable[]
virtual void GetPlainText(std::unique_ptr< MethodResult< rapidjson::Document > > result, std::string_view key)
virtual void RequestAppExit(std::optional< HWND > hwnd, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, AppExitType exit_type, UINT exit_code)
static constexpr char kExitTypeRequired[]
virtual void SetPlainText(const std::string &text, std::unique_ptr< MethodResult< rapidjson::Document > > result)
virtual void SystemSoundPlay(const std::string &sound_type, std::unique_ptr< MethodResult< rapidjson::Document > > result)
virtual void SystemExitApplication(AppExitType exit_type, UINT exit_code, std::unique_ptr< MethodResult< rapidjson::Document > > result)
static constexpr char kSoundTypeAlert[]
GLFWwindow * window
Definition main.cc:60
FlutterEngine engine
Definition main.cc:84
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_BEGIN_DECLS G_MODULE_EXPORT FlMethodCall * method_call
G_BEGIN_DECLS GBytes * message
const uint8_t uint32_t uint32_t GError ** error
static constexpr char kValueKey[]
static constexpr char kInitializationCompleteMethod[]
static constexpr char kExitApplicationMethod[]
static constexpr char kExitResponseExit[]
static constexpr char kRequestAppExitMethod[]
static constexpr char kExitResponseKey[]
static constexpr char kPlaySoundMethod[]
static constexpr char kExitResponseCancel[]
static constexpr char kExitTypeKey[]
uint32_t uint32_t * format
FlutterDesktopBinaryReply callback
#define FML_LOG(severity)
Definition logging.h:101
#define FML_DCHECK(condition)
Definition logging.h:122
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition macros.h:27
static constexpr char kGetClipboardDataMethod[]
static constexpr char kChannelName[]
static constexpr char kTextPlainFormat[]
static constexpr char kTextKey[]
static constexpr char kSetClipboardDataMethod[]
std::u16string text
constexpr char kTextPlainFormat[]
Clipboard plain text format.
static NSString *const kTextKey
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
static constexpr const char * kExitTypeNames[]
static AppExitType StringToAppExitType(const std::string &string)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switch_defs.h:36
std::wstring Utf8ToWideString(const std::string_view str)
std::string WideStringToUtf8(const std::wstring_view str)
Definition ref_ptr.h:261
static constexpr char kValueKey[]
static constexpr char kInitializationCompleteMethod[]
static constexpr char kInvalidExitRequestMessage[]
static constexpr char kHasStringsClipboardMethod[]
static constexpr char kExitApplicationMethod[]
static constexpr char kExitResponseExit[]
static constexpr char kExitCodeKey[]
static constexpr int kErrorSuccess
static constexpr char kRequestAppExitMethod[]
static constexpr char kUnknownClipboardFormatMessage[]
static constexpr char kExitResponseKey[]
static constexpr char kPlaySoundMethod[]
static constexpr int kAccessDeniedErrorCode
static constexpr char kExitResponseCancel[]
static constexpr char kExitTypeKey[]
static constexpr char kExitRequestError[]
LONG_PTR LRESULT
unsigned int UINT
LONG_PTR LPARAM
__w64 long LONG_PTR
WINBASEAPI _Check_return_ _Post_equals_last_error_ DWORD WINAPI GetLastError(VOID)
UINT_PTR WPARAM
void * HANDLE
unsigned long DWORD