Listen to WM_CLOSE message on Windows to allow framework to cancel exit (flutter/engine#40493)

* Intercept WM_CLOSE

* Messy but framework is in place

* Test exit and cancel

* Try to test for windows

* Check for parent HWND

* Move string to PlatformHandler class

* Rename lifecycle manageR

* Change condition for headless

* Move window proc to lambda

* Formatting and licenses

* Encode JSON dart values

* Clean up lifecycle

* PR feedback

* Update shell/platform/windows/platform_handler.h

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/windows/windows_lifecycle_manager.cc

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/windows/windows_lifecycle_manager.cc

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Static cast enum to int

* Formatting

* Update shell/platform/windows/testing/engine_modifier.h

Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>

* Update shell/platform/windows/windows_lifecycle_manager.cc

Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>

* Update shell/platform/windows/platform_handler.cc

Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>

* Update unit tests

* PR Feedback

* PR Feedback

* Constexpr

* Formatting

* Wparam

---------

Co-authored-by: Chris Bracken <chris@bracken.jp>
Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>
This commit is contained in:
yaakovschectman 2023-03-24 16:44:42 +00:00 committed by GitHub
parent 1a76a2115d
commit d7059df4eb
12 changed files with 427 additions and 29 deletions

View File

@ -3087,6 +3087,8 @@ ORIGIN: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.
ORIGIN: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/window_state.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/windows_proc_table.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/windows_proc_table.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/windows_registry.cc + ../../../flutter/LICENSE
@ -5646,6 +5648,8 @@ FILE: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.h
FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.cc
FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.h
FILE: ../../../flutter/shell/platform/windows/window_state.h
FILE: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.cc
FILE: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.h
FILE: ../../../flutter/shell/platform/windows/windows_proc_table.cc
FILE: ../../../flutter/shell/platform/windows/windows_proc_table.h
FILE: ../../../flutter/shell/platform/windows/windows_registry.cc

View File

@ -103,6 +103,8 @@ source_set("flutter_windows_source") {
"window_proc_delegate_manager.cc",
"window_proc_delegate_manager.h",
"window_state.h",
"windows_lifecycle_manager.cc",
"windows_lifecycle_manager.h",
"windows_proc_table.cc",
"windows_proc_table.h",
"windows_registry.cc",

View File

@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:io' as io;
import 'dart:typed_data' show ByteData, Uint8List;
import 'dart:ui' as ui;
import 'dart:convert';
// Signals a waiting latch in the native test.
@pragma('vm:external-name', 'Signal')
@ -86,6 +87,66 @@ void alertPlatformChannel() async {
ui.PlatformDispatcher.instance.sendPlatformMessage('flutter/accessibility', byteData, (ByteData? _){});
}
@pragma('vm:entry-point')
void exitTestExit() async {
final Completer<ByteData?> closed = Completer<ByteData?>();
ui.channelBuffers.setListener('flutter/platform', (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
final String jsonString = json.encode(<Map<String, String>>[{'response': 'exit'}]);
final ByteData responseData = ByteData.sublistView(Uint8List.fromList(utf8.encode(jsonString)));
callback(responseData);
closed.complete(data);
});
await closed.future;
// From here down, nothing should be called, because the application will have already been closed.
final Completer<ByteData?> exited = Completer<ByteData?>();
final String jsonString = json.encode(<String, dynamic>{
'method': 'System.exitApplication',
'args': <String, dynamic>{
'type': 'required', 'exitCode': 0
}
});
ui.PlatformDispatcher.instance.sendPlatformMessage(
'flutter/platform',
ByteData.sublistView(
Uint8List.fromList(utf8.encode(jsonString))
),
(ByteData? reply) {
exited.complete(reply);
});
await exited.future;
}
@pragma('vm:entry-point')
void exitTestCancel() async {
final Completer<ByteData?> closed = Completer<ByteData?>();
ui.channelBuffers.setListener('flutter/platform', (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
final String jsonString = json.encode(<Map<String, String>>[{'response': 'cancel'}]);
final ByteData responseData = ByteData.sublistView(Uint8List.fromList(utf8.encode(jsonString)));
callback(responseData);
closed.complete(data);
});
await closed.future;
// Because the request was canceled, the below shall execute.
final Completer<ByteData?> exited = Completer<ByteData?>();
final String jsonString = json.encode(<String, dynamic>{
'method': 'System.exitApplication',
'args': <String, dynamic>{
'type': 'required', 'exitCode': 0
}
});
ui.PlatformDispatcher.instance.sendPlatformMessage(
'flutter/platform',
ByteData.sublistView(
Uint8List.fromList(utf8.encode(jsonString))
),
(ByteData? reply) {
exited.complete(reply);
});
await exited.future;
}
@pragma('vm:entry-point')
void customEntrypoint() {}

View File

@ -161,7 +161,8 @@ FlutterWindowsEngine::FlutterWindowsEngine(
std::unique_ptr<WindowsRegistry> registry)
: project_(std::make_unique<FlutterProjectBundle>(project)),
aot_data_(nullptr, nullptr),
windows_registry_(std::move(registry)) {
windows_registry_(std::move(registry)),
lifecycle_manager_(std::make_unique<WindowsLifecycleManager>(this)) {
embedder_api_.struct_size = sizeof(FlutterEngineProcTable);
FlutterEngineGetProcAddresses(&embedder_api_);
@ -203,6 +204,17 @@ FlutterWindowsEngine::FlutterWindowsEngine(
std::make_unique<FlutterWindowsTextureRegistrar>(this, gl_procs_);
surface_manager_ = AngleSurfaceManager::Create();
window_proc_delegate_manager_ = std::make_unique<WindowProcDelegateManager>();
window_proc_delegate_manager_->RegisterTopLevelWindowProcDelegate(
[](HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar, void* user_data,
LRESULT* result) {
BASE_DCHECK(user_data);
FlutterWindowsEngine* that =
static_cast<FlutterWindowsEngine*>(user_data);
BASE_DCHECK(that->lifecycle_manager_);
return that->lifecycle_manager_->WindowProc(hwnd, msg, wpar, lpar,
result);
},
static_cast<void*>(this));
// Set up internal channels.
// TODO: Replace this with an embedder.h API. See
@ -751,4 +763,13 @@ void FlutterWindowsEngine::HandleAccessibilityMessage(
reinterpret_cast<const uint8_t*>(""), 0);
}
void FlutterWindowsEngine::RequestApplicationQuit(ExitType exit_type,
UINT exit_code) {
platform_handler_->RequestAppExit(exit_type, exit_code);
}
void FlutterWindowsEngine::OnQuit(UINT exit_code) {
lifecycle_manager_->Quit(exit_code);
}
} // namespace flutter

View File

@ -35,6 +35,7 @@
#include "flutter/shell/platform/windows/text_input_plugin.h"
#include "flutter/shell/platform/windows/window_proc_delegate_manager.h"
#include "flutter/shell/platform/windows/window_state.h"
#include "flutter/shell/platform/windows/windows_lifecycle_manager.h"
#include "flutter/shell/platform/windows/windows_registry.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
@ -254,6 +255,12 @@ class FlutterWindowsEngine {
// Updates accessibility, e.g. switch to high contrast mode
void UpdateAccessibilityFeatures(FlutterAccessibilityFeature flags);
// Called when the application quits in response to a quit request.
void OnQuit(UINT exit_code);
// Called when a WM_CLOSE message is received.
void RequestApplicationQuit(ExitType exit_type, UINT exit_code);
protected:
// Creates the keyboard key handler.
//
@ -395,6 +402,9 @@ class FlutterWindowsEngine {
// Wrapper providing Windows registry access.
std::unique_ptr<WindowsRegistry> windows_registry_;
// Handler for top level window messages.
std::unique_ptr<WindowsLifecycleManager> lifecycle_manager_;
FML_DISALLOW_COPY_AND_ASSIGN(FlutterWindowsEngine);
};

View File

@ -618,5 +618,92 @@ TEST_F(FlutterWindowsEngineTest, AlertPlatformMessage) {
}
}
class MockWindowsLifecycleManager : public WindowsLifecycleManager {
public:
MockWindowsLifecycleManager(FlutterWindowsEngine* engine)
: WindowsLifecycleManager(engine) {}
virtual ~MockWindowsLifecycleManager() {}
MOCK_CONST_METHOD1(Quit, void(UINT));
};
TEST_F(FlutterWindowsEngineTest, TestExit) {
FlutterWindowsEngineBuilder builder{GetContext()};
builder.SetDartEntrypoint("exitTestExit");
bool finished = false;
bool did_call = false;
std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
EngineModifier modifier(engine.get());
modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
ON_CALL(*handler, Quit).WillByDefault([&finished](UINT exit_code) {
finished = exit_code == 0;
});
EXPECT_CALL(*handler, Quit).Times(1);
modifier.SetLifecycleManager(std::move(handler));
auto binary_messenger =
std::make_unique<BinaryMessengerImpl>(engine->messenger());
binary_messenger->SetMessageHandler(
"flutter/platform", [&did_call](const uint8_t* message,
size_t message_size, BinaryReply reply) {
did_call = true;
char response[] = "";
reply(reinterpret_cast<uint8_t*>(response), 0);
});
engine->Run();
engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
0);
while (!finished) {
engine->task_runner()->ProcessTasks();
}
EXPECT_FALSE(did_call);
}
TEST_F(FlutterWindowsEngineTest, TestExitCancel) {
FlutterWindowsEngineBuilder builder{GetContext()};
builder.SetDartEntrypoint("exitTestCancel");
bool finished = false;
bool did_call = false;
std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
EngineModifier modifier(engine.get());
modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
ON_CALL(*handler, Quit).WillByDefault([&finished](int64_t exit_code) {
finished = true;
});
EXPECT_CALL(*handler, Quit).Times(0);
modifier.SetLifecycleManager(std::move(handler));
auto binary_messenger =
std::make_unique<BinaryMessengerImpl>(engine->messenger());
binary_messenger->SetMessageHandler(
"flutter/platform", [&did_call](const uint8_t* message,
size_t message_size, BinaryReply reply) {
did_call = true;
char response[] = "";
reply(reinterpret_cast<uint8_t*>(response), 0);
});
engine->Run();
engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
0);
while (!did_call) {
engine->task_runner()->ProcessTasks();
}
EXPECT_FALSE(finished);
}
} // namespace testing
} // namespace flutter

View File

@ -28,8 +28,6 @@ static constexpr char kPlaySoundMethod[] = "SystemSound.play";
static constexpr char kExitCodeKey[] = "exitCode";
static constexpr char kExitTypeKey[] = "type";
static constexpr char kExitTypeCancelable[] = "cancelable";
static constexpr char kExitTypeRequired[] = "required";
static constexpr char kExitResponseKey[] = "response";
static constexpr char kExitResponseCancel[] = "cancel";
@ -44,6 +42,10 @@ static constexpr char kValueKey[] = "value";
static constexpr int kAccessDeniedErrorCode = 5;
static constexpr int kErrorSuccess = 0;
static constexpr char kExitRequestError[] = "ExitApplication error";
static constexpr char kInvalidExitRequestMessage[] =
"Invalid application exit request";
namespace flutter {
namespace {
@ -205,6 +207,16 @@ int ScopedClipboard::SetString(const std::wstring string) {
} // namespace
static ExitType StringToExitType(const std::string& string) {
if (string.compare(PlatformHandler::kExitTypeRequired) == 0) {
return ExitType::required;
} else if (string.compare(PlatformHandler::kExitTypeCancelable) == 0) {
return ExitType::cancelable;
}
FML_LOG(ERROR) << string << " is not recognized as a valid exit type.";
return ExitType::required;
}
PlatformHandler::PlatformHandler(
BinaryMessenger* messenger,
FlutterWindowsEngine* engine,
@ -354,12 +366,12 @@ void PlatformHandler::SystemSoundPlay(
}
void PlatformHandler::SystemExitApplication(
const std::string& exit_type,
int64_t exit_code,
ExitType exit_type,
UINT exit_code,
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
rapidjson::Document result_doc;
result_doc.SetObject();
if (exit_type.compare(kExitTypeRequired) == 0) {
if (exit_type == ExitType::required) {
QuitApplication(exit_code);
result_doc.GetObjectW().AddMember(kExitResponseKey, kExitResponseExit,
result_doc.GetAllocator());
@ -372,8 +384,12 @@ void PlatformHandler::SystemExitApplication(
}
}
void PlatformHandler::RequestAppExit(const std::string& exit_type,
int64_t exit_code) {
// Indicates whether an exit request may be canceled by the framework.
// These values must be kept in sync with ExitType in platform_handler.h
static constexpr const char* kExitTypeNames[] = {
PlatformHandler::kExitTypeRequired, PlatformHandler::kExitTypeCancelable};
void PlatformHandler::RequestAppExit(ExitType exit_type, UINT exit_code) {
auto callback = std::make_unique<MethodResultFunctions<rapidjson::Document>>(
[this, exit_code](const rapidjson::Document* response) {
RequestAppExitSuccess(response, exit_code);
@ -381,21 +397,31 @@ void PlatformHandler::RequestAppExit(const std::string& exit_type,
nullptr, nullptr);
auto args = std::make_unique<rapidjson::Document>();
args->SetObject();
args->GetObjectW().AddMember(kExitTypeKey, exit_type, args->GetAllocator());
args->GetObjectW().AddMember(
kExitTypeKey, std::string(kExitTypeNames[static_cast<int>(exit_type)]),
args->GetAllocator());
channel_->InvokeMethod(kRequestAppExitMethod, std::move(args),
std::move(callback));
}
void PlatformHandler::RequestAppExitSuccess(const rapidjson::Document* result,
int64_t exit_code) {
const std::string& exit_type = result[0][kExitResponseKey].GetString();
UINT exit_code) {
rapidjson::Value::ConstMemberIterator itr =
result->FindMember(kExitResponseKey);
if (itr == result->MemberEnd() || !itr->value.IsString()) {
FML_LOG(ERROR) << "Application request response did not contain a valid "
"response value";
return;
}
const std::string& exit_type = itr->value.GetString();
if (exit_type.compare(kExitResponseExit) == 0) {
QuitApplication(exit_code);
}
}
void PlatformHandler::QuitApplication(int64_t exit_code) {
PostQuitMessage(exit_code);
void PlatformHandler::QuitApplication(UINT exit_code) {
engine_->OnQuit(exit_code);
}
void PlatformHandler::HandleMethodCall(
@ -404,9 +430,24 @@ void PlatformHandler::HandleMethodCall(
const std::string& method = method_call.method_name();
if (method.compare(kExitApplicationMethod) == 0) {
const rapidjson::Value& arguments = method_call.arguments()[0];
const std::string& exit_type = arguments[kExitTypeKey].GetString();
int64_t exit_code = arguments[kExitCodeKey].GetInt64();
SystemExitApplication(exit_type, exit_code, std::move(result));
rapidjson::Value::ConstMemberIterator itr =
arguments.FindMember(kExitTypeKey);
if (itr == arguments.MemberEnd() || !itr->value.IsString()) {
result->Error(kExitRequestError, kInvalidExitRequestMessage);
return;
}
const std::string& exit_type = itr->value.GetString();
itr = arguments.FindMember(kExitCodeKey);
if (itr == arguments.MemberEnd() || !itr->value.IsInt()) {
result->Error(kExitRequestError, kInvalidExitRequestMessage);
return;
}
UINT exit_code = arguments[kExitCodeKey].GetInt();
SystemExitApplication(StringToExitType(exit_type), exit_code,
std::move(result));
} else if (method.compare(kGetClipboardDataMethod) == 0) {
// Only one string argument is expected.
const rapidjson::Value& format = method_call.arguments()[0];

View File

@ -22,6 +22,13 @@ namespace flutter {
class FlutterWindowsEngine;
class ScopedClipboardInterface;
// Indicates whether an exit request may be canceled by the framework.
// These values must be kept in sync with kExitTypeNames in platform_handler.cc
enum class ExitType {
required,
cancelable,
};
// Handler for internal system channels.
class PlatformHandler {
public:
@ -33,6 +40,14 @@ class PlatformHandler {
virtual ~PlatformHandler();
// String values used for encoding/decoding exit requests.
static constexpr char kExitTypeCancelable[] = "cancelable";
static constexpr char kExitTypeRequired[] = "required";
// Send a request to the framework to test if a cancelable exit request
// should be canceled or honored.
virtual void RequestAppExit(ExitType exit_type, UINT exit_code);
protected:
// Gets plain text from the clipboard and provides it to |result| as the
// value in a dictionary with the given |key|.
@ -57,21 +72,17 @@ class PlatformHandler {
// Handle a request from the framework to exit the application.
virtual void SystemExitApplication(
const std::string& exit_type,
int64_t exit_code,
ExitType exit_type,
UINT exit_code,
std::unique_ptr<MethodResult<rapidjson::Document>> result);
// Actually quit the application with the provided exit code.
virtual void QuitApplication(int64_t exit_code);
// Send a request to the framework to test if a cancelable exit request
// should be canceled or honored.
virtual void RequestAppExit(const std::string& exit_type, int64_t exit_code);
virtual void QuitApplication(UINT exit_code);
// Callback from when the cancelable exit request response request is
// answered by the framework.
virtual void RequestAppExitSuccess(const rapidjson::Document* result,
int64_t exit_code);
UINT exit_code);
// A error type to use for error responses.
static constexpr char kClipboardError[] = "Clipboard error";

View File

@ -82,7 +82,7 @@ class MockPlatformHandler : public PlatformHandler {
void(const std::string&,
std::unique_ptr<MethodResult<rapidjson::Document>>));
MOCK_METHOD1(QuitApplication, void(int64_t exit_code));
MOCK_METHOD1(QuitApplication, void(UINT exit_code));
private:
FML_DISALLOW_COPY_AND_ASSIGN(MockPlatformHandler);
@ -483,7 +483,7 @@ TEST_F(PlatformHandlerTest, PlaySystemSound) {
TEST_F(PlatformHandlerTest, SystemExitApplicationRequired) {
use_headless_engine();
int exit_code = -1;
UINT exit_code = 0;
TestBinaryMessenger messenger([](const std::string& channel,
const uint8_t* message, size_t size,
@ -491,7 +491,7 @@ TEST_F(PlatformHandlerTest, SystemExitApplicationRequired) {
MockPlatformHandler platform_handler(&messenger, engine());
ON_CALL(platform_handler, QuitApplication)
.WillByDefault([&exit_code](int ec) { exit_code = ec; });
.WillByDefault([&exit_code](UINT ec) { exit_code = ec; });
EXPECT_CALL(platform_handler, QuitApplication).Times(1);
std::string result = SimulatePlatformMessage(
@ -524,7 +524,7 @@ TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableCancel) {
TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableExit) {
use_headless_engine();
bool called_cancel = false;
int exit_code = -1;
UINT exit_code = 0;
TestBinaryMessenger messenger(
[&called_cancel](const std::string& channel, const uint8_t* message,
@ -536,7 +536,7 @@ TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableExit) {
MockPlatformHandler platform_handler(&messenger, engine());
ON_CALL(platform_handler, QuitApplication)
.WillByDefault([&exit_code](int ec) { exit_code = ec; });
.WillByDefault([&exit_code](UINT ec) { exit_code = ec; });
EXPECT_CALL(platform_handler, QuitApplication).Times(1);
std::string result = SimulatePlatformMessage(

View File

@ -66,6 +66,10 @@ class EngineModifier {
// restart. This resets the keyboard's state if it exists.
void Restart() { engine_->OnPreEngineRestart(); }
void SetLifecycleManager(std::unique_ptr<WindowsLifecycleManager>&& handler) {
engine_->lifecycle_manager_ = std::move(handler);
}
private:
FlutterWindowsEngine* engine_;

View File

@ -0,0 +1,120 @@
// 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 "windows_lifecycle_manager.h"
#include <TlHelp32.h>
#include <WinUser.h>
#include <Windows.h>
#include <tchar.h>
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
namespace flutter {
WindowsLifecycleManager::WindowsLifecycleManager(FlutterWindowsEngine* engine)
: engine_(engine) {}
WindowsLifecycleManager::~WindowsLifecycleManager() {}
void WindowsLifecycleManager::Quit(UINT exit_code) const {
::PostQuitMessage(exit_code);
}
bool WindowsLifecycleManager::WindowProc(HWND hwnd,
UINT msg,
WPARAM wpar,
LPARAM lpar,
LRESULT* result) {
switch (msg) {
case WM_CLOSE:
if (IsLastWindowOfProcess()) {
engine_->RequestApplicationQuit(ExitType::cancelable, 0);
}
return true;
}
return false;
}
class ThreadSnapshot {
public:
ThreadSnapshot() {
thread_snapshot_ = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
}
~ThreadSnapshot() {
if (thread_snapshot_ != INVALID_HANDLE_VALUE) {
::CloseHandle(thread_snapshot_);
}
}
std::optional<THREADENTRY32> GetFirstThread() {
if (thread_snapshot_ == INVALID_HANDLE_VALUE) {
FML_LOG(ERROR) << "Failed to get thread snapshot";
return std::nullopt;
}
THREADENTRY32 thread;
thread.dwSize = sizeof(thread);
if (!::Thread32First(thread_snapshot_, &thread)) {
DWORD error_num = ::GetLastError();
char msg[256];
::FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
error_num, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), msg, 256,
nullptr);
FML_LOG(ERROR) << "Failed to get thread(" << error_num << "): " << msg;
return std::nullopt;
}
return thread;
}
bool GetNextThread(THREADENTRY32& thread) {
if (thread_snapshot_ == INVALID_HANDLE_VALUE) {
return false;
}
return ::Thread32Next(thread_snapshot_, &thread);
}
private:
HANDLE thread_snapshot_;
};
static int64_t NumWindowsForThread(const THREADENTRY32& thread) {
int64_t num_windows = 0;
::EnumThreadWindows(
thread.th32ThreadID,
[](HWND hwnd, LPARAM lparam) {
int64_t* windows_ptr = reinterpret_cast<int64_t*>(lparam);
if (::GetParent(hwnd) == nullptr) {
(*windows_ptr)++;
}
return *windows_ptr <= 1 ? TRUE : FALSE;
},
reinterpret_cast<LPARAM>(&num_windows));
return num_windows;
}
bool WindowsLifecycleManager::IsLastWindowOfProcess() {
DWORD pid = ::GetCurrentProcessId();
ThreadSnapshot thread_snapshot;
std::optional<THREADENTRY32> first_thread = thread_snapshot.GetFirstThread();
if (!first_thread.has_value()) {
FML_LOG(ERROR) << "No first thread found";
return true;
}
int num_windows = 0;
THREADENTRY32 thread = *first_thread;
do {
if (thread.th32OwnerProcessID == pid) {
num_windows += NumWindowsForThread(thread);
if (num_windows > 1) {
return false;
}
}
} while (thread_snapshot.GetNextThread(thread));
return num_windows <= 1;
}
} // namespace flutter

View File

@ -0,0 +1,37 @@
// 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.
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_LIFECYCLE_MANAGER_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_LIFECYCLE_MANAGER_H_
#include <Windows.h>
#include <cstdint>
namespace flutter {
class FlutterWindowsEngine;
/// A manager for lifecycle events of the top-level window.
///
/// Currently handles the following events:
/// WM_CLOSE
class WindowsLifecycleManager {
public:
WindowsLifecycleManager(FlutterWindowsEngine* engine);
virtual ~WindowsLifecycleManager();
virtual void Quit(UINT exit_code) const;
bool WindowProc(HWND hwnd, UINT msg, WPARAM w, LPARAM l, LRESULT* result);
private:
bool IsLastWindowOfProcess();
FlutterWindowsEngine* engine_;
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_LIFECYCLE_MANAGER_H_