Flutter Engine
The Flutter Engine
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
5#include "flutter/shell/platform/windows/platform_handler.h"
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"
14#include "flutter/fml/platform/win/wstring_conversion.h"
15#include "flutter/shell/platform/common/client_wrapper/include/flutter/method_result_functions.h"
16#include "flutter/shell/platform/common/json_method_codec.h"
17#include "flutter/shell/platform/windows/flutter_windows_view.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
55// A scoped wrapper for GlobalAlloc/GlobalFree.
56class ScopedGlobalMemory {
57 public:
58 // Allocates |bytes| bytes of global memory with the given flags.
59 ScopedGlobalMemory(unsigned int flags, size_t bytes) {
60 memory_ = ::GlobalAlloc(flags, bytes);
61 if (!memory_) {
62 FML_LOG(ERROR) << "Unable to allocate global memory: "
63 << ::GetLastError();
64 }
65 }
66
67 ~ScopedGlobalMemory() {
68 if (memory_) {
69 if (::GlobalFree(memory_) != nullptr) {
70 FML_LOG(ERROR) << "Failed to free global allocation: "
71 << ::GetLastError();
72 }
73 }
74 }
75
76 // Returns the memory pointer, which will be nullptr if allocation failed.
77 void* get() { return memory_; }
78
79 void* release() {
80 void* memory = memory_;
81 memory_ = nullptr;
82 return memory;
83 }
84
85 private:
86 HGLOBAL memory_;
87
88 FML_DISALLOW_COPY_AND_ASSIGN(ScopedGlobalMemory);
89};
90
91// A scoped wrapper for GlobalLock/GlobalUnlock.
92class ScopedGlobalLock {
93 public:
94 // Attempts to acquire a global lock on |memory| for the life of this object.
95 ScopedGlobalLock(HGLOBAL memory) {
96 source_ = memory;
97 if (memory) {
98 locked_memory_ = ::GlobalLock(memory);
99 if (!locked_memory_) {
100 FML_LOG(ERROR) << "Unable to acquire global lock: " << ::GetLastError();
101 }
102 }
103 }
104
105 ~ScopedGlobalLock() {
106 if (locked_memory_) {
107 if (!::GlobalUnlock(source_)) {
109 if (error != NO_ERROR) {
110 FML_LOG(ERROR) << "Unable to release global lock: "
111 << ::GetLastError();
112 }
113 }
114 }
115 }
116
117 // Returns the locked memory pointer, which will be nullptr if acquiring the
118 // lock failed.
119 void* get() { return locked_memory_; }
120
121 private:
122 HGLOBAL source_;
123 void* locked_memory_;
124
125 FML_DISALLOW_COPY_AND_ASSIGN(ScopedGlobalLock);
126};
127
128// A Clipboard wrapper that automatically closes the clipboard when it goes out
129// of scope.
130class ScopedClipboard : public ScopedClipboardInterface {
131 public:
132 ScopedClipboard();
133 virtual ~ScopedClipboard();
134
135 int Open(HWND window) override;
136
137 bool HasString() override;
138
139 std::variant<std::wstring, int> GetString() override;
140
141 int SetString(const std::wstring string) override;
142
143 private:
144 bool opened_ = false;
145
146 FML_DISALLOW_COPY_AND_ASSIGN(ScopedClipboard);
147};
148
149ScopedClipboard::ScopedClipboard() {}
150
151ScopedClipboard::~ScopedClipboard() {
152 if (opened_) {
153 ::CloseClipboard();
154 }
155}
156
157int ScopedClipboard::Open(HWND window) {
158 opened_ = ::OpenClipboard(window);
159
160 if (!opened_) {
162 }
163
164 return kErrorSuccess;
165}
166
167bool ScopedClipboard::HasString() {
168 // Allow either plain text format, since getting data will auto-interpolate.
169 return ::IsClipboardFormatAvailable(CF_UNICODETEXT) ||
170 ::IsClipboardFormatAvailable(CF_TEXT);
171}
172
173std::variant<std::wstring, int> ScopedClipboard::GetString() {
174 FML_DCHECK(opened_) << "Called GetString when clipboard is closed";
175
176 HANDLE data = ::GetClipboardData(CF_UNICODETEXT);
177 if (data == nullptr) {
179 }
180 ScopedGlobalLock locked_data(data);
181
182 if (!locked_data.get()) {
184 }
185 return static_cast<wchar_t*>(locked_data.get());
186}
187
188int ScopedClipboard::SetString(const std::wstring string) {
189 FML_DCHECK(opened_) << "Called GetString when clipboard is closed";
190 if (!::EmptyClipboard()) {
192 }
193 size_t null_terminated_byte_count =
194 sizeof(decltype(string)::traits_type::char_type) * (string.size() + 1);
195 ScopedGlobalMemory destination_memory(GMEM_MOVEABLE,
196 null_terminated_byte_count);
197 ScopedGlobalLock locked_memory(destination_memory.get());
198 if (!locked_memory.get()) {
200 }
201 memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count);
202 if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) {
204 }
205 // The clipboard now owns the global memory.
206 destination_memory.release();
207 return kErrorSuccess;
208}
209
210} // namespace
211
212static AppExitType StringToAppExitType(const std::string& string) {
215 } else if (string.compare(PlatformHandler::kExitTypeCancelable) == 0) {
217 }
218 FML_LOG(ERROR) << string << " is not recognized as a valid exit type.";
220}
221
223 BinaryMessenger* messenger,
225 std::optional<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
226 scoped_clipboard_provider)
227 : channel_(std::make_unique<MethodChannel<rapidjson::Document>>(
228 messenger,
230 &JsonMethodCodec::GetInstance())),
231 engine_(engine) {
232 channel_->SetMethodCallHandler(
234 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
235 HandleMethodCall(call, std::move(result));
236 });
237 if (scoped_clipboard_provider.has_value()) {
238 scoped_clipboard_provider_ = scoped_clipboard_provider.value();
239 } else {
240 scoped_clipboard_provider_ = []() {
241 return std::make_unique<ScopedClipboard>();
242 };
243 }
244}
245
247
250 std::string_view key) {
251 // TODO(loicsharma): Remove implicit view assumption.
252 // https://github.com/flutter/flutter/issues/142845
253 const FlutterWindowsView* view = engine_->view(kImplicitViewId);
254 if (view == nullptr) {
255 result->Error(kClipboardError,
256 "Clipboard is not available in Windows headless mode");
257 return;
258 }
259
260 std::unique_ptr<ScopedClipboardInterface> clipboard =
261 scoped_clipboard_provider_();
262
263 int open_result = clipboard->Open(view->GetWindowHandle());
264 if (open_result != kErrorSuccess) {
265 rapidjson::Document error_code;
266 error_code.SetInt(open_result);
267 result->Error(kClipboardError, "Unable to open clipboard", error_code);
268 return;
269 }
270 if (!clipboard->HasString()) {
271 result->Success(rapidjson::Document());
272 return;
273 }
274 std::variant<std::wstring, int> get_string_result = clipboard->GetString();
275 if (std::holds_alternative<int>(get_string_result)) {
276 rapidjson::Document error_code;
277 error_code.SetInt(std::get<int>(get_string_result));
278 result->Error(kClipboardError, "Unable to get clipboard data", error_code);
279 return;
280 }
281
282 rapidjson::Document document;
283 document.SetObject();
284 rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
285 document.AddMember(
286 rapidjson::Value(key.data(), allocator),
287 rapidjson::Value(
288 fml::WideStringToUtf8(std::get<std::wstring>(get_string_result)),
289 allocator),
290 allocator);
291 result->Success(document);
292}
293
295 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
296 // TODO(loicsharma): Remove implicit view assumption.
297 // https://github.com/flutter/flutter/issues/142845
298 const FlutterWindowsView* view = engine_->view(kImplicitViewId);
299 if (view == nullptr) {
300 result->Error(kClipboardError,
301 "Clipboard is not available in Windows headless mode");
302 return;
303 }
304
305 std::unique_ptr<ScopedClipboardInterface> clipboard =
306 scoped_clipboard_provider_();
307
308 bool hasStrings;
309 int open_result = clipboard->Open(view->GetWindowHandle());
310 if (open_result != kErrorSuccess) {
311 // Swallow errors of type ERROR_ACCESS_DENIED. These happen when the app is
312 // not in the foreground and GetHasStrings is irrelevant.
313 // See https://github.com/flutter/flutter/issues/95817.
314 if (open_result != kAccessDeniedErrorCode) {
315 rapidjson::Document error_code;
316 error_code.SetInt(open_result);
317 result->Error(kClipboardError, "Unable to open clipboard", error_code);
318 return;
319 }
320 hasStrings = false;
321 } else {
322 hasStrings = clipboard->HasString();
323 }
324
325 rapidjson::Document document;
326 document.SetObject();
327 rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
328 document.AddMember(rapidjson::Value(kValueKey, allocator),
329 rapidjson::Value(hasStrings), allocator);
330 result->Success(document);
331}
332
334 const std::string& text,
335 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
336 // TODO(loicsharma): Remove implicit view assumption.
337 // https://github.com/flutter/flutter/issues/142845
338 const FlutterWindowsView* view = engine_->view(kImplicitViewId);
339 if (view == nullptr) {
340 result->Error(kClipboardError,
341 "Clipboard is not available in Windows headless mode");
342 return;
343 }
344
345 std::unique_ptr<ScopedClipboardInterface> clipboard =
346 scoped_clipboard_provider_();
347
348 int open_result = clipboard->Open(view->GetWindowHandle());
349 if (open_result != kErrorSuccess) {
350 rapidjson::Document error_code;
351 error_code.SetInt(open_result);
352 result->Error(kClipboardError, "Unable to open clipboard", error_code);
353 return;
354 }
355 int set_result = clipboard->SetString(fml::Utf8ToWideString(text));
356 if (set_result != kErrorSuccess) {
357 rapidjson::Document error_code;
358 error_code.SetInt(set_result);
359 result->Error(kClipboardError, "Unable to set clipboard data", error_code);
360 return;
361 }
362 result->Success();
363}
364
366 const std::string& sound_type,
367 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
368 if (sound_type.compare(kSoundTypeAlert) == 0) {
369 MessageBeep(MB_OK);
370 result->Success();
371 } else {
372 result->NotImplemented();
373 }
374}
375
377 AppExitType exit_type,
378 UINT exit_code,
379 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
380 rapidjson::Document result_doc;
381 result_doc.SetObject();
382 if (exit_type == AppExitType::required) {
383 QuitApplication(std::nullopt, std::nullopt, std::nullopt, exit_code);
384 result_doc.GetObjectW().AddMember(kExitResponseKey, kExitResponseExit,
385 result_doc.GetAllocator());
386 result->Success(result_doc);
387 } else {
388 RequestAppExit(std::nullopt, std::nullopt, std::nullopt, exit_type,
389 exit_code);
390 result_doc.GetObjectW().AddMember(kExitResponseKey, kExitResponseCancel,
391 result_doc.GetAllocator());
392 result->Success(result_doc);
393 }
394}
395
396// Indicates whether an exit request may be canceled by the framework.
397// These values must be kept in sync with ExitType in platform_handler.h
398static constexpr const char* kExitTypeNames[] = {
400
401void PlatformHandler::RequestAppExit(std::optional<HWND> hwnd,
402 std::optional<WPARAM> wparam,
403 std::optional<LPARAM> lparam,
404 AppExitType exit_type,
405 UINT exit_code) {
406 auto callback = std::make_unique<MethodResultFunctions<rapidjson::Document>>(
407 [this, exit_code, hwnd, wparam,
408 lparam](const rapidjson::Document* response) {
409 RequestAppExitSuccess(hwnd, wparam, lparam, response, exit_code);
410 },
411 nullptr, nullptr);
412 auto args = std::make_unique<rapidjson::Document>();
413 args->SetObject();
414 args->GetObjectW().AddMember(
415 kExitTypeKey, std::string(kExitTypeNames[static_cast<int>(exit_type)]),
416 args->GetAllocator());
417 channel_->InvokeMethod(kRequestAppExitMethod, std::move(args),
418 std::move(callback));
419}
420
421void PlatformHandler::RequestAppExitSuccess(std::optional<HWND> hwnd,
422 std::optional<WPARAM> wparam,
423 std::optional<LPARAM> lparam,
424 const rapidjson::Document* result,
425 UINT exit_code) {
426 rapidjson::Value::ConstMemberIterator itr =
427 result->FindMember(kExitResponseKey);
428 if (itr == result->MemberEnd() || !itr->value.IsString()) {
429 FML_LOG(ERROR) << "Application request response did not contain a valid "
430 "response value";
431 return;
432 }
433 const std::string& exit_type = itr->value.GetString();
434
435 if (exit_type.compare(kExitResponseExit) == 0) {
436 QuitApplication(hwnd, wparam, lparam, exit_code);
437 }
438}
439
440void PlatformHandler::QuitApplication(std::optional<HWND> hwnd,
441 std::optional<WPARAM> wparam,
442 std::optional<LPARAM> lparam,
443 UINT exit_code) {
444 engine_->OnQuit(hwnd, wparam, lparam, exit_code);
445}
446
447void PlatformHandler::HandleMethodCall(
449 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
450 const std::string& method = method_call.method_name();
451 if (method.compare(kExitApplicationMethod) == 0) {
452 const rapidjson::Value& arguments = method_call.arguments()[0];
453
454 rapidjson::Value::ConstMemberIterator itr =
455 arguments.FindMember(kExitTypeKey);
456 if (itr == arguments.MemberEnd() || !itr->value.IsString()) {
458 return;
459 }
460 const std::string& exit_type = itr->value.GetString();
461
462 itr = arguments.FindMember(kExitCodeKey);
463 if (itr == arguments.MemberEnd() || !itr->value.IsInt()) {
465 return;
466 }
467 UINT exit_code = arguments[kExitCodeKey].GetInt();
468
469 SystemExitApplication(StringToAppExitType(exit_type), exit_code,
470 std::move(result));
471 } else if (method.compare(kGetClipboardDataMethod) == 0) {
472 // Only one string argument is expected.
473 const rapidjson::Value& format = method_call.arguments()[0];
474
475 if (strcmp(format.GetString(), kTextPlainFormat) != 0) {
477 return;
478 }
479 GetPlainText(std::move(result), kTextKey);
480 } else if (method.compare(kHasStringsClipboardMethod) == 0) {
481 // Only one string argument is expected.
482 const rapidjson::Value& format = method_call.arguments()[0];
483
484 if (strcmp(format.GetString(), kTextPlainFormat) != 0) {
486 return;
487 }
488 GetHasStrings(std::move(result));
489 } else if (method.compare(kSetClipboardDataMethod) == 0) {
490 const rapidjson::Value& document = *method_call.arguments();
491 rapidjson::Value::ConstMemberIterator itr = document.FindMember(kTextKey);
492 if (itr == document.MemberEnd()) {
494 return;
495 }
496 if (!itr->value.IsString()) {
498 return;
499 }
500 SetPlainText(itr->value.GetString(), std::move(result));
501 } else if (method.compare(kPlaySoundMethod) == 0) {
502 // Only one string argument is expected.
503 const rapidjson::Value& sound_type = method_call.arguments()[0];
504
505 SystemSoundPlay(sound_type.GetString(), std::move(result));
506 } else if (method.compare(kInitializationCompleteMethod) == 0) {
507 // Deprecated but should not cause an error.
508 result->Success();
509 } else {
510 result->NotImplemented();
511 }
512}
513
514} // namespace flutter
FlutterWindowsView * view(FlutterViewId view_id) const
void OnQuit(std::optional< HWND > hwnd, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, UINT exit_code)
virtual HWND GetWindowHandle() const
virtual void QuitApplication(std::optional< HWND > hwnd, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, UINT exit_code)
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[]
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:45
FlutterEngine engine
Definition: main.cc:68
FlutterSemanticsFlag flags
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
G_BEGIN_DECLS G_MODULE_EXPORT FlMethodCall * method_call
const uint8_t uint32_t uint32_t GError ** error
GAsyncResult * result
#define FML_LOG(severity)
Definition: logging.h:82
#define FML_DCHECK(condition)
Definition: logging.h:103
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition: macros.h:27
Dart_NativeFunction function
Definition: fuchsia.cc:51
std::u16string text
def call(args)
Definition: dom.py:159
static constexpr const char * kExitTypeNames[]
static AppExitType StringToAppExitType(const std::string &string)
constexpr FlutterViewId kImplicitViewId
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: switches.h:41
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets Path to the Flutter assets directory enable service port Allow the VM service to fallback to automatic port selection if binding to a specified port fails trace Trace early application lifecycle Automatically switches to an endless trace buffer trace skia Filters out all Skia trace event categories except those that are specified in this comma separated list dump skp on shader Automatically dump the skp that triggers new shader compilations This is useful for writing custom ShaderWarmUp to reduce jank By this is not enabled to reduce the overhead purge persistent Remove all existing persistent cache This is mainly for debugging purposes such as reproducing the shader compilation jank trace to Write the timeline trace to a file at the specified path The file will be in Perfetto s proto format
Definition: switches.h:203
std::wstring Utf8ToWideString(const std::string_view str)
std::string WideStringToUtf8(const std::wstring_view str)
const myers::Point & get(const myers::Segment &)
Definition: ref_ptr.h:256
int compare(const void *untyped_lhs, const void *untyped_rhs)
Definition: skdiff.h:161
#define ERROR(message)
Definition: elf_loader.cc:260
static constexpr char kValueKey[]
static constexpr char kInitializationCompleteMethod[]
static constexpr char kInvalidExitRequestMessage[]
static constexpr char kGetClipboardDataMethod[]
static constexpr char kChannelName[]
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 kTextPlainFormat[]
static constexpr char kTextKey[]
static constexpr char kSetClipboardDataMethod[]
static constexpr char kExitRequestError[]
unsigned int UINT
Definition: windows_types.h:32
WINBASEAPI _Check_return_ _Post_equals_last_error_ DWORD WINAPI GetLastError(VOID)
void * HANDLE
Definition: windows_types.h:36
unsigned long DWORD
Definition: windows_types.h:22