mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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:
parent
1a76a2115d
commit
d7059df4eb
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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() {}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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_;
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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_
|
||||
Loading…
x
Reference in New Issue
Block a user